0.4.1.8 Beta - Date pending testing.

--User changes
 Thread image writing in EmberAnimate and when doing animation sequence in final render dialog.
 Add total time output for verbose mode in EmberAnimate to match EmberRender.

--Bug Fixes
 Fix incorrect iters ran/requested percentage in EmberAnimate to match EmberRender.
 Fix motion blur being disabled when doing animations in final render dialog.
 Allow for boolean command line options which default to true to be set to false.

--Code Changes
 Minor changes to enable a Mac build.
 Double the memory required for the final output buffer in RendererBase::MemoryRequired() when threading image writing.
 Reuse same buffer for RgbaToRgb() in EmberRender and EmberAnimate.
 Only resize in RgbaToRgb() if the two vectors are not the same.
 Add a final output buffer ping-ponging mechanism to facilitate threaded writes in controllers.
This commit is contained in:
mfeemster
2015-01-19 08:39:50 -08:00
parent 2999cd159f
commit 4059767dc4
20 changed files with 203 additions and 99 deletions

View File

@ -58,7 +58,7 @@
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;br/&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Fractorium 0.4.1.7 Beta&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&lt;br/&gt;A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL.&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Matt Feemster&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;br/&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Fractorium 0.4.1.8 Beta&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&lt;br/&gt;A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL.&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Matt Feemster&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>

View File

@ -152,7 +152,8 @@ FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderD
m_Run = true;
m_TotalTimer.Tic();//Begin timing for progress of all operations.
m_GuiState = m_FinalRenderDialog->State();//Cache render settings from the GUI before running.
m_FinalImageIndex = 0;
size_t i;
bool doAll = m_GuiState.m_DoAll && m_EmberFile.Size() > 1;
uint currentStripForProgress = 0;//Sort of a hack to get the strip value to the progress function.
@ -216,6 +217,7 @@ FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderD
//even when using double precision, which most cards at the time of this writing already exceed.
m_GuiState.m_Strips = 1;
m_Renderer->SetEmber(m_EmberFile.m_Embers);//Copy all embers to the local storage inside the renderer.
uint finalImageIndex = m_FinalImageIndex;
//Render each image, cancelling if m_Run ever gets set to false.
for (i = 0; i < m_EmberFile.Size() && m_Run; i++)
@ -224,35 +226,44 @@ FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderD
m_Renderer->Reset();//Have to manually set this since the ember is not set each time through.
m_RenderTimer.Tic();//Toc() is called in RenderComplete().
StripsRender<T>(m_Renderer.get(), m_EmberFile.m_Embers[i], m_FinalImage, i, 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.
//Can't use strips render here. Run() must be called directly for animation.
if (m_Renderer->Run(m_FinalImage[finalImageIndex], i) != RENDER_OK)
{
Output("Renderering failed.\n");
m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRenderDialog->ui.FinalRenderTextOutput, false);//Internally calls invoke.
},
[&](Ember<T>& finalEmber) { RenderComplete(finalEmber); });//Final strip.
}
else
{
if (m_WriteThread.joinable())
m_WriteThread.join();
SetProgressComplete(100);
m_Stats = m_Renderer->Stats();
m_FinalImageIndex = finalImageIndex;//Will be used inside of RenderComplete(). Set here when no threads are running.
//RenderComplete(m_EmberFile.m_Embers[i]);//Non-threaded version for testing.
m_WriteThread = std::thread([&] { RenderComplete(m_EmberFile.m_Embers[i]); });
}
finalImageIndex ^= 1;//Toggle the index.
}
if (m_WriteThread.joinable())
m_WriteThread.join();
}
else//Render all images, but not as an animation sequence (without temporal samples motion blur).
{
for (i = 0; i < m_EmberFile.Size() && m_Run; i++)
{
m_EmberFile.m_Embers[i].m_TemporalSamples = 1;//No temporal sampling.
}
//Render each image, cancelling if m_Run ever gets set to false.
for (i = 0; i < m_EmberFile.Size() && m_Run; i++)
{
Output("Image " + ToString(m_FinishedImageCount) + ":\n" + ComposePath(QString::fromStdString(m_EmberFile.m_Embers[i].m_Name)));
m_EmberFile.m_Embers[i].m_TemporalSamples = 1;//No temporal sampling.
m_Renderer->SetEmber(m_EmberFile.m_Embers[i]);
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_Renderer->PrepFinalAccumVector(m_FinalImage[m_FinalImageIndex]);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run().
m_Stats.Clear();
Memset(m_FinalImage);
Memset(m_FinalImage[m_FinalImageIndex]);
m_RenderTimer.Tic();//Toc() is called in RenderComplete().
StripsRender<T>(m_Renderer.get(), m_EmberFile.m_Embers[i], m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp,
StripsRender<T>(m_Renderer.get(), m_EmberFile.m_Embers[i], m_FinalImage[m_FinalImageIndex], 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.
@ -270,13 +281,13 @@ FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderD
ResetProgress();
m_Ember->m_TemporalSamples = 1;
m_Renderer->SetEmber(*m_Ember);
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_Renderer->PrepFinalAccumVector(m_FinalImage[m_FinalImageIndex]);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run().
m_Stats.Clear();
Memset(m_FinalImage);
Memset(m_FinalImage[m_FinalImageIndex]);
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,
StripsRender<T>(m_Renderer.get(), *m_Ember, m_FinalImage[m_FinalImageIndex], 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.
@ -287,6 +298,7 @@ FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderD
[&](Ember<T>& finalEmber) { RenderComplete(finalEmber); });//Final strip.
}
m_FinalImageIndex = 0;
QString totalTimeString = "All renders completed in: " + QString::fromStdString(m_TotalTimer.Format(m_TotalTimer.Toc())) + ".";
Output(totalTimeString);
@ -589,7 +601,7 @@ tuple<size_t, size_t, size_t> FinalRenderEmberController<T>::SyncAndComputeMemor
CancelPreviewRender();
m_FinalPreviewRenderFunc();
p = m_Renderer->MemoryRequired(strips, true);
p = m_Renderer->MemoryRequired(strips, true, m_FinalRenderDialog->DoSequence());
iterCount = m_Renderer->TotalIterCount(strips);
}
@ -677,11 +689,15 @@ void FinalRenderEmberController<T>::RenderComplete(Ember<T>& ember)
}
m_FinishedImageCount++;
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderIterationProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 100));//Just to be safe.
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderFilteringProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 100));
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderAccumProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 100));
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderTotalProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int((float(m_FinishedImageCount) / float(m_ImageCount)) * 100)));
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderImageCountLabel, "setText", Qt::QueuedConnection, Q_ARG(const QString&, ToString(m_FinishedImageCount) + " / " + ToString(m_ImageCount)));
//In a thread if animating, so don't set to complete because it'll be out of sync with the rest of the progress bars.
if (!m_GuiState.m_DoSequence)
{
SetProgressComplete(100);//Just to be safe.
}
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderTotalProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int((float(m_FinishedImageCount) / float(m_ImageCount)) * 100)));
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderImageCountLabel, "setText", Qt::QueuedConnection, Q_ARG(const QString&, ToString(m_FinishedImageCount) + " / " + ToString(m_ImageCount)));
status = "Pure render time: " + QString::fromStdString(renderTimeString);
Output(status);
@ -733,6 +749,19 @@ void FinalRenderEmberController<T>::SyncGuiToEmber(Ember<T>& ember, size_t width
ember.m_Supersample = m_FinalRenderDialog->m_SupersampleSpin->value();
}
/// <summary>
/// Set the iteration, density filter, and final accumulation progress bars to the same value.
/// Usually 0 or 100.
/// </summary>
/// <param name="val">The value to set them to</param>
template <typename T>
void FinalRenderEmberController<T>::SetProgressComplete(int val)
{
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderIterationProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, val));//Just to be safe.
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderFilteringProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, val));
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderAccumProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, val));
}
template class FinalRenderEmberController<float>;
#ifdef DO_DOUBLE

