Compositor: Refactor recursive methods to iterative

In order to reduce stack size this patch converts full frame 
recursive methods into iterative.
- No functional changes.
- No performance changes.
- Memory peak may slightly vary depending on the tree because
 now breadth-first traversal is used instead of depth-first.

Tests in D11113 have same results except for test1 memory peak:
360MBs instead of 329.50MBs.

Reviewed By: Jeroen Bakker (jbakker)

Differential Revision: https://developer.blender.org/D11515
This commit is contained in:
Manuel Castilla 2021-06-09 10:19:28 +02:00
parent 3ba16afa1e
commit d7c812f15b
4 changed files with 87 additions and 51 deletions

View File

@ -84,18 +84,6 @@ void FullFrameExecutionModel::determine_areas_to_render_and_reads()
}
}
void FullFrameExecutionModel::ensure_inputs_rendered(NodeOperation *op,
ExecutionSystem &exec_system)
{
const int num_inputs = op->getNumberOfInputSockets();
for (int i = 0; i < num_inputs; i++) {
NodeOperation *input_op = op->get_input_operation(i);
if (!active_buffers_.is_operation_rendered(input_op)) {
render_operation(input_op, exec_system);
}
}
}
Vector<MemoryBuffer *> FullFrameExecutionModel::get_input_buffers(NodeOperation *op)
{
const int num_inputs = op->getNumberOfInputSockets();
@ -121,11 +109,6 @@ MemoryBuffer *FullFrameExecutionModel::create_operation_buffer(NodeOperation *op
void FullFrameExecutionModel::render_operation(NodeOperation *op, ExecutionSystem &exec_system)
{
if (active_buffers_.is_operation_rendered(op)) {
return;
}
ensure_inputs_rendered(op, exec_system);
Vector<MemoryBuffer *> input_bufs = get_input_buffers(op);
const bool has_outputs = op->getNumberOfOutputSockets() > 0;
@ -148,6 +131,7 @@ void FullFrameExecutionModel::render_operations(ExecutionSystem &exec_system)
for (eCompositorPriority priority : priorities_) {
for (NodeOperation *op : operations_) {
if (op->isOutputOperation(is_rendering) && op->getRenderPriority() == priority) {
render_output_dependencies(op, exec_system);
render_operation(op, exec_system);
}
}
@ -156,48 +140,99 @@ void FullFrameExecutionModel::render_operations(ExecutionSystem &exec_system)
}
/**
* Determines all input operations areas needed to render given operation area.
* \param operation: Renderer operation.
* \param render_area: Area within given operation bounds to render.
* Returns all dependencies from inputs to outputs. A dependency may be repeated when
* several operations depend on it.
*/
void FullFrameExecutionModel::determine_areas_to_render(NodeOperation *operation,
const rcti &render_area)
static Vector<NodeOperation *> get_operation_dependencies(NodeOperation *operation)
{
if (active_buffers_.is_area_registered(operation, render_area)) {
return;
/* Get dependencies from outputs to inputs. */
Vector<NodeOperation *> dependencies;
Vector<NodeOperation *> next_outputs;
next_outputs.append(operation);
while (next_outputs.size() > 0) {
Vector<NodeOperation *> outputs(next_outputs);
next_outputs.clear();
for (NodeOperation *output : outputs) {
for (int i = 0; i < output->getNumberOfInputSockets(); i++) {
next_outputs.append(output->get_input_operation(i));
}
}
dependencies.extend(next_outputs);
}
active_buffers_.register_area(operation, render_area);
/* Reverse to get dependencies from inputs to outputs. */
std::reverse(dependencies.begin(), dependencies.end());
const int num_inputs = operation->getNumberOfInputSockets();
for (int i = 0; i < num_inputs; i++) {
NodeOperation *input_op = operation->get_input_operation(i);
rcti input_op_rect, input_area;
BLI_rcti_init(&input_op_rect, 0, input_op->getWidth(), 0, input_op->getHeight());
operation->get_area_of_interest(input_op, render_area, input_area);
return dependencies;
}
/* Ensure area of interest is within operation bounds, cropping areas outside. */
BLI_rcti_isect(&input_area, &input_op_rect, &input_area);
determine_areas_to_render(input_op, input_area);
void FullFrameExecutionModel::render_output_dependencies(NodeOperation *output_op,
ExecutionSystem &exec_system)
{
BLI_assert(output_op->isOutputOperation(context_.isRendering()));
Vector<NodeOperation *> dependencies = get_operation_dependencies(output_op);
for (NodeOperation *op : dependencies) {
if (!active_buffers_.is_operation_rendered(op)) {
render_operation(op, exec_system);
}
}
}
/**
* Determines the reads given operation and its inputs will receive (i.e: Number of dependent
* Determines all operations areas needed to render given output area.
*/
void FullFrameExecutionModel::determine_areas_to_render(NodeOperation *output_op,
const rcti &output_area)
{
BLI_assert(output_op->isOutputOperation(context_.isRendering()));
Vector<std::pair<NodeOperation *, const rcti>> stack;
stack.append({output_op, output_area});
while (stack.size() > 0) {
std::pair<NodeOperation *, rcti> pair = stack.pop_last();
NodeOperation *operation = pair.first;
const rcti &render_area = pair.second;
if (active_buffers_.is_area_registered(operation, render_area)) {
continue;
}
active_buffers_.register_area(operation, render_area);
const int num_inputs = operation->getNumberOfInputSockets();
for (int i = 0; i < num_inputs; i++) {
NodeOperation *input_op = operation->get_input_operation(i);
rcti input_op_rect, input_area;
BLI_rcti_init(&input_op_rect, 0, input_op->getWidth(), 0, input_op->getHeight());
operation->get_area_of_interest(input_op, render_area, input_area);
/* Ensure area of interest is within operation bounds, cropping areas outside. */
BLI_rcti_isect(&input_area, &input_op_rect, &input_area);
stack.append({input_op, input_area});
}
}
}
/**
* Determines reads to receive by operations in output operation tree (i.e: Number of dependent
* operations each operation has).
*/
void FullFrameExecutionModel::determine_reads(NodeOperation *operation)
void FullFrameExecutionModel::determine_reads(NodeOperation *output_op)
{
if (active_buffers_.has_registered_reads(operation)) {
return;
}
BLI_assert(output_op->isOutputOperation(context_.isRendering()));
const int num_inputs = operation->getNumberOfInputSockets();
for (int i = 0; i < num_inputs; i++) {
NodeOperation *input_op = operation->get_input_operation(i);
determine_reads(input_op);
active_buffers_.register_read(input_op);
Vector<NodeOperation *> stack;
stack.append(output_op);
while (stack.size() > 0) {
NodeOperation *operation = stack.pop_last();
const int num_inputs = operation->getNumberOfInputSockets();
for (int i = 0; i < num_inputs; i++) {
NodeOperation *input_op = operation->get_input_operation(i);
if (!active_buffers_.has_registered_reads(input_op)) {
stack.append(input_op);
}
active_buffers_.register_read(input_op);
}
}
}

View File

@ -67,8 +67,7 @@ class FullFrameExecutionModel : public ExecutionModel {
private:
void determine_areas_to_render_and_reads();
void render_operations(ExecutionSystem &exec_system);
void ensure_inputs_rendered(NodeOperation *op, ExecutionSystem &exec_system);
void render_output_dependencies(NodeOperation *output_op, ExecutionSystem &exec_system);
Vector<MemoryBuffer *> get_input_buffers(NodeOperation *op);
MemoryBuffer *create_operation_buffer(NodeOperation *op);
void render_operation(NodeOperation *op, ExecutionSystem &exec_system);
@ -76,8 +75,8 @@ class FullFrameExecutionModel : public ExecutionModel {
void operation_finished(NodeOperation *operation);
void get_output_render_area(NodeOperation *output_op, rcti &r_area);
void determine_areas_to_render(NodeOperation *operation, const rcti &render_area);
void determine_reads(NodeOperation *operation);
void determine_areas_to_render(NodeOperation *output_op, const rcti &output_area);
void determine_reads(NodeOperation *output_op);
void update_progress_bar();

View File

@ -23,7 +23,7 @@
namespace blender::compositor {
SharedOperationBuffers::BufferData::BufferData()
: buffer(nullptr), registered_reads(0), received_reads(0)
: buffer(nullptr), registered_reads(0), received_reads(0), is_rendered(false)
{
}
@ -86,7 +86,7 @@ blender::Span<rcti> SharedOperationBuffers::get_areas_to_render(NodeOperation *o
*/
bool SharedOperationBuffers::is_operation_rendered(NodeOperation *op)
{
return get_buffer_data(op).buffer != nullptr;
return get_buffer_data(op).is_rendered;
}
/**
@ -99,6 +99,7 @@ void SharedOperationBuffers::set_rendered_buffer(NodeOperation *op,
BLI_assert(buf_data.received_reads == 0);
BLI_assert(buf_data.buffer == nullptr);
buf_data.buffer = std::move(buffer);
buf_data.is_rendered = true;
}
/**

View File

@ -42,6 +42,7 @@ class SharedOperationBuffers {
blender::Vector<rcti> render_areas;
int registered_reads;
int received_reads;
bool is_rendered;
} BufferData;
blender::Map<NodeOperation *, BufferData> buffers_;