diff --git a/Builds/MSVC/Installer/FractoriumInstaller.wixproj b/Builds/MSVC/Installer/FractoriumInstaller.wixproj
index 76415f8..5def1e6 100644
--- a/Builds/MSVC/Installer/FractoriumInstaller.wixproj
+++ b/Builds/MSVC/Installer/FractoriumInstaller.wixproj
@@ -6,7 +6,7 @@
3.7
{c8096c47-e358-438c-a520-146d46b0637d}
2.0
- Fractorium_Beta_0.4.1.7
+ Fractorium_Beta_0.4.1.8
Package
$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets
$(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets
diff --git a/Builds/MSVC/Installer/Product.wxs b/Builds/MSVC/Installer/Product.wxs
index cab8bba..184aa10 100644
--- a/Builds/MSVC/Installer/Product.wxs
+++ b/Builds/MSVC/Installer/Product.wxs
@@ -1,6 +1,6 @@
-
+
@@ -13,7 +13,7 @@
-
+
::epsilon()//Apoplugin.h uses -20, but it's more mathematically correct to do it this way.
#define ISAAC_SIZE 4
diff --git a/Source/Ember/RendererBase.cpp b/Source/Ember/RendererBase.cpp
index f1f7b69..bd2241d 100644
--- a/Source/Ember/RendererBase.cpp
+++ b/Source/Ember/RendererBase.cpp
@@ -136,15 +136,21 @@ size_t RendererBase::HistMemoryRequired(size_t strips)
/// Return a pair whose first member contains the amount of memory needed for the histogram,
/// and whose second member contains the total the amount of memory needed to render the current ember.
/// Optionally include the memory needed for the final output image in pair.second.
+/// Note that the memory required for the final output image will be doubled if threaded writes
+/// are used because a copy of the final output is passed to a thread.
///
+/// The number of strips being used
/// If true include the memory needed for the final output image, else don't.
+/// Whether the caller will be writing the output in a thread, which doubles the memory required for the final output buffer.
/// The histogram memory required in first, and the total memory required in second
-pair RendererBase::MemoryRequired(size_t strips, bool includeFinal)
+pair RendererBase::MemoryRequired(size_t strips, bool includeFinal, bool threadedWrite)
{
pair p;
+ size_t outSize = includeFinal ? FinalBufferSize() : 0;
+ outSize *= (threadedWrite ? 2 : 1);
p.first = HistMemoryRequired(strips);
- p.second = (p.first * 2) + (includeFinal ? FinalBufferSize() : 0);//Multiply hist by 2 to account for the density filtering buffer which is the same size as the histogram.
+ p.second = (p.first * 2) + outSize;//Multiply hist by 2 to account for the density filtering buffer which is the same size as the histogram.
return p;
}
diff --git a/Source/Ember/RendererBase.h b/Source/Ember/RendererBase.h
index d9afe76..66f06b4 100644
--- a/Source/Ember/RendererBase.h
+++ b/Source/Ember/RendererBase.h
@@ -99,7 +99,7 @@ public:
//Non-virtual processing functions.
void ChangeVal(std::function func, eProcessAction action);
size_t HistMemoryRequired(size_t strips);
- pair MemoryRequired(size_t strips, bool includeFinal);
+ pair MemoryRequired(size_t strips, bool includeFinal, bool threadedWrite);
vector> RandVec();
bool PrepFinalAccumVector(vector& pixels);
diff --git a/Source/EmberAnimate/EmberAnimate.cpp b/Source/EmberAnimate/EmberAnimate.cpp
index d3d5632..87848fc 100644
--- a/Source/EmberAnimate/EmberAnimate.cpp
+++ b/Source/EmberAnimate/EmberAnimate.cpp
@@ -28,15 +28,13 @@ bool EmberAnimate(EmberOptions& opt)
//Regular variables.
Timing t;
bool unsorted = false;
- bool writeSuccess = false;
bool startXml = false;
bool finishXml = false;
bool appendXml = false;
- byte* finalImagep;
+ uint finalImageIndex = 0;
uint i, channels, ftime;
string s, flameName, filename, inputPath = GetPath(opt.Input());
ostringstream os;
- vector finalImage, vecRgb;
vector> embers;
EmberStats stats;
EmberReport emberReport;
@@ -44,6 +42,8 @@ bool EmberAnimate(EmberOptions& opt)
Ember centerEmber;
XmlToEmber parser;
EmberToXml emberToXml;
+ vector finalImages[2];
+ std::thread writeThread;
unique_ptr> progress(new RenderProgress());
unique_ptr> renderer(CreateRenderer(opt.EmberCL() ? OPENCL_RENDERER : CPU_RENDERER, opt.Platform(), opt.Device(), false, 0, emberReport));
vector errorReport = emberReport.ErrorReport();
@@ -259,6 +259,27 @@ bool EmberAnimate(EmberOptions& opt)
renderer->BytesPerChannel(opt.BitsPerChannel() / 8);
renderer->Callback(opt.DoProgress() ? progress.get() : nullptr);
+ std::function saveFunc = [&](uint threadVecIndex)
+ {
+ bool writeSuccess = false;
+ byte* finalImagep = finalImages[threadVecIndex].data();
+
+ if ((opt.Format() == "jpg" || opt.Format() == "bmp") && renderer->NumChannels() == 4)
+ RgbaToRgb(finalImages[threadVecIndex], finalImages[threadVecIndex], renderer->FinalRasW(), renderer->FinalRasH());
+
+ if (opt.Format() == "png")
+ writeSuccess = WritePng(filename.c_str(), finalImagep, renderer->FinalRasW(), renderer->FinalRasH(), opt.BitsPerChannel() / 8, opt.PngComments(), comments, opt.Id(), opt.Url(), opt.Nick());
+ else if (opt.Format() == "jpg")
+ writeSuccess = WriteJpeg(filename.c_str(), finalImagep, renderer->FinalRasW(), renderer->FinalRasH(), opt.JpegQuality(), opt.JpegComments(), comments, opt.Id(), opt.Url(), opt.Nick());
+ else if (opt.Format() == "ppm")
+ writeSuccess = WritePpm(filename.c_str(), finalImagep, renderer->FinalRasW(), renderer->FinalRasH());
+ else if (opt.Format() == "bmp")
+ writeSuccess = WriteBmp(filename.c_str(), finalImagep, renderer->FinalRasW(), renderer->FinalRasH());
+
+ if (!writeSuccess)
+ cout << "Error writing " << filename << endl;/**/
+ };
+
//Begin run.
for (ftime = opt.FirstFrame(); ftime <= opt.LastFrame(); ftime += opt.Dtime())
{
@@ -269,7 +290,7 @@ bool EmberAnimate(EmberOptions& opt)
renderer->Reset();
- if ((renderer->Run(finalImage, localTime) != RENDER_OK) || renderer->Aborted() || finalImage.empty())
+ if ((renderer->Run(finalImages[finalImageIndex], localTime) != RENDER_OK) || renderer->Aborted() || finalImages[finalImageIndex].empty())
{
cout << "Error: image rendering failed, skipping to next image." << endl;
renderer->DumpErrorReport();//Something went wrong, print errors.
@@ -298,12 +319,11 @@ bool EmberAnimate(EmberOptions& opt)
emberToXml.Save(flameName, centerEmber, opt.PrintEditDepth(), true, opt.IntPalette(), opt.HexPalette(), true, startXml, finishXml);
}
- writeSuccess = false;
stats = renderer->Stats();
comments = renderer->ImageComments(stats, opt.PrintEditDepth(), opt.IntPalette(), opt.HexPalette());
os.str("");
size_t iterCount = renderer->TotalIterCount(1);
- os << comments.m_NumIters << " / " << iterCount << " (" << std::fixed << std::setprecision(2) << double(stats.m_Iters) / double(iterCount * 100) << "%)";
+ os << comments.m_NumIters << " / " << iterCount << " (" << std::fixed << std::setprecision(2) << ((double(stats.m_Iters) / double(iterCount)) * 100) << "%)";
VerbosePrint("\nIters ran/requested: " + os.str());
VerbosePrint("Bad values: " << stats.m_Badvals);
@@ -312,33 +332,30 @@ bool EmberAnimate(EmberOptions& opt)
VerbosePrint("Iters/sec: " << size_t(stats.m_Iters / (stats.m_IterMs / 1000.0)) << endl);
VerbosePrint("Writing " + filename);
- if ((opt.Format() == "jpg" || opt.Format() == "bmp") && renderer->NumChannels() == 4)
- {
- RgbaToRgb(finalImage, vecRgb, renderer->FinalRasW(), renderer->FinalRasH());
+ //Run image writing in a thread. Although doing it this way duplicates the final output memory, it saves a lot of time
+ //when running with OpenCL. Call join() to ensure the previous thread call has completed.
+ if (writeThread.joinable())
+ writeThread.join();
- finalImagep = vecRgb.data();
- }
+ uint threadVecIndex = finalImageIndex;//Cache before launching thread.
+
+ if (opt.ThreadedWrite())
+ writeThread = std::thread(saveFunc, threadVecIndex);
else
- {
- finalImagep = finalImage.data();
- }
-
- if (opt.Format() == "png")
- writeSuccess = WritePng(filename.c_str(), finalImagep, renderer->FinalRasW(), renderer->FinalRasH(), opt.BitsPerChannel() / 8, opt.PngComments(), comments, opt.Id(), opt.Url(), opt.Nick());
- else if (opt.Format() == "jpg")
- writeSuccess = WriteJpeg(filename.c_str(), finalImagep, renderer->FinalRasW(), renderer->FinalRasH(), opt.JpegQuality(), opt.JpegComments(), comments, opt.Id(), opt.Url(), opt.Nick());
- else if (opt.Format() == "ppm")
- writeSuccess = WritePpm(filename.c_str(), finalImagep, renderer->FinalRasW(), renderer->FinalRasH());
- else if (opt.Format() == "bmp")
- writeSuccess = WriteBmp(filename.c_str(), finalImagep, renderer->FinalRasW(), renderer->FinalRasH());
-
- if (!writeSuccess)
- cout << "Error writing " << filename << endl;
+ saveFunc(threadVecIndex);
centerEmber.Clear();
+ finalImageIndex ^= 1;//Toggle the index.
}
+ if (writeThread.joinable())
+ writeThread.join();
+
VerbosePrint("Done.\n");
+
+ if (opt.Verbose())
+ t.Toc("\nTotal time: ", true);
+
return true;
}
diff --git a/Source/EmberAnimate/EmberAnimate.rc b/Source/EmberAnimate/EmberAnimate.rc
index 5b8636c..12b7e37 100644
--- a/Source/EmberAnimate/EmberAnimate.rc
+++ b/Source/EmberAnimate/EmberAnimate.rc
@@ -49,8 +49,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 0,4,1,7
- PRODUCTVERSION 0,4,1,7
+ FILEVERSION 0,4,1,8
+ PRODUCTVERSION 0,4,1,8
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -67,12 +67,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "Open Source"
VALUE "FileDescription", "Renders fractal flames as animations with motion blur"
- VALUE "FileVersion", "0.4.1.7"
+ VALUE "FileVersion", "0.4.1.8"
VALUE "InternalName", "EmberAnimate.rc"
VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2013, GPL v3"
VALUE "OriginalFilename", "EmberAnimate.rc"
VALUE "ProductName", "Ember Animate"
- VALUE "ProductVersion", "0.4.1.7"
+ VALUE "ProductVersion", "0.4.1.8"
END
END
BLOCK "VarFileInfo"
diff --git a/Source/EmberCommon/EmberCommon.h b/Source/EmberCommon/EmberCommon.h
index a21f029..9f63626 100644
--- a/Source/EmberCommon/EmberCommon.h
+++ b/Source/EmberCommon/EmberCommon.h
@@ -124,6 +124,7 @@ static bool InitPaletteList(const string& filename)
///
/// Convert an RGBA buffer to an RGB buffer.
+/// The two buffers can point to the same memory location if needed.
///
/// The RGBA buffer
/// The RGB buffer
@@ -131,7 +132,8 @@ static bool InitPaletteList(const string& filename)
/// The height of the image in pixels
static void RgbaToRgb(vector& rgba, vector& rgb, size_t width, size_t height)
{
- rgb.resize(width * height * 3);
+ if (rgba.data() != rgb.data())//Only resize the destination buffer if they are different.
+ rgb.resize(width * height * 3);
for (uint i = 0, j = 0; i < (width * height * 4); i += 4, j += 3)
{
diff --git a/Source/EmberCommon/EmberOptions.h b/Source/EmberCommon/EmberOptions.h
index 9b17f92..a63c742 100644
--- a/Source/EmberCommon/EmberOptions.h
+++ b/Source/EmberCommon/EmberOptions.h
@@ -48,6 +48,7 @@ enum eOptionIDs
OPT_JPEG_COMMENTS,
OPT_PNG_COMMENTS,
OPT_WRITE_GENOME,
+ OPT_THREADED_WRITE,
OPT_ENCLOSED,
OPT_NO_EDITS,
OPT_UNSMOOTH_EDGE,
@@ -176,11 +177,29 @@ public:
*this = entry;
}
+ ///
+ /// Default assignment operator.
+ ///
+ /// The EmberOptionEntry object to copy
+ EmberOptionEntry& operator = (const EmberOptionEntry& entry)
+ {
+ if (this != &entry)
+ {
+ m_OptionUse = entry.m_OptionUse;
+ m_Option = entry.m_Option;
+ m_DocString = entry.m_DocString;
+ m_NameWithoutDashes = entry.m_NameWithoutDashes;
+ m_Val = entry.m_Val;
+ }
+
+ return *this;
+ }
+
///
/// Functor accessors.
///
inline T operator() (void) { return m_Val; }
- inline void operator() (T t) { m_Val = t; }
+ inline void operator() (T t) { m_Val = t; }
private:
eOptionUse m_OptionUse;
@@ -202,7 +221,16 @@ private:
#define PARSEBOOLOPTION(opt, member) \
case (opt): \
- member(true); \
+ { \
+ if (member.m_Option.nArgType == SO_OPT) \
+ { \
+ member(!strcmp(args.OptionArg(), "true")); \
+ } \
+ else \
+ { \
+ member(true); \
+ } \
+ } \
break
//Int.
@@ -284,12 +312,13 @@ public:
INITBOOLOPTION(Transparency, Eob(OPT_USE_ALL, OPT_TRANSPARENCY, _T("--transparency"), false, SO_NONE, "\t--transparency Include alpha channel in final output [default: false except for PNG].\n"));
INITBOOLOPTION(NameEnable, Eob(OPT_USE_RENDER, OPT_NAME_ENABLE, _T("--name_enable"), false, SO_NONE, "\t--name_enable Use the name attribute contained in the xml as the output filename [default: false].\n"));
INITBOOLOPTION(IntPalette, Eob(OPT_RENDER_ANIM, OPT_INT_PALETTE, _T("--intpalette"), false, SO_NONE, "\t--intpalette Force palette RGB values to be integers [default: false (float)].\n"));
- INITBOOLOPTION(HexPalette, Eob(OPT_USE_ALL, OPT_HEX_PALETTE, _T("--hex_palette"), true, SO_NONE, "\t--hex_palette Force palette RGB values to be hex [default: true].\n"));
+ INITBOOLOPTION(HexPalette, Eob(OPT_USE_ALL, OPT_HEX_PALETTE, _T("--hex_palette"), true, SO_OPT, "\t--hex_palette Force palette RGB values to be hex [default: true].\n"));
INITBOOLOPTION(InsertPalette, Eob(OPT_RENDER_ANIM, OPT_INSERT_PALETTE, _T("--insert_palette"), false, SO_NONE, "\t--insert_palette Insert the palette into the image for debugging purposes [default: false].\n"));
- INITBOOLOPTION(JpegComments, Eob(OPT_RENDER_ANIM, OPT_JPEG_COMMENTS, _T("--enable_jpeg_comments"), true, SO_NONE, "\t--enable_jpeg_comments Enables comments in the jpeg header [default: true].\n"));
- INITBOOLOPTION(PngComments, Eob(OPT_RENDER_ANIM, OPT_PNG_COMMENTS, _T("--enable_png_comments"), true, SO_NONE, "\t--enable_png_comments Enables comments in the png header [default: true].\n"));
+ INITBOOLOPTION(JpegComments, Eob(OPT_RENDER_ANIM, OPT_JPEG_COMMENTS, _T("--enable_jpeg_comments"), true, SO_OPT, "\t--enable_jpeg_comments Enables comments in the jpeg header [default: true].\n"));
+ INITBOOLOPTION(PngComments, Eob(OPT_RENDER_ANIM, OPT_PNG_COMMENTS, _T("--enable_png_comments"), true, SO_OPT, "\t--enable_png_comments Enables comments in the png header [default: true].\n"));
INITBOOLOPTION(WriteGenome, Eob(OPT_USE_ANIMATE, OPT_WRITE_GENOME, _T("--write_genome"), false, SO_NONE, "\t--write_genome Write out flame associated with center of motion blur window [default: false].\n"));
- INITBOOLOPTION(Enclosed, Eob(OPT_USE_GENOME, OPT_ENCLOSED, _T("--enclosed"), true, SO_NONE, "\t--enclosed Use enclosing XML tags [default: false].\n"));
+ INITBOOLOPTION(ThreadedWrite, Eob(OPT_RENDER_ANIM, OPT_THREADED_WRITE, _T("--threaded_write"), true, SO_OPT, "\t--threaded_write Use a separate thread to write images to disk. This doubles the memory required for the final output buffer. [default: true].\n"));
+ INITBOOLOPTION(Enclosed, Eob(OPT_USE_GENOME, OPT_ENCLOSED, _T("--enclosed"), true, SO_OPT, "\t--enclosed Use enclosing XML tags [default: true].\n"));
INITBOOLOPTION(NoEdits, Eob(OPT_USE_GENOME, OPT_NO_EDITS, _T("--noedits"), false, SO_NONE, "\t--noedits Exclude edit tags when writing Xml [default: false].\n"));
INITBOOLOPTION(UnsmoothEdge, Eob(OPT_USE_GENOME, OPT_UNSMOOTH_EDGE, _T("--unsmoother"), false, SO_NONE, "\t--unsmoother Do not use smooth blending for sheep edges [default: false].\n"));
INITBOOLOPTION(LockAccum, Eob(OPT_USE_ALL, OPT_LOCK_ACCUM, _T("--lock_accum"), false, SO_NONE, "\t--lock_accum Lock threads when accumulating to the histogram using the CPU. This will drop performance to that of single threading [default: false].\n"));
@@ -422,6 +451,7 @@ public:
PARSEBOOLOPTION(OPT_JPEG_COMMENTS, JpegComments);
PARSEBOOLOPTION(OPT_PNG_COMMENTS, PngComments);
PARSEBOOLOPTION(OPT_WRITE_GENOME, WriteGenome);
+ PARSEBOOLOPTION(OPT_THREADED_WRITE, ThreadedWrite);
PARSEBOOLOPTION(OPT_ENCLOSED, Enclosed);
PARSEBOOLOPTION(OPT_NO_EDITS, NoEdits);
PARSEBOOLOPTION(OPT_UNSMOOTH_EDGE, UnsmoothEdge);
@@ -636,6 +666,7 @@ public:
EmberOptionEntry JpegComments;
EmberOptionEntry PngComments;
EmberOptionEntry WriteGenome;
+ EmberOptionEntry ThreadedWrite;
EmberOptionEntry Enclosed;
EmberOptionEntry NoEdits;
EmberOptionEntry UnsmoothEdge;
diff --git a/Source/EmberGenome/EmberGenome.rc b/Source/EmberGenome/EmberGenome.rc
index 7f3b27f..1d67f0f 100644
--- a/Source/EmberGenome/EmberGenome.rc
+++ b/Source/EmberGenome/EmberGenome.rc
@@ -49,8 +49,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 0,4,1,7
- PRODUCTVERSION 0,4,1,7
+ FILEVERSION 0,4,1,8
+ PRODUCTVERSION 0,4,1,8
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -67,12 +67,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "Open Source"
VALUE "FileDescription", "Manipulates fractal flames parameter files"
- VALUE "FileVersion", "0.4.1.7"
+ VALUE "FileVersion", "0.4.1.8"
VALUE "InternalName", "EmberGenome.rc"
VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2013, GPL v3"
VALUE "OriginalFilename", "EmberGenome.rc"
VALUE "ProductName", "Ember Genome"
- VALUE "ProductVersion", "0.4.1.7"
+ VALUE "ProductVersion", "0.4.1.8"
END
END
BLOCK "VarFileInfo"
diff --git a/Source/EmberRender/EmberRender.cpp b/Source/EmberRender/EmberRender.cpp
index 3d7f2bc..a04340d 100644
--- a/Source/EmberRender/EmberRender.cpp
+++ b/Source/EmberRender/EmberRender.cpp
@@ -36,7 +36,7 @@ bool EmberRender(EmberOptions& opt)
ostringstream os;
pair p;
vector> embers;
- vector finalImage, vecRgb;
+ vector finalImage;
EmberStats stats;
EmberReport emberReport;
EmberImageComments comments;
@@ -202,7 +202,7 @@ bool EmberRender(EmberOptions& opt)
}
else
{
- p = renderer->MemoryRequired(1, true);
+ p = renderer->MemoryRequired(1, true, false);//No threaded write for render, only for animate.
strips = CalcStrips(double(p.second), double(renderer->MemoryAvailable()), opt.UseMem());
if (strips > 1)
@@ -280,16 +280,9 @@ bool EmberRender(EmberOptions& opt)
VerbosePrint("Writing " + filename);
if ((opt.Format() == "jpg" || opt.Format() == "bmp") && renderer->NumChannels() == 4)
- {
- RgbaToRgb(finalImage, vecRgb, finalEmber.m_FinalRasW, finalEmber.m_FinalRasH);
-
- finalImagep = vecRgb.data();
- }
- else
- {
- finalImagep = finalImage.data();
- }
+ RgbaToRgb(finalImage, finalImage, renderer->FinalRasW(), renderer->FinalRasH());
+ finalImagep = finalImage.data();
writeSuccess = false;
if (opt.Format() == "png")
diff --git a/Source/EmberRender/EmberRender.rc b/Source/EmberRender/EmberRender.rc
index 954e4a4..d9adf05 100644
--- a/Source/EmberRender/EmberRender.rc
+++ b/Source/EmberRender/EmberRender.rc
@@ -49,8 +49,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 0,4,1,7
- PRODUCTVERSION 0,4,1,7
+ FILEVERSION 0,4,1,8
+ PRODUCTVERSION 0,4,1,8
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -67,12 +67,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "Open Source"
VALUE "FileDescription", "Renders fractal flames as single images"
- VALUE "FileVersion", "0.4.1.7"
+ VALUE "FileVersion", "0.4.1.8"
VALUE "InternalName", "EmberRender.rc"
VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2013, GPL v3"
VALUE "OriginalFilename", "EmberRender.rc"
VALUE "ProductName", "Ember Render"
- VALUE "ProductVersion", "0.4.1.7"
+ VALUE "ProductVersion", "0.4.1.8"
END
END
BLOCK "VarFileInfo"
diff --git a/Source/Fractorium/AboutDialog.ui b/Source/Fractorium/AboutDialog.ui
index 4b10e3e..1850f40 100644
--- a/Source/Fractorium/AboutDialog.ui
+++ b/Source/Fractorium/AboutDialog.ui
@@ -58,7 +58,7 @@
- <html><head/><body><p align="center"><br/><span style=" font-size:12pt;">Fractorium 0.4.1.7 Beta</span></p><p align="center"><span style=" font-size:10pt;"><br/>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.</span></p><p align="center"><span style=" font-size:10pt;">Matt Feemster</span></p></body></html>
+ <html><head/><body><p align="center"><br/><span style=" font-size:12pt;">Fractorium 0.4.1.8 Beta</span></p><p align="center"><span style=" font-size:10pt;"><br/>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.</span></p><p align="center"><span style=" font-size:10pt;">Matt Feemster</span></p></body></html>
Qt::RichText
diff --git a/Source/Fractorium/FinalRenderEmberController.cpp b/Source/Fractorium/FinalRenderEmberController.cpp
index 85800c8..a28847d 100644
--- a/Source/Fractorium/FinalRenderEmberController.cpp
+++ b/Source/Fractorium/FinalRenderEmberController.cpp
@@ -152,7 +152,8 @@ FinalRenderEmberController::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::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::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(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& 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(m_Renderer.get(), m_EmberFile.m_Embers[i], m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp,
+ StripsRender(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::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(m_Renderer.get(), *m_Ember, m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp,
+ StripsRender(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::FinalRenderEmberController(FractoriumFinalRenderD
[&](Ember& 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 FinalRenderEmberController::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::RenderComplete(Ember& 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::SyncGuiToEmber(Ember& ember, size_t width
ember.m_Supersample = m_FinalRenderDialog->m_SupersampleSpin->value();
}
+///
+/// Set the iteration, density filter, and final accumulation progress bars to the same value.
+/// Usually 0 or 100.
+///
+/// The value to set them to
+template
+void FinalRenderEmberController::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;
#ifdef DO_DOUBLE
diff --git a/Source/Fractorium/FinalRenderEmberController.h b/Source/Fractorium/FinalRenderEmberController.h
index b411c05..6d989b4 100644
--- a/Source/Fractorium/FinalRenderEmberController.h
+++ b/Source/Fractorium/FinalRenderEmberController.h
@@ -131,6 +131,7 @@ protected:
void CancelPreviewRender();
void RenderComplete(Ember& ember);
void SyncGuiToEmber(Ember& ember, size_t widthOverride = 0, size_t heightOverride = 0);
+ void SetProgressComplete(int val);
Ember* m_Ember;
Ember m_PreviewEmber;
diff --git a/Source/Fractorium/Fractorium.rc b/Source/Fractorium/Fractorium.rc
index b66f21e..a55d353 100644
Binary files a/Source/Fractorium/Fractorium.rc and b/Source/Fractorium/Fractorium.rc differ
diff --git a/Source/Fractorium/FractoriumEmberController.cpp b/Source/Fractorium/FractoriumEmberController.cpp
index 5c01216..a1d13f7 100644
--- a/Source/Fractorium/FractoriumEmberController.cpp
+++ b/Source/Fractorium/FractoriumEmberController.cpp
@@ -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;
diff --git a/Source/Fractorium/FractoriumEmberController.h b/Source/Fractorium/FractoriumEmberController.h
index f0c25bd..05e01bb 100644
--- a/Source/Fractorium/FractoriumEmberController.h
+++ b/Source/Fractorium/FractoriumEmberController.h
@@ -41,7 +41,7 @@ public:
//Embers.
virtual void SetEmber(const Ember& ember, bool verbatim = false) { }
- virtual void CopyEmber(Ember& ember, std::function& ember)> perEmberOperation/* = [&](Ember& ember) { }*/) { }
+ virtual void CopyEmber(Ember& ember, std::function& ember)> perEmberOperation/* = [&](Ember& ember) { }*/) { }//Uncomment default lambdas once LLVM fixes a crash in their compiler with default lambda parameters.//TODO
virtual void SetEmberFile(const EmberFile& emberFile) { }
virtual void CopyEmberFile(EmberFile& emberFile, std::function& ember)> perEmberOperation/* = [&](Ember& ember) { }*/) { }
virtual void SetTempPalette(const Palette& palette) { }
@@ -202,7 +202,7 @@ public:
void DeleteRenderer();
void SaveCurrentRender(const QString& filename, bool forcePull);
RendererBase* Renderer() { return m_Renderer.get(); }
- vector* FinalImage() { return &m_FinalImage; }
+ vector* FinalImage() { return &(m_FinalImage[m_FinalImageIndex]); }
vector* 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 m_FinalImage;
+ std::thread m_WriteThread;
+ vector m_FinalImage[2];
vector m_PreviewFinalImage;
vector m_ProcessActions;
unique_ptr m_Renderer;
diff --git a/Source/Fractorium/FractoriumRender.cpp b/Source/Fractorium/FractoriumRender.cpp
index 726d667..7aeebe2 100644
--- a/Source/Fractorium/FractoriumRender.cpp
+++ b/Source/Fractorium/FractoriumRender.cpp
@@ -124,9 +124,9 @@ void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, b
FractoriumSettings* settings = m_Fractorium->m_Settings;
RendererCLBase* rendererCL = dynamic_cast(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::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::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::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& ember) { });
m_Controller->CopyEmberFile(efd, [&](Ember& ember) { });