diff --git a/Data/examples/triptychaos_examples.flame b/Data/examples/triptychaos_examples.flame
index b8ecf1d..5f490af 100755
--- a/Data/examples/triptychaos_examples.flame
+++ b/Data/examples/triptychaos_examples.flame
@@ -48,7 +48,7 @@
-
+
diff --git a/Source/Fractorium/FinalRenderEmberController.cpp b/Source/Fractorium/FinalRenderEmberController.cpp
index a3407c3..52c455a 100644
--- a/Source/Fractorium/FinalRenderEmberController.cpp
+++ b/Source/Fractorium/FinalRenderEmberController.cpp
@@ -91,6 +91,124 @@ void FinalRenderEmberControllerBase::Output(const QString& s)
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(const QString&, s));
}
+
+///
+/// Render a single ember.
+///
+/// The ember to render
+/// Is this is a FULL_RENDER or if we should KEEP_ITERATING.
+/// Used to report progress when strips.
+/// True if rendering succeeded.
+template
+bool FinalRenderEmberController::RenderSingleEmber(Ember& ember, bool fullRender, size_t &stripForProgress)
+{
+ if (!m_Renderer.get()) {
+ return false;
+ }
+
+ ember.m_TemporalSamples = 1;//No temporal sampling.
+
+ m_Renderer->SetEmber(ember, fullRender ? eProcessAction::FULL_RENDER : eProcessAction::KEEP_ITERATING, /* updatePointer */ true);
+
+ m_Renderer->PrepFinalAccumVector(m_FinalImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run().
+ m_Stats.Clear();
+ m_RenderTimer.Tic();//Toc() is called in RenderComplete().
+
+ StripsRender(m_Renderer.get(), ember, m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp,
+ [&](size_t strip) { stripForProgress = strip; },//Pre strip.
+ [&](size_t strip) { m_Stats += m_Renderer->Stats(); },//Post strip.
+ [&](size_t strip)//Error.
+ {
+ Output("Rendering failed.\n");
+ m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRenderDialog->ui.FinalRenderTextOutput, false);//Internally calls invoke.
+ },
+ [&](Ember& finalEmber)
+ {
+ m_FinishedImageCount.fetch_add(1);
+ SaveCurrentRender(finalEmber);
+ RenderComplete(finalEmber);
+ HandleFinishedProgress();
+ });//Final strip.
+
+ return true;
+}
+
+///
+/// Render a single ember from a series of embers.
+/// m_Renderers.SetExternalEmbersPointer should already be set.
+///
+/// Used to coordinate which frame to render.
+/// which index into m_Renderers to use.
+/// True if rendering succeeded.
+template
+bool FinalRenderEmberController::RenderSingleEmberFromSeries(std::atomic* atomfTime, size_t index)
+{
+ if (m_Renderers.size() <= index) {
+ return false;
+ }
+
+ size_t ftime;
+ size_t finalImageIndex = 0;
+ std::thread writeThread;
+ vector finalImages[2];
+ EmberStats stats;
+ EmberImageComments comments;
+ Timing renderTimer;
+ const auto renderer = m_Renderers[index].get();
+
+ //Render each image, cancelling if m_Run ever gets set to false.
+ //The conditions of this loop use atomics to synchronize when running on multiple GPUs.
+ //The order is reversed from the usual loop: rather than compare and increment the counter,
+ //it's incremented, then compared. This is done to ensure the GPU on this thread "claims" this
+ //frame before working on it.
+ //The mechanism for incrementing is:
+ // Do an atomic add, which returns the previous value.
+ // Assign the result to the ftime counter.
+ // Do a < comparison to m_EmberFile.Size() and check m_Run.
+ while (((ftime = (atomfTime->fetch_add(1))) < m_EmberFile.Size()) && m_Run)//Needed to set 1 to claim this iter from other threads, so decrement it below to be zero-indexed here.
+ {
+ Output("Image " + ToString(ftime + 1ULL) + ":\n" + ComposePath(QString::fromStdString(m_EmberFile.Get(ftime)->m_Name)));
+ renderer->Reset();//Have to manually set this since the ember is not set each time through.
+ renderTimer.Tic();//Toc() is called in RenderComplete().
+
+ //Can't use strips render here. Run() must be called directly for animation.
+ if (renderer->Run(finalImages[finalImageIndex], T(ftime)) != eRenderStatus::RENDER_OK)
+ {
+ Output("Rendering failed.\n");
+ m_Fractorium->ErrorReportToQTextEdit(renderer->ErrorReport(), m_FinalRenderDialog->ui.FinalRenderTextOutput, false);//Internally calls invoke.
+ atomfTime->store(m_EmberFile.Size() + 1);//Abort all threads if any of them encounter an error.
+ m_Run = false;
+ break;
+ }
+ else
+ {
+ Join(writeThread);
+ stats = renderer->Stats();
+ comments = renderer->ImageComments(stats, 0, true);
+ writeThread = std::thread([&](size_t tempTime, size_t threadFinalImageIndex)
+ {
+ SaveCurrentRender(*m_EmberFile.Get(tempTime),
+ comments,//These all don't change during the renders, so it's ok to access them in the thread.
+ finalImages[threadFinalImageIndex],
+ renderer->FinalRasW(),
+ renderer->FinalRasH(),
+ m_FinalRenderDialog->Png16Bit(),
+ m_FinalRenderDialog->Transparency());
+ }, ftime, finalImageIndex);
+ m_FinishedImageCount.fetch_add(1);
+ RenderComplete(*m_EmberFile.Get(ftime), stats, renderTimer);
+
+ if (!index)//Only first device has a progress callback, so it also makes sense to only manually set the progress on the first device as well.
+ HandleFinishedProgress();
+ }
+
+ finalImageIndex ^= 1;//Toggle the index.
+ }
+
+ Join(writeThread);//One final check to make sure all writing is done before exiting this thread.
+ return m_Run;
+}
+
///
/// Constructor which accepts a pointer to the final render dialog and passes it to the base.
/// The main final rendering lambda function is constructed here.
@@ -145,8 +263,6 @@ FinalRenderEmberController::FinalRenderEmberController(FractoriumFinalRenderD
{
Ember* prev = nullptr;
vector> embers;
- vector threadVec;
- std::atomic atomfTime;
const auto firstEmber = m_EmberFile.m_Embers.begin();
//Need to loop through and set all w, h, q, ts, ss and t vals.
@@ -157,14 +273,13 @@ FinalRenderEmberController::FinalRenderEmberController(FractoriumFinalRenderD
SyncGuiToEmber(it, firstEmber->m_FinalRasW, firstEmber->m_FinalRasH);
- if (&it == &(*firstEmber))//First.
+ if (prev == nullptr)//First.
{
it.m_Time = 0;
}
- else//All others
+ else if (it.m_Time <= prev->m_Time)
{
- if (it.m_Time <= prev->m_Time)
- it.m_Time = prev->m_Time + 1;
+ it.m_Time = prev->m_Time + 1;
}
it.m_TemporalSamples = m_GuiState.m_TemporalSamples;
@@ -175,83 +290,17 @@ FinalRenderEmberController::FinalRenderEmberController(FractoriumFinalRenderD
//Shouldn't be a problem because animations will be at max 4k x 2k which will take about 1GB
//even when using double precision, which most cards at the time of this writing already exceed.
m_GuiState.m_Strips = 1;
- atomfTime.store(0);
CopyCont(embers, m_EmberFile.m_Embers);
- std::function iterFunc = [&](size_t index)
- {
- size_t ftime;
- size_t finalImageIndex = 0;
- std::thread writeThread;
- vector finalImages[2];
- EmberStats stats;
- EmberImageComments comments;
- Timing renderTimer;
- const auto renderer = m_Renderers[index].get();
- renderer->SetExternalEmbersPointer(&embers);//All will share a pointer to the original vector to conserve memory with large files. Ok because the vec doesn't get modified.
- //Render each image, cancelling if m_Run ever gets set to false.
- //Render each image, cancelling if m_Run ever gets set to false.
- //The conditions of this loop use atomics to synchronize when running on multiple GPUs.
- //The order is reversed from the usual loop: rather than compare and increment the counter,
- //it's incremented, then compared. This is done to ensure the GPU on this thread "claims" this
- //frame before working on it.
- //The mechanism for incrementing is:
- // Do an atomic add, which returns the previous value.
- // Add 1 to the return value to mimic what the new atomic value should be.
- // Assign the result to the ftime counter.
- // Do a <= comparison to m_EmberFile.Size() and check m_Run.
- // If true, enter the loop and immediately decrement the counter by 1 to make up for the fact
- // that it was first incremented before comparing.
- while (((ftime = (atomfTime.fetch_add(1) + 1)) <= m_EmberFile.Size()) && m_Run)//Needed to set 1 to claim this iter from other threads, so decrement it below to be zero-indexed here.
- {
- --ftime;
- Output("Image " + ToString(ftime + 1ULL) + ":\n" + ComposePath(QString::fromStdString(m_EmberFile.Get(ftime)->m_Name)));
- renderer->Reset();//Have to manually set this since the ember is not set each time through.
- renderTimer.Tic();//Toc() is called in RenderComplete().
-
- //Can't use strips render here. Run() must be called directly for animation.
- if (renderer->Run(finalImages[finalImageIndex], T(ftime)) != eRenderStatus::RENDER_OK)
- {
- Output("Rendering failed.\n");
- m_Fractorium->ErrorReportToQTextEdit(renderer->ErrorReport(), m_FinalRenderDialog->ui.FinalRenderTextOutput, false);//Internally calls invoke.
- atomfTime.store(m_EmberFile.Size() + 1);//Abort all threads if any of them encounter an error.
- break;
- }
- else
- {
- Join(writeThread);
- stats = renderer->Stats();
- comments = renderer->ImageComments(stats, 0, true);
- writeThread = std::thread([&](size_t tempTime, size_t threadFinalImageIndex)
- {
- SaveCurrentRender(*m_EmberFile.Get(tempTime),
- comments,//These all don't change during the renders, so it's ok to access them in the thread.
- finalImages[threadFinalImageIndex],
- renderer->FinalRasW(),
- renderer->FinalRasH(),
- m_FinalRenderDialog->Png16Bit(),
- m_FinalRenderDialog->Transparency());
- }, ftime, finalImageIndex);
- m_FinishedImageCount.fetch_add(1);
- RenderComplete(*m_EmberFile.Get(ftime), stats, renderTimer);
-
- if (!index)//Only first device has a progress callback, so it also makes sense to only manually set the progress on the first device as well.
- HandleFinishedProgress();
- }
-
- finalImageIndex ^= 1;//Toggle the index.
- }
-
- Join(writeThread);//One final check to make sure all writing is done before exiting this thread.
- };
+ std::atomic atomfTime(0);
+ vector threadVec;
threadVec.reserve(m_Renderers.size());
for (size_t r = 0; r < m_Renderers.size(); r++)
{
- threadVec.push_back(std::thread([&](size_t index)
- {
- iterFunc(index);
- }, r));
+ //All will share a pointer to the original vector to conserve memory with large files. Ok because the vec doesn't get modified.
+ m_Renderers[r]->SetExternalEmbersPointer(&embers);
+ threadVec.push_back(std::thread(&RenderSingleEmberFromSeries, this, &atomfTime, r));
}
Join(threadVec);
@@ -266,26 +315,7 @@ FinalRenderEmberController::FinalRenderEmberController(FractoriumFinalRenderD
break;
Output("Image " + ToString(m_FinishedImageCount.load() + 1) + ":\n" + ComposePath(QString::fromStdString(it.m_Name)));
- it.m_TemporalSamples = 1;//No temporal sampling.
- m_Renderer->SetEmber(it, eProcessAction::FULL_RENDER, true);
- m_Renderer->PrepFinalAccumVector(m_FinalImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run().
- m_Stats.Clear();
- m_RenderTimer.Tic();//Toc() is called in RenderComplete().
- StripsRender(m_Renderer.get(), it, m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp,
- [&](size_t strip) { currentStripForProgress = strip; },//Pre strip.
- [&](size_t strip) { m_Stats += m_Renderer->Stats(); },//Post strip.
- [&](size_t strip)//Error.
- {
- Output("Rendering failed.\n");
- m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRenderDialog->ui.FinalRenderTextOutput, false);//Internally calls invoke.
- },
- [&](Ember& finalEmber)
- {
- m_FinishedImageCount.fetch_add(1);
- SaveCurrentRender(finalEmber);
- RenderComplete(finalEmber);
- HandleFinishedProgress();
- });//Final strip.
+ RenderSingleEmber(it, /* fullRender */ true, currentStripForProgress);
}
}
else
@@ -295,29 +325,13 @@ FinalRenderEmberController::FinalRenderEmberController(FractoriumFinalRenderD
}
else if (m_Renderer.get())//Render a single image.
{
+ Output(ComposePath(QString::fromStdString(m_Ember->m_Name)));
+
m_ImageCount = 1;
m_Ember->m_TemporalSamples = 1;
m_Fractorium->m_Controller->ParamsToEmber(*m_Ember, true);//Update color and filter params from the main window controls, which only affect the filter and/or final accumulation stage.
- m_Renderer->SetEmber(*m_Ember, isBump ? eProcessAction::KEEP_ITERATING : eProcessAction::FULL_RENDER, true);
- m_Renderer->PrepFinalAccumVector(m_FinalImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run().
- m_Stats.Clear();
- Output(ComposePath(QString::fromStdString(m_Ember->m_Name)));
- m_RenderTimer.Tic();//Toc() is called in RenderComplete().
- StripsRender(m_Renderer.get(), *m_Ember, m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp,
- [&](size_t strip) { currentStripForProgress = strip; },//Pre strip.
- [&](size_t strip) { m_Stats += m_Renderer->Stats(); },//Post strip.
- [&](size_t strip)//Error.
- {
- Output("Rendering failed.\n");
- m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRenderDialog->ui.FinalRenderTextOutput, false);//Internally calls invoke.
- },
- [&](Ember& finalEmber)
- {
- m_FinishedImageCount.fetch_add(1);
- SaveCurrentRender(finalEmber);
- RenderComplete(finalEmber);
- HandleFinishedProgress();
- });//Final strip.
+
+ RenderSingleEmber(*m_Ember, /* fullRender= */ !isBump, currentStripForProgress);
}
else
{
diff --git a/Source/Fractorium/FinalRenderEmberController.h b/Source/Fractorium/FinalRenderEmberController.h
index e4cad12..0e85153 100644
--- a/Source/Fractorium/FinalRenderEmberController.h
+++ b/Source/Fractorium/FinalRenderEmberController.h
@@ -151,6 +151,8 @@ protected:
void SyncGuiToEmber(Ember& ember, size_t widthOverride = 0, size_t heightOverride = 0, bool dowidth = true, bool doheight = true);
bool SyncGuiToRenderer();
void SetProgressComplete(int val);
+ bool RenderSingleEmber(Ember& ember, bool fullRender, size_t &stripForProgress);
+ bool RenderSingleEmberFromSeries(std::atomic* atomfTime, size_t index);
Ember* m_Ember;
EmberFile m_EmberFile;
diff --git a/Source/Fractorium/FractoriumRender.cpp b/Source/Fractorium/FractoriumRender.cpp
index 4728257..b5218e3 100644
--- a/Source/Fractorium/FractoriumRender.cpp
+++ b/Source/Fractorium/FractoriumRender.cpp
@@ -431,7 +431,7 @@ bool FractoriumEmberController::Render()
{
const auto stats = m_Renderer->Stats();
auto iters = ToString(stats.m_Iters);
- auto scaledQuality = ToString(static_cast(m_Renderer->ScaledQuality()));
+ auto scaledQuality = ToString(static_cast(m_Renderer->ScaledQuality()));
auto renderTime = m_RenderElapsedTimer.Format(m_RenderElapsedTimer.Toc());
m_Fractorium->m_ProgressBar->setValue(100);