This patches refactors the compositor File Output mechanism and implements the file output node for the Realtime Compositor. The refactor was done for the following reasons: 1. The existing file output mechanism relied on a global EXR image resource where the result of each compositor execution for each view was accumulated and stored in the global resource, until the last view is executed, when the EXR is finally saved. Aside from relying on global resources, this can cause effective memory leaks since the compositor can be interrupted before the EXR is written and closed. 2. We need common code to share between all compositors since we now have multiple compositor implementations. 3. We needed to take the opportunity to fix some of the issues with the existing implementation, like lossy compression of data passes, and inability to save single values passes. The refactor first introduced a new structure called the Compositor Render Context. This context stores compositor information related to the render pipeline and is persistent across all compositor executions of all views. Its extended lifetime relative to a single compositor execution lends itself well to store data that is accumulated across views. The context currently has a map of File Output objects. Those objects wrap a Render Result structure and can be used to construct multi-view images which can then be saved after all views are executed using the existing BKE_image_render_write function. Minor adjustments were made to the BKE and RE modules to allow saving using the BKE_image_render_write function. Namely, the function now allows the use of a source image format for saving as well as the ability to not save the render result as a render by introducing two new default arguments. Further, for multi-layer EXR saving, the existent of a single unnamed render layer will omit the layer name from the EXR channel full name, and only the pass, view, and channel ID will remain. Finally, the Render Result to Image Buffer conversion now take he number of channels into account, instead of always assuming color channels. The patch implements the File Output node in the Realtime Compositor using the aforementioned mechanisms, replaces the implementation of the CPU compositor using the same Realtime Compositor implementation, and setup the necessary logic in the render pipeline code. Pull Request: https://projects.blender.org/blender/blender/pulls/113982
173 lines
5.1 KiB
C++
173 lines
5.1 KiB
C++
/* SPDX-FileCopyrightText: 2011 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "COM_ExecutionSystem.h"
|
|
|
|
#include "COM_Debug.h"
|
|
#include "COM_ExecutionGroup.h"
|
|
#include "COM_FullFrameExecutionModel.h"
|
|
#include "COM_NodeOperation.h"
|
|
#include "COM_NodeOperationBuilder.h"
|
|
#include "COM_TiledExecutionModel.h"
|
|
#include "COM_WorkPackage.h"
|
|
#include "COM_WorkScheduler.h"
|
|
|
|
#ifdef WITH_CXX_GUARDEDALLOC
|
|
# include "MEM_guardedalloc.h"
|
|
#endif
|
|
|
|
namespace blender::compositor {
|
|
|
|
ExecutionSystem::ExecutionSystem(RenderData *rd,
|
|
Scene *scene,
|
|
bNodeTree *editingtree,
|
|
bool rendering,
|
|
bool fastcalculation,
|
|
const char *view_name,
|
|
realtime_compositor::RenderContext *render_context)
|
|
{
|
|
num_work_threads_ = WorkScheduler::get_num_cpu_threads();
|
|
context_.set_render_context(render_context);
|
|
context_.set_view_name(view_name);
|
|
context_.set_scene(scene);
|
|
context_.set_bnodetree(editingtree);
|
|
context_.set_preview_hash(editingtree->previews);
|
|
context_.set_fast_calculation(fastcalculation);
|
|
/* initialize the CompositorContext */
|
|
if (rendering) {
|
|
context_.set_quality((eCompositorQuality)editingtree->render_quality);
|
|
}
|
|
else {
|
|
context_.set_quality((eCompositorQuality)editingtree->edit_quality);
|
|
}
|
|
context_.set_rendering(rendering);
|
|
context_.setHasActiveOpenCLDevices(WorkScheduler::has_gpu_devices() &&
|
|
(editingtree->flag & NTREE_COM_OPENCL));
|
|
|
|
context_.set_render_data(rd);
|
|
|
|
BLI_mutex_init(&work_mutex_);
|
|
BLI_condition_init(&work_finished_cond_);
|
|
|
|
{
|
|
NodeOperationBuilder builder(&context_, editingtree, this);
|
|
builder.convert_to_operations(this);
|
|
}
|
|
|
|
switch (context_.get_execution_model()) {
|
|
case eExecutionModel::Tiled:
|
|
execution_model_ = new TiledExecutionModel(context_, operations_, groups_);
|
|
break;
|
|
case eExecutionModel::FullFrame:
|
|
execution_model_ = new FullFrameExecutionModel(context_, active_buffers_, operations_);
|
|
break;
|
|
default:
|
|
BLI_assert_msg(0, "Non implemented execution model");
|
|
break;
|
|
}
|
|
}
|
|
|
|
ExecutionSystem::~ExecutionSystem()
|
|
{
|
|
BLI_condition_end(&work_finished_cond_);
|
|
BLI_mutex_end(&work_mutex_);
|
|
|
|
delete execution_model_;
|
|
|
|
for (NodeOperation *operation : operations_) {
|
|
delete operation;
|
|
}
|
|
operations_.clear();
|
|
|
|
for (ExecutionGroup *group : groups_) {
|
|
delete group;
|
|
}
|
|
groups_.clear();
|
|
}
|
|
|
|
void ExecutionSystem::set_operations(const Vector<NodeOperation *> &operations,
|
|
const Vector<ExecutionGroup *> &groups)
|
|
{
|
|
operations_ = operations;
|
|
groups_ = groups;
|
|
}
|
|
|
|
void ExecutionSystem::execute()
|
|
{
|
|
DebugInfo::execute_started(this);
|
|
for (NodeOperation *op : operations_) {
|
|
op->init_data();
|
|
}
|
|
execution_model_->execute(*this);
|
|
}
|
|
|
|
void ExecutionSystem::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 = std::min(num_work_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 = context_.get_bnodetree();
|
|
return btree->runtime->test_break(btree->runtime->tbh);
|
|
}
|
|
|
|
} // namespace blender::compositor
|