This commit is contained in:
Person 2022-08-18 10:29:20 -06:00
commit 64d4470b12
4 changed files with 135 additions and 119 deletions

View File

@ -48,7 +48,7 @@
<xform weight="1" color="0.083558" var_color="1" color_speed="0.00745693" symmetry="0.985086" name="Iterator_2" animate="1" pre_gaussian="0.001" linear="1" coefs="0.552956 -0.83321 0.83321 0.552956 -0.400538 -0.0645161" chaos="8.0664 2.29144 1.53027 9.07866 0 0 0.1 " opacity="1"/>
<xform weight="1" color="0.462617" var_color="1" color_speed="0.180397" symmetry="0.639206" name="Iterator_3" animate="1" pre_gaussian="0.1" linear="0.9" coefs="0.0393396 0.999226 -0.999226 0.0393396 -0.782258 0.489247" chaos="1.0274 9.05155 1.02313 1.57207 0 1 1 " opacity="1"/>
<xform weight="0.7" color="0.560664" var_color="1" color_speed="0.013697" symmetry="0.972606" name="Iterator_4" animate="1" linear="0.45" coefs="-0.172916 0.984937 -0.984937 -0.172916 0.876623 -0.655844" chaos="1.23314 6.36522 1.18045 1.0184 1 0 1 " opacity="1"/>
<xform weight="0.1" color="0.324206" var_color="1" color_speed="0.37535" symmetry="0.2493" name="Iterator_5" animate="1" gaussian_blur="0.01" post_hypertile1="1" post_hypertile1_p="3.05" post_hypertile1_q="7" coefs="1 0 6.12323e-17 1 0 0" post="0.808538 -0.588444 0.588444 0.808538 0.0833333 -0.0107527" chaos="1 1 1 1 1 0 1 " opacity="1"/>
<xform weight="0.1" color="0.324206" var_color="1" color_speed="0.37535" symmetry="0.2493" name="Iterator_5" animate="1" gaussian_blur="0.01" post_hypertile1="1" post_hypertile1_p="3" post_hypertile1_q="7" coefs="1 0 6.12323e-17 1 0 0" post="0.808538 -0.588444 0.588444 0.808538 0.0833333 -0.0107527" chaos="1 1 1 1 1 0 1 " opacity="1"/>
<xform weight="0.3" color="0.90592" var_color="1" color_speed="0.319324" symmetry="0.361352" name="Iterator_6" animate="1" noise="0.2" coefs="1 0 6.12323e-17 1 0 0" post="1 0 6.12323e-17 1 0.317073 0.280488" chaos="0 0 1 0 0 1 1 " opacity="0"/>
<xform weight="0.2" color="0.135651" var_color="1" color_speed="0.0443604" symmetry="0.911279" name="Iterator_7" animate="1" gaussian_blur="0.001" coefs="1 0 6.12323e-17 1 0 0" post="1 0 6.12323e-17 1 0.536585 0.298781" opacity="1"/>
<finalxform color="0" var_color="1" color_speed="0" symmetry="1" name="Camera" animate="0" Mobius="1" Mobius_Re_A="1" Mobius_Im_A="2.2" Mobius_Re_B="0.5" Mobius_Im_B="0" Mobius_Re_C="0" Mobius_Im_C="0" Mobius_Re_D="0.9" Mobius_Im_D="-0.4" coefs="1 0 6.12323e-17 1 0 0" opacity="1"/>

View File

@ -91,6 +91,124 @@ void FinalRenderEmberControllerBase::Output(const QString& s)
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(const QString&, s));
}
/// <summary>
/// Render a single ember.
/// </summary>
/// <param name="ember">The ember to render</param>
/// <param name="fullRender">Is this is a FULL_RENDER or if we should KEEP_ITERATING.</param>
/// <param name="stripForProgress">Used to report progress when strips.</param>
/// <returns>True if rendering succeeded.</returns>
template<typename T>
bool FinalRenderEmberController<T>::RenderSingleEmber(Ember<T>& 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<T>(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<T>& finalEmber)
{
m_FinishedImageCount.fetch_add(1);
SaveCurrentRender(finalEmber);
RenderComplete(finalEmber);
HandleFinishedProgress();
});//Final strip.
return true;
}
/// <summary>
/// Render a single ember from a series of embers.
/// m_Renderers.SetExternalEmbersPointer should already be set.
/// </summary>
/// <param name="atomfTime">Used to coordinate which frame to render.</param>
/// <param name="index">which index into m_Renderers to use.</param>
/// <returns>True if rendering succeeded.</returns>
template<typename T>
bool FinalRenderEmberController<T>::RenderSingleEmberFromSeries(std::atomic<size_t>* atomfTime, size_t index)
{
if (m_Renderers.size() <= index) {
return false;
}
size_t ftime;
size_t finalImageIndex = 0;
std::thread writeThread;
vector<v4F> 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;
}
/// <summary>
/// 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<T>::FinalRenderEmberController(FractoriumFinalRenderD
{
Ember<T>* prev = nullptr;
vector<Ember<T>> embers;
vector<std::thread> threadVec;
std::atomic<size_t> 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,13 +273,12 @@ FinalRenderEmberController<T>::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;
}
@ -175,83 +290,17 @@ FinalRenderEmberController<T>::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<void(size_t)> iterFunc = [&](size_t index)
{
size_t ftime;
size_t finalImageIndex = 0;
std::thread writeThread;
vector<v4F> 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<size_t> atomfTime(0);
vector<std::thread> 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<T>::FinalRenderEmberController(FractoriumFinalRenderD
break;
Output("Image " + ToString<qulonglong>(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<T>(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<T>& finalEmber)
{
m_FinishedImageCount.fetch_add(1);
SaveCurrentRender(finalEmber);
RenderComplete(finalEmber);
HandleFinishedProgress();
});//Final strip.
RenderSingleEmber(it, /* fullRender */ true, currentStripForProgress);
}
}
else
@ -295,29 +325,13 @@ FinalRenderEmberController<T>::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<T>(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<T>& finalEmber)
{
m_FinishedImageCount.fetch_add(1);
SaveCurrentRender(finalEmber);
RenderComplete(finalEmber);
HandleFinishedProgress();
});//Final strip.
RenderSingleEmber(*m_Ember, /* fullRender= */ !isBump, currentStripForProgress);
}
else
{

View File

@ -151,6 +151,8 @@ protected:
void SyncGuiToEmber(Ember<T>& ember, size_t widthOverride = 0, size_t heightOverride = 0, bool dowidth = true, bool doheight = true);
bool SyncGuiToRenderer();
void SetProgressComplete(int val);
bool RenderSingleEmber(Ember<T>& ember, bool fullRender, size_t &stripForProgress);
bool RenderSingleEmberFromSeries(std::atomic<size_t>* atomfTime, size_t index);
Ember<T>* m_Ember;
EmberFile<T> m_EmberFile;

View File

@ -431,7 +431,7 @@ bool FractoriumEmberController<T>::Render()
{
const auto stats = m_Renderer->Stats();
auto iters = ToString<qulonglong>(stats.m_Iters);
auto scaledQuality = ToString(static_cast<intmax_t>(m_Renderer->ScaledQuality()));
auto scaledQuality = ToString(static_cast<qulonglong>(m_Renderer->ScaledQuality()));
auto renderTime = m_RenderElapsedTimer.Format(m_RenderElapsedTimer.Toc());
m_Fractorium->m_ProgressBar->setValue(100);