361 lines
12 KiB
C++
361 lines
12 KiB
C++
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "2D.h"
|
|
#include "Filters.h"
|
|
#include "SIMD.h"
|
|
|
|
namespace mozilla {
|
|
namespace gfx {
|
|
|
|
template<TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, typename u8x16_t>
|
|
class SVGTurbulenceRenderer
|
|
{
|
|
public:
|
|
SVGTurbulenceRenderer(const Size &aBaseFrequency, int32_t aSeed,
|
|
int aNumOctaves, const Rect &aTileRect);
|
|
|
|
already_AddRefed<DataSourceSurface> Render(const IntSize &aSize, const Point &aOffset) const;
|
|
|
|
private:
|
|
/* The turbulence calculation code is an adapted version of what
|
|
appears in the SVG 1.1 specification:
|
|
http://www.w3.org/TR/SVG11/filters.html#feTurbulence
|
|
*/
|
|
|
|
struct StitchInfo {
|
|
int32_t width; // How much to subtract to wrap for stitching.
|
|
int32_t height;
|
|
int32_t wrapX; // Minimum value to wrap.
|
|
int32_t wrapY;
|
|
};
|
|
|
|
const static int sBSize = 0x100;
|
|
const static int sBM = 0xff;
|
|
void InitFromSeed(int32_t aSeed);
|
|
void AdjustBaseFrequencyForStitch(const Rect &aTileRect);
|
|
IntPoint AdjustForStitch(IntPoint aLatticePoint, const StitchInfo& aStitchInfo) const;
|
|
StitchInfo CreateStitchInfo(const Rect &aTileRect) const;
|
|
f32x4_t Noise2(Point aVec, const StitchInfo& aStitchInfo) const;
|
|
i32x4_t Turbulence(const Point &aPoint) const;
|
|
Point EquivalentNonNegativeOffset(const Point &aOffset) const;
|
|
|
|
Size mBaseFrequency;
|
|
int32_t mNumOctaves;
|
|
StitchInfo mStitchInfo;
|
|
bool mStitchable;
|
|
TurbulenceType mType;
|
|
uint8_t mLatticeSelector[sBSize];
|
|
f32x4_t mGradient[sBSize][2];
|
|
};
|
|
|
|
namespace {
|
|
|
|
struct RandomNumberSource
|
|
{
|
|
explicit RandomNumberSource(int32_t aSeed) : mLast(SetupSeed(aSeed)) {}
|
|
int32_t Next() { mLast = Random(mLast); return mLast; }
|
|
|
|
private:
|
|
static const int32_t RAND_M = 2147483647; /* 2**31 - 1 */
|
|
static const int32_t RAND_A = 16807; /* 7**5; primitive root of m */
|
|
static const int32_t RAND_Q = 127773; /* m / a */
|
|
static const int32_t RAND_R = 2836; /* m % a */
|
|
|
|
/* Produces results in the range [1, 2**31 - 2].
|
|
Algorithm is: r = (a * r) mod m
|
|
where a = 16807 and m = 2**31 - 1 = 2147483647
|
|
See [Park & Miller], CACM vol. 31 no. 10 p. 1195, Oct. 1988
|
|
To test: the algorithm should produce the result 1043618065
|
|
as the 10,000th generated number if the original seed is 1.
|
|
*/
|
|
|
|
static int32_t
|
|
SetupSeed(int32_t aSeed) {
|
|
if (aSeed <= 0)
|
|
aSeed = -(aSeed % (RAND_M - 1)) + 1;
|
|
if (aSeed > RAND_M - 1)
|
|
aSeed = RAND_M - 1;
|
|
return aSeed;
|
|
}
|
|
|
|
static int32_t
|
|
Random(int32_t aSeed)
|
|
{
|
|
int32_t result = RAND_A * (aSeed % RAND_Q) - RAND_R * (aSeed / RAND_Q);
|
|
if (result <= 0)
|
|
result += RAND_M;
|
|
return result;
|
|
}
|
|
|
|
int32_t mLast;
|
|
};
|
|
|
|
} // unnamed namespace
|
|
|
|
template<TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, typename u8x16_t>
|
|
SVGTurbulenceRenderer<Type,Stitch,f32x4_t,i32x4_t,u8x16_t>::SVGTurbulenceRenderer(const Size &aBaseFrequency, int32_t aSeed,
|
|
int aNumOctaves, const Rect &aTileRect)
|
|
: mBaseFrequency(aBaseFrequency)
|
|
, mNumOctaves(aNumOctaves)
|
|
{
|
|
InitFromSeed(aSeed);
|
|
if (Stitch) {
|
|
AdjustBaseFrequencyForStitch(aTileRect);
|
|
mStitchInfo = CreateStitchInfo(aTileRect);
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
static void
|
|
Swap(T& a, T& b) {
|
|
T c = a;
|
|
a = b;
|
|
b = c;
|
|
}
|
|
|
|
template<TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, typename u8x16_t>
|
|
void
|
|
SVGTurbulenceRenderer<Type,Stitch,f32x4_t,i32x4_t,u8x16_t>::InitFromSeed(int32_t aSeed)
|
|
{
|
|
RandomNumberSource rand(aSeed);
|
|
|
|
float gradient[4][sBSize][2];
|
|
for (int32_t k = 0; k < 4; k++) {
|
|
for (int32_t i = 0; i < sBSize; i++) {
|
|
float a, b;
|
|
do {
|
|
a = float((rand.Next() % (sBSize + sBSize)) - sBSize) / sBSize;
|
|
b = float((rand.Next() % (sBSize + sBSize)) - sBSize) / sBSize;
|
|
} while (a == 0 && b == 0);
|
|
float s = sqrt(a * a + b * b);
|
|
gradient[k][i][0] = a / s;
|
|
gradient[k][i][1] = b / s;
|
|
}
|
|
}
|
|
|
|
for (int32_t i = 0; i < sBSize; i++) {
|
|
mLatticeSelector[i] = i;
|
|
}
|
|
for (int32_t i1 = sBSize - 1; i1 > 0; i1--) {
|
|
int32_t i2 = rand.Next() % sBSize;
|
|
Swap(mLatticeSelector[i1], mLatticeSelector[i2]);
|
|
}
|
|
|
|
for (int32_t i = 0; i < sBSize; i++) {
|
|
// Contrary to the code in the spec, we build the first lattice selector
|
|
// lookup into mGradient so that we don't need to do it again for every
|
|
// pixel.
|
|
// We also change the order of the gradient indexing so that we can process
|
|
// all four color channels at the same time.
|
|
uint8_t j = mLatticeSelector[i];
|
|
mGradient[i][0] = simd::FromF32<f32x4_t>(gradient[2][j][0], gradient[1][j][0],
|
|
gradient[0][j][0], gradient[3][j][0]);
|
|
mGradient[i][1] = simd::FromF32<f32x4_t>(gradient[2][j][1], gradient[1][j][1],
|
|
gradient[0][j][1], gradient[3][j][1]);
|
|
}
|
|
}
|
|
|
|
// Adjust aFreq such that aLength * AdjustForLength(aFreq, aLength) is integer
|
|
// and as close to aLength * aFreq as possible.
|
|
static inline float
|
|
AdjustForLength(float aFreq, float aLength)
|
|
{
|
|
float lowFreq = floor(aLength * aFreq) / aLength;
|
|
float hiFreq = ceil(aLength * aFreq) / aLength;
|
|
if (aFreq / lowFreq < hiFreq / aFreq) {
|
|
return lowFreq;
|
|
}
|
|
return hiFreq;
|
|
}
|
|
|
|
template<TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, typename u8x16_t>
|
|
void
|
|
SVGTurbulenceRenderer<Type,Stitch,f32x4_t,i32x4_t,u8x16_t>::AdjustBaseFrequencyForStitch(const Rect &aTileRect)
|
|
{
|
|
mBaseFrequency = Size(AdjustForLength(mBaseFrequency.width, aTileRect.width),
|
|
AdjustForLength(mBaseFrequency.height, aTileRect.height));
|
|
}
|
|
|
|
template<TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, typename u8x16_t>
|
|
typename SVGTurbulenceRenderer<Type,Stitch,f32x4_t,i32x4_t,u8x16_t>::StitchInfo
|
|
SVGTurbulenceRenderer<Type,Stitch,f32x4_t,i32x4_t,u8x16_t>::CreateStitchInfo(const Rect &aTileRect) const
|
|
{
|
|
StitchInfo stitch;
|
|
stitch.width = int32_t(floorf(aTileRect.width * mBaseFrequency.width + 0.5f));
|
|
stitch.height = int32_t(floorf(aTileRect.height * mBaseFrequency.height + 0.5f));
|
|
stitch.wrapX = int32_t(aTileRect.x * mBaseFrequency.width) + stitch.width;
|
|
stitch.wrapY = int32_t(aTileRect.y * mBaseFrequency.height) + stitch.height;
|
|
return stitch;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE Float
|
|
SCurve(Float t)
|
|
{
|
|
return t * t * (3 - 2 * t);
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE Point
|
|
SCurve(Point t)
|
|
{
|
|
return Point(SCurve(t.x), SCurve(t.y));
|
|
}
|
|
|
|
template<typename f32x4_t>
|
|
static MOZ_ALWAYS_INLINE f32x4_t
|
|
BiMix(const f32x4_t& aa, const f32x4_t& ab,
|
|
const f32x4_t& ba, const f32x4_t& bb, Point s)
|
|
{
|
|
return simd::MixF32(simd::MixF32(aa, ab, s.x),
|
|
simd::MixF32(ba, bb, s.x), s.y);
|
|
}
|
|
|
|
template<TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, typename u8x16_t>
|
|
IntPoint
|
|
SVGTurbulenceRenderer<Type,Stitch,f32x4_t,i32x4_t,u8x16_t>::AdjustForStitch(IntPoint aLatticePoint,
|
|
const StitchInfo& aStitchInfo) const
|
|
{
|
|
if (Stitch) {
|
|
if (aLatticePoint.x >= aStitchInfo.wrapX) {
|
|
aLatticePoint.x -= aStitchInfo.width;
|
|
}
|
|
if (aLatticePoint.y >= aStitchInfo.wrapY) {
|
|
aLatticePoint.y -= aStitchInfo.height;
|
|
}
|
|
}
|
|
return aLatticePoint;
|
|
}
|
|
|
|
template<TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, typename u8x16_t>
|
|
f32x4_t
|
|
SVGTurbulenceRenderer<Type,Stitch,f32x4_t,i32x4_t,u8x16_t>::Noise2(Point aVec, const StitchInfo& aStitchInfo) const
|
|
{
|
|
// aVec is guaranteed to be non-negative, so casting to int32_t always
|
|
// rounds towards negative infinity.
|
|
IntPoint topLeftLatticePoint(int32_t(aVec.x), int32_t(aVec.y));
|
|
Point r = aVec - topLeftLatticePoint; // fractional offset
|
|
|
|
IntPoint b0 = AdjustForStitch(topLeftLatticePoint, aStitchInfo);
|
|
IntPoint b1 = AdjustForStitch(b0 + IntPoint(1, 1), aStitchInfo);
|
|
|
|
uint8_t i = mLatticeSelector[b0.x & sBM];
|
|
uint8_t j = mLatticeSelector[b1.x & sBM];
|
|
|
|
const f32x4_t* qua = mGradient[(i + b0.y) & sBM];
|
|
const f32x4_t* qub = mGradient[(i + b1.y) & sBM];
|
|
const f32x4_t* qva = mGradient[(j + b0.y) & sBM];
|
|
const f32x4_t* qvb = mGradient[(j + b1.y) & sBM];
|
|
|
|
return BiMix(simd::WSumF32(qua[0], qua[1], r.x, r.y),
|
|
simd::WSumF32(qva[0], qva[1], r.x - 1.f, r.y),
|
|
simd::WSumF32(qub[0], qub[1], r.x, r.y - 1.f),
|
|
simd::WSumF32(qvb[0], qvb[1], r.x - 1.f, r.y - 1.f),
|
|
SCurve(r));
|
|
}
|
|
|
|
template<typename f32x4_t, typename i32x4_t, typename u8x16_t>
|
|
static inline i32x4_t
|
|
ColorToBGRA(f32x4_t aUnscaledUnpreFloat)
|
|
{
|
|
// Color is an unpremultiplied float vector where 1.0f means white. We will
|
|
// convert it into an integer vector where 255 means white.
|
|
f32x4_t alpha = simd::SplatF32<3>(aUnscaledUnpreFloat);
|
|
f32x4_t scaledUnpreFloat = simd::MulF32(aUnscaledUnpreFloat, simd::FromF32<f32x4_t>(255));
|
|
i32x4_t scaledUnpreInt = simd::F32ToI32(scaledUnpreFloat);
|
|
|
|
// Multiply all channels with alpha.
|
|
i32x4_t scaledPreInt = simd::F32ToI32(simd::MulF32(scaledUnpreFloat, alpha));
|
|
|
|
// Use the premultiplied color channels and the unpremultiplied alpha channel.
|
|
i32x4_t alphaMask = simd::From32<i32x4_t>(0, 0, 0, -1);
|
|
return simd::Pick(alphaMask, scaledPreInt, scaledUnpreInt);
|
|
}
|
|
|
|
template<TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, typename u8x16_t>
|
|
i32x4_t
|
|
SVGTurbulenceRenderer<Type,Stitch,f32x4_t,i32x4_t,u8x16_t>::Turbulence(const Point &aPoint) const
|
|
{
|
|
StitchInfo stitchInfo = mStitchInfo;
|
|
f32x4_t sum = simd::FromF32<f32x4_t>(0);
|
|
Point vec(aPoint.x * mBaseFrequency.width, aPoint.y * mBaseFrequency.height);
|
|
f32x4_t ratio = simd::FromF32<f32x4_t>(1);
|
|
|
|
for (int octave = 0; octave < mNumOctaves; octave++) {
|
|
f32x4_t thisOctave = Noise2(vec, stitchInfo);
|
|
if (Type == TURBULENCE_TYPE_TURBULENCE) {
|
|
thisOctave = simd::AbsF32(thisOctave);
|
|
}
|
|
sum = simd::AddF32(sum, simd::DivF32(thisOctave, ratio));
|
|
vec = vec * 2;
|
|
ratio = simd::MulF32(ratio, simd::FromF32<f32x4_t>(2));
|
|
|
|
if (Stitch) {
|
|
stitchInfo.width *= 2;
|
|
stitchInfo.wrapX *= 2;
|
|
stitchInfo.height *= 2;
|
|
stitchInfo.wrapY *= 2;
|
|
}
|
|
}
|
|
|
|
if (Type == TURBULENCE_TYPE_FRACTAL_NOISE) {
|
|
sum = simd::DivF32(simd::AddF32(sum, simd::FromF32<f32x4_t>(1)), simd::FromF32<f32x4_t>(2));
|
|
}
|
|
return ColorToBGRA<f32x4_t,i32x4_t,u8x16_t>(sum);
|
|
}
|
|
|
|
static inline Float
|
|
MakeNonNegative(Float aValue, Float aIncrementSize)
|
|
{
|
|
if (aValue >= 0) {
|
|
return aValue;
|
|
}
|
|
return aValue + ceilf(-aValue / aIncrementSize) * aIncrementSize;
|
|
}
|
|
|
|
template<TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, typename u8x16_t>
|
|
Point
|
|
SVGTurbulenceRenderer<Type,Stitch,f32x4_t,i32x4_t,u8x16_t>::EquivalentNonNegativeOffset(const Point &aOffset) const
|
|
{
|
|
Size basePeriod = Stitch ? Size(mStitchInfo.width, mStitchInfo.height) :
|
|
Size(sBSize, sBSize);
|
|
Size repeatingSize(basePeriod.width / mBaseFrequency.width,
|
|
basePeriod.height / mBaseFrequency.height);
|
|
return Point(MakeNonNegative(aOffset.x, repeatingSize.width),
|
|
MakeNonNegative(aOffset.y, repeatingSize.height));
|
|
}
|
|
|
|
template<TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, typename u8x16_t>
|
|
already_AddRefed<DataSourceSurface>
|
|
SVGTurbulenceRenderer<Type,Stitch,f32x4_t,i32x4_t,u8x16_t>::Render(const IntSize &aSize, const Point &aOffset) const
|
|
{
|
|
RefPtr<DataSourceSurface> target =
|
|
Factory::CreateDataSourceSurface(aSize, SurfaceFormat::B8G8R8A8);
|
|
if (!target) {
|
|
return nullptr;
|
|
}
|
|
|
|
uint8_t* targetData = target->GetData();
|
|
uint32_t stride = target->Stride();
|
|
|
|
Point startOffset = EquivalentNonNegativeOffset(aOffset);
|
|
|
|
for (int32_t y = 0; y < aSize.height; y++) {
|
|
for (int32_t x = 0; x < aSize.width; x += 4) {
|
|
int32_t targIndex = y * stride + x * 4;
|
|
i32x4_t a = Turbulence(startOffset + Point(x, y));
|
|
i32x4_t b = Turbulence(startOffset + Point(x + 1, y));
|
|
i32x4_t c = Turbulence(startOffset + Point(x + 2, y));
|
|
i32x4_t d = Turbulence(startOffset + Point(x + 3, y));
|
|
u8x16_t result1234 = simd::PackAndSaturate32To8(a, b, c, d);
|
|
simd::Store8(&targetData[targIndex], result1234);
|
|
}
|
|
}
|
|
|
|
return target.forget();
|
|
}
|
|
|
|
} // namespace gfx
|
|
} // namespace mozilla
|