// 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 <QEventLoop>
//----------------------------------------------------------------------------
// 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 final : 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<MultimediaRenderer::Listener *> m_listeners;
bool m_precomputingEnabled;
bool m_canceled;
int m_currentFx;
std::set<double>::iterator m_currentFrame;
TRenderer *m_currentTRenderer;
TFxSet m_fxsToRender;
std::set<double> 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) override;
bool onFrameFailed(int frame, TException &e) override;
void onSequenceCompleted(const TFilePath &fp) override;
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();
break;
case LAYERS:
scanSceneForLayers();
break;
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<TColumnFx *>(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<TColumnFx *>(currFx);
while (!colFx) {
if (fx->getInputPortCount() <= 0) break;
currFx = currFx->getInputPort(0)->getFx();
if (!currFx) break;
colFx = dynamic_cast<TColumnFx *>(currFx);
}
return colFx;
}
//---------------------------------------------------------
TFxP MultimediaRenderer::Imp::addPostProcessing(TFxP fx, TFxP postProc) {
if (dynamic_cast<TXsheetFx *>(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<TXsheetFx *>(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";
break;
case LAYERS:
modeStr = L"_lay";
break;
default:
assert(0);
}
// Build the post processing fxs tree
std::vector<TFxP> postFxs(m_framesToRender.size());
std::set<double>::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<std::pair<double, TFxPair>> 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<double, TFxPair>(*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")"));
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<double, TFxPair> ¤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;
}
*/