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 ¶mName) 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 ¶mName)
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 ¶mName) 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 ¶mName)
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