1 /*
2 * Copyright 2013 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "SkPerlinNoiseShader.h"
9 #include "SkColorFilter.h"
10 #include "SkReadBuffer.h"
11 #include "SkWriteBuffer.h"
12 #include "SkShader.h"
13 #include "SkUnPreMultiply.h"
14 #include "SkString.h"
15
16 #if SK_SUPPORT_GPU
17 #include "GrContext.h"
18 #include "GrCoordTransform.h"
19 #include "GrInvariantOutput.h"
20 #include "SkGr.h"
21 #include "effects/GrConstColorProcessor.h"
22 #include "glsl/GrGLSLFragmentProcessor.h"
23 #include "glsl/GrGLSLFragmentShaderBuilder.h"
24 #include "glsl/GrGLSLProgramDataManager.h"
25 #include "glsl/GrGLSLUniformHandler.h"
26 #endif
27
28 static const int kBlockSize = 256;
29 static const int kBlockMask = kBlockSize - 1;
30 static const int kPerlinNoise = 4096;
31 static const int kRandMaximum = SK_MaxS32; // 2**31 - 1
32
33 namespace {
34
35 // noiseValue is the color component's value (or color)
36 // limitValue is the maximum perlin noise array index value allowed
37 // newValue is the current noise dimension (either width or height)
checkNoise(int noiseValue,int limitValue,int newValue)38 inline int checkNoise(int noiseValue, int limitValue, int newValue) {
39 // If the noise value would bring us out of bounds of the current noise array while we are
40 // stiching noise tiles together, wrap the noise around the current dimension of the noise to
41 // stay within the array bounds in a continuous fashion (so that tiling lines are not visible)
42 if (noiseValue >= limitValue) {
43 noiseValue -= newValue;
44 }
45 return noiseValue;
46 }
47
smoothCurve(SkScalar t)48 inline SkScalar smoothCurve(SkScalar t) {
49 static const SkScalar SK_Scalar3 = 3.0f;
50
51 // returns t * t * (3 - 2 * t)
52 return SkScalarMul(SkScalarSquare(t), SK_Scalar3 - 2 * t);
53 }
54
55 } // end namespace
56
57 struct SkPerlinNoiseShader::StitchData {
StitchDataSkPerlinNoiseShader::StitchData58 StitchData()
59 : fWidth(0)
60 , fWrapX(0)
61 , fHeight(0)
62 , fWrapY(0)
63 {}
64
operator ==SkPerlinNoiseShader::StitchData65 bool operator==(const StitchData& other) const {
66 return fWidth == other.fWidth &&
67 fWrapX == other.fWrapX &&
68 fHeight == other.fHeight &&
69 fWrapY == other.fWrapY;
70 }
71
72 int fWidth; // How much to subtract to wrap for stitching.
73 int fWrapX; // Minimum value to wrap.
74 int fHeight;
75 int fWrapY;
76 };
77
78 struct SkPerlinNoiseShader::PaintingData {
PaintingDataSkPerlinNoiseShader::PaintingData79 PaintingData(const SkISize& tileSize, SkScalar seed,
80 SkScalar baseFrequencyX, SkScalar baseFrequencyY,
81 const SkMatrix& matrix)
82 {
83 SkVector vec[2] = {
84 { SkScalarInvert(baseFrequencyX), SkScalarInvert(baseFrequencyY) },
85 { SkIntToScalar(tileSize.fWidth), SkIntToScalar(tileSize.fHeight) },
86 };
87 matrix.mapVectors(vec, 2);
88
89 fBaseFrequency.set(SkScalarInvert(vec[0].fX), SkScalarInvert(vec[0].fY));
90 fTileSize.set(SkScalarRoundToInt(vec[1].fX), SkScalarRoundToInt(vec[1].fY));
91 this->init(seed);
92 if (!fTileSize.isEmpty()) {
93 this->stitch();
94 }
95
96 #if SK_SUPPORT_GPU
97 fPermutationsBitmap.setInfo(SkImageInfo::MakeA8(kBlockSize, 1));
98 fPermutationsBitmap.setPixels(fLatticeSelector);
99
100 fNoiseBitmap.setInfo(SkImageInfo::MakeN32Premul(kBlockSize, 4));
101 fNoiseBitmap.setPixels(fNoise[0][0]);
102 #endif
103 }
104
105 int fSeed;
106 uint8_t fLatticeSelector[kBlockSize];
107 uint16_t fNoise[4][kBlockSize][2];
108 SkPoint fGradient[4][kBlockSize];
109 SkISize fTileSize;
110 SkVector fBaseFrequency;
111 StitchData fStitchDataInit;
112
113 private:
114
115 #if SK_SUPPORT_GPU
116 SkBitmap fPermutationsBitmap;
117 SkBitmap fNoiseBitmap;
118 #endif
119
randomSkPerlinNoiseShader::PaintingData120 inline int random() {
121 static const int gRandAmplitude = 16807; // 7**5; primitive root of m
122 static const int gRandQ = 127773; // m / a
123 static const int gRandR = 2836; // m % a
124
125 int result = gRandAmplitude * (fSeed % gRandQ) - gRandR * (fSeed / gRandQ);
126 if (result <= 0)
127 result += kRandMaximum;
128 fSeed = result;
129 return result;
130 }
131
132 // Only called once. Could be part of the constructor.
initSkPerlinNoiseShader::PaintingData133 void init(SkScalar seed)
134 {
135 static const SkScalar gInvBlockSizef = SkScalarInvert(SkIntToScalar(kBlockSize));
136
137 // According to the SVG spec, we must truncate (not round) the seed value.
138 fSeed = SkScalarTruncToInt(seed);
139 // The seed value clamp to the range [1, kRandMaximum - 1].
140 if (fSeed <= 0) {
141 fSeed = -(fSeed % (kRandMaximum - 1)) + 1;
142 }
143 if (fSeed > kRandMaximum - 1) {
144 fSeed = kRandMaximum - 1;
145 }
146 for (int channel = 0; channel < 4; ++channel) {
147 for (int i = 0; i < kBlockSize; ++i) {
148 fLatticeSelector[i] = i;
149 fNoise[channel][i][0] = (random() % (2 * kBlockSize));
150 fNoise[channel][i][1] = (random() % (2 * kBlockSize));
151 }
152 }
153 for (int i = kBlockSize - 1; i > 0; --i) {
154 int k = fLatticeSelector[i];
155 int j = random() % kBlockSize;
156 SkASSERT(j >= 0);
157 SkASSERT(j < kBlockSize);
158 fLatticeSelector[i] = fLatticeSelector[j];
159 fLatticeSelector[j] = k;
160 }
161
162 // Perform the permutations now
163 {
164 // Copy noise data
165 uint16_t noise[4][kBlockSize][2];
166 for (int i = 0; i < kBlockSize; ++i) {
167 for (int channel = 0; channel < 4; ++channel) {
168 for (int j = 0; j < 2; ++j) {
169 noise[channel][i][j] = fNoise[channel][i][j];
170 }
171 }
172 }
173 // Do permutations on noise data
174 for (int i = 0; i < kBlockSize; ++i) {
175 for (int channel = 0; channel < 4; ++channel) {
176 for (int j = 0; j < 2; ++j) {
177 fNoise[channel][i][j] = noise[channel][fLatticeSelector[i]][j];
178 }
179 }
180 }
181 }
182
183 // Half of the largest possible value for 16 bit unsigned int
184 static const SkScalar gHalfMax16bits = 32767.5f;
185
186 // Compute gradients from permutated noise data
187 for (int channel = 0; channel < 4; ++channel) {
188 for (int i = 0; i < kBlockSize; ++i) {
189 fGradient[channel][i] = SkPoint::Make(
190 SkScalarMul(SkIntToScalar(fNoise[channel][i][0] - kBlockSize),
191 gInvBlockSizef),
192 SkScalarMul(SkIntToScalar(fNoise[channel][i][1] - kBlockSize),
193 gInvBlockSizef));
194 fGradient[channel][i].normalize();
195 // Put the normalized gradient back into the noise data
196 fNoise[channel][i][0] = SkScalarRoundToInt(SkScalarMul(
197 fGradient[channel][i].fX + SK_Scalar1, gHalfMax16bits));
198 fNoise[channel][i][1] = SkScalarRoundToInt(SkScalarMul(
199 fGradient[channel][i].fY + SK_Scalar1, gHalfMax16bits));
200 }
201 }
202 }
203
204 // Only called once. Could be part of the constructor.
stitchSkPerlinNoiseShader::PaintingData205 void stitch() {
206 SkScalar tileWidth = SkIntToScalar(fTileSize.width());
207 SkScalar tileHeight = SkIntToScalar(fTileSize.height());
208 SkASSERT(tileWidth > 0 && tileHeight > 0);
209 // When stitching tiled turbulence, the frequencies must be adjusted
210 // so that the tile borders will be continuous.
211 if (fBaseFrequency.fX) {
212 SkScalar lowFrequencx =
213 SkScalarFloorToScalar(tileWidth * fBaseFrequency.fX) / tileWidth;
214 SkScalar highFrequencx =
215 SkScalarCeilToScalar(tileWidth * fBaseFrequency.fX) / tileWidth;
216 // BaseFrequency should be non-negative according to the standard.
217 if (fBaseFrequency.fX / lowFrequencx < highFrequencx / fBaseFrequency.fX) {
218 fBaseFrequency.fX = lowFrequencx;
219 } else {
220 fBaseFrequency.fX = highFrequencx;
221 }
222 }
223 if (fBaseFrequency.fY) {
224 SkScalar lowFrequency =
225 SkScalarFloorToScalar(tileHeight * fBaseFrequency.fY) / tileHeight;
226 SkScalar highFrequency =
227 SkScalarCeilToScalar(tileHeight * fBaseFrequency.fY) / tileHeight;
228 if (fBaseFrequency.fY / lowFrequency < highFrequency / fBaseFrequency.fY) {
229 fBaseFrequency.fY = lowFrequency;
230 } else {
231 fBaseFrequency.fY = highFrequency;
232 }
233 }
234 // Set up TurbulenceInitial stitch values.
235 fStitchDataInit.fWidth =
236 SkScalarRoundToInt(tileWidth * fBaseFrequency.fX);
237 fStitchDataInit.fWrapX = kPerlinNoise + fStitchDataInit.fWidth;
238 fStitchDataInit.fHeight =
239 SkScalarRoundToInt(tileHeight * fBaseFrequency.fY);
240 fStitchDataInit.fWrapY = kPerlinNoise + fStitchDataInit.fHeight;
241 }
242
243 public:
244
245 #if SK_SUPPORT_GPU
getPermutationsBitmapSkPerlinNoiseShader::PaintingData246 const SkBitmap& getPermutationsBitmap() const { return fPermutationsBitmap; }
247
getNoiseBitmapSkPerlinNoiseShader::PaintingData248 const SkBitmap& getNoiseBitmap() const { return fNoiseBitmap; }
249 #endif
250 };
251
CreateFractalNoise(SkScalar baseFrequencyX,SkScalar baseFrequencyY,int numOctaves,SkScalar seed,const SkISize * tileSize)252 SkShader* SkPerlinNoiseShader::CreateFractalNoise(SkScalar baseFrequencyX, SkScalar baseFrequencyY,
253 int numOctaves, SkScalar seed,
254 const SkISize* tileSize) {
255 return new SkPerlinNoiseShader(kFractalNoise_Type, baseFrequencyX, baseFrequencyY, numOctaves,
256 seed, tileSize);
257 }
258
CreateTurbulence(SkScalar baseFrequencyX,SkScalar baseFrequencyY,int numOctaves,SkScalar seed,const SkISize * tileSize)259 SkShader* SkPerlinNoiseShader::CreateTurbulence(SkScalar baseFrequencyX, SkScalar baseFrequencyY,
260 int numOctaves, SkScalar seed,
261 const SkISize* tileSize) {
262 return new SkPerlinNoiseShader(kTurbulence_Type, baseFrequencyX, baseFrequencyY, numOctaves,
263 seed, tileSize);
264 }
265
SkPerlinNoiseShader(SkPerlinNoiseShader::Type type,SkScalar baseFrequencyX,SkScalar baseFrequencyY,int numOctaves,SkScalar seed,const SkISize * tileSize)266 SkPerlinNoiseShader::SkPerlinNoiseShader(SkPerlinNoiseShader::Type type,
267 SkScalar baseFrequencyX,
268 SkScalar baseFrequencyY,
269 int numOctaves,
270 SkScalar seed,
271 const SkISize* tileSize)
272 : fType(type)
273 , fBaseFrequencyX(baseFrequencyX)
274 , fBaseFrequencyY(baseFrequencyY)
275 , fNumOctaves(numOctaves > 255 ? 255 : numOctaves/*[0,255] octaves allowed*/)
276 , fSeed(seed)
277 , fTileSize(nullptr == tileSize ? SkISize::Make(0, 0) : *tileSize)
278 , fStitchTiles(!fTileSize.isEmpty())
279 {
280 SkASSERT(numOctaves >= 0 && numOctaves < 256);
281 }
282
~SkPerlinNoiseShader()283 SkPerlinNoiseShader::~SkPerlinNoiseShader() {
284 }
285
CreateProc(SkReadBuffer & buffer)286 SkFlattenable* SkPerlinNoiseShader::CreateProc(SkReadBuffer& buffer) {
287 Type type = (Type)buffer.readInt();
288 SkScalar freqX = buffer.readScalar();
289 SkScalar freqY = buffer.readScalar();
290 int octaves = buffer.readInt();
291 SkScalar seed = buffer.readScalar();
292 SkISize tileSize;
293 tileSize.fWidth = buffer.readInt();
294 tileSize.fHeight = buffer.readInt();
295
296 switch (type) {
297 case kFractalNoise_Type:
298 return SkPerlinNoiseShader::CreateFractalNoise(freqX, freqY, octaves, seed, &tileSize);
299 case kTurbulence_Type:
300 return SkPerlinNoiseShader::CreateTubulence(freqX, freqY, octaves, seed, &tileSize);
301 default:
302 return nullptr;
303 }
304 }
305
flatten(SkWriteBuffer & buffer) const306 void SkPerlinNoiseShader::flatten(SkWriteBuffer& buffer) const {
307 buffer.writeInt((int) fType);
308 buffer.writeScalar(fBaseFrequencyX);
309 buffer.writeScalar(fBaseFrequencyY);
310 buffer.writeInt(fNumOctaves);
311 buffer.writeScalar(fSeed);
312 buffer.writeInt(fTileSize.fWidth);
313 buffer.writeInt(fTileSize.fHeight);
314 }
315
noise2D(int channel,const StitchData & stitchData,const SkPoint & noiseVector) const316 SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::noise2D(
317 int channel, const StitchData& stitchData, const SkPoint& noiseVector) const {
318 struct Noise {
319 int noisePositionIntegerValue;
320 int nextNoisePositionIntegerValue;
321 SkScalar noisePositionFractionValue;
322 Noise(SkScalar component)
323 {
324 SkScalar position = component + kPerlinNoise;
325 noisePositionIntegerValue = SkScalarFloorToInt(position);
326 noisePositionFractionValue = position - SkIntToScalar(noisePositionIntegerValue);
327 nextNoisePositionIntegerValue = noisePositionIntegerValue + 1;
328 }
329 };
330 Noise noiseX(noiseVector.x());
331 Noise noiseY(noiseVector.y());
332 SkScalar u, v;
333 const SkPerlinNoiseShader& perlinNoiseShader = static_cast<const SkPerlinNoiseShader&>(fShader);
334 // If stitching, adjust lattice points accordingly.
335 if (perlinNoiseShader.fStitchTiles) {
336 noiseX.noisePositionIntegerValue =
337 checkNoise(noiseX.noisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth);
338 noiseY.noisePositionIntegerValue =
339 checkNoise(noiseY.noisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight);
340 noiseX.nextNoisePositionIntegerValue =
341 checkNoise(noiseX.nextNoisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth);
342 noiseY.nextNoisePositionIntegerValue =
343 checkNoise(noiseY.nextNoisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight);
344 }
345 noiseX.noisePositionIntegerValue &= kBlockMask;
346 noiseY.noisePositionIntegerValue &= kBlockMask;
347 noiseX.nextNoisePositionIntegerValue &= kBlockMask;
348 noiseY.nextNoisePositionIntegerValue &= kBlockMask;
349 int i =
350 fPaintingData->fLatticeSelector[noiseX.noisePositionIntegerValue];
351 int j =
352 fPaintingData->fLatticeSelector[noiseX.nextNoisePositionIntegerValue];
353 int b00 = (i + noiseY.noisePositionIntegerValue) & kBlockMask;
354 int b10 = (j + noiseY.noisePositionIntegerValue) & kBlockMask;
355 int b01 = (i + noiseY.nextNoisePositionIntegerValue) & kBlockMask;
356 int b11 = (j + noiseY.nextNoisePositionIntegerValue) & kBlockMask;
357 SkScalar sx = smoothCurve(noiseX.noisePositionFractionValue);
358 SkScalar sy = smoothCurve(noiseY.noisePositionFractionValue);
359 // This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement
360 SkPoint fractionValue = SkPoint::Make(noiseX.noisePositionFractionValue,
361 noiseY.noisePositionFractionValue); // Offset (0,0)
362 u = fPaintingData->fGradient[channel][b00].dot(fractionValue);
363 fractionValue.fX -= SK_Scalar1; // Offset (-1,0)
364 v = fPaintingData->fGradient[channel][b10].dot(fractionValue);
365 SkScalar a = SkScalarInterp(u, v, sx);
366 fractionValue.fY -= SK_Scalar1; // Offset (-1,-1)
367 v = fPaintingData->fGradient[channel][b11].dot(fractionValue);
368 fractionValue.fX = noiseX.noisePositionFractionValue; // Offset (0,-1)
369 u = fPaintingData->fGradient[channel][b01].dot(fractionValue);
370 SkScalar b = SkScalarInterp(u, v, sx);
371 return SkScalarInterp(a, b, sy);
372 }
373
calculateTurbulenceValueForPoint(int channel,StitchData & stitchData,const SkPoint & point) const374 SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::calculateTurbulenceValueForPoint(
375 int channel, StitchData& stitchData, const SkPoint& point) const {
376 const SkPerlinNoiseShader& perlinNoiseShader = static_cast<const SkPerlinNoiseShader&>(fShader);
377 if (perlinNoiseShader.fStitchTiles) {
378 // Set up TurbulenceInitial stitch values.
379 stitchData = fPaintingData->fStitchDataInit;
380 }
381 SkScalar turbulenceFunctionResult = 0;
382 SkPoint noiseVector(SkPoint::Make(SkScalarMul(point.x(), fPaintingData->fBaseFrequency.fX),
383 SkScalarMul(point.y(), fPaintingData->fBaseFrequency.fY)));
384 SkScalar ratio = SK_Scalar1;
385 for (int octave = 0; octave < perlinNoiseShader.fNumOctaves; ++octave) {
386 SkScalar noise = noise2D(channel, stitchData, noiseVector);
387 SkScalar numer = (perlinNoiseShader.fType == kFractalNoise_Type) ?
388 noise : SkScalarAbs(noise);
389 turbulenceFunctionResult += numer / ratio;
390 noiseVector.fX *= 2;
391 noiseVector.fY *= 2;
392 ratio *= 2;
393 if (perlinNoiseShader.fStitchTiles) {
394 // Update stitch values
395 stitchData.fWidth *= 2;
396 stitchData.fWrapX = stitchData.fWidth + kPerlinNoise;
397 stitchData.fHeight *= 2;
398 stitchData.fWrapY = stitchData.fHeight + kPerlinNoise;
399 }
400 }
401
402 // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2
403 // by fractalNoise and (turbulenceFunctionResult) by turbulence.
404 if (perlinNoiseShader.fType == kFractalNoise_Type) {
405 turbulenceFunctionResult =
406 SkScalarMul(turbulenceFunctionResult, SK_ScalarHalf) + SK_ScalarHalf;
407 }
408
409 if (channel == 3) { // Scale alpha by paint value
410 turbulenceFunctionResult *= SkIntToScalar(getPaintAlpha()) / 255;
411 }
412
413 // Clamp result
414 return SkScalarPin(turbulenceFunctionResult, 0, SK_Scalar1);
415 }
416
shade(const SkPoint & point,StitchData & stitchData) const417 SkPMColor SkPerlinNoiseShader::PerlinNoiseShaderContext::shade(
418 const SkPoint& point, StitchData& stitchData) const {
419 SkPoint newPoint;
420 fMatrix.mapPoints(&newPoint, &point, 1);
421 newPoint.fX = SkScalarRoundToScalar(newPoint.fX);
422 newPoint.fY = SkScalarRoundToScalar(newPoint.fY);
423
424 U8CPU rgba[4];
425 for (int channel = 3; channel >= 0; --channel) {
426 rgba[channel] = SkScalarFloorToInt(255 *
427 calculateTurbulenceValueForPoint(channel, stitchData, newPoint));
428 }
429 return SkPreMultiplyARGB(rgba[3], rgba[0], rgba[1], rgba[2]);
430 }
431
onCreateContext(const ContextRec & rec,void * storage) const432 SkShader::Context* SkPerlinNoiseShader::onCreateContext(const ContextRec& rec,
433 void* storage) const {
434 return new (storage) PerlinNoiseShaderContext(*this, rec);
435 }
436
contextSize(const ContextRec &) const437 size_t SkPerlinNoiseShader::contextSize(const ContextRec&) const {
438 return sizeof(PerlinNoiseShaderContext);
439 }
440
PerlinNoiseShaderContext(const SkPerlinNoiseShader & shader,const ContextRec & rec)441 SkPerlinNoiseShader::PerlinNoiseShaderContext::PerlinNoiseShaderContext(
442 const SkPerlinNoiseShader& shader, const ContextRec& rec)
443 : INHERITED(shader, rec)
444 {
445 SkMatrix newMatrix = *rec.fMatrix;
446 newMatrix.preConcat(shader.getLocalMatrix());
447 if (rec.fLocalMatrix) {
448 newMatrix.preConcat(*rec.fLocalMatrix);
449 }
450 // This (1,1) translation is due to WebKit's 1 based coordinates for the noise
451 // (as opposed to 0 based, usually). The same adjustment is in the setData() function.
452 fMatrix.setTranslate(-newMatrix.getTranslateX() + SK_Scalar1, -newMatrix.getTranslateY() + SK_Scalar1);
453 fPaintingData = new PaintingData(shader.fTileSize, shader.fSeed, shader.fBaseFrequencyX,
454 shader.fBaseFrequencyY, newMatrix);
455 }
456
~PerlinNoiseShaderContext()457 SkPerlinNoiseShader::PerlinNoiseShaderContext::~PerlinNoiseShaderContext() { delete fPaintingData; }
458
shadeSpan(int x,int y,SkPMColor result[],int count)459 void SkPerlinNoiseShader::PerlinNoiseShaderContext::shadeSpan(
460 int x, int y, SkPMColor result[], int count) {
461 SkPoint point = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y));
462 StitchData stitchData;
463 for (int i = 0; i < count; ++i) {
464 result[i] = shade(point, stitchData);
465 point.fX += SK_Scalar1;
466 }
467 }
468
469 /////////////////////////////////////////////////////////////////////
470
471 #if SK_SUPPORT_GPU
472
473 class GrGLPerlinNoise : public GrGLSLFragmentProcessor {
474 public:
475 void emitCode(EmitArgs&) override;
476
477 static inline void GenKey(const GrProcessor&, const GrGLSLCaps&, GrProcessorKeyBuilder*);
478
479 protected:
480 void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
481
482 private:
483 GrGLSLProgramDataManager::UniformHandle fStitchDataUni;
484 GrGLSLProgramDataManager::UniformHandle fBaseFrequencyUni;
485
486 typedef GrGLSLFragmentProcessor INHERITED;
487 };
488
489 /////////////////////////////////////////////////////////////////////
490
491 class GrPerlinNoiseEffect : public GrFragmentProcessor {
492 public:
Create(SkPerlinNoiseShader::Type type,int numOctaves,bool stitchTiles,SkPerlinNoiseShader::PaintingData * paintingData,GrTexture * permutationsTexture,GrTexture * noiseTexture,const SkMatrix & matrix)493 static GrFragmentProcessor* Create(SkPerlinNoiseShader::Type type,
494 int numOctaves, bool stitchTiles,
495 SkPerlinNoiseShader::PaintingData* paintingData,
496 GrTexture* permutationsTexture, GrTexture* noiseTexture,
497 const SkMatrix& matrix) {
498 return new GrPerlinNoiseEffect(type, numOctaves, stitchTiles, paintingData,
499 permutationsTexture, noiseTexture, matrix);
500 }
501
~GrPerlinNoiseEffect()502 virtual ~GrPerlinNoiseEffect() { delete fPaintingData; }
503
name() const504 const char* name() const override { return "PerlinNoise"; }
505
stitchData() const506 const SkPerlinNoiseShader::StitchData& stitchData() const { return fPaintingData->fStitchDataInit; }
507
type() const508 SkPerlinNoiseShader::Type type() const { return fType; }
stitchTiles() const509 bool stitchTiles() const { return fStitchTiles; }
baseFrequency() const510 const SkVector& baseFrequency() const { return fPaintingData->fBaseFrequency; }
numOctaves() const511 int numOctaves() const { return fNumOctaves; }
matrix() const512 const SkMatrix& matrix() const { return fCoordTransform.getMatrix(); }
513
514 private:
onCreateGLSLInstance() const515 GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
516 return new GrGLPerlinNoise;
517 }
518
onGetGLSLProcessorKey(const GrGLSLCaps & caps,GrProcessorKeyBuilder * b) const519 virtual void onGetGLSLProcessorKey(const GrGLSLCaps& caps,
520 GrProcessorKeyBuilder* b) const override {
521 GrGLPerlinNoise::GenKey(*this, caps, b);
522 }
523
onIsEqual(const GrFragmentProcessor & sBase) const524 bool onIsEqual(const GrFragmentProcessor& sBase) const override {
525 const GrPerlinNoiseEffect& s = sBase.cast<GrPerlinNoiseEffect>();
526 return fType == s.fType &&
527 fPaintingData->fBaseFrequency == s.fPaintingData->fBaseFrequency &&
528 fNumOctaves == s.fNumOctaves &&
529 fStitchTiles == s.fStitchTiles &&
530 fPaintingData->fStitchDataInit == s.fPaintingData->fStitchDataInit;
531 }
532
onComputeInvariantOutput(GrInvariantOutput * inout) const533 void onComputeInvariantOutput(GrInvariantOutput* inout) const override {
534 inout->setToUnknown(GrInvariantOutput::kWillNot_ReadInput);
535 }
536
GrPerlinNoiseEffect(SkPerlinNoiseShader::Type type,int numOctaves,bool stitchTiles,SkPerlinNoiseShader::PaintingData * paintingData,GrTexture * permutationsTexture,GrTexture * noiseTexture,const SkMatrix & matrix)537 GrPerlinNoiseEffect(SkPerlinNoiseShader::Type type,
538 int numOctaves, bool stitchTiles,
539 SkPerlinNoiseShader::PaintingData* paintingData,
540 GrTexture* permutationsTexture, GrTexture* noiseTexture,
541 const SkMatrix& matrix)
542 : fType(type)
543 , fNumOctaves(numOctaves)
544 , fStitchTiles(stitchTiles)
545 , fPermutationsAccess(permutationsTexture)
546 , fNoiseAccess(noiseTexture)
547 , fPaintingData(paintingData) {
548 this->initClassID<GrPerlinNoiseEffect>();
549 this->addTextureAccess(&fPermutationsAccess);
550 this->addTextureAccess(&fNoiseAccess);
551 fCoordTransform.reset(kLocal_GrCoordSet, matrix);
552 this->addCoordTransform(&fCoordTransform);
553 }
554
555 GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
556
557 SkPerlinNoiseShader::Type fType;
558 GrCoordTransform fCoordTransform;
559 int fNumOctaves;
560 bool fStitchTiles;
561 GrTextureAccess fPermutationsAccess;
562 GrTextureAccess fNoiseAccess;
563 SkPerlinNoiseShader::PaintingData *fPaintingData;
564
565 private:
566 typedef GrFragmentProcessor INHERITED;
567 };
568
569 /////////////////////////////////////////////////////////////////////
570 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrPerlinNoiseEffect);
571
TestCreate(GrProcessorTestData * d)572 const GrFragmentProcessor* GrPerlinNoiseEffect::TestCreate(GrProcessorTestData* d) {
573 int numOctaves = d->fRandom->nextRangeU(2, 10);
574 bool stitchTiles = d->fRandom->nextBool();
575 SkScalar seed = SkIntToScalar(d->fRandom->nextU());
576 SkISize tileSize = SkISize::Make(d->fRandom->nextRangeU(4, 4096),
577 d->fRandom->nextRangeU(4, 4096));
578 SkScalar baseFrequencyX = d->fRandom->nextRangeScalar(0.01f,
579 0.99f);
580 SkScalar baseFrequencyY = d->fRandom->nextRangeScalar(0.01f,
581 0.99f);
582
583 SkAutoTUnref<SkShader> shader(d->fRandom->nextBool() ?
584 SkPerlinNoiseShader::CreateFractalNoise(baseFrequencyX, baseFrequencyY, numOctaves, seed,
585 stitchTiles ? &tileSize : nullptr) :
586 SkPerlinNoiseShader::CreateTurbulence(baseFrequencyX, baseFrequencyY, numOctaves, seed,
587 stitchTiles ? &tileSize : nullptr));
588
589 return shader->asFragmentProcessor(d->fContext,
590 GrTest::TestMatrix(d->fRandom), nullptr,
591 kNone_SkFilterQuality);
592 }
593
emitCode(EmitArgs & args)594 void GrGLPerlinNoise::emitCode(EmitArgs& args) {
595 const GrPerlinNoiseEffect& pne = args.fFp.cast<GrPerlinNoiseEffect>();
596
597 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
598 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
599 SkString vCoords = fragBuilder->ensureFSCoords2D(args.fCoords, 0);
600
601 fBaseFrequencyUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
602 kVec2f_GrSLType, kDefault_GrSLPrecision,
603 "baseFrequency");
604 const char* baseFrequencyUni = uniformHandler->getUniformCStr(fBaseFrequencyUni);
605
606 const char* stitchDataUni = nullptr;
607 if (pne.stitchTiles()) {
608 fStitchDataUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
609 kVec2f_GrSLType, kDefault_GrSLPrecision,
610 "stitchData");
611 stitchDataUni = uniformHandler->getUniformCStr(fStitchDataUni);
612 }
613
614 // There are 4 lines, so the center of each line is 1/8, 3/8, 5/8 and 7/8
615 const char* chanCoordR = "0.125";
616 const char* chanCoordG = "0.375";
617 const char* chanCoordB = "0.625";
618 const char* chanCoordA = "0.875";
619 const char* chanCoord = "chanCoord";
620 const char* stitchData = "stitchData";
621 const char* ratio = "ratio";
622 const char* noiseVec = "noiseVec";
623 const char* noiseSmooth = "noiseSmooth";
624 const char* floorVal = "floorVal";
625 const char* fractVal = "fractVal";
626 const char* uv = "uv";
627 const char* ab = "ab";
628 const char* latticeIdx = "latticeIdx";
629 const char* bcoords = "bcoords";
630 const char* lattice = "lattice";
631 const char* inc8bit = "0.00390625"; // 1.0 / 256.0
632 // This is the math to convert the two 16bit integer packed into rgba 8 bit input into a
633 // [-1,1] vector and perform a dot product between that vector and the provided vector.
634 const char* dotLattice = "dot(((%s.ga + %s.rb * vec2(%s)) * vec2(2.0) - vec2(1.0)), %s);";
635
636 // Add noise function
637 static const GrGLSLShaderVar gPerlinNoiseArgs[] = {
638 GrGLSLShaderVar(chanCoord, kFloat_GrSLType),
639 GrGLSLShaderVar(noiseVec, kVec2f_GrSLType)
640 };
641
642 static const GrGLSLShaderVar gPerlinNoiseStitchArgs[] = {
643 GrGLSLShaderVar(chanCoord, kFloat_GrSLType),
644 GrGLSLShaderVar(noiseVec, kVec2f_GrSLType),
645 GrGLSLShaderVar(stitchData, kVec2f_GrSLType)
646 };
647
648 SkString noiseCode;
649
650 noiseCode.appendf("\tvec4 %s;\n", floorVal);
651 noiseCode.appendf("\t%s.xy = floor(%s);\n", floorVal, noiseVec);
652 noiseCode.appendf("\t%s.zw = %s.xy + vec2(1.0);\n", floorVal, floorVal);
653 noiseCode.appendf("\tvec2 %s = fract(%s);\n", fractVal, noiseVec);
654
655 // smooth curve : t * t * (3 - 2 * t)
656 noiseCode.appendf("\n\tvec2 %s = %s * %s * (vec2(3.0) - vec2(2.0) * %s);",
657 noiseSmooth, fractVal, fractVal, fractVal);
658
659 // Adjust frequencies if we're stitching tiles
660 if (pne.stitchTiles()) {
661 noiseCode.appendf("\n\tif(%s.x >= %s.x) { %s.x -= %s.x; }",
662 floorVal, stitchData, floorVal, stitchData);
663 noiseCode.appendf("\n\tif(%s.y >= %s.y) { %s.y -= %s.y; }",
664 floorVal, stitchData, floorVal, stitchData);
665 noiseCode.appendf("\n\tif(%s.z >= %s.x) { %s.z -= %s.x; }",
666 floorVal, stitchData, floorVal, stitchData);
667 noiseCode.appendf("\n\tif(%s.w >= %s.y) { %s.w -= %s.y; }",
668 floorVal, stitchData, floorVal, stitchData);
669 }
670
671 // Get texture coordinates and normalize
672 noiseCode.appendf("\n\t%s = fract(floor(mod(%s, 256.0)) / vec4(256.0));\n",
673 floorVal, floorVal);
674
675 // Get permutation for x
676 {
677 SkString xCoords("");
678 xCoords.appendf("vec2(%s.x, 0.5)", floorVal);
679
680 noiseCode.appendf("\n\tvec2 %s;\n\t%s.x = ", latticeIdx, latticeIdx);
681 fragBuilder->appendTextureLookup(&noiseCode, args.fSamplers[0], xCoords.c_str(),
682 kVec2f_GrSLType);
683 noiseCode.append(".r;");
684 }
685
686 // Get permutation for x + 1
687 {
688 SkString xCoords("");
689 xCoords.appendf("vec2(%s.z, 0.5)", floorVal);
690
691 noiseCode.appendf("\n\t%s.y = ", latticeIdx);
692 fragBuilder->appendTextureLookup(&noiseCode, args.fSamplers[0], xCoords.c_str(),
693 kVec2f_GrSLType);
694 noiseCode.append(".r;");
695 }
696
697 #if defined(SK_BUILD_FOR_ANDROID)
698 // Android rounding for Tegra devices, like, for example: Xoom (Tegra 2), Nexus 7 (Tegra 3).
699 // The issue is that colors aren't accurate enough on Tegra devices. For example, if an 8 bit
700 // value of 124 (or 0.486275 here) is entered, we can get a texture value of 123.513725
701 // (or 0.484368 here). The following rounding operation prevents these precision issues from
702 // affecting the result of the noise by making sure that we only have multiples of 1/255.
703 // (Note that 1/255 is about 0.003921569, which is the value used here).
704 noiseCode.appendf("\n\t%s = floor(%s * vec2(255.0) + vec2(0.5)) * vec2(0.003921569);",
705 latticeIdx, latticeIdx);
706 #endif
707
708 // Get (x,y) coordinates with the permutated x
709 noiseCode.appendf("\n\tvec4 %s = fract(%s.xyxy + %s.yyww);", bcoords, latticeIdx, floorVal);
710
711 noiseCode.appendf("\n\n\tvec2 %s;", uv);
712 // Compute u, at offset (0,0)
713 {
714 SkString latticeCoords("");
715 latticeCoords.appendf("vec2(%s.x, %s)", bcoords, chanCoord);
716 noiseCode.appendf("\n\tvec4 %s = ", lattice);
717 fragBuilder->appendTextureLookup(&noiseCode, args.fSamplers[1], latticeCoords.c_str(),
718 kVec2f_GrSLType);
719 noiseCode.appendf(".bgra;\n\t%s.x = ", uv);
720 noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal);
721 }
722
723 noiseCode.appendf("\n\t%s.x -= 1.0;", fractVal);
724 // Compute v, at offset (-1,0)
725 {
726 SkString latticeCoords("");
727 latticeCoords.appendf("vec2(%s.y, %s)", bcoords, chanCoord);
728 noiseCode.append("\n\tlattice = ");
729 fragBuilder->appendTextureLookup(&noiseCode, args.fSamplers[1], latticeCoords.c_str(),
730 kVec2f_GrSLType);
731 noiseCode.appendf(".bgra;\n\t%s.y = ", uv);
732 noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal);
733 }
734
735 // Compute 'a' as a linear interpolation of 'u' and 'v'
736 noiseCode.appendf("\n\tvec2 %s;", ab);
737 noiseCode.appendf("\n\t%s.x = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth);
738
739 noiseCode.appendf("\n\t%s.y -= 1.0;", fractVal);
740 // Compute v, at offset (-1,-1)
741 {
742 SkString latticeCoords("");
743 latticeCoords.appendf("vec2(%s.w, %s)", bcoords, chanCoord);
744 noiseCode.append("\n\tlattice = ");
745 fragBuilder->appendTextureLookup(&noiseCode, args.fSamplers[1], latticeCoords.c_str(),
746 kVec2f_GrSLType);
747 noiseCode.appendf(".bgra;\n\t%s.y = ", uv);
748 noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal);
749 }
750
751 noiseCode.appendf("\n\t%s.x += 1.0;", fractVal);
752 // Compute u, at offset (0,-1)
753 {
754 SkString latticeCoords("");
755 latticeCoords.appendf("vec2(%s.z, %s)", bcoords, chanCoord);
756 noiseCode.append("\n\tlattice = ");
757 fragBuilder->appendTextureLookup(&noiseCode, args.fSamplers[1], latticeCoords.c_str(),
758 kVec2f_GrSLType);
759 noiseCode.appendf(".bgra;\n\t%s.x = ", uv);
760 noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal);
761 }
762
763 // Compute 'b' as a linear interpolation of 'u' and 'v'
764 noiseCode.appendf("\n\t%s.y = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth);
765 // Compute the noise as a linear interpolation of 'a' and 'b'
766 noiseCode.appendf("\n\treturn mix(%s.x, %s.y, %s.y);\n", ab, ab, noiseSmooth);
767
768 SkString noiseFuncName;
769 if (pne.stitchTiles()) {
770 fragBuilder->emitFunction(kFloat_GrSLType,
771 "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseStitchArgs),
772 gPerlinNoiseStitchArgs, noiseCode.c_str(), &noiseFuncName);
773 } else {
774 fragBuilder->emitFunction(kFloat_GrSLType,
775 "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseArgs),
776 gPerlinNoiseArgs, noiseCode.c_str(), &noiseFuncName);
777 }
778
779 // There are rounding errors if the floor operation is not performed here
780 fragBuilder->codeAppendf("\n\t\tvec2 %s = floor(%s.xy) * %s;",
781 noiseVec, vCoords.c_str(), baseFrequencyUni);
782
783 // Clear the color accumulator
784 fragBuilder->codeAppendf("\n\t\t%s = vec4(0.0);", args.fOutputColor);
785
786 if (pne.stitchTiles()) {
787 // Set up TurbulenceInitial stitch values.
788 fragBuilder->codeAppendf("vec2 %s = %s;", stitchData, stitchDataUni);
789 }
790
791 fragBuilder->codeAppendf("float %s = 1.0;", ratio);
792
793 // Loop over all octaves
794 fragBuilder->codeAppendf("for (int octave = 0; octave < %d; ++octave) {", pne.numOctaves());
795
796 fragBuilder->codeAppendf("%s += ", args.fOutputColor);
797 if (pne.type() != SkPerlinNoiseShader::kFractalNoise_Type) {
798 fragBuilder->codeAppend("abs(");
799 }
800 if (pne.stitchTiles()) {
801 fragBuilder->codeAppendf(
802 "vec4(\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s),"
803 "\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s))",
804 noiseFuncName.c_str(), chanCoordR, noiseVec, stitchData,
805 noiseFuncName.c_str(), chanCoordG, noiseVec, stitchData,
806 noiseFuncName.c_str(), chanCoordB, noiseVec, stitchData,
807 noiseFuncName.c_str(), chanCoordA, noiseVec, stitchData);
808 } else {
809 fragBuilder->codeAppendf(
810 "vec4(\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s),"
811 "\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s))",
812 noiseFuncName.c_str(), chanCoordR, noiseVec,
813 noiseFuncName.c_str(), chanCoordG, noiseVec,
814 noiseFuncName.c_str(), chanCoordB, noiseVec,
815 noiseFuncName.c_str(), chanCoordA, noiseVec);
816 }
817 if (pne.type() != SkPerlinNoiseShader::kFractalNoise_Type) {
818 fragBuilder->codeAppendf(")"); // end of "abs("
819 }
820 fragBuilder->codeAppendf(" * %s;", ratio);
821
822 fragBuilder->codeAppendf("\n\t\t\t%s *= vec2(2.0);", noiseVec);
823 fragBuilder->codeAppendf("\n\t\t\t%s *= 0.5;", ratio);
824
825 if (pne.stitchTiles()) {
826 fragBuilder->codeAppendf("\n\t\t\t%s *= vec2(2.0);", stitchData);
827 }
828 fragBuilder->codeAppend("\n\t\t}"); // end of the for loop on octaves
829
830 if (pne.type() == SkPerlinNoiseShader::kFractalNoise_Type) {
831 // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2
832 // by fractalNoise and (turbulenceFunctionResult) by turbulence.
833 fragBuilder->codeAppendf("\n\t\t%s = %s * vec4(0.5) + vec4(0.5);",
834 args.fOutputColor,args.fOutputColor);
835 }
836
837 // Clamp values
838 fragBuilder->codeAppendf("\n\t\t%s = clamp(%s, 0.0, 1.0);", args.fOutputColor, args.fOutputColor);
839
840 // Pre-multiply the result
841 fragBuilder->codeAppendf("\n\t\t%s = vec4(%s.rgb * %s.aaa, %s.a);\n",
842 args.fOutputColor, args.fOutputColor,
843 args.fOutputColor, args.fOutputColor);
844 }
845
GenKey(const GrProcessor & processor,const GrGLSLCaps &,GrProcessorKeyBuilder * b)846 void GrGLPerlinNoise::GenKey(const GrProcessor& processor, const GrGLSLCaps&,
847 GrProcessorKeyBuilder* b) {
848 const GrPerlinNoiseEffect& turbulence = processor.cast<GrPerlinNoiseEffect>();
849
850 uint32_t key = turbulence.numOctaves();
851
852 key = key << 3; // Make room for next 3 bits
853
854 switch (turbulence.type()) {
855 case SkPerlinNoiseShader::kFractalNoise_Type:
856 key |= 0x1;
857 break;
858 case SkPerlinNoiseShader::kTurbulence_Type:
859 key |= 0x2;
860 break;
861 default:
862 // leave key at 0
863 break;
864 }
865
866 if (turbulence.stitchTiles()) {
867 key |= 0x4; // Flip the 3rd bit if tile stitching is on
868 }
869
870 b->add32(key);
871 }
872
onSetData(const GrGLSLProgramDataManager & pdman,const GrProcessor & processor)873 void GrGLPerlinNoise::onSetData(const GrGLSLProgramDataManager& pdman,
874 const GrProcessor& processor) {
875 INHERITED::onSetData(pdman, processor);
876
877 const GrPerlinNoiseEffect& turbulence = processor.cast<GrPerlinNoiseEffect>();
878
879 const SkVector& baseFrequency = turbulence.baseFrequency();
880 pdman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY);
881
882 if (turbulence.stitchTiles()) {
883 const SkPerlinNoiseShader::StitchData& stitchData = turbulence.stitchData();
884 pdman.set2f(fStitchDataUni, SkIntToScalar(stitchData.fWidth),
885 SkIntToScalar(stitchData.fHeight));
886 }
887 }
888
889 /////////////////////////////////////////////////////////////////////
asFragmentProcessor(GrContext * context,const SkMatrix & viewM,const SkMatrix * externalLocalMatrix,SkFilterQuality) const890 const GrFragmentProcessor* SkPerlinNoiseShader::asFragmentProcessor(
891 GrContext* context,
892 const SkMatrix& viewM,
893 const SkMatrix* externalLocalMatrix,
894 SkFilterQuality) const {
895 SkASSERT(context);
896
897 SkMatrix localMatrix = this->getLocalMatrix();
898 if (externalLocalMatrix) {
899 localMatrix.preConcat(*externalLocalMatrix);
900 }
901
902 SkMatrix matrix = viewM;
903 matrix.preConcat(localMatrix);
904
905 if (0 == fNumOctaves) {
906 if (kFractalNoise_Type == fType) {
907 // Extract the incoming alpha and emit rgba = (a/4, a/4, a/4, a/2)
908 SkAutoTUnref<const GrFragmentProcessor> inner(
909 GrConstColorProcessor::Create(0x80404040,
910 GrConstColorProcessor::kModulateRGBA_InputMode));
911 return GrFragmentProcessor::MulOutputByInputAlpha(inner);
912 }
913 // Emit zero.
914 return GrConstColorProcessor::Create(0x0, GrConstColorProcessor::kIgnore_InputMode);
915 }
916
917 // Either we don't stitch tiles, either we have a valid tile size
918 SkASSERT(!fStitchTiles || !fTileSize.isEmpty());
919
920 SkPerlinNoiseShader::PaintingData* paintingData =
921 new PaintingData(fTileSize, fSeed, fBaseFrequencyX, fBaseFrequencyY, matrix);
922 SkAutoTUnref<GrTexture> permutationsTexture(
923 GrRefCachedBitmapTexture(context, paintingData->getPermutationsBitmap(),
924 GrTextureParams::ClampNoFilter()));
925 SkAutoTUnref<GrTexture> noiseTexture(
926 GrRefCachedBitmapTexture(context, paintingData->getNoiseBitmap(),
927 GrTextureParams::ClampNoFilter()));
928
929 SkMatrix m = viewM;
930 m.setTranslateX(-localMatrix.getTranslateX() + SK_Scalar1);
931 m.setTranslateY(-localMatrix.getTranslateY() + SK_Scalar1);
932 if ((permutationsTexture) && (noiseTexture)) {
933 SkAutoTUnref<GrFragmentProcessor> inner(
934 GrPerlinNoiseEffect::Create(fType,
935 fNumOctaves,
936 fStitchTiles,
937 paintingData,
938 permutationsTexture, noiseTexture,
939 m));
940 return GrFragmentProcessor::MulOutputByInputAlpha(inner);
941 }
942 delete paintingData;
943 return nullptr;
944 }
945
946 #endif
947
948 #ifndef SK_IGNORE_TO_STRING
toString(SkString * str) const949 void SkPerlinNoiseShader::toString(SkString* str) const {
950 str->append("SkPerlinNoiseShader: (");
951
952 str->append("type: ");
953 switch (fType) {
954 case kFractalNoise_Type:
955 str->append("\"fractal noise\"");
956 break;
957 case kTurbulence_Type:
958 str->append("\"turbulence\"");
959 break;
960 default:
961 str->append("\"unknown\"");
962 break;
963 }
964 str->append(" base frequency: (");
965 str->appendScalar(fBaseFrequencyX);
966 str->append(", ");
967 str->appendScalar(fBaseFrequencyY);
968 str->append(") number of octaves: ");
969 str->appendS32(fNumOctaves);
970 str->append(" seed: ");
971 str->appendScalar(fSeed);
972 str->append(" stitch tiles: ");
973 str->append(fStitchTiles ? "true " : "false ");
974
975 this->INHERITED::toString(str);
976
977 str->append(")");
978 }
979 #endif
980