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