#pragma once
#include "EmberCommonPch.h"
///
/// Global utility classes and functions that are common to all programs that use
/// Ember and its derivatives.
///
///
/// Derivation of the RenderCallback class to do custom printing action
/// whenever the progress function is internally called inside of Ember
/// and its derivatives.
/// Template argument expected to be float or double.
///
template
class RenderProgress : public RenderCallback
{
public:
///
/// Constructor that initializes the state to zero.
///
RenderProgress()
{
Clear();
}
///
/// The progress function which will be called from inside the renderer.
///
/// The ember currently being rendered
/// An extra dummy parameter
/// The progress fraction from 0-100
/// The stage of iteration. 1 is iterating, 2 is density filtering, 2 is final accumulation.
/// The estimated milliseconds to completion of the current stage
/// 1 since this is intended to run in an environment where the render runs to completion, unlike interactive rendering.
virtual int ProgressFunc(Ember& ember, void* foo, double fraction, int stage, double etaMs)
{
if (stage == 0 || stage == 1)
{
if (m_LastStage != stage)
cout << endl;
cout << "\r" << string(m_S.length() * 2, ' ');//Clear what was previously here, * 2 just to be safe because the end parts of previous strings might be longer.
m_SS.str("");//Begin new output.
m_SS << "\rStage = " << (stage ? "filtering" : "iterating");
m_SS << ", progress = " << int(fraction) << "%";
m_SS << ", eta = " << t.Format(etaMs);
m_S = m_SS.str();
cout << m_S;
}
m_LastStage = stage;
return 1;
}
///
/// Reset the state.
///
void Clear()
{
m_LastStage = 0;
m_LastLength = 0;
m_SS.clear();
m_S.clear();
}
private:
int m_LastStage;
int m_LastLength;
stringstream m_SS;
string m_S;
Timing t;
};
///
/// Wrapper for parsing an ember Xml file, storing the embers in a vector and printing
/// any errors that occurred.
/// Template argument expected to be float or double.
///
/// The parser to use
/// The full path and name of the file
/// Storage for the embers read from the file
/// True if success, else false.
template
static bool ParseEmberFile(XmlToEmber& parser, string filename, vector>& embers)
{
if (!parser.Parse(filename.c_str(), embers))
{
cout << "Error parsing flame file " << filename << ", returning without executing." << endl;
return false;
}
if (embers.empty())
{
cout << "Error: No data present in file " << filename << ". Aborting." << endl;
return false;
}
return true;
}
///
/// Wrapper for parsing palette Xml file and initializing it's private static members,
/// and printing any errors that occurred.
/// Template argument expected to be float or double.
///
/// The full path and name of the file
/// True if success, else false.
template
static bool InitPaletteList(const string& filename)
{
PaletteList paletteList;//Even though this is local, the members are static so they will remain.
paletteList.Add(filename);
if (!paletteList.Size())
{
cout << "Error parsing palette file " << filename << ". Reason: " << endl;
cout << paletteList.ErrorReportString() << endl << "Returning without executing." << endl;
return false;
}
return true;
}
///
/// 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
/// The width of the image in pixels
/// The height of the image in pixels
static void RgbaToRgb(vector& rgba, vector& rgb, size_t width, size_t height)
{
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)
{
rgb[j] = rgba[i];
rgb[j + 1] = rgba[i + 1];
rgb[j + 2] = rgba[i + 2];
}
}
///
/// Calculate the number of strips required if the needed amount of memory
/// is greater than the system memory, or greater than what the user wants to allow.
///
/// Amount of memory required
/// Amount of memory available on the system
/// The maximum amount of memory to use. Use max if 0.
/// The number of strips to use
static uint CalcStrips(double memRequired, double memAvailable, double useMem)
{
uint strips;
if (useMem > 0)
memAvailable = useMem;
else
memAvailable *= 0.8;
if (memAvailable >= memRequired)
return 1;
strips = uint(ceil(memRequired / memAvailable));
return strips;
}
///
/// Given a numerator and a denominator, find the next highest denominator that divides
/// evenly into the numerator.
///
/// The numerator
/// The denominator
/// The next highest divisor if found, else 1.
template
static T NextHighestEvenDiv(T numerator, T denominator)
{
T result = 1;
T numDiv2 = numerator / 2;
do
{
denominator++;
if (numerator % denominator == 0)
{
result = denominator;
break;
}
}
while (denominator <= numDiv2);
return result;
}
///
/// Given a numerator and a denominator, find the next lowest denominator that divides
/// evenly into the numerator.
///
/// The numerator
/// The denominator
/// The next lowest divisor if found, else 1.
template
static T NextLowestEvenDiv(T numerator, T denominator)
{
T result = 1;
T numDiv2 = numerator / 2;
denominator--;
if (denominator > numDiv2)
denominator = numDiv2;
while (denominator >= 1)
{
if (numerator % denominator == 0)
{
result = denominator;
break;
}
denominator--;
}
return result;
}
///
/// Wrapper for creating a renderer of the specified type.
/// First template argument expected to be float or double for CPU renderer,
/// Second argument expected to be float or double for CPU renderer, and only float for OpenCL renderer.
///
/// Type of renderer to create
/// The index platform of the platform to use
/// The index device of the device to use
/// True if shared with OpenGL, else false.
/// The texture ID of the shared OpenGL texture if shared
/// The error report for holding errors if anything goes wrong
/// A pointer to the created renderer if successful, else false.
template
static Renderer* CreateRenderer(eRendererType renderType, uint platform, uint device, bool shared, GLuint texId, EmberReport& errorReport)
{
string s;
unique_ptr> renderer;
try
{
if (renderType == CPU_RENDERER)
{
s = "CPU";
renderer = unique_ptr>(new Renderer());
}
else if (renderType == OPENCL_RENDERER)
{
s = "OpenCL";
renderer = unique_ptr>(new RendererCL(platform, device, shared, texId));
if (!renderer.get() || !renderer->Ok())
{
if (renderer.get())
errorReport.AddToReport(renderer->ErrorReport());
errorReport.AddToReport("Error initializing OpenCL renderer, using CPU renderer instead.");
renderer = unique_ptr>(new Renderer());
}
}
}
catch (...)
{
errorReport.AddToReport("Error creating " + s + " renderer.\n");
}
return renderer.release();
}
template
static bool StripsRender(RendererBase* renderer, Ember& ember, vector& finalImage, double time, size_t strips, bool yAxisUp,
std::function perStripStart,
std::function perStripFinish,
std::function perStripError,
std::function& finalEmber)> allStripsFinished)
{
bool success = false;
size_t origHeight, realHeight = ember.m_FinalRasH;
T centerY = ember.m_CenterY;
T floatStripH = T(ember.m_FinalRasH) / T(strips);
T zoomScale = pow(T(2), ember.m_Zoom);
T centerBase = centerY - ((strips - 1) * floatStripH) / (2 * ember.m_PixelsPerUnit * zoomScale);
vector> randVec;
ember.m_Quality *= strips;
ember.m_FinalRasH = size_t(ceil(floatStripH));
if (strips > 1)
randVec = renderer->RandVec();
for (size_t strip = 0; strip < strips; strip++)
{
size_t stripOffset;
if (yAxisUp)
stripOffset = ember.m_FinalRasH * ((strips - strip) - 1) * renderer->FinalRowSize();
else
stripOffset = ember.m_FinalRasH * strip * renderer->FinalRowSize();
ember.m_CenterY = centerBase + ember.m_FinalRasH * T(strip) / (ember.m_PixelsPerUnit * zoomScale);
if ((ember.m_FinalRasH * (strip + 1)) > realHeight)
{
origHeight = ember.m_FinalRasH;
ember.m_FinalRasH = realHeight - origHeight * strip;
ember.m_CenterY -= (origHeight - ember.m_FinalRasH) * T(0.5) / (ember.m_PixelsPerUnit * zoomScale);
}
perStripStart(strip);
if (strips > 1)
{
renderer->RandVec(randVec);//Use the same vector of ISAAC rands for each strip.
renderer->SetEmber(ember);//Set one final time after modifications for strips.
}
if ((renderer->Run(finalImage, time, 0, false, stripOffset) == RENDER_OK) && !renderer->Aborted() && !finalImage.empty())
{
perStripFinish(strip);
}
else
{
perStripError(strip);
break;
}
if (strip == strips - 1)
success = true;
}
//Restore the ember values to their original values.
ember.m_Quality /= strips;
ember.m_FinalRasH = realHeight;
ember.m_CenterY = centerY;
renderer->SetEmber(ember);//Further processing will require the dimensions to match the original ember, so re-assign.
if (success)
allStripsFinished(ember);
Memset(finalImage);
return success;
}
static size_t VerifyStrips(size_t height, size_t strips,
std::function stripError1,
std::function stripError2,
std::function stripError3)
{
ostringstream os;
if (strips > height)
{
os << "Cannot have more strips than rows: " << strips << " > " << height << ". Setting strips = rows.";
stripError1(os.str()); os.str("");
strips = height;
}
if (height % strips != 0)
{
os << "A strips value of " << strips << " does not divide evenly into a height of " << height << ".";
stripError2(os.str()); os.str("");
strips = NextHighestEvenDiv(height, strips);
if (strips == 1)//No higher divisor, check for a lower one.
strips = NextLowestEvenDiv(height, strips);
os << "Setting strips to " << strips << ".";
stripError3(os.str()); os.str("");
}
return strips;
}
///
/// Simple macro to print a string if the --verbose options has been specified.
///
#define VerbosePrint(s) if (opt.Verbose()) cout << s << endl