Clarify the render process. . . (#311)
* Demystifying Rendering * More info
This commit is contained in:
parent
c0f52612f5
commit
c45605618b
69
doc/rendering_overview.md
Normal file
69
doc/rendering_overview.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Rendering in Tahoma2D
|
||||
|
||||
Understanding the rendering code can be challenging in Tahoma2D
|
||||
|
||||
When a render, fast rener, or preview command is given, it is handled in rendercommand.cpp
|
||||
|
||||
### In rendercommand.cpp:
|
||||
- The file destination is verified.
|
||||
- The output or preview settings are retrieved
|
||||
- The scene background color is adjusted if the exported file type does not support transparency.
|
||||
- The fx stack for each frame is retrieved, including adding an additional over fx to lay the frame over the background color.
|
||||
- The process is then handed off to a movierenderer or a multimediarenderer.
|
||||
|
||||
A movierenderer exports standard image sequences or video formats.
|
||||
(movierenderer.cpp)
|
||||
|
||||
A multimediarenderer exports each layer of a frame individually for compositing elsewhere later.
|
||||
(multimediarenderer.cpp)
|
||||
|
||||
## This document will focus on what happens with a movierenderer.
|
||||
|
||||
### In movierenderer.cpp:
|
||||
- A movierenderer class receives the settings from the rendercommand
|
||||
- This class receives the list of frames to be rendered with their fx stack
|
||||
- Passes this information on to an Imp class which subclasses TrenderPort
|
||||
- The Imp communicates with the renderer and handles post rendering tasks, such as sending rendered frames to the level writer, creating the soundtrack, and adding a clapper board.
|
||||
- The Imp delets old files if neeeded.
|
||||
- The Imp sets the output size based on the shrink setting from the output settings.
|
||||
- The Imp set up the Level Updater and Writer which will handled the frames when rendering is done.
|
||||
- Once all setup is done, the movierenderer calls startRendering.
|
||||
|
||||
### The process is then handed off to trenderer.cpp
|
||||
|
||||
The TRenderer assigns a render id and tells the TRendererStartInvoker to emit start render.
|
||||
|
||||
A TRendererStartInvoker is a middle class that emits a startRender message to itself(?) and tells the renderer to begin (startRendering()).
|
||||
|
||||
### In TRendererImp::startRendering():
|
||||
- The output area is setup
|
||||
- An Individual RenderTask is setup for each frame
|
||||
- The render tasks are launched
|
||||
|
||||
### RenderTask::run() is where the frame renders begin.
|
||||
- An empty raster tile is retrieved
|
||||
- The time is computed in compute()
|
||||
- Compute is handled by TRasterFx::compute() - see below
|
||||
- A notification that the frame is complete is sent
|
||||
|
||||
### in TRasterFx::compute()
|
||||
- The tile is adjusted to make sure that it is not at a fractional position
|
||||
- An alias string is built which contains the fx stack and parameters.
|
||||
- The tile and relevant info is then passed to a ResourceBuilder
|
||||
|
||||
### The ResourceBuilder:
|
||||
- runs build
|
||||
- checks to see if the resource already exists
|
||||
- if it doesn't compute() is called
|
||||
- Compute calls doCompute on the fx
|
||||
- This fx will call compute on any needed info it needs to do it's processing.
|
||||
- This can continue for as many fx need info/data.
|
||||
- Once all fx for a frame have been processed, a final tile should be created and passed back.
|
||||
|
||||
## The MovieRenderer::Imp will then get the final frames and tell the level writer to save them.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
#include "trop.h"
|
||||
#include "timagecache.h"
|
||||
#include "tstopwatch.h"
|
||||
#include "timage_io.h"
|
||||
|
||||
// TnzBase includes
|
||||
#include "trenderresourcemanager.h"
|
||||
|
@ -619,7 +620,7 @@ void TRenderer::addPort(TRenderPort *port) { m_imp->addPort(port); }
|
|||
void TRenderer::removePort(TRenderPort *port) { m_imp->removePort(port); }
|
||||
|
||||
//---------------------------------------------------------
|
||||
|
||||
// Looks like this only handles a single frame?
|
||||
unsigned long TRenderer::startRendering(double f, const TRenderSettings &info,
|
||||
const TFxPair &actualRoot) {
|
||||
assert(f >= 0);
|
||||
|
@ -987,8 +988,14 @@ void RenderTask::run() {
|
|||
if (!m_fieldRender && !m_stereoscopic) {
|
||||
// Common case - just build the first tile
|
||||
buildTile(m_tileA);
|
||||
/*-- 通常はここがFxのレンダリング処理 --*/
|
||||
// static int iCount = 0;
|
||||
// QString qPath("C:\\butta\\image_" +
|
||||
// QString::number(++iCount).rightJustified(3, '0') + ".tif");
|
||||
// TImageWriter::save(TFilePath(qPath.toStdWString()), m_tileA.getRaster());
|
||||
/*-- Normally this is the Fx rendering process --*/
|
||||
m_fx.m_frameA->compute(m_tileA, t, m_info);
|
||||
// The tile now has the image.
|
||||
// TImageWriter::save(TFilePath(qPath.toStdWString()), m_tileA.getRaster());
|
||||
} else {
|
||||
assert(!(m_stereoscopic && m_fieldRender));
|
||||
// Field rendering or stereoscopic case
|
||||
|
@ -1180,8 +1187,8 @@ void TRendererStartInvoker::doStartRender(TRendererImp *renderer,
|
|||
}
|
||||
|
||||
std::vector<const TFx *> calculateSortedFxs(TRasterFxP rootFx) {
|
||||
std::map<const TFx *, std::set<const TFx *>> E; /* 辺の情報 */
|
||||
std::set<const TFx *> Sources; /* 入次数0のノード群 */
|
||||
std::map<const TFx *, std::set<const TFx *>> E;
|
||||
std::set<const TFx *> Sources;
|
||||
|
||||
std::queue<const TFx *> Q;
|
||||
Q.push(rootFx.getPointer());
|
||||
|
@ -1195,8 +1202,8 @@ std::vector<const TFx *> calculateSortedFxs(TRasterFxP rootFx) {
|
|||
continue;
|
||||
}
|
||||
|
||||
/* 繋がっている入力ポートの先の Fx を訪問する
|
||||
入力ポートが無ければ終了 */
|
||||
/* Visit the Fx beyond the connected input port
|
||||
Exit if there is no input port */
|
||||
int portCount = vptr->getInputPortCount();
|
||||
if (portCount < 1) {
|
||||
Sources.insert(vptr);
|
||||
|
@ -1219,7 +1226,7 @@ std::vector<const TFx *> calculateSortedFxs(TRasterFxP rootFx) {
|
|||
}
|
||||
}
|
||||
|
||||
/* トポロジカルソート */
|
||||
/* Topological sort */
|
||||
std::set<const TFx *> visited;
|
||||
std::vector<const TFx *> L;
|
||||
std::function<void(const TFx *)> visit = [&visit, &visited, &E,
|
||||
|
@ -1398,10 +1405,10 @@ void TRendererImp::startRendering(
|
|||
// Build the frame's description alias
|
||||
const TRenderer::RenderData &renderData = *it;
|
||||
|
||||
/*--- カメラサイズ (LevelAutoやノイズで使用する) ---*/
|
||||
/*--- Camera size (used for Level Auto and noise) ---*/
|
||||
TRenderSettings rs = renderData.m_info;
|
||||
rs.m_cameraBox = camBox;
|
||||
/*--- 途中でPreview計算がキャンセルされたときのフラグ ---*/
|
||||
/*--- Flag when Preview calculation is canceled in the middle ---*/
|
||||
rs.m_isCanceled = &renderInfos->m_canceled;
|
||||
|
||||
TRasterFxP fx = renderData.m_fxRoot.m_frameA;
|
||||
|
|
|
@ -599,8 +599,8 @@ std::string TRasterFx::getAlias(double frame,
|
|||
std::string alias = getFxType();
|
||||
alias += "[";
|
||||
|
||||
// alias degli effetti connessi alle porte di input separati da virgole
|
||||
// una porta non connessa da luogo a un alias vuoto (stringa vuota)
|
||||
// effect aliases connected to the input ports separated by commas
|
||||
// an unconnected port gives rise to an empty alias (empty string)
|
||||
int i;
|
||||
for (i = 0; i < getInputPortCount(); i++) {
|
||||
TFxPort *port = getInputPort(i);
|
||||
|
@ -612,7 +612,7 @@ std::string TRasterFx::getAlias(double frame,
|
|||
alias += ",";
|
||||
}
|
||||
|
||||
// alias dei valori dei parametri dell'effetto al frame dato
|
||||
// alias the values of the effect parameters to the given frame
|
||||
for (i = 0; i < getParams()->getParamCount(); i++) {
|
||||
TParam *param = getParams()->getParam(i);
|
||||
alias += param->getName() + "=" + param->getValueAlias(frame, 3);
|
||||
|
@ -808,7 +808,7 @@ void TRasterFx::compute(TTile &tile, double frame,
|
|||
|
||||
TFxPort *port = getInputPort(0);
|
||||
|
||||
// la porta 0 non deve essere una porta di controllo
|
||||
// port 0 must not be a control port
|
||||
assert(port->isaControlPort() == false);
|
||||
|
||||
if (port->isConnected()) {
|
||||
|
@ -830,13 +830,13 @@ void TRasterFx::compute(TTile &tile, double frame,
|
|||
tfloor(fracInfoTranslation.y));
|
||||
TPointD newTilePos(intTilePos.x - intInfoTranslation.x,
|
||||
intTilePos.y - intInfoTranslation.y);
|
||||
/*-- 入力タイルの位置が、小数値を持っていた場合 --*/
|
||||
/*-- If the position of the input tile has a decimal value --*/
|
||||
if (tile.m_pos != newTilePos) {
|
||||
/*-- RenderSettingsのaffine行列に位置ずれを足しこむ --*/
|
||||
/*-- Add misalignment to the affine matrix of Render Settings --*/
|
||||
TRenderSettings newInfo(info);
|
||||
newInfo.m_affine.a13 = fracInfoTranslation.x - intInfoTranslation.x;
|
||||
newInfo.m_affine.a23 = fracInfoTranslation.y - intInfoTranslation.y;
|
||||
/*-- タイルの位置は整数値にする --*/
|
||||
/*-- Tile position should be an integer value --*/
|
||||
TPointD oldPos(tile.m_pos);
|
||||
tile.m_pos = newTilePos;
|
||||
|
||||
|
|
|
@ -195,24 +195,23 @@ RenderCommand renderCommand;
|
|||
bool RenderCommand::init(bool isPreview) {
|
||||
ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
|
||||
TSceneProperties *sprop = scene->getProperties();
|
||||
/*-- Preview/Renderに応じてそれぞれのSettingを取得 --*/
|
||||
|
||||
// Get the right output settings depending on if it's a preview or not.
|
||||
TOutputProperties &outputSettings = isPreview ? *sprop->getPreviewProperties()
|
||||
: *sprop->getOutputProperties();
|
||||
outputSettings.getRange(m_r0, m_r1, m_step);
|
||||
/*-- シーン全体のレンダリングの場合、m_r1をScene長に設定 --*/
|
||||
/*-- Use the whole scene if m_r1 < 0 --*/
|
||||
if (m_r0 == 0 && m_r1 == -1) {
|
||||
m_r0 = 0;
|
||||
m_r1 = scene->getFrameCount() - 1;
|
||||
}
|
||||
if (m_r0 < 0) m_r0 = 0;
|
||||
if (m_r1 >= scene->getFrameCount()) m_r1 = scene->getFrameCount() - 1;
|
||||
// nothing to render
|
||||
if (m_r1 < m_r0) {
|
||||
DVGui::warning(QObject::tr(
|
||||
"The command cannot be executed because the scene is empty."));
|
||||
return false;
|
||||
// throw TException("empty scene");
|
||||
// non so perche', ma termina il programma
|
||||
// nonostante il try all'inizio
|
||||
}
|
||||
|
||||
// Initialize the preview case
|
||||
|
@ -228,7 +227,7 @@ sprop->getOutputProperties()->setRenderSettings(rso);*/
|
|||
|
||||
if (isPreview) {
|
||||
/*--
|
||||
* PreviewではTimeStretchを考慮しないので、そのままフレーム値を格納してゆく
|
||||
* Since time stretch is not considered in Preview, the frame value is stored as it is
|
||||
* --*/
|
||||
m_numFrames = (int)(m_r1 - m_r0 + 1);
|
||||
m_r = m_r0;
|
||||
|
@ -250,16 +249,19 @@ sprop->getOutputProperties()->setRenderSettings(rso);*/
|
|||
return false;
|
||||
}
|
||||
|
||||
/*-- ファイル名が指定されていない場合は、シーン名を出力ファイル名にする --*/
|
||||
/*-- If no file name is specified, use the scene name as the output file name. --*/
|
||||
if (fp.getWideName() == L"")
|
||||
fp = fp.withName(scene->getScenePath().getName());
|
||||
/*-- ラスタ画像の場合、ファイル名にフレーム番号を追加 --*/
|
||||
/*-- For raster images, add the frame number to the filename --*/
|
||||
if (TFileType::getInfo(fp) == TFileType::RASTER_IMAGE ||
|
||||
fp.getType() == "pct" || fp.getType() == "pic" ||
|
||||
fp.getType() == "pict") // pct e' un formato"livello" (ha i settings di
|
||||
// quicktime) ma fatto di diversi frames
|
||||
// Tahoma2D no longer supports the pct, pic or pict file types.
|
||||
fp = fp.withFrame(TFrameId::EMPTY_FRAME);
|
||||
fp = scene->decodeFilePath(fp);
|
||||
|
||||
// make sure there is a destination to write to.
|
||||
if (!TFileStatus(fp.getParentDir()).doesExist()) {
|
||||
try {
|
||||
TFilePath parent = fp.getParentDir();
|
||||
|
@ -451,7 +453,7 @@ void RenderCommand::rasterRender(bool isPreview) {
|
|||
: scene->getProperties()->getOutputProperties();
|
||||
|
||||
// Build thread count
|
||||
/*-- Dedicated CPUs のコンボボックス (Single, Half, All) --*/
|
||||
/*-- Dedicated CPUs (Single, Half, All) --*/
|
||||
int index = prop->getThreadIndex();
|
||||
|
||||
const int procCount = TSystem::getProcessorCount();
|
||||
|
@ -459,7 +461,8 @@ void RenderCommand::rasterRender(bool isPreview) {
|
|||
|
||||
int threadCount = threadCounts[index];
|
||||
|
||||
/*-- MovieRendererを作る。Previewの場合はファイルパスは空 --*/
|
||||
/*-- MovieRenderer --*/
|
||||
// construct a renderer
|
||||
MovieRenderer movieRenderer(scene, isPreview ? TFilePath() : m_fp,
|
||||
threadCount, isPreview);
|
||||
|
||||
|
@ -475,9 +478,9 @@ void RenderCommand::rasterRender(bool isPreview) {
|
|||
|
||||
// Build
|
||||
|
||||
/*-- RenderSettingsをセット --*/
|
||||
/*-- RenderSettings --*/
|
||||
movieRenderer.setRenderSettings(rs);
|
||||
/*-- カメラDPIの取得、セット --*/
|
||||
/*-- get and set the dpi --*/
|
||||
TPointD cameraDpi = isPreview ? scene->getCurrentPreviewCamera()->getDpi()
|
||||
: scene->getCurrentCamera()->getDpi();
|
||||
movieRenderer.setDpi(cameraDpi.x, cameraDpi.y);
|
||||
|
@ -487,7 +490,7 @@ void RenderCommand::rasterRender(bool isPreview) {
|
|||
else
|
||||
movieRenderer.enablePrecomputing(true);
|
||||
|
||||
/*-- プログレス ダイアログの作成 --*/
|
||||
/*-- Creating a progress dialog --*/
|
||||
RenderListener *listener =
|
||||
new RenderListener(movieRenderer.getTRenderer(), m_fp,
|
||||
((m_numFrames - 1) / m_step) + 1, isPreview);
|
||||
|
@ -495,9 +498,7 @@ void RenderCommand::rasterRender(bool isPreview) {
|
|||
SLOT(onCanceled()));
|
||||
movieRenderer.addListener(listener);
|
||||
|
||||
bool fieldRendering = rs.m_fieldPrevalence != TRenderSettings::NoField;
|
||||
|
||||
/*-- buildSceneFxの進行状況を表示するプログレスバー --*/
|
||||
QProgressBar *buildSceneProgressBar =
|
||||
new QProgressBar(TApp::instance()->getMainWindow());
|
||||
buildSceneProgressBar->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
@ -510,10 +511,19 @@ void RenderCommand::rasterRender(bool isPreview) {
|
|||
QObject::tr("Building Schematic...", "RenderCommand"));
|
||||
buildSceneProgressBar->show();
|
||||
|
||||
|
||||
// Interlacing
|
||||
bool fieldRendering = rs.m_fieldPrevalence != TRenderSettings::NoField;
|
||||
|
||||
|
||||
for (int i = 0; i < m_numFrames; ++i, m_r += m_stepd) {
|
||||
buildSceneProgressBar->setValue(i);
|
||||
|
||||
// shift camera if stereoscopic output
|
||||
if (rs.m_stereoscopic) scene->shiftCameraX(-rs.m_stereoscopicShift / 2);
|
||||
|
||||
// get the fx for each frame
|
||||
// m_frameB is only used for stereoscopic or interlacing
|
||||
TFxPair fx;
|
||||
fx.m_frameA = buildSceneFx(scene, m_r, rs.m_shrinkX, isPreview);
|
||||
|
||||
|
@ -527,14 +537,14 @@ void RenderCommand::rasterRender(bool isPreview) {
|
|||
scene->shiftCameraX(-rs.m_stereoscopicShift / 2);
|
||||
} else
|
||||
fx.m_frameB = TRasterFxP();
|
||||
/*-- movieRendererにフレーム毎のFxを登録 --*/
|
||||
/*-- movieRenderer Register Fx for each frame --*/
|
||||
movieRenderer.addFrame(m_r, fx);
|
||||
}
|
||||
/*-- プログレスバーを閉じる --*/
|
||||
|
||||
buildSceneProgressBar->close();
|
||||
|
||||
// resetViewer(); //TODO cancella le immagini dell'eventuale render precedente
|
||||
// FileViewerPopupPool::instance()->getCurrent()->onClose();
|
||||
// resetViewer(); //TODO delete the images of any previous render
|
||||
//FileViewerPopupPool::instance()->getCurrent()->onClose();
|
||||
|
||||
movieRenderer.start();
|
||||
}
|
||||
|
@ -785,9 +795,12 @@ void RenderCommand::doRender(bool isPreview) {
|
|||
bool isWritable = true;
|
||||
bool isMultiFrame;
|
||||
/*--
|
||||
* 初期化処理。フレーム範囲の計算や、Renderの場合はOutputSettingsから保存先パスも作る
|
||||
* Initialization process. Calculate the frame range, and in the case of Render, also create the save destination path from Output Settings
|
||||
* --*/
|
||||
if (!init(isPreview)) return;
|
||||
|
||||
// file name stuff
|
||||
// and check ability to write.
|
||||
if (m_fp.getDots() == ".") {
|
||||
isMultiFrame = false;
|
||||
TFileStatus fs(m_fp);
|
||||
|
@ -802,11 +815,11 @@ void RenderCommand::doRender(bool isPreview) {
|
|||
QString exp(levelName + ".[0-9]{1,4}." + levelType);
|
||||
QRegExp regExp(exp);
|
||||
QStringList list = qDir.entryList(QDir::Files);
|
||||
QStringList livelFrames = list.filter(regExp);
|
||||
QStringList levelFrames = list.filter(regExp);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < livelFrames.size() && isWritable; i++) {
|
||||
TFilePath frame = dir + TFilePath(livelFrames[i].toStdWString());
|
||||
for (i = 0; i < levelFrames.size() && isWritable; i++) {
|
||||
TFilePath frame = dir + TFilePath(levelFrames[i].toStdWString());
|
||||
if (frame.isEmpty() || !frame.isAbsolute()) continue;
|
||||
TFileStatus fs(frame);
|
||||
isWritable = fs.isWritable();
|
||||
|
@ -824,16 +837,12 @@ void RenderCommand::doRender(bool isPreview) {
|
|||
ToonzScene *scene = 0;
|
||||
TCamera *camera = 0;
|
||||
|
||||
// send it to the appropriate renderer
|
||||
try {
|
||||
/*-- Xsheetノードに繋がっている各ラインごとに計算するモード。
|
||||
MultipleRender で Schematic Flows または Fx Schematic Terminal Nodes
|
||||
が選択されている場合
|
||||
--*/
|
||||
if (m_multimediaRender)
|
||||
multimediaRender();
|
||||
|
||||
else
|
||||
/*-- 通常のRendering --*/
|
||||
rasterRender(isPreview);
|
||||
} catch (TException &e) {
|
||||
DVGui::warning(QString::fromStdString(::to_string(e.getMessage())));
|
||||
|
|
|
@ -114,8 +114,8 @@ public:
|
|||
std::map<double, std::pair<TRasterP, TRasterP>> m_toBeSaved;
|
||||
std::vector<std::pair<double, TFxPair>> m_framesToBeRendered;
|
||||
std::string m_renderCacheId;
|
||||
/*--- 同じラスタのキャッシュを使いまわすとき、
|
||||
最初のものだけガンマをかけ、以降はそれを使いまわすようにする。
|
||||
/*--- When reusing the cache of the same raster
|
||||
Gamma is applied only to the first one, and it is reused after that.
|
||||
---*/
|
||||
std::map<double, bool> m_toBeAppliedGamma;
|
||||
|
||||
|
|
|
@ -578,7 +578,9 @@ FxBuilder::FxBuilder(ToonzScene *scene, TXsheet *xsh, double frame,
|
|||
//-------------------------------------------------------------------
|
||||
|
||||
TFxP FxBuilder::buildFx() {
|
||||
// start with the output fx
|
||||
TFx *outputFx = m_xsh->getFxDag()->getOutputFx(0);
|
||||
// if nothing is going into the output or there are no fx, bail.
|
||||
if (!outputFx || outputFx->getInputPortCount() != 1 ||
|
||||
outputFx->getInputPort(0)->getFx() == 0)
|
||||
return TFxP();
|
||||
|
@ -1140,6 +1142,7 @@ TFxP buildSceneFx(ToonzScene *scene, TXsheet *xsh, double row, int whichLevels,
|
|||
fx = TFxUtil::makeAffine(fx, aff);
|
||||
if (fx) fx->setName(L"CameraDPI and Shrink NAffineFx");
|
||||
|
||||
// this creates an over fx to lay the current frame over the background color.
|
||||
fx = TFxUtil::makeOver(
|
||||
TFxUtil::makeColorCard(scene->getProperties()->getBgColor()), fx);
|
||||
return fx;
|
||||
|
|
Loading…
Reference in a new issue