diff --git a/Source/Ember/EmberToXml.h b/Source/Ember/EmberToXml.h
index 3206bdb..afaeb56 100644
--- a/Source/Ember/EmberToXml.h
+++ b/Source/Ember/EmberToXml.h
@@ -78,22 +78,9 @@ public:
{
auto prev = embers.begin();
- //Check to see if there are valid times by checking if any differed.
- //If so, assume they were intentionally entered times.
- for (auto it = Advance(embers.begin(), 1); it != embers.end(); ++it)
- {
- if (it->m_Time != prev->m_Time)
- {
- hasTimes = true;
- break;
- }
-
- prev = it;
- }
-
- if (!hasTimes)
- for (auto& ember : embers)
- ember.m_Time = t++;
+ //Always ensure times make sense.
+ for (auto& ember : embers)
+ ember.m_Time = t++;
if ((append && start) || !append)
{
diff --git a/Source/Ember/Interpolate.h b/Source/Ember/Interpolate.h
index fdbd75e..fbfd99d 100644
--- a/Source/Ember/Interpolate.h
+++ b/Source/Ember/Interpolate.h
@@ -376,7 +376,7 @@ public:
/// The time position in the vector specifying the point of interpolation
/// Stagger if > 0
/// The interpolated result
- static void Interpolate(const vector>& embers, T time, T stagger, Ember& result)
+ void Interpolate(const vector>& embers, T time, T stagger, Ember& result)
{
Interpolate(embers.data(), embers.size(), time, stagger, result);
}
@@ -389,7 +389,7 @@ public:
/// The time position in the vector specifying the point of interpolation
/// Stagger if > 0
/// The interpolated result
- static void Interpolate(const Ember* embers, size_t size, T time, T stagger, Ember& result)
+ void Interpolate(const Ember* embers, size_t size, T time, T stagger, Ember& result)
{
if (size == 1)
{
@@ -398,8 +398,6 @@ public:
}
size_t i1, i2;
- vector c(2);
- Ember localEmbers[4];
bool smoothFlag = false;
if (embers[0].m_Time >= time)
@@ -423,31 +421,31 @@ public:
i2 = i1 + 1;
}
- c[0] = (embers[i2].m_Time - time) / (embers[i2].m_Time - embers[i1].m_Time);
- c[1] = 1 - c[0];
+ m_Coeffs[0] = (embers[i2].m_Time - time) / (embers[i2].m_Time - embers[i1].m_Time);
+ m_Coeffs[1] = 1 - m_Coeffs[0];
//To interpolate the xforms, make copies of the source embers
//and ensure that they both have the same number of xforms before progressing.
if (embers[i1].m_Interp == eInterp::EMBER_INTERP_LINEAR)
{
- Align(&embers[i1], &localEmbers[0], 2);
+ Align(&embers[i1], &m_Embers[0], 2);
smoothFlag = false;
}
else
{
if (i1 == 0)
{
- Align(&embers[i1], &localEmbers[0], 2);
+ Align(&embers[i1], &m_Embers[0], 2);
smoothFlag = false;
}
else if (i2 == size - 1)
{
- Align(&embers[i1], &localEmbers[0], 2);
+ Align(&embers[i1], &m_Embers[0], 2);
smoothFlag = false;
}
else
{
- Align(&embers[i1 - 1], &localEmbers[0], 4);//Should really be doing some sort of checking here to ensure the ember vectors have 4 elements.
+ Align(&embers[i1 - 1], &m_Embers[0], 4);//Should really be doing some sort of checking here to ensure the ember vectors have 4 elements.
smoothFlag = true;
}
}
@@ -458,9 +456,9 @@ public:
result.m_PaletteInterp = ePaletteInterp::INTERP_HSV;
if (!smoothFlag)
- result.Interpolate(&localEmbers[0], 2, c, stagger);
+ result.Interpolate(&m_Embers[0], 2, m_Coeffs, stagger);
else
- result.InterpolateCatmullRom(&localEmbers[0], 4, c[1]);
+ result.InterpolateCatmullRom(&m_Embers[0], 4, m_Coeffs[1]);
}
///
@@ -492,9 +490,9 @@ public:
{
for (size_t i = 0; i < source->TotalVariationCount(); i++)//Iterate through the first xform's variations.
{
- Variation* var = source->GetVariation(i);//Grab the variation at index in in the first xform.
- Variation* var2 = dest->GetVariationById(var->VariationId());//See if the same variation exists in the second xform.
- ParametricVariation* parVar = dynamic_cast*>(var);//Parametric cast of the first var for later.
+ auto var = source->GetVariation(i);//Grab the variation at index in in the first xform.
+ auto var2 = dest->GetVariationById(var->VariationId());//See if the same variation exists in the second xform.
+ auto parVar = dynamic_cast*>(var);//Parametric cast of the first var for later.
if (!var2)//Only take action if the second xform did not contain this variation.
{
@@ -502,7 +500,7 @@ public:
{
if (parVar)
{
- Variation* parVarCopy = parVar->Copy();
+ auto parVarCopy = parVar->Copy();
if (clearWeights)
parVarCopy->m_Weight = 0;
@@ -512,7 +510,7 @@ public:
}
else//Add regardless of type.
{
- Variation* varCopy = var->Copy();
+ auto varCopy = var->Copy();
if (clearWeights)
varCopy->m_Weight = 0;
@@ -732,8 +730,7 @@ public:
{
for (size_t col = 0; col < 2; col++)
{
- int sym0, sym1;
- int padSymFlag;
+ bool sym0, sym1, padSymFlag = false;
d = cxang[k][col] - cxang[k - 1][col];
//Adjust to avoid the -pi/pi discontinuity.
@@ -745,7 +742,6 @@ public:
//If this is an asymmetric case, store the NON-symmetric angle
//Check them pairwise and store the reference angle in the second
//to avoid overwriting if asymmetric on both sides.
- padSymFlag = 0;
sym0 = (embers[k - 1].GetXform(xfi)->m_Animate == 0 || (embers[k - 1].GetXform(xfi)->Empty() && padSymFlag));
sym1 = (embers[k ].GetXform(xfi)->m_Animate == 0 || (embers[k ].GetXform(xfi)->Empty() && padSymFlag));
@@ -936,5 +932,9 @@ public:
return ad > bd;
}
+
+private:
+ vector m_Coeffs = vector(2);
+ Ember m_Embers[4];
};
}
diff --git a/Source/Ember/Renderer.cpp b/Source/Ember/Renderer.cpp
index 3c615a3..c83f3cb 100644
--- a/Source/Ember/Renderer.cpp
+++ b/Source/Ember/Renderer.cpp
@@ -416,7 +416,7 @@ eRenderStatus Renderer::Run(vector& finalImage, double time, s
//it.Tic();
//Interpolate.
if (m_EmbersP->size() > 1)
- Interpolater::Interpolate(*m_EmbersP, T(time), 0, m_Ember);
+ m_Interpolater.Interpolate(*m_EmbersP, T(time), 0, m_Ember);
//it.Toc("Interp 1");
@@ -454,7 +454,7 @@ eRenderStatus Renderer::Run(vector& finalImage, double time, s
//Additional interpolation will be done in the temporal samples loop.
//it.Tic();
if (m_EmbersP->size() > 1)
- Interpolater::Interpolate(*m_EmbersP, deTime, 0, m_Ember);
+ m_Interpolater.Interpolate(*m_EmbersP, deTime, 0, m_Ember);
//it.Toc("Interp 2");
ClampGteRef(m_Ember.m_MinRadDE, 0);
@@ -479,7 +479,7 @@ eRenderStatus Renderer::Run(vector& finalImage, double time, s
//Interpolate again.
//it.Tic();
if (TemporalSamples() > 1 && m_EmbersP->size() > 1)
- Interpolater::Interpolate(*m_EmbersP, temporalTime, 0, m_Ember);//This will perform all necessary precalcs via the ember/xform/variation assignment operators.
+ m_Interpolater.Interpolate(*m_EmbersP, temporalTime, 0, m_Ember);//This will perform all necessary precalcs via the ember/xform/variation assignment operators.
//it.Toc("Interp 3");
diff --git a/Source/Ember/Renderer.h b/Source/Ember/Renderer.h
index cc942d5..8c66a7d 100644
--- a/Source/Ember/Renderer.h
+++ b/Source/Ember/Renderer.h
@@ -184,6 +184,7 @@ private:
protected:
vector>* m_EmbersP = &m_Embers;
vector> m_ThreadEmbers;
+ Interpolater m_Interpolater;
CarToRas m_CarToRas;
unique_ptr> m_StandardIterator = make_unique>();
unique_ptr> m_XaosIterator = make_unique>();
diff --git a/Source/Ember/SheepTools.h b/Source/Ember/SheepTools.h
index 16d5ef0..cd6be0b 100644
--- a/Source/Ember/SheepTools.h
+++ b/Source/Ember/SheepTools.h
@@ -452,14 +452,13 @@ public:
else if (crossMode == eCrossMode::CROSS_INTERPOLATE)
{
//Linearly interpolate somewhere between the two.
- Ember parents[2];
//t = 0.5;//If you ever need to test.
t = m_Rand.Frand01();
- parents[0] = ember0;
- parents[1] = ember1;
- parents[0].m_Time = T(0);
- parents[1].m_Time = T(1);
- Interpolater::Interpolate(parents, 2, t, 0, emberOut);
+ m_Parents[0] = ember0;
+ m_Parents[1] = ember1;
+ m_Parents[0].m_Time = T(0);
+ m_Parents[1].m_Time = T(1);
+ m_Interpolater.Interpolate(m_Parents, 2, t, 0, emberOut);
for (i = 0; i < emberOut.TotalXformCount(); i++)
emberOut.GetTotalXform(i)->DeleteMotionElements();
@@ -990,21 +989,20 @@ public:
void Edge(Ember* embers, Ember& result, T blend, bool seqFlag)
{
size_t i, si;
- Ember spun[2], prealign[2];
//Insert motion magic here :
//If there are motion elements, modify the contents of
//the result xforms before rotate is called.
for (si = 0; si < 2; si++)
{
- prealign[si] = embers[si];
+ m_EdgePrealign[si] = embers[si];
for (i = 0; i < embers[si].TotalXformCount(); i++)
{
auto xform = embers[si].GetTotalXform(i);
if (!xform->m_Motion.empty())
- xform->ApplyMotion(*(prealign[si].GetTotalXform(i)), blend);//Apply motion parameters to result.xform[i] using blend parameter.
+ xform->ApplyMotion(*(m_EdgePrealign[si].GetTotalXform(i)), blend);//Apply motion parameters to result.xform[i] using blend parameter.
}
}
@@ -1012,20 +1010,20 @@ public:
//This keeps the original interpolation type intact.
if (seqFlag && blend == 0)
{
- result = prealign[0];
+ result = m_EdgePrealign[0];
}
else
{
//Align what's going to be interpolated.
- Interpolater::Align(prealign, spun, 2);
- spun[0].m_Time = 0;
- spun[1].m_Time = 1;
+ Interpolater::Align(m_EdgePrealign, m_EdgeSpun, 2);
+ m_EdgeSpun[0].m_Time = 0;
+ m_EdgeSpun[1].m_Time = 1;
//Call this first to establish the asymmetric reference angles.
- Interpolater::AsymmetricRefAngles(spun, 2);
+ Interpolater::AsymmetricRefAngles(m_EdgeSpun, 2);
//Rotate the aligned xforms.
- spun[0].RotateAffines(-blend * 360);
- spun[1].RotateAffines(-blend * 360);
- Interpolater::Interpolate(spun, 2, m_Smooth ? Interpolater::Smoother(blend) : blend, m_Stagger, result);
+ m_EdgeSpun[0].RotateAffines(-blend * 360);
+ m_EdgeSpun[1].RotateAffines(-blend * 360);
+ m_Interpolater.Interpolate(m_EdgeSpun, 2, m_Smooth ? Interpolater::Smoother(blend) : blend, m_Stagger, result);
}
//Make sure there are no motion elements in the result.
@@ -1340,6 +1338,10 @@ private:
vector m_Hist;
EmberToXml m_EmberToXml;
Iterator* m_Iterator;
+ Interpolater m_Interpolater;
+ Ember m_Parents[2];
+ Ember m_EdgeSpun[2];
+ Ember m_EdgePrealign[2];
unique_ptr> m_StandardIterator = make_unique>();
unique_ptr> m_XaosIterator = make_unique>();
unique_ptr> m_Renderer;
diff --git a/Source/Ember/Utils.h b/Source/Ember/Utils.h
index e2cb048..8321c25 100644
--- a/Source/Ember/Utils.h
+++ b/Source/Ember/Utils.h
@@ -1028,6 +1028,26 @@ static vector Split(const string& str, char del)
return vec;
}
+///
+/// Thin wrapper around joining a thread.
+///
+/// The thread to join
+static void Join(std::thread& th)
+{
+ if (th.joinable())
+ th.join();
+}
+
+///
+/// Thin wrapper around joining a vector of threads.
+///
+/// The vector of threads to join
+static void Join(std::vector& vec)
+{
+ for (auto& it : vec)
+ Join(it);
+}
+
///
/// Return a character pointer to a version string composed of the EMBER_OS and EMBER_VERSION values.
///
diff --git a/Source/EmberAnimate/EmberAnimate.cpp b/Source/EmberAnimate/EmberAnimate.cpp
index 27521f2..c85c1fd 100644
--- a/Source/EmberAnimate/EmberAnimate.cpp
+++ b/Source/EmberAnimate/EmberAnimate.cpp
@@ -35,6 +35,7 @@ bool EmberAnimate(EmberOptions& opt)
vector> embers;
XmlToEmber parser;
EmberToXml emberToXml;
+ Interpolater interpolater;
EmberReport emberReport;
const vector> devices = Devices(opt.Devices());
std::atomic atomfTime;
@@ -275,7 +276,7 @@ bool EmberAnimate(EmberOptions& opt)
opt.FirstFrame(size_t(embers[0].m_Time));
if (opt.LastFrame() == UINT_MAX)
- opt.LastFrame(ClampGte(size_t(embers.back().m_Time),//Make sure time - 1 is positive before converting to size_t.
+ opt.LastFrame(ClampGte(size_t(embers.back().m_Time),
opt.FirstFrame() + opt.Dtime()));//Make sure the final value is at least first frame + dtime.
}
@@ -340,9 +341,20 @@ bool EmberAnimate(EmberOptions& opt)
std::thread writeThread;
os.imbue(std::locale(""));
- while (atomfTime.fetch_add(opt.Dtime()), ((ftime = atomfTime.load()) <= opt.LastFrame()))
+ //The conditions of this loop use atomics to synchronize when running on multiple GPUs.
+ //The order is reversed from the usual loop: rather than compare and increment the counter,
+ //it's incremented, then compared. This is done to ensure the GPU on this thread "claims" this
+ //frame before working on it.
+ //The mechanism for incrementing is:
+ // Do an atomic add, which returns the previous value.
+ // Add the time increment Dtime() to the return value to mimic what the new atomic value should be.
+ // Assign the result to the ftime counter.
+ // Do a <= comparison to LastFrame().
+ // If true, enter the loop and immediately decrement the counter by Dtime() to make up for the fact
+ // that it was first incremented before comparing.
+ while ((ftime = (atomfTime.fetch_add(opt.Dtime()) + opt.Dtime())) <= opt.LastFrame())
{
- T localTime = T(ftime) - 1;
+ T localTime = T(ftime) - opt.Dtime();
if (opt.Verbose() && ((opt.LastFrame() - opt.FirstFrame()) / opt.Dtime() >= 1))
{
@@ -374,7 +386,7 @@ bool EmberAnimate(EmberOptions& opt)
cout << "Writing " << flameName << "\n";
}
- Interpolater::Interpolate(embers, localTime, 0, centerEmber);//Get center flame.
+ interpolater.Interpolate(embers, localTime, 0, centerEmber);//Get center flame.
emberToXml.Save(flameName, centerEmber, opt.PrintEditDepth(), true, opt.HexPalette(), true, false, false);
centerEmber.Clear();
}
@@ -400,9 +412,7 @@ bool EmberAnimate(EmberOptions& opt)
//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();
-
+ Join(writeThread);
auto threadVecIndex = finalImageIndex;//Cache before launching thread.
if (opt.ThreadedWrite())//Copies are passed of all but the first parameter to saveFunc(), to avoid conflicting with those values changing when starting the render for the next image.
@@ -414,8 +424,7 @@ bool EmberAnimate(EmberOptions& opt)
saveFunc(finalImages[threadVecIndex], filename, comments, renderer->FinalRasW(), renderer->FinalRasH(), renderer->NumChannels());//Will always use the first index, thereby not requiring more memory.
}
- if (writeThread.joinable())//One final check to make sure all writing is done before exiting this thread.
- writeThread.join();
+ Join(writeThread);//One final check to make sure all writing is done before exiting this thread.
};
threadVec.reserve(renderers.size());
@@ -427,10 +436,7 @@ bool EmberAnimate(EmberOptions& opt)
}, r));
}
- for (auto& th : threadVec)
- if (th.joinable())
- th.join();
-
+ Join(threadVec);
t.Toc("\nFinished in: ", true);
return true;
}
diff --git a/Source/EmberCL/RendererCL.cpp b/Source/EmberCL/RendererCL.cpp
index b3e7fef..3f6e87c 100644
--- a/Source/EmberCL/RendererCL.cpp
+++ b/Source/EmberCL/RendererCL.cpp
@@ -929,9 +929,7 @@ bool RendererCL::BuildIterProgramForEmber(bool doAccum)
func(m_Devices[device].get());
}
- for (auto& th : threads)
- if (th.joinable())
- th.join();
+ Join(threads);
if (b)
{
@@ -992,7 +990,7 @@ bool RendererCL::RunIter(size_t iterCount, size_t temporalSample, si
auto& wrapper = m_Devices[dev]->m_Wrapper;
intmax_t itersRemaining = 0;
- while (atomLaunchesRan.fetch_add(1), (b && (atomLaunchesRan.load() <= launches) && ((itersRemaining = atomItersRemaining.load()) > 0) && !m_Abort))
+ while (b && (atomLaunchesRan.fetch_add(1) + 1 <= launches) && ((itersRemaining = atomItersRemaining.load()) > 0) && !m_Abort)
{
cl_uint argIndex = 0;
#ifdef TEST_CL
@@ -1102,10 +1100,7 @@ bool RendererCL::RunIter(size_t iterCount, size_t temporalSample, si
iterFunc(device, index);
}
- for (auto& th : threadVec)
- if (th.joinable())
- th.join();
-
+ Join(threadVec);
itersRan = atomItersRan.load();
if (m_Devices.size() > 1)//Determine whether/when to sum histograms of secondary devices with the primary.
diff --git a/Source/EmberCommon/EmberOptions.h b/Source/EmberCommon/EmberOptions.h
index 9badfd8..439ffed 100644
--- a/Source/EmberCommon/EmberOptions.h
+++ b/Source/EmberCommon/EmberOptions.h
@@ -145,11 +145,9 @@ private:
///
EmberOptionEntry()
{
- m_OptionUse = eOptionUse::OPT_USE_ALL;
- m_Option.nArgType = SO_NONE;
m_Option.nId = 0;
m_Option.pszArg = _T("--fillmein");
- m_DocString = "Dummy doc";
+ m_Option.nArgType = SO_NONE;
}
public:
@@ -203,13 +201,13 @@ public:
///
/// Functor accessors.
///
- inline T operator() (void) { return m_Val; }
+ inline T operator() (void) const { return m_Val; }
inline void operator() (T t) { m_Val = t; }
private:
- eOptionUse m_OptionUse;
+ eOptionUse m_OptionUse = eOptionUse::OPT_USE_ALL;
CSimpleOpt::SOption m_Option;
- string m_DocString;
+ string m_DocString = "Dummy doc";
string m_NameWithoutDashes;
T m_Val;
};
diff --git a/Source/EmberGenome/EmberGenome.cpp b/Source/EmberGenome/EmberGenome.cpp
index ce5b631..2e5c09d 100644
--- a/Source/EmberGenome/EmberGenome.cpp
+++ b/Source/EmberGenome/EmberGenome.cpp
@@ -117,6 +117,7 @@ bool EmberGenome(EmberOptions& opt)
Ember* aselp0, *aselp1, *pTemplate = nullptr;
XmlToEmber parser;
EmberToXml emberToXml;
+ Interpolater interpolater;
EmberReport emberReport, emberReport2;
const vector> devices = Devices(opt.Devices());
auto progress = make_unique>();
@@ -370,7 +371,7 @@ bool EmberGenome(EmberOptions& opt)
if (!exactTimeMatch)
{
- Interpolater::Interpolate(embers, T(ftime), T(opt.Stagger()), interpolated);
+ interpolater.Interpolate(embers, T(ftime), T(opt.Stagger()), interpolated);
for (i = 0; i < embers.size(); i++)
{
@@ -397,31 +398,20 @@ bool EmberGenome(EmberOptions& opt)
if (opt.Sequence() != "")
{
- frame = std::max(opt.Frame(), opt.Time());
-
if (opt.Frames() == 0)
{
cerr << "nframes must be positive and non-zero, not " << opt.Frames() << ".\n";
return false;
}
- for (i = 0; i < embers.size(); i++)
- {
- if (i > 0 && embers[i].m_Time <= embers[i - 1].m_Time)
- {
- cerr << "Error: control points must be sorted by time, but time " << embers[i].m_Time << " <= " << embers[i - 1].m_Time << ", index " << i << ".\n";
- return false;
- }
- }
-
if (opt.Enclosed())
cout << "\n";
- spread = 1 / T(opt.Frames());
frameCount = 0;
os.str("");
os << setfill('0');
auto padding = streamsize(std::log10(((opt.Frames() * opt.Loops()) + opt.Frames()) * embers.size())) + 1;
+ t.Tic();
for (i = 0; i < embers.size(); i++)
{
@@ -451,7 +441,7 @@ bool EmberGenome(EmberOptions& opt)
for (frame = 0; frame < opt.Frames(); frame++)
{
- seqFlag = (frame == 0 || frame == opt.Frames() - 1);
+ seqFlag = (frame == 0 || (frame == opt.Frames() - 1));
blend = frame / T(opt.Frames());
result.Clear();
tools.SpinInter(&embers[i], pTemplate, result, frameCount++, seqFlag, blend);
@@ -465,6 +455,7 @@ bool EmberGenome(EmberOptions& opt)
tools.Spin(embers.back(), pTemplate, result, frameCount, 0);
FormatName(result, os, padding);
cout << emberToXml.ToString(result, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), opt.HexPalette());
+ t.Toc("Sequencing");
if (opt.Enclosed())
cout << "\n";
diff --git a/Source/EmberTester/EmberTester.cpp b/Source/EmberTester/EmberTester.cpp
index c66226f..557d360 100644
--- a/Source/EmberTester/EmberTester.cpp
+++ b/Source/EmberTester/EmberTester.cpp
@@ -1866,8 +1866,8 @@ void TestThreadedKernel()
cout << "Successful run inside thread 2..." << endl;
}
});
- th1.join();
- th2.join();
+ Join(th1);
+ Join(th2);
cout << "Successful join of kernel thread..." << endl;
}
}
diff --git a/Source/Fractorium/FinalRenderEmberController.cpp b/Source/Fractorium/FinalRenderEmberController.cpp
index 8611fab..a936360 100644
--- a/Source/Fractorium/FinalRenderEmberController.cpp
+++ b/Source/Fractorium/FinalRenderEmberController.cpp
@@ -230,15 +230,27 @@ FinalRenderEmberController::FinalRenderEmberController(FractoriumFinalRenderD
renderer->SetExternalEmbersPointer(&embers);//All will share a pointer to the original vector to conserve memory with large files. Ok because the vec doesn't get modified.
//Render each image, cancelling if m_Run ever gets set to false.
- while (atomfTime.fetch_add(1), ((ftime = atomfTime.load() - 1) < m_EmberFile.Size()) && m_Run)//Needed to set 1 to claim this iter from other threads, so decrement it to be zero-indexed here.
+ //Render each image, cancelling if m_Run ever gets set to false.
+ //The conditions of this loop use atomics to synchronize when running on multiple GPUs.
+ //The order is reversed from the usual loop: rather than compare and increment the counter,
+ //it's incremented, then compared. This is done to ensure the GPU on this thread "claims" this
+ //frame before working on it.
+ //The mechanism for incrementing is:
+ // Do an atomic add, which returns the previous value.
+ // Add 1 to the return value to mimic what the new atomic value should be.
+ // Assign the result to the ftime counter.
+ // Do a <= comparison to m_EmberFile.Size() and check m_Run.
+ // If true, enter the loop and immediately decrement the counter by 1 to make up for the fact
+ // that it was first incremented before comparing.
+ while (((ftime = (atomfTime.fetch_add(1) + 1)) <= m_EmberFile.Size()) && m_Run)//Needed to set 1 to claim this iter from other threads, so decrement it below to be zero-indexed here.
{
- T localTime = T(ftime);
+ --ftime;
Output("Image " + ToString(ftime + 1ULL) + ":\n" + ComposePath(QString::fromStdString(m_EmberFile.Get(ftime)->m_Name)));
renderer->Reset();//Have to manually set this since the ember is not set each time through.
renderTimer.Tic();//Toc() is called in RenderComplete().
//Can't use strips render here. Run() must be called directly for animation.
- if (renderer->Run(finalImages[finalImageIndex], localTime) != eRenderStatus::RENDER_OK)
+ if (renderer->Run(finalImages[finalImageIndex], T(ftime)) != eRenderStatus::RENDER_OK)
{
Output("Rendering failed.\n");
m_Fractorium->ErrorReportToQTextEdit(renderer->ErrorReport(), m_FinalRenderDialog->ui.FinalRenderTextOutput, false);//Internally calls invoke.
@@ -247,9 +259,7 @@ FinalRenderEmberController::FinalRenderEmberController(FractoriumFinalRenderD
}
else
{
- if (writeThread.joinable())
- writeThread.join();
-
+ Join(writeThread);
stats = renderer->Stats();
comments = renderer->ImageComments(stats, 0, true);
writeThread = std::thread([&](size_t tempTime, size_t threadFinalImageIndex)
@@ -272,8 +282,7 @@ FinalRenderEmberController::FinalRenderEmberController(FractoriumFinalRenderD
finalImageIndex ^= 1;//Toggle the index.
}
- if (writeThread.joinable())//One final check to make sure all writing is done before exiting this thread.
- writeThread.join();
+ Join(writeThread);//One final check to make sure all writing is done before exiting this thread.
};
threadVec.reserve(m_Renderers.size());
@@ -285,10 +294,7 @@ FinalRenderEmberController::FinalRenderEmberController(FractoriumFinalRenderD
}, r));
}
- for (auto& th : threadVec)
- if (th.joinable())
- th.join();
-
+ Join(threadVec);
HandleFinishedProgress();//One final check that all images were finished.
}
else if (m_Renderer.get())//Make sure a renderer was created and render all images, but not as an animation sequence (without temporal samples motion blur).