View File

@ -131,6 +131,7 @@ protected:
void CancelPreviewRender();
void RenderComplete(Ember<T>& ember);
void SyncGuiToEmber(Ember<T>& ember, size_t widthOverride = 0, size_t heightOverride = 0);
void SetProgressComplete(int val);
Ember<T>* m_Ember;
Ember<T> m_PreviewEmber;

Binary file not shown.

View File

@ -23,6 +23,7 @@ FractoriumEmberControllerBase::FractoriumEmberControllerBase(Fractorium* fractor
m_RenderType = CPU_RENDERER;
m_OutputTexID = 0;
m_SubBatchCount = 1;//Will be ovewritten by the options on first render.
m_FinalImageIndex = 0;
m_Fractorium = fractorium;
m_RenderTimer = nullptr;
m_RenderRestartTimer = nullptr;

View File

@ -41,7 +41,7 @@ public:
//Embers.
virtual void SetEmber(const Ember<float>& ember, bool verbatim = false) { }
virtual void CopyEmber(Ember<float>& ember, std::function<void(Ember<float>& ember)> perEmberOperation/* = [&](Ember<float>& ember) { }*/) { }
virtual void CopyEmber(Ember<float>& ember, std::function<void(Ember<float>& ember)> perEmberOperation/* = [&](Ember<float>& ember) { }*/) { }//Uncomment default lambdas once LLVM fixes a crash in their compiler with default lambda parameters.//TODO
virtual void SetEmberFile(const EmberFile<float>& emberFile) { }
virtual void CopyEmberFile(EmberFile<float>& emberFile, std::function<void(Ember<float>& ember)> perEmberOperation/* = [&](Ember<float>& ember) { }*/) { }
virtual void SetTempPalette(const Palette<float>& palette) { }
@ -202,7 +202,7 @@ public:
void DeleteRenderer();
void SaveCurrentRender(const QString& filename, bool forcePull);
RendererBase* Renderer() { return m_Renderer.get(); }
vector<byte>* FinalImage() { return &m_FinalImage; }
vector<byte>* FinalImage() { return &(m_FinalImage[m_FinalImageIndex]); }
vector<byte>* PreviewFinalImage() { return &m_PreviewFinalImage; }
protected:
@ -215,6 +215,7 @@ protected:
bool m_Rendering;
bool m_Shared;
bool m_LastEditWasUndoRedo;
uint m_FinalImageIndex;
uint m_Platform;
uint m_Device;
uint m_SubBatchCount;
@ -229,7 +230,8 @@ protected:
QString m_LastSaveAll;
QString m_LastSaveCurrent;
CriticalSection m_Cs;
vector<byte> m_FinalImage;
std::thread m_WriteThread;
vector<byte> m_FinalImage[2];
vector<byte> m_PreviewFinalImage;
vector<eProcessAction> m_ProcessActions;
unique_ptr<EmberNs::RendererBase> m_Renderer;

View File

@ -124,9 +124,9 @@ void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, b
FractoriumSettings* settings = m_Fractorium->m_Settings;
RendererCLBase* rendererCL = dynamic_cast<RendererCLBase*>(m_Renderer.get());
if (forcePull && rendererCL && m_Renderer->PrepFinalAccumVector(m_FinalImage))
if (forcePull && rendererCL && m_Renderer->PrepFinalAccumVector(m_FinalImage[m_FinalImageIndex]))
{
if (!rendererCL->ReadFinal(m_FinalImage.data()))
if (!rendererCL->ReadFinal(m_FinalImage[m_FinalImageIndex].data()))
{
m_Fractorium->ShowCritical("GPU Read Error", "Could not read image from the GPU, aborting image save.", true);
return;
@ -134,17 +134,17 @@ void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, b
}
//Ensure dimensions are valid.
if (m_FinalImage.size() < (width * height * m_Renderer->NumChannels() * m_Renderer->BytesPerChannel()))
if (m_FinalImage[m_FinalImageIndex].size() < (width * height * m_Renderer->NumChannels() * m_Renderer->BytesPerChannel()))
{
m_Fractorium->ShowCritical("Save Failed", "Dimensions didn't match, not saving.", true);
return;
}
data = m_FinalImage.data();//Png and channels == 4.
data = m_FinalImage[m_FinalImageIndex].data();//Png and channels == 4.
if ((suffix == "jpg" || suffix == "bmp") && m_Renderer->NumChannels() == 4)
{
RgbaToRgb(m_FinalImage, vecRgb, width, height);
RgbaToRgb(m_FinalImage[m_FinalImageIndex], vecRgb, width, height);
data = vecRgb.data();
}
@ -358,7 +358,7 @@ bool FractoriumEmberController<T>::Render()
if (ProcessState() != ACCUM_DONE)
{
//if (m_Renderer->Run(m_FinalImage, 0) == RENDER_OK)//Full, non-incremental render for debugging.
if (m_Renderer->Run(m_FinalImage, 0, m_SubBatchCount, iterBegin) == RENDER_OK)//Force output on iterBegin.
if (m_Renderer->Run(m_FinalImage[m_FinalImageIndex], 0, m_SubBatchCount, iterBegin) == RENDER_OK)//Force output on iterBegin.
{
//The amount to increment sub batch while rendering proceeds is purely empirical.
//Change later if better values can be derived/observed.
@ -433,7 +433,7 @@ bool FractoriumEmberController<T>::Render()
//Update it on finish because the rendering process is completely done.
if (iterBegin || ProcessState() == ACCUM_DONE)
{
if (m_FinalImage.size() == m_Renderer->FinalBufferSize())//Make absolutely sure the correct amount of data is passed.
if (m_FinalImage[m_FinalImageIndex].size() == m_Renderer->FinalBufferSize())//Make absolutely sure the correct amount of data is passed.
//gl->repaint();
gl->update();
@ -460,7 +460,7 @@ bool FractoriumEmberController<T>::Render()
m_Rendering = false;
StopRenderTimer(true);
m_Fractorium->m_RenderStatusLabel->setText("Rendering failed 3 or more times, stopping all rendering, see info tab. Try changing renderer types.");
Memset(m_FinalImage);
Memset(m_FinalImage[m_FinalImageIndex]);
if (rendererCL)
rendererCL->ClearFinal();
@ -642,6 +642,11 @@ bool Fractorium::CreateControllerFromOptions()
if (m_Controller.get())
{
m_Controller->CopyTempPalette(tempPalette);//Convert float to double or save double verbatim;
//Replace below with this once LLVM fixes a crash in their compiler with default lambda parameters.//TODO
//m_Controller->CopyEmber(ed);
//m_Controller->CopyEmberFile(efd);
#ifdef DO_DOUBLE
m_Controller->CopyEmber(ed, [&](Ember<double>& ember) { });
m_Controller->CopyEmberFile(efd, [&](Ember<double>& ember) { });