e437b069e337313c58baf0f52e0ba07579ddc001
[blender.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 "BLI_math.h"
29 #include "PIL_time.h"
30 #include "WM_api.h"
31 #include "WM_types.h"
32
33 #include "COM_ExecutionGroup.h"
34 #include "COM_InputSocket.h"
35 #include "COM_SocketConnection.h"
36 #include "COM_defines.h"
37 #include "COM_ExecutionSystem.h"
38 #include "COM_ReadBufferOperation.h"
39 #include "COM_WriteBufferOperation.h"
40 #include "COM_ReadBufferOperation.h"
41 #include "COM_WorkScheduler.h"
42 #include "COM_ViewerOperation.h"
43 #include "COM_ChunkOrder.h"
44 #include "COM_ExecutionSystemHelper.h"
45
46 ExecutionGroup::ExecutionGroup()
47 {
48         this->m_isOutput = false;
49         this->m_complex = false;
50         this->m_chunkExecutionStates = NULL;
51         this->m_bTree = NULL;
52         this->m_height = 0;
53         this->m_width = 0;
54         this->m_cachedMaxReadBufferOffset = 0;
55         this->m_numberOfXChunks = 0;
56         this->m_numberOfYChunks = 0;
57         this->m_numberOfChunks = 0;
58         this->m_initialized = false;
59         this->m_openCL = false;
60         this->m_singleThreaded = false;
61         this->m_chunksFinished = 0;
62 }
63
64 CompositorPriority ExecutionGroup::getRenderPriotrity()
65 {
66         return this->getOutputNodeOperation()->getRenderPriority();
67 }
68
69 bool ExecutionGroup::containsOperation(NodeOperation *operation)
70 {
71         for (vector<NodeOperation *>::const_iterator iterator = this->m_operations.begin(); iterator != this->m_operations.end(); ++iterator) {
72                 NodeOperation *inListOperation = *iterator;
73                 if (inListOperation == operation) {
74                         return true;
75                 }
76         }
77         return false;
78 }
79
80 const bool ExecutionGroup::isComplex() const
81 {
82         return this->m_complex;
83 }
84
85 bool ExecutionGroup::canContainOperation(NodeOperation *operation)
86 {
87         if (!this->m_initialized) { return true; }
88         if (operation->isReadBufferOperation()) { return true; }
89         if (operation->isWriteBufferOperation()) { return false; }
90         if (operation->isSetOperation()) { return true; }
91
92         if (!this->isComplex()) {
93                 return (!operation->isComplex());
94         }
95         else {
96                 return false;
97         }
98 }
99
100 void ExecutionGroup::addOperation(ExecutionSystem *system, NodeOperation *operation)
101 {
102         if (containsOperation(operation)) return;
103         if (canContainOperation(operation)) {
104                 if (!operation->isBufferOperation()) {
105                         this->m_complex = operation->isComplex();
106                         this->m_openCL = operation->isOpenCL();
107                         this->m_singleThreaded = operation->isSingleThreaded();
108                         this->m_initialized = true;
109                 }
110                 this->m_operations.push_back(operation);
111                 if (operation->isReadBufferOperation()) {
112                         ReadBufferOperation *readOperation = (ReadBufferOperation *)operation;
113                         WriteBufferOperation *writeOperation = readOperation->getMemoryProxy()->getWriteBufferOperation();
114                         this->addOperation(system, writeOperation);
115                 }
116                 else {
117                         unsigned int index;
118                         for (index = 0; index < operation->getNumberOfInputSockets(); index++) {
119                                 InputSocket *inputSocket = operation->getInputSocket(index);
120                                 if (inputSocket->isConnected()) {
121                                         NodeOperation *node = (NodeOperation *)inputSocket->getConnection()->getFromNode();
122                                         this->addOperation(system, node);
123                                 }
124                         }
125                 }
126         }
127         else {
128                 if (operation->isWriteBufferOperation()) {
129                         WriteBufferOperation *writeoperation = (WriteBufferOperation *)operation;
130                         if (writeoperation->getMemoryProxy()->getExecutor() == NULL) {
131                                 ExecutionGroup *newGroup = new ExecutionGroup();
132                                 writeoperation->getMemoryProxy()->setExecutor(newGroup);
133                                 newGroup->addOperation(system, operation);
134                                 ExecutionSystemHelper::addExecutionGroup(system->getExecutionGroups(), newGroup);
135                         }
136                 }
137         }
138 }
139
140 NodeOperation *ExecutionGroup::getOutputNodeOperation() const
141 {
142         return this->m_operations[0]; // the first operation of the group is always the output operation.
143 }
144
145 void ExecutionGroup::initExecution()
146 {
147         if (this->m_chunkExecutionStates != NULL) {
148                 delete[] this->m_chunkExecutionStates;
149         }
150         unsigned int index;
151         determineNumberOfChunks();
152
153         this->m_chunkExecutionStates = NULL;
154         if (this->m_numberOfChunks != 0) {
155                 this->m_chunkExecutionStates = new ChunkExecutionState[this->m_numberOfChunks];
156                 for (index = 0; index < this->m_numberOfChunks; index++) {
157                         this->m_chunkExecutionStates[index] = COM_ES_NOT_SCHEDULED;
158                 }
159         }
160
161
162         unsigned int maxNumber = 0;
163
164         for (index = 0; index < this->m_operations.size(); index++) {
165                 NodeOperation *operation = this->m_operations[index];
166                 if (operation->isReadBufferOperation()) {
167                         ReadBufferOperation *readOperation = (ReadBufferOperation *)operation;
168                         this->m_cachedReadOperations.push_back(readOperation);
169                         maxNumber = max(maxNumber, readOperation->getOffset());
170                 }
171         }
172         maxNumber++;
173         this->m_cachedMaxReadBufferOffset = maxNumber;
174
175 }
176
177 void ExecutionGroup::deinitExecution()
178 {
179         if (this->m_chunkExecutionStates != NULL) {
180                 delete[] this->m_chunkExecutionStates;
181                 this->m_chunkExecutionStates = NULL;
182         }
183         this->m_numberOfChunks = 0;
184         this->m_numberOfXChunks = 0;
185         this->m_numberOfYChunks = 0;
186         this->m_cachedReadOperations.clear();
187         this->m_bTree = NULL;
188 }
189 void ExecutionGroup::determineResolution(unsigned int resolution[])
190 {
191         NodeOperation *operation = this->getOutputNodeOperation();
192         resolution[0] = operation->getWidth();
193         resolution[1] = operation->getHeight();
194         this->setResolution(resolution);
195 }
196
197 void ExecutionGroup::determineNumberOfChunks()
198 {
199         if (this->m_singleThreaded) {
200                 this->m_numberOfXChunks = 1;
201                 this->m_numberOfYChunks = 1;
202                 this->m_numberOfChunks = 1;
203         } 
204         else {
205                 const float chunkSizef = this->m_chunkSize;
206                 this->m_numberOfXChunks = ceil(this->m_width / chunkSizef);
207                 this->m_numberOfYChunks = ceil(this->m_height / chunkSizef);
208                 this->m_numberOfChunks = this->m_numberOfXChunks * this->m_numberOfYChunks;
209         }
210 }
211
212 /**
213  * this method is called for the top execution groups. containing the compositor node or the preview node or the viewer node)
214  */
215 void ExecutionGroup::execute(ExecutionSystem *graph)
216 {
217         CompositorContext& context = graph->getContext();
218         const bNodeTree *bTree = context.getbNodeTree();
219         if (this->m_width == 0 || this->m_height == 0) {return; } /// @note: break out... no pixels to calculate.
220         if (bTree->test_break && bTree->test_break(bTree->tbh)) {return; } /// @note: early break out for blur and preview nodes
221         if (this->m_numberOfChunks == 0) {return; } /// @note: early break out
222         unsigned int chunkNumber;
223
224         this->m_chunksFinished = 0;
225         this->m_bTree = bTree;
226         unsigned int index;
227         unsigned int *chunkOrder = new unsigned int[this->m_numberOfChunks];
228
229         for (chunkNumber = 0; chunkNumber < this->m_numberOfChunks; chunkNumber++) {
230                 chunkOrder[chunkNumber] = chunkNumber;
231         }
232         NodeOperation *operation = this->getOutputNodeOperation();
233         float centerX = 0.5;
234         float centerY = 0.5;
235         OrderOfChunks chunkorder = COM_ORDER_OF_CHUNKS_DEFAULT;
236
237         if (operation->isViewerOperation()) {
238                 ViewerBaseOperation *viewer = (ViewerBaseOperation *)operation;
239                 centerX = viewer->getCenterX();
240                 centerY = viewer->getCenterY();
241                 chunkorder = viewer->getChunkOrder();
242         }
243
244         switch (chunkorder) {
245                 case COM_TO_RANDOM:
246                         for (index = 0; index < 2 * this->m_numberOfChunks; index++) {
247                                 int index1 = rand() % this->m_numberOfChunks;
248                                 int index2 = rand() % this->m_numberOfChunks;
249                                 int s = chunkOrder[index1];
250                                 chunkOrder[index1] = chunkOrder[index2];
251                                 chunkOrder[index2] = s;
252                         }
253                         break;
254                 case COM_TO_CENTER_OUT:
255                 {
256                         ChunkOrderHotspot **hotspots = new ChunkOrderHotspot *[1];
257                         hotspots[0] = new ChunkOrderHotspot(this->m_width * centerX, this->m_height * centerY, 0.0f);
258                         rcti rect;
259                         ChunkOrder *chunkOrders = new ChunkOrder[this->m_numberOfChunks];
260                         for (index = 0; index < this->m_numberOfChunks; index++) {
261                                 determineChunkRect(&rect, index);
262                                 chunkOrders[index].setChunkNumber(index);
263                                 chunkOrders[index].setX(rect.xmin);
264                                 chunkOrders[index].setY(rect.ymin);
265                                 chunkOrders[index].determineDistance(hotspots, 1);
266                         }
267
268                         sort(&chunkOrders[0], &chunkOrders[this->m_numberOfChunks - 1]);
269                         for (index = 0; index < this->m_numberOfChunks; index++) {
270                                 chunkOrder[index] = chunkOrders[index].getChunkNumber();
271                         }
272
273                         delete hotspots[0];
274                         delete[] hotspots;
275                         delete[] chunkOrders;
276                 }
277                 break;
278                 case COM_TO_RULE_OF_THIRDS:
279                 {
280                         ChunkOrderHotspot **hotspots = new ChunkOrderHotspot *[9];
281                         unsigned int tx = this->m_width / 6;
282                         unsigned int ty = this->m_height / 6;
283                         unsigned int mx = this->m_width / 2;
284                         unsigned int my = this->m_height / 2;
285                         unsigned int bx = mx + 2 * tx;
286                         unsigned int by = my + 2 * ty;
287
288                         float addition = this->m_numberOfChunks / COM_RULE_OF_THIRDS_DIVIDER;
289                         hotspots[0] = new ChunkOrderHotspot(mx, my, addition * 0);
290                         hotspots[1] = new ChunkOrderHotspot(tx, my, addition * 1);
291                         hotspots[2] = new ChunkOrderHotspot(bx, my, addition * 2);
292                         hotspots[3] = new ChunkOrderHotspot(bx, by, addition * 3);
293                         hotspots[4] = new ChunkOrderHotspot(tx, ty, addition * 4);
294                         hotspots[5] = new ChunkOrderHotspot(bx, ty, addition * 5);
295                         hotspots[6] = new ChunkOrderHotspot(tx, by, addition * 6);
296                         hotspots[7] = new ChunkOrderHotspot(mx, ty, addition * 7);
297                         hotspots[8] = new ChunkOrderHotspot(mx, by, addition * 8);
298                         rcti rect;
299                         ChunkOrder *chunkOrders = new ChunkOrder[this->m_numberOfChunks];
300                         for (index = 0; index < this->m_numberOfChunks; index++) {
301                                 determineChunkRect(&rect, index);
302                                 chunkOrders[index].setChunkNumber(index);
303                                 chunkOrders[index].setX(rect.xmin);
304                                 chunkOrders[index].setY(rect.ymin);
305                                 chunkOrders[index].determineDistance(hotspots, 9);
306                         }
307
308                         sort(&chunkOrders[0], &chunkOrders[this->m_numberOfChunks]);
309
310                         for (index = 0; index < this->m_numberOfChunks; index++) {
311                                 chunkOrder[index] = chunkOrders[index].getChunkNumber();
312                         }
313
314                         delete hotspots[0];
315                         delete hotspots[1];
316                         delete hotspots[2];
317                         delete hotspots[3];
318                         delete hotspots[4];
319                         delete hotspots[5];
320                         delete hotspots[6];
321                         delete hotspots[7];
322                         delete hotspots[8];
323                         delete[] hotspots;
324                         delete[] chunkOrders;
325                 }
326                 break;
327                 case COM_TO_TOP_DOWN:
328                 default:
329                         break;
330         }
331
332         bool breaked = false;
333         bool finished = false;
334         unsigned int startIndex = 0;
335         const int maxNumberEvaluated = BLI_system_thread_count() * 2;
336
337         while (!finished && !breaked) {
338                 bool startEvaluated = false;
339                 finished = true;
340                 int numberEvaluated = 0;
341
342                 for (index = startIndex; index < this->m_numberOfChunks && numberEvaluated < maxNumberEvaluated; index++) {
343                         chunkNumber = chunkOrder[index];
344                         int yChunk = chunkNumber / this->m_numberOfXChunks;
345                         int xChunk = chunkNumber - (yChunk * this->m_numberOfXChunks);
346                         const ChunkExecutionState state = this->m_chunkExecutionStates[chunkNumber];
347                         if (state == COM_ES_NOT_SCHEDULED) {
348                                 scheduleChunkWhenPossible(graph, xChunk, yChunk);
349                                 finished = false;
350                                 startEvaluated = true;
351                                 numberEvaluated++;
352
353                                 WM_main_add_notifier(NC_WINDOW | ND_DRAW, NULL);
354                         }
355                         else if (state == COM_ES_SCHEDULED) {
356                                 finished = false;
357                                 startEvaluated = true;
358                                 numberEvaluated++;
359                         }
360                         else if (state == COM_ES_EXECUTED && !startEvaluated) {
361                                 startIndex = index + 1;
362                         }
363                 }
364
365                 WorkScheduler::finish();
366
367                 if (bTree->test_break && bTree->test_break(bTree->tbh)) {
368                         breaked = true;
369                 }
370         }
371
372         delete[] chunkOrder;
373 }
374
375 MemoryBuffer **ExecutionGroup::getInputBuffersCPU()
376 {
377         vector<MemoryProxy *> memoryproxies;
378         unsigned int index;
379
380         this->determineDependingMemoryProxies(&memoryproxies);
381         MemoryBuffer **memoryBuffers = new MemoryBuffer *[this->m_cachedMaxReadBufferOffset];
382         for (index = 0; index < this->m_cachedMaxReadBufferOffset; index++) {
383                 memoryBuffers[index] = NULL;
384         }
385         for (index = 0; index < this->m_cachedReadOperations.size(); index++) {
386                 ReadBufferOperation *readOperation = (ReadBufferOperation *)this->m_cachedReadOperations[index];
387                 memoryBuffers[readOperation->getOffset()] = readOperation->getMemoryProxy()->getBuffer();
388         }
389         return memoryBuffers;
390 }
391
392 MemoryBuffer **ExecutionGroup::getInputBuffersOpenCL(int chunkNumber)
393 {
394         rcti rect;
395         vector<MemoryProxy *> memoryproxies;
396         unsigned int index;
397         determineChunkRect(&rect, chunkNumber);
398
399         this->determineDependingMemoryProxies(&memoryproxies);
400         MemoryBuffer **memoryBuffers = new MemoryBuffer *[this->m_cachedMaxReadBufferOffset];
401         for (index = 0; index < this->m_cachedMaxReadBufferOffset; index++) {
402                 memoryBuffers[index] = NULL;
403         }
404         rcti output;
405         for (index = 0; index < this->m_cachedReadOperations.size(); index++) {
406                 ReadBufferOperation *readOperation = (ReadBufferOperation *)this->m_cachedReadOperations[index];
407                 MemoryProxy *memoryProxy = readOperation->getMemoryProxy();
408                 this->determineDependingAreaOfInterest(&rect, readOperation, &output);
409                 MemoryBuffer *memoryBuffer = memoryProxy->getExecutor()->constructConsolidatedMemoryBuffer(memoryProxy, &output);
410                 memoryBuffers[readOperation->getOffset()] = memoryBuffer;
411         }
412         return memoryBuffers;
413 }
414
415 MemoryBuffer *ExecutionGroup::constructConsolidatedMemoryBuffer(MemoryProxy *memoryProxy, rcti *rect)
416 {
417         MemoryBuffer *imageBuffer = memoryProxy->getBuffer();
418         MemoryBuffer *result = new MemoryBuffer(memoryProxy, rect);
419         result->copyContentFrom(imageBuffer);
420         return result;
421 }
422
423 void ExecutionGroup::finalizeChunkExecution(int chunkNumber, MemoryBuffer **memoryBuffers)
424 {
425         if (this->m_chunkExecutionStates[chunkNumber] == COM_ES_SCHEDULED)
426                 this->m_chunkExecutionStates[chunkNumber] = COM_ES_EXECUTED;
427         
428         this->m_chunksFinished++;
429         if (memoryBuffers) {
430                 for (unsigned int index = 0; index < this->m_cachedMaxReadBufferOffset; index++) {
431                         MemoryBuffer *buffer = memoryBuffers[index];
432                         if (buffer) {
433                                 if (buffer->isTemporarily()) {
434                                         memoryBuffers[index] = NULL;
435                                         delete buffer;
436                                 }
437                         }
438                 }
439                 delete[] memoryBuffers;
440         }
441         if (this->m_bTree) {
442                 // status report is only performed for top level Execution Groups.
443                 float progress = this->m_chunksFinished;
444                 progress /= this->m_numberOfChunks;
445                 this->m_bTree->progress(this->m_bTree->prh, progress);
446         }
447 }
448
449 inline void ExecutionGroup::determineChunkRect(rcti *rect, const unsigned int xChunk, const unsigned int yChunk) const
450 {
451         if (this->m_singleThreaded) {
452                 BLI_init_rcti(rect, 0, this->m_width, 0, this->m_height);
453         }
454         else {
455                 const unsigned int minx = xChunk * this->m_chunkSize;
456                 const unsigned int miny = yChunk * this->m_chunkSize;
457                 BLI_init_rcti(rect, minx, min(minx + this->m_chunkSize, this->m_width), miny, min(miny + this->m_chunkSize, this->m_height));
458         }
459 }
460
461 void ExecutionGroup::determineChunkRect(rcti *rect, const unsigned int chunkNumber) const
462 {
463         const unsigned int yChunk = chunkNumber / this->m_numberOfXChunks;
464         const unsigned int xChunk = chunkNumber - (yChunk * this->m_numberOfXChunks);
465         determineChunkRect(rect, xChunk, yChunk);
466 }
467
468 MemoryBuffer *ExecutionGroup::allocateOutputBuffer(int chunkNumber, rcti *rect)
469 {
470         // we asume that this method is only called from complex execution groups.
471         NodeOperation *operation = this->getOutputNodeOperation();
472         if (operation->isWriteBufferOperation()) {
473                 WriteBufferOperation *writeOperation = (WriteBufferOperation *)operation;
474                 MemoryBuffer *buffer = new MemoryBuffer(writeOperation->getMemoryProxy(), rect);
475                 return buffer;
476         }
477         return NULL;
478 }
479
480
481 bool ExecutionGroup::scheduleAreaWhenPossible(ExecutionSystem *graph, rcti *area)
482 {
483         if (this->m_singleThreaded) {
484                 return scheduleChunkWhenPossible(graph, 0, 0);
485         }
486         // find all chunks inside the rect
487         // determine minxchunk, minychunk, maxxchunk, maxychunk where x and y are chunknumbers
488
489         float chunkSizef = this->m_chunkSize;
490
491         int indexx, indexy;
492         int minxchunk = floor(area->xmin / chunkSizef);
493         int maxxchunk = ceil((area->xmax - 1) / chunkSizef);
494         int minychunk = floor(area->ymin / chunkSizef);
495         int maxychunk = ceil((area->ymax - 1) / chunkSizef);
496         minxchunk = MAX2(minxchunk, 0);
497         minychunk = MAX2(minychunk, 0);
498         maxxchunk = MIN2(maxxchunk, this->m_numberOfXChunks);
499         maxychunk = MIN2(maxychunk, this->m_numberOfYChunks);
500
501         bool result = true;
502         for (indexx = minxchunk; indexx < maxxchunk; indexx++) {
503                 for (indexy = minychunk; indexy < maxychunk; indexy++) {
504                         if (!scheduleChunkWhenPossible(graph, indexx, indexy)) {
505                                 result = false;
506                         }
507                 }
508         }
509
510         return result;
511 }
512
513 bool ExecutionGroup::scheduleChunk(unsigned int chunkNumber)
514 {
515         if (this->m_chunkExecutionStates[chunkNumber] == COM_ES_NOT_SCHEDULED) {
516                 this->m_chunkExecutionStates[chunkNumber] = COM_ES_SCHEDULED;
517                 WorkScheduler::schedule(this, chunkNumber);
518                 return true;
519         }
520         return false;
521 }
522
523 bool ExecutionGroup::scheduleChunkWhenPossible(ExecutionSystem *graph, int xChunk, int yChunk)
524 {
525         if (xChunk < 0 || xChunk >= (int)this->m_numberOfXChunks) {
526                 return true;
527         }
528         if (yChunk < 0 || yChunk >= (int)this->m_numberOfYChunks) {
529                 return true;
530         }
531         int chunkNumber = yChunk * this->m_numberOfXChunks + xChunk;
532         // chunk is already executed
533         if (this->m_chunkExecutionStates[chunkNumber] == COM_ES_EXECUTED) {
534                 return true;
535         }
536
537         // chunk is scheduled, but not executed
538         if (this->m_chunkExecutionStates[chunkNumber] == COM_ES_SCHEDULED) {
539                 return false;
540         }
541
542         // chunk is nor executed nor scheduled.
543         vector<MemoryProxy *> memoryProxies;
544         this->determineDependingMemoryProxies(&memoryProxies);
545
546         rcti rect;
547         determineChunkRect(&rect, xChunk, yChunk);
548         unsigned int index;
549         bool canBeExecuted = true;
550         rcti area;
551
552         for (index = 0; index < this->m_cachedReadOperations.size(); index++) {
553                 ReadBufferOperation *readOperation = (ReadBufferOperation *)this->m_cachedReadOperations[index];
554                 BLI_init_rcti(&area, 0, 0, 0, 0);
555                 MemoryProxy *memoryProxy = memoryProxies[index];
556                 determineDependingAreaOfInterest(&rect, readOperation, &area);
557                 ExecutionGroup *group = memoryProxy->getExecutor();
558
559                 if (group != NULL) {
560                         if (!group->scheduleAreaWhenPossible(graph, &area)) {
561                                 canBeExecuted = false;
562                         }
563                 }
564                 else {
565                         throw "ERROR";
566                 }
567         }
568
569         if (canBeExecuted) {
570                 scheduleChunk(chunkNumber);
571         }
572
573         return false;
574 }
575
576 void ExecutionGroup::determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output)
577 {
578         this->getOutputNodeOperation()->determineDependingAreaOfInterest(input, readOperation, output);
579 }
580
581 void ExecutionGroup::determineDependingMemoryProxies(vector<MemoryProxy *> *memoryProxies)
582 {
583         unsigned int index;
584         for (index = 0; index < this->m_cachedReadOperations.size(); index++) {
585                 ReadBufferOperation *readOperation = (ReadBufferOperation *) this->m_cachedReadOperations[index];
586                 memoryProxies->push_back(readOperation->getMemoryProxy());
587         }
588 }
589
590 bool ExecutionGroup::isOpenCL()
591 {
592         return this->m_openCL;
593 }