1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-misc <https://github.com/devernay/openfx-misc>,
3  * Copyright (C) 2013-2018 INRIA
4  *
5  * openfx-misc is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * openfx-misc is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with openfx-misc.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17  * ***** END LICENSE BLOCK ***** */
18 
19 /*
20  * OFX TimeBuffer plugin.
21  */
22 
23 #include <cmath>
24 #include <cfloat> // DBL_MAX
25 #include <algorithm>
26 //#include <iostream>
27 #ifdef DEBUG
28 #include <iostream>
29 #endif
30 
31 #include "ofxsCopier.h"
32 #include "ofxsCoords.h"
33 #include "ofxsMacros.h"
34 #include "ofxsThreadSuite.h"
35 #include "ofxsMultiThread.h"
36 #ifdef OFX_EXTENSIONS_NATRON
37 #include "ofxNatron.h"
38 #endif
39 
40 #ifdef OFX_USE_MULTITHREAD_MUTEX
41 namespace {
42 typedef MultiThread::Mutex Mutex;
43 typedef MultiThread::AutoMutex AutoMutex;
44 }
45 #else
46 // some OFX hosts do not have mutex handling in the MT-Suite (e.g. Sony Catalyst Edit)
47 // prefer using the fast mutex by Marcus Geelnard http://tinythreadpp.bitsnbites.eu/
48 #include "fast_mutex.h"
49 namespace {
50 typedef tthread::fast_mutex Mutex;
51 typedef OFX::MultiThread::AutoMutexT<tthread::fast_mutex> AutoMutex;
52 }
53 #endif
54 
55 #ifdef DEBUG
56 #pragma message WARN("TimeBuffer not yet supported by Natron, check again when Natron supports kOfxImageEffectInstancePropSequentialRender")
57 
58 using namespace OFX;
59 
60 OFXS_NAMESPACE_ANONYMOUS_ENTER
61 
62 #define kPluginReadName "TimeBufferRead"
63 #define kPluginReadDescription \
64     "Read an time buffer at current time.\n" \
65     "A time buffer may be used to get the output of any plugin at a previous time, captured using TimeBufferWrite.\n" \
66     "This can typically be used to accumulate several render passes on the same image."
67 #define kPluginReadIdentifier "net.sf.openfx.TimeBufferRead"
68 #define kPluginWriteName "TimeBufferWrite"
69 #define kPluginWriteDescription \
70     "Write an time buffer at currect time.\n" \
71     "Only one instance may exist with a given accumulation buffer name.\n" \
72     "The corresponding TimeBufferRead node, with the same buffer name, must be connected to the 'Sync' input, so that the read operation at the next frame does not start before the write operation at this frame has ended."
73 #define kPluginWriteIdentifier "net.sf.openfx.TimeBufferWrite"
74 #define kPluginGrouping "Time"
75 // History:
76 // version 1.0: initial version
77 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
78 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
79 
80 #define kSupportsTilesRead 0
81 #define kSupportsTilesWrite 0
82 #define kSupportsMultiResolution 1
83 #define kSupportsRenderScale 1
84 #define kSupportsMultipleClipPARs false
85 #define kSupportsMultipleClipDepths false
86 #define kRenderThreadSafety eRenderFullySafe
87 
88 #define kClipSync "Sync"
89 
90 #define kParamBufferName "bufferName"
91 #define kParamBufferNameLabel "Buffer Name"
92 #define kParamBufferNameHint \
93     "Name of the buffer.\n" \
94     "There must be exactly one TimeBufferRead and one TimeBufferWrite instance using this name, and the output of TimeBufferRead must be connected to the \"Sync\" input of TimeBufferWrite.\n" \
95     "This implies that a TimeBufferRead or TimeBufferWrite cannot be duplicated without changing the buffer name, and a unique buffer name must be re-generated when instantiating a PyPlug/Gizmo, or when creating this effect from a script."
96 #define kParamBufferNameHintNatron \
97     "\nNote: In Natron, because OpenFX effects do not know wether they lie in the same project or not, two TimeBufferRead or TimeBufferWrite with the same name can not exist in two projects loaded simultaneously."
98 
99 
100 #define kParamStartFrame "startFrame"
101 #define kParamStartFrameLabel "Start Frame"
102 #define kParamStartFrameHint "First frame of the effect. TimeBufferRead outputs a black and transparent image for this frame and all frames before. The size of the black image is either the size of the Source clip, or the project size if it is not connected."
103 
104 #define kParamUnorderedRender "unorderedRender"
105 #define kParamUnorderedRenderLabel "Unordered Render"
106 #define kParamUnorderedRenderHint \
107     "What should be done whenever rendering is not performed in the expected order (i.e. read at t, write at t, read at t+1, etc.).\n" \
108     "Any value other than \"Error\" may result in a non-reproductible render. For better safety, \"Error\" should be used for all final renders."
109 #define kParamUnorderedRenderOptionError "Error", "Do not render anything and return an error. This value should be used for final renders.", "error"
110 #define kParamUnorderedRenderOptionBlack "Black", "Output a black and transparent image. The size of the black image is either the size of the Source clip, or the project size if it is not connected.", "black"
111 #define kParamUnorderedRenderOptionLast "Last", "Output the last image, even if it was not produced at the previous frame.", "last"
112 
113 enum UnorderedRenderEnum
114 {
115     eUnorderedRenderError = 0,
116     eUnorderedRenderBlack,
117     eUnorderedRenderLast,
118 };
119 
120 #define kParamTimeOut "timeOut"
121 #define kParamTimeOutLabel "Time-out"
122 #define kParamTimeOutHint "Time-out (in ms) for all operations. Should be larger than the execution time of the whole graphe. 0 means infinite."
123 
124 #define kParamReset "reset"
125 #define kParamResetLabel "Reset Buffer"
126 #define kParamResetHint "Reset the buffer state. Should be done on the TimeBufferRead effect if possible."
127 #define kParamResetTrigger "resetTrigger" // a dummy parameter to trigger a re-render
128 
129 #define kParamInfo "info"
130 #define kParamInfoLabel "Info..."
131 #define kParamInfoHint "Reset the buffer state."
132 
133 inline void
sleep(const unsigned int milliseconds)134 sleep(const unsigned int milliseconds)
135 {
136 #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
137     Sleep(milliseconds);
138 #else
139     struct timespec tv;
140     tv.tv_sec = milliseconds / 1000;
141     tv.tv_nsec = (milliseconds % 1000) * 1000000;
142     nanosleep(&tv, 0);
143 #endif
144 }
145 
146 /*
147    We maintain a global map from the buffer name to the buffer data.
148 
149    The buffer data contains:
150    - an image buffers stored with its valid read time (which is the write time +1), or an invalid date
151    - the pointer to the read and the write instances, which should be unique, or NULL if it is not yet created.
152 
153 
154    When TimeBufferReadPlugin::render(t) is called:
155  * if the write instance does not exist, an error is displayed and render fails
156  * if t <= startTime:
157    - a black image is rendered
158    - if t == startTime, the buffer is locked and marked as dirty, with date t+1, then unlocked
159  * if t > startTime:
160    - the buffer is locked, and if it doesn't have date t, then either the render fails, a black image is rendered, or the buffer is used anyway, depending on the user-chosen strategy
161    - if it is marked as dirty, it is unlocked, then locked and read again after a delay (there are no condition variables in the multithread suite, polling is the only solution). The delay starts at 10ms, and is multiplied by two at each unsuccessful lock. abort() is checked at each iteration.
162    - when the buffer is locked and clean, it is copied to output and unlocked
163    - the buffer is re-locked for writing, and marked as dirty, with date t+1, then unlocked
164 
165    When TimeBufferReadPlugin::getRegionOfDefinition(t) is called:
166  * if the write instance does not exist, an error is displayed and render fails
167  * if t <= startTime:
168    - the RoD is empty
169  * if t > startTime:
170    - the buffer is locked, and if it doesn't have date t, then either getRoD fails, a black image with an empty RoD is rendered, or the RoD from buffer is used anyway, depending on the user-chosen strategy
171    - if it is marked as dirty ,it is unlocked, then locked and read again after a delay (there are no condition variables in the multithread suite, polling is the only solution). The delay starts at 10ms, and is multiplied by two at each unsuccessful lock. abort() is checked at each iteration.
172    - when the buffer is locked and clean, the buffer's RoD is returned and it is unlocked
173 
174 
175    When TimeBufferWritePlugin::render(t) is called:
176    - if the read instance does not exist, an error is displayed and render fails
177    - if the "Sync" input is not connected, issue an error message (it should be connected to TimeBufferRead)
178    - the buffer is locked for writing, and if it doesn't have date t+1 or is not dirty, then it is unlocked, render fails and a message is posted. It may be because the TimeBufferRead plugin is not upstream - in this case a solution is to connect TimeBufferRead output to TimeBufferWrite' sync input for syncing.
179    - src is copied to the buffer, and it is marked as not dirty, then unlocked
180    - src is also copied to output.
181 
182 
183    There is a "Reset" button both in TimeBufferRead and TimeBufferWrite, which resets the lock and the buffer.
184 
185    There is a "Info.." button both in TimeBufferRead and TimeBufferWrite, which gives information about all available buffers.
186 
187    If we ever need it, a read-write lock can be implemented using two mutexes, as described in
188    https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Using_two_mutexes
189    This should not be necessary, since the render action of the read node should be called exactly once per frame.
190 
191  */
192 
193 struct TimeBuffer
194 {
195     ImageEffect *readInstance; // written only once, not protected by mutex
196     ImageEffect *writeInstance; // written only once, not protected by mutex
197     mutable Mutex mutex;
198     double time; // can store any integer from 0 to 2^53
199     bool dirty; // TimeBufferRead sets this to true and sets date to t+1, TimeBufferWrite sets this to false
200     std::vector<unsigned char> pixelData;
201     OfxRectI bounds;
202     PixelComponentEnum pixelComponents;
203     int pixelComponentCount;
204     BitDepthEnum bitDepth;
205     int rowBytes;
206     OfxPointD renderScale;
207     double par;
208 
TimeBufferTimeBuffer209     TimeBuffer()
210         : readInstance(NULL)
211         , writeInstance(NULL)
212         , mutex()
213         , time(-DBL_MAX)
214         , dirty(true)
215         , pixelData()
216         , pixelComponents(ePixelComponentNone)
217         , pixelComponentCount(0)
218         , bitDepth(eBitDepthNone)
219         , rowBytes(0)
220         , par(1.)
221     {
222         bounds.x1 = bounds.y1 = bounds.x2 = bounds.y2 = 0;
223         renderScale.x = renderScale.y = 1;
224     }
225 };
226 
227 // This is the global map from buffer names to buffers.
228 // The buffer key should *really* be the concatenation of the ProjectId, the GroupId (if any), and the buffer name,
229 // so that the same name can exist in different groups and/or different projects
230 typedef std::string TimeBufferKey;
231 typedef std::map<TimeBufferKey, TimeBuffer*> TimeBufferMap;
232 static auto_ptr<TimeBufferMap> gTimeBufferMap;
233 static auto_ptr<Mutex> gTimeBufferMapMutex;
234 
235 ////////////////////////////////////////////////////////////////////////////////
236 /** @brief The plugin that does our work */
237 class TimeBufferReadPlugin
238     : public ImageEffect
239 {
240 public:
241 
242     /** @brief ctor */
TimeBufferReadPlugin(OfxImageEffectHandle handle)243     TimeBufferReadPlugin(OfxImageEffectHandle handle)
244         : ImageEffect(handle)
245         , _dstClip(NULL)
246         , _srcClip(NULL)
247         , _bufferName(NULL)
248         , _startFrame(NULL)
249         , _unorderedRender(NULL)
250         , _timeOut(NULL)
251         , _resetTrigger(NULL)
252         , _sublabel(NULL)
253         , _buffer(NULL)
254         , _name()
255     {
256         setSequentialRender(true); // must also be set here, since it is missing from the plugin descriptor in Resolve
257         if ( !gTimeBufferMapMutex.get() ) {
258             gTimeBufferMapMutex.reset(new Mutex);
259         }
260         {
261             AutoMutex guard( gTimeBufferMapMutex.get() );
262             if ( !gTimeBufferMap.get() ) {
263                 gTimeBufferMap.reset(new TimeBufferMap);
264             }
265         }
266         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
267         assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentRGBA) );
268         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
269         assert( (!_srcClip && getContext() == eContextGenerator) ||
270                 ( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() ==  ePixelComponentRGBA) ) );
271 
272         _bufferName = fetchStringParam(kParamBufferName);
273         _startFrame = fetchIntParam(kParamStartFrame);
274         _unorderedRender = fetchChoiceParam(kParamUnorderedRender);
275         _timeOut = fetchDoubleParam(kParamTimeOut);
276         _resetTrigger = fetchBooleanParam(kParamResetTrigger);
277         _sublabel = fetchStringParam(kNatronOfxParamStringSublabelName);
278         assert(_bufferName && _startFrame && _unorderedRender && _sublabel);
279 
280         std::string name;
281         _bufferName->getValue(name);
282         setName(name);
283         _projectId = getPropertySet().propGetString(kNatronOfxImageEffectPropProjectId, false);
284         _groupId = getPropertySet().propGetString(kNatronOfxImageEffectPropGroupId, false);
285     }
286 
~TimeBufferReadPlugin()287     virtual ~TimeBufferReadPlugin()
288     {
289         setName("");
290     }
291 
292 private:
293     /* Override the render */
294     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
295 
296     /* Override the clip preferences, we need to say we are setting the frame varying flag */
297     virtual void getClipPreferences(ClipPreferencesSetter &clipPreferences) OVERRIDE FINAL;
298     virtual bool getRegionOfDefinition(const RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
299     virtual void changedParam(const InstanceChangedArgs &args, const std::string &paramName) OVERRIDE FINAL;
300 
setName(const std::string & name)301     void setName(const std::string &name)
302     {
303         if (name == _name) {
304             // ok!
305             check();
306 
307             return;
308         }
309         std::string key = _projectId + '.' + _groupId + '.' + name;
310         if ( _buffer && (name != _name) ) {
311             _buffer->readInstance = 0; // remove reference to this instance
312             if (!_buffer->writeInstance) {
313                 // we may free this buffer
314                 {
315                     AutoMutex guard( gTimeBufferMapMutex.get() );
316                     gTimeBufferMap->erase(_name);
317                 }
318                 delete _buffer;
319                 _buffer = 0;
320                 _name.clear();
321             }
322         }
323         if (!name.empty() && !_buffer) {
324             TimeBuffer* timeBuffer = 0;
325             {
326                 AutoMutex guard( gTimeBufferMapMutex.get() );
327                 TimeBufferMap::const_iterator it = gTimeBufferMap->find(key);
328                 if ( it != gTimeBufferMap->end() ) {
329                     timeBuffer = it->second;
330                 }
331             }
332             if ( timeBuffer && timeBuffer->readInstance && (timeBuffer->readInstance != this) ) {
333                 // a buffer already exists with that name
334                 setPersistentMessage(Message::eMessageError, "", std::string("A TimeBufferRead already exists with name \"") + name + "\".");
335                 throwSuiteStatusException(kOfxStatFailed);
336 
337                 return;
338             }
339             if (timeBuffer) {
340                 _buffer = timeBuffer;
341             } else {
342                 _buffer = new TimeBuffer;
343             }
344             _buffer->readInstance = this;
345             _name = name;
346             {
347                 AutoMutex guard( gTimeBufferMapMutex.get() );
348                 TimeBufferMap::const_iterator it = gTimeBufferMap->find(key);
349                 if ( it != gTimeBufferMap->end() ) {
350                     assert(it->second == timeBuffer);
351                     if (it->second != timeBuffer) {
352                         setPersistentMessage(Message::eMessageError, "", std::string("A TimeBufferRead already exists with name \"") + name + "\".");
353                         delete _buffer;
354                         _buffer = 0;
355                         _name.clear();
356                         throwSuiteStatusException(kOfxStatFailed);
357 
358                         return;
359                     }
360                 } else {
361                     (*gTimeBufferMap)[key] = _buffer;
362                 }
363             }
364         }
365         clearPersistentMessage();
366         check();
367     } // setName
368 
check()369     void check()
370     {
371 #ifdef DEBUG
372         std::string key = _projectId + '.' + _groupId + '.' + _name;
373         AutoMutex guard( gTimeBufferMapMutex.get() );
374         TimeBufferMap::const_iterator it = gTimeBufferMap->find(key);
375         if ( it == gTimeBufferMap->end() ) {
376             if ( !_name.empty() ) {
377                 std::cout << "Error: Buffer '" << _name << "' not found\n";
378 
379                 return;
380             }
381         } else {
382             if ( _name.empty() ) {
383                 if ( it != gTimeBufferMap->end() ) {
384                     std::cout << "Error: Buffer with empty name found\n";
385                 }
386                 if (_buffer) {
387                     std::cout << "Error: Local buffer with empty name found\n";
388                 }
389 
390                 return;
391             }
392             if (!it->second) {
393                 std::cout << "Error: Buffer '" << _name << "' is NULL\n";
394 
395                 return;
396             }
397             if (it->second->readInstance != this) {
398                 std::cout << "Error: Buffer '" << _name << "' belongs to " << (void*)it->second->readInstance << ", not " << (void*)this << std::endl;
399 
400                 return;
401             }
402         }
403 #endif
404     }
405 
getBuffer()406     TimeBuffer* getBuffer()
407     {
408         std::string key = _projectId + '.' + _groupId + '.' + _name;
409         TimeBuffer* timeBuffer = 0;
410         // * if the write instance does not exist, an error is displayed and render fails
411         {
412             AutoMutex guard( gTimeBufferMapMutex.get() );
413             TimeBufferMap::const_iterator it = gTimeBufferMap->find(key);
414             if ( it != gTimeBufferMap->end() ) {
415                 timeBuffer = it->second;
416             }
417         }
418 
419         if (!timeBuffer) {
420             setPersistentMessage(Message::eMessageError, "", std::string("No TimeBuffer exists with name \"") + _name + "\". Try using another name.");
421             throwSuiteStatusException(kOfxStatFailed);
422 
423             return NULL;
424         }
425         if (timeBuffer && !timeBuffer->readInstance) {
426             setPersistentMessage(Message::eMessageError, "", std::string("Another TimeBufferRead already exists with name \"") + _name + "\". Try using another name.");
427             throwSuiteStatusException(kOfxStatFailed);
428 
429             return NULL;
430         }
431         if ( timeBuffer && timeBuffer->readInstance && (timeBuffer->readInstance != this) ) {
432             // a buffer already exists with that name
433             setPersistentMessage(Message::eMessageError, "", std::string("Another TimeBufferRead already exists with name \"") + _name + "\". Try using another name.");
434             throwSuiteStatusException(kOfxStatFailed);
435 
436             return NULL;
437         }
438         if (timeBuffer && !timeBuffer->writeInstance) {
439             setPersistentMessage(Message::eMessageError, "", std::string("No TimeBufferWrite exists with name \"") + _name + "\". Create one and connect it to this TimeBufferRead via the Sync input.");
440             throwSuiteStatusException(kOfxStatFailed);
441 
442             return NULL;
443         }
444 
445         return timeBuffer;
446     }
447 
448 private:
449     // do not need to delete these, the ImageEffect is managing them for us
450     Clip *_dstClip;
451     Clip *_srcClip;
452     StringParam *_bufferName;
453     IntParam *_startFrame;
454     ChoiceParam *_unorderedRender;
455     DoubleParam *_timeOut;
456     BooleanParam *_resetTrigger;
457     StringParam *_sublabel;
458     TimeBuffer *_buffer; // associated TimeBuffer
459     std::string _name; // name of the TimeBuffer
460     std::string _projectId; // identifier for the project the instance lives in
461     std::string _groupId; // identifier for the group (or subproject) the instance lives in
462 };
463 
464 
465 ////////////////////////////////////////////////////////////////////////////////
466 /** @brief render for the filter */
467 
468 // the overridden render function
469 void
render(const RenderArguments & args)470 TimeBufferReadPlugin::render(const RenderArguments &args)
471 {
472     //std::cout << "render!\n";
473     const double time = args.time;
474 
475     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
476     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
477 
478     auto_ptr<Image> dst( _dstClip->fetchImage(args.time) );
479     if ( !dst.get() ) {
480         throwSuiteStatusException(kOfxStatFailed);
481     }
482     if ( (dst->getRenderScale().x != args.renderScale.x) ||
483          ( dst->getRenderScale().y != args.renderScale.y) ||
484          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
485         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
486         throwSuiteStatusException(kOfxStatFailed);
487 
488         return;
489     }
490     BitDepthEnum dstBitDepth       = dst->getPixelDepth();
491     PixelComponentEnum dstComponents  = dst->getPixelComponents();
492 
493     assert(dstBitDepth == eBitDepthFloat);
494     assert(dstComponents == ePixelComponentRGBA);
495 
496     // do the rendering
497     TimeBuffer* timeBuffer = 0;
498     // * if the write instance does not exist, an error is displayed and render fails
499     timeBuffer = getBuffer();
500     if (!timeBuffer) {
501         throwSuiteStatusException(kOfxStatFailed);
502 
503         return;
504     }
505     int startFrame = _startFrame->getValue();
506     // * if t <= startTime:
507     //   - a black image is rendered
508     //   - if t == startTime, the buffer is locked and marked as dirty, with date t+1, then unlocked
509     if (time <= startFrame) {
510         clearPersistentMessage();
511         fillBlack( *this, args.renderWindow, dst.get() );
512         if (time == startFrame) {
513             AutoMutex guard(timeBuffer->mutex);
514             timeBuffer->dirty = true;
515             timeBuffer->time = time + 1;
516         }
517         clearPersistentMessage();
518 
519         return;
520     }
521     AutoMutex guard(timeBuffer->mutex);
522     // * if t > startTime:
523     //   - the buffer is locked, and if it doesn't have date t, then either the render fails, a black image is rendered, or the buffer is used anyway, depending on the user-chosen strategy
524     if (timeBuffer->time != time) {
525         UnorderedRenderEnum e = (UnorderedRenderEnum)_unorderedRender->getValue();
526         switch (e) {
527         case eUnorderedRenderError:
528             setPersistentMessage(Message::eMessageError, "", "Frames must be rendered in sequential order");
529             throwSuiteStatusException(kOfxStatFailed);
530 
531             return;
532         case eUnorderedRenderBlack:
533             fillBlack( *this, args.renderWindow, dst.get() );
534             timeBuffer->dirty = true;
535             timeBuffer->time = time + 1;
536 
537             return;
538         case eUnorderedRenderLast:
539             // nothing special to do, continue.
540             break;
541         }
542     }
543     //   - if it is marked as dirty, it is unlocked, then locked and read again after a delay (there are no condition variables in the multithread suite, polling is the only solution). The delay starts at 10ms, and is multiplied by two at each unsuccessful lock. abort() is checked at each iteration.
544     int delay = 5; // initial delay, in milliseconds
545     double timeout = _timeOut->getValue();
546     while (timeBuffer->dirty) {
547         guard.unlock();
548         sleep(delay);
549         if ( abort() ) {
550             return;
551         }
552         delay *= 2;
553         if (delay > timeout) {
554             UnorderedRenderEnum e = (UnorderedRenderEnum)_unorderedRender->getValue();
555             switch (e) {
556             case eUnorderedRenderError:
557             case eUnorderedRenderLast:
558                 setPersistentMessage(Message::eMessageError, "", "Timed out");
559                 throwSuiteStatusException(kOfxStatFailed);
560 
561                 return;
562             case eUnorderedRenderBlack:
563                 fillBlack( *this, args.renderWindow, dst.get() );
564                 timeBuffer->dirty = true;
565                 timeBuffer->time = time + 1;
566 
567                 return;
568             }
569         }
570         guard.relock();
571     }
572     if ( (args.renderScale.x != timeBuffer->renderScale.x) || (args.renderScale.y != timeBuffer->renderScale.y) ) {
573         UnorderedRenderEnum e = (UnorderedRenderEnum)_unorderedRender->getValue();
574         switch (e) {
575         case eUnorderedRenderError:
576         case eUnorderedRenderLast:
577             setPersistentMessage(Message::eMessageError, "", "Frames must be rendered in sequential order with the same renderScale");
578             throwSuiteStatusException(kOfxStatFailed);
579 
580             return;
581         case eUnorderedRenderBlack:
582             fillBlack( *this, args.renderWindow, dst.get() );
583             timeBuffer->dirty = true;
584             timeBuffer->time = time + 1;
585 
586             return;
587         }
588     }
589     //   - when the buffer is locked and clean, it is copied to output and unlocked
590     copyPixels( *this, args.renderWindow,
591                 (void*)&timeBuffer->pixelData.front(),
592                 timeBuffer->bounds,
593                 timeBuffer->pixelComponents,
594                 timeBuffer->pixelComponentCount,
595                 timeBuffer->bitDepth,
596                 timeBuffer->rowBytes,
597                 dst.get() );
598     //   - the buffer is re-locked for writing, and marked as dirty, with date t+1, then unlocked
599     timeBuffer->dirty = true;
600     timeBuffer->time = time + 1;
601     clearPersistentMessage();
602     //std::cout << "render! OK\n";
603 } // TimeBufferReadPlugin::render
604 
605 /* Override the clip preferences, we need to say we are setting the frame varying flag */
606 void
getClipPreferences(ClipPreferencesSetter & clipPreferences)607 TimeBufferReadPlugin::getClipPreferences(ClipPreferencesSetter &clipPreferences)
608 {
609     TimeBuffer* timeBuffer = getBuffer();
610 
611     if (!timeBuffer) {
612         throwSuiteStatusException(kOfxStatFailed);
613 
614         return;
615     }
616     clearPersistentMessage();
617     clipPreferences.setOutputFrameVarying(true);
618 }
619 
620 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)621 TimeBufferReadPlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
622                                             OfxRectD &rod)
623 {
624     const double time = args.time;
625     TimeBuffer* timeBuffer = 0;
626 
627     // * if the write instance does not exist, an error is displayed and render fails
628     timeBuffer = getBuffer();
629     if (!timeBuffer) {
630         throwSuiteStatusException(kOfxStatFailed);
631 
632         return false;
633     }
634     // * if t <= startTime:
635     // - the RoD is empty
636     int startFrame = _startFrame->getValue();
637     if (time <= startFrame) {
638         clearPersistentMessage();
639 
640         return false; // use default behavior
641     }
642     AutoMutex guard(timeBuffer->mutex);
643     // * if t > startTime:
644     // - the buffer is locked, and if it doesn't have date t, then either getRoD fails, a black image with an empty RoD is rendered, or the RoD from buffer is used anyway, depending on the user-chosen strategy
645     if (timeBuffer->time != time) {
646         UnorderedRenderEnum e = (UnorderedRenderEnum)_unorderedRender->getValue();
647         switch (e) {
648         case eUnorderedRenderError:
649             setPersistentMessage(Message::eMessageError, "", "Frames must be rendered in sequential order");
650             throwSuiteStatusException(kOfxStatFailed);
651 
652             return false;
653         case eUnorderedRenderBlack:
654 
655             return false;     // use default behavior
656         case eUnorderedRenderLast:
657             // nothing special to do, continue.
658             break;
659         }
660     }
661     // - if it is marked as dirty ,it is unlocked, then locked and read again after a delay (there are no condition variables in the multithread suite, polling is the only solution). The delay starts at 10ms, and is multiplied by two at each unsuccessful lock. abort() is checked at each iteration.
662     int delay = 5; // initial delay, in milliseconds
663     double timeout = _timeOut->getValue();
664     while (timeBuffer->dirty) {
665         guard.unlock();
666         sleep(delay);
667         if ( abort() ) {
668             return false;
669         }
670         delay *= 2;
671         if (delay > timeout) {
672             UnorderedRenderEnum e = (UnorderedRenderEnum)_unorderedRender->getValue();
673             switch (e) {
674             case eUnorderedRenderError:
675             case eUnorderedRenderLast:
676                 setPersistentMessage(Message::eMessageError, "", "Timed out");
677                 throwSuiteStatusException(kOfxStatFailed);
678 
679                 return false;
680             case eUnorderedRenderBlack:
681 
682                 return false;     // use default behavior
683             }
684         }
685         guard.relock();
686     }
687     if ( (args.renderScale.x != timeBuffer->renderScale.x) || (args.renderScale.y != timeBuffer->renderScale.y) ) {
688         UnorderedRenderEnum e = (UnorderedRenderEnum)_unorderedRender->getValue();
689         switch (e) {
690         case eUnorderedRenderError:
691         case eUnorderedRenderLast:
692             setPersistentMessage(Message::eMessageError, "", "Frames must be rendered in sequential order with the same renderScale");
693             throwSuiteStatusException(kOfxStatFailed);
694 
695             return false;
696         case eUnorderedRenderBlack:
697             clearPersistentMessage();
698 
699             return false;
700         }
701     }
702     // - when the buffer is locked and clean, the buffer's RoD is returned and it is unlocked
703     Coords::toCanonical(timeBuffer->bounds,
704                         timeBuffer->renderScale,
705                         timeBuffer->par,
706                         &rod);
707     clearPersistentMessage();
708 
709     return true;
710 } // TimeBufferReadPlugin::getRegionOfDefinition
711 
712 void
changedParam(const InstanceChangedArgs &,const std::string & paramName)713 TimeBufferReadPlugin::changedParam(const InstanceChangedArgs & /*args*/,
714                                    const std::string &paramName)
715 {
716     if (paramName == kParamBufferName) {
717         std::string name;
718         _bufferName->getValue(name);
719         _sublabel->setValue(name);
720         // check if a TimeBufferRead with the same name exists. If yes, issue an error, else clearPersistentMeassage()
721         setName(name);
722     } else if (paramName == kParamReset) {
723         TimeBuffer* timeBuffer = 0;
724         // * if the write instance does not exist, an error is displayed and render fails
725         timeBuffer = getBuffer();
726         if (!timeBuffer) {
727             throwSuiteStatusException(kOfxStatFailed);
728 
729             return;
730         }
731         // reset the buffer to a clean state
732         AutoMutex guard(timeBuffer->mutex);
733         timeBuffer->time = -DBL_MAX;
734         timeBuffer->dirty = true;
735         timeBuffer->pixelComponents = ePixelComponentNone;
736         timeBuffer->pixelComponentCount = 0;
737         timeBuffer->bitDepth = eBitDepthNone;
738         timeBuffer->rowBytes = 0;
739         timeBuffer->par = 1.;
740         timeBuffer->bounds.x1 = timeBuffer->bounds.y1 = timeBuffer->bounds.x2 = timeBuffer->bounds.y2 = 0;
741         timeBuffer->renderScale.x = timeBuffer->renderScale.y = 1;
742         _resetTrigger->setValue( !_resetTrigger->getValue() ); // trigger a render
743     } else if (paramName == kParamInfo) {
744         // give information about allocated buffers
745         // TODO
746     }
747 }
748 
749 mDeclarePluginFactory(TimeBufferReadPluginFactory, {ofxsThreadSuiteCheck();}, { gTimeBufferMapMutex.reset(NULL); gTimeBufferMap.reset(NULL); });
750 void
describe(ImageEffectDescriptor & desc)751 TimeBufferReadPluginFactory::describe(ImageEffectDescriptor &desc)
752 {
753     //std::cout << "describe!\n";
754     // basic labels
755     desc.setLabel(kPluginReadName);
756     desc.setPluginGrouping(kPluginGrouping);
757     desc.setPluginDescription(kPluginReadDescription);
758 
759     desc.addSupportedContext(eContextFilter);
760     desc.addSupportedContext(eContextGeneral);
761     desc.addSupportedContext(eContextGenerator);
762     //desc.addSupportedBitDepth(eBitDepthUByte);
763     //desc.addSupportedBitDepth(eBitDepthUShort);
764     desc.addSupportedBitDepth(eBitDepthFloat);
765 
766     // set a few flags
767     desc.setSingleInstance(false);
768     desc.setHostFrameThreading(false);
769     desc.setSupportsMultiResolution(kSupportsMultiResolution);
770     desc.setSupportsTiles(kSupportsTilesRead);
771     desc.setTemporalClipAccess(false);
772     desc.setRenderTwiceAlways(false);
773     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
774     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
775     desc.setRenderThreadSafety(kRenderThreadSafety);
776     desc.setSequentialRender(true);
777 #ifdef OFX_EXTENSIONS_NATRON
778     desc.setChannelSelector(ePixelComponentNone);
779 #endif
780     //std::cout << "describe! OK\n";
781 }
782 
783 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum)784 TimeBufferReadPluginFactory::describeInContext(ImageEffectDescriptor &desc,
785                                                ContextEnum /*context*/)
786 {
787     //std::cout << "describeInContext!\n";
788     // Source clip only in the filter context
789     // create the mandated source clip
790     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
791 
792     srcClip->addSupportedComponent(ePixelComponentRGBA);
793     srcClip->setTemporalClipAccess(false);
794     srcClip->setSupportsTiles(kSupportsTilesRead);
795     srcClip->setIsMask(false);
796     srcClip->setOptional(true);
797 
798     // create the mandated output clip
799     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
800     dstClip->addSupportedComponent(ePixelComponentRGBA);
801     dstClip->setSupportsTiles(kSupportsTilesRead);
802 
803 
804     // make some pages and to things in
805     PageParamDescriptor *page = desc.definePageParam("Controls");
806 
807     // describe plugin params
808     {
809         StringParamDescriptor* param = desc.defineStringParam(kParamBufferName);
810         param->setLabel(kParamBufferNameLabel);
811         if (getImageEffectHostDescription()->isNatron) {
812             param->setHint(kParamBufferNameHint kParamBufferNameHintNatron);
813         } else {
814             param->setHint(kParamBufferNameHint);
815         }
816         param->setDefault("");
817         param->setAnimates(false);
818         if (page) {
819             page->addChild(*param);
820         }
821     }
822     {
823         IntParamDescriptor* param = desc.defineIntParam(kParamStartFrame);
824         param->setLabel(kParamStartFrameLabel);
825         param->setHint(kParamStartFrameHint);
826         param->setRange(INT_MIN, INT_MAX);
827         param->setDisplayRange(INT_MIN, INT_MAX);
828         param->setDefault(1);
829         param->setAnimates(false);
830         if (page) {
831             page->addChild(*param);
832         }
833     }
834     {
835         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamUnorderedRender);
836         param->setLabel(kParamUnorderedRenderLabel);
837         param->setHint(kParamUnorderedRenderHint);
838         assert(param->getNOptions() == eUnorderedRenderError);
839         param->appendOption(kParamUnorderedRenderOptionError);
840         assert(param->getNOptions() == eUnorderedRenderBlack);
841         param->appendOption(kParamUnorderedRenderOptionBlack);
842         assert(param->getNOptions() == eUnorderedRenderLast);
843         param->appendOption(kParamUnorderedRenderOptionLast);
844         param->setDefault( (int)eUnorderedRenderError );
845         param->setAnimates(false);
846         if (page) {
847             page->addChild(*param);
848         }
849     }
850     {
851         DoubleParamDescriptor* param = desc.defineDoubleParam(kParamTimeOut);
852         param->setLabel(kParamTimeOutLabel);
853         param->setHint(kParamTimeOutHint);
854 #ifdef DEBUG
855         param->setDefault(2000);
856 #else
857         param->setDefault(0);
858 #endif
859         param->setRange(0, DBL_MAX);
860         param->setDisplayRange(0, 10000);
861         param->setAnimates(false);
862         if (page) {
863             page->addChild(*param);
864         }
865     }
866     {
867         PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamReset);
868         param->setLabel(kParamResetLabel);
869         param->setHint(kParamResetHint);
870         if (page) {
871             page->addChild(*param);
872         }
873     }
874     {
875         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamResetTrigger);
876         param->setIsSecretAndDisabled(true);
877         param->setIsPersistent(false);
878         param->setEvaluateOnChange(true);
879         if (page) {
880             page->addChild(*param);
881         }
882     }
883     /*
884        {
885         PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamInfo);
886         param->setLabel(kParamInfoLabel);
887         param->setHint(kParamInfoHint);
888         if (page) {
889             page->addChild(*param);
890         }
891        }
892      */
893     // sublabel
894     {
895         StringParamDescriptor* param = desc.defineStringParam(kNatronOfxParamStringSublabelName);
896         param->setIsSecretAndDisabled(true); // always secret
897         param->setIsPersistent(false);
898         param->setEvaluateOnChange(false);
899         param->setDefault("");
900         if (page) {
901             page->addChild(*param);
902         }
903     }
904 //std::cout << "describeInContext! OK\n";
905 } // TimeBufferReadPluginFactory::describeInContext
906 
907 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)908 TimeBufferReadPluginFactory::createInstance(OfxImageEffectHandle handle,
909                                             ContextEnum /*context*/)
910 {
911     return new TimeBufferReadPlugin(handle);
912 }
913 
914 ////////////////////////////////////////////////////////////////////////////////
915 /** @brief The plugin that does our work */
916 class TimeBufferWritePlugin
917     : public ImageEffect
918 {
919 public:
920 
921     /** @brief ctor */
TimeBufferWritePlugin(OfxImageEffectHandle handle)922     TimeBufferWritePlugin(OfxImageEffectHandle handle)
923         : ImageEffect(handle)
924         , _dstClip(NULL)
925         , _srcClip(NULL)
926         , _syncClip(NULL)
927         , _bufferName(NULL)
928         , _resetTrigger(NULL)
929         , _sublabel(NULL)
930         , _buffer(NULL)
931         , _name()
932     {
933         if ( !gTimeBufferMapMutex.get() ) {
934             gTimeBufferMapMutex.reset(new Mutex);
935         }
936         {
937             AutoMutex guard( gTimeBufferMapMutex.get() );
938             if ( !gTimeBufferMap.get() ) {
939                 gTimeBufferMap.reset(new TimeBufferMap);
940             }
941         }
942 
943         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
944         assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentRGBA) );
945         _srcClip = fetchClip(kOfxImageEffectSimpleSourceClipName);
946         assert( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() == ePixelComponentRGBA) );
947         _syncClip = fetchClip(kOfxImageEffectSimpleSourceClipName);
948         assert(_syncClip && _syncClip->getPixelComponents() == ePixelComponentRGBA);
949 
950         _bufferName = fetchStringParam(kParamBufferName);
951         _resetTrigger = fetchBooleanParam(kParamResetTrigger);
952         _sublabel = fetchStringParam(kNatronOfxParamStringSublabelName);
953         assert(_bufferName && _sublabel);
954         std::string name;
955         _bufferName->getValue(name);
956         setName(name);
957         _sublabel->setValue(name);
958         _projectId = getPropertySet().propGetString(kNatronOfxImageEffectPropProjectId, false);
959         _groupId = getPropertySet().propGetString(kNatronOfxImageEffectPropGroupId, false);
960     }
961 
~TimeBufferWritePlugin()962     virtual ~TimeBufferWritePlugin()
963     {
964         setName("");
965     }
966 
967 private:
968     /* Override the render */
969     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
970     virtual void changedParam(const InstanceChangedArgs &args, const std::string &paramName) OVERRIDE FINAL;
971 
setName(const std::string & name)972     void setName(const std::string &name)
973     {
974         if (name == _name) {
975             // ok!
976             clearPersistentMessage();
977             check();
978 
979             return;
980         }
981         std::string key = _projectId + '.' + _groupId + '.' + name;
982         if ( _buffer && (name != _name) ) {
983             _buffer->writeInstance = 0; // remove reference to this instance
984             if (!_buffer->readInstance) {
985                 // we may free this buffer
986                 {
987                     AutoMutex guard( gTimeBufferMapMutex.get() );
988                     gTimeBufferMap->erase(_name);
989                 }
990                 delete _buffer;
991                 _buffer = 0;
992                 _name.clear();
993             }
994         }
995         if (!name.empty() && !_buffer) {
996             TimeBuffer* timeBuffer = 0;
997             {
998                 AutoMutex guard( gTimeBufferMapMutex.get() );
999                 TimeBufferMap::const_iterator it = gTimeBufferMap->find(key);
1000                 if ( it != gTimeBufferMap->end() ) {
1001                     timeBuffer = it->second;
1002                 }
1003             }
1004             if ( timeBuffer && timeBuffer->writeInstance && (timeBuffer->writeInstance != this) ) {
1005                 // a buffer already exists with that name
1006                 setPersistentMessage(Message::eMessageError, "", std::string("A TimeBufferWrite already exists with name \"") + name + "\".");
1007                 throwSuiteStatusException(kOfxStatFailed);
1008 
1009                 return;
1010             }
1011             if (timeBuffer) {
1012                 _buffer = timeBuffer;
1013             } else {
1014                 _buffer = new TimeBuffer;
1015             }
1016             _buffer->writeInstance = this;
1017             _name = name;
1018             {
1019                 AutoMutex guard( gTimeBufferMapMutex.get() );
1020                 TimeBufferMap::const_iterator it = gTimeBufferMap->find(key);
1021                 if ( it != gTimeBufferMap->end() ) {
1022                     assert(it->second == timeBuffer);
1023                     if (it->second != timeBuffer) {
1024                         setPersistentMessage(Message::eMessageError, "", std::string("A TimeBufferWrite already exists with name \"") + name + "\".");
1025                         delete _buffer;
1026                         _buffer = 0;
1027                         _name.clear();
1028                         throwSuiteStatusException(kOfxStatFailed);
1029 
1030                         return;
1031                     }
1032                 } else {
1033                     (*gTimeBufferMap)[key] = _buffer;
1034                 }
1035             }
1036         }
1037         clearPersistentMessage();
1038         check();
1039     } // setName
1040 
check()1041     void check()
1042     {
1043 #ifdef DEBUG
1044         std::string key = _projectId + '.' + _groupId + '.' + _name;
1045         AutoMutex guard( gTimeBufferMapMutex.get() );
1046         TimeBufferMap::const_iterator it = gTimeBufferMap->find(key);
1047         if ( it == gTimeBufferMap->end() ) {
1048             if ( !_name.empty() ) {
1049                 std::cout << "Error: Buffer '" << _name << "' not found\n";
1050 
1051                 return;
1052             }
1053         } else {
1054             if ( _name.empty() ) {
1055                 if ( it != gTimeBufferMap->end() ) {
1056                     std::cout << "Error: Buffer with empty name found\n";
1057                 }
1058                 if (_buffer) {
1059                     std::cout << "Error: Local buffer with empty name found\n";
1060                 }
1061 
1062                 return;
1063             }
1064             if (!it->second) {
1065                 std::cout << "Error: Buffer '" << _name << "' is NULL\n";
1066 
1067                 return;
1068             }
1069             if (it->second->writeInstance != this) {
1070                 std::cout << "Error: Buffer '" << _name << "' belongs to " << (void*)it->second->writeInstance << ", not " << (void*)this << std::endl;
1071 
1072                 return;
1073             }
1074         }
1075 #endif
1076     }
1077 
getBuffer()1078     TimeBuffer* getBuffer()
1079     {
1080         std::string key = _projectId + '.' + _groupId + '.' + _name;
1081         TimeBuffer* timeBuffer = 0;
1082         // * if the read instance does not exist, an error is displayed and render fails
1083         {
1084             AutoMutex guard( gTimeBufferMapMutex.get() );
1085             TimeBufferMap::const_iterator it = gTimeBufferMap->find(key);
1086             if ( it != gTimeBufferMap->end() ) {
1087                 timeBuffer = it->second;
1088             }
1089         }
1090 
1091         if (!timeBuffer) {
1092             setPersistentMessage(Message::eMessageError, "", std::string("No TimeBuffer exists with name \"") + _name + "\". Try using another name.");
1093             throwSuiteStatusException(kOfxStatFailed);
1094 
1095             return NULL;
1096         }
1097         if (!timeBuffer->writeInstance) {
1098             setPersistentMessage(Message::eMessageError, "", std::string("Another TimeBufferWrite already exists with name \"") + _name + "\". Try using another name.");
1099             throwSuiteStatusException(kOfxStatFailed);
1100 
1101             return NULL;
1102         }
1103         if ( timeBuffer->writeInstance && (timeBuffer->writeInstance != this) ) {
1104             // a buffer already exists with that name
1105             setPersistentMessage(Message::eMessageError, "", std::string("Another TimeBufferWrite already exists with name \"") + _name + "\". Try using another name.");
1106             throwSuiteStatusException(kOfxStatFailed);
1107 
1108             return NULL;
1109         }
1110         if (!timeBuffer->readInstance) {
1111             setPersistentMessage(Message::eMessageError, "", std::string("No TimeBufferRead exists with name \"") + _name + "\". Create one and connect it to this TimeBufferWrite via the Sync input.");
1112             throwSuiteStatusException(kOfxStatFailed);
1113 
1114             return NULL;
1115         }
1116 
1117         return timeBuffer;
1118     }
1119 
1120 private:
1121     // do not need to delete these, the ImageEffect is managing them for us
1122     Clip *_dstClip;
1123     Clip *_srcClip;
1124     Clip *_syncClip;
1125     StringParam *_bufferName;
1126     BooleanParam *_resetTrigger;
1127     StringParam *_sublabel;
1128     TimeBuffer *_buffer; // associated TimeBuffer
1129     std::string _name; // name of the TimeBuffer
1130     std::string _projectId; // identifier for the project the instance lives in
1131     std::string _groupId; // identifier for the group (or subproject) the instance lives in
1132 };
1133 
1134 
1135 ////////////////////////////////////////////////////////////////////////////////
1136 /** @brief render for the filter */
1137 
1138 // the overridden render function
1139 void
render(const RenderArguments & args)1140 TimeBufferWritePlugin::render(const RenderArguments &args)
1141 {
1142     //std::cout << "render!\n";
1143     if ( !_syncClip->isConnected() ) {
1144         setPersistentMessage(Message::eMessageError, "", "The Sync clip must be connected to the output of the corresponding TimeBufferRead effect.");
1145         throwSuiteStatusException(kOfxStatFailed);
1146 
1147         return;
1148     }
1149 
1150     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
1151     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
1152     // do the rendering
1153     // get a dst image
1154     auto_ptr<Image>  dst( _dstClip->fetchImage(args.time) );
1155     if ( !dst.get() ) {
1156         throwSuiteStatusException(kOfxStatFailed);
1157     }
1158     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
1159     PixelComponentEnum dstComponents  = dst->getPixelComponents();
1160     assert(dstComponents == ePixelComponentRGBA);
1161     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
1162          ( dstComponents != _dstClip->getPixelComponents() ) ) {
1163         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
1164         throwSuiteStatusException(kOfxStatFailed);
1165     }
1166     if ( (dst->getRenderScale().x != args.renderScale.x) ||
1167          ( dst->getRenderScale().y != args.renderScale.y) ||
1168          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
1169         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
1170         throwSuiteStatusException(kOfxStatFailed);
1171     }
1172 
1173     const double time = args.time;
1174     auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
1175                                     _srcClip->fetchImage(time) : 0 );
1176     if ( src.get() ) {
1177         if ( (src->getRenderScale().x != args.renderScale.x) ||
1178              ( src->getRenderScale().y != args.renderScale.y) ||
1179              ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
1180             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
1181             throwSuiteStatusException(kOfxStatFailed);
1182         }
1183         BitDepthEnum srcBitDepth      = src->getPixelDepth();
1184         PixelComponentEnum srcComponents = src->getPixelComponents();
1185         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
1186             throwSuiteStatusException(kOfxStatErrImageFormat);
1187         }
1188     }
1189 
1190     // do the rendering
1191     TimeBuffer* timeBuffer = 0;
1192     // - if the read instance does not exist, an error is displayed and render fails
1193     timeBuffer = getBuffer();
1194     if (!timeBuffer) {
1195         throwSuiteStatusException(kOfxStatFailed);
1196 
1197         return;
1198     }
1199     // - the buffer is locked for writing, and if it doesn't have date t+1 or is not dirty, then it is unlocked, render fails and a message is posted. It may be because the TimeBufferRead plugin is not upstream - in this case a solution is to connect TimeBufferRead output to TimeBufferWrite' sync input for syncing.
1200     {
1201         AutoMutex guard(timeBuffer->mutex);
1202         if ( (timeBuffer->time != time + 1) || !timeBuffer->dirty ) {
1203             setPersistentMessage(Message::eMessageError, "", "The TimeBuffer has wrong properties. Check that the corresponding TimeBufferRead effect is connected to the Sync input.");
1204             throwSuiteStatusException(kOfxStatFailed);
1205         }
1206         // - src is copied to the buffer, and it is marked as not dirty, then unlocked
1207         std::vector<unsigned char> pixelData;
1208         timeBuffer->bounds = args.renderWindow;
1209         timeBuffer->pixelComponents = src->getPixelComponents();
1210         timeBuffer->pixelComponentCount = src->getPixelComponentCount();
1211         timeBuffer->bitDepth = src->getPixelDepth();
1212         timeBuffer->rowBytes = (args.renderWindow.x2 - args.renderWindow.x1) * timeBuffer->pixelComponentCount * sizeof(float);
1213         timeBuffer->renderScale = args.renderScale;
1214         timeBuffer->par = src->getPixelAspectRatio();
1215         timeBuffer->pixelData.resize( (size_t)timeBuffer->rowBytes * (args.renderWindow.y2 - args.renderWindow.y1) );
1216         copyPixels(*this, args.renderWindow, src.get(), &timeBuffer->pixelData.front(), timeBuffer->bounds, timeBuffer->pixelComponents, timeBuffer->pixelComponentCount, timeBuffer->bitDepth, timeBuffer->rowBytes);
1217         timeBuffer->dirty = false;
1218     }
1219     // - src is also copied to output.
1220 
1221     copyPixels( *this, args.renderWindow, src.get(), dst.get() );
1222     clearPersistentMessage();
1223     //std::cout << "render! OK\n";
1224 } // TimeBufferWritePlugin::render
1225 
1226 void
changedParam(const InstanceChangedArgs &,const std::string & paramName)1227 TimeBufferWritePlugin::changedParam(const InstanceChangedArgs & /*args*/,
1228                                     const std::string &paramName)
1229 {
1230     if (paramName == kParamBufferName) {
1231         std::string name;
1232         _bufferName->getValue(name);
1233         _sublabel->setValue(name);
1234         // check if a TimeBufferRead with the same name exists. If yes, issue an error, else clearPersistentMeassage()
1235         setName(name);
1236     } else if (paramName == kParamReset) {
1237         // reset the buffer to a clean state
1238         TimeBuffer* timeBuffer = 0;
1239         // * if the write instance does not exist, an error is displayed and render fails
1240         timeBuffer = getBuffer();
1241         if (!timeBuffer) {
1242             throwSuiteStatusException(kOfxStatFailed);
1243 
1244             return;
1245         }
1246         // reset the buffer to a clean state
1247         AutoMutex guard(timeBuffer->mutex);
1248         if (timeBuffer->readInstance) {
1249             sendMessage(Message::eMessageError, "", "A TimeBufferRead instance is connected to this buffer, please reset it instead.");
1250 
1251             return;
1252         }
1253         timeBuffer->time = -DBL_MAX;
1254         timeBuffer->dirty = true;
1255         timeBuffer->pixelComponents = ePixelComponentNone;
1256         timeBuffer->pixelComponentCount = 0;
1257         timeBuffer->bitDepth = eBitDepthNone;
1258         timeBuffer->rowBytes = 0;
1259         timeBuffer->par = 1.;
1260         timeBuffer->bounds.x1 = timeBuffer->bounds.y1 = timeBuffer->bounds.x2 = timeBuffer->bounds.y2 = 0;
1261         timeBuffer->renderScale.x = timeBuffer->renderScale.y = 1;
1262         _resetTrigger->setValue( !_resetTrigger->getValue() ); // trigger a render
1263     } else if (paramName == kParamInfo) {
1264         // give information about allocated buffers
1265         // TODO
1266     }
1267 }
1268 
1269 mDeclarePluginFactory(TimeBufferWritePluginFactory, {ofxsThreadSuiteCheck();}, { gTimeBufferMapMutex.reset(NULL); gTimeBufferMap.reset(NULL); });
1270 void
describe(ImageEffectDescriptor & desc)1271 TimeBufferWritePluginFactory::describe(ImageEffectDescriptor &desc)
1272 {
1273     //std::cout << "describe!\n";
1274     // basic labels
1275     desc.setLabel(kPluginWriteName);
1276     desc.setPluginGrouping(kPluginGrouping);
1277     desc.setPluginDescription(kPluginWriteDescription);
1278 
1279     desc.addSupportedContext(eContextFilter);
1280     desc.addSupportedContext(eContextGeneral);
1281     //desc.addSupportedBitDepth(eBitDepthUByte);
1282     //desc.addSupportedBitDepth(eBitDepthUShort);
1283     desc.addSupportedBitDepth(eBitDepthFloat);
1284 
1285     // set a few flags
1286     desc.setSingleInstance(false);
1287     desc.setHostFrameThreading(false);
1288     desc.setSupportsMultiResolution(kSupportsMultiResolution);
1289     desc.setSupportsTiles(kSupportsTilesWrite);
1290     desc.setTemporalClipAccess(false);
1291     desc.setRenderTwiceAlways(false);
1292     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
1293     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
1294     desc.setRenderThreadSafety(kRenderThreadSafety);
1295     desc.setSequentialRender(true);
1296 #ifdef OFX_EXTENSIONS_NATRON
1297     desc.setChannelSelector(ePixelComponentNone);
1298 #endif
1299 
1300     //std::cout << "describe! OK\n";
1301 }
1302 
1303 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum)1304 TimeBufferWritePluginFactory::describeInContext(ImageEffectDescriptor &desc,
1305                                                 ContextEnum /*context*/)
1306 {
1307     //std::cout << "describeInContext!\n";
1308     // Source clip only in the filter context
1309     // create the mandated source clip
1310     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
1311 
1312     srcClip->addSupportedComponent(ePixelComponentRGBA);
1313     srcClip->setTemporalClipAccess(false);
1314     srcClip->setSupportsTiles(kSupportsTilesWrite);
1315     srcClip->setIsMask(false);
1316 
1317     ClipDescriptor *syncClip = desc.defineClip(kClipSync);
1318     syncClip->addSupportedComponent(ePixelComponentRGBA);
1319     syncClip->setTemporalClipAccess(false);
1320     syncClip->setSupportsTiles(kSupportsTilesRead);
1321     syncClip->setIsMask(false);
1322 
1323     // create the mandated output clip
1324     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
1325     dstClip->addSupportedComponent(ePixelComponentRGBA);
1326     dstClip->setSupportsTiles(kSupportsTilesWrite);
1327 
1328 
1329     // make some pages and to things in
1330     PageParamDescriptor *page = desc.definePageParam("Controls");
1331 
1332     // describe plugin params
1333     {
1334         StringParamDescriptor* param = desc.defineStringParam(kParamBufferName);
1335         param->setLabel(kParamBufferNameLabel);
1336         if (getImageEffectHostDescription()->isNatron) {
1337             param->setHint(kParamBufferNameHint kParamBufferNameHintNatron);
1338         } else {
1339             param->setHint(kParamBufferNameHint);
1340         }
1341         param->setDefault("");
1342         param->setAnimates(false);
1343         if (page) {
1344             page->addChild(*param);
1345         }
1346     }
1347     {
1348         PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamReset);
1349         param->setLabel(kParamResetLabel);
1350         param->setHint(kParamResetHint);
1351         if (page) {
1352             page->addChild(*param);
1353         }
1354     }
1355     {
1356         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamResetTrigger);
1357         param->setIsSecretAndDisabled(true);
1358         param->setIsPersistent(false);
1359         param->setEvaluateOnChange(true);
1360         param->setAnimates(false);
1361         if (page) {
1362             page->addChild(*param);
1363         }
1364     }
1365     /*
1366        {
1367         PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamInfo);
1368         param->setLabel(kParamInfoLabel);
1369         param->setHint(kParamInfoHint);
1370         if (page) {
1371             page->addChild(*param);
1372         }
1373        }
1374      */
1375     // sublabel
1376     {
1377         StringParamDescriptor* param = desc.defineStringParam(kNatronOfxParamStringSublabelName);
1378         param->setIsSecretAndDisabled(true); // always secret
1379         param->setIsPersistent(true);
1380         param->setEvaluateOnChange(false);
1381         param->setDefault("");
1382         if (page) {
1383             page->addChild(*param);
1384         }
1385     }
1386 
1387 //std::cout << "describeInContext! OK\n";
1388 } // TimeBufferWritePluginFactory::describeInContext
1389 
1390 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1391 TimeBufferWritePluginFactory::createInstance(OfxImageEffectHandle handle,
1392                                              ContextEnum /*context*/)
1393 {
1394     return new TimeBufferWritePlugin(handle);
1395 }
1396 
1397 static TimeBufferReadPluginFactory p1(kPluginReadIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1398 static TimeBufferWritePluginFactory p2(kPluginWriteIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1399 mRegisterPluginFactoryInstance(p1)
1400 mRegisterPluginFactoryInstance(p2)
1401 
1402 OFXS_NAMESPACE_ANONYMOUS_EXIT
1403 
1404 #endif // DEBUG
1405