Blender  V3.3
COM_FullFrameExecutionModel.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later
2  * Copyright 2021 Blender Foundation. */
3 
5 
6 #include "BLT_translation.h"
7 
8 #include "COM_Debug.h"
9 #include "COM_ViewerOperation.h"
10 #include "COM_WorkScheduler.h"
11 
12 #ifdef WITH_CXX_GUARDEDALLOC
13 # include "MEM_guardedalloc.h"
14 #endif
15 
16 namespace blender::compositor {
17 
19  SharedOperationBuffers &shared_buffers,
20  Span<NodeOperation *> operations)
21  : ExecutionModel(context, operations),
22  active_buffers_(shared_buffers),
23  num_operations_finished_(0)
24 {
25  priorities_.append(eCompositorPriority::High);
26  if (!context.is_fast_calculation()) {
28  priorities_.append(eCompositorPriority::Low);
29  }
30 }
31 
33 {
34  const bNodeTree *node_tree = this->context_.get_bnodetree();
35  node_tree->stats_draw(node_tree->sdh, TIP_("Compositing | Initializing execution"));
36 
37  DebugInfo::graphviz(&exec_system, "compositor_prior_rendering");
38 
39  determine_areas_to_render_and_reads();
40  render_operations();
41 }
42 
43 void FullFrameExecutionModel::determine_areas_to_render_and_reads()
44 {
45  const bool is_rendering = context_.is_rendering();
47 
48  rcti area;
49  for (eCompositorPriority priority : priorities_) {
50  for (NodeOperation *op : operations_) {
51  op->set_bnodetree(node_tree);
52  if (op->is_output_operation(is_rendering) && op->get_render_priority() == priority) {
53  get_output_render_area(op, area);
54  determine_areas_to_render(op, area);
55  determine_reads(op);
56  }
57  }
58  }
59 }
60 
61 Vector<MemoryBuffer *> FullFrameExecutionModel::get_input_buffers(NodeOperation *op,
62  const int output_x,
63  const int output_y)
64 {
65  const int num_inputs = op->get_number_of_input_sockets();
66  Vector<MemoryBuffer *> inputs_buffers(num_inputs);
67  for (int i = 0; i < num_inputs; i++) {
68  NodeOperation *input = op->get_input_operation(i);
69  const int offset_x = (input->get_canvas().xmin - op->get_canvas().xmin) + output_x;
70  const int offset_y = (input->get_canvas().ymin - op->get_canvas().ymin) + output_y;
71  MemoryBuffer *buf = active_buffers_.get_rendered_buffer(input);
72 
73  rcti rect = buf->get_rect();
74  BLI_rcti_translate(&rect, offset_x, offset_y);
75  inputs_buffers[i] = new MemoryBuffer(
76  buf->get_buffer(), buf->get_num_channels(), rect, buf->is_a_single_elem());
77  }
78  return inputs_buffers;
79 }
80 
81 MemoryBuffer *FullFrameExecutionModel::create_operation_buffer(NodeOperation *op,
82  const int output_x,
83  const int output_y)
84 {
85  rcti rect;
87  &rect, output_x, output_x + op->get_width(), output_y, output_y + op->get_height());
88 
89  const DataType data_type = op->get_output_socket(0)->get_data_type();
90  const bool is_a_single_elem = op->get_flags().is_constant_operation;
91  return new MemoryBuffer(data_type, rect, is_a_single_elem);
92 }
93 
94 void FullFrameExecutionModel::render_operation(NodeOperation *op)
95 {
96  /* Output has no offset for easier image algorithms implementation on operations. */
97  constexpr int output_x = 0;
98  constexpr int output_y = 0;
99 
100  const bool has_outputs = op->get_number_of_output_sockets() > 0;
101  MemoryBuffer *op_buf = has_outputs ? create_operation_buffer(op, output_x, output_y) : nullptr;
102  if (op->get_width() > 0 && op->get_height() > 0) {
103  Vector<MemoryBuffer *> input_bufs = get_input_buffers(op, output_x, output_y);
104  const int op_offset_x = output_x - op->get_canvas().xmin;
105  const int op_offset_y = output_y - op->get_canvas().ymin;
106  Vector<rcti> areas = active_buffers_.get_areas_to_render(op, op_offset_x, op_offset_y);
107  op->render(op_buf, areas, input_bufs);
108  DebugInfo::operation_rendered(op, op_buf);
109 
110  for (MemoryBuffer *buf : input_bufs) {
111  delete buf;
112  }
113  }
114  /* Even if operation has no resolution set the empty buffer. It will be clipped with a
115  * TranslateOperation from convert resolutions if linked to an operation with resolution. */
116  active_buffers_.set_rendered_buffer(op, std::unique_ptr<MemoryBuffer>(op_buf));
117 
118  operation_finished(op);
119 }
120 
121 void FullFrameExecutionModel::render_operations()
122 {
123  const bool is_rendering = context_.is_rendering();
124 
126  for (eCompositorPriority priority : priorities_) {
127  for (NodeOperation *op : operations_) {
128  const bool has_size = op->get_width() > 0 && op->get_height() > 0;
129  const bool is_priority_output = op->is_output_operation(is_rendering) &&
130  op->get_render_priority() == priority;
131  if (is_priority_output && has_size) {
132  render_output_dependencies(op);
133  render_operation(op);
134  }
135  else if (is_priority_output && !has_size && op->is_active_viewer_output()) {
136  static_cast<ViewerOperation *>(op)->clear_display_buffer();
137  }
138  }
139  }
141 }
142 
148 {
149  /* Get dependencies from outputs to inputs. */
150  Vector<NodeOperation *> dependencies;
151  Vector<NodeOperation *> next_outputs;
152  next_outputs.append(operation);
153  while (next_outputs.size() > 0) {
154  Vector<NodeOperation *> outputs(next_outputs);
155  next_outputs.clear();
156  for (NodeOperation *output : outputs) {
157  for (int i = 0; i < output->get_number_of_input_sockets(); i++) {
158  next_outputs.append(output->get_input_operation(i));
159  }
160  }
161  dependencies.extend(next_outputs);
162  }
163 
164  /* Reverse to get dependencies from inputs to outputs. */
165  std::reverse(dependencies.begin(), dependencies.end());
166 
167  return dependencies;
168 }
169 
170 void FullFrameExecutionModel::render_output_dependencies(NodeOperation *output_op)
171 {
172  BLI_assert(output_op->is_output_operation(context_.is_rendering()));
173  Vector<NodeOperation *> dependencies = get_operation_dependencies(output_op);
174  for (NodeOperation *op : dependencies) {
175  if (!active_buffers_.is_operation_rendered(op)) {
176  render_operation(op);
177  }
178  }
179 }
180 
181 void FullFrameExecutionModel::determine_areas_to_render(NodeOperation *output_op,
182  const rcti &output_area)
183 {
184  BLI_assert(output_op->is_output_operation(context_.is_rendering()));
185 
186  Vector<std::pair<NodeOperation *, const rcti>> stack;
187  stack.append({output_op, output_area});
188  while (stack.size() > 0) {
189  std::pair<NodeOperation *, rcti> pair = stack.pop_last();
190  NodeOperation *operation = pair.first;
191  const rcti &render_area = pair.second;
192  if (BLI_rcti_is_empty(&render_area) ||
193  active_buffers_.is_area_registered(operation, render_area)) {
194  continue;
195  }
196 
197  active_buffers_.register_area(operation, render_area);
198 
199  const int num_inputs = operation->get_number_of_input_sockets();
200  for (int i = 0; i < num_inputs; i++) {
201  NodeOperation *input_op = operation->get_input_operation(i);
202  rcti input_area;
203  operation->get_area_of_interest(input_op, render_area, input_area);
204 
205  /* Ensure area of interest is within operation bounds, cropping areas outside. */
206  BLI_rcti_isect(&input_area, &input_op->get_canvas(), &input_area);
207 
208  stack.append({input_op, input_area});
209  }
210  }
211 }
212 
213 void FullFrameExecutionModel::determine_reads(NodeOperation *output_op)
214 {
215  BLI_assert(output_op->is_output_operation(context_.is_rendering()));
216 
217  Vector<NodeOperation *> stack;
218  stack.append(output_op);
219  while (stack.size() > 0) {
220  NodeOperation *operation = stack.pop_last();
221  const int num_inputs = operation->get_number_of_input_sockets();
222  for (int i = 0; i < num_inputs; i++) {
223  NodeOperation *input_op = operation->get_input_operation(i);
224  if (!active_buffers_.has_registered_reads(input_op)) {
225  stack.append(input_op);
226  }
227  active_buffers_.register_read(input_op);
228  }
229  }
230 }
231 
232 void FullFrameExecutionModel::get_output_render_area(NodeOperation *output_op, rcti &r_area)
233 {
234  BLI_assert(output_op->is_output_operation(context_.is_rendering()));
235 
236  /* By default return operation bounds (no border). */
237  rcti canvas = output_op->get_canvas();
238  r_area = canvas;
239 
240  const bool has_viewer_border = border_.use_viewer_border &&
241  (output_op->get_flags().is_viewer_operation ||
242  output_op->get_flags().is_preview_operation);
243  const bool has_render_border = border_.use_render_border;
244  if (has_viewer_border || has_render_border) {
245  /* Get border with normalized coordinates. */
246  const rctf *norm_border = has_viewer_border ? border_.viewer_border : border_.render_border;
247 
248  /* Return de-normalized border within canvas. */
249  const int w = output_op->get_width();
250  const int h = output_op->get_height();
251  r_area.xmin = canvas.xmin + norm_border->xmin * w;
252  r_area.xmax = canvas.xmin + norm_border->xmax * w;
253  r_area.ymin = canvas.ymin + norm_border->ymin * h;
254  r_area.ymax = canvas.ymin + norm_border->ymax * h;
255  }
256 }
257 
258 void FullFrameExecutionModel::operation_finished(NodeOperation *operation)
259 {
260  /* Report inputs reads so that buffers may be freed/reused. */
261  const int num_inputs = operation->get_number_of_input_sockets();
262  for (int i = 0; i < num_inputs; i++) {
263  active_buffers_.read_finished(operation->get_input_operation(i));
264  }
265 
266  num_operations_finished_++;
267  update_progress_bar();
268 }
269 
270 void FullFrameExecutionModel::update_progress_bar()
271 {
273  if (tree) {
274  const float progress = num_operations_finished_ / static_cast<float>(operations_.size());
275  tree->progress(tree->prh, progress);
276 
277  char buf[128];
278  BLI_snprintf(buf,
279  sizeof(buf),
280  TIP_("Compositing | Operation %i-%li"),
281  num_operations_finished_ + 1,
282  operations_.size());
283  tree->stats_draw(tree->sdh, buf);
284  }
285 }
286 
287 } // namespace blender::compositor
#define BLI_assert(a)
Definition: BLI_assert.h:46
void BLI_rcti_translate(struct rcti *rect, int x, int y)
Definition: rct.c:559
void BLI_rcti_init(struct rcti *rect, int xmin, int xmax, int ymin, int ymax)
Definition: rct.c:417
bool BLI_rcti_isect(const struct rcti *src1, const struct rcti *src2, struct rcti *dest)
bool BLI_rcti_is_empty(const struct rcti *rect)
size_t BLI_snprintf(char *__restrict dst, size_t maxncpy, const char *__restrict format,...) ATTR_NONNULL(1
#define TIP_(msgid)
Read Guarded memory(de)allocation.
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition: btQuadWord.h:119
int64_t size() const
Definition: BLI_vector.hh:694
void append(const T &value)
Definition: BLI_vector.hh:433
void extend(Span< T > array)
Definition: BLI_vector.hh:530
Overall context of the compositor.
bool is_rendering() const
get the rendering field of the context
const bNodeTree * get_bnodetree() const
get the bnodetree of the context
static void operation_rendered(const NodeOperation *op, MemoryBuffer *render)
Definition: COM_Debug.h:115
static void graphviz(const ExecutionSystem *system, StringRefNull name="")
Definition: COM_Debug.cc:414
struct blender::compositor::ExecutionModel::@178 border_
the ExecutionSystem contains the whole compositor tree.
FullFrameExecutionModel(CompositorContext &context, SharedOperationBuffers &shared_buffers, Span< NodeOperation * > operations)
void execute(ExecutionSystem &exec_system) override
NodeOperation contains calculation logic.
void set_rendered_buffer(NodeOperation *op, std::unique_ptr< MemoryBuffer > buffer)
Vector< rcti > get_areas_to_render(NodeOperation *op, int offset_x, int offset_y)
bool is_area_registered(NodeOperation *op, const rcti &area_to_render)
MemoryBuffer * get_rendered_buffer(NodeOperation *op)
void register_area(NodeOperation *op, const rcti &area_to_render)
void * tree
eCompositorPriority
Possible priority settings.
Definition: COM_Enums.h:32
ccl_global KernelShaderEvalInput ccl_global float * output
ccl_global KernelShaderEvalInput * input
static void area(int d1, int d2, int e1, int e2, float weights[2])
static Vector< NodeOperation * > get_operation_dependencies(NodeOperation *operation)
static bNodeSocketTemplate outputs[]
void(* stats_draw)(void *, const char *str)
static void start(const CompositorContext &context)
Start the execution this methods will start the WorkScheduler. Inside this method all threads are ini...
static void stop()
stop the execution All created thread by the start method are destroyed.
float xmax
Definition: DNA_vec_types.h:69
float xmin
Definition: DNA_vec_types.h:69
float ymax
Definition: DNA_vec_types.h:70
float ymin
Definition: DNA_vec_types.h:70
int ymin
Definition: DNA_vec_types.h:64
int ymax
Definition: DNA_vec_types.h:64
int xmin
Definition: DNA_vec_types.h:63
int xmax
Definition: DNA_vec_types.h:63