74c1128a37e57726fcd6b672ea6528286d8f8a96
[blender-staging.git] / source / blender / compositor / intern / COM_ExecutionGroup.cpp
1 /*
2  * Copyright 2011, Blender Foundation.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Contributor: 
19  *              Jeroen Bakker 
20  *              Monique Dewanchand
21  */
22
23 #include <algorithm>
24 #include <math.h>
25 #include <sstream>
26 #include <stdlib.h>
27
28 #include "atomic_ops.h"
29
30 #include "COM_ExecutionGroup.h"
31 #include "COM_defines.h"
32 #include "COM_ExecutionSystem.h"
33 #include "COM_ReadBufferOperation.h"
34 #include "COM_WriteBufferOperation.h"
35 #include "COM_ReadBufferOperation.h"
36 #include "COM_WorkScheduler.h"
37 #include "COM_ViewerOperation.h"
38 #include "COM_ChunkOrder.h"
39 #include "COM_Debug.h"
40
41 #include "MEM_guardedalloc.h"
42 #include "BLI_math.h"
43 #include "BLI_string.h"
44 #include "BKE_global.h"
45 #include "PIL_time.h"
46 #include "WM_api.h"
47 #include "WM_types.h"
48
49 ExecutionGroup::ExecutionGroup()
50 {
51         this->m_isOutput = false;
52         this->m_complex = false;
53         this->m_chunkExecutionStates = NULL;
54         this->m_bTree = NULL;
55         this->m_height = 0;
56         this->m_width = 0;
57         this->m_cachedMaxReadBufferOffset = 0;
58         this->m_numberOfXChunks = 0;
59         this->m_numberOfYChunks = 0;
60         this->m_numberOfChunks = 0;
61         this->m_initialized = false;
62         this->m_openCL = false;
63         this->m_singleThreaded = false;
64         this->m_chunksFinished = 0;
65         BLI_rcti_init(&this->m_viewerBorder, 0, 0, 0, 0);
66         this->m_executionStartTime = 0;
67 }
68
69 CompositorPriority ExecutionGroup::getRenderPriotrity()
70 {
71         return this->getOutputOperation()->getRenderPriority();
72 }
73
74 bool ExecutionGroup::canContainOperation(NodeOperation *operation)
75 {
76         if (!this->m_initialized) { return true; }
77         
78         if (operation->isReadBufferOperation()) { return true; }
79         if (operation->isWriteBufferOperation()) { return false; }
80         if (operation->isSetOperation()) { return true; }
81         
82         /* complex groups don't allow further ops (except read buffer and values, see above) */
83         if (m_complex) { return false; }
84         /* complex ops can't be added to other groups (except their own, which they initialize, see above) */
85         if (operation->isComplex()) { return false; }
86         
87         return true;
88 }
89
90 bool ExecutionGroup::addOperation(NodeOperation *operation)
91 {
92         if (!canContainOperation(operation))
93                 return false;
94         
95         if (!operation->isReadBufferOperation() && !operation->isWriteBufferOperation()) {
96                 m_complex = operation->isComplex();
97                 m_openCL = operation->isOpenCL();
98                 m_singleThreaded = operation->isSingleThreaded();
99                 m_initialized = true;
100         }
101         
102         m_operations.push_back(operation);
103         
104         return true;
105 }
106
107 NodeOperation *ExecutionGroup::getOutputOperation() const
108 {
109         return this->m_operations[0]; // the first operation of the group is always the output operation.
110 }
111
112 void ExecutionGroup::initExecution()
113 {
114         if (this->m_chunkExecutionStates != NULL) {
115                 MEM_freeN(this->m_chunkExecutionStates);
116         }
117         unsigned int index;
118         determineNumberOfChunks();
119
120         this->m_chunkExecutionStates = NULL;
121         if (this->m_numberOfChunks != 0) {
122                 this->m_chunkExecutionStates = (ChunkExecutionState *)MEM_mallocN(sizeof(ChunkExecutionState) * this->m_numberOfChunks, __func__);
123                 for (index = 0; index < this->m_numberOfChunks; index++) {
124                         this->m_chunkExecutionStates[index] = COM_ES_NOT_SCHEDULED;
125                 }
126         }
127
128
129         unsigned int maxNumber = 0;
130
131         for (index = 0; index < this->m_operations.size(); index++) {
132                 NodeOperation *operation = this->m_operations[index];
133                 if (operation->isReadBufferOperation()) {
134                         ReadBufferOperation *readOperation = (ReadBufferOperation *)operation;
135                         this->m_cachedReadOperations.push_back(readOperation);
136                         maxNumber = max(maxNumber, readOperation->getOffset());
137                 }
138         }
139         maxNumber++;
140         this->m_cachedMaxReadBufferOffset = maxNumber;
141
142 }
143
144 void ExecutionGroup::deinitExecution()
145 {
146         if (this->m_chunkExecutionStates != NULL) {
147                 MEM_freeN(this->m_chunkExecutionStates);
148                 this->m_chunkExecutionStates = NULL;
149         }
150         this->m_numberOfChunks = 0;
151         this->m_numberOfXChunks = 0;
152         this->m_numberOfYChunks = 0;
153         this->m_cachedReadOperations.clear();
154         this->m_bTree = NULL;
155 }
156 void ExecutionGroup::determineResolution(unsigned int resolution[2])
157 {
158         NodeOperation *operation = this->getOutputOperation();
159         resolution[0] = operation->getWidth();
160         resolution[1] = operation->getHeight();
161         this->setResolution(resolution);
162         BLI_rcti_init(&this->m_viewerBorder, 0, this->m_width, 0, this->m_height);
163 }
164
165 void ExecutionGroup::determineNumberOfChunks()
166 {
167         if (this->m_singleThreaded) {
168                 this->m_numberOfXChunks = 1;
169                 this->m_numberOfYChunks = 1;
170                 this->m_numberOfChunks = 1;
171         }
172         else {
173                 const float chunkSizef = this->m_chunkSize;
174                 const int border_width = BLI_rcti_size_x(&this->m_viewerBorder);
175                 const int border_height = BLI_rcti_size_y(&this->m_viewerBorder);
176                 this->m_numberOfXChunks = ceil(border_width / chunkSizef);
177                 this->m_numberOfYChunks = ceil(border_height / chunkSizef);
178                 this->m_numberOfChunks = this->m_numberOfXChunks * this->m_numberOfYChunks;
179         }
180 }
181
182 /**
183  * this method is called for the top execution groups. containing the compositor node or the preview node or the viewer node)
184  */
185 void ExecutionGroup::execute(ExecutionSystem *graph)
186 {
187         const CompositorContext &context = graph->getContext();
188         const bNodeTree *bTree = context.getbNodeTree();
189         if (this->m_width == 0 || this->m_height == 0) {return; } /// @note: break out... no pixels to calculate.
190         if (bTree->test_break && bTree->test_break(bTree->tbh)) {return; } /// @note: early break out for blur and preview nodes
191         if (this->m_numberOfChunks == 0) {return; } /// @note: early break out
192         unsigned int chunkNumber;
193
194         this->m_executionStartTime = PIL_check_seconds_timer();
195
196         this->m_chunksFinished = 0;
197         this->m_bTree = bTree;
198         unsigned int index;
199         unsigned int *chunkOrder = (unsigned int *)MEM_mallocN(sizeof(unsigned int) * this->m_numberOfChunks, __func__);
200
201         for (chunkNumber = 0; chunkNumber < this->m_numberOfChunks; chunkNumber++) {
202                 chunkOrder[chunkNumber] = chunkNumber;
203         }
204         NodeOperation *operation = this->getOutputOperation();
205         float centerX = 0.5;
206         float centerY = 0.5;
207         OrderOfChunks chunkorder = COM_ORDER_OF_CHUNKS_DEFAULT;
208
209         if (operation->isViewerOperation()) {
210                 ViewerOperation *viewer = (ViewerOperation *)operation;
211                 centerX = viewer->getCenterX();
212                 centerY = viewer->getCenterY();
213                 chunkorder = viewer->getChunkOrder();
214         }
215
216         const int border_width = BLI_rcti_size_x(&this->m_viewerBorder);
217         const int border_height = BLI_rcti_size_y(&this->m_viewerBorder);
218
219         switch (chunkorder) {
220                 case COM_TO_RANDOM:
221                         for (index = 0; index < 2 * this->m_numberOfChunks; index++) {
222                                 int index1 = rand() % this->m_numberOfChunks;
223                                 int index2 = rand() % this->m_numberOfChunks;
224                                 int s = chunkOrder[index1];
225                                 chunkOrder[index1] = chunkOrder[index2];
226                                 chunkOrder[index2] = s;
227                         }
228                         break;
229                 case COM_TO_CENTER_OUT:
230                 {
231                         ChunkOrderHotspot *hotspots[1];
232                         hotspots[0] = new ChunkOrderHotspot(border_width * centerX, border_height * centerY, 0.0f);
233                         rcti rect;
234                         ChunkOrder *chunkOrders = (ChunkOrder *)MEM_mallocN(sizeof(ChunkOrder) * this->m_numberOfChunks, __func__);
235                         for (index = 0; index < this->m_numberOfChunks; index++) {
236                                 determineChunkRect(&rect, index);
237                                 chunkOrders[index].setChunkNumber(index);
238                                 chunkOrders[index].setX(rect.xmin - this->m_viewerBorder.xmin);
239                                 chunkOrders[index].setY(rect.ymin - this->m_viewerBorder.ymin);
240                                 chunkOrders[index].determineDistance(hotspots, 1);
241                         }
242
243                         std::sort(&chunkOrders[0], &chunkOrders[this->m_numberOfChunks - 1]);
244                         for (index = 0; index < this->m_numberOfChunks; index++) {
245                                 chunkOrder[index] = chunkOrders[index].getChunkNumber();
246                         }
247
248                         delete hotspots[0];
249                         MEM_freeN(chunkOrders);
250                         break;
251                 }
252                 case COM_TO_RULE_OF_THIRDS:
253                 {
254                         ChunkOrderHotspot *hotspots[9];
255                         unsigned int tx = border_width / 6;
256                         unsigned int ty = border_height / 6;
257                         unsigned int mx = border_width / 2;
258                         unsigned int my = border_height / 2;
259                         unsigned int bx = mx + 2 * tx;
260                         unsigned int by = my + 2 * ty;
261
262                         float addition = this->m_numberOfChunks / COM_RULE_OF_THIRDS_DIVIDER;
263                         hotspots[0] = new ChunkOrderHotspot(mx, my, addition * 0);
264                         hotspots[1] = new ChunkOrderHotspot(tx, my, addition * 1);
265                         hotspots[2] = new ChunkOrderHotspot(bx, my, addition * 2);
266                         hotspots[3] = new ChunkOrderHotspot(bx, by, addition * 3);
267                         hotspots[4] = new ChunkOrderHotspot(tx, ty, addition * 4);
268                         hotspots[5] = new ChunkOrderHotspot(bx, ty, addition * 5);
269                         hotspots[6] = new ChunkOrderHotspot(tx, by, addition * 6);
270                         hotspots[7] = new ChunkOrderHotspot(mx, ty, addition * 7);
271                         hotspots[8] = new ChunkOrderHotspot(mx, by, addition * 8);
272                         rcti rect;
273                         ChunkOrder *chunkOrders = (ChunkOrder *)MEM_mallocN(sizeof(ChunkOrder) * this->m_numberOfChunks, __func__);
274                         for (index = 0; index < this->m_numberOfChunks; index++) {
275                                 determineChunkRect(&rect, index);
276                                 chunkOrders[index].setChunkNumber(index);
277                                 chunkOrders[index].setX(rect.xmin - this->m_viewerBorder.xmin);
278                                 chunkOrders[index].setY(rect.ymin - this->m_viewerBorder.ymin);
279                                 chunkOrders[index].determineDistance(hotspots, 9);
280                         }
281
282                         std::sort(&chunkOrders[0], &chunkOrders[this->m_numberOfChunks]);
283
284                         for (index = 0; index < this->m_numberOfChunks; index++) {
285                                 chunkOrder[index] = chunkOrders[index].getChunkNumber();
286                         }
287
288                         delete hotspots[0];
289                         delete hotspots[1];
290                         delete hotspots[2];
291                         delete hotspots[3];
292                         delete hotspots[4];
293                         delete hotspots[5];
294                         delete hotspots[6];
295                         delete hotspots[7];
296                         delete hotspots[8];
297                         MEM_freeN(chunkOrders);
298                         break;
299                 }
300                 case COM_TO_TOP_DOWN:
301                 default:
302                         break;
303         }
304
305         DebugInfo::execution_group_started(this);
306         DebugInfo::graphviz(graph);
307
308         bool breaked = false;
309         bool finished = false;
310         unsigned int startIndex = 0;
311         const int maxNumberEvaluated = BLI_system_thread_count() * 2;
312
313         while (!finished && !breaked) {
314                 bool startEvaluated = false;
315                 finished = true;
316                 int numberEvaluated = 0;
317
318                 for (index = startIndex; index < this->m_numberOfChunks && numberEvaluated < maxNumberEvaluated; index++) {
319                         chunkNumber = chunkOrder[index];
320                         int yChunk = chunkNumber / this->m_numberOfXChunks;
321                         int xChunk = chunkNumber - (yChunk * this->m_numberOfXChunks);
322                         const ChunkExecutionState state = this->m_chunkExecutionStates[chunkNumber];
323                         if (state == COM_ES_NOT_SCHEDULED) {
324                                 scheduleChunkWhenPossible(graph, xChunk, yChunk);
325                                 finished = false;
326                                 startEvaluated = true;
327                                 numberEvaluated++;
328
329                                 if (bTree->update_draw)
330                                         bTree->update_draw(bTree->udh);
331                         }
332                         else if (state == COM_ES_SCHEDULED) {
333                                 finished = false;
334                                 startEvaluated = true;
335                                 numberEvaluated++;
336                         }
337                         else if (state == COM_ES_EXECUTED && !startEvaluated) {
338                                 startIndex = index + 1;
339                         }
340                 }
341
342                 WorkScheduler::finish();
343
344                 if (bTree->test_break && bTree->test_break(bTree->tbh)) {
345                         breaked = true;
346                 }
347         }
348         DebugInfo::execution_group_finished(this);
349         DebugInfo::graphviz(graph);
350
351         MEM_freeN(chunkOrder);
352 }
353
354 MemoryBuffer **ExecutionGroup::getInputBuffersOpenCL(int chunkNumber)
355 {
356         rcti rect;
357         vector<MemoryProxy *> memoryproxies;
358         unsigned int index;
359         determineChunkRect(&rect, chunkNumber);
360
361         this->determineDependingMemoryProxies(&memoryproxies);
362         MemoryBuffer **memoryBuffers = (MemoryBuffer **)MEM_callocN(sizeof(MemoryBuffer *) * this->m_cachedMaxReadBufferOffset, __func__);
363         rcti output;
364         for (index = 0; index < this->m_cachedReadOperations.size(); index++) {
365                 ReadBufferOperation *readOperation = (ReadBufferOperation *)this->m_cachedReadOperations[index];
366                 MemoryProxy *memoryProxy = readOperation->getMemoryProxy();
367                 this->determineDependingAreaOfInterest(&rect, readOperation, &output);
368                 MemoryBuffer *memoryBuffer = memoryProxy->getExecutor()->constructConsolidatedMemoryBuffer(memoryProxy, &output);
369                 memoryBuffers[readOperation->getOffset()] = memoryBuffer;
370         }
371         return memoryBuffers;
372 }
373
374 MemoryBuffer *ExecutionGroup::constructConsolidatedMemoryBuffer(MemoryProxy *memoryProxy, rcti *rect)
375 {
376         MemoryBuffer *imageBuffer = memoryProxy->getBuffer();
377         MemoryBuffer *result = new MemoryBuffer(memoryProxy, rect);
378         result->copyContentFrom(imageBuffer);
379         return result;
380 }
381
382 void ExecutionGroup::finalizeChunkExecution(int chunkNumber, MemoryBuffer **memoryBuffers)
383 {
384         if (this->m_chunkExecutionStates[chunkNumber] == COM_ES_SCHEDULED)
385                 this->m_chunkExecutionStates[chunkNumber] = COM_ES_EXECUTED;
386         
387         atomic_add_u(&this->m_chunksFinished, 1);
388         if (memoryBuffers) {
389                 for (unsigned int index = 0; index < this->m_cachedMaxReadBufferOffset; index++) {
390                         MemoryBuffer *buffer = memoryBuffers[index];
391                         if (buffer) {
392                                 if (buffer->isTemporarily()) {
393                                         memoryBuffers[index] = NULL;
394                                         delete buffer;
395                                 }
396                         }
397                 }
398                 MEM_freeN(memoryBuffers);
399         }
400         if (this->m_bTree) {
401                 // status report is only performed for top level Execution Groups.
402                 float progress = this->m_chunksFinished;
403                 progress /= this->m_numberOfChunks;
404                 this->m_bTree->progress(this->m_bTree->prh, progress);
405
406                 char buf[128];
407                 BLI_snprintf(buf, sizeof(buf), "Compositing | Tile %u-%u",
408                              this->m_chunksFinished,
409                              this->m_numberOfChunks);
410                 this->m_bTree->stats_draw(this->m_bTree->sdh, buf);
411         }
412 }
413
414 inline void ExecutionGroup::determineChunkRect(rcti *rect, const unsigned int xChunk, const unsigned int yChunk) const
415 {
416         const int border_width = BLI_rcti_size_x(&this->m_viewerBorder);
417         const int border_height = BLI_rcti_size_y(&this->m_viewerBorder);
418
419         if (this->m_singleThreaded) {
420                 BLI_rcti_init(rect, this->m_viewerBorder.xmin, border_width, this->m_viewerBorder.ymin, border_height);
421         }
422         else {
423                 const unsigned int minx = xChunk * this->m_chunkSize + this->m_viewerBorder.xmin;
424                 const unsigned int miny = yChunk * this->m_chunkSize + this->m_viewerBorder.ymin;
425                 const unsigned int width = min((unsigned int) this->m_viewerBorder.xmax, this->m_width);
426                 const unsigned int height = min((unsigned int) this->m_viewerBorder.ymax, this->m_height);
427                 BLI_rcti_init(rect, min(minx, this->m_width), min(minx + this->m_chunkSize, width), min(miny, this->m_height), min(miny + this->m_chunkSize, height));
428         }
429 }
430
431 void ExecutionGroup::determineChunkRect(rcti *rect, const unsigned int chunkNumber) const
432 {
433         const unsigned int yChunk = chunkNumber / this->m_numberOfXChunks;
434         const unsigned int xChunk = chunkNumber - (yChunk * this->m_numberOfXChunks);
435         determineChunkRect(rect, xChunk, yChunk);
436 }
437
438 MemoryBuffer *ExecutionGroup::allocateOutputBuffer(int /*chunkNumber*/,
439                                                    rcti *rect)
440 {
441         // we asume that this method is only called from complex execution groups.
442         NodeOperation *operation = this->getOutputOperation();
443         if (operation->isWriteBufferOperation()) {
444                 WriteBufferOperation *writeOperation = (WriteBufferOperation *)operation;
445                 MemoryBuffer *buffer = new MemoryBuffer(writeOperation->getMemoryProxy(), rect);
446                 return buffer;
447         }
448         return NULL;
449 }
450
451
452 bool ExecutionGroup::scheduleAreaWhenPossible(ExecutionSystem *graph, rcti *area)
453 {
454         if (this->m_singleThreaded) {
455                 return scheduleChunkWhenPossible(graph, 0, 0);
456         }
457         // find all chunks inside the rect
458         // determine minxchunk, minychunk, maxxchunk, maxychunk where x and y are chunknumbers
459
460         int indexx, indexy;
461         int minx = max_ii(area->xmin - m_viewerBorder.xmin, 0);
462         int maxx = min_ii(area->xmax - m_viewerBorder.xmin, m_viewerBorder.xmax - m_viewerBorder.xmin);
463         int miny = max_ii(area->ymin - m_viewerBorder.ymin, 0);
464         int maxy = min_ii(area->ymax - m_viewerBorder.ymin, m_viewerBorder.ymax - m_viewerBorder.ymin);
465         int minxchunk = minx / (int)m_chunkSize;
466         int maxxchunk = (maxx + (int)m_chunkSize - 1) / (int)m_chunkSize;
467         int minychunk = miny / (int)m_chunkSize;
468         int maxychunk = (maxy + (int)m_chunkSize - 1) / (int)m_chunkSize;
469         minxchunk = max_ii(minxchunk, 0);
470         minychunk = max_ii(minychunk, 0);
471         maxxchunk = min_ii(maxxchunk, (int)m_numberOfXChunks);
472         maxychunk = min_ii(maxychunk, (int)m_numberOfYChunks);
473
474         bool result = true;
475         for (indexx = minxchunk; indexx < maxxchunk; indexx++) {
476                 for (indexy = minychunk; indexy < maxychunk; indexy++) {
477                         if (!scheduleChunkWhenPossible(graph, indexx, indexy)) {
478                                 result = false;
479                         }
480                 }
481         }
482
483         return result;
484 }
485
486 bool ExecutionGroup::scheduleChunk(unsigned int chunkNumber)
487 {
488         if (this->m_chunkExecutionStates[chunkNumber] == COM_ES_NOT_SCHEDULED) {
489                 this->m_chunkExecutionStates[chunkNumber] = COM_ES_SCHEDULED;
490                 WorkScheduler::schedule(this, chunkNumber);
491                 return true;
492         }
493         return false;
494 }
495
496 bool ExecutionGroup::scheduleChunkWhenPossible(ExecutionSystem *graph, int xChunk, int yChunk)
497 {
498         if (xChunk < 0 || xChunk >= (int)this->m_numberOfXChunks) {
499                 return true;
500         }
501         if (yChunk < 0 || yChunk >= (int)this->m_numberOfYChunks) {
502                 return true;
503         }
504         int chunkNumber = yChunk * this->m_numberOfXChunks + xChunk;
505         // chunk is already executed
506         if (this->m_chunkExecutionStates[chunkNumber] == COM_ES_EXECUTED) {
507                 return true;
508         }
509
510         // chunk is scheduled, but not executed
511         if (this->m_chunkExecutionStates[chunkNumber] == COM_ES_SCHEDULED) {
512                 return false;
513         }
514
515         // chunk is nor executed nor scheduled.
516         vector<MemoryProxy *> memoryProxies;
517         this->determineDependingMemoryProxies(&memoryProxies);
518
519         rcti rect;
520         determineChunkRect(&rect, xChunk, yChunk);
521         unsigned int index;
522         bool canBeExecuted = true;
523         rcti area;
524
525         for (index = 0; index < this->m_cachedReadOperations.size(); index++) {
526                 ReadBufferOperation *readOperation = (ReadBufferOperation *)this->m_cachedReadOperations[index];
527                 BLI_rcti_init(&area, 0, 0, 0, 0);
528                 MemoryProxy *memoryProxy = memoryProxies[index];
529                 determineDependingAreaOfInterest(&rect, readOperation, &area);
530                 ExecutionGroup *group = memoryProxy->getExecutor();
531
532                 if (group != NULL) {
533                         if (!group->scheduleAreaWhenPossible(graph, &area)) {
534                                 canBeExecuted = false;
535                         }
536                 }
537                 else {
538                         throw "ERROR";
539                 }
540         }
541
542         if (canBeExecuted) {
543                 scheduleChunk(chunkNumber);
544         }
545
546         return false;
547 }
548
549 void ExecutionGroup::determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output)
550 {
551         this->getOutputOperation()->determineDependingAreaOfInterest(input, readOperation, output);
552 }
553
554 void ExecutionGroup::determineDependingMemoryProxies(vector<MemoryProxy *> *memoryProxies)
555 {
556         unsigned int index;
557         for (index = 0; index < this->m_cachedReadOperations.size(); index++) {
558                 ReadBufferOperation *readOperation = (ReadBufferOperation *) this->m_cachedReadOperations[index];
559                 memoryProxies->push_back(readOperation->getMemoryProxy());
560         }
561 }
562
563 bool ExecutionGroup::isOpenCL()
564 {
565         return this->m_openCL;
566 }
567
568 void ExecutionGroup::setViewerBorder(float xmin, float xmax, float ymin, float ymax)
569 {
570         NodeOperation *operation = this->getOutputOperation();
571
572         if (operation->isViewerOperation() || operation->isPreviewOperation()) {
573                 BLI_rcti_init(&this->m_viewerBorder, xmin * this->m_width, xmax * this->m_width,
574                               ymin * this->m_height, ymax * this->m_height);
575         }
576 }
577
578 void ExecutionGroup::setRenderBorder(float xmin, float xmax, float ymin, float ymax)
579 {
580         NodeOperation *operation = this->getOutputOperation();
581
582         if (operation->isOutputOperation(true)) {
583                 /* Basically, setting border need to happen for only operations
584                  * which operates in render resolution buffers (like compositor
585                  * output nodes).
586                  *
587                  * In this cases adding border will lead to mapping coordinates
588                  * from output buffer space to input buffer spaces when executing
589                  * operation.
590                  *
591                  * But nodes like viewer and file output just shall display or
592                  * safe the same exact buffer which goes to their input, no need
593                  * in any kind of coordinates mapping.
594                  */
595
596                 bool operationNeedsBorder = !(operation->isViewerOperation() ||
597                                               operation->isPreviewOperation() ||
598                                               operation->isFileOutputOperation());
599
600                 if (operationNeedsBorder) {
601                         BLI_rcti_init(&this->m_viewerBorder, xmin * this->m_width, xmax * this->m_width,
602                                       ymin * this->m_height, ymax * this->m_height);
603                 }
604         }
605 }