Compositor: Fix crash when executing works in constant folding

Work scheduler needed initialization and execution models are
not created during constant folding. This moves work execution
method to execution system.
This commit is contained in:
Manuel Castilla 2021-07-07 00:06:46 +02:00
parent f49f406f67
commit 1657fa039d
9 changed files with 83 additions and 103 deletions

View File

@ -23,6 +23,7 @@
#include "COM_SetColorOperation.h"
#include "COM_SetValueOperation.h"
#include "COM_SetVectorOperation.h"
#include "COM_WorkScheduler.h"
namespace blender::compositor {
@ -147,6 +148,7 @@ Vector<ConstantOperation *> ConstantFolder::try_fold_operations(Span<NodeOperati
*/
int ConstantFolder::fold_operations()
{
WorkScheduler::start(operations_builder_.context());
Vector<ConstantOperation *> last_folds = try_fold_operations(
operations_builder_.get_operations());
int folds_count = last_folds.size();
@ -158,6 +160,7 @@ int ConstantFolder::fold_operations()
last_folds = try_fold_operations(ops_to_fold);
folds_count += last_folds.size();
}
WorkScheduler::stop();
delete_constant_buffers();

View File

@ -39,10 +39,4 @@ ExecutionModel::ExecutionModel(CompositorContext &context, Span<NodeOperation *>
border_.render_border = &rd->border;
}
bool ExecutionModel::is_breaked() const
{
const bNodeTree *btree = context_.getbNodeTree();
return btree->test_break(btree->tbh);
}
} // namespace blender::compositor

View File

@ -67,15 +67,6 @@ class ExecutionModel {
virtual void execute(ExecutionSystem &exec_system) = 0;
virtual void execute_work(const rcti &UNUSED(work_rect),
std::function<void(const rcti &split_rect)> UNUSED(work_func))
{
BLI_assert(!"Method not supported by current execution model");
}
protected:
bool is_breaked() const;
#ifdef WITH_CXX_GUARDEDALLOC
MEM_CXX_CLASS_ALLOC_FUNCS("COM:BaseExecutionModel")
#endif

View File

@ -63,6 +63,9 @@ ExecutionSystem::ExecutionSystem(RenderData *rd,
this->m_context.setViewSettings(viewSettings);
this->m_context.setDisplaySettings(displaySettings);
BLI_mutex_init(&work_mutex_);
BLI_condition_init(&work_finished_cond_);
{
NodeOperationBuilder builder(&m_context, editingtree, this);
builder.convertToOperations(this);
@ -83,6 +86,9 @@ ExecutionSystem::ExecutionSystem(RenderData *rd,
ExecutionSystem::~ExecutionSystem()
{
BLI_condition_end(&work_finished_cond_);
BLI_mutex_end(&work_mutex_);
delete execution_model_;
for (NodeOperation *operation : m_operations) {
@ -109,10 +115,74 @@ void ExecutionSystem::execute()
execution_model_->execute(*this);
}
/**
* Multi-threadedly execute given work function passing work_rect splits as argument.
*/
void ExecutionSystem::execute_work(const rcti &work_rect,
std::function<void(const rcti &split_rect)> work_func)
{
execution_model_->execute_work(work_rect, work_func);
if (is_breaked()) {
return;
}
/* Split work vertically to maximize continuous memory. */
const int work_height = BLI_rcti_size_y(&work_rect);
const int num_sub_works = MIN2(WorkScheduler::get_num_cpu_threads(), work_height);
const int split_height = num_sub_works == 0 ? 0 : work_height / num_sub_works;
int remaining_height = work_height - split_height * num_sub_works;
Vector<WorkPackage> sub_works(num_sub_works);
int sub_work_y = work_rect.ymin;
int num_sub_works_finished = 0;
for (int i = 0; i < num_sub_works; i++) {
int sub_work_height = split_height;
/* Distribute remaining height between sub-works. */
if (remaining_height > 0) {
sub_work_height++;
remaining_height--;
}
WorkPackage &sub_work = sub_works[i];
sub_work.type = eWorkPackageType::CustomFunction;
sub_work.execute_fn = [=, &work_func, &work_rect]() {
if (is_breaked()) {
return;
}
rcti split_rect;
BLI_rcti_init(
&split_rect, work_rect.xmin, work_rect.xmax, sub_work_y, sub_work_y + sub_work_height);
work_func(split_rect);
};
sub_work.executed_fn = [&]() {
BLI_mutex_lock(&work_mutex_);
num_sub_works_finished++;
if (num_sub_works_finished == num_sub_works) {
BLI_condition_notify_one(&work_finished_cond_);
}
BLI_mutex_unlock(&work_mutex_);
};
WorkScheduler::schedule(&sub_work);
sub_work_y += sub_work_height;
}
BLI_assert(sub_work_y == work_rect.ymax);
WorkScheduler::finish();
/* Ensure all sub-works finished.
* TODO: This a workaround for WorkScheduler::finish() not waiting all works on queue threading
* model. Sync code should be removed once it's fixed. */
BLI_mutex_lock(&work_mutex_);
if (num_sub_works_finished < num_sub_works) {
BLI_condition_wait(&work_finished_cond_, &work_mutex_);
}
BLI_mutex_unlock(&work_mutex_);
}
bool ExecutionSystem::is_breaked() const
{
const bNodeTree *btree = m_context.getbNodeTree();
return btree->test_break(btree->tbh);
}
} // namespace blender::compositor

View File

@ -150,7 +150,9 @@ class ExecutionSystem {
*/
ExecutionModel *execution_model_;
private: // methods
ThreadMutex work_mutex_;
ThreadCondition work_finished_cond_;
public:
/**
* \brief Create a new ExecutionSystem and initialize it with the
@ -199,6 +201,8 @@ class ExecutionSystem {
void execute_work(const rcti &work_rect, std::function<void(const rcti &split_rect)> work_func);
bool is_breaked() const;
private:
/* allow the DebugInfo class to look at internals */
friend class DebugInfo;

View File

@ -35,24 +35,13 @@ FullFrameExecutionModel::FullFrameExecutionModel(CompositorContext &context,
Span<NodeOperation *> operations)
: ExecutionModel(context, operations),
active_buffers_(shared_buffers),
num_operations_finished_(0),
work_mutex_(),
work_finished_cond_()
num_operations_finished_(0)
{
priorities_.append(eCompositorPriority::High);
if (!context.isFastCalculation()) {
priorities_.append(eCompositorPriority::Medium);
priorities_.append(eCompositorPriority::Low);
}
BLI_mutex_init(&work_mutex_);
BLI_condition_init(&work_finished_cond_);
}
FullFrameExecutionModel::~FullFrameExecutionModel()
{
BLI_condition_end(&work_finished_cond_);
BLI_mutex_end(&work_mutex_);
}
void FullFrameExecutionModel::execute(ExecutionSystem &exec_system)
@ -263,70 +252,6 @@ void FullFrameExecutionModel::get_output_render_area(NodeOperation *output_op, r
}
}
/**
* Multi-threadedly execute given work function passing work_rect splits as argument.
*/
void FullFrameExecutionModel::execute_work(const rcti &work_rect,
std::function<void(const rcti &split_rect)> work_func)
{
if (is_breaked()) {
return;
}
/* Split work vertically to maximize continuous memory. */
const int work_height = BLI_rcti_size_y(&work_rect);
const int num_sub_works = MIN2(WorkScheduler::get_num_cpu_threads(), work_height);
const int split_height = num_sub_works == 0 ? 0 : work_height / num_sub_works;
int remaining_height = work_height - split_height * num_sub_works;
Vector<WorkPackage> sub_works(num_sub_works);
int sub_work_y = work_rect.ymin;
int num_sub_works_finished = 0;
for (int i = 0; i < num_sub_works; i++) {
int sub_work_height = split_height;
/* Distribute remaining height between sub-works. */
if (remaining_height > 0) {
sub_work_height++;
remaining_height--;
}
WorkPackage &sub_work = sub_works[i];
sub_work.type = eWorkPackageType::CustomFunction;
sub_work.execute_fn = [=, &work_func, &work_rect]() {
if (is_breaked()) {
return;
}
rcti split_rect;
BLI_rcti_init(
&split_rect, work_rect.xmin, work_rect.xmax, sub_work_y, sub_work_y + sub_work_height);
work_func(split_rect);
};
sub_work.executed_fn = [&]() {
BLI_mutex_lock(&work_mutex_);
num_sub_works_finished++;
if (num_sub_works_finished == num_sub_works) {
BLI_condition_notify_one(&work_finished_cond_);
}
BLI_mutex_unlock(&work_mutex_);
};
WorkScheduler::schedule(&sub_work);
sub_work_y += sub_work_height;
}
BLI_assert(sub_work_y == work_rect.ymax);
WorkScheduler::finish();
/* Ensure all sub-works finished.
* TODO: This a workaround for WorkScheduler::finish() not waiting all works on queue threading
* model. Sync code should be removed once it's fixed. */
BLI_mutex_lock(&work_mutex_);
if (num_sub_works_finished < num_sub_works) {
BLI_condition_wait(&work_finished_cond_, &work_mutex_);
}
BLI_mutex_unlock(&work_mutex_);
}
void FullFrameExecutionModel::operation_finished(NodeOperation *operation)
{
/* Report inputs reads so that buffers may be freed/reused. */

View File

@ -50,20 +50,13 @@ class FullFrameExecutionModel : public ExecutionModel {
*/
Vector<eCompositorPriority> priorities_;
ThreadMutex work_mutex_;
ThreadCondition work_finished_cond_;
public:
FullFrameExecutionModel(CompositorContext &context,
SharedOperationBuffers &shared_buffers,
Span<NodeOperation *> operations);
~FullFrameExecutionModel();
void execute(ExecutionSystem &exec_system) override;
void execute_work(const rcti &work_rect,
std::function<void(const rcti &split_rect)> work_func) override;
private:
void determine_areas_to_render_and_reads();
void render_operations();

View File

@ -126,7 +126,7 @@ static void *thread_execute_gpu(void *data)
return nullptr;
}
static void opencl_start(CompositorContext &context)
static void opencl_start(const CompositorContext &context)
{
if (context.getHasActiveOpenCLDevices()) {
g_work_scheduler.opencl.queue = BLI_thread_queue_init();
@ -458,7 +458,7 @@ void WorkScheduler::schedule(WorkPackage *package)
}
}
void WorkScheduler::start(CompositorContext &context)
void WorkScheduler::start(const CompositorContext &context)
{
if (COM_is_opencl_enabled()) {
opencl_start(context);

View File

@ -65,7 +65,7 @@ struct WorkScheduler {
* for every device a thread is created.
* \see initialize Initialization and query of the number of devices
*/
static void start(CompositorContext &context);
static void start(const CompositorContext &context);
/**
* \brief stop the execution