//Rendering components #include "toonz/multimediarenderer.h" #include "toonz/movierenderer.h" #include "trenderer.h" //Scene structures #include "toonz/toonzscene.h" #include "toonz/txsheet.h" #include "toonz/fxdag.h" #include "toonz/tcolumnfxset.h" //Fxs tree decomposition #include "toonz/scenefx.h" #include "toonz/tcolumnfx.h" //Idle processing #include //---------------------------------------------------------------------------- // Local stuff namespace { std::wstring removeSpaces(const std::wstring &str) { std::wstring result; std::wstring::size_type a = 0, b; while ((b = str.find_first_of(L" ", a)) != std::wstring::npos) { result += str.substr(a, b - a); a = b + 1; } result += str.substr(a, std::wstring::npos); return result; } } //========================================================= // // MultimediaRenderer::Imp class // //--------------------------------------------------------- class MultimediaRenderer::Imp : public MovieRenderer::Listener, public TSmartObject { public: ToonzScene *m_scene; TFilePath m_fp; int m_threadCount; bool m_cacheResults; //Output movie frames. May be remapped from rendered ones. double m_xDpi, m_yDpi; TRenderSettings m_renderSettings; std::vector m_listeners; bool m_precomputingEnabled; bool m_canceled; int m_currentFx; set::iterator m_currentFrame; TRenderer *m_currentTRenderer; TFxSet m_fxsToRender; set m_framesToRender; QEventLoop m_eventLoop; int m_multimediaMode; Imp(ToonzScene *scene, const TFilePath &moviePath, int multimediaMode, int threadCount, bool cacheResults); ~Imp(); void scanSceneForRenderNodes(); void scanSceneForColumns(); void scanSceneForLayers(); bool scanColsRecursive(TFx *fx); TColumnFx *searchColumn(TFxP fx); TFxP addPostProcessing(TFxP fx, TFxP postProc); void addPostProcessingRecursive(TFxP fx, TFxP postProc); void start(); bool onFrameCompleted(int frame); bool onFrameFailed(int frame, TException &e); void onSequenceCompleted(const TFilePath &fp); void onRenderCompleted(); }; //--------------------------------------------------------- MultimediaRenderer::Imp::Imp( ToonzScene *scene, const TFilePath &moviePath, int multimediaMode, int threadCount, bool cacheResults) : m_scene(scene), m_fp(moviePath), m_threadCount(threadCount), m_cacheResults(cacheResults), m_xDpi(72), m_yDpi(72), m_renderSettings(), m_listeners(), m_precomputingEnabled(true), m_canceled(false), m_currentFx(0), m_currentFrame(), m_multimediaMode(multimediaMode) { //Retrieve all fx nodes to be rendered in this process. scanSceneForRenderNodes(); } //--------------------------------------------------------- MultimediaRenderer::Imp::~Imp() { } //--------------------------------------------------------- void MultimediaRenderer::Imp::scanSceneForRenderNodes() { switch (m_multimediaMode) { case COLUMNS: scanSceneForColumns(); CASE LAYERS : scanSceneForLayers(); DEFAULT: assert(0); } } //--------------------------------------------------------- //!Retrieve the first level of scene columns, climb their paths //!along unary fxs, and then build the scene fxs. void MultimediaRenderer::Imp::scanSceneForColumns() { //Retrieve the terminal fxs (ie, fxs which are implicitly //connected to the xsheet one) TXsheet *xsh = m_scene->getXsheet(); TFxSet *fxs = xsh->getFxDag()->getTerminalFxs(); //Launch the recursive column climber procedure for each for (int i = 0; i < fxs->getFxCount(); ++i) { TFx *fx = fxs->getFx(i); if (!fx) continue; bool isItARenderFx = scanColsRecursive(fx); if (isItARenderFx) m_fxsToRender.addFx(fx); } } //--------------------------------------------------------- //!Returns true if the passed fx is a valid column representant bool MultimediaRenderer::Imp::scanColsRecursive(TFx *fx) { //Columns should return true - they are column representant if (dynamic_cast(fx)) return true; //Search columns in every port bool isChildAnFxRepres; for (int i = 0; i < fx->getInputPortCount(); ++i) { TFx *childFx = fx->getInputPort(i)->getFx(); if (!childFx) continue; isChildAnFxRepres = scanColsRecursive(childFx); if (isChildAnFxRepres && fx->getInputPortCount() > 1) m_fxsToRender.addFx(childFx); } if (isChildAnFxRepres && fx->getInputPortCount() == 1) return true; return false; } //--------------------------------------------------------- //!Build the scene fx for each node below the xsheet one. //!Remember that left xsheet ports must not be expanded. void MultimediaRenderer::Imp::scanSceneForLayers() { //Retrieve the terminal fxs (ie, fxs which are implicitly //connected to the xsheet one) TXsheet *xsh = m_scene->getXsheet(); TFxSet *fxs = xsh->getFxDag()->getTerminalFxs(); //Examine all of them and - eventually - expand left xsheet //ports (ie fx nodes who allow implicit overlaying) for (int i = 0; i < fxs->getFxCount(); ++i) { TFx *fx = fxs->getFx(i); TFxPort *leftXSheetPort; retry: if (!fx) continue; leftXSheetPort = fx->getXsheetPort(); if (!leftXSheetPort) { m_fxsToRender.addFx(fx); continue; } //If the leftXSheetPort is not connected, retry on port 0 if (leftXSheetPort->isConnected()) m_fxsToRender.addFx(fx); else { fx = fx->getInputPort(0)->getFx(); goto retry; } } } //--------------------------------------------------------- TColumnFx *MultimediaRenderer::Imp::searchColumn(TFxP fx) { //Descend ports 0 until a TColumnFx is found. Then, output its number. TFx *currFx = fx.getPointer(); TColumnFx *colFx = dynamic_cast(currFx); while (!colFx) { if (fx->getInputPortCount() <= 0) break; currFx = currFx->getInputPort(0)->getFx(); if (!currFx) break; colFx = dynamic_cast(currFx); } return colFx; } //--------------------------------------------------------- TFxP MultimediaRenderer::Imp::addPostProcessing(TFxP fx, TFxP postProc) { if (dynamic_cast(postProc.getPointer())) return fx; //Clone the postProcessing tree and substitute recursively postProc = postProc->clone(true); addPostProcessingRecursive(fx, postProc); return postProc; } //--------------------------------------------------------- void MultimediaRenderer::Imp::addPostProcessingRecursive(TFxP fx, TFxP postProc) { if (!postProc) return; int i, count = postProc->getInputPortCount(); for (i = 0; i < count; ++i) { TFxPort *port = postProc->getInputPort(i); TFx *childFx = port->getFx(); if (dynamic_cast(childFx)) port->setFx(fx.getPointer()); else addPostProcessingRecursive(fx, childFx); } } //--------------------------------------------------------- void MultimediaRenderer::Imp::start() { //Retrieve some useful infos double stretchTo = m_renderSettings.m_timeStretchTo; double stretchFrom = m_renderSettings.m_timeStretchFrom; double timeStretchFactor = stretchFrom / stretchTo; bool fieldRendering = m_renderSettings.m_fieldPrevalence != TRenderSettings::NoField; std::wstring modeStr; switch (m_multimediaMode) { case COLUMNS: modeStr = L"_col"; CASE LAYERS : modeStr = L"_lay"; DEFAULT: assert(0); } //Build the post processing fxs tree std::vector postFxs(m_framesToRender.size()); std::set::iterator jt; int j; for (j = 0, jt = m_framesToRender.begin(); jt != m_framesToRender.end(); ++j, ++jt) postFxs[j] = buildPostSceneFx(m_scene, *jt, m_renderSettings.m_shrinkX, false); // Adds camera and camera dpi transforms //For each node to be rendered int i, count = m_fxsToRender.getFxCount(); for (i = 0; i < count; ++i) { //In case the process has been canceled, return if (m_canceled) return; //Then, build the scene fx for each frame to be rendered std::vector> pairsToBeRendered; //NOTE: The above pairs should not be directly added to a previously declared movierenderer, //since an output level would be created even before knowing if any cell to be rendered is //actually available. const BSFX_Transforms_Enum transforms = // Do NOT add camera and BSFX_Transforms_Enum(BSFX_COLUMN_TR); // camera dpi transforms int j; for (j = 0, jt = m_framesToRender.begin(); jt != m_framesToRender.end(); ++j, ++jt) { TFxPair fx; if (m_renderSettings.m_stereoscopic) m_scene->shiftCameraX(-m_renderSettings.m_stereoscopicShift / 2); fx.m_frameA = buildSceneFx(m_scene, *jt, 0, m_fxsToRender.getFx(i), transforms); if (m_renderSettings.m_stereoscopic) { m_scene->shiftCameraX(m_renderSettings.m_stereoscopicShift); fx.m_frameB = buildSceneFx(m_scene, *jt, 0, m_fxsToRender.getFx(i), transforms); m_scene->shiftCameraX(-m_renderSettings.m_stereoscopicShift / 2); } else if (fieldRendering) fx.m_frameB = buildSceneFx(m_scene, *jt + 0.5 * timeStretchFactor, 0, m_fxsToRender.getFx(i), transforms); else fx.m_frameB = TRasterFxP(); //Substitute with the post-process... if (fx.m_frameA) fx.m_frameA = addPostProcessing(fx.m_frameA, postFxs[j]); if (fx.m_frameB) fx.m_frameB = addPostProcessing(fx.m_frameB, postFxs[j]); if (fx.m_frameA) //Could be 0, if the corresponding cell is void / not rendered pairsToBeRendered.push_back(std::pair(*jt, fx)); } if (pairsToBeRendered.size() == 0) continue; //Could be, if no cell for this column was in the frame range. //Build the output name TColumnFx *colFx = searchColumn(m_fxsToRender.getFx(i)); TFx *currFx = m_fxsToRender.getFx(i); assert(colFx); if (!colFx) continue; int columnIndex = colFx->getColumnIndex(); std::wstring columnName(colFx->getColumnName()); std::wstring columnId(colFx->getColumnId()); std::wstring fxName(currFx->getName()); std::wstring fxNameNoSpaces(::removeSpaces(fxName)); std::wstring fxId(currFx->getFxId()); std::wstring fpName = m_fp.getWideName() + L"_" + columnName + (columnId == columnName ? L"" : L"(" + columnId + L")") + (fxId.empty() ? L"" : L"_" + fxName + (fxId == fxNameNoSpaces ? L"" : L"(" + fxId + L")")); //+ modeStr + toWideString(columnIndex+1); TFilePath movieFp(m_fp.withName(fpName)); //Initialize a MovieRenderer with our infos MovieRenderer movieRenderer(m_scene, movieFp, m_threadCount, false); movieRenderer.setRenderSettings(m_renderSettings); movieRenderer.setDpi(m_xDpi, m_yDpi); movieRenderer.enablePrecomputing(m_precomputingEnabled); movieRenderer.addListener(this); for (unsigned int j = 0; j < pairsToBeRendered.size(); ++j) { std::pair ¤tPair = pairsToBeRendered[j]; movieRenderer.addFrame(currentPair.first, currentPair.second); } //Finally, start rendering currently initialized MovieRenderer. m_currentFx = i; m_currentFrame = m_framesToRender.begin(); m_currentTRenderer = movieRenderer.getTRenderer(); movieRenderer.start(); //I don't recall Toonz currently supports different, simultaneous rendering processes. //However, one rendering process is supposed to be exhausting the machine's resources //on its own - so, we'll just set up an idle (but GUI-reactive) loop here. //It will quit looping whenever the "onSequenceCompleted" notification gets called. m_eventLoop.exec(); //---------------------- Loops here -------------------------- } //Lastly, inform everyone that rendering stopped onRenderCompleted(); } //--------------------------------------------------------- bool MultimediaRenderer::Imp::onFrameCompleted(int frame) { //Inform all listeners for (unsigned int i = 0; i < m_listeners.size(); ++i) m_listeners[i]->onFrameCompleted(*m_currentFrame, m_currentFx); m_currentFrame++; return !m_canceled; } //--------------------------------------------------------- bool MultimediaRenderer::Imp::onFrameFailed(int frame, TException &e) { //Inform all listeners for (unsigned int i = 0; i < m_listeners.size(); ++i) m_listeners[i]->onFrameFailed(*m_currentFrame, m_currentFx, e); m_currentFrame++; return !m_canceled; } //--------------------------------------------------------- void MultimediaRenderer::Imp::onSequenceCompleted(const TFilePath &fp) { //Inform all listeners m_currentTRenderer = 0; for (unsigned int i = 0; i < m_listeners.size(); ++i) m_listeners[i]->onSequenceCompleted(m_currentFx); m_eventLoop.quit(); } //--------------------------------------------------------- void MultimediaRenderer::Imp::onRenderCompleted() { for (unsigned int i = 0; i < m_listeners.size(); ++i) m_listeners[i]->onRenderCompleted(); } //============================================= // // MultimediaRenderer class // //--------------------------------------------- MultimediaRenderer::MultimediaRenderer( ToonzScene *scene, const TFilePath &moviePath, int multimediaMode, int threadCount, bool cacheResults) : m_imp(new Imp(scene, moviePath, multimediaMode, threadCount, cacheResults)) { m_imp->addRef(); } //--------------------------------------------------------- MultimediaRenderer::~MultimediaRenderer() { m_imp->release(); } //--------------------------------------------------------- const TFilePath &MultimediaRenderer::getFilePath() { return m_imp->m_fp; } //--------------------------------------------------------- int MultimediaRenderer::getFrameCount() { return m_imp->m_framesToRender.size(); } //--------------------------------------------------------- int MultimediaRenderer::getColumnsCount() { return m_imp->m_fxsToRender.getFxCount(); } //--------------------------------------------------------- int MultimediaRenderer::getMultimediaMode() const { return m_imp->m_multimediaMode; } //--------------------------------------------------------- void MultimediaRenderer::setRenderSettings(const TRenderSettings &renderSettings) { //assert(m_imp->m_framesOnRendering.empty()); m_imp->m_renderSettings = renderSettings; } //--------------------------------------------------------- void MultimediaRenderer::setDpi(double xDpi, double yDpi) { m_imp->m_xDpi = xDpi; m_imp->m_yDpi = yDpi; } //--------------------------------------------------------- void MultimediaRenderer::addListener(Listener *listener) { m_imp->m_listeners.push_back(listener); } //--------------------------------------------------------- void MultimediaRenderer::addFrame(double frame) { m_imp->m_framesToRender.insert(frame); } //--------------------------------------------------------- void MultimediaRenderer::enablePrecomputing(bool on) { m_imp->m_precomputingEnabled = on; } //--------------------------------------------------------- //!\b NOTE: Such render may vary from time to time, and even be 0 if no renderer is currently //!active, for example due to preprocessing states. TRenderer *MultimediaRenderer::getTRenderer() { return m_imp->m_currentTRenderer; } //--------------------------------------------------------- bool MultimediaRenderer::isPrecomputingEnabled() const { return m_imp->m_precomputingEnabled; } //--------------------------------------------------------- void MultimediaRenderer::start() { m_imp->start(); } //--------------------------------------------------------- void MultimediaRenderer::onCanceled() { m_imp->m_canceled = true; } //--------------------------------------------------------- /* bool MultimediaRenderer::done() const { return true; } */