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