Initial source commit

Initial source commit
This commit is contained in:
mfeemster
2014-07-08 00:11:14 -07:00
parent 1493c22925
commit ef56c16b2b
214 changed files with 108754 additions and 0 deletions

352
Source/Ember/Affine2D.cpp Normal file
View File

@ -0,0 +1,352 @@
#include "EmberPch.h"
#include "Affine2D.h"
namespace EmberNs
{
/// <summary>
/// Default constructor which sets the matrix to the identity.
/// </summary>
template <typename T>
Affine2D<T>::Affine2D()
{
MakeID();
}
/// <summary>
/// Constructor which takes each column of the affine as a separate parameter.
/// </summary>
/// <param name="x">A and D</param>
/// <param name="y">B and E</param>
/// <param name="t">C and F</param>
template <typename T>
Affine2D<T>::Affine2D(v2T& x, v2T& y, v2T& t)
{
X(x);
Y(y);
O(t);
}
/// <summary>
/// Constructor which takes all six of the affine values as parameters.
/// </summary>
/// <param name="xx">A</param>
/// <param name="xy">D</param>
/// <param name="yx">B</param>
/// <param name="yy">E</param>
/// <param name="tx">C</param>
/// <param name="ty">F</param>
template <typename T>
Affine2D<T>::Affine2D(T xx, T xy, T yx, T yy, T tx, T ty)
{
A(xx);
D(xy);
B(yx);
E(yy);
C(tx);
F(ty);
}
/// <summary>
/// Constructor which takes a 4x4 matrix and assigns the
/// corresponding values in the 2x3 affine matrix.
/// </summary>
/// <param name="mat">The 4x4 affine matrix to read from</param>
template <typename T>
Affine2D<T>::Affine2D(m4T& mat)
{
A(mat[0][0]);
B(mat[0][1]);
C(mat[0][3]);
D(mat[1][0]);
E(mat[1][1]);
F(mat[1][3]);
}
/// <summary>
/// Make this affine transform the identity matrix.
/// A and E = 1, all else 0.
/// </summary>
template <typename T>
void Affine2D<T>::MakeID()
{
A(1);
B(0);
C(0);
D(0);
E(1);
F(0);
}
/// <summary>
/// Determine whether this affine transform is the identity matrix.
/// </summary>
/// <returns>True if A and E are equal to 1 and all others are 0, else false.</returns>
template <typename T>
bool Affine2D<T>::IsID() const
{
return (IsClose<T>(A(), 1)) &&
(IsClose<T>(B(), 0)) &&
(IsClose<T>(C(), 0)) &&
(IsClose<T>(D(), 0)) &&
(IsClose<T>(E(), 1)) &&
(IsClose<T>(F(), 0));
}
/// <summary>
/// Determine whether this affine transform is all zeroes.
/// </summary>
/// <returns>True if all 6 elements equal zero, else false.</returns>
template <typename T>
bool Affine2D<T>::IsZero() const
{
return (IsClose<T>(A(), 0)) &&
(IsClose<T>(B(), 0)) &&
(IsClose<T>(C(), 0)) &&
(IsClose<T>(D(), 0)) &&
(IsClose<T>(E(), 0)) &&
(IsClose<T>(F(), 0));
}
/// <summary>
/// Rotate this affine transform around its origin by the specified angle in degrees.
/// </summary>
/// <param name="angle">The angle to rotate by</param>
template <typename T>
void Affine2D<T>::Rotate(T angle)
{
m4T origMat4 = ToMat4ColMajor(true);//Must center and use column major for glm to work.
m4T newMat4 = glm::rotate(origMat4, angle * DEG_2_RAD_T, v3T(0, 0, 1));//Assuming only rotating around z.
A(newMat4[0][0]);//Use direct assignments instead of constructor to skip assigning C and F.
B(newMat4[0][1]);
D(newMat4[1][0]);
E(newMat4[1][1]);
}
/// <summary>
/// Move by v.
/// </summary>
/// <param name="v">The vec2 describing how far to move in the x and y directions</param>
template <typename T>
void Affine2D<T>::Translate(v2T& v)
{
O(O() + v);
}
/// <summary>
/// Rotate and scale the X and Y components by a certain amount based on X.
/// </summary>
/// <param name="v">The vec2 describing how much to rotate and scale the X and Y components</param>
template <typename T>
void Affine2D<T>::RotateScaleXTo(v2T& v)
{
Affine2D<T> rs = CalcRotateScale(X(), v);
X(rs.TransformNormal(X()));
Y(rs.TransformNormal(Y()));
}
/// <summary>
/// Rotate and scale the X and Y components by a certain amount based on Y.
/// </summary>
/// <param name="v">The vec2 describing how much to rotate and scale the X and Y components</param>
template <typename T>
void Affine2D<T>::RotateScaleYTo(v2T& v)
{
Affine2D<T> rs = CalcRotateScale(Y(), v);
X(rs.TransformNormal(X()));
Y(rs.TransformNormal(Y()));
}
/// <summary>
/// Return the inverse of the 2x3 affine matrix.
/// </summary>
/// <returns>The inverse of this affine transform</returns>
template <typename T>
Affine2D<T> Affine2D<T>::Inverse() const
{
T det = A() * E() - D() * B();
return Affine2D<T>(E() / det, -D() / det,
-B() / det, A() / det,
(F() * B() - C() * E()) / det, (C() * D() - F() * A()) / det);
}
/// <summary>
/// Return a vec2 gotten from transforming this affine transform
/// by the vec2 passed in, but with a T component of 0, 0.
/// </summary>
/// <param name="v">The vec2 describing how much to transform by</param>
/// <returns>The centered, transformed vec2</returns>
template <typename T>
typename v2T Affine2D<T>::TransformNormal(const v2T& v) const
{
return v2T(A() * v.x + B() * v.y, D() * v.x + E() * v.y);
}
/// <summary>
/// Return a vec2 gotten from transforming this affine transform
/// by the vec2 passed in, and applying T translation.
/// </summary>
/// <param name="v">The vec2 describing how much to transform by</param>
/// <returns>The translated, transformed vec2</returns>
template <typename T>
typename v2T Affine2D<T>::TransformVector(const v2T& v) const
{
return v2T(A() * v.x + B() * v.y + C(), D() * v.x + E() * v.y + F());
}
/// <summary>
/// Return the X and Y components as a 2x2 matrix in column major order.
/// </summary>
/// <returns>The 2x2 matrix</returns>
template <typename T>
typename m2T Affine2D<T>::ToMat2ColMajor() const
{
return m2T(A(), B(),//Col0...
D(), E());//1
}
/// <summary>
/// Return the X and Y components as a 2x2 matrix in row major order.
/// </summary>
/// <returns>The 2x2 matrix</returns>
template <typename T>
typename m2T Affine2D<T>::ToMat2RowMajor() const
{
return m2T(A(), D(),//Col0...
B(), E());//1
}
/// <summary>
/// Return the 2x3 affine transform matrix as a 4x4 matrix in column major order.
/// </summary>
/// <param name="center">Whether to use T translation value or just 0 for center</param>
/// <returns>The 4x4 matrix</returns>
template <typename T>
typename m4T Affine2D<T>::ToMat4ColMajor(bool center) const
{
m4T mat(A(), B(), 0, center ? 0 : C(),//Col0...
D(), E(), 0, center ? 0 : F(),//1
0, 0, 1, 0,//2
0, 0, 0, 1);//3
return mat;
}
/// <summary>
/// Return the 2x3 affine transform matrix as a 4x4 matrix in row major order.
/// </summary>
/// <param name="center">Whether to use T translation value or just 0 for center</param>
/// <returns>The 4x4 matrix</returns>
template <typename T>
typename m4T Affine2D<T>::ToMat4RowMajor(bool center) const
{
m4T mat(A(), D(), 0, 0,
B(), E(), 0, 0,
0, 0, 1, 0,
center ? 0 : C(), center ? 0 : F(), 0, 1);
return mat;
}
/// <summary>
/// == operator which tests if all fields are equal with another Affine2D.
/// </summary>
/// <param name="affine">The Affine2D to compare to</param>
/// <returns>True if all fields are equal, else false</returns>
template <typename T>
bool Affine2D<T>::operator == (const Affine2D<T>& affine)
{
return IsClose(A(), affine.A()) &&
IsClose(B(), affine.B()) &&
IsClose(C(), affine.C()) &&
IsClose(D(), affine.D()) &&
IsClose(E(), affine.E()) &&
IsClose(F(), affine.F());
}
/// <summary>
/// * operator to multiply this affine transform by another and return the result.
/// </summary>
/// <param name="affine">The Affine2D to multiply by</param>
/// <returns>A new Affine2D which is the product of the multiplication</returns>
template <typename T>
Affine2D<T>& Affine2D<T>::operator * (const Affine2D<T>& affine)
{
X(TransformNormal(affine.X()));
Y(TransformNormal(affine.Y()));
O(TransformVector(affine.O()));
return *this;
}
/// <summary>
/// * operator to multiply this affine transform by a vec2 and return the result as a vec2.
/// </summary>
/// <param name="v">The vec2 to multiply by</param>
/// <returns>A new vec2 which is the product of the multiplication</returns>
template <typename T>
typename v2T Affine2D<T>::operator * (const v2T& v)
{
return TransformVector(v);
}
/// <summary>
/// Accessors.
/// </summary>
template <typename T> T Affine2D<T>::A() const { return m_Mat[0][0]; }//[0][0]//flam3
template <typename T> T Affine2D<T>::B() const { return m_Mat[0][1]; }//[1][0]
template <typename T> T Affine2D<T>::C() const { return m_Mat[0][2]; }//[2][0]
template <typename T> T Affine2D<T>::D() const { return m_Mat[1][0]; }//[0][1]
template <typename T> T Affine2D<T>::E() const { return m_Mat[1][1]; }//[1][1]
template <typename T> T Affine2D<T>::F() const { return m_Mat[1][2]; }//[2][1]
template <typename T> void Affine2D<T>::A(T a) { m_Mat[0][0] = a; }
template <typename T> void Affine2D<T>::B(T b) { m_Mat[0][1] = b; }
template <typename T> void Affine2D<T>::C(T c) { m_Mat[0][2] = c; }
template <typename T> void Affine2D<T>::D(T d) { m_Mat[1][0] = d; }
template <typename T> void Affine2D<T>::E(T e) { m_Mat[1][1] = e; }
template <typename T> void Affine2D<T>::F(T f) { m_Mat[1][2] = f; }
template <typename T> typename v2T Affine2D<T>::X() const { return v2T(A(), D()); }//X Axis.
template <typename T> typename v2T Affine2D<T>::Y() const { return v2T(B(), E()); }//Y Axis.
template <typename T> typename v2T Affine2D<T>::O() const { return v2T(C(), F()); }//Translation.
template <typename T> void Affine2D<T>::X(v2T& x) { A(x.x); D(x.y); }//X Axis.
template <typename T> void Affine2D<T>::Y(v2T& y) { B(y.x); E(y.y); }//Y Axis.
template <typename T> void Affine2D<T>::O(v2T& t) { C(t.x); F(t.y); }//Translation.
/// <summary>
/// Rotate and scale this affine transform and return as a copy. Orginal is unchanged.
/// </summary>
/// <param name="from">The starting point to rotate and scale from</param>
/// <param name="to">The ending point to rotate and scale to</param>
/// <returns>The newly rotated and scalled Affine2D</returns>
template <typename T>
Affine2D<T> Affine2D<T>::CalcRotateScale(v2T& from, v2T& to)
{
T a, c;
CalcRSAC(from, to, a, c);
return Affine2D<T>(a, c, -c, a, 0, 0);
}
/// <summary>
/// Never fully understood what this did or why it's named what it is.
/// But it seems to handle some rotating and scaling.
/// </summary>
/// <param name="from">The starting point to rotate and scale from</param>
/// <param name="to">The ending point to rotate and scale to</param>
/// <param name="a">a</param>
/// <param name="c">c</param>
template <typename T>
void Affine2D<T>::CalcRSAC(v2T& from, v2T& to, T& a, T& c)
{
T lsq = from.x * from.x + from.y * from.y;
a = (from.y * to.y + from.x * to.x) / lsq;
c = (from.x * to.y - from.y * to.x) / lsq;
}
}

142
Source/Ember/Affine2D.h Normal file
View File

@ -0,0 +1,142 @@
#pragma once
#include "Utils.h"
/// <summary>
/// Affine2D class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Uses matrix composition to handle the
/// affine matrix. Taken almost entirely from
/// Fractron, but using glm, and in C++.
/// Note that the matrix layout differs from flam3 so it's best to use
/// the A, B, C, D, E, F wrappers around the underlying matrix indices. But if the matrix must
/// be accessed directly, the two are laid out as such:
/// flam3: 3 columns of 2 rows each. Accessed col, row.
/// [a(0,0)][b(1,0)][c(2,0)]
/// [d(0,1)][e(1,1)][f(2,1)]
/// Ember: 2 columns of 3 rows each. Accessed col, row.
/// [a(0,0)][d(1,0)]
/// [b(0,1)][e(1,1)]
/// [c(0,2)][f(1,2)]
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API Affine2D
{
public:
Affine2D();
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="affine">The Affine2D object to copy</param>
Affine2D(const Affine2D<T>& affine)
{
Affine2D<T>::operator=<T>(affine);
}
/// <summary>
/// Copy constructor to copy an Affine2D object of type U.
/// </summary>
/// <param name="affine">The Affine2D object to copy</param>
template <typename U>
Affine2D(const Affine2D<U>& affine)
{
Affine2D<T>::operator=<U>(affine);
}
Affine2D(v2T& x, v2T& y, v2T& t);
Affine2D(T xx, T xy, T yx, T yy, T tx, T ty);
Affine2D(m4T& mat);
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="affine">The Affine2D object to copy</param>
Affine2D<T>& operator = (const Affine2D<T>& affine)
{
if (this != &affine)
Affine2D<T>::operator=<T>(affine);
return *this;
}
/// <summary>
/// Assignment operator to assign an Affine2D object of type U.
/// </summary>
/// <param name="affine">The Affine2D object to copy.</param>
/// <returns>Reference to updated self</returns>
template <typename U>
Affine2D<T>& operator = (const Affine2D<U>& affine)
{
A(T(affine.A()));
B(T(affine.B()));
C(T(affine.C()));
D(T(affine.D()));
E(T(affine.E()));
F(T(affine.F()));
return *this;
}
inline void MakeID();
inline bool IsID() const;
inline bool IsZero() const;
inline void Rotate(T angle);
inline void Translate(v2T& v);
inline void RotateScaleXTo(v2T& v);
inline void RotateScaleYTo(v2T& v);
inline Affine2D<T> Inverse() const;
inline v2T TransformNormal(const v2T& v) const;
inline v2T TransformVector(const v2T& v) const;
inline m2T ToMat2ColMajor() const;
inline m2T ToMat2RowMajor() const;
inline m4T ToMat4ColMajor(bool center = false) const;
inline m4T ToMat4RowMajor(bool center = false) const;
bool operator == (const Affine2D<T>& affine);
Affine2D<T>& operator * (const Affine2D<T>& affine);
v2T operator * (const v2T& v);
inline T A() const;
inline T B() const;
inline T C() const;
inline T D() const;
inline T E() const;
inline T F() const;
inline void A(T a);
inline void B(T b);
inline void C(T c);
inline void D(T d);
inline void E(T e);
inline void F(T f);
inline v2T X() const;
inline v2T Y() const;
inline v2T O() const;
inline void X(v2T& x);
inline void Y(v2T& y);
inline void O(v2T& t);
//static Affine2D Identity();//Complains about inline.
static inline Affine2D CalcRotateScale(v2T& from, v2T& to);
static inline void CalcRSAC(v2T& from, v2T& to, T& a, T& c);
m23T m_Mat;
};
//This class had to be implemented in a cpp file because the compiler was breaking.
//So the explicit instantiation must be declared here rather than in Ember.cpp where
//all of the other classes are done.
template EMBER_API class Affine2D<float>;
#ifdef DO_DOUBLE
template EMBER_API class Affine2D<double>;
#endif
}

252
Source/Ember/CarToRas.h Normal file
View File

@ -0,0 +1,252 @@
#pragma once
#include "Point.h"
/// <summary>
/// CarToRas class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// When iterating, everything is positioned in terms of a carteseian plane with 0,0 in the center like so:
/// [-1,1] [1,1]
/// [-1,-1] [1,-1]
/// However, when accumulating to the histogram, the data is stored in the traditional raster coordinate system
/// of 0,0 at the top left and x,y at the bottom right. This class provides functionality to convert from one
/// to the other and is used when accumulating a sub batch of iteration results to the histogram.
/// Note the functions use reference arguments for the converted values because they are slightly faster than returning a value.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API CarToRas
{
public:
/// <summary>
/// Empty constructor. This class should never be used unless it's been properly constructed with the constructor that takes arguments.
/// </summary>
CarToRas()
{
}
/// <summary>
/// Constructor that takes arguments to set up the bounds and passes them to Init().
/// </summary>
/// <param name="carLlX">The lower left x of the cartesian plane</param>
/// <param name="carLlY">The lower left y of the cartesian plane</param>
/// <param name="carUrX">The upper right x of the cartesian plane</param>
/// <param name="carUrY">The upper right y of the cartesian plane</param>
/// <param name="rasW">The width in pixels of the raster image/histogram</param>
/// <param name="rasH">The height in pixels of the raster image/histogram</param>
/// <param name="aspectRatio">The aspect ratio, generally 1</param>
CarToRas(T carLlX, T carLlY, T carUrX, T carUrY, unsigned int rasW, unsigned int rasH, T aspectRatio)
{
Init(carLlX, carLlY, carUrX, carUrY, rasW, rasH, aspectRatio);
}
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="carToRas">The CarToRas object to copy</param>
CarToRas(const CarToRas<T>& carToRas)
{
CarToRas<T>::operator=<T>(carToRas);
}
/// <summary>
/// Copy constructor to copy a CarToRas object of type U.
/// </summary>
/// <param name="carToRas">The CarToRas object to copy</param>
template <typename U>
CarToRas(const CarToRas<U>& carToRas)
{
CarToRas<T>::operator=<U>(carToRas);
}
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="carToRas">The CarToRas object to copy</param>
CarToRas<T>& operator = (const CarToRas<T>& carToRas)
{
if (this != &carToRas)
CarToRas<T>::operator=<T>(carToRas);
return *this;
}
/// <summary>
/// Assignment operator to assign a CarToRas object of type U.
/// </summary>
/// <param name="carToRas">The CarToRas object to copy.</param>
/// <returns>Reference to updated self</returns>
template <typename U>
CarToRas<T>& operator = (const CarToRas<U>& carToRas)
{
m_RasWidth = carToRas.RasWidth();
m_RasHeight = carToRas.RasHeight();
m_OneRow = T(carToRas.OneRow());
m_OneCol = T(carToRas.OneCol());
m_PixPerImageUnitW = T(carToRas.PixPerImageUnitW());
m_RasLlX = T(carToRas.RasLlX());
m_PixPerImageUnitH = T(carToRas.PixPerImageUnitH());
m_RasLlY = T(carToRas.RasLlY());
m_CarLlX = T(carToRas.CarLlX());
m_CarLlY = T(carToRas.CarLlY());
m_CarUrX = T(carToRas.CarUrX());
m_CarUrY = T(carToRas.CarUrY());
m_PadCarLlX = T(carToRas.PadCarLlX());
m_PadCarLlY = T(carToRas.PadCarLlY());
m_PadCarUrX = T(carToRas.PadCarUrX());
m_PadCarUrY = T(carToRas.PadCarUrY());
return *this;
}
/// <summary>
/// Initialize the dimensions with the specified bounds.
/// </summary>
/// <param name="carLlX">The lower left x of the cartesian plane</param>
/// <param name="carLlY">The lower left y of the cartesian plane</param>
/// <param name="carUrX">The upper right x of the cartesian plane</param>
/// <param name="carUrY">The upper right y of the cartesian plane</param>
/// <param name="rasW">The width in pixels of the raster image/histogram</param>
/// <param name="rasH">The height in pixels of the raster image/histogram</param>
/// <param name="aspectRatio">The aspect ratio, generally 1</param>
void Init(T carLlX, T carLlY, T carUrX, T carUrY, unsigned int rasW, unsigned int rasH, T aspectRatio)
{
m_RasWidth = rasW;
m_RasHeight = rasH;
m_CarLlX = carLlX;
m_CarLlY = carLlY;
m_CarUrX = carUrX;
m_CarUrY = carUrY;
T carW = m_CarUrX - m_CarLlX;//Right minus left.
T carH = m_CarUrY - m_CarLlY;//Top minus bottom.
T invSizeW = T(1.0) / carW;
T invSizeH = T(1.0) / carH;
m_PixPerImageUnitW = (T)rasW * invSizeW;
m_RasLlX = m_PixPerImageUnitW * carLlX;
m_PixPerImageUnitH = (T)rasH * invSizeH;
m_RasLlY = m_PixPerImageUnitH * carLlY;
T m_OneRow = abs(m_CarUrY - m_CarLlY) / m_RasHeight;
T m_OneCol = abs(m_CarUrX - m_CarLlX) / m_RasWidth;
m_PadCarLlX = m_CarLlX + m_OneCol;
m_PadCarUrX = m_CarUrX - m_OneCol;
m_PadCarLlY = m_CarLlY + m_OneRow;
m_PadCarUrY = m_CarUrY - m_OneRow;
}
/// <summary>
/// Convert a cartesian x, y coordinate to a raster x, y coordinate.
/// This will flip the Y coordinate, so points that hit the bottom of the cartesian plane will
/// be mapped to the top of the histogram and vice versa.
/// There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test,
/// but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform an additional check after this call to make sure the
/// mapped point is in bounds.
/// </summary>
/// <param name="cartX">The cartesian x</param>
/// <param name="cartY">The cartesian y</param>
/// <param name="rasX">The converted raster x</param>
/// <param name="rasY">The converted raster y</param>
inline void Convert(T cartX, T cartY, unsigned int& rasX, unsigned int& rasY)
{
rasX = (unsigned int)(m_PixPerImageUnitW * cartX - m_RasLlX);
rasY = (unsigned int)(m_RasLlY - (m_PixPerImageUnitH * cartY));
}
/// <summary>
/// Convert a cartesian x, y coordinate to a single raster buffer index.
/// This will flip the Y coordinate, so points that hit the bottom of the cartesian plane will
/// be mapped to the top of the histogram and vice versa.
/// There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test,
/// but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform an additional check after this call to make sure the
/// mapped point is in bounds.
/// </summary>
/// <param name="cartX">The cartesian x</param>
/// <param name="cartY">The cartesian y</param>
/// <param name="singleBufferIndex">The converted single raster buffer index</param>
inline void Convert(T cartX, T cartY, unsigned int& singleBufferIndex)
{
singleBufferIndex = (unsigned int)(m_PixPerImageUnitW * cartX - m_RasLlX) + (m_RasWidth * (unsigned int)(m_PixPerImageUnitH * cartY - m_RasLlY));
}
/// <summary>
/// Convert a cartesian x, y point to a single raster buffer index.
/// This will flip the Y coordinate, so points that hit the bottom of the cartesian plane will
/// be mapped to the top of the histogram and vice versa.
/// This is the most efficient possible way of converting, consisting of only
/// a multiply and subtract per coordinate element.
/// There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test,
/// but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform an additional check after this call to make sure the
/// mapped point is in bounds.
/// </summary>
/// <param name="point">The cartesian y</param>
/// <param name="singleBufferIndex">The converted single raster buffer index</param>
inline void Convert(Point<T>& point, unsigned int& singleBufferIndex)
{
singleBufferIndex = (unsigned int)(m_PixPerImageUnitW * point.m_X - m_RasLlX) + (m_RasWidth * (unsigned int)(m_PixPerImageUnitH * point.m_Y - m_RasLlY));
}
/// <summary>
/// Determine if a point in the cartesian plane can be converted to a point within the raster plane.
/// There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test,
/// but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform an additional check after this call to make sure the
/// mapped point is in bounds.
/// </summary>
/// <param name="point">The point to test</param>
/// <returns>True if within bounds, else false</returns>
inline bool InBounds(Point<T>& point)
{
//Debug check for hitting the very first pixel in the image.
//if (point.m_Y > m_CarLlY && point.m_Y <= m_PadCarLlY && //Mapped to top row...
// point.m_X > m_CarLlX && point.m_X <= m_PadCarLlX)//...first col.
//{
// cout << "First pixel hit." << endl;
//}
return point.m_X >= m_CarLlX &&
point.m_X < m_CarUrX &&
point.m_Y < m_CarUrY &&
point.m_Y >= m_CarLlY;
}
/// <summary>
/// Accessors.
/// </summary>
inline unsigned int RasWidth() const { return m_RasWidth; }
inline unsigned int RasHeight() const { return m_RasHeight; }
inline T OneRow() const { return m_OneRow; }
inline T OneCol() const { return m_OneCol; }
inline T PixPerImageUnitW() const { return m_PixPerImageUnitW; }
inline T RasLlX() const { return m_RasLlX; }
inline T PixPerImageUnitH() const { return m_PixPerImageUnitH; }
inline T RasLlY() const { return m_RasLlY; }
inline T CarLlX() const { return m_CarLlX; }
inline T CarLlY() const { return m_CarLlY; }
inline T CarUrX() const { return m_CarUrX; }
inline T CarUrY() const { return m_CarUrY; }
inline T PadCarLlX() const { return m_PadCarLlX; }
inline T PadCarLlY() const { return m_PadCarLlY; }
inline T PadCarUrX() const { return m_PadCarUrX; }
inline T PadCarUrY() const { return m_PadCarUrY; }
private:
unsigned int m_RasWidth, m_RasHeight;//The width and height of the raster image.
T m_OneRow;//The distance that one raster row represents in the cartesian plane.
T m_OneCol;//The distance that one raster column represents in the cartesian plane.
T m_PixPerImageUnitW;//The number of columns in the raster plane that a horizontal distance of 1 in the cartesian plane represents. The higher the number, the more zoomed in.
T m_RasLlX;//The lower left x of the raster image plane.
T m_PixPerImageUnitH;//The number of rows in the raster plane that a vertical distance of 1 in the cartesian plane represents. The higher the number, the more zoomed in.
T m_RasLlY;//The lower left y of the raster image plane.
T m_CarLlX, m_CarLlY, m_CarUrX, m_CarUrY;//The bounds of the cartesian plane.
T m_PadCarLlX, m_PadCarLlY, m_PadCarUrX, m_PadCarUrY;//The bounds of the cartesian plane padded by one raster row and column on each side.
};
}

View File

@ -0,0 +1,340 @@
#pragma once
#include "SpatialFilter.h"
/// <summary>
/// DensityFilter class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// A base class with virtual functions to allow both templating and polymorphism to work together.
/// Derived classes will implement all of these functions.
/// </summary>
class EMBER_API DensityFilterBase
{
public:
DensityFilterBase() { }
virtual ~DensityFilterBase() { }
virtual int FilterWidth() { return 0; }
};
/// <summary>
/// The density estimation filter is used after iterating, but before final accumulation.
/// It's a variable width Gaussian filter, whose width is inversely proportional
/// to the number of hits a given histogram cell has received.
/// That means the fewer hits in a cell, the more blur is applied. The greater the hits,
/// the less blur.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API DensityFilter : public DensityFilterBase
{
public:
/// <summary>
/// Constructor that assigns various fields but does not create the actual filter vector.
/// This is done because filter creation could fail, so the user must manually call it
/// after construction.
/// </summary>
/// <param name="minRad">The minimum filter radius</param>
/// <param name="maxRad">The maximum filter radius</param>
/// <param name="curve">The curve of the filter</param>
/// <param name="supersample">The supersample of the ember this filter will be used with</param>
DensityFilter(T minRad, T maxRad, T curve, unsigned int supersample)
{
m_MinRad = minRad;
m_MaxRad = maxRad;
m_Curve = curve;
m_Supersample = supersample;
m_MaxFilterIndex = 0;
//Make sure the values make sense.
if (m_Curve <= 0.0)
m_Curve = T(0.5);
if (m_MaxRad < m_MinRad)
m_MaxRad = m_MinRad + 1;
}
/// <summary>
/// Copy constructor.
/// </summary>
/// <param name="filter">The DensityFilter object to copy</param>
DensityFilter(const DensityFilter<T>& filter)
{
*this = filter;
}
/// <summary>
/// Assignment operator.
/// </summary>
/// <param name="filter">The DensityFilter object to copy.</param>
/// <returns>Reference to updated self</returns>
DensityFilter<T>& operator = (const DensityFilter<T>& filter)
{
if (this != &filter)
{
m_MinRad = filter.m_MinRad;
m_MaxRad = filter.m_MaxRad;
m_Curve = filter.m_Curve;
m_Supersample = filter.m_Supersample;
m_KernelSize = filter.m_KernelSize;
m_MaxFilterIndex = filter.m_MaxFilterIndex;
m_MaxFilteredCounts = filter.m_MaxFilteredCounts;
m_FilterWidth = filter.m_FilterWidth;
m_Coefs = filter.m_Coefs;
m_Widths = filter.m_Widths;
}
return *this;
}
/// <summary>
/// Create the filter vector of up to 10M entries.
/// If more than that are requested, it isn't created and
/// false is returned.
/// </summary>
/// <returns>True if success, else false.</returns>
bool Create()
{
int i, j, w;
int intFilterCount, maxIndex;
int rowSize;
int filterLoop;
int keepThresh = 100;
unsigned int filterCoefIndex = 0;
T decFilterCount;
T finalMinRad = m_MinRad * m_Supersample + 1;//Should scale the filter width by the oversample.
T finalMaxRad = m_MaxRad * m_Supersample + 1;//The '+1' comes from the assumed distance to the first pixel.
GaussianFilter<T> gaussianFilter(m_MaxRad, m_Supersample);
m_KernelSize = 0;
m_MaxFilterIndex = 0;
//Calculate how many filter kernels are needed based on the decay function
//
// num filters = (de_max_width / de_min_width)^(1 / estimator_curve)
//
decFilterCount = pow(finalMaxRad / finalMinRad, T(1.0) / m_Curve);
if (decFilterCount > 1e7)//Too many filters.
return false;
intFilterCount = (int)ceil(decFilterCount);
//Condense the smaller kernels to save space.
if (intFilterCount > keepThresh)
{
maxIndex = (int)ceil(DE_THRESH + pow(T(intFilterCount - DE_THRESH), m_Curve)) + 1;
m_MaxFilteredCounts = (int)pow(T(maxIndex - DE_THRESH), T(1.0) / m_Curve) + DE_THRESH;
}
else
{
maxIndex = intFilterCount;
m_MaxFilteredCounts = maxIndex;
}
//Allocate the memory for these filters and the hit/width lookup array.
rowSize = (int)(2 * ceil(finalMaxRad) - 1);
m_FilterWidth = (rowSize - 1) / 2;
m_KernelSize = (m_FilterWidth + 1) * (2 + m_FilterWidth) / 2;
m_Coefs.resize(maxIndex * m_KernelSize);
m_Widths.resize(maxIndex);
//Generate the filter coefficients.
for (filterLoop = 0; filterLoop < maxIndex; filterLoop++)
{
int dej, dek;
int coefIndex;
T filterSum = 0.0;
T filterVal;
T filterHeight;
T loopAdjust;
//Calculate the filter width for this number of hits in a bin.
if (filterLoop < keepThresh)
{
filterHeight = (finalMaxRad / pow(T(filterLoop + 1), m_Curve));
}
else
{
loopAdjust = pow(T(filterLoop - keepThresh), (T(1.0) / m_Curve)) + keepThresh;
filterHeight = (finalMaxRad / pow(loopAdjust + 1, m_Curve));
}
//Once we've reached the min radius, don't populate any more.
if (filterHeight <= finalMinRad)
{
filterHeight = finalMinRad;
m_MaxFilterIndex = filterLoop;
}
m_Widths[filterLoop] = filterHeight;
//Calculate norm of kernel separately (easier).
for (dej = -m_FilterWidth; dej <= m_FilterWidth; dej++)
{
for (dek = -m_FilterWidth; dek <= m_FilterWidth; dek++)
{
filterVal = sqrt((T)(dej * dej + dek * dek)) / filterHeight;
//Only populate the coefs within this radius.
if (filterVal <= 1.0)
filterSum += gaussianFilter.Filter(gaussianFilter.Support() * filterVal);
}
}
coefIndex = filterLoop * m_KernelSize;
//Calculate the unique entries of the kernel.
for (dej = 0; dej <= m_FilterWidth; dej++)
{
for (dek = 0; dek <= dej; dek++)
{
filterVal = sqrt(T(dej * dej + dek * dek)) / filterHeight;
//Only populate the coefs within this radius.
if (filterVal > 1.0)
m_Coefs[coefIndex] = 0.0;
else
m_Coefs[coefIndex] = gaussianFilter.Filter(gaussianFilter.Support() * filterVal) / filterSum;
coefIndex++;
}
}
if (m_MaxFilterIndex > 0)
break;
}
if (m_MaxFilterIndex == 0)
m_MaxFilterIndex = maxIndex - 1;
w = m_FilterWidth + 1;
m_CoefIndices.resize(w * w);
//This will populate one quadrant of filter indices.
//Really only need 1/8th, but that would require a sparse matrix.
for (j = 0; j <= m_FilterWidth; j++)
{
for (i = 0; i <= j; i++, filterCoefIndex++)
{
if (j == 0 && i == 0)
{
m_CoefIndices[(j * w) + i] = filterCoefIndex;
}
else if (i == 0)
{
m_CoefIndices[(0 * w) + j] = filterCoefIndex;
m_CoefIndices[(j * w) + 0] = filterCoefIndex;
}
else if (j == i)
{
m_CoefIndices[(j * w) + i] = filterCoefIndex;
}
else
{
m_CoefIndices[(i * w) + j] = filterCoefIndex;
m_CoefIndices[(j * w) + i] = filterCoefIndex;
}
}
}
return true;
}
/// <summary>
/// Return whether the requested dimensions are valid.
/// Meaning, is the requested filter size less than or equal to 10M?
/// </summary>
/// <returns>True if requested filter size is less than or equal to 10M, else false.</returns>
inline bool Valid() const
{
T finalMaxRad = m_MaxRad * m_Supersample + 1;
T finalMinRad = m_MinRad * m_Supersample + 1;
return pow(finalMaxRad / finalMinRad, T(1.0) / m_Curve) <= 1e7;
}
/// <summary>
/// Return a string representation of this density estimation filter.
/// </summary>
/// <returns>The string representation of this density estimation filter</returns>
string ToString() const
{
unsigned int i, j, coefIndex = 0, w = m_FilterWidth + 1;
stringstream ss;
ss
<< "Density Filter:" << endl
<< " Min radius: " << MinRad() << endl
<< " Max radius: " << MaxRad() << endl
<< " Curve: " << Curve() << endl
<< " Kernel size: " << KernelSize() << endl
<< " Max filter index: " << MaxFilterIndex() << endl
<< "Max Filtered counts: " << MaxFilteredCounts() << endl
<< " Filter width: " << FilterWidth() << endl;
ss << "Coefficients: " << endl;
for (i = 0; i < m_Widths.size(); i++)
{
for (coefIndex = 0; coefIndex < m_KernelSize; coefIndex++)
ss << "Kernel[" << i << "].Coefs[" << coefIndex << "]: " << m_Coefs[(i * m_KernelSize) + coefIndex] << endl;
}
ss << endl << "Widths: " << endl;
for (i = 0; i < m_Widths.size(); i++)
{
ss << "Widths[" << i << "]: " << m_Widths[i] << endl;
}
for (i = 0; i < w; i++)
{
for (j = 0; j < w; j++)
{
cout << std::setw(2) << std::setfill('0') << m_CoefIndices[i * w + j] << "\t";
}
cout << endl;
}
return ss.str();
}
/// <summary>
/// Accessors.
/// </summary>
inline T MinRad() const { return m_MinRad; }
inline T MaxRad() const { return m_MaxRad; }
inline T Curve() const { return m_Curve; }
inline unsigned int KernelSize() const { return m_KernelSize; }
inline unsigned int MaxFilterIndex() const { return m_MaxFilterIndex; }
inline unsigned int MaxFilteredCounts() const { return m_MaxFilteredCounts; }
virtual int FilterWidth() const { return m_FilterWidth; }
inline unsigned int BufferSize() const { return (unsigned int)m_Widths.size(); }
inline unsigned int CoefsSizeBytes() const { return BufferSize() * m_KernelSize * sizeof(T); }
inline unsigned int WidthsSizeBytes() const { return BufferSize() * sizeof(T); }
inline unsigned int CoefsIndicesSizeBytes() const { return unsigned int(m_CoefIndices.size() * sizeof(unsigned int)); }
inline const T* Coefs() const { return m_Coefs.data(); }
inline const T* Widths() const { return m_Widths.data(); }
inline const unsigned int* CoefIndices() const { return m_CoefIndices.data(); }
private:
T m_MinRad;
T m_MaxRad;//The original specified filter radius.
T m_Curve;
unsigned int m_Supersample;
unsigned int m_KernelSize;
unsigned int m_MaxFilterIndex;
unsigned int m_MaxFilteredCounts;
int m_FilterWidth;//The new radius after scaling for super sample and rounding. This is what's actually used.
vector<T> m_Coefs;
vector<T> m_Widths;
vector<unsigned int> m_CoefIndices;
};
}

20
Source/Ember/DllMain.cpp Normal file
View File

@ -0,0 +1,20 @@
#include "EmberPch.h"
/// <summary>
/// Generated by Visual Studio to make the DLL run properly.
/// </summary>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

410
Source/Ember/Ember.cpp Normal file
View File

@ -0,0 +1,410 @@
#include "EmberPch.h"
#include "EmberDefines.h"
#include "Isaac.h"
#include "Ember.h"
#include "Utils.h"
#include "Iterator.h"
#include "Palette.h"
#include "PaletteList.h"
#include "Point.h"
#include "Variation.h"
#include "Variations01.h"
#include "Variations02.h"
#include "Variations03.h"
#include "Variations04.h"
#include "Variations05.h"
#include "VariationsDC.h"
#include "VariationList.h"
#include "Affine2D.h"
#include "Xform.h"
#include "EmberToXml.h"
#include "XmlToEmber.h"
#include "SpatialFilter.h"
#include "DensityFilter.h"
#include "TemporalFilter.h"
#include "Interpolate.h"
#include "Renderer.h"
#include "Timing.h"
#include "SheepTools.h"
/// <summary>
/// Explicit instantiation of all templated classes which aren't implemented in cpp files.
/// All new templated classes, such as new variations, must be added here.
/// Additional instances of static class member variables.
/// </summary>
namespace EmberNs
{
bool Timing::m_TimingInit = false;
int Timing::m_ProcessorCount;
LARGE_INTEGER Timing::m_Freq;
auto_ptr<QTIsaac<ISAAC_SIZE, ISAAC_INT>> QTIsaac<ISAAC_SIZE, ISAAC_INT>::GlobalRand = auto_ptr<QTIsaac<ISAAC_SIZE, ISAAC_INT>>(new QTIsaac<ISAAC_SIZE, ISAAC_INT>());
#define EXPORTPREPOSTREGVAR(varName, T) \
template EMBER_API class varName##Variation<T>; \
template EMBER_API class Pre##varName##Variation<T>; \
template EMBER_API class Post##varName##Variation<T>;
#define EXPORT_SINGLE_TYPE_EMBER(T) \
template EMBER_API class Point<T>; \
template EMBER_API class Color<T>; \
template EMBER_API class Affine2D<T>; \
template EMBER_API class Palette<T>; \
template EMBER_API class PaletteList<T>; \
template EMBER_API class Iterator<T>; \
template EMBER_API class StandardIterator<T>; \
template EMBER_API class XaosIterator<T>; \
template EMBER_API class Xform<T>; \
template EMBER_API class IteratorHelper<T>; \
template EMBER_API class Variation<T>; \
template EMBER_API class ParamWithName<T>; \
template EMBER_API class ParametricVariation<T>; \
EXPORTPREPOSTREGVAR(Linear, T) \
EXPORTPREPOSTREGVAR(Sinusoidal, T) \
EXPORTPREPOSTREGVAR(Spherical, T) \
EXPORTPREPOSTREGVAR(Swirl, T) \
EXPORTPREPOSTREGVAR(Horseshoe, T) \
EXPORTPREPOSTREGVAR(Polar, T) \
EXPORTPREPOSTREGVAR(Handkerchief, T) \
EXPORTPREPOSTREGVAR(Heart, T) \
EXPORTPREPOSTREGVAR(Disc, T) \
EXPORTPREPOSTREGVAR(Spiral, T) \
EXPORTPREPOSTREGVAR(Hyperbolic, T) \
EXPORTPREPOSTREGVAR(Diamond, T) \
EXPORTPREPOSTREGVAR(Ex, T) \
EXPORTPREPOSTREGVAR(Julia, T) \
EXPORTPREPOSTREGVAR(Bent, T) \
EXPORTPREPOSTREGVAR(Waves, T) \
EXPORTPREPOSTREGVAR(Fisheye, T) \
EXPORTPREPOSTREGVAR(Popcorn, T) \
EXPORTPREPOSTREGVAR(Exponential, T) \
EXPORTPREPOSTREGVAR(Power, T) \
EXPORTPREPOSTREGVAR(Cosine, T) \
EXPORTPREPOSTREGVAR(Rings, T) \
EXPORTPREPOSTREGVAR(Fan, T) \
EXPORTPREPOSTREGVAR(Blob, T) \
EXPORTPREPOSTREGVAR(Pdj, T) \
EXPORTPREPOSTREGVAR(Fan2, T) \
EXPORTPREPOSTREGVAR(Rings2, T) \
EXPORTPREPOSTREGVAR(Eyefish, T) \
EXPORTPREPOSTREGVAR(Bubble, T) \
EXPORTPREPOSTREGVAR(Cylinder, T) \
EXPORTPREPOSTREGVAR(Perspective, T) \
EXPORTPREPOSTREGVAR(Noise, T) \
EXPORTPREPOSTREGVAR(JuliaNGeneric, T) \
EXPORTPREPOSTREGVAR(JuliaScope, T) \
EXPORTPREPOSTREGVAR(Blur, T) \
EXPORTPREPOSTREGVAR(GaussianBlur, T) \
EXPORTPREPOSTREGVAR(RadialBlur, T) \
EXPORTPREPOSTREGVAR(Pie, T) \
EXPORTPREPOSTREGVAR(Ngon, T) \
EXPORTPREPOSTREGVAR(Curl, T) \
EXPORTPREPOSTREGVAR(Rectangles, T) \
EXPORTPREPOSTREGVAR(Arch, T) \
EXPORTPREPOSTREGVAR(Tangent, T) \
EXPORTPREPOSTREGVAR(Square, T) \
EXPORTPREPOSTREGVAR(Rays, T) \
EXPORTPREPOSTREGVAR(Blade, T) \
EXPORTPREPOSTREGVAR(Secant2, T) \
EXPORTPREPOSTREGVAR(TwinTrian, T) \
EXPORTPREPOSTREGVAR(Cross, T) \
EXPORTPREPOSTREGVAR(Disc2, T) \
EXPORTPREPOSTREGVAR(SuperShape, T) \
EXPORTPREPOSTREGVAR(Flower, T) \
EXPORTPREPOSTREGVAR(Conic, T) \
EXPORTPREPOSTREGVAR(Parabola, T) \
EXPORTPREPOSTREGVAR(Bent2, T) \
EXPORTPREPOSTREGVAR(Bipolar, T) \
EXPORTPREPOSTREGVAR(Boarders, T) \
EXPORTPREPOSTREGVAR(Butterfly, T) \
EXPORTPREPOSTREGVAR(Cell, T) \
EXPORTPREPOSTREGVAR(Cpow, T) \
EXPORTPREPOSTREGVAR(Curve, T) \
EXPORTPREPOSTREGVAR(Edisc, T) \
EXPORTPREPOSTREGVAR(Elliptic, T) \
EXPORTPREPOSTREGVAR(Escher, T) \
EXPORTPREPOSTREGVAR(Foci, T) \
EXPORTPREPOSTREGVAR(LazySusan, T) \
EXPORTPREPOSTREGVAR(Loonie, T) \
EXPORTPREPOSTREGVAR(Modulus, T) \
EXPORTPREPOSTREGVAR(Oscilloscope, T) \
EXPORTPREPOSTREGVAR(Polar2, T) \
EXPORTPREPOSTREGVAR(Popcorn2, T) \
EXPORTPREPOSTREGVAR(Scry, T) \
EXPORTPREPOSTREGVAR(Separation, T) \
EXPORTPREPOSTREGVAR(Split, T) \
EXPORTPREPOSTREGVAR(Splits, T) \
EXPORTPREPOSTREGVAR(Stripes, T) \
EXPORTPREPOSTREGVAR(Wedge, T) \
EXPORTPREPOSTREGVAR(WedgeJulia, T) \
EXPORTPREPOSTREGVAR(WedgeSph, T) \
EXPORTPREPOSTREGVAR(Whorl, T) \
EXPORTPREPOSTREGVAR(Waves2, T) \
EXPORTPREPOSTREGVAR(Exp, T) \
EXPORTPREPOSTREGVAR(Log, T) \
EXPORTPREPOSTREGVAR(Sin, T) \
EXPORTPREPOSTREGVAR(Cos, T) \
EXPORTPREPOSTREGVAR(Tan, T) \
EXPORTPREPOSTREGVAR(Sec, T) \
EXPORTPREPOSTREGVAR(Csc, T) \
EXPORTPREPOSTREGVAR(Cot, T) \
EXPORTPREPOSTREGVAR(Sinh, T) \
EXPORTPREPOSTREGVAR(Cosh, T) \
EXPORTPREPOSTREGVAR(Tanh, T) \
EXPORTPREPOSTREGVAR(Sech, T) \
EXPORTPREPOSTREGVAR(Csch, T) \
EXPORTPREPOSTREGVAR(Coth, T) \
EXPORTPREPOSTREGVAR(Auger, T) \
EXPORTPREPOSTREGVAR(Flux, T) \
EXPORTPREPOSTREGVAR(Hemisphere, T) \
EXPORTPREPOSTREGVAR(Epispiral, T) \
EXPORTPREPOSTREGVAR(Bwraps, T) \
EXPORTPREPOSTREGVAR(Extrude, T) \
EXPORTPREPOSTREGVAR(BlurCircle, T) \
EXPORTPREPOSTREGVAR(BlurZoom, T) \
EXPORTPREPOSTREGVAR(BlurPixelize, T) \
EXPORTPREPOSTREGVAR(Crop, T) \
EXPORTPREPOSTREGVAR(BCircle, T) \
EXPORTPREPOSTREGVAR(BlurLinear, T) \
EXPORTPREPOSTREGVAR(BlurSquare, T) \
EXPORTPREPOSTREGVAR(Boarders2, T) \
EXPORTPREPOSTREGVAR(Cardioid, T) \
EXPORTPREPOSTREGVAR(Checks, T) \
EXPORTPREPOSTREGVAR(Circlize, T) \
EXPORTPREPOSTREGVAR(Circlize2, T) \
EXPORTPREPOSTREGVAR(CosWrap, T) \
EXPORTPREPOSTREGVAR(DeltaA, T) \
EXPORTPREPOSTREGVAR(Expo, T) \
EXPORTPREPOSTREGVAR(FDisc, T) \
EXPORTPREPOSTREGVAR(Fibonacci, T) \
EXPORTPREPOSTREGVAR(Fibonacci2, T) \
EXPORTPREPOSTREGVAR(Glynnia, T) \
EXPORTPREPOSTREGVAR(GridOut, T) \
EXPORTPREPOSTREGVAR(Hole, T) \
EXPORTPREPOSTREGVAR(Hypertile, T) \
EXPORTPREPOSTREGVAR(Hypertile1, T) \
EXPORTPREPOSTREGVAR(Hypertile2, T) \
EXPORTPREPOSTREGVAR(Hypertile3D, T) \
EXPORTPREPOSTREGVAR(Hypertile3D1, T) \
EXPORTPREPOSTREGVAR(Hypertile3D2, T) \
EXPORTPREPOSTREGVAR(IDisc, T) \
EXPORTPREPOSTREGVAR(Julian2, T) \
EXPORTPREPOSTREGVAR(JuliaQ, T) \
EXPORTPREPOSTREGVAR(Murl, T) \
EXPORTPREPOSTREGVAR(Murl2, T) \
EXPORTPREPOSTREGVAR(NPolar, T) \
EXPORTPREPOSTREGVAR(Ortho, T) \
EXPORTPREPOSTREGVAR(Poincare, T) \
EXPORTPREPOSTREGVAR(Poincare3D, T) \
EXPORTPREPOSTREGVAR(Polynomial, T) \
EXPORTPREPOSTREGVAR(PSphere, T) \
EXPORTPREPOSTREGVAR(Rational3, T) \
EXPORTPREPOSTREGVAR(Ripple, T) \
EXPORTPREPOSTREGVAR(Sigmoid, T) \
EXPORTPREPOSTREGVAR(SinusGrid, T) \
EXPORTPREPOSTREGVAR(Stwin, T) \
EXPORTPREPOSTREGVAR(TwoFace, T) \
EXPORTPREPOSTREGVAR(Unpolar, T) \
EXPORTPREPOSTREGVAR(WavesN, T) \
EXPORTPREPOSTREGVAR(XHeart, T) \
EXPORTPREPOSTREGVAR(Barycentroid, T) \
EXPORTPREPOSTREGVAR(BiSplit, T) \
EXPORTPREPOSTREGVAR(Crescents, T) \
EXPORTPREPOSTREGVAR(Mask, T) \
EXPORTPREPOSTREGVAR(Cpow2, T) \
EXPORTPREPOSTREGVAR(Curl3D, T) \
EXPORTPREPOSTREGVAR(Disc3D, T) \
EXPORTPREPOSTREGVAR(Funnel, T) \
EXPORTPREPOSTREGVAR(Linear3D, T) \
EXPORTPREPOSTREGVAR(PowBlock, T) \
EXPORTPREPOSTREGVAR(Squirrel, T) \
EXPORTPREPOSTREGVAR(Ennepers, T) \
EXPORTPREPOSTREGVAR(SphericalN, T) \
EXPORTPREPOSTREGVAR(Kaleidoscope, T) \
EXPORTPREPOSTREGVAR(GlynnSim1, T) \
EXPORTPREPOSTREGVAR(GlynnSim2, T) \
EXPORTPREPOSTREGVAR(GlynnSim3, T) \
EXPORTPREPOSTREGVAR(Starblur, T) \
EXPORTPREPOSTREGVAR(Sineblur, T) \
EXPORTPREPOSTREGVAR(Circleblur, T) \
EXPORTPREPOSTREGVAR(CropN, T) \
EXPORTPREPOSTREGVAR(ShredRad, T) \
EXPORTPREPOSTREGVAR(Blob2, T) \
EXPORTPREPOSTREGVAR(Julia3D, T) \
EXPORTPREPOSTREGVAR(Julia3Dz, T) \
EXPORTPREPOSTREGVAR(LinearT, T) \
EXPORTPREPOSTREGVAR(LinearT3D, T) \
EXPORTPREPOSTREGVAR(Ovoid, T) \
EXPORTPREPOSTREGVAR(Ovoid3D, T) \
EXPORTPREPOSTREGVAR(Spirograph, T) \
EXPORTPREPOSTREGVAR(Petal, T) \
EXPORTPREPOSTREGVAR(RoundSpher, T) \
EXPORTPREPOSTREGVAR(RoundSpher3D, T) \
EXPORTPREPOSTREGVAR(SpiralWing, T) \
EXPORTPREPOSTREGVAR(Squarize, T) \
EXPORTPREPOSTREGVAR(Sschecks, T) \
EXPORTPREPOSTREGVAR(PhoenixJulia, T) \
EXPORTPREPOSTREGVAR(Mobius, T) \
EXPORTPREPOSTREGVAR(MobiusN, T) \
EXPORTPREPOSTREGVAR(MobiusStrip, T) \
EXPORTPREPOSTREGVAR(Lissajous, T) \
EXPORTPREPOSTREGVAR(Svf, T) \
EXPORTPREPOSTREGVAR(Target, T) \
EXPORTPREPOSTREGVAR(Taurus, T) \
EXPORTPREPOSTREGVAR(Collideoscope, T) \
EXPORTPREPOSTREGVAR(BMod, T) \
EXPORTPREPOSTREGVAR(BSwirl, T) \
EXPORTPREPOSTREGVAR(BTransform, T) \
EXPORTPREPOSTREGVAR(BCollide, T) \
EXPORTPREPOSTREGVAR(Eclipse, T) \
EXPORTPREPOSTREGVAR(FlipCircle, T) \
EXPORTPREPOSTREGVAR(FlipY, T) \
EXPORTPREPOSTREGVAR(ECollide, T) \
EXPORTPREPOSTREGVAR(EJulia, T) \
EXPORTPREPOSTREGVAR(EMod, T) \
EXPORTPREPOSTREGVAR(EMotion, T) \
EXPORTPREPOSTREGVAR(EPush, T) \
EXPORTPREPOSTREGVAR(ERotate, T) \
EXPORTPREPOSTREGVAR(EScale, T) \
EXPORTPREPOSTREGVAR(ESwirl, T) \
EXPORTPREPOSTREGVAR(LazyTravis, T) \
EXPORTPREPOSTREGVAR(Squish, T) \
EXPORTPREPOSTREGVAR(Circus, T) \
EXPORTPREPOSTREGVAR(Tancos, T) \
EXPORTPREPOSTREGVAR(Rippled, T) \
EXPORTPREPOSTREGVAR(Flatten, T) \
EXPORTPREPOSTREGVAR(Zblur, T) \
EXPORTPREPOSTREGVAR(Blur3D, T) \
EXPORTPREPOSTREGVAR(ZScale, T) \
EXPORTPREPOSTREGVAR(ZTranslate, T) \
EXPORTPREPOSTREGVAR(ZCone, T) \
EXPORTPREPOSTREGVAR(Boarders2, T) \
EXPORTPREPOSTREGVAR(RotateX, T) \
EXPORTPREPOSTREGVAR(RotateY, T) \
EXPORTPREPOSTREGVAR(RotateZ, T) \
EXPORTPREPOSTREGVAR(MirrorX, T) \
EXPORTPREPOSTREGVAR(MirrorY, T) \
EXPORTPREPOSTREGVAR(MirrorZ, T) \
EXPORTPREPOSTREGVAR(Depth, T) \
EXPORTPREPOSTREGVAR(RBlur, T) \
EXPORTPREPOSTREGVAR(JuliaNab, T) \
EXPORTPREPOSTREGVAR(Sintrange, T) \
EXPORTPREPOSTREGVAR(Voron, T) \
EXPORTPREPOSTREGVAR(Waffle, T) \
EXPORTPREPOSTREGVAR(Square3D, T) \
EXPORTPREPOSTREGVAR(SuperShape3D, T) \
EXPORTPREPOSTREGVAR(Sphyp3D, T) \
EXPORTPREPOSTREGVAR(Circlecrop, T) \
EXPORTPREPOSTREGVAR(Julian3Dx, T) \
EXPORTPREPOSTREGVAR(Fourth, T) \
EXPORTPREPOSTREGVAR(Mobiq, T) \
EXPORTPREPOSTREGVAR(Spherivoid, T) \
EXPORTPREPOSTREGVAR(Farblur, T) \
EXPORTPREPOSTREGVAR(CurlSP, T) \
EXPORTPREPOSTREGVAR(Heat, T) \
EXPORTPREPOSTREGVAR(Interference2, T) \
EXPORTPREPOSTREGVAR(Sinq, T) \
EXPORTPREPOSTREGVAR(Sinhq, T) \
EXPORTPREPOSTREGVAR(Secq, T) \
EXPORTPREPOSTREGVAR(Sechq, T) \
EXPORTPREPOSTREGVAR(Tanq, T) \
EXPORTPREPOSTREGVAR(Tanhq, T) \
EXPORTPREPOSTREGVAR(Cosq, T) \
EXPORTPREPOSTREGVAR(Coshq, T) \
EXPORTPREPOSTREGVAR(Cotq, T) \
EXPORTPREPOSTREGVAR(Cothq, T) \
EXPORTPREPOSTREGVAR(Cscq, T) \
EXPORTPREPOSTREGVAR(Cschq, T) \
EXPORTPREPOSTREGVAR(Estiq, T) \
EXPORTPREPOSTREGVAR(Loq, T) \
EXPORTPREPOSTREGVAR(Curvature, T) \
EXPORTPREPOSTREGVAR(Qode, T) \
EXPORTPREPOSTREGVAR(BlurHeart, T) \
EXPORTPREPOSTREGVAR(Truchet, T) \
EXPORTPREPOSTREGVAR(Gdoffs, T) \
EXPORTPREPOSTREGVAR(Octagon, T) \
EXPORTPREPOSTREGVAR(Trade, T) \
EXPORTPREPOSTREGVAR(Juliac, T) \
EXPORTPREPOSTREGVAR(Blade3D, T) \
EXPORTPREPOSTREGVAR(Blob3D, T) \
EXPORTPREPOSTREGVAR(Blocky, T) \
EXPORTPREPOSTREGVAR(Bubble2, T) \
EXPORTPREPOSTREGVAR(CircleLinear, T) \
EXPORTPREPOSTREGVAR(CircleRand, T) \
EXPORTPREPOSTREGVAR(CircleTrans1, T) \
EXPORTPREPOSTREGVAR(Cubic3D, T) \
EXPORTPREPOSTREGVAR(CubicLattice3D, T) \
EXPORTPREPOSTREGVAR(Foci3D, T) \
EXPORTPREPOSTREGVAR(Ho, T) \
EXPORTPREPOSTREGVAR(Julia3Dq, T) \
EXPORTPREPOSTREGVAR(Line, T) \
EXPORTPREPOSTREGVAR(Loonie3D, T) \
EXPORTPREPOSTREGVAR(Mcarpet, T) \
EXPORTPREPOSTREGVAR(Waves23D, T) \
EXPORTPREPOSTREGVAR(Pie3D, T) \
EXPORTPREPOSTREGVAR(Popcorn23D, T) \
EXPORTPREPOSTREGVAR(Sinusoidal3D, T) \
EXPORTPREPOSTREGVAR(Scry3D, T) \
EXPORTPREPOSTREGVAR(Shredlin, T) \
EXPORTPREPOSTREGVAR(SplitBrdr, T) \
EXPORTPREPOSTREGVAR(Wdisc, T) \
EXPORTPREPOSTREGVAR(Falloff, T) \
EXPORTPREPOSTREGVAR(Falloff2, T) \
EXPORTPREPOSTREGVAR(Falloff3, T) \
EXPORTPREPOSTREGVAR(Xtrb, T) \
template EMBER_API class DCBubbleVariation<T>; \
EXPORTPREPOSTREGVAR(DCCarpet, T) \
EXPORTPREPOSTREGVAR(DCCube, T) \
template EMBER_API class DCCylinderVariation<T>; \
EXPORTPREPOSTREGVAR(DCGridOut, T) \
template EMBER_API class DCLinearVariation<T>; \
EXPORTPREPOSTREGVAR(DCZTransl, T) \
EXPORTPREPOSTREGVAR(DCTriangle, T) \
template EMBER_API class VariationList<T>; \
template EMBER_API class SpatialFilter<T>; \
template EMBER_API class GaussianFilter<T>; \
template EMBER_API class HermiteFilter<T>; \
template EMBER_API class BoxFilter<T>; \
template EMBER_API class TriangleFilter<T>; \
template EMBER_API class BellFilter<T>; \
template EMBER_API class BsplineFilter<T>; \
template EMBER_API class MitchellFilter<T>; \
template EMBER_API class BlackmanFilter<T>; \
template EMBER_API class CatromFilter<T>; \
template EMBER_API class HanningFilter<T>; \
template EMBER_API class HammingFilter<T>; \
template EMBER_API class Lanczos3Filter<T>; \
template EMBER_API class Lanczos2Filter<T>; \
template EMBER_API class QuadraticFilter<T>; \
template EMBER_API class DensityFilter<T>; \
template EMBER_API class TemporalFilter<T>; \
template EMBER_API class ExpTemporalFilter<T>; \
template EMBER_API class GaussianTemporalFilter<T>; \
template EMBER_API class BoxTemporalFilter<T>; \
template EMBER_API class SpatialFilterCreator<T>; \
template EMBER_API class TemporalFilterCreator<T>; \
template EMBER_API class Interpolater<T>; \
template EMBER_API class Ember<T>; \
/*template EMBER_API class RenderCallback<T>;*/ \
template EMBER_API class CarToRas<T>; \
template EMBER_API class XmlToEmber<T>; \
template EMBER_API class EmberToXml<T>; \
bool PaletteList<T>::m_Init = false; \
vector<Palette<T>> PaletteList<T>::m_Palettes = vector<Palette<T>>(); \
bool XmlToEmber<T>::m_Init = false; \
vector<pair<string, string>> XmlToEmber<T>::m_BadParamNames = vector<pair<string, string>>(); \
vector<pair<string, string>> XmlToEmber<T>::m_BadVariationNames = vector<pair<string, string>>();
EXPORT_SINGLE_TYPE_EMBER(float)
#define EXPORT_TWO_TYPE_EMBER(T, bucketT) \
template EMBER_API class Renderer<T, bucketT>; \
template EMBER_API class SheepTools<T, bucketT>;
EXPORT_TWO_TYPE_EMBER(float, float)
#ifdef DO_DOUBLE
EXPORT_SINGLE_TYPE_EMBER(double)
EXPORT_TWO_TYPE_EMBER(double, double)
#endif
}

1607
Source/Ember/Ember.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,83 @@
#pragma once
#include "EmberPch.h"
/// <summary>
/// Basic #defines used throughout the library.
/// </summary>
//MSVC specific?
#if defined(BUILDING_EMBER)
#define EMBER_API __declspec(dllexport)
#else
#define EMBER_API __declspec(dllimport)
#endif
#define RESTRICT __restrict//This might make things faster, unsure if it really does though.
//#define RESTRICT
namespace EmberNs
{
//Wrap the sincos function for Macs and PC.
#if defined(__APPLE__) || defined(_MSC_VER)
#define sincos(x, s, c) *(s)=sin(x); *(c)=cos(x);
#else
extern void sincos(double x, double *s, double *c);
#endif
#define EMBER_VERSION "0.4.0.2"
#define EPS6 T(1e-6)
#define EPS T(1e-10)//Apoplugin.h uses -20, but -10 seems to work fine.
#define ISAAC_SIZE 4
#define MEMALIGN 32
#define DE_THRESH 100
#define MAX_VARS_PER_XFORM 8
#define DEG_2_RAD (M_PI / 180)
#define RAD_2_DEG (180 / M_PI)
#define DEG_2_RAD_T (T(M_PI) / T(180))
#define RAD_2_DEG_T (T(180) / T(M_PI))
#define M_2PI (T(M_PI * 2))
#define M_3PI (T(M_PI * 3))
#define SQRT5 T(2.2360679774997896964091736687313)
#define M_PHI T(1.61803398874989484820458683436563)
#define COLORMAP_LENGTH 256//These will need to change if 2D palette support is ever added, or variable sized palettes.
#define COLORMAP_LENGTH_MINUS_1 255
#define WHITE 255
#define XC (const xmlChar*)
#define BadVal(x) (((x) != (x)) || ((x) > 1e10) || ((x) < -1e10))
#define Rint(A) floor((A) + (((A) < 0) ? T(-0.5) : T(0.5)))
#define Vlen(x) (sizeof(x) / sizeof(*x))
#define SQR(x) ((x) * (x))
#define CUBE(x) ((x) * (x) * (x))
#define TLOW std::numeric_limits<T>::lowest()
#define TMAX std::numeric_limits<T>::max()
#ifndef acosh
#define acosh(x) (log(x + sqrt(SQR(x) - 1)))//Remove this once you upgrade compilers to VS 2013 or later.//TODO
#endif
#ifndef fma
#define fma(x, y, z) ((x * y) + z)
#endif
#define DO_DOUBLE 1//Comment this out for shorter build times during development. Always uncomment for release.
#define v2T glm::detail::tvec2<T, glm::defaultp>
#define v3T glm::detail::tvec3<T, glm::defaultp>
#define v4T glm::detail::tvec4<T, glm::defaultp>
#define m2T glm::detail::tmat2x2<T, glm::defaultp>
#define m3T glm::detail::tmat3x3<T, glm::defaultp>
#define m4T glm::detail::tmat4x4<T, glm::defaultp>
#define m23T glm::detail::tmat2x3<T, glm::defaultp>
enum eInterp : unsigned int { EMBER_INTERP_LINEAR = 0, EMBER_INTERP_SMOOTH = 1 };
enum eAffineInterp : unsigned int { INTERP_LINEAR = 0, INTERP_LOG = 1, INTERP_COMPAT = 2, INTERP_OLDER = 3 };
enum ePaletteMode : unsigned int { PALETTE_STEP = 0, PALETTE_LINEAR = 1 };
enum ePaletteInterp : unsigned int { INTERP_HSV = 0, INTERP_SWEEP = 1 };
enum eMotion : unsigned int { MOTION_SIN = 1, MOTION_TRIANGLE = 2, MOTION_HILL = 3 };
enum eProcessAction : unsigned int { NOTHING = 0, ACCUM_ONLY = 1, FILTER_AND_ACCUM = 2, KEEP_ITERATING = 3, FULL_RENDER = 4 };
enum eProcessState : unsigned int { NONE = 0, ITER_STARTED = 1, ITER_DONE = 2, FILTER_DONE = 3, ACCUM_DONE = 4 };
enum eInteractiveFilter : unsigned int { FILTER_LOG = 0, FILTER_DE = 1 };
enum eScaleType : unsigned int { SCALE_NONE = 0, SCALE_WIDTH = 1, SCALE_HEIGHT = 2 };
enum eRenderStatus : unsigned int { RENDER_OK = 0, RENDER_ERROR = 1, RENDER_ABORT = 2 };
}

View File

@ -0,0 +1 @@
#include "EmberPch.h"

62
Source/Ember/EmberPch.h Normal file
View File

@ -0,0 +1,62 @@
#pragma once
/// <summary>
/// Precompiled header file. Place all system includes here with appropriate #defines for different operating systems and compilers.
/// </summary>
#define NOMINMAX
#define _USE_MATH_DEFINES
#ifdef _WIN32
#define basename(x) _strdup(x)
#define snprintf _snprintf
#define snprintf_s _snprintf_s
#define WIN32_LEAN_AND_MEAN
#define EMBER_OS "WIN"
#include <SDKDDKVer.h>
#include <windows.h>
#elif __APPLE__
#define EMBER_OS "OSX"
#else
#include <libgen.h>
#include <unistd.h>
#define EMBER_OS "LNX"
#endif
//Standard headers.
#include <algorithm>
#include <complex>
#include <fstream>
#include <functional>
#include <iostream>
#include <iomanip>
#include <limits>
#include <malloc.h>
#include <math.h>
#include <numeric>
#include <ostream>
#include <sstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <vector>
//Third party headers.
#include <libxml/parser.h>
//Intel's Threading Building Blocks is what's used for all threading.
#include "tbb/task_group.h"
#include "tbb/parallel_for.h"
#include "tbb/task_scheduler_init.h"
#define GLM_FORCE_RADIANS
//glm is what's used for matrix math.
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "glm/gtx/string_cast.hpp"
using namespace tbb;
using namespace std;

686
Source/Ember/EmberToXml.h Normal file
View File

@ -0,0 +1,686 @@
#pragma once
#include "Utils.h"
#include "PaletteList.h"
#include "VariationList.h"
#include "Ember.h"
/// <summary>
/// EmberToXml class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Class for converting ember objects to Xml documents.
/// Support for saving one or more to a single file.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API EmberToXml : public EmberReport
{
public:
/// <summary>
/// Empty constructor.
/// </summary>
EmberToXml()
{
}
/// <summary>
/// Save the ember to the specified file.
/// </summary>
/// <param name="filename">Full path and filename</param>
/// <param name="ember">The ember to save</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <param name="doEdits">If true included edit tags, else don't.</param>
/// <param name="intPalette">If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers.</param>
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
/// <param name="append">If true, append to the file if it already exists, else create a new file.</param>
/// <param name="start">Whether a new file is to be started</param>
/// <param name="finish">Whether an existing file is to be ended</param>
/// <returns>True if successful, else false</returns>
bool Save(string filename, Ember<T>& ember, unsigned int printEditDepth, bool doEdits, bool intPalette, bool hexPalette, bool append = false, bool start = false, bool finish = false)
{
vector<Ember<T>> vec;
vec.push_back(ember);
return Save(filename, vec, printEditDepth, doEdits, intPalette, hexPalette, append, start, finish);
}
/// <summary>
/// Save a vector of embers to the specified file.
/// </summary>
/// <param name="filename">Full path and filename</param>
/// <param name="embers">The vector of embers to save</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <param name="doEdits">If true included edit tags, else don't.</param>
/// <param name="intPalette">If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers.</param>
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
/// <param name="append">If true, append to the file if it already exists, else create a new file.</param>
/// <param name="start">Whether a new file is to be started</param>
/// <param name="finish">Whether an existing file is to be ended</param>
/// <returns>True if successful, else false</returns>
bool Save(string filename, vector<Ember<T>>& embers, unsigned int printEditDepth, bool doEdits, bool intPalette, bool hexPalette, bool append = false, bool start = false, bool finish = false)
{
bool b = false;
string temp;
ofstream f;
try
{
if (append)
f.open(filename, std::ofstream::out | std::ofstream::app);//Appending allows us to write multiple embers to a single file.
else
f.open(filename);
if (f.is_open())
{
if ((append && start) || !append)
{
temp = "<flames>\n";
f.write(temp.c_str(), temp.size());
}
for (size_t i = 0; i < embers.size(); i++)
{
string s = ToString(embers[i], "", printEditDepth, doEdits, intPalette, hexPalette);
f.write(s.c_str(), s.size());
}
if ((append && finish) || !append)
{
temp = "</flames>\n";
f.write(temp.c_str(), temp.size());
}
f.close();
b = true;
}
else
{
cout << "Error: Writing flame " << filename << " failed." << endl;
b = false;
}
}
catch (...)
{
if (f.is_open())
f.close();
b = false;
}
return b;
}
/// <summary>
/// Return the Xml string representation of an ember.
/// </summary>
/// <param name="ember">The ember to create the Xml with</param>
/// <param name="extraAttributes">If true, add extra attributes, else don't</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <param name="doEdits">If true included edit tags, else don't.</param>
/// <param name="intPalette">If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers.</param>
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
/// <returns>The Xml string representation of the passed in ember</returns>
string ToString(Ember<T>& ember, string extraAttributes, unsigned int printEditDepth, bool doEdits, bool intPalette, bool hexPalette = true)
{
unsigned int i, j;
ostringstream os;
os << "<flame version=\"EMBER-" << EmberVersion() << "\" time=\"" << ember.m_Time << "\"";
if (!ember.m_Name.empty())
os << " name=\"" << ember.m_Name << "\"";
os << " size=\"" << ember.m_FinalRasW << " " << ember.m_FinalRasH << "\"";
os << " center=\"" << ember.m_CenterX << " " << ember.m_CenterY << "\"";
os << " scale=\"" << ember.m_PixelsPerUnit << "\"";
if (ember.m_Zoom != 0)
os << " zoom=\"" << ember.m_Zoom << "\"";
os << " rotate=\"" << ember.m_Rotate << "\"";
os << " supersample=\"" << max(1u, ember.m_Supersample) << "\"";
os << " filter=\"" << ember.m_SpatialFilterRadius << "\"";
os << " filter_shape=\"" << ToLower(SpatialFilterCreator<T>::ToString(ember.m_SpatialFilterType)) << "\"";
os << " temporal_filter_type=\"" << ToLower(TemporalFilterCreator<T>::ToString(ember.m_TemporalFilterType)) << "\"";
if (ember.m_TemporalFilterType == EXP_TEMPORAL_FILTER)
os << " temporal_filter_exp=\"" << ember.m_TemporalFilterExp << "\"";
os << " temporal_filter_width=\"" << ember.m_TemporalFilterWidth << "\"";
os << " quality=\"" << ember.m_Quality << "\"";
os << " passes=\"" << ember.m_Passes << "\"";
os << " temporal_samples=\"" << ember.m_TemporalSamples << "\"";
os << " background=\"" << ember.m_Background.r << " " << ember.m_Background.g << " " << ember.m_Background.b << "\"";
os << " brightness=\"" << ember.m_Brightness << "\"";
os << " gamma=\"" << ember.m_Gamma << "\"";
os << " highlight_power=\"" << ember.m_HighlightPower << "\"";
os << " vibrancy=\"" << ember.m_Vibrancy << "\"";
//os << " hue=\"" << ember.m_Hue << "\"";//Oddly enough, flam3 never wrote this value out.//ORIG
os << " estimator_radius=\"" << ember.m_MaxRadDE << "\"";
os << " estimator_minimum=\"" << ember.m_MinRadDE << "\"";
os << " estimator_curve=\"" << ember.m_CurveDE << "\"";
os << " gamma_threshold=\"" << ember.m_GammaThresh << "\"";
os << " cam_zpos=\"" << ember.m_CamZPos << "\"";
os << " cam_persp=\"" << ember.m_CamPerspective << "\"";
os << " cam_yaw=\"" << ember.m_CamYaw << "\"";
os << " cam_pitch=\"" << ember.m_CamPitch << "\"";
os << " cam_dof=\"" << ember.m_CamDepthBlur << "\"";
if (ember.m_PaletteMode == PALETTE_STEP)
os << " palette_mode=\"step\"";
else if (ember.m_PaletteMode == PALETTE_LINEAR)
os << " palette_mode=\"linear\"";
if (ember.m_Interp == EMBER_INTERP_SMOOTH)
os << " interpolation=\"smooth\"";
if (ember.m_AffineInterp == INTERP_LINEAR)
os << " interpolation_type=\"linear\"";
else if (ember.m_AffineInterp == INTERP_LOG)
os << " interpolation_type=\"log\"";
else if (ember.m_AffineInterp == INTERP_COMPAT)
os << " interpolation_type=\"old\"";
else if (ember.m_AffineInterp == INTERP_OLDER)
os << " interpolation_type=\"older\"";
if (ember.m_PaletteInterp == INTERP_SWEEP)
os << " palette_interpolation=\"sweep\"";
if (!extraAttributes.empty())
os << " " << extraAttributes;
os << ">\n";
//This is a grey area, what to do about symmetry to avoid duplicating the symmetry xforms when reading back?//TODO//BUG.
//if (ember.m_Symmetry)
// os << " <symmetry kind=\"" << ember.m_Symmetry << "\"/>\n";
for (i = 0; i < ember.XformCount(); i++)
os << ToString(*ember.GetXform(i), ember.XformCount(), false, false);//Not final, don't do motion.
if (ember.UseFinalXform())
os << ToString(*ember.NonConstFinalXform(), ember.XformCount(), true, false);//Final, don't do motion.
if (hexPalette)
{
os << " <palette count=\"256\" format=\"RGB\">\n";
for (i = 0; i < 32; i++)
{
os << " ";
for (j = 0; j < 8; j++)
{
int idx = 8 * i + j;
os << hex << setw(2) << setfill('0') << (int)Rint(ember.m_Palette[idx][0] * 255);
os << hex << setw(2) << setfill('0') << (int)Rint(ember.m_Palette[idx][1] * 255);
os << hex << setw(2) << setfill('0') << (int)Rint(ember.m_Palette[idx][2] * 255);
}
os << endl;
}
os << " </palette>\n";
}
else
{
for (i = 0; i < 256; i++)
{
double r = ember.m_Palette[i][0] * 255;
double g = ember.m_Palette[i][1] * 255;
double b = ember.m_Palette[i][2] * 255;
double a = ember.m_Palette[i][3] * 255;
os << " ";
//The original used a precision of 6 which is totally unnecessary, use 2.
if (IsClose(a, 255.0))
{
if (intPalette)
os << "<color index=\"" << i << "\" rgb=\"" << (int)Rint(r) << " " << (int)Rint(g) << " " << (int)Rint(b) << "\"/>";
else
os << "<color index=\"" << i << "\" rgb=\"" << std::fixed << std::setprecision(2) << r << " " << g << " " << b << "\"/>";
}
else
{
if (intPalette)
os << " <color index=\"" << i << "\" rgba=\"" << (int)Rint(r) << " " << (int)Rint(g) << " " << (int)Rint(b) << " " << (int)Rint(a) << "\"/>";
else
os << " <color index=\"" << i << "\" rgba=\"" << std::fixed << std::setprecision(2) << r << " " << g << " " << b << " " << a << "\"/>";
}
os << "\n";
}
}
if (doEdits && ember.m_Edits != NULL)
os << ToString(xmlDocGetRootElement(ember.m_Edits), 1, true, printEditDepth);
os << "</flame>\n";
return os.str();
}
/// <summary>
/// Create a new editdoc optionally based on parents passed in.
/// This is used when an ember is made out of some mutation or edit from one or two existing embers and
/// the user wants to capture the genetic lineage history information in the edit doc of the new ember.
/// </summary>
/// <param name="parent0">The first parent, optionally NULL.</param>
/// <param name="parent1">The second parent, optionally NULL.</param>
/// <param name="action">The action that was taken to create the new ember</param>
/// <param name="nick">The nickname of the author</param>
/// <param name="url">The Url of the author</param>
/// <param name="id">The id of the author</param>
/// <param name="comment">The comment to include</param>
/// <param name="sheepGen">The sheep generation used if > 0. Default: 0.</param>
/// <param name="sheepId">The sheep id used if > 0. Default: 0.</param>
/// <returns></returns>
xmlDocPtr CreateNewEditdoc(Ember<T>* parent0, Ember<T>* parent1, string action, string nick, string url, string id, string comment, int sheepGen = 0, int sheepId = 0)
{
char timeString[128];
char buffer[128];
char commentString[128];
tm localt;
time_t myTime;
xmlDocPtr commentDoc = NULL;
xmlDocPtr doc = xmlNewDoc(XC "1.0");
xmlNodePtr rootNode = NULL, node = NULL, nodeCopy = NULL;
xmlNodePtr rootComment = NULL;
//Create the root node, called "edit".
rootNode = xmlNewNode(NULL, XC "edit");
xmlDocSetRootElement(doc, rootNode);
//Add the edit attributes.
//Date.
myTime = time(NULL);
localtime_s(&localt, &myTime);
strftime(timeString, 128, "%a %b %d %H:%M:%S %z %Y", &localt);//XXX use standard time format including timezone.
xmlNewProp(rootNode, XC "date", XC timeString);
//Nick.
if (nick != "")
xmlNewProp(rootNode, XC "nick", XC nick.c_str());
//Url.
if (url != "")
xmlNewProp(rootNode, XC "url", XC url.c_str());
if (id != "")
xmlNewProp(rootNode, XC "id", XC id.c_str());
//Action.
xmlNewProp(rootNode, XC "action", XC action.c_str());
//Sheep info.
if (sheepGen > 0 && sheepId > 0)
{
//Create a child node of the root node called sheep.
node = xmlNewChild(rootNode, NULL, XC "sheep", NULL);
//Create the sheep attributes.
sprintf_s(buffer, 128, "%d", sheepGen);
xmlNewProp(node, XC "generation", XC buffer);
sprintf_s(buffer, 128, "%d", sheepId);
xmlNewProp(node, XC "id", XC buffer);
}
//Check for the parents.
//If parent 0 not specified, this is a randomly generated genome.
if (parent0)
{
if (parent0->m_Edits)
{
//Copy the node from the parent.
node = xmlDocGetRootElement(parent0->m_Edits);
nodeCopy = xmlCopyNode(node, 1);
AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename);
sprintf_s(buffer, 128, "%d", parent0->m_Index);
xmlNewProp(nodeCopy, XC "index", XC buffer);
xmlAddChild(rootNode, nodeCopy);
}
else
{
//Insert a (parent has no edit) message.
nodeCopy = xmlNewChild(rootNode, NULL, XC "edit", NULL);
AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename);
sprintf_s(buffer, 128, "%d", parent0->m_Index);
xmlNewProp(nodeCopy, XC "index", XC buffer);
}
}
if (parent1)
{
if (parent1->m_Edits)
{
//Copy the node from the parent.
node = xmlDocGetRootElement(parent1->m_Edits);
nodeCopy = xmlCopyNode(node, 1);
AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename);
sprintf_s(buffer, 128, "%d", parent1->m_Index);
xmlNewProp(nodeCopy, XC "index", XC buffer);
xmlAddChild(rootNode, nodeCopy);
}
else
{
//Insert a (parent has no edit) message.
nodeCopy = xmlNewChild(rootNode, NULL, XC "edit",NULL);
AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename);
sprintf_s(buffer, 128, "%d", parent1->m_Index);
xmlNewProp(nodeCopy, XC "index", XC buffer);
}
}
//Comment string:
//This one's hard, since the comment string must be treated as
//a valid XML document. Create a new document using the comment
//string as the in-memory document, and then copy all children of
//the root node into the edit structure
//Parsing the comment string should be done once and then copied
//for each new edit doc, but that's for later.
if (comment != "")
{
sprintf_s(commentString, 128, "<comm>%s</comm>", comment.c_str());
commentDoc = xmlReadMemory(commentString, (int)strlen(commentString), "comment.env", NULL, XML_PARSE_NONET);
//Check for errors.
if (commentDoc != NULL)
{
//Loop through the children of the new document and copy them into the rootNode.
rootComment = xmlDocGetRootElement(commentDoc);
for (node = rootComment->children; node; node = node->next)
{
nodeCopy = xmlCopyNode(node, 1);
xmlAddChild(rootNode, nodeCopy);
}
//Free the created document.
xmlFreeDoc(commentDoc);
}
else
{
cout << "Failed to parse comment into Xml." << endl;
}
}
//Return the xml doc.
return doc;
}
private:
/// <summary>
/// Return the Xml string representation of an xform.
/// </summary>
/// <param name="xform">The xform to create the Xml with</param>
/// <param name="xformCount">The number of non-final xforms in the ember to which this xform belongs. Used for xaos.</param>
/// <param name="isFinal">True if the xform is the final xform in the ember, else false.</param>
/// <param name="doMotion">If true, include motion elements in the Xml string, else omit.</param>
/// <returns>The Xml string representation of the passed in xform</returns>
string ToString(Xform<T>& xform, unsigned int xformCount, bool isFinal, bool doMotion)
{
unsigned int i, j;
ostringstream os;
if (doMotion)
{
os << " <motion motion_frequency=\"" << xform.m_MotionFreq << "\" ";
if (xform.m_MotionFunc == MOTION_SIN)
os << "motion_function=\"sin\" ";
else if (xform.m_MotionFunc == MOTION_TRIANGLE)
os << "motion_function=\"triangle\" ";
else if (xform.m_MotionFunc== MOTION_HILL)
os << "motion_function=\"hill\" ";
}
else
{
if (isFinal)
os << " <finalxform ";
else
os << " <xform weight=\"" << xform.m_Weight << "\" ";
}
if (!doMotion)
{
os << "color=\"" << xform.m_ColorX << "\" ";
//os << "color=\"" << xform.m_ColorX << " " << xform.m_ColorY << "\" ";
os << "var_color=\"" << xform.m_DirectColor << "\" ";
os << "color_speed=\"" << xform.m_ColorSpeed << "\" ";
os << "symmetry=\"" << xform.m_ColorSpeed << "\" ";//Legacy support.
string s = xform.m_Name;
std::replace(s.begin(), s.end(), ' ', '_');
os << "name=\"" << s << "\" ";//Flam3 didn't do this, but Apo does.
if (!isFinal)
os << "animate=\"" << xform.m_Animate << "\" ";
}
//Variation writing order differs slightly from the original to make it a bit more readable.
//The original wrote out all of the variation names and weights. Then wrote out the parameters for
//the parametric variations. Here, write out the params immediately after each parametric variation
//so they are more closely grouped with the variation they apply to, rather than being all grouped at the end.
for (i = 0; i < xform.TotalVariationCount(); i++)
{
Variation<T>* var = xform.GetVariation(i);
ParametricVariation<T>* parVar = dynamic_cast<ParametricVariation<T>*>(var);
if (var->m_Weight != 0)
{
os << var->Name() << "=\"" << var->m_Weight << "\" ";
if (parVar)
{
ParamWithName<T>* params = parVar->Params();
for (j = 0; j < parVar->ParamCount(); j++)
{
if ((!doMotion || (doMotion && (params[j].ParamVal() != 0))) && !params[j].IsPrecalc())
os << params[j].Name() << "=\"" << params[j].ParamVal() << "\" ";
}
}
}
}
if (!doMotion || (doMotion && !xform.m_Affine.IsZero()))
{
os << "coefs=\"" << xform.m_Affine.A() << " " << xform.m_Affine.D() << " " << xform.m_Affine.B() << " "
<< xform.m_Affine.E() << " " << xform.m_Affine.C() << " " << xform.m_Affine.F() << "\"";
}
if ((!doMotion && !xform.m_Post.IsID()) || (doMotion && !xform.m_Post.IsZero()))
{
os << " post=\"" << xform.m_Post.A() << " " << xform.m_Post.D() << " " << xform.m_Post.B() << " "
<< xform.m_Post.E() << " " << xform.m_Post.C() << " " << xform.m_Post.F() << "\"";
}
//Original only printed xaos values that were not 1. Here, print them all out if any are present.
if (!isFinal && !doMotion && xform.XaosPresent())
{
os << " chaos=\"";
for (i = 0; i < xformCount; i++)
os << xform.Xaos(i) << " ";
os << "\"";
}
if (!doMotion)
os << " opacity=\"" << xform.m_Opacity << "\"";
if (!doMotion && !xform.m_Motion.empty())
{
os << ">\n";
for (i = 0; i < xform.m_Motion.size(); i++)
os << ToString(xform.m_Motion[i], 0, false, true);
if (isFinal)//Fixed to properly close final.//SMOULDER
os << " </finalxform>\n";
else
os << " </xform>\n";
}
else
os << "/>\n";
return os.str();
}
/// <summary>
/// Return an edit node Xml string.
/// </summary>
/// <param name="editNode">The edit node to get the string for</param>
/// <param name="tabs">How many tabs to use</param>
/// <param name="formatting">If true, include newlines and tabs, else don't.</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <returns>The edit node Xml string</returns>
string ToString(xmlNodePtr editNode, unsigned int tabs, bool formatting, unsigned int printEditDepth)
{
bool indentPrinted = false;
char* tabString = " ", *attStr;
unsigned int ti, editOrSheep = 0;
xmlAttrPtr attPtr = NULL, curAtt = NULL;
xmlNodePtr childPtr = NULL, curChild = NULL;
ostringstream os;
if (printEditDepth > 0 && tabs > printEditDepth)
return "";
//If this node is an XML_ELEMENT_NODE, print it and its attributes.
if (editNode->type == XML_ELEMENT_NODE)
{
//Print the node at the tab specified.
if (formatting)
for (ti = 0; ti < tabs; ti++)
os << tabString;
os << "<" << editNode->name;
//This can either be an edit node or a sheep node.
//If it's an edit node, add one to the tab.
if (!Compare(editNode->name, "edit"))
{
editOrSheep = 1;
tabs++;
}
else if (!Compare(editNode->name, "sheep"))
editOrSheep = 2;
else
editOrSheep = 0;
//Print the attributes.
attPtr = editNode->properties;
for (curAtt = attPtr; curAtt; curAtt = curAtt->next)
{
attStr = (char*)xmlGetProp(editNode, curAtt->name);
os << " " << curAtt->name << "=\"" << attStr << "\"";
xmlFree(attStr);
}
//Does this node have children?
if (!editNode->children || (printEditDepth > 0 && tabs > printEditDepth))
{
//Close the tag and subtract the tab.
os << "/>";
if (formatting)
os << "\n";
tabs--;
}
else
{
//Close the tag.
os << ">";
if (formatting)
os << "\n";
//Loop through the children and print them.
childPtr = editNode->children;
indentPrinted = false;
for (curChild = childPtr; curChild; curChild = curChild->next)
{
//If child is an element, indent first and then print it.
if (curChild->type == XML_ELEMENT_NODE &&
(!Compare(curChild->name, "edit") || !Compare(curChild->name, "sheep")))
{
if (indentPrinted)
{
indentPrinted = false;
os << "\n";
}
os << ToString(curChild, tabs, true, printEditDepth);
}
else
{
//Child is a text node, don't want to indent more than once.
if (xmlIsBlankNode(curChild))
continue;
if (!indentPrinted && formatting)
{
for (ti = 0; ti < tabs; ti++)
os << tabString;
indentPrinted = true;
}
//Print nodes without formatting.
os << ToString(curChild, tabs, false, printEditDepth);
}
}
if (indentPrinted && formatting)
os << "\n";
tabs--;//Tab out.
if (formatting)
for (ti = 0; ti < tabs; ti++)
os << tabString;
os << "</" << editNode->name << ">";//Close the tag.
if (formatting)
os << "\n";
}
}
else if (editNode->type == XML_TEXT_NODE)
{
string s((char*)xmlNodeGetContent(editNode));
os << Trim(s);
}
return os.str();
}
void AddFilenameWithoutAmpersand(xmlNodePtr node, string& filename)
{
if (filename.find_first_of('&') != std::string::npos)
{
string filenameWithoutAmpersands = filename;
FindAndReplace<string>(filenameWithoutAmpersands, "&", "&amp;");
xmlNewProp(node, XC "filename", XC filenameWithoutAmpersands.c_str());
}
else
{
xmlNewProp(node, XC "filename", XC filename.c_str());
}
}
};
}

1018
Source/Ember/Interpolate.h Normal file

File diff suppressed because it is too large Load Diff

386
Source/Ember/Isaac.h Normal file
View File

@ -0,0 +1,386 @@
#pragma once
#include "EmberDefines.h"
/// <summary>
/// C++ TEMPLATE VERSION OF Robert J. Jenkins Jr.'s
/// ISAAC Random Number Generator.
///
/// Ported from vanilla C to to template C++ class
/// by Quinn Tyler Jackson on 16-23 July 1998.
///
/// quinn@qtj.net
///
/// The function for the expected period of this
/// random number generator, according to Jenkins is:
///
/// f(a,b) = 2**((a+b*(3+2^^a)-1)
///
/// (where a is ALPHA and b is bitwidth)
///
/// So, for a bitwidth of 32 and an ALPHA of 8,
/// the expected period of ISAAC is:
///
/// 2^^(8+32*(3+2^^8)-1) = 2^^8295
///
/// Jackson has been able to run implementations
/// with an ALPHA as high as 16, or
///
/// 2^^2097263
///
/// -Modified by Matt Feemster to eliminate needless dynamic memory allocation and virtual functions and bring inline with Ember coding style.
/// </summary>
#ifndef __ISAAC64
typedef unsigned long int ISAAC_INT;
const ISAAC_INT GOLDEN_RATIO = ISAAC_INT(0x9e3779b9);
#else
typedef unsigned __int64 ISAAC_INT;
const ISAAC_INT GOLDEN_RATIO = ISAAC_INT(0x9e3779b97f4a7c13);
#endif
namespace EmberNs
{
/// <summary>
/// QTIsaac class which allows using ISAAC in an OOP manner.
/// </summary>
template <int ALPHA = 4, class T = ISAAC_INT>
class EMBER_API QTIsaac
{
public:
typedef unsigned char byte;
enum { N = (1 << ALPHA) };
/// <summary>
/// Global ISAAC RNG to be used from anywhere. This is not thread safe, so take caution to only
/// use it when no other threads are.
/// </summary>
static auto_ptr<QTIsaac<ALPHA, ISAAC_INT>> GlobalRand;
/// <summary>
/// The structure which holds all of the random information.
/// </summary>
struct EMBER_API randctx
{
T randcnt;
T randrsl[N];
T randmem[N];
T randa;
T randb;
T randc;
};
/// <summary>
/// Constructor which initialized the random context using the values passed in.
/// Leaving these as their defaults is fine, and will still give different
/// results because time is internally used if they are default.
/// However, specifying specific values is useful if you want to duplicate
/// a sequence of random numbers.
/// </summary>
/// <param name="a">First random seed. Default: 0.</param>
/// <param name="b">Second random seed. Default: 0.</param>
/// <param name="c">Third random seed. Default: 0.</param>
/// <param name="s">Pointer to a buffer of 256 random integer seeds. Default: NULL.</param>
QTIsaac(T a = 0, T b = 0, T c = 0, T* s = NULL)
{
Srand(a, b, c, s);
}
/// <summary>
/// Return the next random integer.
/// </summary>
/// <returns>The next random integer</returns>
inline T Rand()
{
return (m_Rc.randcnt++ == N ? (Isaac(&m_Rc), m_Rc.randcnt=0, m_Rc.randrsl[m_Rc.randcnt]) : m_Rc.randrsl[m_Rc.randcnt]);
}
/// <summary>
/// Return the next random integer between 0 and the value passed in.
/// </summary>
/// <returns>A value one greater than the maximum value that will be returned</returns>
inline T Rand(T upper)
{
return (upper == 0) ? Rand() : Rand() % upper;
}
/// <summary>
/// Returns a random floating point value between the specified minimum and maximum.
/// Template argument expected to be float or double.
/// </summary>
/// <param name="fMin">The minimum value allowed, inclusive.</param>
/// <param name="fMax">The maximum value allowed, inclusive.</param>
/// <returns>A new random floating point value within the specified range, inclusive.</returns>
template<typename floatType>
inline floatType Frand(floatType fMin, floatType fMax)
{
floatType f = (floatType)Rand() / (floatType)std::numeric_limits<T>::max();
return fMin + (f * (fMax - fMin));
}
/// <summary>
/// Thin wrapper around a call to Frand() with a range of 0-1.
/// Template argument expected to be float or double.
/// </summary>
/// <returns>A new random number in the range of 0-1, inclusive.</returns>
template<typename floatType>
inline floatType Frand01()
{
return Frand<floatType>(floatType(0), floatType(1));
}
/// <summary>
/// Thin wrapper around a call to Frand() with a range of -1-1.
/// Template argument expected to be float or double.
/// </summary>
/// <returns>A new random number in the range of -1-1, inclusive.</returns>
template<typename floatType>
inline floatType Frand11()
{
return Frand<floatType>(floatType(-1), floatType(1));
}
/// <summary>
/// Not sure what this does.
/// </summary>
/// <returns>Something that is golden</returns>
template<typename floatType>
inline floatType GoldenBit()
{
return RandBit() ? floatType(0.38196) : floatType(0.61804);
}
/// <summary>
/// Returns a random 0 or 1.
/// </summary>
/// <returns>A random 0 or 1</returns>
inline unsigned int RandBit()
{
return Rand() & 1;
}
/// <summary>
/// A different way of getting a floating point rand in the range -1-1.
/// Flam3 used this but it seems unnecessary now, keep around if it's ever needed.
/// </summary>
/// <returns>A new random number in the range of -1-1, inclusive.</returns>
//double drand11()
//{
// return (((int)Rand() & 0xfffffff) - 0x7ffffff) / (double) 0x7ffffff;
//}
/// <summary>
/// Initializes a random context.
/// Unsure exacly how this works, but it does.
/// </summary>
/// <param name="ctx">The random context to initialize</param>
/// <param name="useSeed">Whether to use the seeds passed in to the constructor, else zero.</param>
void RandInit(randctx* ctx, bool useSeed)
{
int i;
T a, b, c, d, e, f, g, h;
T* m = ctx->randmem;
T* r = ctx->randrsl;
a = b = c = d = e = f = g = h = GOLDEN_RATIO;
if (!useSeed)
{
ctx->randa = 0;
ctx->randb = 0;
ctx->randc = 0;
}
//Scramble it.
for (i = 0; i < 4; ++i)
{
Shuffle(a, b, c, d, e, f, g, h);
}
if (useSeed)
{
//Initialize using the contents of r[] as the seed.
for (i = 0; i < N; i += 8)
{
a += r[i ]; b += r[i + 1]; c += r[i + 2]; d += r[i + 3];
e += r[i + 4]; f += r[i + 5]; g += r[i + 6]; h += r[i + 7];
Shuffle(a, b, c, d, e, f, g, h);
m[i ] = a; m[i + 1] = b; m[i + 2] = c; m[i + 3] = d;
m[i + 4] = e; m[i + 5] = f; m[i + 6] = g; m[i + 7] = h;
}
//Do a second pass to make all of the seed affect all of m.
for (i = 0; i < N; i += 8)
{
a += m[i ]; b += m[i + 1]; c += m[i + 2]; d += m[i + 3];
e += m[i + 4]; f += m[i + 5]; g += m[i + 6]; h += m[i + 7];
Shuffle(a, b, c, d, e, f, g, h);
m[i ] = a; m[i + 1] = b; m[i + 2] = c; m[i + 3] = d;
m[i + 4] = e; m[i + 5] = f; m[i + 6] = g; m[i + 7] = h;
}
}
else
{
//Fill in mm[] with messy stuff.
Shuffle(a, b, c, d, e, f, g, h);
m[i ] = a; m[i + 1] = b; m[i + 2] = c; m[i + 3] = d;
m[i + 4] = e; m[i + 5] = f; m[i + 6] = g; m[i + 7] = h;
}
Isaac(ctx); //Fill in the first set of results.
ctx->randcnt = 0;//Prepare to use the first set of results.
}
/// <summary>
/// Initialize the seeds of the member random context using the specified seeds.
/// If s is null, time plus index up to 256 is used for the random buffer.
/// </summary>
/// <param name="a">First random seed. Default: 0.</param>
/// <param name="b">Second random seed. Default: 0.</param>
/// <param name="c">Third random seed. Default: 0.</param>
/// <param name="s">Pointer to a buffer of 256 random integer seeds. Default: NULL.</param>
void Srand(T a = 0, T b = 0, T c = 0, T* s = NULL)
{
if (s == NULL)//Default to using time plus index as the seed if s was NULL.
{
for (int i = 0; i < N; i++)
m_Rc.randrsl[i] = (T)time(0) + i;
}
else
{
for (int i = 0; i < N; i++)
m_Rc.randrsl[i] = s[i];
}
if (a == 0 && b == 0 && c == 0)
{
m_Rc.randa = (T)time(0);
m_Rc.randb = (T)time(0) * (T)time(0);
m_Rc.randc = (T)time(0) * (T)time(0) * (T)time(0);
}
else
{
m_Rc.randa = a;
m_Rc.randb = b;
m_Rc.randc = c;
}
RandInit(&m_Rc, true);
}
protected:
/// <summary>
/// Compute the next batch of random numbers for a random context.
/// </summary>
/// <param name="ctx">The context to populate.</param>
void Isaac(randctx* ctx)
{
T x,y;
T* mm = ctx->randmem;
T* r = ctx->randrsl;
T a = (ctx->randa);
T b = (ctx->randb + (++ctx->randc));
T* m = mm;
T* m2 = (m + (N / 2));
T* mend = m2;
for(; m < mend; )
{
#ifndef __ISAAC64
RngStep((a << 13), a, b, mm, m, m2, r, x, y);
RngStep((a >> 6) , a, b, mm, m, m2, r, x, y);
RngStep((a << 2) , a, b, mm, m, m2, r, x, y);
RngStep((a >> 16), a, b, mm, m, m2, r, x, y);
#else // __ISAAC64
RngStep(~(a ^ (a << 21)), a, b, mm, m, m2, r, x, y);
RngStep( a ^ (a >> 5) , a, b, mm, m, m2, r, x, y);
RngStep( a ^ (a << 12) , a, b, mm, m, m2, r, x, y);
RngStep( a ^ (a >> 33) , a, b, mm, m, m2, r, x, y);
#endif // __ISAAC64
}
m2 = mm;
for(; m2<mend;)
{
#ifndef __ISAAC64
RngStep((a << 13), a, b, mm, m, m2, r, x, y);
RngStep((a >> 6) , a, b, mm, m, m2, r, x, y);
RngStep((a << 2) , a, b, mm, m, m2, r, x, y);
RngStep((a >> 16), a, b, mm, m, m2, r, x, y);
#else // __ISAAC64
RngStep(~(a ^ (a << 21)), a, b, mm, m, m2, r, x, y);
RngStep( a ^ (a >> 5) , a, b, mm, m, m2, r, x, y);
RngStep( a ^ (a << 12) , a, b, mm, m, m2, r, x, y);
RngStep( a ^ (a >> 33) , a, b, mm, m, m2, r, x, y);
#endif // __ISAAC64
}
ctx->randb = b;
ctx->randa = a;
}
/// <summary>
/// Retrieves a value using indirection.
/// </summary>
/// <param name="mm">The buffer.</param>
/// <param name="x">The offset.</param>
/// <returns>A new value</returns>
inline T Ind(T* mm, T x)
{
#ifndef __ISAAC64
return (*(T*)((byte*)(mm) + ((x) & ((N - 1) << 2))));
#else // __ISAAC64
return (*(T*)((byte*)(mm) + ((x) & ((N - 1) << 3))));
#endif // __ISAAC64
}
/// <summary>
/// Unsure what this does.
/// </summary>
void RngStep(T mix, T& a, T& b, T*& mm, T*& m, T*& m2, T*& r, T& x, T& y)
{
x = *m;
a = (a ^ (mix)) + *(m2++);
*(m++) = y = Ind(mm, x) + a + b;
*(r++) = b = Ind(mm, y >> ALPHA) + x;
}
/// <summary>
/// Unsure what this does.
/// </summary>
void Shuffle(T& a, T& b, T& c, T& d, T& e, T& f, T& g, T& h)
{
#ifndef __ISAAC64
a ^= b << 11; d += a; b += c;
b ^= c >> 2; e += b; c += d;
c ^= d << 8; f += c; d += e;
d ^= e >> 16; g += d; e += f;
e ^= f << 10; h += e; f += g;
f ^= g >> 4; a += f; g += h;
g ^= h << 8; b += g; h += a;
h ^= a >> 9; c += h; a += b;
#else // __ISAAC64
a -= e; f ^= h >> 9; h += a;
b -= f; g ^= a << 9; a += b;
c -= g; h ^= b >> 23; b += c;
d -= h; a ^= c << 15; c += d;
e -= a; b ^= d >> 14; d += e;
f -= b; c ^= e << 20; e += f;
g -= c; d ^= f >> 17; f += g;
h -= d; e ^= g << 14; g += h;
#endif // __ISAAC64
}
private:
randctx m_Rc;//The random context which holds all of the seed and state information as well as the random number values.
};
}

541
Source/Ember/Iterator.h Normal file
View File

@ -0,0 +1,541 @@
#pragma once
#include "Ember.h"
/// <summary>
/// Iterator and derived classes.
/// </summary>
//#define CHOOSE_XFORM_GRAIN 256
#define CHOOSE_XFORM_GRAIN 10000//The size of xform random selection buffer. Multiply by the (number of non-final xforms present + 1) if xaos is used.
namespace EmberNs
{
/// <summary>
/// Iterator base class.
/// Iterating is one loop level outside of the inner xform application loop so it's still very important
/// to take every optimization possible here.
/// The original had many temporary assignments in order to feed the output of the current iteration
/// into the input of the next iteration. All unneccessary temporary assignments are eliminated by simply using i and i + 1
/// as the input and output indices on the samples array passed to Xform.Apply().
/// Note that the samples array is assigned to while fusing. Although this technically doesn't make sense
/// since values computed during fusing get thrown out, it doesn't matter because it will get overwritten
/// in the actual loop below it since the index counter is reset to zero when fusing is complete.
/// Flam3 needlessly computed the final xform on each fuse iteration only to throw it away. It's omitted here as an optimization.
/// Rather than place many conditionals inside the iteration loop, they are broken into separate classes depending
/// on what's contained in the ember's xforms.
/// The biggest difference is whether xaos is present or not it requires extra work when picking
/// the next random xform to use. Further, each of those is broken into two loops, one for embers with a final xform
/// and one without.
/// Last, the fuse loop and real loop are separated and duplicated to omit the conditional check for fuse inside the real loop.
/// Although this makes this file about four times as verbose as it would normally be, it does lead to performance improvements.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API Iterator
{
public:
/// <summary>
/// Empty constructor.
/// </summary>
Iterator()
{
}
/// <summary>
/// Empty virtual destructor so proper derived class destructors get called.
/// </summary>
virtual ~Iterator()
{
}
/// <summary>
/// Accessors.
/// </summary>
const unsigned char* XformDistributions() const { return m_XformDistributions.empty() ? NULL : &m_XformDistributions[0]; }
const unsigned int XformDistributionsSize() const { return (unsigned int)m_XformDistributions.size(); }
/// <summary>
/// Virtual empty iteration function that will be overidden in derived iterator classes.
/// </summary>
/// <param name="ember">The ember whose xforms will be applied</param>
/// <param name="count">The number of iterations to do</param>
/// <param name="skip">The number of times to fuse</param>
/// <param name="samples">The buffer to store the output points</param>
/// <param name="rand">The random context to use</param>
/// <returns>The number of bad values</returns>
virtual unsigned int Iterate(Ember<T>& ember, unsigned int count, unsigned int skip, Point<T>* samples, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) { return 0; }
/// <summary>
/// Initialize the xform selection vector by normalizing the weights of all xforms and
/// setting the corresponding percentage of elements in the vector to each xform's index in its
/// parent ember.
/// Note that this method of looking up and index in a vector is how flam3 did it and is about 10%
/// faster than using a while loop to check a random number against a normalized weight.
/// Also, the ember used to initialize this must be the same ember, unchanged, used to iterate.
/// If one is passed to this function, its parameters are changed and then it's passed to Iterate(),
/// the behavior is undefined.
/// </summary>
/// <param name="ember">The ember whose xforms will be used to populate the distribution vector</param>
/// <returns>True if success, else false.</returns>
bool InitDistributions(Ember<T>& ember)
{
unsigned int i, j = 0;
unsigned int distribCount = ember.XaosPresent() ? (unsigned int)ember.XformCount() + 1 : 1;
const Xform<T>* xforms = ember.Xforms();
if (m_XformDistributions.size() < CHOOSE_XFORM_GRAIN * distribCount)
m_XformDistributions.resize(CHOOSE_XFORM_GRAIN * distribCount);
if (m_XformDistributions.size() < CHOOSE_XFORM_GRAIN * distribCount)
return false;
for (unsigned int distrib = 0; distrib < distribCount; distrib++)
{
T totalDensity = 0;
//First find the total densities of all xforms.
for (i = 0; i < ember.XformCount(); i++)
{
T d = xforms[i].m_Weight;
if (distrib > 0)
d *= xforms[distrib - 1].Xaos(i);
//Original returned false if any xform had 0 density, it's allowed here
//because it can be useful when experimenting to test the effects of removing an
//xform by setting its probability to 0.
totalDensity += d;
}
//Original returned false if all were 0, but it's allowed here
//which will just end up setting all elements to 0 which means
//only the first xform will get used.
//Calculate how much of a fraction of a the total density each element represents.
T densityPerElement = totalDensity / CHOOSE_XFORM_GRAIN;
j = 0;
//Assign xform indices in order to each element of m_XformDistributions.
//The number of elements assigned a given index is proportional to that xform's
//density relative to the sum of all densities.
for (i = 0; i < ember.XformCount(); i++)
{
T tempDensity = 0;
T currentDensityLimit = xforms[i].m_Weight;
if (distrib > 0)
currentDensityLimit *= xforms[distrib - 1].Xaos(i);
//Populate points corresponding to this xform's weight/density.
//Also check that j is within the bounds of the distribution array just to be safe in the case of a rounding error.
while (tempDensity <= currentDensityLimit && j < CHOOSE_XFORM_GRAIN)
{
m_XformDistributions[(distrib * CHOOSE_XFORM_GRAIN) + j] = i;
tempDensity += densityPerElement;
j++;
}
}
}
return true;
}
protected:
/// <summary>
/// When iterating, if the computed location of the point is either very close to zero, or very close to infinity,
/// it's considered a bad value. In that case, a new random input point is fed into a new randomly chosen xform. This
/// process is repeated up to 5 times until a good value is computed. If after 5 tries, a good value is not found, then
/// the coordinates of the output point are just set to a random number between -1 and 1.
/// </summary>
/// <param name="xforms">The xforms array</param>
/// <param name="badVals">The counter for the total number of bad values this sub batch</param>
/// <param name="point">The point which initially had the bad values and which will store the newly computed values</param>
/// <param name="rand">The random context this iterator is using</param>
/// <returns>True if a good value was computed within 5 tries, else false</returns>
inline bool DoBadVals(Xform<T>* xforms, unsigned int& badVals, Point<T>* point, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
unsigned int xformIndex, consec = 0;
Point<T> firstBadPoint;
while (consec < 5)
{
consec++;
badVals++;
firstBadPoint.m_X = rand.Frand11<T>();//Re-randomize points, but keep the computed color and viz.
firstBadPoint.m_Y = rand.Frand11<T>();
firstBadPoint.m_Z = 0;
firstBadPoint.m_ColorX = point->m_ColorX;
firstBadPoint.m_VizAdjusted = point->m_VizAdjusted;
xformIndex = NextXformFromIndex(rand.Rand());
if (!xforms[xformIndex].Apply(&firstBadPoint, point, rand))
return true;
}
//After 5 tries, nothing worked, so just assign random values between -1 and 1.
if (consec == 5)
{
point->m_X = rand.Frand11<T>();
point->m_Y = rand.Frand11<T>();
point->m_Z = 0;
}
return false;
}
/// <summary>
/// Apply the final xform.
/// Note that as stated in the paper, the output of the final xform is not fed back into the next iteration.
/// Rather, only the value computed from the randomly chosen xform is. However, the output of the final xform
/// is still saved in the output samples buffer and accumulated to the histogram later.
/// </summary>
/// <param name="ember">The ember being iterated</param>
/// <param name="tempPoint">The input point</param>
/// <param name="sample">The output point</param>
/// <param name="rand">The random context to use.</param>
inline void DoFinalXform(Ember<T>& ember, Point<T>& tempPoint, Point<T>* sample, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
if (IsClose<T>(ember.FinalXform()->m_Opacity, 1) || rand.Frand01<T>() < ember.FinalXform()->m_Opacity)
{
T tempVizAdjusted = tempPoint.m_VizAdjusted;
ember.NonConstFinalXform()->Apply(&tempPoint, sample, rand);
sample->m_VizAdjusted = tempVizAdjusted;
}
}
/// <summary>
/// Retrieve an element in the distributions vector between 0 and CHOOSE_XFORM_GRAIN which will
/// contain the index of the next xform to use. When xaos is prsent, the offset is the index in
/// the ember of the previous xform used when.
/// </summary>
/// <param name="index">The index to retrieve</param>
/// <param name="distribOffset">When xaos is prsent, the index of the previous xform used. Default: 0 (xaos not present).</param>
/// <returns></returns>
unsigned int NextXformFromIndex(unsigned int index, unsigned int distribOffset = 0)
{
return (unsigned int)m_XformDistributions[(index % CHOOSE_XFORM_GRAIN) + (CHOOSE_XFORM_GRAIN * distribOffset)];
}
vector<unsigned char> m_XformDistributions;
};
/// <summary>
/// Derived iterator class for embers whose xforms do not use xaos.
/// </summary>
template <typename T>
class EMBER_API StandardIterator : public Iterator<T>
{
public:
/// <summary>
/// Empty constructor.
/// </summary>
StandardIterator()
{
}
/// <summary>
/// Overridden virtual function which iterates an ember a given number of times and does not use xaos.
/// </summary>
/// <param name="ember">The ember whose xforms will be applied</param>
/// <param name="count">The number of iterations to do</param>
/// <param name="skip">The number of times to fuse</param>
/// <param name="samples">The buffer to store the output points</param>
/// <param name="rand">The random context to use</param>
/// <returns>The number of bad values</returns>
virtual unsigned int Iterate(Ember<T>& ember, unsigned int count, unsigned int skip, Point<T>* samples, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
unsigned int i, badVals = 0;
Point<T> tempPoint, p1;
Xform<T>* xforms = ember.NonConstXforms();
if (ember.ProjBits())
{
if (ember.UseFinalXform())
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
DoBadVals(xforms, badVals, &p1, rand);
}
DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples.
ember.Proj(samples[0], rand);
for (i = 1; i < count; i++)//Real loop.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
DoBadVals(xforms, badVals, &p1, rand);
DoFinalXform(ember, p1, samples + i, rand);
ember.Proj(samples[i], rand);
}
}
else
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
DoBadVals(xforms, badVals, &p1, rand);
}
samples[0] = p1;
ember.Proj(samples[0], rand);
for (i = 1; i < count; i++)//Real loop.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &samples[i], rand))
DoBadVals(xforms, badVals, samples + i, rand);
p1 = samples[i];
ember.Proj(samples[i], rand);
}
}
}
else
{
if (ember.UseFinalXform())
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
DoBadVals(xforms, badVals, &p1, rand);
}
DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples.
for (i = 1; i < count; i++)//Real loop.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))//Feed the resulting value of applying the randomly selected xform back into the next iter, and not the result of applying the final xform.
DoBadVals(xforms, badVals, &p1, rand);
DoFinalXform(ember, p1, samples + i, rand);
}
}
else
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
DoBadVals(xforms, badVals, &p1, rand);
}
samples[0] = p1;
for (i = 0; i < count - 1; i++)//Real loop.
if (xforms[NextXformFromIndex(rand.Rand())].Apply(samples + i, samples + i + 1, rand))
DoBadVals(xforms, badVals, samples + i + 1, rand);
}
}
return badVals;
}
};
/// <summary>
/// Derived iterator class for embers whose xforms use xaos.
/// </summary>
template <typename T>
class EMBER_API XaosIterator : public Iterator<T>
{
public:
/// <summary>
/// Empty constructor.
/// </summary>
XaosIterator()
{
}
/// <summary>
/// Handler for bad values similar to the one in the base class, except it takes the last xform used
/// as a parameter and saves the xform used back out because this iterator is meant to be used with xaos.
/// </summary>
/// <param name="xforms">The xforms array</param>
/// <param name="xformIndex">Index of the last used xform before calling this function</param>
/// <param name="lastXformUsed">The saved index of the last xform used within this function</param>
/// <param name="badVals">The counter for the total number of bad values this sub batch</param>
/// <param name="point">The point which initially had the bad values and which will store the newly computed values</param>
/// <param name="rand">The random context this iterator is using</param>
/// <returns>True if a good value was computed within 5 tries, else false</returns>
inline bool DoBadVals(Xform<T>* xforms, unsigned int& xformIndex, unsigned int lastXformUsed, unsigned int& badVals, Point<T>* point, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
unsigned int consec = 0;
Point<T> firstBadPoint;
while (consec < 5)
{
consec++;
badVals++;
firstBadPoint.m_X = rand.Frand11<T>();//Re-randomize points, but keep the computed color and viz.
firstBadPoint.m_Y = rand.Frand11<T>();
firstBadPoint.m_Z = 0;
firstBadPoint.m_ColorX = point->m_ColorX;
firstBadPoint.m_VizAdjusted = point->m_VizAdjusted;
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (!xforms[xformIndex].Apply(&firstBadPoint, point, rand))
return true;
}
//After 5 tries, nothing worked, so just assign random.
if (consec == 5)
{
point->m_X = rand.Frand11<T>();
point->m_Y = rand.Frand11<T>();
point->m_Z = 0;
}
return false;
}
/// <summary>
/// Overridden virtual function which iterates an ember a given number of times and uses xaos.
/// </summary>
/// <param name="ember">The ember whose xforms will be applied</param>
/// <param name="count">The number of iterations to do</param>
/// <param name="skip">The number of times to fuse</param>
/// <param name="samples">The buffer to store the output points</param>
/// <param name="rand">The random context to use</param>
/// <returns>The number of bad values</returns>
virtual unsigned int Iterate(Ember<T>& ember, unsigned int count, unsigned int skip, Point<T>* samples, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
unsigned int i, xformIndex;
unsigned int lastXformUsed = 0;
unsigned int badVals = 0;
Point<T> tempPoint, p1;
Xform<T>* xforms = ember.NonConstXforms();
if (ember.ProjBits())
{
if (ember.UseFinalXform())
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples.
ember.Proj(samples[0], rand);
for (i = 1; i < count; i++)//Real loop.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))//Feed the resulting value of applying the randomly selected xform back into the next iter, and not the result of applying the final xform.
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
DoFinalXform(ember, p1, samples + i, rand);
ember.Proj(samples[i], rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
}
else
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
ember.Proj(p1, rand);
samples[0] = p1;
for (i = 1; i < count; i++)//Real loop.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
samples[i] = p1;
ember.Proj(samples[i], rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
}
}
else
{
if (ember.UseFinalXform())
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples.
for (i = 1; i < count; i++)//Real loop.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))//Feed the resulting value of applying the randomly selected xform back into the next iter, and not the result of applying the final xform.
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
DoFinalXform(ember, p1, samples + i, rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
}
else
{
p1 = samples[0];
for (i = 0; i < skip; i++)//Fuse.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(&p1, &p1, rand))
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
samples[0] = p1;
for (i = 0; i < count - 1; i++)//Real loop.
{
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
if (xforms[xformIndex].Apply(samples + i, samples + i + 1, rand))
DoBadVals(xforms, xformIndex, lastXformUsed, badVals, samples + i + 1, rand);
lastXformUsed = xformIndex + 1;//Store the last used transform.
}
}
}
return badVals;
}
};
}

571
Source/Ember/Palette.h Normal file
View File

@ -0,0 +1,571 @@
#pragma once
#include "Utils.h"
#include "Isaac.h"
/// <summary>
/// Palette class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// The palette stores a set of 256 colors which are what get accumulated to the histogram
/// for each iteration. The colors come from either the main palette Xml file or directly
/// from the ember parameter file. Either way, they come in as 0-255 and get normalized to 0-1.
/// In the future, 2D palette support might be added in which case this class will have to be modified.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API Palette
{
public:
/// <summary>
/// Constructor which sets the palette index to random and allocates space to hold the color entries.
/// </summary>
Palette()
{
m_Name = "-";
m_Index = -1;
m_Entries.resize(COLORMAP_LENGTH);
Clear();
}
/// <summary>
/// Constructor that takes a name various parameters. If no color buffer is specified, a default is used.
/// This is a safety fallback, and it's highly recommended to always supply a buffer of color entries.
/// </summary>
/// <param name="name">The name of the palette</param>
/// <param name="index">The index in the palette file</param>
/// <param name="size">The size of the palette which should be 256</param>
/// <param name="xmlPaletteEntries">A pointer to 256 color entries</param>
Palette(string name, int index, unsigned int size, v4T* xmlPaletteEntries)
{
m_Name = name;
m_Index = index;
m_Entries.resize(size);
if (xmlPaletteEntries)
{
memcpy(&m_Entries[0], xmlPaletteEntries, Size() * sizeof(m_Entries[0]));
}
else//They passed in null, so just fill with hard coded values so they at least have something.
{
//Palette 15 used in the test ember file.
unsigned char palette15[COLORMAP_LENGTH * 4] = {
0x00, 0xda, 0xde, 0xbc, 0x00, 0xee, 0xe6, 0xc5, 0x00, 0xee, 0xf2, 0xce, 0x00, 0xee, 0xf2, 0xcf, 0x00, 0xe6, 0xee, 0xe1, 0x00, 0xea, 0xee, 0xd8, 0x00, 0xf2, 0xf1, 0xeb, 0x00, 0xf2, 0xf5, 0xd8,
0x00, 0xe6, 0xf2, 0xce, 0x00, 0xde, 0xea, 0xc5, 0x00, 0xd6, 0xda, 0xc6, 0x00, 0xce, 0xd2, 0xbc, 0x00, 0xc2, 0xca, 0xa9, 0x00, 0xbe, 0xca, 0xa0, 0x00, 0xce, 0xd6, 0xaa, 0x00, 0xde, 0xe2, 0xc5,
0x00, 0xea, 0xed, 0xce, 0x00, 0xea, 0xf2, 0xc5, 0x00, 0xde, 0xe2, 0xc5, 0x00, 0xc2, 0xca, 0xaa, 0x00, 0xae, 0xbe, 0xaa, 0x00, 0xa5, 0xb2, 0x96, 0x00, 0xa2, 0xa9, 0x8d, 0x00, 0x96, 0xa2, 0x84,
0x00, 0x8d, 0x8d, 0x7a, 0x00, 0x85, 0x89, 0x71, 0x00, 0x85, 0x8d, 0x71, 0x00, 0x85, 0x85, 0x67, 0x00, 0x79, 0x7d, 0x67, 0x00, 0x79, 0x7d, 0x67, 0x00, 0x71, 0x79, 0x5e, 0x00, 0x65, 0x6d, 0x55,
0x00, 0x4d, 0x5d, 0x42, 0x00, 0x34, 0x40, 0x25, 0x00, 0x30, 0x40, 0x25, 0x00, 0x30, 0x38, 0x1c, 0x00, 0x2c, 0x3c, 0x1c, 0x00, 0x2c, 0x34, 0x1c, 0x00, 0x24, 0x2c, 0x12, 0x00, 0x24, 0x24, 0x00,
0x00, 0x24, 0x2c, 0x09, 0x00, 0x28, 0x34, 0x09, 0x00, 0x38, 0x40, 0x12, 0x00, 0x30, 0x40, 0x1c, 0x00, 0x40, 0x50, 0x2f, 0x00, 0x55, 0x69, 0x42, 0x00, 0x65, 0x75, 0x55, 0x00, 0x6c, 0x7d, 0x5e,
0x00, 0x74, 0x8d, 0x71, 0x00, 0x74, 0x89, 0x84, 0x00, 0x74, 0x8d, 0x84, 0x00, 0x78, 0x8d, 0x84, 0x00, 0x79, 0x89, 0x7a, 0x00, 0x79, 0x85, 0x71, 0x00, 0x75, 0x7d, 0x67, 0x00, 0x71, 0x79, 0x5e,
0x00, 0x6c, 0x71, 0x5e, 0x00, 0x6d, 0x70, 0x5e, 0x00, 0x6c, 0x79, 0x5e, 0x00, 0x68, 0x75, 0x5e, 0x00, 0x69, 0x71, 0x55, 0x00, 0x6d, 0x75, 0x55, 0x00, 0x6d, 0x75, 0x55, 0x00, 0x69, 0x71, 0x55,
0x00, 0x65, 0x71, 0x55, 0x00, 0x69, 0x6d, 0x55, 0x00, 0x64, 0x71, 0x5e, 0x00, 0x68, 0x70, 0x67, 0x00, 0x68, 0x70, 0x67, 0x00, 0x68, 0x6c, 0x67, 0x00, 0x6c, 0x6c, 0x5e, 0x00, 0x71, 0x71, 0x5e,
0x00, 0x79, 0x79, 0x67, 0x00, 0x81, 0x85, 0x71, 0x00, 0x7d, 0x91, 0x71, 0x00, 0x85, 0x92, 0x7a, 0x00, 0x85, 0x92, 0x7a, 0x00, 0x7d, 0x92, 0x84, 0x00, 0x79, 0x92, 0x84, 0x00, 0x78, 0x92, 0x8d,
0x00, 0x78, 0x8d, 0x8d, 0x00, 0x74, 0x8d, 0x84, 0x00, 0x74, 0x92, 0x84, 0x00, 0x75, 0x92, 0x7a, 0x00, 0x6c, 0x85, 0x67, 0x00, 0x64, 0x79, 0x5e, 0x00, 0x59, 0x69, 0x4b, 0x00, 0xaa, 0x57, 0x00,
0x00, 0x38, 0x44, 0x1c, 0x00, 0x30, 0x3c, 0x1c, 0x00, 0x2c, 0x3c, 0x1c, 0x00, 0x34, 0x40, 0x25, 0x00, 0x50, 0x61, 0x4b, 0x00, 0x5d, 0x6d, 0x5e, 0x00, 0x64, 0x71, 0x5e, 0x00, 0x60, 0x71, 0x5e,
0x00, 0x60, 0x75, 0x5e, 0x00, 0x68, 0x75, 0x5e, 0x00, 0x6c, 0x79, 0x5e, 0x00, 0x6c, 0x79, 0x5e, 0x00, 0x71, 0x79, 0x67, 0x00, 0x70, 0x79, 0x67, 0x00, 0x6c, 0x7d, 0x67, 0x00, 0x68, 0x79, 0x67,
0x00, 0x6c, 0x79, 0x67, 0x00, 0x6c, 0x75, 0x67, 0x00, 0x71, 0x75, 0x5e, 0x00, 0x71, 0x75, 0x5e, 0x00, 0x75, 0x79, 0x5e, 0x00, 0x75, 0x7d, 0x5e, 0x00, 0x81, 0x8d, 0x5e, 0x00, 0x8d, 0x92, 0x5e,
0x00, 0x8d, 0x92, 0x67, 0x00, 0x9a, 0x9a, 0x71, 0x00, 0x9a, 0xa2, 0x7a, 0x00, 0x9a, 0xa2, 0x7a, 0x00, 0x9a, 0xa1, 0x7a, 0x00, 0x92, 0x9a, 0x71, 0x00, 0x89, 0x92, 0x67, 0x00, 0x81, 0x85, 0x5e,
0x00, 0x7d, 0x7d, 0x55, 0x00, 0x69, 0x79, 0x4b, 0x00, 0x61, 0x6d, 0x42, 0x00, 0x44, 0x4c, 0x25, 0x00, 0x38, 0x44, 0x1c, 0x00, 0x40, 0x51, 0x25, 0x00, 0x45, 0x4d, 0x25, 0x00, 0x71, 0x6d, 0x42,
0x00, 0x79, 0x7d, 0x4b, 0x00, 0x81, 0x7d, 0x55, 0x00, 0x79, 0x79, 0x55, 0x00, 0x6d, 0x75, 0x55, 0x00, 0x69, 0x7d, 0x55, 0x00, 0x6c, 0x79, 0x5e, 0x00, 0x65, 0x79, 0x54, 0x00, 0x68, 0x79, 0x5e,
0x00, 0x64, 0x79, 0x67, 0x00, 0x64, 0x79, 0x67, 0x00, 0x68, 0x75, 0x5e, 0x00, 0x64, 0x71, 0x5e, 0x00, 0x64, 0x6c, 0x5e, 0x00, 0x65, 0x6d, 0x55, 0x00, 0x4d, 0x58, 0x42, 0x00, 0x34, 0x40, 0x25,
0x00, 0x2c, 0x38, 0x1c, 0x00, 0x20, 0x28, 0x1c, 0x00, 0x1c, 0x14, 0x09, 0x00, 0x18, 0x18, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x1c, 0x28, 0x09,
0x00, 0x24, 0x30, 0x12, 0x00, 0x3c, 0x44, 0x25, 0x00, 0x5d, 0x65, 0x55, 0x00, 0x75, 0x79, 0x55, 0x00, 0x85, 0x89, 0x5e, 0x00, 0x89, 0x91, 0x71, 0x00, 0x96, 0xa2, 0x71, 0x00, 0x9a, 0xa2, 0x7a,
0x00, 0x9e, 0xaa, 0x7a, 0x00, 0x9e, 0xaa, 0x7a, 0x00, 0xaa, 0xae, 0x71, 0x00, 0xa6, 0xaa, 0x7a, 0x00, 0xa2, 0xaa, 0x7a, 0x00, 0xa1, 0xa5, 0x7a, 0x00, 0x96, 0x9e, 0x7a, 0x00, 0x85, 0x96, 0x7a,
0x00, 0x81, 0x92, 0x7a, 0x00, 0x78, 0x92, 0x7a, 0x00, 0x75, 0x92, 0x7a, 0x00, 0x75, 0x8d, 0x7a, 0x00, 0x70, 0x81, 0x67, 0x00, 0x7d, 0x7d, 0x67, 0x00, 0x89, 0x89, 0x67, 0x00, 0x92, 0x9a, 0x71,
0x00, 0x9e, 0xaa, 0x7a, 0x00, 0xaa, 0xb6, 0x84, 0x00, 0xb2, 0xb6, 0x8d, 0x00, 0xb6, 0xba, 0x97, 0x00, 0xc2, 0xca, 0x97, 0x00, 0xb2, 0xbe, 0x8d, 0x00, 0xb2, 0xb6, 0x8d, 0x00, 0xaa, 0xb2, 0x8d,
0x00, 0xa2, 0xae, 0x84, 0x00, 0x9a, 0xa6, 0x7a, 0x00, 0x92, 0x9e, 0x7a, 0x00, 0x85, 0x9a, 0x7a, 0x00, 0x7d, 0x96, 0x7a, 0x00, 0x7d, 0x92, 0x7a, 0x00, 0x7d, 0x92, 0x84, 0x00, 0x7d, 0x92, 0x84,
0x00, 0x81, 0x96, 0x84, 0x00, 0x85, 0x96, 0x84, 0x00, 0x85, 0x96, 0x84, 0x00, 0x81, 0x92, 0x84, 0x00, 0x85, 0x9a, 0x84, 0x00, 0x85, 0x9a, 0x84, 0x00, 0x8d, 0x9a, 0x84, 0x00, 0x92, 0x96, 0x84,
0x00, 0x9e, 0xa9, 0x84, 0x00, 0xae, 0xb2, 0x84, 0x00, 0xaa, 0xba, 0x84, 0x00, 0xb2, 0xbe, 0x8d, 0x00, 0xb6, 0xc2, 0xa0, 0x00, 0xc6, 0xca, 0xa0, 0x00, 0xc6, 0xce, 0xaa, 0x00, 0xd6, 0xda, 0xb3,
0x00, 0xda, 0xe2, 0xc5, 0x00, 0xd2, 0xd6, 0xbc, 0x00, 0xbe, 0xc2, 0xa0, 0x00, 0xaa, 0xb6, 0x8d, 0x00, 0x9e, 0xa6, 0x7a, 0x00, 0x92, 0x9a, 0x71, 0x00, 0x89, 0x89, 0x71, 0x00, 0x81, 0x7d, 0x67,
0x00, 0x7d, 0x7d, 0x67, 0x00, 0x81, 0x78, 0x67, 0x00, 0x7d, 0x7d, 0x5e, 0x00, 0x79, 0x79, 0x5e, 0x00, 0x79, 0x81, 0x5e, 0x00, 0x81, 0x7d, 0x67, 0x00, 0x81, 0x7d, 0x67, 0x00, 0x81, 0x81, 0x67,
0x00, 0x81, 0x89, 0x71, 0x00, 0x85, 0x91, 0x7a, 0x00, 0x89, 0x92, 0x7a, 0x00, 0x96, 0x9d, 0x7a, 0x00, 0x96, 0x9e, 0x7a, 0x00, 0x92, 0x96, 0x84, 0x00, 0x96, 0x9a, 0x8d, 0x00, 0x92, 0x92, 0x84,
0x00, 0x89, 0x91, 0x84, 0x00, 0x81, 0x92, 0x84, 0x00, 0x7d, 0x92, 0x8d, 0x00, 0x78, 0x92, 0x8d, 0x00, 0x74, 0x92, 0x8d, 0x00, 0x78, 0x92, 0x8d, 0x00, 0x78, 0x96, 0x97, 0x00, 0x81, 0x96, 0x8d,
0x00, 0x81, 0x96, 0x8d, 0x00, 0x81, 0x9a, 0x8d, 0x00, 0x85, 0x9a, 0x8d, 0x00, 0x89, 0x9e, 0x8d, 0x00, 0x89, 0x9e, 0x8d, 0x00, 0x8d, 0xa2, 0x97, 0x00, 0x95, 0xa2, 0x97, 0x00, 0x8d, 0xa2, 0x97,
0x00, 0x96, 0xa6, 0x8d, 0x00, 0x9a, 0xa1, 0x8d, 0x00, 0x9e, 0xa9, 0x84, 0x00, 0x9e, 0xa6, 0x7a, 0x00, 0xa2, 0xa5, 0x71, 0x00, 0x9e, 0xa6, 0x71, 0x00, 0x9a, 0xa6, 0x71, 0x00, 0x95, 0x9d, 0x71 };
for (unsigned int i = 0; i < size; i++)
{
m_Entries[i].a = (T)palette15[i * 4 + 0];
m_Entries[i].r = (T)palette15[i * 4 + 1];
m_Entries[i].g = (T)palette15[i * 4 + 2];
m_Entries[i].b = (T)palette15[i * 4 + 3];
}
}
}
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="palette">The Palette object to copy</param>
Palette(const Palette<T>& palette)
{
Palette<T>::operator=<T>(palette);
}
/// <summary>
/// Copy constructor to copy a Palette object of type U.
/// </summary>
/// <param name="palette">The Palette object to copy</param>
template <typename U>
Palette(const Palette<U>& palette)
{
Palette<T>::operator=<U>(palette);
}
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="palette">The Palette object to copy</param>
Palette<T>& operator = (const Palette<T>& palette)
{
if (this != &palette)
Palette<T>::operator=<T>(palette);
return *this;
}
/// <summary>
/// Assignment operator to assign a Palette object of type U.
/// </summary>
/// <param name="palette">The Palette object to copy</param>
/// <returns>Reference to updated self</returns>
template <typename U>
Palette<T>& operator = (const Palette<U>& palette)
{
m_Index = palette.m_Index;
m_Name = palette.m_Name;
CopyVec(m_Entries, palette.m_Entries);
return *this;
}
/// <summary>
/// Convenience [] operator to index into the color entries vector.
/// </summary>
/// <param name="i">The index to get</param>
/// <returns>The color value at the specified index</returns>
v4T& operator[] (size_t i)
{
return m_Entries[i];
}
/// <summary>
/// Convenience * operator to get a pointer to the beginning of the color entries vector.
/// </summary>
/// <returns>The address of the first element in the color entries vector</returns>
inline v4T* operator() (void)
{
return &m_Entries[0];
}
/// <summary>
/// The size of the color entries vector.
/// </summary>
/// <returns>The size of the color entries vector</returns>
size_t Size() { return m_Entries.size(); }
/// <summary>
/// Set all colors to either black or white, including the alpha channel.
/// </summary>
/// <param name="black">Set all colors to black if true, else white</param>
void Clear(bool black = true)
{
for (glm::length_t i = 0; i < Size(); i++)
{
for (glm::length_t j = 0; j < 4; j++)
{
if (black)
m_Entries[i][j] = 0;
else
m_Entries[i][j] = 1;
}
}
}
/// <summary>
/// Make a copy of this palette, adjust for hue and store in the passed in palette.
/// This is used because one way an ember Xml can specify color is with an index in the
/// palette Xml file and a hue rotation value.
/// </summary>
/// <param name="palette">The palette to store the results in</param>
/// <param name="hue">The hue rotation to apply</param>
void MakeHueAdjustedPalette(Palette<T>& palette, T hue)
{
palette.m_Index = m_Index;
palette.m_Name = m_Name;
palette.m_Entries.resize(Size());
for (unsigned int i = 0; i < Size(); i++)
{
size_t ii = (i * 256) / COLORMAP_LENGTH;
T rgb[3], hsv[3];
rgb[0] = m_Entries[ii].r;
rgb[1] = m_Entries[ii].g;
rgb[2] = m_Entries[ii].b;
RgbToHsv(rgb, hsv);
hsv[0] += hue * T(6.0);
HsvToRgb(hsv, rgb);
//Alpha serves as merely a hit counter that gets incremented by 1 each time, see Renderer::Accumulate() for its usage.
//Removing it saves no memory since it's 16 byte aligned. This also means alpha is not used.
palette[i].r = rgb[0];
palette[i].g = rgb[1];
palette[i].b = rgb[2];
palette[i].a = 1;
}
}
/// <summary>
/// More advanced adjustment than MakeHueAdjustedPalette() provides.
/// Adjustments are applied in the order:
/// Frequency, index rotation, hue rotation, saturation, brightness, contrast, blur.
/// </summary>
/// <param name="palette">The palette to store the result in</param>
/// <param name="rot">Index rotation.</param>
/// <param name="hue">Hue rotation -5 - 5</param>
/// <param name="sat">Saturation 0 - 1</param>
/// <param name="bright">Brightness 0 - 1</param>
/// <param name="cont">Contrast -1 - 2</param>
/// <param name="blur">Blur 0 - 127</param>
/// <param name="freq">Frequency 1 - 10</param>
void MakeAdjustedPalette(Palette<T>& palette, int rot, T hue, T sat, T bright, T cont, unsigned int blur, unsigned int freq)
{
T rgb[3], hsv[3];
if (freq > 1)
{
size_t n = Size() / freq;
for (size_t j = 0; j <= freq; j++)
{
for (size_t i = 0; i <= n; i++)
{
if ((i + j * n) < Size())
{
palette[i + j * n].r = m_Entries[i * freq].r;
palette[i + j * n].g = m_Entries[i * freq].g;
palette[i + j * n].b = m_Entries[i * freq].b;
}
}
}
palette.m_Name = m_Name;
}
else
{
palette = *this;
}
for (size_t i = 0; i < Size(); i++)
{
size_t ii = (i * 256) / COLORMAP_LENGTH;
rgb[0] = palette[(COLORMAP_LENGTH + ii - rot) % COLORMAP_LENGTH].r;//Rotation.
rgb[1] = palette[(COLORMAP_LENGTH + ii - rot) % COLORMAP_LENGTH].g;
rgb[2] = palette[(COLORMAP_LENGTH + ii - rot) % COLORMAP_LENGTH].b;
RgbToHsv(rgb, hsv);
hsv[0] += hue * T(6.0);//Hue.
hsv[1] = Clamp<T>(hsv[1] + sat, 0, 1);//Saturation.
HsvToRgb(hsv, rgb);
rgb[0] = Clamp<T>(rgb[0] + bright, 0, 1);//Brightness.
rgb[1] = Clamp<T>(rgb[1] + bright, 0, 1);
rgb[2] = Clamp<T>(rgb[2] + bright, 0, 1);
rgb[0] = Clamp<T>(((rgb[0] - T(0.5)) * (cont + T(1.0))) + T(0.5), 0, 1);//Contrast.
rgb[1] = Clamp<T>(((rgb[1] - T(0.5)) * (cont + T(1.0))) + T(0.5), 0, 1);
rgb[2] = Clamp<T>(((rgb[2] - T(0.5)) * (cont + T(1.0))) + T(0.5), 0, 1);
//Alpha serves as merely a hit counter that gets incremented by 1 each time, see Renderer::Accumulate() for its usage.
//Removing it saves no memory since it's 16 byte aligned.
palette[i].r = rgb[0];
palette[i].g = rgb[1];
palette[i].b = rgb[2];
palette[i].a = 1;
}
if (blur > 0)
{
Palette<T> blurPal = palette;
for (int i = 0; i < 256; i++)
{
int n = -1;
rgb[0] = 0;
rgb[1] = 0;
rgb[2] = 0;
for (int j = i - (int)blur; j <= i + (int)blur; j++)
{
n++;
int k = (256 + j) % 256;
if (k != i)
{
rgb[0] = rgb[0] + blurPal[k].r;
rgb[1] = rgb[1] + blurPal[k].g;
rgb[2] = rgb[2] + blurPal[k].b;
}
}
if (n != 0)
{
palette[i].r = rgb[0] / n;
palette[i].g = rgb[1] / n;
palette[i].b = rgb[2] / n;
}
}
}
}
/// <summary>
/// Make a copy of this palette and multiply all RGB values by a scalar.
/// </summary>
/// <param name="palette">The palette to store the result in</param>
/// <param name="colorScalar">The color scalar to multiply each RGB value by</param>
template<typename bucketT>
void MakeDmap(Palette<bucketT>& palette, T colorScalar = 1)
{
palette.m_Index = m_Index;
palette.m_Name = m_Name;
if (palette.Size() != Size())
palette.m_Entries.resize(Size());
for (unsigned int j = 0; j < palette.Size(); j++)
{
palette.m_Entries[j] = m_Entries[j] * colorScalar;
palette.m_Entries[j].a = 1;
}
}
/// <summary>
/// Make a buffer with the color values of this palette scaled to 255
/// and repeated for a number of rows.
/// Convenience function for displaying this palette on a GUI.
/// </summary>
/// <param name="height">The height of the output block</param>
/// <returns>A vector holding the color values</returns>
vector<unsigned char> MakeRgbPaletteBlock(unsigned int height)
{
size_t width = Size();
vector<unsigned char> v(height * width * 3);
if (v.size() == (height * Size() * 3))
{
for (unsigned int i = 0; i < height; i++)
{
for (unsigned int j = 0; j < width; j++)
{
v[(width * 3 * i) + (j * 3)] = (unsigned char)(m_Entries[j][0] * T(255));//Palettes are as [0..1], so convert to [0..255] here since it's for GUI display.
v[(width * 3 * i) + (j * 3) + 1] = (unsigned char)(m_Entries[j][1] * T(255));
v[(width * 3 * i) + (j * 3) + 2] = (unsigned char)(m_Entries[j][2] * T(255));
}
}
}
return v;
}
/// <summary>
/// Convert RGB to HSV.
/// </summary>
/// <param name="r">Red 0 - 1</param>
/// <param name="g">Green 0 - 1</param>
/// <param name="b">Blue 0 - 1</param>
/// <param name="h">Hue 0 - 6</param>
/// <param name="s">Saturation 0 - 1</param>
/// <param name="v">Value 0 - 1</param>
static void RgbToHsv(T r, T g, T b, T& h, T& s, T& v)
{
T max, min, del, rc, gc, bc;
max = std::max(std::max(r, g), b);//Compute maximum of r, g, b.
min = std::min(std::min(r, g), b);//Compute minimum of r, g, b.
del = max - min;
v = max;
s = (max != 0) ? (del / max) : 0;
h = 0;
if (s != 0)
{
rc = (max - r) / del;
gc = (max - g) / del;
bc = (max - b) / del;
if (r == max)
h = bc - gc;
else if (g == max)
h = 2 + rc - bc;
else if (b == max)
h = 4 + gc - rc;
if (h < 0)
h += 6;
}
}
/// <summary>
/// Wrapper around RgbToHsv() which takes buffers as parameters instead of individual components.
/// </summary>
/// <param name="rgb">The RGB buffer</param>
/// <param name="hsv">The HSV buffer</param>
static void RgbToHsv(T* rgb, T* hsv)
{
RgbToHsv(rgb[0], rgb[1], rgb[2], hsv[0], hsv[1], hsv[2]);
}
/// <summary>
/// Convert HSV to RGB.
/// </summary>
/// <param name="h">Hue 0 - 6</param>
/// <param name="s">Saturation 0 - 1</param>
/// <param name="v">Value 0 - 1</param>
/// <param name="r">Red 0 - 1</param>
/// <param name="g">Green 0 - 1</param>
/// <param name="b">Blue 0 - 1</param>
static void HsvToRgb(T h, T s, T v, T& r, T& g, T& b)
{
int j;
T f, p, q, t;
while (h >= 6)
h -= 6;
while (h < 0)
h += 6;
j = Floor<T>(h);
f = h - j;
p = v * (1 - s);
q = v * (1 - (s * f));
t = v * (1 - (s * (1 - f)));
switch (j)
{
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
default: r = v; g = t; b = p; break;
}
}
/// <summary>
/// Wrapper around HsvToRgb() which takes buffers as parameters instead of individual components.
/// </summary>
/// <param name="hsv">The HSV buffer</param>
/// <param name="rgb">The RGB buffer</param>
static void HsvToRgb(T* hsv, T* rgb)
{
HsvToRgb(hsv[0], hsv[1], hsv[2], rgb[0], rgb[1], rgb[2]);
}
/// <summary>
/// Calculates the alpha.
/// Used for gamma correction in final accumulation.
/// Not the slightest clue what this is doing.
/// </summary>
/// <param name="density">Density</param>
/// <param name="gamma">Gamma</param>
/// <param name="linrange">Linear range</param>
/// <returns>Alpha</returns>
static T CalcAlpha(T density, T gamma, T linrange)
{
T frac, alpha;
T funcval = pow(linrange, gamma);
if (density > 0)
{
if (density < linrange)
{
frac = density / linrange;
alpha = (T(1.0) - frac) * density * (funcval / linrange) + frac * pow(density, gamma);
}
else
alpha = pow(density, gamma);
}
else
alpha = 0;
return alpha;
}
/// <summary>
/// Calculates the new RGB and stores in the supplied buffer.
/// Used for gamma correction in final accumulation.
/// Not the slightest clue what this is doing.
/// </summary>
/// <param name="cBuf">The input RGB color buffer 0 - 1</param>
/// <param name="ls">Log scaling</param>
/// <param name="highPow">Highlight power, -1 - 1</param>
/// <param name="newRgb">Newly computed RGB value</param>
template<typename bucketT>
static void CalcNewRgb(bucketT* cBuf, T ls, T highPow, bucketT* newRgb)
{
int rgbi;
T newls, lsratio;
bucketT newhsv[3];
T maxa, maxc;
T adjustedHighlight;
if (ls == 0 || (cBuf[0] == 0 && cBuf[1] == 0 && cBuf[2] == 0))
{
newRgb[0] = 0;
newRgb[1] = 0;
newRgb[2] = 0;
return;
}
//Identify the most saturated channel.
maxc = max(max(cBuf[0], cBuf[1]), cBuf[2]);
maxa = ls * maxc;
//If a channel is saturated and highlight power is non-negative
//modify the color to prevent hue shift.
if (maxa > 255 && highPow >= 0)
{
newls = T(255.0) / maxc;
lsratio = pow(newls / ls, highPow);
//Calculate the max-value color (ranged 0 - 1).
for (rgbi = 0; rgbi < 3; rgbi++)
newRgb[rgbi] = (bucketT)newls * cBuf[rgbi] / bucketT(255.0);
//Reduce saturation by the lsratio.
Palette<bucketT>::RgbToHsv(newRgb, newhsv);
newhsv[1] *= (bucketT)lsratio;
Palette<bucketT>::HsvToRgb(newhsv, newRgb);
for (rgbi = 0; rgbi < 3; rgbi++)
newRgb[rgbi] *= T(255.0);
}
else
{
newls = T(255.0) / maxc;
adjustedHighlight = -highPow;
if (adjustedHighlight > 1)
adjustedHighlight = 1;
if (maxa <= 255)
adjustedHighlight = 1;
//Calculate the max-value color (ranged 0 - 1) interpolated with the old behavior.
for (rgbi = 0; rgbi < 3; rgbi++)
newRgb[rgbi] = bucketT((T(1.0) - adjustedHighlight) * newls + adjustedHighlight * ls) * cBuf[rgbi];
}
}
int m_Index;//Index in the xml palette file of this palette, use -1 for random.
string m_Name;//Name of this palette.
vector<v4T> m_Entries;//Storage for the color values.
};
}

226
Source/Ember/PaletteList.h Normal file
View File

@ -0,0 +1,226 @@
#pragma once
#include "Palette.h"
/// <summary>
/// PaletteList class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Holds a list of palettes read from an Xml file. Since the default list from flam3-palettes.xml is fairly large at 700 palettes,
/// the list member is kept as a static. This class derives from EmberReport in order to report any errors that occurred while reading the Xml.
/// Note that although the Xml color values are expected to be 0-255, they are converted and stored as normalized colors, with values from 0-1.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API PaletteList : public EmberReport
{
public:
/// <summary>
/// Empty constructor which does nothing.
/// </summary>
PaletteList()
{
}
/// <summary>
/// Read an Xml palette file into memory.
/// This must be called before any palette file usage.
/// </summary>
/// <param name="filename">The full path to the file to read</param>
/// <param name="force">If true, override the initialization state and force a read, else observe the initialization state.</param>
/// <returns>The initialization state</returns>
bool Init(string filename, bool force = false)
{
if (!m_Init || force)
{
const char* loc = __FUNCTION__;
m_Init = false;
m_Palettes.clear();
m_ErrorReport.clear();
string buf;
if (ReadFile(filename.c_str(), buf))
{
xmlDocPtr doc = xmlReadMemory((const char*)buf.data(), (int)buf.size(), filename.c_str(), NULL, XML_PARSE_NONET);
if (doc != NULL)
{
xmlNode* rootNode = xmlDocGetRootElement(doc);
m_Palettes.reserve(buf.size() / 2048);//Roughly what it takes per palette.
ParsePalettes(rootNode);
xmlFreeDoc(doc);
m_Init = m_ErrorReport.empty();
}
else
{
m_ErrorReport.push_back(string(loc) + " : Couldn't load xml doc");
}
}
else
{
m_ErrorReport.push_back(string(loc) + " : Couldn't read palette file " + filename);
}
}
return m_Init;
}
/// <summary>
/// Gets the palette at a specified index.
/// </summary>
/// <param name="i">The index of the palette to read. A value of -1 indicates a random palette.</param>
/// <returns>A pointer to the requested palette if the index was in range, else NULL.</returns>
Palette<T>* GetPalette(int i)
{
if (!m_Palettes.empty())
{
if (i == -1)
return &m_Palettes[QTIsaac<ISAAC_SIZE, ISAAC_INT>::GlobalRand->Rand() % Count()];
else if (i < (int)m_Palettes.size())
return &m_Palettes[i];
}
return NULL;
}
/// <summary>
/// Gets a pointer to a palette with a specified name.
/// </summary>
/// <param name="name">The name of the palette to retrieve</param>
/// <returns>A pointer to the palette if found, else NULL</returns>
Palette<T>* GetPaletteByName(string& name)
{
for (unsigned int i = 0; i < Count(); i++)
if (m_Palettes[i].m_Name == name)
return &m_Palettes[i];
return NULL;
}
/// <summary>
/// Gets a copy of the palette at a specified index with its hue adjusted by the specified amount.
/// </summary>
/// <param name="i">The index of the palette to read. A value of -1 indicates a random palette.</param>
/// <param name="hue">The hue adjustment to apply</param>
/// <param name="palette">The palette to store the output</param>
/// <returns>True if successful, else false.</returns>
bool GetHueAdjustedPalette(int i, T hue, Palette<T>& palette)
{
bool b = false;
Palette<T>* unadjustedPal = GetPalette(i);
if (unadjustedPal)
{
unadjustedPal->MakeHueAdjustedPalette(palette, hue);
b = true;
}
return b;
}
/// <summary>
/// Clear the palette list and reset the initialization state.
/// </summary>
void Clear()
{
m_Palettes.clear();
m_Init = false;
}
/// <summary>
/// Accessors.
/// </summary>
bool Init() { return m_Init; }
unsigned int Count() { return (unsigned int)m_Palettes.size(); }
private:
/// <summary>
/// Parses an Xml node for all palettes present and stores in the palette list.
/// Note that although the Xml color values are expected to be 0-255, they are converted and
/// stored as normalized colors, with values from 0-1.
/// </summary>
/// <param name="node">The parent note of all palettes in the Xml file.</param>
void ParsePalettes(xmlNode* node)
{
bool hexError = false;
char* val;
const char* loc = __FUNCTION__;
xmlAttrPtr attr;
while (node)
{
if (node->type == XML_ELEMENT_NODE && !Compare(node->name, "palette"))
{
attr = node->properties;
Palette<T> palette;
while (attr)
{
val = (char*)xmlGetProp(node, attr->name);
if (!Compare(attr->name, "data"))
{
int colorIndex = 0;
int r, g, b;
int colorCount = 0;
hexError = false;
do
{
int ret = sscanf_s((char*)&(val[colorIndex]),"00%2x%2x%2x", &r, &g, &b);
if (ret != 3)
{
m_ErrorReport.push_back(string(loc) + " : Problem reading hexadecimal color data " + string(&val[colorIndex]));
hexError = true;
break;
}
colorIndex += 8;
while (isspace((int)val[colorIndex]))
colorIndex++;
palette[colorCount].r = T(r) / T(255);//Store as normalized colors in the range of 0-1.
palette[colorCount].g = T(g) / T(255);
palette[colorCount].b = T(b) / T(255);
colorCount++;
} while (colorCount < COLORMAP_LENGTH);
}
else if (!Compare(attr->name, "number"))
{
palette.m_Index = atoi(val);
}
else if (!Compare(attr->name, "name"))
{
palette.m_Name = string(val);
}
xmlFree(val);
attr = attr->next;
}
if (!hexError)
{
m_Palettes.push_back(palette);
}
}
else
{
ParsePalettes(node->children);
}
node = node->next;
}
}
static bool m_Init;//Initialized to false in Ember.cpp, and will be set to true upon successful reading of an Xml palette file.
static vector<Palette<T>> m_Palettes;//The vector that stores the palettes.
};
}

217
Source/Ember/Point.h Normal file
View File

@ -0,0 +1,217 @@
#pragma once
#include "EmberDefines.h"
#include "Affine2D.h"
#include "Timing.h"
/// <summary>
/// Basic point and color structures used in iteration.
/// </summary>
namespace EmberNs
{
/// <summary>
/// The point used to store the result of each iteration, which is
/// a spatial coordinate, a color index/coordinate and a visibility value.
/// Note that a Y color coordinate is not used at the moment because
/// only 1D palettes are supported like the original. However, in the future
/// 2D palettes may be supported like Fractron does.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API Point
{
public:
/// <summary>
/// Constructor to initialize spatial and color coordinates to zero, with full visibility.
/// </summary>
Point()
{
Init();
}
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="point">The Point object to copy</param>
Point(const Point<T>& point)
{
Point<T>::operator=<T>(point);
}
/// <summary>
/// Copy constructor to copy a Point object of type U.
/// </summary>
/// <param name="point">The Point object to copy</param>
template <typename U>
Point(const Point<U>& point)
{
Point<T>::operator=<U>(point);
}
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="point">The Point object to copy</param>
Point<T>& operator = (const Point<T>& point)
{
if (this != &point)
Point<T>::operator=<T>(point);
return *this;
}
/// <summary>
/// Assignment operator to assign a Point object of type U.
/// </summary>
/// <param name="point">The Point object to copy.</param>
/// <returns>Reference to updated self</returns>
template <typename U>
Point<T>& operator = (const Point<U>& point)
{
m_X = point.m_X;
m_Y = point.m_Y;
m_Z = point.m_Z;
m_ColorX = point.m_ColorX;
//m_ColorY = point.m_ColorY;
m_VizAdjusted = point.m_VizAdjusted;
return *this;
}
/// <summary>
/// Set spatial and color coordinates to zero, with full visibility.
/// </summary>
void Init()
{
m_X = 0;
m_Y = 0;
m_Z = 0;
m_ColorX = 0;
//m_ColorY = 0;
m_VizAdjusted = 1;
}
T m_X;
T m_Y;
T m_Z;
T m_ColorX;
//T m_ColorY;
T m_VizAdjusted;
};
/// <summary>
/// Comparer used for sorting the results of iteration by their spatial x coordinates.
/// </summary>
/// <param name="a">The first point to compare</param>
/// <param name="b">The second point to compare</param>
/// <returns>1 if the first point had an x coordinate less than the second point, else 0</returns>
template <typename T>
static int SortPointByX(const Point<T>& a, const Point<T>& b)
{
return a.m_X < b.m_X;
}
/// <summary>
/// Comparer used for sorting the results of iteration by their spatial y coordinates.
/// </summary>
/// <param name="a">The first point to compare</param>
/// <param name="b">The second point to compare</param>
/// <returns>1 if the first point had an y coordinate less than the second point, else 0</returns>
template <typename T>
static int SortPointByY(const Point<T>& a, const Point<T>& b)
{
return a.m_Y < b.m_Y;
}
/// <summary>
/// Thin override of a glm::vec4 which adds a couple of functions
/// specific to color handling.
/// </summary>
template <typename T>
class EMBER_API Color : public v4T
{
public:
/// <summary>
/// Constructor to set color values to zero, with full visibility.
/// </summary>
Color()
{
Reset();
}
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="color">The Color object to copy</param>
Color(const Color<T>& color)
{
Color<T>::operator=<T>(color);
}
/// <summary>
/// Copy constructor to copy a Color object of type U.
/// </summary>
/// <param name="color">The Color object to copy</param>
template <typename U>
Color(const Color<U>& color)
{
Color<T>::operator=<U>(color);
}
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="color">The Color object to copy</param>
Color<T>& operator = (const Color<T>& color)
{
if (this != &color)
Color<T>::operator=<T>(color);
return *this;
}
/// <summary>
/// Assignment operator to assign a Color object of type U.
/// </summary>
/// <param name="color">The Color object to copy.</param>
/// <returns>Reference to updated self</returns>
template <typename U>
Color<T>& operator = (const Color<U>& color)
{
v4T::operator=<U>(color);
return *this;
}
/// <summary>
/// Member-wise constructor.
/// </summary>
Color(T rr, T gg, T bb, T aa)
: v4T(rr, gg, bb, aa)
{
}
/// <summary>
/// Set color values and visibility to zero.
/// </summary>
inline void Clear()
{
r = 0;
g = 0;
b = 0;
a = 0;
}
/// <summary>
/// Set color values to zero, with full visibility.
/// </summary>
/// <param name="norm">If norm is true, the color fields are expected to have a range of 0-1, else 0-255</param>
inline void Reset(bool norm = true)
{
r = 0;
g = 0;
b = 0;
a = norm ? T(1) : T(255);
}
};
}

2222
Source/Ember/Renderer.cpp Normal file

File diff suppressed because it is too large Load Diff

412
Source/Ember/Renderer.h Normal file
View File

@ -0,0 +1,412 @@
#pragma once
#include "Ember.h"
#include "Iterator.h"
#include "Utils.h"
#include "SpatialFilter.h"
#include "DensityFilter.h"
#include "TemporalFilter.h"
#include "Interpolate.h"
#include "CarToRas.h"
#include "EmberToXml.h"
/// <summary>
/// Renderer, RenderCallback and EmberStats classes.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Function pointers present a major restriction when dealing
/// with member functions, and that is they can only point to
/// static ones. So instead of a straight function pointer, use
/// a callback class with a single virtual callback
/// member function.
/// Template argument expected to be float or double.
/// </summary>
class EMBER_API RenderCallback
{
public:
/// <summary>
/// Virtual destructor to ensure anything declared in derived classes gets cleaned up.
/// </summary>
virtual ~RenderCallback() { }
/// <summary>
/// Empty progress function to be implemented in derived classes to take action on progress updates.
/// </summary>
/// <param name="ember">The ember currently being rendered</param>
/// <param name="foo">An extra dummy parameter</param>
/// <param name="fraction">The progress fraction from 0-100</param>
/// <param name="stage">The stage of iteration. 1 is iterating, 2 is density filtering, 2 is final accumulation.</param>
/// <param name="etaMs">The estimated milliseconds to completion of the current stage</param>
/// <returns>Override should return 0 if an abort is requested, else 1 to continue rendering</returns>
virtual int ProgressFunc(Ember<float>& ember, void* foo, double fraction, int stage, double etaMs) { return 0; }
virtual int ProgressFunc(Ember<double>& ember, void* foo, double fraction, int stage, double etaMs) { return 0; }
};
/// <summary>
/// Render statistics for the number of iterations ran,
/// number of bad values calculated during iteration, and
/// the total time for the entire render from the start of
/// iteration to the end of final accumulation.
/// </summary>
class EMBER_API EmberStats
{
public:
/// <summary>
/// Constructor which sets all values to 0.
/// </summary>
EmberStats()
{
m_Iters = 0;
m_Badvals = 0;
m_RenderSeconds = 0;
}
unsigned __int64 m_Iters, m_Badvals;
double m_RenderSeconds;
};
/// <summary>
/// The types of available renderers.
/// Add more in the future as different rendering methods are experimented with.
/// Possible values might be: CPU+OpenGL, Particle, Inverse.
/// </summary>
enum eRendererType { CPU_RENDERER, OPENCL_RENDERER };
/// <summary>
/// A base class with virtual functions to allow both templating and polymorphism to work together.
/// Derived classes will implement all of these functions.
/// Note that functions which return a decimal number use the most precise type, double.
/// </summary>
class EMBER_API RendererBase : public EmberReport
{
public:
RendererBase() { }
virtual ~RendererBase() { }
virtual void SetEmber(Ember<float>& ember, eProcessAction action = FULL_RENDER) { }
virtual void SetEmber(vector<Ember<float>>& embers) { }
virtual void SetEmber(Ember<double>& ember, eProcessAction action = FULL_RENDER) { }
virtual void SetEmber(vector<Ember<double>>& embers) { }
virtual void Callback(RenderCallback* callback) { }
virtual bool CreateSpatialFilter(bool& newAlloc) { return false; }
virtual bool CreateTemporalFilter(bool& newAlloc) { return false; }
virtual void ComputeBounds() { }
virtual bool Ok() const { return false; }
virtual void Reset() { }
virtual void EnterRender() { }
virtual void LeaveRender() { }
virtual void EnterFinalAccum() { }
virtual void LeaveFinalAccum() { }
virtual void EnterResize() { }
virtual void LeaveResize() { }
virtual void Abort() { }
virtual bool Aborted() { return false; }
virtual bool InRender() { return false; }
virtual bool InFinalAccum() { return false; }
virtual unsigned int NumChannels() const { return 0; }
virtual void NumChannels(unsigned int numChannels) { }
virtual eRendererType RendererType() const { return CPU_RENDERER; }
virtual void ReclaimOnResize(bool reclaimOnResize) { }
virtual bool EarlyClip() const { return false; }
virtual void EarlyClip(bool earlyClip) { }
virtual void ThreadCount(unsigned int threads, const char* seedString = NULL) { }
virtual void Transparency(bool transparency) { }
virtual void InteractiveFilter(eInteractiveFilter filter) { }
virtual unsigned int FinalRasW() const { return 0; }
virtual unsigned int FinalRasH() const { return 0; }
virtual unsigned int SuperRasW() const { return 0; }
virtual unsigned int SuperRasH() const { return 0; }
virtual unsigned int FinalBufferSize() const { return 0; }
virtual unsigned int GutterWidth() const { return 0; }
virtual double ScaledQuality() const { return 0; }
virtual double LowerLeftX(bool gutter = true) const { return 0; }
virtual double LowerLeftY(bool gutter = true) const { return 0; }
virtual double UpperRightX(bool gutter = true) const { return 0; }
virtual double UpperRightY(bool gutter = true) const { return 0; }
virtual unsigned __int64 MemoryRequired(bool includeFinal) { return 0; }
virtual unsigned __int64 MemoryAvailable() { return 0; }
virtual bool PrepFinalAccumVector(vector<unsigned char>& pixels) { return false; }
virtual eProcessState ProcessState() const { return NONE; }
virtual eProcessAction ProcessAction() const { return NOTHING; }
virtual EmberStats Stats() const { EmberStats stats; return stats; }
virtual eRenderStatus Run(vector<unsigned char>& finalImage, double time = 0, unsigned int subBatchCountOverride = 0, bool forceOutput = false, size_t finalOffset = 0) { return RENDER_ERROR; }
virtual EmberImageComments ImageComments(unsigned int printEditDepth = 0, bool intPalette = false, bool hexPalette = true) { EmberImageComments comments; return comments; }
virtual DensityFilterBase* GetDensityFilter() { return NULL; }
};
/// <summary>
/// Renderer is the main class where all of the execution takes place.
/// It is intended that the program have one instance of it that it
/// keeps around for its duration. After a user sets up an ember, it's passed
/// in to be rendered.
/// This class derives from EmberReport, so the caller is able
/// to retrieve a text dump of error information if any errors occur.
/// The final image output vector is also passed in because the calling code has more
/// use for it than this class does.
/// Several functions are made virtual and have a default CPU-based implementation
/// that roughly matches what flam3 did. However they can be overriden in derived classes
/// to provide alternative rendering implementations, such as using the GPU.
/// Since this is a templated class, it's supposed to be entirely implemented in this .h file.
/// However, VC++ 2010 has very crippled support for lambdas, which Renderer makes use of.
/// If too many lambdas are used in a .h file, it will crash the compiler when another library
/// tries to link to it. To work around the bug, only declarations are here and all implementations
/// are in the .cpp file. It's unclear at what point it starts/stops working. But it seems that once
/// enough code is placed in the .h file, the compiler crashes. So for the sake of consistency, everything
/// is moved to the .cpp, even simple getters. One drawback however, is that the class must be
/// explicitly exported at the bottom of the file.
/// Also, despite explicitly doing this, the compiler throws a C4661 warning
/// for every single function in this class, saying it can't find the implementation. This warning
/// can be safely ignored.
/// Template argument T expected to be float or double.
/// Template argument bucketT was originally used to experiment with different types for the histogram, however
/// the only types that work are float and double, so it's useless and should always match what T is.
/// Mismatched types between T and bucketT are undefined.
/// </summary>
template <typename T, typename bucketT>
class EMBER_API Renderer : public RendererBase
{
public:
Renderer();
virtual ~Renderer();
virtual void ComputeBounds();
void ComputeCamera();
void ChangeVal(std::function<void (void)> func, eProcessAction action);
virtual void SetEmber(Ember<T>& ember, eProcessAction action = FULL_RENDER);
virtual void SetEmber(vector<Ember<T>>& embers);
void AddEmber(Ember<T>& ember);
bool CreateTemporalFilter(bool& newAlloc);
bool AssignIterator();
virtual bool PrepFinalAccumVector(vector<unsigned char>& pixels);
virtual eRenderStatus Run(vector<unsigned char>& finalImage, double time = 0, unsigned int subBatchCountOverride = 0, bool forceOutput = false, size_t finalOffset = 0);
virtual EmberImageComments ImageComments(unsigned int printEditDepth = 0, bool intPalette = false, bool hexPalette = true);
virtual unsigned __int64 MemoryRequired(bool includeFinal);
//Virtual functions to be overriden in derived renderers that use the GPU.
virtual unsigned __int64 MemoryAvailable();
virtual void Reset();
virtual bool Ok() const;
virtual bool CreateDEFilter(bool& newAlloc);
virtual bool CreateSpatialFilter(bool& newAlloc);
virtual unsigned int SubBatchSize() const;
virtual void SubBatchSize(unsigned int sbs);
virtual unsigned int NumChannels() const;
virtual void NumChannels(unsigned int numChannels);
virtual eRendererType RendererType() const;
virtual unsigned int ThreadCount() const;
virtual void ThreadCount(unsigned int threads, const char* seedString = NULL);
virtual void Callback(RenderCallback* callback);
protected:
//Virtual functions to be overriden in derived renderers that use the GPU, but not accessed outside.
virtual void MakeDmap(T colorScalar);
virtual bool Alloc();
virtual bool ResetBuckets(bool resetHist = true, bool resetAccum = true);
virtual eRenderStatus LogScaleDensityFilter();
virtual eRenderStatus GaussianDensityFilter();
virtual eRenderStatus AccumulatorToFinalImage(vector<unsigned char>& pixels, size_t finalOffset);
virtual eRenderStatus AccumulatorToFinalImage(unsigned char* pixels, size_t finalOffset);
virtual EmberStats Iterate(unsigned __int64 iterCount, unsigned int pass, unsigned int temporalSample);
public:
//Accessors for render properties.
vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>> RandVec();
bool RandVec(vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>>& randVec);
inline bool LockAccum() const;
void LockAccum(bool lockAccum);
virtual bool EarlyClip() const;
virtual void EarlyClip(bool earlyClip);
inline bool InsertPalette() const;
void InsertPalette(bool insertPalette);
inline bool ReclaimOnResize() const;
virtual void ReclaimOnResize(bool reclaimOnResize);
inline bool Transparency() const;
virtual void Transparency(bool transparency);
inline unsigned int BytesPerChannel() const;
void BytesPerChannel(unsigned int bytesPerChannel);
inline T PixelAspectRatio() const;
void PixelAspectRatio(T pixelAspectRatio);
inline eInteractiveFilter InteractiveFilter() const;
virtual void InteractiveFilter(eInteractiveFilter filter);
//Threading control.
virtual void EnterRender();
virtual void LeaveRender();
virtual void EnterFinalAccum();
virtual void LeaveFinalAccum();
virtual void EnterResize();
virtual void LeaveResize();
virtual void Abort();
virtual bool Aborted();
virtual bool InRender();
virtual bool InFinalAccum();
//Renderer properties, getters only.
virtual unsigned int SuperRasW() const;
virtual unsigned int SuperRasH() const;
inline unsigned int SuperSize() const;
virtual unsigned int FinalBufferSize() const;
inline unsigned int FinalRowSize() const;
inline unsigned int FinalDimensions() const;
inline unsigned int PixelSize() const;
virtual unsigned int GutterWidth() const;
inline unsigned int DensityFilterOffset() const;
virtual double ScaledQuality() const;
inline T Scale() const;
inline T PixelsPerUnitX() const;
inline T PixelsPerUnitY() const;
virtual double LowerLeftX(bool gutter = true) const;
virtual double LowerLeftY(bool gutter = true) const;
virtual double UpperRightX(bool gutter = true) const;
virtual double UpperRightY(bool gutter = true) const;
inline T K1() const;
inline T K2() const;
inline unsigned __int64 TotalIterCount() const;
inline unsigned __int64 ItersPerTemporalSample() const;
virtual eProcessState ProcessState() const;
virtual eProcessAction ProcessAction() const;
virtual EmberStats Stats() const;
inline const CarToRas<T>* CoordMap() const;
inline glm::detail::tvec4<bucketT, glm::defaultp>* HistBuckets();
inline glm::detail::tvec4<bucketT, glm::defaultp>* AccumulatorBuckets();
inline SpatialFilter<T>* GetSpatialFilter();
inline TemporalFilter<T>* GetTemporalFilter();
virtual DensityFilter<T>* GetDensityFilter();
//Ember wrappers, getters only.
inline bool XaosPresent();
virtual inline unsigned int FinalRasW() const;
virtual inline unsigned int FinalRasH() const;
inline unsigned int Supersample() const;
inline unsigned int Passes() const;
inline unsigned int TemporalSamples() const;
inline unsigned int PaletteIndex() const;
inline T Time() const;
inline T Quality() const;
inline T SpatialFilterRadius() const;
inline T PixelsPerUnit() const;
inline T Zoom() const;
inline T CenterX() const;
inline T CenterY() const;
inline T Rotate() const;
inline T Hue() const;
inline T Brightness() const;
inline T Contrast() const;
inline T Gamma() const;
inline T Vibrancy() const;
inline T GammaThresh() const;
inline T HighlightPower() const;
inline Color<T> Background() const;
inline const Xform<T>* Xforms() const;
inline Xform<T>* NonConstXforms();
inline unsigned int XformCount() const;
inline const Xform<T>* FinalXform() const;
inline Xform<T>* NonConstFinalXform();
inline bool UseFinalXform() const;
inline const Palette<T>* GetPalette() const;
inline ePaletteMode PaletteMode() const;
//Iterator wrappers.
const unsigned char* XformDistributions() const;
const unsigned int XformDistributionsSize() const;
Point<T>* Samples(unsigned int threadIndex) const;
void* m_ProgressParameter;
protected:
//Non-virtual functions that might be needed by a derived class.
void PrepFinalAccumVals(Color<T>& background, T& g, T& linRange, T& vibrancy);
private:
//Miscellaneous functions used only in this class.
void Accumulate(Point<T>* samples, unsigned int sampleCount, const Palette<bucketT>* palette);
inline void AddToAccum(const glm::detail::tvec4<bucketT, glm::defaultp>& bucket, int i, int ii, int j, int jj);
template <typename accumT> void GammaCorrection(glm::detail::tvec4<bucketT, glm::defaultp>& bucket, Color<T>& background, T g, T linRange, T vibrancy, bool doAlpha, bool scale, accumT* correctedChannels);
protected:
bool m_EarlyClip;
bool m_Transparency;
unsigned int m_SuperRasW;
unsigned int m_SuperRasH;
unsigned int m_SuperSize;
unsigned int m_GutterWidth;
unsigned int m_DensityFilterOffset;
unsigned int m_NumChannels;
unsigned int m_BytesPerChannel;
unsigned int m_SubBatchSize;
unsigned int m_ThreadsToUse;
T m_ScaledQuality;
T m_Scale;
T m_PixelsPerUnitX;
T m_PixelsPerUnitY;
T m_PixelAspectRatio;
T m_LowerLeftX;
T m_LowerLeftY;
T m_UpperRightX;
T m_UpperRightY;
T m_K1;
T m_K2;
T m_Vibrancy;//Accumulate these after each temporal sample.
T m_Gamma;
Color<T> m_Background;
Affine2D<T> m_RotMat;
volatile bool m_Abort;
bool m_LockAccum;
bool m_InRender;
bool m_InFinalAccum;
bool m_InsertPalette;
bool m_ReclaimOnResize;
unsigned int m_VibGamCount;
unsigned int m_LastPass;
unsigned int m_LastTemporalSample;
unsigned __int64 m_LastIter;
double m_LastIterPercent;
eProcessAction m_ProcessAction;
eProcessState m_ProcessState;
eInteractiveFilter m_InteractiveFilter;
EmberStats m_Stats;
Ember<T> m_Ember;
Ember<T> m_TempEmber;
Ember<T> m_LastEmber;
vector<Ember<T>> m_Embers;
CarToRas<T> m_CarToRas;
RenderCallback* m_Callback;
Iterator<T>* m_Iterator;
auto_ptr<StandardIterator<T>> m_StandardIterator;
auto_ptr<XaosIterator<T>> m_XaosIterator;
Palette<bucketT> m_Dmap;
vector<glm::detail::tvec4<bucketT, glm::defaultp>> m_HistBuckets;
vector<glm::detail::tvec4<bucketT, glm::defaultp>> m_AccumulatorBuckets;
auto_ptr<SpatialFilter<T>> m_SpatialFilter;
auto_ptr<TemporalFilter<T>> m_TemporalFilter;
auto_ptr<DensityFilter<T>> m_DensityFilter;
vector<vector<Point<T>>> m_Samples;
vector<unsigned __int64> m_SubBatch;
vector<unsigned __int64> m_BadVals;
vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>> m_Rand;
tbb::task_group m_TaskGroup;
CriticalSection m_RenderingCs, m_AccumCs, m_FinalAccumCs, m_ResizeCs;
Timing m_RenderTimer, m_ProgressTimer;
EmberToXml<T> m_EmberToXml;
};
//This class had to be implemented in a cpp file because the compiler was breaking.
//So the explicit instantiation must be declared here rather than in Ember.cpp where
//all of the other classes are done.
template EMBER_API class Renderer<float, float>;
#ifdef DO_DOUBLE
template EMBER_API class Renderer<double, double>;
#endif
}

1394
Source/Ember/SheepTools.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,909 @@
#pragma once
#include "EmberDefines.h"
/// <summary>
/// SpatialFilter base, derived and factory classes.
/// </summary>
namespace EmberNs
{
/// <summary>
/// The types of spatial filters available.
/// </summary>
enum eSpatialFilterType
{
GAUSSIAN_SPATIAL_FILTER = 0,
HERMITE_SPATIAL_FILTER = 1,
BOX_SPATIAL_FILTER = 2,
TRIANGLE_SPATIAL_FILTER = 3,
BELL_SPATIAL_FILTER = 4,
BSPLINE_SPATIAL_FILTER = 5,
LANCZOS3_SPATIAL_FILTER = 6,
LANCZOS2_SPATIAL_FILTER = 7,
MITCHELL_SPATIAL_FILTER = 8,
BLACKMAN_SPATIAL_FILTER = 9,
CATROM_SPATIAL_FILTER = 10,
HAMMING_SPATIAL_FILTER = 11,
HANNING_SPATIAL_FILTER = 12,
QUADRATIC_SPATIAL_FILTER = 13
};
/// <summary>
/// Spatial filtering is done in the final accumulation stage to add some additional
/// bluring to smooth out noisy areas.
/// The bulk of the work is done in this base class Create() function.
/// Because it calls the virtual Filter() function, it cannot be automatically called in the constructor.
/// So the caller must manually call it after constructing the filter object.
/// Each derived class will implement an override of Filter() which
/// contains the specific filter calculation for the algorithm whose name the class matches.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API SpatialFilter
{
public:
/// <summary>
/// Assign basic parameters for creating a spatial filter. The caller must still call Create().
/// </summary>
/// <param name="filterType">Type of filter to create</param>
/// <param name="support">Miscellaneous value</param>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
SpatialFilter(eSpatialFilterType filterType, T support, T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
{
m_FilterType = filterType;
m_Support = support;
m_FilterRadius = filterRadius;
m_Supersample = superSample;
m_PixelAspectRatio = pixelAspectRatio;
//Sadly, cannot call create here because it calls the Filter() virtual function and unlike C#, the vtables
//are not yet set up in C++ constructors. The code that instantiates this object must explicitly call Create().
}
/// <summary>
/// Copy constructor.
/// </summary>
/// <param name="filter">The SpatialFilter object to copy</param>
SpatialFilter(const SpatialFilter<T>& filter)
{
*this = filter;
}
/// <summary>
/// Virtual destructor so derived class destructors get called.
/// </summary>
virtual ~SpatialFilter()
{
}
/// <summary>
/// Assignment operator.
/// </summary>
/// <param name="filter">The SpatialFilter object to copy.</param>
/// <returns>Reference to updated self</returns>
SpatialFilter<T>& operator = (const SpatialFilter<T>& filter)
{
if (this != &filter)
{
m_FinalFilterWidth = filter.m_FinalFilterWidth;
m_Supersample = filter.m_Supersample;
m_Support = filter.m_Support;
m_FilterRadius = filter.m_FilterRadius;
m_PixelAspectRatio = filter.m_PixelAspectRatio;
m_FilterType = filter.m_FilterType;
m_Filter = filter.m_Filter;
}
return *this;
}
/// <summary>
/// Allocates and populates the filter buffer with virtual calls to derived Filter() functions.
/// The caller must manually call this after construction.
/// </summary>
void Create()
{
T fw = T(2.0) * m_Support * m_Supersample * m_FilterRadius / m_PixelAspectRatio;
T adjust, ii, jj;
int fwidth = ((int)fw) + 1;
int i, j;
//Make sure the filter kernel has same parity as oversample.
if ((fwidth ^ m_Supersample) & 1)
fwidth++;
//Calculate the coordinate scaling factor for the kernel values.
if (fw > 0.0)
adjust = m_Support * fwidth / fw;
else
adjust = T(1.0);
m_Filter.resize(fwidth * fwidth);
//Fill in the coefs.
for (i = 0; i < fwidth; i++)
{
for (j = 0; j < fwidth; j++)
{
//Calculate the function inputs for the kernel function.
ii = ((T(2.0) * i + T(1.0)) / T(fwidth) - T(1.0)) * adjust;
jj = ((T(2.0) * j + T(1.0)) / T(fwidth) - T(1.0)) * adjust;
//Adjust for aspect ratio.
jj /= m_PixelAspectRatio;
m_Filter[i + j * fwidth] = Filter(ii) * Filter(jj);//Call virtual Filter(), implemented in specific derived filter classes.
}
}
//Normalize, and return a bad value if the values were too small.
if (!Normalize())
m_FinalFilterWidth = -1;
else
m_FinalFilterWidth = fwidth;
}
/// <summary>
/// Return a string representation of this filter.
/// </summary>
/// <returns>The string representation of this filter</returns>
string ToString() const
{
unsigned int i;
stringstream ss;
ss
<< "Spatial Filter:" << endl
<< " Support: " << m_Support << endl
<< " Filter radius: " << m_FilterRadius << endl
<< " Supersample: " << m_Supersample << endl
<< "Pixel aspect ratio: " << m_PixelAspectRatio << endl
<< "Final filter width: " << m_FinalFilterWidth << endl
<< "Filter buffer size: " << m_Filter.size() << endl;
ss << "Filter: " << endl;
for (i = 0; i < m_Filter.size(); i++)
{
ss << "Filter[" << i << "]: " << m_Filter[i] << endl;
}
return ss.str();
}
/// <summary>
/// Accessors.
/// </summary>
void Apply() { }
inline int FinalFilterWidth() const { return m_FinalFilterWidth; }
inline unsigned int Supersample() const { return m_Supersample; }
inline unsigned int BufferSize() const { return (unsigned int)m_Filter.size(); }
inline unsigned int BufferSizeBytes() const { return BufferSize() * sizeof(T); }
inline T Support() const { return m_Support; }
inline T FilterRadius() const { return m_FilterRadius; }
inline T PixelAspectRatio() const { return m_PixelAspectRatio; }
inline eSpatialFilterType FilterType() const { return m_FilterType; }
inline T* Filter() { return m_Filter.data(); }
inline const T& operator[] (size_t index) const { return m_Filter[index]; }
virtual T Filter(T t) const = 0;
protected:
/// <summary>
/// Calculation function used in Lanczos filters.
/// </summary>
/// <param name="x">The x</param>
/// <returns>The calculated value</returns>
static T Sinc(T x)
{
x *= T(M_PI);
if (x != 0)
return sin(x) / x;
return 1.0;
}
private:
/// <summary>
/// Normalize all filter values.
/// </summary>
/// <returns>True if any value was non-zero, else false if all were zero.</returns>
bool Normalize()
{
size_t i;
T t = T(0.0);
for (i = 0; i < m_Filter.size(); i++)
t += m_Filter[i];
if (t == 0.0)
return false;
t = T(1.0) / t;
for (i = 0; i < m_Filter.size(); i++)
m_Filter[i] *= t;
return true;
}
int m_FinalFilterWidth;//The final width that the filter ends up being.
unsigned int m_Supersample;//The supersample value of the ember using this filter to render.
T m_Support;//Extra value.
T m_FilterRadius;//The requested filter radius.
T m_PixelAspectRatio;//The aspect ratio of the ember using this filter to render, usually 1.
eSpatialFilterType m_FilterType;//The type of filter this is.
vector<T> m_Filter;//The vector holding the calculated filter values.
};
/// <summary>
/// Derivation for Gaussian filter.
/// </summary>
template <typename T>
class EMBER_API GaussianFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.5.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
GaussianFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(GAUSSIAN_SPATIAL_FILTER, T(1.5), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Gaussian filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
return exp(-2 * t * t) * sqrt(2 / T(M_PI));
}
};
/// <summary>
/// Derivation for Hermite filter.
/// </summary>
template <typename T>
class EMBER_API HermiteFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
HermiteFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(HERMITE_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Hermite filter to t parameter and return.
/// f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if (t < 0)
t = -t;
if (t < 1)
return ((T(2.0) * t - T(3.0)) * t * t + T(1.0));
return 0;
}
};
/// <summary>
/// Derivation for Box filter.
/// </summary>
template <typename T>
class EMBER_API BoxFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 0.5.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
BoxFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(BOX_SPATIAL_FILTER, T(0.5), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Box filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if ((t > T(-0.5)) && (t <= T(0.5)))
return 1;
return 0;
}
};
/// <summary>
/// Derivation for Triangle filter.
/// </summary>
template <typename T>
class EMBER_API TriangleFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
TriangleFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(TRIANGLE_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Triangle filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if (t < 0)
t = -t;
if (t < 1)
return 1 - t;
return 0;
}
};
/// <summary>
/// Derivation for Bell filter.
/// </summary>
template <typename T>
class EMBER_API BellFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.5.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
BellFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(BELL_SPATIAL_FILTER, T(1.5), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Bell filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
//box (*) box (*) box.
if (t < 0)
t = -t;
if (t < T(0.5))
return (T(0.75) - (t * t));
if (t < T(1.5))
{
t = (t - T(1.5));
return (T(0.5) * (t * t));
}
return 0;
}
};
/// <summary>
/// Derivation for B Spline filter.
/// </summary>
template <typename T>
class EMBER_API BsplineFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 2.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
BsplineFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(BSPLINE_SPATIAL_FILTER, T(2.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply B Spline filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
//box (*) box (*) box (*) box.
T tt;
if (t < 0)
t = -t;
if (t < 1)
{
tt = t * t;
return ((T(0.5) * tt * t) - tt + (T(2.0) / T(3.0)));
}
else if (t < 2)
{
t = 2 - t;
return ((T(1.0) / T(6.0)) * (t * t * t));
}
return 0;
}
};
/// <summary>
/// Derivation for Lanczos 3 filter.
/// </summary>
template <typename T>
class EMBER_API Lanczos3Filter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 3.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
Lanczos3Filter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(LANCZOS3_SPATIAL_FILTER, T(3.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Lanczos 3 filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if (t < 0)
t = -t;
if (t < 3)
return Sinc(t) * Sinc(t / 3);
return 0;
}
};
/// <summary>
/// Derivation for Lanczos 2 filter.
/// </summary>
template <typename T>
class EMBER_API Lanczos2Filter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 2.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
Lanczos2Filter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(LANCZOS2_SPATIAL_FILTER, T(2.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Lanczos 2 filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if (t < 0)
t = -t;
if (t < 2)
return Sinc(t) * Sinc(t / 2);
return 0;
}
};
/// <summary>
/// Derivation for Mitchell filter.
/// </summary>
template <typename T>
class EMBER_API MitchellFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 2.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
MitchellFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(MITCHELL_SPATIAL_FILTER, T(2.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Mitchell filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
T tt = t * t;
const T b = T(1) / T(3);
const T c = T(1) / T(3);
if (t < 0)
t = -t;
if (t < 1)
{
t = (((T(12.0) - T(9.0) * b - T(6.0) * c) * (t * tt))
+ ((T(-18.0) + T(12.0) * b + T(6.0) * c) * tt)
+ (T(6.0) - 2 * b));
return t / 6;
}
else if (t < 2)
{
t = (((T(-1.0) * b - T(6.0) * c) * (t * tt))
+ ((T(6.0) * b + T(30.0) * c) * tt)
+ ((T(-12.0) * b - T(48.0) * c) * t)
+ (T(8.0) * b + 24 * c));
return t / 6;
}
return 0;
}
};
/// <summary>
/// Derivation for Blackman filter.
/// </summary>
template <typename T>
class EMBER_API BlackmanFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
BlackmanFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(BLACKMAN_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Blackman filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
return (T(0.42) + T(0.5) * cos(T(M_PI) * t) + T(0.08) * cos(2 * T(M_PI) * t));
}
};
/// <summary>
/// Derivation for Catmull-Rom filter.
/// </summary>
template <typename T>
class EMBER_API CatromFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 2.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
CatromFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(CATROM_SPATIAL_FILTER, T(2.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Catmull-Rom filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if (t < 0)
return 0;
if (t < -1)
return T(0.5) * (4 + t * (8 + t * (5 + t)));
if (t < 0)
return T(0.5) * (2 + t * t * (-5 - 3 * t));
if (t < 1)
return T(0.5) * (2 + t * t * (-5 + 3 * t));
if (t < 2)
return T(0.5) * (4 + t * (-8 + t * (5 - t)));
return 0;
}
};
/// <summary>
/// Derivation for Hamming filter.
/// </summary>
template <typename T>
class EMBER_API HammingFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
HammingFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(HAMMING_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Hamming filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
return T(0.54) + T(0.46) * cos(T(M_PI) * t);
}
};
/// <summary>
/// Derivation for Hanning filter.
/// </summary>
template <typename T>
class EMBER_API HanningFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
HanningFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(HANNING_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Hanning filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
return T(0.5) + T(0.5) * cos(T(M_PI) * t);
}
};
/// <summary>
/// Derivation for Quadratic filter.
/// </summary>
template <typename T>
class EMBER_API QuadraticFilter : public SpatialFilter<T>
{
public:
/// <summary>
/// Constructor which does nothing but pass values to the base class. The caller must still call Create().
/// Support = 1.5.
/// </summary>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample of the ember being rendered</param>
/// <param name="pixelAspectRatio">The pixel aspect ratio being used to render. Default: 1.</param>
QuadraticFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0))
: SpatialFilter<T>(QUADRATIC_SPATIAL_FILTER, T(1.5), filterRadius, superSample, pixelAspectRatio) { }
/// <summary>
/// Apply Quadratic filter to t parameter and return.
/// </summary>
/// <param name="t">The value to apply the filter to</param>
/// <returns>The filtered value</returns>
virtual T Filter(T t) const
{
if (t < -1.5)
return 0.0;
if (t < -0.5)
return T(0.5) * (t + T(1.5)) * (t + T(1.5));
if (t < 0.5)
return T(0.75) - (t * t);
if (t < 1.5)
return T(0.5) * (t - T(1.5)) * (t - T(1.5));
return 0;
}
};
/// <summary>
/// Convenience class to assist in converting between filter names and the filter objects themselves.
/// </summary>
template <typename T>
class EMBER_API SpatialFilterCreator
{
public:
/// <summary>
/// Creates the specified filter type based on the filterType enum parameter.
/// </summary>
/// <param name="filterType">Type of the filter</param>
/// <param name="filterRadius">The filter radius</param>
/// <param name="superSample">The supersample value of the ember using this filter to render</param>
/// <param name="pixelAspectRatio">The aspect ratio of the ember using this filter to render. Default: 1.</param>
/// <returns>A pointer to the newly created filter object</returns>
static SpatialFilter<T>* Create(eSpatialFilterType filterType, T filterRadius, unsigned int superSample, T pixelAspectRatio = 1)
{
SpatialFilter<T>* filter = NULL;
if (filterType == GAUSSIAN_SPATIAL_FILTER)
filter = new GaussianFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == HERMITE_SPATIAL_FILTER)
filter = new HermiteFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == BOX_SPATIAL_FILTER)
filter = new BoxFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == TRIANGLE_SPATIAL_FILTER)
filter = new TriangleFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == BELL_SPATIAL_FILTER)
filter = new BellFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == BSPLINE_SPATIAL_FILTER)
filter = new BsplineFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == LANCZOS3_SPATIAL_FILTER)
filter = new Lanczos3Filter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == LANCZOS2_SPATIAL_FILTER)
filter = new Lanczos2Filter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == MITCHELL_SPATIAL_FILTER)
filter = new MitchellFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == BLACKMAN_SPATIAL_FILTER)
filter = new BlackmanFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == CATROM_SPATIAL_FILTER)
filter = new CatromFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == HAMMING_SPATIAL_FILTER)
filter = new HammingFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == HANNING_SPATIAL_FILTER)
filter = new HanningFilter<T>(filterRadius, superSample, pixelAspectRatio);
else if (filterType == QUADRATIC_SPATIAL_FILTER)
filter = new QuadraticFilter<T>(filterRadius, superSample, pixelAspectRatio);
else
filter = new GaussianFilter<T>(filterRadius, superSample, pixelAspectRatio);
if (filter)
filter->Create();
return filter;
}
/// <summary>
/// Return a string vector of the available filter types.
/// </summary>
/// <returns>A vector of strings populated with the available filter types</returns>
static vector<string> FilterTypes()
{
vector<string> v;
v.reserve(14);
v.push_back("Gaussian");
v.push_back("Hermite");
v.push_back("Box");
v.push_back("Triangle");
v.push_back("Bell");
v.push_back("Bspline");
v.push_back("Lanczos3");
v.push_back("Lanczos2");
v.push_back("Mitchell");
v.push_back("Blackman");
v.push_back("Catrom");
v.push_back("Hamming");
v.push_back("Hanning");
v.push_back("Quadratic");
return v;
}
/// <summary>
/// Convert between the filter name string and its type enum.
/// </summary>
/// <param name="filterType">The string name of the filter</param>
/// <returns>The filter type enum</returns>
static eSpatialFilterType FromString(string filterType)
{
if (!_stricmp(filterType.c_str(), "Gaussian"))
return GAUSSIAN_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Hermite"))
return HERMITE_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Box"))
return BOX_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Triangle"))
return TRIANGLE_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Bell"))
return BELL_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Bspline"))
return BSPLINE_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Lanczos3"))
return LANCZOS3_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Lanczos2"))
return LANCZOS2_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Mitchell"))
return MITCHELL_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Blackman"))
return BLACKMAN_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Catrom"))
return CATROM_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Hamming"))
return HAMMING_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Hanning"))
return HANNING_SPATIAL_FILTER;
else if (!_stricmp(filterType.c_str(), "Quadratic"))
return QUADRATIC_SPATIAL_FILTER;
else
return GAUSSIAN_SPATIAL_FILTER;
}
/// <summary>
/// Convert between the filter type enum and its name string.
/// </summary>
/// <param name="eTemporalFilterType">The filter type enum</param>
/// <returns>The string name of the filter</returns>
static string ToString(eSpatialFilterType filterType)
{
string filter;
if (filterType == GAUSSIAN_SPATIAL_FILTER)
filter = "Gaussian";
else if (filterType == HERMITE_SPATIAL_FILTER)
filter = "Hermite";
else if (filterType == BOX_SPATIAL_FILTER)
filter = "Box";
else if (filterType == TRIANGLE_SPATIAL_FILTER)
filter = "Triangle";
else if (filterType == BELL_SPATIAL_FILTER)
filter = "Bell";
else if (filterType == BSPLINE_SPATIAL_FILTER)
filter = "Bspline";
else if (filterType == LANCZOS3_SPATIAL_FILTER)
filter = "Lanczos3";
else if (filterType == LANCZOS2_SPATIAL_FILTER)
filter = "Lanczos2";
else if (filterType == MITCHELL_SPATIAL_FILTER)
filter = "Mitchell";
else if (filterType == BLACKMAN_SPATIAL_FILTER)
filter = "Blackman";
else if (filterType == CATROM_SPATIAL_FILTER)
filter = "Catrom";
else if (filterType == HAMMING_SPATIAL_FILTER)
filter = "Hamming";
else if (filterType == HANNING_SPATIAL_FILTER)
filter = "Hanning";
else if (filterType == QUADRATIC_SPATIAL_FILTER)
filter = "Quadratic";
else
filter = "Gaussian";
return filter;
}
};
}

View File

@ -0,0 +1,347 @@
#pragma once
#include "EmberDefines.h"
/// <summary>
/// TemporalFilter base, derived and factory classes.
/// </summary>
namespace EmberNs
{
/// <summary>
/// The types of temporal filters available.
/// </summary>
enum eTemporalFilterType
{
BOX_TEMPORAL_FILTER = 0,
GAUSSIAN_TEMPORAL_FILTER = 1,
EXP_TEMPORAL_FILTER = 2
};
/// <summary>
/// Temporal filter is for doing motion blur while rendering a series of frames for animation.
/// The filter created is used as a vector of scalar values to multiply the time value by in between embers.
/// There are three possible types: Gaussian, Box and Exp.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API TemporalFilter
{
public:
/// <summary>
/// Constructor to set up basic filtering parameters, allocate buffers and calculate deltas.
/// Derived class constructors will complete the final part of filter setup.
/// </summary>
/// <param name="filterType">Type of the filter.</param>
/// <param name="passes">The number of passes used in the ember being rendered</param>
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
/// <param name="filterWidth">The width of the filter.</param>
TemporalFilter(eTemporalFilterType filterType, unsigned int passes, unsigned int temporalSamples, T filterWidth)
{
unsigned int i, steps = passes * temporalSamples;
m_Deltas.resize(steps);
m_Filter.resize(steps);
m_FilterType = filterType;
if (steps == 1)
{
m_SumFilt = 1;
m_Deltas[0] = 0;
m_Filter[0] = 1;
}
else
{
//Define the temporal deltas.
for (i = 0; i < steps; i++)
m_Deltas[i] = (T(i) / T(steps - 1) - T(0.5)) * filterWidth;
}
}
/// <summary>
/// Copy constructor.
/// </summary>
/// <param name="filter">The TemporalFilter object to copy</param>
TemporalFilter(const TemporalFilter<T>& filter)
{
*this = filter;
}
/// <summary>
/// Virtual destructor so derived class destructors get called.
/// </summary>
virtual ~TemporalFilter()
{
}
/// <summary>
/// Assignment operator.
/// </summary>
/// <param name="filter">The TemporalFilter object to copy.</param>
/// <returns>Reference to updated self</returns>
TemporalFilter<T>& operator = (const TemporalFilter<T>& filter)
{
if (this != &filter)
{
m_SumFilt = filter.m_SumFilt;
m_Deltas = filter.m_Deltas;
m_Filter = filter.m_Filter;
m_FilterType = filter.m_FilterType;
}
return *this;
}
/// <summary>
/// Return a string representation of this filter.
/// </summary>
/// <returns>The string representation of this filter</returns>
string ToString() const
{
unsigned int i;
stringstream ss;
ss << "Temporal Filter:" << endl
<< " Size: " << Size() << endl
<< " Type: " << TemporalFilterCreator<T>::ToString(m_FilterType) << endl
<< " Sum Filt: " << SumFilt() << endl;
ss << "Deltas: " << endl;
for (i = 0; i < m_Deltas.size(); i++)
{
ss << "Deltas[" << i << "]: " << m_Deltas[i] << endl;
}
ss << "Filter: " << endl;
for (i = 0; i < m_Filter.size(); i++)
{
ss << "Filter[" << i << "]: " << m_Filter[i] << endl;
}
return ss.str();
}
/// <summary>
/// Accessors.
/// </summary>
size_t Size() const { return m_Filter.size(); }
T SumFilt() const { return m_SumFilt; }
T* Deltas() { return &m_Deltas[0]; }
T* Filter() { return &m_Filter[0]; }
eTemporalFilterType FilterType() const { return m_FilterType; }
protected:
/// <summary>
/// Normalize the filter and the sum filt.
/// </summary>
/// <param name="maxFilt">The maximum filter value contained in the filter vector after it was created</param>
void FinishFilter(T maxFilt)
{
m_SumFilt = 0;
for (unsigned int i = 0; i < Size(); i++)
{
m_Filter[i] /= maxFilt;
m_SumFilt += m_Filter[i];
}
m_SumFilt /= Size();
}
T m_SumFilt;//The sum of all filter values.
vector<T> m_Deltas;//Delta vector.
vector<T> m_Filter;//Filter vector.
eTemporalFilterType m_FilterType;//The type of filter this is.
};
/// <summary>
/// Derivation which implements the Exp filter.
/// </summary>
template <typename T>
class EMBER_API ExpTemporalFilter : public TemporalFilter<T>
{
public:
/// <summary>
/// Constructor to create an Exp filter.
/// </summary>
/// <param name="passes">The number of passes used in the ember being rendered</param>
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
/// <param name="filterWidth">The width of the filter.</param>
/// <param name="filterExp">The filter exp.</param>
ExpTemporalFilter(unsigned int passes, unsigned int temporalSamples, T filterWidth, T filterExp)
: TemporalFilter<T>(BOX_TEMPORAL_FILTER, passes, temporalSamples, filterWidth)
{
if (Size() > 1)
{
T slpx, maxFilt = 0;
for (unsigned int i = 0; i < Size(); i++)
{
if (filterExp >= 0)
slpx = (T(i) + 1) / Size();
else
slpx = T(Size() - i) / Size();
//Scale the color based on these values.
m_Filter[i] = pow(slpx, fabs(filterExp));
//Keep the max.
if (m_Filter[i] > maxFilt)
maxFilt = m_Filter[i];
}
FinishFilter(maxFilt);
}
}
};
/// <summary>
/// Derivation which implements the Gaussian filter.
/// </summary>
template <typename T>
class EMBER_API GaussianTemporalFilter : public TemporalFilter<T>
{
public:
/// <summary>
/// Constructor to create a Gaussian filter.
/// </summary>
/// <param name="passes">The number of passes used in the ember being rendered</param>
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
/// <param name="filterWidth">The width of the filter.</param>
GaussianTemporalFilter(unsigned int passes, unsigned int temporalSamples, T filterWidth)
: TemporalFilter<T>(GAUSSIAN_TEMPORAL_FILTER, passes, temporalSamples, filterWidth)
{
if (Size() > 1)
{
T maxFilt = 0, halfSteps = T(Size()) / T(2);
GaussianFilter<T> gaussian(1, 1);//Just pass dummy values, they are unused in this case.
for (unsigned int i = 0; i < Size(); i++)
{
m_Filter[i] = gaussian.Filter(gaussian.Support() * fabs(i - halfSteps) / halfSteps);
//Keep the max.
if (m_Filter[i] > maxFilt)
maxFilt = m_Filter[i];
}
FinishFilter(maxFilt);
}
}
};
/// <summary>
/// Derivation which implements the Box filter.
/// </summary>
template <typename T>
class EMBER_API BoxTemporalFilter : public TemporalFilter<T>
{
public:
/// <summary>
/// Constructor to create a Box filter.
/// </summary>
/// <param name="passes">The number of passes used in the ember being rendered</param>
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
/// <param name="filterWidth">The width of the filter.</param>
BoxTemporalFilter(unsigned int passes, unsigned int temporalSamples, T filterWidth)
: TemporalFilter<T>(BOX_TEMPORAL_FILTER, passes, temporalSamples, filterWidth)
{
if (Size() > 1)
{
for (unsigned int i = 0; i < Size(); i++)
m_Filter[i] = 1;
FinishFilter(1);
}
}
};
/// <summary>
/// Convenience class to assist in converting between filter names and the filter objects themselves.
/// </summary>
template <typename T>
class EMBER_API TemporalFilterCreator
{
public:
/// <summary>
/// Creates the specified filter type based on the filterType enum parameter.
/// </summary>
/// <param name="filterType">Type of the filter</param>
/// <param name="passes">The number of passes used in the ember being rendered</param>
/// <param name="temporalSamples">The number of temporal samples in the ember being rendered</param>
/// <param name="filterWidth">The width of the filter</param>
/// <param name="filterExp">The filter exp, only used with Exp filter, otherwise ignored.</param>
/// <returns>A pointer to the newly created filter object</returns>
static TemporalFilter<T>* Create(eTemporalFilterType filterType, unsigned int passes, unsigned int temporalSamples, T filterWidth, T filterExp = 1)
{
TemporalFilter<T>* filter = NULL;
if (filterType == BOX_TEMPORAL_FILTER)
filter = new BoxTemporalFilter<T>(passes, temporalSamples, filterWidth);
else if (filterType == GAUSSIAN_TEMPORAL_FILTER)
filter = new GaussianTemporalFilter<T>(passes, temporalSamples, filterWidth);
else if (filterType == EXP_TEMPORAL_FILTER)
filter = new ExpTemporalFilter<T>(passes, temporalSamples, filterWidth, filterExp);
else
filter = new BoxTemporalFilter<T>(passes, temporalSamples, filterWidth);//Default to box if bad enum passed in.
return filter;
}
/// <summary>
/// Return a string vector of the available filter types.
/// </summary>
/// <returns>A vector of strings populated with the available filter types</returns>
static vector<string> FilterTypes()
{
vector<string> v;
v.reserve(3);
v.push_back("Box");
v.push_back("Gaussian");
v.push_back("Exp");
return v;
}
/// <summary>
/// Convert between the filter name string and its type enum.
/// </summary>
/// <param name="filterType">The string name of the filter</param>
/// <returns>The filter type enum</returns>
static eTemporalFilterType FromString(string filterType)
{
if (!_stricmp(filterType.c_str(), "box"))
return BOX_TEMPORAL_FILTER;
else if (!_stricmp(filterType.c_str(), "gaussian"))
return GAUSSIAN_TEMPORAL_FILTER;
else if (!_stricmp(filterType.c_str(), "exp"))
return EXP_TEMPORAL_FILTER;
else
return BOX_TEMPORAL_FILTER;
}
/// <summary>
/// Convert between the filter type enum and its name string.
/// </summary>
/// <param name="eTemporalFilterType">The filter type enum</param>
/// <returns>The string name of the filter</returns>
static string ToString(eTemporalFilterType filterType)
{
string filter;
if (filterType == BOX_TEMPORAL_FILTER)
filter = "Box";
else if (filterType == GAUSSIAN_TEMPORAL_FILTER)
filter = "Gaussian";
else if (filterType == EXP_TEMPORAL_FILTER)
filter = "Exp";
else
filter = "Box";
return filter;
}
};
}

7
Source/Ember/Timing.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "stdafx.h"
#include "Timing.h"
namespace Flam3
{
}

231
Source/Ember/Timing.h Normal file
View File

@ -0,0 +1,231 @@
#pragma once
#include "EmberDefines.h"
/// <summary>
/// Timing and CriticalSection classes.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Since the algorithm is so computationally intensive, timing and benchmarking are an integral portion
/// of both the development process and the execution results. This class provides an easy way to time
/// things by simply calling its Tic() and Toc() member functions. It also assists with formatting the
/// elapsed time as a string.
/// </summary>
class EMBER_API Timing
{
public:
/// <summary>
/// Constructor that takes an optional precision argument which specifies how many digits after the decimal place should be printed for seconds.
/// As a convenience, the Tic() function is called automatically.
/// </summary>
/// <param name="precision">The precision of the seconds field of the elapsed time. Default: 2.</param>
Timing(int precision = 2)
{
m_Precision = precision;
Init();
Tic();
}
/// <summary>
/// Set the begin time.
/// </summary>
/// <returns>The quad part of the begin time cast to a double</returns>
double Tic()
{
QueryPerformanceCounter(&m_BeginTime);
return BeginTime();
}
/// <summary>
/// Set the end time and optionally output a string showing the elapsed time.
/// </summary>
/// <param name="str">The string to output. Default: NULL.</param>
/// <param name="fullString">If true, output the string verbatim, else output the text " processing time: " in between str and the formatted time.</param>
/// <returns>The elapsed time in milliseconds as a double</returns>
double Toc(const char* str = NULL, bool fullString = false)
{
QueryPerformanceCounter(&m_EndTime);
double ms = ElapsedTime();
if (str != NULL)
{
cout << string(str) << (fullString ? "" : " processing time: ") << Format(ms) << endl;
}
return ms;
}
/// <summary>
/// Return the quad part of the begin time as a double.
/// </summary>
/// <returns></returns>
double BeginTime() { return (double)m_BeginTime.QuadPart; }
/// <summary>
/// Return the quad part of the end time as a double.
/// </summary>
/// <returns></returns>
double EndTime() { return (double)m_EndTime.QuadPart; }
/// <summary>
/// Return the elapsed time in milliseconds.
/// </summary>
/// <returns>The elapsed time in milliseconds as a double</returns>
double ElapsedTime() { return double(m_EndTime.QuadPart - m_BeginTime.QuadPart) * 1000.0 / double(m_Freq.QuadPart); }
/// <summary>
/// Formats a specified milliseconds value as a string.
/// This uses some intelligence to determine what to return depending on how much time has elapsed.
/// Days, hours and minutes are only included if 1 or more of them has elapsed. Seconds are always
/// included as a decimal value with the precision the user specified in the constructor.
/// </summary>
/// <param name="ms">The ms</param>
/// <returns>The formatted string</returns>
string Format(double ms)
{
stringstream ss;
double x = ms / 1000;
double secs = fmod(x, 60);
x /= 60;
double mins = fmod(x, 60);
x /= 60;
double hours = fmod(x, 24);
x /= 24;
double days = x;
if (days >= 1)
ss << (int)days << "d ";
if (hours >= 1)
ss << (int)hours << "h ";
if (mins >= 1)
ss << (int)mins << "m ";
ss << std::fixed << std::setprecision(m_Precision) << secs << "s";
return ss.str();
}
/// <summary>
/// Return the frequency of the clock as a double.
/// </summary>
/// <returns></returns>
static double Freq()
{
Init();
return (double)m_Freq.QuadPart;
}
/// <summary>
/// Return the number of cores in the system.
/// </summary>
/// <returns>The number of cores in the system</returns>
static int ProcessorCount()
{
Init();
return m_ProcessorCount;
}
private:
/// <summary>
/// Query and store the performance info of the system.
/// Since it will never change it only needs to be queried once.
/// This is achieved by keeping static state and performance variables.
/// </summary>
static void Init()
{
if (!m_TimingInit)
{
SYSTEM_INFO sysinfo;
QueryPerformanceFrequency(&m_Freq);
GetSystemInfo(&sysinfo);
m_ProcessorCount = sysinfo.dwNumberOfProcessors;
m_TimingInit = true;
}
}
int m_Precision;//How many digits after the decimal place to print for seconds.
LARGE_INTEGER m_BeginTime;//The start of the timing, set with Tic().
LARGE_INTEGER m_EndTime;//The end of the timing, set with Toc().
static bool m_TimingInit;//Whether the performance info has bee queried.
static int m_ProcessorCount;//The number of cores on the system, set in Init().
static LARGE_INTEGER m_Freq;//The clock frequency, set in Init().
};
/// <summary>
/// Cross platform critical section class which can be used for thread locking.
/// </summary>
class EMBER_API CriticalSection
{
#ifdef _WIN32
public:
/// <summary>
/// Constructor which initialized the underlying CRITICAL_SECTION object.
/// </summary>
CriticalSection() { InitializeCriticalSection(&m_CriticalSection); }
/// <summary>
/// Constructor which initialized the underlying CRITICAL_SECTION object
/// with the specified spin count value.
/// </summary>
/// <param name="spinCount">The spin count.</param>
CriticalSection(DWORD spinCount) { InitializeCriticalSectionAndSpinCount(&m_CriticalSection, spinCount); }
/// <summary>
/// Deletes the underlying CRITICAL_SECTION object.
/// </summary>
~CriticalSection() { DeleteCriticalSection(&m_CriticalSection); }
/// <summary>
/// Lock the critical section.
/// </summary>
void Enter() { EnterCriticalSection(&m_CriticalSection); }
/// <summary>
/// Unlock the critical section.
/// </summary>
void Leave() { LeaveCriticalSection(&m_CriticalSection); }
private:
CRITICAL_SECTION m_CriticalSection;//The Windows specific critical section object.
#else
/// <summary>
/// Constructor which initialized the underlying pthread_mutex_t object.
/// </summary>
CriticalSection()
{
pthread_mutexattr_t attr;
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
pthread_mutex_init(&m_CriticalSection, &attr);
pthread_mutexattr_destroy(&attr);
}
/// <summary>
/// Deletes the underlying pthread_mutex_t object.
/// </summary>
~CriticalSection() { pthread_mutex_destroy(&m_CriticalSection); }
/// <summary>
/// Lock the critical section.
/// </summary>
void Enter() { pthread_mutex_lock(&m_CriticalSection); }
/// <summary>
/// Unlock the critical section.
/// </summary>
void Leave() { pthread_mutex_unlock(&m_CriticalSection); }
private:
pthread_mutex_t m_CriticalSection;//The *nix/pthread specific critical section object.
#endif
};
}

887
Source/Ember/Utils.h Normal file
View File

@ -0,0 +1,887 @@
#pragma once
#include "Isaac.h"
/// <summary>
/// Global utility classes and functions that don't really fit anywhere else, but are
/// too small to justify being in their own file.
/// </summary>
namespace EmberNs
{
/// <summary>
/// After a run completes, information about what was run can be saved as strings to the comments
/// section of a jpg or png file. This class is just a container for those values.
/// </summary>
class EMBER_API EmberImageComments
{
public:
/// <summary>
/// Set all values to the empty string.
/// </summary>
void Clear()
{
m_Genome = "";
m_Badvals = "";
m_NumIters = "";
m_Runtime = "";
}
string m_Genome;
string m_Badvals;
string m_NumIters;
string m_Runtime;
};
/// <summary>
/// Since running is an incredibly complex process with multiple points of possible failure,
/// it's important that as much information as possible is captured if something goes wrong.
/// Classes wishing to capture this failure information will derive from this class and populate
/// the vector of strings with any useful error information. Note that a small complication can occur
/// when a class derives from this class, yet also has one or more members which do too. In that case, they should
/// override the methods to aggregate the error information from themselves, as well as their members.
/// </summary>
class EMBER_API EmberReport
{
public:
/// <summary>
/// Write the entire error report as a single string to the console.
/// Derived classes with members that also derive from EmberReport should override this to capture
/// their error information as well as that of their members.
/// </summary>
virtual void DumpErrorReport() { cout << ErrorReportString(); }
/// <summary>
/// Clear the error report string vector.
/// Derived classes with members that also derive from EmberReport should override this to clear
/// their error information as well as that of their members.
/// </summary>
virtual void ClearErrorReport() { m_ErrorReport.clear(); }
/// <summary>
/// Return the entire error report as a single string.
/// Derived classes with members that also derive from EmberReport should override this to capture
/// their error information as well as that of their members.
/// </summary>
/// <returns>The entire error report as a single string. Empty if no errors.</returns>
virtual string ErrorReportString() { return StaticErrorReportString(m_ErrorReport); }
/// <summary>
/// Return the entire error report as a vector of strings.
/// Derived classes with members that also derive from EmberReport should override this to capture
/// their error information as well as that of their members.
/// </summary>
/// <returns>The entire error report as a vector of strings. Empty if no errors.</returns>
virtual vector<string> ErrorReport() { return m_ErrorReport; }
/// <summary>
/// Add string to report.
/// </summary>
/// <param name="s">The string to add</param>
virtual void AddToReport(string s) { m_ErrorReport.push_back(s); }
/// <summary>
/// Add a vector of strings to report.
/// </summary>
/// <param name="vec">The vector of strings to add</param>
virtual void AddToReport(vector<string>& vec) { m_ErrorReport.insert(m_ErrorReport.end(), vec.begin(), vec.end()); }
/// <summary>
/// Static function to dump a vector of strings passed in.
/// </summary>
/// <param name="errorReport">The vector of strings to dump</param>
static void StaticDumpErrorReport(vector<string>& errorReport) { cout << StaticErrorReportString(errorReport); }
/// <summary>
/// Static function to return the entire error report passed in as a single string.
/// </summary>
/// <param name="errorReport">The vector of strings to concatenate</param>
/// <returns>A string containing all strings in the vector passed in separated by newlines</returns>
static string StaticErrorReportString(vector<string>& errorReport)
{
stringstream ss;
std::for_each(errorReport.begin() , errorReport.end() , [&](string s) { ss << s << endl; });
return ss.str();
}
protected:
vector<string> m_ErrorReport;
};
/// <summary>
/// Open a file in binary mode and read its entire contents into a vector of unsigned chars. Optionally null terminate.
/// </summary>
/// <param name="filename">The full path to the file to read</param>
/// <param name="buf">The vector which will be populated with the file's contents</param>
/// <param name="nullTerminate">Whether to append a NULL character as the last element of the vector. Needed when reading text files. Default: true.</param>
/// <returns>True if successfully read and populated, else false</returns>
static bool ReadFile(const char* filename, string& buf, bool nullTerminate = true)
{
bool b = false;
FILE* f;
try
{
fopen_s(&f, filename, "rb");//Open in binary mode.
if (f != NULL)
{
struct _stat statBuf;
int statResult = _fstat(f->_file, &statBuf);//Get data associated with file.
if (statResult == 0)//Check if statistics are valid.
{
buf.resize(statBuf.st_size + (nullTerminate ? 1 : 0));//Allocate vector to be the size of the entire file, with an optional additional character for NULL.
if (buf.size() == statBuf.st_size + 1)//Ensure allocation succeeded.
{
size_t bytesRead = fread(&buf[0], 1, statBuf.st_size, f);//Read the entire file at once.
if (bytesRead == statBuf.st_size)//Ensure the number of bytes read matched what was requested.
{
if (nullTerminate)//Optionally NULL terminate if they want to treat it as a string.
buf[buf.size() - 1] = NULL;
b = true;//Success.
}
}
}
fclose(f);
}
}
catch (...)
{
if (f != NULL)
fclose(f);
b = false;
}
return b;
}
/// <summary>
/// Clear dest and copy all of the elements of vector source with elements of type U to the vector
/// dest with elements of type T.
/// </summary>
/// <param name="dest">The vector of type T to copy to</param>
/// <param name="source">The vector of type U to copy from</param>
template <typename T, typename U>
void CopyVec(vector<T>& dest, const vector<U>& source)
{
dest.clear();
dest.resize(source.size());
for (size_t i = 0; i < source.size(); i++)
dest[i] = T(source[i]);//Valid assignment operator between T and U types must be defined somewhere.
}
/// <summary>
/// Clear a vector of pointers to any type by checking each element for NULL and calling delete on it, then clearing the entire vector.
/// Optionally call array delete if the elements themselves are pointers to dynamically allocated arrays.
/// </summary>
/// <param name="vec">The vector to be cleared</param>
/// <param name="arrayDelete">Whether to call delete or delete []. Default: false.</param>
template <typename T>
static void ClearVec(vector<T*>& vec, bool arrayDelete = false)
{
for (unsigned int i = 0; i < vec.size(); i++)
{
if (vec[i] != NULL)
{
if (arrayDelete)
delete [] vec[i];
else
delete vec[i];
}
vec[i] = NULL;
}
vec.clear();
}
/// <summary>
/// Convert an RGBA buffer to an RGB buffer.
/// </summary>
/// <param name="rgba">The RGBA buffer</param>
/// <param name="rgb">The RGB buffer</param>
/// <param name="width">The width of the image in pixels</param>
/// <param name="height">The height of the image in pixels</param>
static void RgbaToRgb(vector<unsigned char>& rgba, vector<unsigned char>& rgb, unsigned int width, unsigned int height)
{
rgb.resize(width * height * 3);
for (unsigned int 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];
}
}
/// <summary>
/// Clamp and return a value to be greater than or equal to a specified minimum and less than
/// or equal to a specified maximum.
/// </summary>
/// <param name="val">The value to be clamped</param>
/// <param name="min">A value which the clamped value must be greater than or equal to</param>
/// <param name="max">A value which the clamped value must be less than or equal to</param>
/// <returns>The clamped value</returns>
template <typename T>
static inline T Clamp(T val, T min, T max)
{
if (val < min)
return min;
else if (val > max)
return max;
else
return val;
}
/// <summary>
/// Clamp and return a value to be greater than or equal to a specified minimum and less than
/// or equal to a specified maximum. If lesser, the value is fmod(val - min, max - min). If greater,
/// the value is max - fmod(max - val, max - min).
/// </summary>
/// <param name="val">The value to be clamped</param>
/// <param name="min">A value which the clamped value must be greater than or equal to</param>
/// <param name="max">A value which the clamped value must be less than or equal to</param>
/// <returns>The clamped and modded value</returns>
template <typename T>
static inline T ClampMod(T val, T min, T max)
{
if (val < min)
return min + fmod(val - min, max - min);
else if (val > max)
return max - fmod(max - val, max - min);
else
return val;
}
/// <summary>
/// Similar to Clamp(), but clamps a reference value in place rather than returning.
/// </summary>
/// <param name="val">The reference value to be clamped in place</param>
/// <param name="min">A value which the clamped value must be greater than or equal to</param>
/// <param name="max">A value which the clamped value must be less than or equal to</param>
template <typename T>
static inline void ClampRef(T& val, T min, T max)
{
if (val < min)
val = min;
else if (val > max)
val = max;
}
/// <summary>
/// Similar to Clamp(), but clamps a reference value in place rather than returning.
/// </summary>
/// <param name="val">The reference value to be clamped in place</param>
/// <param name="gte">A value which the clamped value must be less than or equal to</param>
template <typename T>
static inline void ClampLteRef(T& val, T lte)
{
if (val > lte)
val = lte;
}
/// <summary>
/// Clamp and return a value to be greater than or equal to a specified value.
/// Useful for ensuring something is not less than zero.
/// </summary>
/// <param name="val">The value to be clamped</param>
/// <param name="gte">A value which the clamped value must be greater than or equal to</param>
/// <returns>The clamped value</returns>
template <typename T>
static inline T ClampGte(T val, T gte)
{
return (val < gte) ? gte : val;
}
/// <summary>
/// Similar to Clamp(), but clamps a reference value in place rather than returning.
/// </summary>
/// <param name="val">The reference value to be clamped in place</param>
/// <param name="gte">A value which the clamped value must be greater than or equal to</param>
template <typename T>
static inline void ClampGteRef(T& val, T gte)
{
if (val < gte)
val = gte;
}
/// <summary>
/// Thin wrapper around a call to ClampGte() with a gte value of zero.
/// </summary>
/// <param name="val">The value to be clamped</param>
/// <returns>The clamped value</returns>
template <typename T>
static inline T ClampGte0(T val)
{
return ClampGte<T>(val, 0);
}
/// <summary>
/// Thin wrapper around a call to ClampGteRef() with a gte value of zero.
/// </summary>
/// <param name="val">The reference value to be clamped in place</param>
template <typename T>
static inline void ClampGte0Ref(T& val)
{
ClampGteRef<T>(val, 0);
}
/// <summary>
/// Return a value rounded up or down. Works for positive and negative numbers.
/// </summary>
/// <param name="r">The value to round</param>
/// <returns>The rounded value</returns>
template <typename T>
T Round(T r)
{
return (r > 0) ? (T)Floor<T>(r + T(0.5)) : ceil(r - T(0.5));
}
/// <summary>
/// Special rounding for certain variations, gotten from Apophysis.
/// </summary>
/// <param name="x">The value to round</param>
/// <returns>The rounded value</returns>
inline float LRint(float x)
{
int temp = (x >= 0 ? (int)(x + 0.5f) : (int)(x - 0.5f));
return (float)temp;
}
/// <summary>
/// Special rounding for certain variations, gotten from Apophysis.
/// </summary>
/// <param name="x">The value to round</param>
/// <returns>The rounded value</returns>
inline double LRint(double x)
{
__int64 temp = (x >= 0 ? (__int64)(x + 0.5) : (__int64)(x - 0.5));
return (double)temp;
}
/// <summary>
/// System floor() extremely slow because it accounts for various error conditions.
/// This is a much faster version that works on data that is not NaN.
/// </summary>
/// <param name="x">The value to return the floor of</param>
/// <returns>The floored value</returns>
template <typename T>
static inline int Floor(T val)
{
if (val >= 0)
{
return (int)val;
}
else
{
int i = (int)val;//Truncate.
return i - (i > val);//Convert trunc to floor.
}
}
/// <summary>
/// Never really understood what this did.
/// </summary>
/// <param name="r">The value to round</param>
/// <returns>The rounded value</returns>
template <typename T>
static inline T Round6(T r)
{
r *= 1e6;
if (r < 0)
r -= 1;
return T(1e-6 * (int)(r + T(0.5)));
}
/// <summary>
/// Return -1 if the value is less than 0, 1 if it's greater and
/// 0 if it's equal to 0.
/// </summary>
/// <param name="v">The value to inspect</param>
/// <returns>-1, 0 or 1</returns>
template <typename T>
static inline T Sign(T v)
{
return (v < 0) ? T(-1) : (v > 0) ? T(1) : T(0);
}
/// <summary>
/// Return -1 if the value is less than 0, 1 if it's greater.
/// This differs from Sign() in that it doesn't return 0.
/// </summary>
/// <param name="v">The value to inspect</param>
/// <returns>-1 or 1</returns>
template <typename T>
static inline T SignNz(T v)
{
return (v < 0) ? T(-1) : T(1);
}
/// <summary>
/// Return the square of the passed in value.
/// This is useful when the value is a result of a computation
/// rather than a fixed number. Otherwise, use the SQR macro.
/// </summary>
/// <param name="v">The value to square</param>
/// <returns>The squared value</returns>
template <typename T>
static inline T Sqr(T t)
{
return t * t;
}
/// <summary>
/// Taking the square root of numbers close to zero is dangerous. If x is negative
/// due to floating point errors, it can return NaN results.
/// </summary>
template <typename T>
static inline T SafeSqrt(T x)
{
if (x <= 0)
return 0;
return sqrt(x);
}
/// <summary>
/// Return the cube of the passed in value.
/// This is useful when the value is a result of a computation
/// rather than a fixed number. Otherwise, use the CUBE macro.
/// </summary>
/// <param name="v">The value to cube</param>
/// <returns>The cubed value</returns>
template <typename T>
static inline T Cube(T t)
{
return t * t * t;
}
/// <summary>
/// Return the hypotenuse of the passed in values.
/// </summary>
/// <param name="x">The x distance</param>
/// <param name="y">The y distance</param>
/// <returns>The hypotenuse</returns>
template <typename T>
static inline T Hypot(T x, T y)
{
return sqrt(SQR(x) + SQR(y));
}
/// <summary>
/// Spread the values.
/// </summary>
/// <param name="x">The x distance</param>
/// <param name="y">The y distance</param>
/// <returns>The spread</returns>
template <typename T>
static inline T Spread(T x, T y)
{
return Hypot<T>(x, y) * ((x) > 0 ? 1 : -1);
}
/// <summary>
/// Unsure.
/// </summary>
/// <param name="x">The x distance</param>
/// <param name="y">The y distance</param>
/// <returns>The powq4</returns>
template <typename T>
static inline T Powq4(T x, T y)
{
return pow(fabs(x), y) * SignNz(x);
}
/// <summary>
/// Unsure.
/// </summary>
/// <param name="x">The x distance</param>
/// <param name="y">The y distance</param>
/// <returns>The powq4c</returns>
template <typename T>
static inline T Powq4c(T x, T y)
{
return y == 1 ? x : Powq4(x, y);
}
/// <summary>
/// Return EPS6 if the passed in value was zero, else return the value.
/// </summary>
/// <param name="x">The value</param>
/// <param name="y">The y distance</param>
/// <returns>EPS6 or the value if it was non-zero</returns>
template <typename T>
static inline T Zeps(T x)
{
return x == 0 ? EPS6 : x;
}
/// <summary>
/// Interpolate a given percentage between two values.
/// </summary>
/// <param name="a">The first value to interpolate between.</param>
/// <param name="b">The secod value to interpolate between.</param>
/// <param name="p">The percentage between the two values to calculate.</param>
/// <returns>The interpolated value.</returns>
template <typename T>
static inline T Lerp(T a, T b, T p)
{
return a + (b - a) * p;
}
/// <summary>
/// Thin wrapper around a call to modf that discards the integer portion
/// and returns the signed fractional portion.
/// </summary>
/// <param name="v">The value to retrieve the signed fractional portion of.</param>
/// <returns>The signed fractional portion of v.</returns>
template <typename T>
static inline T Fabsmod(T v)
{
T dummy;
return modf(v, &dummy);
}
/// <summary>
/// Unsure.
/// </summary>
/// <param name="p">Unsure.</param>
/// <param name="amp">Unsure.</param>
/// <param name="ph">Unsure.</param>
/// <returns>Unsure.</returns>
template <typename T>
static inline T Fosc(T p, T amp, T ph)
{
return T(0.5) - cos(p * amp + ph) * T(0.5);
}
/// <summary>
/// Unsure.
/// </summary>
/// <param name="p">Unsure.</param>
/// <param name="ph">Unsure.</param>
/// <returns>Unsure.</returns>
template <typename T>
static inline T Foscn(T p, T ph)
{
return T(0.5) - cos(p + ph) * T(0.5);
}
/// <summary>
/// Log scale from Apophysis.
/// </summary>
/// <param name="x">The value to log scale</param>
/// <returns>The log scaled value</returns>
template <typename T>
static inline T LogScale(T x)
{
return x == 0 ? 0 : log((fabs(x) + 1) * T(M_E)) * SignNz(x) / T(M_E);
}
/// <summary>
/// Log map from Apophysis.
/// </summary>
/// <param name="x">The value to log map</param>
/// <returns>The log mapped value</returns>
template <typename T>
static inline T LogMap(T x)
{
return x == 0 ? 0 : (T(M_E) + log(x * T(M_E))) * T(0.25) * SignNz(x);
}
/// <summary>
/// Thin wrapper around calling xmlStrcmp() on an Xml tag to tell
/// if its name is a given value.
/// </summary>
/// <param name="name">The name of the tag of the to inspect</param>
/// <param name="val">The value compare against</param>
/// <returns>True if the comparison matched, else false</returns>
static inline bool Compare(const xmlChar* name, char* val)
{
return xmlStrcmp(name, XC val) != 0;
}
/// <summary>
/// Determine whether the specified value is very close to zero.
/// This is useful for determining equality of float/double types.
/// </summary>
/// <param name="val">The value to compare against</param>
/// <param name="tolerance">The tolerance. Default: 1e-6.</param>
/// <returns>True if the value was very close to zero, else false</returns>
template <typename T>
static inline bool IsNearZero(T val, T tolerance = 1e-6)
{
return (val > -tolerance && val < tolerance);
}
/// <summary>
/// Determine whether a specified value is very close to another value.
/// This is useful for determining equality of float/double types.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <param name="tolerance">The tolerance. Default: 1e-6.</param>
/// <returns>True if the values were very close to each other, else false</returns>
template <typename T>
static bool IsClose(T val1, T val2, T tolerance = 1e-6)
{
return IsNearZero(val1 - val2, tolerance);
}
/// <summary>
/// Put an angular measurement in degrees into the range of -180 - 180.
/// </summary>
/// <param name="angle">The angle to normalize</param>
/// <returns>The normalized angle in a range of -180 - 180</returns>
template <typename T>
static inline T NormalizeDeg180(T angle)
{
angle = fmod(angle, 360);
if (angle > 180)
{
angle -= 360;
}
else if (angle < -180)
{
angle += 360;
}
return angle;
}
/// <summary>
/// Put an angular measurement in degrees into the range of 0 - 360.
/// </summary>
/// <param name="angle">The angle to normalize</param>
/// <returns>The normalized angle in a range of 0 - 360</returns>
template <typename T>
static inline T NormalizeDeg360(T angle)
{
if (angle > 360 || angle < -360)
angle = fmod(angle, 360);
if (angle < 0)
angle += 360;
return angle;
}
/// <summary>
/// Return a lower case copy of a string.
/// </summary>
/// <param name="str">The string to copy and make lower case</param>
/// <returns>The lower case string</returns>
static inline string ToLower(string& str)
{
string lower;
lower.resize(str.size());//Allocate the destination space.
std::transform(str.begin(), str.end(), lower.begin(), ::tolower);//Convert the source string to lower case storing the result in the destination string.
return lower;
}
/// <summary>
/// Return an upper case copy of a string.
/// </summary>
/// <param name="str">The string to copy and make upper case</param>
/// <returns>The upper case string</returns>
static inline string ToUpper(string& str)
{
string upper;
upper.resize(str.size());//Allocate the destination space.
std::transform(str.begin(), str.end(), upper.begin(), ::toupper);//Convert the source string to lower case storing the result in the destination string.
return upper;
}
/// <summary>
/// Return a copy of a string with leading and trailing occurrences of a specified character removed.
/// The default character is a space.
/// </summary>
/// <param name="str">The string to trim</param>
/// <param name="ch">The character to trim. Default: space.</param>
/// <returns>The trimmed string</returns>
static inline string Trim(string& str, char ch = ' ')
{
string ret;
if (str != "")
{
size_t firstChar = str.find_first_not_of(ch);
size_t lastChar = str.find_last_not_of(ch);
if (firstChar == string::npos)
firstChar = 0;
if (lastChar == string::npos)
lastChar = str.size();
ret = str.substr(firstChar, lastChar - firstChar + 1);
}
return ret;
}
/// <summary>
/// Placeholder for a templated function to query the value of a specified system environment variable
/// of a specific type. This function does nothing as the functions for specific types implement the behavior
/// via template specialization.
/// </summary>
/// <param name="name">The name of the environment variable to query</param>
/// <param name="def">The default value to return if the environment variable was not present</param>
/// <returns>The value of the specified environment variable if found, else default</returns>
template <typename T>
static T Arg(char* name, T def)
{
T t;
return t;
}
/// <summary>
/// Template specialization for Arg<>() with a type of int.
/// </summary>
/// <param name="name">The name of the environment variable to query</param>
/// <param name="def">The default value to return if the environment variable was not present</param>
/// <returns>The value of the specified environment variable if found, else default</returns>
template <>
static int Arg<int>(char* name, int def)
{
char* ch;
int returnVal;
size_t len;
errno_t err = _dupenv_s(&ch, &len, name);
if (err || !ch)
returnVal = def;
else
returnVal = atoi(ch);
free(ch);
return returnVal;
}
/// <summary>
/// Template specialization for Arg<>() with a type of unsigned int.
/// </summary>
/// <param name="name">The name of the environment variable to query</param>
/// <param name="def">The default value to return if the environment variable was not present</param>
/// <returns>The value of the specified environment variable if found, else default</returns>
template <>
static unsigned int Arg<unsigned int>(char* name, unsigned int def)
{
return Arg<int>(name, (int)def);
}
/// <summary>
/// Template specialization for Arg<>() with a type of bool.
/// </summary>
/// <param name="name">The name of the environment variable to query</param>
/// <param name="def">The default value to return if the environment variable was not present</param>
/// <returns>The value of the specified environment variable if found, else default</returns>
template <>
static bool Arg<bool>(char* name, bool def)
{
return (Arg<int>(name, -999) != -999) ? true : def;
}
/// <summary>
/// Template specialization for Arg<>() with a type of double.
/// </summary>
/// <param name="name">The name of the environment variable to query</param>
/// <param name="def">The default value to return if the environment variable was not present</param>
/// <returns>The value of the specified environment variable if found, else default</returns>
template <>
static double Arg<double>(char* name, double def)
{
char* ch;
double returnVal;
size_t len;
errno_t err = _dupenv_s(&ch, &len, name);
if (err || !ch)
returnVal = def;
else
returnVal = atof(ch);
free(ch);
return returnVal;
}
/// <summary>
/// Template specialization for Arg<>() with a type of string.
/// </summary>
/// <param name="name">The name of the environment variable to query</param>
/// <param name="def">The default value to return if the environment variable was not present</param>
/// <returns>The value of the specified environment variable if found, else default</returns>
template <>
static string Arg<string>(char* name, string def)
{
char* ch;
string returnVal;
size_t len;
errno_t err = _dupenv_s(&ch, &len, name);
if (err || !ch)
{
if (def != "")
returnVal = def;
}
else
returnVal = string(ch);
free(ch);
return returnVal;
}
/// <summary>
/// Replaces all instances of a value within a collection, with the specified value.
/// Taken from a StackOverflow.com post.
/// Modified to account for the scenario where the find and replace strings each start with
/// the same character.
/// Template argument should be any STL container.
/// </summary>
/// <param name="source">Collection to replace values in</param>
/// <param name="find">The value to replace</param>
/// <param name="replace">The value to replace with</param>
/// <returns>The number of instances replaced</returns>
template<typename T>
unsigned int inline FindAndReplace(T& source, const T& find, const T& replace)
{
unsigned int replaceCount = 0;
typename T::size_type fLen = find.size();
typename T::size_type rLen = replace.size();
for (typename T::size_type pos = 0; (pos = source.find(find, pos)) != T::npos; pos += rLen)
{
typename T::size_type pos2 = source.find(replace, pos);
if (pos != pos2)
{
replaceCount++;
source.replace(pos, fLen, replace);
}
}
return replaceCount;
}
/// <summary>
/// Return a character pointer to a version string composed of the EMBER_OS and EMBER_VERSION values.
/// </summary>
static char* EmberVersion()
{
return EMBER_OS "-" EMBER_VERSION;
}
}

2168
Source/Ember/Variation.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,537 @@
#pragma once
#include "Variations01.h"
#include "Variations02.h"
#include "Variations03.h"
#include "Variations04.h"
#include "Variations05.h"
#include "VariationsDC.h"
/// <summary>
/// VariationList class.
/// </summary>
namespace EmberNs
{
/// <summary>
/// Since the list of variations is numerous, it's convenient to be able to make copies
/// of specific ones. This class holds a list of pointers to variation objects for every
/// variation available. Similar to the PaletteList class, a caller can look up a variation
/// by name or ID and retrieve a copy of it.
/// All variations are deleted upon destruction.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBER_API VariationList
{
#define ADDPREPOSTREGVAR(varName) \
m_Variations.push_back(new varName##Variation<T>()); \
m_Variations.push_back(new Pre##varName##Variation<T>()); \
m_Variations.push_back(new Post##varName##Variation<T>());
public:
/// <summary>
/// Constructor which initializes all of the variation objects and stores them in the list.
/// </summary>
VariationList()
{
m_Variations.reserve(900);//Change this as the list grows.
ADDPREPOSTREGVAR(Linear)
ADDPREPOSTREGVAR(Sinusoidal)
ADDPREPOSTREGVAR(Spherical)
ADDPREPOSTREGVAR(Swirl)
ADDPREPOSTREGVAR(Horseshoe)
ADDPREPOSTREGVAR(Polar)
ADDPREPOSTREGVAR(Handkerchief)
ADDPREPOSTREGVAR(Heart)
ADDPREPOSTREGVAR(Disc)
ADDPREPOSTREGVAR(Spiral)
ADDPREPOSTREGVAR(Hyperbolic)
ADDPREPOSTREGVAR(Diamond)
ADDPREPOSTREGVAR(Ex)
ADDPREPOSTREGVAR(Julia)
ADDPREPOSTREGVAR(Bent)
ADDPREPOSTREGVAR(Waves)
ADDPREPOSTREGVAR(Fisheye)
ADDPREPOSTREGVAR(Popcorn)
ADDPREPOSTREGVAR(Exponential)
ADDPREPOSTREGVAR(Power)
ADDPREPOSTREGVAR(Cosine)
ADDPREPOSTREGVAR(Rings)
ADDPREPOSTREGVAR(Fan)
ADDPREPOSTREGVAR(Blob)
ADDPREPOSTREGVAR(Pdj)
ADDPREPOSTREGVAR(Fan2)
ADDPREPOSTREGVAR(Rings2)
ADDPREPOSTREGVAR(Eyefish)
ADDPREPOSTREGVAR(Bubble)
ADDPREPOSTREGVAR(Cylinder)
ADDPREPOSTREGVAR(Perspective)
ADDPREPOSTREGVAR(Noise)
ADDPREPOSTREGVAR(JuliaNGeneric)
ADDPREPOSTREGVAR(JuliaScope)
ADDPREPOSTREGVAR(Blur)
ADDPREPOSTREGVAR(GaussianBlur)
ADDPREPOSTREGVAR(RadialBlur)
ADDPREPOSTREGVAR(Pie)
ADDPREPOSTREGVAR(Ngon)
ADDPREPOSTREGVAR(Curl)
ADDPREPOSTREGVAR(Rectangles)
ADDPREPOSTREGVAR(Arch)
ADDPREPOSTREGVAR(Tangent)
ADDPREPOSTREGVAR(Square)
ADDPREPOSTREGVAR(Rays)
ADDPREPOSTREGVAR(Blade)
ADDPREPOSTREGVAR(Secant2)
ADDPREPOSTREGVAR(TwinTrian)
ADDPREPOSTREGVAR(Cross)
ADDPREPOSTREGVAR(Disc2)
ADDPREPOSTREGVAR(SuperShape)
ADDPREPOSTREGVAR(Flower)
ADDPREPOSTREGVAR(Conic)
ADDPREPOSTREGVAR(Parabola)
ADDPREPOSTREGVAR(Bent2)
ADDPREPOSTREGVAR(Bipolar)
ADDPREPOSTREGVAR(Boarders)
ADDPREPOSTREGVAR(Butterfly)
ADDPREPOSTREGVAR(Cell)
ADDPREPOSTREGVAR(Cpow)
ADDPREPOSTREGVAR(Curve)
ADDPREPOSTREGVAR(Edisc)
ADDPREPOSTREGVAR(Elliptic)
ADDPREPOSTREGVAR(Escher)
ADDPREPOSTREGVAR(Foci)
ADDPREPOSTREGVAR(LazySusan)
ADDPREPOSTREGVAR(Loonie)
ADDPREPOSTREGVAR(Modulus)
ADDPREPOSTREGVAR(Oscilloscope)
ADDPREPOSTREGVAR(Polar2)
ADDPREPOSTREGVAR(Popcorn2)
ADDPREPOSTREGVAR(Scry)
ADDPREPOSTREGVAR(Separation)
ADDPREPOSTREGVAR(Split)
ADDPREPOSTREGVAR(Splits)
ADDPREPOSTREGVAR(Stripes)
ADDPREPOSTREGVAR(Wedge)
ADDPREPOSTREGVAR(WedgeJulia)
ADDPREPOSTREGVAR(WedgeSph)
ADDPREPOSTREGVAR(Whorl)
ADDPREPOSTREGVAR(Waves2)
ADDPREPOSTREGVAR(Exp)
ADDPREPOSTREGVAR(Log)
ADDPREPOSTREGVAR(Sin)
ADDPREPOSTREGVAR(Cos)
ADDPREPOSTREGVAR(Tan)
ADDPREPOSTREGVAR(Sec)
ADDPREPOSTREGVAR(Csc)
ADDPREPOSTREGVAR(Cot)
ADDPREPOSTREGVAR(Sinh)
ADDPREPOSTREGVAR(Cosh)
ADDPREPOSTREGVAR(Tanh)
ADDPREPOSTREGVAR(Sech)
ADDPREPOSTREGVAR(Csch)
ADDPREPOSTREGVAR(Coth)
ADDPREPOSTREGVAR(Auger)
ADDPREPOSTREGVAR(Flux)
ADDPREPOSTREGVAR(Hemisphere)
ADDPREPOSTREGVAR(Epispiral)
ADDPREPOSTREGVAR(Bwraps)
ADDPREPOSTREGVAR(BlurCircle)
ADDPREPOSTREGVAR(BlurZoom)
ADDPREPOSTREGVAR(BlurPixelize)
ADDPREPOSTREGVAR(Crop)
ADDPREPOSTREGVAR(BCircle)
ADDPREPOSTREGVAR(BlurLinear)
ADDPREPOSTREGVAR(BlurSquare)
ADDPREPOSTREGVAR(Boarders2)
ADDPREPOSTREGVAR(Cardioid)
ADDPREPOSTREGVAR(Checks)
ADDPREPOSTREGVAR(Circlize)
ADDPREPOSTREGVAR(Circlize2)
ADDPREPOSTREGVAR(CosWrap)
ADDPREPOSTREGVAR(DeltaA)
ADDPREPOSTREGVAR(Expo)
ADDPREPOSTREGVAR(Extrude)
ADDPREPOSTREGVAR(FDisc)
ADDPREPOSTREGVAR(Fibonacci)
ADDPREPOSTREGVAR(Fibonacci2)
ADDPREPOSTREGVAR(Glynnia)
ADDPREPOSTREGVAR(GridOut)
ADDPREPOSTREGVAR(Hole)
ADDPREPOSTREGVAR(Hypertile)
ADDPREPOSTREGVAR(Hypertile1)
ADDPREPOSTREGVAR(Hypertile2)
ADDPREPOSTREGVAR(Hypertile3D)
ADDPREPOSTREGVAR(Hypertile3D1)
ADDPREPOSTREGVAR(Hypertile3D2)
ADDPREPOSTREGVAR(IDisc)
ADDPREPOSTREGVAR(Julian2)
ADDPREPOSTREGVAR(JuliaQ)
ADDPREPOSTREGVAR(Murl)
ADDPREPOSTREGVAR(Murl2)
ADDPREPOSTREGVAR(NPolar)
ADDPREPOSTREGVAR(Ortho)
ADDPREPOSTREGVAR(Poincare)
ADDPREPOSTREGVAR(Poincare3D)
ADDPREPOSTREGVAR(Polynomial)
ADDPREPOSTREGVAR(PSphere)
ADDPREPOSTREGVAR(Rational3)
ADDPREPOSTREGVAR(Ripple)
ADDPREPOSTREGVAR(Sigmoid)
ADDPREPOSTREGVAR(SinusGrid)
ADDPREPOSTREGVAR(Stwin)
ADDPREPOSTREGVAR(TwoFace)
ADDPREPOSTREGVAR(Unpolar)
ADDPREPOSTREGVAR(WavesN)
ADDPREPOSTREGVAR(XHeart)
ADDPREPOSTREGVAR(Barycentroid)
ADDPREPOSTREGVAR(BiSplit)
ADDPREPOSTREGVAR(Crescents)
ADDPREPOSTREGVAR(Mask)
ADDPREPOSTREGVAR(Cpow2)
ADDPREPOSTREGVAR(Curl3D)
ADDPREPOSTREGVAR(Disc3D)
ADDPREPOSTREGVAR(Funnel)
ADDPREPOSTREGVAR(Linear3D)
ADDPREPOSTREGVAR(PowBlock)
ADDPREPOSTREGVAR(Squirrel)
ADDPREPOSTREGVAR(Ennepers)
ADDPREPOSTREGVAR(SphericalN)
ADDPREPOSTREGVAR(Kaleidoscope)
ADDPREPOSTREGVAR(GlynnSim1)
ADDPREPOSTREGVAR(GlynnSim2)
ADDPREPOSTREGVAR(GlynnSim3)
ADDPREPOSTREGVAR(Starblur)
ADDPREPOSTREGVAR(Sineblur)
ADDPREPOSTREGVAR(Circleblur)
ADDPREPOSTREGVAR(CropN)
ADDPREPOSTREGVAR(ShredRad)
ADDPREPOSTREGVAR(Blob2)
ADDPREPOSTREGVAR(Julia3D)
ADDPREPOSTREGVAR(Julia3Dz)
ADDPREPOSTREGVAR(LinearT)
ADDPREPOSTREGVAR(LinearT3D)
ADDPREPOSTREGVAR(Ovoid)
ADDPREPOSTREGVAR(Ovoid3D)
ADDPREPOSTREGVAR(Spirograph)
ADDPREPOSTREGVAR(Petal)
ADDPREPOSTREGVAR(RoundSpher)
ADDPREPOSTREGVAR(RoundSpher3D)
ADDPREPOSTREGVAR(SpiralWing)
ADDPREPOSTREGVAR(Squarize)
ADDPREPOSTREGVAR(Sschecks)
ADDPREPOSTREGVAR(PhoenixJulia)
ADDPREPOSTREGVAR(Mobius)
ADDPREPOSTREGVAR(MobiusN)
ADDPREPOSTREGVAR(MobiusStrip)
ADDPREPOSTREGVAR(Lissajous)
ADDPREPOSTREGVAR(Svf)
ADDPREPOSTREGVAR(Target)
ADDPREPOSTREGVAR(Taurus)
ADDPREPOSTREGVAR(Collideoscope)
ADDPREPOSTREGVAR(BMod)
ADDPREPOSTREGVAR(BSwirl)
ADDPREPOSTREGVAR(BTransform)
ADDPREPOSTREGVAR(BCollide)
ADDPREPOSTREGVAR(Eclipse)
ADDPREPOSTREGVAR(FlipCircle)
ADDPREPOSTREGVAR(FlipY)
ADDPREPOSTREGVAR(ECollide)
ADDPREPOSTREGVAR(EJulia)
ADDPREPOSTREGVAR(EMod)
ADDPREPOSTREGVAR(EMotion)
ADDPREPOSTREGVAR(EPush)
ADDPREPOSTREGVAR(ERotate)
ADDPREPOSTREGVAR(EScale)
ADDPREPOSTREGVAR(ESwirl)
ADDPREPOSTREGVAR(LazyTravis)
ADDPREPOSTREGVAR(Squish)
ADDPREPOSTREGVAR(Circus)
ADDPREPOSTREGVAR(Tancos)
ADDPREPOSTREGVAR(Rippled)
ADDPREPOSTREGVAR(RotateX)
ADDPREPOSTREGVAR(RotateY)
ADDPREPOSTREGVAR(RotateZ)
ADDPREPOSTREGVAR(Flatten)
ADDPREPOSTREGVAR(Zblur)
ADDPREPOSTREGVAR(Blur3D)
ADDPREPOSTREGVAR(ZScale)
ADDPREPOSTREGVAR(ZTranslate)
ADDPREPOSTREGVAR(ZCone)
ADDPREPOSTREGVAR(MirrorX)
ADDPREPOSTREGVAR(MirrorY)
ADDPREPOSTREGVAR(MirrorZ)
ADDPREPOSTREGVAR(Depth)
ADDPREPOSTREGVAR(Spherical3D)
ADDPREPOSTREGVAR(RBlur)
ADDPREPOSTREGVAR(JuliaNab)
ADDPREPOSTREGVAR(Sintrange)
ADDPREPOSTREGVAR(Voron)
ADDPREPOSTREGVAR(Waffle)
ADDPREPOSTREGVAR(Square3D)
ADDPREPOSTREGVAR(SuperShape3D)
ADDPREPOSTREGVAR(Sphyp3D)
ADDPREPOSTREGVAR(Circlecrop)
ADDPREPOSTREGVAR(Julian3Dx)
ADDPREPOSTREGVAR(Fourth)
ADDPREPOSTREGVAR(Mobiq)
ADDPREPOSTREGVAR(Spherivoid)
ADDPREPOSTREGVAR(Farblur)
ADDPREPOSTREGVAR(CurlSP)
ADDPREPOSTREGVAR(Heat)
ADDPREPOSTREGVAR(Interference2)
ADDPREPOSTREGVAR(Sinq)
ADDPREPOSTREGVAR(Sinhq)
ADDPREPOSTREGVAR(Secq)
ADDPREPOSTREGVAR(Sechq)
ADDPREPOSTREGVAR(Tanq)
ADDPREPOSTREGVAR(Tanhq)
ADDPREPOSTREGVAR(Cosq)
ADDPREPOSTREGVAR(Coshq)
ADDPREPOSTREGVAR(Cotq)
ADDPREPOSTREGVAR(Cothq)
ADDPREPOSTREGVAR(Cscq)
ADDPREPOSTREGVAR(Cschq)
ADDPREPOSTREGVAR(Estiq)
ADDPREPOSTREGVAR(Loq)
ADDPREPOSTREGVAR(Curvature)
ADDPREPOSTREGVAR(Qode)
ADDPREPOSTREGVAR(BlurHeart)
ADDPREPOSTREGVAR(Truchet)
ADDPREPOSTREGVAR(Gdoffs)
ADDPREPOSTREGVAR(Octagon)
ADDPREPOSTREGVAR(Trade)
ADDPREPOSTREGVAR(Juliac)
ADDPREPOSTREGVAR(Blade3D)
ADDPREPOSTREGVAR(Blob3D)
ADDPREPOSTREGVAR(Blocky)
ADDPREPOSTREGVAR(Bubble2)
ADDPREPOSTREGVAR(CircleLinear)
ADDPREPOSTREGVAR(CircleRand)
ADDPREPOSTREGVAR(CircleTrans1)
ADDPREPOSTREGVAR(Cubic3D)
ADDPREPOSTREGVAR(CubicLattice3D)
ADDPREPOSTREGVAR(Foci3D)
ADDPREPOSTREGVAR(Ho)
ADDPREPOSTREGVAR(Julia3Dq)
ADDPREPOSTREGVAR(Line)
ADDPREPOSTREGVAR(Loonie3D)
ADDPREPOSTREGVAR(Mcarpet)
ADDPREPOSTREGVAR(Waves23D)
ADDPREPOSTREGVAR(Pie3D)
ADDPREPOSTREGVAR(Popcorn23D)
ADDPREPOSTREGVAR(Sinusoidal3D)
ADDPREPOSTREGVAR(Scry3D)
ADDPREPOSTREGVAR(Shredlin)
ADDPREPOSTREGVAR(SplitBrdr)
ADDPREPOSTREGVAR(Wdisc)
ADDPREPOSTREGVAR(Falloff)
ADDPREPOSTREGVAR(Falloff2)
ADDPREPOSTREGVAR(Falloff3)
ADDPREPOSTREGVAR(Xtrb)
//ADDPREPOSTREGVAR(LinearXZ)
//ADDPREPOSTREGVAR(LinearYZ)
//DC are special.
m_Variations.push_back(new DCBubbleVariation<T>());
ADDPREPOSTREGVAR(DCCarpet)
ADDPREPOSTREGVAR(DCCube)
m_Variations.push_back(new DCCylinderVariation<T>());
ADDPREPOSTREGVAR(DCGridOut)
m_Variations.push_back(new DCLinearVariation<T>());
ADDPREPOSTREGVAR(DCTriangle)
ADDPREPOSTREGVAR(DCZTransl)
std::for_each(m_Variations.begin(), m_Variations.end(), [&](Variation<T>* var) { var->Precalc(); });
std::sort(m_Variations.begin(), m_Variations.end(), [&](const Variation<T>* var1, const Variation<T>* var2) { return var1->VariationId() < var2->VariationId(); });
m_RegVariations.reserve(m_Variations.size() / 3);
m_PreVariations.reserve(m_Variations.size() / 3);
m_PostVariations.reserve(m_Variations.size() / 3);
std::for_each(m_Variations.begin(), m_Variations.end(), [&](Variation<T>* var) { if (var->VarType() == VARTYPE_REG) m_RegVariations.push_back(var); });
std::for_each(m_Variations.begin(), m_Variations.end(), [&](Variation<T>* var) { if (var->VarType() == VARTYPE_PRE) m_PreVariations.push_back(var); });
std::for_each(m_Variations.begin(), m_Variations.end(), [&](Variation<T>* var) { if (var->VarType() == VARTYPE_POST) m_PostVariations.push_back(var); });
//Keep a list of which variations derive from ParametricVariation.
//Note that these are not new copies, rather just pointers to the original instances in m_Variations.
for (unsigned int i = 0; i < m_Variations.size(); i++)
{
if (ParametricVariation<T>* parVar = dynamic_cast<ParametricVariation<T>*>(m_Variations[i]))
m_ParametricVariations.push_back(parVar);
}
}
/// <summary>
/// Delete each element of the list.
/// </summary>
~VariationList()
{
ClearVec(m_Variations);//No need to delete parametric because they point to the entries in original vector.
}
/// <summary>
/// Get a pointer to the variation at the specified index.
/// </summary>
/// <param name="index">The index in the list to retrieve</param>
/// <returns>A pointer to the variation at the index if in range, else NULL.</returns>
Variation<T>* GetVariation(size_t index) { return index < m_Variations.size() ? m_Variations[index] : NULL; }
/// <summary>
/// Get a pointer to the variation of a specified type at the specified index.
/// </summary>
/// <param name="index">The index in the list to retrieve</param>
/// <param name="varType">The type of variation to retrieve</param>
/// <returns>A pointer to the variation of the specified type at the index if in range, else NULL.</returns>
Variation<T>* GetVariation(size_t index, eVariationType varType)
{
if (varType == VARTYPE_REG)
return index < m_RegVariations.size() ? m_RegVariations[index] : NULL;
else if (varType == VARTYPE_PRE)
return index < m_PreVariations.size() ? m_PreVariations[index] : NULL;
else if (varType == VARTYPE_POST)
return index < m_PostVariations.size() ? m_PostVariations[index] : NULL;
else
return NULL;
}
/// <summary>
/// Gets a pointer to a copy of the variation at the specified index.
/// Optionally specify a weight to assign the new copy.
/// </summary>
/// <param name="index">The index in the list to make a copy of</param>
/// <param name="weight">The weight to assign the new copy. Default: 1</param>
/// <returns>A pointer to the variation at the index if in range, else NULL.</returns>
Variation<T>* GetVariationCopy(size_t index, T weight = 1) { return MakeCopyWithWeight(GetVariation(index), weight); }
Variation<T>* GetVariationCopy(size_t index, eVariationType varType, T weight = 1) { return MakeCopyWithWeight(GetVariation(index, varType), weight); }
/// <summary>
/// Get a pointer to the variation with the specified ID.
/// </summary>
/// <param name="id">The ID to search for</param>
/// <returns>A pointer to the variation if found, else NULL.</returns>
Variation<T>* GetVariation(eVariationId id)
{
for (unsigned int i = 0; i < m_Variations.size() && m_Variations[i] != NULL; i++)
if (id == m_Variations[i]->VariationId())
return m_Variations[i];
return NULL;
}
/// <summary>
/// Gets a pointer to a copy of the variation with the specified ID.
/// Optionally specify a weight to assign the new copy.
/// </summary>
/// <param name="id">The id of the variation in the list to make a copy of</param>
/// <param name="weight">The weight to assign the new copy. Default: 1</param>
/// <returns>A pointer to the variation with a matching ID, else NULL.</returns>
Variation<T>* GetVariationCopy(eVariationId id, T weight = 1) { return MakeCopyWithWeight(GetVariation(id), weight); }
/// <summary>
/// Get a pointer to the variation with the specified name.
/// </summary>
/// <param name="name">The name to search for</param>
/// <returns>A pointer to the variation if found, else NULL.</returns>
Variation<T>* GetVariation(string name)
{
for (unsigned int i = 0; i < m_Variations.size() && m_Variations[i] != NULL; i++)
if (name == m_Variations[i]->Name())
return m_Variations[i];
return NULL;
}
/// <summary>
/// Gets a pointer to a copy of the variation with the specified name.
/// Optionally specify a weight to assign the new copy.
/// </summary>
/// <param name="name">The name of the variation in the list to make a copy of</param>
/// <param name="weight">The weight to assign the new copy. Default: 1</param>
/// <returns>A pointer to the variation with a matching name, else NULL.</returns>
Variation<T>* GetVariationCopy(string name, T weight = 1) { return MakeCopyWithWeight(GetVariation(name), weight); }
/// <summary>
/// Get a parametric variation at the specified index.
/// Note this is the index in the parametric variations list, not in the master list.
/// </summary>
/// <param name="index">The index in the parametric variations list to retrieve</param>
/// <returns>The parametric variation at the index specified if in range, else NULL.</returns>
ParametricVariation<T>* GetParametricVariation(size_t index) { return index < m_ParametricVariations.size() ? m_ParametricVariations[index] : NULL; }
/// <summary>
/// Get a parametric variation with the specified name.
/// </summary>
/// <param name="name">The name of the variation in the parametric variations list to retrieve</param>
/// <returns>The parametric variation with a matching name, else NULL.</returns>
ParametricVariation<T>* GetParametricVariation(string name)
{
for (unsigned int i = 0; i < m_ParametricVariations.size() && m_ParametricVariations[i] != NULL; i++)
if (name == m_ParametricVariations[i]->Name())
return m_ParametricVariations[i];
return NULL;
}
/// <summary>
/// Get the index of the variation with the specified name.
/// </summary>
/// <param name="name">The name of the variation whose index is returned</param>
/// <returns>The index of the variation with the matching name, else -1</returns>
int GetVariationIndex(string name)
{
for (unsigned int i = 0; i < m_Variations.size() && m_Variations[i] != NULL; i++)
if (name == m_Variations[i]->Name())
return i;
return -1;
}
/// <summary>
/// Accessors.
/// </summary>
size_t Size() { return m_Variations.size(); }
size_t RegSize() { return m_RegVariations.size(); }
size_t PreSize() { return m_PreVariations.size(); }
size_t PostSize() { return m_PostVariations.size(); }
size_t ParametricSize() { return m_ParametricVariations.size(); }
private:
/// <summary>
/// Make a dyncamically allocated copy of a variation and assign it a specified weight.
/// Return a pointer to the new copy.
/// </summary>
/// <param name="var">The variation to copy</param>
/// <param name="weight">The weight to assign it</param>
/// <returns>A pointer to the new variation copy if success, else NULL.</returns>
Variation<T>* MakeCopyWithWeight(Variation<T>* var, T weight)
{
if (var)
{
Variation<T>* var2 = var->Copy();
var2->m_Weight = weight;
return var2;
}
return NULL;
}
/// <summary>
/// Assignment operator which does nothing since these are non-copyable.
/// Do not provide a copy constructor and ensure the assignment operator does nothing.
/// </summary>
/// <param name="varList">The VariationList object which won't be copied</param>
/// <returns>Reference to unchanged self</returns>
VariationList<T>& operator = (const VariationList<T>& varList)
{
return *this;
}
vector<Variation<T>*> m_Variations;//A list of pointers to dynamically allocated variation objects.
vector<Variation<T>*> m_RegVariations;
vector<Variation<T>*> m_PreVariations;
vector<Variation<T>*> m_PostVariations;
vector<ParametricVariation<T>*> m_ParametricVariations;//A list of pointers to elements in m_Variations which are derived from ParametricVariation.
};
}

6296
Source/Ember/Variations01.h Normal file

File diff suppressed because it is too large Load Diff

5756
Source/Ember/Variations02.h Normal file

File diff suppressed because it is too large Load Diff

4596
Source/Ember/Variations03.h Normal file

File diff suppressed because it is too large Load Diff

5233
Source/Ember/Variations04.h Normal file

File diff suppressed because it is too large Load Diff

3253
Source/Ember/Variations05.h Normal file

File diff suppressed because it is too large Load Diff

1039
Source/Ember/VariationsDC.h Normal file

File diff suppressed because it is too large Load Diff

1173
Source/Ember/Xform.h Normal file

File diff suppressed because it is too large Load Diff

1350
Source/Ember/XmlToEmber.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,372 @@
#include "EmberCommonPch.h"
#include "EmberAnimate.h"
#include "JpegUtils.h"
/// <summary>
/// The core of the EmberAnimate.exe program.
/// Template argument expected to be float or double.
/// </summary>
/// <param name="opt">A populated EmberOptions object which specifies all program options to be used</param>
/// <returns>True if success, else false.</returns>
template <typename T, typename bucketT>
bool EmberAnimate(EmberOptions& opt)
{
OpenCLWrapper wrapper;
std::cout.imbue(std::locale(""));
if (opt.DumpArgs())
cout << opt.GetValues(OPT_USE_ANIMATE) << endl;
if (opt.OpenCLInfo())
{
cout << "\nOpenCL Info: " << endl;
cout << wrapper.DumpInfo();
return true;
}
//Regular variables.
Timing t;
bool unsorted = false;
bool writeSuccess = false;
bool startXml = false;
bool finishXml = false;
bool appendXml = false;
unsigned char* finalImagep;
unsigned int i, channels, ftime;
string s, flameName, filename;
ostringstream os;
vector<unsigned char> finalImage, vecRgb;
vector<Ember<T>> embers;
EmberStats stats;
EmberReport emberReport;
EmberImageComments comments;
Ember<T> centerEmber;
XmlToEmber<T> parser;
EmberToXml<T> emberToXml;
auto_ptr<RenderProgress<T>> progress(new RenderProgress<T>());
auto_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();
if (!errorReport.empty())
emberReport.DumpErrorReport();
if (!renderer.get())
{
cout << "Renderer creation failed, exiting." << endl;
return false;
}
if (opt.EmberCL() && renderer->RendererType() != OPENCL_RENDERER)//OpenCL init failed, so fall back to CPU.
opt.EmberCL(false);
if (!InitPaletteList<T>(opt.PalettePath()))
return false;
if (!ParseEmberFile(parser, opt.Input(), embers))
return false;
if (!opt.EmberCL())
{
if (opt.ThreadCount() == 0)
{
cout << "Using " << Timing::ProcessorCount() << " automatically detected threads." << endl;
opt.ThreadCount(Timing::ProcessorCount());
}
else
{
cout << "Using " << opt.ThreadCount() << " manually specified threads." << endl;
}
renderer->ThreadCount(opt.ThreadCount(), opt.IsaacSeed() != "" ? opt.IsaacSeed().c_str() : NULL);
}
else
{
cout << "Using OpenCL to render." << endl;
if (opt.Verbose())
{
cout << "Platform: " << wrapper.PlatformName(opt.Platform()) << endl;
cout << "Device: " << wrapper.DeviceName(opt.Platform(), opt.Device()) << endl;
}
if (opt.ThreadCount() > 1)
cout << "Cannot specify threads with OpenCL, using 1 thread." << endl;
opt.ThreadCount(1);
renderer->ThreadCount(opt.ThreadCount(), opt.IsaacSeed() != "" ? opt.IsaacSeed().c_str() : NULL);
if (opt.BitsPerChannel() != 8)
{
cout << "Bits per channel cannot be anything other than 8 with OpenCL, setting to 8." << endl;
opt.BitsPerChannel(8);
}
}
if (opt.Format() != "jpg" &&
opt.Format() != "png" &&
opt.Format() != "ppm" &&
opt.Format() != "bmp")
{
cout << "Format must be jpg, png, ppm, or bmp not " << opt.Format() << ". Setting to jpg." << endl;
}
channels = opt.Format() == "png" ? 4 : 3;
if (opt.BitsPerChannel() == 16 && opt.Format() != "png")
{
cout << "Support for 16 bits per channel images is only present for the png format. Setting to 8." << endl;
opt.BitsPerChannel(8);
}
else if (opt.BitsPerChannel() != 8 && opt.BitsPerChannel() != 16)
{
cout << "Unexpected bits per channel specified " << opt.BitsPerChannel() << ". Setting to 8." << endl;
opt.BitsPerChannel(8);
}
if (opt.InsertPalette() && opt.BitsPerChannel() != 8)
{
cout << "Inserting palette only supported with 8 bits per channel, insertion will not take place." << endl;
opt.InsertPalette(false);
}
if (opt.AspectRatio() < 0)
{
cout << "Invalid pixel aspect ratio " << opt.AspectRatio() << endl << ". Must be positive, setting to 1." << endl;
opt.AspectRatio(1);
}
if (opt.Dtime() < 1)
{
cout << "Warning: dtime must be positive, not " << opt.Dtime() << ". Setting to 1." << endl;
opt.Dtime(1);
}
if (opt.Frame())
{
if (opt.Time())
{
cout << "Cannot specify both time and frame." << endl;
return false;
}
if (opt.FirstFrame() || opt.LastFrame())
{
cout << "Cannot specify both frame and begin or end." << endl;
return false;
}
opt.FirstFrame(opt.Frame());
opt.LastFrame(opt.Frame());
}
if (opt.Time())
{
if (opt.FirstFrame() || opt.LastFrame())
{
cout << "Cannot specify both time and begin or end." << endl;
return false;
}
opt.FirstFrame(opt.Time());
opt.LastFrame(opt.Time());
}
//Prep all embers, by ensuring they:
//-Are sorted by time.
//-Do not have a dimension of 0.
//-Do not have a memory requirement greater than max uint.
//-Have quality and size scales applied, if present.
//-Have equal dimensions.
for (i = 0; i < embers.size(); i++)
{
if (i > 0 && embers[i].m_Time <= embers[i - 1].m_Time)
unsorted = true;
embers[i].m_Quality *= T(opt.QualityScale());
embers[i].m_FinalRasW = (unsigned int)((T)embers[i].m_FinalRasW * opt.SizeScale());
embers[i].m_FinalRasH = (unsigned int)((T)embers[i].m_FinalRasH * opt.SizeScale());
embers[i].m_PixelsPerUnit *= T(opt.SizeScale());
//Cast to double in case the value exceeds 2^32.
double imageMem = (double)channels * (double)embers[i].m_FinalRasW
* (double)embers[i].m_FinalRasH * (double)renderer->BytesPerChannel();
double maxMem = pow(2.0, double((sizeof(void*) * 8) - 1));
if (imageMem > maxMem)//Ensure the max amount of memory for a process isn't exceeded.
{
cout << "Image " << i << " size > " << maxMem << ". Setting to 1920 x 1080." << endl;
embers[i].m_FinalRasW = 1920;
embers[i].m_FinalRasH = 1080;
}
if (embers[i].m_FinalRasW == 0 || embers[i].m_FinalRasH == 0)
{
cout << "Warning: Output image " << i << " has dimension 0: " << embers[i].m_FinalRasW << ", " << embers[i].m_FinalRasH << ". Setting to 1920 x 1080." << endl;
embers[i].m_FinalRasW = 1920;
embers[i].m_FinalRasH = 1080;
}
if ((embers[i].m_FinalRasW != embers[0].m_FinalRasW) ||
(embers[i].m_FinalRasH != embers[0].m_FinalRasH))
{
cout << "Warning: flame " << i << " at time " << embers[i].m_Time << " size mismatch. (" << embers[i].m_FinalRasW << ", " << embers[i].m_FinalRasH <<
") should be (" << embers[0].m_FinalRasW << ", " << embers[0].m_FinalRasH << "). Setting to " << embers[0].m_FinalRasW << ", " << embers[0].m_FinalRasH << "." << endl;
embers[i].m_FinalRasW = embers[0].m_FinalRasW;
embers[i].m_FinalRasH = embers[0].m_FinalRasH;
}
}
if (unsorted)
{
cout << "Embers were unsorted by time. First out of order index was " << i << ". Sorting." << endl;
std::sort(embers.begin(), embers.end(), &CompareEmbers<T>);
}
if (!opt.Time() && !opt.Frame())
{
if (opt.FirstFrame() == UINT_MAX)
opt.FirstFrame((int)embers[0].m_Time);
if (opt.LastFrame() == UINT_MAX)
opt.LastFrame(ClampGte<unsigned int>((unsigned int)embers.back().m_Time - 1, opt.FirstFrame()));
}
if (!opt.Out().empty())
{
appendXml = true;
filename = opt.Out();
cout << "Single output file " << opt.Out() << " specified for multiple images. They will be all overwritten and only the last image will remain." << endl;
}
//Final setup steps before running.
os.imbue(std::locale(""));
renderer->SetEmber(embers);
renderer->EarlyClip(opt.EarlyClip());
renderer->LockAccum(opt.LockAccum());
renderer->InsertPalette(opt.InsertPalette());
renderer->SubBatchSize(opt.SubBatchSize());
renderer->PixelAspectRatio(T(opt.AspectRatio()));
renderer->Transparency(opt.Transparency());
renderer->NumChannels(channels);
renderer->BytesPerChannel(opt.BitsPerChannel() / 8);
renderer->Callback(opt.DoProgress() ? progress.get() : NULL);
//Begin run.
for (ftime = opt.FirstFrame(); ftime <= opt.LastFrame(); ftime += opt.Dtime())
{
T localTime = T(ftime);
if ((opt.LastFrame() - opt.FirstFrame()) / opt.Dtime() >= 1)
VerbosePrint("Time = " << ftime << " / " << opt.LastFrame() << " / " << opt.Dtime());
renderer->Reset();
if ((renderer->Run(finalImage, localTime) != RENDER_OK) || renderer->Aborted() || finalImage.empty())
{
cout << "Error: image rendering failed, skipping to next image." << endl;
renderer->DumpErrorReport();//Something went wrong, print errors.
continue;
}
if (opt.Out().empty())
{
os.str("");
os << opt.Prefix() << setfill('0') << setw(5) << ftime << opt.Suffix() << "." << opt.Format();
filename = os.str();
}
if (opt.WriteGenome())
{
flameName = filename.substr(0, filename.find_last_of('.')) + ".flam3";
VerbosePrint("Writing " + flameName);
Interpolater<T>::Interpolate(embers, localTime, 0, centerEmber);//Get center flame.
if (appendXml)
{
startXml = ftime == opt.FirstFrame();
finishXml = ftime == opt.LastFrame();
}
emberToXml.Save(flameName, centerEmber, opt.PrintEditDepth(), true, opt.IntPalette(), opt.HexPalette(), true, startXml, finishXml);
}
writeSuccess = false;
comments = renderer->ImageComments(opt.PrintEditDepth(), opt.IntPalette(), opt.HexPalette());
stats = renderer->Stats();
os.str("");
os << comments.m_NumIters << "/" << renderer->TotalIterCount() << " (" << std::fixed << std::setprecision(2) << ((double)stats.m_Iters/(double)renderer->TotalIterCount() * 100) << "%)";
VerbosePrint("\nIters ran/requested: " + os.str());
VerbosePrint("Bad values: " << stats.m_Badvals);
VerbosePrint("Render time: " + t.Format(stats.m_RenderSeconds * 1000));
VerbosePrint("Writing " + filename);
if ((opt.Format() == "jpg" || opt.Format() == "bmp") && renderer->NumChannels() == 4)
{
EmberNs::RgbaToRgb(finalImage, vecRgb, renderer->FinalRasW(), renderer->FinalRasH());
finalImagep = vecRgb.data();
}
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;
centerEmber.Clear();
}
VerbosePrint("Done.\n");
return true;
}
/// <summary>
/// Main program entry point for EmberAnimate.exe.
/// </summary>
/// <param name="argc">The number of command line arguments passed</param>
/// <param name="argv">The command line arguments passed</param>
/// <returns>0 if successful, else 1.</returns>
int _tmain(int argc, _TCHAR* argv[])
{
bool b, d = true;
EmberOptions opt;
//Required for large allocs, else GPU memory usage will be severely limited to small sizes.
//This must be done in the application and not in the EmberCL DLL.
_putenv_s("GPU_MAX_ALLOC_PERCENT", "100");
if (opt.Populate(argc, argv, OPT_USE_ANIMATE))
return 0;
#ifdef DO_DOUBLE
if (opt.Bits() == 64)
{
b = EmberAnimate<double, double>(opt);
}
else
#endif
if (opt.Bits() == 33)
{
b = EmberAnimate<float, float>(opt);
}
else if (opt.Bits() == 32)
{
cout << "Bits 32/int histogram no longer supported. Using bits == 33 (float)." << endl;
b = EmberAnimate<float, float>(opt);
}
return b ? 0 : 1;
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "EmberOptions.h"
/// <summary>
/// Declaration for the EmberAnimate() function.
/// </summary>
/// <summary>
/// The core of the EmberAnimate.exe program.
/// Template argument expected to be float or double.
/// </summary>
/// <param name="opt">A populated EmberOptions object which specifies all program options to be used</param>
/// <returns>True if success, else false.</returns>
template <typename T, typename bucketT>
static bool EmberAnimate(EmberOptions& opt);

View File

@ -0,0 +1,98 @@
// Microsoft Visual C++ generated resource script.
//
#include <windows.h>
#include "resource.h"
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1 ICON "..\\Fractorium\\Icons\\\\Fractorium.ico"
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,4,0,2
PRODUCTVERSION 0,4,0,2
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x0L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Open Source"
VALUE "FileDescription", "Renders fractal flames as animations with motion blur"
VALUE "FileVersion", "0.4.0.2"
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.0.2"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@ -0,0 +1,15 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by EmberAnimate.rc
//
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 101
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View File

@ -0,0 +1,698 @@
#include "EmberCLPch.h"
#include "DEOpenCLKernelCreator.h"
namespace EmberCLns
{
/// <summary>
/// Empty constructor that does nothing. The user must call the one which takes a bool
/// argument before using this class.
/// This constructor only exists so the class can be a member of a class.
/// </summary>
template <typename T>
DEOpenCLKernelCreator<T>::DEOpenCLKernelCreator()
{
}
/// <summary>
/// Constructor for float template type that sets all kernel entry points as well as composes
/// all kernel source strings.
/// No program compilation is done here, the user must explicitly do it.
/// The caller must specify whether they are using an nVidia or AMD card because it changes
/// the amount of local memory available.
/// </summary>
/// <param name="nVidia">True if running on an nVidia card, else false.</param>
template <>
DEOpenCLKernelCreator<float>::DEOpenCLKernelCreator(bool nVidia)
{
m_NVidia = nVidia;
m_LogScaleSumDEEntryPoint = "LogScaleSumDensityFilterKernel";
m_LogScaleAssignDEEntryPoint = "LogScaleAssignDensityFilterKernel";
m_GaussianDEWithoutSsEntryPoint = "GaussianDEWithoutSsKernel";
m_GaussianDESsWithScfEntryPoint = "GaussianDESsWithScfKernel";
m_GaussianDESsWithoutScfEntryPoint = "GaussianDESsWithoutScfKernel";
m_GaussianDEWithoutSsNoCacheEntryPoint = "GaussianDEWithoutSsNoCacheKernel";
m_GaussianDESsWithScfNoCacheEntryPoint = "GaussianDESsWithScfNoCacheKernel";
m_GaussianDESsWithoutScfNoCacheEntryPoint = "GaussianDESsWithoutScfNoCacheKernel";
m_LogScaleSumDEKernel = CreateLogScaleSumDEKernelString();
m_LogScaleAssignDEKernel = CreateLogScaleAssignDEKernelString();
m_GaussianDEWithoutSsKernel = CreateGaussianDEKernel(1);
m_GaussianDESsWithScfKernel = CreateGaussianDEKernel(2);
m_GaussianDESsWithoutScfKernel = CreateGaussianDEKernel(3);
m_GaussianDEWithoutSsNoCacheKernel = CreateGaussianDEKernelNoLocalCache(1);
m_GaussianDESsWithScfNoCacheKernel = CreateGaussianDEKernelNoLocalCache(2);
m_GaussianDESsWithoutScfNoCacheKernel = CreateGaussianDEKernelNoLocalCache(3);
}
/// <summary>
/// Constructor for double template type that sets all kernel entry points as well as composes
/// all kernel source strings.
/// Note that no versions of kernels that use the cache are compiled because
/// the cache is not big enough to hold double4.
/// No program compilation is done here, the user must explicitly do it.
/// Specifying true or false for the bool parameter has no effect since no local memory
/// is used when instantiated with type double.
/// </summary>
/// <param name="nVidia">True if running on an nVidia card, else false. Ignored.</param>
template <>
DEOpenCLKernelCreator<double>::DEOpenCLKernelCreator(bool nVidia)
{
m_NVidia = nVidia;
m_LogScaleSumDEEntryPoint = "LogScaleSumDensityFilterKernel";
m_LogScaleAssignDEEntryPoint = "LogScaleAssignDensityFilterKernel";
m_GaussianDEWithoutSsNoCacheEntryPoint = "GaussianDEWithoutSsNoCacheKernel";
m_GaussianDESsWithScfNoCacheEntryPoint = "GaussianDESsWithScfNoCacheKernel";
m_GaussianDESsWithoutScfNoCacheEntryPoint = "GaussianDESsWithoutScfNoCacheKernel";
m_LogScaleSumDEKernel = CreateLogScaleSumDEKernelString();
m_LogScaleAssignDEKernel = CreateLogScaleAssignDEKernelString();
m_GaussianDEWithoutSsNoCacheKernel = CreateGaussianDEKernelNoLocalCache(1);
m_GaussianDESsWithScfNoCacheKernel = CreateGaussianDEKernelNoLocalCache(2);
m_GaussianDESsWithoutScfNoCacheKernel = CreateGaussianDEKernelNoLocalCache(3);
}
/// <summary>
/// Kernel source and entry point properties, getters only.
/// </summary>
template <typename T> string DEOpenCLKernelCreator<T>::LogScaleSumDEKernel() { return m_LogScaleSumDEKernel; }
template <typename T> string DEOpenCLKernelCreator<T>::LogScaleSumDEEntryPoint() { return m_LogScaleSumDEEntryPoint; }
template <typename T> string DEOpenCLKernelCreator<T>::LogScaleAssignDEKernel() { return m_LogScaleAssignDEKernel; }
template <typename T> string DEOpenCLKernelCreator<T>::LogScaleAssignDEEntryPoint() { return m_LogScaleAssignDEEntryPoint; }
/// <summary>
/// Get the kernel source for the specified supersample and filterWidth.
/// </summary>
/// <param name="ss">The supersample being used</param>
/// <param name="filterWidth">Filter width</param>
/// <returns>The kernel source</returns>
template <typename T>
string DEOpenCLKernelCreator<T>::GaussianDEKernel(unsigned int ss, unsigned int filterWidth)
{
if ((typeid(T) == typeid(double)) || (filterWidth > MaxDEFilterSize()))//Type double does not use cache.
{
if (ss > 1)
{
if (!(ss & 1))
return m_GaussianDESsWithScfNoCacheKernel;
else
return m_GaussianDESsWithoutScfNoCacheKernel;
}
else
return m_GaussianDEWithoutSsNoCacheKernel;
}
else
{
if (ss > 1)
{
if (!(ss & 1))
return m_GaussianDESsWithScfKernel;
else
return m_GaussianDESsWithoutScfKernel;
}
else
return m_GaussianDEWithoutSsKernel;
}
}
/// <summary>
/// Get the kernel entry point for the specified supersample and filterWidth.
/// </summary>
/// <param name="ss">The supersample being used</param>
/// <param name="filterWidth">Filter width</param>
/// <returns>The name of the density estimation filtering entry point kernel function</returns>
template <typename T>
string DEOpenCLKernelCreator<T>::GaussianDEEntryPoint(unsigned int ss, unsigned int filterWidth)
{
if ((typeid(T) == typeid(double)) || (filterWidth > MaxDEFilterSize()))//Type double does not use cache.
{
if (ss > 1)
{
if (!(ss & 1))
return m_GaussianDESsWithScfNoCacheEntryPoint;
else
return m_GaussianDESsWithoutScfNoCacheEntryPoint;
}
else
return m_GaussianDEWithoutSsNoCacheEntryPoint;
}
else
{
if (ss > 1)
{
if (!(ss & 1))
return m_GaussianDESsWithScfEntryPoint;
else
return m_GaussianDESsWithoutScfEntryPoint;
}
else
return m_GaussianDEWithoutSsEntryPoint;
}
}
/// <summary>
/// Get the maximum filter size allowed for running the local memory version of density filtering
/// Filters larger than this value will run the version without local memory caching.
/// </summary>
/// <returns>The maximum filter size allowed for running the local memory version of density filtering</returns>
template <typename T>
unsigned int DEOpenCLKernelCreator<T>::MaxDEFilterSize() { return 9; }//The true max would be (maxBoxSize - 1) / 2, but that's impractical because it can give us a tiny block size.
/// <summary>
/// Solve for the maximum filter radius.
/// The final filter width is calculated by: (unsigned int)(ceil(m_MaxRad) * (T)m_Supersample) + (m_Supersample - 1);
/// Must solve for what max rad should be in order to give a maximum final width of (maxBoxSize - 1) / 2, assuming
/// a minimum block size of 1 which processes 1 pixel.
/// Example: If a box size of 20 was allowed, a filter
/// size of up to 9: (20 - 1) / 2 == (19 / 2) == 9 could be supported.
/// This function is deprecated, the appropriate kernels take care of this problem now.
/// </summary>
/// <param name="maxBoxSize">Maximum size of the box.</param>
/// <param name="desiredFilterSize">Size of the desired filter.</param>
/// <param name="ss">The supersample being used</param>
/// <returns>The maximum filter radius allowed</returns>
template <typename T>
T DEOpenCLKernelCreator<T>::SolveMaxDERad(unsigned int maxBoxSize, T desiredFilterSize, T ss)
{
unsigned int finalFilterSize = (unsigned int)((ceil(desiredFilterSize) * ss) + (ss - 1.0));
//Return the desired size if the final size of it will fit.
if (finalFilterSize <= MaxDEFilterSize())
return desiredFilterSize;
//The final size doesn't fit, so scale the original down until it fits.
return (T)floor((MaxDEFilterSize() - (ss - 1.0)) / ss);
}
/// <summary>
/// Determine the maximum filter box size based on the amount of local memory available
/// to each block.
/// </summary>
/// <param name="localMem">The local memory available to a block</param>
/// <returns>The maximum filter box size allowed</returns>
template <typename T>
unsigned int DEOpenCLKernelCreator<T>::SolveMaxBoxSize(unsigned int localMem)
{
return (unsigned int)floor(sqrt(floor((T)localMem / 16.0)));//Divide by 16 because each element is float4.
}
/// <summary>
/// Create the log scale kernel string, using summation.
/// This means each cell will be added to, rather than just assigned.
/// Since adding is slower than assigning, this should only be used when Passes > 1,
/// otherwise use the kernel created from CreateLogScaleAssignDEKernelString().
/// </summary>
/// <returns>The kernel string</returns>
template <typename T>
string DEOpenCLKernelCreator<T>::CreateLogScaleSumDEKernelString()
{
ostringstream os;
os <<
ConstantDefinesString(typeid(T) == typeid(double)) <<
DensityFilterCLStructString <<
"__kernel void " << m_LogScaleSumDEEntryPoint << "(\n"
" const __global real4* histogram,\n"
" __global real4* accumulator,\n"
" __constant DensityFilterCL* logFilter\n"
"\t)\n"
"{\n"
" if ((GLOBAL_ID_X < logFilter->m_SuperRasW) && (GLOBAL_ID_Y < logFilter->m_SuperRasH))\n"
" {\n"
" uint index = (GLOBAL_ID_Y * logFilter->m_SuperRasW) + GLOBAL_ID_X;\n"
"\n"
" if (histogram[index].w != 0)\n"
" {\n"
" real_t logScale = (logFilter->m_K1 * log(1.0 + histogram[index].w * logFilter->m_K2)) / histogram[index].w;\n"
"\n"
" accumulator[index] += histogram[index] * logScale;\n"//Using a single real4 vector operation doubles the speed from doing each component individually.
" }\n"
"\n"
" barrier(CLK_GLOBAL_MEM_FENCE);\n"//Just to be safe. Makes no speed difference to do all of the time or only when there's a hit.
" }\n"
"}\n";
return os.str();
}
/// <summary>
/// Create the log scale kernel string, using assignment.
/// Use this when Passes == 1.
/// </summary>
/// <returns>The kernel string</returns>
template <typename T>
string DEOpenCLKernelCreator<T>::CreateLogScaleAssignDEKernelString()
{
ostringstream os;
os <<
ConstantDefinesString(typeid(T) == typeid(double)) <<
DensityFilterCLStructString <<
"__kernel void " << m_LogScaleAssignDEEntryPoint << "(\n"
" const __global real4* histogram,\n"
" __global real4* accumulator,\n"
" __constant DensityFilterCL* logFilter\n"
"\t)\n"
"{\n"
" if ((GLOBAL_ID_X < logFilter->m_SuperRasW) && (GLOBAL_ID_Y < logFilter->m_SuperRasH))\n"
" {\n"
" uint index = (GLOBAL_ID_Y * logFilter->m_SuperRasW) + GLOBAL_ID_X;\n"
"\n"
" if (histogram[index].w != 0)\n"
" {\n"
" real_t logScale = (logFilter->m_K1 * log(1.0 + histogram[index].w * logFilter->m_K2)) / histogram[index].w;\n"
"\n"
" accumulator[index] = histogram[index] * logScale;\n"//Using a single real4 vector operation doubles the speed from doing each component individually.
" }\n"
"\n"
" barrier(CLK_GLOBAL_MEM_FENCE);\n"//Just to be safe. Makes no speed difference to do all of the time or only when there's a hit.
" }\n"
"}\n";
return os.str();
}
/// <summary>
/// Create the gaussian density filtering kernel string.
/// 6 different methods of processing were tried before settling on this final and fastest 7th one.
/// Each block processes a box and exits. No column or row advancements happen.
/// The block accumulates to a temporary box and writes the contents to the global density filter buffer when done.
/// Note this applies the filter from top to bottom row and not from the center outward like the CPU version does.
/// This allows the image to be filtered without suffering from pixel loss due to race conditions.
/// It is run in multiple passes that are spaced far enough apart on the image so as to not overlap.
/// This allows writing to the global buffer without ever overlapping or using atomics.
/// The supersample parameter will produce three different kernels.
/// SS = 1, SS > 1 && SS even, SS > 1 && SS odd.
/// The width of the kernl this runs in must be evenly divisible by 16 or else artifacts will occur.
/// Note that because this function uses so many variables and is so complex, OpenCL can easily run
/// out of resources in some cases. Certain variables had to be reused to condense the kernel footprint
/// down enough to be able to run a block size of 32x32.
/// For double precision, or for SS > 1, a size of 32x30 is used.
/// Box width = (BLOCK_SIZE_X + (fw * 2)).
/// Box height = (BLOCK_SIZE_Y + (fw * 2)).
/// </summary>
/// <param name="ss">The supersample being used</param>
/// <returns>The kernel string</returns>
template <typename T>
string DEOpenCLKernelCreator<T>::CreateGaussianDEKernel(unsigned int ss)
{
bool doSS = ss > 1;
bool doScf = !(ss & 1);
ostringstream os;
os <<
ConstantDefinesString(typeid(T) == typeid(double)) <<
DensityFilterCLStructString <<
UnionCLStructString <<
"__kernel void " << GaussianDEEntryPoint(ss, MaxDEFilterSize()) << "(\n" <<
" const __global real4* histogram,\n"
" __global real4reals* accumulator,\n"
" __constant DensityFilterCL* densityFilter,\n"
" const __global real_t* filterCoefs,\n"
" const __global real_t* filterWidths,\n"
" const __global uint* coefIndices,\n"
" const uint chunkSizeW,\n"
" const uint chunkSizeH,\n"
" const uint rowParity,\n"
" const uint colParity\n"
"\t)\n"
"{\n"
//Parity determines if this function should execute.
" if ((GLOBAL_ID_X >= densityFilter->m_SuperRasW) ||\n"
" (GLOBAL_ID_Y >= densityFilter->m_SuperRasH) ||\n"
" ((BLOCK_ID_X % chunkSizeW) != colParity) ||\n"
" ((BLOCK_ID_Y % chunkSizeH) != rowParity)) \n"
" return;\n"
"\n";
if (doSS)
{
os <<
" uint ss = (uint)floor((real_t)densityFilter->m_Supersample / 2.0);\n"
" int densityBoxLeftX;\n"
" int densityBoxRightX;\n"
" int densityBoxTopY;\n"
" int densityBoxBottomY;\n"
"\n";
if (doScf)
os <<
" real_t scfact = pow(densityFilter->m_Supersample / (densityFilter->m_Supersample + 1.0), 2.0);\n";
}
//Compute the size of the temporary box which is the block width + 2 * filter width x block height + 2 * filter width.
//Ideally the block width and height are both 32. However, the height might be smaller if there isn't enough memory.
os <<
" uint fullTempBoxWidth, fullTempBoxHeight;\n"
" uint leftBound, rightBound, topBound, botBound;\n"
" uint blockHistStartRow, blockHistEndRow, boxReadStartRow, boxReadEndRow;\n"
" uint blockHistStartCol, boxReadStartCol, boxReadEndCol;\n"
" uint accumWriteStartRow, accumWriteStartCol, colsToWrite;\n"
//If any of the variables above end up being made __local, init them here.
//At the moment, it's slower even though it's more memory efficient.
//" if (THREAD_ID_X == 0 && THREAD_ID_Y == 0)\n"
//" {\n"
//Init local vars here.
//" }\n"
//"\n"
//" barrier(CLK_LOCAL_MEM_FENCE);\n"
"\n"
" fullTempBoxWidth = BLOCK_SIZE_X + (densityFilter->m_FilterWidth * 2);\n"
" fullTempBoxHeight = BLOCK_SIZE_Y + (densityFilter->m_FilterWidth * 2);\n"
//Compute the bounds of the area to be sampled, which is just the ends minus the super sample minus 1.
" leftBound = densityFilter->m_Supersample - 1;\n"
" rightBound = densityFilter->m_SuperRasW - (densityFilter->m_Supersample - 1);\n"
" topBound = densityFilter->m_Supersample - 1;\n"
" botBound = densityFilter->m_SuperRasH - (densityFilter->m_Supersample - 1);\n"
"\n"
//Start and end values are the indices in the histogram read from
//and written to in the accumulator. They are not the indices for the local block of data.
//Before computing local offsets, compute the global offsets first to determine if any rows or cols fall outside of the bounds.
" blockHistStartRow = min(botBound, topBound + (BLOCK_ID_Y * BLOCK_SIZE_Y));\n"//The first histogram row this block will process.
" blockHistEndRow = min(botBound, blockHistStartRow + BLOCK_SIZE_Y);\n"//The last histogram row this block will process, clamped to the last row.
" boxReadStartRow = densityFilter->m_FilterWidth - min(densityFilter->m_FilterWidth, blockHistStartRow);\n"//The first row in the local box to read from when writing back to the final accumulator for this block.
" boxReadEndRow = densityFilter->m_FilterWidth + min(densityFilter->m_FilterWidth + BLOCK_SIZE_Y, densityFilter->m_SuperRasH - blockHistStartRow);\n"//The last row in the local box to read from when writing back to the final accumulator for this block.
" blockHistStartCol = min(rightBound, leftBound + (BLOCK_ID_X * BLOCK_SIZE_X));\n"//The first histogram column this block will process.
" boxReadStartCol = densityFilter->m_FilterWidth - min(densityFilter->m_FilterWidth, blockHistStartCol);\n"//The first box row this block will read from when copying to the accumulator.
" boxReadEndCol = densityFilter->m_FilterWidth + min(densityFilter->m_FilterWidth + BLOCK_SIZE_X, densityFilter->m_SuperRasW - blockHistStartCol);\n"//The last box row this block will read from when copying to the accumulator.
"\n"
//Last, the indices in the global accumulator that the local bounds will be writing to.
" accumWriteStartRow = blockHistStartRow - min(densityFilter->m_FilterWidth, blockHistStartRow);\n"//Will be fw - 0 except for boundary columns, it will be less.
" accumWriteStartCol = blockHistStartCol - min(densityFilter->m_FilterWidth, blockHistStartCol);\n"
" colsToWrite = ceil((real_t)(boxReadEndCol - boxReadStartCol) / (real_t)BLOCK_SIZE_X);\n"
"\n"
" uint threadHistRow = blockHistStartRow + THREAD_ID_Y;\n"//The histogram row this individual thread will be reading from.
" uint threadHistCol = blockHistStartCol + THREAD_ID_X;\n"//The histogram column this individual thread will be reading from.
"\n"
//Compute the center position in this local box to serve as the center position
//from which filter application offsets are computed.
//These are the local indices for the local data that are temporarily accumulated to before
//writing out to the global accumulator.
" uint boxRow = densityFilter->m_FilterWidth + THREAD_ID_Y;\n"
" uint boxCol = densityFilter->m_FilterWidth + THREAD_ID_X;\n"
" uint colElementsToZero = ceil((real_t)fullTempBoxWidth / (real_t)(BLOCK_SIZE_X));\n"//Usually is 2.
" int i, j, k;\n"
" uint filterSelectInt, filterCoefIndex;\n"
" real_t cacheLog;\n"
" real_t filterSelect;\n"
" real4 bucket;\n"
;
//This will be treated as having dimensions of (BLOCK_SIZE_X + (fw * 2)) x (BLOCK_SIZE_Y + (fw * 2)).
if (m_NVidia)
os << " __local real4reals filterBox[3000];\n";
else
os << " __local real4reals filterBox[1200];\n";
os <<
//Zero the temp buffers first. This splits the zeroization evenly across all threads (columns) in the first block row.
//This is a middle ground solution. Previous methods tried:
//Thread (0, 0) does all init. This works, but is the slowest.
//Init is divided among all threads. This is the fastest but exposes a severe flaw in OpenCL,
//in that it will not get executed by all threads before proceeding, despite the barrier statement
//below. As a result, strange artifacts will get left around because filtering gets executed on a temp
//box that has not been properly zeroized.
//The only way to do it and still achieve reasonable speed is to have the first row do it. This is
//most likely because the first row gets executed first, ensuring zeroization is done when the rest
//of the threads execute.
"\n"//Dummy test zeroization for debugging.
//" if (THREAD_ID_Y == 0 && THREAD_ID_X == 0)\n"//First thread of the block takes the responsibility of zeroizing.
//" {\n"
//" for (k = 0; k < 2 * 1024; k++)\n"
//" {\n"
//" filterBox[k].m_Real4 = 0;\n"
//" }\n"
//" }\n"
" if (THREAD_ID_Y == 0)\n"//First row of the block takes the responsibility of zeroizing.
" {\n"
" for (i = 0; i < fullTempBoxHeight; i++)\n"//Each column in the row iterates through all rows.
" {\n"
" for (j = 0; j < colElementsToZero && ((colElementsToZero * THREAD_ID_X) + j) < fullTempBoxWidth; j++)\n"//And zeroizes a few columns from that row.
" {\n"
" filterBox[(i * fullTempBoxWidth) + ((colElementsToZero * THREAD_ID_X) + j)].m_Real4 = 0;\n"
" }\n"
" }\n"
" }\n"
"\n"
" barrier(CLK_LOCAL_MEM_FENCE);\n"
"\n"
" if (threadHistRow < botBound && threadHistCol < rightBound)\n"
" {\n"
" bucket = histogram[(threadHistRow * densityFilter->m_SuperRasW) + threadHistCol];\n"
"\n"
" if (bucket.w != 0)\n"
" {\n"
" cacheLog = (densityFilter->m_K1 * log(1.0 + bucket.w * densityFilter->m_K2)) / bucket.w;\n";
if (doSS)
{
os <<
" filterSelect = 0;\n"
" densityBoxLeftX = threadHistCol - min(threadHistCol, ss);\n"
" densityBoxRightX = threadHistCol + min(ss, (densityFilter->m_SuperRasW - threadHistCol) - 1);\n"
" densityBoxTopY = threadHistRow - min(threadHistRow, ss);\n"
" densityBoxBottomY = threadHistRow + min(ss, (densityFilter->m_SuperRasH - threadHistRow) - 1);\n"
"\n"
" for (j = densityBoxTopY; j <= densityBoxBottomY; j++)\n"
" {\n"
" for (i = densityBoxLeftX; i <= densityBoxRightX; i++)\n"
" {\n"
" filterSelect += histogram[i + (j * densityFilter->m_SuperRasW)].w;\n"
" }\n"
" }\n"
"\n";
if (doScf)
os << " filterSelect *= scfact;\n";
}
else
{
os
<< " filterSelect = bucket.w;\n";
}
os <<
"\n"
" if (filterSelect > densityFilter->m_MaxFilteredCounts)\n"
" filterSelectInt = densityFilter->m_MaxFilterIndex;\n"
" else if (filterSelect <= DE_THRESH)\n"
" filterSelectInt = (int)ceil(filterSelect) - 1;\n"
" else\n"
" filterSelectInt = (int)DE_THRESH + (int)floor(pow((real_t)(filterSelect - DE_THRESH), densityFilter->m_Curve));\n"
"\n"
" if (filterSelectInt > densityFilter->m_MaxFilterIndex)\n"
" filterSelectInt = densityFilter->m_MaxFilterIndex;\n"
"\n"
" filterCoefIndex = filterSelectInt * densityFilter->m_KernelSize;\n"
"\n"
//With this new method, only accumulate to the temp local buffer first. Write to the final accumulator last.
//For each loop through, note that there is a local memory barrier call inside of each call to AddToAccumNoCheck().
//If this isn't done, pixel errors occurr and even an out of resources error occurrs because too many writes are done to the same place in memory at once.
" k = (int)densityFilter->m_FilterWidth;\n"//Need a signed int to use below, really is filter width, but reusing a variable to save space.
"\n"
" for (j = -k; j <= k; j++)\n"
" {\n"
" for (i = -k; i <= k; i++)\n"
" {\n"
" filterSelectInt = filterCoefIndex + coefIndices[(abs(j) * (densityFilter->m_FilterWidth + 1)) + abs(i)];\n"//Really is filterCoeffIndexPlusOffset, but reusing a variable to save space.
"\n"
" if (filterCoefs[filterSelectInt] != 0)\n"
" {\n"
" filterBox[(i + boxCol) + ((j + boxRow) * fullTempBoxWidth)].m_Real4 += (bucket * (filterCoefs[filterSelectInt] * cacheLog));\n"
" }\n"
" }\n"
" barrier(CLK_LOCAL_MEM_FENCE);\n"//If this is the only barrier and the block size is exactly 16, it works perfectly. Otherwise, no chunks occur, but a many streaks.
" }\n"
" }\n"//bucket.w != 0.
" }\n"//In bounds.
"\n"
"\n"
" barrier(CLK_LOCAL_MEM_FENCE | CLK_GLOBAL_MEM_FENCE);\n"
"\n"
" if (THREAD_ID_Y == 0)\n"
" {\n"
//At this point, all threads in this block have applied the filter to their surrounding pixel and stored the results in the temp local box.
//Add the cells of it that are in bounds to the global accumulator.
//Compute offsets in local box to read from, and offsets into global accumulator to write to.
//Use a method here that is similar to the zeroization above: Each thread (column) in the first row iterates through all of the
//rows and adds a few columns to the accumulator.
" for (i = boxReadStartRow, j = accumWriteStartRow; i < boxReadEndRow; i++, j++)\n"
" {\n"
" for (k = 0; k < colsToWrite; k++)\n"//Write a few columns.
" {\n"
" boxCol = (colsToWrite * THREAD_ID_X) + k;\n"//Really is colOffset, but reusing a variable to save space.
"\n"
" if (boxReadStartCol + boxCol < boxReadEndCol)\n"
" accumulator[(j * densityFilter->m_SuperRasW) + (accumWriteStartCol + boxCol)].m_Real4 += filterBox[(i * fullTempBoxWidth) + (boxReadStartCol + boxCol)].m_Real4;\n"
" }\n"
" barrier(CLK_GLOBAL_MEM_FENCE);\n"//This must be here or else chunks will go missing.
" }\n"
" }\n"
"}\n";
return os.str();
}
/// <summary>
/// Create the gaussian density filtering kernel string, but use no local cache and perform
/// all writes directly to the global density filtering buffer.
/// Note this applies the filter from top to bottom row and not from the center outward like the CPU version does.
/// This allows the image to be filtered without suffering from pixel loss due to race conditions.
/// This is used for when the filter box is greater than can fit in the local cache.
/// While the cached version is incredibly fast, this version offers no real gain over doing it
/// on the CPU because the frequent global memory access brings performance to a crawl.
/// The supersample parameter will produce three different kernels.
/// SS = 1, SS > 1 && SS even, SS > 1 && SS odd.
/// The width of the kernl this runs in must be evenly divisible by 16 or else artifacts will occur.
/// Note that because this function uses so many variables and is so complex, OpenCL can easily run
/// out of resources in some cases. Certain variables had to be reused to condense the kernel footprint
/// down enough to be able to run a block size of 32x32.
/// For double precision, or for SS > 1, a size of 32x30 is used.
/// </summary>
/// <param name="ss">The supersample being used</param>
/// <returns>The kernel string</returns>
template <typename T>
string DEOpenCLKernelCreator<T>::CreateGaussianDEKernelNoLocalCache(unsigned int ss)
{
bool doSS = ss > 1;
bool doScf = !(ss & 1);
ostringstream os;
os <<
ConstantDefinesString(typeid(T) == typeid(double)) <<
DensityFilterCLStructString <<
UnionCLStructString <<
AddToAccumWithCheckFunctionString <<
"__kernel void " << GaussianDEEntryPoint(ss, MaxDEFilterSize() + 1) << "(\n" <<
" const __global real4* histogram,\n"
" __global real4reals* accumulator,\n"
" __constant DensityFilterCL* densityFilter,\n"
" const __global real_t* filterCoefs,\n"
" const __global real_t* filterWidths,\n"
" const __global uint* coefIndices,\n"
" const uint chunkSizeW,\n"
" const uint chunkSizeH,\n"
" const uint rowParity,\n"
" const uint colParity\n"
"\t)\n"
"{\n"
//Parity determines if this function should execute.
" if ((GLOBAL_ID_X >= densityFilter->m_SuperRasW) ||\n"
" (GLOBAL_ID_Y >= densityFilter->m_SuperRasH) ||\n"
" ((BLOCK_ID_X % chunkSizeW) != colParity) ||\n"
" ((BLOCK_ID_Y % chunkSizeH) != rowParity)) \n"
" return;\n"
"\n";
if (doSS)
{
os <<
" uint ss = (uint)floor((real_t)densityFilter->m_Supersample / 2.0);\n"
" int densityBoxLeftX;\n"
" int densityBoxRightX;\n"
" int densityBoxTopY;\n"
" int densityBoxBottomY;\n";
if (doScf)
os << " real_t scfact = pow((real_t)densityFilter->m_Supersample / ((real_t)densityFilter->m_Supersample + 1.0), 2.0);\n";
}
os <<
//Compute the bounds of the area to be sampled, which is just the ends minus the super sample minus 1.
" uint leftBound = densityFilter->m_Supersample - 1;\n"
" uint rightBound = densityFilter->m_SuperRasW - (densityFilter->m_Supersample - 1);\n"
" uint topBound = densityFilter->m_Supersample - 1;\n"
" uint botBound = densityFilter->m_SuperRasH - (densityFilter->m_Supersample - 1);\n"
"\n"
//Start and end values are the indices in the histogram read from and written to in the accumulator.
//Before computing local offsets, compute the global offsets first to determine if any rows or cols fall outside of the bounds.
" uint blockHistStartRow = min(botBound, topBound + (BLOCK_ID_Y * BLOCK_SIZE_Y));\n"//The first histogram row this block will process.
" uint threadHistRow = blockHistStartRow + THREAD_ID_Y;\n"//The histogram row this individual thread will be reading from.
"\n"
" uint blockHistStartCol = min(rightBound, leftBound + (BLOCK_ID_X * BLOCK_SIZE_X));\n"//The first histogram column this block will process.
" uint threadHistCol = blockHistStartCol + THREAD_ID_X;\n"//The histogram column this individual thread will be reading from.
"\n"
" int i, j;\n"
" uint filterSelectInt, filterCoefIndex;\n"
" real_t cacheLog;\n"
" real_t logScale;\n"
" real_t filterSelect;\n"
" real4 bucket;\n"
"\n"
" if (threadHistRow < botBound && threadHistCol < rightBound)\n"
" {\n"
" bucket = histogram[(threadHistRow * densityFilter->m_SuperRasW) + threadHistCol];\n"
"\n"
" if (bucket.w != 0)\n"
" {\n"
" cacheLog = (densityFilter->m_K1 * log(1.0 + bucket.w * densityFilter->m_K2)) / bucket.w;\n";
if (doSS)
{
os <<
" filterSelect = 0;\n"
" densityBoxLeftX = threadHistCol - min(threadHistCol, ss);\n"
" densityBoxRightX = threadHistCol + min(ss, (densityFilter->m_SuperRasW - threadHistCol) - 1);\n"
" densityBoxTopY = threadHistRow - min(threadHistRow, ss);\n"
" densityBoxBottomY = threadHistRow + min(ss, (densityFilter->m_SuperRasH - threadHistRow) - 1);\n"
"\n"
" for (j = densityBoxTopY; j <= densityBoxBottomY; j++)\n"
" {\n"
" for (i = densityBoxLeftX; i <= densityBoxRightX; i++)\n"
" {\n"
" filterSelect += histogram[i + (j * densityFilter->m_SuperRasW)].w;\n"
" }\n"
" }\n"
"\n";
if (doScf)
os << " filterSelect *= scfact;\n";
}
else
{
os
<< " filterSelect = bucket.w;\n";
}
os <<
"\n"
" if (filterSelect > densityFilter->m_MaxFilteredCounts)\n"
" filterSelectInt = densityFilter->m_MaxFilterIndex;\n"
" else if (filterSelect <= DE_THRESH)\n"
" filterSelectInt = (int)ceil(filterSelect) - 1;\n"
" else\n"
" filterSelectInt = (int)DE_THRESH + (int)floor(pow((real_t)(filterSelect - DE_THRESH), densityFilter->m_Curve));\n"
"\n"
" if (filterSelectInt > densityFilter->m_MaxFilterIndex)\n"
" filterSelectInt = densityFilter->m_MaxFilterIndex;\n"
"\n"
" filterCoefIndex = filterSelectInt * densityFilter->m_KernelSize;\n"
"\n"
" int fw = (int)densityFilter->m_FilterWidth;\n"//Need a signed int to use below.
"\n"
" for (j = -fw; j <= fw; j++)\n"
" {\n"
" for (i = -fw; i <= fw; i++)\n"
" {\n"
" if (AccumCheck(densityFilter->m_SuperRasW, densityFilter->m_SuperRasH, threadHistCol, i, threadHistRow, j))\n"
" {\n"
" filterSelectInt = filterCoefIndex + coefIndices[(abs(j) * (densityFilter->m_FilterWidth + 1)) + abs(i)];\n"//Really is filterCoeffIndexPlusOffset, but reusing a variable to save space.
"\n"
" if (filterCoefs[filterSelectInt] != 0)\n"
" {\n"
" accumulator[(i + threadHistCol) + ((j + threadHistRow) * densityFilter->m_SuperRasW)].m_Real4 += (bucket * (filterCoefs[filterSelectInt] * cacheLog));\n"
" }\n"
" }\n"
"\n"
" barrier(CLK_GLOBAL_MEM_FENCE);\n"//Required to avoid streaks.
" }\n"
" }\n"
" }\n"//bucket.w != 0.
" }\n"//In bounds.
"\n"
//" barrier(CLK_GLOBAL_MEM_FENCE);\n"//Just to be safe.
"}\n";
return os.str();
}
}

View File

@ -0,0 +1,89 @@
#pragma once
#include "EmberCLPch.h"
#include "EmberCLStructs.h"
#include "EmberCLFunctions.h"
/// <summary>
/// DEOpenCLKernelCreator class.
/// </summary>
namespace EmberCLns
{
/// <summary>
/// Kernel creator for density filtering.
/// This implements both basic log scale filtering
/// as well as the full flam3 density estimation filtering
/// in OpenCL.
/// Several conditionals are present in the CPU version. They
/// are stripped out of the kernels and instead a separate kernel
/// is created for every possible case.
/// If the filter width is 9 or less, then the entire process can be
/// done in shared memory which is very fast.
/// However, if the filter width is greater than 9, shared memory is not
/// used and all filtering is done directly with main global VRAM. This
/// ends up being not much faster than doing it on the CPU.
/// String members are kept for the program source and entry points
/// for each version of the program.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBERCL_API DEOpenCLKernelCreator
{
public:
DEOpenCLKernelCreator();
DEOpenCLKernelCreator(bool nVidia);
//Accessors.
string LogScaleSumDEKernel();
string LogScaleSumDEEntryPoint();
string LogScaleAssignDEKernel();
string LogScaleAssignDEEntryPoint();
string GaussianDEKernel(unsigned int ss, unsigned int filterWidth);
string GaussianDEEntryPoint(unsigned int ss, unsigned int filterWidth);
//Miscellaneous static functions.
static unsigned int MaxDEFilterSize();
static T SolveMaxDERad(unsigned int maxBoxSize, T desiredFilterSize, T ss);
static unsigned int SolveMaxBoxSize(unsigned int localMem);
private:
//Kernel creators.
string CreateLogScaleSumDEKernelString();
string CreateLogScaleAssignDEKernelString();
string CreateGaussianDEKernel(unsigned int ss);
string CreateGaussianDEKernelNoLocalCache(unsigned int ss);
string m_LogScaleSumDEKernel;
string m_LogScaleSumDEEntryPoint;
string m_LogScaleAssignDEKernel;
string m_LogScaleAssignDEEntryPoint;
string m_GaussianDEWithoutSsKernel;
string m_GaussianDEWithoutSsEntryPoint;
string m_GaussianDESsWithScfKernel;
string m_GaussianDESsWithScfEntryPoint;
string m_GaussianDESsWithoutScfKernel;
string m_GaussianDESsWithoutScfEntryPoint;
string m_GaussianDEWithoutSsNoCacheKernel;
string m_GaussianDEWithoutSsNoCacheEntryPoint;
string m_GaussianDESsWithScfNoCacheKernel;
string m_GaussianDESsWithScfNoCacheEntryPoint;
string m_GaussianDESsWithoutScfNoCacheKernel;
string m_GaussianDESsWithoutScfNoCacheEntryPoint;
bool m_NVidia;
};
template EMBERCL_API class DEOpenCLKernelCreator<float>;
#ifdef DO_DOUBLE
template EMBERCL_API class DEOpenCLKernelCreator<double>;
#endif
}

View File

@ -0,0 +1,20 @@
#include "EmberCLPch.h"
/// <summary>
/// Generated by Visual Studio to make the DLL run properly.
/// </summary>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

View File

@ -0,0 +1,413 @@
#pragma once
#include "EmberCLPch.h"
#include "EmberCLStructs.h"
/// <summary>
/// OpenCL global function strings.
/// </summary>
namespace EmberCLns
{
/// <summary>
/// OpenCL equivalent of Palette::RgbToHsv().
/// </summary>
static const char* RgbToHsvFunctionString =
//rgb 0 - 1,
//h 0 - 6, s 0 - 1, v 0 - 1
"static inline void RgbToHsv(real4* rgb, real4* hsv)\n"
"{\n"
" real_t max, min, del, rc, gc, bc;\n"
"\n"
//Compute maximum of r, g, b.
" if ((*rgb).x >= (*rgb).y)\n"
" {\n"
" if ((*rgb).x >= (*rgb).z)\n"
" max = (*rgb).x;\n"
" else\n"
" max = (*rgb).z;\n"
" }\n"
" else\n"
" {\n"
" if ((*rgb).y >= (*rgb).z)\n"
" max = (*rgb).y;\n"
" else\n"
" max = (*rgb).z;\n"
" }\n"
"\n"
//Compute minimum of r, g, b.
" if ((*rgb).x <= (*rgb).y)\n"
" {\n"
" if ((*rgb).x <= (*rgb).z)\n"
" min = (*rgb).x;\n"
" else\n"
" min = (*rgb).z;\n"
" }\n"
" else\n"
" {\n"
" if ((*rgb).y <= (*rgb).z)\n"
" min = (*rgb).y;\n"
" else\n"
" min = (*rgb).z;\n"
" }\n"
"\n"
" del = max - min;\n"
" (*hsv).z = max;\n"
"\n"
" if (max != 0)\n"
" (*hsv).y = del / max;\n"
" else\n"
" (*hsv).y = 0;\n"
"\n"
" (*hsv).x = 0;\n"
" if ((*hsv).y != 0)\n"
" {\n"
" rc = (max - (*rgb).x) / del;\n"
" gc = (max - (*rgb).y) / del;\n"
" bc = (max - (*rgb).z) / del;\n"
"\n"
" if ((*rgb).x == max)\n"
" (*hsv).x = bc - gc;\n"
" else if ((*rgb).y == max)\n"
" (*hsv).x = 2 + rc - bc;\n"
" else if ((*rgb).z == max)\n"
" (*hsv).x = 4 + gc - rc;\n"
"\n"
" if ((*hsv).x < 0)\n"
" (*hsv).x += 6;\n"
" }\n"
"}\n"
"\n";
/// <summary>
/// OpenCL equivalent of Palette::HsvToRgb().
/// </summary>
static const char* HsvToRgbFunctionString =
//h 0 - 6, s 0 - 1, v 0 - 1
//rgb 0 - 1
"static inline void HsvToRgb(real4* hsv, real4* rgb)\n"
"{\n"
" int j;\n"
" real_t f, p, q, t;\n"
"\n"
" while ((*hsv).x >= 6)\n"
" (*hsv).x = (*hsv).x - 6;\n"
"\n"
" while ((*hsv).x < 0)\n"
" (*hsv).x = (*hsv).x + 6;\n"
"\n"
" j = (int)floor((*hsv).x);\n"
" f = (*hsv).x - j;\n"
" p = (*hsv).z * (1 - (*hsv).y);\n"
" q = (*hsv).z * (1 - ((*hsv).y * f));\n"
" t = (*hsv).z * (1 - ((*hsv).y * (1 - f)));\n"
"\n"
" switch (j)\n"
" {\n"
" case 0: (*rgb).x = (*hsv).z; (*rgb).y = t; (*rgb).z = p; break;\n"
" case 1: (*rgb).x = q; (*rgb).y = (*hsv).z; (*rgb).z = p; break;\n"
" case 2: (*rgb).x = p; (*rgb).y = (*hsv).z; (*rgb).z = t; break;\n"
" case 3: (*rgb).x = p; (*rgb).y = q; (*rgb).z = (*hsv).z; break;\n"
" case 4: (*rgb).x = t; (*rgb).y = p; (*rgb).z = (*hsv).z; break;\n"
" case 5: (*rgb).x = (*hsv).z; (*rgb).y = p; (*rgb).z = q; break;\n"
" default: (*rgb).x = (*hsv).z; (*rgb).y = t; (*rgb).z = p; break;\n"
" }\n"
"}\n"
"\n";
/// <summary>
/// OpenCL equivalent of Palette::CalcAlpha().
/// </summary>
static const char* CalcAlphaFunctionString =
"static inline real_t CalcAlpha(real_t density, real_t gamma, real_t linrange)\n"//Not the slightest clue what this is doing.//DOC
"{\n"
" real_t frac, alpha, funcval = pow(linrange, gamma);\n"
"\n"
" if (density > 0)\n"
" {\n"
" if (density < linrange)\n"
" {\n"
" frac = density / linrange;\n"
" alpha = (1.0 - frac) * density * (funcval / linrange) + frac * pow(density, gamma);\n"
" }\n"
" else\n"
" alpha = pow(density, gamma);\n"
" }\n"
" else\n"
" alpha = 0;\n"
"\n"
" return alpha;\n"
"}\n"
"\n";
/// <summary>
/// Use MWC 64 from David Thomas at the Imperial College of London for
/// random numbers in OpenCL, instead of ISAAC which was used
/// for CPU rendering.
/// </summary>
static const char* RandFunctionString =
"enum { MWC64X_A = 4294883355u };\n\n"
"inline uint MwcNext(uint2* s)\n"
"{\n"
" uint res = (*s).x ^ (*s).y; \n"//Calculate the result.
" uint hi = mul_hi((*s).x, MWC64X_A); \n"//Step the RNG.
" (*s).x = (*s).x * MWC64X_A + (*s).y;\n"//Pack the state back up.
" (*s).y = hi + ((*s).x < (*s).y); \n"
" return res; \n"//Return the next result.
"}\n"
"\n"
"inline uint MwcNextRange(uint2* s, uint val)\n"
"{\n"
" return (val == 0) ? MwcNext(s) : (MwcNext(s) % val);\n"
"}\n"
"\n"
"inline real_t MwcNext01(uint2* s)\n"
"{\n"
" return MwcNext(s) * (1.0 / 4294967296.0);\n"
"}\n"
"\n"
"inline real_t MwcNextNeg1Pos1(uint2* s)\n"
"{\n"
" real_t f = (real_t)MwcNext(s) / UINT_MAX;\n"
" return -1.0 + (f * (1.0 - (-1.0)));\n"
"}\n"
"\n";
/// <summary>
/// OpenCL equivalent of the global ClampRef().
/// </summary>
static const char* ClampRealFunctionString =
"inline real_t Clamp(real_t val, real_t min, real_t max)\n"
"{\n"
" if (val < min)\n"
" return min;\n"
" else if (val > max)\n"
" return max;\n"
" else\n"
" return val;\n"
"}\n"
"\n"
"inline void ClampRef(real_t* val, real_t min, real_t max)\n"
"{\n"
" if (*val < min)\n"
" *val = min;\n"
" else if (*val > max)\n"
" *val = max;\n"
"}\n"
"\n"
"inline real_t ClampGte(real_t val, real_t gte)\n"
"{\n"
" return (val < gte) ? gte : val;\n"
"}\n"
"\n";
/// <summary>
/// OpenCL equivalent of the global LRint().
/// </summary>
static const char* InlineMathFunctionsString =
"inline real_t LRint(real_t x)\n"
"{\n"
" intPrec temp = (x >= 0.0 ? (intPrec)(x + 0.5) : (intPrec)(x - 0.5));\n"
" return (real_t)temp;\n"
"}\n"
"\n"
"inline real_t Round(real_t r)\n"
"{\n"
" return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5);\n"
"}\n"
"\n"
"inline real_t Sign(real_t v)\n"
"{\n"
" return (v < 0.0) ? -1 : (v > 0.0) ? 1 : 0.0;\n"
"}\n"
"\n"
"inline real_t SignNz(real_t v)\n"
"{\n"
" return (v < 0.0) ? -1.0 : 1.0;\n"
"}\n"
"\n"
"inline real_t Sqr(real_t v)\n"
"{\n"
" return v * v;\n"
"}\n"
"\n"
"inline real_t SafeSqrt(real_t x)\n"
"{\n"
" if (x <= 0.0)\n"
" return 0.0;\n"
"\n"
" return sqrt(x);\n"
"}\n"
"\n"
"inline real_t Cube(real_t v)\n"
"{\n"
" return v * v * v;\n"
"}\n"
"\n"
"inline real_t Hypot(real_t x, real_t y)\n"
"{\n"
" return sqrt(SQR(x) + SQR(y));\n"
"}\n"
"\n"
"inline real_t Spread(real_t x, real_t y)\n"
"{\n"
" return Hypot(x, y) * ((x) > 0.0 ? 1.0 : -1.0);\n"
"}\n"
"\n"
"inline real_t Powq4(real_t x, real_t y)\n"
"{\n"
" return pow(fabs(x), y) * SignNz(x);\n"
"}\n"
"\n"
"inline real_t Powq4c(real_t x, real_t y)\n"
"{\n"
" return y == 1.0 ? x : Powq4(x, y);\n"
"}\n"
"\n"
"inline real_t Zeps(real_t x)\n"
"{\n"
" return x == 0.0 ? EPS6 : x;\n"
"}\n"
"\n"
"inline real_t Lerp(real_t a, real_t b, real_t p)\n"
"{\n"
" return a + (b - a) * p;\n"
"}\n"
"\n"
"inline real_t Fabsmod(real_t v)\n"
"{\n"
" real_t dummy;\n"
"\n"
" return modf(v, &dummy);\n"
"}\n"
"\n"
"inline real_t Fosc(real_t p, real_t amp, real_t ph)\n"
"{\n"
" return 0.5 - cos(p * amp + ph) * 0.5;\n"
"}\n"
"\n"
"inline real_t Foscn(real_t p, real_t ph)\n"
"{\n"
" return 0.5 - cos(p + ph) * 0.5;\n"
"}\n"
"\n"
"inline real_t LogScale(real_t x)\n"
"{\n"
" return x == 0.0 ? 0.0 : log((fabs(x) + 1) * M_E) * SignNz(x) / M_E;\n"
"}\n"
"\n"
"inline real_t LogMap(real_t x)\n"
"{\n"
" return x == 0.0 ? 0.0 : (M_E + log(x * M_E)) * 0.25 * SignNz(x);\n"
"}\n"
"\n";
/// <summary>
/// OpenCL equivalent Renderer::AddToAccum().
/// </summary>
static const char* AddToAccumWithCheckFunctionString =
"inline bool AccumCheck(int superRasW, int superRasH, int i, int ii, int j, int jj)\n"
"{\n"
" return (j + jj >= 0 && j + jj < superRasH && i + ii >= 0 && i + ii < superRasW);\n"
"}\n"
"\n";
/// <summary>
/// OpenCL equivalent various CarToRas member functions.
/// </summary>
static const char* CarToRasFunctionString =
"inline void CarToRasConvertPointToSingle(__constant CarToRasCL* carToRas, Point* point, unsigned int* singleBufferIndex)\n"
"{\n"
" *singleBufferIndex = (unsigned int)(carToRas->m_PixPerImageUnitW * point->m_X - carToRas->m_RasLlX) + (carToRas->m_RasWidth * (unsigned int)(carToRas->m_PixPerImageUnitH * point->m_Y - carToRas->m_RasLlY));\n"
"}\n"
"\n"
"inline bool CarToRasInBounds(__constant CarToRasCL* carToRas, Point* point)\n"
"{\n"
" return point->m_X >= carToRas->m_CarLlX &&\n"
" point->m_X < carToRas->m_CarUrX &&\n"
" point->m_Y < carToRas->m_CarUrY &&\n"
" point->m_Y >= carToRas->m_CarLlY;\n"
"}\n"
"\n";
static string AtomicString(bool doublePrecision, bool dp64AtomicSupport)
{
ostringstream os;
//If they want single precision, or if they want double precision and have dp atomic support.
if (!doublePrecision || dp64AtomicSupport)
{
os <<
"void AtomicAdd(volatile __global real_t* source, const real_t operand)\n"
"{\n"
" union\n"
" {\n"
" atomi intVal;\n"
" real_t realVal;\n"
" } newVal;\n"
"\n"
" union\n"
" {\n"
" atomi intVal;\n"
" real_t realVal;\n"
" } prevVal;\n"
"\n"
" do\n"
" {\n"
" prevVal.realVal = *source;\n"
" newVal.realVal = prevVal.realVal + operand;\n"
" } while (atomic_cmpxchg((volatile __global atomi*)source, prevVal.intVal, newVal.intVal) != prevVal.intVal);\n"
"}\n";
}
else//They want double precision and do not have dp atomic support.
{
os <<
"void AtomicAdd(volatile __global real_t* source, const real_t operand)\n"
"{\n"
" union\n"
" {\n"
" uint intVal[2];\n"
" real_t realVal;\n"
" } newVal;\n"
"\n"
" union\n"
" {\n"
" uint intVal[2];\n"
" real_t realVal;\n"
" } prevVal;\n"
"\n"
" do\n"
" {\n"
" prevVal.realVal = *source;\n"
" newVal.realVal = prevVal.realVal + operand;\n"
" } while ((atomic_cmpxchg((volatile __global uint*)source, prevVal.intVal[0], newVal.intVal[0]) != prevVal.intVal[0]) ||\n"
" (atomic_cmpxchg((volatile __global uint*)source + 1, prevVal.intVal[1], newVal.intVal[1]) != prevVal.intVal[1]));\n"
"}\n";
}
return os.str();
}
#ifdef GRAVEYARD
/*"void AtomicLocalAdd(volatile __local real_t* source, const real_t operand)\n"
"{\n"
" union\n"
" {\n"
" atomi intVal;\n"
" real_t realVal;\n"
" } newVal;\n"
"\n"
" union\n"
" {\n"
" atomi intVal;\n"
" real_t realVal;\n"
" } prevVal;\n"
"\n"
" do\n"
" {\n"
" prevVal.realVal = *source;\n"
" newVal.realVal = prevVal.realVal + operand;\n"
" } while (atomic_cmpxchg((volatile __local atomi*)source, prevVal.intVal, newVal.intVal) != prevVal.intVal);\n"
"}\n"*/
#endif
}

View File

@ -0,0 +1,39 @@
#pragma once
/// <summary>
/// Precompiled header file. Place all system includes here with appropriate #defines for different operating systems and compilers.
/// </summary>
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN//Exclude rarely-used stuff from Windows headers.
#define _USE_MATH_DEFINES
#ifdef _WIN32
#include <windows.h>
#include <SDKDDKVer.h>
#endif
#include <utility>
#include <CL/cl.hpp>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <iterator>
#include <time.h>
#include "Timing.h"
#include "Renderer.h"
#if defined(BUILDING_EMBERCL)
#define EMBERCL_API __declspec(dllexport)
#else
#define EMBERCL_API __declspec(dllimport)
#endif
using namespace std;
using namespace EmberNs;
//#define TEST_CL 1

View File

@ -0,0 +1,383 @@
#pragma once
#include "EmberCLPch.h"
/// <summary>
/// Various data structures defined for the CPU and OpenCL.
/// These are stripped down versions of THE classes in Ember, for use with OpenCL.
/// Their sole purpose is to pass values from the host to the device.
/// They retain most of the member variables, but do not contain the functions.
/// Visual Studio defaults to alighment of 16, but it's made explicit in case another compiler is used.
/// This must match the alignment specified in the kernel.
/// </summary>
namespace EmberCLns
{
#define ALIGN __declspec(align(16))//These two must always match.
#define ALIGN_CL "((aligned (16)))"//The extra parens are necessary.
/// <summary>
/// Various constants needed for rendering.
/// </summary>
static string ConstantDefinesString(bool doublePrecision)
{
ostringstream os;
if (doublePrecision)
{
os << "#if defined(cl_amd_fp64)\n"//AMD extension available?
<< " #pragma OPENCL EXTENSION cl_amd_fp64 : enable\n"
<< "#endif\n"
<< "#if defined(cl_khr_fp64)\n"//Khronos extension available?
<< " #pragma OPENCL EXTENSION cl_khr_fp64 : enable\n"
<< "#endif\n"
<< "#pragma OPENCL EXTENSION cl_khr_int64_base_atomics : enable\n"//Only supported on nVidia.
<< "typedef long intPrec;\n"
<< "typedef ulong atomi;\n"
<< "typedef double real_t;\n"
<< "typedef double4 real4;\n";
}
else
{
os << "typedef int intPrec;\n"
"typedef unsigned int atomi;\n"
"typedef float real_t;\n"
"typedef float4 real4;\n";
}
os <<
"typedef long int int64;\n"
"typedef unsigned long int uint64;\n"
"\n"
"#define EPS ((1e-10))\n"//May need to change this, it might not be enough in some cases. Maybe try 1e-9 if things look funny when close to zero.
"#define EPS6 ((1e-6))\n"
"\n"
"//The number of threads per block used in the iteration function. Don't change\n"
"//it lightly; the block size is hard coded to be exactly 32 x 8.\n"
"#define NTHREADS 256u\n"
"#define THREADS_PER_WARP 32u\n"
"#define NWARPS (NTHREADS / THREADS_PER_WARP)\n"
"#define COLORMAP_LENGTH 256u\n"
"#define COLORMAP_LENGTH_MINUS_1 255u\n"
"#define DE_THRESH 100u\n"
"#define BadVal(x) (((x) != (x)) || ((x) > 1e10) || ((x) < -1e10))\n"
"#define Rint(A) floor((A) + (((A) < 0) ? -0.5 : 0.5))\n"
"#define SQR(x) ((x) * (x))\n"
"#define CUBE(x) ((x) * (x) * (x))\n"
"#define M_2PI (M_PI * 2)\n"
"#define M_3PI (M_PI * 3)\n"
"#define SQRT5 2.2360679774997896964091736687313\n"
"#define M_PHI 1.61803398874989484820458683436563\n"
"#define DEG_2_RAD (M_PI / 180)\n"
"\n"
"//Index in each dimension of a thread within a block.\n"
"#define THREAD_ID_X (get_local_id(0))\n"
"#define THREAD_ID_Y (get_local_id(1))\n"
"#define THREAD_ID_Z (get_local_id(2))\n"
"\n"
"//Index in each dimension of a block within a grid.\n"
"#define BLOCK_ID_X (get_group_id(0))\n"
"#define BLOCK_ID_Y (get_group_id(1))\n"
"#define BLOCK_ID_Z (get_group_id(2))\n"
"\n"
"//Absolute index in each dimension of a thread within a grid.\n"
"#define GLOBAL_ID_X (get_global_id(0))\n"
"#define GLOBAL_ID_Y (get_global_id(1))\n"
"#define GLOBAL_ID_Z (get_global_id(2))\n"
"\n"
"//Dimensions of a block.\n"
"#define BLOCK_SIZE_X (get_local_size(0))\n"
"#define BLOCK_SIZE_Y (get_local_size(1))\n"
"#define BLOCK_SIZE_Z (get_local_size(2))\n"
"\n"
"//Dimensions of a grid, in terms of blocks.\n"
"#define GRID_SIZE_X (get_num_groups(0))\n"
"#define GRID_SIZE_Y (get_num_groups(1))\n"
"#define GRID_SIZE_Z (get_num_groups(2))\n"
"\n"
"//Dimensions of a grid, in terms of threads.\n"
"#define GLOBAL_SIZE_X (get_global_size(0))\n"
"#define GLOBAL_SIZE_Y (get_global_size(1))\n"
"#define GLOBAL_SIZE_Z (get_global_size(2))\n"
"\n"
"#define INDEX_IN_BLOCK_2D (THREAD_ID_Y * BLOCK_SIZE_X + THREAD_ID_X)\n"
"#define INDEX_IN_BLOCK_3D ((BLOCK_SIZE_X * BLOCK_SIZE_Y * THREAD_ID_Z) + INDEX_IN_BLOCK_2D)\n"
"\n"
"#define INDEX_IN_GRID_2D (GLOBAL_ID_Y * GLOBAL_SIZE_X + GLOBAL_ID_X)\n"
"#define INDEX_IN_GRID_3D ((GLOBAL_SIZE_X * GLOBAL_SIZE_Y * GLOBAL_ID_Z) + INDEX_IN_GRID_2D)\n"
"\n";
return os.str();
}
/// <summary>
/// A point structure on the host that maps to the one used on the device to iterate in OpenCL.
/// It might seem better to use vec4, however 2D palettes and even 3D coordinates may eventually
/// be supported, which will make it more than 4 members.
/// </summary>
template <typename T>
struct ALIGN PointCL
{
T m_X;
T m_Y;
T m_Z;
T m_ColorX;
T m_LastXfUsed;
};
/// <summary>
/// The point structure used to iterate in OpenCL.
/// It might seem better to use float4, however 2D palettes and even 3D coordinates may eventually
/// be supported, which will make it more than 4 members.
/// </summary>
static const char* PointCLStructString =
"typedef struct __attribute__ " ALIGN_CL " _Point\n"
"{\n"
" real_t m_X;\n"
" real_t m_Y;\n"
" real_t m_Z;\n"
" real_t m_ColorX;\n"
" uint m_LastXfUsed;\n"
"} Point;\n"
"\n";
#define MAX_CL_VARS 8//These must always match.
#define MAX_CL_VARS_STRING "8"
/// <summary>
/// A structure on the host used to hold all of the needed information for an xform used on the device to iterate in OpenCL.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
struct ALIGN XformCL
{
T m_A, m_B, m_C, m_D, m_E, m_F;//24 (48)
T m_VariationWeights[MAX_CL_VARS];//56 (112)
T m_PostA, m_PostB, m_PostC, m_PostD, m_PostE, m_PostF;//80 (160)
T m_DirectColor;//84 (168)
T m_ColorSpeedCache;//88 (176)
T m_OneMinusColorCache;//92 (184)
T m_Opacity;//96 (192)
T m_VizAdjusted;//100 (200)
};
/// <summary>
/// The xform structure used to iterate in OpenCL.
/// </summary>
static const char* XformCLStructString =
"typedef struct __attribute__ " ALIGN_CL " _XformCL\n"
"{\n"
" real_t m_A, m_B, m_C, m_D, m_E, m_F;\n"
" real_t m_VariationWeights[" MAX_CL_VARS_STRING "];\n"
" real_t m_PostA, m_PostB, m_PostC, m_PostD, m_PostE, m_PostF;\n"
" real_t m_DirectColor;\n"
" real_t m_ColorSpeedCache;\n"
" real_t m_OneMinusColorCache;\n"
" real_t m_Opacity;\n"
" real_t m_VizAdjusted;\n"
"} XformCL;\n"
"\n";
#define MAX_CL_XFORM 21//These must always match.
#define MAX_CL_XFORM_STRING "21"
/// <summary>
/// A structure on the host used to hold all of the needed information for an ember used on the device to iterate in OpenCL.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
struct ALIGN EmberCL
{
unsigned int m_FinalXformIndex;
XformCL<T> m_Xforms[MAX_CL_XFORM];
T m_CamZPos;
T m_CamPerspective;
T m_CamYaw;
T m_CamPitch;
T m_CamDepthBlur;
T m_BlurCoef;
m3T m_CamMat;
T m_CenterX, m_CenterY;
T m_RotA, m_RotB, m_RotD, m_RotE;
};
/// <summary>
/// The ember structure used to iterate in OpenCL.
/// </summary>
static const char* EmberCLStructString =
"typedef struct __attribute__ " ALIGN_CL " _EmberCL\n"
"{\n"
" uint m_FinalXformIndex;\n"
" XformCL m_Xforms[" MAX_CL_XFORM_STRING "];\n"
" real_t m_CamZPos;\n"
" real_t m_CamPerspective;\n"
" real_t m_CamYaw;\n"
" real_t m_CamPitch;\n"
" real_t m_CamDepthBlur;\n"
" real_t m_BlurCoef;\n"
" real_t m_C00;\n"
" real_t m_C01;\n"
" real_t m_C02;\n"
" real_t m_C10;\n"
" real_t m_C11;\n"
" real_t m_C12;\n"
" real_t m_C20;\n"
" real_t m_C21;\n"
" real_t m_C22;\n"
" real_t m_CenterX, m_CenterY;\n"
" real_t m_RotA, m_RotB, m_RotD, m_RotE;\n"
"} EmberCL;\n"
"\n";
/// <summary>
/// A structure on the host used to hold all of the needed information for cartesian to raster mapping used on the device to iterate in OpenCL.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
struct ALIGN CarToRasCL
{
T m_PixPerImageUnitW, m_RasLlX;
unsigned int m_RasWidth;
T m_PixPerImageUnitH, m_RasLlY;
T m_CarLlX, m_CarUrX, m_CarUrY, m_CarLlY;
};
/// <summary>
/// The cartesian to raster structure used to iterate in OpenCL.
/// </summary>
static const char* CarToRasCLStructString =
"typedef struct __attribute__ " ALIGN_CL " _CarToRasCL\n"
"{\n"
" real_t m_PixPerImageUnitW, m_RasLlX;\n"
" uint m_RasWidth;\n"
" real_t m_PixPerImageUnitH, m_RasLlY;\n"
" real_t m_CarLlX, m_CarUrX, m_CarUrY, m_CarLlY;\n"
"} CarToRasCL;\n"
"\n";
/// <summary>
/// A structure on the host used to hold all of the needed information for density filtering used on the device to iterate in OpenCL.
/// Note that the actual filter buffer is held elsewhere.
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
struct ALIGN DensityFilterCL
{
T m_Curve;
T m_K1;
T m_K2;
unsigned int m_Supersample;
unsigned int m_SuperRasW;
unsigned int m_SuperRasH;
unsigned int m_KernelSize;
unsigned int m_MaxFilterIndex;
unsigned int m_MaxFilteredCounts;
unsigned int m_FilterWidth;
};
/// <summary>
/// The density filtering structure used to iterate in OpenCL.
/// Note that the actual filter buffer is held elsewhere.
/// </summary>
static const char* DensityFilterCLStructString =
"typedef struct __attribute__ " ALIGN_CL " _DensityFilterCL\n"
"{\n"
" real_t m_Curve;\n"
" real_t m_K1;\n"
" real_t m_K2;\n"
" uint m_Supersample;\n"
" uint m_SuperRasW;\n"
" uint m_SuperRasH;\n"
" uint m_KernelSize;\n"
" uint m_MaxFilterIndex;\n"
" uint m_MaxFilteredCounts;\n"
" uint m_FilterWidth;\n"
"} DensityFilterCL;\n"
"\n";
/// <summary>
/// A structure on the host used to hold all of the needed information for spatial filtering used on the device to iterate in OpenCL.
/// Note that the actual filter buffer is held elsewhere.
/// </summary>
template <typename T>
struct ALIGN SpatialFilterCL
{
unsigned int m_SuperRasW;
unsigned int m_SuperRasH;
unsigned int m_FinalRasW;
unsigned int m_FinalRasH;
unsigned int m_Supersample;
unsigned int m_FilterWidth;
unsigned int m_NumChannels;
unsigned int m_BytesPerChannel;
unsigned int m_DensityFilterOffset;
unsigned int m_Transparency;
T m_Vibrancy;
T m_HighlightPower;
T m_Gamma;
T m_LinRange;
Color<T> m_Background;
};
/// <summary>
/// The spatial filtering structure used to iterate in OpenCL.
/// Note that the actual filter buffer is held elsewhere.
/// </summary>
static const char* SpatialFilterCLStructString =
"typedef struct __attribute__ ((aligned (16))) _SpatialFilterCL\n"
"{\n"
" uint m_SuperRasW;\n"
" uint m_SuperRasH;\n"
" uint m_FinalRasW;\n"
" uint m_FinalRasH;\n"
" uint m_Supersample;\n"
" uint m_FilterWidth;\n"
" uint m_NumChannels;\n"
" uint m_BytesPerChannel;\n"
" uint m_DensityFilterOffset;\n"
" uint m_Transparency;\n"
" real_t m_Vibrancy;\n"
" real_t m_HighlightPower;\n"
" real_t m_Gamma;\n"
" real_t m_LinRange;\n"
" real_t m_Background[4];\n"//For some reason, using float4/double4 here does not align no matter what. So just use an array of 4.
"} SpatialFilterCL;\n"
"\n";
/// <summary>
/// EmberCL makes extensive use of the build in vector types, however accessing
/// their members as a buffer is not natively supported.
/// Declaring them in a union with a buffer resolves this problem.
/// </summary>
static const char* UnionCLStructString =
"typedef union\n"
"{\n"
" uchar3 m_Uchar3;\n"
" uchar m_Uchars[3];\n"
"} uchar3uchars;\n"
"\n"
"typedef union\n"
"{\n"
" uchar4 m_Uchar4;\n"
" uchar m_Uchars[4];\n"
"} uchar4uchars;\n"
"\n"
"typedef union\n"
"{\n"
" uint4 m_Uint4;\n"
" uint m_Uints[4];\n"
"} uint4uints;\n"
"\n"
"typedef union\n"//Use in places where float is required.
"{\n"
" float4 m_Float4;\n"
" float m_Floats[4];\n"
"} float4floats;\n"
"\n"
"typedef union\n"//Use in places where float or double can be used depending on the template type.
"{\n"
" real4 m_Real4;\n"
" real_t m_Reals[4];\n"
"} real4reals;\n"
"\n";
}

View File

@ -0,0 +1,517 @@
#include "EmberCLPch.h"
#include "FinalAccumOpenCLKernelCreator.h"
namespace EmberCLns
{
/// <summary>
/// Constructor that creates all kernel strings.
/// The caller will access these strings through the accessor functions.
/// </summary>
template <typename T>
FinalAccumOpenCLKernelCreator<T>::FinalAccumOpenCLKernelCreator()
{
m_GammaCorrectionWithAlphaCalcEntryPoint = "GammaCorrectionWithAlphaCalcKernel";
m_GammaCorrectionWithoutAlphaCalcEntryPoint = "GammaCorrectionWithoutAlphaCalcKernel";
m_GammaCorrectionWithAlphaCalcKernel = CreateGammaCorrectionKernelString(true);
m_GammaCorrectionWithoutAlphaCalcKernel = CreateGammaCorrectionKernelString(false);
m_FinalAccumEarlyClipEntryPoint = "FinalAccumEarlyClipKernel";
m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint = "FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel";
m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint = "FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel";
m_FinalAccumEarlyClipKernel = CreateFinalAccumKernelString(true, false, false);
m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel = CreateFinalAccumKernelString(true, true, true);
m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel = CreateFinalAccumKernelString(true, false, true);
m_FinalAccumLateClipEntryPoint = "FinalAccumLateClipKernel";
m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint = "FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel";
m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint = "FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel";
m_FinalAccumLateClipKernel = CreateFinalAccumKernelString(false, false, false);
m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel = CreateFinalAccumKernelString(false, true, true);
m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel = CreateFinalAccumKernelString(false, false, true);
}
/// <summary>
/// Kernel source and entry point properties, getters only.
/// </summary>
template <typename T> string FinalAccumOpenCLKernelCreator<T>::GammaCorrectionWithAlphaCalcKernel() { return m_GammaCorrectionWithAlphaCalcKernel; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::GammaCorrectionWithAlphaCalcEntryPoint() { return m_GammaCorrectionWithAlphaCalcEntryPoint; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::GammaCorrectionWithoutAlphaCalcKernel() { return m_GammaCorrectionWithoutAlphaCalcKernel; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::GammaCorrectionWithoutAlphaCalcEntryPoint() { return m_GammaCorrectionWithoutAlphaCalcEntryPoint; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::FinalAccumEarlyClipKernel() { return m_FinalAccumEarlyClipKernel; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::FinalAccumEarlyClipEntryPoint() { return m_FinalAccumEarlyClipEntryPoint; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel() { return m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint() { return m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel() { return m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint() { return m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::FinalAccumLateClipKernel() { return m_FinalAccumLateClipKernel; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::FinalAccumLateClipEntryPoint() { return m_FinalAccumLateClipEntryPoint; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel() { return m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint() { return m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel() { return m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel; }
template <typename T> string FinalAccumOpenCLKernelCreator<T>::FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint() { return m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint; }
/// <summary>
/// Get the gamma correction entry point.
/// </summary>
/// <param name="channels">The number of channels used, 3 or 4.</param>
/// <param name="transparency">True if channels equals 4 and using transparency, else false.</param>
/// <returns>The name of the gamma correction entry point kernel function</returns>
template <typename T>
string FinalAccumOpenCLKernelCreator<T>::GammaCorrectionEntryPoint(unsigned int channels, bool transparency)
{
bool alphaCalc = (channels > 3 && transparency);
return alphaCalc ? m_GammaCorrectionWithAlphaCalcEntryPoint : m_GammaCorrectionWithoutAlphaCalcEntryPoint;
}
/// <summary>
/// Get the gamma correction kernel string.
/// </summary>
/// <param name="channels">The number of channels used, 3 or 4.</param>
/// <param name="transparency">True if channels equals 4 and using transparency, else false.</param>
/// <returns>The gamma correction kernel string</returns>
template <typename T>
string FinalAccumOpenCLKernelCreator<T>::GammaCorrectionKernel(unsigned int channels, bool transparency)
{
bool alphaCalc = (channels > 3 && transparency);
return alphaCalc ? m_GammaCorrectionWithAlphaCalcKernel : m_GammaCorrectionWithoutAlphaCalcKernel;
}
/// <summary>
/// Get the final accumulation entry point.
/// </summary>
/// <param name="earlyClip">True if early clip is desired, else false.</param>
/// <param name="channels">The number of channels used, 3 or 4.</param>
/// <param name="transparency">True if channels equals 4 and using transparency, else false.</param>
/// <param name="alphaBase">Storage for the alpha base value used in the kernel. 0 if transparency is true, else 255.</param>
/// <param name="alphaScale">Storage for the alpha scale value used in the kernel. 255 if transparency is true, else 0.</param>
/// <returns>The name of the final accumulation entry point kernel function</returns>
template <typename T>
string FinalAccumOpenCLKernelCreator<T>::FinalAccumEntryPoint(bool earlyClip, unsigned int channels, bool transparency, T& alphaBase, T& alphaScale)
{
bool alphaCalc = (channels > 3 && transparency);
bool alphaAccum = channels > 3;
if (alphaAccum)
{
alphaBase = transparency ? 0.0f : 255.0f;//See the table below.
alphaScale = transparency ? 255.0f : 0.0f;
}
if (earlyClip)
{
if (!alphaCalc && !alphaAccum)//Rgb output, the most common case.
return FinalAccumEarlyClipEntryPoint();
else if (alphaCalc && alphaAccum)//Rgba output and Transparency.
return FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint();
else if (!alphaCalc && alphaAccum)//Rgba output and !Transparency.
return FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint();
else
return "";//Cannot have alphaCalc and !alphaAccum, it makes no sense.
}
else
{
if (!alphaCalc && !alphaAccum)//Rgb output, the most common case.
return FinalAccumLateClipEntryPoint();
else if (alphaCalc && alphaAccum)//Rgba output and Transparency.
return FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint();
else if (!alphaCalc && alphaAccum)//Rgba output and !Transparency.
return FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint();
else
return "";//Cannot have alphaCalc and !alphaAccum, it makes no sense.
}
}
/// <summary>
/// Get the final accumulation kernel string.
/// </summary>
/// <param name="earlyClip">True if early clip is desired, else false.</param>
/// <param name="channels">The number of channels used, 3 or 4.</param>
/// <param name="transparency">True if channels equals 4 and using transparency, else false.</param>
/// <returns>The final accumulation kernel string</returns>
template <typename T>
string FinalAccumOpenCLKernelCreator<T>::FinalAccumKernel(bool earlyClip, unsigned int channels, bool transparency)
{
bool alphaCalc = (channels > 3 && transparency);
bool alphaAccum = channels > 3;
if (earlyClip)
{
if (!alphaCalc && !alphaAccum)//Rgb output, the most common case.
return FinalAccumEarlyClipKernel();
else if (alphaCalc && alphaAccum)//Rgba output and Transparency.
return FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel();
else if (!alphaCalc && alphaAccum)//Rgba output and !Transparency.
return FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel();
else
return "";//Cannot have alphaCalc and !alphaAccum, it makes no sense.
}
else
{
if (!alphaCalc && !alphaAccum)//Rgb output, the most common case.
return FinalAccumLateClipKernel();
else if (alphaCalc && alphaAccum)//Rgba output and Transparency.
return FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel();
else if (!alphaCalc && alphaAccum)//Rgba output and !Transparency.
return FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel();
else
return "";//Cannot have alphaCalc and !alphaAccum, it makes no sense.
}
}
/// <summary>
/// Wrapper around CreateFinalAccumKernelString().
/// </summary>
/// <param name="earlyClip">True if early clip is desired, else false.</param>
/// <param name="channels">The number of channels used, 3 or 4.</param>
/// <param name="transparency">True if channels equals 4 and using transparency, else false.</param>
/// <returns>The final accumulation kernel string</returns>
template <typename T>
string FinalAccumOpenCLKernelCreator<T>::CreateFinalAccumKernelString(bool earlyClip, unsigned int channels, bool transparency)
{
return CreateFinalAccumKernelString(earlyClip, (channels > 3 && transparency), channels > 3);
}
/// <summary>
/// Create the final accumulation kernel string
/// </summary>
/// <param name="earlyClip">True if early clip is desired, else false.</param>
/// <param name="alphaCalc">True if channels equals 4 and transparency is desired, else false.</param>
/// <param name="alphaAccum">True if channels equals 4</param>
/// <returns>The final accumulation kernel string</returns>
template <typename T>
string FinalAccumOpenCLKernelCreator<T>::CreateFinalAccumKernelString(bool earlyClip, bool alphaCalc, bool alphaAccum)
{
ostringstream os;
string channels = alphaAccum ? "4" : "3";
os <<
ConstantDefinesString(typeid(T) == typeid(double)) <<
ClampRealFunctionString <<
UnionCLStructString <<
RgbToHsvFunctionString <<
HsvToRgbFunctionString <<
CalcAlphaFunctionString <<
SpatialFilterCLStructString;
if (earlyClip)
{
if (!alphaCalc && !alphaAccum)//Rgb output, the most common case.
os << "__kernel void " << m_FinalAccumEarlyClipEntryPoint << "(\n";
else if (alphaCalc && alphaAccum)//Rgba output and Transparency.
os << "__kernel void " << m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint << "(\n";
else if (!alphaCalc && alphaAccum)//Rgba output and !Transparency.
os << "__kernel void " << m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint << "(\n";
else
return "";//Cannot have alphaCalc and !alphaAccum, it makes no sense.
}
else
{
os <<
CreateCalcNewRgbFunctionString(false) <<
CreateGammaCorrectionFunctionString(false, alphaCalc, alphaAccum, true);
if (!alphaCalc && !alphaAccum)//Rgb output, the most common case.
os << "__kernel void " << m_FinalAccumLateClipEntryPoint << "(\n";
else if (alphaCalc && alphaAccum)//Rgba output and Transparency.
os << "__kernel void " << m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint << "(\n";
else if (!alphaCalc && alphaAccum)//Rgba output and !Transparency.
os << "__kernel void " << m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint << "(\n";
else
return "";//Cannot have alphaCalc and !alphaAccum, it makes no sense.
}
os <<
" const __global real4reals* accumulator,\n"
" __write_only image2d_t pixels,\n"
" __constant SpatialFilterCL* spatialFilter,\n"
" __constant real_t* filterCoefs,\n"
" const real_t alphaBase,\n"
" const real_t alphaScale\n"
"\t)\n"
"{\n"
"\n"
" if ((GLOBAL_ID_Y >= spatialFilter->m_FinalRasH) || (GLOBAL_ID_X >= spatialFilter->m_FinalRasW))\n"
" return;\n"
"\n"
" unsigned int accumX = spatialFilter->m_DensityFilterOffset + (GLOBAL_ID_X * spatialFilter->m_Supersample);\n"
" unsigned int accumY = spatialFilter->m_DensityFilterOffset + (GLOBAL_ID_Y * spatialFilter->m_Supersample);\n"
" int2 finalCoord;\n"
" finalCoord.x = GLOBAL_ID_X;\n"
" finalCoord.y = GLOBAL_ID_Y;\n"
" float4floats finalColor;\n"
" real_t alpha, ls;\n"
" int ii, jj;\n"
" unsigned int filterKRowIndex;\n"
" const __global real4reals* accumBucket;\n"
" real4reals newBucket;\n"
" newBucket.m_Real4 = 0;\n"
" real4reals newRgb;\n"
" newRgb.m_Real4 = 0;\n"
"\n"
" for (jj = 0; jj < spatialFilter->m_FilterWidth; jj++)\n"
" {\n"
" filterKRowIndex = jj * spatialFilter->m_FilterWidth;\n"
"\n"
" for (ii = 0; ii < spatialFilter->m_FilterWidth; ii++)\n"
" {\n"
" real_t k = filterCoefs[ii + filterKRowIndex];\n"
"\n"
" accumBucket = accumulator + (accumX + ii) + ((accumY + jj) * spatialFilter->m_SuperRasW);\n"
" newBucket.m_Real4 += (k * accumBucket->m_Real4);\n"
" }\n"
" }\n"
"\n";
//Not supporting 2 bytes per channel on the GPU. If the user wants it, run on the CPU.
if (earlyClip)//If early clip, simply assign values directly to the temp uint4 since they've been gamma corrected already, then write it straight to the output image below.
{
os <<
" finalColor.m_Float4.x = (float)newBucket.m_Real4.x;\n"//CPU side clamps, skip here because write_imagef() does the clamping for us.
" finalColor.m_Float4.y = (float)newBucket.m_Real4.y;\n"
" finalColor.m_Float4.z = (float)newBucket.m_Real4.z;\n";
if (alphaAccum)
{
if (alphaCalc)
os << " finalColor.m_Float4.w = (float)newBucket.m_Real4.w * 255.0f;\n";
else
os << " finalColor.m_Float4.w = 255;\n";
}
}
else
{
//Late clip, so must gamma correct from the temp new bucket to temp float4.
if (typeid(T) == typeid(double))
{
os <<
" real4reals realFinal;\n"
"\n"
" GammaCorrectionFloats(&newBucket, &(spatialFilter->m_Background[0]), spatialFilter->m_Gamma, spatialFilter->m_LinRange, spatialFilter->m_Vibrancy, spatialFilter->m_HighlightPower, alphaBase, alphaScale, &(realFinal.m_Reals[0]));\n"
" finalColor.m_Float4.x = (float)realFinal.m_Real4.x;\n"
" finalColor.m_Float4.y = (float)realFinal.m_Real4.y;\n"
" finalColor.m_Float4.z = (float)realFinal.m_Real4.z;\n"
" finalColor.m_Float4.w = (float)realFinal.m_Real4.w;\n"
;
}
else
{
os <<
" GammaCorrectionFloats(&newBucket, &(spatialFilter->m_Background[0]), spatialFilter->m_Gamma, spatialFilter->m_LinRange, spatialFilter->m_Vibrancy, spatialFilter->m_HighlightPower, alphaBase, alphaScale, &(finalColor.m_Floats[0]));\n";
}
}
os <<
" finalColor.m_Float4 /= 255.0f;\n"
" write_imagef(pixels, finalCoord, finalColor.m_Float4);\n"//Use write_imagef instead of write_imageui because only the former works when sharing with an OpenGL texture.
" barrier(CLK_GLOBAL_MEM_FENCE);\n"//Required, or else page tearing will occur during interactive rendering.
"}\n"
;
return os.str();
}
/// <summary>
/// Creates the gamma correction function string.
/// This is not a full kernel, just a function that is used in the kernels.
/// </summary>
/// <param name="globalBucket">True if writing to a global buffer (early clip), else false (late clip).</param>
/// <param name="alphaCalc">True if channels equals 4 and transparency is desired, else false.</param>
/// <param name="alphaAccum">True if channels equals 4</param>
/// <param name="finalOut">True if writing to global buffer (late clip), else false (early clip).</param>
/// <returns>The gamma correction function string</returns>
template <typename T>
string FinalAccumOpenCLKernelCreator<T>::CreateGammaCorrectionFunctionString(bool globalBucket, bool alphaCalc, bool alphaAccum, bool finalOut)
{
ostringstream os;
string dataType;
string unionMember;
dataType = "real_t";
//Use real_t for all cases, early clip and final accum.
os << "void GammaCorrectionFloats(" << (globalBucket ? "__global " : "") << "real4reals* bucket, __constant real_t* background, real_t g, real_t linRange, real_t vibrancy, real_t highlightPower, real_t alphaBase, real_t alphaScale, " << (finalOut ? "" : "__global") << " real_t* correctedChannels)\n";
os
<< "{\n"
<< " real_t alpha, ls, tmp, a;\n"
<< " real4reals newRgb;\n"
<< "\n"
<< " if (bucket->m_Reals[3] <= 0)\n"
<< " {\n"
<< " alpha = 0;\n"
<< " ls = 0;\n"
<< " }\n"
<< " else\n"
<< " {\n"
<< " tmp = bucket->m_Reals[3];\n"
<< " alpha = CalcAlpha(tmp, g, linRange);\n"
<< " ls = vibrancy * 256.0 * alpha / tmp;\n"
<< " ClampRef(&alpha, 0.0, 1.0);\n"
<< " }\n"
<< "\n"
<< " CalcNewRgb(bucket, ls, highlightPower, &newRgb);\n"
<< "\n"
<< " for (unsigned int rgbi = 0; rgbi < 3; rgbi++)\n"
<< " {\n"
<< " a = newRgb.m_Reals[rgbi] + ((1.0 - vibrancy) * 256.0 * pow(bucket->m_Reals[rgbi], g));\n"
<< "\n";
if (!alphaCalc)
{
os <<
" a += ((1.0 - alpha) * background[rgbi]);\n";
}
else
{
os
<< " if (alpha > 0)\n"
<< " a /= alpha;\n"
<< " else\n"
<< " a = 0;\n";
}
os <<
"\n"
" correctedChannels[rgbi] = (" << dataType << ")clamp(a, 0.0, 255.0);\n"
" }\n"
"\n";
//The CPU code has 3 cases for assigning alpha:
//[3] = alpha.//Early clip.
//[3] = alpha * 255.//Final Rgba with transparency.
//[3] = 255.//Final Rgba without transparency.
//Putting conditionals in GPU code is to be avoided. So do base + alpha * scale which will
//work for all 3 cases without using a conditional, which should be faster on a GPU. This gives:
//Base = 0, scale = 1. [3] = (0 + (alpha * 1)). [3] = alpha.
//Base = 0, scale = 255. [3] = (0 + (alpha * 255)). [3] = alpha * 255.
//Base = 255, scale = 0. [3] = (255 + (alpha * 0)). [3] = 255.
if (alphaAccum)
{
os
<< " correctedChannels[3] = (" << dataType << ")(alphaBase + (alpha * alphaScale));\n";
}
os <<
"}\n"
"\n";
return os.str();
}
/// <summary>
/// OpenCL equivalent of Palette::CalcNewRgb().
/// </summary>
/// <param name="globalBucket">True if writing the corrected value to a global buffer (early clip), else false (late clip).</param>
/// <returns>The CalcNewRgb function string</returns>
template <typename T>
string FinalAccumOpenCLKernelCreator<T>::CreateCalcNewRgbFunctionString(bool globalBucket)
{
ostringstream os;
os <<
"static void CalcNewRgb(" << (globalBucket ? "__global " : "") << "real4reals* oldRgb, real_t ls, real_t highPow, real4reals* newRgb)\n"
"{\n"
" int rgbi;\n"
" real_t newls, lsratio;\n"
" real4reals newHsv;\n"
" real_t maxa, maxc;\n"
" real_t adjhlp;\n"
"\n"
" if (ls == 0 || (oldRgb->m_Real4.x == 0 && oldRgb->m_Real4.y == 0 && oldRgb->m_Real4.z == 0))\n"//Can't do a vector compare to zero.
" {\n"
" newRgb->m_Real4 = 0;\n"
" return;\n"
" }\n"
"\n"
//Identify the most saturated channel.
" maxc = max(max(oldRgb->m_Reals[0], oldRgb->m_Reals[1]), oldRgb->m_Reals[2]);\n"
" maxa = ls * maxc;\n"
"\n"
//If a channel is saturated and highlight power is non-negative
//modify the color to prevent hue shift.
" if (maxa > 255 && highPow >= 0)\n"
" {\n"
" newls = 255.0 / maxc;\n"
" lsratio = pow(newls / ls, highPow);\n"
"\n"
//Calculate the max-value color (ranged 0 - 1).
" for (rgbi = 0; rgbi < 3; rgbi++)\n"
" newRgb->m_Reals[rgbi] = newls * oldRgb->m_Reals[rgbi] / 255.0;\n"
"\n"
//Reduce saturation by the lsratio.
" RgbToHsv(&(newRgb->m_Real4), &(newHsv.m_Real4));\n"
" newHsv.m_Real4.y *= lsratio;\n"
" HsvToRgb(&(newHsv.m_Real4), &(newRgb->m_Real4));\n"
"\n"
" for (rgbi = 0; rgbi < 3; rgbi++)\n"//Unrolling and vectorizing makes no difference.
" newRgb->m_Reals[rgbi] *= 255.0;\n"
" }\n"
" else\n"
" {\n"
" newls = 255.0 / maxc;\n"
" adjhlp = -highPow;\n"
"\n"
" if (adjhlp > 1)\n"
" adjhlp = 1;\n"
"\n"
" if (maxa <= 255)\n"
" adjhlp = 1;\n"
"\n"
//Calculate the max-value color (ranged 0 - 1) interpolated with the old behavior.
" for (rgbi = 0; rgbi < 3; rgbi++)\n"//Unrolling, caching and vectorizing makes no difference.
" newRgb->m_Reals[rgbi] = ((1.0 - adjhlp) * newls + adjhlp * ls) * oldRgb->m_Reals[rgbi];\n"
" }\n"
"}\n"
"\n";
return os.str();
}
/// <summary>
/// Create the gamma correction kernel string used for early clipping.
/// </summary>
/// <param name="alphaCalc">True if channels equals 4 and transparency is desired, else false.</param>
/// <returns>The gamma correction kernel string used for early clipping</returns>
template <typename T>
string FinalAccumOpenCLKernelCreator<T>::CreateGammaCorrectionKernelString(bool alphaCalc)
{
ostringstream os;
string dataType;
os <<
ConstantDefinesString(typeid(T) == typeid(double)) <<
ClampRealFunctionString <<
UnionCLStructString <<
RgbToHsvFunctionString <<
HsvToRgbFunctionString <<
CalcAlphaFunctionString <<
CreateCalcNewRgbFunctionString(true) <<
SpatialFilterCLStructString <<
CreateGammaCorrectionFunctionString(true, alphaCalc, true, false);//Will only be used with float in this case, early clip. Will always alpha accum.
os << "__kernel void " << (alphaCalc ? m_GammaCorrectionWithAlphaCalcEntryPoint : m_GammaCorrectionWithoutAlphaCalcEntryPoint) << "(\n" <<
" __global real4reals* accumulator,\n"
" __constant SpatialFilterCL* spatialFilter\n"
")\n"
"{\n"
" int testGutter = 0;\n"
"\n"
" if (GLOBAL_ID_Y >= (spatialFilter->m_SuperRasH - testGutter) || GLOBAL_ID_X >= (spatialFilter->m_SuperRasW - testGutter))\n"
" return;\n"
"\n"
" unsigned int superIndex = (GLOBAL_ID_Y * spatialFilter->m_SuperRasW) + GLOBAL_ID_X;\n"
" __global real4reals* bucket = accumulator + superIndex;\n"
//Pass in an alphaBase and alphaScale of 0, 1 which means to just directly assign the computed alpha value.
" GammaCorrectionFloats(bucket, &(spatialFilter->m_Background[0]), spatialFilter->m_Gamma, spatialFilter->m_LinRange, spatialFilter->m_Vibrancy, spatialFilter->m_HighlightPower, 0.0, 1.0, &(bucket->m_Reals[0]));\n"
"}\n"
;
return os.str();
}
}

View File

@ -0,0 +1,87 @@
#pragma once
#include "EmberCLPch.h"
#include "EmberCLStructs.h"
#include "EmberCLFunctions.h"
/// <summary>
/// FinalAccumOpenCLKernelCreator class.
/// </summary>
namespace EmberCLns
{
/// <summary>
/// Class for creating the final accumulation code in OpenCL.
/// There are many conditionals in the CPU code to create the
/// final output image. This class creates many different kernels
/// with all conditionals and unnecessary calculations stripped out.
/// The conditionals are:
/// Early clip/late clip
/// Alpha channel, no alpha channel
/// Alpha with/without transparency
/// Template argument expected to be float or double.
/// </summary>
template <typename T>
class EMBERCL_API FinalAccumOpenCLKernelCreator
{
public:
FinalAccumOpenCLKernelCreator();
string GammaCorrectionWithAlphaCalcKernel();
string GammaCorrectionWithAlphaCalcEntryPoint();
string GammaCorrectionWithoutAlphaCalcKernel();
string GammaCorrectionWithoutAlphaCalcEntryPoint();
string FinalAccumEarlyClipKernel();
string FinalAccumEarlyClipEntryPoint();
string FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel();
string FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint();
string FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel();
string FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint();
string FinalAccumLateClipKernel();
string FinalAccumLateClipEntryPoint();
string FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel();
string FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint();
string FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel();
string FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint();
string GammaCorrectionEntryPoint(unsigned int channels, bool transparency);
string GammaCorrectionKernel(unsigned int channels, bool transparency);
string FinalAccumEntryPoint(bool earlyClip, unsigned int channels, bool transparency, T& alphaBase, T& alphaScale);
string FinalAccumKernel(bool earlyClip, unsigned int channels, bool transparency);
private:
string CreateFinalAccumKernelString(bool earlyClip, unsigned int channels, bool transparency);
string CreateGammaCorrectionKernelString(bool alphaCalc);
string CreateFinalAccumKernelString(bool earlyClip, bool alphaCalc, bool alphaAccum);
string CreateGammaCorrectionFunctionString(bool globalBucket, bool alphaCalc, bool alphaAccum, bool finalOut);
string CreateCalcNewRgbFunctionString(bool globalBucket);
string m_GammaCorrectionWithAlphaCalcKernel;
string m_GammaCorrectionWithAlphaCalcEntryPoint;
string m_GammaCorrectionWithoutAlphaCalcKernel;
string m_GammaCorrectionWithoutAlphaCalcEntryPoint;
string m_FinalAccumEarlyClipKernel;//False, false.
string m_FinalAccumEarlyClipEntryPoint;
string m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel;//True, true.
string m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint;
string m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel;//False, true.
string m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint;
string m_FinalAccumLateClipKernel;//False, false.
string m_FinalAccumLateClipEntryPoint;
string m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel;//True, true.
string m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint;
string m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel;//False, true.
string m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint;
};
template EMBERCL_API class FinalAccumOpenCLKernelCreator<float>;
#ifdef DO_DOUBLE
template EMBERCL_API class FinalAccumOpenCLKernelCreator<double>;
#endif
}

View File

@ -0,0 +1,785 @@
#include "EmberCLPch.h"
#include "IterOpenCLKernelCreator.h"
namespace EmberCLns
{
/// <summary>
/// Empty constructor that does nothing. The user must call the one which takes a bool
/// argument before using this class.
/// This constructor only exists so the class can be a member of a class.
/// </summary>
template <typename T>
IterOpenCLKernelCreator<T>::IterOpenCLKernelCreator()
{
}
/// <summary>
/// Constructor that sets up some basic entry point strings and creates
/// the zeroization kernel string since it requires no conditional inputs.
/// </summary>
template <typename T>
IterOpenCLKernelCreator<T>::IterOpenCLKernelCreator(bool nVidia)
{
m_NVidia = nVidia;
m_IterEntryPoint = "IterateKernel";
m_ZeroizeEntryPoint = "ZeroizeKernel";
m_ZeroizeKernel = CreateZeroizeKernelString();
}
/// <summary>
/// Accessors.
/// </summary>
template <typename T> string IterOpenCLKernelCreator<T>::ZeroizeKernel() { return m_ZeroizeKernel; }
template <typename T> string IterOpenCLKernelCreator<T>::ZeroizeEntryPoint() { return m_ZeroizeEntryPoint; }
template <typename T> string IterOpenCLKernelCreator<T>::IterEntryPoint() { return m_IterEntryPoint; }
/// <summary>
/// Create the iteration kernel string using the Cuburn method.
/// Template argument expected to be float or double.
/// </summary>
/// <param name="ember">The ember to create the kernel string for</param>
/// <param name="params">The parametric variation #define string</param>
/// <param name="doAccum">Debugging parameter to include or omit accumulating to the histogram. Default: true.</param>
/// <returns>The kernel string</returns>
template <typename T>
string IterOpenCLKernelCreator<T>::CreateIterKernelString(Ember<T>& ember, string& parVarDefines, bool lockAccum, bool doAccum)
{
bool doublePrecision = typeid(T) == typeid(double);
unsigned int i, v, varIndex, varCount, totalXformCount = ember.TotalXformCount();
ostringstream kernelIterBody, xformFuncs, os;
vector<Variation<T>*> variations;
xformFuncs << "\n" << parVarDefines << endl;
ember.GetPresentVariations(variations);
std::for_each(variations.begin(), variations.end(), [&](Variation<T>* var) { if (var) xformFuncs << var->OpenCLFuncsString(); });
for (i = 0; i < totalXformCount; i++)
{
Xform<T>* xform = ember.GetTotalXform(i);
unsigned int totalVarCount = xform->TotalVariationCount();
bool needPrecalcSumSquares = false;
bool needPrecalcSqrtSumSquares = false;
bool needPrecalcAngles = false;
bool needPrecalcAtanXY = false;
bool needPrecalcAtanYX = false;
v = varIndex = varCount = 0;
xformFuncs <<
"void Xform" << i << "(__constant XformCL* xform, __constant real_t* parVars, Point* inPoint, Point* outPoint, uint2* mwc)\n" <<
"{\n"
" real_t transX, transY, transZ;\n"
" real4 vIn, vOut = 0.0;\n";
//Determine if any variations, regular, pre, or post need precalcs.
while (Variation<T>* var = xform->GetVariation(v++))
{
needPrecalcSumSquares |= var->NeedPrecalcSumSquares();
needPrecalcSqrtSumSquares |= var->NeedPrecalcSqrtSumSquares();
needPrecalcAngles |= var->NeedPrecalcAngles();
needPrecalcAtanXY |= var->NeedPrecalcAtanXY();
needPrecalcAtanYX |= var->NeedPrecalcAtanYX();
}
if (needPrecalcSumSquares)
xformFuncs << "\treal_t precalcSumSquares;\n";
if (needPrecalcSqrtSumSquares)
xformFuncs << "\treal_t precalcSqrtSumSquares;\n";
if (needPrecalcAngles)
{
xformFuncs << "\treal_t precalcSina;\n";
xformFuncs << "\treal_t precalcCosa;\n";
}
if (needPrecalcAtanXY)
xformFuncs << "\treal_t precalcAtanxy;\n";
if (needPrecalcAtanYX)
xformFuncs << "\treal_t precalcAtanyx;\n";
xformFuncs << "\treal_t tempColor = outPoint->m_ColorX = xform->m_ColorSpeedCache + (xform->m_OneMinusColorCache * inPoint->m_ColorX);\n";
if (xform->PreVariationCount() + xform->VariationCount() == 0)
{
xformFuncs <<
" outPoint->m_X = (xform->m_A * inPoint->m_X) + (xform->m_B * inPoint->m_Y) + xform->m_C;\n" <<
" outPoint->m_Y = (xform->m_D * inPoint->m_X) + (xform->m_E * inPoint->m_Y) + xform->m_F;\n" <<
" outPoint->m_Z = inPoint->m_Z;\n";
}
else
{
xformFuncs <<
" transX = (xform->m_A * inPoint->m_X) + (xform->m_B * inPoint->m_Y) + xform->m_C;\n" <<
" transY = (xform->m_D * inPoint->m_X) + (xform->m_E * inPoint->m_Y) + xform->m_F;\n" <<
" transZ = inPoint->m_Z;\n";
varCount = xform->PreVariationCount();
if (varCount > 0)
{
xformFuncs << "\n\t//Apply each of the " << varCount << " pre variations in this xform.\n";
//Output the code for each pre variation in this xform.
for (varIndex = 0; varIndex < varCount; varIndex++)
{
if (Variation<T>* var = xform->GetVariation(varIndex))
{
xformFuncs << "\n\t//" << var->Name() << ".\n";
xformFuncs << var->PrecalcOpenCLString();
xformFuncs << xform->ReadOpenCLString(VARTYPE_PRE) << endl;
xformFuncs << var->OpenCLString() << endl;
xformFuncs << xform->WriteOpenCLString(VARTYPE_PRE, var->AssignType()) << endl;
}
}
}
if (xform->VariationCount() > 0)
{
if (xform->NeedPrecalcSumSquares())
xformFuncs << "\tprecalcSumSquares = SQR(transX) + SQR(transY);\n";
if (xform->NeedPrecalcSqrtSumSquares())
xformFuncs << "\tprecalcSqrtSumSquares = sqrt(precalcSumSquares);\n";
if (xform->NeedPrecalcAngles())
{
xformFuncs << "\tprecalcSina = transX / precalcSqrtSumSquares;\n";
xformFuncs << "\tprecalcCosa = transY / precalcSqrtSumSquares;\n";
}
if (xform->NeedPrecalcAtanXY())
xformFuncs << "\tprecalcAtanxy = atan2(transX, transY);\n";
if (xform->NeedPrecalcAtanYX())
xformFuncs << "\tprecalcAtanyx = atan2(transY, transX);\n";
xformFuncs << "\n\toutPoint->m_X = 0;";
xformFuncs << "\n\toutPoint->m_Y = 0;";
xformFuncs << "\n\toutPoint->m_Z = 0;\n";
xformFuncs << "\n\t//Apply each of the " << xform->VariationCount() << " regular variations in this xform.\n\n";
xformFuncs << xform->ReadOpenCLString(VARTYPE_REG);
varCount += xform->VariationCount();
//Output the code for each regular variation in this xform.
for (; varIndex < varCount; varIndex++)
{
if (Variation<T>* var = xform->GetVariation(varIndex))
{
xformFuncs << "\n\t//" << var->Name() << ".\n"
<< var->OpenCLString() << (varIndex == varCount - 1 ? "\n" : "\n\n")
<< xform->WriteOpenCLString(VARTYPE_REG, ASSIGNTYPE_SUM);
}
}
}
else
{
xformFuncs <<
" outPoint->m_X = transX;\n"
" outPoint->m_Y = transY;\n"
" outPoint->m_Z = transZ;\n";
}
}
if (xform->PostVariationCount() > 0)
{
varCount += xform->PostVariationCount();
xformFuncs << "\n\t//Apply each of the " << xform->PostVariationCount() << " post variations in this xform.\n";
//Output the code for each post variation in this xform.
for (; varIndex < varCount; varIndex++)
{
if (Variation<T>* var = xform->GetVariation(varIndex))
{
xformFuncs << "\n\t//" << var->Name() << ".\n";
xformFuncs << var->PrecalcOpenCLString();
xformFuncs << xform->ReadOpenCLString(VARTYPE_POST) << endl;
xformFuncs << var->OpenCLString() << endl;
xformFuncs << xform->WriteOpenCLString(VARTYPE_POST, var->AssignType()) << (varIndex == varCount - 1 ? "\n" : "\n\n");
}
}
}
if (xform->HasPost())
{
xformFuncs <<
"\n\t//Apply post affine transform.\n"
"\treal_t tempX = outPoint->m_X;\n"
"\n"
"\toutPoint->m_X = (xform->m_PostA * tempX) + (xform->m_PostB * outPoint->m_Y) + xform->m_PostC;\n" <<
"\toutPoint->m_Y = (xform->m_PostD * tempX) + (xform->m_PostE * outPoint->m_Y) + xform->m_PostF;\n";
}
xformFuncs << "\toutPoint->m_ColorX = outPoint->m_ColorX + xform->m_DirectColor * (tempColor - outPoint->m_ColorX);\n";
xformFuncs << "}\n"
<< "\n";
}
os <<
ConstantDefinesString(doublePrecision) <<
InlineMathFunctionsString <<
ClampRealFunctionString <<
RandFunctionString <<
PointCLStructString <<
XformCLStructString <<
EmberCLStructString <<
UnionCLStructString <<
CarToRasCLStructString <<
CarToRasFunctionString <<
AtomicString(doublePrecision, m_NVidia) <<
xformFuncs.str() <<
"__kernel void " << m_IterEntryPoint << "(\n" <<
" uint iterCount,\n"
" uint fuseCount,\n"
" uint seed,\n"
" __constant EmberCL* ember,\n"
" __constant real_t* parVars,\n"
" __global uchar* xformDistributions,\n"//Using uchar is quicker than uint. Can't be constant because the size can be too large to fit when using xaos.//FINALOPT
" __constant CarToRasCL* carToRas,\n"
" __global real4reals* histogram,\n"
" uint histSize,\n"
" __read_only image2d_t palette,\n"
" __global Point* points\n"
"\t)\n"
"{\n"
" bool fuse, ok;\n"
" uint threadIndex = INDEX_IN_BLOCK_2D;\n"
" uint i, itersToDo;\n"
" uint consec = 0;\n"
//" int badvals = 0;\n"
" uint histIndex;\n"
" real_t p00, p01;\n"
" Point firstPoint, secondPoint, tempPoint;\n"
" uint2 mwc;\n"
" float4 palColor1;\n"
" int2 iPaletteCoord;\n"
" const sampler_t paletteSampler = CLK_NORMALIZED_COORDS_FALSE |\n"//Coords from 0 to 255.
" CLK_ADDRESS_CLAMP_TO_EDGE |\n"//Clamp to edge
" CLK_FILTER_NEAREST;\n"//Don't interpolate
" uint threadXY = (THREAD_ID_X + THREAD_ID_Y);\n"
" uint threadXDivRows = (THREAD_ID_X / (NTHREADS / THREADS_PER_WARP));\n"
" uint threadsMinus1 = NTHREADS - 1;\n"
;
os <<
"\n"
" __local Point swap[NTHREADS];\n"
" __local uint xfsel[NWARPS];\n"
"\n"
" unsigned int pointsIndex = INDEX_IN_GRID_2D;\n"
" mwc.x = (pointsIndex + 1 * seed) & 0x7FFFFFFF;\n"
" mwc.y = ((BLOCK_ID_X + 1) + (pointsIndex + 1) * seed) & 0x7FFFFFFF;\n"
" iPaletteCoord.y = 0;\n"
"\n"
" if (fuseCount > 0)\n"
" {\n"
" fuse = true;\n"
" itersToDo = fuseCount;\n"
" firstPoint.m_X = MwcNextNeg1Pos1(&mwc);\n"
" firstPoint.m_Y = MwcNextNeg1Pos1(&mwc);\n"
" firstPoint.m_Z = 0.0;\n"
" firstPoint.m_ColorX = MwcNext01(&mwc);\n"
" firstPoint.m_LastXfUsed = 0;\n"
" }\n"
" else\n"
" {\n"
" fuse = false;\n"
" itersToDo = iterCount;\n"
" firstPoint = points[pointsIndex];\n"
" }\n"
"\n";
//This is done once initially here and then again after each swap-sync in the main loop.
//This along with the randomness that the point shuffle provides gives sufficient randomness
//to produce results identical to those produced on the CPU.
os <<
" if (THREAD_ID_Y == 0 && THREAD_ID_X < NWARPS)\n"
" xfsel[THREAD_ID_X] = MwcNext(&mwc) % " << CHOOSE_XFORM_GRAIN << ";\n"//It's faster to do the % here ahead of time than every time an xform is looked up to use inside the loop.
"\n"
" barrier(CLK_LOCAL_MEM_FENCE);\n"
"\n"
" for (i = 0; i < itersToDo; i++)\n"
" {\n";
os <<
" consec = 0;\n"
"\n"
" do\n"
" {\n";
if (ember.XaosPresent())
{
os <<
" secondPoint.m_LastXfUsed = xformDistributions[xfsel[THREAD_ID_Y] + (" << CHOOSE_XFORM_GRAIN << " * (firstPoint.m_LastXfUsed + 1u))];\n\n";
}
else
{
os <<
" secondPoint.m_LastXfUsed = xformDistributions[xfsel[THREAD_ID_Y]];\n\n";
}
for (i = 0; i < ember.XformCount(); i++)
{
if (i == 0)
os <<
" if (secondPoint.m_LastXfUsed == " << i << ")\n";
else
os <<
" else if (secondPoint.m_LastXfUsed == " << i << ")\n";
os <<
" {\n" <<
" Xform" << i << "(&(ember->m_Xforms[" << i << "]), parVars, &firstPoint, &secondPoint, &mwc);\n" <<
" }\n";
}
os <<
"\n"
" ok = !BadVal(secondPoint.m_X) && !BadVal(secondPoint.m_Y);\n"
//" ok = !BadVal(secondPoint.m_X) && !BadVal(secondPoint.m_Y) && !BadVal(secondPoint.m_Z);\n"
"\n"
" if (!ok)\n"
" {\n"
" firstPoint.m_X = MwcNextNeg1Pos1(&mwc);\n"
" firstPoint.m_Y = MwcNextNeg1Pos1(&mwc);\n"
" firstPoint.m_Z = 0.0;\n"
" firstPoint.m_ColorX = secondPoint.m_ColorX;\n"
" consec++;\n"
//" badvals++;\n"
" }\n"
" }\n"
" while (!ok && consec < 5);\n"
"\n"
" if (!ok)\n"
" {\n"
" secondPoint.m_X = MwcNextNeg1Pos1(&mwc);\n"
" secondPoint.m_Y = MwcNextNeg1Pos1(&mwc);\n"
" secondPoint.m_Z = 0.0;\n"
" }\n"
"\n"//Rotate points between threads. This is how randomization is achieved.
" uint swr = threadXY + ((i & 1u) * threadXDivRows);\n"
" uint sw = (swr * THREADS_PER_WARP + THREAD_ID_X) & threadsMinus1;\n"
"\n"
//Write to another thread's location.
" swap[sw] = secondPoint;\n"
"\n"
//Populate randomized xform index buffer with new random values.
" if (THREAD_ID_Y == 0 && THREAD_ID_X < NWARPS)\n"
" xfsel[THREAD_ID_X] = MwcNext(&mwc) % " << CHOOSE_XFORM_GRAIN << ";\n"
"\n"
" barrier(CLK_LOCAL_MEM_FENCE);\n"
"\n"
//Another thread will have written to this thread's location, so read the new value and use it for accumulation below.
" firstPoint = swap[threadIndex];\n"
"\n"
" if (fuse)\n"
" {\n"
" if (i >= fuseCount - 1)\n"
" {\n"
" i = 0;\n"
" fuse = false;\n"
" itersToDo = iterCount;\n"
" barrier(CLK_LOCAL_MEM_FENCE);\n"//Sort of seems necessary, sort of doesn't. Makes no speed difference.
" }\n"
"\n"
" continue;\n"
" }\n"
"\n";
if (ember.UseFinalXform())
{
//CPU takes an extra step here to preserve the opacity of the randomly selected xform, rather than the final xform's opacity.
//The same thing takes place here automatically because secondPoint.m_LastXfUsed is used below to retrieve the opacity when accumulating.
os <<
" if ((ember->m_Xforms[ember->m_FinalXformIndex].m_Opacity == 1) || (MwcNext01(&mwc) < ember->m_Xforms[ember->m_FinalXformIndex].m_Opacity))\n"
" {\n"
" Xform" << (ember.TotalXformCount() - 1) << "(&(ember->m_Xforms[ember->m_FinalXformIndex]), parVars, &secondPoint, &tempPoint, &mwc);\n"
" secondPoint = tempPoint;\n"
" }\n"
"\n";
}
os << CreateProjectionString(ember);
if (doAccum)
{
os <<
" p00 = secondPoint.m_X - ember->m_CenterX;\n"
" p01 = secondPoint.m_Y - ember->m_CenterY;\n"
" tempPoint.m_X = (p00 * ember->m_RotA) + (p01 * ember->m_RotB) + ember->m_CenterX;\n"
" tempPoint.m_Y = (p00 * ember->m_RotD) + (p01 * ember->m_RotE) + ember->m_CenterY;\n"
"\n"
//Add this point to the appropriate location in the histogram.
" if (CarToRasInBounds(carToRas, &tempPoint))\n"
" {\n"
" CarToRasConvertPointToSingle(carToRas, &tempPoint, &histIndex);\n"
"\n"
" if (histIndex < histSize)\n"//Provides an extra level of safety and makes no speed difference.
" {\n";
//Basic texture index interoplation does not produce identical results
//to the CPU. So the code here must explicitly do the same thing and not
//rely on the GPU texture coordinate lookup.
if (ember.m_PaletteMode == PALETTE_LINEAR)
{
os <<
" real_t colorIndexFrac;\n"
" real_t colorIndex = secondPoint.m_ColorX * COLORMAP_LENGTH;\n"
" int intColorIndex = (int)colorIndex;\n"
" float4 palColor2;\n"
"\n"
" if (intColorIndex < 0)\n"
" {\n"
" intColorIndex = 0;\n"
" colorIndexFrac = 0;\n"
" }\n"
" else if (intColorIndex >= COLORMAP_LENGTH_MINUS_1)\n"
" {\n"
" intColorIndex = COLORMAP_LENGTH_MINUS_1 - 1;\n"
" colorIndexFrac = 1.0;\n"
" }\n"
" else\n"
" {\n"
" colorIndexFrac = colorIndex - (real_t)intColorIndex;\n"//Interpolate between intColorIndex and intColorIndex + 1.
" }\n"
"\n"
" iPaletteCoord.x = intColorIndex;\n"//Palette operations are strictly float because OpenCL does not support dp64 textures.
" palColor1 = read_imagef(palette, paletteSampler, iPaletteCoord);\n"
" iPaletteCoord.x += 1;\n"
" palColor2 = read_imagef(palette, paletteSampler, iPaletteCoord);\n"
" palColor1 = (palColor1 * (1.0f - (float)colorIndexFrac)) + (palColor2 * (float)colorIndexFrac);\n";//The 1.0f here *must* have the 'f' suffix at the end to compile.
}
else if (ember.m_PaletteMode == PALETTE_STEP)
{
os <<
" iPaletteCoord.x = (int)(secondPoint.m_ColorX * COLORMAP_LENGTH);\n"
" palColor1 = read_imagef(palette, paletteSampler, iPaletteCoord);\n";
}
if (lockAccum)
{
if (typeid(T) == typeid(double))
{
os <<
" AtomicAdd(&(histogram[histIndex].m_Reals[0]), (real_t)palColor1.x * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n"//Always apply opacity, even though it's usually 1.
" AtomicAdd(&(histogram[histIndex].m_Reals[1]), (real_t)palColor1.y * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n"
" AtomicAdd(&(histogram[histIndex].m_Reals[2]), (real_t)palColor1.z * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n"
" AtomicAdd(&(histogram[histIndex].m_Reals[3]), (real_t)palColor1.w * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n";
}
else
{
os <<
" AtomicAdd(&(histogram[histIndex].m_Reals[0]), palColor1.x * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n"//Always apply opacity, even though it's usually 1.
" AtomicAdd(&(histogram[histIndex].m_Reals[1]), palColor1.y * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n"
" AtomicAdd(&(histogram[histIndex].m_Reals[2]), palColor1.z * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n"
" AtomicAdd(&(histogram[histIndex].m_Reals[3]), palColor1.w * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n";
}
}
else
{
if (typeid(T) == typeid(double))
{
os <<
" real4 realColor;\n"
"\n"
" realColor.x = (real_t)palColor1.x;\n"
" realColor.y = (real_t)palColor1.y;\n"
" realColor.z = (real_t)palColor1.z;\n"
" realColor.w = (real_t)palColor1.w;\n"
" histogram[histIndex].m_Real4 += (realColor * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n";
}
else
{
os <<
" histogram[histIndex].m_Real4 += (palColor1 * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n";
}
}
os <<
" }\n"//histIndex < histSize.
" }\n"//CarToRasInBounds.
"\n"
" barrier(CLK_GLOBAL_MEM_FENCE);\n";//Barrier every time, whether or not the point was in bounds, else artifacts will occur when doing strips.
}
os <<
" }\n"//Main for loop.
"\n"
//At this point, iterating for this round is done, so write the final points back out
//to the global points buffer to be used as inputs for the next round. This preserves point trajectory
//between kernel calls.
" points[pointsIndex] = firstPoint;\n"
" barrier(CLK_GLOBAL_MEM_FENCE);\n"
"}\n";
return os.str();
}
/// <summary>
/// Create an OpenCL string of #defines and a corresponding host side vector for parametric variation values.
/// Parametric variations present a special problem in the iteration code.
/// The values can't be passed in with the array of other xform values because
/// the length of the parametric values is unknown.
/// This is solved by passing a separate buffer of values dedicated specifically
/// to parametric variations.
/// In OpenCL, a series of #define constants are declared which specify the indices in
/// the buffer where the various values are stored.
/// The possibility of a parametric variation type being present in multiple xforms is taken
/// into account by appending the xform index to the #define, thus making each unique.
/// The kernel creator then uses these to retrieve the values in the iteration code.
/// Example:
/// Xform1: Curl (curl_c1: 1.1, curl_c2: 2.2)
/// Xform2: Curl (curl_c1: 4.4, curl_c2: 5.5)
/// Xform3: Blob (blob_low: 1, blob_high: 2, blob_waves: 3)
///
/// Host vector to be passed as arg to the iter kernel call:
/// [1.1][2.2][4.4][5.5][1][2][3]
///
/// #defines in OpenCL to access the buffer:
///
/// #define CURL_C1_1 0
/// #define CURL_C2_1 1
/// #define CURL_C1_2 2
/// #define CURL_C2_2 3
/// #define BLOB_LOW_3 4
/// #define BLOB_HIGH_3 5
/// #define BLOB_WAVES_ 6
///
/// The variations the use these #defines by first looking up the index of the
/// xform they belong to in the parent ember and generating the OpenCL string based on that
/// in their overriden OpenCLString() functions.
/// Template argument expected to be float or double.
/// </summary>
/// <param name="ember">The ember to create the values from</param>
/// <param name="params">The string,vector pair to store the values in</param>
/// <param name="doVals">True if the vector should be populated, else false. Default: true.</param>
/// <param name="doString">True if the string should be populated, else false. Default: true.</param>
template <typename T>
void IterOpenCLKernelCreator<T>::ParVarIndexDefines(Ember<T>& ember, pair<string, vector<T>>& params, bool doVals, bool doString)
{
unsigned int i, j, k, size = 0, xformCount = ember.TotalXformCount();
Xform<T>* xform;
ParametricVariation<T>* parVar;
ostringstream os;
if (doVals)
params.second.clear();
for (i = 0; i < xformCount; i++)
{
if (xform = ember.GetTotalXform(i))
{
unsigned int varCount = xform->TotalVariationCount();
for (j = 0; j < varCount; j++)
{
if (parVar = dynamic_cast<ParametricVariation<T>*>(xform->GetVariation(j)))
{
for (k = 0; k < parVar->ParamCount(); k++)
{
if (doString)
os << "#define " << ToUpper(parVar->Params()[k].Name()) << "_" << i << " " << size << endl;//Uniquely identify this param in this variation in this xform.
if (doVals)
params.second.push_back(parVar->Params()[k].ParamVal());
size++;
}
}
}
}
}
if (doString)
{
os << "\n";
params.first = os.str();
}
}
/// <summary>
/// Determine whether the two embers passed in differ enough
/// to require a rebuild of the iteration code.
/// A rebuild is required if they differ in the following ways:
/// Xform count
/// Final xform presence
/// Xaos presence
/// Palette accumulation mode
/// Xform post affine presence
/// Variation count
/// Variation type
/// Template argument expected to be float or double.
/// </summary>
/// <param name="ember1">The first ember to compare</param>
/// <param name="ember2">The second ember to compare</param>
/// <returns>True if a rebuild is required, else false</returns>
template <typename T>
bool IterOpenCLKernelCreator<T>::IsBuildRequired(Ember<T>& ember1, Ember<T>& ember2)
{
unsigned int i, j, xformCount = ember1.TotalXformCount();
if (xformCount != ember2.TotalXformCount())
return true;
if (ember1.UseFinalXform() != ember2.UseFinalXform())
return true;
if (ember1.XaosPresent() != ember2.XaosPresent())
return true;
if (ember1.m_PaletteMode != ember2.m_PaletteMode)
return true;
if (ember1.ProjBits() != ember2.ProjBits())
return true;
for (i = 0; i < xformCount; i++)
{
Xform<T>* xform1 = ember1.GetTotalXform(i);
Xform<T>* xform2 = ember2.GetTotalXform(i);
unsigned int varCount = xform1->TotalVariationCount();
if (xform1->HasPost() != xform2->HasPost())
return true;
if (varCount != xform2->TotalVariationCount())
return true;
for (j = 0; j < varCount; j++)
if (xform1->GetVariation(j)->VariationId() != xform2->GetVariation(j)->VariationId())
return true;
}
return false;
}
/// <summary>
/// Create the zeroize kernel string.
/// OpenCL comes with no way to zeroize a buffer like memset()
/// would do on the CPU. So a special kernel must be ran to set a range
/// of memory addresses to zero.
/// </summary>
/// <returns>The kernel string</returns>
template <typename T>
string IterOpenCLKernelCreator<T>::CreateZeroizeKernelString()
{
ostringstream os;
os <<
ConstantDefinesString(typeid(T) == typeid(double)) <<//Double precision doesn't matter here since it's not used.
"__kernel void " << m_ZeroizeEntryPoint << "(__global uchar* buffer, uint width, uint height)\n"
"{\n"
" if (GLOBAL_ID_X >= width || GLOBAL_ID_Y >= height)\n"
" return;\n"
"\n"
" buffer[(GLOBAL_ID_Y * width) + GLOBAL_ID_X] = 0;\n"//Can't use INDEX_IN_GRID_2D here because the grid might be larger than the buffer to make even dimensions.
" barrier(CLK_GLOBAL_MEM_FENCE);\n"//Just to be safe.
"}\n"
"\n";
return os.str();
}
/// <summary>
/// Create the string for 3D projection based on the 3D values of the ember.
/// Projection is done on the second point.
/// If any of these fields toggle between 0 and nonzero between runs, a recompile is triggered.
/// </summary>
/// <param name="ember">The ember to create the projection string for</param>
/// <returns>The kernel string</returns>
template <typename T>
string IterOpenCLKernelCreator<T>::CreateProjectionString(Ember<T>& ember)
{
unsigned int projBits = ember.ProjBits();
ostringstream os;
if (projBits)
{
if (projBits & PROJBITS_BLUR)
{
if (projBits & PROJBITS_YAW)
{
os <<
" real_t dsin, dcos;\n"
" real_t t = MwcNext01(&mwc) * M_2PI;\n"
" real_t z = secondPoint.m_Z - ember->m_CamZPos;\n"
" real_t x = ember->m_C00 * secondPoint.m_X + ember->m_C10 * secondPoint.m_Y;\n"
" real_t y = ember->m_C01 * secondPoint.m_X + ember->m_C11 * secondPoint.m_Y + ember->m_C21 * z;\n"
"\n"
" z = ember->m_C02 * secondPoint.m_X + ember->m_C12 * secondPoint.m_Y + ember->m_C22 * z;\n"
"\n"
" real_t zr = 1 - ember->m_CamPerspective * z;\n"
" real_t dr = MwcNext01(&mwc) * ember->m_BlurCoef * z;\n"
"\n"
" dsin = sin(t);\n"
" dcos = cos(t);\n"
"\n"
" secondPoint.m_X = (x + dr * dcos) / zr;\n"
" secondPoint.m_Y = (y + dr * dsin) / zr;\n"
" secondPoint.m_Z -= ember->m_CamZPos;\n";
}
else
{
os <<
" real_t y, z, zr;\n"
" real_t dsin, dcos;\n"
" real_t t = MwcNext01(&mwc) * M_2PI;\n"
"\n"
" z = secondPoint.m_Z - ember->m_CamZPos;\n"
" y = ember->m_C11 * secondPoint.m_Y + ember->m_C21 * z;\n"
" z = ember->m_C12 * secondPoint.m_Y + ember->m_C22 * z;\n"
" zr = 1 - ember->m_CamPerspective * z;\n"
"\n"
" dsin = sin(t);\n"
" dcos = cos(t);\n"
"\n"
" real_t dr = MwcNext01(&mwc) * ember->m_BlurCoef * z;\n"
"\n"
" secondPoint.m_X = (secondPoint.m_X + dr * dcos) / zr;\n"
" secondPoint.m_Y = (y + dr * dsin) / zr;\n"
" secondPoint.m_Z -= ember->m_CamZPos;\n";
}
}
else if ((projBits & PROJBITS_PITCH) || (projBits & PROJBITS_YAW))
{
if (projBits & PROJBITS_YAW)
{
os <<
" real_t z = secondPoint.m_Z - ember->m_CamZPos;\n"
" real_t x = ember->m_C00 * secondPoint.m_X + ember->m_C10 * secondPoint.m_Y;\n"
" real_t y = ember->m_C01 * secondPoint.m_X + ember->m_C11 * secondPoint.m_Y + ember->m_C21 * z;\n"
" real_t zr = 1 - ember->m_CamPerspective * (ember->m_C02 * secondPoint.m_X + ember->m_C12 * secondPoint.m_Y + ember->m_C22 * z);\n"
"\n"
" secondPoint.m_X = x / zr;\n"
" secondPoint.m_Y = y / zr;\n"
" secondPoint.m_Z -= ember->m_CamZPos;\n";
}
else
{
os <<
" real_t z = secondPoint.m_Z - ember->m_CamZPos;\n"
" real_t y = ember->m_C11 * secondPoint.m_Y + ember->m_C21 * z;\n"
" real_t zr = 1 - ember->m_CamPerspective * (ember->m_C12 * secondPoint.m_Y + ember->m_C22 * z);\n"
"\n"
" secondPoint.m_X /= zr;\n"
" secondPoint.m_Y = y / zr;\n"
" secondPoint.m_Z -= ember->m_CamZPos;\n";
}
}
else
{
os <<
" real_t zr = 1 - ember->m_CamPerspective * (secondPoint.m_Z - ember->m_CamZPos);\n"
"\n"
" secondPoint.m_X /= zr;\n"
" secondPoint.m_Y /= zr;\n"
" secondPoint.m_Z -= ember->m_CamZPos;\n";
}
}
return os.str();
}
}

View File

@ -0,0 +1,89 @@
#pragma once
#include "EmberCLPch.h"
#include "EmberCLStructs.h"
#include "EmberCLFunctions.h"
/// <summary>
/// IterOpenCLKernelCreator class.
/// </summary>
namespace EmberCLns
{
/// <summary>
/// Class for creating the main iteration code in OpenCL.
/// It uses the Cuburn method of iterating where all conditionals
/// are stripped out and a specific kernel is compiled at run-time.
/// It uses a very sophisticated method for randomization that avoids
/// the problem of warp/wavefront divergence that would occur if every
/// thread selected a random xform to apply.
/// This only works with embers of type float, double is not supported.
/// </summary>
template <typename T>
class EMBERCL_API IterOpenCLKernelCreator
{
public:
IterOpenCLKernelCreator();
IterOpenCLKernelCreator(bool nVidia);
string ZeroizeKernel();
string ZeroizeEntryPoint();
string IterEntryPoint();
string CreateIterKernelString(Ember<T>& ember, string& parVarDefines, bool lockAccum = false, bool doAccum = true);
static void ParVarIndexDefines(Ember<T>& ember, pair<string, vector<T>>& params, bool doVals = true, bool doString = true);
static bool IsBuildRequired(Ember<T>& ember1, Ember<T>& ember2);
private:
string CreateZeroizeKernelString();
string CreateProjectionString(Ember<T>& ember);
string m_IterEntryPoint;
string m_ZeroizeKernel;
string m_ZeroizeEntryPoint;
bool m_NVidia;
};
template EMBERCL_API class IterOpenCLKernelCreator<float>;
#ifdef DO_DOUBLE
template EMBERCL_API class IterOpenCLKernelCreator<double>;
#endif
//
//template EMBERCL_API string IterOpenCLKernelCreator::CreateIterKernelString<float>(Ember<float>& ember, string& parVarDefines, bool lockAccum, bool doAccum);
//template EMBERCL_API string IterOpenCLKernelCreator::CreateIterKernelString<double>(Ember<double>& ember, string& parVarDefines, bool lockAccum, bool doAccum);
//
//template EMBERCL_API void IterOpenCLKernelCreator::ParVarIndexDefines<float>(Ember<float>& ember, pair<string, vector<float>>& params, bool doVals, bool doString);
//template EMBERCL_API void IterOpenCLKernelCreator::ParVarIndexDefines<double>(Ember<double>& ember, pair<string, vector<double>>& params, bool doVals, bool doString);
//
//template EMBERCL_API bool IterOpenCLKernelCreator::IsBuildRequired<float>(Ember<float>& ember1, Ember<float>& ember2);
//template EMBERCL_API bool IterOpenCLKernelCreator::IsBuildRequired<double>(Ember<double>& ember1, Ember<double>& ember2);
#ifdef OPEN_CL_TEST_AREA
typedef void (*KernelFuncPointer) (unsigned int gridWidth, unsigned int gridHeight, unsigned int blockWidth, unsigned int blockHeight,
unsigned int BLOCK_ID_X, unsigned int BLOCK_ID_Y, unsigned int THREAD_ID_X, unsigned int THREAD_ID_Y);
static void OpenCLSim(unsigned int gridWidth, unsigned int gridHeight, unsigned int blockWidth, unsigned int blockHeight, KernelFuncPointer func)
{
cout << "OpenCLSim(): " << endl;
cout << " Params: " << endl;
cout << " gridW: " << gridWidth << endl;
cout << " gridH: " << gridHeight << endl;
cout << " blockW: " << blockWidth << endl;
cout << " blockH: " << blockHeight << endl;
for (unsigned int i = 0; i < gridHeight; i += blockHeight)
{
for (unsigned int j = 0; j < gridWidth; j += blockWidth)
{
for (unsigned int k = 0; k < blockHeight; k++)
{
for (unsigned int l = 0; l < blockWidth; l++)
{
func(gridWidth, gridHeight, blockWidth, blockHeight, j / blockWidth, i / blockHeight, l, k);
}
}
}
}
}
#endif
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,219 @@
#pragma once
#include "EmberCLPch.h"
/// <summary>
/// OpenCLWrapper, Spk, NamedBuffer, NamedImage2D, NamedImage2DGL classes.
/// </summary>
namespace EmberCLns
{
#if CL_VERSION_1_2
#define IMAGEGL2D cl::ImageGL
#else
#define IMAGEGL2D cl::Image2DGL
#endif
/// <summary>
/// Class to contain all of the things needed to store an OpenCL program.
/// The name of it, the source, the compiled program object and the kernel.
/// </summary>
class EMBERCL_API Spk
{
public:
string m_Name;
cl::Program::Sources m_Source;
cl::Program m_Program;
cl::Kernel m_Kernel;
};
/// <summary>
/// Class to hold an OpenCL buffer with a name to identify it by.
/// </summary>
class EMBERCL_API NamedBuffer
{
public:
NamedBuffer()
{
}
NamedBuffer(cl::Buffer& buff, string name)
{
m_Buffer = buff;
m_Name = name;
}
cl::Buffer m_Buffer;
string m_Name;
};
/// <summary>
/// Class to hold a 2D image with a name to identify it by.
/// </summary>
class EMBERCL_API NamedImage2D
{
public:
NamedImage2D()
{
}
NamedImage2D(cl::Image2D& image, string name)
{
m_Image = image;
m_Name = name;
}
cl::Image2D m_Image;
string m_Name;
};
/// <summary>
/// Class to hold a 2D image that is mapped to an OpenGL texture
/// and a name to identify it by.
/// </summary>
class EMBERCL_API NamedImage2DGL
{
public:
NamedImage2DGL()
{
}
NamedImage2DGL(IMAGEGL2D& image, string name)
{
m_Image = image;
m_Name = name;
}
IMAGEGL2D m_Image;
string m_Name;
};
/// <summary>
/// Running kernels in OpenCL can require quite a bit of setup, tear down and
/// general housekeeping. This class helps shield the user from such hassles.
/// It's main utility is in holding collections of programs, buffers and images
/// all identified by names. That way, a user can access them as needed without
/// having to pollute their code.
/// In addition, writing to an existing object by name determines if the object
/// can be overwritten, or if it needs to be deleted and replaced by the new one.
/// This class derives from EmberReport, so the caller is able
/// to retrieve a text dump of error information if any errors occur.
/// </summary>
class EMBERCL_API OpenCLWrapper : public EmberReport
{
public:
OpenCLWrapper();
bool CheckOpenCL();
bool Init(unsigned int platform, unsigned int device, bool shared = false);
//Programs.
bool AddProgram(std::string name, std::string& program, std::string& entryPoint, bool doublePrecision);
void ClearPrograms();
//Buffers.
bool AddBuffer(string name, size_t size, cl_mem_flags flags = CL_MEM_READ_WRITE);
bool AddAndWriteBuffer(string name, void* data, size_t size);
bool WriteBuffer(string name, void* data, size_t size);
bool WriteBuffer(unsigned int bufferIndex, void* data, size_t size);
bool ReadBuffer(string name, void* data, size_t size);
bool ReadBuffer(unsigned int bufferIndex, void* data, size_t size);
int FindBufferIndex(string name);
unsigned int GetBufferSize(string name);
unsigned int GetBufferSize(unsigned int bufferIndex);
void ClearBuffers();
//Images.
bool AddAndWriteImage(string name, cl_mem_flags flags, const cl::ImageFormat& format, ::size_t width, ::size_t height, ::size_t row_pitch, void* data = NULL, bool shared = false, GLuint texName = 0);
bool WriteImage2D(unsigned int index, bool shared, ::size_t width, ::size_t height, ::size_t row_pitch, void* data);
bool ReadImage(string name, ::size_t width, ::size_t height, ::size_t row_pitch, bool shared, void* data);
bool ReadImage(unsigned int imageIndex, ::size_t width, ::size_t height, ::size_t row_pitch, bool shared, void* data);
int FindImageIndex(string name, bool shared);
unsigned int GetImageSize(string name, bool shared);
unsigned int GetImageSize(unsigned int imageIndex, bool shared);
bool CompareImageParams(cl::Image& image, cl_mem_flags flags, const cl::ImageFormat& format, ::size_t width, ::size_t height, ::size_t row_pitch);
void ClearImages(bool shared);
bool CreateImage2D(cl::Image2D& image2D, cl_mem_flags flags, cl::ImageFormat format, ::size_t width, ::size_t height, ::size_t row_pitch = 0, void* data = NULL);
bool CreateImage2DGL(IMAGEGL2D& image2DGL, cl_mem_flags flags, GLenum target, GLint miplevel, GLuint texobj);
bool EnqueueAcquireGLObjects(string name);
bool EnqueueAcquireGLObjects(IMAGEGL2D& image);
bool EnqueueReleaseGLObjects(string name);
bool EnqueueReleaseGLObjects(IMAGEGL2D& image);
bool EnqueueAcquireGLObjects(const VECTOR_CLASS<cl::Memory>* memObjects = NULL);
bool EnqueueReleaseGLObjects(const VECTOR_CLASS<cl::Memory>* memObjects = NULL);
bool CreateSampler(cl::Sampler& sampler, cl_bool normalizedCoords, cl_addressing_mode addressingMode, cl_filter_mode filterMode);
//Arguments.
bool SetBufferArg(unsigned int kernelIndex, unsigned int argIndex, string name);
bool SetBufferArg(unsigned int kernelIndex, unsigned int argIndex, unsigned int bufferIndex);
bool SetImageArg(unsigned int kernelIndex, unsigned int argIndex, bool shared, string name);
bool SetImageArg(unsigned int kernelIndex, unsigned int argIndex, bool shared, unsigned int imageIndex);
/// <summary>
/// Set an argument in the specified kernel, at the specified argument index.
/// Must keep this here in the .h because it's templated.
/// </summary>
/// <param name="kernelIndex">Index of the kernel whose argument will be set</param>
/// <param name="argIndex">Index of the argument to set</param>
/// <param name="arg">The argument value to set</param>
/// <returns>True if success, else false</returns>
template <typename T>
bool SetArg(unsigned int kernelIndex, unsigned int argIndex, T arg)
{
if (m_Init && kernelIndex < m_Programs.size())
{
cl_int err = m_Programs[kernelIndex].m_Kernel.setArg(argIndex, arg);
return CheckCL(err, "cl::Kernel::setArg()");
}
return false;
}
//Kernels.
int FindKernelIndex(string name);
bool RunKernel(unsigned int kernelIndex, unsigned int totalGridWidth, unsigned int totalGridHeight, unsigned int totalGridDepth, unsigned int blockWidth, unsigned int blockHeight, unsigned int blockDepth);
//Info.
template<typename T>
T GetInfo(size_t platform, size_t device, cl_device_info name);
string PlatformName(size_t platform);
vector<string> PlatformNames();
string DeviceName(size_t platform, size_t device);
vector<string> DeviceNames(size_t platform);
string DeviceAndPlatformNames();
string DumpInfo();
//Accessors.
bool Ok();
bool Shared();
cl::Context Context();
unsigned int PlatformIndex();
unsigned int DeviceIndex();
unsigned int LocalMemSize();
static void MakeEvenGridDims(unsigned int blockW, unsigned int blockH, unsigned int& gridW, unsigned int& gridH);
private:
bool CreateContext(bool shared);
bool CreateSPK(std::string& name, std::string& program, std::string& entryPoint, Spk& spk, bool doublePrecision);
bool CheckCL(cl_int err, const char* name);
std::string ErrorToStringCL(cl_int err);
bool m_Init;
bool m_Shared;
unsigned int m_PlatformIndex;
unsigned int m_DeviceIndex;
unsigned int m_LocalMemSize;
cl::Platform m_Platform;
cl::Context m_Context;
cl::Device m_Device;
cl::CommandQueue m_Queue;
std::vector<cl::Platform> m_Platforms;
std::vector<std::vector<cl::Device>> m_Devices;
std::vector<cl::Device> m_DeviceVec;
std::vector<Spk> m_Programs;
std::vector<NamedBuffer> m_Buffers;
std::vector<NamedImage2D> m_Images;
std::vector<NamedImage2DGL> m_GLImages;
};
}

File diff suppressed because it is too large Load Diff

156
Source/EmberCL/RendererCL.h Normal file
View File

@ -0,0 +1,156 @@
#pragma once
#include "EmberCLPch.h"
#include "OpenCLWrapper.h"
#include "IterOpenCLKernelCreator.h"
#include "DEOpenCLKernelCreator.h"
#include "FinalAccumOpenCLKernelCreator.h"
/// <summary>
/// RendererCL class.
/// </summary>
namespace EmberCLns
{
class EMBERCL_API RendererCLBase
{
public:
virtual bool ReadFinal(unsigned char* pixels) { return false; }
virtual bool ClearFinal() { return false; }
};
/// <summary>
/// RendererCL is a derivation of the basic CPU renderer which
/// overrides various functions to render on the GPU using OpenCL.
/// Since this class derives from EmberReport and also contains an
/// OpenCLWrapper member which also derives from EmberReport, the
/// reporting functions are overridden to aggregate the errors from
/// both sources.
/// It does not support different types for T and bucketT, so it only has one template argument
/// and uses both for the base.
/// </summary>
template <typename T>
class EMBERCL_API RendererCL : public RendererCLBase, public Renderer<T, T>
{
public:
RendererCL(unsigned int platform = 0, unsigned int device = 0, bool shared = false, GLuint outputTexID = 0);
~RendererCL();
//Ordinary member functions for OpenCL specific tasks.
bool Init(unsigned int platform, unsigned int device, bool shared, GLuint outputTexID);
inline unsigned int IterBlocksWide();
inline unsigned int IterBlocksHigh();
inline unsigned int IterBlockWidth();
inline unsigned int IterBlockHeight();
inline unsigned int IterGridWidth();
inline unsigned int IterGridHeight();
inline unsigned int TotalIterKernelCount();
unsigned int PlatformIndex();
unsigned int DeviceIndex();
bool ReadHist();
bool ReadAccum();
bool ReadPoints(vector<PointCL<T>>& vec);
virtual bool ReadFinal(unsigned char* pixels);
virtual bool ClearFinal();
bool ClearHist();
bool ClearAccum();
bool WritePoints(vector<PointCL<T>>& vec);
string IterKernel();
//Public virtual functions overriden from Renderer.
virtual unsigned __int64 MemoryAvailable();
virtual bool Ok() const;
virtual void NumChannels(unsigned int numChannels);
virtual void DumpErrorReport();
virtual void ClearErrorReport();
virtual unsigned int SubBatchSize() const;
virtual unsigned int ThreadCount() const;
virtual void ThreadCount(unsigned int threads, const char* seedString = NULL);
virtual bool CreateDEFilter(bool& newAlloc);
virtual bool CreateSpatialFilter(bool& newAlloc);
virtual eRendererType RendererType() const;
virtual string ErrorReportString();
virtual vector<string> ErrorReport();
#ifndef TEST_CL
protected:
#endif
//Protected virtual functions overriden from Renderer.
virtual void MakeDmap(T colorScalar);
virtual bool Alloc();
virtual bool ResetBuckets(bool resetHist = true, bool resetAccum = true);
virtual eRenderStatus LogScaleDensityFilter();
virtual eRenderStatus GaussianDensityFilter();
virtual eRenderStatus AccumulatorToFinalImage(unsigned char* pixels, size_t finalOffset);
virtual EmberStats Iterate(unsigned __int64 iterCount, unsigned int pass, unsigned int temporalSample);
private:
//Private functions for making and running OpenCL programs.
bool BuildIterProgramForEmber(bool doAccum = true);
bool RunIter(unsigned __int64 iterCount, unsigned int pass, unsigned int temporalSample, unsigned __int64& itersRan);
eRenderStatus RunLogScaleFilter();
eRenderStatus RunDensityFilter();
eRenderStatus RunFinalAccum();
bool ClearBuffer(string bufferName, unsigned int width, unsigned int height, unsigned int elementSize);
bool RunDensityFilterPrivate(unsigned int kernelIndex, unsigned int gridW, unsigned int gridH, unsigned int blockW, unsigned int blockH, unsigned int chunkSizeW, unsigned int chunkSizeH, unsigned int rowParity, unsigned int colParity);
int MakeAndGetDensityFilterProgram(unsigned int ss, unsigned int filterWidth);
int MakeAndGetFinalAccumProgram(T& alphaBase, T& alphaScale);
int MakeAndGetGammaCorrectionProgram();
//Private functions passing data to OpenCL programs.
DensityFilterCL<T> ConvertDensityFilter();
SpatialFilterCL<T> ConvertSpatialFilter();
EmberCL<T> ConvertEmber(Ember<T>& ember);
static CarToRasCL<T> ConvertCarToRas(const CarToRas<T>& carToRas);
bool m_Init;
bool m_NVidia;
bool m_DoublePrecision;
unsigned int m_IterBlocksWide, m_IterBlockWidth;
unsigned int m_IterBlocksHigh, m_IterBlockHeight;
unsigned int m_MaxDEBlockSizeW;
unsigned int m_MaxDEBlockSizeH;
unsigned int m_WarpSize;
unsigned int m_Calls;
string m_EmberBufferName;
string m_ParVarsBufferName;
string m_DistBufferName;
string m_CarToRasBufferName;
string m_DEFilterParamsBufferName;
string m_SpatialFilterParamsBufferName;
string m_DECoefsBufferName;
string m_DEWidthsBufferName;
string m_DECoefIndicesBufferName;
string m_SpatialFilterCoefsBufferName;
string m_HistBufferName;
string m_AccumBufferName;
string m_FinalImageName;
string m_PointsBufferName;
string m_IterKernel;
OpenCLWrapper m_Wrapper;
cl::ImageFormat m_PaletteFormat;
cl::ImageFormat m_FinalFormat;
cl::Image2D m_Palette;
IMAGEGL2D m_AccumImage;
GLuint m_OutputTexID;
EmberCL<T> m_EmberCL;
Palette<float> m_Dmap;//Used instead of the base class' m_Dmap because OpenCL only supports float textures.
CarToRasCL<T> m_CarToRasCL;
DensityFilterCL<T> m_DensityFilterCL;
SpatialFilterCL<T> m_SpatialFilterCL;
IterOpenCLKernelCreator<T> m_IterOpenCLKernelCreator;
DEOpenCLKernelCreator<T> m_DEOpenCLKernelCreator;
FinalAccumOpenCLKernelCreator<T> m_FinalAccumOpenCLKernelCreator;
pair<string, vector<T>> m_Params;
Ember<T> m_LastBuiltEmber;
};
template EMBERCL_API class RendererCL<float>;
#ifdef DO_DOUBLE
template EMBERCL_API class RendererCL<double>;
#endif
}

View File

@ -0,0 +1,261 @@
#pragma once
#include "EmberCommonPch.h"
/// <summary>
/// Global utility classes and functions that are common to all programs that use
/// Ember and its derivatives.
/// </summary>
/// <summary>
/// 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.
/// </summary>
template <typename T>
class RenderProgress : public RenderCallback
{
public:
/// <summary>
/// Constructor that initializes the state to zero.
/// </summary>
RenderProgress()
{
Clear();
}
/// <summary>
/// The progress function which will be called from inside the renderer.
/// </summary>
/// <param name="ember">The ember currently being rendered</param>
/// <param name="foo">An extra dummy parameter</param>
/// <param name="fraction">The progress fraction from 0-100</param>
/// <param name="stage">The stage of iteration. 1 is iterating, 2 is density filtering, 2 is final accumulation.</param>
/// <param name="etaMs">The estimated milliseconds to completion of the current stage</param>
/// <returns>1 since this is intended to run in an environment where the render runs to completion, unlike interactive rendering.</returns>
virtual int ProgressFunc(Ember<T>& 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(), ' ');//Clear what was previously here.
m_SS.str("");//Begin new output.
m_SS << "\rStage = " << (stage ? "filtering" : "chaos");
m_SS << ", progress = " << int(fraction) << "%";
m_SS << ", eta = " << t.Format(etaMs);
m_S = m_SS.str();
cout << m_S;
}
m_LastStage = stage;
return 1;
}
/// <summary>
/// Reset the state.
/// </summary>
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;
};
/// <summary>
/// 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.
/// </summary>
/// <param name="parser">The parser to use</param>
/// <param name="filename">The full path and name of the file</param>
/// <param name="embers">Storage for the embers read from the file</param>
/// <returns>True if success, else false.</returns>
template <typename T>
static bool ParseEmberFile(XmlToEmber<T>& parser, string filename, vector<Ember<T>>& 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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <returns>True if success, else false.</returns>
template <typename T>
static bool InitPaletteList(string filename)
{
PaletteList<T> paletteList;//Even though this is local, the members are static so they will remain.
if (!paletteList.Init(filename))
{
cout << "Error parsing palette file " << filename << ". Reason: " << endl;
cout << paletteList.ErrorReportString() << endl << "Returning without executing." << endl;
return false;
}
return true;
}
/// <summary>
/// Calculate the number of strips required if the needed amount of memory
/// is greater than the system memory, or greater than what the user want to allow.
/// </summary>
/// <param name="mem">Amount of memory required</param>
/// <param name="useMem">The maximum amount of memory to use. Use max if 0.</param>
/// <returns>The number of strips to use</returns>
static unsigned int CalcStrips(double mem, double memAvailable, double useMem)
{
unsigned int strips;
double memRequired;
if (useMem > 0)
memAvailable = useMem;
else
memAvailable *= 0.8;
memRequired = mem;
if (memAvailable >= memRequired)
return 1;
strips = (unsigned int)ceil(memRequired / memAvailable);
return strips;
}
/// <summary>
/// Given a numerator and a denominator, find the next highest denominator that divides
/// evenly into the numerator.
/// </summary>
/// <param name="numerator">The numerator</param>
/// <param name="denominator">The denominator</param>
/// <returns>The next highest divisor if found, else 1.</returns>
static unsigned int NextHighestEvenDiv(unsigned int numerator, unsigned int denominator)
{
unsigned int result = 1;
unsigned int numDiv2 = numerator / 2;
do
{
denominator++;
if (numerator % denominator == 0)
{
result = denominator;
break;
}
}
while (denominator <= numDiv2);
return result;
}
/// <summary>
/// Given a numerator and a denominator, find the next lowest denominator that divides
/// evenly into the numerator.
/// </summary>
/// <param name="numerator">The numerator</param>
/// <param name="denominator">The denominator</param>
/// <returns>The next lowest divisor if found, else 1.</returns>
static unsigned int NextLowestEvenDiv(unsigned int numerator, unsigned int denominator)
{
unsigned int result = 1;
unsigned int numDiv2 = numerator / 2;
denominator--;
if (denominator > numDiv2)
denominator = numDiv2;
while (denominator >= 1)
{
if (numerator % denominator == 0)
{
result = denominator;
break;
}
denominator--;
}
return result;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="renderType">Type of renderer to create</param>
/// <param name="platform">The index platform of the platform to use</param>
/// <param name="device">The index device of the device to use</param>
/// <param name="shared">True if shared with OpenGL, else false.</param>
/// <param name="texId">The texture ID of the shared OpenGL texture if shared</param>
/// <param name="errorReport">The error report for holding errors if anything goes wrong</param>
/// <returns>A pointer to the created renderer if successful, else false.</returns>
template <typename T, typename bucketT>
static Renderer<T, bucketT>* CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared, GLuint texId, EmberReport& errorReport)
{
string s;
auto_ptr<Renderer<T, bucketT>> renderer;
try
{
if (renderType == CPU_RENDERER)
{
s = "CPU";
renderer = auto_ptr<Renderer<T, bucketT>>(new Renderer<T, bucketT>());
}
else if (renderType == OPENCL_RENDERER)
{
s = "OpenCL";
renderer = auto_ptr<Renderer<T, bucketT>>(new RendererCL<T>(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 = auto_ptr<Renderer<T, bucketT>>(new Renderer<T, bucketT>());
}
}
}
catch (...)
{
errorReport.AddToReport("Error creating " + s + " renderer.\n");
}
return renderer.release();
}
/// <summary>
/// Simple macro to print a string if the --verbose options has been specified.
/// </summary>
#define VerbosePrint(s) if (opt.Verbose()) cout << s << endl

View File

@ -0,0 +1 @@
#include "EmberCommonPch.h"

View File

@ -0,0 +1,54 @@
#pragma once
/// <summary>
/// Precompiled header file. Place all system includes here with appropriate #defines for different operating systems and compilers.
/// </summary>
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN//Exclude rarely-used stuff from Windows headers.
#define _USE_MATH_DEFINES
#ifdef _WIN32
#include <SDKDDKVer.h>
#include <windows.h>
#include <winsock.h>//For htons().
#define snprintf _snprintf
#else
#include <arpa/inet.h>
#endif
#include <BaseTsd.h>
#include <crtdbg.h>
#include <iostream>
#include <iomanip>
#include <ostream>
#include <sstream>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>
#include "jconfig.h"
#include "jpeglib.h"
#include "png.h"
//#include "pnginfo.h"
//Ember.
#include "Ember.h"
#include "Variation.h"
#include "EmberToXml.h"
#include "XmlToEmber.h"
#include "PaletteList.h"
#include "Iterator.h"
#include "Renderer.h"
#include "RendererCL.h"
#include "SheepTools.h"
//Options.
#include "SimpleGlob.h"
#include "SimpleOpt.h"
using namespace EmberNs;
using namespace EmberCLns;

View File

@ -0,0 +1,705 @@
#pragma once
#include "EmberCommon.h"
/// <summary>
/// EmberOptionEntry and EmberOptions classes.
/// </summary>
static char* DescriptionString = "Ember - Fractal flames C++ port and enhancement with OpenCL GPU support";
/// <summary>
/// Enum for specifying which command line programs an option is meant to be used with.
/// If an option is used with multiple programs, their values are ORed together.
/// </summary>
enum eOptionUse
{
OPT_USE_RENDER = 1,
OPT_USE_ANIMATE = 1 << 1,
OPT_USE_GENOME = 1 << 2,
OPT_RENDER_ANIM = OPT_USE_RENDER | OPT_USE_ANIMATE,
OPT_ANIM_GENOME = OPT_USE_ANIMATE | OPT_USE_GENOME,
OPT_USE_ALL = OPT_USE_RENDER | OPT_USE_ANIMATE | OPT_USE_GENOME
};
/// <summary>
/// Unique identifiers for every available option across all programs.
/// </summary>
enum eOptionIDs
{
//Diagnostic args.
OPT_HELP,
OPT_VERSION,
OPT_VERBOSE,
OPT_DEBUG,
OPT_DUMP_ARGS,
OPT_PROGRESS,
OPT_DUMP_OPENCL_INFO,
//Boolean args.
OPT_OPENCL,
OPT_EARLYCLIP,
OPT_TRANSPARENCY,
OPT_NAME_ENABLE,
OPT_INT_PALETTE,
OPT_HEX_PALETTE,
OPT_INSERT_PALETTE,
OPT_JPEG_COMMENTS,
OPT_PNG_COMMENTS,
OPT_WRITE_GENOME,
OPT_ENCLOSED,
OPT_NO_EDITS,
OPT_UNSMOOTH_EDGE,
OPT_LOCK_ACCUM,
OPT_DUMP_KERNEL,
//Value args.
OPT_OPENCL_PLATFORM,//Int value args.
OPT_OPENCL_DEVICE,
OPT_SEED,
OPT_NTHREADS,
OPT_STRIPS,
OPT_BITS,
OPT_BPC,
OPT_SBS,
OPT_PRINT_EDIT_DEPTH,
OPT_JPEG,
OPT_BEGIN,
OPT_END,
OPT_FRAME,
OPT_TIME,
OPT_DTIME,
OPT_NFRAMES,
OPT_SYMMETRY,
OPT_SHEEP_GEN,
OPT_SHEEP_ID,
OPT_LOOPS,
OPT_REPEAT,
OPT_TRIES,
OPT_MAX_XFORMS,
OPT_SS,//Float value args.
OPT_QS,
OPT_PIXEL_ASPECT,
OPT_STAGGER,
OPT_AVG_THRESH,
OPT_BLACK_THRESH,
OPT_WHITE_LIMIT,
OPT_SPEED,
OPT_OFFSETX,
OPT_OFFSETY,
OPT_USEMEM,
OPT_ISAAC_SEED,//String value args.
OPT_IN,
OPT_OUT,
OPT_PREFIX,
OPT_SUFFIX,
OPT_FORMAT,
OPT_PALETTE_FILE,
OPT_PALETTE_IMAGE,
OPT_ID,
OPT_URL,
OPT_NICK,
OPT_COMMENT,
OPT_TEMPLATE,
OPT_CLONE,
OPT_CLONE_ALL,
OPT_CLONE_ACTION,
OPT_ANIMATE,
OPT_MUTATE,
OPT_CROSS0,
OPT_CROSS1,
OPT_METHOD,
OPT_INTER,
OPT_ROTATE,
OPT_STRIP,
OPT_SEQUENCE,
OPT_USE_VARS,
OPT_DONT_USE_VARS,
OPT_EXTRAS
};
class EmberOptions;
/// <summary>
/// A single option.
/// Template argument expected to be bool, int, unsigned int, double or string.
/// </summary>
template <typename T>
class EmberOptionEntry
{
friend class EmberOptions;
private:
/// <summary>
/// Default constructor. This should never be used, instead use the one that takes arguments.
/// </summary>
EmberOptionEntry()
{
m_OptionUse = OPT_USE_ALL;
m_Option.nArgType = SO_NONE;
m_Option.nId = 0;
m_Option.pszArg = _T("--fillmein");
m_DocString = "Dummy doc";
}
public:
/// <summary>
/// Constructor that takes arguments.
/// </summary>
/// <param name="optUsage">The specified program usage</param>
/// <param name="optId">The option identifier enum</param>
/// <param name="arg">The command line argument (--arg)</param>
/// <param name="defaultVal">The default value to use the option was not given on the command line</param>
/// <param name="argType">The format the argument should be given in</param>
/// <param name="docString">The documentation string describing what the argument means</param>
EmberOptionEntry(eOptionUse optUsage, eOptionIDs optId, const CharT* arg, T defaultVal, ESOArgType argType, string docString)
{
m_OptionUse = optUsage;
m_Option.nId = (int)optId;
m_Option.pszArg = arg;
m_Option.nArgType = argType;
m_DocString = docString;
m_NameWithoutDashes = Trim(string(arg), '-');
m_Val = Arg<T>((char*)m_NameWithoutDashes.c_str(), defaultVal);
}
/// <summary>
/// Copy constructor.
/// </summary>
/// <param name="entry">The EmberOptionEntry object to copy</param>
EmberOptionEntry(const EmberOptionEntry& entry)
{
*this = entry;
}
/// <summary>
/// Functor accessors.
/// </summary>
inline T operator() (void) { return m_Val; }
inline void operator() (T t) { m_Val = t; }
private:
eOptionUse m_OptionUse;
CSimpleOpt::SOption m_Option;
string m_DocString;
string m_NameWithoutDashes;
T m_Val;
};
/// <summary>
/// Macros for setting up and parsing various option types.
/// </summary>
//Bool.
#define Eob EmberOptionEntry<bool>
#define INITBOOLOPTION(member, option) \
member = option; \
m_BoolArgs.push_back(&member)
#define PARSEBOOLOPTION(opt, member) \
case (opt): \
member(true); \
break
//Int.
#define Eoi EmberOptionEntry<int>
#define INITINTOPTION(member, option) \
member = option; \
m_IntArgs.push_back(&member)
#define PARSEINTOPTION(opt, member) \
case (opt): \
sscanf_s(args.OptionArg(), "%d", &member.m_Val); \
break
//Uint.
#define Eou EmberOptionEntry<unsigned int>
#define INITUINTOPTION(member, option) \
member = option; \
m_UintArgs.push_back(&member)
#define PARSEUINTOPTION(opt, member) \
case (opt): \
sscanf_s(args.OptionArg(), "%u", &member.m_Val); \
break
//Double.
#define Eod EmberOptionEntry<double>
#define INITDOUBLEOPTION(member, option) \
member = option; \
m_DoubleArgs.push_back(&member)
#define PARSEDOUBLEOPTION(opt, member) \
case (opt): \
sscanf_s(args.OptionArg(), "%lf", &member.m_Val); \
break
//String.
#define Eos EmberOptionEntry<string>
#define INITSTRINGOPTION(member, option) \
member = option; \
m_StringArgs.push_back(&member)
#define PARSESTRINGOPTION(opt, member) \
case (opt): \
member.m_Val = args.OptionArg(); \
break
/// <summary>
/// Class for holding all available options across all command line programs.
/// Some are used only for a single program, while others are used for more than one.
/// This prevents having to keep separate documentation strings around for different programs.
/// </summary>
class EmberOptions
{
public:
/// <summary>
/// Constructor that populates all available options.
/// </summary>
EmberOptions()
{
m_BoolArgs.reserve(25);
m_IntArgs.reserve(25);
m_UintArgs.reserve(25);
m_DoubleArgs.reserve(25);
m_StringArgs.reserve(35);
//Diagnostic bools.
INITBOOLOPTION(Help, Eob(OPT_USE_ALL, OPT_HELP, _T("--help"), false, SO_NONE, "\t--help Show this screen.\n"));
INITBOOLOPTION(Version, Eob(OPT_USE_ALL, OPT_VERSION, _T("--version"), false, SO_NONE, "\t--version Show version.\n"));
INITBOOLOPTION(Verbose, Eob(OPT_USE_ALL, OPT_VERBOSE, _T("--verbose"), false, SO_NONE, "\t--verbose Verbose output.\n"));
INITBOOLOPTION(Debug, Eob(OPT_USE_ALL, OPT_DEBUG, _T("--debug"), false, SO_NONE, "\t--debug Debug output.\n"));
INITBOOLOPTION(DumpArgs, Eob(OPT_USE_ALL, OPT_DUMP_ARGS, _T("--dumpargs"), false, SO_NONE, "\t--dumpargs Print all arguments entered from either the command line or environment variables.\n"));
INITBOOLOPTION(DoProgress, Eob(OPT_USE_ALL, OPT_PROGRESS, _T("--progress"), false, SO_NONE, "\t--progress Display progress. This will slow down processing by about 10%%.\n"));
INITBOOLOPTION(OpenCLInfo, Eob(OPT_USE_ALL, OPT_DUMP_OPENCL_INFO, _T("--openclinfo"), false, SO_NONE, "\t--openclinfo Display platforms and devices for OpenCL.\n"));
//Execution bools.
INITBOOLOPTION(EmberCL, Eob(OPT_USE_ALL, OPT_OPENCL, _T("--opencl"), false, SO_NONE, "\t--opencl Use OpenCL renderer (EmberCL) for rendering [default: false].\n"));
INITBOOLOPTION(EarlyClip, Eob(OPT_USE_ALL, OPT_EARLYCLIP, _T("--earlyclip"), false, SO_NONE, "\t--earlyclip Perform clipping of RGB values before spatial filtering for better antialiasing and resizing [default: false].\n"));
INITBOOLOPTION(Transparency, Eob(OPT_RENDER_ANIM, 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_USE_ALL, 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(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(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(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 (ignored for OpenCL). This will drop performance to that of single threading [default: false].\n"));
INITBOOLOPTION(DumpKernel, Eob(OPT_USE_RENDER, OPT_DUMP_KERNEL, _T("--dump_kernel"), false, SO_NONE, "\t--dump_kernel Print the iteration kernel string when using OpenCL (ignored for CPU) [default: false].\n"));
//Int.
INITINTOPTION(Symmetry, Eoi(OPT_USE_GENOME, OPT_SYMMETRY, _T("--symmetry"), 0, SO_REQ_SEP, "\t--symmetry=<val> Set symmetry of result [default: 0].\n"));
INITINTOPTION(SheepGen, Eoi(OPT_USE_GENOME, OPT_SHEEP_GEN, _T("--sheep_gen"), -1, SO_REQ_SEP, "\t--sheep_gen=<val> Sheep generation of this flame [default: -1].\n"));
INITINTOPTION(SheepId, Eoi(OPT_USE_GENOME, OPT_SHEEP_ID, _T("--sheep_id"), -1, SO_REQ_SEP, "\t--sheep_id=<val> Sheep ID of this flame [default: -1].\n"));
INITUINTOPTION(Platform, Eou(OPT_RENDER_ANIM, OPT_OPENCL_PLATFORM, _T("--platform"), 0, SO_REQ_SEP, "\t--platform The OpenCL platform index to use [default: 0].\n"));
INITUINTOPTION(Device, Eou(OPT_RENDER_ANIM, OPT_OPENCL_DEVICE, _T("--device"), 0, SO_REQ_SEP, "\t--device The OpenCL device index within the specified platform to use [default: 0].\n"));
INITUINTOPTION(Seed, Eou(OPT_USE_ALL, OPT_SEED, _T("--seed"), 0, SO_REQ_SEP, "\t--seed=<val> Integer seed to use for the random number generator [default: random].\n"));
INITUINTOPTION(ThreadCount, Eou(OPT_USE_ALL, OPT_NTHREADS, _T("--nthreads"), 0, SO_REQ_SEP, "\t--nthreads=<val> The number of threads to use [default: use all available cores].\n"));
INITUINTOPTION(Strips, Eou(OPT_USE_RENDER, OPT_STRIPS, _T("--nstrips"), 1, SO_REQ_SEP, "\t--nstrips=<val> The number of fractions to split a single render frame into. Useful for print size renders or low memory systems [default: 1].\n"));
INITUINTOPTION(BitsPerChannel, Eou(OPT_RENDER_ANIM, OPT_BPC, _T("--bpc"), 8, SO_REQ_SEP, "\t--bpc=<val> Bits per channel. 8 or 16 for PNG, 8 for all others [default: 8].\n"));
INITUINTOPTION(SubBatchSize, Eou(OPT_USE_ALL, OPT_SBS, _T("--sub_batch_size"), 10000, SO_REQ_SEP, "\t--sub_batch_size=<val> The chunk size that iterating will be broken into [default: 10000].\n"));
INITUINTOPTION(Bits, Eou(OPT_USE_ALL, OPT_BITS, _T("--bits"), 33, SO_REQ_SEP, "\t--bits=<val> Determines the types used for the histogram and accumulator [default: 33].\n"
"\t\t\t\t\t32: Histogram: float, Accumulator: float.\n"
"\t\t\t\t\t33: Histogram: float, Accumulator: float.\n"//This differs from the original which used an int hist for bits 33.
"\t\t\t\t\t64: Histogram: double, Accumulator: double.\n"));
INITUINTOPTION(PrintEditDepth, Eou(OPT_USE_ALL, OPT_PRINT_EDIT_DEPTH, _T("--print_edit_depth"), 0, SO_REQ_SEP, "\t--print_edit_depth=<val> Depth to truncate <edit> tag structure when converting a flame to xml. 0 prints all <edit> tags [default: 0].\n"));
INITUINTOPTION(JpegQuality, Eou(OPT_USE_ALL, OPT_JPEG, _T("--jpeg"), 95, SO_REQ_SEP, "\t--jpeg=<val> Jpeg quality 0-100 for compression [default: 95].\n"));
INITUINTOPTION(FirstFrame, Eou(OPT_USE_ANIMATE, OPT_BEGIN, _T("--begin"), UINT_MAX, SO_REQ_SEP, "\t--begin=<val> Time of first frame to render [default: first time specified in file].\n"));
INITUINTOPTION(LastFrame, Eou(OPT_USE_ANIMATE, OPT_END, _T("--end"), UINT_MAX, SO_REQ_SEP, "\t--end=<val> Time of last frame to render [default: last time specified in the input file].\n"));
INITUINTOPTION(Time, Eou(OPT_ANIM_GENOME, OPT_TIME, _T("--time"), 0, SO_REQ_SEP, "\t--time=<val> Time of first and last frame (ie do one frame).\n"));
INITUINTOPTION(Frame, Eou(OPT_ANIM_GENOME, OPT_FRAME, _T("--frame"), 0, SO_REQ_SEP, "\t--frame=<val> Synonym for \"time\".\n"));
INITUINTOPTION(Dtime, Eou(OPT_USE_ANIMATE, OPT_DTIME, _T("--dtime"), 1, SO_REQ_SEP, "\t--dtime=<val> Time between frames [default: 1].\n"));
INITUINTOPTION(Frames, Eou(OPT_USE_GENOME, OPT_NFRAMES, _T("--nframes"), 20, SO_REQ_SEP, "\t--nframes=<val> Number of frames for each stage of the animation [default: 20].\n"));
INITUINTOPTION(Loops, Eou(OPT_USE_GENOME, OPT_LOOPS, _T("--loops"), 1, SO_REQ_SEP, "\t--loops=<val> Number of times to rotate each control point in sequence [default: 1].\n"));
INITUINTOPTION(Repeat, Eou(OPT_USE_GENOME, OPT_REPEAT, _T("--repeat"), 1, SO_REQ_SEP, "\t--repeat=<val> Number of new flames to create. Ignored if sequence, inter or rotate were specified [default: 1].\n"));
INITUINTOPTION(Tries, Eou(OPT_USE_GENOME, OPT_TRIES, _T("--tries"), 10, SO_REQ_SEP, "\t--tries=<val> Number times to try creating a flame that meets the specified constraints. Ignored if sequence, inter or rotate were specified [default: 10].\n"));
INITUINTOPTION(MaxXforms, Eou(OPT_USE_GENOME, OPT_MAX_XFORMS, _T("--maxxforms"), UINT_MAX, SO_REQ_SEP, "\t--maxxforms=<val> The maximum number of xforms allowed in the final output.\n"));
//Double.
INITDOUBLEOPTION(SizeScale, Eod(OPT_USE_ALL, OPT_SS, _T("--ss"), 1, SO_REQ_SEP, "\t--ss=<val> Size scale. All dimensions are scaled by this amount [default: 1.0].\n"));
INITDOUBLEOPTION(QualityScale, Eod(OPT_USE_ALL, OPT_QS, _T("--qs"), 1, SO_REQ_SEP, "\t--qs=<val> Quality scale. All quality values are scaled by this amount [default: 1.0].\n"));
INITDOUBLEOPTION(AspectRatio, Eod(OPT_USE_ALL, OPT_PIXEL_ASPECT, _T("--pixel_aspect"), 1, SO_REQ_SEP, "\t--pixel_aspect=<val> Aspect ratio of pixels (width over height), eg. 0.90909 for NTSC [default: 1.0].\n"));
INITDOUBLEOPTION(Stagger, Eod(OPT_USE_ALL, OPT_STAGGER, _T("--stagger"), 0, SO_REQ_SEP, "\t--stagger=<val> Affects simultaneity of xform interpolation during flame interpolation.\n"
"\t Represents how 'separate' the xforms are interpolated. Set to 1 for each\n"
"\t xform to be interpolated individually, fractions control interpolation overlap [default: 0].\n"));
INITDOUBLEOPTION(AvgThresh, Eod(OPT_USE_GENOME, OPT_AVG_THRESH, _T("--avg"), 20.0, SO_REQ_SEP, "\t--avg=<val> Minimum average pixel channel sum (r + g + b) threshold from 0 - 765. Ignored if sequence, inter or rotate were specified [default: 20].\n"));
INITDOUBLEOPTION(BlackThresh, Eod(OPT_USE_GENOME, OPT_BLACK_THRESH, _T("--black"), 0.01, SO_REQ_SEP, "\t--black=<val> Minimum number of allowed black pixels as a percentage from 0 - 1. Ignored if sequence, inter or rotate were specified [default: 0.01].\n"));
INITDOUBLEOPTION(WhiteLimit, Eod(OPT_USE_GENOME, OPT_WHITE_LIMIT, _T("--white"), 0.05, SO_REQ_SEP, "\t--white=<val> Maximum number of allowed white pixels as a percentage from 0 - 1. Ignored if sequence, inter or rotate were specified [default: 0.05].\n"));
INITDOUBLEOPTION(Speed, Eod(OPT_USE_GENOME, OPT_SPEED, _T("--speed"), 0.1, SO_REQ_SEP, "\t--speed=<val> Speed as a percentage from 0 - 1 that the affine transform of an existing flame mutates with the new flame. Ignored if sequence, inter or rotate were specified [default: 0.1].\n"));
INITDOUBLEOPTION(OffsetX, Eod(OPT_USE_GENOME, OPT_OFFSETX, _T("--offsetx"), 0.0, SO_REQ_SEP, "\t--offsetx=<val> Amount to jitter each flame horizontally when applying genome tools [default: 0].\n"));
INITDOUBLEOPTION(OffsetY, Eod(OPT_USE_GENOME, OPT_OFFSETY, _T("--offsety"), 0.0, SO_REQ_SEP, "\t--offsety=<val> Amount to jitter each flame vertically when applying genome tools [default: 0].\n"));
INITDOUBLEOPTION(UseMem, Eod(OPT_USE_RENDER, OPT_USEMEM, _T("--use_mem"), 0.0, SO_REQ_SEP, "\t--use_mem=<val> Number of bytes of memory to use [default: max system memory].\n"));
//String.
INITSTRINGOPTION(IsaacSeed, Eos(OPT_USE_ALL, OPT_ISAAC_SEED, _T("--isaac_seed"), "", SO_REQ_SEP, "\t--isaac_seed=<val> Character-based seed for the random number generator [default: random].\n"));
INITSTRINGOPTION(Input, Eos(OPT_USE_ALL, OPT_IN, _T("--in"), "", SO_REQ_SEP, "\t--in=<val> Name of the input file.\n"));
INITSTRINGOPTION(Out, Eos(OPT_USE_ALL, OPT_OUT, _T("--out"), "", SO_REQ_SEP, "\t--out=<val> Name of a single output file. Not recommended when rendering more than one image.\n"));
INITSTRINGOPTION(Prefix, Eos(OPT_USE_ALL, OPT_PREFIX, _T("--prefix"), "", SO_REQ_SEP, "\t--prefix=<val> Prefix to prepend to all output files.\n"));
INITSTRINGOPTION(Suffix, Eos(OPT_USE_ALL, OPT_SUFFIX, _T("--suffix"), "", SO_REQ_SEP, "\t--suffix=<val> Suffix to append to all output files.\n"));
INITSTRINGOPTION(Format, Eos(OPT_RENDER_ANIM, OPT_FORMAT, _T("--format"), "png", SO_REQ_SEP, "\t--format=<val> Format of the output file. Valid values are: bmp, jpg, png, ppm [default: jpg].\n"));
INITSTRINGOPTION(PalettePath, Eos(OPT_USE_ALL, OPT_PALETTE_FILE, _T("--flam3_palettes"), "flam3-palettes.xml", SO_REQ_SEP, "\t--flam3_palettes=<val> Path and name of the palette file [default: flam3-palettes.xml].\n"));
INITSTRINGOPTION(PaletteImage, Eos(OPT_USE_ALL, OPT_PALETTE_IMAGE, _T("--image"), "", SO_REQ_SEP, "\t--image=<val> Replace palette with png, jpg, or ppm image.\n"));
INITSTRINGOPTION(Id, Eos(OPT_USE_ALL, OPT_ID, _T("--id"), "", SO_REQ_SEP, "\t--id=<val> ID to use in <edit> tags.\n"));
INITSTRINGOPTION(Url, Eos(OPT_USE_ALL, OPT_URL, _T("--url"), "", SO_REQ_SEP, "\t--url=<val> URL to use in <edit> tags / img comments.\n"));
INITSTRINGOPTION(Nick, Eos(OPT_USE_ALL, OPT_NICK, _T("--nick"), "", SO_REQ_SEP, "\t--nick=<val> Nickname to use in <edit> tags / img comments.\n"));
INITSTRINGOPTION(Comment, Eos(OPT_USE_GENOME, OPT_COMMENT, _T("--comment"), "", SO_REQ_SEP, "\t--comment=<val> Comment to use in <edit> tags.\n"));
INITSTRINGOPTION(TemplateFile, Eos(OPT_USE_GENOME, OPT_TEMPLATE, _T("--template"), "", SO_REQ_SEP, "\t--template=<val> Apply defaults based on this flame.\n"));
INITSTRINGOPTION(Clone, Eos(OPT_USE_GENOME, OPT_CLONE, _T("--clone"), "", SO_REQ_SEP, "\t--clone=<val> Clone random flame in input.\n"));
INITSTRINGOPTION(CloneAll, Eos(OPT_USE_GENOME, OPT_CLONE_ALL, _T("--clone_all"), "", SO_REQ_SEP, "\t--clone_all=<val> Clones all flames in the input file. Useful for applying template to all flames.\n"));
INITSTRINGOPTION(CloneAction, Eos(OPT_USE_GENOME, OPT_CLONE_ACTION, _T("--clone_action"), "", SO_REQ_SEP, "\t--clone_action=<val> A description of the clone action taking place.\n"));
INITSTRINGOPTION(Animate, Eos(OPT_USE_GENOME, OPT_ANIMATE, _T("--animate"), "", SO_REQ_SEP, "\t--animate=<val> Interpolates between all flames in the input file, using times specified in file.\n"));
INITSTRINGOPTION(Mutate, Eos(OPT_USE_GENOME, OPT_MUTATE, _T("--mutate"), "", SO_REQ_SEP, "\t--mutate=<val> Randomly mutate a random flame from the input file.\n"));
INITSTRINGOPTION(Cross0, Eos(OPT_USE_GENOME, OPT_CROSS0, _T("--cross0"), "", SO_REQ_SEP, "\t--cross0=<val> Randomly select one flame from the input file to genetically cross...\n"));
INITSTRINGOPTION(Cross1, Eos(OPT_USE_GENOME, OPT_CROSS1, _T("--cross1"), "", SO_REQ_SEP, "\t--cross1=<val> ...with one flame from this file.\n"));
INITSTRINGOPTION(Method, Eos(OPT_USE_GENOME, OPT_METHOD, _T("--method"), "", SO_REQ_SEP, "\t--method=<val> Method used for genetic cross: alternate, interpolate, or union. For mutate: all_vars, one_xform, add_symmetry, post_xforms, color_palette, delete_xform, all_coefs [default: random].\n"));//Original ommitted this important documentation for mutate!
INITSTRINGOPTION(Inter, Eos(OPT_USE_GENOME, OPT_INTER, _T("--inter"), "", SO_REQ_SEP, "\t--inter=<val> Interpolate the input file.\n"));
INITSTRINGOPTION(Rotate, Eos(OPT_USE_GENOME, OPT_ROTATE, _T("--rotate"), "", SO_REQ_SEP, "\t--rotate=<val> Rotate the input file.\n"));
INITSTRINGOPTION(Strip, Eos(OPT_USE_GENOME, OPT_STRIP, _T("--strip"), "", SO_REQ_SEP, "\t--strip=<val> Break strip out of each flame in the input file.\n"));
INITSTRINGOPTION(Sequence, Eos(OPT_USE_GENOME, OPT_SEQUENCE, _T("--sequence"), "", SO_REQ_SEP, "\t--sequence=<val> 360 degree rotation 'loops' times of each control point in the input file plus rotating transitions.\n"));
INITSTRINGOPTION(UseVars, Eos(OPT_USE_GENOME, OPT_USE_VARS, _T("--use_vars"), "", SO_REQ_SEP, "\t--use_vars=<val> Comma separated list of variation #'s to use when generating a random flame.\n"));
INITSTRINGOPTION(DontUseVars, Eos(OPT_USE_GENOME, OPT_DONT_USE_VARS, _T("--dont_use_vars"), "", SO_REQ_SEP, "\t--dont_use_vars=<val> Comma separated list of variation #'s to NOT use when generating a random flame.\n"));
INITSTRINGOPTION(Extras, Eos(OPT_USE_GENOME, OPT_EXTRAS, _T("--extras"), "", SO_REQ_SEP, "\t--extras=<val> Extra attributes to place in the flame section of the Xml.\n"));
}
/// <summary>
/// Parse and populate the supplied command line options for the specified program usage.
/// If --help or --version were specified, information will be printed
/// and parsing will cease.
/// </summary>
/// <param name="argc">The number of command line arguments passed</param>
/// <param name="argv">The command line arguments passed</param>
/// <param name="optUsage">The program for which these options are to be parsed and used.</param>
/// <returns>True if --help or --version specified, else false</returns>
bool Populate(int argc, _TCHAR* argv[], eOptionUse optUsage)
{
EmberOptions options;
vector<CSimpleOpt::SOption> sOptions = options.GetSimpleOptions();
CSimpleOpt args(argc, argv, sOptions.data());
//Process args.
while (args.Next())
{
ESOError errorCode = args.LastError();
if (errorCode == SO_SUCCESS)
{
switch (args.OptionId())
{
case OPT_HELP://Bool args.
{
ShowUsage(optUsage);
return true;
}
case OPT_VERSION:
{
cout << EmberVersion() << endl;
return true;
}
PARSEBOOLOPTION(OPT_VERBOSE, Verbose);
PARSEBOOLOPTION(OPT_DEBUG, Debug);
PARSEBOOLOPTION(OPT_DUMP_ARGS, DumpArgs);
PARSEBOOLOPTION(OPT_PROGRESS, DoProgress);
PARSEBOOLOPTION(OPT_DUMP_OPENCL_INFO, OpenCLInfo);
PARSEBOOLOPTION(OPT_OPENCL, EmberCL);
PARSEBOOLOPTION(OPT_EARLYCLIP, EarlyClip);
PARSEBOOLOPTION(OPT_TRANSPARENCY, Transparency);
PARSEBOOLOPTION(OPT_NAME_ENABLE, NameEnable);
PARSEBOOLOPTION(OPT_INT_PALETTE, IntPalette);
PARSEBOOLOPTION(OPT_HEX_PALETTE, HexPalette);
PARSEBOOLOPTION(OPT_INSERT_PALETTE, InsertPalette);
PARSEBOOLOPTION(OPT_JPEG_COMMENTS, JpegComments);
PARSEBOOLOPTION(OPT_PNG_COMMENTS, PngComments);
PARSEBOOLOPTION(OPT_WRITE_GENOME, WriteGenome);
PARSEBOOLOPTION(OPT_ENCLOSED, Enclosed);
PARSEBOOLOPTION(OPT_NO_EDITS, NoEdits);
PARSEBOOLOPTION(OPT_UNSMOOTH_EDGE, UnsmoothEdge);
PARSEBOOLOPTION(OPT_LOCK_ACCUM, LockAccum);
PARSEBOOLOPTION(OPT_DUMP_KERNEL, DumpKernel);
PARSEINTOPTION(OPT_SYMMETRY, Symmetry);//Int args
PARSEINTOPTION(OPT_SHEEP_GEN, SheepGen);
PARSEINTOPTION(OPT_SHEEP_ID, SheepId);
PARSEUINTOPTION(OPT_OPENCL_PLATFORM, Platform);//Unsigned int args.
PARSEUINTOPTION(OPT_OPENCL_DEVICE, Device);
PARSEUINTOPTION(OPT_SEED, Seed);
PARSEUINTOPTION(OPT_NTHREADS, ThreadCount);
PARSEUINTOPTION(OPT_STRIPS, Strips);
PARSEUINTOPTION(OPT_BITS, Bits);
PARSEUINTOPTION(OPT_BPC, BitsPerChannel);
PARSEUINTOPTION(OPT_SBS, SubBatchSize);
PARSEUINTOPTION(OPT_PRINT_EDIT_DEPTH, PrintEditDepth);
PARSEUINTOPTION(OPT_JPEG, JpegQuality);
PARSEUINTOPTION(OPT_BEGIN, FirstFrame);
PARSEUINTOPTION(OPT_END, LastFrame);
PARSEUINTOPTION(OPT_FRAME, Frame);
PARSEUINTOPTION(OPT_TIME, Time);
PARSEUINTOPTION(OPT_DTIME, Dtime);
PARSEUINTOPTION(OPT_NFRAMES, Frames);
PARSEUINTOPTION(OPT_LOOPS, Loops);
PARSEUINTOPTION(OPT_REPEAT, Repeat);
PARSEUINTOPTION(OPT_TRIES, Tries);
PARSEUINTOPTION(OPT_MAX_XFORMS, MaxXforms);
PARSEDOUBLEOPTION(OPT_SS, SizeScale);//Float args.
PARSEDOUBLEOPTION(OPT_QS, QualityScale);
PARSEDOUBLEOPTION(OPT_PIXEL_ASPECT, AspectRatio);
PARSEDOUBLEOPTION(OPT_STAGGER, Stagger);
PARSEDOUBLEOPTION(OPT_AVG_THRESH, AvgThresh);
PARSEDOUBLEOPTION(OPT_BLACK_THRESH, BlackThresh);
PARSEDOUBLEOPTION(OPT_WHITE_LIMIT, WhiteLimit);
PARSEDOUBLEOPTION(OPT_SPEED, Speed);
PARSEDOUBLEOPTION(OPT_OFFSETX, OffsetX);
PARSEDOUBLEOPTION(OPT_OFFSETY, OffsetY);
PARSEDOUBLEOPTION(OPT_USEMEM, UseMem);
PARSESTRINGOPTION(OPT_ISAAC_SEED, IsaacSeed);//String args.
PARSESTRINGOPTION(OPT_IN, Input);
PARSESTRINGOPTION(OPT_OUT, Out);
PARSESTRINGOPTION(OPT_PREFIX, Prefix);
PARSESTRINGOPTION(OPT_SUFFIX, Suffix);
PARSESTRINGOPTION(OPT_FORMAT, Format);
PARSESTRINGOPTION(OPT_PALETTE_FILE, PalettePath);
PARSESTRINGOPTION(OPT_PALETTE_IMAGE, PaletteImage);
PARSESTRINGOPTION(OPT_ID, Id);
PARSESTRINGOPTION(OPT_URL, Url);
PARSESTRINGOPTION(OPT_NICK, Nick);
PARSESTRINGOPTION(OPT_COMMENT, Comment);
PARSESTRINGOPTION(OPT_TEMPLATE, TemplateFile);
PARSESTRINGOPTION(OPT_CLONE, Clone);
PARSESTRINGOPTION(OPT_CLONE_ALL, CloneAll);
PARSESTRINGOPTION(OPT_CLONE_ACTION, CloneAction);
PARSESTRINGOPTION(OPT_ANIMATE, Animate);
PARSESTRINGOPTION(OPT_MUTATE, Mutate);
PARSESTRINGOPTION(OPT_CROSS0, Cross0);
PARSESTRINGOPTION(OPT_CROSS1, Cross1);
PARSESTRINGOPTION(OPT_METHOD, Method);
PARSESTRINGOPTION(OPT_INTER, Inter);
PARSESTRINGOPTION(OPT_ROTATE, Rotate);
PARSESTRINGOPTION(OPT_STRIP, Strip);
PARSESTRINGOPTION(OPT_SEQUENCE, Sequence);
PARSESTRINGOPTION(OPT_USE_VARS, UseVars);
PARSESTRINGOPTION(OPT_DONT_USE_VARS, DontUseVars);
PARSESTRINGOPTION(OPT_EXTRAS, Extras);
}
}
else
{
cout << "Invalid argument: " << args.OptionText() << endl;
cout << "\tReason: " << GetLastErrorText(errorCode) << endl;
}
}
return false;
}
/// <summary>
/// Return a vector of all available options for the specified program.
/// </summary>
/// <param name="optUsage">The specified program usage</param>
/// <returns>A vector of all available options for the specified program</returns>
vector<CSimpleOpt::SOption> GetSimpleOptions(eOptionUse optUsage = OPT_USE_ALL)
{
vector<CSimpleOpt::SOption> entries;
CSimpleOpt::SOption endOption = SO_END_OF_OPTIONS;
entries.reserve(75);
std::for_each(m_BoolArgs.begin(), m_BoolArgs.end(), [&](Eob* entry) { if (entry->m_OptionUse & optUsage) entries.push_back(entry->m_Option); });
std::for_each(m_IntArgs.begin(), m_IntArgs.end(), [&](Eoi* entry) { if (entry->m_OptionUse & optUsage) entries.push_back(entry->m_Option); });
std::for_each(m_UintArgs.begin(), m_UintArgs.end(), [&](Eou* entry) { if (entry->m_OptionUse & optUsage) entries.push_back(entry->m_Option); });
std::for_each(m_DoubleArgs.begin(), m_DoubleArgs.end(), [&](Eod* entry) { if (entry->m_OptionUse & optUsage) entries.push_back(entry->m_Option); });
std::for_each(m_StringArgs.begin(), m_StringArgs.end(), [&](Eos* entry) { if (entry->m_OptionUse & optUsage) entries.push_back(entry->m_Option); });
entries.push_back(endOption);
return entries;
}
/// <summary>
/// Return a string with the descriptions of all available options for the specified program.
/// </summary>
/// <param name="optUsage">The specified program usage</param>
/// <returns>A string with the descriptions of all available options for the specified program</returns>
string GetUsage(eOptionUse optUsage = OPT_USE_ALL)
{
ostringstream os;
std::for_each(m_BoolArgs.begin(), m_BoolArgs.end(), [&](Eob* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_DocString << endl; });
std::for_each(m_IntArgs.begin(), m_IntArgs.end(), [&](Eoi* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_DocString << endl; });
std::for_each(m_UintArgs.begin(), m_UintArgs.end(), [&](Eou* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_DocString << endl; });
std::for_each(m_DoubleArgs.begin(), m_DoubleArgs.end(), [&](Eod* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_DocString << endl; });
std::for_each(m_StringArgs.begin(), m_StringArgs.end(), [&](Eos* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_DocString << endl; });
return os.str();
}
/// <summary>
/// Return a string with all of the names and values for all available options for the specified program.
/// </summary>
/// <param name="optUsage">The specified program usage</param>
/// <returns>A string with all of the names and values for all available options for the specified program</returns>
string GetValues(eOptionUse optUsage = OPT_USE_ALL)
{
ostringstream os;
os << std::boolalpha;
std::for_each(m_BoolArgs.begin(), m_BoolArgs.end(), [&](Eob* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_NameWithoutDashes << ": " << (*entry)() << endl; });
std::for_each(m_IntArgs.begin(), m_IntArgs.end(), [&](Eoi* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_NameWithoutDashes << ": " << (*entry)() << endl; });
std::for_each(m_UintArgs.begin(), m_UintArgs.end(), [&](Eou* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_NameWithoutDashes << ": " << (*entry)() << endl; });
std::for_each(m_DoubleArgs.begin(), m_DoubleArgs.end(), [&](Eod* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_NameWithoutDashes << ": " << (*entry)() << endl; });
std::for_each(m_StringArgs.begin(), m_StringArgs.end(), [&](Eos* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_NameWithoutDashes << ": " << (*entry)() << endl; });
return os.str();
}
/// <summary>
/// Print description string, version and description of all available options for the specified program.
/// </summary>
/// <param name="optUsage">The specified program usage</param>
void ShowUsage(eOptionUse optUsage)
{
cout << DescriptionString << " version " << EmberVersion() << endl << endl;
if (optUsage == OPT_USE_RENDER)
{
cout << "Usage:\n"
"\tEmberRender.exe --in=test.flam3 [--out=outfile --format=png --verbose --progress --opencl]\n" << endl;
}
else if (optUsage == OPT_USE_ANIMATE)
{
cout << "Usage:\n"
"\tEmberAnimate.exe --in=sequence.flam3 [--format=png --verbose --progress --opencl]\n" << endl;
}
else if (optUsage == OPT_USE_GENOME)
{
cout << "Usage:\n"
"\tEmberGenome.exe --sequence=test.flam3 > sequenceout.flam3\n" << endl;
}
cout << GetUsage(optUsage) << endl;
}
/// <summary>
/// Return the last option parsing error text as a string.
/// </summary>
/// <param name="errorCode">The code of the last parsing error</param>
/// <returns>The last option parsing error text as a string</returns>
string GetLastErrorText(int errorCode)
{
switch (errorCode)
{
case SO_SUCCESS: return "Success";
case SO_OPT_INVALID: return "Unrecognized option";
case SO_OPT_MULTIPLE: return "Option matched multiple strings";
case SO_ARG_INVALID: return "Option does not accept argument";
case SO_ARG_INVALID_TYPE: return "Invalid argument format";
case SO_ARG_MISSING: return "Required argument is missing";
case SO_ARG_INVALID_DATA: return "Invalid argument data";
default: return "Unknown error";
}
}
//Break from the usual m_* notation for members here because
//each of these is a functor, so it looks nicer and is less typing
//to just say opt.Member().
EmberOptionEntry<bool> Help;//Diagnostic bool.
EmberOptionEntry<bool> Version;
EmberOptionEntry<bool> Verbose;
EmberOptionEntry<bool> Debug;
EmberOptionEntry<bool> DumpArgs;
EmberOptionEntry<bool> DoProgress;
EmberOptionEntry<bool> OpenCLInfo;
EmberOptionEntry<bool> EmberCL;//Value bool.
EmberOptionEntry<bool> EarlyClip;
EmberOptionEntry<bool> Transparency;
EmberOptionEntry<bool> NameEnable;
EmberOptionEntry<bool> IntPalette;
EmberOptionEntry<bool> HexPalette;
EmberOptionEntry<bool> InsertPalette;
EmberOptionEntry<bool> JpegComments;
EmberOptionEntry<bool> PngComments;
EmberOptionEntry<bool> WriteGenome;
EmberOptionEntry<bool> Enclosed;
EmberOptionEntry<bool> NoEdits;
EmberOptionEntry<bool> UnsmoothEdge;
EmberOptionEntry<bool> LockAccum;
EmberOptionEntry<bool> DumpKernel;
EmberOptionEntry<int> Symmetry;//Value int.
EmberOptionEntry<int> SheepGen;//Value int.
EmberOptionEntry<int> SheepId;//Value int.
EmberOptionEntry<unsigned int> Platform;//Value unsigned int.
EmberOptionEntry<unsigned int> Device;
EmberOptionEntry<unsigned int> Seed;
EmberOptionEntry<unsigned int> ThreadCount;
EmberOptionEntry<unsigned int> Strips;
EmberOptionEntry<unsigned int> BitsPerChannel;
EmberOptionEntry<unsigned int> SubBatchSize;
EmberOptionEntry<unsigned int> Bits;
EmberOptionEntry<unsigned int> PrintEditDepth;
EmberOptionEntry<unsigned int> JpegQuality;
EmberOptionEntry<unsigned int> FirstFrame;
EmberOptionEntry<unsigned int> LastFrame;
EmberOptionEntry<unsigned int> Frame;
EmberOptionEntry<unsigned int> Time;
EmberOptionEntry<unsigned int> Dtime;
EmberOptionEntry<unsigned int> Frames;
EmberOptionEntry<unsigned int> Loops;
EmberOptionEntry<unsigned int> Repeat;
EmberOptionEntry<unsigned int> Tries;
EmberOptionEntry<unsigned int> MaxXforms;
EmberOptionEntry<double> SizeScale;//Value double.
EmberOptionEntry<double> QualityScale;
EmberOptionEntry<double> AspectRatio;
EmberOptionEntry<double> Stagger;
EmberOptionEntry<double> AvgThresh;
EmberOptionEntry<double> BlackThresh;
EmberOptionEntry<double> WhiteLimit;
EmberOptionEntry<double> Speed;
EmberOptionEntry<double> OffsetX;
EmberOptionEntry<double> OffsetY;
EmberOptionEntry<double> UseMem;
EmberOptionEntry<string> IsaacSeed;//Value string.
EmberOptionEntry<string> Input;
EmberOptionEntry<string> Out;
EmberOptionEntry<string> Prefix;
EmberOptionEntry<string> Suffix;
EmberOptionEntry<string> Format;
EmberOptionEntry<string> PalettePath;
EmberOptionEntry<string> PaletteImage;
EmberOptionEntry<string> Id;
EmberOptionEntry<string> Url;
EmberOptionEntry<string> Nick;
EmberOptionEntry<string> Comment;
EmberOptionEntry<string> TemplateFile;
EmberOptionEntry<string> Clone;
EmberOptionEntry<string> CloneAll;
EmberOptionEntry<string> CloneAction;
EmberOptionEntry<string> Animate;
EmberOptionEntry<string> Mutate;
EmberOptionEntry<string> Cross0;
EmberOptionEntry<string> Cross1;
EmberOptionEntry<string> Method;
EmberOptionEntry<string> Inter;
EmberOptionEntry<string> Rotate;
EmberOptionEntry<string> Strip;
EmberOptionEntry<string> Sequence;
EmberOptionEntry<string> UseVars;
EmberOptionEntry<string> DontUseVars;
EmberOptionEntry<string> Extras;
private:
vector<EmberOptionEntry<bool>*> m_BoolArgs;
vector<EmberOptionEntry<int>*> m_IntArgs;
vector<EmberOptionEntry<unsigned int>*> m_UintArgs;
vector<EmberOptionEntry<double>*> m_DoubleArgs;
vector<EmberOptionEntry<string>*> m_StringArgs;
};

View File

@ -0,0 +1,362 @@
#pragma once
#include "EmberCommonPch.h"
#define PNG_COMMENT_MAX 8
/// <summary>
/// Write a PPM file.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <param name="image">Pointer to the image data to write</param>
/// <param name="width">Width of the image in pixels</param>
/// <param name="height">Height of the image in pixels</param>
/// <returns>True if success, else false</returns>
static bool WritePpm(const char* filename, unsigned char* image, int width, int height)
{
bool b = false;
unsigned int size = width * height * 3;
FILE* file;
if (fopen_s(&file, filename, "wb") == 0)
{
fprintf_s(file, "P6\n");
fprintf_s(file, "%d %d\n255\n", width, height);
b = (size == fwrite(image, 1, size, file));
fclose(file);
}
return b;
}
/// <summary>
/// Write a JPEG file.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <param name="image">Pointer to the image data to write</param>
/// <param name="width">Width of the image in pixels</param>
/// <param name="height">Height of the image in pixels</param>
/// <param name="quality">The quality to use</param>
/// <param name="enableComments">True to embed comments, else false</param>
/// <param name="comments">The comment string to embed</param>
/// <param name="id">Id of the author</param>
/// <param name="url">Url of the author</param>
/// <param name="nick">Nickname of the author</param>
/// <returns>True if success, else false</returns>
static bool WriteJpeg(const char* filename, unsigned char* image, unsigned int width, unsigned int height, int quality, bool enableComments, EmberImageComments& comments, string id, string url, string nick)
{
bool b = false;
FILE* file;
if (fopen_s(&file, filename, "wb") == 0)
{
size_t i;
jpeg_error_mgr jerr;
jpeg_compress_struct info;
char nickString[64], urlString[128], idString[128];
char bvString[64], niString[64], rtString[64];
char genomeString[65536], verString[64];
//Create the mandatory comment strings.
snprintf_s(genomeString, 65536, "flam3_genome: %s", comments.m_Genome.c_str());
snprintf_s(bvString, 64, "flam3_error_rate: %s", comments.m_Badvals.c_str());
snprintf_s(niString, 64, "flam3_samples: %s", comments.m_NumIters);
snprintf_s(rtString, 64, "flam3_time: %s", comments.m_Runtime.c_str());
snprintf_s(verString, 64, "flam3_version: %s", EmberVersion());
info.err = jpeg_std_error(&jerr);
jpeg_create_compress(&info);
jpeg_stdio_dest(&info, file);
info.in_color_space = JCS_RGB;
info.input_components = 3;
info.image_width = width;
info.image_height = height;
jpeg_set_defaults(&info);
jpeg_set_quality(&info, quality, TRUE);
jpeg_start_compress(&info, TRUE);
//Write comments to jpeg.
if (enableComments)
{
jpeg_write_marker(&info, JPEG_COM, (unsigned char*)verString, (int)strlen(verString));
if (nick != "")
{
snprintf_s(nickString, 64, "flam3_nickname: %s", nick.c_str());
jpeg_write_marker(&info, JPEG_COM, (unsigned char*)nickString, (int)strlen(nickString));
}
if (url != "")
{
snprintf_s(urlString, 128, "flam3_url: %s", url.c_str());
jpeg_write_marker(&info, JPEG_COM, (unsigned char*)urlString, (int)strlen(urlString));
}
if (id != "")
{
snprintf_s(idString, 128, "flam3_id: %s", id.c_str());
jpeg_write_marker(&info, JPEG_COM, (unsigned char*)idString, (int)strlen(idString));
}
jpeg_write_marker(&info, JPEG_COM, (unsigned char*)bvString, (int)strlen(bvString));
jpeg_write_marker(&info, JPEG_COM, (unsigned char*)niString, (int)strlen(niString));
jpeg_write_marker(&info, JPEG_COM, (unsigned char*)rtString, (int)strlen(rtString));
jpeg_write_marker(&info, JPEG_COM, (unsigned char*)genomeString, (int)strlen(genomeString));
}
for (i = 0; i < height; i++)
{
JSAMPROW row_pointer[1];
row_pointer[0] = (unsigned char*)image + (3 * width * i);
jpeg_write_scanlines(&info, row_pointer, 1);
}
jpeg_finish_compress(&info);
jpeg_destroy_compress(&info);
fclose(file);
b = true;
}
return b;
}
/// <summary>
/// Write a PNG file.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <param name="image">Pointer to the image data to write</param>
/// <param name="width">Width of the image in pixels</param>
/// <param name="height">Height of the image in pixels</param>
/// <param name="bytesPerChannel">Bytes per channel, 1 or 2.</param>
/// <param name="enableComments">True to embed comments, else false</param>
/// <param name="comments">The comment string to embed</param>
/// <param name="id">Id of the author</param>
/// <param name="url">Url of the author</param>
/// <param name="nick">Nickname of the author</param>
/// <returns>True if success, else false</returns>
static bool WritePng(const char* filename, unsigned char* image, unsigned int width, unsigned int height, int bytesPerChannel, bool enableComments, EmberImageComments& comments, string id, string url, string nick)
{
bool b = false;
FILE* file;
if (fopen_s(&file, filename, "wb") == 0)
{
png_structp png_ptr;
png_infop info_ptr;
png_text text[PNG_COMMENT_MAX];
size_t i;
unsigned short testbe = 1;
vector<unsigned char*> rows(height);
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
text[0].key = "flam3_version";
text[0].text = EmberVersion();
text[1].compression = PNG_TEXT_COMPRESSION_NONE;
text[1].key = "flam3_nickname";
text[1].text = (png_charp)nick.c_str();
text[2].compression = PNG_TEXT_COMPRESSION_NONE;
text[2].key = "flam3_url";
text[2].text = (png_charp)url.c_str();
text[3].compression = PNG_TEXT_COMPRESSION_NONE;
text[3].key = "flam3_id";
text[3].text = (png_charp)id.c_str();
text[4].compression = PNG_TEXT_COMPRESSION_NONE;
text[4].key = "flam3_error_rate";
text[4].text = (png_charp)comments.m_Badvals.c_str();
text[5].compression = PNG_TEXT_COMPRESSION_NONE;
text[5].key = "flam3_samples";
text[5].text = (png_charp)comments.m_NumIters.c_str();
text[6].compression = PNG_TEXT_COMPRESSION_NONE;
text[6].key = "flam3_time";
text[6].text = (png_charp)comments.m_Runtime.c_str();
text[7].compression = PNG_TEXT_COMPRESSION_zTXt;
text[7].key = "flam3_genome";
text[7].text = (png_charp)comments.m_Genome.c_str();
for (i = 0; i < height; i++)
rows[i] = (unsigned char*)image + i * width * 4 * bytesPerChannel;
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
info_ptr = png_create_info_struct(png_ptr);
if (setjmp(png_jmpbuf(png_ptr)))
{
fclose(file);
png_destroy_write_struct(&png_ptr, &info_ptr);
perror("writing file");
return false;
}
png_init_io(png_ptr, file);
png_set_IHDR(png_ptr, info_ptr, width, height, 8 * bytesPerChannel,
PNG_COLOR_TYPE_RGBA,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE,
PNG_FILTER_TYPE_BASE);
if (enableComments == 1)
png_set_text(png_ptr, info_ptr, text, PNG_COMMENT_MAX);
png_write_info(png_ptr, info_ptr);
//Must set this after png_write_info().
if (bytesPerChannel == 2 && testbe != htons(testbe))
{
png_set_swap(png_ptr);
}
png_write_image(png_ptr, (png_bytepp) rows.data());
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(file);
b = true;
}
return b;
}
/// <summary>
/// Convert an RGB buffer to BGR for usage with BMP.
/// </summary>
/// <param name="buffer">The buffer to convert</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="newSize">The size of the new buffer created</param>
/// <returns>The converted buffer if successful, else NULL.</returns>
static BYTE* ConvertRGBToBMPBuffer(BYTE* buffer, int width, int height, long& newSize)
{
if (NULL == buffer || width == 0 || height == 0)
return NULL;
int padding = 0;
int scanlinebytes = width * 3;
while ((scanlinebytes + padding ) % 4 != 0)
padding++;
int psw = scanlinebytes + padding;
newSize = height * psw;
BYTE* newBuf = new BYTE[newSize];
if (newBuf)
{
memset (newBuf, 0, newSize);
long bufpos = 0;
long newpos = 0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < 3 * width; x += 3)
{
bufpos = y * 3 * width + x; // position in original buffer
newpos = (height - y - 1) * psw + x; // position in padded buffer
newBuf[newpos] = buffer[bufpos+2]; // swap r and b
newBuf[newpos + 1] = buffer[bufpos + 1]; // g stays
newBuf[newpos + 2] = buffer[bufpos]; // swap b and r
//No swap.
//newBuf[newpos] = buffer[bufpos];
//newBuf[newpos + 1] = buffer[bufpos + 1];
//newBuf[newpos + 2] = buffer[bufpos + 2];
}
}
return newBuf;
}
return NULL;
}
/// <summary>
/// Save a Bmp file.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <param name="image">Pointer to the image data to write</param>
/// <param name="width">Width of the image in pixels</param>
/// <param name="height">Height of the image in pixels</param>
/// <param name="paddedSize">Padded size, greater than or equal to total image size.</param>
/// <returns>True if success, else false</returns>
static bool SaveBmp(const char* filename, BYTE* image, int width, int height, long paddedSize)
{
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER info;
unsigned long bwritten;
HANDLE file;
memset (&bmfh, 0, sizeof (BITMAPFILEHEADER));
memset (&info, 0, sizeof (BITMAPINFOHEADER));
bmfh.bfType = 0x4d42; // 0x4d42 = 'BM'
bmfh.bfReserved1 = 0;
bmfh.bfReserved2 = 0;
bmfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + paddedSize;
bmfh.bfOffBits = 0x36;
info.biSize = sizeof(BITMAPINFOHEADER);
info.biWidth = width;
info.biHeight = height;
info.biPlanes = 1;
info.biBitCount = 24;
info.biCompression = BI_RGB;
info.biSizeImage = 0;
info.biXPelsPerMeter = 0x0ec4;
info.biYPelsPerMeter = 0x0ec4;
info.biClrUsed = 0;
info.biClrImportant = 0;
if ((file = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == NULL)
{
CloseHandle(file);
return false;
}
if (WriteFile(file, &bmfh, sizeof (BITMAPFILEHEADER), &bwritten, NULL) == false)
{
CloseHandle(file);
return false;
}
if (WriteFile(file, &info, sizeof(BITMAPINFOHEADER), &bwritten, NULL) == false)
{
CloseHandle(file);
return false;
}
if (WriteFile(file, image, paddedSize, &bwritten, NULL) == false)
{
CloseHandle(file);
return false;
}
CloseHandle(file);
return true;
}
/// <summary>
/// Convert a buffer from RGB to BGR and write a Bmp file.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <param name="image">Pointer to the image data to write</param>
/// <param name="width">Width of the image in pixels</param>
/// <param name="height">Height of the image in pixels</param>
/// <returns>True if success, else false</returns>
static bool WriteBmp(const char* filename, unsigned char* image, int width, int height)
{
bool b = false;
long newSize;
auto_ptr<BYTE> bgrBuf(ConvertRGBToBMPBuffer(image, width, height, newSize));
if (bgrBuf.get())
b = SaveBmp(filename, bgrBuf.get(), width, height, newSize);
return b;
}

View File

@ -0,0 +1,959 @@
/*! @file SimpleGlob.h
@version 3.6
@brief A cross-platform file globbing library providing the ability to
expand wildcards in command-line arguments to a list of all matching
files. It is designed explicitly to be portable to any platform and has
been tested on Windows and Linux. See CSimpleGlobTempl for the class
definition.
@section features FEATURES
- MIT Licence allows free use in all software (including GPL and
commercial)
- multi-platform (Windows 95/98/ME/NT/2K/XP, Linux, Unix)
- supports most of the standard linux glob() options
- recognition of a forward paths as equivalent to a backward slash
on Windows. e.g. "c:/path/foo*" is equivalent to "c:\path\foo*".
- implemented with only a single C++ header file
- char, wchar_t and Windows TCHAR in the same program
- complete working examples included
- compiles cleanly at warning level 4 (Windows/VC.NET 2003),
warning level 3 (Windows/VC6) and -Wall (Linux/gcc)
@section usage USAGE
The SimpleGlob class is used by following these steps:
<ol>
<li> Include the SimpleGlob.h header file
<pre>
\#include "SimpleGlob.h"
</pre>
<li> Instantiate a CSimpleGlob object supplying the appropriate flags.
<pre>
@link CSimpleGlobTempl CSimpleGlob @endlink glob(FLAGS);
</pre>
<li> Add all file specifications to the glob class.
<pre>
glob.Add("file*");
glob.Add(argc, argv);
</pre>
<li> Process all files with File(), Files() and FileCount()
<pre>
for (int n = 0; n < glob.FileCount(); ++n) {
ProcessFile(glob.File(n));
}
</pre>
</ol>
@section licence MIT LICENCE
<pre>
The licence text below is the boilerplate "MIT Licence" used from:
http://www.opensource.org/licenses/mit-license.php
Copyright (c) 2006-2013, Brodie Thiesfield
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</pre>
*/
#ifndef INCLUDED_SimpleGlob
#define INCLUDED_SimpleGlob
/*! @brief The operation of SimpleGlob is fine-tuned via the use of a
combination of the following flags.
The flags may be passed at initialization of the class and used for every
filespec added, or alternatively they may optionally be specified in the
call to Add() and be different for each filespec.
@param SG_GLOB_ERR
Return upon read error (e.g. directory does not have read permission)
@param SG_GLOB_MARK
Append a slash (backslash in Windows) to every path which corresponds
to a directory
@param SG_GLOB_NOSORT
By default, files are returned in sorted into string order. With this
flag, no sorting is done. This is not compatible with
SG_GLOB_FULLSORT.
@param SG_GLOB_FULLSORT
By default, files are sorted in groups belonging to each filespec that
was added. For example if the filespec "b*" was added before the
filespec "a*" then the argv array will contain all b* files sorted in
order, followed by all a* files sorted in order. If this flag is
specified, the entire array will be sorted ignoring the filespec
groups.
@param SG_GLOB_NOCHECK
If the pattern doesn't match anything, return the original pattern.
@param SG_GLOB_TILDE
Tilde expansion is carried out (on Unix platforms)
@param SG_GLOB_ONLYDIR
Return only directories which match (not compatible with
SG_GLOB_ONLYFILE)
@param SG_GLOB_ONLYFILE
Return only files which match (not compatible with SG_GLOB_ONLYDIR)
@param SG_GLOB_NODOT
Do not return the "." or ".." special directories.
*/
enum SG_Flags {
SG_GLOB_ERR = 1 << 0,
SG_GLOB_MARK = 1 << 1,
SG_GLOB_NOSORT = 1 << 2,
SG_GLOB_NOCHECK = 1 << 3,
SG_GLOB_TILDE = 1 << 4,
SG_GLOB_ONLYDIR = 1 << 5,
SG_GLOB_ONLYFILE = 1 << 6,
SG_GLOB_NODOT = 1 << 7,
SG_GLOB_FULLSORT = 1 << 8
};
/*! @brief Error return codes */
enum SG_Error {
SG_SUCCESS = 0,
SG_ERR_NOMATCH = 1,
SG_ERR_MEMORY = -1,
SG_ERR_FAILURE = -2
};
// ---------------------------------------------------------------------------
// Platform dependent implementations
// if we aren't on Windows and we have ICU available, then enable ICU
// by default. Define this to 0 to intentially disable it.
#ifndef SG_HAVE_ICU
# if !defined(_WIN32) && defined(USTRING_H)
# define SG_HAVE_ICU 1
# else
# define SG_HAVE_ICU 0
# endif
#endif
// don't include this in documentation as it isn't relevant
#ifndef DOXYGEN
// on Windows we want to use MBCS aware string functions and mimic the
// Unix glob functionality. On Unix we just use glob.
#ifdef _WIN32
# include <mbstring.h>
# define sg_strchr ::_mbschr
# define sg_strrchr ::_mbsrchr
# define sg_strlen ::_mbslen
# if __STDC_WANT_SECURE_LIB__
# define sg_strcpy_s(a,n,b) ::_mbscpy_s(a,n,b)
# else
# define sg_strcpy_s(a,n,b) ::_mbscpy(a,b)
# endif
# define sg_strcmp ::_mbscmp
# define sg_strcasecmp ::_mbsicmp
# define SOCHAR_T unsigned char
#else
# include <sys/types.h>
# include <sys/stat.h>
# include <glob.h>
# include <limits.h>
# define MAX_PATH PATH_MAX
# define sg_strchr ::strchr
# define sg_strrchr ::strrchr
# define sg_strlen ::strlen
# define sg_strcpy_s(a,n,b) ::strcpy(a,b)
# define sg_strcmp ::strcmp
# define sg_strcasecmp ::strcasecmp
# define SOCHAR_T char
#endif
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
// use assertions to test the input data
#ifdef _DEBUG
# ifdef _MSC_VER
# include <crtdbg.h>
# define SG_ASSERT(b) _ASSERTE(b)
# else
# include <assert.h>
# define SG_ASSERT(b) assert(b)
# endif
#else
# define SG_ASSERT(b)
#endif
/*! @brief String manipulation functions. */
class SimpleGlobUtil
{
public:
static const char * strchr(const char *s, char c) {
return (char *) sg_strchr((const SOCHAR_T *)s, c);
}
static const wchar_t * strchr(const wchar_t *s, wchar_t c) {
return ::wcschr(s, c);
}
#if SG_HAVE_ICU
static const UChar * strchr(const UChar *s, UChar c) {
return ::u_strchr(s, c);
}
#endif
static const char * strrchr(const char *s, char c) {
return (char *) sg_strrchr((const SOCHAR_T *)s, c);
}
static const wchar_t * strrchr(const wchar_t *s, wchar_t c) {
return ::wcsrchr(s, c);
}
#if SG_HAVE_ICU
static const UChar * strrchr(const UChar *s, UChar c) {
return ::u_strrchr(s, c);
}
#endif
// Note: char strlen returns number of bytes, not characters
static size_t strlen(const char *s) { return ::strlen(s); }
static size_t strlen(const wchar_t *s) { return ::wcslen(s); }
#if SG_HAVE_ICU
static size_t strlen(const UChar *s) { return ::u_strlen(s); }
#endif
static void strcpy_s(char *dst, size_t n, const char *src) {
(void) n;
sg_strcpy_s((SOCHAR_T *)dst, n, (const SOCHAR_T *)src);
}
static void strcpy_s(wchar_t *dst, size_t n, const wchar_t *src) {
# if __STDC_WANT_SECURE_LIB__
::wcscpy_s(dst, n, src);
#else
(void) n;
::wcscpy(dst, src);
#endif
}
#if SG_HAVE_ICU
static void strcpy_s(UChar *dst, size_t n, const UChar *src) {
::u_strncpy(dst, src, n);
}
#endif
static int strcmp(const char *s1, const char *s2) {
return sg_strcmp((const SOCHAR_T *)s1, (const SOCHAR_T *)s2);
}
static int strcmp(const wchar_t *s1, const wchar_t *s2) {
return ::wcscmp(s1, s2);
}
#if SG_HAVE_ICU
static int strcmp(const UChar *s1, const UChar *s2) {
return ::u_strcmp(s1, s2);
}
#endif
static int strcasecmp(const char *s1, const char *s2) {
return sg_strcasecmp((const SOCHAR_T *)s1, (const SOCHAR_T *)s2);
}
#if _WIN32
static int strcasecmp(const wchar_t *s1, const wchar_t *s2) {
return ::_wcsicmp(s1, s2);
}
#endif // _WIN32
#if SG_HAVE_ICU
static int strcasecmp(const UChar *s1, const UChar *s2) {
return u_strcasecmp(s1, s2, 0);
}
#endif
};
enum SG_FileType {
SG_FILETYPE_INVALID,
SG_FILETYPE_FILE,
SG_FILETYPE_DIR
};
#ifdef _WIN32
#ifndef INVALID_FILE_ATTRIBUTES
# define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
#endif
#define SG_PATH_CHAR '\\'
/*! @brief Windows glob implementation. */
template<class SOCHAR>
struct SimpleGlobBase
{
SimpleGlobBase() : m_hFind(INVALID_HANDLE_VALUE) { }
int FindFirstFileS(const char * a_pszFileSpec, unsigned int) {
m_hFind = FindFirstFileA(a_pszFileSpec, &m_oFindDataA);
if (m_hFind != INVALID_HANDLE_VALUE) {
return SG_SUCCESS;
}
DWORD dwErr = GetLastError();
if (dwErr == ERROR_FILE_NOT_FOUND) {
return SG_ERR_NOMATCH;
}
return SG_ERR_FAILURE;
}
int FindFirstFileS(const wchar_t * a_pszFileSpec, unsigned int) {
m_hFind = FindFirstFileW(a_pszFileSpec, &m_oFindDataW);
if (m_hFind != INVALID_HANDLE_VALUE) {
return SG_SUCCESS;
}
DWORD dwErr = GetLastError();
if (dwErr == ERROR_FILE_NOT_FOUND) {
return SG_ERR_NOMATCH;
}
return SG_ERR_FAILURE;
}
bool FindNextFileS(char) {
return FindNextFileA(m_hFind, &m_oFindDataA) != FALSE;
}
bool FindNextFileS(wchar_t) {
return FindNextFileW(m_hFind, &m_oFindDataW) != FALSE;
}
void FindDone() {
FindClose(m_hFind);
}
const char * GetFileNameS(char) const {
return m_oFindDataA.cFileName;
}
const wchar_t * GetFileNameS(wchar_t) const {
return m_oFindDataW.cFileName;
}
bool IsDirS(char) const {
return this->GetFileTypeS(m_oFindDataA.dwFileAttributes) == SG_FILETYPE_DIR;
}
bool IsDirS(wchar_t) const {
return this->GetFileTypeS(m_oFindDataW.dwFileAttributes) == SG_FILETYPE_DIR;
}
SG_FileType GetFileTypeS(const char * a_pszPath) {
return this->GetFileTypeS(GetFileAttributesA(a_pszPath));
}
SG_FileType GetFileTypeS(const wchar_t * a_pszPath) {
return this->GetFileTypeS(GetFileAttributesW(a_pszPath));
}
SG_FileType GetFileTypeS(DWORD a_dwAttribs) const {
if (a_dwAttribs == INVALID_FILE_ATTRIBUTES) {
return SG_FILETYPE_INVALID;
}
if (a_dwAttribs & FILE_ATTRIBUTE_DIRECTORY) {
return SG_FILETYPE_DIR;
}
return SG_FILETYPE_FILE;
}
private:
HANDLE m_hFind;
WIN32_FIND_DATAA m_oFindDataA;
WIN32_FIND_DATAW m_oFindDataW;
};
#else // !_WIN32
#define SG_PATH_CHAR '/'
/*! @brief Unix glob implementation. */
template<class SOCHAR>
struct SimpleGlobBase
{
SimpleGlobBase() {
memset(&m_glob, 0, sizeof(m_glob));
m_uiCurr = (size_t)-1;
}
~SimpleGlobBase() {
globfree(&m_glob);
}
void FilePrep() {
m_bIsDir = false;
size_t len = strlen(m_glob.gl_pathv[m_uiCurr]);
if (m_glob.gl_pathv[m_uiCurr][len-1] == '/') {
m_bIsDir = true;
m_glob.gl_pathv[m_uiCurr][len-1] = 0;
}
}
int FindFirstFileS(const char * a_pszFileSpec, unsigned int a_uiFlags) {
int nFlags = GLOB_MARK | GLOB_NOSORT;
if (a_uiFlags & SG_GLOB_ERR) nFlags |= GLOB_ERR;
if (a_uiFlags & SG_GLOB_TILDE) nFlags |= GLOB_TILDE;
int rc = glob(a_pszFileSpec, nFlags, NULL, &m_glob);
if (rc == GLOB_NOSPACE) return SG_ERR_MEMORY;
if (rc == GLOB_ABORTED) return SG_ERR_FAILURE;
if (rc == GLOB_NOMATCH) return SG_ERR_NOMATCH;
m_uiCurr = 0;
FilePrep();
return SG_SUCCESS;
}
#if SG_HAVE_ICU
int FindFirstFileS(const UChar * a_pszFileSpec, unsigned int a_uiFlags) {
char buf[PATH_MAX] = { 0 };
UErrorCode status = U_ZERO_ERROR;
u_strToUTF8(buf, sizeof(buf), NULL, a_pszFileSpec, -1, &status);
if (U_FAILURE(status)) return SG_ERR_FAILURE;
return this->FindFirstFileS(buf, a_uiFlags);
}
#endif
bool FindNextFileS(char) {
SG_ASSERT(m_uiCurr != (size_t)-1);
if (++m_uiCurr >= m_glob.gl_pathc) {
return false;
}
FilePrep();
return true;
}
#if SG_HAVE_ICU
bool FindNextFileS(UChar) {
return this->FindNextFileS((char)0);
}
#endif
void FindDone() {
globfree(&m_glob);
memset(&m_glob, 0, sizeof(m_glob));
m_uiCurr = (size_t)-1;
}
const char * GetFileNameS(char) const {
SG_ASSERT(m_uiCurr != (size_t)-1);
return m_glob.gl_pathv[m_uiCurr];
}
#if SG_HAVE_ICU
const UChar * GetFileNameS(UChar) const {
const char * pszFile = this->GetFileNameS((char)0);
if (!pszFile) return NULL;
UErrorCode status = U_ZERO_ERROR;
memset(m_szBuf, 0, sizeof(m_szBuf));
u_strFromUTF8(m_szBuf, PATH_MAX, NULL, pszFile, -1, &status);
if (U_FAILURE(status)) return NULL;
return m_szBuf;
}
#endif
bool IsDirS(char) const {
SG_ASSERT(m_uiCurr != (size_t)-1);
return m_bIsDir;
}
#if SG_HAVE_ICU
bool IsDirS(UChar) const {
return this->IsDirS((char)0);
}
#endif
SG_FileType GetFileTypeS(const char * a_pszPath) const {
struct stat sb;
if (0 != stat(a_pszPath, &sb)) {
return SG_FILETYPE_INVALID;
}
if (S_ISDIR(sb.st_mode)) {
return SG_FILETYPE_DIR;
}
if (S_ISREG(sb.st_mode)) {
return SG_FILETYPE_FILE;
}
return SG_FILETYPE_INVALID;
}
#if SG_HAVE_ICU
SG_FileType GetFileTypeS(const UChar * a_pszPath) const {
char buf[PATH_MAX] = { 0 };
UErrorCode status = U_ZERO_ERROR;
u_strToUTF8(buf, sizeof(buf), NULL, a_pszPath, -1, &status);
if (U_FAILURE(status)) return SG_FILETYPE_INVALID;
return this->GetFileTypeS(buf);
}
#endif
private:
glob_t m_glob;
size_t m_uiCurr;
bool m_bIsDir;
#if SG_HAVE_ICU
mutable UChar m_szBuf[PATH_MAX];
#endif
};
#endif // _WIN32
#endif // DOXYGEN
// ---------------------------------------------------------------------------
// MAIN TEMPLATE CLASS
// ---------------------------------------------------------------------------
/*! @brief Implementation of the SimpleGlob class */
template<class SOCHAR>
class CSimpleGlobTempl : private SimpleGlobBase<SOCHAR>
{
public:
/*! @brief Initialize the class.
@param a_uiFlags Combination of SG_GLOB flags.
@param a_nReservedSlots Number of slots in the argv array that
should be reserved. In the returned array these slots
argv[0] ... argv[a_nReservedSlots-1] will be left empty for
the caller to fill in.
*/
CSimpleGlobTempl(unsigned int a_uiFlags = 0, int a_nReservedSlots = 0);
/*! @brief Deallocate all memory buffers. */
~CSimpleGlobTempl();
/*! @brief Initialize (or re-initialize) the class in preparation for
adding new filespecs.
All existing files are cleared. Note that allocated memory is only
deallocated at object destruction.
@param a_uiFlags Combination of SG_GLOB flags.
@param a_nReservedSlots Number of slots in the argv array that
should be reserved. In the returned array these slots
argv[0] ... argv[a_nReservedSlots-1] will be left empty for
the caller to fill in.
*/
int Init(unsigned int a_uiFlags = 0, int a_nReservedSlots = 0);
/*! @brief Add a new filespec to the glob.
The filesystem will be immediately scanned for all matching files and
directories and they will be added to the glob.
@param a_pszFileSpec Filespec to add to the glob.
@return SG_SUCCESS Matching files were added to the glob.
@return SG_ERR_NOMATCH Nothing matched the pattern. To ignore this
error compare return value to >= SG_SUCCESS.
@return SG_ERR_MEMORY Out of memory failure.
@return SG_ERR_FAILURE General failure.
*/
int Add(const SOCHAR *a_pszFileSpec);
/*! @brief Add an array of filespec to the glob.
The filesystem will be immediately scanned for all matching files and
directories in each filespec and they will be added to the glob.
@param a_nCount Number of filespec in the array.
@param a_rgpszFileSpec Array of filespec to add to the glob.
@return SG_SUCCESS Matching files were added to the glob.
@return SG_ERR_NOMATCH Nothing matched the pattern. To ignore this
error compare return value to >= SG_SUCCESS.
@return SG_ERR_MEMORY Out of memory failure.
@return SG_ERR_FAILURE General failure.
*/
int Add(int a_nCount, const SOCHAR * const * a_rgpszFileSpec);
/*! @brief Return the number of files in the argv array.
*/
inline int FileCount() const { return m_nArgsLen; }
/*! @brief Return the full argv array. */
inline SOCHAR ** Files() {
SetArgvArrayType(POINTERS);
return m_rgpArgs;
}
/*! @brief Return the a single file. */
inline SOCHAR * File(int n) {
SG_ASSERT(n >= 0 && n < m_nArgsLen);
return Files()[n];
}
private:
CSimpleGlobTempl(const CSimpleGlobTempl &); // disabled
CSimpleGlobTempl & operator=(const CSimpleGlobTempl &); // disabled
/*! @brief The argv array has it's members stored as either an offset into
the string buffer, or as pointers to their string in the buffer. The
offsets are used because if the string buffer is dynamically resized,
all pointers into that buffer would become invalid.
*/
enum ARG_ARRAY_TYPE { OFFSETS, POINTERS };
/*! @brief Change the type of data stored in the argv array. */
void SetArgvArrayType(ARG_ARRAY_TYPE a_nNewType);
/*! @brief Add a filename to the array if it passes all requirements. */
int AppendName(const SOCHAR *a_pszFileName, bool a_bIsDir);
/*! @brief Grow the argv array to the required size. */
bool GrowArgvArray(int a_nNewLen);
/*! @brief Grow the string buffer to the required size. */
bool GrowStringBuffer(size_t a_uiMinSize);
/*! @brief Compare two (possible NULL) strings */
static int fileSortCompare(const void *a1, const void *a2);
private:
unsigned int m_uiFlags;
ARG_ARRAY_TYPE m_nArgArrayType; //!< argv is indexes or pointers
SOCHAR ** m_rgpArgs; //!< argv
int m_nReservedSlots; //!< # client slots in argv array
int m_nArgsSize; //!< allocated size of array
int m_nArgsLen; //!< used length
SOCHAR * m_pBuffer; //!< argv string buffer
size_t m_uiBufferSize; //!< allocated size of buffer
size_t m_uiBufferLen; //!< used length of buffer
SOCHAR m_szPathPrefix[MAX_PATH]; //!< wildcard path prefix
};
// ---------------------------------------------------------------------------
// IMPLEMENTATION
// ---------------------------------------------------------------------------
template<class SOCHAR>
CSimpleGlobTempl<SOCHAR>::CSimpleGlobTempl(
unsigned int a_uiFlags,
int a_nReservedSlots
)
{
m_rgpArgs = NULL;
m_nArgsSize = 0;
m_pBuffer = NULL;
m_uiBufferSize = 0;
Init(a_uiFlags, a_nReservedSlots);
}
template<class SOCHAR>
CSimpleGlobTempl<SOCHAR>::~CSimpleGlobTempl()
{
if (m_rgpArgs) free(m_rgpArgs);
if (m_pBuffer) free(m_pBuffer);
}
template<class SOCHAR>
int
CSimpleGlobTempl<SOCHAR>::Init(
unsigned int a_uiFlags,
int a_nReservedSlots
)
{
m_nArgArrayType = POINTERS;
m_uiFlags = a_uiFlags;
m_nArgsLen = a_nReservedSlots;
m_nReservedSlots = a_nReservedSlots;
m_uiBufferLen = 0;
if (m_nReservedSlots > 0) {
if (!GrowArgvArray(m_nReservedSlots)) {
return SG_ERR_MEMORY;
}
for (int n = 0; n < m_nReservedSlots; ++n) {
m_rgpArgs[n] = NULL;
}
}
return SG_SUCCESS;
}
template<class SOCHAR>
int
CSimpleGlobTempl<SOCHAR>::Add(
const SOCHAR *a_pszFileSpec
)
{
#ifdef _WIN32
// Windows FindFirst/FindNext recognizes forward slash as the same as
// backward slash and follows the directories. We need to do the same
// when calculating the prefix and when we have no wildcards.
SOCHAR szFileSpec[MAX_PATH];
SimpleGlobUtil::strcpy_s(szFileSpec, MAX_PATH, a_pszFileSpec);
const SOCHAR * pszPath = SimpleGlobUtil::strchr(szFileSpec, '/');
while (pszPath) {
szFileSpec[pszPath - szFileSpec] = SG_PATH_CHAR;
pszPath = SimpleGlobUtil::strchr(pszPath + 1, '/');
}
a_pszFileSpec = szFileSpec;
#endif
// if this doesn't contain wildcards then we can just add it directly
m_szPathPrefix[0] = 0;
if (!SimpleGlobUtil::strchr(a_pszFileSpec, '*') &&
!SimpleGlobUtil::strchr(a_pszFileSpec, '?'))
{
SG_FileType nType = this->GetFileTypeS(a_pszFileSpec);
if (nType == SG_FILETYPE_INVALID) {
if (m_uiFlags & SG_GLOB_NOCHECK) {
return AppendName(a_pszFileSpec, false);
}
return SG_ERR_NOMATCH;
}
return AppendName(a_pszFileSpec, nType == SG_FILETYPE_DIR);
}
#ifdef _WIN32
// Windows doesn't return the directory with the filename, so we need to
// extract the path from the search string ourselves and prefix it to the
// filename we get back.
const SOCHAR * pszFilename =
SimpleGlobUtil::strrchr(a_pszFileSpec, SG_PATH_CHAR);
if (pszFilename) {
SimpleGlobUtil::strcpy_s(m_szPathPrefix, MAX_PATH, a_pszFileSpec);
m_szPathPrefix[pszFilename - a_pszFileSpec + 1] = 0;
}
#endif
// search for the first match on the file
int rc = this->FindFirstFileS(a_pszFileSpec, m_uiFlags);
if (rc != SG_SUCCESS) {
if (rc == SG_ERR_NOMATCH && (m_uiFlags & SG_GLOB_NOCHECK)) {
int ok = AppendName(a_pszFileSpec, false);
if (ok != SG_SUCCESS) rc = ok;
}
return rc;
}
// add it and find all subsequent matches
int nError, nStartLen = m_nArgsLen;
bool bSuccess;
do {
nError = AppendName(this->GetFileNameS((SOCHAR)0), this->IsDirS((SOCHAR)0));
bSuccess = this->FindNextFileS((SOCHAR)0);
}
while (nError == SG_SUCCESS && bSuccess);
SimpleGlobBase<SOCHAR>::FindDone();
// sort these files if required
if (m_nArgsLen > nStartLen && !(m_uiFlags & SG_GLOB_NOSORT)) {
if (m_uiFlags & SG_GLOB_FULLSORT) {
nStartLen = m_nReservedSlots;
}
SetArgvArrayType(POINTERS);
qsort(
m_rgpArgs + nStartLen,
m_nArgsLen - nStartLen,
sizeof(m_rgpArgs[0]), fileSortCompare);
}
return nError;
}
template<class SOCHAR>
int
CSimpleGlobTempl<SOCHAR>::Add(
int a_nCount,
const SOCHAR * const * a_rgpszFileSpec
)
{
int nResult;
for (int n = 0; n < a_nCount; ++n) {
nResult = Add(a_rgpszFileSpec[n]);
if (nResult != SG_SUCCESS) {
return nResult;
}
}
return SG_SUCCESS;
}
template<class SOCHAR>
int
CSimpleGlobTempl<SOCHAR>::AppendName(
const SOCHAR * a_pszFileName,
bool a_bIsDir
)
{
// we need the argv array as offsets in case we resize it
SetArgvArrayType(OFFSETS);
// check for special cases which cause us to ignore this entry
if ((m_uiFlags & SG_GLOB_ONLYDIR) && !a_bIsDir) {
return SG_SUCCESS;
}
if ((m_uiFlags & SG_GLOB_ONLYFILE) && a_bIsDir) {
return SG_SUCCESS;
}
if ((m_uiFlags & SG_GLOB_NODOT) && a_bIsDir) {
if (a_pszFileName[0] == '.') {
if (a_pszFileName[1] == '\0') {
return SG_SUCCESS;
}
if (a_pszFileName[1] == '.' && a_pszFileName[2] == '\0') {
return SG_SUCCESS;
}
}
}
// ensure that we have enough room in the argv array
if (!GrowArgvArray(m_nArgsLen + 1)) {
return SG_ERR_MEMORY;
}
// ensure that we have enough room in the string buffer (+1 for null)
size_t uiPrefixLen = SimpleGlobUtil::strlen(m_szPathPrefix);
size_t uiLen = uiPrefixLen + SimpleGlobUtil::strlen(a_pszFileName) + 1;
if (a_bIsDir && (m_uiFlags & SG_GLOB_MARK) == SG_GLOB_MARK) {
++uiLen; // need space for the backslash
}
if (!GrowStringBuffer(m_uiBufferLen + uiLen)) {
return SG_ERR_MEMORY;
}
// add this entry. m_uiBufferLen is offset from beginning of buffer.
m_rgpArgs[m_nArgsLen++] = (SOCHAR*)m_uiBufferLen;
SimpleGlobUtil::strcpy_s(m_pBuffer + m_uiBufferLen,
m_uiBufferSize - m_uiBufferLen, m_szPathPrefix);
SimpleGlobUtil::strcpy_s(m_pBuffer + m_uiBufferLen + uiPrefixLen,
m_uiBufferSize - m_uiBufferLen - uiPrefixLen, a_pszFileName);
m_uiBufferLen += uiLen;
// add the directory slash if desired
if (a_bIsDir && (m_uiFlags & SG_GLOB_MARK) == SG_GLOB_MARK) {
const static SOCHAR szDirSlash[] = { SG_PATH_CHAR, 0 };
SimpleGlobUtil::strcpy_s(m_pBuffer + m_uiBufferLen - 2,
m_uiBufferSize - (m_uiBufferLen - 2), szDirSlash);
}
return SG_SUCCESS;
}
template<class SOCHAR>
void
CSimpleGlobTempl<SOCHAR>::SetArgvArrayType(
ARG_ARRAY_TYPE a_nNewType
)
{
if (m_nArgArrayType == a_nNewType) return;
if (a_nNewType == POINTERS) {
SG_ASSERT(m_nArgArrayType == OFFSETS);
for (int n = 0; n < m_nArgsLen; ++n) {
m_rgpArgs[n] = (m_rgpArgs[n] == (SOCHAR*)-1) ?
NULL : m_pBuffer + (size_t) m_rgpArgs[n];
}
}
else {
SG_ASSERT(a_nNewType == OFFSETS);
SG_ASSERT(m_nArgArrayType == POINTERS);
for (int n = 0; n < m_nArgsLen; ++n) {
m_rgpArgs[n] = (m_rgpArgs[n] == NULL) ?
(SOCHAR*) -1 : (SOCHAR*) (m_rgpArgs[n] - m_pBuffer);
}
}
m_nArgArrayType = a_nNewType;
}
template<class SOCHAR>
bool
CSimpleGlobTempl<SOCHAR>::GrowArgvArray(
int a_nNewLen
)
{
if (a_nNewLen >= m_nArgsSize) {
static const int SG_ARGV_INITIAL_SIZE = 32;
int nNewSize = (m_nArgsSize > 0) ?
m_nArgsSize * 2 : SG_ARGV_INITIAL_SIZE;
while (a_nNewLen >= nNewSize) {
nNewSize *= 2;
}
void * pNewBuffer = realloc(m_rgpArgs, nNewSize * sizeof(SOCHAR*));
if (!pNewBuffer) return false;
m_nArgsSize = nNewSize;
m_rgpArgs = (SOCHAR**) pNewBuffer;
}
return true;
}
template<class SOCHAR>
bool
CSimpleGlobTempl<SOCHAR>::GrowStringBuffer(
size_t a_uiMinSize
)
{
if (a_uiMinSize >= m_uiBufferSize) {
static const int SG_BUFFER_INITIAL_SIZE = 1024;
size_t uiNewSize = (m_uiBufferSize > 0) ?
m_uiBufferSize * 2 : SG_BUFFER_INITIAL_SIZE;
while (a_uiMinSize >= uiNewSize) {
uiNewSize *= 2;
}
void * pNewBuffer = realloc(m_pBuffer, uiNewSize * sizeof(SOCHAR));
if (!pNewBuffer) return false;
m_uiBufferSize = uiNewSize;
m_pBuffer = (SOCHAR*) pNewBuffer;
}
return true;
}
template<class SOCHAR>
int
CSimpleGlobTempl<SOCHAR>::fileSortCompare(
const void *a1,
const void *a2
)
{
const SOCHAR * s1 = *(const SOCHAR **)a1;
const SOCHAR * s2 = *(const SOCHAR **)a2;
if (s1 && s2) {
return SimpleGlobUtil::strcasecmp(s1, s2);
}
// NULL sorts first
return s1 == s2 ? 0 : (s1 ? 1 : -1);
}
// ---------------------------------------------------------------------------
// TYPE DEFINITIONS
// ---------------------------------------------------------------------------
/*! @brief ASCII/MBCS version of CSimpleGlob */
typedef CSimpleGlobTempl<char> CSimpleGlobA;
/*! @brief wchar_t version of CSimpleGlob */
typedef CSimpleGlobTempl<wchar_t> CSimpleGlobW;
#if SG_HAVE_ICU
/*! @brief UChar version of CSimpleGlob */
typedef CSimpleGlobTempl<UChar> CSimpleGlobU;
#endif
#ifdef _UNICODE
/*! @brief TCHAR version dependent on if _UNICODE is defined */
# if SG_HAVE_ICU
# define CSimpleGlob CSimpleGlobU
# else
# define CSimpleGlob CSimpleGlobW
# endif
#else
/*! @brief TCHAR version dependent on if _UNICODE is defined */
# define CSimpleGlob CSimpleGlobA
#endif
#endif // INCLUDED_SimpleGlob

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,776 @@
#include "EmberCommonPch.h"
#include "EmberGenome.h"
#include "JpegUtils.h"
/// <summary>
/// Set various default test values on the passed in ember.
/// </summary>
/// <param name="ember">The ember to test</param>
template <typename T>
void SetDefaultTestValues(Ember<T>& ember)
{
ember.m_Time = 0.0;
ember.m_Interp = EMBER_INTERP_LINEAR;
ember.m_PaletteInterp = INTERP_HSV;
ember.m_Background[0] = 0;
ember.m_Background[1] = 0;
ember.m_Background[2] = 0;
ember.m_Background[3] = 255;
ember.m_CenterX = 0;
ember.m_CenterY = 0;
ember.m_Rotate = 0;
ember.m_PixelsPerUnit = 64;
ember.m_FinalRasW = 128;
ember.m_FinalRasH = 128;
ember.m_Supersample = 1;
ember.m_SpatialFilterRadius = T(0.5);
ember.m_SpatialFilterType = GAUSSIAN_SPATIAL_FILTER;
ember.m_Zoom = 0;
ember.m_Quality = 1;
ember.m_Passes = 1;
ember.m_TemporalSamples = 1;
ember.m_MaxRadDE = 0;
ember.m_MinRadDE = 0;
ember.m_CurveDE = T(0.6);
}
/// <summary>
/// The core of the EmberGenome.exe program.
/// Template argument expected to be float or double.
/// </summary>
/// <param name="opt">A populated EmberOptions object which specifies all program options to be used</param>
/// <returns>True if success, else false.</returns>
template <typename T, typename bucketT>
bool EmberGenome(EmberOptions& opt)
{
OpenCLWrapper wrapper;
std::cout.imbue(std::locale(""));
if (opt.DumpArgs())
cout << opt.GetValues(OPT_USE_GENOME) << endl;
if (opt.OpenCLInfo())
{
cout << "\nOpenCL Info: " << endl;
cout << wrapper.DumpInfo();
return true;
}
//Regular variables.
Timing t;
bool exactTimeMatch, randomMode, didColor, seqFlag;
unsigned int i, j, i0, i1, rep, val, frame, frameCount, count = 0;
unsigned int ftime, firstFrame, lastFrame;
unsigned int n, tot, totb, totw;
T avgPix, fractionBlack, fractionWhite, blend, spread, mix0, mix1;
string token, filename;
ostringstream os, os2;
vector<Ember<T>> embers, embers2, templateEmbers;
vector<eVariationId> vars, noVars;
vector<unsigned char> finalImage;
eCrossMode crossMeth;
eMutateMode mutMeth;
Ember<T> orig, save, selp0, selp1, parent0, parent1;
Ember<T> result, result1, result2, result3, interpolated;
Ember<T>* aselp0, *aselp1, *pTemplate = NULL;
XmlToEmber<T> parser;
EmberToXml<T> emberToXml;
VariationList<T> varList;
EmberReport emberReport, emberReport2;
auto_ptr<RenderProgress<T>> progress(new RenderProgress<T>());
auto_ptr<Renderer<T, bucketT>> renderer(CreateRenderer<T, bucketT>(opt.EmberCL() ? OPENCL_RENDERER : CPU_RENDERER, opt.Platform(), opt.Device(), false, 0, emberReport));
QTIsaac<ISAAC_SIZE, ISAAC_INT> rand(ISAAC_INT(t.Tic()), ISAAC_INT(t.Tic() * 2), ISAAC_INT(t.Tic() * 3));
vector<string> errorReport = emberReport.ErrorReport();
os.imbue(std::locale(""));
os2.imbue(std::locale(""));
if (!errorReport.empty())
emberReport.DumpErrorReport();
if (!renderer.get())
{
cout << "Renderer creation failed, exiting." << endl;
return false;
}
if (!InitPaletteList<T>(opt.PalettePath()))
return false;
if (!opt.EmberCL())
{
if (opt.ThreadCount() != 0)
renderer->ThreadCount(opt.ThreadCount(), opt.IsaacSeed() != "" ? opt.IsaacSeed().c_str() : NULL);
renderer->LockAccum(opt.LockAccum());
}
else
{
cout << "Using OpenCL to render." << endl;
if (opt.Verbose())
{
cout << "Platform: " << wrapper.PlatformName(opt.Platform()) << endl;
cout << "Device: " << wrapper.DeviceName(opt.Platform(), opt.Device()) << endl;
}
}
//SheepTools will own the created renderer and will take care of cleaning it up.
SheepTools<T, bucketT> tools(opt.PalettePath(), CreateRenderer<T, bucketT>(opt.EmberCL() ? OPENCL_RENDERER : CPU_RENDERER, opt.Platform(), opt.Device(), false, 0, emberReport2));
tools.SetSpinParams(!opt.UnsmoothEdge(),
T(opt.Stagger()),
T(opt.OffsetX()),
T(opt.OffsetY()),
opt.Nick(),
opt.Url(),
opt.Id(),
opt.Comment(),
opt.SheepGen(),
opt.SheepId());
if (opt.UseVars() != "" && opt.DontUseVars() != "")
{
cout << "use_vars and dont_use_vars cannot both be specified. Returning without executing." << endl;
return false;
}
//Specify reasonable defaults if nothing is specified.
if (opt.UseVars() == "" && opt.DontUseVars() == "")
{
noVars.push_back(VAR_NOISE);
noVars.push_back(VAR_BLUR);
noVars.push_back(VAR_GAUSSIAN_BLUR);
noVars.push_back(VAR_RADIAL_BLUR);
noVars.push_back(VAR_NGON);
noVars.push_back(VAR_SQUARE);
noVars.push_back(VAR_RAYS);
noVars.push_back(VAR_CROSS);
noVars.push_back(VAR_PRE_BLUR);
noVars.push_back(VAR_SEPARATION);
noVars.push_back(VAR_SPLIT);
noVars.push_back(VAR_SPLITS);
//Loop over the novars and set ivars to the complement.
for (i = 0; i < varList.Size(); i++)
{
for (j = 0; j < noVars.size(); j++)
{
if (noVars[j] == varList.GetVariation(i)->VariationId())
break;
}
if (j == noVars.size())
vars.push_back(varList.GetVariation(i)->VariationId());
}
}
else
{
if (opt.UseVars() != "")//Parse comma-separated list of variations to use.
{
istringstream iss(opt.UseVars());
while (std::getline(iss, token, ','))
{
if (parser.Atoi((char*)token.c_str(), val))
{
if (val < varList.Size())
vars.push_back((eVariationId)val);
}
}
}
else if (opt.DontUseVars() != "")
{
istringstream iss(opt.DontUseVars());
while (std::getline(iss, token, ','))
{
if (parser.Atoi((char*)token.c_str(), val))
{
if (val < varList.Size())
noVars.push_back((eVariationId)val);
}
}
//Loop over the novars and set ivars to the complement.
for (i = 0; i < varList.Size(); i++)
{
for (j = 0; j < noVars.size(); j++)
{
if (noVars[j] == varList.GetVariation(i)->VariationId())
break;
}
if (j == noVars.size())
vars.push_back(varList.GetVariation(i)->VariationId());
}
}
}
bool doMutate = opt.Mutate() != "";
bool doInter = opt.Inter() != "";
bool doRotate = opt.Rotate() != "";
bool doClone = opt.Clone() != "";
bool doStrip = opt.Strip() != "";
bool doCross0 = opt.Cross0() != "";
bool doCross1 = opt.Cross1() != "";
count += (doMutate ? 1 : 0);
count += (doInter ? 1 : 0);
count += (doRotate ? 1 : 0);
count += (doClone ? 1 : 0);
count += (doStrip ? 1 : 0);
count += ((doCross0 || doCross1) ? 1 : 0);
if (count > 1)
{
cout << "Can only specify one of mutate, clone, cross, rotate, strip, or inter. Returning without executing." << endl;
return false;
}
if ((!doCross0) ^ (!doCross1))
{
cout << "Must specify both crossover arguments. Returning without executing." << endl;
return false;
}
if (opt.Method() != "" && (!doCross0 && !doMutate))
{
cout << "Cannot specify method unless doing crossover or mutate. Returning without executing." << endl;
return false;
}
if (opt.TemplateFile() != "")
{
if (!ParseEmberFile(parser, opt.TemplateFile(), templateEmbers))
return false;
if (templateEmbers.size() > 1)
cout << "More than one control point in template, ignoring all but first." << endl;
pTemplate = &templateEmbers[0];
}
//Methods for genetic manipulation begin here.
if (doMutate) filename = opt.Mutate();
else if (doInter) filename = opt.Inter();
else if (doRotate) filename = opt.Rotate();
else if (doClone) filename = opt.Clone();
else if (doStrip) filename = opt.Strip();
else if (doCross0) filename = opt.Cross0();
else if (opt.CloneAll() != "") filename = opt.CloneAll();
else if (opt.Animate() != "") filename = opt.Animate();
else if (opt.Sequence() != "") filename = opt.Sequence();
else if (opt.Inter() != "") filename = opt.Inter();
else if (opt.Rotate() != "") filename = opt.Rotate();
else if (opt.Strip() != "") filename = opt.Strip();
else if (opt.Clone() != "") filename = opt.Clone();
else if (opt.Mutate() != "") filename = opt.Mutate();
if (!ParseEmberFile(parser, filename, embers))
return false;
if (doCross1 && !ParseEmberFile(parser, opt.Cross1(), embers2))
return false;
if (opt.CloneAll() != "")
{
cout << "<clone_all version=\"Ember-" << EmberVersion() << "\">" << endl;
for (i = 0; i < embers.size(); i++)
{
if (pTemplate)
tools.ApplyTemplate(embers[i], *pTemplate);
tools.Offset(embers[i], (T)opt.OffsetX(), (T)opt.OffsetY());
cout << emberToXml.ToString(embers[i], opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
}
cout << "</clone_all>" << endl;
return true;
}
if (opt.Animate() != "")
{
for (i = 0; i < embers.size(); i++)
{
if (i > 0 && embers[i].m_Time <= embers[i - 1].m_Time)
{
cout << "Error: control points must be sorted by time, but " << embers[i].m_Time << " <= " << embers[i - 1].m_Time << ", index " << i << "." << endl;
return false;
}
embers[i].DeleteMotionElements();
}
firstFrame = (unsigned int)(opt.FirstFrame() == UINT_MAX ? embers[0].m_Time : opt.FirstFrame());
lastFrame = (unsigned int)(opt.LastFrame() == UINT_MAX ? embers.back().m_Time : opt.LastFrame());
if (lastFrame < firstFrame)
lastFrame = firstFrame;
cout << "<animate version=\"EMBER-" << EmberVersion() << "\">" << endl;
for (ftime = firstFrame; ftime <= lastFrame; ftime++)
{
exactTimeMatch = false;
for (i = 0; i < embers.size(); i++)
{
if (ftime == (unsigned int)embers[i].m_Time)
{
interpolated = embers[i];
exactTimeMatch = true;
break;
}
}
if (!exactTimeMatch)
{
Interpolater<T>::Interpolate(embers, T(ftime), T(opt.Stagger()), interpolated);
for (i = 0; i < embers.size(); i++)
{
if (ftime == (unsigned int)(embers[i].m_Time - 1))
{
exactTimeMatch = true;
break;
}
}
if (!exactTimeMatch)
interpolated.m_AffineInterp = INTERP_LINEAR;
}
if (pTemplate)
tools.ApplyTemplate(interpolated, *pTemplate);
cout << emberToXml.ToString(interpolated, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
}
cout << "</animate>" << endl;
return true;
}
if (opt.Sequence() != "")
{
frame = std::max(opt.Frame(), opt.Time());
if (opt.Frames() == 0)
{
cout << "nframes must be positive and non-zero, not " << opt.Frames() << "." << endl;
return false;
}
if (opt.Enclosed())
cout << "<sequence version=\"EMBER-" << EmberVersion() << "\">" << endl;
spread = 1 / T(opt.Frames());
frameCount = 0;
for (i = 0; i < embers.size(); i++)
{
if (opt.Loops())
{
for (frame = 0; frame < opt.Frames(); frame++)
{
blend = (T)frame / (T)opt.Frames();
tools.Spin(embers[i], pTemplate, result, frameCount++, blend);//Result is cleared and reassigned each time inside of Spin().
cout << emberToXml.ToString(result, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
}
}
if (i < embers.size() - 1)
{
for (frame = 0; frame < opt.Frames(); frame++)
{
seqFlag = (frame == 0 || frame == opt.Frames() - 1);
blend = frame / (T)opt.Frames();
result.Clear();
tools.SpinInter(&embers[i], pTemplate, result, frameCount++, seqFlag, blend);
cout << emberToXml.ToString(result, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
}
}
}
result = embers.back();
tools.Spin(embers.back(), pTemplate, result, frameCount, 0);
cout << emberToXml.ToString(result, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
if (opt.Enclosed())
cout << "</sequence>" << endl;
return true;
}
if (doInter || doRotate)
{
frame = std::max(opt.Frame(), opt.Time());
if (opt.Frames() == 0)
{
cout << "nframes must be positive and non-zero, not " << opt.Frames() << "." << endl;
return false;
}
blend = frame / T(opt.Frames());
spread = 1 / T(opt.Frames());
if (opt.Enclosed())
cout << "<pick version=\"EMBER-" << EmberVersion() << "\">" << endl;
if (doRotate)
{
if (embers.size() != 1)
{
cout << "rotation requires one control point, not " << embers.size() << "." << endl;
return false;
}
tools.Spin(embers[0], pTemplate, result1, frame - 1, blend - spread);
tools.Spin(embers[0], pTemplate, result2, frame , blend );
tools.Spin(embers[0], pTemplate, result3, frame + 1, blend + spread);
cout << emberToXml.ToString(result1, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
cout << emberToXml.ToString(result2, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
cout << emberToXml.ToString(result3, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
}
else
{
if (embers.size() != 2)
{
cout << "interpolation requires two control points, not " << embers.size() << "." << endl;
return false;
}
tools.SpinInter(embers.data(), pTemplate, result1, frame - 1, 0, blend - spread);
tools.SpinInter(embers.data(), pTemplate, result2, frame , 0, blend );
tools.SpinInter(embers.data(), pTemplate, result3, frame + 1, 0, blend + spread);
cout << emberToXml.ToString(result1, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
cout << emberToXml.ToString(result2, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
cout << emberToXml.ToString(result3, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
}
if (opt.Enclosed())
cout << "</pick>" << endl;
return true;
}
if (doStrip)
{
if (opt.Enclosed())
cout << "<pick version=\"EMBER-" << EmberVersion() << "\">" << endl;
for (i = 0; i < embers.size(); i++)
{
T oldX, oldY;
embers[i].DeleteMotionElements();
oldX = embers[i].m_CenterX;
oldY = embers[i].m_CenterY;
embers[i].m_FinalRasH = (unsigned int)((T)embers[i].m_FinalRasH / (T)opt.Frames());
embers[i].m_CenterY = embers[i].m_CenterY - ((opt.Frames() - 1) * embers[i].m_FinalRasH) /
(2 * embers[i].m_PixelsPerUnit * pow(T(2.0), embers[i].m_Zoom));
embers[i].m_CenterY += embers[i].m_FinalRasH * opt.Frame() / (embers[i].m_PixelsPerUnit * pow(T(2.0), embers[i].m_Zoom));
tools.RotateOldCenterBy(embers[i].m_CenterX, embers[i].m_CenterY, oldX, oldY, embers[i].m_Rotate);
if (pTemplate)
tools.ApplyTemplate(embers[i], *pTemplate);
tools.Offset(embers[i], T(opt.OffsetX()), T(opt.OffsetY()));
cout << emberToXml.ToString(embers[i], opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
}
if (opt.Enclosed())
cout << "</pick>" << endl;
return true;
}
//Repeat.
renderer->EarlyClip(opt.EarlyClip());
renderer->SubBatchSize(opt.SubBatchSize());
renderer->PixelAspectRatio(T(opt.AspectRatio()));
if (opt.Repeat() == 0)
{
cout << "Repeat must be positive, not " << opt.Repeat() << endl;
return false;
}
if (opt.Enclosed())
cout << "<pick version=\"EMBER-" << EmberVersion() << "\">" << endl;
for (rep = 0; rep < opt.Repeat(); rep++)
{
count = 0;
os.str("");
save.Clear();
VerbosePrint("Flame = " << rep + 1 << "/" << opt.Repeat() << "..");
if (opt.Clone() != "")
{
os << "clone";//Action is 'clone' with trunc vars concat.
if (opt.CloneAction() != "")
os << " " << opt.CloneAction();
selp0 = embers[rand.Rand() % embers.size()];
save = selp0;
aselp0 = &selp0;
aselp1 = NULL;
os << tools.TruncateVariations(save, 5);
save.m_Edits = emberToXml.CreateNewEditdoc(aselp0, aselp1, os.str(), opt.Nick(), opt.Url(), opt.Id(), opt.Comment(), opt.SheepGen(), opt.SheepId());
}
else
{
do
{
randomMode = false;
didColor = false;
os.str("");
VerbosePrint(".");
if (doMutate)
{
selp0 = embers[rand.Rand() % embers.size()];
orig = selp0;
aselp0 = &selp0;
aselp1 = NULL;
if (opt.Method() == "")
mutMeth = MUTATE_NOT_SPECIFIED;
else if (opt.Method() == "all_vars")
mutMeth = MUTATE_ALL_VARIATIONS;
else if (opt.Method() == "one_xform")
mutMeth = MUTATE_ONE_XFORM_COEFS;
else if (opt.Method() == "add_symmetry")
mutMeth = MUTATE_ADD_SYMMETRY;
else if (opt.Method() == "post_xforms")
mutMeth = MUTATE_POST_XFORMS;
else if (opt.Method() == "color_palette")
mutMeth = MUTATE_COLOR_PALETTE;
else if (opt.Method() == "delete_xform")
mutMeth = MUTATE_DELETE_XFORM;
else if (opt.Method() == "all_coefs")
mutMeth = MUTATE_ALL_COEFS;
else
{
cout << "method " << opt.Method() << " not defined for mutate. Defaulting to random." << endl;
mutMeth = MUTATE_NOT_SPECIFIED;
}
os << tools.Mutate(orig, mutMeth, vars, opt.Symmetry(), T(opt.Speed()));
//Scan string returned for 'mutate color'.
if (strstr(os.str().c_str(), "mutate color"))
didColor = true;
if (orig.m_Name != "")
{
os2.str("");
os2 << "mutation " << rep << " of " << orig.m_Name;
orig.m_Name = os2.str();
}
}
else if (doCross0)
{
i0 = rand.Rand() % embers.size();
i1 = rand.Rand() % embers2.size();
selp0 = embers[i0];
selp1 = embers2[i1];
aselp0 = &selp0;
aselp1 = &selp1;
if (opt.Method() == "")
crossMeth = CROSS_NOT_SPECIFIED;
else if (opt.Method() == "union")
crossMeth = CROSS_UNION;
else if (opt.Method() == "interpolate")
crossMeth = CROSS_INTERPOLATE;
else if (opt.Method() == "alternate")
crossMeth = CROSS_ALTERNATE;
else
{
cout << "method '" << opt.Method() << "' not defined for cross. Defaulting to random." << endl;
crossMeth = CROSS_NOT_SPECIFIED;
}
tools.Cross(embers[i0], embers2[i1], orig, crossMeth);
if (embers[i0].m_Name != "" || embers2[i1].m_Name != "")
{
os2.str("");
os2 << rep << " of " << embers[i0].m_Name << " x " << embers2[i1].m_Name;
orig.m_Name = os2.str();
}
}
else
{
os << "random";
randomMode = true;
tools.Random(orig, vars, opt.Symmetry(), 0);
aselp0 = NULL;
aselp1 = NULL;
}
//Adjust bounding box half the time.
if (rand.RandBit() || randomMode)
{
T bmin[2], bmax[2];
tools.EstimateBoundingBox(orig, T(0.01), 100000, bmin, bmax);
if (rand.Frand01<T>() < T(0.3))
{
orig.m_CenterX = (bmin[0] + bmax[0]) / 2;
orig.m_CenterY = (bmin[1] + bmax[1]) / 2;
os << " recentered";
}
else
{
if (rand.RandBit())
{
mix0 = rand.GoldenBit<T>() + rand.Frand11<T>() / 5;
mix1 = rand.GoldenBit<T>();
os << " reframed0";
}
else if (rand.RandBit())
{
mix0 = rand.GoldenBit<T>();
mix1 = rand.GoldenBit<T>() + rand.Frand11<T>() / 5;
os << " reframed1";
}
else
{
mix0 = rand.GoldenBit<T>() + rand.Frand11<T>() / 5;
mix1 = rand.GoldenBit<T>() + rand.Frand11<T>() / 5;
os << " reframed2";
}
orig.m_CenterX = mix0 * bmin[0] + (1 - mix0) * bmax[0];
orig.m_CenterY = mix1 * bmin[1] + (1 - mix1) * bmax[1];
}
orig.m_PixelsPerUnit = orig.m_FinalRasW / (bmax[0] - bmin[0]);
}
os << tools.TruncateVariations(orig, 5);
if (!didColor && rand.RandBit())
{
if (opt.Debug())
cout << "improving colors..." << endl;
tools.ImproveColors(orig, 100, false, 10);
os << " improved colors";
}
orig.m_Edits = emberToXml.CreateNewEditdoc(aselp0, aselp1, os.str(), opt.Nick(), opt.Url(), opt.Id(), opt.Comment(), opt.SheepGen(), opt.SheepId());
save = orig;
SetDefaultTestValues(orig);
renderer->SetEmber(orig);
if (renderer->Run(finalImage) != RENDER_OK)
{
cout << "Error: test image rendering failed, aborting." << endl;
return false;
}
tot = totb = totw = 0;
n = orig.m_FinalRasW * orig.m_FinalRasH;
for (i = 0; i < 3 * n; i += 3)
{
tot += (finalImage[i] + finalImage[i + 1] + finalImage[i + 2]);
if (0 == finalImage[i] && 0 == finalImage[i + 1] && 0 == finalImage[i + 2]) totb++;
if (255 == finalImage[i] && 255 == finalImage[i + 1] && 255 == finalImage[i + 2]) totw++;
}
avgPix = (tot / T(3 * n));
fractionBlack = totb / T(n);
fractionWhite = totw / T(n);
if (opt.Debug())
cout << "avgPix = " << avgPix << " fractionBlack = " << fractionBlack << " fractionWhite = " << fractionWhite << " n = " << n << endl;
orig.Clear();
count++;
} while ((avgPix < opt.AvgThresh() ||
fractionBlack < opt.BlackThresh() ||
fractionWhite > opt.WhiteLimit()) &&
count < opt.Tries());
if (count == opt.Tries())
cout << "Warning: reached maximum attempts, giving up." << endl;
}
if (pTemplate)
tools.ApplyTemplate(save, *pTemplate);
save.m_Time = T(rep);
if (opt.MaxXforms() != UINT_MAX)
{
save.m_Symmetry = 0;
while (save.TotalXformCount() > opt.MaxXforms())
save.DeleteTotalXform(save.TotalXformCount() - 1);
}
cout << emberToXml.ToString(save, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
VerbosePrint("\nDone. Action = " << os.str() << "\n");
cout.flush();
save.Clear();
}
if (opt.Enclosed())
cout << "</pick>\n";
return true;
}
/// <summary>
/// Main program entry point for EmberGenome.exe.
/// </summary>
/// <param name="argc">The number of command line arguments passed</param>
/// <param name="argv">The command line arguments passed</param>
/// <returns>0 if successful, else 1.</returns>
int _tmain(int argc, _TCHAR* argv[])
{
bool b, d = true;
EmberOptions opt;
//Required for large allocs, else GPU memory usage will be severely limited to small sizes.
//This must be done in the application and not in the EmberCL DLL.
_putenv_s("GPU_MAX_ALLOC_PERCENT", "100");
if (opt.Populate(argc, argv, OPT_USE_GENOME))
return 0;
#ifdef DO_DOUBLE
if (opt.Bits() == 64)
{
b = EmberGenome<double, double>(opt);
}
else
#endif
if (opt.Bits() == 33)
{
b = EmberGenome<float, float>(opt);
}
else if (opt.Bits() == 32)
{
cout << "Bits 32/int histogram no longer supported. Using bits == 33 (float)." << endl;
b = EmberGenome<float, float>(opt);
}
return b ? 0 : 1;
}

View File

@ -0,0 +1,23 @@
#pragma once
#include "EmberOptions.h"
/// <summary>
/// Declaration for the EmberGenome() and SetDefaultTestValues() functions.
/// </summary>
/// <summary>
/// Set various default test values on the passed in ember.
/// </summary>
/// <param name="ember">The ember to test</param>
template <typename T>
static void SetDefaultTestValues(Ember<T>& ember);
/// <summary>
/// The core of the EmberGenome.exe program.
/// Template argument expected to be float or double.
/// </summary>
/// <param name="opt">A populated EmberOptions object which specifies all program options to be used</param>
/// <returns>True if success, else false.</returns>
template <typename T, typename bucketT>
static bool EmberGenome(EmberOptions& opt);

View File

@ -0,0 +1,98 @@
// Microsoft Visual C++ generated resource script.
//
#include <windows.h>
#include "resource.h"
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1 ICON "..\\Fractorium\\Icons\\\\Fractorium.ico"
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,4,0,2
PRODUCTVERSION 0,4,0,2
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x0L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Open Source"
VALUE "FileDescription", "Manipulates fractal flames parameter files"
VALUE "FileVersion", "0.4.0.2"
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.0.2"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@ -0,0 +1,15 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by EmberGenome.rc
//
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 101
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View File

@ -0,0 +1,386 @@
#include "EmberCommonPch.h"
#include "EmberRender.h"
#include "JpegUtils.h"
/// <summary>
/// The core of the EmberRender.exe program.
/// Template argument expected to be float or double.
/// </summary>
/// <param name="opt">A populated EmberOptions object which specifies all program options to be used</param>
/// <returns>True if success, else false.</returns>
template <typename T, typename bucketT>
bool EmberRender(EmberOptions& opt)
{
OpenCLWrapper wrapper;
std::cout.imbue(std::locale(""));
if (opt.DumpArgs())
cout << opt.GetValues(OPT_USE_RENDER) << endl;
if (opt.OpenCLInfo())
{
cout << "\nOpenCL Info: " << endl;
cout << wrapper.DumpInfo();
return true;
}
Timing t;
bool writeSuccess = false;
unsigned char* finalImagep;
unsigned int i, channels, strip, strips, realHeight, origHeight;
size_t stripOffset;
T centerY, centerBase, zoomScale, floatStripH;
string filename;
ostringstream os;
vector<Ember<T>> embers;
vector<unsigned char> finalImage, vecRgb;
EmberStats stats;
EmberReport emberReport;
EmberImageComments comments;
XmlToEmber<T> parser;
EmberToXml<T> emberToXml;
vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>> randVec;
auto_ptr<RenderProgress<T>> progress(new RenderProgress<T>());
auto_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();
if (!errorReport.empty())
emberReport.DumpErrorReport();
if (!renderer.get())
{
cout << "Renderer creation failed, exiting." << endl;
return false;
}
if (opt.EmberCL() && renderer->RendererType() != OPENCL_RENDERER)//OpenCL init failed, so fall back to CPU.
opt.EmberCL(false);
if (!InitPaletteList<T>(opt.PalettePath()))
return false;
if (!ParseEmberFile(parser, opt.Input(), embers))
return false;
if (!opt.EmberCL())
{
if (opt.ThreadCount() == 0)
{
cout << "Using " << Timing::ProcessorCount() << " automatically detected threads." << endl;
opt.ThreadCount(Timing::ProcessorCount());
}
else
{
cout << "Using " << opt.ThreadCount() << " manually specified threads." << endl;
}
renderer->ThreadCount(opt.ThreadCount(), opt.IsaacSeed() != "" ? opt.IsaacSeed().c_str() : NULL);
}
else
{
cout << "Using OpenCL to render." << endl;
if (opt.Verbose())
{
cout << "Platform: " << wrapper.PlatformName(opt.Platform()) << endl;
cout << "Device: " << wrapper.DeviceName(opt.Platform(), opt.Device()) << endl;
}
if (opt.ThreadCount() > 1)
cout << "Cannot specify threads with OpenCL, using 1 thread." << endl;
opt.ThreadCount(1);
renderer->ThreadCount(opt.ThreadCount(), opt.IsaacSeed() != "" ? opt.IsaacSeed().c_str() : NULL);
if (opt.BitsPerChannel() != 8)
{
cout << "Bits per channel cannot be anything other than 8 with OpenCL, setting to 8." << endl;
opt.BitsPerChannel(8);
}
}
if (opt.Format() != "jpg" &&
opt.Format() != "png" &&
opt.Format() != "ppm" &&
opt.Format() != "bmp")
{
cout << "Format must be jpg, png, ppm, or bmp not " << opt.Format() << ". Setting to jpg." << endl;
}
channels = opt.Format() == "png" ? 4 : 3;
if (opt.BitsPerChannel() == 16 && opt.Format() != "png")
{
cout << "Support for 16 bits per channel images is only present for the png format. Setting to 8." << endl;
opt.BitsPerChannel(8);
}
else if (opt.BitsPerChannel() != 8 && opt.BitsPerChannel() != 16)
{
cout << "Unexpected bits per channel specified " << opt.BitsPerChannel() << ". Setting to 8." << endl;
opt.BitsPerChannel(8);
}
if (opt.InsertPalette() && opt.BitsPerChannel() != 8)
{
cout << "Inserting palette only supported with 8 bits per channel, insertion will not take place." << endl;
opt.InsertPalette(false);
}
if (opt.AspectRatio() < 0)
{
cout << "Invalid pixel aspect ratio " << opt.AspectRatio() << endl << ". Must be positive, setting to 1." << endl;
opt.AspectRatio(1);
}
if (!opt.Out().empty() && (embers.size() > 1))
{
cout << "Single output file " << opt.Out() << " specified for multiple images. Changing to use prefix of badname-changethis instead. Always specify prefixes when reading a file with multiple embers." << endl;
opt.Out("");
opt.Prefix("badname-changethis");
}
//Final setup steps before running.
os.imbue(std::locale(""));
renderer->EarlyClip(opt.EarlyClip());
renderer->LockAccum(opt.LockAccum());
renderer->InsertPalette(opt.InsertPalette());
renderer->SubBatchSize(opt.SubBatchSize());
renderer->PixelAspectRatio(T(opt.AspectRatio()));
renderer->Transparency(opt.Transparency());
renderer->NumChannels(channels);
renderer->BytesPerChannel(opt.BitsPerChannel() / 8);
renderer->Callback(opt.DoProgress() ? progress.get() : NULL);
for (i = 0; i < embers.size(); i++)
{
if (opt.Verbose() && embers.size() > 1)
cout << "\nFlame = " << i + 1 << "/" << embers.size() << endl;
else
cout << endl;
embers[i].m_TemporalSamples = 1;//Force temporal samples to 1 for render.
embers[i].m_Quality *= T(opt.QualityScale());
embers[i].m_FinalRasW = (unsigned int)((T)embers[i].m_FinalRasW * opt.SizeScale());
embers[i].m_FinalRasH = (unsigned int)((T)embers[i].m_FinalRasH * opt.SizeScale());
embers[i].m_PixelsPerUnit *= T(opt.SizeScale());
if (embers[i].m_FinalRasW == 0 || embers[i].m_FinalRasH == 0)
{
cout << "Output image " << i << " has dimension 0: " << embers[i].m_FinalRasW << ", " << embers[i].m_FinalRasH << ". Setting to 1920 x 1080." << endl;
embers[i].m_FinalRasW = 1920;
embers[i].m_FinalRasH = 1080;
}
//Cast to double in case the value exceeds 2^32.
double imageMem = (double)renderer->NumChannels() * (double)embers[i].m_FinalRasW
* (double)embers[i].m_FinalRasH * (double)renderer->BytesPerChannel();
double maxMem = pow(2.0, double((sizeof(void*) * 8) - 1));
if (imageMem > maxMem)//Ensure the max amount of memory for a process is not exceeded.
{
cout << "Image " << i << " size > " << maxMem << ". Setting to 1920 x 1080." << endl;
embers[i].m_FinalRasW = 1920;
embers[i].m_FinalRasH = 1080;
}
renderer->SetEmber(embers[i]);
renderer->PrepFinalAccumVector(finalImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run().
if (opt.Strips() > 1)
{
strips = opt.Strips();
}
else
{
strips = CalcStrips((double)renderer->MemoryRequired(false), (double)renderer->MemoryAvailable(), opt.UseMem());
if (strips > 1)
VerbosePrint("Setting strips to " << strips << " with specified memory usage of " << opt.UseMem());
}
if (strips > embers[i].m_FinalRasH)
{
cout << "Cannot have more strips than rows: " << opt.Strips() << " > " << embers[i].m_FinalRasH << ". Setting strips = rows." << endl;
opt.Strips(strips = embers[i].m_FinalRasH);
}
if (embers[i].m_FinalRasH % strips != 0)
{
cout << "A strips value of " << strips << " does not divide evenly into a height of " << embers[i].m_FinalRasH;
strips = NextHighestEvenDiv(embers[i].m_FinalRasH, strips);
if (strips == 1)//No higher divisor, check for a lower one.
strips = NextLowestEvenDiv(embers[i].m_FinalRasH, strips);
cout << ". Setting strips to " << strips << "." << endl;
}
embers[i].m_Quality *= strips;
realHeight = embers[i].m_FinalRasH;
floatStripH = T(embers[i].m_FinalRasH) / T(strips);
embers[i].m_FinalRasH = (unsigned int)ceil(floatStripH);
centerY = embers[i].m_CenterY;
zoomScale = pow(T(2), embers[i].m_Zoom);
centerBase = centerY - ((strips - 1) * floatStripH) / (2 * embers[i].m_PixelsPerUnit * zoomScale);
if (strips > 1)
randVec = renderer->RandVec();
//For testing incremental renderer.
//int sb = 1;
//bool resume = false, success = false;
//do
//{
// success = renderer->Run(finalImage, 0, sb, false/*resume == false*/) == RENDER_OK;
// sb++;
// resume = true;
//}
//while (success && renderer->ProcessState() != ACCUM_DONE);
for (strip = 0; strip < strips; strip++)
{
stripOffset = (size_t)embers[i].m_FinalRasH * strip * renderer->FinalRowSize();
embers[i].m_CenterY = centerBase + embers[i].m_FinalRasH * T(strip) / (embers[i].m_PixelsPerUnit * zoomScale);
if ((embers[i].m_FinalRasH * (strip + 1)) > realHeight)
{
origHeight = embers[i].m_FinalRasH;
embers[i].m_FinalRasH = realHeight - origHeight * strip;
embers[i].m_CenterY -= (origHeight - embers[i].m_FinalRasH) * T(0.5) / (embers[i].m_PixelsPerUnit * zoomScale);
}
if (strips > 1)
{
renderer->RandVec(randVec);//Use the same vector of ISAAC rands for each strip.
renderer->SetEmber(embers[i]);//Set one final time after modifications for strips.
if (opt.Verbose() && (strips > 1) && strip > 0)
cout << endl;
VerbosePrint("Strip = " << (strip + 1) << "/" << strips);
}
if ((renderer->Run(finalImage, 0, 0, false, stripOffset) != RENDER_OK) || renderer->Aborted() || finalImage.empty())
{
cout << "Error: image rendering failed, skipping to next image." << endl;
renderer->DumpErrorReport();//Something went wrong, print errors.
break;//Exit strips loop, resume next iter in embers loop.
}
progress->Clear();
//Original wrote every strip as a full image which could be very slow with many large images.
//Only write once all strips for this image are finished.
if (strip == strips - 1)
{
if (!opt.Out().empty())
{
filename = opt.Out();
}
else if (opt.NameEnable() && !embers[i].m_Name.empty())
{
filename = opt.Prefix() + embers[i].m_Name + opt.Suffix() + "." + opt.Format();
}
else
{
ostringstream ssLocal;
ssLocal << opt.Prefix() << setfill('0') << setw(5) << i << opt.Suffix() << "." << opt.Format();
filename = ssLocal.str();
}
writeSuccess = false;
comments = renderer->ImageComments(opt.PrintEditDepth(), opt.IntPalette(), opt.HexPalette());
stats = renderer->Stats();
os.str("");
os << comments.m_NumIters << "/" << renderer->TotalIterCount() << " (" << std::fixed << std::setprecision(2) << ((double)stats.m_Iters/(double)renderer->TotalIterCount() * 100) << "%)";
VerbosePrint("\nIters ran/requested: " + os.str());
VerbosePrint("Bad values: " << stats.m_Badvals);
VerbosePrint("Render time: " + t.Format(stats.m_RenderSeconds * 1000));
VerbosePrint("Writing " + filename);
if ((opt.Format() == "jpg" || opt.Format() == "bmp") && renderer->NumChannels() == 4)
{
EmberNs::RgbaToRgb(finalImage, vecRgb, renderer->FinalRasW(), realHeight);
finalImagep = vecRgb.data();
}
else
{
finalImagep = finalImage.data();
}
if (opt.Format() == "png")
writeSuccess = WritePng(filename.c_str(), finalImagep, renderer->FinalRasW(), realHeight, 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(), realHeight, opt.JpegQuality(), opt.JpegComments(), comments, opt.Id(), opt.Url(), opt.Nick());
else if (opt.Format() == "ppm")
writeSuccess = WritePpm(filename.c_str(), finalImagep, renderer->FinalRasW(), realHeight);
else if (opt.Format() == "bmp")
writeSuccess = WriteBmp(filename.c_str(), finalImagep, renderer->FinalRasW(), realHeight);
if (!writeSuccess)
cout << "Error writing " << filename << endl;
}
}
//Restore the ember values to their original values.
if (strips > 1)
{
embers[i].m_Quality /= strips;
embers[i].m_FinalRasH = realHeight;
embers[i].m_CenterY = centerY;
memset(finalImage.data(), 0, finalImage.size());
}
if (opt.EmberCL() && opt.DumpKernel())
cout << "Iteration kernel: \n" << ((RendererCL<T>*)renderer.get())->IterKernel() << endl;
VerbosePrint("Done.");
}
if (opt.Verbose())
t.Toc("\nTotal time: ", true);
return true;
}
/// <summary>
/// Main program entry point for EmberRender.exe.
/// </summary>
/// <param name="argc">The number of command line arguments passed</param>
/// <param name="argv">The command line arguments passed</param>
/// <returns>0 if successful, else 1.</returns>
int _tmain(int argc, _TCHAR* argv[])
{
bool b, d = true;
EmberOptions opt;
//Required for large allocs, else GPU memory usage will be severely limited to small sizes.
//This must be done in the application and not in the EmberCL DLL.
_putenv_s("GPU_MAX_ALLOC_PERCENT", "100");
if (opt.Populate(argc, argv, OPT_USE_RENDER))
return 0;
#ifdef DO_DOUBLE
if (opt.Bits() == 64)
{
b = EmberRender<double, double>(opt);
}
else
#endif
if (opt.Bits() == 33)
{
b = EmberRender<float, float>(opt);
}
else if (opt.Bits() == 32)
{
cout << "Bits 32/int histogram no longer supported. Using bits == 33 (float)." << endl;
b = EmberRender<float, float>(opt);
}
return b ? 0 : 1;
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "EmberOptions.h"
/// <summary>
/// Declaration for the EmberRender() function.
/// </summary>
/// <summary>
/// The core of the EmberRender.exe program.
/// Template argument expected to be float or double.
/// </summary>
/// <param name="opt">A populated EmberOptions object which specifies all program options to be used</param>
/// <returns>True if success, else false.</returns>
template <typename T, typename bucketT>
static bool EmberRender(EmberOptions& opt);

View File

@ -0,0 +1,98 @@
// Microsoft Visual C++ generated resource script.
//
#include <windows.h>
#include "resource.h"
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1 ICON "..\\Fractorium\\Icons\\\\Fractorium.ico"
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,4,0,2
PRODUCTVERSION 0,4,0,2
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x0L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Open Source"
VALUE "FileDescription", "Renders fractal flames as single images"
VALUE "FileVersion", "0.4.0.2"
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.0.2"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@ -0,0 +1,15 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by EmberRender.rc
//
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 101
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
#pragma once
#include "EmberCommon.h"
/// <summary>
/// EmberTester is a scratch area used for on the fly testing.
/// It may become a more formalized automated testing system
/// in the future. At the moment it isn't expected to build or
/// give any useful insight into the workings of Ember.
/// </summary>

View File

@ -0,0 +1,14 @@
#include "FractoriumPch.h"
#include "AboutDialog.h"
/// <summary>
/// Constructor that takes a parent widget and passes it to the base, then
/// sets up the GUI.
/// </summary>
/// <param name="parent">The parent widget. Default: NULL.</param>
/// <param name="f">The window flags. Default: 0.</param>
FractoriumAboutDialog::FractoriumAboutDialog(QWidget* parent, Qt::WindowFlags f)
: QDialog(parent, f)
{
ui.setupUi(this);
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "ui_AboutDialog.h"
/// <summary>
/// FractoriumAboutDialog class.
/// </summary>
/// <summary>
/// The about dialog displays several group boxes showing the
/// code and icons used in this project and their respective authors
/// and licenses. It performs no other function.
/// </summary>
class FractoriumAboutDialog : public QDialog
{
Q_OBJECT
public:
FractoriumAboutDialog(QWidget* parent = 0, Qt::WindowFlags f = 0);
private:
Ui::AboutDialog ui;
};

View File

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutDialog</class>
<widget class="QDialog" name="AboutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>488</width>
<height>580</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>488</width>
<height>580</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>488</width>
<height>580</height>
</size>
</property>
<property name="windowTitle">
<string>About</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<pointsize>12</pointsize>
</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.0.2 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>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="CodeCopiedGroupBox">
<property name="title">
<string>Code Copied</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="topMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://code.google.com/p/flam3&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;flam3&lt;/span&gt;&lt;/a&gt;: Scott Draves, Erik Reckase (GPL v2)&lt;br/&gt;&lt;a href=&quot;http://github.com/stevenrobertson/cuburn&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;cuburn&lt;/span&gt;&lt;/a&gt;: Steven Robertson, Michael Semeniuk, Matthew Znoj, Nicolas Mejia (GPL v3)&lt;br/&gt;&lt;a href=&quot;http://fractron9000.sourceforge.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Fractron 9000&lt;/span&gt;&lt;/a&gt;: Mike Thiesen (GPL)&lt;br/&gt;&lt;a href=&quot;http://sourceforge.net/projects/apophysis7x&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Apophysis&lt;/span&gt;&lt;/a&gt;: Mark Townsend, Ronald Hordijk, Peter Sdobnov, Piotr Borys, Georg Kiehne (GPL)&lt;br/&gt;&lt;a href=&quot;http://jwildfire.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;JWildfire&lt;/span&gt;&lt;/a&gt;: Andreas Maschke (LGPL)&lt;br/&gt;Numerous Apophysis plugin developers (GPL)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="LibrariesLinkedGroupBox">
<property name="title">
<string>Libraries Linked</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="topMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://qt-project.org&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Qt&lt;/span&gt;&lt;/a&gt;: Digia Plc (GPL v3, LGPL v2)&lt;br/&gt;&lt;a href=&quot;http://g-truc.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;glm&lt;/span&gt;&lt;/a&gt;: Christophe Riccio (MIT License)&lt;br/&gt;&lt;a href=&quot;http://threadingbuildingblocks.org&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Threading Building Blocks&lt;/span&gt;&lt;/a&gt;: Intel Corporation (GPLv2)&lt;br/&gt;&lt;a href=&quot;http://libjpeg.sourceforge.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;libjpeg&lt;/span&gt;&lt;/a&gt;: Independent JPEG Group (Free Software License)&lt;br/&gt;&lt;a href=&quot;http://libpng.org&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;libpng&lt;/span&gt;&lt;/a&gt;: Glenn Randers-Pehrson et al (Libpng License)&lt;br/&gt;&lt;a href=&quot;http://xmlsoft.org&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;libxml2&lt;/span&gt;&lt;/a&gt;: Daniel Veillard (MIT License)&lt;br/&gt;&lt;a href=&quot;http://zlib.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;zlib&lt;/span&gt;&lt;/a&gt;: Jean-loup Gailly, Mark Adler (Zlib License)&lt;br/&gt;&lt;a href=&quot;http://burtleburtle.net/bob/rand/isaac.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;QTIsaac&lt;/span&gt;&lt;/a&gt;: Robert J. Jenkins, Quinn Tyler Jackson (Public Domain)&lt;br/&gt;&lt;a href=&quot;http://cas.ee.ic.ac.uk/people/dt10/index.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;MWC64X Random Number Generator&lt;/span&gt;&lt;/a&gt;: David Thomas (Public Domain)&lt;br/&gt;&lt;a href=&quot;http://code.jellycan.com/simpleopt/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;SimpleOpt&lt;/span&gt;&lt;/a&gt;: Brodie Thiesfield (MIT License)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="IconsUsedGroupBox">
<property name="title">
<string>Icons Used</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://famfamfam.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Silk&lt;/span&gt;&lt;/a&gt;: Mark James (Creative Commons Attribution 2.5 License)&lt;br/&gt;&lt;a href=&quot;http://momentumdesignlab.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Momentum&lt;/span&gt;&lt;/a&gt;: Momentum Design Lab (Creative Commons Attribution-ShareAlike 3.5 License)&lt;br/&gt;&lt;a href=&quot;http://everaldo.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Crystal Clear&lt;/span&gt;&lt;/a&gt;: Everaldo Coelho (LGPL)&lt;br/&gt;&lt;a href=&quot;http://openiconlibrary.sourceforge.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Open Icon Library&lt;/span&gt;&lt;/a&gt;: Jeff Israel (GPL, LGPL, Creative Commons, Public Domain)&lt;br/&gt;&lt;a href=&quot;http://icons.mysitemyway.com/category/3d-transparent-glass-icons/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;3D Transparent Glass&lt;/span&gt;&lt;/a&gt;: iconsETC (Public Domain)&lt;br/&gt;&lt;a href=&quot;http://p.yusukekamiyamane.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Fugue&lt;/span&gt;&lt;/a&gt;: Yusuke Kamiyamane (Creative Commons Attribution 3.0 License)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPushButton" name="okButton">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>okButton</sender>
<signal>clicked()</signal>
<receiver>AboutDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>278</x>
<y>253</y>
</hint>
<hint type="destinationlabel">
<x>96</x>
<y>254</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,215 @@
#include "FractoriumPch.h"
#include "DoubleSpinBox.h"
/// <summary>
/// Constructor that passes parent to the base and sets up height and step.
/// Specific focus policy is used to allow the user to hover over the control
/// and change its value using the mouse wheel without explicitly having to click
/// inside of it.
/// </summary>
/// <param name="parent">The parent widget. Default: NULL.</param>
/// <param name="height">The height of the spin box. Default: 16.</param>
/// <param name="step">The step used to increment/decrement the spin box when using the mouse wheel. Default: 0.05.</param>
DoubleSpinBox::DoubleSpinBox(QWidget* parent, int height, double step)
: QDoubleSpinBox(parent)
{
m_Select = false;
m_DoubleClick = false;
m_DoubleClickNonZero = 0;
m_DoubleClickZero = 1;
m_Step = step;
m_SmallStep = step / 10.0;
setSingleStep(step);
setFrame(false);
setButtonSymbols(QAbstractSpinBox::NoButtons);
setFocusPolicy(Qt::StrongFocus);
setMinimumHeight(height);//setGeometry() has no effect, so must set both of these instead.
setMaximumHeight(height);
lineEdit()->installEventFilter(this);
lineEdit()->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
connect(this, SIGNAL(valueChanged(double)), this, SLOT(onSpinBoxValueChanged(double)), Qt::QueuedConnection);
}
/// <summary>
/// Set the value of the control without triggering signals.
/// </summary>
/// <param name="d">The value to set it to</param>
void DoubleSpinBox::SetValueStealth(double d)
{
blockSignals(true);
setValue(d);
blockSignals(false);
}
/// <summary>
/// Set whether to respond to double click events.
/// </summary>
/// <param name="b">True if this should respond to double click events, else false.</param>
void DoubleSpinBox::DoubleClick(bool b)
{
m_DoubleClick = b;
}
/// <summary>
/// Set the value to be used when the user double clicks the spinner while
/// it contains zero.
/// </summary>
/// <param name="val">The value to be used</param>
void DoubleSpinBox::DoubleClickZero(double val)
{
m_DoubleClickZero = val;
}
/// <summary>
/// Set the value to be used when the user double clicks the spinner while
/// it contains a non-zero value.
/// </summary>
/// <param name="val">The value to be used</param>
void DoubleSpinBox::DoubleClickNonZero(double val)
{
m_DoubleClickNonZero = val;
}
/// <summary>
/// Set the default step to be used when the user scrolls.
/// </summary>
/// <param name="step">The step to use for scrolling</param>
void DoubleSpinBox::Step(double step)
{
m_Step = step;
}
/// <summary>
/// Set the small step to be used when the user holds down shift while scrolling.
/// The default is step / 10, so use this if something else is needed.
/// </summary>
/// <param name="step">The small step to use for scrolling while the shift key is down</param>
void DoubleSpinBox::SmallStep(double step)
{
m_SmallStep = step;
}
/// <summary>
/// Expose the underlying QLineEdit control to the caller.
/// </summary>
/// <returns>A pointer to the QLineEdit</returns>
QLineEdit* DoubleSpinBox::lineEdit()
{
return QDoubleSpinBox::lineEdit();
}
/// <summary>
/// Another workaround for the persistent text selection bug in Qt.
/// </summary>
void DoubleSpinBox::onSpinBoxValueChanged(double d)
{
lineEdit()->deselect();//Gets rid of nasty "feature" that always has text selected.
}
/// <summary>
/// Event filter for taking special action on double click events.
/// </summary>
/// <param name="o">The object</param>
/// <param name="e">The eevent</param>
/// <returns>false</returns>
bool DoubleSpinBox::eventFilter(QObject* o, QEvent* e)
{
if (e->type() == QMouseEvent::MouseButtonPress && isEnabled())
{
QPoint pt;
if (QMouseEvent* me = (QMouseEvent*)e)
pt = me->localPos().toPoint();
int pos = lineEdit()->cursorPositionAt(pt);
if (lineEdit()->selectedText() != "")
{
lineEdit()->deselect();
lineEdit()->setCursorPosition(pos);
return true;
}
else if (m_Select)
{
lineEdit()->setCursorPosition(pos);
selectAll();
m_Select = false;
return true;
}
}
else if (m_DoubleClick && e->type() == QMouseEvent::MouseButtonDblClick && isEnabled())
{
if (IsNearZero(value()))
setValue(m_DoubleClickZero);
else
setValue(m_DoubleClickNonZero);
}
else
{
if (e->type() == QEvent::Wheel)
{
//Take special action for shift to reduce the scroll amount. Control already
//increases it automatically.
if (QWheelEvent* wheelEvent = dynamic_cast<QWheelEvent*>(e))
{
Qt::KeyboardModifiers mod = wheelEvent->modifiers();
if (mod.testFlag(Qt::ShiftModifier))
setSingleStep(m_SmallStep);
else
setSingleStep(m_Step);
}
}
}
return QDoubleSpinBox::eventFilter(o, e);
}
/// <summary>
/// Called when focus enters the spinner.
/// </summary>
/// <param name="e">The event</param>
void DoubleSpinBox::focusInEvent(QFocusEvent* e)
{
lineEdit()->setReadOnly(false);
QDoubleSpinBox::focusInEvent(e);
}
/// <summary>
/// Called when focus leaves the spinner.
/// Qt has a nasty "feature" that leaves the text in a spinner selected
/// and the cursor visible, regardless of whether it has the focus.
/// Manually clear both here.
/// </summary>
/// <param name="e">The event</param>
void DoubleSpinBox::focusOutEvent(QFocusEvent* e)
{
lineEdit()->deselect();//Clear selection when leaving.
lineEdit()->setReadOnly(true);//Clever hack to clear the cursor when leaving.
QDoubleSpinBox::focusOutEvent(e);
}
/// <summary>
/// Called when focus enters the spinner.
/// Must set the focus to make sure key down messages don't erroneously go to the GLWidget.
/// </summary>
/// <param name="e">The event</param>
void DoubleSpinBox::enterEvent(QEvent* e)
{
m_Select = true;
setFocus();
QDoubleSpinBox::enterEvent(e);
}
/// <summary>
/// Called when focus leaves the spinner.
/// Must clear the focus to make sure key down messages don't erroneously go to the GLWidget.
/// </summary>
/// <param name="e">The event</param>
void DoubleSpinBox::leaveEvent(QEvent* e)
{
m_Select = false;
clearFocus();
QDoubleSpinBox::leaveEvent(e);
}

View File

@ -0,0 +1,80 @@
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// DoubleSpinBox and VariationTreeDoubleSpinBox classes.
/// </summary>
/// <summary>
/// A derivation to prevent the spin box from selecting its own text
/// when editing. Also to prevent multiple spin boxes from all having
/// selected text at once.
/// </summary>
class DoubleSpinBox : public QDoubleSpinBox
{
Q_OBJECT
public:
explicit DoubleSpinBox(QWidget* parent = 0, int height = 16, double step = 0.05);
virtual ~DoubleSpinBox() { }
void SetValueStealth(double d);
void DoubleClick(bool b);
void DoubleClickZero(double val);
void DoubleClickNonZero(double val);
void Step(double step);
void SmallStep(double step);
QLineEdit* lineEdit();
public slots:
void onSpinBoxValueChanged(double d);
protected:
bool eventFilter(QObject* o, QEvent* e);
virtual void focusInEvent(QFocusEvent* e);
virtual void focusOutEvent(QFocusEvent* e);
virtual void enterEvent(QEvent* e);
virtual void leaveEvent(QEvent* e);
private:
bool m_Select;
bool m_DoubleClick;
double m_DoubleClickNonZero;
double m_DoubleClickZero;
double m_Step;
double m_SmallStep;
};
/// <summary>
/// Derivation for the double spin boxes that are in the
/// variations tree.
/// </summary>
template <typename T>
class VariationTreeDoubleSpinBox : public DoubleSpinBox
{
public:
/// <summary>
/// Constructor that passes agruments to the base and assigns the m_Param and m_Variation members.
/// </summary>
/// <param name="parent">The parent widget</param>
/// <param name="var">The variation this spinner is for</param>
/// <param name="param">The name of the parameter this is for</param>
/// <param name="height">The height of the spin box. Default: 16.</param>
/// <param name="step">The step used to increment/decrement the spin box when using the mouse wheel. Default: 0.05.</param>
explicit VariationTreeDoubleSpinBox(QWidget* parent, Variation<T>* var, string param, int height = 16, double step = 0.05)
: DoubleSpinBox(parent, height, step)
{
m_Param = param;
m_Variation = var;
setDecimals(3);
}
virtual ~VariationTreeDoubleSpinBox() { }
bool IsParam() { return !m_Param.empty(); }
string ParamName() { return m_Param; }
Variation<T>* GetVariation() { return m_Variation; }
private:
string m_Param;
Variation<T>* m_Variation;
};

View File

@ -0,0 +1,145 @@
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// EmberFile class.
/// </summary>
/// <summary>
/// Class for representing an ember Xml file in memory.
/// It contains a filename and a vector of embers.
/// It also provides static helper functions for creating
/// default names for the file and the embers in it.
/// </summary>
template <typename T>
class EmberFile
{
public:
/// <summary>
/// Empty constructor that does nothing.
/// </summary>
EmberFile()
{
}
/// <summary>
/// Default copy constructor.
/// </summary>
/// <param name="emberFile">The EmberFile object to copy</param>
EmberFile(const EmberFile<T>& emberFile)
{
EmberFile<T>::operator=<T>(emberFile);
}
/// <summary>
/// Copy constructor to copy an EmberFile object of type U.
/// </summary>
/// <param name="emberFile">The EmberFile object to copy</param>
template <typename U>
EmberFile(const EmberFile<U>& emberFile)
{
EmberFile<T>::operator=<U>(emberFile);
}
/// <summary>
/// Default assignment operator.
/// </summary>
/// <param name="emberFile">The EmberFile object to copy</param>
EmberFile<T>& operator = (const EmberFile<T>& emberFile)
{
if (this != &emberFile)
EmberFile<T>::operator=<T>(emberFile);
return *this;
}
/// <summary>
/// Assignment operator to assign a EmberFile object of type U.
/// </summary>
/// <param name="emberFile">The EmberFile object to copy.</param>
/// <returns>Reference to updated self</returns>
template <typename U>
EmberFile<T>& operator = (const EmberFile<U>& emberFile)
{
m_Filename = emberFile.m_Filename;
CopyVec(m_Embers, emberFile.m_Embers);
return *this;
}
/// <summary>
/// Clear the file name and the vector of embers.
/// </summary>
void Clear()
{
m_Filename.clear();
m_Embers.clear();
}
/// <summary>
/// Ensure all ember names are unique.
/// </summary>
void MakeNamesUnique()
{
int x = 0;
for (size_t i = 0; i < m_Embers.size(); i++)
{
for (size_t j = 0; j < m_Embers.size(); j++)
{
if (i != j && m_Embers[i].m_Name == m_Embers[j].m_Name)
{
m_Embers[j].m_Name = m_Embers[j].m_Name + "_" + QString::number(++x).toStdString();
j = 0;
}
}
}
}
/// <summary>
/// Return the default filename based on the current date/time.
/// </summary>
/// <returns>The default filename</returns>
static QString DefaultFilename()
{
return "Flame_" + QDateTime(QDateTime::currentDateTime()).toString("yyyy-MM-dd-hhmmss");
}
/// <summary>
/// Ensures a given input filename is unique by appending a count to the end.
/// </summary>
/// <returns>The passed in name if it was unique, else a uniquely made name.</returns>
static QString UniqueFilename(QString& filename)
{
if (!QFile::exists(filename))
return filename;
int counter = 2;
QString newPath;
QFileInfo original(filename);
QString base = original.completeBaseName();
QString extension = original.suffix();
do
{
newPath = base + "_" + QString::number(counter++) + "." + extension;
}
while (QFile::exists(newPath));
return newPath;
}
/// <summary>
/// Return the default ember name based on the current date/time and
/// the ember's index in the file.
/// </summary>
/// <param name="i">The index in the file of the ember</param>
/// <returns>The default ember name</returns>
static QString DefaultEmberName(unsigned int i)
{
return DefaultFilename() + "-" + QString::number(i);
}
QString m_Filename;
vector<Ember<T>> m_Embers;
};

View File

@ -0,0 +1,120 @@
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// EmberTreeWidgetItem
/// </summary>
/// <summary>
/// A thin derivation of QTreeWidgetItem for a tree of embers in an open file.
/// The tree is intended to contain one open ember file at a time.
/// This is a non-templated base for casting purposes.
/// </summary>
class EmberTreeWidgetItemBase : public QTreeWidgetItem
{
public:
/// <summary>
/// Constructor that takes a pointer to a QTreeWidget as a parent widget.
/// This is meant to be a root level item.
/// </summary>
/// <param name="parent">The parent widget of this item</param>
explicit EmberTreeWidgetItemBase(QTreeWidget* parent = 0)
: QTreeWidgetItem(parent)
{
}
/// <summary>
/// Constructor that takes a pointer to a QTreeWidgetItem as a parent widget.
/// This is meant to be the child of a root level item.
/// </summary>
/// <param name="parent">The parent widget of this item</param>
explicit EmberTreeWidgetItemBase(QTreeWidgetItem* parent = 0)
: QTreeWidgetItem(parent)
{
}
/// <summary>
/// Set the preview image for the tree widget item.
/// </summary>
/// <param name="v">The vector containing the RGB pixels [0..255] which will make up the preview image</param>
/// <param name="width">The width of the image in pixels</param>
/// <param name="height">The height of the image in pixels</param>
void SetImage(vector<unsigned char>& v, unsigned int width, unsigned int height)
{
int size = 64;
m_Image = QImage(width, height, QImage::Format_RGBA8888);
memcpy(m_Image.scanLine(0), v.data(), v.size() * sizeof(v[0]));//Memcpy the data in.
m_Pixmap = QPixmap::fromImage(m_Image).scaled(QSize(size, size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);//Create a QPixmap out of the QImage, scaled to size.
setData(0, Qt::DecorationRole, m_Pixmap);
}
protected:
QImage m_Image;
QPixmap m_Pixmap;
};
/// <summary>
/// A thin derivation of QTreeWidgetItem for a tree of embers in an open file.
/// The tree is intended to contain one open ember file at a time.
/// </summary>
template <typename T>
class EmberTreeWidgetItem : public EmberTreeWidgetItemBase
{
public:
/// <summary>
/// Constructor that takes a pointer to an ember and a QTreeWidget as a parent widget.
/// This is meant to be a root level item.
/// </summary>
/// <param name="ember">A pointer to the ember this item will represent</param>
/// <param name="parent">The parent widget of this item</param>
explicit EmberTreeWidgetItem(Ember<T>* ember, QTreeWidget* parent = 0)
: EmberTreeWidgetItemBase(parent)
{
m_Ember = ember;
}
/// <summary>
/// Constructor that takes a pointer to an ember and a QTreeWidgetItem as a parent widget.
/// This is meant to be the child of a root level item.
/// </summary>
/// <param name="ember">A pointer to the ember this item will represent</param>
/// <param name="parent">The parent widget of this item</param>
explicit EmberTreeWidgetItem(Ember<T>* ember, QTreeWidgetItem* parent = 0)
: EmberTreeWidgetItemBase(parent)
{
m_Ember = ember;
}
/// <summary>
/// Copy the text of the tree item to the name of the ember.
/// </summary>
void UpdateEmberName() { m_Ember->m_Name = text(0).toStdString(); }
/// <summary>
/// Set the text of the tree item.
/// </summary>
void UpdateEditText() { setText(0, QString::fromStdString(m_Ember->m_Name)); }
/// <summary>
/// Get a pointer to the ember held by the tree item.
/// </summary>
Ember<T>* GetEmber() const { return m_Ember; }
/// <summary>
/// Perform a deep copy from the passed in ember to the dereferenced
/// ember pointer of the tree item.
/// </summary>
/// <param name="ember">The ember to copy</param>
void SetEmber(Ember<T>& ember) { *m_Ember = ember; }
/// <summary>
/// Set the ember pointer member to point to the passed in ember pointer.
/// </summary>
/// <param name="ember">The ember to point to</param>
void SetEmberPointer(Ember<T>* ember) { m_Ember = ember; }
private:
Ember<T>* m_Ember;
};

View File

@ -0,0 +1,550 @@
#include "FractoriumPch.h"
#include "FinalRenderDialog.h"
#include "Fractorium.h"
/// <summary>
/// Constructor which sets up the GUI for the final rendering dialog.
/// Settings used to populate widgets with initial values.
/// This function contains the render function as a lambda.
/// </summary>
/// <param name="settings">Pointer to the global settings object to use</param>
/// <param name="parent">The parent widget</param>
/// <param name="f">The window flags. Default: 0.</param>
FractoriumFinalRenderDialog::FractoriumFinalRenderDialog(FractoriumSettings* settings, QWidget* parent, Qt::WindowFlags f)
: QDialog(parent, f)
{
ui.setupUi(this);
int row = 0, spinHeight = 20;
unsigned int i;
QTableWidget* table = ui.FinalRenderGeometryTable;
QTableWidgetItem* item = NULL;
m_Fractorium = (Fractorium*)parent;
m_Settings = settings;
ui.FinalRenderThreadCountSpin->setRange(1, Timing::ProcessorCount());
connect(ui.FinalRenderEarlyClipCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnEarlyClipCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderTransparencyCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnTransparencyCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderOpenCLCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnOpenCLCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderDoublePrecisionCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnDoublePrecisionCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderPlatformCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(OnPlatformComboCurrentIndexChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderDoAllCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnDoAllCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderKeepAspectCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnKeepAspectCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderScaleNoneRadioButton, SIGNAL(toggled(bool)), this, SLOT(OnScaleRadioButtonChanged(bool)), Qt::QueuedConnection);
connect(ui.FinalRenderScaleWidthRadioButton, SIGNAL(toggled(bool)), this, SLOT(OnScaleRadioButtonChanged(bool)), Qt::QueuedConnection);
connect(ui.FinalRenderScaleHeightRadioButton, SIGNAL(toggled(bool)), this, SLOT(OnScaleRadioButtonChanged(bool)), Qt::QueuedConnection);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_WidthSpin, spinHeight, 10, 100000, 50, SIGNAL(valueChanged(int)), SLOT(OnWidthChanged(int)), true, 1980);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_HeightSpin, spinHeight, 10, 100000, 50, SIGNAL(valueChanged(int)), SLOT(OnHeightChanged(int)), true, 1080);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_QualitySpin, spinHeight, 1, 200000, 50, SIGNAL(valueChanged(int)), SLOT(OnQualityChanged(int)), true, 1000);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_TemporalSamplesSpin, spinHeight, 1, 5000, 50, SIGNAL(valueChanged(int)), SLOT(OnTemporalSamplesChanged(int)), true, 1000);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_SupersampleSpin, spinHeight, 1, 4, 1, SIGNAL(valueChanged(int)), SLOT(OnSupersampleChanged(int)), true, 2);
row++;//Memory usage.
TwoButtonWidget* tbw = new TwoButtonWidget("...", "Open", 22, 40, 24, table);
table->setCellWidget(row, 1, tbw);
table->item(row++, 1)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
connect(tbw->m_Button1, SIGNAL(clicked(bool)), this, SLOT(OnFileButtonClicked(bool)), Qt::QueuedConnection);
connect(tbw->m_Button2, SIGNAL(clicked(bool)), this, SLOT(OnShowFolderButtonClicked(bool)), Qt::QueuedConnection);
m_PrefixEdit = new QLineEdit(table);
table->setCellWidget(row++, 1, m_PrefixEdit);
m_SuffixEdit = new QLineEdit(table);
table->setCellWidget(row++, 1, m_SuffixEdit);
ui.StartRenderButton->disconnect(SIGNAL(clicked(bool)));
connect(ui.StartRenderButton, SIGNAL(clicked(bool)), this, SLOT(OnRenderClicked(bool)), Qt::QueuedConnection);
connect(ui.StopRenderButton, SIGNAL(clicked(bool)), this, SLOT(OnCancelRenderClicked(bool)), Qt::QueuedConnection);
if (m_Wrapper.CheckOpenCL())
{
vector<string> platforms = m_Wrapper.PlatformNames();
//Populate combo boxes with available OpenCL platforms and devices.
for (i = 0; i < platforms.size(); i++)
ui.FinalRenderPlatformCombo->addItem(QString::fromStdString(platforms[i]));
//If init succeeds, set the selected platform and device combos to match what was saved in the settings.
if (m_Wrapper.Init(m_Settings->FinalPlatformIndex(), m_Settings->FinalDeviceIndex()))
{
ui.FinalRenderOpenCLCheckBox->setChecked( m_Settings->FinalOpenCL());
ui.FinalRenderPlatformCombo->setCurrentIndex(m_Settings->FinalPlatformIndex());
ui.FinalRenderDeviceCombo->setCurrentIndex( m_Settings->FinalDeviceIndex());
}
else
{
OnPlatformComboCurrentIndexChanged(0);
ui.FinalRenderOpenCLCheckBox->setChecked(false);
}
}
else
{
ui.FinalRenderOpenCLCheckBox->setChecked(false);
ui.FinalRenderOpenCLCheckBox->setEnabled(false);
}
ui.FinalRenderEarlyClipCheckBox->setChecked( m_Settings->FinalEarlyClip());
ui.FinalRenderTransparencyCheckBox->setChecked( m_Settings->FinalTransparency());
ui.FinalRenderDoublePrecisionCheckBox->setChecked(m_Settings->FinalDouble());
ui.FinalRenderSaveXmlCheckBox->setChecked( m_Settings->FinalSaveXml());
ui.FinalRenderDoAllCheckBox->setChecked( m_Settings->FinalDoAll());
ui.FinalRenderDoSequenceCheckBox->setChecked( m_Settings->FinalDoSequence());
ui.FinalRenderKeepAspectCheckBox->setChecked( m_Settings->FinalKeepAspect());
ui.FinalRenderThreadCountSpin->setValue( m_Settings->FinalThreadCount());
m_WidthSpin->setValue(m_Settings->FinalWidth());
m_HeightSpin->setValue(m_Settings->FinalHeight());
m_QualitySpin->setValue(m_Settings->FinalQuality());
m_TemporalSamplesSpin->setValue(m_Settings->FinalTemporalSamples());
m_SupersampleSpin->setValue(m_Settings->FinalSupersample());
Scale((eScaleType)m_Settings->FinalScale());
if (m_Settings->FinalDoAllExt() == "jpg")
ui.FinalRenderJpgRadioButton->setChecked(true);
else
ui.FinalRenderPngRadioButton->setChecked(true);
//Explicitly call these to enable/disable the appropriate controls.
OnOpenCLCheckBoxStateChanged(ui.FinalRenderOpenCLCheckBox->isChecked());
OnDoAllCheckBoxStateChanged(ui.FinalRenderDoAllCheckBox->isChecked());
}
/// <summary>
/// GUI settings wrapper functions, getters only.
/// </summary>
bool FractoriumFinalRenderDialog::EarlyClip() { return ui.FinalRenderEarlyClipCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::Transparency() { return ui.FinalRenderTransparencyCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::OpenCL() { return ui.FinalRenderOpenCLCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::Double() { return ui.FinalRenderDoublePrecisionCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::SaveXml() { return ui.FinalRenderSaveXmlCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::DoAll() { return ui.FinalRenderDoAllCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::DoSequence() { return ui.FinalRenderDoSequenceCheckBox->isChecked(); }
bool FractoriumFinalRenderDialog::KeepAspect() { return ui.FinalRenderKeepAspectCheckBox->isChecked(); }
QString FractoriumFinalRenderDialog::DoAllExt() { return ui.FinalRenderJpgRadioButton->isChecked() ? "jpg" : "png"; }
QString FractoriumFinalRenderDialog::Path() { return ui.FinalRenderGeometryTable->item(6, 1)->text(); }
void FractoriumFinalRenderDialog::Path(QString s) { ui.FinalRenderGeometryTable->item(6, 1)->setText(s); }
QString FractoriumFinalRenderDialog::Prefix() { return m_PrefixEdit->text(); }
QString FractoriumFinalRenderDialog::Suffix() { return m_SuffixEdit->text(); }
unsigned int FractoriumFinalRenderDialog::PlatformIndex() { return ui.FinalRenderPlatformCombo->currentIndex(); }
unsigned int FractoriumFinalRenderDialog::DeviceIndex() { return ui.FinalRenderDeviceCombo->currentIndex(); }
unsigned int FractoriumFinalRenderDialog::ThreadCount() { return ui.FinalRenderThreadCountSpin->value(); }
unsigned int FractoriumFinalRenderDialog::Width() { return m_WidthSpin->value(); }
unsigned int FractoriumFinalRenderDialog::Height() { return m_HeightSpin->value(); }
unsigned int FractoriumFinalRenderDialog::Quality() { return m_QualitySpin->value(); }
unsigned int FractoriumFinalRenderDialog::TemporalSamples() { return m_TemporalSamplesSpin->value(); }
unsigned int FractoriumFinalRenderDialog::Supersample() { return m_SupersampleSpin->value(); }
/// <summary>
/// Capture the current state of the Gui.
/// Used to hold options for performing the final render.
/// </summary>
/// <returns>The state of the Gui as a struct</returns>
FinalRenderGuiState FractoriumFinalRenderDialog::State()
{
FinalRenderGuiState state;
state.m_EarlyClip = EarlyClip();
state.m_Transparency = Transparency();
state.m_OpenCL = OpenCL();
state.m_Double = Double();
state.m_SaveXml = SaveXml();
state.m_DoAll = DoAll();
state.m_DoSequence = DoSequence();
state.m_KeepAspect = KeepAspect();
state.m_Scale = Scale();
state.m_Path = Path();
state.m_DoAllExt = DoAllExt();
state.m_Prefix = Prefix();
state.m_Suffix = Suffix();
state.m_PlatformIndex = PlatformIndex();
state.m_DeviceIndex = DeviceIndex();
state.m_ThreadCount = ThreadCount();
state.m_Width = Width();
state.m_Height = Height();
state.m_Quality = Quality();
state.m_TemporalSamples = TemporalSamples();
state.m_Supersample = Supersample();
return state;
}
/// <summary>
/// Return the type of scaling desired based on what radio button has been selected.
/// </summary>
/// <returns>The type of scaling as an eScaleType enum</returns>
eScaleType FractoriumFinalRenderDialog::Scale()
{
if (ui.FinalRenderScaleNoneRadioButton->isChecked())
return SCALE_NONE;
else if (ui.FinalRenderScaleWidthRadioButton->isChecked())
return SCALE_WIDTH;
else if (ui.FinalRenderScaleHeightRadioButton->isChecked())
return SCALE_HEIGHT;
else
return SCALE_NONE;
}
/// <summary>
/// Set the type of scaling desired which will select the corresponding radio button.
/// </summary>
/// <param name="scale">The type of scaling to use</param>
void FractoriumFinalRenderDialog::Scale(eScaleType scale)
{
if (scale == SCALE_NONE)
ui.FinalRenderScaleNoneRadioButton->setChecked(true);
else if (scale == SCALE_WIDTH)
ui.FinalRenderScaleWidthRadioButton->setChecked(true);
else if (scale == SCALE_HEIGHT)
ui.FinalRenderScaleHeightRadioButton->setChecked(true);
else
ui.FinalRenderScaleNoneRadioButton->setChecked(true);
}
/// <summary>
/// Simple wrapper to put moving the cursor to the end in a signal
/// so it can be called from a thread.
/// </summary>
void FractoriumFinalRenderDialog::MoveCursorToEnd()
{
ui.FinalRenderTextOutput->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor);
}
/// <summary>
/// Whether to use early clipping before spatial filtering.
/// </summary>
/// <param name="index">True to early clip, else don't.</param>
void FractoriumFinalRenderDialog::OnEarlyClipCheckBoxStateChanged(int state)
{
SetMemory();
}
/// <summary>
/// Whether to use transparency in png images.
/// </summary>
/// <param name="index">True to use transparency, else don't.</param>
void FractoriumFinalRenderDialog::OnTransparencyCheckBoxStateChanged(int state)
{
SetMemory();
}
/// <summary>
/// Set whether to use OpenCL in the rendering process or not.
/// </summary>
/// <param name="state">Use OpenCL if state == Qt::Checked, else don't.</param>
void FractoriumFinalRenderDialog::OnOpenCLCheckBoxStateChanged(int state)
{
bool checked = state == Qt::Checked;
ui.FinalRenderPlatformCombo->setEnabled(checked);
ui.FinalRenderDeviceCombo->setEnabled(checked);
ui.FinalRenderThreadCountSpin->setEnabled(!checked);
SetMemory();
}
/// <summary>
/// Set whether to use double or single precision in the rendering process or not.
/// This will recreate the entire controller.
/// </summary>
/// <param name="state">Use double if state == Qt::Checked, else float.</param>
void FractoriumFinalRenderDialog::OnDoublePrecisionCheckBoxStateChanged(int state)
{
SetMemory();
}
/// <summary>
/// Populate the the device combo box with all available
/// OpenCL devices for the selected platform.
/// Called when the platform combo box index changes.
/// </summary>
/// <param name="index">The selected index of the combo box</param>
void FractoriumFinalRenderDialog::OnPlatformComboCurrentIndexChanged(int index)
{
vector<string> devices = m_Wrapper.DeviceNames(index);
ui.FinalRenderDeviceCombo->clear();
for (size_t i = 0; i < devices.size(); i++)
ui.FinalRenderDeviceCombo->addItem(QString::fromStdString(devices[i]));
}
/// <summary>
/// The do all checkbox was changed.
/// If checked, render all embers available in the currently opened file, else
/// only render the current ember.
/// </summary>
/// <param name="state">The state of the checkbox</param>
void FractoriumFinalRenderDialog::OnDoAllCheckBoxStateChanged(int state)
{
ui.FinalRenderDoSequenceCheckBox->setEnabled(ui.FinalRenderDoAllCheckBox->isChecked());
ui.FinalRenderExtensionGroupBox->setEnabled(ui.FinalRenderDoAllCheckBox->isChecked());
}
/// <summary>
/// Whether to keep the aspect ratio of the desired width and height the same
/// as that of the original width and height.
/// </summary>
/// <param name="checked">The state of the checkbox</param>
void FractoriumFinalRenderDialog::OnKeepAspectCheckBoxStateChanged(int state)
{
if (state && m_Controller.get())
m_HeightSpin->SetValueStealth(m_WidthSpin->value() / m_Controller->OriginalAspect());
SetMemory();
}
/// <summary>
/// The scaling method radio button selection was changed.
/// </summary>
/// <param name="checked">The state of the radio button</param>
void FractoriumFinalRenderDialog::OnScaleRadioButtonChanged(bool checked)
{
if (checked)
SetMemory();
}
/// <summary>
/// The width spinner was changed, recompute required memory.
/// If the aspect ratio checkbox is checked, set the value of
/// the height spinner as well to be in proportion.
/// </summary>
/// <param name="d">Ignored</param>
void FractoriumFinalRenderDialog::OnWidthChanged(int d)
{
if (ui.FinalRenderKeepAspectCheckBox->isChecked() && m_Controller.get())
m_HeightSpin->SetValueStealth(m_WidthSpin->value() / m_Controller->OriginalAspect());
SetMemory();
}
/// <summary>
/// The height spinner was changed, recompute required memory.
/// If the aspect ratio checkbox is checked, set the value of
/// the width spinner as well to be in proportion.
/// </summary>
/// <param name="d">Ignored</param>
void FractoriumFinalRenderDialog::OnHeightChanged(int d)
{
if (ui.FinalRenderKeepAspectCheckBox->isChecked() && m_Controller.get())
m_WidthSpin->SetValueStealth(m_HeightSpin->value() * m_Controller->OriginalAspect());
SetMemory();
}
/// <summary>
/// The quality spinner was changed, recompute required memory.
/// </summary>
/// <param name="d">Ignored</param>
void FractoriumFinalRenderDialog::OnQualityChanged(int d)
{
SetMemory();
}
/// <summary>
/// The temporal samples spinner was changed, recompute required memory.
/// </summary>
/// <param name="d">Ignored</param>
void FractoriumFinalRenderDialog::OnTemporalSamplesChanged(int d)
{
SetMemory();
}
/// <summary>
/// The supersample spinner was changed, recompute required memory.
/// </summary>
/// <param name="d">Ignored</param>
void FractoriumFinalRenderDialog::OnSupersampleChanged(int d)
{
SetMemory();
}
/// <summary>
/// If a single ember is being rendered, show the save file dialog.
/// If a more than one is being rendered, show the save folder dialog.
/// Called when the ... file button is clicked.
/// </summary>
/// <param name="checked">Ignored</param>
void FractoriumFinalRenderDialog::OnFileButtonClicked(bool checked)
{
bool doAll = ui.FinalRenderDoAllCheckBox->isChecked();
QString filename;
if (doAll)
filename = m_Fractorium->SetupSaveFolderDialog();
else
filename = m_Fractorium->SetupSaveImageDialog(m_Controller->Name());
if (filename != "")
{
if (doAll)
{
if (!filename.endsWith(QDir::separator()))
filename += "/";
}
QFileInfo fileInfo(filename);
QString path = fileInfo.absolutePath();
m_Settings->SaveFolder(path);//Any time they exit the box with a valid value, preserve it in the settings.
Path(filename);
SetMemory();
}
}
/// <summary>
/// Show the folder where the last rendered image was saved to.
/// </summary>
/// <param name="checked">Ignored</param>
void FractoriumFinalRenderDialog::OnShowFolderButtonClicked(bool checked)
{
QString text = Path();
if (text != "")
{
QFileInfo fileInfo(text);
QString path = fileInfo.absolutePath();
QDir dir(path);
if (dir.exists())
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}
}
/// <summary>
/// Start the render process.
/// </summary>
/// <param name="checked">Ignored</param>
void FractoriumFinalRenderDialog::OnRenderClicked(bool checked)
{
if (CreateControllerFromGUI(true))
m_Controller->Render();
}
/// <summary>
/// Cancel the render.
/// </summary>
/// <param name="checked">Ignored</param>
void FractoriumFinalRenderDialog::OnCancelRenderClicked(bool checked)
{
if (m_Controller.get())
m_Controller->CancelRender();
}
/// <summary>
/// Uses the options and the ember to populate widgets.
/// Called when the dialog is initially shown.
/// </summary>
/// <param name="e">The event</param>
void FractoriumFinalRenderDialog::showEvent(QShowEvent* e)
{
#ifdef DO_DOUBLE
Ember<double> ed;
#else
Ember<float> ed;
#endif
if (CreateControllerFromGUI(true))
{
m_Fractorium->m_Controller->CopyEmber(ed);//Copy the current ember from the main window out in to a temp.
m_Controller->SetEmber(ed);//Copy the temp into the final render controller.
m_Controller->SetOriginalEmber(ed);
SetMemory();
m_Controller->ResetProgress();
}
QDir dir(m_Settings->SaveFolder());
QString name = m_Controller->Name();
if (dir.exists() && name != "")
Path(dir.absolutePath() + "/" + name + ".png");
ui.FinalRenderTextOutput->clear();
QDialog::showEvent(e);
}
/// <summary>
/// Close the dialog without running, or if running, cancel and exit.
/// Settings will not be saved.
/// Control will be returned to Fractorium::OnActionFinalRender().
/// </summary>
void FractoriumFinalRenderDialog::reject()
{
if (m_Controller.get())
{
m_Controller->CancelRender();
m_Controller->DeleteRenderer();
}
QDialog::reject();
}
/// <summary>
/// Create the controller from the options and optionally its underlying renderer.
/// </summary>
/// <returns>True if successful, else false.</returns>
bool FractoriumFinalRenderDialog::CreateControllerFromGUI(bool createRenderer)
{
bool ok = true;
#ifdef DO_DOUBLE
size_t size = Double() ? sizeof(double) : sizeof(float);
Ember<double> ed;
Ember<double> orig;
EmberFile<double> efd;
#else
size_t size = sizeof(float);
Ember<float> ed;
Ember<float> orig;
EmberFile<float> efd;
#endif
if (!m_Controller.get() || (m_Controller->SizeOfT() != size))
{
//First check if a controller has already been created, and if so, save its embers and gracefully shut it down.
if (m_Controller.get())
{
m_Controller->CopyEmber(ed);//Convert float to double or save double verbatim;
m_Controller->CopyEmberFile(efd);
m_Controller->Shutdown();
}
//Create a float or double controller based on the GUI.
#ifdef DO_DOUBLE
if (Double())
m_Controller = auto_ptr<FinalRenderEmberControllerBase>(new FinalRenderEmberController<double>(this));
else
#endif
m_Controller = auto_ptr<FinalRenderEmberControllerBase>(new FinalRenderEmberController<float>(this));
//Restore the ember and ember file.
if (m_Controller.get())
{
m_Controller->SetEmber(ed);//Convert float to double or set double verbatim;
m_Controller->SetEmberFile(efd);
m_Fractorium->m_Controller->CopyEmber(orig);//Copy the current ember from the main window out in to a temp.
m_Controller->SetOriginalEmber(orig);
}
}
if (m_Controller.get())
{
if (createRenderer)
return m_Controller->CreateRendererFromGUI();
else
return true;
}
else
return false;
}
/// <summary>
/// Compute the amount of memory needed via call to SyncAndComputeMemory(), then
/// assign the result to the table cell as text.
/// </summary>
void FractoriumFinalRenderDialog::SetMemory()
{
if (isVisible() && CreateControllerFromGUI(true))
ui.FinalRenderGeometryTable->item(5, 1)->setText(QLocale(QLocale::English).toString(m_Controller->SyncAndComputeMemory()));
}

View File

@ -0,0 +1,113 @@
#pragma once
#include "ui_FinalRenderDialog.h"
#include "SpinBox.h"
#include "DoubleSpinBox.h"
#include "TwoButtonWidget.h"
#include "FractoriumSettings.h"
#include "FinalRenderEmberController.h"
/// <summary>
/// FractoriumFinalRenderDialog class.
/// </summary>
class Fractorium;//Forward declaration since Fractorium uses this dialog.
/// <summary>
/// The final render dialog is for when the user is satisfied with the parameters they've
/// set and they want to do a final render at a higher quality and at a specific resolution
/// and save it out to a file.
/// It supports rendering either the current ember, or all of them in the opened file.
/// If a single ember is rendered, it will be saved to a single output file.
/// If multiple embers are rendered, they will all be saved to a specified directory using
/// default names.
/// The user can optionally save the Xml file with each ember.
/// They can be treated as individual images, or as an animation sequence in which case
/// motion blurring with temporal samples will be applied.
/// It keeps a pointer to the main window and the global settings object for convenience.
/// The settings used here are saved to the /finalrender portion of the settings file.
/// It has its own OpenCLWrapper member for populating the combo boxes.
/// Upon running, it will delete the main window's renderer to save memory/GPU resources and restore it to its
/// original state upon exiting.
/// This class uses a controller-based design similar to the main window.
/// </summary>
class FractoriumFinalRenderDialog : public QDialog
{
Q_OBJECT
friend Fractorium;
friend FinalRenderEmberControllerBase;
friend FinalRenderEmberController<float>;
#ifdef DO_DOUBLE
friend FinalRenderEmberController<double>;
#endif
public:
FractoriumFinalRenderDialog(FractoriumSettings* settings, QWidget* parent, Qt::WindowFlags f = 0);
bool EarlyClip();
bool Transparency();
bool OpenCL();
bool Double();
bool SaveXml();
bool DoAll();
bool DoSequence();
bool KeepAspect();
eScaleType Scale();
void Scale(eScaleType scale);
QString DoAllExt();
QString Path();
void Path(QString s);
QString Prefix();
QString Suffix();
unsigned int PlatformIndex();
unsigned int DeviceIndex();
unsigned int ThreadCount();
unsigned int Width();
unsigned int Height();
unsigned int Quality();
unsigned int TemporalSamples();
unsigned int Supersample();
FinalRenderGuiState State();
public Q_SLOTS:
void MoveCursorToEnd();
void OnEarlyClipCheckBoxStateChanged(int state);
void OnTransparencyCheckBoxStateChanged(int state);
void OnOpenCLCheckBoxStateChanged(int state);
void OnDoublePrecisionCheckBoxStateChanged(int state);
void OnPlatformComboCurrentIndexChanged(int index);
void OnDoAllCheckBoxStateChanged(int state);
void OnKeepAspectCheckBoxStateChanged(int state);
void OnScaleRadioButtonChanged(bool checked);
void OnWidthChanged(int d);
void OnHeightChanged(int d);
void OnQualityChanged(int d);
void OnTemporalSamplesChanged(int d);
void OnSupersampleChanged(int d);
void OnFileButtonClicked(bool checked);
void OnShowFolderButtonClicked(bool checked);
void OnRenderClicked(bool checked);
void OnCancelRenderClicked(bool checked);
protected:
virtual void reject();
virtual void showEvent(QShowEvent* e);
private:
bool CreateControllerFromGUI(bool createRenderer);
void SetMemory();
OpenCLWrapper m_Wrapper;
Timing m_RenderTimer;
SpinBox* m_WidthSpin;
SpinBox* m_HeightSpin;
SpinBox* m_QualitySpin;
SpinBox* m_TemporalSamplesSpin;
SpinBox* m_SupersampleSpin;
QLineEdit* m_PrefixEdit;
QLineEdit* m_SuffixEdit;
FractoriumSettings* m_Settings;
Fractorium* m_Fractorium;
auto_ptr<FinalRenderEmberControllerBase> m_Controller;
Ui::FinalRenderDialog ui;
};

View File

@ -0,0 +1,904 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FinalRenderDialog</class>
<widget class="QDialog" name="FinalRenderDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>519</width>
<height>801</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="windowTitle">
<string>Final Render</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0">
<item row="1" column="1">
<widget class="QCheckBox" name="FinalRenderDoAllCheckBox">
<property name="toolTip">
<string>Render all open flames instead of just the current one</string>
</property>
<property name="text">
<string>Render All</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="FinalRenderDoSequenceCheckBox">
<property name="toolTip">
<string>Use temporal samples value to achieve motion blur effect between flames</string>
</property>
<property name="text">
<string>Render as Animation Sequence</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="FinalRenderDoublePrecisionCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Checked: use 64-bit double precision numbers (slower, but better image quality).&lt;/p&gt;&lt;p&gt;Unchecked: use 32-bit single precision numbers (faster, but worse image quality).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use Double Precision</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="FinalRenderOpenCLCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use OpenCL to render if your video card supports it.&lt;/p&gt;&lt;p&gt;This is highly recommended as it will dramatically speed up render time.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use OpenCL</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="FinalRenderEarlyClipCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Checked: clip colors and gamma correct after density filtering.&lt;/p&gt;&lt;p&gt;Unchecked: do it after spatial filtering.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Early Clip</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="FinalRenderSaveXmlCheckBox">
<property name="toolTip">
<string>Save an Xml parameter file for each flame rendered</string>
</property>
<property name="text">
<string>Save Xml</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="FinalRenderTransparencyCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use transparency in the final image.&lt;/p&gt;&lt;p&gt;Only supported for Png format.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Transparency</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QGroupBox" name="FinalRenderScaleGroupBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>40</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>40</height>
</size>
</property>
<property name="toolTip">
<string>The scaling to perform from the editor to the final rendered image</string>
</property>
<property name="title">
<string>Scale</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QRadioButton" name="FinalRenderScaleNoneRadioButton">
<property name="text">
<string>None</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="FinalRenderScaleWidthRadioButton">
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="FinalRenderScaleHeightRadioButton">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="4" column="1">
<widget class="QGroupBox" name="FinalRenderExtensionGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>110</width>
<height>40</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>40</height>
</size>
</property>
<property name="toolTip">
<string>The image type to save the final output as when rendering all open flames</string>
</property>
<property name="title">
<string>Render All Extension</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QRadioButton" name="FinalRenderJpgRadioButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Jpg</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="FinalRenderPngRadioButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Png</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="FinalRenderKeepAspectCheckBox">
<property name="toolTip">
<string>Maintain the aspect ratio between width and height to be equal to base width and base height</string>
</property>
<property name="text">
<string>Keep Aspect Ratio</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="FinalRenderPreviewLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="sizeIncrement">
<size>
<width>1</width>
<height>1</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="FinalRenderPlatformCombo">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>320</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="FinalRenderDeviceCombo">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>320</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="FinalRenderThreadCountSpin">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The number of threads to use with CPU rendering.&lt;/p&gt;&lt;p&gt;Decrease for a more responsive system during rendering, increase for better performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="prefix">
<string>Threads </string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>64</number>
</property>
</widget>
</item>
<item>
<widget class="TableWidget" name="FinalRenderGeometryTable">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>218</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>218</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="tabKeyNavigation">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="showGrid">
<bool>true</bool>
</property>
<property name="gridStyle">
<enum>Qt::SolidLine</enum>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>110</number>
</attribute>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>35</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<attribute name="verticalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>24</number>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<row>
<property name="text">
<string>Width</string>
</property>
<property name="toolTip">
<string/>
</property>
</row>
<row>
<property name="text">
<string>Height</string>
</property>
</row>
<row>
<property name="text">
<string>Quality</string>
</property>
</row>
<row>
<property name="text">
<string>Temporal Samples</string>
</property>
</row>
<row>
<property name="text">
<string>Supersample</string>
</property>
</row>
<row>
<property name="text">
<string>Memory Usage</string>
</property>
</row>
<row>
<property name="text">
<string>Output</string>
</property>
</row>
<row>
<property name="text">
<string>Prefix</string>
</property>
</row>
<row>
<property name="text">
<string>Suffix</string>
</property>
</row>
<column>
<property name="text">
<string>Field</string>
</property>
</column>
<column>
<property name="text">
<string/>
</property>
</column>
<item row="0" column="0">
<property name="text">
<string>Width</string>
</property>
<property name="toolTip">
<string>The width in pixels of the final output image</string>
</property>
</item>
<item row="0" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="1" column="0">
<property name="text">
<string>Height</string>
</property>
<property name="toolTip">
<string>The height in pixels of the final output image</string>
</property>
</item>
<item row="1" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="2" column="0">
<property name="text">
<string>Quality</string>
</property>
<property name="toolTip">
<string>The quality in iterations per pixel of the final output image</string>
</property>
</item>
<item row="2" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="3" column="0">
<property name="text">
<string>Temporal Samples</string>
</property>
<property name="toolTip">
<string>The number of interpolated renders to do for each flame when rendering as an animation sequence</string>
</property>
</item>
<item row="3" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="4" column="0">
<property name="text">
<string>Supersample</string>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The number to multiply the dimensions of the histogram and density filtering buffer by to achieve anti-aliasing.&lt;/p&gt;&lt;p&gt;Use this very sparingly as it increases the required memory by n squared.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</item>
<item row="4" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="5" column="0">
<property name="text">
<string>Memory Usage</string>
</property>
<property name="toolTip">
<string>The amount of memory including the final output image required to perform this render</string>
</property>
</item>
<item row="5" column="1">
<property name="text">
<string>0</string>
</property>
</item>
<item row="6" column="0">
<property name="text">
<string>Output</string>
</property>
<property name="toolTip">
<string>The output file path for rendering a single flame, or folder location for rendering multiple flames</string>
</property>
</item>
<item row="6" column="1">
<property name="text">
<string/>
</property>
</item>
<item row="7" column="0">
<property name="text">
<string>Prefix</string>
</property>
<property name="toolTip">
<string>The prefix to attach to all image filenames</string>
</property>
</item>
<item row="7" column="1">
<property name="text">
<string/>
</property>
</item>
<item row="8" column="0">
<property name="text">
<string>Suffix</string>
</property>
<property name="toolTip">
<string>The suffix to attach to all image filenames</string>
</property>
</item>
<item row="8" column="1">
<property name="text">
<string/>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="FinalRenderImageCountLabel">
<property name="text">
<string>0 / 0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0" columnstretch="1,4">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item row="2" column="1">
<widget class="QProgressBar" name="FinalRenderFilteringProgress">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="FinalRenderIterationProgressLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Iteration:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="FinalRenderAccumProgressLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Final Accumulation:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QProgressBar" name="FinalRenderIterationProgress">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QProgressBar" name="FinalRenderAccumProgress">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="FinalRenderFilteringProgressLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Density Filtering:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="FinalRenderTotalProgressLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Total Progress:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QProgressBar" name="FinalRenderTotalProgress">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTextEdit" name="FinalRenderTextOutput">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="FinalRenderButtonHBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="FinalRenderButtonSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>131</width>
<height>31</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="StartRenderButton">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="text">
<string>Start</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="StopRenderButton">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="CloseButton">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="text">
<string>Close</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>TableWidget</class>
<extends>QTableWidget</extends>
<header>TableWidget.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>StartRenderButton</sender>
<signal>clicked()</signal>
<receiver>FinalRenderDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>278</x>
<y>253</y>
</hint>
<hint type="destinationlabel">
<x>96</x>
<y>254</y>
</hint>
</hints>
</connection>
<connection>
<sender>CloseButton</sender>
<signal>clicked()</signal>
<receiver>FinalRenderDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>369</x>
<y>253</y>
</hint>
<hint type="destinationlabel">
<x>179</x>
<y>282</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,553 @@
#include "FractoriumPch.h"
#include "FractoriumEmberController.h"
#include "FinalRenderEmberController.h"
#include "FinalRenderDialog.h"
#include "Fractorium.h"
/// <summary>
/// Constructor which accepts a pointer to the final render dialog.
/// It passes a pointer to the main window to the base and initializes members.
/// </summary>
/// <param name="finalRender">Pointer to the final render dialog</param>
FinalRenderEmberControllerBase::FinalRenderEmberControllerBase(FractoriumFinalRenderDialog* finalRender)
: FractoriumEmberControllerBase(finalRender->m_Fractorium)
{
m_Run = false;
m_PreviewRun = false;
m_ImageCount = 0;
m_FinishedImageCount = 0;
m_FinalRender = finalRender;
m_Settings = m_Fractorium->m_Settings;
}
/// <summary>
/// Cancel the render by calling Abort().
/// This will block until the cancelling is actually finished.
/// It should never take longer than a few milliseconds because the
/// renderer checks the m_Abort flag in many places during the process.
/// </summary>
void FinalRenderEmberControllerBase::CancelRender()
{
if (m_Result.isRunning())
{
tbb::task_group g;
g.run([&]
{
m_Run = false;
if (m_Renderer.get())
{
m_Renderer->Abort();
while (m_Renderer->InRender())
QApplication::processEvents();
m_Renderer->EnterRender();
m_Renderer->EnterFinalAccum();
m_Renderer->LeaveFinalAccum();
m_Renderer->LeaveRender();
}
});
g.wait();
while (m_Result.isRunning())
QApplication::processEvents();
m_FinalRender->ui.FinalRenderTextOutput->append("Render canceled.");
}
}
/// <summary>
/// Create a new renderer based on the options selected on the GUI.
/// If a renderer matching the options has already been created, no action is taken.
/// </summary>
/// <returns>True if a valid renderer is created or if no action is taken, else false.</returns>
bool FinalRenderEmberControllerBase::CreateRendererFromGUI()
{
bool useOpenCL = m_Wrapper.CheckOpenCL() && m_FinalRender->OpenCL();
return CreateRenderer(useOpenCL ? OPENCL_RENDERER : CPU_RENDERER,
m_FinalRender->PlatformIndex(),
m_FinalRender->DeviceIndex(),
false);//Not shared.
}
/// <summary>
/// Constructor which accepts a pointer to the final render dialog and passes it to the base.
/// The main final rendering lambda function is constructed here.
/// </summary>
/// <param name="finalRender">Pointer to the final render dialog</param>
template<typename T>
FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderDialog* finalRender)
: FinalRenderEmberControllerBase(finalRender)
{
m_PreviewRenderer = auto_ptr<EmberNs::Renderer<T, T>>(new EmberNs::Renderer<T, T>());
m_PreviewRenderer->Callback(NULL);
m_PreviewRenderer->NumChannels(4);
m_PreviewRenderer->ReclaimOnResize(true);
m_PreviewRenderFunc = [&]()
{
m_PreviewCs.Enter();//Thread prep.
m_PreviewRun = true;
m_PreviewRenderer->Abort();
QLabel* widget = m_FinalRender->ui.FinalRenderPreviewLabel;
unsigned int maxDim = 100u;
T scalePercentage;
//Determine how to scale the scaled ember to fit in the label with a max of 100x100.
if (m_Ember.m_FinalRasW >= m_Ember.m_FinalRasH)
scalePercentage = T(maxDim) / m_Ember.m_FinalRasW;
else
scalePercentage = T(maxDim) / m_Ember.m_FinalRasH;
m_PreviewEmber = m_Ember;
m_PreviewEmber.m_Quality = 100;
m_PreviewEmber.m_Supersample = 1;
m_PreviewEmber.m_TemporalSamples = 1;
m_PreviewEmber.m_FinalRasW = min(maxDim, unsigned int(scalePercentage * m_Ember.m_FinalRasW));
m_PreviewEmber.m_FinalRasH = min(maxDim, unsigned int(scalePercentage * m_Ember.m_FinalRasH));
m_PreviewEmber.m_PixelsPerUnit = scalePercentage * m_Ember.m_PixelsPerUnit;
while (!m_PreviewRenderer->Aborted() || m_PreviewRenderer->InRender())
QApplication::processEvents();
m_PreviewRenderer->EarlyClip(m_FinalRender->EarlyClip());
m_PreviewRenderer->Transparency(m_FinalRender->Transparency());
m_PreviewRenderer->SetEmber(m_PreviewEmber);
if (m_PreviewRenderer->Run(m_PreviewFinalImage) == RENDER_OK)
{
QImage image(m_PreviewEmber.m_FinalRasW, m_PreviewEmber.m_FinalRasH, QImage::Format_RGBA8888);//The label wants RGBA.
memcpy(image.scanLine(0), m_PreviewFinalImage.data(), m_PreviewFinalImage.size() * sizeof(m_PreviewFinalImage[0]));//Memcpy the data in.
QPixmap pixmap = QPixmap::fromImage(image);
QMetaObject::invokeMethod(widget, "setPixmap", Qt::QueuedConnection, Q_ARG(QPixmap, pixmap));
}
m_PreviewRun = false;
m_PreviewCs.Leave();
};
//The main rendering function which will be called in a Qt thread.
//A backup Xml is made before the rendering process starts just in case it crashes before finishing.
//If it finishes successfully, delete the backup file.
m_RenderFunc = [&]()
{
size_t i;
m_Run = true;
m_TotalTimer.Tic();//Begin timing for progress.
m_GuiState = m_FinalRender->State();//Cache render settings from the GUI before running.
m_FinishedImageCount = 0;
QFileInfo original(m_GuiState.m_Path);
QString backup = original.absolutePath() + QDir::separator() + m_GuiState.m_Prefix + original.completeBaseName() + m_GuiState.m_Suffix + "_backup.flame";
QMetaObject::invokeMethod(m_Fractorium, "OnActionSaveCurrentToOpenedFile", Qt::QueuedConnection, Q_ARG(bool, true));//First, save the current ember back to its opened file.
m_Fractorium->m_Controller->CopyEmber(m_Ember);
m_Fractorium->m_Controller->CopyEmberFile(m_EmberFile);//Copy the whole file, will take about 0.2ms per ember in the file.
//Save backup Xml.
if (m_GuiState.m_DoAll && m_EmberFile.m_Embers.size() > 1)
m_XmlWriter.Save(backup.toStdString().c_str(), m_EmberFile.m_Embers, 0, true, false, true);
else
m_XmlWriter.Save(backup.toStdString().c_str(), m_Ember, 0, true, false, true);
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "setText", Qt::QueuedConnection, Q_ARG(QString, "Begin rendering..."));
m_Renderer->EarlyClip(m_GuiState.m_EarlyClip);
m_Renderer->ThreadCount(m_GuiState.m_ThreadCount);
m_Renderer->Transparency(m_GuiState.m_Transparency);
if (m_GuiState.m_Path.endsWith(".png", Qt::CaseInsensitive) || m_Renderer->RendererType() == OPENCL_RENDERER)
m_Renderer->NumChannels(4);
else
m_Renderer->NumChannels(3);
//The rendering process is different between doing a single image, and doing multiple.
if (m_GuiState.m_DoAll && m_EmberFile.m_Embers.size() > 1)
{
m_ImageCount = m_EmberFile.m_Embers.size();
ResetProgress();
//Different action required for rendering as animation or not.
if (m_GuiState.m_DoSequence)
{
//Need to loop through and set all w, h, q, ts, ss and t vals.
for (i = 0; i < m_EmberFile.m_Embers.size() && m_Run; i++)
{
Sync(m_EmberFile.m_Embers[i]);
if (i > 0)
{
if (m_EmberFile.m_Embers[i].m_Time <= m_EmberFile.m_Embers[i - 1].m_Time)
m_EmberFile.m_Embers[i].m_Time = m_EmberFile.m_Embers[i - 1].m_Time + 1;
}
else if (i == 0)
{
m_EmberFile.m_Embers[i].m_Time = 0;
}
m_EmberFile.m_Embers[i].m_TemporalSamples = m_GuiState.m_TemporalSamples;
}
m_Renderer->SetEmber(m_EmberFile.m_Embers);//Copy all embers to the local storage inside the renderer.
//Render each image, cancelling if m_Run ever gets set to false.
for (i = 0; i < m_EmberFile.m_Embers.size() && m_Run; i++)
{
m_Renderer->Reset();//Have to manually set this since the ember is not set each time through.
m_RenderTimer.Tic();//Toc() is called in the progress function.
if (m_Renderer->Run(m_FinalImage, i) != RENDER_OK)
{
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, "Renderering failed.\n"));
m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRender->ui.FinalRenderTextOutput, false);
}
}
}
else//Render all images, but not as an animation sequence (without temporal samples motion blur).
{
//Copy widget values to all embers
for (i = 0; i < m_EmberFile.m_Embers.size() && m_Run; i++)
{
Sync(m_EmberFile.m_Embers[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.m_Embers.size() && m_Run; i++)
{
m_Renderer->SetEmber(m_EmberFile.m_Embers[i]);
m_RenderTimer.Tic();//Toc() is called in the progress function.
if (m_Renderer->Run(m_FinalImage) != RENDER_OK)
{
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, "Renderering failed.\n"));
m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRender->ui.FinalRenderTextOutput, false);
}
}
}
}
else//Render a single image.
{
m_ImageCount = 1;
Sync(m_Ember);
ResetProgress();
m_Ember.m_TemporalSamples = 1;
m_Renderer->SetEmber(m_Ember);
m_RenderTimer.Tic();//Toc() is called in the progress function.
if (m_Renderer->Run(m_FinalImage) != RENDER_OK)
{
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, "Renderering failed.\n"));
m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRender->ui.FinalRenderTextOutput, false);
}
}
QFile::remove(backup);
m_Run = false;
};
}
/// <summary>
/// Setters for embers and ember files which convert between float and double types.
/// These are used to preserve the current ember/file when switching between renderers.
/// Note that some precision will be lost when going from double to float.
/// </summary>
template <typename T> void FinalRenderEmberController<T>::SetEmber(const Ember<float>& ember, bool verbatim) { m_Ember = ember; }
template <typename T> void FinalRenderEmberController<T>::CopyEmber(Ember<float>& ember) { ember = m_Ember; }
template <typename T> void FinalRenderEmberController<T>::SetEmberFile(const EmberFile<float>& emberFile) { m_EmberFile = emberFile; }
template <typename T> void FinalRenderEmberController<T>::CopyEmberFile(EmberFile<float>& emberFile) { emberFile = m_EmberFile; }
template <typename T> double FinalRenderEmberController<T>::OriginalAspect() { return double(m_OriginalEmber.m_OrigFinalRasW) / m_OriginalEmber.m_OrigFinalRasH; }
#ifdef DO_DOUBLE
template <typename T> void FinalRenderEmberController<T>::SetEmber(const Ember<double>& ember, bool verbatim) { m_Ember = ember; }
template <typename T> void FinalRenderEmberController<T>::CopyEmber(Ember<double>& ember) { ember = m_Ember; }
template <typename T> void FinalRenderEmberController<T>::SetEmberFile(const EmberFile<double>& emberFile) { m_EmberFile = emberFile; }
template <typename T> void FinalRenderEmberController<T>::CopyEmberFile(EmberFile<double>& emberFile) { emberFile = m_EmberFile; }
template <typename T> void FinalRenderEmberController<T>::SetOriginalEmber(Ember<double>& ember) { m_OriginalEmber = ember; }
#else
template <typename T> void FinalRenderEmberController<T>::SetOriginalEmber(Ember<float>& ember) { m_OriginalEmber = ember; }
#endif
/// <summary>
/// Progress function.
/// Take special action to sync options upon finishing.
/// </summary>
/// <param name="ember">The ember currently being rendered</param>
/// <param name="foo">An extra dummy parameter</param>
/// <param name="fraction">The progress fraction from 0-100</param>
/// <param name="stage">The stage of iteration. 1 is iterating, 2 is density filtering, 2 is final accumulation.</param>
/// <param name="etaMs">The estimated milliseconds to completion of the current stage</param>
/// <returns>0 if the user has clicked cancel, else 1 to continue rendering.</returns>
template <typename T>
int FinalRenderEmberController<T>::ProgressFunc(Ember<T>& ember, void* foo, double fraction, int stage, double etaMs)
{
static int count = 0;
if (stage == 0)
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderIterationProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int(fraction)));
else if (stage == 1)
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderFilteringProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int(fraction)));
else if (stage == 2)
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderAccumProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int(fraction)));
//Finished, so take special action.
if (stage == 2 && (int)fraction == 100)
{
string renderTimeString = m_RenderTimer.Format(m_RenderTimer.Toc()), totalTimeString;
QString status, filename = m_GuiState.m_Path;
QFileInfo original(filename);
EmberStats stats = m_Renderer->Stats();
QString iters = QLocale(QLocale::English).toString(stats.m_Iters);
if (m_GuiState.m_DoAll && m_EmberFile.m_Embers.size() > 1)
filename = original.absolutePath() + QDir::separator() + m_GuiState.m_Prefix + QString::fromStdString(m_EmberFile.m_Embers[m_FinishedImageCount].m_Name) + m_GuiState.m_Suffix + "." + m_GuiState.m_DoAllExt;
else
filename = original.absolutePath() + QDir::separator() + m_GuiState.m_Prefix + original.completeBaseName() + m_GuiState.m_Suffix + "." + original.suffix();
filename = EmberFile<T>::UniqueFilename(filename);
//Save whatever options were specified on the GUI to the settings.
m_Settings->FinalEarlyClip(m_GuiState.m_EarlyClip);
m_Settings->FinalTransparency(m_GuiState.m_Transparency);
m_Settings->FinalOpenCL(m_GuiState.m_OpenCL);
m_Settings->FinalDouble(m_GuiState.m_Double);
m_Settings->FinalPlatformIndex(m_GuiState.m_PlatformIndex);
m_Settings->FinalDeviceIndex(m_GuiState.m_DeviceIndex);
m_Settings->FinalSaveXml(m_GuiState.m_SaveXml);
m_Settings->FinalDoAll(m_GuiState.m_DoAll);
m_Settings->FinalDoSequence(m_GuiState.m_DoSequence);
m_Settings->FinalKeepAspect(m_GuiState.m_KeepAspect);
m_Settings->FinalScale(m_GuiState.m_Scale);
m_Settings->FinalDoAllExt(m_GuiState.m_DoAllExt);
m_Settings->FinalThreadCount(m_GuiState.m_ThreadCount);
m_Settings->FinalWidth(m_GuiState.m_Width);
m_Settings->FinalHeight(m_GuiState.m_Height);
m_Settings->FinalQuality(m_GuiState.m_Quality);
m_Settings->FinalTemporalSamples(m_GuiState.m_TemporalSamples);
m_Settings->FinalSupersample(m_GuiState.m_Supersample);
SaveCurrentRender(filename);
if (m_GuiState.m_SaveXml)
{
QFileInfo xmlFileInfo(filename);//Create another one in case it was modified for batch rendering.
QString newPath = xmlFileInfo.absolutePath() + QDir::separator() + xmlFileInfo.completeBaseName() + ".flame";
xmlDocPtr tempEdit = ember.m_Edits;
ember.m_Edits = m_XmlWriter.CreateNewEditdoc(&ember, NULL, "edit", m_Settings->Nick().toStdString(), m_Settings->Url().toStdString(), m_Settings->Id().toStdString(), "", 0, 0);
m_XmlWriter.Save(newPath.toStdString().c_str(), ember, 0, true, false, true);//Note that the ember passed is used, rather than m_Ember because it's what was actually rendered.
if (tempEdit != NULL)
xmlFreeDoc(tempEdit);
}
m_FinishedImageCount++;
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTotalProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int(((float)m_FinishedImageCount / (float)m_ImageCount) * 100)));
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderImageCountLabel, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::number(m_FinishedImageCount) + " / " + QString::number(m_ImageCount)));
status = "Image " + QString::number(m_FinishedImageCount) + ":\nPure render time: " + QString::fromStdString(renderTimeString);
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, status));
totalTimeString = m_TotalTimer.Format(m_TotalTimer.Toc());
status = "Total render time: " + QString::fromStdString(totalTimeString) + "\nTotal iters: " + iters + "\n";
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, status));
QMetaObject::invokeMethod(m_FinalRender, "MoveCursorToEnd", Qt::QueuedConnection);
if (m_FinishedImageCount != m_ImageCount)
{
ResetProgress(false);
}
}
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "update", Qt::QueuedConnection);
QApplication::processEvents();
return m_Run ? 1 : 0;
}
/// <summary>
/// Start the final rendering process.
/// Create the needed renderer from the GUI if it has not been created yet.
/// </summary>
/// <returns></returns>
template<typename T>
bool FinalRenderEmberController<T>::Render()
{
QString filename = m_FinalRender->Path();
if (filename == "")
{
QMessageBox::critical(m_FinalRender, "File Error", "Please enter a valid path and filename for the output.");
return false;
}
if (CreateRendererFromGUI())
{
m_FinalRender->ui.FinalRenderTextOutput->clear();
//Note that a Qt thread must be used, rather than a tbb task.
//This is because tbb does a very poor job of allocating thread resources
//and dedicates an entire core just to this thread which does nothing waiting for the
//parallel iteration loops inside of the CPU renderer to finish. The result is that
//the renderer ends up using ThreadCount - 1 to iterate, instead of ThreadCount.
//By using a Qt thread here, and tbb inside the renderer, all cores can be maxed out.
m_Result = QtConcurrent::run(m_RenderFunc);
m_Settings->sync();
return true;
}
else
return false;
}
/// <summary>
/// Stop rendering and initialize a new renderer, using the specified type and the options on the final render dialog.
/// </summary>
/// <param name="renderType">The type of render to create</param>
/// <param name="platform">The index platform of the platform to use</param>
/// <param name="device">The index device of the device to use</param>
/// <param name="outputTexID">The texture ID of the shared OpenGL texture if shared</param>
/// <param name="shared">True if shared with OpenGL, else false. Default: true.</param>
/// <returns>True if nothing went wrong, else false.</returns>
template <typename T>
bool FinalRenderEmberController<T>::CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared)
{
bool ok = true;
vector<string> errorReport;
QString filename = m_FinalRender->Path();
unsigned int channels = filename.endsWith(".png", Qt::CaseInsensitive) ? 4 : 3;
CancelRender();
if (m_Renderer.get() &&
m_Renderer->Ok() &&
m_Renderer->RendererType() == renderType &&
m_Platform == platform &&
m_Device == device &&
m_Shared == shared)
{
return ok;
}
if (!m_Renderer.get() || (m_Renderer->RendererType() != renderType))
{
EmberReport emberReport;
vector<string> errorReport;
m_Platform = platform;//Store values for re-creation later on.
m_Device = device;
m_OutputTexID = 0;//Don't care about tex ID when doing final render.
m_Shared = shared;
m_Renderer = auto_ptr<EmberNs::RendererBase>(::CreateRenderer<T, T>(renderType, platform, device, shared, m_OutputTexID, emberReport));
errorReport = emberReport.ErrorReport();
if (!errorReport.empty())
{
ok = false;
QMessageBox::critical(m_Fractorium, "Renderer Creation Error", "Could not create requested renderer, fallback CPU renderer created. See info tab for details.");
m_Fractorium->ErrorReportToQTextEdit(errorReport, m_Fractorium->ui.InfoRenderingTextEdit);
}
}
if (m_Renderer.get())
{
if (m_Renderer->RendererType() == OPENCL_RENDERER)
channels = 4;//Always using 4 since the GL texture is RGBA.
m_Renderer->Callback(this);
m_Renderer->NumChannels(channels);
m_Renderer->ReclaimOnResize(false);
m_Renderer->EarlyClip(m_FinalRender->EarlyClip());
m_Renderer->ThreadCount(m_FinalRender->ThreadCount());
m_Renderer->Transparency(m_FinalRender->Transparency());
}
else
{
ok = false;
QMessageBox::critical(m_FinalRender, "Renderer Creation Error", "Could not create renderer, aborting. See info tab for details.");
}
return ok;
}
/// <summary>
/// Set various parameters in the renderer and current ember with the values
/// specified in the widgets and compute the amount of memory required to render.
/// This includes the memory needed for the final output image.
/// </summary>
/// <returns>If successful, memory required in bytes, else zero.</returns>
template <typename T>
unsigned __int64 FinalRenderEmberController<T>::SyncAndComputeMemory()
{
if (m_Renderer.get())
{
bool b = false;
QString filename = m_FinalRender->Path();
unsigned int channels = filename.endsWith(".png", Qt::CaseInsensitive) ? 4 : 3;//4 channels for Png, else 3.
Sync(m_Ember);
m_Renderer->SetEmber(m_Ember);
m_Renderer->CreateSpatialFilter(b);
m_Renderer->CreateTemporalFilter(b);
m_Renderer->NumChannels(channels);
m_Renderer->ComputeBounds();
CancelPreviewRender();
//m_PreviewResult = QtConcurrent::run(m_PreviewRenderFunc);
//while (!m_PreviewResult.isRunning()) { QApplication::processEvents(); }//Wait for it to start up.
m_PreviewRenderFunc();
return m_Renderer->MemoryRequired(true);
}
return 0;
}
/// <summary>
/// Reset the progress bars.
/// </summary>
/// <param name="total">True to reset render image and total progress bars, else false to only do iter, filter and accum bars.</param>
template <typename T>
void FinalRenderEmberController<T>::ResetProgress(bool total)
{
if (total)
{
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderImageCountLabel, "setText", Qt::QueuedConnection, Q_ARG(QString, "0 / " + QString::number(m_ImageCount)));
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTotalProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 0));
}
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderIterationProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 0));
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderFilteringProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 0));
QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderAccumProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 0));
}
template <typename T>
void FinalRenderEmberController<T>::CancelPreviewRender()
{
m_PreviewRenderer->Abort();
while (m_PreviewRenderer->InRender()) { QApplication::processEvents(); }
while (m_PreviewRun) { QApplication::processEvents(); }
while (m_PreviewResult.isRunning()) { QApplication::processEvents(); }
}
/// <summary>
/// Copy widget values to the ember passed in.
/// </summary>
/// <param name="ember">The ember whose values will be modified</param>
template <typename T>
void FinalRenderEmberController<T>::Sync(Ember<T>& ember)
{
int w = m_FinalRender->m_WidthSpin->value();
int h = m_FinalRender->m_HeightSpin->value();
ember.m_FinalRasW = m_OriginalEmber.m_OrigFinalRasW;//Scale is always in terms of the original dimensions of the ember in the editor.
ember.m_FinalRasH = m_OriginalEmber.m_OrigFinalRasH;
ember.m_PixelsPerUnit = m_OriginalEmber.m_PixelsPerUnit;
ember.SetSizeAndAdjustScale(w, h, false, m_FinalRender->Scale());
ember.m_Quality = m_FinalRender->m_QualitySpin->value();
ember.m_Supersample = m_FinalRender->m_SupersampleSpin->value();
if (m_FinalRender->ui.FinalRenderDoSequenceCheckBox->isChecked())
ember.m_TemporalSamples = m_FinalRender->m_TemporalSamplesSpin->value();
}

View File

@ -0,0 +1,143 @@
#pragma once
#include "FractoriumSettings.h"
#include "FractoriumEmberController.h"
/// <summary>
/// FinalRenderEmberControllerBase and FinalRenderEmberController<T> classes.
/// </summary>
/// <summary>
/// FractoriumEmberController and Fractorium need each other, but each can't include the other.
/// So Fractorium includes this file, and Fractorium is declared as a forward declaration here.
/// </summary>
class Fractorium;
class FractoriumFinalRenderDialog;
/// <summary>
/// Used to hold the options specified in the current state of the Gui for performing the final render.
/// </summary>
struct FinalRenderGuiState
{
bool m_EarlyClip;
bool m_AlphaChannel;
bool m_Transparency;
bool m_OpenCL;
bool m_Double;
bool m_SaveXml;
bool m_DoAll;
bool m_DoSequence;
bool m_KeepAspect;
eScaleType m_Scale;
QString m_Path;
QString m_DoAllExt;
QString m_Prefix;
QString m_Suffix;
unsigned int m_PlatformIndex;
unsigned int m_DeviceIndex;
unsigned int m_ThreadCount;
unsigned int m_Width;
unsigned int m_Height;
unsigned int m_Quality;
unsigned int m_TemporalSamples;
unsigned int m_Supersample;
};
/// <summary>
/// FinalRenderEmberControllerBase serves as a non-templated base class with virtual
/// functions which will be overridden in a derived class that takes a template parameter.
/// Although not meant to be used as an interactive renderer, it derives from FractoriumEmberControllerBase
/// to access a few of its members to avoid having to redefine them here.
/// </summary>
class FinalRenderEmberControllerBase : public FractoriumEmberControllerBase
{
friend FractoriumFinalRenderDialog;
public:
FinalRenderEmberControllerBase(FractoriumFinalRenderDialog* finalRender);
virtual ~FinalRenderEmberControllerBase() { }
virtual unsigned __int64 SyncAndComputeMemory() { return 0; }
virtual QString Name() const { return ""; }
virtual void ResetProgress(bool total = true) { }
#ifdef DO_DOUBLE
virtual void SetOriginalEmber(Ember<double>& ember) { }
#else
virtual void SetOriginalEmber(Ember<float>& ember) { }
#endif
virtual double OriginalAspect() { return 1; }
void CancelRender();
bool CreateRendererFromGUI();
protected:
bool m_Run;
bool m_PreviewRun;
unsigned int m_ImageCount;
unsigned int m_FinishedImageCount;
QFuture<void> m_Result;
QFuture<void> m_PreviewResult;
std::function<void (void)> m_RenderFunc;
std::function<void (void)> m_PreviewRenderFunc;
vector<unsigned char> m_PreviewFinalImage;
FractoriumSettings* m_Settings;
FractoriumFinalRenderDialog* m_FinalRender;
FinalRenderGuiState m_GuiState;
OpenCLWrapper m_Wrapper;
CriticalSection m_PreviewCs;
Timing m_RenderTimer;
Timing m_TotalTimer;
};
/// <summary>
/// Templated derived class which implements all interaction functionality between the embers
/// of a specific template type and the final render dialog;
/// </summary>
template<typename T>
class FinalRenderEmberController : public FinalRenderEmberControllerBase
{
public:
FinalRenderEmberController(FractoriumFinalRenderDialog* finalRender);
virtual ~FinalRenderEmberController() { }
virtual void SetEmber(const Ember<float>& ember, bool verbatim = false);
virtual void CopyEmber(Ember<float>& ember);
virtual void SetEmberFile(const EmberFile<float>& emberFile);
virtual void CopyEmberFile(EmberFile<float>& emberFile);
#ifdef DO_DOUBLE
virtual void SetEmber(const Ember<double>& ember, bool verbatim = false);
virtual void CopyEmber(Ember<double>& ember);
virtual void SetEmberFile(const EmberFile<double>& emberFile);
virtual void CopyEmberFile(EmberFile<double>& emberFile);
virtual void SetOriginalEmber(Ember<double>& ember);
#else
virtual void SetOriginalEmber(Ember<float>& ember);
#endif
virtual double OriginalAspect();
virtual int ProgressFunc(Ember<T>& ember, void* foo, double fraction, int stage, double etaMs);
virtual bool Render();
virtual bool CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared = true);
virtual unsigned int SizeOfT() { return sizeof(T); }
virtual unsigned __int64 SyncAndComputeMemory();
virtual QString Name() const { return QString::fromStdString(m_Ember.m_Name); }
virtual void ResetProgress(bool total = true);
void CancelPreviewRender();
protected:
void Sync(Ember<T>& ember);
Ember<T> m_Ember;
Ember<T> m_OriginalEmber;
Ember<T> m_PreviewEmber;
EmberFile<T> m_EmberFile;
EmberToXml<T> m_XmlWriter;
auto_ptr<EmberNs::Renderer<T, T>> m_PreviewRenderer;
};
template class FinalRenderEmberController<float>;
#ifdef DO_DOUBLE
template class FinalRenderEmberController<double>;
#endif

Binary file not shown.

View File

@ -0,0 +1,544 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Constructor that initializes the entire program.
/// The setup process is very lengthy because it requires many custom modifications
/// to the GUI widgets that are not possible to do through the designer. So if something
/// is present here, it's safe to assume it can't be done in the designer.
/// </summary>
Fractorium::Fractorium(QWidget* parent)
: QMainWindow(parent)
{
int spinHeight = 20;
size_t i, j;
Timing t;
ui.setupUi(this);
qRegisterMetaType<QVector<int>>("QVector<int>");//For previews.
qRegisterMetaType<vector<unsigned char>>("vector<unsigned char>");
qRegisterMetaType<EmberTreeWidgetItemBase*>("EmberTreeWidgetItemBase*");
m_FontSize = 9;
m_VarSortMode = 1;//Sort by weight by default.
m_ColorDialog = new QColorDialog(this);
m_Settings = new FractoriumSettings(this);
m_FileDialog = NULL;//Use lazy instantiation upon first use.
m_FolderDialog = NULL;
m_FinalRenderDialog = new FractoriumFinalRenderDialog(m_Settings, this);
m_OptionsDialog = new FractoriumOptionsDialog(m_Settings, this);
m_AboutDialog = new FractoriumAboutDialog(this);
//The options dialog should be a fixed size without a size grip, however even if it's here, it still shows up. Perhaps Qt will fix it some day.
m_OptionsDialog->layout()->setSizeConstraint(QLayout::SetFixedSize);
m_OptionsDialog->setSizeGripEnabled(false);
connect(m_ColorDialog, SIGNAL(colorSelected(const QColor&)), this, SLOT(OnColorSelected(const QColor&)), Qt::QueuedConnection);
InitToolbarUI();
InitParamsUI();
InitXformsUI();
InitXformsColorUI();
InitXformsAffineUI();
InitXformsVariationsUI();
InitXformsXaosUI();
InitPaletteUI();
InitLibraryUI();
InitMenusUI();
//This will init the controller and fill in the variations and palette tables with template specific instances
//of their respective objects.
#ifdef DO_DOUBLE
if (m_Settings->Double())
m_Controller = auto_ptr<FractoriumEmberControllerBase>(new FractoriumEmberController<double>(this));
else
#endif
m_Controller = auto_ptr<FractoriumEmberControllerBase>(new FractoriumEmberController<float>(this));
if (m_Wrapper.CheckOpenCL() && m_Settings->OpenCL() && m_QualitySpin->value() < 30)
m_QualitySpin->setValue(30);
int statusBarHeight = 20;
ui.statusBar->setMinimumHeight(statusBarHeight);
ui.statusBar->setMaximumHeight(statusBarHeight);
m_RenderStatusLabel = new QLabel(this);
m_RenderStatusLabel->setMinimumWidth(200);
m_RenderStatusLabel->setAlignment(Qt::AlignRight);
ui.statusBar->addPermanentWidget(m_RenderStatusLabel);
m_CoordinateStatusLabel = new QLabel(this);
m_CoordinateStatusLabel->setMinimumWidth(300);
m_CoordinateStatusLabel->setMaximumWidth(300);
m_CoordinateStatusLabel->setAlignment(Qt::AlignLeft);
ui.statusBar->addWidget(m_CoordinateStatusLabel);
int progressBarHeight = 15;
int progressBarWidth = 300;
m_ProgressBar = new QProgressBar(this);
m_ProgressBar->setRange(0, 100);
m_ProgressBar->setValue(0);
m_ProgressBar->setMinimumHeight(progressBarHeight);
m_ProgressBar->setMaximumHeight(progressBarHeight);
m_ProgressBar->setMinimumWidth(progressBarWidth);
m_ProgressBar->setMaximumWidth(progressBarWidth);
ui.statusBar->addPermanentWidget(m_ProgressBar);
showMaximized();
connect(ui.DockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(dockLocationChanged(Qt::DockWidgetArea)));
connect(ui.DockWidget, SIGNAL(topLevelChanged(bool)), this, SLOT(OnDockTopLevelChanged(bool)));
//Always ensure the library tab is selected, which will show preview renders.
ui.ParamsTabWidget->setCurrentIndex(0);
ui.XformsTabWidget->setCurrentIndex(2);//Make variations tab the currently selected one under the Xforms tab.
//Setting certain values will completely throw off the GUI, doing everything
//from setting strange margins, to arbitrarily changing the fonts used.
//For these cases, the only way to fix the problem is to use style sheets.
ui.ColorTable->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.GeometryTable->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.FilterTable->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.IterationTable->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.AffineTab->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.XformWeightNameTable->setStyleSheet("QTableWidget::item { padding: 0px; }");
ui.XformColorIndexTable->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.XformColorValuesTable->setStyleSheet("QTableWidget::item { padding: 1px; }");
ui.XformPaletteRefTable->setStyleSheet("QTableWidget::item { padding: 0px; border: none; margin: 0px; }");
ui.PaletteAdjustTable->setStyleSheet("QTableWidget::item { padding: 1px; }");//Need this to avoid covering the top border pixel with the spinners.
ui.statusBar->setStyleSheet("QStatusBar QLabel { padding-left: 2px; padding-right: 2px; }");
m_PreviousPaletteRow = -1;//Force click handler the first time through.
//Setup pointer in the GL window to point back to here.
ui.GLDisplay->SetMainWindow(this);
SetCoordinateStatus(0, 0, 0, 0);
//At this point, everything has been setup except the renderer. Shortly after
//this constructor exits, GLWidget::initializeGL() will create the initial flock and start the rendering timer
//which executes whenever the program is idle. Upon starting the timer, the renderer
//will be initialized.
}
/// <summary>
/// Destructor which saves out the settings file.
/// All other memory is cleared automatically through the use of STL.
/// </summary>
Fractorium::~Fractorium()
{
m_Settings->sync();
}
/// <summary>
/// Stop the render timer and start the delayed restart timer.
/// This is a massive hack because Qt has no way of detecting when a resize event
/// is started and stopped. Detecting if mouse buttons down is also not an option
/// because the documentation says it gives the wrong result.
/// </summary>
void Fractorium::Resize()
{
if (!QCoreApplication::closingDown())
m_Controller->DelayedStartRenderTimer();
}
/// <summary>
/// Set the coordinate text in the status bar.
/// </summary>
/// <param name="x">The raster x coordinate</param>
/// <param name="y">The raster y coordinate</param>
/// <param name="worldX">The cartesian world x coordinate</param>
/// <param name="worldY">The cartesian world y coordinate</param>
void Fractorium::SetCoordinateStatus(int x, int y, float worldX, float worldY)
{
//Use sprintf rather than allocating and concatenating 6 QStrings for efficiency since this is called on every mouse move.
sprintf_s(m_CoordinateString, 128, "Window: %4d, %4d World: %2.2f, %2.2f", x, y, worldX, worldY);
m_CoordinateStatusLabel->setText(QString(m_CoordinateString));
}
/// <summary>
/// Apply the settings for saving an ember to an Xml file to an ember (presumably about to be saved).
/// </summary>
/// <param name="ember">The ember to apply the settings to</param>
template <typename T>
void FractoriumEmberController<T>::ApplyXmlSavingTemplate(Ember<T>& ember)
{
ember.m_FinalRasW = m_Fractorium->m_Settings->XmlWidth();
ember.m_FinalRasH = m_Fractorium->m_Settings->XmlHeight();
ember.m_TemporalSamples = m_Fractorium->m_Settings->XmlTemporalSamples();
ember.m_Quality = m_Fractorium->m_Settings->XmlQuality();
ember.m_Supersample = m_Fractorium->m_Settings->XmlSupersample();
}
/// <summary>
/// Return whether the current ember contains a final xform and the GUI is aware of it.
/// </summary>
/// <returns>True if the current ember contains a final xform, else false.</returns>
bool Fractorium::HaveFinal()
{
QComboBox* combo = ui.CurrentXformCombo;
return (combo->count() > 0 && combo->itemText(combo->count() - 1) == "Final");
}
/// <summary>
/// Slots.
/// </summary>
/// <summary>
/// Empty placeholder for now.
/// Qt has a severe bug where the dock gets hidden behind the window.
/// Perhaps this will be used in the future if Qt ever fixes that bug.
/// Called when the top level dock is changed.
/// </summary>
/// <param name="topLevel">True if top level, else false.</param>
void Fractorium::OnDockTopLevelChanged(bool topLevel)
{
//if (topLevel)
//{
// if (ui.DockWidget->y() <= 0)
// ui.DockWidget->setGeometry(ui.DockWidget->x(), ui.DockWidget->y() + 100, ui.DockWidget->width(), ui.DockWidget->height());
//
// ui.DockWidget->setFloating(true);
//}
//else
// ui.DockWidget->setFloating(false);
}
/// <summary>
/// Empty placeholder for now.
/// Qt has a severe bug where the dock gets hidden behind the window.
/// Perhaps this will be used in the future if Qt ever fixes that bug.
/// Called when the dock location is changed.
/// </summary>
/// <param name="area">The dock widget area</param>
void Fractorium::dockLocationChanged(Qt::DockWidgetArea area)
{
//ui.DockWidget->resize(500, ui.DockWidget->height());
//ui.DockWidget->update();
//ui.dockWidget->setFloating(true);
//ui.dockWidget->setFloating(false);
}
/// <summary>
/// Virtual event overrides.
/// </summary>
/// <summary>
/// Resize event, just pass to base.
/// </summary>
/// <param name="e">The event</param>
void Fractorium::resizeEvent(QResizeEvent* e)
{
QMainWindow::resizeEvent(e);
}
/// <summary>
/// Stop rendering and block before exiting.
/// Called on program exit.
/// </summary>
/// <param name="e">The event</param>
void Fractorium::closeEvent(QCloseEvent* e)
{
if (m_Controller.get())
{
m_Controller->StopRenderTimer(true);//Will wait until fully exited and stopped.
m_Controller->StopPreviewRender();
}
if (e)
e->accept();
}
/// <summary>
/// Examine the files dragged when it first enters the window area.
/// Ok if at least one file is .flam3, .flam3 or .xml, else not ok.
/// Called when the user first drags files in.
/// </summary>
/// <param name="e">The event</param>
void Fractorium::dragEnterEvent(QDragEnterEvent* e)
{
if (e->mimeData()->hasUrls())
{
foreach (QUrl url, e->mimeData()->urls())
{
QString localFile = url.toLocalFile();
QFileInfo fileInfo(localFile);
QString suf = fileInfo.suffix();
if (suf == "flam3" || suf == "flame" || suf == "xml")
{
e->accept();
break;
}
}
}
}
/// <summary>
/// Always accept drag when moving, so that the drop event will correctly be called.
/// </summary>
/// <param name="e">The event</param>
void Fractorium::dragMoveEvent(QDragMoveEvent* e)
{
e->accept();
}
/// <summary>
/// Examine and open the dropped files.
/// Called when the user drops a file in.
/// </summary>
/// <param name="e">The event</param>
void Fractorium::dropEvent(QDropEvent* e)
{
QStringList filenames;
Qt::KeyboardModifiers mod = e->keyboardModifiers();
bool append = mod.testFlag(Qt::ControlModifier) ? true : false;
if (e->mimeData()->hasUrls())
{
foreach (QUrl url, e->mimeData()->urls())
{
QString localFile = url.toLocalFile();
QFileInfo fileInfo(localFile);
QString suf = fileInfo.suffix();
if (suf == "flam3" || suf == "flame" || suf == "xml")
filenames << localFile;
}
}
if (!filenames.empty())
m_Controller->OpenAndPrepFiles(filenames, append);
}
/// <summary>
/// Setup a combo box to be placed in a table cell.
/// </summary>
/// <param name="table">The table the combo box belongs to</param>
/// <param name="receiver">The receiver object</param>
/// <param name="row">The row in the table where this combo box resides</param>
/// <param name="col">The col in the table where this combo box resides</param>
/// <param name="comboBox">Double pointer to combo box which will hold the spinner upon exit</param>
/// <param name="vals">The string values to populate the combo box with</param>
/// <param name="signal">The signal the combo box emits</param>
/// <param name="slot">The slot to receive the signal</param>
/// <param name="connectionType">Type of the connection. Default: Qt::QueuedConnection.</param>
void Fractorium::SetupCombo(QTableWidget* table, const QObject* receiver, int& row, int col, StealthComboBox*& comboBox, vector<string>& vals, const char* signal, const char* slot, Qt::ConnectionType connectionType)
{
comboBox = new StealthComboBox(table);
std::for_each(vals.begin(), vals.end(), [&](string s) { comboBox->addItem(s.c_str()); });
table->setCellWidget(row, col, comboBox);
connect(comboBox, signal, receiver, slot, connectionType);
row++;
}
/// <summary>
/// Set the header of a table to be fixed.
/// </summary>
/// <param name="header">The header to set</param>
/// <param name="mode">The resizing mode to use. Default: QHeaderView::Fixed.</param>
void Fractorium::SetFixedTableHeader(QHeaderView* header, QHeaderView::ResizeMode mode)
{
header->setVisible(true);//For some reason, the designer keeps clobbering this value, so force it here.
header->setSectionsClickable(false);
header->setSectionResizeMode(mode);
}
/// <summary>
/// Setup and show the open XML dialog.
/// This will perform lazy instantiation.
/// </summary>
/// <returns>The filename selected</returns>
QStringList Fractorium::SetupOpenXmlDialog()
{
//Lazy instantiate since it takes a long time.
if (!m_FileDialog)
{
m_FileDialog = new QFileDialog(this);
m_FileDialog->setViewMode(QFileDialog::List);
}
if (!m_FileDialog)
return QStringList("");
QStringList filenames;
m_FileDialog->disconnect(SIGNAL(filterSelected(const QString&)));
connect(m_FileDialog, &QFileDialog::filterSelected, [=](const QString &filter) { m_Settings->OpenXmlExt(filter); });
m_FileDialog->setFileMode(QFileDialog::ExistingFiles);
m_FileDialog->setAcceptMode(QFileDialog::AcceptOpen);
m_FileDialog->setNameFilter("Flam3 (*.flam3);;Flame (*.flame);;Xml (*.xml)");
m_FileDialog->setWindowTitle("Open flame");
m_FileDialog->setDirectory(m_Settings->OpenFolder());
m_FileDialog->selectNameFilter(m_Settings->OpenXmlExt());
if (m_FileDialog->exec() == QDialog::Accepted)
{
filenames = m_FileDialog->selectedFiles();
if (!filenames.empty())
m_Settings->OpenFolder(QFileInfo(filenames[0]).canonicalPath());
}
return filenames;
}
/// <summary>
/// Setup and show the save XML dialog.
/// This will perform lazy instantiation.
/// </summary>
/// <param name="defaultFilename">The default filename to populate the text box with</param>
/// <returns>The filename selected</returns>
QString Fractorium::SetupSaveXmlDialog(QString defaultFilename)
{
//Lazy instantiate since it takes a long time.
if (!m_FileDialog)
{
m_FileDialog = new QFileDialog(this);
m_FileDialog->setViewMode(QFileDialog::List);
}
if (!m_FileDialog)
return "";
QString filename;
m_FileDialog->disconnect(SIGNAL(filterSelected(const QString&)));
connect(m_FileDialog, &QFileDialog::filterSelected, [=](const QString &filter) { m_Settings->SaveXmlExt(filter); });
//This must come first because it clears various internal states which allow the file text to be properly set.
//This is most likely a bug in QFileDialog.
m_FileDialog->setAcceptMode(QFileDialog::AcceptSave);
m_FileDialog->selectFile(defaultFilename);
m_FileDialog->setNameFilter("Flam3 (*.flam3);;Flame (*.flame);;Xml (*.xml)");
m_FileDialog->setWindowTitle("Save flame as xml");
m_FileDialog->setDirectory(m_Settings->SaveFolder());
m_FileDialog->selectNameFilter(m_Settings->SaveXmlExt());
if (m_FileDialog->exec() == QDialog::Accepted)
filename = m_FileDialog->selectedFiles().value(0);
return filename;
}
/// <summary>
/// Setup and show the save image dialog.
/// This will perform lazy instantiation.
/// </summary>
/// <param name="defaultFilename">The default filename to populate the text box with</param>
/// <returns>The filename selected</returns>
QString Fractorium::SetupSaveImageDialog(QString defaultFilename)
{
//Lazy instantiate since it takes a long time.
if (!m_FileDialog)
{
m_FileDialog = new QFileDialog(this);
m_FileDialog->setViewMode(QFileDialog::List);
}
if (!m_FileDialog)
return "";
QString filename;
m_FileDialog->disconnect(SIGNAL(filterSelected(const QString&)));
connect(m_FileDialog, &QFileDialog::filterSelected, [=](const QString &filter) { m_Settings->SaveImageExt(filter); });
//This must come first because it clears various internal states which allow the file text to be properly set.
//This is most likely a bug in QFileDialog.
m_FileDialog->setAcceptMode(QFileDialog::AcceptSave);
m_FileDialog->selectFile(defaultFilename);
m_FileDialog->setFileMode(QFileDialog::AnyFile);
m_FileDialog->setOption(QFileDialog::ShowDirsOnly, false);
m_FileDialog->setOption(QFileDialog::DontUseNativeDialog, false);
m_FileDialog->setNameFilter("Jpeg (*.jpg);;Png (*.png);;Bmp (*.bmp)");
m_FileDialog->setWindowTitle("Save image");
m_FileDialog->setDirectory(m_Settings->SaveFolder());
m_FileDialog->selectNameFilter(m_Settings->SaveImageExt());
if (m_FileDialog->exec() == QDialog::Accepted)
filename = m_FileDialog->selectedFiles().value(0);
return filename;
}
/// <summary>
/// Setup and show the save folder dialog.
/// This will perform lazy instantiation.
/// </summary>
/// <returns>The folder selected</returns>
QString Fractorium::SetupSaveFolderDialog()
{
//Lazy instantiate since it takes a long time.
if (!m_FolderDialog)
{
m_FolderDialog = new QFileDialog(this);
m_FolderDialog->setViewMode(QFileDialog::List);
}
if (!m_FolderDialog)
return "";
QString filename;
//This must come first because it clears various internal states which allow the file text to be properly set.
//This is most likely a bug in QFileDialog.
m_FolderDialog->setAcceptMode(QFileDialog::AcceptSave);
m_FolderDialog->setFileMode(QFileDialog::Directory);
m_FolderDialog->setOption(QFileDialog::ShowDirsOnly, true);
m_FolderDialog->setOption(QFileDialog::DontUseNativeDialog, true);
m_FolderDialog->selectFile("");
m_FolderDialog->setWindowTitle("Save to folder");
m_FolderDialog->setDirectory(m_Settings->SaveFolder());
if (m_FolderDialog->exec() == QDialog::Accepted)
filename = m_FolderDialog->selectedFiles().value(0);
return filename;
}
/// <summary>
/// This is no longer needed and was used to compensate for a different bug
/// however the code is interesting, so keep it around for possible future use.
/// This was used to correct a rotation bug where matrix rotation comes out in the wrong direction
/// if x1, y1 (a & d) are on the left side of the line from 0,0 to
/// x2, y2 (b, e). In that case, the angle must be flipped. In order
/// to determine which side of the line it's on, create a mat2
/// and find its determinant. Values > 0 are on the left side of the line.
/// </summary>
/// <param name="affine">The affine.</param>
/// <returns></returns>
int Fractorium::FlipDet(Affine2D<float>& affine)
{
float x1 = affine.A();
float y1 = affine.D();
float x2 = affine.B();
float y2 = affine.E();
//Just make the other end of the line be the center of the circle.
glm::mat2 mat( 0 - x1, 0 - y1,//Col 0.
x2 - x1, y2 - y1);//Col 1.
return (glm::determinant(mat) > 0) ? -1 : 1;
}
//template<typename spinType, typename valType>//See note at the end of Fractorium.h
//void Fractorium::SetupSpinner(QTableWidget* table, const QObject* receiver, int& row, int col, spinType*& spinBox, int height, valType min, valType max, valType step, const char* signal, const char* slot, bool incRow, valType val, valType doubleClickZero, valType doubleClickNonZero)
//{
// spinBox = new spinType(table, height, step);
// spinBox->setRange(min, max);
// spinBox->setValue(val);
// table->setCellWidget(row, col, spinBox);
//
// if (string(signal) != "" && string(slot) != "")
// connect(spinBox, signal, receiver, slot, connectionType);
//
// if (doubleClickNonZero != -999 && doubleClickZero != -999)
// {
// spinBox->DoubleClick(true);
// spinBox->DoubleClickZero((valType)doubleClickZero);
// spinBox->DoubleClickNonZero((valType)doubleClickNonZero);
// }
//
// if (incRow)
// row++;
//}

View File

@ -0,0 +1,445 @@
#pragma once
#include "ui_Fractorium.h"
#include "GLWidget.h"
#include "EmberTreeWidgetItem.h"
#include "VariationTreeWidgetItem.h"
#include "StealthComboBox.h"
#include "TableWidget.h"
#include "FinalRenderDialog.h"
#include "OptionsDialog.h"
#include "AboutDialog.h"
/// <summary>
/// Fractorium class.
/// </summary>
/// <summary>
/// Fractorium is the main window for the interactive renderer. The main viewable area
/// is a derivation of QGLWidget named GLWidget. The design uses the concept of a controller
/// to allow for both polymorphism and templating to coexist. Most processing functionality
/// is contained within the controller, and the GUI events just call the appropriate controller
/// member functions.
/// The rendering takes place on a timer with
/// a period of 0 which means it will trigger an event whenever the event input queue is idle.
/// As it's rendering, if the user changes anything on the GUI, a re-render will trigger. Since
/// certain parameters don't require a full render, the minimum necessary processing will be ran.
/// When the user changes something on the GUI, the required processing action is added to a vector.
/// Upon the next execution of the idle timer function, the most significant action will be extracted
/// and applied to the renderer. The vector is then cleared.
/// On the left side of the window is a dock widget which contains all controls needed for
/// manipulating embers.
/// Qt takes very long to create file dialog windows, so they are kept as members and initialized
/// upon first use with lazy instantiation and then kept around for the remainder of the program.
/// Additional dialogs are for the about box, options, and final rendering out to a file.
/// While all class member variables and functions are declared in this .h file, the implementation
/// for them is far too lengthy to put in a single .cpp file. So general functionality is placed in
/// Fractorium.cpp and the other functional areas are each broken out into their own files.
/// The order of the functions in each .cpp file should roughly match the order they appear in the .h file.
/// Future todo list:
/// Add all of the plugins/variations that work with Apophysis and are open source.
/// Allow specifying variations to include/exclude from random generation.
/// Allow the option to specify a different palette file rather than the default flam3-palettes.xml.
/// Implement more rendering types.
/// Add support for animation previewing.
/// Add support for full animation editing and rendering.
/// Possibly add some features from JWildFire.
/// </summary>
class Fractorium : public QMainWindow
{
Q_OBJECT
friend GLWidget;
friend FractoriumOptionsDialog;
friend FractoriumFinalRenderDialog;
friend FractoriumAboutDialog;
friend GLEmberControllerBase;
friend GLEmberController<float>;
friend GLEmberController<double>;
friend FractoriumEmberControllerBase;
friend FractoriumEmberController<float>;
friend FractoriumEmberController<double>;
friend FinalRenderEmberControllerBase;
friend FinalRenderEmberController<float>;
friend FinalRenderEmberController<double>;
public:
Fractorium(QWidget* parent = 0);
~Fractorium();
//Geometry.
void SetCenter(float x, float y);
void SetRotation(double rot, bool stealth);
void SetScale(double scale);
void Resize();
void SetCoordinateStatus(int x, int y, float worldX, float worldY);
//Xforms.
void CurrentXform(unsigned int i);
//Xforms Affine.
bool DrawAllPre();
bool DrawAllPost();
bool LocalPivot();
public slots:
//Dock.
void OnDockTopLevelChanged(bool topLevel);
void dockLocationChanged(Qt::DockWidgetArea area);
//Menu.
void OnActionNewFlock(bool checked);//File.
void OnActionNewEmptyFlameInCurrentFile(bool checked);
void OnActionNewRandomFlameInCurrentFile(bool checked);
void OnActionCopyFlameInCurrentFile(bool checked);
void OnActionOpen(bool checked);
void OnActionSaveCurrentAsXml(bool checked);
void OnActionSaveEntireFileAsXml(bool checked);
void OnActionSaveCurrentScreen(bool checked);
void OnActionSaveCurrentToOpenedFile(bool checked);
void OnActionExit(bool checked);
void OnActionUndo(bool checked);//Edit.
void OnActionRedo(bool checked);
void OnActionCopyXml(bool checked);
void OnActionCopyAllXml(bool checked);
void OnActionPasteXmlAppend(bool checked);
void OnActionPasteXmlOver(bool checked);
void OnActionAddReflectiveSymmetry(bool checked);//Tools.
void OnActionAddRotationalSymmetry(bool checked);
void OnActionAddBothSymmetry(bool checked);
void OnActionClearFlame(bool checked);
void OnActionRenderPreviews(bool checked);
void OnActionStopRenderingPreviews(bool checked);
void OnActionFinalRender(bool checked);
void OnFinalRenderClose(int result);
void OnActionOptions(bool checked);
void OnActionAbout(bool checked);//Help.
//Toolbar.
void OnSaveCurrentAsXmlButtonClicked(bool checked);
void OnSaveEntireFileAsXmlButtonClicked(bool checked);
void OnSaveCurrentToOpenedFileButtonClicked(bool checked);
//Params.
void OnBrightnessChanged(double d);//Color.
void OnGammaChanged(double d);
void OnGammaThresholdChanged(double d);
void OnVibrancyChanged(double d);
void OnHighlightPowerChanged(double d);
void OnBackgroundColorButtonClicked(bool checked);
void OnColorSelected(const QColor& color);
void OnPaletteModeComboCurrentIndexChanged(int index);
void OnWidthChanged(int d);//Geometry.
void OnHeightChanged(int d);
void OnCenterXChanged(double d);
void OnCenterYChanged(double d);
void OnScaleChanged(double d);
void OnZoomChanged(double d);
void OnRotateChanged(double d);
void OnZPosChanged(double d);
void OnPerspectiveChanged(double d);
void OnPitchChanged(double d);
void OnYawChanged(double d);
void OnDepthBlurChanged(double d);
void OnSpatialFilterWidthChanged(double d);//Filter.
void OnSpatialFilterTypeComboCurrentIndexChanged(const QString& text);
void OnTemporalFilterWidthChanged(double d);
void OnTemporalFilterTypeComboCurrentIndexChanged(const QString& text);
void OnDEFilterMinRadiusWidthChanged(double d);
void OnDEFilterMaxRadiusWidthChanged(double d);
void OnDEFilterCurveWidthChanged(double d);
void OnPassesChanged(int d);//Iteration.
void OnTemporalSamplesChanged(int d);
void OnQualityChanged(double d);
void OnSupersampleChanged(int d);
void OnAffineInterpTypeComboCurrentIndexChanged(int index);
void OnInterpTypeComboCurrentIndexChanged(int index);
//Xforms.
void OnCurrentXformComboChanged(int index);
void OnAddXformButtonClicked(bool checked);
void OnDuplicateXformButtonClicked(bool checked);
void OnClearXformButtonClicked(bool checked);
void OnDeleteXformButtonClicked(bool checked);
void OnAddFinalXformButtonClicked(bool checked);
void OnXformWeightChanged(double d);
void OnEqualWeightButtonClicked(bool checked);
void OnXformNameChanged(int row, int col);
//Xforms Affine.
void OnX1Changed(double d);
void OnX2Changed(double d);
void OnY1Changed(double d);
void OnY2Changed(double d);
void OnO1Changed(double d);
void OnO2Changed(double d);
void OnFlipHorizontalButtonClicked(bool checked);
void OnFlipVerticalButtonClicked(bool checked);
void OnRotate90CButtonClicked(bool checked);
void OnRotate90CcButtonClicked(bool checked);
void OnRotateCButtonClicked(bool checked);
void OnRotateCcButtonClicked(bool checked);
void OnMoveUpButtonClicked(bool checked);
void OnMoveDownButtonClicked(bool checked);
void OnMoveLeftButtonClicked(bool checked);
void OnMoveRightButtonClicked(bool checked);
void OnScaleDownButtonClicked(bool checked);
void OnScaleUpButtonClicked(bool checked);
void OnResetAffineButtonClicked(bool checked);
void OnAffineGroupBoxToggled(bool on);
void OnAffineDrawAllCurrentRadioButtonToggled(bool checked);
//Xforms Color.
void OnXformColorIndexChanged(double d);
void OnXformColorIndexChanged(double d, bool updateRender);
void OnXformScrollColorIndexChanged(int d);
void OnXformColorSpeedChanged(double d);
void OnXformOpacityChanged(double d);
void OnXformDirectColorChanged(double d);
void OnSoloXformCheckBoxStateChanged(int state);
void OnXformRefPaletteResized(int logicalIndex, int oldSize, int newSize);
//Xforms Variations.
void OnVariationSpinBoxValueChanged(double d);
void OnTreeHeaderSectionClicked(int);
void OnVariationsFilterLineEditTextChanged(const QString& text);
void OnVariationsFilterClearButtonClicked(bool checked);
//Xforms Xaos.
void OnXaosChanged(double d);
void OnXaosFromToToggled(bool checked);
void OnClearXaosButtonClicked(bool checked);
//Palette.
void OnPaletteAdjust(int d);
void OnPaletteCellClicked(int row, int col);
void OnPaletteCellDoubleClicked(int row, int col);
//Library.
void OnEmberTreeItemChanged(QTreeWidgetItem* item, int col);
void OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col);
//Rendering/progress.
void StartRenderTimer();
void IdleTimer();
bool ControllersOk();
//Can't have a template function be a slot.
void SetLibraryTreeItemData(EmberTreeWidgetItemBase* item, vector<unsigned char>& v, unsigned int width, unsigned int height);
public:
//template<typename spinType, typename valType>//See below.
//static void SetupSpinner(QTableWidget* table, const QObject* receiver, int& row, int col, spinType*& spinBox, int height, valType min, valType max, valType step, const char* signal, const char* slot, bool incRow = true, valType val = 0, valType doubleClickZero = -999, valType doubleClickNonZero = -999);
static void SetupAffineSpinner(QTableWidget* table, const QObject* receiver, int row, int col, DoubleSpinBox*& spinBox, int height, double min, double max, double step, double prec, const char* signal, const char* slot);
static void SetupCombo(QTableWidget* table, const QObject* receiver, int& row, int col, StealthComboBox*& comboBox, vector<string>& vals, const char* signal, const char* slot, Qt::ConnectionType connectionType = Qt::QueuedConnection);
static void SetFixedTableHeader(QHeaderView* header, QHeaderView::ResizeMode mode = QHeaderView::Fixed);
static int FlipDet(Affine2D<float>& affine);
protected:
virtual void resizeEvent(QResizeEvent* e);
virtual void closeEvent(QCloseEvent* e);
virtual void dragEnterEvent(QDragEnterEvent* e);
virtual void dragMoveEvent(QDragMoveEvent* e);
virtual void dropEvent(QDropEvent* e);
private:
void InitMenusUI();
void InitToolbarUI();
void InitParamsUI();
void InitXformsUI();
void InitXformsColorUI();
void InitXformsAffineUI();
void InitXformsVariationsUI();
void InitXformsXaosUI();
void InitPaletteUI();
void InitLibraryUI();
//Embers.
bool HaveFinal();
//Params.
//Xforms.
void FillXforms();
//Xforms Color.
//Xforms Affine.
//Xforms Variations.
//Xforms Xaos.
void FillXaosTable();
//Palette.
void ResetPaletteControls();
//Library.
//Info.
void UpdateHistogramBounds();
void ErrorReportToQTextEdit(vector<string>& errors, QTextEdit* textEdit, bool clear = true);
//Rendering/progress.
bool CreateRendererFromOptions();
bool CreateControllerFromOptions();
//Dialogs.
QStringList SetupOpenXmlDialog();
QString SetupSaveXmlDialog(QString defaultFilename);
QString SetupSaveImageDialog(QString defaultFilename);
QString SetupSaveFolderDialog();
QColorDialog* m_ColorDialog;
FractoriumFinalRenderDialog* m_FinalRenderDialog;
FractoriumOptionsDialog* m_OptionsDialog;
FractoriumAboutDialog* m_AboutDialog;
//Params.
DoubleSpinBox* m_BrightnessSpin;//Color.
DoubleSpinBox* m_GammaSpin;
DoubleSpinBox* m_GammaThresholdSpin;
DoubleSpinBox* m_VibrancySpin;
DoubleSpinBox* m_HighlightSpin;
QPushButton* m_BackgroundColorButton;
StealthComboBox* m_PaletteModeCombo;
SpinBox* m_WidthSpin;//Geometry.
SpinBox* m_HeightSpin;
DoubleSpinBox* m_CenterXSpin;
DoubleSpinBox* m_CenterYSpin;
DoubleSpinBox* m_ScaleSpin;
DoubleSpinBox* m_ZoomSpin;
DoubleSpinBox* m_RotateSpin;
DoubleSpinBox* m_ZPosSpin;
DoubleSpinBox* m_PerspectiveSpin;
DoubleSpinBox* m_PitchSpin;
DoubleSpinBox* m_YawSpin;
DoubleSpinBox* m_DepthBlurSpin;
DoubleSpinBox* m_SpatialFilterWidthSpin;//Filter.
StealthComboBox* m_SpatialFilterTypeCombo;
DoubleSpinBox* m_TemporalFilterWidthSpin;
StealthComboBox* m_TemporalFilterTypeCombo;
DoubleSpinBox* m_DEFilterMinRadiusSpin;
DoubleSpinBox* m_DEFilterMaxRadiusSpin;
DoubleSpinBox* m_DECurveSpin;
SpinBox* m_PassesSpin;//Iteration.
SpinBox* m_TemporalSamplesSpin;
DoubleSpinBox* m_QualitySpin;
SpinBox* m_SupersampleSpin;
StealthComboBox* m_AffineInterpTypeCombo;
StealthComboBox* m_InterpTypeCombo;
//Xforms.
DoubleSpinBox* m_XformWeightSpin;
SpinnerButtonWidget* m_XformWeightSpinnerButtonWidget;
//Xforms Color.
QTableWidgetItem* m_XformColorValueItem;
QTableWidgetItem* m_PaletteRefItem;
DoubleSpinBox* m_XformColorIndexSpin;
DoubleSpinBox* m_XformColorSpeedSpin;
DoubleSpinBox* m_XformOpacitySpin;
DoubleSpinBox* m_XformDirectColorSpin;
//Xforms Affine.
DoubleSpinBox* m_PreX1Spin;//Pre.
DoubleSpinBox* m_PreX2Spin;
DoubleSpinBox* m_PreY1Spin;
DoubleSpinBox* m_PreY2Spin;
DoubleSpinBox* m_PreO1Spin;
DoubleSpinBox* m_PreO2Spin;
DoubleSpinBox* m_PostX1Spin;//Post.
DoubleSpinBox* m_PostX2Spin;
DoubleSpinBox* m_PostY1Spin;
DoubleSpinBox* m_PostY2Spin;
DoubleSpinBox* m_PostO1Spin;
DoubleSpinBox* m_PostO2Spin;
//Palette.
SpinBox* m_PaletteHueSpin;
SpinBox* m_PaletteSaturationSpin;
SpinBox* m_PaletteBrightnessSpin;
SpinBox* m_PaletteContrastSpin;
SpinBox* m_PaletteBlurSpin;
SpinBox* m_PaletteFrequencySpin;
//Files.
QFileDialog* m_FileDialog;
QFileDialog* m_FolderDialog;
QString m_LastSaveAll;
QString m_LastSaveCurrent;
//QMenu* m_FileTreeMenu;
QProgressBar* m_ProgressBar;
QLabel* m_RenderStatusLabel;
QLabel* m_CoordinateStatusLabel;
FractoriumSettings* m_Settings;
char m_ULString[32];
char m_URString[32];
char m_LRString[32];
char m_LLString[32];
char m_WString[16];
char m_HString[16];
char m_DEString[16];
char m_CoordinateString[128];
int m_FontSize;
int m_VarSortMode;
int m_PreviousPaletteRow;
OpenCLWrapper m_Wrapper;
auto_ptr<FractoriumEmberControllerBase> m_Controller;
Ui::FractoriumClass ui;
};
/// <summary>
/// Setup a spinner to be placed in a table cell.
/// Due to a serious compiler bug in MSVC, this must be declared as an outside function instead of a static member of Fractorium.
/// The reason is that the default arguments of type valType will not be interpreted correctly by the compiler.
/// If the bug is ever fixed, put it back as a static member function.
/// </summary>
/// <param name="table">The table the spinner belongs to</param>
/// <param name="receiver">The receiver object</param>
/// <param name="row">The row in the table where this spinner resides</param>
/// <param name="col">The col in the table where this spinner resides</param>
/// <param name="spinBox">Double pointer to spin box which will hold the spinner upon exit</param>
/// <param name="height">The height of the spinner</param>
/// <param name="min">The minimum value of the spinner</param>
/// <param name="max">The maximum value of the spinner</param>
/// <param name="step">The step of the spinner</param>
/// <param name="signal">The signal the spinner emits</param>
/// <param name="slot">The slot to receive the signal</param>
/// <param name="incRow">Whether to increment the row value</param>
/// <param name="val">The default value for the spinner</param>
/// <param name="doubleClickZero">When the spinner has a value of zero and is double clicked, assign this value</param>
/// <param name="doubleClickNonZero">When the spinner has a value of non-zero and is double clicked, assign this value</param>
template<typename spinType, typename valType>
static void SetupSpinner(QTableWidget* table, const QObject* receiver, int& row, int col, spinType*& spinBox, int height, valType min, valType max, valType step, const char* signal, const char* slot, bool incRow = true, valType val = 0, valType doubleClickZero = -999, valType doubleClickNonZero = -999)
{
spinBox = new spinType(table, height, step);
spinBox->setRange(min, max);
spinBox->setValue(val);
if (col >= 0)
table->setCellWidget(row, col, spinBox);
if (string(signal) != "" && string(slot) != "")
receiver->connect(spinBox, signal, receiver, slot, Qt::QueuedConnection);
if (doubleClickNonZero != -999 && doubleClickZero != -999)
{
spinBox->DoubleClick(true);
spinBox->DoubleClickZero((valType)doubleClickZero);
spinBox->DoubleClickNonZero((valType)doubleClickNonZero);
}
if (incRow)
row++;
}
//template void Fractorium::SetupSpinner<SpinBox, int> (QTableWidget* table, const QObject* receiver, int& row, int col, SpinBox*& spinBox, int height, int min, int max, int step, const char* signal, const char* slot, bool incRow, int val, int doubleClickZero, int doubleClickNonZero);
//template void Fractorium::SetupSpinner<DoubleSpinBox, double>(QTableWidget* table, const QObject* receiver, int& row, int col, DoubleSpinBox*& spinBox, int height, double min, double max, double step, const char* signal, const char* slot, bool incRow, double val, double doubleClickZero, double doubleClickNonZero);

View File

@ -0,0 +1,44 @@
<RCC>
<qresource prefix="/Fractorium">
<file>Icons/arrow-undo.png</file>
<file>Icons/arrow-redo.png</file>
<file>Icons/stop.png</file>
<file>Icons/application_side_boxes.png</file>
<file>Icons/page_copy.png</file>
<file>Icons/page_paste.png</file>
<file>Icons/window-close.png</file>
<file>Icons/068123-3d-transparent-glass-icon-alphanumeric-question-mark3.png</file>
<file>Icons/layer--plus.png</file>
<file>Icons/layers.png</file>
<file>Icons/layers-stack.png</file>
<file>Icons/monitor.png</file>
<file>Icons/016938-3d-transparent-glass-icon-symbols-shapes-shape-square-clear-16.png</file>
<file>Icons/document-hf-insert.png</file>
<file>Icons/010425-3d-transparent-glass-icon-animals-spiderweb2.png</file>
<file>Icons/database-medium.png</file>
<file>Icons/databases.png</file>
<file>Icons/drive-harddisk-5.png</file>
<file>Icons/folder-visiting-4.png</file>
<file>Icons/display-brightness-off.png</file>
<file>Icons/cog.png</file>
<file>Icons/proxy.png</file>
<file>Icons/shape_flip_horizontal.png</file>
<file>Icons/shape_flip_vertical.png</file>
<file>Icons/arrow_down.png</file>
<file>Icons/arrow_in.png</file>
<file>Icons/arrow_left.png</file>
<file>Icons/arrow_out.png</file>
<file>Icons/arrow_right.png</file>
<file>Icons/arrow_rotate_anticlockwise.png</file>
<file>Icons/arrow_rotate_clockwise.png</file>
<file>Icons/arrow_turn_left.png</file>
<file>Icons/arrow_turn_right.png</file>
<file>Icons/arrow_up.png</file>
<file>Icons/configure.png</file>
<file>Icons/infomation.png</file>
<file>Icons/del.png</file>
<file>Icons/add.png</file>
<file>Icons/eraser.png</file>
<file>Icons/editraise.png</file>
</qresource>
</RCC>

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,244 @@
#include "FractoriumPch.h"
#include "FractoriumEmberController.h"
#include "Fractorium.h"
#include "GLEmberController.h"
/// <summary>
/// Constructor which initializes the non-templated members contained in this class.
/// The renderer, other templated members and GUI setup will be done in the templated derived controller class.
/// </summary>
/// <param name="fractorium">Pointer to the main window.</param>
FractoriumEmberControllerBase::FractoriumEmberControllerBase(Fractorium* fractorium)
{
Timing t;
m_Rendering = false;
m_Shared = true;
m_Platform = 0;
m_Device = 0;
m_FailedRenders = 0;
m_UndoIndex = 0;
m_RenderType = CPU_RENDERER;
m_OutputTexID = 0;
m_SubBatchCount = 1;//Will be ovewritten by the options on first render.
m_Fractorium = fractorium;
m_RenderTimer = NULL;
m_RenderRestartTimer = NULL;
m_Rand = QTIsaac<ISAAC_SIZE, ISAAC_INT>(ISAAC_INT(t.Tic()), ISAAC_INT(t.Tic() * 2), ISAAC_INT(t.Tic() * 3));//Ensure a different rand seed on each instance.
m_RenderTimer = new QTimer(m_Fractorium);
m_RenderTimer->setInterval(0);
m_Fractorium->connect(m_RenderTimer, SIGNAL(timeout()), SLOT(IdleTimer()));
m_RenderRestartTimer = new QTimer(m_Fractorium);
m_Fractorium->connect(m_RenderRestartTimer, SIGNAL(timeout()), SLOT(StartRenderTimer()));
}
/// <summary>
/// Destructor which stops rendering and deletes the timers.
/// All other memory is cleared automatically through the use of STL.
/// </summary>
FractoriumEmberControllerBase::~FractoriumEmberControllerBase()
{
StopRenderTimer(true);
if (m_RenderTimer)
{
m_RenderTimer->stop();
delete m_RenderTimer;
m_RenderTimer = NULL;
}
if (m_RenderRestartTimer)
{
m_RenderRestartTimer->stop();
delete m_RenderRestartTimer;
m_RenderRestartTimer = NULL;
}
}
/// <summary>
/// Constructor which passes the main window parameter to the base, initializes the templated members contained in this class.
/// Then sets up the parts of the GUI that require templated Widgets, such as the variations tree and the palette table.
/// Note the renderer is not setup here automatically. Instead, it must be manually created by the caller later.
/// </summary>
/// <param name="fractorium">Pointer to the main window.</param>
template <typename T>
FractoriumEmberController<T>::FractoriumEmberController(Fractorium* fractorium)
: FractoriumEmberControllerBase(fractorium)
{
m_PreviewRun = false;
m_PreviewRunning = false;
m_SheepTools = auto_ptr<SheepTools<T, T>>(new SheepTools<T, T>("flam3-palettes.xml", new EmberNs::Renderer<T, T>()));
m_GLController = auto_ptr<GLEmberController<T>>(new GLEmberController<T>(fractorium, fractorium->ui.GLDisplay, this));
m_PreviewRenderer = auto_ptr<EmberNs::Renderer<T, T>>(new EmberNs::Renderer<T, T>());
SetupVariationTree();
InitPaletteTable("flam3-palettes.xml");
BackgroundChanged(QColor(0, 0, 0));//Default to black.
ClearUndo();
m_PreviewRenderer->Callback(NULL);
m_PreviewRenderer->NumChannels(4);
m_PreviewRenderer->ReclaimOnResize(true);
m_PreviewRenderer->SetEmber(m_Ember);//Give it an initial ember, will be updated many times later.
//m_PreviewRenderer->ThreadCount(1);//For debugging.
m_PreviewRenderFunc = [&](unsigned int start, unsigned int end)
{
while(m_PreviewRun || m_PreviewRunning)
{
}
m_PreviewRun = true;
m_PreviewRunning = true;
m_PreviewRenderer->ThreadCount(max(1, Timing::ProcessorCount() - 1));//Leave one processor free so the GUI can breathe.
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
if (QTreeWidgetItem* top = tree->topLevelItem(0))
{
for (size_t i = start; m_PreviewRun && i < end && i < m_EmberFile.m_Embers.size(); i++)
{
Ember<T> ember = m_EmberFile.m_Embers[i];
ember.SetSizeAndAdjustScale(PREVIEW_SIZE, PREVIEW_SIZE, false, SCALE_WIDTH);
ember.m_TemporalSamples = 1;
ember.m_Quality = 25;
ember.m_Supersample = 1;
m_PreviewRenderer->SetEmber(ember);
if (m_PreviewRenderer->Run(m_PreviewFinalImage) == RENDER_OK)
{
if (EmberTreeWidgetItem<T>* treeItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i)))
{
//It is critical that Qt::BlockingQueuedConnection is passed because this is running on a different thread than the UI.
//This ensures the events are processed in order as each preview is updated, and that control does not return here
//until the update is complete.
QMetaObject::invokeMethod(m_Fractorium, "SetLibraryTreeItemData", Qt::BlockingQueuedConnection,
Q_ARG(EmberTreeWidgetItemBase*, (EmberTreeWidgetItemBase*)treeItem),
Q_ARG(vector<unsigned char>&, m_PreviewFinalImage),
Q_ARG(unsigned int, PREVIEW_SIZE),
Q_ARG(unsigned int, PREVIEW_SIZE));
//treeItem->SetImage(m_PreviewFinalImage, PREVIEW_SIZE, PREVIEW_SIZE);
}
}
}
}
m_PreviewRun = false;
m_PreviewRunning = false;
};
}
/// <summary>
/// Empty destructor that does nothing.
/// </summary>
template <typename T>
FractoriumEmberController<T>::~FractoriumEmberController() { }
/// <summary>
/// Setters for embers, ember files and palettes which convert between float and double types.
/// These are used to preserve the current ember/file when switching between renderers.
/// Note that some precision will be lost when going from double to float.
/// </summary>
template <typename T> void FractoriumEmberController<T>::SetEmber(const Ember<float>& ember, bool verbatim) { SetEmberPrivate<float>(ember, verbatim); }
template <typename T> void FractoriumEmberController<T>::CopyEmber(Ember<float>& ember) { ember = m_Ember; }
template <typename T> void FractoriumEmberController<T>::SetEmberFile(const EmberFile<float>& emberFile) { m_EmberFile = emberFile; }
template <typename T> void FractoriumEmberController<T>::CopyEmberFile(EmberFile<float>& emberFile) { emberFile = m_EmberFile; }
template <typename T> void FractoriumEmberController<T>::SetTempPalette(const Palette<float>& palette) { m_TempPalette = palette; }
template <typename T> void FractoriumEmberController<T>::CopyTempPalette(Palette<float>& palette) { palette = m_TempPalette; }
#ifdef DO_DOUBLE
template <typename T> void FractoriumEmberController<T>::SetEmber(const Ember<double>& ember, bool verbatim) { SetEmberPrivate<double>(ember, verbatim); }
template <typename T> void FractoriumEmberController<T>::CopyEmber(Ember<double>& ember) { ember = m_Ember; }
template <typename T> void FractoriumEmberController<T>::SetEmberFile(const EmberFile<double>& emberFile) { m_EmberFile = emberFile; }
template <typename T> void FractoriumEmberController<T>::CopyEmberFile(EmberFile<double>& emberFile) { emberFile = m_EmberFile; }
template <typename T> void FractoriumEmberController<T>::SetTempPalette(const Palette<double>& palette) { m_TempPalette = palette; }
template <typename T> void FractoriumEmberController<T>::CopyTempPalette(Palette<double>& palette) { palette = m_TempPalette; }
#endif
template <typename T> Ember<T>* FractoriumEmberController<T>::CurrentEmber() { return &m_Ember; }
/// <summary>
/// Set the ember at the specified index from the currently opened file as the current Ember.
/// Clears the undo state.
/// Resets the rendering process.
/// </summary>
/// <param name="index">The index in the file from which to retrieve the ember</param>
template <typename T>
void FractoriumEmberController<T>::SetEmber(size_t index)
{
if (index < m_EmberFile.m_Embers.size())
{
if (QTreeWidgetItem* top = m_Fractorium->ui.LibraryTree->topLevelItem(0))
{
for (unsigned int i = 0; i < top->childCount(); i++)
{
if (EmberTreeWidgetItem<T>* emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i)))
emberItem->setSelected(i == index);
}
}
ClearUndo();
SetEmber(m_EmberFile.m_Embers[index]);
}
}
/// <summary>
/// Wrapper to call a function, then optionally add the requested action to the rendering queue.
/// </summary>
/// <param name="func">The function to call</param>
/// <param name="updateRender">True to update renderer, else false. Default: false.</param>
/// <param name="action">The action to add to the rendering queue. Default: FULL_RENDER.</param>
template <typename T>
void FractoriumEmberController<T>::Update(std::function<void (void)> func, bool updateRender, eProcessAction action)
{
func();
if (updateRender)
UpdateRender(action);
}
/// <summary>
/// Wrapper to call a function on the current xform, then optionally add the requested action to the rendering queue.
/// </summary>
/// <param name="func">The function to call</param>
/// <param name="updateRender">True to update renderer, else false. Default: false.</param>
/// <param name="action">The action to add to the rendering queue. Default: FULL_RENDER.</param>
template <typename T>
void FractoriumEmberController<T>::UpdateCurrentXform(std::function<void (Xform<T>*)> func, bool updateRender, eProcessAction action)
{
if (Xform<T>* xform = CurrentXform())
{
func(xform);
if (updateRender)
UpdateRender(action);
}
}
/// <summary>
/// Set the current ember, but use GUI values for the fields which make sense to
/// keep the same between ember selection changes.
/// Note the extra template parameter U allows for assigning ember of different types.
/// Resets the rendering process.
/// </summary>
/// <param name="ember">The ember to set as the current</param>
template <typename T>
template <typename U>
void FractoriumEmberController<T>::SetEmberPrivate(const Ember<U>& ember, bool verbatim)
{
if (ember.m_Name != m_Ember.m_Name)
m_LastSaveCurrent = "";
m_Ember = ember;
if (!verbatim)
{
m_Ember.SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH);
m_Ember.m_TemporalSamples = 1;//Change once animation is supported.
m_Ember.m_Quality = m_Fractorium->m_QualitySpin->value();
m_Ember.m_Supersample = m_Fractorium->m_SupersampleSpin->value();
}
m_Fractorium->FillXforms();//Must do this first because the palette setup in FillParamTablesAndPalette() uses the xforms combo.
FillParamTablesAndPalette();
}

View File

@ -0,0 +1,446 @@
#pragma once
#include "EmberFile.h"
#include "DoubleSpinBox.h"
#include "GLEmberController.h"
/// <summary>
/// FractoriumEmberControllerBase and FractoriumEmberController<T> classes.
/// </summary>
/// <summary>
/// An enum representing the type of edit being done.
/// </summary>
enum eEditUndoState : unsigned int { REGULAR_EDIT = 0, UNDO_REDO = 1, EDIT_UNDO = 2 };
/// <summary>
/// FractoriumEmberController and Fractorium need each other, but each can't include the other.
/// So Fractorium includes this file, and Fractorium is declared as a forward declaration here.
/// </summary>
class Fractorium;
#define PREVIEW_SIZE 256
#define UNDO_SIZE 128
/// <summary>
/// FractoriumEmberControllerBase serves as a non-templated base class with virtual
/// functions which will be overridden in a derived class that takes a template parameter.
/// The controller serves as a way to access both the Fractorium GUI and the underlying ember
/// objects through an interface that doesn't require template argument, but does allow
/// templated objects to be used underneath.
/// Note that there are a few functions which access a templated object, so for those both
/// versions for float and double must be provided, then overridden in the templated derived
/// class. It's definitely a design flaw, but C++ doesn't offer any alternative since
/// templated virtual functions are not supported.
/// The functions not implemented in this file can be found in the GUI files which use them.
/// </summary>
class FractoriumEmberControllerBase : public RenderCallback
{
public:
FractoriumEmberControllerBase(Fractorium* fractorium);
virtual ~FractoriumEmberControllerBase();
//Embers.
virtual void SetEmber(const Ember<float>& ember, bool verbatim = false) { }
virtual void CopyEmber(Ember<float>& ember) { }
virtual void SetEmberFile(const EmberFile<float>& emberFile) { }
virtual void CopyEmberFile(EmberFile<float>& emberFile) { }
virtual void SetTempPalette(const Palette<float>& palette) { }
virtual void CopyTempPalette(Palette<float>& palette) { }
#ifdef DO_DOUBLE
virtual void SetEmber(const Ember<double>& ember, bool verbatim = false) { }
virtual void CopyEmber(Ember<double>& ember) { }
virtual void SetEmberFile(const EmberFile<double>& emberFile) { }
virtual void CopyEmberFile(EmberFile<double>& emberFile) { }
virtual void SetTempPalette(const Palette<double>& palette) { }
virtual void CopyTempPalette(Palette<double>& palette) { }
#endif
virtual void SetEmber(size_t index) { }
virtual void Clear() { }
virtual void AddXform() { }
virtual void DuplicateXform() { }
virtual void ClearCurrentXform() { }
virtual void DeleteCurrentXform() { }
virtual void AddFinalXform() { }
virtual bool UseFinalXform() { return false; }
virtual size_t XformCount() { return 0; }
virtual size_t TotalXformCount() { return 0; }
virtual string Name() { return ""; }
virtual void Name(string s) { }
virtual unsigned int FinalRasW() { return 0; }
virtual void FinalRasW(unsigned int w) { }
virtual unsigned int FinalRasH() { return 0; }
virtual void FinalRasH(unsigned int h) { }
virtual void AddSymmetry(int sym, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) { }
virtual void CalcNormalizedWeights() { }
//Menu.
virtual void NewFlock(unsigned int count) { }//File.
virtual void NewEmptyFlameInCurrentFile() { }
virtual void NewRandomFlameInCurrentFile() { }
virtual void CopyFlameInCurrentFile() { }
virtual void OpenAndPrepFiles(QStringList filenames, bool append) { }
virtual void SaveCurrentAsXml() { }
virtual void SaveEntireFileAsXml() { }
virtual void SaveCurrentToOpenedFile() { }
virtual void Undo() { }//Edit.
virtual void Redo() { }
virtual void CopyXml() { }
virtual void CopyAllXml() { }
virtual void PasteXmlAppend() { }
virtual void PasteXmlOver() { }
virtual void AddReflectiveSymmetry() { }//Tools.
virtual void AddRotationalSymmetry() { }
virtual void AddBothSymmetry() { }
virtual void ClearFlame() { }
//Toolbar.
//Params.
virtual void SetCenter(double x, double y) { }
virtual void FillParamTablesAndPalette() { }
virtual void BrightnessChanged(double d) { }
virtual void GammaChanged(double d) { }
virtual void GammaThresholdChanged(double d) { }
virtual void VibrancyChanged(double d) { }
virtual void HighlightPowerChanged(double d) { }
virtual void PaletteModeChanged(unsigned int i) { }
virtual void CenterXChanged(double d) { }
virtual void CenterYChanged(double d) { }
virtual void ScaleChanged(double d) { }
virtual void ZoomChanged(double d) { }
virtual void RotateChanged(double d) { }
virtual void ZPosChanged(double d) { }
virtual void PerspectiveChanged(double d) { }
virtual void PitchChanged(double d) { }
virtual void YawChanged(double d) { }
virtual void DepthBlurChanged(double d) { }
virtual void SpatialFilterWidthChanged(double d) { }
virtual void SpatialFilterTypeChanged(const QString& text) { }
virtual void TemporalFilterWidthChanged(double d) { }
virtual void TemporalFilterTypeChanged(const QString& text) { }
virtual void DEFilterMinRadiusWidthChanged(double d) { }
virtual void DEFilterMaxRadiusWidthChanged(double d) { }
virtual void DEFilterCurveWidthChanged(double d) { }
virtual void PassesChanged(int i) { }
virtual void TemporalSamplesChanged(int d) { }
virtual void QualityChanged(double d) { }
virtual void SupersampleChanged(int d) { }
virtual void AffineInterpTypeChanged(int i) { }
virtual void InterpTypeChanged(int i) { }
virtual void BackgroundChanged(const QColor& color) { }
//Xforms.
virtual void CurrentXformComboChanged(int index) { }
virtual void XformWeightChanged(double d) { }
virtual void EqualizeWeights() { }
virtual void XformNameChanged(int row, int col) { }
//Xforms Affine.
virtual void AffineSetHelper(double d, int index, bool pre) { }
virtual void FlipCurrentXform(bool horizontal, bool vertical, bool pre) { }
virtual void RotateCurrentXformByAngle(double angle, bool pre) { }
virtual void MoveCurrentXform(double x, double y, bool pre) { }
virtual void ScaleCurrentXform(double scale, bool pre) { }
virtual void ResetCurrentXformAffine(bool pre) { }
//Xforms Color.
virtual void XformColorIndexChanged(double d, bool updateRender) { }
virtual void XformScrollColorIndexChanged(int d) { }
virtual void XformColorSpeedChanged(double d) { }
virtual void XformOpacityChanged(double d) { }
virtual void XformDirectColorChanged(double d) { }
void SetPaletteRefTable(QPixmap* pixmap);
//Xforms Variations.
virtual void SetupVariationTree() { }
virtual void ClearVariationsTree() { }
virtual void VariationSpinBoxValueChanged(double d) { }
//Xforms Xaos.
virtual void FillXaosWithCurrentXform() { }
virtual QString MakeXaosNameString(unsigned int i) { return ""; }
virtual void XaosChanged(DoubleSpinBox* sender) { }
virtual void ClearXaos() { }
//Palette.
virtual bool InitPaletteTable(string s) { return false; }
virtual void ApplyPaletteToEmber() { }
virtual void PaletteAdjust() { }
virtual QRgb GetQRgbFromPaletteIndex(unsigned int i) { return QRgb(); }
virtual void PaletteCellClicked(int row, int col) { }
//Library.
virtual void SyncNames() { }
virtual void FillLibraryTree(int selectIndex = -1) { }
virtual void UpdateLibraryTree() { }
virtual void EmberTreeItemChanged(QTreeWidgetItem* item, int col) { }
virtual void EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) { }
virtual void RenderPreviews(unsigned int start = UINT_MAX, unsigned int end = UINT_MAX) { }
virtual void StopPreviewRender() { }
//Info.
//Rendering/progress.
virtual bool Render() { return false; }
virtual bool CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared = true) { return false; }
virtual unsigned int SizeOfT() { return 0; }
virtual void ClearUndo() { }
virtual GLEmberControllerBase* GLController() { return NULL; }
bool RenderTimerRunning();
void StartRenderTimer();
void DelayedStartRenderTimer();
void StopRenderTimer(bool wait);
void Shutdown();
void UpdateRender(eProcessAction action = FULL_RENDER);
void DeleteRenderer();
void SaveCurrentRender(QString filename);
RendererBase* Renderer() { return m_Renderer.get(); }
vector<unsigned char>* FinalImage() { return &m_FinalImage; }
vector<unsigned char>* PreviewFinalImage() { return &m_PreviewFinalImage; }
protected:
//Rendering/progress.
void AddProcessAction(eProcessAction action);
eProcessAction CondenseAndClearProcessActions();
eProcessState ProcessState() { return m_Renderer.get() ? m_Renderer->ProcessState() : NONE; }
//Non-templated members.
bool m_Rendering;
bool m_Shared;
bool m_LastEditWasUndoRedo;
unsigned int m_Platform;
unsigned int m_Device;
unsigned int m_SubBatchCount;
unsigned int m_FailedRenders;
unsigned int m_UndoIndex;
eRendererType m_RenderType;
eEditUndoState m_EditState;
GLuint m_OutputTexID;
Timing m_RenderElapsedTimer;
QImage m_FinalPaletteImage;
QString m_LastSaveAll;
QString m_LastSaveCurrent;
CriticalSection m_Cs;
vector<unsigned char> m_FinalImage;
vector<unsigned char> m_PreviewFinalImage;
vector<eProcessAction> m_ProcessActions;
auto_ptr<EmberNs::RendererBase> m_Renderer;
QTIsaac<ISAAC_SIZE, ISAAC_INT> m_Rand;
Fractorium* m_Fractorium;
QTimer* m_RenderTimer;
QTimer* m_RenderRestartTimer;
};
/// <summary>
/// Templated derived class which implements all interaction functionality between the embers
/// of a specific template type and the GUI.
/// Switching between template arguments requires complete re-creation of the controller and the
/// underlying renderer. Switching between CPU and OpenCL only requires re-creation of the renderer.
/// </summary>
template<typename T>
class FractoriumEmberController : public FractoriumEmberControllerBase
{
public:
FractoriumEmberController(Fractorium* fractorium);
virtual ~FractoriumEmberController();
//Embers.
virtual void SetEmber(const Ember<float>& ember, bool verbatim = false);
virtual void CopyEmber(Ember<float>& ember);
virtual void SetEmberFile(const EmberFile<float>& emberFile);
virtual void CopyEmberFile(EmberFile<float>& emberFile);
virtual void SetTempPalette(const Palette<float>& palette);
virtual void CopyTempPalette(Palette<float>& palette);
#ifdef DO_DOUBLE
virtual void SetEmber(const Ember<double>& ember, bool verbatim = false);
virtual void CopyEmber(Ember<double>& ember);
virtual void SetEmberFile(const EmberFile<double>& emberFile);
virtual void CopyEmberFile(EmberFile<double>& emberFile);
virtual void SetTempPalette(const Palette<double>& palette);
virtual void CopyTempPalette(Palette<double>& palette);
#endif
virtual void SetEmber(size_t index);
virtual void Clear() { }
virtual void AddXform();
virtual void DuplicateXform();
virtual void ClearCurrentXform();
virtual void DeleteCurrentXform();
virtual void AddFinalXform();
virtual bool UseFinalXform() { return m_Ember.UseFinalXform(); }
//virtual bool IsFinal(unsigned int i) { return false; }
virtual size_t XformCount() { return m_Ember.XformCount(); }
virtual size_t TotalXformCount() { return m_Ember.TotalXformCount(); }
virtual string Name() { return m_Ember.m_Name; }
virtual void Name(string s) { m_Ember.m_Name = s; }
virtual unsigned int FinalRasW() { return m_Ember.m_FinalRasW; }
virtual void FinalRasW(unsigned int w) { m_Ember.m_FinalRasW = w; }
virtual unsigned int FinalRasH() { return m_Ember.m_FinalRasH; }
virtual void FinalRasH(unsigned int h) { m_Ember.m_FinalRasH = h; }
virtual void AddSymmetry(int sym, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) { m_Ember.AddSymmetry(sym, rand); }
virtual void CalcNormalizedWeights() { m_Ember.CalcNormalizedWeights(m_NormalizedWeights); }
Ember<T>* CurrentEmber();
//Menu.
virtual void NewFlock(unsigned int count);
virtual void NewEmptyFlameInCurrentFile();
virtual void NewRandomFlameInCurrentFile();
virtual void CopyFlameInCurrentFile();
virtual void OpenAndPrepFiles(QStringList filenames, bool append);
virtual void SaveCurrentAsXml();
virtual void SaveEntireFileAsXml();
virtual void SaveCurrentToOpenedFile();
virtual void Undo();
virtual void Redo();
virtual void CopyXml();
virtual void CopyAllXml();
virtual void PasteXmlAppend();
virtual void PasteXmlOver();
virtual void AddReflectiveSymmetry();
virtual void AddRotationalSymmetry();
virtual void AddBothSymmetry();
virtual void ClearFlame();
//Toolbar.
//Params.
virtual void SetCenter(double x, double y);
virtual void FillParamTablesAndPalette();
virtual void BrightnessChanged(double d);
virtual void GammaChanged(double d);
virtual void GammaThresholdChanged(double d);
virtual void VibrancyChanged(double d);
virtual void HighlightPowerChanged(double d);
virtual void PaletteModeChanged(unsigned int i);
virtual void CenterXChanged(double d);
virtual void CenterYChanged(double d);
virtual void ScaleChanged(double d);
virtual void ZoomChanged(double d);
virtual void RotateChanged(double d);
virtual void ZPosChanged(double d);
virtual void PerspectiveChanged(double d);
virtual void PitchChanged(double d);
virtual void YawChanged(double d);
virtual void DepthBlurChanged(double d);
virtual void SpatialFilterWidthChanged(double d);
virtual void SpatialFilterTypeChanged(const QString& text);
virtual void TemporalFilterWidthChanged(double d);
virtual void TemporalFilterTypeChanged(const QString& text);
virtual void DEFilterMinRadiusWidthChanged(double d);
virtual void DEFilterMaxRadiusWidthChanged(double d);
virtual void DEFilterCurveWidthChanged(double d);
virtual void PassesChanged(int d);
virtual void TemporalSamplesChanged(int d);
virtual void QualityChanged(double d);
virtual void SupersampleChanged(int d);
virtual void AffineInterpTypeChanged(int index);
virtual void InterpTypeChanged(int index);
virtual void BackgroundChanged(const QColor& col);
//Xforms.
virtual void CurrentXformComboChanged(int index);
virtual void XformWeightChanged(double d);
virtual void EqualizeWeights();
virtual void XformNameChanged(int row, int col);
void FillWithXform(Xform<T>* xform);
Xform<T>* CurrentXform();
//Xforms Affine.
virtual void AffineSetHelper(double d, int index, bool pre);
virtual void FlipCurrentXform(bool horizontal, bool vertical, bool pre);
virtual void RotateCurrentXformByAngle(double angle, bool pre);
virtual void MoveCurrentXform(double x, double y, bool pre);
virtual void ScaleCurrentXform(double scale, bool pre);
virtual void ResetCurrentXformAffine(bool pre);
void FillAffineWithXform(Xform<T>* xform, bool pre);
//Xforms Color.
virtual void XformColorIndexChanged(double d, bool updateRender);
virtual void XformScrollColorIndexChanged(int d);
virtual void XformColorSpeedChanged(double d);
virtual void XformOpacityChanged(double d);
virtual void XformDirectColorChanged(double d);
void FillColorWithXform(Xform<T>* xform);
//Xforms Variations.
virtual void SetupVariationTree();
virtual void ClearVariationsTree();
virtual void VariationSpinBoxValueChanged(double d);
void FillVariationTreeWithXform(Xform<T>* xform);
//Xforms Xaos.
virtual void FillXaosWithCurrentXform();
virtual QString MakeXaosNameString(unsigned int i);
virtual void XaosChanged(DoubleSpinBox* sender);
virtual void ClearXaos();
//Palette.
virtual bool InitPaletteTable(string s);
virtual void ApplyPaletteToEmber();
virtual void PaletteAdjust();
virtual QRgb GetQRgbFromPaletteIndex(unsigned int i) { return QRgb(); }
virtual void PaletteCellClicked(int row, int col);
//Library.
virtual void SyncNames();
virtual void FillLibraryTree(int selectIndex = -1);
virtual void UpdateLibraryTree();
virtual void EmberTreeItemChanged(QTreeWidgetItem* item, int col);
virtual void EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col);
virtual void RenderPreviews(unsigned int start = UINT_MAX, unsigned int end = UINT_MAX);
virtual void StopPreviewRender();
//Info.
//Rendering/progress.
virtual bool Render();
virtual bool CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared = true);
virtual unsigned int SizeOfT() { return sizeof(T); }
virtual int ProgressFunc(Ember<T>& ember, void* foo, double fraction, int stage, double etaMs);
virtual void ClearUndo();
virtual GLEmberControllerBase* GLController() { return m_GLController.get(); }
private:
//Embers.
void ApplyXmlSavingTemplate(Ember<T>& ember);
template <typename U> void SetEmberPrivate(const Ember<U>& ember, bool verbatim);
//Params.
void ParamsToEmber(Ember<T>& ember);
//Xforms.
void SetNormalizedWeightText(Xform<T>* xform);
bool IsFinal(Xform<T>* xform);
//Xforms Color.
void SetCurrentXformColorIndex(double d);
//Palette.
void UpdateAdjustedPaletteGUI(Palette<T>& palette);
//Rendering/progress.
void Update(std::function<void (void)> func, bool updateRender = true, eProcessAction action = FULL_RENDER);
void UpdateCurrentXform(std::function<void (Xform<T>*)> func, bool updateRender = true, eProcessAction action = FULL_RENDER);
//Templated members.
bool m_PreviewRun;
bool m_PreviewRunning;
vector<T> m_TempOpacities;
vector<T> m_NormalizedWeights;
Ember<T> m_Ember;
EmberFile<T> m_EmberFile;
deque<Ember<T>> m_UndoList;
Palette<T> m_TempPalette;
PaletteList<T> m_PaletteList;
VariationList<T> m_VariationList;
auto_ptr<SheepTools<T, T>> m_SheepTools;
auto_ptr<GLEmberController<T>> m_GLController;
auto_ptr<EmberNs::Renderer<T, T>> m_PreviewRenderer;
QFuture<void> m_PreviewResult;
std::function<void (unsigned int, unsigned int)> m_PreviewRenderFunc;
};
template class FractoriumEmberController<float>;
#ifdef DO_DOUBLE
template class FractoriumEmberController<double>;
#endif

View File

@ -0,0 +1,58 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Update the histogram bounds display labels.
/// This shows the user the actual bounds of what's
/// being rendered. Mostly of engineering interest.
/// </summary>
void Fractorium::UpdateHistogramBounds()
{
if (RendererBase* r = m_Controller->Renderer())
{
sprintf_s(m_ULString, 32, "UL: %3.3f, %3.3f", r->LowerLeftX(), r->UpperRightY());//These bounds include gutter padding.
sprintf_s(m_URString, 32, "UR: %3.3f, %3.3f", -r->LowerLeftX(), r->UpperRightY());
sprintf_s(m_LRString, 32, "LR: %3.3f, %3.3f", -r->LowerLeftX(), -r->UpperRightY());
sprintf_s(m_LLString, 32, "LL: %3.3f, %3.3f", r->LowerLeftX(), -r->UpperRightY());
sprintf_s(m_WString, 16, "W: %4d" , r->SuperRasW());
sprintf_s(m_HString, 16, "H: %4d" , r->SuperRasH());
ui.InfoBoundsLabelUL->setText(QString(m_ULString));
ui.InfoBoundsLabelUR->setText(QString(m_URString));
ui.InfoBoundsLabelLR->setText(QString(m_LRString));
ui.InfoBoundsLabelLL->setText(QString(m_LLString));
ui.InfoBoundsLabelW->setText(QString(m_WString));
ui.InfoBoundsLabelH->setText(QString(m_HString));
ui.InfoBoundsTable->item(0, 1)->setText(QString::number(r->GutterWidth()));
if (r->GetDensityFilter())
{
unsigned int deWidth = (r->GetDensityFilter()->FilterWidth() * 2) + 1;
sprintf_s(m_DEString, 16, "%d x %d", deWidth, deWidth);
ui.InfoBoundsTable->item(1, 1)->setText(QString(m_DEString));
}
else
ui.InfoBoundsTable->item(1, 1)->setText("N/A");
}
}
/// <summary>
/// Fill the passed in QTextEdit with the vector of strings.
/// Optionally clear first.
/// Serves as a convenience function because the error reports coming
/// from Ember and EmberCL use vector<string>.
/// Use invokeMethod() in case this is called from a thread.
/// </summary>
/// <param name="errors">The vector of error strings</param>
/// <param name="textEdit">The QTextEdit to fill</param>
/// <param name="clear">Clear if true, else don't.</param>
void Fractorium::ErrorReportToQTextEdit(vector<string>& errors, QTextEdit* textEdit, bool clear)
{
if (clear)
QMetaObject::invokeMethod(textEdit, "clear", Qt::QueuedConnection);
for (size_t i = 0; i < errors.size(); i++)
QMetaObject::invokeMethod(textEdit, "append", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(errors[i]) + "\n"));
}

View File

@ -0,0 +1,277 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Initialize the library tree UI.
/// </summary>
void Fractorium::InitLibraryUI()
{
connect(ui.LibraryTree, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(OnEmberTreeItemChanged(QTreeWidgetItem*, int)), Qt::QueuedConnection);
connect(ui.LibraryTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(OnEmberTreeItemDoubleClicked(QTreeWidgetItem*, int)), Qt::QueuedConnection);
}
/// <summary>
/// Slot function to be called via QMetaObject::invokeMethod() to update preview images in the preview thread.
/// </summary>
/// <param name="item">The item double clicked on</param>
/// <param name="v">The vector holding the RGBA bitmap</param>
/// <param name="width">The width of the bitmap</param>
/// <param name="height">The height of the bitmap</param>
void Fractorium::SetLibraryTreeItemData(EmberTreeWidgetItemBase* item, vector<unsigned char>& v, unsigned int width, unsigned int height)
{
item->SetImage(v, width, height);
}
/// <summary>
/// Set all libary tree entries to the name of the corresponding ember they represent.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SyncNames()
{
EmberTreeWidgetItem<T>* item;
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
QTreeWidgetItem* top = tree->topLevelItem(0);
tree->blockSignals(true);
if (top)
{
for (int i = 0; i < top->childCount(); i++)//Iterate through all of the children, which will represent the open embers.
{
if ((item = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i))) && i < m_EmberFile.m_Embers.size())//Cast the child widget to the EmberTreeWidgetItem type.
item->setText(0, QString::fromStdString(m_EmberFile.m_Embers[i].m_Name));
}
}
tree->blockSignals(false);
}
/// <summary>
/// Fill the library tree with the names of the embers in the
/// currently opened file.
/// Start preview render thread.
/// </summary>
/// <param name="selectIndex">After the tree is filled, select this index. Pass -1 to omit selecting an index.</param>
template <typename T>
void FractoriumEmberController<T>::FillLibraryTree(int selectIndex)
{
unsigned int i, j, size = 64;
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
vector<unsigned char> v(size * size * 4);
StopPreviewRender();
tree->clear();
QCoreApplication::flush();
tree->blockSignals(true);
QTreeWidgetItem* fileItem = new QTreeWidgetItem(tree);
QFileInfo info(m_EmberFile.m_Filename);
fileItem->setText(0, info.fileName());
fileItem->setToolTip(0, m_EmberFile.m_Filename);
fileItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable);
for (j = 0; j < m_EmberFile.m_Embers.size(); j++)
{
Ember<T>* ember = &m_EmberFile.m_Embers[j];
EmberTreeWidgetItem<T>* emberItem = new EmberTreeWidgetItem<T>(ember, fileItem);
emberItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable);
if (ember->m_Name.empty())
emberItem->setText(0, QString::number(j));
else
emberItem->setText(0, ember->m_Name.c_str());
emberItem->setToolTip(0, emberItem->text(0));
emberItem->SetImage(v, size, size);
}
tree->blockSignals(false);
if (selectIndex != -1)
if (QTreeWidgetItem* top = tree->topLevelItem(0))
if (EmberTreeWidgetItem<T>* emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(selectIndex)))
emberItem->setSelected(true);
QCoreApplication::flush();
RenderPreviews(0, m_EmberFile.m_Embers.size());
tree->expandAll();
}
/// <summary>
/// Update the library tree with the newly added embers (most likely from pasting) and
/// only render previews for the new ones, without clearing the entire tree.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::UpdateLibraryTree()
{
unsigned int i, size = 64;
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
vector<unsigned char> v(size * size * 4);
if (QTreeWidgetItem* top = tree->topLevelItem(0))
{
int childCount = top->childCount();
tree->blockSignals(true);
for (i = childCount; i < m_EmberFile.m_Embers.size(); i++)
{
Ember<T>* ember = &m_EmberFile.m_Embers[i];
EmberTreeWidgetItem<T>* emberItem = new EmberTreeWidgetItem<T>(ember, top);
emberItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable);
if (ember->m_Name.empty())
emberItem->setText(0, QString::number(i));
else
emberItem->setText(0, ember->m_Name.c_str());
emberItem->setToolTip(0, emberItem->text(0));
emberItem->SetImage(v, size, size);
}
//When adding elements to the vector, they may have been reshuffled which will have invalidated
//the pointers contained in the EmberTreeWidgetItems. So reassign all pointers here.
for (i = 0; i < m_EmberFile.m_Embers.size(); i++)
{
if (EmberTreeWidgetItem<T>* emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i)))
emberItem->SetEmberPointer(&m_EmberFile.m_Embers[i]);
}
tree->blockSignals(false);
RenderPreviews(childCount, m_EmberFile.m_Embers.size());
}
}
/// <summary>
/// Copy the text of the item which was changed to the name of the current ember.
/// Ensure all names are unique in the opened file.
/// This seems to be called spuriously, so we do a check inside to make sure
/// the text was actually changed.
/// We also have to wrap the dynamic_cast call in a try/catch block because this can
/// be called on a widget that has already been deleted.
/// </summary>
/// <param name="item">The libary tree item changed</param>
/// <param name="col">The column clicked, ignored.</param>
template <typename T>
void FractoriumEmberController<T>::EmberTreeItemChanged(QTreeWidgetItem* item, int col)
{
try
{
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
EmberTreeWidgetItem<T>* emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(item);
if (emberItem)
{
string oldName = emberItem->GetEmber()->m_Name;//First preserve the previous name.
tree->blockSignals(true);
emberItem->UpdateEmberName();//Copy edit text to the ember's name variable.
m_EmberFile.MakeNamesUnique();//Ensure all names remain unique.
SyncNames();//Copy all ember names to the tree items since some might have changed to be made unique.
string newName = emberItem->GetEmber()->m_Name;//Get the new, final, unique name.
if (m_Ember.m_Name == oldName && oldName != newName)//If the ember edited was the current one, and the name was indeed changed, update the name of the current one.
{
m_Ember.m_Name = newName;
m_LastSaveCurrent = "";//Reset will force the dialog to show on the next save current since the user probably wants a different name.
}
tree->blockSignals(false);
}
else if (QTreeWidgetItem* parentItem = dynamic_cast<QTreeWidgetItem*>(item))
{
QString text = parentItem->text(0);
if (text != "")
{
m_EmberFile.m_Filename = text;
m_LastSaveAll = "";//Reset will force the dialog to show on the next save all since the user probably wants a different name.
}
}
}
catch(std::exception& e)
{
qDebug() << "FractoriumEmberController<T>::EmberTreeItemChanged() : Exception thrown: " << e.what();
}
}
void Fractorium::OnEmberTreeItemChanged(QTreeWidgetItem* item, int col) { m_Controller->EmberTreeItemChanged(item, col); }
/// <summary>
/// Set the current ember to the selected item.
/// Clears the undo state.
/// Resets the rendering process.
/// Called when the user double clicks on a library tree item.
/// </summary>
/// <param name="item">The item double clicked on</param>
/// <param name="col">The column clicked, ignored.</param>
template <typename T>
void FractoriumEmberController<T>::EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col)
{
if (EmberTreeWidgetItem<T>* emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(item))
{
ClearUndo();
SetEmber(*emberItem->GetEmber());
}
}
void Fractorium::OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) { m_Controller->EmberTreeItemDoubleClicked(item, col); }
/// <summary>
/// Stop the preview renderer if it's already running.
/// Clear all of the existing preview images, then start the preview rendering thread.
/// Optionally only render previews for a subset of all open embers.
/// </summary>
/// <param name="start">The 0-based index to start rendering previews for</param>
/// <param name="end">The 0-based index which is one beyond the last ember to render a preview for</param>
template <typename T>
void FractoriumEmberController<T>::RenderPreviews(unsigned int start, unsigned int end)
{
StopPreviewRender();
if (start == UINT_MAX && end == UINT_MAX)
{
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
tree->blockSignals(true);
if (QTreeWidgetItem* top = tree->topLevelItem(0))
{
int childCount = top->childCount();
vector<unsigned char> emptyPreview(PREVIEW_SIZE * PREVIEW_SIZE * 3);
for (int i = 0; i < childCount; i++)
if (EmberTreeWidgetItem<T>* treeItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i)))
treeItem->SetImage(emptyPreview, PREVIEW_SIZE, PREVIEW_SIZE);
}
tree->blockSignals(false);
m_PreviewResult = QtConcurrent::run(m_PreviewRenderFunc, 0, m_EmberFile.m_Embers.size());
}
else
m_PreviewResult = QtConcurrent::run(m_PreviewRenderFunc, start, end);
}
/// <summary>
/// Stop the preview rendering thread.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::StopPreviewRender()
{
m_PreviewRun = false;
while (m_PreviewRunning)
QApplication::processEvents();
m_PreviewResult.cancel();
while (m_PreviewResult.isRunning())
QApplication::processEvents();
QCoreApplication::sendPostedEvents(m_Fractorium->ui.LibraryTree);
QCoreApplication::flush();
}

View File

@ -0,0 +1,752 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Initialize the menus UI.
/// </summary>
void Fractorium::InitMenusUI()
{
//File menu.
connect(ui.ActionNewFlock, SIGNAL(triggered(bool)), this, SLOT(OnActionNewFlock(bool)), Qt::QueuedConnection);
connect(ui.ActionNewEmptyFlameInCurrentFile, SIGNAL(triggered(bool)), this, SLOT(OnActionNewEmptyFlameInCurrentFile(bool)), Qt::QueuedConnection);
connect(ui.ActionNewRandomFlameInCurrentFile, SIGNAL(triggered(bool)), this, SLOT(OnActionNewRandomFlameInCurrentFile(bool)), Qt::QueuedConnection);
connect(ui.ActionCopyFlameInCurrentFile, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyFlameInCurrentFile(bool)), Qt::QueuedConnection);
connect(ui.ActionOpen, SIGNAL(triggered(bool)), this, SLOT(OnActionOpen(bool)), Qt::QueuedConnection);
connect(ui.ActionSaveCurrentAsXml, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveCurrentAsXml(bool)), Qt::QueuedConnection);
connect(ui.ActionSaveEntireFileAsXml, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveEntireFileAsXml(bool)), Qt::QueuedConnection);
connect(ui.ActionSaveCurrentToOpenedFile, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveCurrentToOpenedFile(bool)), Qt::QueuedConnection);
connect(ui.ActionSaveCurrentScreen, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveCurrentScreen(bool)), Qt::QueuedConnection);
connect(ui.ActionExit, SIGNAL(triggered(bool)), this, SLOT(OnActionExit(bool)), Qt::QueuedConnection);
//Edit menu.
connect(ui.ActionUndo, SIGNAL(triggered(bool)), this, SLOT(OnActionUndo(bool)), Qt::QueuedConnection);
connect(ui.ActionRedo, SIGNAL(triggered(bool)), this, SLOT(OnActionRedo(bool)), Qt::QueuedConnection);
connect(ui.ActionCopyXml, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyXml(bool)), Qt::QueuedConnection);
connect(ui.ActionCopyAllXml, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyAllXml(bool)), Qt::QueuedConnection);
connect(ui.ActionPasteXmlAppend, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteXmlAppend(bool)), Qt::QueuedConnection);
connect(ui.ActionPasteXmlOver, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteXmlOver(bool)), Qt::QueuedConnection);
//Tools menu.
connect(ui.ActionAddReflectiveSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddReflectiveSymmetry(bool)), Qt::QueuedConnection);
connect(ui.ActionAddRotationalSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddRotationalSymmetry(bool)), Qt::QueuedConnection);
connect(ui.ActionAddBothSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddBothSymmetry(bool)), Qt::QueuedConnection);
connect(ui.ActionClearFlame, SIGNAL(triggered(bool)), this, SLOT(OnActionClearFlame(bool)), Qt::QueuedConnection);
connect(ui.ActionStopRenderingPreviews, SIGNAL(triggered(bool)), this, SLOT(OnActionStopRenderingPreviews(bool)), Qt::QueuedConnection);
connect(ui.ActionRenderPreviews, SIGNAL(triggered(bool)), this, SLOT(OnActionRenderPreviews(bool)), Qt::QueuedConnection);
connect(ui.ActionFinalRender, SIGNAL(triggered(bool)), this, SLOT(OnActionFinalRender(bool)), Qt::QueuedConnection);
connect(m_FinalRenderDialog, SIGNAL(finished(int)), this, SLOT(OnFinalRenderClose(int)), Qt::QueuedConnection);
connect(ui.ActionOptions, SIGNAL(triggered(bool)), this, SLOT(OnActionOptions(bool)), Qt::QueuedConnection);
//Help menu.
connect(ui.ActionAbout, SIGNAL(triggered(bool)), this, SLOT(OnActionAbout(bool)), Qt::QueuedConnection);
}
/// <summary>
/// Create a new flock of random embers, with the specified length.
/// </summary>
/// <param name="count">The number of embers to include in the flock</param>
template <typename T>
void FractoriumEmberController<T>::NewFlock(unsigned int count)
{
Ember<T> ember;
StopPreviewRender();
m_EmberFile.Clear();
m_EmberFile.m_Embers.reserve(count);
m_EmberFile.m_Filename = EmberFile<T>::DefaultFilename();
for (unsigned int i = 0; i < count; i++)
{
m_SheepTools->Random(ember);
ParamsToEmber(ember);
ember.m_OrigFinalRasW = ember.m_FinalRasW;
ember.m_OrigFinalRasH = ember.m_FinalRasH;
ember.m_Name = m_EmberFile.m_Filename.toStdString() + "-" + QString::number(i + 1).toStdString();
m_EmberFile.m_Embers.push_back(ember);
}
m_LastSaveAll = "";
FillLibraryTree();
}
/// <summary>
/// Create a new flock and assign the first ember as the current one.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnActionNewFlock(bool checked)
{
m_Controller->NewFlock(10);
m_Controller->SetEmber(0);
}
/// <summary>
/// Create and add a new empty ember in the currently opened file
/// and set it as the current one.
/// It will have one empty xform in it.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::NewEmptyFlameInCurrentFile()
{
Ember<T> ember;
Xform<T> xform;
QDateTime local(QDateTime::currentDateTime());
StopPreviewRender();
ParamsToEmber(ember);
ember.m_OrigFinalRasW = ember.m_FinalRasW;
ember.m_OrigFinalRasH = ember.m_FinalRasH;
xform.m_Weight = T(0.25);
xform.m_ColorX = m_Rand.Frand01<T>();
ember.AddXform(xform);
ember.m_Palette = *m_PaletteList.GetPalette(-1);
ember.m_Name = EmberFile<T>::DefaultEmberName(m_EmberFile.m_Embers.size() + 1).toStdString();
m_EmberFile.m_Embers.push_back(ember);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync.
m_EmberFile.MakeNamesUnique();
UpdateLibraryTree();
SetEmber(m_EmberFile.m_Embers.size() - 1);
}
void Fractorium::OnActionNewEmptyFlameInCurrentFile(bool checked) { m_Controller->NewEmptyFlameInCurrentFile(); }
/// <summary>
/// Create and add a new random ember in the currently opened file
/// and set it as the current one.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::NewRandomFlameInCurrentFile()
{
Ember<T> ember;
StopPreviewRender();
m_SheepTools->Random(ember);
ParamsToEmber(ember);
ember.m_OrigFinalRasW = ember.m_FinalRasW;
ember.m_OrigFinalRasH = ember.m_FinalRasH;
ember.m_Name = EmberFile<T>::DefaultEmberName(m_EmberFile.m_Embers.size() + 1).toStdString();
m_EmberFile.m_Embers.push_back(ember);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync.
m_EmberFile.MakeNamesUnique();
UpdateLibraryTree();
SetEmber(m_EmberFile.m_Embers.size() - 1);
}
void Fractorium::OnActionNewRandomFlameInCurrentFile(bool checked) { m_Controller->NewRandomFlameInCurrentFile(); }
/// <summary>
/// Create and add a a copy of the current ember in the currently opened file
/// and set it as the current one.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::CopyFlameInCurrentFile()
{
Ember<T> ember = m_Ember;
StopPreviewRender();
ember.m_Name = EmberFile<T>::DefaultEmberName(m_EmberFile.m_Embers.size() + 1).toStdString();
m_EmberFile.m_Embers.push_back(ember);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync.
m_EmberFile.MakeNamesUnique();
UpdateLibraryTree();
SetEmber(m_EmberFile.m_Embers.size() - 1);
}
void Fractorium::OnActionCopyFlameInCurrentFile(bool checked) { m_Controller->CopyFlameInCurrentFile(); }
/// <summary>
/// Open a list of ember Xml files, apply various values from the GUI widgets.
/// Either append these newly read embers to the existing open embers,
/// or clear the current ember file first.
/// When appending, add the new embers the the end of library tree.
/// When not appending, clear and populate the library tree with the new embers.
/// Set the current ember to the first one in the newly opened list.
/// Clears the undo state.
/// Resets the rendering process.
/// </summary>
/// <param name="filenames">A list of full paths and filenames</param>
/// <param name="append">True to append the embers in the new files to the end of the currently open embers, false to clear and replace them</param>
template <typename T>
void FractoriumEmberController<T>::OpenAndPrepFiles(QStringList filenames, bool append)
{
if (!filenames.empty())
{
size_t i;
EmberFile<T> emberFile;
XmlToEmber<T> parser;
vector<Ember<T>> embers;
unsigned int previousSize = append ? m_EmberFile.m_Embers.size() : 0;
StopPreviewRender();
emberFile.m_Filename = filenames[0];
foreach (QString filename, filenames)
{
embers.clear();
if (parser.Parse(filename.toStdString().c_str(), embers) && !embers.empty())
{
//Disregard whatever size was specified in the file and fit it to the output window size.
for (i = 0; i < embers.size(); i++)
{
embers[i].SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH);
//Also ensure it has a name.
if (embers[i].m_Name == "" || embers[i].m_Name == "No name")
embers[i].m_Name = QString::number(i).toStdString();
embers[i].m_Quality = m_Fractorium->m_QualitySpin->value();
embers[i].m_Supersample = m_Fractorium->m_SupersampleSpin->value();
}
m_LastSaveAll = "";
emberFile.m_Embers.insert(emberFile.m_Embers.end(), embers.begin(), embers.end());
}
else
{
vector<string> errors = parser.ErrorReport();
m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit);
QMessageBox::critical(m_Fractorium, "Open Failed", "Could not open file, see info tab for details.");
}
}
if (append)
{
if (m_EmberFile.m_Filename == "")
m_EmberFile.m_Filename = filenames[0];
m_EmberFile.m_Embers.insert(m_EmberFile.m_Embers.end(), emberFile.m_Embers.begin(), emberFile.m_Embers.end());
}
else
m_EmberFile = emberFile;
//Resync indices and names.
for (i = 0; i < m_EmberFile.m_Embers.size(); i++)
m_EmberFile.m_Embers[i].m_Index = i;
m_EmberFile.MakeNamesUnique();
if (append)
UpdateLibraryTree();
else
FillLibraryTree(append ? previousSize - 1 : 0);
ClearUndo();
SetEmber(previousSize);
}
}
/// <summary>
/// Show a file open dialog to open ember Xml files.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnActionOpen(bool checked) { m_Controller->OpenAndPrepFiles(SetupOpenXmlDialog(), false); }
/// <summary>
/// Save current ember as Xml, using the Xml saving template values from the options.
/// This will first save the current ember back to the opened file in memory before
/// saving it to disk.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SaveCurrentAsXml()
{
QString filename;
FractoriumSettings* s = m_Fractorium->m_Settings;
if (QFile::exists(m_LastSaveCurrent))
{
filename = m_LastSaveCurrent;
}
else
{
if (m_EmberFile.m_Embers.size() == 1)
filename = m_Fractorium->SetupSaveXmlDialog(m_EmberFile.m_Filename);//If only one ember present, just use parent filename.
else
filename = m_Fractorium->SetupSaveXmlDialog(QString::fromStdString(m_Ember.m_Name));//More than one ember present, use individual ember name.
}
if (filename != "")
{
Ember<T> ember = m_Ember;
EmberToXml<T> writer;
QFileInfo fileInfo(filename);
xmlDocPtr tempEdit = ember.m_Edits;
SaveCurrentToOpenedFile();//Save the current ember back to the opened file before writing to disk.
ApplyXmlSavingTemplate(ember);
ember.m_Edits = writer.CreateNewEditdoc(&ember, NULL, "edit", s->Nick().toStdString(), s->Url().toStdString(), s->Id().toStdString(), "", 0, 0);
if (tempEdit != NULL)
xmlFreeDoc(tempEdit);
if (writer.Save(filename.toStdString().c_str(), ember, 0, true, false, true))
{
s->SaveFolder(fileInfo.canonicalPath());
m_LastSaveCurrent = filename;
}
else
QMessageBox::critical(m_Fractorium, "Save Failed", "Could not save file, try saving to a different folder.");
}
}
void Fractorium::OnActionSaveCurrentAsXml(bool checked) { m_Controller->SaveCurrentAsXml(); }
/// <summary>
/// Save entire opened file Xml, using the Xml saving template values from the options on each ember.
/// This will first save the current ember back to the opened file in memory before
/// saving all to disk.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SaveEntireFileAsXml()
{
QString filename;
FractoriumSettings* s = m_Fractorium->m_Settings;
if (QFile::exists(m_LastSaveAll))
filename = m_LastSaveAll;
else
filename = m_Fractorium->SetupSaveXmlDialog(m_EmberFile.m_Filename);
if (filename != "")
{
EmberFile<T> emberFile;
EmberToXml<T> writer;
QFileInfo fileInfo(filename);
SaveCurrentToOpenedFile();//Save the current ember back to the opened file before writing to disk.
emberFile = m_EmberFile;
for (size_t i = 0; i < emberFile.m_Embers.size(); i++)
ApplyXmlSavingTemplate(emberFile.m_Embers[i]);
if (writer.Save(filename.toStdString().c_str(), emberFile.m_Embers, 0, true, false, true))
{
m_LastSaveAll = filename;
s->SaveFolder(fileInfo.canonicalPath());
}
else
QMessageBox::critical(m_Fractorium, "Save Failed", "Could not save file, try saving to a different folder.");
}
}
void Fractorium::OnActionSaveEntireFileAsXml(bool checked) { m_Controller->SaveEntireFileAsXml(); }
/// <summary>
/// Show a file save dialog and save what is currently shown in the render window to disk as an image.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnActionSaveCurrentScreen(bool checked)
{
QString filename = SetupSaveImageDialog(QString::fromStdString(m_Controller->Name()));
m_Controller->SaveCurrentRender(filename);
}
/// <summary>
/// Save the current ember back to its position in the opened file.
/// This does not save to disk.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SaveCurrentToOpenedFile()
{
size_t i;
bool fileFound = false;
for (i = 0; i < m_EmberFile.m_Embers.size(); i++)
{
if (m_Ember.m_Name == m_EmberFile.m_Embers[i].m_Name)
{
m_EmberFile.m_Embers[i] = m_Ember;
fileFound = true;
break;
}
}
if (!fileFound)
{
StopPreviewRender();
m_EmberFile.m_Embers.push_back(m_Ember);
m_EmberFile.MakeNamesUnique();
UpdateLibraryTree();
}
else
{
RenderPreviews(i, i + 1);
}
}
void Fractorium::OnActionSaveCurrentToOpenedFile(bool checked) { m_Controller->SaveCurrentToOpenedFile(); }
/// <summary>
/// Exit the application.
/// </summary>
/// <param name="checked">Ignore.</param>
void Fractorium::OnActionExit(bool checked)
{
closeEvent(NULL);
QApplication::exit();
}
/// <summary>
/// Undoes this instance.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::Undo()
{
if (m_UndoList.size() > 1 && m_UndoIndex > 0)
{
int index = m_Ember.GetTotalXformIndex(CurrentXform());
m_LastEditWasUndoRedo = true;
m_UndoIndex = max(0u, m_UndoIndex - 1u);
SetEmber(m_UndoList[m_UndoIndex], true);
m_EditState = UNDO_REDO;
if (index >= 0)
m_Fractorium->CurrentXform(index);
m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1 && (m_UndoIndex > 0));
m_Fractorium->ui.ActionRedo->setEnabled(m_UndoList.size() > 1 && !(m_UndoIndex == m_UndoList.size() - 1));
}
}
void Fractorium::OnActionUndo(bool checked) { m_Controller->Undo(); }
/// <summary>
/// Redoes this instance.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::Redo()
{
if (m_UndoList.size() > 1 && m_UndoIndex < m_UndoList.size() - 1)
{
int index = m_Ember.GetTotalXformIndex(CurrentXform());
m_LastEditWasUndoRedo = true;
m_UndoIndex = min<unsigned int>(m_UndoIndex + 1, m_UndoList.size() - 1);
SetEmber(m_UndoList[m_UndoIndex], true);
m_EditState = UNDO_REDO;
if (index >= 0)
m_Fractorium->CurrentXform(index);
m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1 && (m_UndoIndex > 0));
m_Fractorium->ui.ActionRedo->setEnabled(m_UndoList.size() > 1 && !(m_UndoIndex == m_UndoList.size() - 1));
}
}
void Fractorium::OnActionRedo(bool checked) { m_Controller->Redo(); }
/// <summary>
/// Copy the current ember Xml to the clipboard.
/// Apply Xml saving settings from the options first.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::CopyXml()
{
Ember<T> ember = m_Ember;
EmberToXml<T> emberToXml;
FractoriumSettings* settings = m_Fractorium->m_Settings;
ember.m_FinalRasW = settings->XmlWidth();
ember.m_FinalRasH = settings->XmlHeight();
ember.m_Quality = settings->XmlQuality();
ember.m_Supersample = settings->XmlSupersample();
ember.m_TemporalSamples = settings->XmlTemporalSamples();
QApplication::clipboard()->setText(QString::fromStdString(emberToXml.ToString(ember, "", 0, false, false, true)));
}
void Fractorium::OnActionCopyXml(bool checked) { m_Controller->CopyXml(); }
/// <summary>
/// Copy the Xmls for all open embers as a single string to the clipboard, enclosed with the <flames> tag.
/// Apply Xml saving settings from the options first for each ember.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::CopyAllXml()
{
ostringstream os;
EmberToXml<T> emberToXml;
FractoriumSettings* settings = m_Fractorium->m_Settings;
os << "<flames>\n";
for (size_t i = 0; i < m_EmberFile.m_Embers.size(); i++)
{
Ember<T> ember = m_EmberFile.m_Embers[i];
ember.m_FinalRasW = settings->XmlWidth();
ember.m_FinalRasH = settings->XmlHeight();
ember.m_Quality = settings->XmlQuality();
ember.m_Supersample = settings->XmlSupersample();
ember.m_TemporalSamples = settings->XmlTemporalSamples();
os << emberToXml.ToString(ember, "", 0, false, false, true);
}
os << "</flames>\n";
QApplication::clipboard()->setText(QString::fromStdString(os.str()));
}
void Fractorium::OnActionCopyAllXml(bool checked) { m_Controller->CopyAllXml(); }
/// <summary>
/// Convert the Xml text from the clipboard to an ember, add it to the end
/// of the current file and set it as the current ember. If multiple Xmls were
/// copied to the clipboard and were enclosed in <flames> tags, then all of them will be added.
/// Clears the undo state.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::PasteXmlAppend()
{
unsigned int i, previousSize = m_EmberFile.m_Embers.size();
string s, errors;
XmlToEmber<T> parser;
vector<Ember<T>> embers;
QTextCodec* codec = QTextCodec::codecForName("UTF-8");
QByteArray b = codec->fromUnicode(QApplication::clipboard()->text());
s.reserve(b.size());
for (i = 0; i < b.size(); i++)
{
if ((unsigned int)b[i] < 128u)
s.push_back(b[i]);
}
b.clear();
StopPreviewRender();
parser.Parse((unsigned char*)s.c_str(), "", embers);
errors = parser.ErrorReportString();
if (errors != "")
{
QMessageBox::critical(m_Fractorium, "Paste Error", QString::fromStdString(errors));
}
if (!embers.empty())
{
for (i = 0; i < embers.size(); i++)
{
//Disregard whatever size was specified in the file and fit it to the output window size.
embers[i].m_Index = m_EmberFile.m_Embers.size();
embers[i].SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH);
//Also ensure it has a name.
if (embers[i].m_Name == "" || embers[i].m_Name == "No name")
embers[i].m_Name = QString::number(embers[i].m_Index).toStdString();
m_EmberFile.m_Embers.push_back(embers[i]);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync.
}
m_EmberFile.MakeNamesUnique();
UpdateLibraryTree();
SetEmber(previousSize);
}
}
void Fractorium::OnActionPasteXmlAppend(bool checked) { m_Controller->PasteXmlAppend(); }
/// <summary>
/// Convert the Xml text from the clipboard to an ember, overwrite the
/// current file and set the first as the current ember. If multiple Xmls were
/// copied to the clipboard and were enclosed in <flames> tags, then the current file will contain all of them.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::PasteXmlOver()
{
unsigned int i;
string s, errors;
XmlToEmber<T> parser;
Ember<T> backupEmber = m_EmberFile.m_Embers[0];
QTextCodec* codec = QTextCodec::codecForName("UTF-8");
QByteArray b = codec->fromUnicode(QApplication::clipboard()->text());
s.reserve(b.size());
for (i = 0; i < b.size(); i++)
{
if ((unsigned int)b[i] < 128u)
s.push_back(b[i]);
}
b.clear();
StopPreviewRender();
m_EmberFile.m_Embers.clear();//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync.
parser.Parse((unsigned char*)s.c_str(), "", m_EmberFile.m_Embers);
errors = parser.ErrorReportString();
if (errors != "")
{
QMessageBox::critical(m_Fractorium, "Paste Error", QString::fromStdString(errors));
}
if (!m_EmberFile.m_Embers.empty())
{
for (i = 0; i < m_EmberFile.m_Embers.size(); i++)
{
//Disregard whatever size was specified in the file and fit it to the output window size.
m_EmberFile.m_Embers[i].m_Index = i;
m_EmberFile.m_Embers[i].SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH);
//Also ensure it has a name.
if (m_EmberFile.m_Embers[i].m_Name == "" || m_EmberFile.m_Embers[i].m_Name == "No name")
m_EmberFile.m_Embers[i].m_Name = QString::number(m_EmberFile.m_Embers[i].m_Index).toStdString();
}
}
else
{
backupEmber.m_Index = 0;
m_EmberFile.m_Embers.push_back(backupEmber);
}
m_EmberFile.MakeNamesUnique();
FillLibraryTree();
SetEmber(0);
}
void Fractorium::OnActionPasteXmlOver(bool checked) { m_Controller->PasteXmlOver(); }
/// <summary>
/// Add reflective symmetry to the current ember.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::AddReflectiveSymmetry()
{
QComboBox* combo = m_Fractorium->ui.CurrentXformCombo;
m_Ember.AddSymmetry(-1, m_Rand);
m_Fractorium->FillXforms();
combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final.
UpdateRender();
}
void Fractorium::OnActionAddReflectiveSymmetry(bool checked) { m_Controller->AddReflectiveSymmetry(); }
/// <summary>
/// Add rotational symmetry to the current ember.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::AddRotationalSymmetry()
{
QComboBox* combo = m_Fractorium->ui.CurrentXformCombo;
m_Ember.AddSymmetry(2, m_Rand);
m_Fractorium->FillXforms();
combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final.
UpdateRender();
}
void Fractorium::OnActionAddRotationalSymmetry(bool checked) { m_Controller->AddRotationalSymmetry(); }
/// <summary>
/// Add both reflective and rotational symmetry to the current ember.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::AddBothSymmetry()
{
QComboBox* combo = m_Fractorium->ui.CurrentXformCombo;
m_Ember.AddSymmetry(-2, m_Rand);
m_Fractorium->FillXforms();
combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final.
UpdateRender();
}
void Fractorium::OnActionAddBothSymmetry(bool checked) { m_Controller->AddBothSymmetry(); }
/// <summary>
/// Delete all but one xform in the current ember.
/// Clear that xform's variations.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::ClearFlame()
{
while (m_Ember.TotalXformCount() > 1)
m_Ember.DeleteTotalXform(m_Ember.TotalXformCount() - 1);
if (m_Ember.XformCount() == 1)
{
if (Xform<T>* xform = m_Ember.GetXform(0))
{
xform->Clear();
xform->ParentEmber(&m_Ember);
}
}
m_Fractorium->FillXforms();
m_Fractorium->ui.CurrentXformCombo->setCurrentIndex(0);
UpdateRender();
}
void Fractorium::OnActionClearFlame(bool checked) { m_Controller->ClearFlame(); }
/// <summary>
/// Re-render all previews.
/// </summary>
void Fractorium::OnActionRenderPreviews(bool checked)
{
m_Controller->RenderPreviews();
}
/// <summary>
/// Stop all previews from being rendered. This is handy if the user
/// opens a large file with many embers in it, such as an animation sequence.
/// </summary>
void Fractorium::OnActionStopRenderingPreviews(bool checked) { m_Controller->StopPreviewRender(); }
/// <summary>
/// Show the final render dialog as a modeless dialog to allow
/// the user to minimize the main window while doing a lengthy final render.
/// Note: The user probably should not be otherwise interacting with the main GUI
/// while the final render is taking place.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnActionFinalRender(bool checked)
{
//First completely stop what the current rendering process is doing.
m_Controller->DeleteRenderer();//Delete the renderer, but not the controller.
m_RenderStatusLabel->setText("Renderer stopped.");
m_FinalRenderDialog->show();
}
/// <summary>
/// Called when the final render dialog has been closed.
/// </summary>
/// <param name="result">Ignored</param>
void Fractorium::OnFinalRenderClose(int result)
{
m_RenderStatusLabel->setText("Renderer starting...");
StartRenderTimer();//Re-create the renderer and start rendering again.
}
/// <summary>
/// Show the final options dialog.
/// Restart rendering and sync options after the options dialog is dismissed with Ok.
/// Called when the options dialog is finished with ok.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnActionOptions(bool checked)
{
if (m_OptionsDialog->exec())
{
//First completely stop what the current rendering process is doing.
m_Controller->Shutdown();
StartRenderTimer();//This will recreate the controller and/or the renderer from the options if necessary, then start the render timer.
m_Settings->sync();
}
}
/// <summary>
/// Show the about dialog.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnActionAbout(bool checked)
{
m_AboutDialog->exec();
}

View File

@ -0,0 +1,235 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
#define PALETTE_CELL_HEIGHT 16
/// <summary>
/// Initialize the palette UI.
/// </summary>
void Fractorium::InitPaletteUI()
{
int spinHeight = 20, row = 0;
QTableWidget* paletteTable = ui.PaletteListTable;
QTableWidget* palettePreviewTable = ui.PalettePreviewTable;
connect(paletteTable, SIGNAL(cellClicked(int, int)), this, SLOT(OnPaletteCellClicked(int, int)), Qt::QueuedConnection);
connect(paletteTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(OnPaletteCellDoubleClicked(int, int)), Qt::QueuedConnection);
//Palette adjustment table.
QTableWidget* table = ui.PaletteAdjustTable;
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);//Split width over all columns evenly.
SetupSpinner<SpinBox, int>(table, this, row, 1, m_PaletteHueSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_PaletteSaturationSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
SetupSpinner<SpinBox, int>(table, this, row, 1, m_PaletteBrightnessSpin, spinHeight, -255, 255, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
row = 0;
SetupSpinner<SpinBox, int>(table, this, row, 3, m_PaletteContrastSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
SetupSpinner<SpinBox, int>(table, this, row, 3, m_PaletteBlurSpin, spinHeight, 0, 127, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
SetupSpinner<SpinBox, int>(table, this, row, 3, m_PaletteFrequencySpin, spinHeight, 1, 10, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 1, 1, 1);
}
/// <summary>
/// Read a palette Xml file and populate the palette table with the contents.
/// This will clear any previous contents.
/// Called upon initialization, or controller type change.
/// </summary>
/// <param name="s">The full path to the palette file</param>
/// <returns>True if successful, else false.</returns>
template <typename T>
bool FractoriumEmberController<T>::InitPaletteTable(string s)
{
QTableWidget* paletteTable = m_Fractorium->ui.PaletteListTable;
QTableWidget* palettePreviewTable = m_Fractorium->ui.PalettePreviewTable;
paletteTable->clear();
if (m_PaletteList.Init(s))//Default to this, but add an option later.//TODO
{
//Preview table.
palettePreviewTable->setRowCount(1);
palettePreviewTable->setColumnWidth(1, 260);//256 plus small margin on each side.
QTableWidgetItem* previewNameCol = new QTableWidgetItem("");
palettePreviewTable->setItem(0, 0, previewNameCol);
QTableWidgetItem* previewPaletteItem = new QTableWidgetItem();
palettePreviewTable->setItem(0, 1, previewPaletteItem);
//Palette list table.
paletteTable->setRowCount(m_PaletteList.Count());
paletteTable->setColumnWidth(1, 260);//256 plus small margin on each side.
paletteTable->horizontalHeader()->setSectionsClickable(false);
//Headers get removed when clearing, so must re-create here.
QTableWidgetItem* nameHeader = new QTableWidgetItem("Name");
QTableWidgetItem* paletteHeader = new QTableWidgetItem("Palette");
nameHeader->setTextAlignment(Qt::AlignLeft|Qt::AlignVCenter);
paletteHeader->setTextAlignment(Qt::AlignLeft|Qt::AlignVCenter);
paletteTable->setHorizontalHeaderItem(0, nameHeader);
paletteTable->setHorizontalHeaderItem(1, paletteHeader);
//Palette list table.
for (size_t i = 0; i < m_PaletteList.Count(); i++)
{
Palette<T>* p = m_PaletteList.GetPalette(i);
vector<unsigned char> v = p->MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT);
QTableWidgetItem* nameCol = new QTableWidgetItem(p->m_Name.c_str());
nameCol->setToolTip(p->m_Name.c_str());
paletteTable->setItem(i, 0, nameCol);
QImage image(v.data(), p->Size(), PALETTE_CELL_HEIGHT, QImage::Format_RGB888);
QTableWidgetItem* paletteItem = new QTableWidgetItem();
paletteItem->setData(Qt::DecorationRole, QPixmap::fromImage(image));
paletteTable->setItem(i, 1, paletteItem);
}
return true;
}
else
{
vector<string> errors = m_PaletteList.ErrorReport();
m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit);
QMessageBox::critical(m_Fractorium, "Palette Read Error", "Could not load palette file, all images will be black. See info tab for details.");
}
return false;
}
/// <summary>
/// Apply adjustments to the current ember's palette.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::ApplyPaletteToEmber()
{
int i, rot = 0;
unsigned int blur = m_Fractorium->m_PaletteBlurSpin->value();
unsigned int freq = m_Fractorium->m_PaletteFrequencySpin->value();
double sat = (double)m_Fractorium->m_PaletteSaturationSpin->value() / 100.0;
double brightness = (double)m_Fractorium->m_PaletteBrightnessSpin->value() / 255.0;
double contrast = (double)(m_Fractorium->m_PaletteContrastSpin->value() > 0 ? (m_Fractorium->m_PaletteContrastSpin->value() * 2) : m_Fractorium->m_PaletteContrastSpin->value()) / 100.0;
m_Ember.m_Hue = (double)(m_Fractorium->m_PaletteHueSpin->value()) / 360.0;//This is the only palette adjustment value that gets saved with the ember, so just assign it here.
//Use the temp palette as the base and apply the adjustments gotten from the GUI and save the result in the ember palette.
m_TempPalette.MakeAdjustedPalette(m_Ember.m_Palette, 0, m_Ember.m_Hue, sat, brightness, contrast, blur, freq);
}
/// <summary>
/// Use adjusted palette to update all related GUI controls with new color values.
/// Resets the rendering process.
/// </summary>
/// <param name="palette">The palette to use</param>
/// <param name="paletteName">Name of the palette</param>
template <typename T>
void FractoriumEmberController<T>::UpdateAdjustedPaletteGUI(Palette<T>& palette)
{
Xform<T>* xform = CurrentXform();
QTableWidget* palettePreviewTable = m_Fractorium->ui.PalettePreviewTable;
QTableWidgetItem* previewPaletteItem = palettePreviewTable->item(0, 1);
QString paletteName = QString::fromStdString(m_Ember.m_Palette.m_Name);
if (previewPaletteItem)//This can be null if the palette file was moved or corrupted.
{
//Use the adjusted palette to fill the preview palette control so the user can see the effects of applying the adjustements.
vector<unsigned char> v = palette.MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT);//Make the palette repeat for PALETTE_CELL_HEIGHT rows.
m_FinalPaletteImage = QImage(palette.Size(), PALETTE_CELL_HEIGHT, QImage::Format_RGB888);//Create a QImage out of it.
memcpy(m_FinalPaletteImage.scanLine(0), v.data(), v.size() * sizeof(v[0]));//Memcpy the data in.
QPixmap pixmap = QPixmap::fromImage(m_FinalPaletteImage);//Create a QPixmap out of the QImage.
previewPaletteItem->setData(Qt::DecorationRole, pixmap.scaled(QSize(pixmap.width(), palettePreviewTable->rowHeight(0) + 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));//Set the pixmap on the palette tab.
SetPaletteRefTable(&pixmap);//Set the palette ref table on the xforms | color tab.
QTableWidgetItem* previewNameItem = palettePreviewTable->item(0, 0);
previewNameItem->setText(paletteName);//Finally, set the name of the palette to be both the text and the tooltip.
previewNameItem->setToolTip(paletteName);
}
//Update the current xform's color and reset the rendering process.
if (xform)
XformColorIndexChanged(xform->m_ColorX, true);
}
/// <summary>
/// Apply all adjustments to the selected palette, show it
/// and assign it to the current ember.
/// Called when any adjustment spinner is modified.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::PaletteAdjust()
{
UpdateCurrentXform([&] (Xform<T>* xform)
{
ApplyPaletteToEmber();
UpdateAdjustedPaletteGUI(m_Ember.m_Palette);
});
}
void Fractorium::OnPaletteAdjust(int d) { m_Controller->PaletteAdjust(); }
/// <summary>
/// Set the selected palette as the current one,
/// applying any adjustments previously specified.
/// Called when a palette cell is clicked. Unfortunately,
/// this will get called twice on a double click when moving
/// from one palette to another. It happens quickly so it shouldn't
/// be too much of a problem.
/// Resets the rendering process.
/// </summary>
/// <param name="row">The table row clicked</param>
/// <param name="col">The table col clicked</param>
template <typename T>
void FractoriumEmberController<T>::PaletteCellClicked(int row, int col)
{
Palette<T>* palette = m_PaletteList.GetPalette(row);
QTableWidgetItem* nameItem = m_Fractorium->ui.PaletteListTable->item(row, 0);
if (palette)
{
m_TempPalette = *palette;//Deep copy.
ApplyPaletteToEmber();//Copy temp palette to ember palette and apply adjustments.
UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Show the adjusted palette.
}
}
void Fractorium::OnPaletteCellClicked(int row, int col)
{
if (m_PreviousPaletteRow != row)
{
m_Controller->PaletteCellClicked(row, col);
m_PreviousPaletteRow = row;//Save for comparison on next click.
}
}
/// <summary>
/// Set the selected palette as the current one,
/// resetting any adjustments previously specified.
/// Called when a palette cell is double clicked.
/// Resets the rendering process.
/// </summary>
/// <param name="row">The table row clicked</param>
/// <param name="col">The table col clicked</param>
void Fractorium::OnPaletteCellDoubleClicked(int row, int col)
{
ResetPaletteControls();
m_PreviousPaletteRow = -1;
OnPaletteCellClicked(row, col);
}
/// <summary>
/// Reset the palette controls.
/// Usually in response to a palette cell double click.
/// </summary>
void Fractorium::ResetPaletteControls()
{
m_PaletteHueSpin->SetValueStealth(0);
m_PaletteSaturationSpin->SetValueStealth(0);
m_PaletteBrightnessSpin->SetValueStealth(0);
m_PaletteContrastSpin->SetValueStealth(0);
m_PaletteBlurSpin->SetValueStealth(0);
m_PaletteFrequencySpin->SetValueStealth(1);
}

View File

@ -0,0 +1,627 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Initialize the parameters UI.
/// </summary>
void Fractorium::InitParamsUI()
{
int row = 0;
int spinHeight = 20;
vector<string> comboVals;
QTableWidget* table = ui.ColorTable;
//Because QTableWidget does not allow for a single title bar/header
//at the top of a multi-column table, the workaround hack is to just
//make another single column table with no rows, and use the single
//column header as the title bar. Then positioning it right above the table
//that holds the data. Disallow selecting and resizing of the title bar.
SetFixedTableHeader(ui.ColorTableHeader->horizontalHeader());
SetFixedTableHeader(ui.GeometryTableHeader->horizontalHeader());
SetFixedTableHeader(ui.FilterTableHeader->horizontalHeader());
SetFixedTableHeader(ui.IterationTableHeader->horizontalHeader());
//Color.
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_BrightnessSpin, spinHeight, 0.05, 100, 1, SIGNAL(valueChanged(double)), SLOT(OnBrightnessChanged(double)), true, 4.0, 4.0, 4.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_GammaSpin, spinHeight, 1, 9999, 0.5, SIGNAL(valueChanged(double)), SLOT(OnGammaChanged(double)), true, 4.0, 4.0, 4.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_GammaThresholdSpin, spinHeight, 0, 10, 0.1, SIGNAL(valueChanged(double)), SLOT(OnGammaThresholdChanged(double)), true, 0.1, 0.1, 0.1);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_VibrancySpin, spinHeight, 0, 1, 0.01, SIGNAL(valueChanged(double)), SLOT(OnVibrancyChanged(double)), true, 1.0, 1.0, 1.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_HighlightSpin, spinHeight, -1.0, 2.0, 0.1, SIGNAL(valueChanged(double)), SLOT(OnHighlightPowerChanged(double)), true, -1.0, -1.0, -1.0);
m_BackgroundColorButton = new QPushButton("...", table);
m_BackgroundColorButton->setMinimumWidth(21);
m_BackgroundColorButton->setMaximumWidth(21);
table->setCellWidget(row, 1, m_BackgroundColorButton);
table->item(row, 1)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
connect(m_BackgroundColorButton, SIGNAL(clicked(bool)), this, SLOT(OnBackgroundColorButtonClicked(bool)), Qt::QueuedConnection);
row++;
comboVals.push_back("Step");
comboVals.push_back("Linear");
SetupCombo(table, this, row, 1, m_PaletteModeCombo, comboVals, SIGNAL(currentIndexChanged(int)), SLOT(OnPaletteModeComboCurrentIndexChanged(int)));
//Geometry.
row = 0;
table = ui.GeometryTable;
SetupSpinner<SpinBox, int> (table, this, row, 1, m_WidthSpin, spinHeight, 10, 100000, 50, SIGNAL(valueChanged(int)), SLOT(OnWidthChanged(int)));
SetupSpinner<SpinBox, int> (table, this, row, 1, m_HeightSpin, spinHeight, 10, 100000, 50, SIGNAL(valueChanged(int)), SLOT(OnHeightChanged(int)));
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_CenterXSpin, spinHeight, -10, 10, 0.05, SIGNAL(valueChanged(double)), SLOT(OnCenterXChanged(double)), true, 0, 0, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_CenterYSpin, spinHeight, -10, 10, 0.05, SIGNAL(valueChanged(double)), SLOT(OnCenterYChanged(double)), true, 0, 0, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_ScaleSpin, spinHeight, 10, 3000, 20, SIGNAL(valueChanged(double)), SLOT(OnScaleChanged(double)), true, 240, 240, 240);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_ZoomSpin, spinHeight, 0, 5, 0.2, SIGNAL(valueChanged(double)), SLOT(OnZoomChanged(double)), true, 0, 0, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_RotateSpin, spinHeight, -180, 180, 10, SIGNAL(valueChanged(double)), SLOT(OnRotateChanged(double)), true, 0, 0, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_ZPosSpin, spinHeight, -1000, 1000, 1, SIGNAL(valueChanged(double)), SLOT(OnZPosChanged(double)), true, 0, 1, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_PerspectiveSpin, spinHeight, -500, 500, 0.01, SIGNAL(valueChanged(double)), SLOT(OnPerspectiveChanged(double)), true, 0, 1, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_PitchSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(double)), SLOT(OnPitchChanged(double)), true, 0, 45, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_YawSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(double)), SLOT(OnYawChanged(double)), true, 0, 45, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_DepthBlurSpin, spinHeight, -100, 100, 0.01, SIGNAL(valueChanged(double)), SLOT(OnDepthBlurChanged(double)), true, 0, 1, 0);
m_WidthSpin->setEnabled(false);//Will programatically change these to match the window size, but the user should never be allowed to.
m_HeightSpin->setEnabled(false);
m_CenterXSpin->setDecimals(3);
m_CenterYSpin->setDecimals(3);
m_ZPosSpin->setDecimals(3);
m_PerspectiveSpin->setDecimals(4);
m_DepthBlurSpin->setDecimals(3);
//Filter.
row = 0;
table = ui.FilterTable;
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_SpatialFilterWidthSpin, spinHeight, 0.1, 10, 0.1, SIGNAL(valueChanged(double)), SLOT(OnSpatialFilterWidthChanged(double)), true, 1.0, 1.0, 1.0);
comboVals = SpatialFilterCreator<float>::FilterTypes();
SetupCombo(table, this, row, 1, m_SpatialFilterTypeCombo, comboVals, SIGNAL(currentIndexChanged(const QString&)), SLOT(OnSpatialFilterTypeComboCurrentIndexChanged(const QString&)));
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_TemporalFilterWidthSpin, spinHeight, 1, 10, 1, SIGNAL(valueChanged(double)), SLOT(OnTemporalFilterWidthChanged(double)), true, 1);
comboVals = TemporalFilterCreator<float>::FilterTypes();
SetupCombo(table, this, row, 1, m_TemporalFilterTypeCombo, comboVals, SIGNAL(currentIndexChanged(const QString&)), SLOT(OnTemporalFilterTypeComboCurrentIndexChanged(const QString&)));
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_DEFilterMinRadiusSpin, spinHeight, 0, 25, 1, SIGNAL(valueChanged(double)), SLOT(OnDEFilterMinRadiusWidthChanged(double)), true, 0, 0, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_DEFilterMaxRadiusSpin, spinHeight, 0, 25, 1, SIGNAL(valueChanged(double)), SLOT(OnDEFilterMaxRadiusWidthChanged(double)), true, 9.0, 9.0, 0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_DECurveSpin, spinHeight, 0.15, 5, 0.1, SIGNAL(valueChanged(double)), SLOT(OnDEFilterCurveWidthChanged(double)), true, 0.4, 0.4, 0.4);
//Iteration.
row = 0;
table = ui.IterationTable;
SetupSpinner<SpinBox, int> (table, this, row, 1, m_PassesSpin, spinHeight, 1, 3, 1, SIGNAL(valueChanged(int)), SLOT(OnPassesChanged(int)), true, 1, 1, 1);
SetupSpinner<SpinBox, int> (table, this, row, 1, m_TemporalSamplesSpin, spinHeight, 1, 5000, 50, SIGNAL(valueChanged(int)), SLOT(OnTemporalSamplesChanged(int)), true, 1000);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_QualitySpin, spinHeight, 1, 200000, 50, SIGNAL(valueChanged(double)), SLOT(OnQualityChanged(double)), true, 10, 10, 10);
SetupSpinner<SpinBox, int> (table, this, row, 1, m_SupersampleSpin, spinHeight, 1, 4, 1, SIGNAL(valueChanged(int)), SLOT(OnSupersampleChanged(int)), true, 1, 1, 1);
comboVals.clear();
comboVals.push_back("Step");
comboVals.push_back("Linear");
SetupCombo(table, this, row, 1, m_AffineInterpTypeCombo, comboVals, SIGNAL(currentIndexChanged(int)), SLOT(OnAffineInterpTypeComboCurrentIndexChanged(int)));
comboVals.clear();
comboVals.push_back("Linear");
comboVals.push_back("Smooth");
SetupCombo(table, this, row, 1, m_InterpTypeCombo, comboVals, SIGNAL(currentIndexChanged(int)), SLOT(OnInterpTypeComboCurrentIndexChanged(int)));
}
/// <summary>
/// Color.
/// </summary>
/// <summary>
/// Set the brightness to be used for calculating K1 and K2 for filtering and final accum.
/// Called when brightness spinner is changed.
/// Resets the rendering process to the filtering stage.
/// </summary>
/// <param name="d">The brightness</param>
template <typename T>
void FractoriumEmberController<T>::BrightnessChanged(double d) { Update([&] { m_Ember.m_Brightness = d; }, true, FILTER_AND_ACCUM); }
void Fractorium::OnBrightnessChanged(double d) { m_Controller->BrightnessChanged(d); }
/// <summary>
/// Set the gamma to be used for final accum.
/// Called when gamma spinner is changed.
/// Resets the rendering process if temporal samples is greater than 1,
/// else if early clip is true, filter and accum, else final accum only.
/// </summary>
/// <param name="d">The gamma value</param>
template <typename T> void FractoriumEmberController<T>::GammaChanged(double d) { Update([&] { m_Ember.m_Gamma = d; }, true, m_Ember.m_TemporalSamples > 1 ? FULL_RENDER : (m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY)); }
void Fractorium::OnGammaChanged(double d) { m_Controller->GammaChanged(d); }
/// <summary>
/// Set the gamma threshold to be used for final accum.
/// Called when gamma threshold spinner is changed.
/// Resets the rendering process to the final accumulation stage.
/// </summary>
/// <param name="d">The gamma threshold</param>
template <typename T> void FractoriumEmberController<T>::GammaThresholdChanged(double d) { Update([&] { m_Ember.m_GammaThresh = d; }, true, m_Ember.m_TemporalSamples > 1 ? FULL_RENDER : (m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY)); }
void Fractorium::OnGammaThresholdChanged(double d) { m_Controller->GammaThresholdChanged(d); }
/// <summary>
/// Set the vibrancy to be used for final accum.
/// Called when vibrancy spinner is changed.
/// Resets the rendering process to the final accumulation stage if temporal samples is 1, else full reset.
/// </summary>
/// <param name="d">The vibrancy</param>
template <typename T> void FractoriumEmberController<T>::VibrancyChanged(double d) { Update([&] { m_Ember.m_Vibrancy = d; }, true, m_Ember.m_TemporalSamples > 1 ? FULL_RENDER : (m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY)); }
void Fractorium::OnVibrancyChanged(double d) { m_Controller->VibrancyChanged(d); }
/// <summary>
/// Set the highlight power to be used for final accum.
/// Called when highlight power spinner is changed.
/// Resets the rendering process to the final accumulation stage.
/// </summary>
/// <param name="d">The highlight power</param>
template <typename T> void FractoriumEmberController<T>::HighlightPowerChanged(double d) { Update([&] { m_Ember.m_HighlightPower = d; }, true, m_Ember.m_TemporalSamples > 1 ? FULL_RENDER : (m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY)); }
void Fractorium::OnHighlightPowerChanged(double d) { m_Controller->HighlightPowerChanged(d); }
/// <summary>
/// Show the color selection dialog.
/// Called when background color button is clicked.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnBackgroundColorButtonClicked(bool checked)
{
m_ColorDialog->show();
}
/// <summary>
/// Set a new ember background color when the user accepts the color dialog.
/// Also change the background and foreground colors of the color cell in the
/// color params table.
/// Resets the rendering process.
/// </summary>
/// <param name="color">The color to set, RGB in the 0-255 range</param>
template <typename T>
void FractoriumEmberController<T>::BackgroundChanged(const QColor& color)
{
Update([&]
{
int itemRow = 5;
QTableWidget* colorTable = m_Fractorium->ui.ColorTable;
colorTable->item(itemRow, 1)->setBackgroundColor(color);
QString r = QString::number(color.red());
QString g = QString::number(color.green());
QString b = QString::number(color.blue());
int threshold = 105;
int delta = (color.red() * 0.299) + //Magic numbers gotten from a Stack Overflow post.
(color.green() * 0.587) +
(color.blue() * 0.114);
QColor textColor = (255 - delta < threshold) ? QColor(0, 0, 0) : QColor(255, 255, 255);
colorTable->item(itemRow, 1)->setTextColor(textColor);
colorTable->item(itemRow, 1)->setText("rgb(" + r + ", " + g + ", " + b + ")");
//Color is 0-255, normalize to 0-1.
m_Ember.m_Background.r = color.red() / 255.0;
m_Ember.m_Background.g = color.green() / 255.0;
m_Ember.m_Background.b = color.blue() / 255.0;
});
}
void Fractorium::OnColorSelected(const QColor& color) { m_Controller->BackgroundChanged(color); }
/// <summary>
/// Set the palette index interpolation mode.
/// Called when palette mode combo box index is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="index">The index of the palette mode combo box</param>
template <typename T> void FractoriumEmberController<T>::PaletteModeChanged(unsigned int i) { Update([&] { m_Ember.m_PaletteMode = i == 0 ? PALETTE_STEP : PALETTE_LINEAR; }); }
void Fractorium::OnPaletteModeComboCurrentIndexChanged(int index) { m_Controller->PaletteModeChanged(index); }
/// <summary>
/// Geometry.
/// </summary>
/// <summary>
/// Placeholder, do nothing.
/// Dimensions are set automatically to match the dimensions of GLWidget.
/// </summary>
/// <param name="d">Ignored</param>
void Fractorium::OnWidthChanged(int d) { }
/// <summary>
/// Placeholder, do nothing.
/// Dimensions are set automatically to match the dimensions of GLWidget.
/// </summary>
/// <param name="d">Ignored</param>
void Fractorium::OnHeightChanged(int d) { }
/// <summary>
/// Set the x offset applied to the center of the image.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The x offset value</param>
template <typename T> void FractoriumEmberController<T>::CenterXChanged(double d) { Update([&] { m_Ember.m_CenterX = d; }); }
void Fractorium::OnCenterXChanged(double d) { m_Controller->CenterXChanged(d); }
/// <summary>
/// Set the y offset applied to the center of the image.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The y offset value</param>
template <typename T> void FractoriumEmberController<T>::CenterYChanged(double d) { Update([&] { m_Ember.m_CenterY = d; }); }
void Fractorium::OnCenterYChanged(double d) { m_Controller->CenterYChanged(d); }
/// <summary>
/// Set the scale (pixels per unit) value of the image.
/// Note this will not increase the number of iters ran, but will degrade quality.
/// To preserve quality, but exponentially increase iters, use zoom.
/// Called when scale spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The scale value</param>
template <typename T> void FractoriumEmberController<T>::ScaleChanged(double d) { Update([&] { m_Ember.m_PixelsPerUnit = d; }); }
void Fractorium::OnScaleChanged(double d) { m_Controller->ScaleChanged(d); }
/// <summary>
/// Set the zoom value of the image.
/// Note this will increase the number of iters ran exponentially.
/// To zoom in without increasing iters, but sacrifice quality, use scale.
/// Called when zoom spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The zoom value</param>
template <typename T> void FractoriumEmberController<T>::ZoomChanged(double d) { Update([&] { m_Ember.m_Zoom = d; }); }
void Fractorium::OnZoomChanged(double d) { m_Controller->ZoomChanged(d); }
/// <summary>
/// Set the angular rotation of the image.
/// Called when rotate spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The rotation in angles</param>
template <typename T> void FractoriumEmberController<T>::RotateChanged(double d) { Update([&] { m_Ember.m_Rotate = d; }); }
void Fractorium::OnRotateChanged(double d) { m_Controller->RotateChanged(d); }
template <typename T> void FractoriumEmberController<T>::ZPosChanged(double d) { Update([&] { m_Ember.m_CamZPos = d; }); }
void Fractorium::OnZPosChanged(double d) { m_Controller->ZPosChanged(d); }
template <typename T> void FractoriumEmberController<T>::PerspectiveChanged(double d) { Update([&] { m_Ember.m_CamPerspective = d; }); }
void Fractorium::OnPerspectiveChanged(double d) { m_Controller->PerspectiveChanged(d); }
template <typename T> void FractoriumEmberController<T>::PitchChanged(double d) { Update([&] { m_Ember.m_CamPitch = d * DEG_2_RAD; }); }
void Fractorium::OnPitchChanged(double d) { m_Controller->PitchChanged(d); }
template <typename T> void FractoriumEmberController<T>::YawChanged(double d) { Update([&] { m_Ember.m_CamYaw = d * DEG_2_RAD; }); }
void Fractorium::OnYawChanged(double d) { m_Controller->YawChanged(d); }
template <typename T> void FractoriumEmberController<T>::DepthBlurChanged(double d) { Update([&] { m_Ember.m_CamDepthBlur = d; }); }
void Fractorium::OnDepthBlurChanged(double d) { m_Controller->DepthBlurChanged(d); }
/// <summary>
/// Filter.
/// </summary>
/// <summary>
/// Set the spatial filter width.
/// Called when the spatial filter width spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The spatial filter width</param>
template <typename T> void FractoriumEmberController<T>::SpatialFilterWidthChanged(double d) { Update([&] { m_Ember.m_SpatialFilterRadius = d; }); }//Must fully reset because it's used to create bounds.
void Fractorium::OnSpatialFilterWidthChanged(double d) { m_Controller->SpatialFilterWidthChanged(d); }
/// <summary>
/// Set the spatial filter type.
/// Called when the spatial filter type combo box index is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="text">The spatial filter type</param>
template <typename T> void FractoriumEmberController<T>::SpatialFilterTypeChanged(const QString& text) { Update([&] { m_Ember.m_SpatialFilterType = SpatialFilterCreator<T>::FromString(text.toStdString()); }); }//Must fully reset because it's used to create bounds.
void Fractorium::OnSpatialFilterTypeComboCurrentIndexChanged(const QString& text) { m_Controller->SpatialFilterTypeChanged(text); }
/// <summary>
/// Set the temporal filter width to be used with animation.
/// Called when the temporal filter width spinner is changed.
/// Does not reset anything because this is only used for animation.
/// In the future, when animation is implemented, this will have an effect.
/// </summary>
/// <param name="d">The temporal filter width</param>
template <typename T> void FractoriumEmberController<T>::TemporalFilterWidthChanged(double d) { Update([&] { m_Ember.m_TemporalFilterWidth = d; }, true, NOTHING); }//Don't do anything until animation is implemented.
void Fractorium::OnTemporalFilterWidthChanged(double d) { m_Controller->TemporalFilterWidthChanged(d); }
/// <summary>
/// Set the temporal filter type to be used with animation.
/// Called when the temporal filter combo box index is changed.
/// Does not reset anything because this is only used for animation.
/// In the future, when animation is implemented, this will have an effect.
/// </summary>
/// <param name="text">The name of the temporal filter</param>
template <typename T> void FractoriumEmberController<T>::TemporalFilterTypeChanged(const QString& text) { Update([&] { m_Ember.m_TemporalFilterType = TemporalFilterCreator<T>::FromString(text.toStdString()); }, true, NOTHING); }//Don't do anything until animation is implemented.
void Fractorium::OnTemporalFilterTypeComboCurrentIndexChanged(const QString& text) { m_Controller->TemporalFilterTypeChanged(text); }
/// <summary>
/// Set the density estimation filter min radius value.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The min radius value</param>
template <typename T>
void FractoriumEmberController<T>::DEFilterMinRadiusWidthChanged(double d)
{
Update([&]
{
if (m_Fractorium->m_DEFilterMinRadiusSpin->value() > m_Fractorium->m_DEFilterMaxRadiusSpin->value())
{
m_Fractorium->m_DEFilterMinRadiusSpin->setValue(m_Fractorium->m_DEFilterMaxRadiusSpin->value() - 1);
return;
}
m_Ember.m_MinRadDE = d;
});
}
void Fractorium::OnDEFilterMinRadiusWidthChanged(double d) { m_Controller->DEFilterMinRadiusWidthChanged(d); }
/// <summary>
/// Set the density estimation filter max radius value.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The max radius value</param>
template <typename T>
void FractoriumEmberController<T>::DEFilterMaxRadiusWidthChanged(double d)
{
Update([&]
{
if (m_Fractorium->m_DEFilterMaxRadiusSpin->value() < m_Fractorium->m_DEFilterMinRadiusSpin->value())
{
m_Fractorium->m_DEFilterMaxRadiusSpin->setValue(m_Fractorium->m_DEFilterMinRadiusSpin->value() + 1);
return;
}
m_Ember.m_MaxRadDE = d;
});
}
void Fractorium::OnDEFilterMaxRadiusWidthChanged(double d) { m_Controller->DEFilterMaxRadiusWidthChanged(d); }
/// <summary>
/// Set the density estimation filter curve value.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The curve value</param>
template <typename T> void FractoriumEmberController<T>::DEFilterCurveWidthChanged(double d) { Update([&] { m_Ember.m_CurveDE = d; }); }
void Fractorium::OnDEFilterCurveWidthChanged(double d) { m_Controller->DEFilterCurveWidthChanged(d); }
/// <summary>
/// Iteration.
/// </summary>
/// <summary>
/// Set the number of passes.
/// This is a feature that is mostly useless and unused, and may even be removed soon.
/// It should never be set to a value greater than 1.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The passes value</param>
template <typename T> void FractoriumEmberController<T>::PassesChanged(int i) { Update([&] { m_Ember.m_Passes = i; }); }
void Fractorium::OnPassesChanged(int d) { m_Controller->PassesChanged(d); }
/// <summary>
/// Set the temporal samples to be used with animation.
/// Called when the temporal samples spinner is changed.
/// Does not reset anything because this is only used for animation.
/// In the future, when animation is implemented, this will have an effect.
/// </summary>
/// <param name="d">The temporal samples value</param>
template <typename T> void FractoriumEmberController<T>::TemporalSamplesChanged(int i) { Update([&] { m_Ember.m_TemporalSamples = i; }, true, NOTHING); }//Don't do anything until animation is implemented.
void Fractorium::OnTemporalSamplesChanged(int d) { m_Controller->TemporalSamplesChanged(d); }
/// <summary>
/// Set the quality.
/// 10 is good for interactive rendering on the CPU.
/// 20-50 is good for OpenCL.
/// Above 500 seems to offer little additional value for final renders.
/// Called when the quality spinner is changed.
/// If rendering is done, and the value is greater than the last value,
/// the rendering process is continued, else it's reset.
/// </summary>
/// <param name="d">The quality in terms of iterations per pixel</param>
template <typename T> void FractoriumEmberController<T>::QualityChanged(double d) { Update([&] { m_Ember.m_Quality = d; }, true, d > m_Ember.m_Quality ? KEEP_ITERATING : FULL_RENDER); }
void Fractorium::OnQualityChanged(double d) { m_Controller->QualityChanged(d); }
/// <summary>
/// Set the supersample.
/// Note this will dramatically degrade performance, especially in
/// OpenCL, while only giving a minor improvement in visual quality.
/// Values above 2 add no noticeable difference.
/// The user should only use this for a final render, or a quick preview, and then
/// reset it back to 1 when done.
/// Called when the supersample spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The supersample value to set</param>
template <typename T> void FractoriumEmberController<T>::SupersampleChanged(int d) { Update([&] { m_Ember.m_Supersample = d; }); }
void Fractorium::OnSupersampleChanged(int d) { m_Controller->SupersampleChanged(d); }
/// <summary>
/// Set the affine interpolation type.
/// Does not reset anything because this is only used for animation.
/// In the future, when animation is implemented, this will have an effect.
/// Called when the affine interp type combo box index is changed.
/// </summary>
/// <param name="index">The index</param>
template <typename T>
void FractoriumEmberController<T>::AffineInterpTypeChanged(int i)
{
Update([&]
{
if (i == 0)
m_Ember.m_AffineInterp = INTERP_LINEAR;
else if (i == 1)
m_Ember.m_AffineInterp = INTERP_LOG;
else
m_Ember.m_AffineInterp = INTERP_LINEAR;
}, true, NOTHING);
}
void Fractorium::OnAffineInterpTypeComboCurrentIndexChanged(int index) { m_Controller->AffineInterpTypeChanged(index); }
/// <summary>
/// Set the interpolation type.
/// Does not reset anything because this is only used for animation.
/// In the future, when animation is implemented, this will have an effect.
/// Called when the interp type combo box index is changed.
/// </summary>
/// <param name="i">The index</param>
template <typename T>
void FractoriumEmberController<T>::InterpTypeChanged(int i)
{
Update([&]
{
if (i == 0)
m_Ember.m_Interp = EMBER_INTERP_LINEAR;
else if (i == 1)
m_Ember.m_Interp = EMBER_INTERP_SMOOTH;
else
m_Ember.m_Interp = EMBER_INTERP_LINEAR;
}, true, NOTHING);
}
void Fractorium::OnInterpTypeComboCurrentIndexChanged(int index) { m_Controller->InterpTypeChanged(index); }
/// <summary>
/// Set the center.
/// This updates the spinners as well as the current ember center.
/// Resets the renering process.
/// </summary>
/// <param name="x">The x offset</param>
/// <param name="y">The y offset</param>
template <typename T>
void FractoriumEmberController<T>::SetCenter(double x, double y)
{
m_Ember.m_CenterX = x;
m_Ember.m_CenterY = y;
m_Fractorium->m_CenterXSpin->SetValueStealth(x);//Don't trigger a redraw twice.
m_Fractorium->m_CenterYSpin->SetValueStealth(y);
if (m_Renderer.get())//On startup, this will be null at first because a resize takes place before the rendering thread is started.
CenterXChanged(m_Ember.m_CenterX);//Trigger update using both new values.
}
/// <summary>
/// Fill the parameter tables and palette widgets with values from the current ember.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::FillParamTablesAndPalette()
{
m_Fractorium->m_BrightnessSpin->SetValueStealth(m_Ember.m_Brightness);//Color.
m_Fractorium->m_GammaSpin->SetValueStealth(m_Ember.m_Gamma);
m_Fractorium->m_GammaThresholdSpin->SetValueStealth(m_Ember.m_GammaThresh);
m_Fractorium->m_VibrancySpin->SetValueStealth(m_Ember.m_Vibrancy);
m_Fractorium->m_HighlightSpin->SetValueStealth(m_Ember.m_HighlightPower);
m_Fractorium->m_ColorDialog->setCurrentColor(QColor(m_Ember.m_Background.r * 255, m_Ember.m_Background.g * 255, m_Ember.m_Background.b * 255));
m_Fractorium->ui.ColorTable->item(5, 1)->setBackgroundColor(m_Fractorium->m_ColorDialog->currentColor());
m_Fractorium->m_PaletteModeCombo->SetCurrentIndexStealth((int)m_Ember.m_PaletteMode);
m_Fractorium->m_WidthSpin->SetValueStealth(m_Ember.m_FinalRasW);//Geometry.
m_Fractorium->m_HeightSpin->SetValueStealth(m_Ember.m_FinalRasH);
m_Fractorium->m_CenterXSpin->SetValueStealth(m_Ember.m_CenterX);
m_Fractorium->m_CenterYSpin->SetValueStealth(m_Ember.m_CenterY);
m_Fractorium->m_ScaleSpin->SetValueStealth(m_Ember.m_PixelsPerUnit);
m_Fractorium->m_RotateSpin->SetValueStealth(m_Ember.m_Rotate);
m_Fractorium->m_ZPosSpin->SetValueStealth(m_Ember.m_CamZPos);
m_Fractorium->m_PerspectiveSpin->SetValueStealth(m_Ember.m_CamPerspective);
m_Fractorium->m_PitchSpin->SetValueStealth(m_Ember.m_CamPitch * RAD_2_DEG_T);
m_Fractorium->m_YawSpin->SetValueStealth(m_Ember.m_CamYaw * RAD_2_DEG_T);
m_Fractorium->m_DepthBlurSpin->SetValueStealth(m_Ember.m_CamDepthBlur);
m_Fractorium->m_SpatialFilterWidthSpin->SetValueStealth(m_Ember.m_SpatialFilterRadius);//Filter.
m_Fractorium->m_SpatialFilterTypeCombo->SetCurrentIndexStealth((int)m_Ember.m_SpatialFilterType);
m_Fractorium->m_TemporalFilterWidthSpin->SetValueStealth(m_Ember.m_TemporalFilterWidth);
m_Fractorium->m_TemporalFilterTypeCombo->SetCurrentIndexStealth((int)m_Ember.m_TemporalFilterType);
m_Fractorium->m_DEFilterMinRadiusSpin->SetValueStealth(m_Ember.m_MinRadDE);
m_Fractorium->m_DEFilterMaxRadiusSpin->SetValueStealth(m_Ember.m_MaxRadDE);
m_Fractorium->m_DECurveSpin->SetValueStealth(m_Ember.m_CurveDE);
m_Fractorium->m_PassesSpin->SetValueStealth(m_Ember.m_Passes);//Iteration.
m_Fractorium->m_TemporalSamplesSpin->SetValueStealth(m_Ember.m_TemporalSamples);
m_Fractorium->m_QualitySpin->SetValueStealth(m_Ember.m_Quality);
m_Fractorium->m_SupersampleSpin->SetValueStealth(m_Ember.m_Supersample);
m_Fractorium->m_AffineInterpTypeCombo->SetCurrentIndexStealth(m_Ember.m_AffineInterp);
m_Fractorium->m_InterpTypeCombo->SetCurrentIndexStealth(m_Ember.m_Interp);
//Palette.
m_Fractorium->ResetPaletteControls();
m_Fractorium->m_PaletteHueSpin->SetValueStealth(NormalizeDeg180<double>(m_Ember.m_Hue * 360.0));//Convert -0.5 to 0.5 range to -180 - 180.
//Use -1 as a placeholder to mean either generate a random palette from the list or
//to just use the values "as-is" without looking them up in the list.
if (m_Ember.m_Palette.m_Index >= 0)
{
m_Fractorium->OnPaletteCellClicked(Clamp<int>(m_Ember.m_Palette.m_Index, 0, m_Fractorium->ui.PaletteListTable->rowCount() - 1), 1);
}
else
{
//An ember with an embedded palette was loaded, rather than one from the list, so assign it directly to the controls without applying adjustments.
//Normally, temp palette is assigned whenever the user clicks on a palette cell. But since that is skipped here just make a copy of the ember's palette.
m_TempPalette = m_Ember.m_Palette;
UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Will clear name string since embedded palettes have no name. This will trigger a full render.
}
}
/// <summary>
/// Copy all GUI widget values on the parameters tab to the passed in ember.
/// </summary>
/// <param name="ember">The ember to copy values to.</param>
template <typename T>
void FractoriumEmberController<T>::ParamsToEmber(Ember<T>& ember)
{
QColor color = m_Fractorium->ui.ColorTable->item(5, 1)->backgroundColor();
ember.m_Brightness = m_Fractorium->m_BrightnessSpin->value();//Color.
ember.m_Gamma = m_Fractorium->m_GammaSpin->value();
ember.m_GammaThresh = m_Fractorium->m_GammaThresholdSpin->value();
ember.m_Vibrancy = m_Fractorium->m_VibrancySpin->value();
ember.m_HighlightPower = m_Fractorium->m_HighlightSpin->value();
ember.m_Background.r = color.red() / 255.0;
ember.m_Background.g = color.green() / 255.0;
ember.m_Background.b = color.blue() / 255.0;
ember.m_PaletteMode = (ePaletteMode)m_Fractorium->m_PaletteModeCombo->currentIndex();
ember.m_FinalRasW = m_Fractorium->m_WidthSpin->value();//Geometry.
ember.m_FinalRasH = m_Fractorium->m_HeightSpin->value();
ember.m_CenterX = m_Fractorium->m_CenterXSpin->value();
ember.m_CenterY = m_Fractorium->m_CenterYSpin->value();
ember.m_PixelsPerUnit = m_Fractorium->m_ScaleSpin->value();
ember.m_Rotate = m_Fractorium->m_RotateSpin->value();
ember.m_CamZPos = m_Fractorium->m_ZPosSpin->value();
ember.m_CamPerspective = m_Fractorium->m_PerspectiveSpin->value();
ember.m_CamPitch = m_Fractorium->m_PitchSpin->value() * DEG_2_RAD_T;
ember.m_CamYaw = m_Fractorium->m_YawSpin->value() * DEG_2_RAD_T;
ember.m_CamDepthBlur = m_Fractorium->m_DepthBlurSpin->value();
ember.m_SpatialFilterRadius = m_Fractorium->m_SpatialFilterWidthSpin->value();//Filter.
ember.m_SpatialFilterType = (eSpatialFilterType)m_Fractorium->m_SpatialFilterTypeCombo->currentIndex();
ember.m_TemporalFilterWidth = m_Fractorium->m_TemporalFilterWidthSpin->value();
ember.m_TemporalFilterType = (eTemporalFilterType)m_Fractorium->m_TemporalFilterTypeCombo->currentIndex();
ember.m_MinRadDE = m_Fractorium->m_DEFilterMinRadiusSpin->value();
ember.m_MaxRadDE = m_Fractorium->m_DEFilterMaxRadiusSpin->value();
ember.m_CurveDE = m_Fractorium->m_DECurveSpin->value();
ember.m_Passes = m_Fractorium->m_PassesSpin->value();
ember.m_TemporalSamples = m_Fractorium->m_TemporalSamplesSpin->value();
ember.m_Quality = m_Fractorium->m_QualitySpin->value();
ember.m_Supersample = m_Fractorium->m_SupersampleSpin->value();
ember.m_AffineInterp = (eAffineInterp)m_Fractorium->m_AffineInterpTypeCombo->currentIndex();
ember.m_Interp = (eInterp)m_Fractorium->m_InterpTypeCombo->currentIndex();
}
/// <summary>
/// Set the rotation.
/// This updates the spinner, optionally stealth.
/// </summary>
/// <param name="rot">The rotation value in angles to set</param>
/// <param name="stealth">True if stealth to skip re-rendering, else false to trigger a new render</param>
void Fractorium::SetRotation(double rot, bool stealth)
{
if (stealth)
m_RotateSpin->SetValueStealth(rot);
else
m_RotateSpin->setValue(rot);
}
/// <summary>
/// Set the scale.
/// This is the number of raster pixels that correspond to the distance
/// between 0-1 in the cartesian plane. The higher the number, the more
/// zoomed in the image is.
/// Resets the rendering process.
/// </summary>
/// <param name="scale">The scale value</param>
void Fractorium::SetScale(double scale)
{
m_ScaleSpin->setValue(scale);
}

View File

@ -0,0 +1 @@
#include "FractoriumPch.h"

View File

@ -0,0 +1,44 @@
#pragma once
#define GL_GLEXT_PROTOTYPES
#define GLM_FORCE_INLINE 1
#include "Renderer.h"
#include "RendererCL.h"
#include "VariationList.h"
#include "OpenCLWrapper.h"
#include "XmlToEmber.h"
#include "EmberToXml.h"
#include "SheepTools.h"
#include "JpegUtils.h"
#include "EmberCommon.h"
#include <deque>
#undef QT_OPENGL_ES_2//Make absolutely sure OpenGL ES is not used.
#define QT_NO_OPENGL_ES_2
#include <QtWidgets>
#include <QLineEdit>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QPushButton>
#include <QComboBox>
#include <QColorDialog>
#include <QTreeWidget>
#include <QWheelEvent>
#include <QItemDelegate>
#include <QApplication>
#include <QSettings>
#include <QGLWidget>
#include <QOpenGLFunctions_2_0.h>
#include <QtWidgets/QMainWindow>
#include <QFuture>
#include <QtConcurrentRun>
#define GLM_FORCE_RADIANS
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
using namespace std;
using namespace EmberNs;
using namespace EmberCLns;

View File

@ -0,0 +1,630 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Return whether the render timer is running.
/// </summary>
/// <returns>True if running, else false.</returns>
bool FractoriumEmberControllerBase::RenderTimerRunning()
{
return m_RenderTimer && m_RenderTimer->isActive();
}
/// <summary>
/// Start the render timer.
/// If a renderer has not been created yet, it will be created from the options.
/// </summary>
void FractoriumEmberControllerBase::StartRenderTimer()
{
if (m_RenderTimer)
{
UpdateRender();
m_RenderTimer->start();
m_RenderElapsedTimer.Tic();
}
}
/// <summary>
/// Start the render timer after a short delay.
/// If the timer is already running, stop it first.
/// This is useful for stopping and restarting the render
/// process in response to things like a window resize.
/// </summary>
void FractoriumEmberControllerBase::DelayedStartRenderTimer()
{
DeleteRenderer();
if (m_RenderRestartTimer)
{
m_RenderRestartTimer->setSingleShot(true);
m_RenderRestartTimer->start(300);//Will stop the timer if it's already running, and start again.
}
}
/// <summary>
/// Stop the render timer and abort the rendering process.
/// Optionally block until stopping is complete.
/// </summary>
/// <param name="wait">True to block, else false.</param>
void FractoriumEmberControllerBase::StopRenderTimer(bool wait)
{
if (m_RenderTimer)
m_RenderTimer->stop();
if (m_Renderer.get())
m_Renderer->Abort();
if (wait)
{
while (m_Rendering || RenderTimerRunning() || (Renderer() && (!m_Renderer->Aborted() || m_Renderer->InRender())))
QApplication::processEvents();
}
}
/// <summary>
/// Stop all timers, rendering and drawing and block until they are done.
/// </summary>
void FractoriumEmberControllerBase::Shutdown()
{
StopRenderTimer(true);
while(m_Fractorium->ui.GLDisplay->Drawing())
QApplication::processEvents();
}
/// <summary>
/// Update the state of the renderer.
/// Upon changing values, some intelligence is used to avoid blindly restarting the
/// entire iteration proceess every time a value changes. This is because some values don't affect the
/// iteration, and only affect filtering and final accumulation. They are broken into three categories:
/// 1) Restart the entire process.
/// 2) Log/density filter, then final accum.
/// 3) Final accum only.
/// 4) Continue iterating.
/// </summary>
/// <param name="action">The action to take</param>
void FractoriumEmberControllerBase::UpdateRender(eProcessAction action)
{
AddProcessAction(action);
m_RenderElapsedTimer.Tic();
}
/// <summary>
/// Call Shutdown() then delete the renderer and clear the textures in the output window if there is one.
/// </summary>
void FractoriumEmberControllerBase::DeleteRenderer()
{
Shutdown();
m_Renderer.reset();
if (GLController())
GLController()->ClearWindow();
}
/// <summary>
/// Save the current contents of the GL window to a file.
/// This could benefit from QImageWriter, however it's compression capabilities are
/// severely lacking. A Png file comes out larger than a bitmap, so instead use the
/// Png and Jpg wrapper functions from the command line programs.
/// This will embed the id, url and nick fields from the options in the image comments.
/// </summary>
/// <param name="filename">The full path and filename</param>
void FractoriumEmberControllerBase::SaveCurrentRender(QString filename)
{
if (filename != "")
{
bool b = false;
unsigned int i, j;
unsigned int width = m_Renderer->FinalRasW();
unsigned int height = m_Renderer->FinalRasH();
unsigned char* data = NULL;
vector<unsigned char> vecRgb;
QFileInfo fileInfo(filename);
QString suffix = fileInfo.suffix();
FractoriumSettings* settings = m_Fractorium->m_Settings;
RendererCLBase* rendererCL = dynamic_cast<RendererCLBase*>(m_Renderer.get());
if (rendererCL && m_Renderer->PrepFinalAccumVector(m_FinalImage))
{
if (!rendererCL->ReadFinal(m_FinalImage.data()))
{
QMessageBox::critical(m_Fractorium, "GPU Read Error", "Could not read image from the GPU, aborting image save.");
return;
}
}
data = m_FinalImage.data();//Png and channels = 4.
if ((suffix == "jpg" || suffix == "bmp") && m_Renderer->NumChannels() == 4)
{
EmberNs::RgbaToRgb(m_FinalImage, vecRgb, width, height);
data = vecRgb.data();
}
string s = filename.toStdString();
string id = settings->Id().toStdString();
string url = settings->Url().toStdString();
string nick = settings->Nick().toStdString();
EmberImageComments comments = m_Renderer->ImageComments(0, false, true);
if (suffix == "png")
b = WritePng(s.c_str(), data, width, height, 1, true, comments, id, url, nick);
else if (suffix == "jpg")
b = WriteJpeg(s.c_str(), data, width, height, 100, true, comments, id, url, nick);
else if (suffix == "bmp")
b = WriteBmp(s.c_str(), data, width, height);
else
QMessageBox::critical(m_Fractorium, "Save Failed", "Unrecognized format " + suffix + ", not saving.");
if (b)
settings->SaveFolder(fileInfo.canonicalPath());
else
QMessageBox::critical(m_Fractorium, "Save Failed", "Could not save file, try saving to a different folder.");
}
}
/// <summary>
/// Add a process action to the list of actions to take.
/// Called in response to the user changing something on the GUI.
/// </summary>
/// <param name="action">The action for the renderer to take</param>
void FractoriumEmberControllerBase::AddProcessAction(eProcessAction action)
{
m_Cs.Enter();
m_ProcessActions.push_back(action);
if (m_Renderer.get())
m_Renderer->Abort();
m_Cs.Leave();
}
/// <summary>
/// Condense and clear the process actions into a single action and return.
/// Many actions may be specified, but only the one requiring the greatest amount
/// of processing matters. Extract and return the greatest and clear the vector.
/// </summary>
/// <returns>The most significant processing action desired</returns>
eProcessAction FractoriumEmberControllerBase::CondenseAndClearProcessActions()
{
m_Cs.Enter();
eProcessAction action = NOTHING;
for (size_t i = 0; i < m_ProcessActions.size(); i++)
if (m_ProcessActions[i] > action)
action = m_ProcessActions[i];
m_ProcessActions.clear();
m_Cs.Leave();
return action;
}
/// <summary>
/// Render progress callback function to update progress bar.
/// </summary>
/// <param name="ember">The ember currently being rendered</param>
/// <param name="foo">An extra dummy parameter</param>
/// <param name="fraction">The progress fraction from 0-100</param>
/// <param name="stage">The stage of iteration. 1 is iterating, 2 is density filtering, 2 is final accumulation.</param>
/// <param name="etaMs">The estimated milliseconds to completion of the current stage</param>
/// <returns>0 if the user has changed anything on the GUI, else 1 to continue rendering.</returns>
template <typename T>
int FractoriumEmberController<T>::ProgressFunc(Ember<T>& ember, void* foo, double fraction, int stage, double etaMs)
{
QString status;
m_Fractorium->m_ProgressBar->setValue((int)fraction);//Only really applies to iter and filter, because final accum only gives progress 0 and 100.
if (stage == 0)
status = "Iterating";
else if (stage == 1)
status = "Density Filtering";
else if (stage == 2)
status = "Spatial Filtering + Final Accumulation";
m_Fractorium->m_RenderStatusLabel->setText(status);
return m_ProcessActions.empty() ? 1 : 0;//If they've done anything, abort.
}
/// <summary>
/// Clear the undo list as well as the undo/redo index and state.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::ClearUndo()
{
m_UndoIndex = 0;
m_UndoList.clear();
m_EditState = REGULAR_EDIT;
m_LastEditWasUndoRedo = false;
m_Fractorium->ui.ActionUndo->setEnabled(false);
m_Fractorium->ui.ActionRedo->setEnabled(false);
}
/// <summary>
/// The main rendering function.
/// Called whenever the event loop is idle.
/// </summary>
template <typename T>
bool FractoriumEmberController<T>::Render()
{
bool success = true;
m_Rendering = true;
GLWidget* gl = m_Fractorium->ui.GLDisplay;
eProcessAction action = CondenseAndClearProcessActions();
//Set dimensions first.
if ((m_Ember.m_FinalRasW != gl->width() ||
m_Ember.m_FinalRasH != gl->height()))
{
m_Ember.SetSizeAndAdjustScale(gl->width(), gl->height(), false, SCALE_WIDTH);
m_Fractorium->m_ScaleSpin->SetValueStealth(m_Ember.m_PixelsPerUnit);
action = FULL_RENDER;
}
//Force temporal samples to always be 1. Perhaps change later when animation is implemented.
m_Ember.m_TemporalSamples = 1;
//Take care of solo xforms and set the current ember and action.
if (action != NOTHING)
{
int i, solo = m_Fractorium->ui.CurrentXformCombo->property("soloxform").toInt();
if (solo != -1)
{
m_TempOpacities.resize(m_Ember.TotalXformCount());
for (i = 0; i < m_Ember.TotalXformCount(); i++)
{
m_TempOpacities[i] = m_Ember.GetTotalXform(i)->m_Opacity;
m_Ember.GetTotalXform(i)->m_Opacity = i == solo ? 1 : 0;
}
}
m_Renderer->SetEmber(m_Ember, action);
if (solo != -1)
{
for (i = 0; i < m_Ember.TotalXformCount(); i++)
{
m_Ember.GetTotalXform(i)->m_Opacity = m_TempOpacities[i];
}
}
}
//Determining if a completely new rendering process is being started.
bool iterBegin = ProcessState() == NONE;
if (iterBegin)
{
if (m_Renderer->RendererType() == CPU_RENDERER)
m_SubBatchCount = m_Fractorium->m_Settings->CpuSubBatch();
else if (m_Renderer->RendererType() == OPENCL_RENDERER)
m_SubBatchCount = m_Fractorium->m_Settings->OpenCLSubBatch();
m_Fractorium->m_ProgressBar->setValue(0);
m_Fractorium->m_RenderStatusLabel->setText("Starting");
}
//If the rendering process hasn't finished, render with the current specified action.
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.
{
//The amount to increment sub batch while rendering proceeds is purely empirical.
//Change later if better values can be derived/observed.
if (m_Renderer->RendererType() == OPENCL_RENDERER)
{
if (m_SubBatchCount < 3)//More than 3 with OpenCL gives a sluggish UI.
m_SubBatchCount++;
}
else
{
if (m_SubBatchCount < 5)
m_SubBatchCount++;
else if (m_SubBatchCount < 105)//More than 105 with CPU gives a sluggish UI.
m_SubBatchCount += 25;
}
//Rendering has finished, update final stats.
if (ProcessState() == ACCUM_DONE)
{
EmberStats stats = m_Renderer->Stats();
m_Fractorium->m_ProgressBar->setValue(100);
QString iters = QLocale(QLocale::English).toString(stats.m_Iters);
QString scaledQuality = QString::number((unsigned int)m_Renderer->ScaledQuality());
string renderTime = m_RenderElapsedTimer.Format(m_RenderElapsedTimer.Toc());
//Only certain status can be reported with OpenCL.
if (m_Renderer->RendererType() == OPENCL_RENDERER)
{
m_Fractorium->m_RenderStatusLabel->setText("Iters: " + iters + ". Scaled quality: " + scaledQuality + ". Total time: " + QString::fromStdString(renderTime));
}
else
{
double percent = (double)stats.m_Badvals / (double)stats.m_Iters;
QString badVals = QLocale(QLocale::English).toString(stats.m_Badvals);
QString badPercent = QLocale(QLocale::English).toString(percent * 100, 'f', 2);
m_Fractorium->m_RenderStatusLabel->setText("Iters: " + iters + ". Scaled quality: " + scaledQuality + ". Bad values: " + badVals + " (" + badPercent + "%). Total time: " + QString::fromStdString(renderTime));
}
if (m_LastEditWasUndoRedo && (m_UndoIndex == m_UndoList.size() - 1))//Traversing through undo list, reached the end, so put back in regular edit mode.
{
m_EditState = REGULAR_EDIT;
}
else if (m_EditState == REGULAR_EDIT)//Regular edit, just add to the end of the undo list.
{
m_UndoList.push_back(m_Ember);
m_UndoIndex = m_UndoList.size() - 1;
m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1);
m_Fractorium->ui.ActionRedo->setEnabled(false);
if (m_UndoList.size() >= UNDO_SIZE)
m_UndoList.pop_front();
}
else if (!m_LastEditWasUndoRedo && m_UndoIndex != m_UndoList.size() - 1)//They were in the middle of the undo list, then did a manual edit, so clear the undo list.
{
ClearUndo();
m_UndoList.push_back(m_Ember);
}
m_LastEditWasUndoRedo = false;
m_Fractorium->UpdateHistogramBounds();//Mostly of engineering interest.
}
//Update the GL window on start because the output will be forced.
//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.
gl->repaint();
//Uncomment for debugging kernel build and execution errors.
//m_Fractorium->ui.InfoRenderingTextEdit->setText(QString::fromStdString(m_Fractorium->m_Wrapper.DumpInfo()));
//if (RendererCL<T>* rendererCL = dynamic_cast<RendererCL<T>*>(m_Renderer.get()))
// m_Fractorium->ui.InfoRenderingTextEdit->setText(QString::fromStdString(rendererCL->IterKernel()));
}
}
else//Something went very wrong, show error report.
{
vector<string> errors = m_Renderer->ErrorReport();
success = false;
m_FailedRenders++;
m_Fractorium->m_RenderStatusLabel->setText("Rendering failed, see info tab. Try changing parameters.");
m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoRenderingTextEdit);
m_Renderer->ClearErrorReport();
if (m_FailedRenders >= 3)
{
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.data(), 0, m_FinalImage.size());
if (m_Renderer->RendererType() == OPENCL_RENDERER)
((RendererCL<T>*)m_Renderer.get())->ClearFinal();
m_GLController->ClearWindow();
}
}
}
//Upon finishing, or having nothing to do, rest.
if (ProcessState() == ACCUM_DONE)
QThread::msleep(1);
m_Rendering = false;
return success;
}
/// <summary>
/// Stop rendering and initialize a new renderer, using the specified type.
/// Rendering will be left in a stopped state. The caller is responsible for restarting the render loop again.
/// </summary>
/// <param name="renderType">The type of render to create</param>
/// <param name="platform">The index platform of the platform to use</param>
/// <param name="device">The index device of the device to use</param>
/// <param name="outputTexID">The texture ID of the shared OpenGL texture if shared</param>
/// <param name="shared">True if shared with OpenGL, else false. Default: true.</param>
/// <returns>True if nothing went wrong, else false.</returns>
template <typename T>
bool FractoriumEmberController<T>::CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared)
{
bool ok = true;
FractoriumSettings* s = m_Fractorium->m_Settings;
GLWidget* gl = m_Fractorium->ui.GLDisplay;
if (!m_Renderer.get() || (m_Renderer->RendererType() != renderType) || (m_Platform != platform) || (m_Device != device))
{
EmberReport emberReport;
vector<string> errorReport;
DeleteRenderer();//Delete the renderer and refresh the textures.
//Before starting, must take care of allocations.
gl->Allocate(true);//Forcing a realloc of the texture is necessary on AMD, but not on nVidia.
m_Renderer = auto_ptr<EmberNs::RendererBase>(::CreateRenderer<T, T>(renderType, platform, device, shared, gl->OutputTexID(), emberReport));
errorReport = emberReport.ErrorReport();
if (errorReport.empty())
{
m_Platform = platform;//Store values for re-creation later on.
m_Device = device;
m_OutputTexID = gl->OutputTexID();
m_Shared = shared;
}
else
{
ok = false;
QMessageBox::critical(m_Fractorium, "Renderer Creation Error", "Could not create requested renderer, fallback CPU renderer created. See info tab for details.");
m_Fractorium->ErrorReportToQTextEdit(errorReport, m_Fractorium->ui.InfoRenderingTextEdit);
}
}
if (m_Renderer.get())
{
m_RenderType = m_Renderer->RendererType();
if (m_RenderType == OPENCL_RENDERER && m_Fractorium->m_QualitySpin->value() < 30)
m_Fractorium->m_QualitySpin->setValue(30);
m_Renderer->Callback(this);
m_Renderer->NumChannels(4);//Always using 4 since the GL texture is RGBA.
m_Renderer->ReclaimOnResize(true);
m_Renderer->SetEmber(m_Ember);//Give it an initial ember, will be updated many times later.
m_Renderer->EarlyClip(s->EarlyClip());
m_Renderer->ThreadCount(s->ThreadCount());
m_Renderer->Transparency(s->Transparency());
if (m_Renderer->RendererType() == CPU_RENDERER)
m_Renderer->InteractiveFilter(s->CpuDEFilter() ? FILTER_DE : FILTER_LOG);
else
m_Renderer->InteractiveFilter(s->OpenCLDEFilter() ? FILTER_DE : FILTER_LOG);
m_FailedRenders = 0;
m_RenderElapsedTimer.Tic();
//Leave rendering in a stopped state. The caller is responsible for restarting the render loop again.
}
else
{
ok = false;
QMessageBox::critical(m_Fractorium, "Renderer Creation Error", "Creating a basic CPU renderer failed, something is catastrophically wrong. Exiting program.");
QApplication::quit();
}
return ok;
}
/// <summary>
/// Create a new renderer from the options.
/// </summary>
/// <returns>True if nothing went wrong, else false.</returns>
bool Fractorium::CreateRendererFromOptions()
{
bool ok = true;
bool useOpenCL = m_Wrapper.CheckOpenCL() && m_Settings->OpenCL();
//The most important option to process is what kind of renderer is desired, so do it first.
if (!m_Controller->CreateRenderer(useOpenCL ? OPENCL_RENDERER : CPU_RENDERER,
m_Settings->PlatformIndex(),
m_Settings->DeviceIndex()))
{
//If using OpenCL, will only get here if creating RendererCL failed, but creating a backup CPU Renderer succeeded.
QMessageBox::critical(this, "Renderer Creation Error", "Error creating renderer, most likely a GPU problem. Using CPU instead.");
m_Settings->OpenCL(false);
m_OptionsDialog->ui.OpenCLCheckBox->setChecked(false);
m_FinalRenderDialog->ui.FinalRenderOpenCLCheckBox->setChecked(false);
ok = false;
}
return ok;
}
/// <summary>
/// Create a new controller from the options.
/// This does not create the internal renderer or start the timers.
/// </summary>
/// <returns>True if successful, else false.</returns>
bool Fractorium::CreateControllerFromOptions()
{
bool ok = true;
size_t size =
#ifdef DO_DOUBLE
m_Settings->Double() ? sizeof(double) :
#endif
sizeof(float);
if (!m_Controller.get() || (m_Controller->SizeOfT() != size))
{
double hue = m_PaletteHueSpin->value();
double sat = m_PaletteSaturationSpin->value();
double bright = m_PaletteBrightnessSpin->value();
double con = m_PaletteContrastSpin->value();
double blur = m_PaletteBlurSpin->value();
double freq = m_PaletteFrequencySpin->value();
#ifdef DO_DOUBLE
Ember<double> ed;
EmberFile<double> efd;
Palette<double> tempPalette;
#else
Ember<float> ed;
EmberFile<float> efd;
Palette<float> tempPalette;
#endif
QModelIndex index = ui.LibraryTree->currentIndex();
//First check if a controller has already been created, and if so, save its embers and gracefully shut it down.
if (m_Controller.get())
{
m_Controller->CopyTempPalette(tempPalette);//Convert float to double or save double verbatim;
m_Controller->CopyEmber(ed);
m_Controller->CopyEmberFile(efd);
m_Controller->Shutdown();
}
#ifdef DO_DOUBLE
if (m_Settings->Double())
m_Controller = auto_ptr<FractoriumEmberControllerBase>(new FractoriumEmberController<double>(this));
else
#endif
m_Controller = auto_ptr<FractoriumEmberControllerBase>(new FractoriumEmberController<float>(this));
//Restore the ember and ember file.
if (m_Controller.get())
{
m_Controller->SetEmber(ed);//Convert float to double or set double verbatim;
m_Controller->SetEmberFile(efd);
//Template specific palette table and variations tree setup in controller constructor, but
//must manually setup the library tree here because it's after the embers were assigned.
m_Controller->FillLibraryTree(index.row());//Passing row re-selects the item that was previously selected.
m_Controller->SetTempPalette(tempPalette);//Restore palette.
m_PaletteHueSpin->SetValueStealth(hue);
m_PaletteSaturationSpin->SetValueStealth(sat);
m_PaletteBrightnessSpin->SetValueStealth(bright);
m_PaletteContrastSpin->SetValueStealth(con);
m_PaletteBlurSpin->SetValueStealth(blur);
m_PaletteFrequencySpin->SetValueStealth(freq);
m_Controller->PaletteAdjust();//Fills in the palette.
}
}
return m_Controller.get() != NULL;
}
/// <summary>
/// Start the render timer.
/// If a renderer has not been created yet, or differs form the options, it will first be created from the options.
/// </summary>
void Fractorium::StartRenderTimer()
{
//Starting the render timer, either for the first time
//or from a paused state, such as resizing or applying new options.
CreateControllerFromOptions();
if (m_Controller.get())
{
//On program startup, the renderer does not get initialized until now.
CreateRendererFromOptions();
if (m_Controller->Renderer())
m_Controller->StartRenderTimer();
}
}
/// <summary>
/// Idle timer event which calls the controller's Render() function.
/// </summary>
void Fractorium::IdleTimer() { m_Controller->Render(); }
/// <summary>
/// Thin wrapper to determine if the controllers have been properly initialized.
/// </summary>
/// <returns>True if the ember controller and GL controllers are both not NULL, else false.</returns>
bool Fractorium::ControllersOk() { return m_Controller.get() && m_Controller->GLController(); }

View File

@ -0,0 +1,239 @@
#include "FractoriumPch.h"
#include "FractoriumSettings.h"
/// <summary>
/// Constructor that passes the parent to the base and sets up reasonable defaults
/// if the settings file was not present or corrupted.
/// </summary>
/// <param name="parent">The parent widget</param>
FractoriumSettings::FractoriumSettings(QObject* parent)
: QSettings(QSettings::IniFormat, QSettings::UserScope, "Fractorium", "Fractorium", parent)
{
EnsureDefaults();
}
/// <summary>
/// Make sure options have reasonable values in them first.
/// </summary>
void FractoriumSettings::EnsureDefaults()
{
if (FinalWidth() == 0)
FinalWidth(1920);
if (FinalHeight() == 0)
FinalHeight(1080);
if (FinalQuality() == 0)
FinalQuality(1000);
if (FinalTemporalSamples() == 0)
FinalTemporalSamples(1000);
if (FinalSupersample() == 0)
FinalSupersample(2);
if (XmlWidth() == 0)
XmlWidth(1920);
if (XmlHeight() == 0)
XmlHeight(1080);
if (XmlTemporalSamples() == 0)
XmlTemporalSamples(1000);
if (XmlQuality() == 0)
XmlQuality(1000);
if (XmlSupersample() == 0)
XmlSupersample(2);
if (ThreadCount() == 0 || ThreadCount() > Timing::ProcessorCount())
ThreadCount(max(1, Timing::ProcessorCount() - 1));//Default to one less to keep the UI responsive for first time users.
if (FinalThreadCount() == 0 || FinalThreadCount() > Timing::ProcessorCount())
FinalThreadCount(Timing::ProcessorCount());
if (CpuSubBatch() < 1)
CpuSubBatch(10);
if (OpenCLSubBatch() < 1)
OpenCLSubBatch(1);
//There normally wouldn't be any more than 10 OpenCL platforms and devices
//on the system, so if a value greater than that is read, then the settings file
//was corrupted.
if (PlatformIndex() > 10)
PlatformIndex(0);
if (DeviceIndex() > 10)
DeviceIndex(0);
if (FinalScale() > SCALE_HEIGHT)
FinalScale(0);
if (FinalPlatformIndex() > 10)
FinalPlatformIndex(0);
if (FinalDeviceIndex() > 10)
FinalDeviceIndex(0);
if (OpenXmlExt() == "")
OpenXmlExt("Flame (*.flame)");
if (SaveXmlExt() == "")
SaveXmlExt("Flame (*.flame)");
if (OpenImageExt() == "")
OpenImageExt("Png (*.png)");
if (SaveImageExt() == "")
SaveImageExt("Png (*.png)");
if (FinalDoAllExt() != "jpg" && FinalDoAllExt() != "png")
FinalDoAllExt("png");
}
/// <summary>
/// Interactive renderer settings.
/// </summary>
bool FractoriumSettings::EarlyClip() { return value(EARLYCLIP).toBool(); }
void FractoriumSettings::EarlyClip(bool b) { setValue(EARLYCLIP, b); }
bool FractoriumSettings::Transparency() { return value(TRANSPARENCY).toBool(); }
void FractoriumSettings::Transparency(bool b) { setValue(TRANSPARENCY, b); }
bool FractoriumSettings::Double() { return value(DOUBLEPRECISION).toBool(); }
void FractoriumSettings::Double(bool b) { setValue(DOUBLEPRECISION, b); }
bool FractoriumSettings::OpenCL() { return value(OPENCL).toBool(); }
void FractoriumSettings::OpenCL(bool b) { setValue(OPENCL, b); }
unsigned int FractoriumSettings::PlatformIndex() { return value(PLATFORMINDEX).toUInt(); }
void FractoriumSettings::PlatformIndex(unsigned int i) { setValue(PLATFORMINDEX, i); }
unsigned int FractoriumSettings::DeviceIndex() { return value(DEVICEINDEX).toUInt(); }
void FractoriumSettings::DeviceIndex(unsigned int i) { setValue(DEVICEINDEX, i); }
unsigned int FractoriumSettings::ThreadCount() { return value(THREADCOUNT).toUInt(); }
void FractoriumSettings::ThreadCount(unsigned int i) { setValue(THREADCOUNT, i); }
bool FractoriumSettings::CpuDEFilter() { return value(CPUDEFILTER).toBool(); }
void FractoriumSettings::CpuDEFilter(bool b) { setValue(CPUDEFILTER, b); }
bool FractoriumSettings::OpenCLDEFilter() { return value(OPENCLDEFILTER).toBool(); }
void FractoriumSettings::OpenCLDEFilter(bool b) { setValue(OPENCLDEFILTER, b); }
unsigned int FractoriumSettings::CpuSubBatch() { return value(CPUSUBBATCH).toUInt(); }
void FractoriumSettings::CpuSubBatch(unsigned int b) { setValue(CPUSUBBATCH, b); }
unsigned int FractoriumSettings::OpenCLSubBatch() { return value(OPENCLSUBBATCH).toUInt(); }
void FractoriumSettings::OpenCLSubBatch(unsigned int b) { setValue(OPENCLSUBBATCH, b); }
/// <summary>
/// Final render settings.
/// </summary>
bool FractoriumSettings::FinalEarlyClip() { return value(FINALEARLYCLIP).toBool(); }
void FractoriumSettings::FinalEarlyClip(bool b) { setValue(FINALEARLYCLIP, b); }
bool FractoriumSettings::FinalTransparency() { return value(FINALTRANSPARENCY).toBool(); }
void FractoriumSettings::FinalTransparency(bool b) { setValue(FINALTRANSPARENCY, b); }
bool FractoriumSettings::FinalOpenCL() { return value(FINALOPENCL).toBool(); }
void FractoriumSettings::FinalOpenCL(bool b) { setValue(FINALOPENCL, b); }
bool FractoriumSettings::FinalDouble() { return value(FINALDOUBLEPRECISION).toBool(); }
void FractoriumSettings::FinalDouble(bool b) { setValue(FINALDOUBLEPRECISION, b); }
bool FractoriumSettings::FinalSaveXml() { return value(FINALSAVEXML).toBool(); }
void FractoriumSettings::FinalSaveXml(bool b) { setValue(FINALSAVEXML, b); }
bool FractoriumSettings::FinalDoAll() { return value(FINALDOALL).toBool(); }
void FractoriumSettings::FinalDoAll(bool b) { setValue(FINALDOALL, b); }
bool FractoriumSettings::FinalDoSequence() { return value(FINALDOSEQUENCE).toBool(); }
void FractoriumSettings::FinalDoSequence(bool b) { setValue(FINALDOSEQUENCE, b); }
bool FractoriumSettings::FinalKeepAspect() { return value(FINALKEEPASPECT).toBool(); }
void FractoriumSettings::FinalKeepAspect(bool b) { setValue(FINALKEEPASPECT, b); }
unsigned int FractoriumSettings::FinalScale() { return value(FINALSCALE).toUInt(); }
void FractoriumSettings::FinalScale(unsigned int i) { setValue(FINALSCALE, i); }
QString FractoriumSettings::FinalDoAllExt() { return value(FINALDOALLEXT).toString(); }
void FractoriumSettings::FinalDoAllExt(QString s) { setValue(FINALDOALLEXT, s); }
unsigned int FractoriumSettings::FinalPlatformIndex() { return value(FINALPLATFORMINDEX).toUInt(); }
void FractoriumSettings::FinalPlatformIndex(unsigned int i) { setValue(FINALPLATFORMINDEX, i); }
unsigned int FractoriumSettings::FinalDeviceIndex() { return value(FINALDEVICEINDEX).toUInt(); }
void FractoriumSettings::FinalDeviceIndex(unsigned int i) { setValue(FINALDEVICEINDEX, i); }
unsigned int FractoriumSettings::FinalThreadCount() { return value(FINALTHREADCOUNT).toUInt(); }
void FractoriumSettings::FinalThreadCount(unsigned int i) { setValue(FINALTHREADCOUNT, i); }
unsigned int FractoriumSettings::FinalWidth() { return value(FINALWIDTH).toUInt(); }
void FractoriumSettings::FinalWidth(unsigned int i) { setValue(FINALWIDTH, i); }
unsigned int FractoriumSettings::FinalHeight() { return value(FINALHEIGHT).toUInt(); }
void FractoriumSettings::FinalHeight(unsigned int i) { setValue(FINALHEIGHT, i); }
unsigned int FractoriumSettings::FinalQuality() { return value(FINALQUALITY).toUInt(); }
void FractoriumSettings::FinalQuality(unsigned int i) { setValue(FINALQUALITY, i); }
unsigned int FractoriumSettings::FinalTemporalSamples() { return value(FINALTEMPORALSAMPLES).toUInt(); }
void FractoriumSettings::FinalTemporalSamples(unsigned int i) { setValue(FINALTEMPORALSAMPLES, i); }
unsigned int FractoriumSettings::FinalSupersample() { return value(FINALSUPERSAMPLE).toUInt(); }
void FractoriumSettings::FinalSupersample(unsigned int i) { setValue(FINALSUPERSAMPLE, i); }
/// <summary>
/// Xml file saving settings.
/// </summary>
unsigned int FractoriumSettings::XmlWidth() { return value(XMLWIDTH).toUInt(); }
void FractoriumSettings::XmlWidth(unsigned int i) { setValue(XMLWIDTH, i); }
unsigned int FractoriumSettings::XmlHeight() { return value(XMLHEIGHT).toUInt(); }
void FractoriumSettings::XmlHeight(unsigned int i) { setValue(XMLHEIGHT, i); }
unsigned int FractoriumSettings::XmlTemporalSamples() { return value(XMLTEMPORALSAMPLES).toUInt(); }
void FractoriumSettings::XmlTemporalSamples(unsigned int i) { setValue(XMLTEMPORALSAMPLES, i); }
unsigned int FractoriumSettings::XmlQuality() { return value(XMLQUALITY).toUInt(); }
void FractoriumSettings::XmlQuality(unsigned int i) { setValue(XMLQUALITY, i); }
unsigned int FractoriumSettings::XmlSupersample() { return value(XMLSUPERSAMPLE).toUInt(); }
void FractoriumSettings::XmlSupersample(unsigned int i) { setValue(XMLSUPERSAMPLE, i); }
QString FractoriumSettings::Id() { return value(IDENTITYID).toString(); }
void FractoriumSettings::Id(QString s) { setValue(IDENTITYID, s); }
QString FractoriumSettings::Url() { return value(IDENTITYURL).toString(); }
void FractoriumSettings::Url(QString s) { setValue(IDENTITYURL, s); }
QString FractoriumSettings::Nick() { return value(IDENTITYNICK).toString(); }
void FractoriumSettings::Nick(QString s) { setValue(IDENTITYNICK, s); }
/// <summary>
/// General operations settings.
/// </summary>
QString FractoriumSettings::OpenFolder() { return value(OPENFOLDER).toString(); }
void FractoriumSettings::OpenFolder(QString s) { setValue(OPENFOLDER, s); }
QString FractoriumSettings::SaveFolder() { return value(SAVEFOLDER).toString(); }
void FractoriumSettings::SaveFolder(QString s) { setValue(SAVEFOLDER, s); }
QString FractoriumSettings::OpenXmlExt() { return value(OPENXMLEXT).toString(); }
void FractoriumSettings::OpenXmlExt(QString s) { setValue(OPENXMLEXT, s); }
QString FractoriumSettings::SaveXmlExt() { return value(SAVEXMLEXT).toString(); }
void FractoriumSettings::SaveXmlExt(QString s) { setValue(SAVEXMLEXT, s); }
QString FractoriumSettings::OpenImageExt() { return value(OPENIMAGEEXT).toString(); }
void FractoriumSettings::OpenImageExt(QString s) { setValue(OPENIMAGEEXT, s); }
QString FractoriumSettings::SaveImageExt() { return value(SAVEIMAGEEXT).toString(); }
void FractoriumSettings::SaveImageExt(QString s) { setValue(SAVEIMAGEEXT, s); }

View File

@ -0,0 +1,198 @@
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// FractoriumSettings class.
/// </summary>
#define EARLYCLIP "render/earlyclip"
#define TRANSPARENCY "render/transparency"
#define OPENCL "render/opencl"
#define DOUBLEPRECISION "render/dp64"
#define PLATFORMINDEX "render/platformindex"
#define DEVICEINDEX "render/deviceindex"
#define THREADCOUNT "render/threadcount"
#define CPUDEFILTER "render/cpudefilter"
#define OPENCLDEFILTER "render/opencldefilter"
#define CPUSUBBATCH "render/cpusubbatch"
#define OPENCLSUBBATCH "render/openclsubbatch"
#define FINALEARLYCLIP "finalrender/earlyclip"
#define FINALTRANSPARENCY "finalrender/transparency"
#define FINALOPENCL "finalrender/opencl"
#define FINALDOUBLEPRECISION "finalrender/dp64"
#define FINALSAVEXML "finalrender/savexml"
#define FINALDOALL "finalrender/doall"
#define FINALDOSEQUENCE "finalrender/dosequence"
#define FINALKEEPASPECT "finalrender/keepaspect"
#define FINALSCALE "finalrender/scale"
#define FINALDOALLEXT "finalrender/doallext"
#define FINALPLATFORMINDEX "finalrender/platformindex"
#define FINALDEVICEINDEX "finalrender/deviceindex"
#define FINALTHREADCOUNT "finalrender/threadcount"
#define FINALWIDTH "finalrender/width"
#define FINALHEIGHT "finalrender/height"
#define FINALQUALITY "finalrender/quality"
#define FINALTEMPORALSAMPLES "finalrender/temporalsamples"
#define FINALSUPERSAMPLE "finalrender/supersample"
#define XMLWIDTH "xml/width"
#define XMLHEIGHT "xml/height"
#define XMLTEMPORALSAMPLES "xml/temporalsamples"
#define XMLQUALITY "xml/quality"
#define XMLSUPERSAMPLE "xml/supersample"
#define OPENFOLDER "path/open"
#define SAVEFOLDER "path/save"
#define OPENXMLEXT "file/openxmlext"
#define SAVEXMLEXT "file/savexmlext"
#define OPENIMAGEEXT "file/openimageext"
#define SAVEIMAGEEXT "file/saveimageext"
#define IDENTITYID "identity/id"
#define IDENTITYURL "identity/url"
#define IDENTITYNICK "identity/nick"
/// <summary>
/// Class for preserving various program options between
/// runs of Fractorium. Each of these generally corresponds
/// to items in the options dialog and the final render dialog.
/// </summary>
class FractoriumSettings : public QSettings
{
Q_OBJECT
public:
FractoriumSettings(QObject* parent);
void EnsureDefaults();
bool EarlyClip();
void EarlyClip(bool b);
bool Transparency();
void Transparency(bool b);
bool OpenCL();
void OpenCL(bool b);
bool Double();
void Double(bool b);
unsigned int PlatformIndex();
void PlatformIndex(unsigned int b);
unsigned int DeviceIndex();
void DeviceIndex(unsigned int b);
unsigned int ThreadCount();
void ThreadCount(unsigned int b);
bool CpuDEFilter();
void CpuDEFilter(bool b);
bool OpenCLDEFilter();
void OpenCLDEFilter(bool b);
unsigned int CpuSubBatch();
void CpuSubBatch(unsigned int b);
unsigned int OpenCLSubBatch();
void OpenCLSubBatch(unsigned int b);
bool FinalEarlyClip();
void FinalEarlyClip(bool b);
bool FinalTransparency();
void FinalTransparency(bool b);
bool FinalOpenCL();
void FinalOpenCL(bool b);
bool FinalDouble();
void FinalDouble(bool b);
bool FinalSaveXml();
void FinalSaveXml(bool b);
bool FinalDoAll();
void FinalDoAll(bool b);
bool FinalDoSequence();
void FinalDoSequence(bool b);
bool FinalKeepAspect();
void FinalKeepAspect(bool b);
unsigned int FinalScale();
void FinalScale(unsigned int i);
QString FinalDoAllExt();
void FinalDoAllExt(QString s);
unsigned int FinalPlatformIndex();
void FinalPlatformIndex(unsigned int b);
unsigned int FinalDeviceIndex();
void FinalDeviceIndex(unsigned int b);
unsigned int FinalThreadCount();
void FinalThreadCount(unsigned int b);
unsigned int FinalWidth();
void FinalWidth(unsigned int i);
unsigned int FinalHeight();
void FinalHeight(unsigned int i);
unsigned int FinalQuality();
void FinalQuality(unsigned int i);
unsigned int FinalTemporalSamples();
void FinalTemporalSamples(unsigned int i);
unsigned int FinalSupersample();
void FinalSupersample(unsigned int i);
unsigned int XmlWidth();
void XmlWidth(unsigned int i);
unsigned int XmlHeight();
void XmlHeight(unsigned int i);
unsigned int XmlTemporalSamples();
void XmlTemporalSamples(unsigned int i);
unsigned int XmlQuality();
void XmlQuality(unsigned int i);
unsigned int XmlSupersample();
void XmlSupersample(unsigned int i);
QString OpenFolder();
void OpenFolder(QString s);
QString SaveFolder();
void SaveFolder(QString s);
QString OpenXmlExt();
void OpenXmlExt(QString s);
QString SaveXmlExt();
void SaveXmlExt(QString s);
QString OpenImageExt();
void OpenImageExt(QString s);
QString SaveImageExt();
void SaveImageExt(QString s);
QString Id();
void Id(QString s);
QString Url();
void Url(QString s);
QString Nick();
void Nick(QString s);
};

Some files were not shown because too many files have changed in this diff Show More