From 4059767dc4c3a2cea519ffe8dcd131aba6c8b524 Mon Sep 17 00:00:00 2001 From: mfeemster Date: Mon, 19 Jan 2015 08:39:50 -0800 Subject: [PATCH] 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. --- .../Installer/FractoriumInstaller.wixproj | 2 +- Builds/MSVC/Installer/Product.wxs | 4 +- Data/Version History.txt | 17 ++++ Source/Ember/EmberDefines.h | 2 +- Source/Ember/RendererBase.cpp | 10 ++- Source/Ember/RendererBase.h | 2 +- Source/EmberAnimate/EmberAnimate.cpp | 69 ++++++++++------ Source/EmberAnimate/EmberAnimate.rc | 8 +- Source/EmberCommon/EmberCommon.h | 4 +- Source/EmberCommon/EmberOptions.h | 43 ++++++++-- Source/EmberGenome/EmberGenome.rc | 8 +- Source/EmberRender/EmberRender.cpp | 15 +--- Source/EmberRender/EmberRender.rc | 8 +- Source/Fractorium/AboutDialog.ui | 2 +- .../Fractorium/FinalRenderEmberController.cpp | 77 ++++++++++++------ .../Fractorium/FinalRenderEmberController.h | 1 + Source/Fractorium/Fractorium.rc | Bin 4574 -> 4574 bytes .../Fractorium/FractoriumEmberController.cpp | 1 + Source/Fractorium/FractoriumEmberController.h | 8 +- Source/Fractorium/FractoriumRender.cpp | 21 +++-- 20 files changed, 203 insertions(+), 99 deletions(-) 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 b66f21e168ffcc1d874a11666d3f9e080bf8959e..a55d353207d4cb8c5f783a01ac2d1308edc27c2e 100644 GIT binary patch delta 46 wcmcbod{23U7YC!oWN!{bMvKk09PG?Mjv|-%WC32W&6~KHm>}HEXLyBJ05Dn$$N&HU delta 46 wcmcbod{23U7YC#HWN!{bM)S?J9PG?Mjv|-%WC32W&6~KHm>}HEXLyBJ059& 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) { });