summaryrefslogtreecommitdiffstats
path: root/gfx/2d/SVGTurbulenceRenderer-inl.h
blob: 27448befe1402e8cc738cbe5db624e10efd4c7ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
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