mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2026-03-09 19:30:23 -04:00
Initial source commit
Initial source commit
This commit is contained in:
352
Source/Ember/Affine2D.cpp
Normal file
352
Source/Ember/Affine2D.cpp
Normal 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
142
Source/Ember/Affine2D.h
Normal 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
252
Source/Ember/CarToRas.h
Normal 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.
|
||||
};
|
||||
}
|
||||
340
Source/Ember/DensityFilter.h
Normal file
340
Source/Ember/DensityFilter.h
Normal 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
20
Source/Ember/DllMain.cpp
Normal 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
410
Source/Ember/Ember.cpp
Normal 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
1607
Source/Ember/Ember.h
Normal file
File diff suppressed because it is too large
Load Diff
83
Source/Ember/EmberDefines.h
Normal file
83
Source/Ember/EmberDefines.h
Normal 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 };
|
||||
}
|
||||
1
Source/Ember/EmberPch.cpp
Normal file
1
Source/Ember/EmberPch.cpp
Normal file
@ -0,0 +1 @@
|
||||
#include "EmberPch.h"
|
||||
62
Source/Ember/EmberPch.h
Normal file
62
Source/Ember/EmberPch.h
Normal 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
686
Source/Ember/EmberToXml.h
Normal 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, "&", "&");
|
||||
xmlNewProp(node, XC "filename", XC filenameWithoutAmpersands.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
xmlNewProp(node, XC "filename", XC filename.c_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
1018
Source/Ember/Interpolate.h
Normal file
1018
Source/Ember/Interpolate.h
Normal file
File diff suppressed because it is too large
Load Diff
386
Source/Ember/Isaac.h
Normal file
386
Source/Ember/Isaac.h
Normal 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
541
Source/Ember/Iterator.h
Normal 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
571
Source/Ember/Palette.h
Normal 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
226
Source/Ember/PaletteList.h
Normal 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
217
Source/Ember/Point.h
Normal 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
2222
Source/Ember/Renderer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
412
Source/Ember/Renderer.h
Normal file
412
Source/Ember/Renderer.h
Normal 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
1394
Source/Ember/SheepTools.h
Normal file
File diff suppressed because it is too large
Load Diff
909
Source/Ember/SpatialFilter.h
Normal file
909
Source/Ember/SpatialFilter.h
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
347
Source/Ember/TemporalFilter.h
Normal file
347
Source/Ember/TemporalFilter.h
Normal 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
7
Source/Ember/Timing.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
#include "stdafx.h"
|
||||
#include "Timing.h"
|
||||
|
||||
namespace Flam3
|
||||
{
|
||||
|
||||
}
|
||||
231
Source/Ember/Timing.h
Normal file
231
Source/Ember/Timing.h
Normal 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
887
Source/Ember/Utils.h
Normal 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
2168
Source/Ember/Variation.h
Normal file
File diff suppressed because it is too large
Load Diff
537
Source/Ember/VariationList.h
Normal file
537
Source/Ember/VariationList.h
Normal 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
6296
Source/Ember/Variations01.h
Normal file
File diff suppressed because it is too large
Load Diff
5756
Source/Ember/Variations02.h
Normal file
5756
Source/Ember/Variations02.h
Normal file
File diff suppressed because it is too large
Load Diff
4596
Source/Ember/Variations03.h
Normal file
4596
Source/Ember/Variations03.h
Normal file
File diff suppressed because it is too large
Load Diff
5233
Source/Ember/Variations04.h
Normal file
5233
Source/Ember/Variations04.h
Normal file
File diff suppressed because it is too large
Load Diff
3253
Source/Ember/Variations05.h
Normal file
3253
Source/Ember/Variations05.h
Normal file
File diff suppressed because it is too large
Load Diff
1039
Source/Ember/VariationsDC.h
Normal file
1039
Source/Ember/VariationsDC.h
Normal file
File diff suppressed because it is too large
Load Diff
1173
Source/Ember/Xform.h
Normal file
1173
Source/Ember/Xform.h
Normal file
File diff suppressed because it is too large
Load Diff
1350
Source/Ember/XmlToEmber.h
Normal file
1350
Source/Ember/XmlToEmber.h
Normal file
File diff suppressed because it is too large
Load Diff
372
Source/EmberAnimate/EmberAnimate.cpp
Normal file
372
Source/EmberAnimate/EmberAnimate.cpp
Normal 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;
|
||||
}
|
||||
16
Source/EmberAnimate/EmberAnimate.h
Normal file
16
Source/EmberAnimate/EmberAnimate.h
Normal 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);
|
||||
98
Source/EmberAnimate/EmberAnimate.rc
Normal file
98
Source/EmberAnimate/EmberAnimate.rc
Normal 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
|
||||
|
||||
15
Source/EmberAnimate/resource.h
Normal file
15
Source/EmberAnimate/resource.h
Normal 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
|
||||
698
Source/EmberCL/DEOpenCLKernelCreator.cpp
Normal file
698
Source/EmberCL/DEOpenCLKernelCreator.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
89
Source/EmberCL/DEOpenCLKernelCreator.h
Normal file
89
Source/EmberCL/DEOpenCLKernelCreator.h
Normal 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
|
||||
}
|
||||
20
Source/EmberCL/DllMain.cpp
Normal file
20
Source/EmberCL/DllMain.cpp
Normal 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;
|
||||
}
|
||||
413
Source/EmberCL/EmberCLFunctions.h
Normal file
413
Source/EmberCL/EmberCLFunctions.h
Normal 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
|
||||
}
|
||||
39
Source/EmberCL/EmberCLPch.h
Normal file
39
Source/EmberCL/EmberCLPch.h
Normal 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
|
||||
383
Source/EmberCL/EmberCLStructs.h
Normal file
383
Source/EmberCL/EmberCLStructs.h
Normal 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";
|
||||
}
|
||||
517
Source/EmberCL/FinalAccumOpenCLKernelCreator.cpp
Normal file
517
Source/EmberCL/FinalAccumOpenCLKernelCreator.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
87
Source/EmberCL/FinalAccumOpenCLKernelCreator.h
Normal file
87
Source/EmberCL/FinalAccumOpenCLKernelCreator.h
Normal 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
|
||||
}
|
||||
785
Source/EmberCL/IterOpenCLKernelCreator.cpp
Normal file
785
Source/EmberCL/IterOpenCLKernelCreator.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
89
Source/EmberCL/IterOpenCLKernelCreator.h
Normal file
89
Source/EmberCL/IterOpenCLKernelCreator.h
Normal 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
|
||||
}
|
||||
1366
Source/EmberCL/OpenCLWrapper.cpp
Normal file
1366
Source/EmberCL/OpenCLWrapper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
219
Source/EmberCL/OpenCLWrapper.h
Normal file
219
Source/EmberCL/OpenCLWrapper.h
Normal 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;
|
||||
};
|
||||
}
|
||||
1340
Source/EmberCL/RendererCL.cpp
Normal file
1340
Source/EmberCL/RendererCL.cpp
Normal file
File diff suppressed because it is too large
Load Diff
156
Source/EmberCL/RendererCL.h
Normal file
156
Source/EmberCL/RendererCL.h
Normal 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
|
||||
}
|
||||
261
Source/EmberCommon/EmberCommon.h
Normal file
261
Source/EmberCommon/EmberCommon.h
Normal 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
|
||||
1
Source/EmberCommon/EmberCommonPch.cpp
Normal file
1
Source/EmberCommon/EmberCommonPch.cpp
Normal file
@ -0,0 +1 @@
|
||||
#include "EmberCommonPch.h"
|
||||
54
Source/EmberCommon/EmberCommonPch.h
Normal file
54
Source/EmberCommon/EmberCommonPch.h
Normal 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;
|
||||
705
Source/EmberCommon/EmberOptions.h
Normal file
705
Source/EmberCommon/EmberOptions.h
Normal 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;
|
||||
};
|
||||
362
Source/EmberCommon/JpegUtils.h
Normal file
362
Source/EmberCommon/JpegUtils.h
Normal 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;
|
||||
}
|
||||
959
Source/EmberCommon/SimpleGlob.h
Normal file
959
Source/EmberCommon/SimpleGlob.h
Normal 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
|
||||
1063
Source/EmberCommon/SimpleOpt.h
Normal file
1063
Source/EmberCommon/SimpleOpt.h
Normal file
File diff suppressed because it is too large
Load Diff
776
Source/EmberGenome/EmberGenome.cpp
Normal file
776
Source/EmberGenome/EmberGenome.cpp
Normal 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;
|
||||
}
|
||||
23
Source/EmberGenome/EmberGenome.h
Normal file
23
Source/EmberGenome/EmberGenome.h
Normal 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);
|
||||
98
Source/EmberGenome/EmberGenome.rc
Normal file
98
Source/EmberGenome/EmberGenome.rc
Normal 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
|
||||
|
||||
15
Source/EmberGenome/resource.h
Normal file
15
Source/EmberGenome/resource.h
Normal 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
|
||||
386
Source/EmberRender/EmberRender.cpp
Normal file
386
Source/EmberRender/EmberRender.cpp
Normal 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;
|
||||
}
|
||||
16
Source/EmberRender/EmberRender.h
Normal file
16
Source/EmberRender/EmberRender.h
Normal 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);
|
||||
98
Source/EmberRender/EmberRender.rc
Normal file
98
Source/EmberRender/EmberRender.rc
Normal 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
|
||||
|
||||
15
Source/EmberRender/resource.h
Normal file
15
Source/EmberRender/resource.h
Normal 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
|
||||
1671
Source/EmberTester/EmberTester.cpp
Normal file
1671
Source/EmberTester/EmberTester.cpp
Normal file
File diff suppressed because it is too large
Load Diff
10
Source/EmberTester/EmberTester.h
Normal file
10
Source/EmberTester/EmberTester.h
Normal 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>
|
||||
14
Source/Fractorium/AboutDialog.cpp
Normal file
14
Source/Fractorium/AboutDialog.cpp
Normal 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);
|
||||
}
|
||||
22
Source/Fractorium/AboutDialog.h
Normal file
22
Source/Fractorium/AboutDialog.h
Normal 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;
|
||||
};
|
||||
199
Source/Fractorium/AboutDialog.ui
Normal file
199
Source/Fractorium/AboutDialog.ui
Normal 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><html><head/><body><p align="center"><br/><span style=" font-size:12pt;">Fractorium 0.4.0.2 Beta</span></p><p align="center"><span style=" font-size:10pt;"><br/>A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL.</span></p><p align="center"><span style=" font-size:10pt;">Matt Feemster</span></p></body></html></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><html><head/><body><p><a href="http://code.google.com/p/flam3"><span style=" text-decoration: underline; color:#0000ff;">flam3</span></a>: Scott Draves, Erik Reckase (GPL v2)<br/><a href="http://github.com/stevenrobertson/cuburn"><span style=" text-decoration: underline; color:#0000ff;">cuburn</span></a>: Steven Robertson, Michael Semeniuk, Matthew Znoj, Nicolas Mejia (GPL v3)<br/><a href="http://fractron9000.sourceforge.net"><span style=" text-decoration: underline; color:#0000ff;">Fractron 9000</span></a>: Mike Thiesen (GPL)<br/><a href="http://sourceforge.net/projects/apophysis7x"><span style=" text-decoration: underline; color:#0000ff;">Apophysis</span></a>: Mark Townsend, Ronald Hordijk, Peter Sdobnov, Piotr Borys, Georg Kiehne (GPL)<br/><a href="http://jwildfire.org/"><span style=" text-decoration: underline; color:#0000ff;">JWildfire</span></a>: Andreas Maschke (LGPL)<br/>Numerous Apophysis plugin developers (GPL)</p></body></html></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><html><head/><body><p><a href="http://qt-project.org"><span style=" text-decoration: underline; color:#0000ff;">Qt</span></a>: Digia Plc (GPL v3, LGPL v2)<br/><a href="http://g-truc.net"><span style=" text-decoration: underline; color:#0000ff;">glm</span></a>: Christophe Riccio (MIT License)<br/><a href="http://threadingbuildingblocks.org"><span style=" text-decoration: underline; color:#0000ff;">Threading Building Blocks</span></a>: Intel Corporation (GPLv2)<br/><a href="http://libjpeg.sourceforge.net"><span style=" text-decoration: underline; color:#0000ff;">libjpeg</span></a>: Independent JPEG Group (Free Software License)<br/><a href="http://libpng.org"><span style=" text-decoration: underline; color:#0000ff;">libpng</span></a>: Glenn Randers-Pehrson et al (Libpng License)<br/><a href="http://xmlsoft.org"><span style=" text-decoration: underline; color:#0000ff;">libxml2</span></a>: Daniel Veillard (MIT License)<br/><a href="http://zlib.net"><span style=" text-decoration: underline; color:#0000ff;">zlib</span></a>: Jean-loup Gailly, Mark Adler (Zlib License)<br/><a href="http://burtleburtle.net/bob/rand/isaac.html"><span style=" text-decoration: underline; color:#0000ff;">QTIsaac</span></a>: Robert J. Jenkins, Quinn Tyler Jackson (Public Domain)<br/><a href="http://cas.ee.ic.ac.uk/people/dt10/index.html"><span style=" text-decoration: underline; color:#0000ff;">MWC64X Random Number Generator</span></a>: David Thomas (Public Domain)<br/><a href="http://code.jellycan.com/simpleopt/"><span style=" text-decoration: underline; color:#0000ff;">SimpleOpt</span></a>: Brodie Thiesfield (MIT License)</p></body></html></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><html><head/><body><p><a href="http://famfamfam.com"><span style=" text-decoration: underline; color:#0000ff;">Silk</span></a>: Mark James (Creative Commons Attribution 2.5 License)<br/><a href="http://momentumdesignlab.com"><span style=" text-decoration: underline; color:#0000ff;">Momentum</span></a>: Momentum Design Lab (Creative Commons Attribution-ShareAlike 3.5 License)<br/><a href="http://everaldo.com"><span style=" text-decoration: underline; color:#0000ff;">Crystal Clear</span></a>: Everaldo Coelho (LGPL)<br/><a href="http://openiconlibrary.sourceforge.net"><span style=" text-decoration: underline; color:#0000ff;">Open Icon Library</span></a>: Jeff Israel (GPL, LGPL, Creative Commons, Public Domain)<br/><a href="http://icons.mysitemyway.com/category/3d-transparent-glass-icons/"><span style=" text-decoration: underline; color:#0000ff;">3D Transparent Glass</span></a>: iconsETC (Public Domain)<br/><a href="http://p.yusukekamiyamane.com"><span style=" text-decoration: underline; color:#0000ff;">Fugue</span></a>: Yusuke Kamiyamane (Creative Commons Attribution 3.0 License)</p></body></html></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>
|
||||
215
Source/Fractorium/DoubleSpinBox.cpp
Normal file
215
Source/Fractorium/DoubleSpinBox.cpp
Normal 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);
|
||||
}
|
||||
80
Source/Fractorium/DoubleSpinBox.h
Normal file
80
Source/Fractorium/DoubleSpinBox.h
Normal 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;
|
||||
};
|
||||
145
Source/Fractorium/EmberFile.h
Normal file
145
Source/Fractorium/EmberFile.h
Normal 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;
|
||||
};
|
||||
120
Source/Fractorium/EmberTreeWidgetItem.h
Normal file
120
Source/Fractorium/EmberTreeWidgetItem.h
Normal 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;
|
||||
};
|
||||
550
Source/Fractorium/FinalRenderDialog.cpp
Normal file
550
Source/Fractorium/FinalRenderDialog.cpp
Normal 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()));
|
||||
}
|
||||
113
Source/Fractorium/FinalRenderDialog.h
Normal file
113
Source/Fractorium/FinalRenderDialog.h
Normal 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;
|
||||
};
|
||||
904
Source/Fractorium/FinalRenderDialog.ui
Normal file
904
Source/Fractorium/FinalRenderDialog.ui
Normal 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><html><head/><body><p>Checked: use 64-bit double precision numbers (slower, but better image quality).</p><p>Unchecked: use 32-bit single precision numbers (faster, but worse image quality).</p></body></html></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><html><head/><body><p>Use OpenCL to render if your video card supports it.</p><p>This is highly recommended as it will dramatically speed up render time.</p></body></html></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><html><head/><body><p>Checked: clip colors and gamma correct after density filtering.</p><p>Unchecked: do it after spatial filtering.</p></body></html></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><html><head/><body><p>Use transparency in the final image.</p><p>Only supported for Png format.</p></body></html></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><html><head/><body><p>The number of threads to use with CPU rendering.</p><p>Decrease for a more responsive system during rendering, increase for better performance.</p></body></html></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><html><head/><body><p>The number to multiply the dimensions of the histogram and density filtering buffer by to achieve anti-aliasing.</p><p>Use this very sparingly as it increases the required memory by n squared.</p></body></html></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>
|
||||
553
Source/Fractorium/FinalRenderEmberController.cpp
Normal file
553
Source/Fractorium/FinalRenderEmberController.cpp
Normal 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();
|
||||
}
|
||||
143
Source/Fractorium/FinalRenderEmberController.h
Normal file
143
Source/Fractorium/FinalRenderEmberController.h
Normal 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
|
||||
BIN
Source/Fractorium/Fractorium.aps
Normal file
BIN
Source/Fractorium/Fractorium.aps
Normal file
Binary file not shown.
544
Source/Fractorium/Fractorium.cpp
Normal file
544
Source/Fractorium/Fractorium.cpp
Normal 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++;
|
||||
//}
|
||||
445
Source/Fractorium/Fractorium.h
Normal file
445
Source/Fractorium/Fractorium.h
Normal 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);
|
||||
44
Source/Fractorium/Fractorium.qrc
Normal file
44
Source/Fractorium/Fractorium.qrc
Normal 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>
|
||||
BIN
Source/Fractorium/Fractorium.rc
Normal file
BIN
Source/Fractorium/Fractorium.rc
Normal file
Binary file not shown.
5304
Source/Fractorium/Fractorium.ui
Normal file
5304
Source/Fractorium/Fractorium.ui
Normal file
File diff suppressed because it is too large
Load Diff
244
Source/Fractorium/FractoriumEmberController.cpp
Normal file
244
Source/Fractorium/FractoriumEmberController.cpp
Normal 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();
|
||||
}
|
||||
446
Source/Fractorium/FractoriumEmberController.h
Normal file
446
Source/Fractorium/FractoriumEmberController.h
Normal 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
|
||||
58
Source/Fractorium/FractoriumInfo.cpp
Normal file
58
Source/Fractorium/FractoriumInfo.cpp
Normal 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"));
|
||||
}
|
||||
277
Source/Fractorium/FractoriumLibrary.cpp
Normal file
277
Source/Fractorium/FractoriumLibrary.cpp
Normal 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();
|
||||
}
|
||||
752
Source/Fractorium/FractoriumMenus.cpp
Normal file
752
Source/Fractorium/FractoriumMenus.cpp
Normal 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();
|
||||
}
|
||||
235
Source/Fractorium/FractoriumPalette.cpp
Normal file
235
Source/Fractorium/FractoriumPalette.cpp
Normal 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);
|
||||
}
|
||||
627
Source/Fractorium/FractoriumParams.cpp
Normal file
627
Source/Fractorium/FractoriumParams.cpp
Normal 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);
|
||||
}
|
||||
1
Source/Fractorium/FractoriumPch.cpp
Normal file
1
Source/Fractorium/FractoriumPch.cpp
Normal file
@ -0,0 +1 @@
|
||||
#include "FractoriumPch.h"
|
||||
44
Source/Fractorium/FractoriumPch.h
Normal file
44
Source/Fractorium/FractoriumPch.h
Normal 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;
|
||||
630
Source/Fractorium/FractoriumRender.cpp
Normal file
630
Source/Fractorium/FractoriumRender.cpp
Normal 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(); }
|
||||
239
Source/Fractorium/FractoriumSettings.cpp
Normal file
239
Source/Fractorium/FractoriumSettings.cpp
Normal 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); }
|
||||
198
Source/Fractorium/FractoriumSettings.h
Normal file
198
Source/Fractorium/FractoriumSettings.h
Normal 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
Reference in New Issue
Block a user