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