mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-21 13:10:04 -05:00
--Bug fixes
-Fix crash on palette editor when opening it with certain palette files in a particular order. -An xform with only a post variation in it might have showed up wrong. -The xforms combo box was obscuring the name of the xforms by not being wide enough. -Make variation state preservation be a little bit more correct in OpenCL. --Code changes -Make all iterators on the CPU use a temporary point.
This commit is contained in:
parent
8cd2340484
commit
8c66fa2c24
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<?define ProductVersion="1.0.0.18" ?>
|
||||
<?define ProductVersion="1.0.0.19" ?>
|
||||
<?define ProductName="Fractorium $(var.ProductVersion) ($(var.GpuType))" ?>
|
||||
<?define UpgradeCode="{4714cd15-bfba-44f6-8059-9e1466ebfa6e}"?>
|
||||
<?define Manufacturer="Fractorium"?>
|
||||
@ -13,7 +13,7 @@
|
||||
<!--
|
||||
Change this for every release.
|
||||
-->
|
||||
<?define ProductCode="{B0ECFACF-9166-4AE2-A1BF-FF0C8D87FFDF}"?>
|
||||
<?define ProductCode="{8A8580F6-017D-455D-899E-C0FF91ECEB68}"?>
|
||||
|
||||
<Product Id="$(var.ProductCode)" Name="$(var.ProductName)" Language="1033" Version="$(var.ProductVersion)" Manufacturer="$(var.Manufacturer)" UpgradeCode="$(var.UpgradeCode)">
|
||||
<Package
|
||||
|
Binary file not shown.
@ -49,8 +49,8 @@
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1, 0, 0, 18
|
||||
PRODUCTVERSION 1, 0, 0, 18
|
||||
FILEVERSION 1, 0, 0, 19
|
||||
PRODUCTVERSION 1, 0, 0, 19
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@ -67,12 +67,12 @@
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Open Source"
|
||||
VALUE "FileDescription", "Renders fractal flames as animations with motion blur"
|
||||
VALUE "FileVersion", "1, 0, 0, 18"
|
||||
VALUE "FileVersion", "1, 0, 0, 19"
|
||||
VALUE "InternalName", "EmberAnimate.exe"
|
||||
VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2019, GPL v3"
|
||||
VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2020, GPL v3"
|
||||
VALUE "OriginalFilename", "EmberAnimate.exe"
|
||||
VALUE "ProductName", "Ember Animate"
|
||||
VALUE "ProductVersion", "1, 0, 0, 18"
|
||||
VALUE "ProductVersion", "1, 0, 0, 19"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
Binary file not shown.
@ -49,8 +49,8 @@
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1, 0, 0, 18
|
||||
PRODUCTVERSION 1, 0, 0, 18
|
||||
FILEVERSION 1, 0, 0, 19
|
||||
PRODUCTVERSION 1, 0, 0, 19
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@ -66,13 +66,13 @@
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Open Source"
|
||||
VALUE "FileDescription", "Manipulates fractal flames parameter files"
|
||||
VALUE "FileVersion", "1, 0, 0, 18"
|
||||
VALUE "FileDescription", "Manipulates fractal flame parameter files"
|
||||
VALUE "FileVersion", "1, 0, 0, 19"
|
||||
VALUE "InternalName", "EmberGenome.exe"
|
||||
VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2019, GPL v3"
|
||||
VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2020, GPL v3"
|
||||
VALUE "OriginalFilename", "EmberGenome.exe"
|
||||
VALUE "ProductName", "Ember Genome"
|
||||
VALUE "ProductVersion", "1, 0, 0, 18"
|
||||
VALUE "ProductVersion", "1, 0, 0, 19"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
@ -49,8 +49,8 @@
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1, 0, 0, 18
|
||||
PRODUCTVERSION 1, 0, 0, 18
|
||||
FILEVERSION 1, 0, 0, 19
|
||||
PRODUCTVERSION 1, 0, 0, 19
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@ -67,12 +67,12 @@
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Open Source"
|
||||
VALUE "FileDescription", "Renders fractal flames as single images"
|
||||
VALUE "FileVersion", "1, 0, 0, 18"
|
||||
VALUE "FileVersion", "1, 0, 0, 19"
|
||||
VALUE "InternalName", "EmberRender.exe"
|
||||
VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2019, GPL v3"
|
||||
VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2020, GPL v3"
|
||||
VALUE "OriginalFilename", "EmberRender.exe"
|
||||
VALUE "ProductName", "Ember Render"
|
||||
VALUE "ProductVersion", "1, 0, 0, 18"
|
||||
VALUE "ProductVersion", "1, 0, 0, 19"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
Binary file not shown.
@ -37,7 +37,7 @@ static void sincos(float x, float* s, float* c)
|
||||
|
||||
namespace EmberNs
|
||||
{
|
||||
#define EMBER_VERSION "1.0.0.18"
|
||||
#define EMBER_VERSION "1.0.0.19"
|
||||
//#define FLAM3_COMPAT 1//Uncomment this if you want full compatibility with flam3 regarding some of the trig-based variations in Variations01.h
|
||||
#define EPS6 T(1e-6)
|
||||
#define EPS std::numeric_limits<T>::epsilon()//Apoplugin.h uses -20, but it's more mathematically correct to do it this way.
|
||||
|
@ -300,7 +300,7 @@ public:
|
||||
virtual size_t Iterate(Ember<T>& ember, const IterParams<T> params, const CarToRas<T>& ctr, Point<T>* samples, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) override
|
||||
{
|
||||
size_t i, badVals = 0;
|
||||
Point<T> tempPoint, p1;
|
||||
Point<T> tempPoint, p1, p2;
|
||||
auto xforms = ember.NonConstXforms();
|
||||
|
||||
if (ember.ProjBits())//No xaos, 3D.
|
||||
@ -311,8 +311,10 @@ public:
|
||||
|
||||
for (i = 0; i < params.m_Skip; i++)//Fuse.
|
||||
{
|
||||
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
|
||||
DoBadVals(xforms, ember.m_RandPointRange, badVals, &p1, rand);
|
||||
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p2, rand))
|
||||
DoBadVals(xforms, ember.m_RandPointRange, badVals, &p2, rand);
|
||||
|
||||
p1 = p2;
|
||||
}
|
||||
|
||||
DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples.
|
||||
@ -320,10 +322,11 @@ public:
|
||||
|
||||
for (i = 1; i < params.m_Count; i++)//Real loop.
|
||||
{
|
||||
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
|
||||
DoBadVals(xforms, ember.m_RandPointRange, badVals, &p1, rand);
|
||||
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p2, rand))
|
||||
DoBadVals(xforms, ember.m_RandPointRange, badVals, &p2, rand);
|
||||
|
||||
DoFinalXform(ember, p1, samples + i, rand);
|
||||
p1 = p2;
|
||||
DoFinalXform(ember, p2, samples + i, rand);
|
||||
ember.Proj(samples[i], rand, ctr);
|
||||
}
|
||||
}
|
||||
@ -333,8 +336,10 @@ public:
|
||||
|
||||
for (i = 0; i < params.m_Skip; i++)//Fuse.
|
||||
{
|
||||
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
|
||||
DoBadVals(xforms, ember.m_RandPointRange, badVals, &p1, rand);
|
||||
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p2, rand))
|
||||
DoBadVals(xforms, ember.m_RandPointRange, badVals, &p2, rand);
|
||||
|
||||
p1 = p2;
|
||||
}
|
||||
|
||||
samples[0] = p1;
|
||||
@ -358,18 +363,21 @@ public:
|
||||
|
||||
for (i = 0; i < params.m_Skip; i++)//Fuse.
|
||||
{
|
||||
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
|
||||
DoBadVals(xforms, ember.m_RandPointRange, badVals, &p1, rand);
|
||||
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p2, rand))
|
||||
DoBadVals(xforms, ember.m_RandPointRange, badVals, &p2, rand);
|
||||
|
||||
p1 = p2;
|
||||
}
|
||||
|
||||
DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples.
|
||||
|
||||
for (i = 1; i < params.m_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, ember.m_RandPointRange, badVals, &p1, rand);
|
||||
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p2, 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, ember.m_RandPointRange, badVals, &p2, rand);
|
||||
|
||||
DoFinalXform(ember, p1, samples + i, rand);
|
||||
p1 = p2;
|
||||
DoFinalXform(ember, p2, samples + i, rand);
|
||||
}
|
||||
}
|
||||
else//No xaos, no 3D, no final.
|
||||
@ -378,8 +386,10 @@ public:
|
||||
|
||||
for (i = 0; i < params.m_Skip; i++)//Fuse.
|
||||
{
|
||||
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))
|
||||
DoBadVals(xforms, ember.m_RandPointRange, badVals, &p1, rand);
|
||||
if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p2, rand))
|
||||
DoBadVals(xforms, ember.m_RandPointRange, badVals, &p2, rand);
|
||||
|
||||
p1 = p2;
|
||||
}
|
||||
|
||||
samples[0] = p1;
|
||||
@ -468,7 +478,7 @@ public:
|
||||
size_t i, xformIndex;
|
||||
size_t lastXformUsed = 0;
|
||||
size_t badVals = 0;
|
||||
Point<T> tempPoint, p1;
|
||||
Point<T> tempPoint, p1, p2;
|
||||
auto xforms = ember.NonConstXforms();
|
||||
|
||||
if (ember.ProjBits())//Xaos, 3D.
|
||||
@ -481,9 +491,10 @@ public:
|
||||
{
|
||||
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
|
||||
|
||||
if (xforms[xformIndex].Apply(&p1, &p1, rand))
|
||||
DoBadVals(xforms, xformIndex, ember.m_RandPointRange, lastXformUsed, badVals, &p1, rand);
|
||||
if (xforms[xformIndex].Apply(&p1, &p2, rand))
|
||||
DoBadVals(xforms, xformIndex, ember.m_RandPointRange, lastXformUsed, badVals, &p2, rand);
|
||||
|
||||
p1 = p2;
|
||||
lastXformUsed = xformIndex + 1;//Store the last used transform.
|
||||
}
|
||||
|
||||
@ -494,10 +505,11 @@ public:
|
||||
{
|
||||
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, ember.m_RandPointRange, lastXformUsed, badVals, &p1, rand);
|
||||
if (xforms[xformIndex].Apply(&p1, &p2, 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, ember.m_RandPointRange, lastXformUsed, badVals, &p2, rand);
|
||||
|
||||
DoFinalXform(ember, p1, samples + i, rand);
|
||||
p1 = p2;
|
||||
DoFinalXform(ember, p2, samples + i, rand);
|
||||
ember.Proj(samples[i], rand, ctr);
|
||||
lastXformUsed = xformIndex + 1;//Store the last used transform.
|
||||
}
|
||||
@ -510,9 +522,10 @@ public:
|
||||
{
|
||||
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
|
||||
|
||||
if (xforms[xformIndex].Apply(&p1, &p1, rand))
|
||||
DoBadVals(xforms, xformIndex, ember.m_RandPointRange, lastXformUsed, badVals, &p1, rand);
|
||||
if (xforms[xformIndex].Apply(&p1, &p2, rand))
|
||||
DoBadVals(xforms, xformIndex, ember.m_RandPointRange, lastXformUsed, badVals, &p2, rand);
|
||||
|
||||
p1 = p2;
|
||||
lastXformUsed = xformIndex + 1;//Store the last used transform.
|
||||
}
|
||||
|
||||
@ -523,10 +536,10 @@ public:
|
||||
{
|
||||
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
|
||||
|
||||
if (xforms[xformIndex].Apply(&p1, &p1, rand))
|
||||
DoBadVals(xforms, xformIndex, ember.m_RandPointRange, lastXformUsed, badVals, &p1, rand);
|
||||
if (xforms[xformIndex].Apply(&p1, &p2, rand))
|
||||
DoBadVals(xforms, xformIndex, ember.m_RandPointRange, lastXformUsed, badVals, &p2, rand);
|
||||
|
||||
samples[i] = p1;
|
||||
samples[i] = p1 = p2;
|
||||
ember.Proj(samples[i], rand, ctr);
|
||||
lastXformUsed = xformIndex + 1;//Store the last used transform.
|
||||
}
|
||||
@ -542,9 +555,10 @@ public:
|
||||
{
|
||||
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
|
||||
|
||||
if (xforms[xformIndex].Apply(&p1, &p1, rand))
|
||||
DoBadVals(xforms, xformIndex, ember.m_RandPointRange, lastXformUsed, badVals, &p1, rand);
|
||||
if (xforms[xformIndex].Apply(&p1, &p2, rand))
|
||||
DoBadVals(xforms, xformIndex, ember.m_RandPointRange, lastXformUsed, badVals, &p2, rand);
|
||||
|
||||
p1 = p2;
|
||||
lastXformUsed = xformIndex + 1;//Store the last used transform.
|
||||
}
|
||||
|
||||
@ -554,10 +568,11 @@ public:
|
||||
{
|
||||
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, ember.m_RandPointRange, lastXformUsed, badVals, &p1, rand);
|
||||
if (xforms[xformIndex].Apply(&p1, &p2, 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, ember.m_RandPointRange, lastXformUsed, badVals, &p2, rand);
|
||||
|
||||
DoFinalXform(ember, p1, samples + i, rand);
|
||||
p1 = p2;
|
||||
DoFinalXform(ember, p2, samples + i, rand);
|
||||
lastXformUsed = xformIndex + 1;//Store the last used transform.
|
||||
}
|
||||
}
|
||||
@ -569,9 +584,10 @@ public:
|
||||
{
|
||||
xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed);
|
||||
|
||||
if (xforms[xformIndex].Apply(&p1, &p1, rand))
|
||||
DoBadVals(xforms, xformIndex, ember.m_RandPointRange, lastXformUsed, badVals, &p1, rand);
|
||||
if (xforms[xformIndex].Apply(&p1, &p2, rand))
|
||||
DoBadVals(xforms, xformIndex, ember.m_RandPointRange, lastXformUsed, badVals, &p2, rand);
|
||||
|
||||
p1 = p2;
|
||||
lastXformUsed = xformIndex + 1;//Store the last used transform.
|
||||
}
|
||||
|
||||
|
@ -603,24 +603,24 @@ public:
|
||||
outPoint->m_Opacity = m_Opacity;
|
||||
iterHelper.m_Color.x = outPoint->m_ColorX = m_ColorSpeedCache + (m_OneMinusColorCache * inPoint->m_ColorX);
|
||||
|
||||
//Compute the pre affine portion of the transform.
|
||||
//These x, y values are what get passed to the variations below.
|
||||
//Note that they are not changed after this, except in the case of pre_ variations.
|
||||
if (m_HasPre)
|
||||
{
|
||||
iterHelper.m_TransX = (m_Affine.A() * inPoint->m_X) + (m_Affine.B() * inPoint->m_Y) + m_Affine.C();
|
||||
iterHelper.m_TransY = (m_Affine.D() * inPoint->m_X) + (m_Affine.E() * inPoint->m_Y) + m_Affine.F();
|
||||
}
|
||||
else
|
||||
{
|
||||
iterHelper.m_TransX = inPoint->m_X;
|
||||
iterHelper.m_TransY = inPoint->m_Y;
|
||||
}
|
||||
|
||||
iterHelper.m_TransZ = inPoint->m_Z;
|
||||
|
||||
if (m_HasPreOrRegularVars)
|
||||
{
|
||||
//Compute the pre affine portion of the transform.
|
||||
//These x, y values are what get passed to the variations below.
|
||||
//Note that they are not changed after this, except in the case of pre_ variations.
|
||||
if (m_HasPre)
|
||||
{
|
||||
iterHelper.m_TransX = (m_Affine.A() * inPoint->m_X) + (m_Affine.B() * inPoint->m_Y) + m_Affine.C();
|
||||
iterHelper.m_TransY = (m_Affine.D() * inPoint->m_X) + (m_Affine.E() * inPoint->m_Y) + m_Affine.F();
|
||||
}
|
||||
else
|
||||
{
|
||||
iterHelper.m_TransX = inPoint->m_X;
|
||||
iterHelper.m_TransY = inPoint->m_Y;
|
||||
}
|
||||
|
||||
iterHelper.m_TransZ = inPoint->m_Z;
|
||||
|
||||
//Apply pre_ variations, these don't affect outPoint, only iterHelper.m_TransX, Y, Z.
|
||||
for (i = 0; i < PreVariationCount(); i++)
|
||||
{
|
||||
|
@ -25,10 +25,87 @@ template <typename T> const string& IterOpenCLKernelCreator<T>::ZeroizeEntryPoin
|
||||
template <typename T> const string& IterOpenCLKernelCreator<T>::SumHistKernel() const { return m_SumHistKernel; }
|
||||
template <typename T> const string& IterOpenCLKernelCreator<T>::SumHistEntryPoint() const { return m_SumHistEntryPoint; }
|
||||
template <typename T> const string& IterOpenCLKernelCreator<T>::IterEntryPoint() const { return m_IterEntryPoint; }
|
||||
|
||||
/// <summary>
|
||||
/// Create the iteration kernel string using the Cuburn method.
|
||||
/// Template argument expected to be float or double.
|
||||
/// Pre Reg Post Formula
|
||||
/// x trans = affine(inpoint)
|
||||
/// foreach prevar
|
||||
/// tempin = trans
|
||||
/// tempout = prevar(i, tempin)
|
||||
/// trans = tempout
|
||||
/// outpoint = trans
|
||||
///
|
||||
/// x x trans = affine(inpoint)
|
||||
/// foreach prevar
|
||||
/// tempin = trans
|
||||
/// tempout = prevar(i, tempin)
|
||||
/// trans = tempout
|
||||
/// tempin = trans
|
||||
/// outpoint = 0
|
||||
/// foreach regvar
|
||||
/// tempout = regvar(i, tempin)
|
||||
/// outpoint += tempout
|
||||
//
|
||||
/// x x x
|
||||
/// trans = affine(inpoint)
|
||||
/// foreach prevar
|
||||
/// tempin = trans
|
||||
/// tempout = prevar(i, tempin)
|
||||
/// trans = tempout
|
||||
/// tempin = trans
|
||||
/// outpoint = 0
|
||||
/// foreach regvar
|
||||
/// tempout = regvar(i, tempin)
|
||||
/// outpoint += tempout
|
||||
/// foreach postvar
|
||||
/// tempin = outpoint
|
||||
/// tempout = postvar(i, tempin)
|
||||
/// outpoint = tempout
|
||||
///
|
||||
/// x x
|
||||
/// trans = affine(inpoint)
|
||||
/// foreach prevar
|
||||
/// tempin = trans
|
||||
/// tempout = prevar(i, tempin)
|
||||
/// trans = tempout
|
||||
/// outpoint = trans
|
||||
/// foreach postvar
|
||||
/// tempin = outpoint
|
||||
/// tempout = postvar(i, tempin)
|
||||
/// outpoint = tempout
|
||||
///
|
||||
/// x
|
||||
/// trans = affine(inpoint)
|
||||
/// tempin = trans
|
||||
/// outpoint = 0
|
||||
/// foreach regvar
|
||||
/// tempout = regvar(i, tempin)
|
||||
/// outpoint += tempout
|
||||
///
|
||||
/// x x
|
||||
/// trans = affine(inpoint)
|
||||
/// tempin = trans
|
||||
/// outpoint = 0
|
||||
/// foreach regvar
|
||||
/// tempout = regvar(i, tempin)
|
||||
/// outpoint += tempout
|
||||
/// foreach postvar
|
||||
/// tempin = outpoint
|
||||
/// tempout = postvar(i, tempin)
|
||||
/// outpoint = tempout
|
||||
///
|
||||
/// x
|
||||
/// trans = affine(inpoint)
|
||||
/// outpoint = 0
|
||||
/// foreach postvar
|
||||
/// tempin = outpoint
|
||||
/// tempout = postvar(i, tempin)
|
||||
/// outpoint = tempout
|
||||
///
|
||||
/// none trans = affine(inpoint)
|
||||
/// outpoint = 0
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ember">The ember to create the kernel string for</param>
|
||||
/// <param name="params">The parametric variation #define string</param>
|
||||
@ -59,6 +136,7 @@ string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember,
|
||||
bool needPrecalcAngles = false;
|
||||
bool needPrecalcAtanXY = false;
|
||||
bool needPrecalcAtanYX = false;
|
||||
bool hasPreReg = (xform->PreVariationCount() + xform->VariationCount()) > 0;
|
||||
v = varIndex = varCount = 0;
|
||||
xformFuncs <<
|
||||
"void Xform" << i << "(__constant XformCL* xform, __constant real_t* parVars, __global real_t* globalShared, Point* inPoint, Point* outPoint, uint2* mwc, VariationState* varState)\n" <<
|
||||
@ -96,32 +174,24 @@ string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember,
|
||||
|
||||
xformFuncs << "\treal_t tempColor = outPoint->m_ColorX = fma(xform->m_OneMinusColorCache, inPoint->m_ColorX, xform->m_ColorSpeedCache);\n\n";
|
||||
|
||||
if (xform->PreVariationCount() + xform->VariationCount() == 0)
|
||||
if (optAffine && xform->m_Affine.IsID())
|
||||
{
|
||||
xformFuncs <<
|
||||
" outPoint->m_X = 0;\n"
|
||||
" outPoint->m_Y = 0;\n"
|
||||
" outPoint->m_Z = 0;\n";
|
||||
" transX = inPoint->m_X;\n" <<
|
||||
" transY = inPoint->m_Y;\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (optAffine && xform->m_Affine.IsID())
|
||||
{
|
||||
xformFuncs <<
|
||||
" transX = inPoint->m_X;\n" <<
|
||||
" transY = inPoint->m_Y;\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
xformFuncs <<
|
||||
" transX = fma(xform->m_A, inPoint->m_X, fma(xform->m_B, inPoint->m_Y, xform->m_C));\n" <<
|
||||
" transY = fma(xform->m_D, inPoint->m_X, fma(xform->m_E, inPoint->m_Y, xform->m_F));\n";
|
||||
}
|
||||
|
||||
xformFuncs <<
|
||||
" transZ = inPoint->m_Z;\n";
|
||||
varCount = xform->PreVariationCount();
|
||||
" transX = fma(xform->m_A, inPoint->m_X, fma(xform->m_B, inPoint->m_Y, xform->m_C));\n" <<
|
||||
" transY = fma(xform->m_D, inPoint->m_X, fma(xform->m_E, inPoint->m_Y, xform->m_F));\n";
|
||||
}
|
||||
|
||||
xformFuncs << " transZ = inPoint->m_Z;\n";
|
||||
varCount = xform->PreVariationCount();
|
||||
|
||||
if (hasPreReg)
|
||||
{
|
||||
if (varCount > 0)
|
||||
{
|
||||
xformFuncs << "\n\t//Apply each of the " << varCount << " pre variations in this xform.\n";
|
||||
@ -186,6 +256,13 @@ string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember,
|
||||
" outPoint->m_Z = transZ;\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
xformFuncs <<
|
||||
" outPoint->m_X = 0;\n"
|
||||
" outPoint->m_Y = 0;\n"
|
||||
" outPoint->m_Z = 0;\n";
|
||||
}
|
||||
|
||||
if (xform->PostVariationCount() > 0)
|
||||
{
|
||||
@ -450,13 +527,6 @@ string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember,
|
||||
"\n"
|
||||
//Write to another thread's location.
|
||||
" swap[sw] = secondPoint;\n";
|
||||
|
||||
if (hasVarState)
|
||||
{
|
||||
os <<
|
||||
" varStates[blockStartIndex + sw] = varState;\n";
|
||||
}
|
||||
|
||||
os <<
|
||||
"\n"
|
||||
//Populate randomized xform index buffer with new random values.
|
||||
@ -466,13 +536,6 @@ string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember,
|
||||
" barrier(CLK_LOCAL_MEM_FENCE);\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";
|
||||
|
||||
if (hasVarState)
|
||||
{
|
||||
os <<
|
||||
" varState = varStates[blockStartThreadIndex];\n"
|
||||
;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -488,14 +551,40 @@ string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember,
|
||||
os <<
|
||||
"\n"
|
||||
" if (fuse)\n"
|
||||
" {\n"
|
||||
" {\n";
|
||||
|
||||
if (hasVarState && ember.XformCount() > 1)
|
||||
{
|
||||
os <<
|
||||
" varStates[blockStartIndex + sw] = varState;\n\n";
|
||||
}
|
||||
|
||||
os <<
|
||||
" 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.
|
||||
" itersToDo = iterCount;\n";
|
||||
|
||||
if (ember.XformCount() > 1)
|
||||
os <<
|
||||
" barrier(CLK_LOCAL_MEM_FENCE);\n"//Sort of seems necessary, sort of doesn't. Makes no speed difference.
|
||||
;
|
||||
|
||||
os <<
|
||||
" }\n"
|
||||
;
|
||||
|
||||
if (hasVarState && ember.XformCount() > 1)
|
||||
{
|
||||
os <<
|
||||
"\n"
|
||||
" barrier(CLK_GLOBAL_MEM_FENCE);\n"//Sort of seems necessary, sort of doesn't. Makes no speed difference.
|
||||
" varState = varStates[blockStartThreadIndex];"
|
||||
;
|
||||
}
|
||||
|
||||
os <<
|
||||
"\n"
|
||||
" continue;\n"
|
||||
" }\n"
|
||||
@ -516,6 +605,13 @@ string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember,
|
||||
"\n";
|
||||
}
|
||||
|
||||
if (hasVarState && ember.XformCount() > 1)
|
||||
{
|
||||
os <<
|
||||
" varStates[blockStartIndex + sw] = varState;\n"
|
||||
;
|
||||
}
|
||||
|
||||
os << CreateProjectionString(ember);
|
||||
|
||||
if (doAccum)
|
||||
@ -591,8 +687,16 @@ string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember,
|
||||
os <<
|
||||
" }\n"//histIndex < histSize.
|
||||
" }\n"//CarToRasInBounds.
|
||||
"\n"
|
||||
"\n";
|
||||
os <<
|
||||
" barrier(CLK_GLOBAL_MEM_FENCE);\n";//Barrier every time, whether or not the point was in bounds, else artifacts will occur when doing strips.
|
||||
|
||||
if (hasVarState && ember.XformCount() > 1)
|
||||
{
|
||||
os <<
|
||||
" varState = varStates[blockStartThreadIndex];\n"
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
os <<
|
||||
@ -610,7 +714,7 @@ string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember,
|
||||
" seeds[pointsIndex] = mwc;\n"
|
||||
" points[blockStartThreadIndex] = firstPoint;\n";
|
||||
|
||||
if (hasVarState)
|
||||
if (hasVarState && ember.XformCount() == 1)
|
||||
{
|
||||
os <<
|
||||
" varStates[blockStartThreadIndex] = varState;\n";
|
||||
|
@ -58,7 +58,7 @@
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p align="center"><span style=" font-size:10pt;">Fractorium 1.0.0.18</span></p><p align="center"><span style=" font-size:10pt;">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"><a href="http://fractorium.com"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">fractorium.com</span></a></p></body></html></string>
|
||||
<string><html><head/><body><p align="center"><span style=" font-size:10pt;">Fractorium 1.0.0.19</span></p><p align="center"><span style=" font-size:10pt;">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"><a href="http://fractorium.com"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">fractorium.com</span></a></p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
|
@ -139,6 +139,7 @@ public slots:
|
||||
void OnActionNewEmptyFlameInCurrentFile(bool checked);
|
||||
void OnActionNewRandomFlameInCurrentFile(bool checked);
|
||||
void OnActionCopyFlameInCurrentFile(bool checked);
|
||||
void OnActionCreateReferenceFile(bool checked);
|
||||
void OnActionOpen(bool checked);
|
||||
void OnActionSaveCurrentAsXml(bool checked);
|
||||
void OnActionSaveEntireFileAsXml(bool checked);
|
||||
|
@ -96,6 +96,7 @@ public:
|
||||
virtual void NewEmptyFlameInCurrentFile() { }
|
||||
virtual void NewRandomFlameInCurrentFile() { }
|
||||
virtual void CopyFlameInCurrentFile() { }
|
||||
virtual void CreateReferenceFile() { }
|
||||
virtual void OpenAndPrepFiles(const QStringList& filenames, bool append) { }
|
||||
virtual void SaveCurrentAsXml() { }
|
||||
virtual void SaveEntireFileAsXml() { }
|
||||
@ -384,6 +385,7 @@ public:
|
||||
virtual void NewEmptyFlameInCurrentFile() override;
|
||||
virtual void NewRandomFlameInCurrentFile() override;
|
||||
virtual void CopyFlameInCurrentFile() override;
|
||||
virtual void CreateReferenceFile() override;
|
||||
virtual void OpenAndPrepFiles(const QStringList& filenames, bool append) override;
|
||||
virtual void SaveCurrentAsXml() override;
|
||||
virtual void SaveEntireFileAsXml() override;
|
||||
|
@ -11,6 +11,7 @@ void Fractorium::InitMenusUI()
|
||||
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.ActionCreateReferenceFile, SIGNAL(triggered(bool)), this, SLOT(OnActionCreateReferenceFile(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);
|
||||
@ -172,6 +173,104 @@ void FractoriumEmberController<T>::CopyFlameInCurrentFile()
|
||||
|
||||
void Fractorium::OnActionCopyFlameInCurrentFile(bool checked) { m_Controller->CopyFlameInCurrentFile(); }
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference file containing one ember for every possible regular variation, plus one for post_smartcrop
|
||||
/// since it only exists in post form.
|
||||
/// This will replace whatever file the user has open.
|
||||
/// Clears the undo state.
|
||||
/// Resets the rendering process.
|
||||
/// </summary>
|
||||
template <typename T>
|
||||
void FractoriumEmberController<T>::CreateReferenceFile()
|
||||
{
|
||||
bool nv = false;
|
||||
size_t i;
|
||||
StopAllPreviewRenderers();
|
||||
auto temppal = m_Ember.m_Palette;
|
||||
m_EmberFile.Clear();
|
||||
m_EmberFile.m_Filename = QString("Reference_") + EMBER_VERSION;
|
||||
auto varList = VariationList<T>::Instance();
|
||||
auto& regVars = varList->RegVars();
|
||||
auto count = regVars.size();
|
||||
auto addsquaresfunc = [&](size_t i, const Variation<T>* var)
|
||||
{
|
||||
Ember<T> ember;
|
||||
Xform<T> xf0, xf1, xf2, xf3, xf4, xffinal;
|
||||
//
|
||||
xf0.AddVariation(varList->GetVariationCopy(eVariationId::VAR_SQUARE, T(0.5)));
|
||||
//
|
||||
xf1.m_Affine.C(T(0.5));
|
||||
xf1.m_Affine.F(T(0.5));
|
||||
xf1.AddVariation(varList->GetVariationCopy(eVariationId::VAR_LINEAR));
|
||||
//
|
||||
xf2.m_Affine.C(T(-0.5));
|
||||
xf2.m_Affine.F(T(0.5));
|
||||
xf2.AddVariation(varList->GetVariationCopy(eVariationId::VAR_LINEAR));
|
||||
//
|
||||
xf3.m_Affine.C(T(-0.5));
|
||||
xf3.m_Affine.F(T(-0.5));
|
||||
xf3.AddVariation(varList->GetVariationCopy(eVariationId::VAR_LINEAR));
|
||||
//
|
||||
xf4.m_Affine.C(T(0.5));
|
||||
xf4.m_Affine.F(T(-0.5));
|
||||
xf4.AddVariation(varList->GetVariationCopy(eVariationId::VAR_LINEAR));
|
||||
//
|
||||
xffinal.AddVariation(var->Copy());
|
||||
//
|
||||
ember.AddXform(xf0);
|
||||
ember.AddXform(xf1);
|
||||
ember.AddXform(xf2);
|
||||
ember.AddXform(xf3);
|
||||
ember.AddXform(xf4);
|
||||
ember.SetFinalXform(xffinal);
|
||||
ember.EqualizeWeights();
|
||||
ember.m_Index = i;
|
||||
ember.m_MaxRadDE = 0;
|
||||
ember.m_Name = var->Name() + " Squares";
|
||||
ember.m_Palette = temppal;
|
||||
m_EmberFile.m_Embers.push_back(ember);
|
||||
};
|
||||
auto addsbarsfunc = [&](size_t i, const Variation<T>* var)
|
||||
{
|
||||
Ember<T> ember;
|
||||
Xform<T> xf0, xf1, xf2, xffinal;
|
||||
//
|
||||
xf0.AddVariation(varList->GetVariationCopy(eVariationId::VAR_PRE_BLUR, T(100)));
|
||||
xf0.AddVariation(varList->GetVariationCopy(eVariationId::VAR_CYLINDER, T(0.1)));
|
||||
//
|
||||
xf1.m_Affine.C(T(0.4));
|
||||
xf1.AddVariation(varList->GetVariationCopy(eVariationId::VAR_LINEAR));
|
||||
//
|
||||
xf2.m_Affine.C(T(-0.4));
|
||||
xf2.AddVariation(varList->GetVariationCopy(eVariationId::VAR_LINEAR));
|
||||
//
|
||||
xffinal.AddVariation(var->Copy());
|
||||
ember.AddXform(xf0);
|
||||
ember.AddXform(xf1);
|
||||
ember.AddXform(xf2);
|
||||
ember.SetFinalXform(xffinal);
|
||||
ember.EqualizeWeights();
|
||||
ember.m_Index = i;
|
||||
ember.m_MaxRadDE = 0;
|
||||
ember.m_Name = var->Name() + " Bars";
|
||||
ember.m_Palette = temppal;
|
||||
m_EmberFile.m_Embers.push_back(ember);
|
||||
};
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
addsquaresfunc(i, regVars[i]);
|
||||
addsbarsfunc(i, regVars[i]);
|
||||
}
|
||||
|
||||
addsquaresfunc(i, varList->GetVariation(eVariationId::VAR_POST_SMARTCROP));//post_smartcrop is the only variation that exists only in post form, so it must be done manually here.
|
||||
addsbarsfunc(i, varList->GetVariation(eVariationId::VAR_POST_SMARTCROP));
|
||||
m_LastSaveAll = "";
|
||||
FillLibraryTree();
|
||||
}
|
||||
|
||||
void Fractorium::OnActionCreateReferenceFile(bool checked) { m_Controller->CreateReferenceFile(); }
|
||||
|
||||
/// <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,
|
||||
|
@ -36,7 +36,7 @@ void Fractorium::InitXformsUI()
|
||||
ui.CurrentXformCombo->view()->setMinimumWidth(100);
|
||||
ui.CurrentXformCombo->view()->setMaximumWidth(500);
|
||||
//ui.CurrentXformCombo->view()->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
ui.CurrentXformCombo->view()->setSizeAdjustPolicy(QAbstractScrollArea::SizeAdjustPolicy::AdjustToContentsOnFirstShow);
|
||||
ui.CurrentXformCombo->view()->setSizeAdjustPolicy(QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents);
|
||||
#ifndef _WIN32
|
||||
//For some reason linux makes these 24x24, even though the designer explicitly says 16x16.
|
||||
ui.AddXformButton->setIconSize(QSize(16, 16));
|
||||
@ -518,9 +518,9 @@ void FractoriumEmberController<T>::FillWithXform(Xform<T>* xform)
|
||||
|
||||
if (auto item = m_Fractorium->ui.XformWeightNameTable->item(0, 1))
|
||||
{
|
||||
m_Fractorium->ui.XformWeightNameTable->blockSignals(true);
|
||||
item->setText(QString::fromStdString(xform->m_Name));
|
||||
m_Fractorium->ui.XformWeightNameTable->blockSignals(false);
|
||||
m_Fractorium->m_XformNameEdit->blockSignals(true);
|
||||
m_Fractorium->m_XformNameEdit->setText(QString::fromStdString(xform->m_Name));
|
||||
m_Fractorium->m_XformNameEdit->blockSignals(false);
|
||||
}
|
||||
|
||||
FillVariationTreeWithXform(xform);
|
||||
@ -656,7 +656,7 @@ void FractoriumEmberController<T>::UpdateXformName(int index)
|
||||
auto view = m_Fractorium->ui.CurrentXformCombo->view();
|
||||
auto fontMetrics1 = view->fontMetrics();
|
||||
auto textWidth = m_Fractorium->ui.CurrentXformCombo->width();
|
||||
auto ww = fontMetrics1.width("WW");
|
||||
auto ww = fontMetrics1.width("WW") * 3;
|
||||
|
||||
for (int i = 0; i < m_Fractorium->ui.CurrentXformCombo->count(); ++i)
|
||||
textWidth = std::max(fontMetrics1.width(m_Fractorium->ui.CurrentXformCombo->itemText(i)) + ww, textWidth);
|
||||
|
@ -433,7 +433,7 @@ void PaletteEditor::OnCopyPaletteFileButtonClicked()
|
||||
/// </summary>
|
||||
void PaletteEditor::OnAppendPaletteButtonClicked()
|
||||
{
|
||||
auto& pal = m_GradientColorView->GetPalette(256);
|
||||
auto& pal = GetPalette(256);
|
||||
m_PaletteList->AddPaletteToFile(m_CurrentPaletteFilePath, pal);
|
||||
::FillPaletteTable(m_CurrentPaletteFilePath, ui->PaletteListTable, m_PaletteList);
|
||||
m_PaletteIndex = ui->PaletteListTable->rowCount() - 1;
|
||||
@ -446,7 +446,7 @@ void PaletteEditor::OnAppendPaletteButtonClicked()
|
||||
/// </summary>
|
||||
void PaletteEditor::OnOverwritePaletteButtonClicked()
|
||||
{
|
||||
auto& pal = m_GradientColorView->GetPalette(256);
|
||||
auto& pal = GetPalette(256);
|
||||
m_PaletteList->Replace(m_CurrentPaletteFilePath, pal, m_PaletteIndex);
|
||||
::FillPaletteTable(m_CurrentPaletteFilePath, ui->PaletteListTable, m_PaletteList);
|
||||
emit PaletteFileChanged();
|
||||
@ -632,10 +632,11 @@ void PaletteEditor::EnablePaletteFileControls()
|
||||
void PaletteEditor::EnablePaletteControls()
|
||||
{
|
||||
bool b = IsCurrentPaletteAndFileEditable();//Both the file and the current palette must be editable.
|
||||
auto& pal = GetPalette(256);
|
||||
ui->DeletePaletteButton->setEnabled(b);
|
||||
ui->CopyPaletteFileButton->setEnabled(b);
|
||||
ui->AppendPaletteButton->setEnabled(b);
|
||||
ui->OverwritePaletteButton->setEnabled(b && (GetFilename(m_CurrentPaletteFilePath) == GetFilename(*m_GradientColorView->GetPalette(256).m_Filename.get())));//Only allow overwrite if the palette is from the file it's overwriting a palette in.
|
||||
ui->OverwritePaletteButton->setEnabled(b && pal.m_Filename.get() && (GetFilename(m_CurrentPaletteFilePath) == GetFilename(*pal.m_Filename.get())));//Only allow overwrite if the palette is from the file it's overwriting a palette in.
|
||||
ui->AddColorButton->setEnabled(b);
|
||||
ui->DistributeColorsButton->setEnabled(b);
|
||||
ui->AutoDistributeCheckBox->setEnabled(b);
|
||||
@ -654,5 +655,5 @@ void PaletteEditor::EnablePaletteControls()
|
||||
/// <returns>True if both the currently selected palette is editable and if all palettes in the currently selected file are editable.</returns>
|
||||
bool PaletteEditor::IsCurrentPaletteAndFileEditable()
|
||||
{
|
||||
return m_PaletteList->IsModifiable(m_CurrentPaletteFilePath) && !m_GradientColorView->GetPalette(256).m_SourceColors.empty();
|
||||
return m_PaletteList->IsModifiable(m_CurrentPaletteFilePath) && !GetPalette(256).m_SourceColors.empty();
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
@ -10,7 +9,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("apoconv")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user