0.4.1.8 Beta - Date pending testing.

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

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

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

View File

@ -6,7 +6,7 @@
<ProductVersion>3.7</ProductVersion>
<ProjectGuid>{c8096c47-e358-438c-a520-146d46b0637d}</ProjectGuid>
<SchemaVersion>2.0</SchemaVersion>
<OutputName>Fractorium_Beta_0.4.1.7</OutputName>
<OutputName>Fractorium_Beta_0.4.1.8</OutputName>
<OutputType>Package</OutputType>
<WixTargetsPath Condition=" '$(WixTargetsPath)' == '' AND '$(MSBuildExtensionsPath32)' != '' ">$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets</WixTargetsPath>
<WixTargetsPath Condition=" '$(WixTargetsPath)' == '' ">$(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets</WixTargetsPath>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?define ProductVersion="0.4.1.7" ?>
<?define ProductVersion="0.4.1.8" ?>
<?define ProductName="Fractorium Beta $(var.ProductVersion) ($(var.GpuType))" ?>
<?define UpgradeCode="{4714cd15-bfba-44f6-8059-9e1466ebfa6e}"?>
<?define Manufacturer="Fractorium"?>
@ -13,7 +13,7 @@
<!--
Change this for every release.
-->
<?define ProductCode="{20639B5F-7EF0-4708-8746-321466EECD84}"?>
<?define ProductCode="{1992CA14-0611-4980-95B8-1347161BD9EC}"?>
<Product Id="$(var.ProductCode)" Name="$(var.ProductName)" Language="1033" Version="$(var.ProductVersion)" Manufacturer="$(var.Manufacturer)" UpgradeCode="$(var.UpgradeCode)">
<Package

View File

@ -1,3 +1,20 @@
0.4.1.8 Beta
--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.
0.4.1.7 Beta 01/02/2015
--User changes
Use actual strips count when computing memory requirements in the final render dialog.

View File

@ -42,7 +42,7 @@
namespace EmberNs
{
#define EMBER_VERSION "0.4.1.7"
#define EMBER_VERSION "0.4.1.8"
#define EPS6 T(1e-6)
#define EPS std::numeric_limits<T>::epsilon()//Apoplugin.h uses -20, but it's more mathematically correct to do it this way.
#define ISAAC_SIZE 4

View File

@ -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.
/// </summary>
/// <param name="strips">The number of strips being used</param>
/// <param name="includeFinal">If true include the memory needed for the final output image, else don't.</param>
/// <param name="threadedWrite">Whether the caller will be writing the output in a thread, which doubles the memory required for the final output buffer.</param>
/// <returns>The histogram memory required in first, and the total memory required in second</returns>
pair<size_t, size_t> RendererBase::MemoryRequired(size_t strips, bool includeFinal)
pair<size_t, size_t> RendererBase::MemoryRequired(size_t strips, bool includeFinal, bool threadedWrite)
{
pair<size_t, size_t> 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;
}

View File

@ -99,7 +99,7 @@ public:
//Non-virtual processing functions.
void ChangeVal(std::function<void(void)> func, eProcessAction action);
size_t HistMemoryRequired(size_t strips);
pair<size_t, size_t> MemoryRequired(size_t strips, bool includeFinal);
pair<size_t, size_t> MemoryRequired(size_t strips, bool includeFinal, bool threadedWrite);
vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>> RandVec();
bool PrepFinalAccumVector(vector<byte>& pixels);

View File

@ -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<byte> finalImage, vecRgb;
vector<Ember<T>> embers;
EmberStats stats;
EmberReport emberReport;
@ -44,6 +42,8 @@ bool EmberAnimate(EmberOptions& opt)
Ember<T> centerEmber;
XmlToEmber<T> parser;
EmberToXml<T> emberToXml;
vector<byte> finalImages[2];
std::thread writeThread;
unique_ptr<RenderProgress<T>> progress(new RenderProgress<T>());
unique_ptr<Renderer<T, bucketT>> renderer(CreateRenderer<T, bucketT>(opt.EmberCL() ? OPENCL_RENDERER : CPU_RENDERER, opt.Platform(), opt.Device(), false, 0, emberReport));
vector<string> 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<void(uint)> 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;
}

View File

@ -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"

View File

@ -124,6 +124,7 @@ static bool InitPaletteList(const string& filename)
/// <summary>
/// Convert an RGBA buffer to an RGB buffer.
/// The two buffers can point to the same memory location if needed.
/// </summary>
/// <param name="rgba">The RGBA buffer</param>
/// <param name="rgb">The RGB buffer</param>
@ -131,7 +132,8 @@ static bool InitPaletteList(const string& filename)
/// <param name="height">The height of the image in pixels</param>
static void RgbaToRgb(vector<byte>& rgba, vector<byte>& 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)
{

View File

@ -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;
}
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="entry">The EmberOptionEntry object to copy</param>
EmberOptionEntry<T>& operator = (const EmberOptionEntry<T>& 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;
}
/// <summary>
/// Functor accessors.
/// </summary>
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<bool> JpegComments;
EmberOptionEntry<bool> PngComments;
EmberOptionEntry<bool> WriteGenome;
EmberOptionEntry<bool> ThreadedWrite;
EmberOptionEntry<bool> Enclosed;
EmberOptionEntry<bool> NoEdits;
EmberOptionEntry<bool> UnsmoothEdge;

View File

@ -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"

View File

@ -36,7 +36,7 @@ bool EmberRender(EmberOptions& opt)
ostringstream os;
pair<size_t, size_t> p;
vector<Ember<T>> embers;
vector<byte> finalImage, vecRgb;
vector<byte> 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")

View File

@ -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"

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

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