Clarify the render process. . . (#311)

* Demystifying Rendering

* More info
This commit is contained in:
Jeremy Bullock 2020-10-07 15:27:55 -06:00 committed by GitHub
parent c0f52612f5
commit c45605618b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 134 additions and 46 deletions

69
doc/rendering_overview.md Normal file
View 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.

View file

@ -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;

View file

@ -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;

View file

@ -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())));

View file

@ -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;

View file

@ -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;