diff options
Diffstat (limited to 'gfx/2d/SVGTurbulenceRenderer-inl.h')
-rw-r--r-- | gfx/2d/SVGTurbulenceRenderer-inl.h | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/gfx/2d/SVGTurbulenceRenderer-inl.h b/gfx/2d/SVGTurbulenceRenderer-inl.h new file mode 100644 index 0000000000..27448befe1 --- /dev/null +++ b/gfx/2d/SVGTurbulenceRenderer-inl.h @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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), + mStitchInfo(), + mStitchable(false), + mType(TURBULENCE_TYPE_TURBULENCE) { + InitFromSeed(aSeed); + if (Stitch) { + AdjustBaseFrequencyForStitch(aTileRect); + mStitchInfo = CreateStitchInfo(aTileRect); + } +} + +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; + std::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 (aIncrementSize == 0) { + return 0; + } + if (aValue >= 0) { + return aValue; + } + return aValue + ceilf(-aValue / aIncrementSize) * aIncrementSize; +} + +static inline Float FiniteDivide(Float aValue, Float aDivisor) { + if (aDivisor == 0) { + return 0; + } + return aValue / aDivisor; +} + +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(FiniteDivide(basePeriod.width, mBaseFrequency.width), + FiniteDivide(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; + } + + DataSourceSurface::ScopedMap map(target, DataSourceSurface::READ_WRITE); + uint8_t* targetData = map.GetData(); + uint32_t stride = map.GetStride(); + + 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 |