From d8872be505c5c9c48072fe62c244e53e9b7334f1 Mon Sep 17 00:00:00 2001 From: Stan Iliev Date: Thu, 25 May 2017 22:07:16 +0000 Subject: Revert "Relocate shaders to own dir" This reverts commit fabe0b26d05624ce7374f6ca89bd66df6142534e. Reason for revert: Last android roll failed with "external/skia/src/effects/SkGaussianEdgeShader.h:11:10: fatal error: 'SkShaderBase.h' file not found" Original change's description: > Relocate shaders to own dir > > Consolidate all shader impls under src/shaders/. > > Change-Id: I450e37541214704c1ad9e379d9d753b7cc62fac3 > Reviewed-on: https://skia-review.googlesource.com/17927 > Commit-Queue: Florin Malita > Reviewed-by: Herb Derby > TBR=mtklein@google.com,herb@google.com,fmalita@chromium.org,reed@google.com No-Presubmit: true No-Tree-Checks: true No-Try: true Change-Id: Idbb2b75053969df1dad9d8ce0217cd39189b9ddb Reviewed-on: https://skia-review.googlesource.com/18020 Reviewed-by: Stan Iliev Commit-Queue: Stan Iliev --- src/effects/SkPerlinNoiseShader.cpp | 1056 +++++++++++ src/effects/gradients/Sk4fGradientBase.cpp | 451 +++++ src/effects/gradients/Sk4fGradientBase.h | 97 + src/effects/gradients/Sk4fGradientPriv.h | 194 ++ src/effects/gradients/Sk4fLinearGradient.cpp | 528 ++++++ src/effects/gradients/Sk4fLinearGradient.h | 51 + src/effects/gradients/SkClampRange.cpp | 178 ++ src/effects/gradients/SkClampRange.h | 62 + src/effects/gradients/SkGradientBitmapCache.cpp | 154 ++ src/effects/gradients/SkGradientBitmapCache.h | 48 + src/effects/gradients/SkGradientShader.cpp | 2004 ++++++++++++++++++++ src/effects/gradients/SkGradientShaderPriv.h | 540 ++++++ src/effects/gradients/SkLinearGradient.cpp | 804 ++++++++ src/effects/gradients/SkLinearGradient.h | 87 + src/effects/gradients/SkRadialGradient.cpp | 405 ++++ src/effects/gradients/SkRadialGradient.h | 53 + src/effects/gradients/SkSweepGradient.cpp | 225 +++ src/effects/gradients/SkSweepGradient.h | 43 + .../gradients/SkTwoPointConicalGradient.cpp | 421 ++++ src/effects/gradients/SkTwoPointConicalGradient.h | 93 + .../gradients/SkTwoPointConicalGradient_gpu.cpp | 1341 +++++++++++++ .../gradients/SkTwoPointConicalGradient_gpu.h | 24 + 22 files changed, 8859 insertions(+) create mode 100644 src/effects/SkPerlinNoiseShader.cpp create mode 100644 src/effects/gradients/Sk4fGradientBase.cpp create mode 100644 src/effects/gradients/Sk4fGradientBase.h create mode 100644 src/effects/gradients/Sk4fGradientPriv.h create mode 100644 src/effects/gradients/Sk4fLinearGradient.cpp create mode 100644 src/effects/gradients/Sk4fLinearGradient.h create mode 100644 src/effects/gradients/SkClampRange.cpp create mode 100644 src/effects/gradients/SkClampRange.h create mode 100644 src/effects/gradients/SkGradientBitmapCache.cpp create mode 100644 src/effects/gradients/SkGradientBitmapCache.h create mode 100644 src/effects/gradients/SkGradientShader.cpp create mode 100644 src/effects/gradients/SkGradientShaderPriv.h create mode 100644 src/effects/gradients/SkLinearGradient.cpp create mode 100644 src/effects/gradients/SkLinearGradient.h create mode 100644 src/effects/gradients/SkRadialGradient.cpp create mode 100644 src/effects/gradients/SkRadialGradient.h create mode 100644 src/effects/gradients/SkSweepGradient.cpp create mode 100644 src/effects/gradients/SkSweepGradient.h create mode 100644 src/effects/gradients/SkTwoPointConicalGradient.cpp create mode 100644 src/effects/gradients/SkTwoPointConicalGradient.h create mode 100644 src/effects/gradients/SkTwoPointConicalGradient_gpu.cpp create mode 100644 src/effects/gradients/SkTwoPointConicalGradient_gpu.h (limited to 'src/effects') diff --git a/src/effects/SkPerlinNoiseShader.cpp b/src/effects/SkPerlinNoiseShader.cpp new file mode 100644 index 0000000000..87f8967242 --- /dev/null +++ b/src/effects/SkPerlinNoiseShader.cpp @@ -0,0 +1,1056 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkPerlinNoiseShader.h" + +#include "SkArenaAlloc.h" +#include "SkColorFilter.h" +#include "SkReadBuffer.h" +#include "SkShaderBase.h" +#include "SkString.h" +#include "SkUnPreMultiply.h" +#include "SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "GrContext.h" +#include "GrCoordTransform.h" +#include "SkGr.h" +#include "effects/GrConstColorProcessor.h" +#include "glsl/GrGLSLFragmentProcessor.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLProgramDataManager.h" +#include "glsl/GrGLSLUniformHandler.h" +#endif + +static const int kBlockSize = 256; +static const int kBlockMask = kBlockSize - 1; +static const int kPerlinNoise = 4096; +static const int kRandMaximum = SK_MaxS32; // 2**31 - 1 + +namespace { + +// noiseValue is the color component's value (or color) +// limitValue is the maximum perlin noise array index value allowed +// newValue is the current noise dimension (either width or height) +inline int checkNoise(int noiseValue, int limitValue, int newValue) { + // If the noise value would bring us out of bounds of the current noise array while we are + // stiching noise tiles together, wrap the noise around the current dimension of the noise to + // stay within the array bounds in a continuous fashion (so that tiling lines are not visible) + if (noiseValue >= limitValue) { + noiseValue -= newValue; + } + return noiseValue; +} + +inline SkScalar smoothCurve(SkScalar t) { + return t * t * (3 - 2 * t); +} + +class SkPerlinNoiseShaderImpl final : public SkShaderBase { +public: + /** + * About the noise types : the difference between the 2 is just minor tweaks to the algorithm, + * they're not 2 entirely different noises. The output looks different, but once the noise is + * generated in the [1, -1] range, the output is brought back in the [0, 1] range by doing : + * kFractalNoise_Type : noise * 0.5 + 0.5 + * kTurbulence_Type : abs(noise) + * Very little differences between the 2 types, although you can tell the difference visually. + */ + enum Type { + kFractalNoise_Type, + kTurbulence_Type, + kFirstType = kFractalNoise_Type, + kLastType = kTurbulence_Type + }; + + SkPerlinNoiseShaderImpl(Type type, SkScalar baseFrequencyX, + SkScalar baseFrequencyY, int numOctaves, SkScalar seed, + const SkISize* tileSize); + ~SkPerlinNoiseShaderImpl() override = default; + +#if SK_SUPPORT_GPU + sk_sp asFragmentProcessor(const AsFPArgs&) const override; +#endif + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPerlinNoiseShaderImpl) + + struct StitchData; + struct PaintingData; + +protected: + void flatten(SkWriteBuffer&) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc* storage) const override; + +private: + class PerlinNoiseShaderContext final : public Context { + public: + PerlinNoiseShaderContext(const SkPerlinNoiseShaderImpl& shader, const ContextRec&); + ~PerlinNoiseShaderContext() override; + + void shadeSpan(int x, int y, SkPMColor[], int count) override; + + private: + SkPMColor shade(const SkPoint& point, StitchData& stitchData) const; + SkScalar calculateTurbulenceValueForPoint( + int channel, + StitchData& stitchData, const SkPoint& point) const; + SkScalar noise2D(int channel, + const StitchData& stitchData, const SkPoint& noiseVector) const; + + SkMatrix fMatrix; + PaintingData* fPaintingData; + + typedef Context INHERITED; + }; + + const Type fType; + const SkScalar fBaseFrequencyX; + const SkScalar fBaseFrequencyY; + const int fNumOctaves; + const SkScalar fSeed; + const SkISize fTileSize; + const bool fStitchTiles; + + friend class ::SkPerlinNoiseShader; + + typedef SkShaderBase INHERITED; +}; + +} // end namespace + +struct SkPerlinNoiseShaderImpl::StitchData { + StitchData() + : fWidth(0) + , fWrapX(0) + , fHeight(0) + , fWrapY(0) + {} + + bool operator==(const StitchData& other) const { + return fWidth == other.fWidth && + fWrapX == other.fWrapX && + fHeight == other.fHeight && + fWrapY == other.fWrapY; + } + + int fWidth; // How much to subtract to wrap for stitching. + int fWrapX; // Minimum value to wrap. + int fHeight; + int fWrapY; +}; + +struct SkPerlinNoiseShaderImpl::PaintingData { + PaintingData(const SkISize& tileSize, SkScalar seed, + SkScalar baseFrequencyX, SkScalar baseFrequencyY, + const SkMatrix& matrix) + { + SkVector vec[2] = { + { SkScalarInvert(baseFrequencyX), SkScalarInvert(baseFrequencyY) }, + { SkIntToScalar(tileSize.fWidth), SkIntToScalar(tileSize.fHeight) }, + }; + matrix.mapVectors(vec, 2); + + fBaseFrequency.set(SkScalarInvert(vec[0].fX), SkScalarInvert(vec[0].fY)); + fTileSize.set(SkScalarRoundToInt(vec[1].fX), SkScalarRoundToInt(vec[1].fY)); + this->init(seed); + if (!fTileSize.isEmpty()) { + this->stitch(); + } + +#if SK_SUPPORT_GPU + fPermutationsBitmap.setInfo(SkImageInfo::MakeA8(kBlockSize, 1)); + fPermutationsBitmap.setPixels(fLatticeSelector); + + fNoiseBitmap.setInfo(SkImageInfo::MakeN32Premul(kBlockSize, 4)); + fNoiseBitmap.setPixels(fNoise[0][0]); +#endif + } + + int fSeed; + uint8_t fLatticeSelector[kBlockSize]; + uint16_t fNoise[4][kBlockSize][2]; + SkPoint fGradient[4][kBlockSize]; + SkISize fTileSize; + SkVector fBaseFrequency; + StitchData fStitchDataInit; + +private: + +#if SK_SUPPORT_GPU + SkBitmap fPermutationsBitmap; + SkBitmap fNoiseBitmap; +#endif + + inline int random() { + static const int gRandAmplitude = 16807; // 7**5; primitive root of m + static const int gRandQ = 127773; // m / a + static const int gRandR = 2836; // m % a + + int result = gRandAmplitude * (fSeed % gRandQ) - gRandR * (fSeed / gRandQ); + if (result <= 0) + result += kRandMaximum; + fSeed = result; + return result; + } + + // Only called once. Could be part of the constructor. + void init(SkScalar seed) + { + static const SkScalar gInvBlockSizef = SkScalarInvert(SkIntToScalar(kBlockSize)); + + // According to the SVG spec, we must truncate (not round) the seed value. + fSeed = SkScalarTruncToInt(seed); + // The seed value clamp to the range [1, kRandMaximum - 1]. + if (fSeed <= 0) { + fSeed = -(fSeed % (kRandMaximum - 1)) + 1; + } + if (fSeed > kRandMaximum - 1) { + fSeed = kRandMaximum - 1; + } + for (int channel = 0; channel < 4; ++channel) { + for (int i = 0; i < kBlockSize; ++i) { + fLatticeSelector[i] = i; + fNoise[channel][i][0] = (random() % (2 * kBlockSize)); + fNoise[channel][i][1] = (random() % (2 * kBlockSize)); + } + } + for (int i = kBlockSize - 1; i > 0; --i) { + int k = fLatticeSelector[i]; + int j = random() % kBlockSize; + SkASSERT(j >= 0); + SkASSERT(j < kBlockSize); + fLatticeSelector[i] = fLatticeSelector[j]; + fLatticeSelector[j] = k; + } + + // Perform the permutations now + { + // Copy noise data + uint16_t noise[4][kBlockSize][2]; + for (int i = 0; i < kBlockSize; ++i) { + for (int channel = 0; channel < 4; ++channel) { + for (int j = 0; j < 2; ++j) { + noise[channel][i][j] = fNoise[channel][i][j]; + } + } + } + // Do permutations on noise data + for (int i = 0; i < kBlockSize; ++i) { + for (int channel = 0; channel < 4; ++channel) { + for (int j = 0; j < 2; ++j) { + fNoise[channel][i][j] = noise[channel][fLatticeSelector[i]][j]; + } + } + } + } + + // Half of the largest possible value for 16 bit unsigned int + static const SkScalar gHalfMax16bits = 32767.5f; + + // Compute gradients from permutated noise data + for (int channel = 0; channel < 4; ++channel) { + for (int i = 0; i < kBlockSize; ++i) { + fGradient[channel][i] = SkPoint::Make( + (fNoise[channel][i][0] - kBlockSize) * gInvBlockSizef, + (fNoise[channel][i][1] - kBlockSize) * gInvBlockSizef); + fGradient[channel][i].normalize(); + // Put the normalized gradient back into the noise data + fNoise[channel][i][0] = SkScalarRoundToInt( + (fGradient[channel][i].fX + 1) * gHalfMax16bits); + fNoise[channel][i][1] = SkScalarRoundToInt( + (fGradient[channel][i].fY + 1) * gHalfMax16bits); + } + } + } + + // Only called once. Could be part of the constructor. + void stitch() { + SkScalar tileWidth = SkIntToScalar(fTileSize.width()); + SkScalar tileHeight = SkIntToScalar(fTileSize.height()); + SkASSERT(tileWidth > 0 && tileHeight > 0); + // When stitching tiled turbulence, the frequencies must be adjusted + // so that the tile borders will be continuous. + if (fBaseFrequency.fX) { + SkScalar lowFrequencx = + SkScalarFloorToScalar(tileWidth * fBaseFrequency.fX) / tileWidth; + SkScalar highFrequencx = + SkScalarCeilToScalar(tileWidth * fBaseFrequency.fX) / tileWidth; + // BaseFrequency should be non-negative according to the standard. + if (fBaseFrequency.fX / lowFrequencx < highFrequencx / fBaseFrequency.fX) { + fBaseFrequency.fX = lowFrequencx; + } else { + fBaseFrequency.fX = highFrequencx; + } + } + if (fBaseFrequency.fY) { + SkScalar lowFrequency = + SkScalarFloorToScalar(tileHeight * fBaseFrequency.fY) / tileHeight; + SkScalar highFrequency = + SkScalarCeilToScalar(tileHeight * fBaseFrequency.fY) / tileHeight; + if (fBaseFrequency.fY / lowFrequency < highFrequency / fBaseFrequency.fY) { + fBaseFrequency.fY = lowFrequency; + } else { + fBaseFrequency.fY = highFrequency; + } + } + // Set up TurbulenceInitial stitch values. + fStitchDataInit.fWidth = + SkScalarRoundToInt(tileWidth * fBaseFrequency.fX); + fStitchDataInit.fWrapX = kPerlinNoise + fStitchDataInit.fWidth; + fStitchDataInit.fHeight = + SkScalarRoundToInt(tileHeight * fBaseFrequency.fY); + fStitchDataInit.fWrapY = kPerlinNoise + fStitchDataInit.fHeight; + } + +public: + +#if SK_SUPPORT_GPU + const SkBitmap& getPermutationsBitmap() const { return fPermutationsBitmap; } + + const SkBitmap& getNoiseBitmap() const { return fNoiseBitmap; } +#endif +}; + +sk_sp SkPerlinNoiseShader::MakeFractalNoise(SkScalar baseFrequencyX, + SkScalar baseFrequencyY, + int numOctaves, SkScalar seed, + const SkISize* tileSize) { + return sk_make_sp(SkPerlinNoiseShaderImpl::kFractalNoise_Type, + baseFrequencyX, baseFrequencyY, numOctaves, + seed, tileSize); +} + +sk_sp SkPerlinNoiseShader::MakeTurbulence(SkScalar baseFrequencyX, + SkScalar baseFrequencyY, + int numOctaves, SkScalar seed, + const SkISize* tileSize) { + return sk_make_sp(SkPerlinNoiseShaderImpl::kTurbulence_Type, + baseFrequencyX, baseFrequencyY, + numOctaves, seed, tileSize); +} + +SkPerlinNoiseShaderImpl::SkPerlinNoiseShaderImpl(Type type, + SkScalar baseFrequencyX, + SkScalar baseFrequencyY, + int numOctaves, + SkScalar seed, + const SkISize* tileSize) + : fType(type) + , fBaseFrequencyX(baseFrequencyX) + , fBaseFrequencyY(baseFrequencyY) + , fNumOctaves(SkTPin(numOctaves, 0, 255)) // [0,255] octaves allowed + , fSeed(seed) + , fTileSize(nullptr == tileSize ? SkISize::Make(0, 0) : *tileSize) + , fStitchTiles(!fTileSize.isEmpty()) +{ + SkASSERT(fNumOctaves >= 0 && fNumOctaves < 256); +} + +sk_sp SkPerlinNoiseShaderImpl::CreateProc(SkReadBuffer& buffer) { + Type type = (Type)buffer.readInt(); + SkScalar freqX = buffer.readScalar(); + SkScalar freqY = buffer.readScalar(); + int octaves = buffer.readInt(); + SkScalar seed = buffer.readScalar(); + SkISize tileSize; + tileSize.fWidth = buffer.readInt(); + tileSize.fHeight = buffer.readInt(); + + switch (type) { + case kFractalNoise_Type: + return SkPerlinNoiseShader::MakeFractalNoise(freqX, freqY, octaves, seed, + &tileSize); + case kTurbulence_Type: + return SkPerlinNoiseShader::MakeTurbulence(freqX, freqY, octaves, seed, + &tileSize); + default: + return nullptr; + } +} + +void SkPerlinNoiseShaderImpl::flatten(SkWriteBuffer& buffer) const { + buffer.writeInt((int) fType); + buffer.writeScalar(fBaseFrequencyX); + buffer.writeScalar(fBaseFrequencyY); + buffer.writeInt(fNumOctaves); + buffer.writeScalar(fSeed); + buffer.writeInt(fTileSize.fWidth); + buffer.writeInt(fTileSize.fHeight); +} + +SkScalar SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::noise2D( + int channel, const StitchData& stitchData, const SkPoint& noiseVector) const { + struct Noise { + int noisePositionIntegerValue; + int nextNoisePositionIntegerValue; + SkScalar noisePositionFractionValue; + Noise(SkScalar component) + { + SkScalar position = component + kPerlinNoise; + noisePositionIntegerValue = SkScalarFloorToInt(position); + noisePositionFractionValue = position - SkIntToScalar(noisePositionIntegerValue); + nextNoisePositionIntegerValue = noisePositionIntegerValue + 1; + } + }; + Noise noiseX(noiseVector.x()); + Noise noiseY(noiseVector.y()); + SkScalar u, v; + const SkPerlinNoiseShaderImpl& perlinNoiseShader = + static_cast(fShader); + // If stitching, adjust lattice points accordingly. + if (perlinNoiseShader.fStitchTiles) { + noiseX.noisePositionIntegerValue = + checkNoise(noiseX.noisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth); + noiseY.noisePositionIntegerValue = + checkNoise(noiseY.noisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight); + noiseX.nextNoisePositionIntegerValue = + checkNoise(noiseX.nextNoisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth); + noiseY.nextNoisePositionIntegerValue = + checkNoise(noiseY.nextNoisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight); + } + noiseX.noisePositionIntegerValue &= kBlockMask; + noiseY.noisePositionIntegerValue &= kBlockMask; + noiseX.nextNoisePositionIntegerValue &= kBlockMask; + noiseY.nextNoisePositionIntegerValue &= kBlockMask; + int i = + fPaintingData->fLatticeSelector[noiseX.noisePositionIntegerValue]; + int j = + fPaintingData->fLatticeSelector[noiseX.nextNoisePositionIntegerValue]; + int b00 = (i + noiseY.noisePositionIntegerValue) & kBlockMask; + int b10 = (j + noiseY.noisePositionIntegerValue) & kBlockMask; + int b01 = (i + noiseY.nextNoisePositionIntegerValue) & kBlockMask; + int b11 = (j + noiseY.nextNoisePositionIntegerValue) & kBlockMask; + SkScalar sx = smoothCurve(noiseX.noisePositionFractionValue); + SkScalar sy = smoothCurve(noiseY.noisePositionFractionValue); + if (sx < 0 || sy < 0 || sx > 1 || sy > 1) { + return 0; // Check for pathological inputs. + } + // This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement + SkPoint fractionValue = SkPoint::Make(noiseX.noisePositionFractionValue, + noiseY.noisePositionFractionValue); // Offset (0,0) + u = fPaintingData->fGradient[channel][b00].dot(fractionValue); + fractionValue.fX -= SK_Scalar1; // Offset (-1,0) + v = fPaintingData->fGradient[channel][b10].dot(fractionValue); + SkScalar a = SkScalarInterp(u, v, sx); + fractionValue.fY -= SK_Scalar1; // Offset (-1,-1) + v = fPaintingData->fGradient[channel][b11].dot(fractionValue); + fractionValue.fX = noiseX.noisePositionFractionValue; // Offset (0,-1) + u = fPaintingData->fGradient[channel][b01].dot(fractionValue); + SkScalar b = SkScalarInterp(u, v, sx); + return SkScalarInterp(a, b, sy); +} + +SkScalar SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::calculateTurbulenceValueForPoint( + int channel, StitchData& stitchData, const SkPoint& point) const { + const SkPerlinNoiseShaderImpl& perlinNoiseShader = + static_cast(fShader); + if (perlinNoiseShader.fStitchTiles) { + // Set up TurbulenceInitial stitch values. + stitchData = fPaintingData->fStitchDataInit; + } + SkScalar turbulenceFunctionResult = 0; + SkPoint noiseVector(SkPoint::Make(point.x() * fPaintingData->fBaseFrequency.fX, + point.y() * fPaintingData->fBaseFrequency.fY)); + SkScalar ratio = SK_Scalar1; + for (int octave = 0; octave < perlinNoiseShader.fNumOctaves; ++octave) { + SkScalar noise = noise2D(channel, stitchData, noiseVector); + SkScalar numer = (perlinNoiseShader.fType == kFractalNoise_Type) ? + noise : SkScalarAbs(noise); + turbulenceFunctionResult += numer / ratio; + noiseVector.fX *= 2; + noiseVector.fY *= 2; + ratio *= 2; + if (perlinNoiseShader.fStitchTiles) { + // Update stitch values + stitchData.fWidth *= 2; + stitchData.fWrapX = stitchData.fWidth + kPerlinNoise; + stitchData.fHeight *= 2; + stitchData.fWrapY = stitchData.fHeight + kPerlinNoise; + } + } + + // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2 + // by fractalNoise and (turbulenceFunctionResult) by turbulence. + if (perlinNoiseShader.fType == kFractalNoise_Type) { + turbulenceFunctionResult = turbulenceFunctionResult * SK_ScalarHalf + SK_ScalarHalf; + } + + if (channel == 3) { // Scale alpha by paint value + turbulenceFunctionResult *= SkIntToScalar(getPaintAlpha()) / 255; + } + + // Clamp result + return SkScalarPin(turbulenceFunctionResult, 0, SK_Scalar1); +} + +SkPMColor SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::shade( + const SkPoint& point, StitchData& stitchData) const { + SkPoint newPoint; + fMatrix.mapPoints(&newPoint, &point, 1); + newPoint.fX = SkScalarRoundToScalar(newPoint.fX); + newPoint.fY = SkScalarRoundToScalar(newPoint.fY); + + U8CPU rgba[4]; + for (int channel = 3; channel >= 0; --channel) { + rgba[channel] = SkScalarFloorToInt(255 * + calculateTurbulenceValueForPoint(channel, stitchData, newPoint)); + } + return SkPreMultiplyARGB(rgba[3], rgba[0], rgba[1], rgba[2]); +} + +SkShaderBase::Context* SkPerlinNoiseShaderImpl::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const { + return alloc->make(*this, rec); +} + +SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::PerlinNoiseShaderContext( + const SkPerlinNoiseShaderImpl& shader, const ContextRec& rec) + : INHERITED(shader, rec) +{ + SkMatrix newMatrix = SkMatrix::Concat(*rec.fMatrix, shader.getLocalMatrix()); + if (rec.fLocalMatrix) { + newMatrix.preConcat(*rec.fLocalMatrix); + } + // This (1,1) translation is due to WebKit's 1 based coordinates for the noise + // (as opposed to 0 based, usually). The same adjustment is in the setData() function. + fMatrix.setTranslate(-newMatrix.getTranslateX() + SK_Scalar1, -newMatrix.getTranslateY() + SK_Scalar1); + fPaintingData = new PaintingData(shader.fTileSize, shader.fSeed, shader.fBaseFrequencyX, + shader.fBaseFrequencyY, newMatrix); +} + +SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::~PerlinNoiseShaderContext() { delete fPaintingData; } + +void SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::shadeSpan( + int x, int y, SkPMColor result[], int count) { + SkPoint point = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y)); + StitchData stitchData; + for (int i = 0; i < count; ++i) { + result[i] = shade(point, stitchData); + point.fX += SK_Scalar1; + } +} + +///////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +class GrGLPerlinNoise : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + GrGLSLProgramDataManager::UniformHandle fStitchDataUni; + GrGLSLProgramDataManager::UniformHandle fBaseFrequencyUni; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrPerlinNoiseEffect : public GrFragmentProcessor { +public: + static sk_sp Make(GrResourceProvider* resourceProvider, + SkPerlinNoiseShaderImpl::Type type, + int numOctaves, bool stitchTiles, + SkPerlinNoiseShaderImpl::PaintingData* paintingData, + sk_sp permutationsProxy, + sk_sp noiseProxy, + const SkMatrix& matrix) { + return sk_sp( + new GrPerlinNoiseEffect(resourceProvider, type, numOctaves, stitchTiles, paintingData, + std::move(permutationsProxy), std::move(noiseProxy), matrix)); + } + + ~GrPerlinNoiseEffect() override { delete fPaintingData; } + + const char* name() const override { return "PerlinNoise"; } + + const SkPerlinNoiseShaderImpl::StitchData& stitchData() const { + return fPaintingData->fStitchDataInit; + } + + SkPerlinNoiseShaderImpl::Type type() const { return fType; } + bool stitchTiles() const { return fStitchTiles; } + const SkVector& baseFrequency() const { return fPaintingData->fBaseFrequency; } + int numOctaves() const { return fNumOctaves; } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { + return new GrGLPerlinNoise; + } + + virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override { + GrGLPerlinNoise::GenKey(*this, caps, b); + } + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const GrPerlinNoiseEffect& s = sBase.cast(); + return fType == s.fType && + fPaintingData->fBaseFrequency == s.fPaintingData->fBaseFrequency && + fNumOctaves == s.fNumOctaves && + fStitchTiles == s.fStitchTiles && + fPaintingData->fStitchDataInit == s.fPaintingData->fStitchDataInit; + } + + GrPerlinNoiseEffect(GrResourceProvider* resourceProvider, + SkPerlinNoiseShaderImpl::Type type, int numOctaves, bool stitchTiles, + SkPerlinNoiseShaderImpl::PaintingData* paintingData, + sk_sp permutationsProxy, sk_sp noiseProxy, + const SkMatrix& matrix) + : INHERITED(kNone_OptimizationFlags) + , fType(type) + , fCoordTransform(matrix) + , fNumOctaves(numOctaves) + , fStitchTiles(stitchTiles) + , fPermutationsSampler(resourceProvider, std::move(permutationsProxy)) + , fNoiseSampler(resourceProvider, std::move(noiseProxy)) + , fPaintingData(paintingData) { + this->initClassID(); + this->addTextureSampler(&fPermutationsSampler); + this->addTextureSampler(&fNoiseSampler); + this->addCoordTransform(&fCoordTransform); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + SkPerlinNoiseShaderImpl::Type fType; + GrCoordTransform fCoordTransform; + int fNumOctaves; + bool fStitchTiles; + TextureSampler fPermutationsSampler; + TextureSampler fNoiseSampler; + SkPerlinNoiseShaderImpl::PaintingData *fPaintingData; + +private: + typedef GrFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrPerlinNoiseEffect); + +#if GR_TEST_UTILS +sk_sp GrPerlinNoiseEffect::TestCreate(GrProcessorTestData* d) { + int numOctaves = d->fRandom->nextRangeU(2, 10); + bool stitchTiles = d->fRandom->nextBool(); + SkScalar seed = SkIntToScalar(d->fRandom->nextU()); + SkISize tileSize = SkISize::Make(d->fRandom->nextRangeU(4, 4096), + d->fRandom->nextRangeU(4, 4096)); + SkScalar baseFrequencyX = d->fRandom->nextRangeScalar(0.01f, + 0.99f); + SkScalar baseFrequencyY = d->fRandom->nextRangeScalar(0.01f, + 0.99f); + + sk_sp shader(d->fRandom->nextBool() ? + SkPerlinNoiseShader::MakeFractalNoise(baseFrequencyX, baseFrequencyY, numOctaves, seed, + stitchTiles ? &tileSize : nullptr) : + SkPerlinNoiseShader::MakeTurbulence(baseFrequencyX, baseFrequencyY, numOctaves, seed, + stitchTiles ? &tileSize : nullptr)); + + GrTest::TestAsFPArgs asFPArgs(d); + return as_SB(shader)->asFragmentProcessor(asFPArgs.args()); +} +#endif + +void GrGLPerlinNoise::emitCode(EmitArgs& args) { + const GrPerlinNoiseEffect& pne = args.fFp.cast(); + + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + SkString vCoords = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + + fBaseFrequencyUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec2f_GrSLType, kDefault_GrSLPrecision, + "baseFrequency"); + const char* baseFrequencyUni = uniformHandler->getUniformCStr(fBaseFrequencyUni); + + const char* stitchDataUni = nullptr; + if (pne.stitchTiles()) { + fStitchDataUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec2f_GrSLType, kDefault_GrSLPrecision, + "stitchData"); + stitchDataUni = uniformHandler->getUniformCStr(fStitchDataUni); + } + + // There are 4 lines, so the center of each line is 1/8, 3/8, 5/8 and 7/8 + const char* chanCoordR = "0.125"; + const char* chanCoordG = "0.375"; + const char* chanCoordB = "0.625"; + const char* chanCoordA = "0.875"; + const char* chanCoord = "chanCoord"; + const char* stitchData = "stitchData"; + const char* ratio = "ratio"; + const char* noiseVec = "noiseVec"; + const char* noiseSmooth = "noiseSmooth"; + const char* floorVal = "floorVal"; + const char* fractVal = "fractVal"; + const char* uv = "uv"; + const char* ab = "ab"; + const char* latticeIdx = "latticeIdx"; + const char* bcoords = "bcoords"; + const char* lattice = "lattice"; + const char* inc8bit = "0.00390625"; // 1.0 / 256.0 + // This is the math to convert the two 16bit integer packed into rgba 8 bit input into a + // [-1,1] vector and perform a dot product between that vector and the provided vector. + const char* dotLattice = "dot(((%s.ga + %s.rb * vec2(%s)) * vec2(2.0) - vec2(1.0)), %s);"; + + // Add noise function + static const GrShaderVar gPerlinNoiseArgs[] = { + GrShaderVar(chanCoord, kFloat_GrSLType), + GrShaderVar(noiseVec, kVec2f_GrSLType) + }; + + static const GrShaderVar gPerlinNoiseStitchArgs[] = { + GrShaderVar(chanCoord, kFloat_GrSLType), + GrShaderVar(noiseVec, kVec2f_GrSLType), + GrShaderVar(stitchData, kVec2f_GrSLType) + }; + + SkString noiseCode; + + noiseCode.appendf("\tvec4 %s;\n", floorVal); + noiseCode.appendf("\t%s.xy = floor(%s);\n", floorVal, noiseVec); + noiseCode.appendf("\t%s.zw = %s.xy + vec2(1.0);\n", floorVal, floorVal); + noiseCode.appendf("\tvec2 %s = fract(%s);\n", fractVal, noiseVec); + + // smooth curve : t * t * (3 - 2 * t) + noiseCode.appendf("\n\tvec2 %s = %s * %s * (vec2(3.0) - vec2(2.0) * %s);", + noiseSmooth, fractVal, fractVal, fractVal); + + // Adjust frequencies if we're stitching tiles + if (pne.stitchTiles()) { + noiseCode.appendf("\n\tif(%s.x >= %s.x) { %s.x -= %s.x; }", + floorVal, stitchData, floorVal, stitchData); + noiseCode.appendf("\n\tif(%s.y >= %s.y) { %s.y -= %s.y; }", + floorVal, stitchData, floorVal, stitchData); + noiseCode.appendf("\n\tif(%s.z >= %s.x) { %s.z -= %s.x; }", + floorVal, stitchData, floorVal, stitchData); + noiseCode.appendf("\n\tif(%s.w >= %s.y) { %s.w -= %s.y; }", + floorVal, stitchData, floorVal, stitchData); + } + + // Get texture coordinates and normalize + noiseCode.appendf("\n\t%s = fract(floor(mod(%s, 256.0)) / vec4(256.0));\n", + floorVal, floorVal); + + // Get permutation for x + { + SkString xCoords(""); + xCoords.appendf("vec2(%s.x, 0.5)", floorVal); + + noiseCode.appendf("\n\tvec2 %s;\n\t%s.x = ", latticeIdx, latticeIdx); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[0], xCoords.c_str(), + kVec2f_GrSLType); + noiseCode.append(".r;"); + } + + // Get permutation for x + 1 + { + SkString xCoords(""); + xCoords.appendf("vec2(%s.z, 0.5)", floorVal); + + noiseCode.appendf("\n\t%s.y = ", latticeIdx); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[0], xCoords.c_str(), + kVec2f_GrSLType); + noiseCode.append(".r;"); + } + +#if defined(SK_BUILD_FOR_ANDROID) + // Android rounding for Tegra devices, like, for example: Xoom (Tegra 2), Nexus 7 (Tegra 3). + // The issue is that colors aren't accurate enough on Tegra devices. For example, if an 8 bit + // value of 124 (or 0.486275 here) is entered, we can get a texture value of 123.513725 + // (or 0.484368 here). The following rounding operation prevents these precision issues from + // affecting the result of the noise by making sure that we only have multiples of 1/255. + // (Note that 1/255 is about 0.003921569, which is the value used here). + noiseCode.appendf("\n\t%s = floor(%s * vec2(255.0) + vec2(0.5)) * vec2(0.003921569);", + latticeIdx, latticeIdx); +#endif + + // Get (x,y) coordinates with the permutated x + noiseCode.appendf("\n\tvec4 %s = fract(%s.xyxy + %s.yyww);", bcoords, latticeIdx, floorVal); + + noiseCode.appendf("\n\n\tvec2 %s;", uv); + // Compute u, at offset (0,0) + { + SkString latticeCoords(""); + latticeCoords.appendf("vec2(%s.x, %s)", bcoords, chanCoord); + noiseCode.appendf("\n\tvec4 %s = ", lattice); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[1], latticeCoords.c_str(), + kVec2f_GrSLType); + noiseCode.appendf(".bgra;\n\t%s.x = ", uv); + noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); + } + + noiseCode.appendf("\n\t%s.x -= 1.0;", fractVal); + // Compute v, at offset (-1,0) + { + SkString latticeCoords(""); + latticeCoords.appendf("vec2(%s.y, %s)", bcoords, chanCoord); + noiseCode.append("\n\tlattice = "); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[1], latticeCoords.c_str(), + kVec2f_GrSLType); + noiseCode.appendf(".bgra;\n\t%s.y = ", uv); + noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); + } + + // Compute 'a' as a linear interpolation of 'u' and 'v' + noiseCode.appendf("\n\tvec2 %s;", ab); + noiseCode.appendf("\n\t%s.x = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth); + + noiseCode.appendf("\n\t%s.y -= 1.0;", fractVal); + // Compute v, at offset (-1,-1) + { + SkString latticeCoords(""); + latticeCoords.appendf("vec2(%s.w, %s)", bcoords, chanCoord); + noiseCode.append("\n\tlattice = "); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[1], latticeCoords.c_str(), + kVec2f_GrSLType); + noiseCode.appendf(".bgra;\n\t%s.y = ", uv); + noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); + } + + noiseCode.appendf("\n\t%s.x += 1.0;", fractVal); + // Compute u, at offset (0,-1) + { + SkString latticeCoords(""); + latticeCoords.appendf("vec2(%s.z, %s)", bcoords, chanCoord); + noiseCode.append("\n\tlattice = "); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[1], latticeCoords.c_str(), + kVec2f_GrSLType); + noiseCode.appendf(".bgra;\n\t%s.x = ", uv); + noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); + } + + // Compute 'b' as a linear interpolation of 'u' and 'v' + noiseCode.appendf("\n\t%s.y = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth); + // Compute the noise as a linear interpolation of 'a' and 'b' + noiseCode.appendf("\n\treturn mix(%s.x, %s.y, %s.y);\n", ab, ab, noiseSmooth); + + SkString noiseFuncName; + if (pne.stitchTiles()) { + fragBuilder->emitFunction(kFloat_GrSLType, + "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseStitchArgs), + gPerlinNoiseStitchArgs, noiseCode.c_str(), &noiseFuncName); + } else { + fragBuilder->emitFunction(kFloat_GrSLType, + "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseArgs), + gPerlinNoiseArgs, noiseCode.c_str(), &noiseFuncName); + } + + // There are rounding errors if the floor operation is not performed here + fragBuilder->codeAppendf("\n\t\tvec2 %s = floor(%s.xy) * %s;", + noiseVec, vCoords.c_str(), baseFrequencyUni); + + // Clear the color accumulator + fragBuilder->codeAppendf("\n\t\t%s = vec4(0.0);", args.fOutputColor); + + if (pne.stitchTiles()) { + // Set up TurbulenceInitial stitch values. + fragBuilder->codeAppendf("vec2 %s = %s;", stitchData, stitchDataUni); + } + + fragBuilder->codeAppendf("float %s = 1.0;", ratio); + + // Loop over all octaves + fragBuilder->codeAppendf("for (int octave = 0; octave < %d; ++octave) {", pne.numOctaves()); + + fragBuilder->codeAppendf("%s += ", args.fOutputColor); + if (pne.type() != SkPerlinNoiseShaderImpl::kFractalNoise_Type) { + fragBuilder->codeAppend("abs("); + } + if (pne.stitchTiles()) { + fragBuilder->codeAppendf( + "vec4(\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s)," + "\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s))", + noiseFuncName.c_str(), chanCoordR, noiseVec, stitchData, + noiseFuncName.c_str(), chanCoordG, noiseVec, stitchData, + noiseFuncName.c_str(), chanCoordB, noiseVec, stitchData, + noiseFuncName.c_str(), chanCoordA, noiseVec, stitchData); + } else { + fragBuilder->codeAppendf( + "vec4(\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s)," + "\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s))", + noiseFuncName.c_str(), chanCoordR, noiseVec, + noiseFuncName.c_str(), chanCoordG, noiseVec, + noiseFuncName.c_str(), chanCoordB, noiseVec, + noiseFuncName.c_str(), chanCoordA, noiseVec); + } + if (pne.type() != SkPerlinNoiseShaderImpl::kFractalNoise_Type) { + fragBuilder->codeAppendf(")"); // end of "abs(" + } + fragBuilder->codeAppendf(" * %s;", ratio); + + fragBuilder->codeAppendf("\n\t\t\t%s *= vec2(2.0);", noiseVec); + fragBuilder->codeAppendf("\n\t\t\t%s *= 0.5;", ratio); + + if (pne.stitchTiles()) { + fragBuilder->codeAppendf("\n\t\t\t%s *= vec2(2.0);", stitchData); + } + fragBuilder->codeAppend("\n\t\t}"); // end of the for loop on octaves + + if (pne.type() == SkPerlinNoiseShaderImpl::kFractalNoise_Type) { + // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2 + // by fractalNoise and (turbulenceFunctionResult) by turbulence. + fragBuilder->codeAppendf("\n\t\t%s = %s * vec4(0.5) + vec4(0.5);", + args.fOutputColor,args.fOutputColor); + } + + // Clamp values + fragBuilder->codeAppendf("\n\t\t%s = clamp(%s, 0.0, 1.0);", args.fOutputColor, args.fOutputColor); + + // Pre-multiply the result + fragBuilder->codeAppendf("\n\t\t%s = vec4(%s.rgb * %s.aaa, %s.a);\n", + args.fOutputColor, args.fOutputColor, + args.fOutputColor, args.fOutputColor); +} + +void GrGLPerlinNoise::GenKey(const GrProcessor& processor, const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrPerlinNoiseEffect& turbulence = processor.cast(); + + uint32_t key = turbulence.numOctaves(); + + key = key << 3; // Make room for next 3 bits + + switch (turbulence.type()) { + case SkPerlinNoiseShaderImpl::kFractalNoise_Type: + key |= 0x1; + break; + case SkPerlinNoiseShaderImpl::kTurbulence_Type: + key |= 0x2; + break; + default: + // leave key at 0 + break; + } + + if (turbulence.stitchTiles()) { + key |= 0x4; // Flip the 3rd bit if tile stitching is on + } + + b->add32(key); +} + +void GrGLPerlinNoise::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + + const GrPerlinNoiseEffect& turbulence = processor.cast(); + + const SkVector& baseFrequency = turbulence.baseFrequency(); + pdman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY); + + if (turbulence.stitchTiles()) { + const SkPerlinNoiseShaderImpl::StitchData& stitchData = turbulence.stitchData(); + pdman.set2f(fStitchDataUni, SkIntToScalar(stitchData.fWidth), + SkIntToScalar(stitchData.fHeight)); + } +} + +///////////////////////////////////////////////////////////////////// +sk_sp SkPerlinNoiseShaderImpl::asFragmentProcessor( + const AsFPArgs& args) const { + SkASSERT(args.fContext); + + SkMatrix localMatrix = this->getLocalMatrix(); + if (args.fLocalMatrix) { + localMatrix.preConcat(*args.fLocalMatrix); + } + + SkMatrix matrix = *args.fViewMatrix; + matrix.preConcat(localMatrix); + + if (0 == fNumOctaves) { + if (kFractalNoise_Type == fType) { + // Extract the incoming alpha and emit rgba = (a/4, a/4, a/4, a/2) + // TODO: Either treat the output of this shader as sRGB or allow client to specify a + // color space of the noise. Either way, this case (and the GLSL) need to convert to + // the destination. + sk_sp inner( + GrConstColorProcessor::Make(GrColor4f::FromGrColor(0x80404040), + GrConstColorProcessor::kModulateRGBA_InputMode)); + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); + } + // Emit zero. + return GrConstColorProcessor::Make(GrColor4f::TransparentBlack(), + GrConstColorProcessor::kIgnore_InputMode); + } + + // Either we don't stitch tiles, either we have a valid tile size + SkASSERT(!fStitchTiles || !fTileSize.isEmpty()); + + SkPerlinNoiseShaderImpl::PaintingData* paintingData = + new PaintingData(fTileSize, fSeed, fBaseFrequencyX, fBaseFrequencyY, matrix); + sk_sp permutationsProxy(GrMakeCachedBitmapProxy( + args.fContext->resourceProvider(), + paintingData->getPermutationsBitmap())); + sk_sp noiseProxy(GrMakeCachedBitmapProxy(args.fContext->resourceProvider(), + paintingData->getNoiseBitmap())); + + SkMatrix m = *args.fViewMatrix; + m.setTranslateX(-localMatrix.getTranslateX() + SK_Scalar1); + m.setTranslateY(-localMatrix.getTranslateY() + SK_Scalar1); + if (permutationsProxy && noiseProxy) { + sk_sp inner( + GrPerlinNoiseEffect::Make(args.fContext->resourceProvider(), + fType, + fNumOctaves, + fStitchTiles, + paintingData, + std::move(permutationsProxy), + std::move(noiseProxy), + m)); + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); + } + delete paintingData; + return nullptr; +} + +#endif + +#ifndef SK_IGNORE_TO_STRING +void SkPerlinNoiseShaderImpl::toString(SkString* str) const { + str->append("SkPerlinNoiseShader: ("); + + str->append("type: "); + switch (fType) { + case kFractalNoise_Type: + str->append("\"fractal noise\""); + break; + case kTurbulence_Type: + str->append("\"turbulence\""); + break; + default: + str->append("\"unknown\""); + break; + } + str->append(" base frequency: ("); + str->appendScalar(fBaseFrequencyX); + str->append(", "); + str->appendScalar(fBaseFrequencyY); + str->append(") number of octaves: "); + str->appendS32(fNumOctaves); + str->append(" seed: "); + str->appendScalar(fSeed); + str->append(" stitch tiles: "); + str->append(fStitchTiles ? "true " : "false "); + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif + +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkPerlinNoiseShader) + SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkPerlinNoiseShaderImpl) +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END diff --git a/src/effects/gradients/Sk4fGradientBase.cpp b/src/effects/gradients/Sk4fGradientBase.cpp new file mode 100644 index 0000000000..e20f5f4702 --- /dev/null +++ b/src/effects/gradients/Sk4fGradientBase.cpp @@ -0,0 +1,451 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "Sk4fGradientBase.h" + +#include + +namespace { + +Sk4f pack_color(SkColor c, bool premul, const Sk4f& component_scale) { + const SkColor4f c4f = SkColor4f::FromColor(c); + const Sk4f pm4f = premul + ? c4f.premul().to4f() + : Sk4f{c4f.fR, c4f.fG, c4f.fB, c4f.fA}; + + return pm4f * component_scale; +} + +class IntervalIterator { +public: + IntervalIterator(const SkColor* colors, const SkScalar* pos, int count, bool reverse) + : fColors(colors) + , fPos(pos) + , fCount(count) + , fFirstPos(reverse ? SK_Scalar1 : 0) + , fBegin(reverse ? count - 1 : 0) + , fAdvance(reverse ? -1 : 1) { + SkASSERT(colors); + SkASSERT(count > 0); + } + + void iterate(std::function func) const { + if (!fPos) { + this->iterateImplicitPos(func); + return; + } + + const int end = fBegin + fAdvance * (fCount - 1); + const SkScalar lastPos = 1 - fFirstPos; + int prev = fBegin; + SkScalar prevPos = fFirstPos; + + do { + const int curr = prev + fAdvance; + SkASSERT(curr >= 0 && curr < fCount); + + // TODO: this sanitization should be done in SkGradientShaderBase + const SkScalar currPos = (fAdvance > 0) + ? SkTPin(fPos[curr], prevPos, lastPos) + : SkTPin(fPos[curr], lastPos, prevPos); + + if (currPos != prevPos) { + SkASSERT((currPos - prevPos > 0) == (fAdvance > 0)); + func(fColors[prev], fColors[curr], prevPos, currPos); + } + + prev = curr; + prevPos = currPos; + } while (prev != end); + } + +private: + void iterateImplicitPos(std::function func) const { + // When clients don't provide explicit color stop positions (fPos == nullptr), + // the color stops are distributed evenly across the unit interval + // (implicit positioning). + const SkScalar dt = fAdvance * SK_Scalar1 / (fCount - 1); + const int end = fBegin + fAdvance * (fCount - 2); + int prev = fBegin; + SkScalar prevPos = fFirstPos; + + while (prev != end) { + const int curr = prev + fAdvance; + SkASSERT(curr >= 0 && curr < fCount); + + const SkScalar currPos = prevPos + dt; + func(fColors[prev], fColors[curr], prevPos, currPos); + prev = curr; + prevPos = currPos; + } + + // emit the last interval with a pinned end position, to avoid precision issues + func(fColors[prev], fColors[prev + fAdvance], prevPos, 1 - fFirstPos); + } + + const SkColor* fColors; + const SkScalar* fPos; + const int fCount; + const SkScalar fFirstPos; + const int fBegin; + const int fAdvance; +}; + +void addMirrorIntervals(const SkColor colors[], + const SkScalar pos[], int count, + const Sk4f& componentScale, + bool premulColors, bool reverse, + Sk4fGradientIntervalBuffer::BufferType* buffer) { + const IntervalIterator iter(colors, pos, count, reverse); + iter.iterate([&] (SkColor c0, SkColor c1, SkScalar t0, SkScalar t1) { + SkASSERT(buffer->empty() || buffer->back().fT1 == 2 - t0); + + const auto mirror_t0 = 2 - t0; + const auto mirror_t1 = 2 - t1; + // mirror_p1 & mirror_p1 may collapse for very small values - recheck to avoid + // triggering Interval asserts. + if (mirror_t0 != mirror_t1) { + buffer->emplace_back(pack_color(c0, premulColors, componentScale), mirror_t0, + pack_color(c1, premulColors, componentScale), mirror_t1); + } + }); +} + +} // anonymous namespace + +Sk4fGradientInterval::Sk4fGradientInterval(const Sk4f& c0, SkScalar t0, + const Sk4f& c1, SkScalar t1) + : fT0(t0) + , fT1(t1) { + SkASSERT(t0 != t1); + // Either p0 or p1 can be (-)inf for synthetic clamp edge intervals. + SkASSERT(SkScalarIsFinite(t0) || SkScalarIsFinite(t1)); + + const auto dt = t1 - t0; + + // Clamp edge intervals are always zero-ramp. + SkASSERT(SkScalarIsFinite(dt) || (c0 == c1).allTrue()); + SkASSERT(SkScalarIsFinite(t0) || (c0 == c1).allTrue()); + const Sk4f dc = SkScalarIsFinite(dt) ? (c1 - c0) / dt : 0; + const Sk4f bias = c0 - (SkScalarIsFinite(t0) ? t0 * dc : 0); + + bias.store(&fCb.fVec); + dc.store(&fCg.fVec); +} + +void Sk4fGradientIntervalBuffer::init(const SkColor colors[], const SkScalar pos[], int count, + SkShader::TileMode tileMode, bool premulColors, + SkScalar alpha, bool reverse) { + // The main job here is to build a specialized interval list: a different + // representation of the color stops data, optimized for efficient scan line + // access during shading. + // + // [{P0,C0} , {P1,C1}) [{P1,C2} , {P2,c3}) ... [{Pn,C2n} , {Pn+1,C2n+1}) + // + // The list may be inverted when requested (such that e.g. points are sorted + // in increasing x order when dx < 0). + // + // Note: the current representation duplicates pos data; we could refactor to + // avoid this if interval storage size becomes a concern. + // + // Aside from reordering, we also perform two more pre-processing steps at + // this stage: + // + // 1) scale the color components depending on paint alpha and the requested + // interpolation space (note: the interval color storage is SkPM4f, but + // that doesn't necessarily mean the colors are premultiplied; that + // property is tracked in fColorsArePremul) + // + // 2) inject synthetic intervals to support tiling. + // + // * for kRepeat, no extra intervals are needed - the iterator just + // wraps around at the end: + // + // ->[P0,P1)->..[Pn-1,Pn)-> + // + // * for kClamp, we add two "infinite" intervals before/after: + // + // [-/+inf , P0)->[P0 , P1)->..[Pn-1 , Pn)->[Pn , +/-inf) + // + // (the iterator should never run off the end in this mode) + // + // * for kMirror, we extend the range to [0..2] and add a flipped + // interval series - then the iterator operates just as in the + // kRepeat case: + // + // ->[P0,P1)->..[Pn-1,Pn)->[2 - Pn,2 - Pn-1)->..[2 - P1,2 - P0)-> + // + // TODO: investigate collapsing intervals << 1px. + + SkASSERT(count > 0); + SkASSERT(colors); + + fIntervals.reset(); + + const Sk4f componentScale = premulColors + ? Sk4f(alpha) + : Sk4f(1.0f, 1.0f, 1.0f, alpha); + const int first_index = reverse ? count - 1 : 0; + const int last_index = count - 1 - first_index; + const SkScalar first_pos = reverse ? SK_Scalar1 : 0; + const SkScalar last_pos = SK_Scalar1 - first_pos; + + if (tileMode == SkShader::kClamp_TileMode) { + // synthetic edge interval: -/+inf .. P0 + const Sk4f clamp_color = pack_color(colors[first_index], + premulColors, componentScale); + const SkScalar clamp_pos = reverse ? SK_ScalarInfinity : SK_ScalarNegativeInfinity; + fIntervals.emplace_back(clamp_color, clamp_pos, + clamp_color, first_pos); + } else if (tileMode == SkShader::kMirror_TileMode && reverse) { + // synthetic mirror intervals injected before main intervals: (2 .. 1] + addMirrorIntervals(colors, pos, count, componentScale, premulColors, false, &fIntervals); + } + + const IntervalIterator iter(colors, pos, count, reverse); + iter.iterate([&] (SkColor c0, SkColor c1, SkScalar t0, SkScalar t1) { + SkASSERT(fIntervals.empty() || fIntervals.back().fT1 == t0); + + fIntervals.emplace_back(pack_color(c0, premulColors, componentScale), t0, + pack_color(c1, premulColors, componentScale), t1); + }); + + if (tileMode == SkShader::kClamp_TileMode) { + // synthetic edge interval: Pn .. +/-inf + const Sk4f clamp_color = pack_color(colors[last_index], premulColors, componentScale); + const SkScalar clamp_pos = reverse ? SK_ScalarNegativeInfinity : SK_ScalarInfinity; + fIntervals.emplace_back(clamp_color, last_pos, + clamp_color, clamp_pos); + } else if (tileMode == SkShader::kMirror_TileMode && !reverse) { + // synthetic mirror intervals injected after main intervals: [1 .. 2) + addMirrorIntervals(colors, pos, count, componentScale, premulColors, true, &fIntervals); + } +} + +const Sk4fGradientInterval* Sk4fGradientIntervalBuffer::find(SkScalar t) const { + // Binary search. + const auto* i0 = fIntervals.begin(); + const auto* i1 = fIntervals.end() - 1; + + while (i0 != i1) { + SkASSERT(i0 < i1); + SkASSERT(t >= i0->fT0 && t <= i1->fT1); + + const auto* i = i0 + ((i1 - i0) >> 1); + + if (t > i->fT1) { + i0 = i + 1; + } else { + i1 = i; + } + } + + SkASSERT(i0->contains(t)); + return i0; +} + +const Sk4fGradientInterval* Sk4fGradientIntervalBuffer::findNext( + SkScalar t, const Sk4fGradientInterval* prev, bool increasing) const { + + SkASSERT(!prev->contains(t)); + SkASSERT(prev >= fIntervals.begin() && prev < fIntervals.end()); + SkASSERT(t >= fIntervals.front().fT0 && t <= fIntervals.back().fT1); + + const auto* i = prev; + + // Use the |increasing| signal to figure which direction we should search for + // the next interval, then perform a linear search. + if (increasing) { + do { + i += 1; + if (i >= fIntervals.end()) { + i = fIntervals.begin(); + } + } while (!i->contains(t)); + } else { + do { + i -= 1; + if (i < fIntervals.begin()) { + i = fIntervals.end() - 1; + } + } while (!i->contains(t)); + } + + return i; +} + +SkGradientShaderBase:: +GradientShaderBase4fContext::GradientShaderBase4fContext(const SkGradientShaderBase& shader, + const ContextRec& rec) + : INHERITED(shader, rec) + , fFlags(this->INHERITED::getFlags()) +#ifdef SK_SUPPORT_LEGACY_GRADIENT_DITHERING + , fDither(true) +#else + , fDither(rec.fPaint->isDither()) +#endif +{ + const SkMatrix& inverse = this->getTotalInverse(); + fDstToPos.setConcat(shader.fPtsToUnit, inverse); + fDstToPosProc = fDstToPos.getMapXYProc(); + fDstToPosClass = static_cast(INHERITED::ComputeMatrixClass(fDstToPos)); + + if (shader.fColorsAreOpaque && this->getPaintAlpha() == SK_AlphaOPAQUE) { + fFlags |= kOpaqueAlpha_Flag; + } + + fColorsArePremul = + (shader.fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag) + || shader.fColorsAreOpaque; +} + +bool SkGradientShaderBase:: +GradientShaderBase4fContext::isValid() const { + return fDstToPos.isFinite(); +} + +void SkGradientShaderBase:: +GradientShaderBase4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) { + if (fColorsArePremul) { + this->shadePremulSpan(x, y, dst, count); + } else { + this->shadePremulSpan(x, y, dst, count); + } +} + +void SkGradientShaderBase:: +GradientShaderBase4fContext::shadeSpan4f(int x, int y, SkPM4f dst[], int count) { + if (fColorsArePremul) { + this->shadePremulSpan(x, y, dst, count); + } else { + this->shadePremulSpan(x, y, dst, count); + } +} + +template +void SkGradientShaderBase:: +GradientShaderBase4fContext::shadePremulSpan(int x, int y, + typename DstTraits::Type dst[], + int count) const { + const SkGradientShaderBase& shader = + static_cast(fShader); + + switch (shader.fTileMode) { + case kClamp_TileMode: + this->shadeSpanInternal(x, y, dst, count); + break; + case kRepeat_TileMode: + this->shadeSpanInternal(x, y, dst, count); + break; + case kMirror_TileMode: + this->shadeSpanInternal(x, y, dst, count); + break; + } +} + +template +void SkGradientShaderBase:: +GradientShaderBase4fContext::shadeSpanInternal(int x, int y, + typename DstTraits::Type dst[], + int count) const { + static const int kBufSize = 128; + SkScalar ts[kBufSize]; + TSampler sampler(*this); + + SkASSERT(count > 0); + do { + const int n = SkTMin(kBufSize, count); + this->mapTs(x, y, ts, n); + for (int i = 0; i < n; ++i) { + const Sk4f c = sampler.sample(ts[i]); + DstTraits::store(c, dst++); + } + x += n; + count -= n; + } while (count > 0); +} + +template +class SkGradientShaderBase::GradientShaderBase4fContext::TSampler { +public: + TSampler(const GradientShaderBase4fContext& ctx) + : fCtx(ctx) + , fInterval(nullptr) { + switch (tileMode) { + case kClamp_TileMode: + fLargestIntervalValue = SK_ScalarInfinity; + break; + case kRepeat_TileMode: + fLargestIntervalValue = nextafterf(1, 0); + break; + case kMirror_TileMode: + fLargestIntervalValue = nextafterf(2.0f, 0); + break; + } + } + + Sk4f sample(SkScalar t) { + const auto tiled_t = tileProc(t); + + if (!fInterval) { + // Very first sample => locate the initial interval. + // TODO: maybe do this in ctor to remove a branch? + fInterval = fCtx.fIntervals.find(tiled_t); + this->loadIntervalData(fInterval); + } else if (!fInterval->contains(tiled_t)) { + fInterval = fCtx.fIntervals.findNext(tiled_t, fInterval, t >= fPrevT); + this->loadIntervalData(fInterval); + } + + fPrevT = t; + return lerp(tiled_t); + } + +private: + SkScalar tileProc(SkScalar t) const { + switch (tileMode) { + case kClamp_TileMode: + // synthetic clamp-mode edge intervals allow for a free-floating t: + // [-inf..0)[0..1)[1..+inf) + return t; + case kRepeat_TileMode: + // t % 1 (intervals range: [0..1)) + // Due to the extra arithmetic, we must clamp to ensure the value remains less than 1. + return SkTMin(t - SkScalarFloorToScalar(t), fLargestIntervalValue); + case kMirror_TileMode: + // t % 2 (synthetic mirror intervals expand the range to [0..2) + // Due to the extra arithmetic, we must clamp to ensure the value remains less than 2. + return SkTMin(t - SkScalarFloorToScalar(t / 2) * 2, fLargestIntervalValue); + } + + SK_ABORT("Unhandled tile mode."); + return 0; + } + + Sk4f lerp(SkScalar t) { + SkASSERT(fInterval->contains(t)); + return fCb + fCg * t; + } + + void loadIntervalData(const Sk4fGradientInterval* i) { + fCb = DstTraits::load(i->fCb); + fCg = DstTraits::load(i->fCg); + } + + const GradientShaderBase4fContext& fCtx; + const Sk4fGradientInterval* fInterval; + SkScalar fPrevT; + SkScalar fLargestIntervalValue; + Sk4f fCb; + Sk4f fCg; +}; diff --git a/src/effects/gradients/Sk4fGradientBase.h b/src/effects/gradients/Sk4fGradientBase.h new file mode 100644 index 0000000000..a660d6bde5 --- /dev/null +++ b/src/effects/gradients/Sk4fGradientBase.h @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4fGradientBase_DEFINED +#define Sk4fGradientBase_DEFINED + +#include "Sk4fGradientPriv.h" +#include "SkColor.h" +#include "SkGradientShaderPriv.h" +#include "SkMatrix.h" +#include "SkNx.h" +#include "SkPM4f.h" +#include "SkShaderBase.h" +#include "SkTArray.h" + +struct Sk4fGradientInterval { + Sk4fGradientInterval(const Sk4f& c0, SkScalar t0, + const Sk4f& c1, SkScalar t1); + + bool contains(SkScalar t) const { + // True if t is in [p0,p1]. Note: this helper assumes a + // natural/increasing interval - so it's not usable in Sk4fLinearGradient. + SkASSERT(fT0 < fT1); + return t >= fT0 && t <= fT1; + } + + // Color bias and color gradient, such that for a t in this interval + // + // C = fCb + t * fCg; + SkPM4f fCb, fCg; + SkScalar fT0, fT1; +}; + +class Sk4fGradientIntervalBuffer { +public: + void init(const SkColor colors[], const SkScalar pos[], int count, + SkShader::TileMode tileMode, bool premulColors, SkScalar alpha, bool reverse); + + const Sk4fGradientInterval* find(SkScalar t) const; + const Sk4fGradientInterval* findNext(SkScalar t, const Sk4fGradientInterval* prev, + bool increasing) const; + + using BufferType = SkSTArray<8, Sk4fGradientInterval, true>; + + const BufferType* operator->() const { return &fIntervals; } + +private: + BufferType fIntervals; +}; + +class SkGradientShaderBase:: +GradientShaderBase4fContext : public Context { +public: + GradientShaderBase4fContext(const SkGradientShaderBase&, + const ContextRec&); + + uint32_t getFlags() const override { return fFlags; } + + void shadeSpan(int x, int y, SkPMColor dst[], int count) override; + void shadeSpan4f(int x, int y, SkPM4f dst[], int count) override; + + bool isValid() const; + +protected: + virtual void mapTs(int x, int y, SkScalar ts[], int count) const = 0; + + Sk4fGradientIntervalBuffer fIntervals; + SkMatrix fDstToPos; + SkMatrix::MapXYProc fDstToPosProc; + uint8_t fDstToPosClass; + uint8_t fFlags; + bool fDither; + bool fColorsArePremul; + +private: + using INHERITED = Context; + + void addMirrorIntervals(const SkGradientShaderBase&, + const Sk4f& componentScale, bool reverse); + + template + class TSampler; + + template + void shadePremulSpan(int x, int y, typename DstTraits::Type[], + int count) const; + + template + void shadeSpanInternal(int x, int y, typename DstTraits::Type[], + int count) const; +}; + +#endif // Sk4fGradientBase_DEFINED diff --git a/src/effects/gradients/Sk4fGradientPriv.h b/src/effects/gradients/Sk4fGradientPriv.h new file mode 100644 index 0000000000..b8f4bcaee1 --- /dev/null +++ b/src/effects/gradients/Sk4fGradientPriv.h @@ -0,0 +1,194 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4fGradientPriv_DEFINED +#define Sk4fGradientPriv_DEFINED + +#include "SkColor.h" +#include "SkHalf.h" +#include "SkImageInfo.h" +#include "SkNx.h" +#include "SkPM4f.h" +#include "SkPM4fPriv.h" +#include "SkUtils.h" + +// Templates shared by various 4f gradient flavors. + +namespace { + +enum class ApplyPremul { True, False }; + +enum class DstType { + L32, // Linear 32bit. Used for both shader/blitter paths. + S32, // SRGB 32bit. Used for the blitter path only. + F16, // Linear half-float. Used for blitters only. + F32, // Linear float. Used for shaders only. +}; + +template +struct PremulTraits; + +template <> +struct PremulTraits { + static Sk4f apply(const Sk4f& c) { return c; } +}; + +template <> +struct PremulTraits { + static Sk4f apply(const Sk4f& c) { + const float alpha = c[SkPM4f::A]; + // FIXME: portable swizzle? + return c * Sk4f(alpha, alpha, alpha, 1); + } +}; + +// Struct encapsulating various dest-dependent ops: +// +// - load() Load a SkPM4f value into Sk4f. Normally called once per interval +// advance. Also applies a scale and swizzle suitable for DstType. +// +// - store() Store one Sk4f to dest. Optionally handles premul, color space +// conversion, etc. +// +// - store(count) Store the Sk4f value repeatedly to dest, count times. +// +// - store4x() Store 4 Sk4f values to dest (opportunistic optimization). +// +template +struct DstTraits; + +template +struct DstTraits { + using PM = PremulTraits; + using Type = SkPMColor; + + // For L32, prescaling by 255 saves a per-pixel multiplication when premul is not needed. + static Sk4f load(const SkPM4f& c) { + return premul == ApplyPremul::False + ? c.to4f_pmorder() * Sk4f(255) + : c.to4f_pmorder(); + } + + static void store(const Sk4f& c, Type* dst) { + if (premul == ApplyPremul::False) { + // c is prescaled by 255, just store. + SkNx_cast(c).store(dst); + } else { + *dst = Sk4f_toL32(PM::apply(c)); + } + } + + static void store(const Sk4f& c, Type* dst, int n) { + Type pmc; + store(c, &pmc); + sk_memset32(dst, pmc, n); + } + + static void store4x(const Sk4f& c0, const Sk4f& c1, + const Sk4f& c2, const Sk4f& c3, + Type* dst) { + if (premul == ApplyPremul::False) { + Sk4f_ToBytes((uint8_t*)dst, c0, c1, c2, c3); + } else { + store(c0, dst + 0); + store(c1, dst + 1); + store(c2, dst + 2); + store(c3, dst + 3); + } + } +}; + +template +struct DstTraits { + using PM = PremulTraits; + using Type = SkPMColor; + + static Sk4f load(const SkPM4f& c) { + return c.to4f_pmorder(); + } + + static void store(const Sk4f& c, Type* dst) { + // FIXME: this assumes opaque colors. Handle unpremultiplication. + *dst = Sk4f_toS32(PM::apply(c)); + } + + static void store(const Sk4f& c, Type* dst, int n) { + sk_memset32(dst, Sk4f_toS32(PM::apply(c)), n); + } + + static void store4x(const Sk4f& c0, const Sk4f& c1, + const Sk4f& c2, const Sk4f& c3, + Type* dst) { + store(c0, dst + 0); + store(c1, dst + 1); + store(c2, dst + 2); + store(c3, dst + 3); + } +}; + +template +struct DstTraits { + using PM = PremulTraits; + using Type = uint64_t; + + static Sk4f load(const SkPM4f& c) { + return c.to4f(); + } + + static void store(const Sk4f& c, Type* dst) { + SkFloatToHalf_finite_ftz(PM::apply(c)).store(dst); + } + + static void store(const Sk4f& c, Type* dst, int n) { + uint64_t color; + SkFloatToHalf_finite_ftz(PM::apply(c)).store(&color); + sk_memset64(dst, color, n); + } + + static void store4x(const Sk4f& c0, const Sk4f& c1, + const Sk4f& c2, const Sk4f& c3, + Type* dst) { + store(c0, dst + 0); + store(c1, dst + 1); + store(c2, dst + 2); + store(c3, dst + 3); + } +}; + +template +struct DstTraits { + using PM = PremulTraits; + using Type = SkPM4f; + + static Sk4f load(const SkPM4f& c) { + return c.to4f(); + } + + static void store(const Sk4f& c, Type* dst) { + PM::apply(c).store(dst->fVec); + } + + static void store(const Sk4f& c, Type* dst, int n) { + const Sk4f pmc = PM::apply(c); + for (int i = 0; i < n; ++i) { + pmc.store(dst[i].fVec); + } + } + + static void store4x(const Sk4f& c0, const Sk4f& c1, + const Sk4f& c2, const Sk4f& c3, + Type* dst) { + store(c0, dst + 0); + store(c1, dst + 1); + store(c2, dst + 2); + store(c3, dst + 3); + } +}; + +} // anonymous namespace + +#endif // Sk4fGradientPriv_DEFINED diff --git a/src/effects/gradients/Sk4fLinearGradient.cpp b/src/effects/gradients/Sk4fLinearGradient.cpp new file mode 100644 index 0000000000..49b98fd86e --- /dev/null +++ b/src/effects/gradients/Sk4fLinearGradient.cpp @@ -0,0 +1,528 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "Sk4fLinearGradient.h" +#include "Sk4x4f.h" + +#include + +namespace { + +template +void ramp(const Sk4f& c, const Sk4f& dc, typename DstTraits::Type dst[], int n) { + SkASSERT(n > 0); + + const Sk4f dc2 = dc + dc; + const Sk4f dc4 = dc2 + dc2; + + Sk4f c0 = c ; + Sk4f c1 = c + dc; + Sk4f c2 = c0 + dc2; + Sk4f c3 = c1 + dc2; + + while (n >= 4) { + DstTraits::store4x(c0, c1, c2, c3, dst); + dst += 4; + + c0 = c0 + dc4; + c1 = c1 + dc4; + c2 = c2 + dc4; + c3 = c3 + dc4; + n -= 4; + } + if (n & 2) { + DstTraits::store(c0, dst++); + DstTraits::store(c1, dst++); + c0 = c0 + dc2; + } + if (n & 1) { + DstTraits::store(c0, dst); + } +} + +// Planar version of ramp (S32 no-premul only). +template<> +void ramp(const Sk4f& c, const Sk4f& dc, SkPMColor dst[], int n) { + SkASSERT(n > 0); + + const Sk4f dc4 = dc * 4; + const Sk4x4f dc4x = { Sk4f(dc4[0]), Sk4f(dc4[1]), Sk4f(dc4[2]), Sk4f(dc4[3]) }; + Sk4x4f c4x = Sk4x4f::Transpose(c, c + dc, c + dc * 2, c + dc * 3); + + while (n >= 4) { + ( sk_linear_to_srgb(c4x.r) << 0 + | sk_linear_to_srgb(c4x.g) << 8 + | sk_linear_to_srgb(c4x.b) << 16 + | Sk4f_round(255.0f*c4x.a) << 24).store(dst); + + c4x.r += dc4x.r; + c4x.g += dc4x.g; + c4x.b += dc4x.b; + c4x.a += dc4x.a; + + dst += 4; + n -= 4; + } + + if (n & 2) { + DstTraits + ::store(Sk4f(c4x.r[0], c4x.g[0], c4x.b[0], c4x.a[0]), dst++); + DstTraits + ::store(Sk4f(c4x.r[1], c4x.g[1], c4x.b[1], c4x.a[1]), dst++); + } + + if (n & 1) { + DstTraits + ::store(Sk4f(c4x.r[n & 2], c4x.g[n & 2], c4x.b[n & 2], c4x.a[n & 2]), dst); + } +} + +template +SkScalar pinFx(SkScalar); + +template<> +SkScalar pinFx(SkScalar fx) { + return fx; +} + +template<> +SkScalar pinFx(SkScalar fx) { + SkScalar f = SkScalarFraction(fx); + if (f < 0) { + f = SkTMin(f + 1, nextafterf(1, 0)); + } + SkASSERT(f >= 0); + SkASSERT(f < 1.0f); + return f; +} + +template<> +SkScalar pinFx(SkScalar fx) { + SkScalar f = SkScalarMod(fx, 2.0f); + if (f < 0) { + f = SkTMin(f + 2, nextafterf(2, 0)); + } + SkASSERT(f >= 0); + SkASSERT(f < 2.0f); + return f; +} + +// true when x is in [k1,k2], or [k2, k1] when the interval is reversed. +// TODO(fmalita): hoist the reversed interval check out of this helper. +bool in_range(SkScalar x, SkScalar k1, SkScalar k2) { + SkASSERT(k1 != k2); + return (k1 < k2) + ? (x >= k1 && x <= k2) + : (x >= k2 && x <= k1); +} + +} // anonymous namespace + +SkLinearGradient:: +LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader, + const ContextRec& rec) + : INHERITED(shader, rec) { + + // Our fast path expects interval points to be monotonically increasing in x. + const bool reverseIntervals = this->isFast() && std::signbit(fDstToPos.getScaleX()); + fIntervals.init(shader.fOrigColors, shader.fOrigPos, shader.fColorCount, shader.fTileMode, + fColorsArePremul, rec.fPaint->getAlpha() * (1.0f / 255), reverseIntervals); + + SkASSERT(fIntervals->count() > 0); + fCachedInterval = fIntervals->begin(); +} + +const Sk4fGradientInterval* +SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const { + SkASSERT(in_range(fx, fIntervals->front().fT0, fIntervals->back().fT1)); + + if (1) { + // Linear search, using the last scanline interval as a starting point. + SkASSERT(fCachedInterval >= fIntervals->begin()); + SkASSERT(fCachedInterval < fIntervals->end()); + const int search_dir = fDstToPos.getScaleX() >= 0 ? 1 : -1; + while (!in_range(fx, fCachedInterval->fT0, fCachedInterval->fT1)) { + fCachedInterval += search_dir; + if (fCachedInterval >= fIntervals->end()) { + fCachedInterval = fIntervals->begin(); + } else if (fCachedInterval < fIntervals->begin()) { + fCachedInterval = fIntervals->end() - 1; + } + } + return fCachedInterval; + } else { + // Binary search. Seems less effective than linear + caching. + const auto* i0 = fIntervals->begin(); + const auto* i1 = fIntervals->end() - 1; + + while (i0 != i1) { + SkASSERT(i0 < i1); + SkASSERT(in_range(fx, i0->fT0, i1->fT1)); + + const auto* i = i0 + ((i1 - i0) >> 1); + + if (in_range(fx, i0->fT0, i->fT1)) { + i1 = i; + } else { + SkASSERT(in_range(fx, i->fT1, i1->fT1)); + i0 = i + 1; + } + } + + SkASSERT(in_range(fx, i0->fT0, i0->fT1)); + return i0; + } +} + +void SkLinearGradient:: +LinearGradient4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) { + if (!this->isFast()) { + this->INHERITED::shadeSpan(x, y, dst, count); + return; + } + + // TODO: plumb dithering + SkASSERT(count > 0); + if (fColorsArePremul) { + this->shadePremulSpan(x, y, dst, count); + } else { + this->shadePremulSpan(x, y, dst, count); + } +} + +void SkLinearGradient:: +LinearGradient4fContext::shadeSpan4f(int x, int y, SkPM4f dst[], int count) { + if (!this->isFast()) { + this->INHERITED::shadeSpan4f(x, y, dst, count); + return; + } + + // TONOTDO: plumb dithering + SkASSERT(count > 0); + if (fColorsArePremul) { + this->shadePremulSpan(x, y, dst, count); + } else { + this->shadePremulSpan(x, y, dst, count); + } +} + +template +void SkLinearGradient:: +LinearGradient4fContext::shadePremulSpan(int x, int y, + typename DstTraits::Type dst[], + int count) const { + const SkLinearGradient& shader = + static_cast(fShader); + switch (shader.fTileMode) { + case kClamp_TileMode: + this->shadeSpanInternal(x, y, dst, count); + break; + case kRepeat_TileMode: + this->shadeSpanInternal(x, y, dst, count); + break; + case kMirror_TileMode: + this->shadeSpanInternal(x, y, dst, count); + break; + } +} + +template +void SkLinearGradient:: +LinearGradient4fContext::shadeSpanInternal(int x, int y, + typename DstTraits::Type dst[], + int count) const { + SkPoint pt; + fDstToPosProc(fDstToPos, + x + SK_ScalarHalf, + y + SK_ScalarHalf, + &pt); + const SkScalar fx = pinFx(pt.x()); + const SkScalar dx = fDstToPos.getScaleX(); + LinearIntervalProcessor proc(fIntervals->begin(), + fIntervals->end() - 1, + this->findInterval(fx), + fx, + dx, + SkScalarNearlyZero(dx * count)); + while (count > 0) { + // What we really want here is SkTPin(advance, 1, count) + // but that's a significant perf hit for >> stops; investigate. + const int n = SkScalarTruncToInt( + SkTMin(proc.currentAdvance() + 1, SkIntToScalar(count))); + + // The current interval advance can be +inf (e.g. when reaching + // the clamp mode end intervals) - when that happens, we expect to + // a) consume all remaining count in one swoop + // b) return a zero color gradient + SkASSERT(SkScalarIsFinite(proc.currentAdvance()) + || (n == count && proc.currentRampIsZero())); + + if (proc.currentRampIsZero()) { + DstTraits::store(proc.currentColor(), + dst, n); + } else { + ramp(proc.currentColor(), + proc.currentColorGrad(), + dst, n); + } + + proc.advance(SkIntToScalar(n)); + count -= n; + dst += n; + } +} + +template +class SkLinearGradient:: +LinearGradient4fContext::LinearIntervalProcessor { +public: + LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval, + const Sk4fGradientInterval* lastInterval, + const Sk4fGradientInterval* i, + SkScalar fx, + SkScalar dx, + bool is_vertical) + : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx) + , fFirstInterval(firstInterval) + , fLastInterval(lastInterval) + , fInterval(i) + , fDx(dx) + , fIsVertical(is_vertical) + { + SkASSERT(fAdvX >= 0); + SkASSERT(firstInterval <= lastInterval); + + if (tileMode != kClamp_TileMode && !is_vertical) { + const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx; + SkASSERT(spanX >= 0); + + // If we're in a repeating tile mode and the whole gradient is compressed into a + // fraction of a pixel, we just use the average color in zero-ramp mode. + // This also avoids cases where we make no progress due to interval advances being + // close to zero. + static constexpr SkScalar kMinSpanX = .25f; + if (spanX < kMinSpanX) { + this->init_average_props(); + return; + } + } + + this->compute_interval_props(fx); + } + + SkScalar currentAdvance() const { + SkASSERT(fAdvX >= 0); + SkASSERT(fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx || !std::isfinite(fAdvX)); + return fAdvX; + } + + bool currentRampIsZero() const { return fZeroRamp; } + const Sk4f& currentColor() const { return fCc; } + const Sk4f& currentColorGrad() const { return fDcDx; } + + void advance(SkScalar advX) { + SkASSERT(advX > 0); + SkASSERT(fAdvX >= 0); + + if (advX >= fAdvX) { + advX = this->advance_interval(advX); + } + SkASSERT(advX < fAdvX); + + fCc = fCc + fDcDx * Sk4f(advX); + fAdvX -= advX; + } + +private: + void compute_interval_props(SkScalar t) { + SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1)); + + const Sk4f dc = DstTraits::load(fInterval->fCg); + fCc = DstTraits::load(fInterval->fCb) + dc * Sk4f(t); + fDcDx = dc * fDx; + fZeroRamp = fIsVertical || (dc == 0).allTrue(); + } + + void init_average_props() { + fAdvX = SK_ScalarInfinity; + fZeroRamp = true; + fDcDx = 0; + fCc = Sk4f(0); + + // TODO: precompute the average at interval setup time? + for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) { + // Each interval contributes its average color to the total/weighted average: + // + // C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2 + // + // Avg += C * (t1 - t0) + // + const auto c = DstTraits::load(i->fCb) + + DstTraits::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f; + fCc = fCc + c * (i->fT1 - i->fT0); + } + } + + const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const { + SkASSERT(i >= fFirstInterval); + SkASSERT(i <= fLastInterval); + i++; + + if (tileMode == kClamp_TileMode) { + SkASSERT(i <= fLastInterval); + return i; + } + + return (i <= fLastInterval) ? i : fFirstInterval; + } + + SkScalar advance_interval(SkScalar advX) { + SkASSERT(advX >= fAdvX); + + do { + advX -= fAdvX; + fInterval = this->next_interval(fInterval); + fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx; + SkASSERT(fAdvX > 0); + } while (advX >= fAdvX); + + compute_interval_props(fInterval->fT0); + + SkASSERT(advX >= 0); + return advX; + } + + // Current interval properties. + Sk4f fDcDx; // dst color gradient (dc/dx) + Sk4f fCc; // current color, interpolated in dst + SkScalar fAdvX; // remaining interval advance in dst + bool fZeroRamp; // current interval color grad is 0 + + const Sk4fGradientInterval* fFirstInterval; + const Sk4fGradientInterval* fLastInterval; + const Sk4fGradientInterval* fInterval; // current interval + const SkScalar fDx; // 'dx' for consistency with other impls; actually dt/dx + const bool fIsVertical; +}; + +void SkLinearGradient:: +LinearGradient4fContext::mapTs(int x, int y, SkScalar ts[], int count) const { + SkASSERT(count > 0); + SkASSERT(fDstToPosClass != kLinear_MatrixClass); + + SkScalar sx = x + SK_ScalarHalf; + const SkScalar sy = y + SK_ScalarHalf; + SkPoint pt; + + if (fDstToPosClass != kPerspective_MatrixClass) { + // kLinear_MatrixClass, kFixedStepInX_MatrixClass => fixed dt per scanline + const SkScalar dtdx = fDstToPos.fixedStepInX(sy).x(); + fDstToPosProc(fDstToPos, sx, sy, &pt); + + const Sk4f dtdx4 = Sk4f(4 * dtdx); + Sk4f t4 = Sk4f(pt.x() + 0 * dtdx, + pt.x() + 1 * dtdx, + pt.x() + 2 * dtdx, + pt.x() + 3 * dtdx); + + while (count >= 4) { + t4.store(ts); + t4 = t4 + dtdx4; + ts += 4; + count -= 4; + } + + if (count & 2) { + *ts++ = t4[0]; + *ts++ = t4[1]; + t4 = SkNx_shuffle<2, 0, 1, 3>(t4); + } + + if (count & 1) { + *ts++ = t4[0]; + } + } else { + for (int i = 0; i < count; ++i) { + fDstToPosProc(fDstToPos, sx, sy, &pt); + // Perspective may yield NaN values. + // Short of a better idea, drop to 0. + ts[i] = SkScalarIsNaN(pt.x()) ? 0 : pt.x(); + sx += SK_Scalar1; + } + } +} + +bool SkLinearGradient::LinearGradient4fContext::onChooseBlitProcs(const SkImageInfo& info, + BlitState* state) { + if (state->fMode != SkBlendMode::kSrc && + !(state->fMode == SkBlendMode::kSrcOver && (fFlags & kOpaqueAlpha_Flag))) { + return false; + } + + switch (info.colorType()) { + case kN32_SkColorType: + state->fBlitBW = D32_BlitBW; + return true; + case kRGBA_F16_SkColorType: + state->fBlitBW = D64_BlitBW; + return true; + default: + return false; + } +} + +void SkLinearGradient:: +LinearGradient4fContext::D32_BlitBW(BlitState* state, int x, int y, const SkPixmap& dst, + int count) { + // FIXME: ignoring coverage for now + const LinearGradient4fContext* ctx = + static_cast(state->fCtx); + + if (!dst.info().gammaCloseToSRGB()) { + if (ctx->fColorsArePremul) { + ctx->shadePremulSpan( + x, y, dst.writable_addr32(x, y), count); + } else { + ctx->shadePremulSpan( + x, y, dst.writable_addr32(x, y), count); + } + } else { + if (ctx->fColorsArePremul) { + ctx->shadePremulSpan( + x, y, dst.writable_addr32(x, y), count); + } else { + ctx->shadePremulSpan( + x, y, dst.writable_addr32(x, y), count); + } + } +} + +void SkLinearGradient:: +LinearGradient4fContext::D64_BlitBW(BlitState* state, int x, int y, const SkPixmap& dst, + int count) { + // FIXME: ignoring coverage for now + const LinearGradient4fContext* ctx = + static_cast(state->fCtx); + + if (ctx->fColorsArePremul) { + ctx->shadePremulSpan( + x, y, dst.writable_addr64(x, y), count); + } else { + ctx->shadePremulSpan( + x, y, dst.writable_addr64(x, y), count); + } +} diff --git a/src/effects/gradients/Sk4fLinearGradient.h b/src/effects/gradients/Sk4fLinearGradient.h new file mode 100644 index 0000000000..eebd30fbf5 --- /dev/null +++ b/src/effects/gradients/Sk4fLinearGradient.h @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4fLinearGradient_DEFINED +#define Sk4fLinearGradient_DEFINED + +#include "Sk4fGradientBase.h" +#include "SkLinearGradient.h" + +class SkLinearGradient:: +LinearGradient4fContext final : public GradientShaderBase4fContext { +public: + LinearGradient4fContext(const SkLinearGradient&, const ContextRec&); + + void shadeSpan(int x, int y, SkPMColor dst[], int count) override; + void shadeSpan4f(int x, int y, SkPM4f dst[], int count) override; + +protected: + void mapTs(int x, int y, SkScalar ts[], int count) const override; + + bool onChooseBlitProcs(const SkImageInfo&, BlitState*) override; + +private: + using INHERITED = GradientShaderBase4fContext; + + template + class LinearIntervalProcessor; + + template + void shadePremulSpan(int x, int y, typename DstTraits::Type[], + int count) const; + + template + void shadeSpanInternal(int x, int y, typename DstTraits::Type[], + int count) const; + + const Sk4fGradientInterval* findInterval(SkScalar fx) const; + + bool isFast() const { return fDstToPosClass == kLinear_MatrixClass; } + + static void D32_BlitBW(BlitState*, int x, int y, const SkPixmap& dst, int count); + static void D64_BlitBW(BlitState*, int x, int y, const SkPixmap& dst, int count); + + mutable const Sk4fGradientInterval* fCachedInterval; +}; + +#endif // Sk4fLinearGradient_DEFINED diff --git a/src/effects/gradients/SkClampRange.cpp b/src/effects/gradients/SkClampRange.cpp new file mode 100644 index 0000000000..efc93959d1 --- /dev/null +++ b/src/effects/gradients/SkClampRange.cpp @@ -0,0 +1,178 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkClampRange.h" +#include "SkMathPriv.h" + +static int SkCLZ64(uint64_t value) { + int count = 0; + if (value >> 32) { + value >>= 32; + } else { + count += 32; + } + return count + SkCLZ(SkToU32(value)); +} + +static bool sk_64_smul_check(int64_t count, int64_t dx, int64_t* result) { + // Do it the slow way until we have some assembly. + if (dx == std::numeric_limits::min()) { + return false; // SkTAbs overflow + } + + SkASSERT(count >= 0); + uint64_t ucount = static_cast(count); + uint64_t udx = static_cast(SkTAbs(dx)); + int zeros = SkCLZ64(ucount) + SkCLZ64(udx); + // this is a conservative check: it may return false when in fact it would not have overflowed. + // Hackers Delight uses 34 as its convervative check, but that is for 32x32 multiplies. + // Since we are looking at 64x64 muls, we add 32 to the check. + if (zeros < (32 + 34)) { + return false; + } + *result = count * dx; + return true; +} + +static bool sk_64_sadd_check(int64_t a, int64_t b, int64_t* result) { + if (a > 0) { + if (b > std::numeric_limits::max() - a) { + return false; + } + } else { + if (b < std::numeric_limits::min() - a) { + return false; + } + } + + *result = a + b; + return true; +} + + +/* + * returns [0..count] for the number of steps (<= count) for which x0 <= edge + * given each step is followed by x0 += dx + */ +static int chop(int64_t x0, SkGradFixed edge, int64_t x1, int64_t dx, int count) { + SkASSERT(dx > 0); + SkASSERT(count >= 0); + + if (x0 >= edge) { + return 0; + } + if (x1 <= edge) { + return count; + } + int64_t n = (edge - x0 + dx - 1) / dx; + SkASSERT(n >= 0); + SkASSERT(n <= count); + return (int)n; +} + +void SkClampRange::initFor1(SkGradFixed fx) { + fCount0 = fCount1 = fCount2 = 0; + if (fx <= 0) { + fCount0 = 1; + } else if (fx < kFracMax_SkGradFixed) { + fCount1 = 1; + fFx1 = fx; + } else { + fCount2 = 1; + } +} + +void SkClampRange::init(SkGradFixed fx0, SkGradFixed dx0, int count, int v0, int v1) { + SkASSERT(count > 0); + + fV0 = v0; + fV1 = v1; + + // special case 1 == count, as it is slightly common for skia + // and avoids us ever calling divide or 64bit multiply + if (1 == count) { + this->initFor1(fx0); + return; + } + + int64_t fx = fx0; + int64_t dx = dx0; + + // start with ex equal to the last computed value + int64_t count_times_dx, ex; + if (!sk_64_smul_check(count - 1, dx, &count_times_dx) || + !sk_64_sadd_check(fx, count_times_dx, &ex)) { + // we can't represent the computed end in 32.32, so just draw something (first color) + fCount1 = fCount2 = 0; + fCount0 = count; + return; + } + + if ((uint64_t)(fx | ex) <= kFracMax_SkGradFixed) { + fCount0 = fCount2 = 0; + fCount1 = count; + fFx1 = fx0; + return; + } + if (fx <= 0 && ex <= 0) { + fCount1 = fCount2 = 0; + fCount0 = count; + return; + } + if (fx >= kFracMax_SkGradFixed && ex >= kFracMax_SkGradFixed) { + fCount0 = fCount1 = 0; + fCount2 = count; + return; + } + + // now make ex be 1 past the last computed value + ex += dx; + + bool doSwap = dx < 0; + + if (doSwap) { + ex -= dx; + fx -= dx; + SkTSwap(fx, ex); + dx = -dx; + } + + + fCount0 = chop(fx, 0, ex, dx, count); + SkASSERT(fCount0 >= 0); + SkASSERT(fCount0 <= count); + count -= fCount0; + fx += fCount0 * dx; + SkASSERT(fx >= 0); + SkASSERT(fCount0 == 0 || (fx - dx) < 0); + fCount1 = chop(fx, kFracMax_SkGradFixed, ex, dx, count); + SkASSERT(fCount1 >= 0); + SkASSERT(fCount1 <= count); + count -= fCount1; + fCount2 = count; + +#ifdef SK_DEBUG + fx += fCount1 * dx; + SkASSERT(fx <= ex); + if (fCount2 > 0) { + SkASSERT(fx >= kFracMax_SkGradFixed); + if (fCount1 > 0) { + SkASSERT(fx - dx < kFracMax_SkGradFixed); + } + } +#endif + + if (doSwap) { + SkTSwap(fCount0, fCount2); + SkTSwap(fV0, fV1); + dx = -dx; + } + + if (fCount1 > 0) { + fFx1 = fx0 + fCount0 * dx; + } +} diff --git a/src/effects/gradients/SkClampRange.h b/src/effects/gradients/SkClampRange.h new file mode 100644 index 0000000000..8a22e72d38 --- /dev/null +++ b/src/effects/gradients/SkClampRange.h @@ -0,0 +1,62 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkClampRange_DEFINED +#define SkClampRange_DEFINED + +#include "SkFixed.h" +#include "SkScalar.h" + +#define SkGradFixed SkFixed3232 + +// We want the largest 32.32 value representable as a float. (float)0x7FFFFFFF +// becomes too big, due to limited mantissa on the float and its rounding rules, so +// we have to manually compute the next smaller value (aka nextafter). + +// #define SkGradFixedMaxScalar nextafterf(SkFixed3232ToFloat(SkFixed3232Max), 0) +// #define SkGradFixedMinScalar nextafterf(SkFixed3232ToFloat(SkFixed3232Min), 0) +#define SkGradFixedMaxScalar ( 2147483520.0f) +#define SkGradFixedMinScalar (-2147483520.0f) +#define SkScalarPinToGradFixed(x) SkScalarToFixed3232(SkTPin(x, \ + SkGradFixedMinScalar,\ + SkGradFixedMaxScalar)) +#define SkFixedToGradFixed(x) SkFixedToFixed3232(x) +#define SkGradFixedToFixed(x) (SkFixed)((x) >> 16) +#define kFracMax_SkGradFixed 0xFFFFFFFFLL + +/** + * Iteration fixed fx by dx, clamping as you go to [0..kFracMax_SkGradFixed], this class + * computes the (up to) 3 spans there are: + * + * range0: use constant value V0 + * range1: iterate as usual fx += dx + * range2: use constant value V1 + */ +struct SkClampRange { + int fCount0; // count for fV0 + int fCount1; // count for interpolating (fV0...fV1) + int fCount2; // count for fV1 + SkGradFixed fFx1; // initial fx value for the fCount1 range. + // only valid if fCount1 > 0 + int fV0, fV1; + + void init(SkGradFixed fx, SkGradFixed dx, int count, int v0, int v1); + + void validate(int count) const { +#ifdef SK_DEBUG + SkASSERT(fCount0 >= 0); + SkASSERT(fCount1 >= 0); + SkASSERT(fCount2 >= 0); + SkASSERT(fCount0 + fCount1 + fCount2 == count); +#endif + } + +private: + void initFor1(SkGradFixed fx); +}; + +#endif diff --git a/src/effects/gradients/SkGradientBitmapCache.cpp b/src/effects/gradients/SkGradientBitmapCache.cpp new file mode 100644 index 0000000000..06b2d8c3fe --- /dev/null +++ b/src/effects/gradients/SkGradientBitmapCache.cpp @@ -0,0 +1,154 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "SkGradientBitmapCache.h" + +#include "SkMalloc.h" + +struct SkGradientBitmapCache::Entry { + Entry* fPrev; + Entry* fNext; + + void* fBuffer; + size_t fSize; + SkBitmap fBitmap; + + Entry(const void* buffer, size_t size, const SkBitmap& bm) + : fPrev(nullptr), + fNext(nullptr), + fBitmap(bm) { + fBuffer = sk_malloc_throw(size); + fSize = size; + memcpy(fBuffer, buffer, size); + } + + ~Entry() { sk_free(fBuffer); } + + bool equals(const void* buffer, size_t size) const { + return (fSize == size) && !memcmp(fBuffer, buffer, size); + } +}; + +SkGradientBitmapCache::SkGradientBitmapCache(int max) : fMaxEntries(max) { + fEntryCount = 0; + fHead = fTail = nullptr; + + this->validate(); +} + +SkGradientBitmapCache::~SkGradientBitmapCache() { + this->validate(); + + Entry* entry = fHead; + while (entry) { + Entry* next = entry->fNext; + delete entry; + entry = next; + } +} + +SkGradientBitmapCache::Entry* SkGradientBitmapCache::release(Entry* entry) const { + if (entry->fPrev) { + SkASSERT(fHead != entry); + entry->fPrev->fNext = entry->fNext; + } else { + SkASSERT(fHead == entry); + fHead = entry->fNext; + } + if (entry->fNext) { + SkASSERT(fTail != entry); + entry->fNext->fPrev = entry->fPrev; + } else { + SkASSERT(fTail == entry); + fTail = entry->fPrev; + } + return entry; +} + +void SkGradientBitmapCache::attachToHead(Entry* entry) const { + entry->fPrev = nullptr; + entry->fNext = fHead; + if (fHead) { + fHead->fPrev = entry; + } else { + fTail = entry; + } + fHead = entry; +} + +bool SkGradientBitmapCache::find(const void* buffer, size_t size, SkBitmap* bm) const { + AutoValidate av(this); + + Entry* entry = fHead; + while (entry) { + if (entry->equals(buffer, size)) { + if (bm) { + *bm = entry->fBitmap; + } + // move to the head of our list, so we purge it last + this->release(entry); + this->attachToHead(entry); + return true; + } + entry = entry->fNext; + } + return false; +} + +void SkGradientBitmapCache::add(const void* buffer, size_t len, const SkBitmap& bm) { + AutoValidate av(this); + + if (fEntryCount == fMaxEntries) { + SkASSERT(fTail); + delete this->release(fTail); + fEntryCount -= 1; + } + + Entry* entry = new Entry(buffer, len, bm); + this->attachToHead(entry); + fEntryCount += 1; +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG + +void SkGradientBitmapCache::validate() const { + SkASSERT(fEntryCount >= 0 && fEntryCount <= fMaxEntries); + + if (fEntryCount > 0) { + SkASSERT(nullptr == fHead->fPrev); + SkASSERT(nullptr == fTail->fNext); + + if (fEntryCount == 1) { + SkASSERT(fHead == fTail); + } else { + SkASSERT(fHead != fTail); + } + + Entry* entry = fHead; + int count = 0; + while (entry) { + count += 1; + entry = entry->fNext; + } + SkASSERT(count == fEntryCount); + + entry = fTail; + while (entry) { + count -= 1; + entry = entry->fPrev; + } + SkASSERT(0 == count); + } else { + SkASSERT(nullptr == fHead); + SkASSERT(nullptr == fTail); + } +} + +#endif diff --git a/src/effects/gradients/SkGradientBitmapCache.h b/src/effects/gradients/SkGradientBitmapCache.h new file mode 100644 index 0000000000..0dcd32272e --- /dev/null +++ b/src/effects/gradients/SkGradientBitmapCache.h @@ -0,0 +1,48 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkGradientBitmapCache_DEFINED +#define SkGradientBitmapCache_DEFINED + +#include "SkBitmap.h" + +class SkGradientBitmapCache : SkNoncopyable { +public: + SkGradientBitmapCache(int maxEntries); + ~SkGradientBitmapCache(); + + bool find(const void* buffer, size_t len, SkBitmap*) const; + void add(const void* buffer, size_t len, const SkBitmap&); + +private: + int fEntryCount; + const int fMaxEntries; + + struct Entry; + mutable Entry* fHead; + mutable Entry* fTail; + + inline Entry* release(Entry*) const; + inline void attachToHead(Entry*) const; + +#ifdef SK_DEBUG + void validate() const; +#else + void validate() const {} +#endif + + class AutoValidate : SkNoncopyable { + public: + AutoValidate(const SkGradientBitmapCache* bc) : fBC(bc) { bc->validate(); } + ~AutoValidate() { fBC->validate(); } + private: + const SkGradientBitmapCache* fBC; + }; +}; + +#endif diff --git a/src/effects/gradients/SkGradientShader.cpp b/src/effects/gradients/SkGradientShader.cpp new file mode 100644 index 0000000000..137da84d0c --- /dev/null +++ b/src/effects/gradients/SkGradientShader.cpp @@ -0,0 +1,2004 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include "Sk4fLinearGradient.h" +#include "SkColorSpace_XYZ.h" +#include "SkGradientShaderPriv.h" +#include "SkHalf.h" +#include "SkLinearGradient.h" +#include "SkMallocPixelRef.h" +#include "SkRadialGradient.h" +#include "SkSweepGradient.h" +#include "SkTwoPointConicalGradient.h" +#include "../../jumper/SkJumper.h" + + +enum GradientSerializationFlags { + // Bits 29:31 used for various boolean flags + kHasPosition_GSF = 0x80000000, + kHasLocalMatrix_GSF = 0x40000000, + kHasColorSpace_GSF = 0x20000000, + + // Bits 12:28 unused + + // Bits 8:11 for fTileMode + kTileModeShift_GSF = 8, + kTileModeMask_GSF = 0xF, + + // Bits 0:7 for fGradFlags (note that kForce4fContext_PrivateFlag is 0x80) + kGradFlagsShift_GSF = 0, + kGradFlagsMask_GSF = 0xFF, +}; + +void SkGradientShaderBase::Descriptor::flatten(SkWriteBuffer& buffer) const { + uint32_t flags = 0; + if (fPos) { + flags |= kHasPosition_GSF; + } + if (fLocalMatrix) { + flags |= kHasLocalMatrix_GSF; + } + sk_sp colorSpaceData = fColorSpace ? fColorSpace->serialize() : nullptr; + if (colorSpaceData) { + flags |= kHasColorSpace_GSF; + } + SkASSERT(static_cast(fTileMode) <= kTileModeMask_GSF); + flags |= (fTileMode << kTileModeShift_GSF); + SkASSERT(fGradFlags <= kGradFlagsMask_GSF); + flags |= (fGradFlags << kGradFlagsShift_GSF); + + buffer.writeUInt(flags); + + buffer.writeColor4fArray(fColors, fCount); + if (colorSpaceData) { + buffer.writeDataAsByteArray(colorSpaceData.get()); + } + if (fPos) { + buffer.writeScalarArray(fPos, fCount); + } + if (fLocalMatrix) { + buffer.writeMatrix(*fLocalMatrix); + } +} + +bool SkGradientShaderBase::DescriptorScope::unflatten(SkReadBuffer& buffer) { + if (buffer.isVersionLT(SkReadBuffer::kGradientShaderFloatColor_Version)) { + fCount = buffer.getArrayCount(); + if (fCount > kStorageCount) { + size_t allocSize = (sizeof(SkColor4f) + sizeof(SkScalar)) * fCount; + fDynamicStorage.reset(allocSize); + fColors = (SkColor4f*)fDynamicStorage.get(); + fPos = (SkScalar*)(fColors + fCount); + } else { + fColors = fColorStorage; + fPos = fPosStorage; + } + + // Old gradients serialized SkColor. Read that to a temporary location, then convert. + SkSTArray<2, SkColor, true> colors; + colors.resize_back(fCount); + if (!buffer.readColorArray(colors.begin(), fCount)) { + return false; + } + for (int i = 0; i < fCount; ++i) { + mutableColors()[i] = SkColor4f::FromColor(colors[i]); + } + + if (buffer.readBool()) { + if (!buffer.readScalarArray(const_cast(fPos), fCount)) { + return false; + } + } else { + fPos = nullptr; + } + + fColorSpace = nullptr; + fTileMode = (SkShader::TileMode)buffer.read32(); + fGradFlags = buffer.read32(); + + if (buffer.readBool()) { + fLocalMatrix = &fLocalMatrixStorage; + buffer.readMatrix(&fLocalMatrixStorage); + } else { + fLocalMatrix = nullptr; + } + } else { + // New gradient format. Includes floating point color, color space, densely packed flags + uint32_t flags = buffer.readUInt(); + + fTileMode = (SkShader::TileMode)((flags >> kTileModeShift_GSF) & kTileModeMask_GSF); + fGradFlags = (flags >> kGradFlagsShift_GSF) & kGradFlagsMask_GSF; + + fCount = buffer.getArrayCount(); + if (fCount > kStorageCount) { + size_t allocSize = (sizeof(SkColor4f) + sizeof(SkScalar)) * fCount; + fDynamicStorage.reset(allocSize); + fColors = (SkColor4f*)fDynamicStorage.get(); + fPos = (SkScalar*)(fColors + fCount); + } else { + fColors = fColorStorage; + fPos = fPosStorage; + } + if (!buffer.readColor4fArray(mutableColors(), fCount)) { + return false; + } + if (SkToBool(flags & kHasColorSpace_GSF)) { + sk_sp data = buffer.readByteArrayAsData(); + fColorSpace = SkColorSpace::Deserialize(data->data(), data->size()); + } else { + fColorSpace = nullptr; + } + if (SkToBool(flags & kHasPosition_GSF)) { + if (!buffer.readScalarArray(mutablePos(), fCount)) { + return false; + } + } else { + fPos = nullptr; + } + if (SkToBool(flags & kHasLocalMatrix_GSF)) { + fLocalMatrix = &fLocalMatrixStorage; + buffer.readMatrix(&fLocalMatrixStorage); + } else { + fLocalMatrix = nullptr; + } + } + return buffer.isValid(); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +SkGradientShaderBase::SkGradientShaderBase(const Descriptor& desc, const SkMatrix& ptsToUnit) + : INHERITED(desc.fLocalMatrix) + , fPtsToUnit(ptsToUnit) +{ + fPtsToUnit.getType(); // Precache so reads are threadsafe. + SkASSERT(desc.fCount > 1); + + fGradFlags = static_cast(desc.fGradFlags); + + SkASSERT((unsigned)desc.fTileMode < SkShader::kTileModeCount); + SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs)); + fTileMode = desc.fTileMode; + fTileProc = gTileProcs[desc.fTileMode]; + + /* Note: we let the caller skip the first and/or last position. + i.e. pos[0] = 0.3, pos[1] = 0.7 + In these cases, we insert dummy entries to ensure that the final data + will be bracketed by [0, 1]. + i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1 + + Thus colorCount (the caller's value, and fColorCount (our value) may + differ by up to 2. In the above example: + colorCount = 2 + fColorCount = 4 + */ + fColorCount = desc.fCount; + // check if we need to add in dummy start and/or end position/colors + bool dummyFirst = false; + bool dummyLast = false; + if (desc.fPos) { + dummyFirst = desc.fPos[0] != 0; + dummyLast = desc.fPos[desc.fCount - 1] != SK_Scalar1; + fColorCount += dummyFirst + dummyLast; + } + + if (fColorCount > kColorStorageCount) { + size_t size = sizeof(SkColor) + sizeof(SkColor4f) + sizeof(Rec); + if (desc.fPos) { + size += sizeof(SkScalar); + } + fOrigColors = reinterpret_cast(sk_malloc_throw(size * fColorCount)); + } + else { + fOrigColors = fStorage; + } + + fOrigColors4f = (SkColor4f*)(fOrigColors + fColorCount); + + // Now copy over the colors, adding the dummies as needed + SkColor4f* origColors = fOrigColors4f; + if (dummyFirst) { + *origColors++ = desc.fColors[0]; + } + memcpy(origColors, desc.fColors, desc.fCount * sizeof(SkColor4f)); + if (dummyLast) { + origColors += desc.fCount; + *origColors = desc.fColors[desc.fCount - 1]; + } + + // Convert our SkColor4f colors to SkColor as well. Note that this is incorrect if the + // source colors are not in sRGB gamut. We would need to do a gamut transformation, but + // SkColorSpaceXform can't do that (yet). GrColorSpaceXform can, but we may not have GPU + // support compiled in here. For the common case (sRGB colors), this does the right thing. + for (int i = 0; i < fColorCount; ++i) { + fOrigColors[i] = fOrigColors4f[i].toSkColor(); + } + + if (!desc.fColorSpace) { + // This happens if we were constructed from SkColors, so our colors are really sRGB + fColorSpace = SkColorSpace::MakeSRGBLinear(); + } else { + // The color space refers to the float colors, so it must be linear gamma + SkASSERT(desc.fColorSpace->gammaIsLinear()); + fColorSpace = desc.fColorSpace; + } + + if (desc.fPos && fColorCount) { + fOrigPos = (SkScalar*)(fOrigColors4f + fColorCount); + fRecs = (Rec*)(fOrigPos + fColorCount); + } else { + fOrigPos = nullptr; + fRecs = (Rec*)(fOrigColors4f + fColorCount); + } + + if (fColorCount > 2) { + Rec* recs = fRecs; + recs->fPos = 0; + // recs->fScale = 0; // unused; + recs += 1; + if (desc.fPos) { + SkScalar* origPosPtr = fOrigPos; + *origPosPtr++ = 0; + + /* We need to convert the user's array of relative positions into + fixed-point positions and scale factors. We need these results + to be strictly monotonic (no two values equal or out of order). + Hence this complex loop that just jams a zero for the scale + value if it sees a segment out of order, and it assures that + we start at 0 and end at 1.0 + */ + SkScalar prev = 0; + int startIndex = dummyFirst ? 0 : 1; + int count = desc.fCount + dummyLast; + for (int i = startIndex; i < count; i++) { + // force the last value to be 1.0 + SkScalar curr; + if (i == desc.fCount) { // we're really at the dummyLast + curr = 1; + } else { + curr = SkScalarPin(desc.fPos[i], 0, 1); + } + *origPosPtr++ = curr; + + recs->fPos = SkScalarToFixed(curr); + SkFixed diff = SkScalarToFixed(curr - prev); + if (diff > 0) { + recs->fScale = (1 << 24) / diff; + } else { + recs->fScale = 0; // ignore this segment + } + // get ready for the next value + prev = curr; + recs += 1; + } + } else { // assume even distribution + fOrigPos = nullptr; + + SkFixed dp = SK_Fixed1 / (desc.fCount - 1); + SkFixed p = dp; + SkFixed scale = (desc.fCount - 1) << 8; // (1 << 24) / dp + for (int i = 1; i < desc.fCount - 1; i++) { + recs->fPos = p; + recs->fScale = scale; + recs += 1; + p += dp; + } + recs->fPos = SK_Fixed1; + recs->fScale = scale; + } + } else if (desc.fPos) { + SkASSERT(2 == fColorCount); + fOrigPos[0] = SkScalarPin(desc.fPos[0], 0, 1); + fOrigPos[1] = SkScalarPin(desc.fPos[1], fOrigPos[0], 1); + if (0 == fOrigPos[0] && 1 == fOrigPos[1]) { + fOrigPos = nullptr; + } + } + this->initCommon(); +} + +SkGradientShaderBase::~SkGradientShaderBase() { + if (fOrigColors != fStorage) { + sk_free(fOrigColors); + } +} + +void SkGradientShaderBase::initCommon() { + unsigned colorAlpha = 0xFF; + for (int i = 0; i < fColorCount; i++) { + colorAlpha &= SkColorGetA(fOrigColors[i]); + } + fColorsAreOpaque = colorAlpha == 0xFF; +} + +void SkGradientShaderBase::flatten(SkWriteBuffer& buffer) const { + Descriptor desc; + desc.fColors = fOrigColors4f; + desc.fColorSpace = fColorSpace; + desc.fPos = fOrigPos; + desc.fCount = fColorCount; + desc.fTileMode = fTileMode; + desc.fGradFlags = fGradFlags; + + const SkMatrix& m = this->getLocalMatrix(); + desc.fLocalMatrix = m.isIdentity() ? nullptr : &m; + desc.flatten(buffer); +} + +void SkGradientShaderBase::FlipGradientColors(SkColor* colorDst, Rec* recDst, + SkColor* colorSrc, Rec* recSrc, + int count) { + SkAutoSTArray<8, SkColor> colorsTemp(count); + for (int i = 0; i < count; ++i) { + int offset = count - i - 1; + colorsTemp[i] = colorSrc[offset]; + } + if (count > 2) { + SkAutoSTArray<8, Rec> recsTemp(count); + for (int i = 0; i < count; ++i) { + int offset = count - i - 1; + recsTemp[i].fPos = SK_Fixed1 - recSrc[offset].fPos; + recsTemp[i].fScale = recSrc[offset].fScale; + } + memcpy(recDst, recsTemp.get(), count * sizeof(Rec)); + } + memcpy(colorDst, colorsTemp.get(), count * sizeof(SkColor)); +} + +static void add_stop_color(SkJumper_GradientCtx* ctx, size_t stop, SkPM4f Fs, SkPM4f Bs) { + (ctx->fs[0])[stop] = Fs.r(); + (ctx->fs[1])[stop] = Fs.g(); + (ctx->fs[2])[stop] = Fs.b(); + (ctx->fs[3])[stop] = Fs.a(); + (ctx->bs[0])[stop] = Bs.r(); + (ctx->bs[1])[stop] = Bs.g(); + (ctx->bs[2])[stop] = Bs.b(); + (ctx->bs[3])[stop] = Bs.a(); +} + +static void add_const_color(SkJumper_GradientCtx* ctx, size_t stop, SkPM4f color) { + add_stop_color(ctx, stop, SkPM4f::FromPremulRGBA(0,0,0,0), color); +} + +// Calculate a factor F and a bias B so that color = F*t + B when t is in range of +// the stop. Assume that the distance between stops is 1/gapCount. +static void init_stop_evenly( + SkJumper_GradientCtx* ctx, float gapCount, size_t stop, SkPM4f c_l, SkPM4f c_r) { + // Clankium's GCC 4.9 targeting ARMv7 is barfing when we use Sk4f math here, so go scalar... + SkPM4f Fs = {{ + (c_r.r() - c_l.r()) * gapCount, + (c_r.g() - c_l.g()) * gapCount, + (c_r.b() - c_l.b()) * gapCount, + (c_r.a() - c_l.a()) * gapCount, + }}; + SkPM4f Bs = {{ + c_l.r() - Fs.r()*(stop/gapCount), + c_l.g() - Fs.g()*(stop/gapCount), + c_l.b() - Fs.b()*(stop/gapCount), + c_l.a() - Fs.a()*(stop/gapCount), + }}; + add_stop_color(ctx, stop, Fs, Bs); +} + +// For each stop we calculate a bias B and a scale factor F, such that +// for any t between stops n and n+1, the color we want is B[n] + F[n]*t. +static void init_stop_pos( + SkJumper_GradientCtx* ctx, size_t stop, float t_l, float t_r, SkPM4f c_l, SkPM4f c_r) { + // See note about Clankium's old compiler in init_stop_evenly(). + SkPM4f Fs = {{ + (c_r.r() - c_l.r()) / (t_r - t_l), + (c_r.g() - c_l.g()) / (t_r - t_l), + (c_r.b() - c_l.b()) / (t_r - t_l), + (c_r.a() - c_l.a()) / (t_r - t_l), + }}; + SkPM4f Bs = {{ + c_l.r() - Fs.r()*t_l, + c_l.g() - Fs.g()*t_l, + c_l.b() - Fs.b()*t_l, + c_l.a() - Fs.a()*t_l, + }}; + ctx->ts[stop] = t_l; + add_stop_color(ctx, stop, Fs, Bs); +} + +bool SkGradientShaderBase::onAppendStages(SkRasterPipeline* p, + SkColorSpace* dstCS, + SkArenaAlloc* alloc, + const SkMatrix& ctm, + const SkPaint& paint, + const SkMatrix* localM) const { + SkMatrix matrix; + if (!this->computeTotalInverse(ctm, localM, &matrix)) { + return false; + } + + SkRasterPipeline_<256> subclass; + if (!this->adjustMatrixAndAppendStages(alloc, &matrix, &subclass)) { + return false; + } + + auto* m = alloc->makeArrayDefault(9); + if (matrix.asAffine(m)) { + p->append(SkRasterPipeline::matrix_2x3, m); + } else { + matrix.get9(m); + p->append(SkRasterPipeline::matrix_perspective, m); + } + + p->extend(subclass); + + switch(fTileMode) { + case kMirror_TileMode: p->append(SkRasterPipeline::mirror_x_1); break; + case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_x_1); break; + case kClamp_TileMode: + if (!fOrigPos) { + // We clamp only when the stops are evenly spaced. + // If not, there may be hard stops, and clamping ruins hard stops at 0 and/or 1. + // In that case, we must make sure we're using the general "gradient" stage, + // which is the only stage that will correctly handle unclamped t. + p->append(SkRasterPipeline::clamp_x_1); + } + } + + const bool premulGrad = fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag; + auto prepareColor = [premulGrad, dstCS, this](int i) { + SkColor4f c = dstCS ? to_colorspace(fOrigColors4f[i], fColorSpace.get(), dstCS) + : SkColor4f_from_SkColor(fOrigColors[i], nullptr); + return premulGrad ? c.premul() + : SkPM4f::From4f(Sk4f::Load(&c)); + }; + + // The two-stop case with stops at 0 and 1. + if (fColorCount == 2 && fOrigPos == nullptr) { + const SkPM4f c_l = prepareColor(0), + c_r = prepareColor(1); + + // See F and B below. + auto* f_and_b = alloc->makeArrayDefault(2); + f_and_b[0] = SkPM4f::From4f(c_r.to4f() - c_l.to4f()); + f_and_b[1] = c_l; + + p->append(SkRasterPipeline::evenly_spaced_2_stop_gradient, f_and_b); + } else { + auto* ctx = alloc->make(); + + // Note: In order to handle clamps in search, the search assumes a stop conceptully placed + // at -inf. Therefore, the max number of stops is fColorCount+1. + for (int i = 0; i < 4; i++) { + // Allocate at least at for the AVX2 gather from a YMM register. + ctx->fs[i] = alloc->makeArray(std::max(fColorCount+1, 8)); + ctx->bs[i] = alloc->makeArray(std::max(fColorCount+1, 8)); + } + + if (fOrigPos == nullptr) { + // Handle evenly distributed stops. + + size_t stopCount = fColorCount; + float gapCount = stopCount - 1; + + SkPM4f c_l = prepareColor(0); + for (size_t i = 0; i < stopCount - 1; i++) { + SkPM4f c_r = prepareColor(i + 1); + init_stop_evenly(ctx, gapCount, i, c_l, c_r); + c_l = c_r; + } + add_const_color(ctx, stopCount - 1, c_l); + + ctx->stopCount = stopCount; + p->append(SkRasterPipeline::evenly_spaced_gradient, ctx); + } else { + // Handle arbitrary stops. + + ctx->ts = alloc->makeArray(fColorCount+1); + + // Remove the dummy stops inserted by SkGradientShaderBase::SkGradientShaderBase + // because they are naturally handled by the search method. + int firstStop; + int lastStop; + if (fColorCount > 2) { + firstStop = fOrigColors4f[0] != fOrigColors4f[1] ? 0 : 1; + lastStop = fOrigColors4f[fColorCount - 2] != fOrigColors4f[fColorCount - 1] + ? fColorCount - 1 : fColorCount - 2; + } else { + firstStop = 0; + lastStop = 1; + } + + size_t stopCount = 0; + float t_l = fOrigPos[firstStop]; + SkPM4f c_l = prepareColor(firstStop); + add_const_color(ctx, stopCount++, c_l); + // N.B. lastStop is the index of the last stop, not one after. + for (int i = firstStop; i < lastStop; i++) { + float t_r = fOrigPos[i + 1]; + SkPM4f c_r = prepareColor(i + 1); + if (t_l < t_r) { + init_stop_pos(ctx, stopCount, t_l, t_r, c_l, c_r); + stopCount += 1; + } + t_l = t_r; + c_l = c_r; + } + + ctx->ts[stopCount] = t_l; + add_const_color(ctx, stopCount++, c_l); + + ctx->stopCount = stopCount; + p->append(SkRasterPipeline::gradient, ctx); + } + } + + if (!premulGrad && !this->colorsAreOpaque()) { + p->append(SkRasterPipeline::premul); + } + + return true; +} + + +bool SkGradientShaderBase::isOpaque() const { + return fColorsAreOpaque; +} + +static unsigned rounded_divide(unsigned numer, unsigned denom) { + return (numer + (denom >> 1)) / denom; +} + +bool SkGradientShaderBase::onAsLuminanceColor(SkColor* lum) const { + // we just compute an average color. + // possibly we could weight this based on the proportional width for each color + // assuming they are not evenly distributed in the fPos array. + int r = 0; + int g = 0; + int b = 0; + const int n = fColorCount; + for (int i = 0; i < n; ++i) { + SkColor c = fOrigColors[i]; + r += SkColorGetR(c); + g += SkColorGetG(c); + b += SkColorGetB(c); + } + *lum = SkColorSetRGB(rounded_divide(r, n), rounded_divide(g, n), rounded_divide(b, n)); + return true; +} + +SkGradientShaderBase::GradientShaderBaseContext::GradientShaderBaseContext( + const SkGradientShaderBase& shader, const ContextRec& rec) + : INHERITED(shader, rec) +#ifdef SK_SUPPORT_LEGACY_GRADIENT_DITHERING + , fDither(true) +#else + , fDither(rec.fPaint->isDither()) +#endif + , fCache(shader.refCache(getPaintAlpha(), fDither)) +{ + const SkMatrix& inverse = this->getTotalInverse(); + + fDstToIndex.setConcat(shader.fPtsToUnit, inverse); + + fDstToIndexProc = fDstToIndex.getMapXYProc(); + fDstToIndexClass = (uint8_t)SkShaderBase::Context::ComputeMatrixClass(fDstToIndex); + + // now convert our colors in to PMColors + unsigned paintAlpha = this->getPaintAlpha(); + + fFlags = this->INHERITED::getFlags(); + if (shader.fColorsAreOpaque && paintAlpha == 0xFF) { + fFlags |= kOpaqueAlpha_Flag; + } +} + +bool SkGradientShaderBase::GradientShaderBaseContext::isValid() const { + return fDstToIndex.isFinite(); +} + +SkGradientShaderBase::GradientShaderCache::GradientShaderCache( + U8CPU alpha, bool dither, const SkGradientShaderBase& shader) + : fCacheAlpha(alpha) + , fCacheDither(dither) + , fShader(shader) +{ + // Only initialize the cache in getCache32. + fCache32 = nullptr; +} + +SkGradientShaderBase::GradientShaderCache::~GradientShaderCache() {} + +/* + * r,g,b used to be SkFixed, but on gcc (4.2.1 mac and 4.6.3 goobuntu) in + * release builds, we saw a compiler error where the 0xFF parameter in + * SkPackARGB32() was being totally ignored whenever it was called with + * a non-zero add (e.g. 0x8000). + * + * We found two work-arounds: + * 1. change r,g,b to unsigned (or just one of them) + * 2. change SkPackARGB32 to + its (a << SK_A32_SHIFT) value instead + * of using | + * + * We chose #1 just because it was more localized. + * See http://code.google.com/p/skia/issues/detail?id=1113 + * + * The type SkUFixed encapsulate this need for unsigned, but logically Fixed. + */ +typedef uint32_t SkUFixed; + +void SkGradientShaderBase::GradientShaderCache::Build32bitCache( + SkPMColor cache[], SkColor c0, SkColor c1, + int count, U8CPU paintAlpha, uint32_t gradFlags, bool dither) { + SkASSERT(count > 1); + + // need to apply paintAlpha to our two endpoints + uint32_t a0 = SkMulDiv255Round(SkColorGetA(c0), paintAlpha); + uint32_t a1 = SkMulDiv255Round(SkColorGetA(c1), paintAlpha); + + + const bool interpInPremul = SkToBool(gradFlags & + SkGradientShader::kInterpolateColorsInPremul_Flag); + + uint32_t r0 = SkColorGetR(c0); + uint32_t g0 = SkColorGetG(c0); + uint32_t b0 = SkColorGetB(c0); + + uint32_t r1 = SkColorGetR(c1); + uint32_t g1 = SkColorGetG(c1); + uint32_t b1 = SkColorGetB(c1); + + if (interpInPremul) { + r0 = SkMulDiv255Round(r0, a0); + g0 = SkMulDiv255Round(g0, a0); + b0 = SkMulDiv255Round(b0, a0); + + r1 = SkMulDiv255Round(r1, a1); + g1 = SkMulDiv255Round(g1, a1); + b1 = SkMulDiv255Round(b1, a1); + } + + SkFixed da = SkIntToFixed(a1 - a0) / (count - 1); + SkFixed dr = SkIntToFixed(r1 - r0) / (count - 1); + SkFixed dg = SkIntToFixed(g1 - g0) / (count - 1); + SkFixed db = SkIntToFixed(b1 - b0) / (count - 1); + + /* We pre-add 1/8 to avoid having to add this to our [0] value each time + in the loop. Without this, the bias for each would be + 0x2000 0xA000 0xE000 0x6000 + With this trick, we can add 0 for the first (no-op) and just adjust the + others. + */ + const SkUFixed bias0 = dither ? 0x2000 : 0x8000; + const SkUFixed bias1 = dither ? 0x8000 : 0; + const SkUFixed bias2 = dither ? 0xC000 : 0; + const SkUFixed bias3 = dither ? 0x4000 : 0; + + SkUFixed a = SkIntToFixed(a0) + bias0; + SkUFixed r = SkIntToFixed(r0) + bias0; + SkUFixed g = SkIntToFixed(g0) + bias0; + SkUFixed b = SkIntToFixed(b0) + bias0; + + /* + * Our dither-cell (spatially) is + * 0 2 + * 3 1 + * Where + * [0] -> [-1/8 ... 1/8 ) values near 0 + * [1] -> [ 1/8 ... 3/8 ) values near 1/4 + * [2] -> [ 3/8 ... 5/8 ) values near 1/2 + * [3] -> [ 5/8 ... 7/8 ) values near 3/4 + */ + + if (0xFF == a0 && 0 == da) { + do { + cache[kCache32Count*0] = SkPackARGB32(0xFF, (r + 0 ) >> 16, + (g + 0 ) >> 16, + (b + 0 ) >> 16); + cache[kCache32Count*1] = SkPackARGB32(0xFF, (r + bias1) >> 16, + (g + bias1) >> 16, + (b + bias1) >> 16); + cache[kCache32Count*2] = SkPackARGB32(0xFF, (r + bias2) >> 16, + (g + bias2) >> 16, + (b + bias2) >> 16); + cache[kCache32Count*3] = SkPackARGB32(0xFF, (r + bias3) >> 16, + (g + bias3) >> 16, + (b + bias3) >> 16); + cache += 1; + r += dr; + g += dg; + b += db; + } while (--count != 0); + } else if (interpInPremul) { + do { + cache[kCache32Count*0] = SkPackARGB32((a + 0 ) >> 16, + (r + 0 ) >> 16, + (g + 0 ) >> 16, + (b + 0 ) >> 16); + cache[kCache32Count*1] = SkPackARGB32((a + bias1) >> 16, + (r + bias1) >> 16, + (g + bias1) >> 16, + (b + bias1) >> 16); + cache[kCache32Count*2] = SkPackARGB32((a + bias2) >> 16, + (r + bias2) >> 16, + (g + bias2) >> 16, + (b + bias2) >> 16); + cache[kCache32Count*3] = SkPackARGB32((a + bias3) >> 16, + (r + bias3) >> 16, + (g + bias3) >> 16, + (b + bias3) >> 16); + cache += 1; + a += da; + r += dr; + g += dg; + b += db; + } while (--count != 0); + } else { // interpolate in unpreml space + do { + cache[kCache32Count*0] = SkPremultiplyARGBInline((a + 0 ) >> 16, + (r + 0 ) >> 16, + (g + 0 ) >> 16, + (b + 0 ) >> 16); + cache[kCache32Count*1] = SkPremultiplyARGBInline((a + bias1) >> 16, + (r + bias1) >> 16, + (g + bias1) >> 16, + (b + bias1) >> 16); + cache[kCache32Count*2] = SkPremultiplyARGBInline((a + bias2) >> 16, + (r + bias2) >> 16, + (g + bias2) >> 16, + (b + bias2) >> 16); + cache[kCache32Count*3] = SkPremultiplyARGBInline((a + bias3) >> 16, + (r + bias3) >> 16, + (g + bias3) >> 16, + (b + bias3) >> 16); + cache += 1; + a += da; + r += dr; + g += dg; + b += db; + } while (--count != 0); + } +} + +static inline int SkFixedToFFFF(SkFixed x) { + SkASSERT((unsigned)x <= SK_Fixed1); + return x - (x >> 16); +} + +const SkPMColor* SkGradientShaderBase::GradientShaderCache::getCache32() { + fCache32InitOnce(SkGradientShaderBase::GradientShaderCache::initCache32, this); + SkASSERT(fCache32); + return fCache32; +} + +void SkGradientShaderBase::GradientShaderCache::initCache32(GradientShaderCache* cache) { + const int kNumberOfDitherRows = 4; + const SkImageInfo info = SkImageInfo::MakeN32Premul(kCache32Count, kNumberOfDitherRows); + + SkASSERT(nullptr == cache->fCache32PixelRef); + cache->fCache32PixelRef = SkMallocPixelRef::MakeAllocate(info, 0, nullptr); + cache->fCache32 = (SkPMColor*)cache->fCache32PixelRef->pixels(); + if (cache->fShader.fColorCount == 2) { + Build32bitCache(cache->fCache32, cache->fShader.fOrigColors[0], + cache->fShader.fOrigColors[1], kCache32Count, cache->fCacheAlpha, + cache->fShader.fGradFlags, cache->fCacheDither); + } else { + Rec* rec = cache->fShader.fRecs; + int prevIndex = 0; + for (int i = 1; i < cache->fShader.fColorCount; i++) { + int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift; + SkASSERT(nextIndex < kCache32Count); + + if (nextIndex > prevIndex) + Build32bitCache(cache->fCache32 + prevIndex, cache->fShader.fOrigColors[i-1], + cache->fShader.fOrigColors[i], nextIndex - prevIndex + 1, + cache->fCacheAlpha, cache->fShader.fGradFlags, cache->fCacheDither); + prevIndex = nextIndex; + } + } +} + +void SkGradientShaderBase::initLinearBitmap(SkBitmap* bitmap) const { + const bool interpInPremul = SkToBool(fGradFlags & + SkGradientShader::kInterpolateColorsInPremul_Flag); + SkHalf* pixelsF16 = reinterpret_cast(bitmap->getPixels()); + uint32_t* pixelsS32 = reinterpret_cast(bitmap->getPixels()); + + typedef std::function pixelWriteFn_t; + + pixelWriteFn_t writeF16Pixel = [&](const Sk4f& x, int index) { + Sk4h c = SkFloatToHalf_finite_ftz(x); + pixelsF16[4*index+0] = c[0]; + pixelsF16[4*index+1] = c[1]; + pixelsF16[4*index+2] = c[2]; + pixelsF16[4*index+3] = c[3]; + }; + pixelWriteFn_t writeS32Pixel = [&](const Sk4f& c, int index) { + pixelsS32[index] = Sk4f_toS32(c); + }; + + pixelWriteFn_t writeSizedPixel = + (kRGBA_F16_SkColorType == bitmap->colorType()) ? writeF16Pixel : writeS32Pixel; + pixelWriteFn_t writeUnpremulPixel = [&](const Sk4f& c, int index) { + writeSizedPixel(c * Sk4f(c[3], c[3], c[3], 1.0f), index); + }; + + pixelWriteFn_t writePixel = interpInPremul ? writeSizedPixel : writeUnpremulPixel; + + int prevIndex = 0; + for (int i = 1; i < fColorCount; i++) { + int nextIndex = (fColorCount == 2) ? (kCache32Count - 1) + : SkFixedToFFFF(fRecs[i].fPos) >> kCache32Shift; + SkASSERT(nextIndex < kCache32Count); + + if (nextIndex > prevIndex) { + Sk4f c0 = Sk4f::Load(fOrigColors4f[i - 1].vec()); + Sk4f c1 = Sk4f::Load(fOrigColors4f[i].vec()); + if (interpInPremul) { + c0 = c0 * Sk4f(c0[3], c0[3], c0[3], 1.0f); + c1 = c1 * Sk4f(c1[3], c1[3], c1[3], 1.0f); + } + + Sk4f step = Sk4f(1.0f / static_cast(nextIndex - prevIndex)); + Sk4f delta = (c1 - c0) * step; + + for (int curIndex = prevIndex; curIndex <= nextIndex; ++curIndex) { + writePixel(c0, curIndex); + c0 += delta; + } + } + prevIndex = nextIndex; + } + SkASSERT(prevIndex == kCache32Count - 1); +} + +/* + * The gradient holds a cache for the most recent value of alpha. Successive + * callers with the same alpha value will share the same cache. + */ +sk_sp SkGradientShaderBase::refCache(U8CPU alpha, + bool dither) const { + SkAutoMutexAcquire ama(fCacheMutex); + if (!fCache || fCache->getAlpha() != alpha || fCache->getDither() != dither) { + fCache.reset(new GradientShaderCache(alpha, dither, *this)); + } + // Increment the ref counter inside the mutex to ensure the returned pointer is still valid. + // Otherwise, the pointer may have been overwritten on a different thread before the object's + // ref count was incremented. + return fCache; +} + +SK_DECLARE_STATIC_MUTEX(gGradientCacheMutex); +/* + * Because our caller might rebuild the same (logically the same) gradient + * over and over, we'd like to return exactly the same "bitmap" if possible, + * allowing the client to utilize a cache of our bitmap (e.g. with a GPU). + * To do that, we maintain a private cache of built-bitmaps, based on our + * colors and positions. Note: we don't try to flatten the fMapper, so if one + * is present, we skip the cache for now. + */ +void SkGradientShaderBase::getGradientTableBitmap(SkBitmap* bitmap, + GradientBitmapType bitmapType) const { + // our caller assumes no external alpha, so we ensure that our cache is built with 0xFF + sk_sp cache(this->refCache(0xFF, true)); + + // build our key: [numColors + colors[] + {positions[]} + flags + colorType ] + int count = 1 + fColorCount + 1 + 1; + if (fColorCount > 2) { + count += fColorCount - 1; // fRecs[].fPos + } + + SkAutoSTMalloc<16, int32_t> storage(count); + int32_t* buffer = storage.get(); + + *buffer++ = fColorCount; + memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor)); + buffer += fColorCount; + if (fColorCount > 2) { + for (int i = 1; i < fColorCount; i++) { + *buffer++ = fRecs[i].fPos; + } + } + *buffer++ = fGradFlags; + *buffer++ = static_cast(bitmapType); + SkASSERT(buffer - storage.get() == count); + + /////////////////////////////////// + + static SkGradientBitmapCache* gCache; + // each cache cost 1K or 2K of RAM, since each bitmap will be 1x256 at either 32bpp or 64bpp + static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32; + SkAutoMutexAcquire ama(gGradientCacheMutex); + + if (nullptr == gCache) { + gCache = new SkGradientBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS); + } + size_t size = count * sizeof(int32_t); + + if (!gCache->find(storage.get(), size, bitmap)) { + if (GradientBitmapType::kLegacy == bitmapType) { + // force our cache32pixelref to be built + (void)cache->getCache32(); + bitmap->setInfo(SkImageInfo::MakeN32Premul(kCache32Count, 1)); + bitmap->setPixelRef(sk_ref_sp(cache->getCache32PixelRef()), 0, 0); + } else { + // For these cases we use the bitmap cache, but not the GradientShaderCache. So just + // allocate and populate the bitmap's data directly. + + SkImageInfo info; + switch (bitmapType) { + case GradientBitmapType::kSRGB: + info = SkImageInfo::Make(kCache32Count, 1, kRGBA_8888_SkColorType, + kPremul_SkAlphaType, + SkColorSpace::MakeSRGB()); + break; + case GradientBitmapType::kHalfFloat: + info = SkImageInfo::Make( + kCache32Count, 1, kRGBA_F16_SkColorType, kPremul_SkAlphaType, + SkColorSpace::MakeSRGBLinear()); + break; + default: + SkFAIL("Unexpected bitmap type"); + return; + } + bitmap->allocPixels(info); + this->initLinearBitmap(bitmap); + } + gCache->add(storage.get(), size, *bitmap); + } +} + +void SkGradientShaderBase::commonAsAGradient(GradientInfo* info, bool flipGrad) const { + if (info) { + if (info->fColorCount >= fColorCount) { + SkColor* colorLoc; + Rec* recLoc; + SkAutoSTArray<8, SkColor> colorStorage; + SkAutoSTArray<8, Rec> recStorage; + if (flipGrad && (info->fColors || info->fColorOffsets)) { + colorStorage.reset(fColorCount); + recStorage.reset(fColorCount); + colorLoc = colorStorage.get(); + recLoc = recStorage.get(); + FlipGradientColors(colorLoc, recLoc, fOrigColors, fRecs, fColorCount); + } else { + colorLoc = fOrigColors; + recLoc = fRecs; + } + if (info->fColors) { + memcpy(info->fColors, colorLoc, fColorCount * sizeof(SkColor)); + } + if (info->fColorOffsets) { + if (fColorCount == 2) { + info->fColorOffsets[0] = 0; + info->fColorOffsets[1] = SK_Scalar1; + } else if (fColorCount > 2) { + for (int i = 0; i < fColorCount; ++i) { + info->fColorOffsets[i] = SkFixedToScalar(recLoc[i].fPos); + } + } + } + } + info->fColorCount = fColorCount; + info->fTileMode = fTileMode; + info->fGradientFlags = fGradFlags; + } +} + +#ifndef SK_IGNORE_TO_STRING +void SkGradientShaderBase::toString(SkString* str) const { + + str->appendf("%d colors: ", fColorCount); + + for (int i = 0; i < fColorCount; ++i) { + str->appendHex(fOrigColors[i], 8); + if (i < fColorCount-1) { + str->append(", "); + } + } + + if (fColorCount > 2) { + str->append(" points: ("); + for (int i = 0; i < fColorCount; ++i) { + str->appendScalar(SkFixedToScalar(fRecs[i].fPos)); + if (i < fColorCount-1) { + str->append(", "); + } + } + str->append(")"); + } + + static const char* gTileModeName[SkShader::kTileModeCount] = { + "clamp", "repeat", "mirror" + }; + + str->append(" "); + str->append(gTileModeName[fTileMode]); + + this->INHERITED::toString(str); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// Return true if these parameters are valid/legal/safe to construct a gradient +// +static bool valid_grad(const SkColor4f colors[], const SkScalar pos[], int count, + unsigned tileMode) { + return nullptr != colors && count >= 1 && tileMode < (unsigned)SkShader::kTileModeCount; +} + +static void desc_init(SkGradientShaderBase::Descriptor* desc, + const SkColor4f colors[], sk_sp colorSpace, + const SkScalar pos[], int colorCount, + SkShader::TileMode mode, uint32_t flags, const SkMatrix* localMatrix) { + SkASSERT(colorCount > 1); + + desc->fColors = colors; + desc->fColorSpace = std::move(colorSpace); + desc->fPos = pos; + desc->fCount = colorCount; + desc->fTileMode = mode; + desc->fGradFlags = flags; + desc->fLocalMatrix = localMatrix; +} + +// assumes colors is SkColor4f* and pos is SkScalar* +#define EXPAND_1_COLOR(count) \ + SkColor4f tmp[2]; \ + do { \ + if (1 == count) { \ + tmp[0] = tmp[1] = colors[0]; \ + colors = tmp; \ + pos = nullptr; \ + count = 2; \ + } \ + } while (0) + +struct ColorStopOptimizer { + ColorStopOptimizer(const SkColor4f* colors, const SkScalar* pos, + int count, SkShader::TileMode mode) + : fColors(colors) + , fPos(pos) + , fCount(count) { + + if (!pos || count != 3) { + return; + } + + if (SkScalarNearlyEqual(pos[0], 0.0f) && + SkScalarNearlyEqual(pos[1], 0.0f) && + SkScalarNearlyEqual(pos[2], 1.0f)) { + + if (SkShader::kRepeat_TileMode == mode || + SkShader::kMirror_TileMode == mode || + colors[0] == colors[1]) { + + // Ignore the leftmost color/pos. + fColors += 1; + fPos += 1; + fCount = 2; + } + } else if (SkScalarNearlyEqual(pos[0], 0.0f) && + SkScalarNearlyEqual(pos[1], 1.0f) && + SkScalarNearlyEqual(pos[2], 1.0f)) { + + if (SkShader::kRepeat_TileMode == mode || + SkShader::kMirror_TileMode == mode || + colors[1] == colors[2]) { + + // Ignore the rightmost color/pos. + fCount = 2; + } + } + } + + const SkColor4f* fColors; + const SkScalar* fPos; + int fCount; +}; + +struct ColorConverter { + ColorConverter(const SkColor* colors, int count) { + for (int i = 0; i < count; ++i) { + fColors4f.push_back(SkColor4f::FromColor(colors[i])); + } + } + + SkSTArray<2, SkColor4f, true> fColors4f; +}; + +sk_sp SkGradientShader::MakeLinear(const SkPoint pts[2], + const SkColor colors[], + const SkScalar pos[], int colorCount, + SkShader::TileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + ColorConverter converter(colors, colorCount); + return MakeLinear(pts, converter.fColors4f.begin(), nullptr, pos, colorCount, mode, flags, + localMatrix); +} + +sk_sp SkGradientShader::MakeLinear(const SkPoint pts[2], + const SkColor4f colors[], + sk_sp colorSpace, + const SkScalar pos[], int colorCount, + SkShader::TileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + if (!pts || !SkScalarIsFinite((pts[1] - pts[0]).length())) { + return nullptr; + } + if (!valid_grad(colors, pos, colorCount, mode)) { + return nullptr; + } + if (1 == colorCount) { + return SkShader::MakeColorShader(colors[0], std::move(colorSpace)); + } + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + + ColorStopOptimizer opt(colors, pos, colorCount, mode); + + SkGradientShaderBase::Descriptor desc; + desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags, + localMatrix); + return sk_make_sp(pts, desc); +} + +sk_sp SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius, + const SkColor colors[], + const SkScalar pos[], int colorCount, + SkShader::TileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + ColorConverter converter(colors, colorCount); + return MakeRadial(center, radius, converter.fColors4f.begin(), nullptr, pos, colorCount, mode, + flags, localMatrix); +} + +sk_sp SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius, + const SkColor4f colors[], + sk_sp colorSpace, + const SkScalar pos[], int colorCount, + SkShader::TileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + if (radius <= 0) { + return nullptr; + } + if (!valid_grad(colors, pos, colorCount, mode)) { + return nullptr; + } + if (1 == colorCount) { + return SkShader::MakeColorShader(colors[0], std::move(colorSpace)); + } + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + + ColorStopOptimizer opt(colors, pos, colorCount, mode); + + SkGradientShaderBase::Descriptor desc; + desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags, + localMatrix); + return sk_make_sp(center, radius, desc); +} + +sk_sp SkGradientShader::MakeTwoPointConical(const SkPoint& start, + SkScalar startRadius, + const SkPoint& end, + SkScalar endRadius, + const SkColor colors[], + const SkScalar pos[], + int colorCount, + SkShader::TileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + ColorConverter converter(colors, colorCount); + return MakeTwoPointConical(start, startRadius, end, endRadius, converter.fColors4f.begin(), + nullptr, pos, colorCount, mode, flags, localMatrix); +} + +sk_sp SkGradientShader::MakeTwoPointConical(const SkPoint& start, + SkScalar startRadius, + const SkPoint& end, + SkScalar endRadius, + const SkColor4f colors[], + sk_sp colorSpace, + const SkScalar pos[], + int colorCount, + SkShader::TileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + if (startRadius < 0 || endRadius < 0) { + return nullptr; + } + if (!valid_grad(colors, pos, colorCount, mode)) { + return nullptr; + } + if (startRadius == endRadius) { + if (start == end || startRadius == 0) { + return SkShader::MakeEmptyShader(); + } + } + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + EXPAND_1_COLOR(colorCount); + + ColorStopOptimizer opt(colors, pos, colorCount, mode); + + bool flipGradient = startRadius > endRadius; + + SkGradientShaderBase::Descriptor desc; + + if (!flipGradient) { + desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags, + localMatrix); + return sk_make_sp(start, startRadius, end, endRadius, + flipGradient, desc); + } else { + SkAutoSTArray<8, SkColor4f> colorsNew(opt.fCount); + SkAutoSTArray<8, SkScalar> posNew(opt.fCount); + for (int i = 0; i < opt.fCount; ++i) { + colorsNew[i] = opt.fColors[opt.fCount - i - 1]; + } + + if (pos) { + for (int i = 0; i < opt.fCount; ++i) { + posNew[i] = 1 - opt.fPos[opt.fCount - i - 1]; + } + desc_init(&desc, colorsNew.get(), std::move(colorSpace), posNew.get(), opt.fCount, mode, + flags, localMatrix); + } else { + desc_init(&desc, colorsNew.get(), std::move(colorSpace), nullptr, opt.fCount, mode, + flags, localMatrix); + } + + return sk_make_sp(end, endRadius, start, startRadius, + flipGradient, desc); + } +} + +sk_sp SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy, + const SkColor colors[], + const SkScalar pos[], + int colorCount, + uint32_t flags, + const SkMatrix* localMatrix) { + ColorConverter converter(colors, colorCount); + return MakeSweep(cx, cy, converter.fColors4f.begin(), nullptr, pos, colorCount, flags, + localMatrix); +} + +sk_sp SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy, + const SkColor4f colors[], + sk_sp colorSpace, + const SkScalar pos[], + int colorCount, + uint32_t flags, + const SkMatrix* localMatrix) { + if (!valid_grad(colors, pos, colorCount, SkShader::kClamp_TileMode)) { + return nullptr; + } + if (1 == colorCount) { + return SkShader::MakeColorShader(colors[0], std::move(colorSpace)); + } + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + + auto mode = SkShader::kClamp_TileMode; + + ColorStopOptimizer opt(colors, pos, colorCount, mode); + + SkGradientShaderBase::Descriptor desc; + desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags, + localMatrix); + return sk_make_sp(cx, cy, desc); +} + +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader) + SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLinearGradient) + SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkRadialGradient) + SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSweepGradient) + SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkTwoPointConicalGradient) +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END + +/////////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "GrContext.h" +#include "GrShaderCaps.h" +#include "GrTextureStripAtlas.h" +#include "gl/GrGLContext.h" +#include "glsl/GrGLSLColorSpaceXformHelper.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLProgramDataManager.h" +#include "glsl/GrGLSLUniformHandler.h" +#include "SkGr.h" + +static inline bool close_to_one_half(const SkFixed& val) { + return SkScalarNearlyEqual(SkFixedToScalar(val), SK_ScalarHalf); +} + +static inline int color_type_to_color_count(GrGradientEffect::ColorType colorType) { + switch (colorType) { +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + case GrGradientEffect::kSingleHardStop_ColorType: + return 4; + case GrGradientEffect::kHardStopLeftEdged_ColorType: + case GrGradientEffect::kHardStopRightEdged_ColorType: + return 3; +#endif + case GrGradientEffect::kTwo_ColorType: + return 2; + case GrGradientEffect::kThree_ColorType: + return 3; + case GrGradientEffect::kTexture_ColorType: + return 0; + } + + SkDEBUGFAIL("Unhandled ColorType in color_type_to_color_count()"); + return -1; +} + +GrGradientEffect::ColorType GrGradientEffect::determineColorType( + const SkGradientShaderBase& shader) { +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + if (shader.fOrigPos) { + if (4 == shader.fColorCount) { + if (SkScalarNearlyEqual(shader.fOrigPos[0], 0.0f) && + SkScalarNearlyEqual(shader.fOrigPos[1], shader.fOrigPos[2]) && + SkScalarNearlyEqual(shader.fOrigPos[3], 1.0f)) { + + return kSingleHardStop_ColorType; + } + } else if (3 == shader.fColorCount) { + if (SkScalarNearlyEqual(shader.fOrigPos[0], 0.0f) && + SkScalarNearlyEqual(shader.fOrigPos[1], 0.0f) && + SkScalarNearlyEqual(shader.fOrigPos[2], 1.0f)) { + + return kHardStopLeftEdged_ColorType; + } else if (SkScalarNearlyEqual(shader.fOrigPos[0], 0.0f) && + SkScalarNearlyEqual(shader.fOrigPos[1], 1.0f) && + SkScalarNearlyEqual(shader.fOrigPos[2], 1.0f)) { + + return kHardStopRightEdged_ColorType; + } + } + } +#endif + + if (SkShader::kClamp_TileMode == shader.getTileMode()) { + if (2 == shader.fColorCount) { + return kTwo_ColorType; + } else if (3 == shader.fColorCount && + close_to_one_half(shader.getRecs()[1].fPos)) { + return kThree_ColorType; + } + } + + return kTexture_ColorType; +} + +void GrGradientEffect::GLSLProcessor::emitUniforms(GrGLSLUniformHandler* uniformHandler, + const GrGradientEffect& ge) { + if (int colorCount = color_type_to_color_count(ge.getColorType())) { + fColorsUni = uniformHandler->addUniformArray(kFragment_GrShaderFlag, + kVec4f_GrSLType, + kDefault_GrSLPrecision, + "Colors", + colorCount); + if (ge.fColorType == kSingleHardStop_ColorType) { + fHardStopT = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat_GrSLType, + kDefault_GrSLPrecision, "HardStopT"); + } + } else { + fFSYUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat_GrSLType, kDefault_GrSLPrecision, + "GradientYCoordFS"); + } +} + +static inline void set_after_interp_color_uni_array( + const GrGLSLProgramDataManager& pdman, + const GrGLSLProgramDataManager::UniformHandle uni, + const SkTDArray& colors, + const GrColorSpaceXform* colorSpaceXform) { + int count = colors.count(); + if (colorSpaceXform) { + constexpr int kSmallCount = 10; + SkAutoSTArray<4 * kSmallCount, float> vals(4 * count); + + for (int i = 0; i < count; i++) { + colorSpaceXform->srcToDst().mapScalars(colors[i].vec(), &vals[4 * i]); + } + + pdman.set4fv(uni, count, vals.get()); + } else { + pdman.set4fv(uni, count, (float*)&colors[0]); + } +} + +static inline void set_before_interp_color_uni_array( + const GrGLSLProgramDataManager& pdman, + const GrGLSLProgramDataManager::UniformHandle uni, + const SkTDArray& colors, + const GrColorSpaceXform* colorSpaceXform) { + int count = colors.count(); + constexpr int kSmallCount = 10; + SkAutoSTArray<4 * kSmallCount, float> vals(4 * count); + + for (int i = 0; i < count; i++) { + float a = colors[i].fA; + vals[4 * i + 0] = colors[i].fR * a; + vals[4 * i + 1] = colors[i].fG * a; + vals[4 * i + 2] = colors[i].fB * a; + vals[4 * i + 3] = a; + } + + if (colorSpaceXform) { + for (int i = 0; i < count; i++) { + colorSpaceXform->srcToDst().mapScalars(&vals[4 * i]); + } + } + + pdman.set4fv(uni, count, vals.get()); +} + +static inline void set_after_interp_color_uni_array(const GrGLSLProgramDataManager& pdman, + const GrGLSLProgramDataManager::UniformHandle uni, + const SkTDArray& colors) { + int count = colors.count(); + constexpr int kSmallCount = 10; + + SkAutoSTArray<4*kSmallCount, float> vals(4*count); + + for (int i = 0; i < colors.count(); i++) { + // RGBA + vals[4*i + 0] = SkColorGetR(colors[i]) / 255.f; + vals[4*i + 1] = SkColorGetG(colors[i]) / 255.f; + vals[4*i + 2] = SkColorGetB(colors[i]) / 255.f; + vals[4*i + 3] = SkColorGetA(colors[i]) / 255.f; + } + + pdman.set4fv(uni, colors.count(), vals.get()); +} + +static inline void set_before_interp_color_uni_array(const GrGLSLProgramDataManager& pdman, + const GrGLSLProgramDataManager::UniformHandle uni, + const SkTDArray& colors) { + int count = colors.count(); + constexpr int kSmallCount = 10; + + SkAutoSTArray<4*kSmallCount, float> vals(4*count); + + for (int i = 0; i < count; i++) { + float a = SkColorGetA(colors[i]) / 255.f; + float aDiv255 = a / 255.f; + + // RGBA + vals[4*i + 0] = SkColorGetR(colors[i]) * aDiv255; + vals[4*i + 1] = SkColorGetG(colors[i]) * aDiv255; + vals[4*i + 2] = SkColorGetB(colors[i]) * aDiv255; + vals[4*i + 3] = a; + } + + pdman.set4fv(uni, count, vals.get()); +} + +void GrGradientEffect::GLSLProcessor::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) { + const GrGradientEffect& e = processor.cast(); + + switch (e.getColorType()) { +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + case GrGradientEffect::kSingleHardStop_ColorType: + pdman.set1f(fHardStopT, e.fPositions[1]); + // fall through + case GrGradientEffect::kHardStopLeftEdged_ColorType: + case GrGradientEffect::kHardStopRightEdged_ColorType: +#endif + case GrGradientEffect::kTwo_ColorType: + case GrGradientEffect::kThree_ColorType: { + if (e.fColors4f.count() > 0) { + // Gamma-correct / color-space aware + if (GrGradientEffect::kBeforeInterp_PremulType == e.getPremulType()) { + set_before_interp_color_uni_array(pdman, fColorsUni, e.fColors4f, + e.fColorSpaceXform.get()); + } else { + set_after_interp_color_uni_array(pdman, fColorsUni, e.fColors4f, + e.fColorSpaceXform.get()); + } + } else { + // Legacy mode. Would be nice if we had converted the 8-bit colors to float earlier + if (GrGradientEffect::kBeforeInterp_PremulType == e.getPremulType()) { + set_before_interp_color_uni_array(pdman, fColorsUni, e.fColors); + } else { + set_after_interp_color_uni_array(pdman, fColorsUni, e.fColors); + } + } + + break; + } + + case GrGradientEffect::kTexture_ColorType: { + SkScalar yCoord = e.getYCoord(); + if (yCoord != fCachedYCoord) { + pdman.set1f(fFSYUni, yCoord); + fCachedYCoord = yCoord; + } + if (SkToBool(e.fColorSpaceXform)) { + fColorSpaceHelper.setData(pdman, e.fColorSpaceXform.get()); + } + break; + } + } +} + +uint32_t GrGradientEffect::GLSLProcessor::GenBaseGradientKey(const GrProcessor& processor) { + const GrGradientEffect& e = processor.cast(); + + uint32_t key = 0; + + if (GrGradientEffect::kBeforeInterp_PremulType == e.getPremulType()) { + key |= kPremulBeforeInterpKey; + } + + if (GrGradientEffect::kTwo_ColorType == e.getColorType()) { + key |= kTwoColorKey; + } else if (GrGradientEffect::kThree_ColorType == e.getColorType()) { + key |= kThreeColorKey; + } +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + else if (GrGradientEffect::kSingleHardStop_ColorType == e.getColorType()) { + key |= kHardStopCenteredKey; + } else if (GrGradientEffect::kHardStopLeftEdged_ColorType == e.getColorType()) { + key |= kHardStopZeroZeroOneKey; + } else if (GrGradientEffect::kHardStopRightEdged_ColorType == e.getColorType()) { + key |= kHardStopZeroOneOneKey; + } + + if (SkShader::TileMode::kClamp_TileMode == e.fTileMode) { + key |= kClampTileMode; + } else if (SkShader::TileMode::kRepeat_TileMode == e.fTileMode) { + key |= kRepeatTileMode; + } else { + key |= kMirrorTileMode; + } +#endif + + key |= GrColorSpaceXform::XformKey(e.fColorSpaceXform.get()) << kReservedBits; + + return key; +} + +void GrGradientEffect::GLSLProcessor::emitColor(GrGLSLFPFragmentBuilder* fragBuilder, + GrGLSLUniformHandler* uniformHandler, + const GrShaderCaps* shaderCaps, + const GrGradientEffect& ge, + const char* gradientTValue, + const char* outputColor, + const char* inputColor, + const TextureSamplers& texSamplers) { + switch (ge.getColorType()) { +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + case kSingleHardStop_ColorType: { + const char* t = gradientTValue; + const char* colors = uniformHandler->getUniformCStr(fColorsUni); + const char* stopT = uniformHandler->getUniformCStr(fHardStopT); + + fragBuilder->codeAppendf("float clamp_t = clamp(%s, 0.0, 1.0);", t); + + // Account for tile mode + if (SkShader::kRepeat_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("clamp_t = fract(%s);", t); + } else if (SkShader::kMirror_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("if (%s < 0.0 || %s > 1.0) {", t, t); + fragBuilder->codeAppendf(" if (mod(floor(%s), 2.0) == 0.0) {", t); + fragBuilder->codeAppendf(" clamp_t = fract(%s);", t); + fragBuilder->codeAppendf(" } else {"); + fragBuilder->codeAppendf(" clamp_t = 1.0 - fract(%s);", t); + fragBuilder->codeAppendf(" }"); + fragBuilder->codeAppendf("}"); + } + + // Calculate color + fragBuilder->codeAppend ("vec4 start, end;"); + fragBuilder->codeAppend ("float relative_t;"); + fragBuilder->codeAppendf("if (clamp_t < %s) {", stopT); + fragBuilder->codeAppendf(" start = %s[0];", colors); + fragBuilder->codeAppendf(" end = %s[1];", colors); + fragBuilder->codeAppendf(" relative_t = clamp_t / %s;", stopT); + fragBuilder->codeAppend ("} else {"); + fragBuilder->codeAppendf(" start = %s[2];", colors); + fragBuilder->codeAppendf(" end = %s[3];", colors); + fragBuilder->codeAppendf(" relative_t = (clamp_t - %s) / (1 - %s);", stopT, stopT); + fragBuilder->codeAppend ("}"); + fragBuilder->codeAppend ("vec4 colorTemp = mix(start, end, relative_t);"); + + if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) { + fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;"); + } + if (ge.fColorSpaceXform) { + fragBuilder->codeAppend("colorTemp.rgb = clamp(colorTemp.rgb, 0, colorTemp.a);"); + } + fragBuilder->codeAppendf("%s = %s * colorTemp;", outputColor, inputColor); + + break; + } + + case kHardStopLeftEdged_ColorType: { + const char* t = gradientTValue; + const char* colors = uniformHandler->getUniformCStr(fColorsUni); + + fragBuilder->codeAppendf("float clamp_t = clamp(%s, 0.0, 1.0);", t); + + // Account for tile mode + if (SkShader::kRepeat_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("clamp_t = fract(%s);", t); + } else if (SkShader::kMirror_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("if (%s < 0.0 || %s > 1.0) {", t, t); + fragBuilder->codeAppendf(" if (mod(floor(%s), 2.0) == 0.0) {", t); + fragBuilder->codeAppendf(" clamp_t = fract(%s);", t); + fragBuilder->codeAppendf(" } else {"); + fragBuilder->codeAppendf(" clamp_t = 1.0 - fract(%s);", t); + fragBuilder->codeAppendf(" }"); + fragBuilder->codeAppendf("}"); + } + + fragBuilder->codeAppendf("vec4 colorTemp = mix(%s[1], %s[2], clamp_t);", colors, + colors); + if (SkShader::kClamp_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("if (%s < 0.0) {", t); + fragBuilder->codeAppendf(" colorTemp = %s[0];", colors); + fragBuilder->codeAppendf("}"); + } + + if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) { + fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;"); + } + if (ge.fColorSpaceXform) { + fragBuilder->codeAppend("colorTemp.rgb = clamp(colorTemp.rgb, 0, colorTemp.a);"); + } + fragBuilder->codeAppendf("%s = %s * colorTemp;", outputColor, inputColor); + + break; + } + + case kHardStopRightEdged_ColorType: { + const char* t = gradientTValue; + const char* colors = uniformHandler->getUniformCStr(fColorsUni); + + fragBuilder->codeAppendf("float clamp_t = clamp(%s, 0.0, 1.0);", t); + + // Account for tile mode + if (SkShader::kRepeat_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("clamp_t = fract(%s);", t); + } else if (SkShader::kMirror_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("if (%s < 0.0 || %s > 1.0) {", t, t); + fragBuilder->codeAppendf(" if (mod(floor(%s), 2.0) == 0.0) {", t); + fragBuilder->codeAppendf(" clamp_t = fract(%s);", t); + fragBuilder->codeAppendf(" } else {"); + fragBuilder->codeAppendf(" clamp_t = 1.0 - fract(%s);", t); + fragBuilder->codeAppendf(" }"); + fragBuilder->codeAppendf("}"); + } + + fragBuilder->codeAppendf("vec4 colorTemp = mix(%s[0], %s[1], clamp_t);", colors, + colors); + if (SkShader::kClamp_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("if (%s > 1.0) {", t); + fragBuilder->codeAppendf(" colorTemp = %s[2];", colors); + fragBuilder->codeAppendf("}"); + } + + if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) { + fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;"); + } + if (ge.fColorSpaceXform) { + fragBuilder->codeAppend("colorTemp.rgb = clamp(colorTemp.rgb, 0, colorTemp.a);"); + } + fragBuilder->codeAppendf("%s = %s * colorTemp;", outputColor, inputColor); + + break; + } +#endif + + case kTwo_ColorType: { + const char* t = gradientTValue; + const char* colors = uniformHandler->getUniformCStr(fColorsUni); + + fragBuilder->codeAppendf("vec4 colorTemp = mix(%s[0], %s[1], clamp(%s, 0.0, 1.0));", + colors, colors, t); + + // We could skip this step if both colors are known to be opaque. Two + // considerations: + // The gradient SkShader reporting opaque is more restrictive than necessary in the two + // pt case. Make sure the key reflects this optimization (and note that it can use the + // same shader as thekBeforeIterp case). This same optimization applies to the 3 color + // case below. + if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) { + fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;"); + } + if (ge.fColorSpaceXform) { + fragBuilder->codeAppend("colorTemp.rgb = clamp(colorTemp.rgb, 0, colorTemp.a);"); + } + + fragBuilder->codeAppendf("%s = %s * colorTemp;", outputColor, inputColor); + + break; + } + + case kThree_ColorType: { + const char* t = gradientTValue; + const char* colors = uniformHandler->getUniformCStr(fColorsUni); + + fragBuilder->codeAppendf("float oneMinus2t = 1.0 - (2.0 * %s);", t); + fragBuilder->codeAppendf("vec4 colorTemp = clamp(oneMinus2t, 0.0, 1.0) * %s[0];", + colors); + if (!shaderCaps->canUseMinAndAbsTogether()) { + // The Tegra3 compiler will sometimes never return if we have + // min(abs(oneMinus2t), 1.0), or do the abs first in a separate expression. + fragBuilder->codeAppendf("float minAbs = abs(oneMinus2t);"); + fragBuilder->codeAppendf("minAbs = minAbs > 1.0 ? 1.0 : minAbs;"); + fragBuilder->codeAppendf("colorTemp += (1.0 - minAbs) * %s[1];", colors); + } else { + fragBuilder->codeAppendf("colorTemp += (1.0 - min(abs(oneMinus2t), 1.0)) * %s[1];", + colors); + } + fragBuilder->codeAppendf("colorTemp += clamp(-oneMinus2t, 0.0, 1.0) * %s[2];", colors); + + if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) { + fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;"); + } + if (ge.fColorSpaceXform) { + fragBuilder->codeAppend("colorTemp.rgb = clamp(colorTemp.rgb, 0, colorTemp.a);"); + } + + fragBuilder->codeAppendf("%s = %s * colorTemp;", outputColor, inputColor); + + break; + } + + case kTexture_ColorType: { + fColorSpaceHelper.emitCode(uniformHandler, ge.fColorSpaceXform.get()); + + const char* fsyuni = uniformHandler->getUniformCStr(fFSYUni); + + fragBuilder->codeAppendf("vec2 coord = vec2(%s, %s);", gradientTValue, fsyuni); + fragBuilder->codeAppendf("%s = ", outputColor); + fragBuilder->appendTextureLookupAndModulate(inputColor, texSamplers[0], "coord", + kVec2f_GrSLType, &fColorSpaceHelper); + fragBuilder->codeAppend(";"); + + break; + } + } +} + +///////////////////////////////////////////////////////////////////// + +inline GrFragmentProcessor::OptimizationFlags GrGradientEffect::OptFlags(bool isOpaque) { + return isOpaque + ? kPreservesOpaqueInput_OptimizationFlag | + kCompatibleWithCoverageAsAlpha_OptimizationFlag + : kCompatibleWithCoverageAsAlpha_OptimizationFlag; +} + +GrGradientEffect::GrGradientEffect(const CreateArgs& args, bool isOpaque) + : INHERITED(OptFlags(isOpaque)) { + const SkGradientShaderBase& shader(*args.fShader); + + fIsOpaque = shader.isOpaque(); + + fColorType = this->determineColorType(shader); + fColorSpaceXform = std::move(args.fColorSpaceXform); + + if (kTexture_ColorType != fColorType) { + SkASSERT(shader.fOrigColors && shader.fOrigColors4f); + if (args.fGammaCorrect) { + fColors4f = SkTDArray(shader.fOrigColors4f, shader.fColorCount); + } else { + fColors = SkTDArray(shader.fOrigColors, shader.fColorCount); + } + +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + if (shader.fOrigPos) { + fPositions = SkTDArray(shader.fOrigPos, shader.fColorCount); + } +#endif + } + +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + fTileMode = args.fTileMode; +#endif + + switch (fColorType) { + // The two and three color specializations do not currently support tiling. + case kTwo_ColorType: + case kThree_ColorType: +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + case kHardStopLeftEdged_ColorType: + case kHardStopRightEdged_ColorType: + case kSingleHardStop_ColorType: +#endif + fRow = -1; + + if (SkGradientShader::kInterpolateColorsInPremul_Flag & shader.getGradFlags()) { + fPremulType = kBeforeInterp_PremulType; + } else { + fPremulType = kAfterInterp_PremulType; + } + + fCoordTransform.reset(*args.fMatrix); + + break; + case kTexture_ColorType: + // doesn't matter how this is set, just be consistent because it is part of the + // effect key. + fPremulType = kBeforeInterp_PremulType; + + SkGradientShaderBase::GradientBitmapType bitmapType = + SkGradientShaderBase::GradientBitmapType::kLegacy; + if (args.fGammaCorrect) { + // Try to use F16 if we can + if (args.fContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig)) { + bitmapType = SkGradientShaderBase::GradientBitmapType::kHalfFloat; + } else if (args.fContext->caps()->isConfigTexturable(kSRGBA_8888_GrPixelConfig)) { + bitmapType = SkGradientShaderBase::GradientBitmapType::kSRGB; + } else { + // This can happen, but only if someone explicitly creates an unsupported + // (eg sRGB) surface. Just fall back to legacy behavior. + } + } + + SkBitmap bitmap; + shader.getGradientTableBitmap(&bitmap, bitmapType); + SkASSERT(1 == bitmap.height() && SkIsPow2(bitmap.width())); + + + GrTextureStripAtlas::Desc desc; + desc.fWidth = bitmap.width(); + desc.fHeight = 32; + desc.fRowHeight = bitmap.height(); + desc.fContext = args.fContext; + desc.fConfig = SkImageInfo2GrPixelConfig(bitmap.info(), *args.fContext->caps()); + fAtlas = GrTextureStripAtlas::GetAtlas(desc); + SkASSERT(fAtlas); + + // We always filter the gradient table. Each table is one row of a texture, always + // y-clamp. + GrSamplerParams params; + params.setFilterMode(GrSamplerParams::kBilerp_FilterMode); + params.setTileModeX(args.fTileMode); + + fRow = fAtlas->lockRow(bitmap); + if (-1 != fRow) { + fYCoord = fAtlas->getYOffset(fRow)+SK_ScalarHalf*fAtlas->getNormalizedTexelHeight(); + // This is 1/2 places where auto-normalization is disabled + fCoordTransform.reset(args.fContext->resourceProvider(), *args.fMatrix, + fAtlas->asTextureProxyRef().get(), false); + fTextureSampler.reset(args.fContext->resourceProvider(), + fAtlas->asTextureProxyRef(), params); + } else { + // In this instance we know the params are: + // clampY, bilerp + // and the proxy is: + // exact fit, power of two in both dimensions + // Only the x-tileMode is unknown. However, given all the other knowns we know + // that GrMakeCachedBitmapProxy is sufficient (i.e., it won't need to be + // extracted to a subset or mipmapped). + sk_sp proxy = GrMakeCachedBitmapProxy( + args.fContext->resourceProvider(), + bitmap); + if (!proxy) { + return; + } + // This is 2/2 places where auto-normalization is disabled + fCoordTransform.reset(args.fContext->resourceProvider(), *args.fMatrix, + proxy.get(), false); + fTextureSampler.reset(args.fContext->resourceProvider(), + std::move(proxy), params); + fYCoord = SK_ScalarHalf; + } + + this->addTextureSampler(&fTextureSampler); + + break; + } + + this->addCoordTransform(&fCoordTransform); +} + +GrGradientEffect::~GrGradientEffect() { + if (this->useAtlas()) { + fAtlas->unlockRow(fRow); + } +} + +bool GrGradientEffect::onIsEqual(const GrFragmentProcessor& processor) const { + const GrGradientEffect& ge = processor.cast(); + + if (this->fColorType != ge.getColorType()) { + return false; + } + SkASSERT(this->useAtlas() == ge.useAtlas()); + if (kTexture_ColorType == fColorType) { + if (fYCoord != ge.getYCoord()) { + return false; + } + } else { + if (kSingleHardStop_ColorType == fColorType) { + if (!SkScalarNearlyEqual(ge.fPositions[1], fPositions[1])) { + return false; + } + } + if (this->getPremulType() != ge.getPremulType() || + this->fColors.count() != ge.fColors.count() || + this->fColors4f.count() != ge.fColors4f.count()) { + return false; + } + + for (int i = 0; i < this->fColors.count(); i++) { + if (*this->getColors(i) != *ge.getColors(i)) { + return false; + } + } + for (int i = 0; i < this->fColors4f.count(); i++) { + if (*this->getColors4f(i) != *ge.getColors4f(i)) { + return false; + } + } + } + return GrColorSpaceXform::Equals(this->fColorSpaceXform.get(), ge.fColorSpaceXform.get()); +} + +#if GR_TEST_UTILS +GrGradientEffect::RandomGradientParams::RandomGradientParams(SkRandom* random) { + // Set color count to min of 2 so that we don't trigger the const color optimization and make + // a non-gradient processor. + fColorCount = random->nextRangeU(2, kMaxRandomGradientColors); + fUseColors4f = random->nextBool(); + + // if one color, omit stops, otherwise randomly decide whether or not to + if (fColorCount == 1 || (fColorCount >= 2 && random->nextBool())) { + fStops = nullptr; + } else { + fStops = fStopStorage; + } + + // if using SkColor4f, attach a random (possibly null) color space (with linear gamma) + if (fUseColors4f) { + fColorSpace = GrTest::TestColorSpace(random); + if (fColorSpace) { + SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(fColorSpace)->type()); + fColorSpace = static_cast(fColorSpace.get())->makeLinearGamma(); + } + } + + SkScalar stop = 0.f; + for (int i = 0; i < fColorCount; ++i) { + if (fUseColors4f) { + fColors4f[i].fR = random->nextUScalar1(); + fColors4f[i].fG = random->nextUScalar1(); + fColors4f[i].fB = random->nextUScalar1(); + fColors4f[i].fA = random->nextUScalar1(); + } else { + fColors[i] = random->nextU(); + } + if (fStops) { + fStops[i] = stop; + stop = i < fColorCount - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f; + } + } + fTileMode = static_cast(random->nextULessThan(SkShader::kTileModeCount)); +} +#endif + +#endif diff --git a/src/effects/gradients/SkGradientShaderPriv.h b/src/effects/gradients/SkGradientShaderPriv.h new file mode 100644 index 0000000000..7a66edaffc --- /dev/null +++ b/src/effects/gradients/SkGradientShaderPriv.h @@ -0,0 +1,540 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGradientShaderPriv_DEFINED +#define SkGradientShaderPriv_DEFINED + +#include "SkGradientBitmapCache.h" +#include "SkGradientShader.h" + +#include "SkArenaAlloc.h" +#include "SkAutoMalloc.h" +#include "SkClampRange.h" +#include "SkColorPriv.h" +#include "SkColorSpace.h" +#include "SkOnce.h" +#include "SkPM4fPriv.h" +#include "SkRasterPipeline.h" +#include "SkReadBuffer.h" +#include "SkShaderBase.h" +#include "SkUtils.h" +#include "SkWriteBuffer.h" + +#if SK_SUPPORT_GPU + #define GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS 1 +#endif + +static inline void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1, + int count) { + if (count > 0) { + if (v0 == v1) { + sk_memset32(dst, v0, count); + } else { + int pairs = count >> 1; + for (int i = 0; i < pairs; i++) { + *dst++ = v0; + *dst++ = v1; + } + if (count & 1) { + *dst = v0; + } + } + } +} + +// Clamp + +static inline SkFixed clamp_tileproc(SkFixed x) { + return SkClampMax(x, 0xFFFF); +} + +// Repeat + +static inline SkFixed repeat_tileproc(SkFixed x) { + return x & 0xFFFF; +} + +// Mirror + +static inline SkFixed mirror_tileproc(SkFixed x) { + int s = SkLeftShift(x, 15) >> 31; + return (x ^ s) & 0xFFFF; +} + +/////////////////////////////////////////////////////////////////////////////// + +typedef SkFixed (*TileProc)(SkFixed); + +/////////////////////////////////////////////////////////////////////////////// + +static const TileProc gTileProcs[] = { + clamp_tileproc, + repeat_tileproc, + mirror_tileproc +}; + +/////////////////////////////////////////////////////////////////////////////// + +class SkGradientShaderBase : public SkShaderBase { +public: + struct Descriptor { + Descriptor() { + sk_bzero(this, sizeof(*this)); + fTileMode = SkShader::kClamp_TileMode; + } + + const SkMatrix* fLocalMatrix; + const SkColor4f* fColors; + sk_sp fColorSpace; + const SkScalar* fPos; + int fCount; + SkShader::TileMode fTileMode; + uint32_t fGradFlags; + + void flatten(SkWriteBuffer&) const; + }; + + class DescriptorScope : public Descriptor { + public: + DescriptorScope() {} + + bool unflatten(SkReadBuffer&); + + // fColors and fPos always point into local memory, so they can be safely mutated + // + SkColor4f* mutableColors() { return const_cast(fColors); } + SkScalar* mutablePos() { return const_cast(fPos); } + + private: + enum { + kStorageCount = 16 + }; + SkColor4f fColorStorage[kStorageCount]; + SkScalar fPosStorage[kStorageCount]; + SkMatrix fLocalMatrixStorage; + SkAutoMalloc fDynamicStorage; + }; + + SkGradientShaderBase(const Descriptor& desc, const SkMatrix& ptsToUnit); + ~SkGradientShaderBase() override; + + // The cache is initialized on-demand when getCache32 is called. + class GradientShaderCache : public SkRefCnt { + public: + GradientShaderCache(U8CPU alpha, bool dither, const SkGradientShaderBase& shader); + ~GradientShaderCache(); + + const SkPMColor* getCache32(); + + SkPixelRef* getCache32PixelRef() const { return fCache32PixelRef.get(); } + + unsigned getAlpha() const { return fCacheAlpha; } + bool getDither() const { return fCacheDither; } + + private: + // Working pointer. If it's nullptr, we need to recompute the cache values. + SkPMColor* fCache32; + + sk_sp fCache32PixelRef; + const unsigned fCacheAlpha; // The alpha value we used when we computed the cache. + // Larger than 8bits so we can store uninitialized + // value. + const bool fCacheDither; // The dither flag used when we computed the cache. + + const SkGradientShaderBase& fShader; + + // Make sure we only initialize the cache once. + SkOnce fCache32InitOnce; + + static void initCache32(GradientShaderCache* cache); + + static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count, + U8CPU alpha, uint32_t gradFlags, bool dither); + }; + + class GradientShaderBaseContext : public Context { + public: + GradientShaderBaseContext(const SkGradientShaderBase& shader, const ContextRec&); + + uint32_t getFlags() const override { return fFlags; } + + bool isValid() const; + + protected: + SkMatrix fDstToIndex; + SkMatrix::MapXYProc fDstToIndexProc; + uint8_t fDstToIndexClass; + uint8_t fFlags; + bool fDither; + + sk_sp fCache; + + private: + typedef Context INHERITED; + }; + + bool isOpaque() const override; + + enum class GradientBitmapType : uint8_t { + kLegacy, + kSRGB, + kHalfFloat, + }; + + void getGradientTableBitmap(SkBitmap*, GradientBitmapType bitmapType) const; + + enum { + /// Seems like enough for visual accuracy. TODO: if pos[] deserves + /// it, use a larger cache. + kCache32Bits = 8, + kCache32Count = (1 << kCache32Bits), + kCache32Shift = 16 - kCache32Bits, + kSqrt32Shift = 8 - kCache32Bits, + + /// This value is used to *read* the dither cache; it may be 0 + /// if dithering is disabled. + kDitherStride32 = kCache32Count, + }; + + uint32_t getGradFlags() const { return fGradFlags; } + +protected: + struct Rec { + SkFixed fPos; // 0...1 + uint32_t fScale; // (1 << 24) / range + }; + + class GradientShaderBase4fContext; + + SkGradientShaderBase(SkReadBuffer& ); + void flatten(SkWriteBuffer&) const override; + SK_TO_STRING_OVERRIDE() + + void commonAsAGradient(GradientInfo*, bool flipGrad = false) const; + + bool onAsLuminanceColor(SkColor*) const override; + + void initLinearBitmap(SkBitmap* bitmap) const; + + /* + * Takes in pointers to gradient color and Rec info as colorSrc and recSrc respectively. + * Count is the number of colors in the gradient + * It will then flip all the color and rec information and return in their respective Dst + * pointers. It is assumed that space has already been allocated for the Dst pointers. + * The rec src and dst are only assumed to be valid if count > 2 + */ + static void FlipGradientColors(SkColor* colorDst, Rec* recDst, + SkColor* colorSrc, Rec* recSrc, + int count); + + bool onAppendStages(SkRasterPipeline* pipeline, SkColorSpace* dstCS, SkArenaAlloc* alloc, + const SkMatrix& ctm, const SkPaint& paint, + const SkMatrix* localM) const override; + + virtual bool adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const { return false; } + + template + static Context* CheckedMakeContext(SkArenaAlloc* alloc, Args&&... args) { + auto* ctx = alloc->make(std::forward(args)...); + if (!ctx->isValid()) { + return nullptr; + } + return ctx; + } + + const SkMatrix fPtsToUnit; + TileMode fTileMode; + TileProc fTileProc; + uint8_t fGradFlags; + Rec* fRecs; + +private: + enum { + kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space + + kStorageSize = kColorStorageCount * + (sizeof(SkColor) + sizeof(SkScalar) + sizeof(Rec) + sizeof(SkColor4f)) + }; + SkColor fStorage[(kStorageSize + 3) >> 2]; +public: + SkColor* fOrigColors; // original colors, before modulation by paint in context. + SkColor4f* fOrigColors4f; // original colors, as linear floats + SkScalar* fOrigPos; // original positions + int fColorCount; + sk_sp fColorSpace; // color space of gradient stops + + bool colorsAreOpaque() const { return fColorsAreOpaque; } + + TileMode getTileMode() const { return fTileMode; } + Rec* getRecs() const { return fRecs; } + +private: + bool fColorsAreOpaque; + + sk_sp refCache(U8CPU alpha, bool dither) const; + mutable SkMutex fCacheMutex; + mutable sk_sp fCache; + + void initCommon(); + + typedef SkShaderBase INHERITED; +}; + + +static inline int init_dither_toggle(int x, int y) { + x &= 1; + y = (y & 1) << 1; + return (x | y) * SkGradientShaderBase::kDitherStride32; +} + +static inline int next_dither_toggle(int toggle) { + return toggle ^ SkGradientShaderBase::kDitherStride32; +} + +/////////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "GrColorSpaceXform.h" +#include "GrCoordTransform.h" +#include "GrFragmentProcessor.h" +#include "glsl/GrGLSLColorSpaceXformHelper.h" +#include "glsl/GrGLSLFragmentProcessor.h" +#include "glsl/GrGLSLProgramDataManager.h" + +class GrInvariantOutput; + +/* + * The interpretation of the texture matrix depends on the sample mode. The + * texture matrix is applied both when the texture coordinates are explicit + * and when vertex positions are used as texture coordinates. In the latter + * case the texture matrix is applied to the pre-view-matrix position + * values. + * + * Normal SampleMode + * The post-matrix texture coordinates are in normalize space with (0,0) at + * the top-left and (1,1) at the bottom right. + * RadialGradient + * The matrix specifies the radial gradient parameters. + * (0,0) in the post-matrix space is center of the radial gradient. + * Radial2Gradient + * Matrix transforms to space where first circle is centered at the + * origin. The second circle will be centered (x, 0) where x may be + * 0 and is provided by setRadial2Params. The post-matrix space is + * normalized such that 1 is the second radius - first radius. + * SweepGradient + * The angle from the origin of texture coordinates in post-matrix space + * determines the gradient value. + */ + + class GrTextureStripAtlas; + +// Base class for Gr gradient effects +class GrGradientEffect : public GrFragmentProcessor { +public: + struct CreateArgs { + CreateArgs(GrContext* context, + const SkGradientShaderBase* shader, + const SkMatrix* matrix, + SkShader::TileMode tileMode, + sk_sp colorSpaceXform, + bool gammaCorrect) + : fContext(context) + , fShader(shader) + , fMatrix(matrix) + , fTileMode(tileMode) + , fColorSpaceXform(std::move(colorSpaceXform)) + , fGammaCorrect(gammaCorrect) {} + + GrContext* fContext; + const SkGradientShaderBase* fShader; + const SkMatrix* fMatrix; + SkShader::TileMode fTileMode; + sk_sp fColorSpaceXform; + bool fGammaCorrect; + }; + + class GLSLProcessor; + + ~GrGradientEffect() override; + + bool useAtlas() const { return SkToBool(-1 != fRow); } + SkScalar getYCoord() const { return fYCoord; } + + enum ColorType { + kTwo_ColorType, + kThree_ColorType, // Symmetric three color + kTexture_ColorType, + +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + kSingleHardStop_ColorType, // 0, t, t, 1 + kHardStopLeftEdged_ColorType, // 0, 0, 1 + kHardStopRightEdged_ColorType, // 0, 1, 1 +#endif + }; + + ColorType getColorType() const { return fColorType; } + + // Determines the type of gradient, one of: + // - Two-color + // - Symmetric three-color + // - Texture + // - Centered hard stop + // - Left-edged hard stop + // - Right-edged hard stop + ColorType determineColorType(const SkGradientShaderBase& shader); + + enum PremulType { + kBeforeInterp_PremulType, + kAfterInterp_PremulType, + }; + + PremulType getPremulType() const { return fPremulType; } + + const SkColor* getColors(int pos) const { + SkASSERT(fColorType != kTexture_ColorType); + SkASSERT(pos < fColors.count()); + return &fColors[pos]; + } + + const SkColor4f* getColors4f(int pos) const { + SkASSERT(fColorType != kTexture_ColorType); + SkASSERT(pos < fColors4f.count()); + return &fColors4f[pos]; + } + +protected: + GrGradientEffect(const CreateArgs&, bool isOpaque); + + #if GR_TEST_UTILS + /** Helper struct that stores (and populates) parameters to construct a random gradient. + If fUseColors4f is true, then the SkColor4f factory should be called, with fColors4f and + fColorSpace. Otherwise, the SkColor factory should be called, with fColors. fColorCount + will be the number of color stops in either case, and fColors and fStops can be passed to + the gradient factory. (The constructor may decide not to use stops, in which case fStops + will be nullptr). */ + struct RandomGradientParams { + static const int kMaxRandomGradientColors = 5; + + RandomGradientParams(SkRandom* r); + + bool fUseColors4f; + SkColor fColors[kMaxRandomGradientColors]; + SkColor4f fColors4f[kMaxRandomGradientColors]; + sk_sp fColorSpace; + SkScalar fStopStorage[kMaxRandomGradientColors]; + SkShader::TileMode fTileMode; + int fColorCount; + SkScalar* fStops; + }; + #endif + + bool onIsEqual(const GrFragmentProcessor&) const override; + + const GrCoordTransform& getCoordTransform() const { return fCoordTransform; } + +private: + static OptimizationFlags OptFlags(bool isOpaque); + + // If we're in legacy mode, then fColors will be populated. If we're gamma-correct, then + // fColors4f and fColorSpaceXform will be populated. + SkTDArray fColors; + + SkTDArray fColors4f; + sk_sp fColorSpaceXform; + + SkTDArray fPositions; + SkShader::TileMode fTileMode; + + GrCoordTransform fCoordTransform; + TextureSampler fTextureSampler; + SkScalar fYCoord; + GrTextureStripAtlas* fAtlas; + int fRow; + bool fIsOpaque; + ColorType fColorType; + PremulType fPremulType; // This is already baked into the table for texture gradients, and + // only changes behavior for gradients that don't use a texture. + typedef GrFragmentProcessor INHERITED; + +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Base class for GL gradient effects +class GrGradientEffect::GLSLProcessor : public GrGLSLFragmentProcessor { +public: + GLSLProcessor() { + fCachedYCoord = SK_ScalarMax; + } + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +protected: + /** + * Subclasses must call this. It will return a key for the part of the shader code controlled + * by the base class. The subclasses must stick it in their key and then pass it to the below + * emit* functions from their emitCode function. + */ + static uint32_t GenBaseGradientKey(const GrProcessor&); + + // Emits the uniform used as the y-coord to texture samples in derived classes. Subclasses + // should call this method from their emitCode(). + void emitUniforms(GrGLSLUniformHandler*, const GrGradientEffect&); + + // Emit code that gets a fragment's color from an expression for t; has branches for + // several control flows inside -- 2-color gradients, 3-color symmetric gradients, 4+ + // color gradients that use the traditional texture lookup, as well as several varieties + // of hard stop gradients + void emitColor(GrGLSLFPFragmentBuilder* fragBuilder, + GrGLSLUniformHandler* uniformHandler, + const GrShaderCaps* shaderCaps, + const GrGradientEffect&, + const char* gradientTValue, + const char* outputColor, + const char* inputColor, + const TextureSamplers&); + +private: + enum { + // First bit for premul before/after interp + kPremulBeforeInterpKey = 1, + + // Next three bits for 2/3 color type or different special + // hard stop cases (neither means using texture atlas) + kTwoColorKey = 2, + kThreeColorKey = 4, +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + kHardStopCenteredKey = 6, + kHardStopZeroZeroOneKey = 8, + kHardStopZeroOneOneKey = 10, + + // Next two bits for tile mode + kClampTileMode = 16, + kRepeatTileMode = 32, + kMirrorTileMode = 48, + + // Lower six bits for premul, 2/3 color type, and tile mode + kReservedBits = 6, +#endif + }; + + SkScalar fCachedYCoord; + GrGLSLProgramDataManager::UniformHandle fColorsUni; + GrGLSLProgramDataManager::UniformHandle fHardStopT; + GrGLSLProgramDataManager::UniformHandle fFSYUni; + GrGLSLColorSpaceXformHelper fColorSpaceHelper; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +#endif + +#endif diff --git a/src/effects/gradients/SkLinearGradient.cpp b/src/effects/gradients/SkLinearGradient.cpp new file mode 100644 index 0000000000..17c4fd36a4 --- /dev/null +++ b/src/effects/gradients/SkLinearGradient.cpp @@ -0,0 +1,804 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "Sk4fLinearGradient.h" +#include "SkColorSpaceXformer.h" +#include "SkLinearGradient.h" +#include "SkRefCnt.h" + +// define to test the 4f gradient path +// #define FORCE_4F_CONTEXT + +static const float kInv255Float = 1.0f / 255; + +static inline int repeat_8bits(int x) { + return x & 0xFF; +} + +static inline int mirror_8bits(int x) { + if (x & 256) { + x = ~x; + } + return x & 255; +} + +static SkMatrix pts_to_unit_matrix(const SkPoint pts[2]) { + SkVector vec = pts[1] - pts[0]; + SkScalar mag = vec.length(); + SkScalar inv = mag ? SkScalarInvert(mag) : 0; + + vec.scale(inv); + SkMatrix matrix; + matrix.setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY); + matrix.postTranslate(-pts[0].fX, -pts[0].fY); + matrix.postScale(inv, inv); + return matrix; +} + +static bool use_4f_context(const SkShaderBase::ContextRec& rec, uint32_t flags) { +#ifdef FORCE_4F_CONTEXT + return true; +#else + return rec.fPreferredDstType == SkShaderBase::ContextRec::kPM4f_DstType + || SkToBool(flags & SkLinearGradient::kForce4fContext_PrivateFlag); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// + +SkLinearGradient::SkLinearGradient(const SkPoint pts[2], const Descriptor& desc) + : SkGradientShaderBase(desc, pts_to_unit_matrix(pts)) + , fStart(pts[0]) + , fEnd(pts[1]) { +} + +sk_sp SkLinearGradient::CreateProc(SkReadBuffer& buffer) { + DescriptorScope desc; + if (!desc.unflatten(buffer)) { + return nullptr; + } + SkPoint pts[2]; + pts[0] = buffer.readPoint(); + pts[1] = buffer.readPoint(); + return SkGradientShader::MakeLinear(pts, desc.fColors, std::move(desc.fColorSpace), desc.fPos, + desc.fCount, desc.fTileMode, desc.fGradFlags, + desc.fLocalMatrix); +} + +void SkLinearGradient::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fStart); + buffer.writePoint(fEnd); +} + +SkShaderBase::Context* SkLinearGradient::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const +{ + return use_4f_context(rec, fGradFlags) + ? CheckedMakeContext(alloc, *this, rec) + : CheckedMakeContext< LinearGradientContext>(alloc, *this, rec); +} + +bool SkLinearGradient::adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const { + *matrix = SkMatrix::Concat(fPtsToUnit, *matrix); + // If the gradient is less than a quarter of a pixel, this falls into the + // subpixel gradient code handled on a different path. + SkVector dx = matrix->mapVector(1, 0); + if (dx.fX >= 4) { + return false; + } + return true; +} + +sk_sp SkLinearGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + SkPoint pts[2] = { fStart, fEnd }; + SkSTArray<8, SkColor> xformedColors(fColorCount); + xformer->apply(xformedColors.begin(), fOrigColors, fColorCount); + return SkGradientShader::MakeLinear(pts, xformedColors.begin(), fOrigPos, fColorCount, + fTileMode, fGradFlags, &this->getLocalMatrix()); +} + +// This swizzles SkColor into the same component order as SkPMColor, but does not actually +// "pre" multiply the color components. +// +// This allows us to map directly to Sk4f, and eventually scale down to bytes to output a +// SkPMColor from the floats, without having to swizzle each time. +// +static uint32_t SkSwizzle_Color_to_PMColor(SkColor c) { + return SkPackARGB32NoCheck(SkColorGetA(c), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c)); +} + +SkLinearGradient::LinearGradientContext::LinearGradientContext( + const SkLinearGradient& shader, const ContextRec& ctx) + : INHERITED(shader, ctx) +{ + // setup for Sk4f + const int count = shader.fColorCount; + SkASSERT(count > 1); + + fRecs.setCount(count); + Rec* rec = fRecs.begin(); + if (shader.fOrigPos) { + rec[0].fPos = 0; + SkDEBUGCODE(rec[0].fPosScale = SK_FloatNaN;) // should never get used + for (int i = 1; i < count; ++i) { + rec[i].fPos = SkTPin(shader.fOrigPos[i], rec[i - 1].fPos, 1.0f); + float diff = rec[i].fPos - rec[i - 1].fPos; + if (diff > 0) { + rec[i].fPosScale = 1.0f / diff; + } else { + rec[i].fPosScale = 0; + } + } + } else { + // no pos specified, so we compute evenly spaced values + const float scale = float(count - 1); + const float invScale = 1.0f / scale; + for (int i = 0; i < count; ++i) { + rec[i].fPos = i * invScale; + rec[i].fPosScale = scale; + } + } + rec[count - 1].fPos = 1; // overwrite the last value just to be sure we end at 1.0 + + fApplyAlphaAfterInterp = true; + if ((shader.getGradFlags() & SkGradientShader::kInterpolateColorsInPremul_Flag) || + shader.colorsAreOpaque()) + { + fApplyAlphaAfterInterp = false; + } + + if (fApplyAlphaAfterInterp) { + // Our fColor values are in PMColor order, but are still unpremultiplied, allowing us to + // interpolate in unpremultiplied space first, and then scale by alpha right before we + // convert to SkPMColor bytes. + const float paintAlpha = ctx.fPaint->getAlpha() * kInv255Float; + const Sk4f scale(1, 1, 1, paintAlpha); + for (int i = 0; i < count; ++i) { + uint32_t c = SkSwizzle_Color_to_PMColor(shader.fOrigColors[i]); + rec[i].fColor = SkNx_cast(Sk4b::Load(&c)) * scale; + if (i > 0) { + SkASSERT(rec[i - 1].fPos <= rec[i].fPos); + } + } + } else { + // Our fColor values are premultiplied, so converting to SkPMColor is just a matter + // of converting the floats down to bytes. + unsigned alphaScale = ctx.fPaint->getAlpha() + (ctx.fPaint->getAlpha() >> 7); + for (int i = 0; i < count; ++i) { + SkPMColor pmc = SkPreMultiplyColor(shader.fOrigColors[i]); + pmc = SkAlphaMulQ(pmc, alphaScale); + rec[i].fColor = SkNx_cast(Sk4b::Load(&pmc)); + if (i > 0) { + SkASSERT(rec[i - 1].fPos <= rec[i].fPos); + } + } + } +} + +#define NO_CHECK_ITER \ + do { \ + unsigned fi = SkGradFixedToFixed(fx) >> SkGradientShaderBase::kCache32Shift; \ + SkASSERT(fi <= 0xFF); \ + fx += dx; \ + *dstC++ = cache[toggle + fi]; \ + toggle = next_dither_toggle(toggle); \ + } while (0) + +namespace { + +typedef void (*LinearShadeProc)(TileProc proc, SkGradFixed dx, SkGradFixed fx, + SkPMColor* dstC, const SkPMColor* cache, + int toggle, int count); + +// Linear interpolation (lerp) is unnecessary if there are no sharp +// discontinuities in the gradient - which must be true if there are +// only 2 colors - but it's cheap. +void shadeSpan_linear_vertical_lerp(TileProc proc, SkGradFixed dx, SkGradFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { + // We're a vertical gradient, so no change in a span. + // If colors change sharply across the gradient, dithering is + // insufficient (it subsamples the color space) and we need to lerp. + unsigned fullIndex = proc(SkGradFixedToFixed(fx)); + unsigned fi = fullIndex >> SkGradientShaderBase::kCache32Shift; + unsigned remainder = fullIndex & ((1 << SkGradientShaderBase::kCache32Shift) - 1); + + int index0 = fi + toggle; + int index1 = index0; + if (fi < SkGradientShaderBase::kCache32Count - 1) { + index1 += 1; + } + SkPMColor lerp = SkFastFourByteInterp(cache[index1], cache[index0], remainder); + index0 ^= SkGradientShaderBase::kDitherStride32; + index1 ^= SkGradientShaderBase::kDitherStride32; + SkPMColor dlerp = SkFastFourByteInterp(cache[index1], cache[index0], remainder); + sk_memset32_dither(dstC, lerp, dlerp, count); +} + +void shadeSpan_linear_clamp(TileProc proc, SkGradFixed dx, SkGradFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { + SkClampRange range; + range.init(fx, dx, count, 0, SkGradientShaderBase::kCache32Count - 1); + range.validate(count); + + if ((count = range.fCount0) > 0) { + sk_memset32_dither(dstC, + cache[toggle + range.fV0], + cache[next_dither_toggle(toggle) + range.fV0], + count); + dstC += count; + } + if ((count = range.fCount1) > 0) { + int unroll = count >> 3; + fx = range.fFx1; + for (int i = 0; i < unroll; i++) { + NO_CHECK_ITER; NO_CHECK_ITER; + NO_CHECK_ITER; NO_CHECK_ITER; + NO_CHECK_ITER; NO_CHECK_ITER; + NO_CHECK_ITER; NO_CHECK_ITER; + } + if ((count &= 7) > 0) { + do { + NO_CHECK_ITER; + } while (--count != 0); + } + } + if ((count = range.fCount2) > 0) { + sk_memset32_dither(dstC, + cache[toggle + range.fV1], + cache[next_dither_toggle(toggle) + range.fV1], + count); + } +} + +void shadeSpan_linear_mirror(TileProc proc, SkGradFixed dx, SkGradFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { + do { + unsigned fi = mirror_8bits(SkGradFixedToFixed(fx) >> 8); + SkASSERT(fi <= 0xFF); + fx += dx; + *dstC++ = cache[toggle + fi]; + toggle = next_dither_toggle(toggle); + } while (--count != 0); +} + +void shadeSpan_linear_repeat(TileProc proc, SkGradFixed dx, SkGradFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { + do { + unsigned fi = repeat_8bits(SkGradFixedToFixed(fx) >> 8); + SkASSERT(fi <= 0xFF); + fx += dx; + *dstC++ = cache[toggle + fi]; + toggle = next_dither_toggle(toggle); + } while (--count != 0); +} + +} + +void SkLinearGradient::LinearGradientContext::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, + int count) { + SkASSERT(count > 0); + const SkLinearGradient& linearGradient = static_cast(fShader); + + if (SkShader::kClamp_TileMode == linearGradient.fTileMode && + kLinear_MatrixClass == fDstToIndexClass) + { + this->shade4_clamp(x, y, dstC, count); + return; + } + + SkPoint srcPt; + SkMatrix::MapXYProc dstProc = fDstToIndexProc; + TileProc proc = linearGradient.fTileProc; + const SkPMColor* SK_RESTRICT cache = fCache->getCache32(); + int toggle = init_dither_toggle(x, y); + + if (fDstToIndexClass != kPerspective_MatrixClass) { + dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, + SkIntToScalar(y) + SK_ScalarHalf, &srcPt); + SkGradFixed dx, fx = SkScalarPinToGradFixed(srcPt.fX); + + if (fDstToIndexClass == kFixedStepInX_MatrixClass) { + const auto step = fDstToIndex.fixedStepInX(SkIntToScalar(y)); + // todo: do we need a real/high-precision value for dx here? + dx = SkScalarPinToGradFixed(step.fX); + } else { + SkASSERT(fDstToIndexClass == kLinear_MatrixClass); + dx = SkScalarPinToGradFixed(fDstToIndex.getScaleX()); + } + + LinearShadeProc shadeProc = shadeSpan_linear_repeat; + if (0 == dx) { + shadeProc = shadeSpan_linear_vertical_lerp; + } else if (SkShader::kClamp_TileMode == linearGradient.fTileMode) { + shadeProc = shadeSpan_linear_clamp; + } else if (SkShader::kMirror_TileMode == linearGradient.fTileMode) { + shadeProc = shadeSpan_linear_mirror; + } else { + SkASSERT(SkShader::kRepeat_TileMode == linearGradient.fTileMode); + } + (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count); + } else { + SkScalar dstX = SkIntToScalar(x); + SkScalar dstY = SkIntToScalar(y); + do { + dstProc(fDstToIndex, dstX, dstY, &srcPt); + unsigned fi = proc(SkScalarToFixed(srcPt.fX)); + SkASSERT(fi <= 0xFFFF); + *dstC++ = cache[toggle + (fi >> kCache32Shift)]; + toggle = next_dither_toggle(toggle); + dstX += SK_Scalar1; + } while (--count != 0); + } +} + +SkShader::GradientType SkLinearGradient::asAGradient(GradientInfo* info) const { + if (info) { + commonAsAGradient(info); + info->fPoint[0] = fStart; + info->fPoint[1] = fEnd; + } + return kLinear_GradientType; +} + +#if SK_SUPPORT_GPU + +#include "GrColorSpaceXform.h" +#include "GrShaderCaps.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "SkGr.h" + +///////////////////////////////////////////////////////////////////// + +class GrLinearGradient : public GrGradientEffect { +public: + class GLSLLinearProcessor; + + static sk_sp Make(const CreateArgs& args) { + return sk_sp(new GrLinearGradient(args)); + } + + ~GrLinearGradient() override {} + + const char* name() const override { return "Linear Gradient"; } + +private: + GrLinearGradient(const CreateArgs& args) : INHERITED(args, args.fShader->colorsAreOpaque()) { + this->initClassID(); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + typedef GrGradientEffect INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrLinearGradient::GLSLLinearProcessor : public GrGradientEffect::GLSLProcessor { +public: + GLSLLinearProcessor(const GrProcessor&) {} + + ~GLSLLinearProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor& processor, const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); + } + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +GrGLSLFragmentProcessor* GrLinearGradient::onCreateGLSLInstance() const { + return new GrLinearGradient::GLSLLinearProcessor(*this); +} + +void GrLinearGradient::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrLinearGradient::GLSLLinearProcessor::GenKey(*this, caps, b); +} + +///////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrLinearGradient); + +#if GR_TEST_UTILS +sk_sp GrLinearGradient::TestCreate(GrProcessorTestData* d) { + SkPoint points[] = {{d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}, + {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}}; + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeLinear(points, params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeLinear(points, params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +///////////////////////////////////////////////////////////////////// + +void GrLinearGradient::GLSLLinearProcessor::emitCode(EmitArgs& args) { + const GrLinearGradient& ge = args.fFp.cast(); + this->emitUniforms(args.fUniformHandler, ge); + SkString t = args.fFragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + t.append(".x"); + this->emitColor(args.fFragBuilder, + args.fUniformHandler, + args.fShaderCaps, + ge, + t.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); +} + +///////////////////////////////////////////////////////////////////// + +sk_sp SkLinearGradient::asFragmentProcessor(const AsFPArgs& args) const { + SkASSERT(args.fContext); + + SkMatrix matrix; + if (!this->getLocalMatrix().invert(&matrix)) { + return nullptr; + } + if (args.fLocalMatrix) { + SkMatrix inv; + if (!args.fLocalMatrix->invert(&inv)) { + return nullptr; + } + matrix.postConcat(inv); + } + matrix.postConcat(fPtsToUnit); + + sk_sp colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), + args.fDstColorSpace); + sk_sp inner(GrLinearGradient::Make( + GrGradientEffect::CreateArgs(args.fContext, this, &matrix, fTileMode, + std::move(colorSpaceXform), SkToBool(args.fDstColorSpace)))); + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); +} + + +#endif + +#ifndef SK_IGNORE_TO_STRING +void SkLinearGradient::toString(SkString* str) const { + str->append("SkLinearGradient ("); + + str->appendf("start: (%f, %f)", fStart.fX, fStart.fY); + str->appendf(" end: (%f, %f) ", fEnd.fX, fEnd.fY); + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "SkNx.h" + +static const SkLinearGradient::LinearGradientContext::Rec* +find_forward(const SkLinearGradient::LinearGradientContext::Rec rec[], float tiledX) { + SkASSERT(tiledX >= 0 && tiledX <= 1); + + SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1); + SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1); + SkASSERT(rec[0].fPos <= rec[1].fPos); + rec += 1; + while (rec->fPos < tiledX || rec->fPosScale == 0) { + SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1); + SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1); + SkASSERT(rec[0].fPos <= rec[1].fPos); + rec += 1; + } + return rec - 1; +} + +static const SkLinearGradient::LinearGradientContext::Rec* +find_backward(const SkLinearGradient::LinearGradientContext::Rec rec[], float tiledX) { + SkASSERT(tiledX >= 0 && tiledX <= 1); + + SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1); + SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1); + SkASSERT(rec[0].fPos <= rec[1].fPos); + while (tiledX < rec->fPos || rec[1].fPosScale == 0) { + rec -= 1; + SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1); + SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1); + SkASSERT(rec[0].fPos <= rec[1].fPos); + } + return rec; +} + +// As an optimization, we can apply the dither bias before interpolation -- but only when +// operating in premul space (apply_alpha == false). When apply_alpha == true, we must +// defer the bias application until after premul. +// +// The following two helpers encapsulate this logic: pre_bias is called before interpolation, +// and effects the bias when apply_alpha == false, while post_bias is called after premul and +// effects the bias for the apply_alpha == true case. + +template +Sk4f pre_bias(const Sk4f& x, const Sk4f& bias) { + return apply_alpha ? x : x + bias; +} + +template +Sk4f post_bias(const Sk4f& x, const Sk4f& bias) { + return apply_alpha ? x + bias : x; +} + +template SkPMColor trunc_from_255(const Sk4f& x, const Sk4f& bias) { + SkPMColor c; + Sk4f c4f255 = x; + if (apply_alpha) { + const float scale = x[SkPM4f::A] * (1 / 255.f); + c4f255 *= Sk4f(scale, scale, scale, 1); + } + SkNx_cast(post_bias(c4f255, bias)).store(&c); + + return c; +} + +template void fill(SkPMColor dst[], int count, + const Sk4f& c4, const Sk4f& bias0, const Sk4f& bias1) { + const SkPMColor c0 = trunc_from_255(pre_bias(c4, bias0), bias0); + const SkPMColor c1 = trunc_from_255(pre_bias(c4, bias1), bias1); + sk_memset32_dither(dst, c0, c1, count); +} + +template void fill(SkPMColor dst[], int count, const Sk4f& c4) { + // Assumes that c4 does not need to be dithered. + sk_memset32(dst, trunc_from_255(c4, 0), count); +} + +/* + * TODOs + * + * - tilemodes + * - interp before or after premul + * - perspective + * - optimizations + * - use fixed (32bit or 16bit) instead of floats? + */ + +static Sk4f lerp_color(float fx, const SkLinearGradient::LinearGradientContext::Rec* rec) { + SkASSERT(fx >= rec[0].fPos); + SkASSERT(fx <= rec[1].fPos); + + const float p0 = rec[0].fPos; + const Sk4f c0 = rec[0].fColor; + const Sk4f c1 = rec[1].fColor; + const Sk4f diffc = c1 - c0; + const float scale = rec[1].fPosScale; + const float t = (fx - p0) * scale; + return c0 + Sk4f(t) * diffc; +} + +template void ramp(SkPMColor dstC[], int n, const Sk4f& c, const Sk4f& dc, + const Sk4f& dither0, const Sk4f& dither1) { + Sk4f dc2 = dc + dc; + Sk4f dc4 = dc2 + dc2; + Sk4f cd0 = pre_bias(c , dither0); + Sk4f cd1 = pre_bias(c + dc, dither1); + Sk4f cd2 = cd0 + dc2; + Sk4f cd3 = cd1 + dc2; + while (n >= 4) { + if (!apply_alpha) { + Sk4f_ToBytes((uint8_t*)dstC, cd0, cd1, cd2, cd3); + dstC += 4; + } else { + *dstC++ = trunc_from_255(cd0, dither0); + *dstC++ = trunc_from_255(cd1, dither1); + *dstC++ = trunc_from_255(cd2, dither0); + *dstC++ = trunc_from_255(cd3, dither1); + } + cd0 = cd0 + dc4; + cd1 = cd1 + dc4; + cd2 = cd2 + dc4; + cd3 = cd3 + dc4; + n -= 4; + } + if (n & 2) { + *dstC++ = trunc_from_255(cd0, dither0); + *dstC++ = trunc_from_255(cd1, dither1); + cd0 = cd0 + dc2; + } + if (n & 1) { + *dstC++ = trunc_from_255(cd0, dither0); + } +} + +template +void SkLinearGradient::LinearGradientContext::shade4_dx_clamp(SkPMColor dstC[], int count, + float fx, float dx, float invDx, + const float dither[2]) { + Sk4f dither0(dither[0]); + Sk4f dither1(dither[1]); + const Rec* rec = fRecs.begin(); + + const Sk4f dx4 = Sk4f(dx); + SkDEBUGCODE(SkPMColor* endDstC = dstC + count;) + + if (dx_is_pos) { + if (fx < 0) { + // count is guaranteed to be positive, but the first arg may overflow int32 after + // increment => casting to uint32 ensures correct clamping. + int n = SkTMin(static_cast(SkFloatToIntFloor(-fx * invDx)) + 1, + count); + SkASSERT(n > 0); + fill(dstC, n, rec[0].fColor); + count -= n; + dstC += n; + fx += n * dx; + SkASSERT(0 == count || fx >= 0); + if (n & 1) { + SkTSwap(dither0, dither1); + } + } + } else { // dx < 0 + if (fx > 1) { + // count is guaranteed to be positive, but the first arg may overflow int32 after + // increment => casting to uint32 ensures correct clamping. + int n = SkTMin(static_cast(SkFloatToIntFloor((1 - fx) * invDx)) + 1, + count); + SkASSERT(n > 0); + fill(dstC, n, rec[fRecs.count() - 1].fColor); + count -= n; + dstC += n; + fx += n * dx; + SkASSERT(0 == count || fx <= 1); + if (n & 1) { + SkTSwap(dither0, dither1); + } + } + } + SkASSERT(count >= 0); + + const Rec* r; + if (dx_is_pos) { + r = fRecs.begin(); // start at the beginning + } else { + r = fRecs.begin() + fRecs.count() - 2; // start at the end + } + + while (count > 0) { + if (dx_is_pos) { + if (fx >= 1) { + fill(dstC, count, rec[fRecs.count() - 1].fColor); + return; + } + } else { // dx < 0 + if (fx <= 0) { + fill(dstC, count, rec[0].fColor); + return; + } + } + + if (dx_is_pos) { + r = find_forward(r, fx); + } else { + r = find_backward(r, fx); + } + SkASSERT(r >= fRecs.begin() && r < fRecs.begin() + fRecs.count() - 1); + + const float p0 = r[0].fPos; + const Sk4f c0 = r[0].fColor; + const float p1 = r[1].fPos; + const Sk4f diffc = Sk4f(r[1].fColor) - c0; + const float scale = r[1].fPosScale; + const float t = (fx - p0) * scale; + const Sk4f c = c0 + Sk4f(t) * diffc; + const Sk4f dc = diffc * dx4 * Sk4f(scale); + + int n; + if (dx_is_pos) { + n = SkTMin((int)((p1 - fx) * invDx) + 1, count); + } else { + n = SkTMin((int)((p0 - fx) * invDx) + 1, count); + } + + fx += n * dx; + // fx should now outside of the p0..p1 interval. However, due to float precision loss, + // its possible that fx is slightly too small/large, so we clamp it. + if (dx_is_pos) { + fx = SkTMax(fx, p1); + } else { + fx = SkTMin(fx, p0); + } + + ramp(dstC, n, c, dc, dither0, dither1); + dstC += n; + SkASSERT(dstC <= endDstC); + + if (n & 1) { + SkTSwap(dither0, dither1); + } + + count -= n; + SkASSERT(count >= 0); + } +} + +void SkLinearGradient::LinearGradientContext::shade4_clamp(int x, int y, SkPMColor dstC[], + int count) { + SkASSERT(count > 0); + SkASSERT(kLinear_MatrixClass == fDstToIndexClass); + + SkPoint srcPt; + fDstToIndexProc(fDstToIndex, x + SK_ScalarHalf, y + SK_ScalarHalf, &srcPt); + float fx = srcPt.x(); + const float dx = fDstToIndex.getScaleX(); + + // Default our dither bias values to 1/2, (rounding), which is no dithering + float dither0 = 0.5f; + float dither1 = 0.5f; + if (fDither) { + const float ditherCell[] = { + 1/8.0f, 5/8.0f, + 7/8.0f, 3/8.0f, + }; + const int rowIndex = (y & 1) << 1; + dither0 = ditherCell[rowIndex]; + dither1 = ditherCell[rowIndex + 1]; + if (x & 1) { + SkTSwap(dither0, dither1); + } + } + const float dither[2] = { dither0, dither1 }; + + if (SkScalarNearlyZero(dx * count)) { // gradient is vertical + const float pinFx = SkTPin(fx, 0.0f, 1.0f); + Sk4f c = lerp_color(pinFx, find_forward(fRecs.begin(), pinFx)); + if (fApplyAlphaAfterInterp) { + fill(dstC, count, c, dither0, dither1); + } else { + fill(dstC, count, c, dither0, dither1); + } + return; + } + + SkASSERT(0.f != dx); + const float invDx = 1 / dx; + if (dx > 0) { + if (fApplyAlphaAfterInterp) { + this->shade4_dx_clamp(dstC, count, fx, dx, invDx, dither); + } else { + this->shade4_dx_clamp(dstC, count, fx, dx, invDx, dither); + } + } else { + if (fApplyAlphaAfterInterp) { + this->shade4_dx_clamp(dstC, count, fx, dx, invDx, dither); + } else { + this->shade4_dx_clamp(dstC, count, fx, dx, invDx, dither); + } + } +} diff --git a/src/effects/gradients/SkLinearGradient.h b/src/effects/gradients/SkLinearGradient.h new file mode 100644 index 0000000000..19a965c7bb --- /dev/null +++ b/src/effects/gradients/SkLinearGradient.h @@ -0,0 +1,87 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLinearGradient_DEFINED +#define SkLinearGradient_DEFINED + +#include "SkGradientShaderPriv.h" +#include "SkNx.h" + +struct Sk4fStorage { + float fArray[4]; + + operator Sk4f() const { + return Sk4f::Load(fArray); + } + + Sk4fStorage& operator=(const Sk4f& src) { + src.store(fArray); + return *this; + } +}; + +class SkLinearGradient : public SkGradientShaderBase { +public: + enum { + // Temp flag for testing the 4f impl. + kForce4fContext_PrivateFlag = 1 << 7, + }; + + SkLinearGradient(const SkPoint pts[2], const Descriptor&); + + class LinearGradientContext : public SkGradientShaderBase::GradientShaderBaseContext { + public: + LinearGradientContext(const SkLinearGradient&, const ContextRec&); + + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override; + + struct Rec { + Sk4fStorage fColor; + float fPos; + float fPosScale; + }; + private: + SkTDArray fRecs; + bool fApplyAlphaAfterInterp; + + void shade4_clamp(int x, int y, SkPMColor dstC[], int count); + template void shade4_dx_clamp(SkPMColor dstC[], int count, float fx, float dx, + float invDx, const float dither[2]); + + typedef SkGradientShaderBase::GradientShaderBaseContext INHERITED; + }; + + GradientType asAGradient(GradientInfo* info) const override; +#if SK_SUPPORT_GPU + sk_sp asFragmentProcessor(const AsFPArgs&) const override; +#endif + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLinearGradient) + +protected: + SkLinearGradient(SkReadBuffer& buffer); + void flatten(SkWriteBuffer& buffer) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + + bool adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const final; + + + sk_sp onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + +private: + class LinearGradient4fContext; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; + const SkPoint fStart; + const SkPoint fEnd; +}; + +#endif diff --git a/src/effects/gradients/SkRadialGradient.cpp b/src/effects/gradients/SkRadialGradient.cpp new file mode 100644 index 0000000000..d49b3dd8e1 --- /dev/null +++ b/src/effects/gradients/SkRadialGradient.cpp @@ -0,0 +1,405 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkColorSpaceXformer.h" +#include "SkRadialGradient.h" +#include "SkNx.h" + +namespace { + +// GCC doesn't like using static functions as template arguments. So force these to be non-static. +inline SkFixed mirror_tileproc_nonstatic(SkFixed x) { + return mirror_tileproc(x); +} + +inline SkFixed repeat_tileproc_nonstatic(SkFixed x) { + return repeat_tileproc(x); +} + +SkMatrix rad_to_unit_matrix(const SkPoint& center, SkScalar radius) { + SkScalar inv = SkScalarInvert(radius); + + SkMatrix matrix; + matrix.setTranslate(-center.fX, -center.fY); + matrix.postScale(inv, inv); + return matrix; +} + + +} // namespace + +///////////////////////////////////////////////////////////////////// + +SkRadialGradient::SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor& desc) + : SkGradientShaderBase(desc, rad_to_unit_matrix(center, radius)) + , fCenter(center) + , fRadius(radius) { +} + +SkShaderBase::Context* SkRadialGradient::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const +{ + return CheckedMakeContext(alloc, *this, rec); +} + +SkRadialGradient::RadialGradientContext::RadialGradientContext( + const SkRadialGradient& shader, const ContextRec& rec) + : INHERITED(shader, rec) {} + +SkShader::GradientType SkRadialGradient::asAGradient(GradientInfo* info) const { + if (info) { + commonAsAGradient(info); + info->fPoint[0] = fCenter; + info->fRadius[0] = fRadius; + } + return kRadial_GradientType; +} + +sk_sp SkRadialGradient::CreateProc(SkReadBuffer& buffer) { + DescriptorScope desc; + if (!desc.unflatten(buffer)) { + return nullptr; + } + const SkPoint center = buffer.readPoint(); + const SkScalar radius = buffer.readScalar(); + return SkGradientShader::MakeRadial(center, radius, desc.fColors, std::move(desc.fColorSpace), + desc.fPos, desc.fCount, desc.fTileMode, desc.fGradFlags, + desc.fLocalMatrix); +} + +void SkRadialGradient::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fCenter); + buffer.writeScalar(fRadius); +} + +namespace { + +inline bool radial_completely_pinned(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy) { + // fast, overly-conservative test: checks unit square instead of unit circle + bool xClamped = (fx >= 1 && dx >= 0) || (fx <= -1 && dx <= 0); + bool yClamped = (fy >= 1 && dy >= 0) || (fy <= -1 && dy <= 0); + return xClamped || yClamped; +} + +typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx, + SkScalar sfy, SkScalar sdy, + SkPMColor* dstC, const SkPMColor* cache, + int count, int toggle); + +static inline Sk4f fast_sqrt(const Sk4f& R) { + return R * R.rsqrt(); +} + +static inline Sk4f sum_squares(const Sk4f& a, const Sk4f& b) { + return a * a + b * b; +} + +void shadeSpan_radial_clamp2(SkScalar sfx, SkScalar sdx, SkScalar sfy, SkScalar sdy, + SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, + int count, int toggle) { + if (radial_completely_pinned(sfx, sdx, sfy, sdy)) { + unsigned fi = SkGradientShaderBase::kCache32Count - 1; + sk_memset32_dither(dstC, + cache[toggle + fi], + cache[next_dither_toggle(toggle) + fi], + count); + } else { + const Sk4f min(SK_ScalarNearlyZero); + const Sk4f max(255); + const float scale = 255; + sfx *= scale; + sfy *= scale; + sdx *= scale; + sdy *= scale; + const Sk4f fx4(sfx, sfx + sdx, sfx + 2*sdx, sfx + 3*sdx); + const Sk4f fy4(sfy, sfy + sdy, sfy + 2*sdy, sfy + 3*sdy); + const Sk4f dx4(sdx * 4); + const Sk4f dy4(sdy * 4); + + Sk4f tmpxy = fx4 * dx4 + fy4 * dy4; + Sk4f tmpdxdy = sum_squares(dx4, dy4); + Sk4f R = Sk4f::Max(sum_squares(fx4, fy4), min); + Sk4f dR = tmpxy + tmpxy + tmpdxdy; + const Sk4f ddR = tmpdxdy + tmpdxdy; + + for (int i = 0; i < (count >> 2); ++i) { + Sk4f dist = Sk4f::Min(fast_sqrt(R), max); + R = Sk4f::Max(R + dR, min); + dR = dR + ddR; + + uint8_t fi[4]; + SkNx_cast(dist).store(fi); + + for (int i = 0; i < 4; i++) { + *dstC++ = cache[toggle + fi[i]]; + toggle = next_dither_toggle(toggle); + } + } + count &= 3; + if (count) { + Sk4f dist = Sk4f::Min(fast_sqrt(R), max); + + uint8_t fi[4]; + SkNx_cast(dist).store(fi); + for (int i = 0; i < count; i++) { + *dstC++ = cache[toggle + fi[i]]; + toggle = next_dither_toggle(toggle); + } + } + } +} + +// Unrolling this loop doesn't seem to help (when float); we're stalling to +// get the results of the sqrt (?), and don't have enough extra registers to +// have many in flight. +template +void shadeSpan_radial(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, + SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, + int count, int toggle) { + do { + const SkFixed dist = SkFloatToFixed(sk_float_sqrt(fx*fx + fy*fy)); + const unsigned fi = TileProc(dist); + SkASSERT(fi <= 0xFFFF); + *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)]; + toggle = next_dither_toggle(toggle); + fx += dx; + fy += dy; + } while (--count != 0); +} + +void shadeSpan_radial_mirror(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, + SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, + int count, int toggle) { + shadeSpan_radial(fx, dx, fy, dy, dstC, cache, count, toggle); +} + +void shadeSpan_radial_repeat(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, + SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, + int count, int toggle) { + shadeSpan_radial(fx, dx, fy, dy, dstC, cache, count, toggle); +} + +} // namespace + +void SkRadialGradient::RadialGradientContext::shadeSpan(int x, int y, + SkPMColor* SK_RESTRICT dstC, int count) { + SkASSERT(count > 0); + + const SkRadialGradient& radialGradient = static_cast(fShader); + + SkPoint srcPt; + SkMatrix::MapXYProc dstProc = fDstToIndexProc; + TileProc proc = radialGradient.fTileProc; + const SkPMColor* SK_RESTRICT cache = fCache->getCache32(); + int toggle = init_dither_toggle(x, y); + + if (fDstToIndexClass != kPerspective_MatrixClass) { + dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, + SkIntToScalar(y) + SK_ScalarHalf, &srcPt); + SkScalar sdx = fDstToIndex.getScaleX(); + SkScalar sdy = fDstToIndex.getSkewY(); + + if (fDstToIndexClass == kFixedStepInX_MatrixClass) { + const auto step = fDstToIndex.fixedStepInX(SkIntToScalar(y)); + sdx = step.fX; + sdy = step.fY; + } else { + SkASSERT(fDstToIndexClass == kLinear_MatrixClass); + } + + RadialShadeProc shadeProc = shadeSpan_radial_repeat; + if (SkShader::kClamp_TileMode == radialGradient.fTileMode) { + shadeProc = shadeSpan_radial_clamp2; + } else if (SkShader::kMirror_TileMode == radialGradient.fTileMode) { + shadeProc = shadeSpan_radial_mirror; + } else { + SkASSERT(SkShader::kRepeat_TileMode == radialGradient.fTileMode); + } + (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle); + } else { // perspective case + SkScalar dstX = SkIntToScalar(x); + SkScalar dstY = SkIntToScalar(y); + do { + dstProc(fDstToIndex, dstX, dstY, &srcPt); + unsigned fi = proc(SkScalarToFixed(srcPt.length())); + SkASSERT(fi <= 0xFFFF); + *dstC++ = cache[fi >> SkGradientShaderBase::kCache32Shift]; + dstX += SK_Scalar1; + } while (--count != 0); + } +} + +///////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "SkGr.h" +#include "GrShaderCaps.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" + +class GrRadialGradient : public GrGradientEffect { +public: + class GLSLRadialProcessor; + + static sk_sp Make(const CreateArgs& args) { + return sk_sp(new GrRadialGradient(args)); + } + + ~GrRadialGradient() override {} + + const char* name() const override { return "Radial Gradient"; } + +private: + GrRadialGradient(const CreateArgs& args) : INHERITED(args, args.fShader->colorsAreOpaque()) { + this->initClassID(); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + typedef GrGradientEffect INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrRadialGradient::GLSLRadialProcessor : public GrGradientEffect::GLSLProcessor { +public: + GLSLRadialProcessor(const GrProcessor&) {} + ~GLSLRadialProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor& processor, const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); + } + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +///////////////////////////////////////////////////////////////////// + +GrGLSLFragmentProcessor* GrRadialGradient::onCreateGLSLInstance() const { + return new GrRadialGradient::GLSLRadialProcessor(*this); +} + +void GrRadialGradient::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrRadialGradient::GLSLRadialProcessor::GenKey(*this, caps, b); +} + +///////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRadialGradient); + +#if GR_TEST_UTILS +sk_sp GrRadialGradient::TestCreate(GrProcessorTestData* d) { + sk_sp shader; + do { + RandomGradientParams params(d->fRandom); + SkPoint center = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius = d->fRandom->nextUScalar1(); + shader = params.fUseColors4f + ? SkGradientShader::MakeRadial(center, radius, params.fColors4f, + params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) + : SkGradientShader::MakeRadial(center, radius, params.fColors, + params.fStops, params.fColorCount, + params.fTileMode); + } while (!shader); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +///////////////////////////////////////////////////////////////////// + +void GrRadialGradient::GLSLRadialProcessor::emitCode(EmitArgs& args) { + const GrRadialGradient& ge = args.fFp.cast(); + this->emitUniforms(args.fUniformHandler, ge); + SkString t("length("); + t.append(args.fFragBuilder->ensureCoords2D(args.fTransformedCoords[0])); + t.append(")"); + this->emitColor(args.fFragBuilder, + args.fUniformHandler, + args.fShaderCaps, + ge, t.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); +} + +///////////////////////////////////////////////////////////////////// + +sk_sp SkRadialGradient::asFragmentProcessor(const AsFPArgs& args) const { + SkASSERT(args.fContext); + + SkMatrix matrix; + if (!this->getLocalMatrix().invert(&matrix)) { + return nullptr; + } + if (args.fLocalMatrix) { + SkMatrix inv; + if (!args.fLocalMatrix->invert(&inv)) { + return nullptr; + } + matrix.postConcat(inv); + } + matrix.postConcat(fPtsToUnit); + sk_sp colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), + args.fDstColorSpace); + sk_sp inner(GrRadialGradient::Make( + GrGradientEffect::CreateArgs(args.fContext, this, &matrix, fTileMode, + std::move(colorSpaceXform), SkToBool(args.fDstColorSpace)))); + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); +} + +#endif + +sk_sp SkRadialGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + SkSTArray<8, SkColor> xformedColors(fColorCount); + xformer->apply(xformedColors.begin(), fOrigColors, fColorCount); + return SkGradientShader::MakeRadial(fCenter, fRadius, xformedColors.begin(), fOrigPos, + fColorCount, fTileMode, fGradFlags, + &this->getLocalMatrix()); +} + +bool SkRadialGradient::adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const { + matrix->postTranslate(-fCenter.fX, -fCenter.fY); + matrix->postScale(1/fRadius, 1/fRadius); + + p->append(SkRasterPipeline::xy_to_radius); + return true; +} + +#ifndef SK_IGNORE_TO_STRING +void SkRadialGradient::toString(SkString* str) const { + str->append("SkRadialGradient: ("); + + str->append("center: ("); + str->appendScalar(fCenter.fX); + str->append(", "); + str->appendScalar(fCenter.fY); + str->append(") radius: "); + str->appendScalar(fRadius); + str->append(" "); + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif diff --git a/src/effects/gradients/SkRadialGradient.h b/src/effects/gradients/SkRadialGradient.h new file mode 100644 index 0000000000..69ec4b1285 --- /dev/null +++ b/src/effects/gradients/SkRadialGradient.h @@ -0,0 +1,53 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRadialGradient_DEFINED +#define SkRadialGradient_DEFINED + +#include "SkGradientShaderPriv.h" + +class SkRadialGradient : public SkGradientShaderBase { +public: + SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor&); + + class RadialGradientContext : public SkGradientShaderBase::GradientShaderBaseContext { + public: + RadialGradientContext(const SkRadialGradient&, const ContextRec&); + + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override; + + private: + typedef SkGradientShaderBase::GradientShaderBaseContext INHERITED; + }; + + GradientType asAGradient(GradientInfo* info) const override; +#if SK_SUPPORT_GPU + sk_sp asFragmentProcessor(const AsFPArgs&) const override; +#endif + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkRadialGradient) + +protected: + SkRadialGradient(SkReadBuffer& buffer); + void flatten(SkWriteBuffer& buffer) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + sk_sp onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + + bool adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const final; + +private: + const SkPoint fCenter; + const SkScalar fRadius; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; +}; + +#endif diff --git a/src/effects/gradients/SkSweepGradient.cpp b/src/effects/gradients/SkSweepGradient.cpp new file mode 100644 index 0000000000..a138a8e213 --- /dev/null +++ b/src/effects/gradients/SkSweepGradient.cpp @@ -0,0 +1,225 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkColorSpaceXformer.h" +#include "SkSweepGradient.h" + +#include "SkPM4fPriv.h" +#include "SkRasterPipeline.h" + +static SkMatrix translate(SkScalar dx, SkScalar dy) { + SkMatrix matrix; + matrix.setTranslate(dx, dy); + return matrix; +} + +SkSweepGradient::SkSweepGradient(SkScalar cx, SkScalar cy, const Descriptor& desc) + : SkGradientShaderBase(desc, translate(-cx, -cy)) + , fCenter(SkPoint::Make(cx, cy)) +{ + // overwrite the tilemode to a canonical value (since sweep ignores it) + fTileMode = SkShader::kClamp_TileMode; +} + +SkShader::GradientType SkSweepGradient::asAGradient(GradientInfo* info) const { + if (info) { + commonAsAGradient(info); + info->fPoint[0] = fCenter; + } + return kSweep_GradientType; +} + +sk_sp SkSweepGradient::CreateProc(SkReadBuffer& buffer) { + DescriptorScope desc; + if (!desc.unflatten(buffer)) { + return nullptr; + } + const SkPoint center = buffer.readPoint(); + return SkGradientShader::MakeSweep(center.x(), center.y(), desc.fColors, + std::move(desc.fColorSpace), desc.fPos, desc.fCount, + desc.fGradFlags, desc.fLocalMatrix); +} + +void SkSweepGradient::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fCenter); +} + +///////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "SkGr.h" +#include "GrShaderCaps.h" +#include "gl/GrGLContext.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" + +class GrSweepGradient : public GrGradientEffect { +public: + class GLSLSweepProcessor; + + static sk_sp Make(const CreateArgs& args) { + return sk_sp(new GrSweepGradient(args)); + } + ~GrSweepGradient() override {} + + const char* name() const override { return "Sweep Gradient"; } + +private: + GrSweepGradient(const CreateArgs& args) : INHERITED(args, args.fShader->colorsAreOpaque()) { + this->initClassID(); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + typedef GrGradientEffect INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrSweepGradient::GLSLSweepProcessor : public GrGradientEffect::GLSLProcessor { +public: + GLSLSweepProcessor(const GrProcessor&) {} + ~GLSLSweepProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor& processor, const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); + } + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +///////////////////////////////////////////////////////////////////// + +GrGLSLFragmentProcessor* GrSweepGradient::onCreateGLSLInstance() const { + return new GrSweepGradient::GLSLSweepProcessor(*this); +} + +void GrSweepGradient::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrSweepGradient::GLSLSweepProcessor::GenKey(*this, caps, b); +} + + +///////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSweepGradient); + +#if GR_TEST_UTILS +sk_sp GrSweepGradient::TestCreate(GrProcessorTestData* d) { + SkPoint center = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeSweep(center.fX, center.fY, params.fColors4f, params.fColorSpace, + params.fStops, params.fColorCount) : + SkGradientShader::MakeSweep(center.fX, center.fY, params.fColors, + params.fStops, params.fColorCount); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +///////////////////////////////////////////////////////////////////// + +void GrSweepGradient::GLSLSweepProcessor::emitCode(EmitArgs& args) { + const GrSweepGradient& ge = args.fFp.cast(); + this->emitUniforms(args.fUniformHandler, ge); + SkString coords2D = args.fFragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + SkString t; + // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi] + if (args.fShaderCaps->atan2ImplementedAsAtanYOverX()) { + // On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is + // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in + // (sqrt(x^2 + y^2) + x) as the second parameter to atan2 in these cases. We let the device + // handle the undefined behavior of the second paramenter being 0 instead of doing the + // divide ourselves and using atan instead. + t.printf("(2.0 * atan(- %s.y, length(%s) - %s.x) * 0.1591549430918 + 0.5)", + coords2D.c_str(), coords2D.c_str(), coords2D.c_str()); + } else { + t.printf("(atan(- %s.y, - %s.x) * 0.1591549430918 + 0.5)", + coords2D.c_str(), coords2D.c_str()); + } + this->emitColor(args.fFragBuilder, + args.fUniformHandler, + args.fShaderCaps, + ge, t.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); +} + +///////////////////////////////////////////////////////////////////// + +sk_sp SkSweepGradient::asFragmentProcessor(const AsFPArgs& args) const { + + SkMatrix matrix; + if (!this->getLocalMatrix().invert(&matrix)) { + return nullptr; + } + if (args.fLocalMatrix) { + SkMatrix inv; + if (!args.fLocalMatrix->invert(&inv)) { + return nullptr; + } + matrix.postConcat(inv); + } + matrix.postConcat(fPtsToUnit); + + sk_sp colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), + args.fDstColorSpace); + sk_sp inner(GrSweepGradient::Make( + GrGradientEffect::CreateArgs(args.fContext, this, &matrix, SkShader::kClamp_TileMode, + std::move(colorSpaceXform), SkToBool(args.fDstColorSpace)))); + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); +} + +#endif + +sk_sp SkSweepGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + SkSTArray<8, SkColor> xformedColors(fColorCount); + xformer->apply(xformedColors.begin(), fOrigColors, fColorCount); + return SkGradientShader::MakeSweep(fCenter.fX, fCenter.fY, xformedColors.begin(), fOrigPos, + fColorCount, fGradFlags, &this->getLocalMatrix()); +} + +#ifndef SK_IGNORE_TO_STRING +void SkSweepGradient::toString(SkString* str) const { + str->append("SkSweepGradient: ("); + + str->append("center: ("); + str->appendScalar(fCenter.fX); + str->append(", "); + str->appendScalar(fCenter.fY); + str->append(") "); + + this->INHERITED::toString(str); + + str->append(")"); +} + +bool SkSweepGradient::adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const { + matrix->postTranslate(-fCenter.fX, -fCenter.fY); + p->append(SkRasterPipeline::xy_to_unit_angle); + + return true; +} + +#endif diff --git a/src/effects/gradients/SkSweepGradient.h b/src/effects/gradients/SkSweepGradient.h new file mode 100644 index 0000000000..c30ca4d916 --- /dev/null +++ b/src/effects/gradients/SkSweepGradient.h @@ -0,0 +1,43 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSweepGradient_DEFINED +#define SkSweepGradient_DEFINED + +#include "SkGradientShaderPriv.h" + +class SkSweepGradient final : public SkGradientShaderBase { +public: + SkSweepGradient(SkScalar cx, SkScalar cy, const Descriptor&); + + GradientType asAGradient(GradientInfo* info) const override; + +#if SK_SUPPORT_GPU + sk_sp asFragmentProcessor(const AsFPArgs&) const override; +#endif + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSweepGradient) + +protected: + void flatten(SkWriteBuffer& buffer) const override; + sk_sp onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + + bool adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const override; + + bool isRasterPipelineOnly() const override { return true; } + +private: + const SkPoint fCenter; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; +}; + +#endif diff --git a/src/effects/gradients/SkTwoPointConicalGradient.cpp b/src/effects/gradients/SkTwoPointConicalGradient.cpp new file mode 100644 index 0000000000..4549527d51 --- /dev/null +++ b/src/effects/gradients/SkTwoPointConicalGradient.cpp @@ -0,0 +1,421 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkTwoPointConicalGradient.h" + +struct TwoPtRadialContext { + const TwoPtRadial& fRec; + float fRelX, fRelY; + const float fIncX, fIncY; + float fB; + const float fDB; + + TwoPtRadialContext(const TwoPtRadial& rec, SkScalar fx, SkScalar fy, + SkScalar dfx, SkScalar dfy); + SkFixed nextT(); +}; + +static int valid_divide(float numer, float denom, float* ratio) { + SkASSERT(ratio); + if (0 == denom) { + return 0; + } + *ratio = numer / denom; + return 1; +} + +// Return the number of distinct real roots, and write them into roots[] in +// ascending order +static int find_quad_roots(float A, float B, float C, float roots[2], bool descendingOrder = false) { + SkASSERT(roots); + + if (A == 0) { + return valid_divide(-C, B, roots); + } + + float R = B*B - 4*A*C; + if (R < 0) { + return 0; + } + R = sk_float_sqrt(R); + +#if 1 + float Q = B; + if (Q < 0) { + Q -= R; + } else { + Q += R; + } +#else + // on 10.6 this was much slower than the above branch :( + float Q = B + copysignf(R, B); +#endif + Q *= -0.5f; + if (0 == Q) { + roots[0] = 0; + return 1; + } + + float r0 = Q / A; + float r1 = C / Q; + roots[0] = r0 < r1 ? r0 : r1; + roots[1] = r0 > r1 ? r0 : r1; + if (descendingOrder) { + SkTSwap(roots[0], roots[1]); + } + return 2; +} + +static float lerp(float x, float dx, float t) { + return x + t * dx; +} + +static float sqr(float x) { return x * x; } + +void TwoPtRadial::init(const SkPoint& center0, SkScalar rad0, + const SkPoint& center1, SkScalar rad1, + bool flipped) { + fCenterX = SkScalarToFloat(center0.fX); + fCenterY = SkScalarToFloat(center0.fY); + fDCenterX = SkScalarToFloat(center1.fX) - fCenterX; + fDCenterY = SkScalarToFloat(center1.fY) - fCenterY; + fRadius = SkScalarToFloat(rad0); + fDRadius = SkScalarToFloat(rad1) - fRadius; + + fA = sqr(fDCenterX) + sqr(fDCenterY) - sqr(fDRadius); + fRadius2 = sqr(fRadius); + fRDR = fRadius * fDRadius; + + fFlipped = flipped; +} + +TwoPtRadialContext::TwoPtRadialContext(const TwoPtRadial& rec, SkScalar fx, SkScalar fy, + SkScalar dfx, SkScalar dfy) + : fRec(rec) + , fRelX(SkScalarToFloat(fx) - rec.fCenterX) + , fRelY(SkScalarToFloat(fy) - rec.fCenterY) + , fIncX(SkScalarToFloat(dfx)) + , fIncY(SkScalarToFloat(dfy)) + , fB(-2 * (rec.fDCenterX * fRelX + rec.fDCenterY * fRelY + rec.fRDR)) + , fDB(-2 * (rec.fDCenterX * fIncX + rec.fDCenterY * fIncY)) {} + +SkFixed TwoPtRadialContext::nextT() { + float roots[2]; + + float C = sqr(fRelX) + sqr(fRelY) - fRec.fRadius2; + int countRoots = find_quad_roots(fRec.fA, fB, C, roots, fRec.fFlipped); + + fRelX += fIncX; + fRelY += fIncY; + fB += fDB; + + if (0 == countRoots) { + return TwoPtRadial::kDontDrawT; + } + + // Prefer the bigger t value if both give a radius(t) > 0 + // find_quad_roots returns the values sorted, so we start with the last + float t = roots[countRoots - 1]; + float r = lerp(fRec.fRadius, fRec.fDRadius, t); + if (r < 0) { + t = roots[0]; // might be the same as roots[countRoots-1] + r = lerp(fRec.fRadius, fRec.fDRadius, t); + if (r < 0) { + return TwoPtRadial::kDontDrawT; + } + } + return SkFloatToFixed(t); +} + +typedef void (*TwoPointConicalProc)(TwoPtRadialContext* rec, SkPMColor* dstC, + const SkPMColor* cache, int toggle, int count); + +static void twopoint_clamp(TwoPtRadialContext* rec, SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, int toggle, + int count) { + for (; count > 0; --count) { + SkFixed t = rec->nextT(); + if (TwoPtRadial::DontDrawT(t)) { + *dstC++ = 0; + } else { + SkFixed index = SkClampMax(t, 0xFFFF); + SkASSERT(index <= 0xFFFF); + *dstC++ = cache[toggle + + (index >> SkGradientShaderBase::kCache32Shift)]; + } + toggle = next_dither_toggle(toggle); + } +} + +static void twopoint_repeat(TwoPtRadialContext* rec, SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, int toggle, + int count) { + for (; count > 0; --count) { + SkFixed t = rec->nextT(); + if (TwoPtRadial::DontDrawT(t)) { + *dstC++ = 0; + } else { + SkFixed index = repeat_tileproc(t); + SkASSERT(index <= 0xFFFF); + *dstC++ = cache[toggle + + (index >> SkGradientShaderBase::kCache32Shift)]; + } + toggle = next_dither_toggle(toggle); + } +} + +static void twopoint_mirror(TwoPtRadialContext* rec, SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, int toggle, + int count) { + for (; count > 0; --count) { + SkFixed t = rec->nextT(); + if (TwoPtRadial::DontDrawT(t)) { + *dstC++ = 0; + } else { + SkFixed index = mirror_tileproc(t); + SkASSERT(index <= 0xFFFF); + *dstC++ = cache[toggle + + (index >> SkGradientShaderBase::kCache32Shift)]; + } + toggle = next_dither_toggle(toggle); + } +} + +///////////////////////////////////////////////////////////////////// + +SkTwoPointConicalGradient::SkTwoPointConicalGradient( + const SkPoint& start, SkScalar startRadius, + const SkPoint& end, SkScalar endRadius, + bool flippedGrad, const Descriptor& desc) + : SkGradientShaderBase(desc, SkMatrix::I()) + , fCenter1(start) + , fCenter2(end) + , fRadius1(startRadius) + , fRadius2(endRadius) + , fFlippedGrad(flippedGrad) +{ + // this is degenerate, and should be caught by our caller + SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2); + fRec.init(fCenter1, fRadius1, fCenter2, fRadius2, fFlippedGrad); +} + +bool SkTwoPointConicalGradient::isOpaque() const { + // Because areas outside the cone are left untouched, we cannot treat the + // shader as opaque even if the gradient itself is opaque. + // TODO(junov): Compute whether the cone fills the plane crbug.com/222380 + return false; +} + +SkShaderBase::Context* SkTwoPointConicalGradient::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const { + return CheckedMakeContext(alloc, *this, rec); +} + +SkTwoPointConicalGradient::TwoPointConicalGradientContext::TwoPointConicalGradientContext( + const SkTwoPointConicalGradient& shader, const ContextRec& rec) + : INHERITED(shader, rec) +{ + // in general, we might discard based on computed-radius, so clear + // this flag (todo: sometimes we can detect that we never discard...) + fFlags &= ~kOpaqueAlpha_Flag; +} + +void SkTwoPointConicalGradient::TwoPointConicalGradientContext::shadeSpan( + int x, int y, SkPMColor* dstCParam, int count) { + const SkTwoPointConicalGradient& twoPointConicalGradient = + static_cast(fShader); + + int toggle = init_dither_toggle(x, y); + + SkASSERT(count > 0); + + SkPMColor* SK_RESTRICT dstC = dstCParam; + + SkMatrix::MapXYProc dstProc = fDstToIndexProc; + + const SkPMColor* SK_RESTRICT cache = fCache->getCache32(); + + TwoPointConicalProc shadeProc = twopoint_repeat; + if (SkShader::kClamp_TileMode == twoPointConicalGradient.fTileMode) { + shadeProc = twopoint_clamp; + } else if (SkShader::kMirror_TileMode == twoPointConicalGradient.fTileMode) { + shadeProc = twopoint_mirror; + } else { + SkASSERT(SkShader::kRepeat_TileMode == twoPointConicalGradient.fTileMode); + } + + if (fDstToIndexClass != kPerspective_MatrixClass) { + SkPoint srcPt; + dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, + SkIntToScalar(y) + SK_ScalarHalf, &srcPt); + SkScalar dx, fx = srcPt.fX; + SkScalar dy, fy = srcPt.fY; + + if (fDstToIndexClass == kFixedStepInX_MatrixClass) { + const auto step = fDstToIndex.fixedStepInX(SkIntToScalar(y)); + dx = step.fX; + dy = step.fY; + } else { + SkASSERT(fDstToIndexClass == kLinear_MatrixClass); + dx = fDstToIndex.getScaleX(); + dy = fDstToIndex.getSkewY(); + } + + TwoPtRadialContext rec(twoPointConicalGradient.fRec, fx, fy, dx, dy); + (*shadeProc)(&rec, dstC, cache, toggle, count); + } else { // perspective case + SkScalar dstX = SkIntToScalar(x) + SK_ScalarHalf; + SkScalar dstY = SkIntToScalar(y) + SK_ScalarHalf; + for (; count > 0; --count) { + SkPoint srcPt; + dstProc(fDstToIndex, dstX, dstY, &srcPt); + TwoPtRadialContext rec(twoPointConicalGradient.fRec, srcPt.fX, srcPt.fY, 0, 0); + (*shadeProc)(&rec, dstC, cache, toggle, 1); + + dstX += SK_Scalar1; + toggle = next_dither_toggle(toggle); + dstC += 1; + } + } +} + +// Returns the original non-sorted version of the gradient +SkShader::GradientType SkTwoPointConicalGradient::asAGradient( + GradientInfo* info) const { + if (info) { + commonAsAGradient(info, fFlippedGrad); + info->fPoint[0] = fCenter1; + info->fPoint[1] = fCenter2; + info->fRadius[0] = fRadius1; + info->fRadius[1] = fRadius2; + if (fFlippedGrad) { + SkTSwap(info->fPoint[0], info->fPoint[1]); + SkTSwap(info->fRadius[0], info->fRadius[1]); + } + } + return kConical_GradientType; +} + +sk_sp SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) { + DescriptorScope desc; + if (!desc.unflatten(buffer)) { + return nullptr; + } + SkPoint c1 = buffer.readPoint(); + SkPoint c2 = buffer.readPoint(); + SkScalar r1 = buffer.readScalar(); + SkScalar r2 = buffer.readScalar(); + + if (buffer.readBool()) { // flipped + SkTSwap(c1, c2); + SkTSwap(r1, r2); + + SkColor4f* colors = desc.mutableColors(); + SkScalar* pos = desc.mutablePos(); + const int last = desc.fCount - 1; + const int half = desc.fCount >> 1; + for (int i = 0; i < half; ++i) { + SkTSwap(colors[i], colors[last - i]); + if (pos) { + SkScalar tmp = pos[i]; + pos[i] = SK_Scalar1 - pos[last - i]; + pos[last - i] = SK_Scalar1 - tmp; + } + } + if (pos) { + if (desc.fCount & 1) { + pos[half] = SK_Scalar1 - pos[half]; + } + } + } + + return SkGradientShader::MakeTwoPointConical(c1, r1, c2, r2, desc.fColors, + std::move(desc.fColorSpace), desc.fPos, + desc.fCount, desc.fTileMode, desc.fGradFlags, + desc.fLocalMatrix); +} + +void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fCenter1); + buffer.writePoint(fCenter2); + buffer.writeScalar(fRadius1); + buffer.writeScalar(fRadius2); + buffer.writeBool(fFlippedGrad); +} + +#if SK_SUPPORT_GPU + +#include "SkGr.h" +#include "SkTwoPointConicalGradient_gpu.h" + +sk_sp SkTwoPointConicalGradient::asFragmentProcessor( + const AsFPArgs& args) const { + SkASSERT(args.fContext); + SkASSERT(fPtsToUnit.isIdentity()); + sk_sp colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), + args.fDstColorSpace); + sk_sp inner(Gr2PtConicalGradientEffect::Make( + GrGradientEffect::CreateArgs(args.fContext, this, args.fLocalMatrix, fTileMode, + std::move(colorSpaceXform), SkToBool(args.fDstColorSpace)))); + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); +} + +#endif + +sk_sp SkTwoPointConicalGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + SkSTArray<8, SkColor> origColorsStorage(fColorCount); + SkSTArray<8, SkScalar> origPosStorage(fColorCount); + SkSTArray<8, SkColor> xformedColorsStorage(fColorCount); + SkColor* origColors = origColorsStorage.begin(); + SkScalar* origPos = fOrigPos ? origPosStorage.begin() : nullptr; + SkColor* xformedColors = xformedColorsStorage.begin(); + + // Flip if necessary + SkPoint center1 = fFlippedGrad ? fCenter2 : fCenter1; + SkPoint center2 = fFlippedGrad ? fCenter1 : fCenter2; + SkScalar radius1 = fFlippedGrad ? fRadius2 : fRadius1; + SkScalar radius2 = fFlippedGrad ? fRadius1 : fRadius2; + for (int i = 0; i < fColorCount; i++) { + origColors[i] = fFlippedGrad ? fOrigColors[fColorCount - i - 1] : fOrigColors[i]; + if (origPos) { + origPos[i] = fFlippedGrad ? 1.0f - fOrigPos[fColorCount - i - 1] : fOrigPos[i]; + } + } + + xformer->apply(xformedColors, origColors, fColorCount); + return SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, xformedColors, + origPos, fColorCount, fTileMode, fGradFlags, + &this->getLocalMatrix()); +} + + +#ifndef SK_IGNORE_TO_STRING +void SkTwoPointConicalGradient::toString(SkString* str) const { + str->append("SkTwoPointConicalGradient: ("); + + str->append("center1: ("); + str->appendScalar(fCenter1.fX); + str->append(", "); + str->appendScalar(fCenter1.fY); + str->append(") radius1: "); + str->appendScalar(fRadius1); + str->append(" "); + + str->append("center2: ("); + str->appendScalar(fCenter2.fX); + str->append(", "); + str->appendScalar(fCenter2.fY); + str->append(") radius2: "); + str->appendScalar(fRadius2); + str->append(" "); + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif diff --git a/src/effects/gradients/SkTwoPointConicalGradient.h b/src/effects/gradients/SkTwoPointConicalGradient.h new file mode 100644 index 0000000000..b32f52c1e0 --- /dev/null +++ b/src/effects/gradients/SkTwoPointConicalGradient.h @@ -0,0 +1,93 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTwoPointConicalGradient_DEFINED +#define SkTwoPointConicalGradient_DEFINED + +#include "SkColorSpaceXformer.h" +#include "SkGradientShaderPriv.h" + +// TODO(dominikg): Worth making it truly immutable (i.e. set values in constructor)? +// Should only be initialized once via init(). Immutable afterwards. +struct TwoPtRadial { + enum { + // This value is outside the range SK_FixedMin to SK_FixedMax. + kDontDrawT = 0x80000000 + }; + + float fCenterX, fCenterY; + float fDCenterX, fDCenterY; + float fRadius; + float fDRadius; + float fA; + float fRadius2; + float fRDR; + bool fFlipped; + + void init(const SkPoint& center0, SkScalar rad0, + const SkPoint& center1, SkScalar rad1, + bool flipped); + + static bool DontDrawT(SkFixed t) { + return kDontDrawT == (uint32_t)t; + } +}; + + +class SkTwoPointConicalGradient : public SkGradientShaderBase { + TwoPtRadial fRec; +public: + SkTwoPointConicalGradient(const SkPoint& start, SkScalar startRadius, + const SkPoint& end, SkScalar endRadius, + bool flippedGrad, const Descriptor&); + + class TwoPointConicalGradientContext : public SkGradientShaderBase::GradientShaderBaseContext { + public: + TwoPointConicalGradientContext(const SkTwoPointConicalGradient&, const ContextRec&); + ~TwoPointConicalGradientContext() override {} + + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override; + + private: + typedef SkGradientShaderBase::GradientShaderBaseContext INHERITED; + }; + + SkShader::GradientType asAGradient(GradientInfo* info) const override; +#if SK_SUPPORT_GPU + sk_sp asFragmentProcessor(const AsFPArgs&) const override; +#endif + bool isOpaque() const override; + + SkScalar getCenterX1() const { return SkPoint::Distance(fCenter1, fCenter2); } + SkScalar getStartRadius() const { return fRadius1; } + SkScalar getDiffRadius() const { return fRadius2 - fRadius1; } + const SkPoint& getStartCenter() const { return fCenter1; } + const SkPoint& getEndCenter() const { return fCenter2; } + SkScalar getEndRadius() const { return fRadius2; } + bool isFlippedGrad() const { return fFlippedGrad; } + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTwoPointConicalGradient) + +protected: + SkTwoPointConicalGradient(SkReadBuffer& buffer); + void flatten(SkWriteBuffer& buffer) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + sk_sp onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + +private: + SkPoint fCenter1; + SkPoint fCenter2; + SkScalar fRadius1; + SkScalar fRadius2; + bool fFlippedGrad; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; +}; + +#endif diff --git a/src/effects/gradients/SkTwoPointConicalGradient_gpu.cpp b/src/effects/gradients/SkTwoPointConicalGradient_gpu.cpp new file mode 100644 index 0000000000..8402199362 --- /dev/null +++ b/src/effects/gradients/SkTwoPointConicalGradient_gpu.cpp @@ -0,0 +1,1341 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "SkTwoPointConicalGradient.h" + +#if SK_SUPPORT_GPU +#include "GrCoordTransform.h" +#include "GrPaint.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLProgramDataManager.h" +#include "glsl/GrGLSLUniformHandler.h" +#include "SkTwoPointConicalGradient_gpu.h" + +// For brevity +typedef GrGLSLProgramDataManager::UniformHandle UniformHandle; + +static const SkScalar kErrorTol = 0.00001f; +static const SkScalar kEdgeErrorTol = 5.f * kErrorTol; + +/** + * We have three general cases for 2pt conical gradients. First we always assume that + * the start radius <= end radius. Our first case (kInside_) is when the start circle + * is completely enclosed by the end circle. The second case (kOutside_) is the case + * when the start circle is either completely outside the end circle or the circles + * overlap. The final case (kEdge_) is when the start circle is inside the end one, + * but the two are just barely touching at 1 point along their edges. + */ +enum ConicalType { + kInside_ConicalType, + kOutside_ConicalType, + kEdge_ConicalType, +}; + +////////////////////////////////////////////////////////////////////////////// + +static void set_matrix_edge_conical(const SkTwoPointConicalGradient& shader, + SkMatrix* invLMatrix) { + // Inverse of the current local matrix is passed in then, + // translate to center1, rotate so center2 is on x axis. + const SkPoint& center1 = shader.getStartCenter(); + const SkPoint& center2 = shader.getEndCenter(); + + invLMatrix->postTranslate(-center1.fX, -center1.fY); + + SkPoint diff = center2 - center1; + SkScalar diffLen = diff.length(); + if (0 != diffLen) { + SkScalar invDiffLen = SkScalarInvert(diffLen); + SkMatrix rot; + rot.setSinCos(-invDiffLen * diff.fY, invDiffLen * diff.fX); + invLMatrix->postConcat(rot); + } +} + +class Edge2PtConicalEffect : public GrGradientEffect { +public: + class GLSLEdge2PtConicalProcessor; + + static sk_sp Make(const CreateArgs& args) { + return sk_sp(new Edge2PtConicalEffect(args)); + } + + ~Edge2PtConicalEffect() override {} + + const char* name() const override { + return "Two-Point Conical Gradient Edge Touching"; + } + + // The radial gradient parameters can collapse to a linear (instead of quadratic) equation. + SkScalar center() const { return fCenterX1; } + SkScalar diffRadius() const { return fDiffRadius; } + SkScalar radius() const { return fRadius0; } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const Edge2PtConicalEffect& s = sBase.cast(); + return (INHERITED::onIsEqual(sBase) && + this->fCenterX1 == s.fCenterX1 && + this->fRadius0 == s.fRadius0 && + this->fDiffRadius == s.fDiffRadius); + } + + Edge2PtConicalEffect(const CreateArgs& args) + : INHERITED(args, false /* opaque: draws transparent black outside of the cone. */) { + const SkTwoPointConicalGradient& shader = + *static_cast(args.fShader); + fCenterX1 = shader.getCenterX1(); + fRadius0 = shader.getStartRadius(); + fDiffRadius = shader.getDiffRadius(); + this->initClassID(); + // We should only be calling this shader if we are degenerate case with touching circles + // When deciding if we are in edge case, we scaled by the end radius for cases when the + // start radius was close to zero, otherwise we scaled by the start radius. In addition + // Our test for the edge case in set_matrix_circle_conical has a higher tolerance so we + // need the sqrt value below + SkASSERT(SkScalarAbs(SkScalarAbs(fDiffRadius) - fCenterX1) < + (fRadius0 < kErrorTol ? shader.getEndRadius() * kEdgeErrorTol : + fRadius0 * sqrt(kEdgeErrorTol))); + + // We pass the linear part of the quadratic as a varying. + // float b = -2.0 * (fCenterX1 * x + fRadius0 * fDiffRadius * z) + fBTransform = this->getCoordTransform(); + SkMatrix& bMatrix = *fBTransform.accessMatrix(); + SkScalar r0dr = fRadius0 * fDiffRadius; + bMatrix[SkMatrix::kMScaleX] = -2 * (fCenterX1 * bMatrix[SkMatrix::kMScaleX] + + r0dr * bMatrix[SkMatrix::kMPersp0]); + bMatrix[SkMatrix::kMSkewX] = -2 * (fCenterX1 * bMatrix[SkMatrix::kMSkewX] + + r0dr * bMatrix[SkMatrix::kMPersp1]); + bMatrix[SkMatrix::kMTransX] = -2 * (fCenterX1 * bMatrix[SkMatrix::kMTransX] + + r0dr * bMatrix[SkMatrix::kMPersp2]); + this->addCoordTransform(&fBTransform); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + // @{ + // Cache of values - these can change arbitrarily, EXCEPT + // we shouldn't change between degenerate and non-degenerate?! + + GrCoordTransform fBTransform; + SkScalar fCenterX1; + SkScalar fRadius0; + SkScalar fDiffRadius; + + // @} + + typedef GrGradientEffect INHERITED; +}; + +class Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor : public GrGradientEffect::GLSLProcessor { +public: + GLSLEdge2PtConicalProcessor(const GrProcessor&); + ~GLSLEdge2PtConicalProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrShaderCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + + UniformHandle fParamUni; + + const char* fVSVaryingName; + const char* fFSVaryingName; + + // @{ + /// Values last uploaded as uniforms + + SkScalar fCachedRadius; + SkScalar fCachedDiffRadius; + + // @} + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +void Edge2PtConicalEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* Edge2PtConicalEffect::onCreateGLSLInstance() const { + return new Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor(*this); +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(Edge2PtConicalEffect); + +/* + * All Two point conical gradient test create functions may occasionally create edge case shaders + */ +#if GR_TEST_UTILS +sk_sp Edge2PtConicalEffect::TestCreate(GrProcessorTestData* d) { + SkPoint center1 = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius1 = d->fRandom->nextUScalar1(); + SkPoint center2; + SkScalar radius2; + do { + center2.set(d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()); + // If the circles are identical the factory will give us an empty shader. + // This will happen if we pick identical centers + } while (center1 == center2); + + // Below makes sure that circle one is contained within circle two + // and both circles are touching on an edge + SkPoint diff = center2 - center1; + SkScalar diffLen = diff.length(); + radius2 = radius1 + diffLen; + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor::GLSLEdge2PtConicalProcessor(const GrProcessor&) + : fVSVaryingName(nullptr) + , fFSVaryingName(nullptr) + , fCachedRadius(-SK_ScalarMax) + , fCachedDiffRadius(-SK_ScalarMax) {} + +void Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor::emitCode(EmitArgs& args) { + const Edge2PtConicalEffect& ge = args.fFp.cast(); + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + this->emitUniforms(uniformHandler, ge); + fParamUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec3f_GrSLType, kDefault_GrSLPrecision, + "Conical2FSParams"); + + SkString cName("c"); + SkString tName("t"); + SkString p0; // start radius + SkString p1; // start radius squared + SkString p2; // difference in radii (r1 - r0) + + + p0.appendf("%s.x", uniformHandler->getUniformVariable(fParamUni).getName().c_str()); + p1.appendf("%s.y", uniformHandler->getUniformVariable(fParamUni).getName().c_str()); + p2.appendf("%s.z", uniformHandler->getUniformVariable(fParamUni).getName().c_str()); + + // We interpolate the linear component in coords[1]. + SkASSERT(args.fTransformedCoords[0].getType() == args.fTransformedCoords[1].getType()); + const char* coords2D; + SkString bVar; + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + if (kVec3f_GrSLType == args.fTransformedCoords[0].getType()) { + fragBuilder->codeAppendf("\tvec3 interpolants = vec3(%s.xy / %s.z, %s.x / %s.z);\n", + args.fTransformedCoords[0].c_str(), + args.fTransformedCoords[0].c_str(), + args.fTransformedCoords[1].c_str(), + args.fTransformedCoords[1].c_str()); + coords2D = "interpolants.xy"; + bVar = "interpolants.z"; + } else { + coords2D = args.fTransformedCoords[0].c_str(); + bVar.printf("%s.x", args.fTransformedCoords[1].c_str()); + } + + // output will default to transparent black (we simply won't write anything + // else to it if invalid, instead of discarding or returning prematurely) + fragBuilder->codeAppendf("\t%s = vec4(0.0,0.0,0.0,0.0);\n", args.fOutputColor); + + // c = (x^2)+(y^2) - params[1] + fragBuilder->codeAppendf("\tfloat %s = dot(%s, %s) - %s;\n", + cName.c_str(), coords2D, coords2D, p1.c_str()); + + // linear case: t = -c/b + fragBuilder->codeAppendf("\tfloat %s = -(%s / %s);\n", tName.c_str(), + cName.c_str(), bVar.c_str()); + + // if r(t) > 0, then t will be the x coordinate + fragBuilder->codeAppendf("\tif (%s * %s + %s > 0.0) {\n", tName.c_str(), + p2.c_str(), p0.c_str()); + fragBuilder->codeAppend("\t"); + this->emitColor(fragBuilder, + uniformHandler, + args.fShaderCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); + fragBuilder->codeAppend("\t}\n"); +} + +void Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + const Edge2PtConicalEffect& data = processor.cast(); + SkScalar radius0 = data.radius(); + SkScalar diffRadius = data.diffRadius(); + + if (fCachedRadius != radius0 || + fCachedDiffRadius != diffRadius) { + + pdman.set3f(fParamUni, radius0, radius0 * radius0, diffRadius); + fCachedRadius = radius0; + fCachedDiffRadius = diffRadius; + } +} + +void Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor::GenKey(const GrProcessor& processor, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); +} + +////////////////////////////////////////////////////////////////////////////// +// Focal Conical Gradients +////////////////////////////////////////////////////////////////////////////// + +static ConicalType set_matrix_focal_conical(const SkTwoPointConicalGradient& shader, + SkMatrix* invLMatrix, SkScalar* focalX) { + // Inverse of the current local matrix is passed in then, + // translate, scale, and rotate such that endCircle is unit circle on x-axis, + // and focal point is at the origin. + ConicalType conicalType; + const SkPoint& focal = shader.getStartCenter(); + const SkPoint& centerEnd = shader.getEndCenter(); + SkScalar radius = shader.getEndRadius(); + SkScalar invRadius = 1.f / radius; + + SkMatrix matrix; + + matrix.setTranslate(-centerEnd.fX, -centerEnd.fY); + matrix.postScale(invRadius, invRadius); + + SkPoint focalTrans; + matrix.mapPoints(&focalTrans, &focal, 1); + *focalX = focalTrans.length(); + + if (0.f != *focalX) { + SkScalar invFocalX = SkScalarInvert(*focalX); + SkMatrix rot; + rot.setSinCos(-invFocalX * focalTrans.fY, invFocalX * focalTrans.fX); + matrix.postConcat(rot); + } + + matrix.postTranslate(-(*focalX), 0.f); + + // If the focal point is touching the edge of the circle it will + // cause a degenerate case that must be handled separately + // kEdgeErrorTol = 5 * kErrorTol was picked after manual testing the + // stability trade off versus the linear approx used in the Edge Shader + if (SkScalarAbs(1.f - (*focalX)) < kEdgeErrorTol) { + return kEdge_ConicalType; + } + + // Scale factor 1 / (1 - focalX * focalX) + SkScalar oneMinusF2 = 1.f - *focalX * *focalX; + SkScalar s = SkScalarInvert(oneMinusF2); + + + if (s >= 0.f) { + conicalType = kInside_ConicalType; + matrix.postScale(s, s * SkScalarSqrt(oneMinusF2)); + } else { + conicalType = kOutside_ConicalType; + matrix.postScale(s, s); + } + + invLMatrix->postConcat(matrix); + + return conicalType; +} + +////////////////////////////////////////////////////////////////////////////// + +class FocalOutside2PtConicalEffect : public GrGradientEffect { +public: + class GLSLFocalOutside2PtConicalProcessor; + + static sk_sp Make(const CreateArgs& args, SkScalar focalX) { + return sk_sp( + new FocalOutside2PtConicalEffect(args, focalX)); + } + + ~FocalOutside2PtConicalEffect() override {} + + const char* name() const override { + return "Two-Point Conical Gradient Focal Outside"; + } + + bool isFlipped() const { return fIsFlipped; } + SkScalar focal() const { return fFocalX; } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const FocalOutside2PtConicalEffect& s = sBase.cast(); + return (INHERITED::onIsEqual(sBase) && + this->fFocalX == s.fFocalX && + this->fIsFlipped == s.fIsFlipped); + } + + static bool IsFlipped(const CreateArgs& args) { + // eww. + return static_cast(args.fShader)->isFlippedGrad(); + } + + FocalOutside2PtConicalEffect(const CreateArgs& args, SkScalar focalX) + : INHERITED(args, false /* opaque: draws transparent black outside of the cone. */) + , fFocalX(focalX) + , fIsFlipped(IsFlipped(args)) { + this->initClassID(); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + SkScalar fFocalX; + bool fIsFlipped; + + typedef GrGradientEffect INHERITED; +}; + +class FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor + : public GrGradientEffect::GLSLProcessor { +public: + GLSLFocalOutside2PtConicalProcessor(const GrProcessor&); + ~GLSLFocalOutside2PtConicalProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrShaderCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + + UniformHandle fParamUni; + + const char* fVSVaryingName; + const char* fFSVaryingName; + + bool fIsFlipped; + + // @{ + /// Values last uploaded as uniforms + + SkScalar fCachedFocal; + + // @} + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +void FocalOutside2PtConicalEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* FocalOutside2PtConicalEffect::onCreateGLSLInstance() const { + return new FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor(*this); +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(FocalOutside2PtConicalEffect); + +/* + * All Two point conical gradient test create functions may occasionally create edge case shaders + */ +#if GR_TEST_UTILS +sk_sp FocalOutside2PtConicalEffect::TestCreate(GrProcessorTestData* d) { + SkPoint center1 = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius1 = 0.f; + SkPoint center2; + SkScalar radius2; + do { + center2.set(d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()); + // Need to make sure the centers are not the same or else focal point will be inside + } while (center1 == center2); + + SkPoint diff = center2 - center1; + SkScalar diffLen = diff.length(); + // Below makes sure that the focal point is not contained within circle two + radius2 = d->fRandom->nextRangeF(0.f, diffLen); + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor + ::GLSLFocalOutside2PtConicalProcessor(const GrProcessor& processor) + : fVSVaryingName(nullptr) + , fFSVaryingName(nullptr) + , fCachedFocal(SK_ScalarMax) { + const FocalOutside2PtConicalEffect& data = processor.cast(); + fIsFlipped = data.isFlipped(); +} + +void FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor::emitCode(EmitArgs& args) { + const FocalOutside2PtConicalEffect& ge = args.fFp.cast(); + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + this->emitUniforms(uniformHandler, ge); + fParamUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec2f_GrSLType, kDefault_GrSLPrecision, + "Conical2FSParams"); + SkString tName("t"); + SkString p0; // focalX + SkString p1; // 1 - focalX * focalX + + p0.appendf("%s.x", uniformHandler->getUniformVariable(fParamUni).getName().c_str()); + p1.appendf("%s.y", uniformHandler->getUniformVariable(fParamUni).getName().c_str()); + + // if we have a vec3 from being in perspective, convert it to a vec2 first + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2DString = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + const char* coords2D = coords2DString.c_str(); + + // t = p.x * focal.x +/- sqrt(p.x^2 + (1 - focal.x^2) * p.y^2) + + // output will default to transparent black (we simply won't write anything + // else to it if invalid, instead of discarding or returning prematurely) + fragBuilder->codeAppendf("\t%s = vec4(0.0,0.0,0.0,0.0);\n", args.fOutputColor); + + fragBuilder->codeAppendf("\tfloat xs = %s.x * %s.x;\n", coords2D, coords2D); + fragBuilder->codeAppendf("\tfloat ys = %s.y * %s.y;\n", coords2D, coords2D); + fragBuilder->codeAppendf("\tfloat d = xs + %s * ys;\n", p1.c_str()); + + // Must check to see if we flipped the circle order (to make sure start radius < end radius) + // If so we must also flip sign on sqrt + if (!fIsFlipped) { + fragBuilder->codeAppendf("\tfloat %s = %s.x * %s + sqrt(d);\n", tName.c_str(), + coords2D, p0.c_str()); + } else { + fragBuilder->codeAppendf("\tfloat %s = %s.x * %s - sqrt(d);\n", tName.c_str(), + coords2D, p0.c_str()); + } + + fragBuilder->codeAppendf("\tif (%s >= 0.0 && d >= 0.0) {\n", tName.c_str()); + fragBuilder->codeAppend("\t\t"); + this->emitColor(fragBuilder, + uniformHandler, + args.fShaderCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); + fragBuilder->codeAppend("\t}\n"); +} + +void FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + const FocalOutside2PtConicalEffect& data = processor.cast(); + SkASSERT(data.isFlipped() == fIsFlipped); + SkScalar focal = data.focal(); + + if (fCachedFocal != focal) { + SkScalar oneMinus2F = 1.f - focal * focal; + + pdman.set2f(fParamUni, SkScalarToFloat(focal), SkScalarToFloat(oneMinus2F)); + fCachedFocal = focal; + } +} + +void FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor::GenKey( + const GrProcessor& processor, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + uint32_t* key = b->add32n(2); + key[0] = GenBaseGradientKey(processor); + key[1] = processor.cast().isFlipped(); +} + +////////////////////////////////////////////////////////////////////////////// + +class FocalInside2PtConicalEffect : public GrGradientEffect { +public: + class GLSLFocalInside2PtConicalProcessor; + + static sk_sp Make(const CreateArgs& args, SkScalar focalX) { + return sk_sp( + new FocalInside2PtConicalEffect(args, focalX)); + } + + ~FocalInside2PtConicalEffect() override {} + + const char* name() const override { + return "Two-Point Conical Gradient Focal Inside"; + } + + SkScalar focal() const { return fFocalX; } + + typedef FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor GLSLProcessor; + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const FocalInside2PtConicalEffect& s = sBase.cast(); + return (INHERITED::onIsEqual(sBase) && + this->fFocalX == s.fFocalX); + } + + FocalInside2PtConicalEffect(const CreateArgs& args, SkScalar focalX) + : INHERITED(args, args.fShader->colorsAreOpaque()), fFocalX(focalX) { + this->initClassID(); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + SkScalar fFocalX; + + typedef GrGradientEffect INHERITED; +}; + +class FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor + : public GrGradientEffect::GLSLProcessor { +public: + GLSLFocalInside2PtConicalProcessor(const GrProcessor&); + ~GLSLFocalInside2PtConicalProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrShaderCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + + UniformHandle fFocalUni; + + const char* fVSVaryingName; + const char* fFSVaryingName; + + // @{ + /// Values last uploaded as uniforms + + SkScalar fCachedFocal; + + // @} + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +void FocalInside2PtConicalEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* FocalInside2PtConicalEffect::onCreateGLSLInstance() const { + return new FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor(*this); +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(FocalInside2PtConicalEffect); + +/* + * All Two point conical gradient test create functions may occasionally create edge case shaders + */ +#if GR_TEST_UTILS +sk_sp FocalInside2PtConicalEffect::TestCreate(GrProcessorTestData* d) { + SkPoint center1 = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius1 = 0.f; + SkPoint center2; + SkScalar radius2; + do { + center2.set(d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()); + // Below makes sure radius2 is larger enouch such that the focal point + // is inside the end circle + SkScalar increase = d->fRandom->nextUScalar1(); + SkPoint diff = center2 - center1; + SkScalar diffLen = diff.length(); + radius2 = diffLen + increase; + // If the circles are identical the factory will give us an empty shader. + } while (radius1 == radius2 && center1 == center2); + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor + ::GLSLFocalInside2PtConicalProcessor(const GrProcessor&) + : fVSVaryingName(nullptr) + , fFSVaryingName(nullptr) + , fCachedFocal(SK_ScalarMax) {} + +void FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor::emitCode(EmitArgs& args) { + const FocalInside2PtConicalEffect& ge = args.fFp.cast(); + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + this->emitUniforms(uniformHandler, ge); + fFocalUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat_GrSLType, kDefault_GrSLPrecision, + "Conical2FSParams"); + SkString tName("t"); + + // this is the distance along x-axis from the end center to focal point in + // transformed coordinates + GrShaderVar focal = uniformHandler->getUniformVariable(fFocalUni); + + // if we have a vec3 from being in perspective, convert it to a vec2 first + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2DString = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + const char* coords2D = coords2DString.c_str(); + + // t = p.x * focalX + length(p) + fragBuilder->codeAppendf("\tfloat %s = %s.x * %s + length(%s);\n", tName.c_str(), + coords2D, focal.c_str(), coords2D); + + this->emitColor(fragBuilder, + uniformHandler, + args.fShaderCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); +} + +void FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + const FocalInside2PtConicalEffect& data = processor.cast(); + SkScalar focal = data.focal(); + + if (fCachedFocal != focal) { + pdman.set1f(fFocalUni, SkScalarToFloat(focal)); + fCachedFocal = focal; + } +} + +void FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor::GenKey( + const GrProcessor& processor, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); +} + +////////////////////////////////////////////////////////////////////////////// +// Circle Conical Gradients +////////////////////////////////////////////////////////////////////////////// + +struct CircleConicalInfo { + SkPoint fCenterEnd; + SkScalar fA; + SkScalar fB; + SkScalar fC; +}; + +// Returns focal distance along x-axis in transformed coords +static ConicalType set_matrix_circle_conical(const SkTwoPointConicalGradient& shader, + SkMatrix* invLMatrix, CircleConicalInfo* info) { + // Inverse of the current local matrix is passed in then, + // translate and scale such that start circle is on the origin and has radius 1 + const SkPoint& centerStart = shader.getStartCenter(); + const SkPoint& centerEnd = shader.getEndCenter(); + SkScalar radiusStart = shader.getStartRadius(); + SkScalar radiusEnd = shader.getEndRadius(); + + SkMatrix matrix; + + matrix.setTranslate(-centerStart.fX, -centerStart.fY); + + SkScalar invStartRad = 1.f / radiusStart; + matrix.postScale(invStartRad, invStartRad); + + radiusEnd /= radiusStart; + + SkPoint centerEndTrans; + matrix.mapPoints(¢erEndTrans, ¢erEnd, 1); + + SkScalar A = centerEndTrans.fX * centerEndTrans.fX + centerEndTrans.fY * centerEndTrans.fY + - radiusEnd * radiusEnd + 2 * radiusEnd - 1; + + // Check to see if start circle is inside end circle with edges touching. + // If touching we return that it is of kEdge_ConicalType, and leave the matrix setting + // to the edge shader. kEdgeErrorTol = 5 * kErrorTol was picked after manual testing + // so that C = 1 / A is stable, and the linear approximation used in the Edge shader is + // still accurate. + if (SkScalarAbs(A) < kEdgeErrorTol) { + return kEdge_ConicalType; + } + + SkScalar C = 1.f / A; + SkScalar B = (radiusEnd - 1.f) * C; + + matrix.postScale(C, C); + + invLMatrix->postConcat(matrix); + + info->fCenterEnd = centerEndTrans; + info->fA = A; + info->fB = B; + info->fC = C; + + // if A ends up being negative, the start circle is contained completely inside the end cirlce + if (A < 0.f) { + return kInside_ConicalType; + } + return kOutside_ConicalType; +} + +class CircleInside2PtConicalEffect : public GrGradientEffect { +public: + class GLSLCircleInside2PtConicalProcessor; + + static sk_sp Make(const CreateArgs& args, const CircleConicalInfo& info) { + return sk_sp( + new CircleInside2PtConicalEffect(args, info)); + } + + ~CircleInside2PtConicalEffect() override {} + + const char* name() const override { return "Two-Point Conical Gradient Inside"; } + + SkScalar centerX() const { return fInfo.fCenterEnd.fX; } + SkScalar centerY() const { return fInfo.fCenterEnd.fY; } + SkScalar A() const { return fInfo.fA; } + SkScalar B() const { return fInfo.fB; } + SkScalar C() const { return fInfo.fC; } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override; + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const CircleInside2PtConicalEffect& s = sBase.cast(); + return (INHERITED::onIsEqual(sBase) && + this->fInfo.fCenterEnd == s.fInfo.fCenterEnd && + this->fInfo.fA == s.fInfo.fA && + this->fInfo.fB == s.fInfo.fB && + this->fInfo.fC == s.fInfo.fC); + } + + CircleInside2PtConicalEffect(const CreateArgs& args, const CircleConicalInfo& info) + : INHERITED(args, args.fShader->colorsAreOpaque()), fInfo(info) { + this->initClassID(); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + const CircleConicalInfo fInfo; + + typedef GrGradientEffect INHERITED; +}; + +class CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor + : public GrGradientEffect::GLSLProcessor { +public: + GLSLCircleInside2PtConicalProcessor(const GrProcessor&); + ~GLSLCircleInside2PtConicalProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrShaderCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + + UniformHandle fCenterUni; + UniformHandle fParamUni; + + const char* fVSVaryingName; + const char* fFSVaryingName; + + // @{ + /// Values last uploaded as uniforms + + SkScalar fCachedCenterX; + SkScalar fCachedCenterY; + SkScalar fCachedA; + SkScalar fCachedB; + SkScalar fCachedC; + + // @} + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +void CircleInside2PtConicalEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* CircleInside2PtConicalEffect::onCreateGLSLInstance() const { + return new CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor(*this); +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(CircleInside2PtConicalEffect); + +/* + * All Two point conical gradient test create functions may occasionally create edge case shaders + */ +#if GR_TEST_UTILS +sk_sp CircleInside2PtConicalEffect::TestCreate(GrProcessorTestData* d) { + SkPoint center1 = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius1 = d->fRandom->nextUScalar1() + 0.0001f; // make sure radius1 != 0 + SkPoint center2; + SkScalar radius2; + do { + center2.set(d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()); + // Below makes sure that circle one is contained within circle two + SkScalar increase = d->fRandom->nextUScalar1(); + SkPoint diff = center2 - center1; + SkScalar diffLen = diff.length(); + radius2 = radius1 + diffLen + increase; + // If the circles are identical the factory will give us an empty shader. + } while (radius1 == radius2 && center1 == center2); + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor + ::GLSLCircleInside2PtConicalProcessor(const GrProcessor& processor) + : fVSVaryingName(nullptr) + , fFSVaryingName(nullptr) + , fCachedCenterX(SK_ScalarMax) + , fCachedCenterY(SK_ScalarMax) + , fCachedA(SK_ScalarMax) + , fCachedB(SK_ScalarMax) + , fCachedC(SK_ScalarMax) {} + +void CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor::emitCode(EmitArgs& args) { + const CircleInside2PtConicalEffect& ge = args.fFp.cast(); + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + this->emitUniforms(uniformHandler, ge); + fCenterUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec2f_GrSLType, kDefault_GrSLPrecision, + "Conical2FSCenter"); + fParamUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec3f_GrSLType, kDefault_GrSLPrecision, + "Conical2FSParams"); + SkString tName("t"); + + GrShaderVar center = uniformHandler->getUniformVariable(fCenterUni); + // params.x = A + // params.y = B + // params.z = C + GrShaderVar params = uniformHandler->getUniformVariable(fParamUni); + + // if we have a vec3 from being in perspective, convert it to a vec2 first + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2DString = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + const char* coords2D = coords2DString.c_str(); + + // p = coords2D + // e = center end + // r = radius end + // A = dot(e, e) - r^2 + 2 * r - 1 + // B = (r -1) / A + // C = 1 / A + // d = dot(e, p) + B + // t = d +/- sqrt(d^2 - A * dot(p, p) + C) + fragBuilder->codeAppendf("\tfloat pDotp = dot(%s, %s);\n", coords2D, coords2D); + fragBuilder->codeAppendf("\tfloat d = dot(%s, %s) + %s.y;\n", coords2D, center.c_str(), + params.c_str()); + fragBuilder->codeAppendf("\tfloat %s = d + sqrt(d * d - %s.x * pDotp + %s.z);\n", + tName.c_str(), params.c_str(), params.c_str()); + + this->emitColor(fragBuilder, + uniformHandler, + args.fShaderCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); +} + +void CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + const CircleInside2PtConicalEffect& data = processor.cast(); + SkScalar centerX = data.centerX(); + SkScalar centerY = data.centerY(); + SkScalar A = data.A(); + SkScalar B = data.B(); + SkScalar C = data.C(); + + if (fCachedCenterX != centerX || fCachedCenterY != centerY || + fCachedA != A || fCachedB != B || fCachedC != C) { + + pdman.set2f(fCenterUni, SkScalarToFloat(centerX), SkScalarToFloat(centerY)); + pdman.set3f(fParamUni, SkScalarToFloat(A), SkScalarToFloat(B), SkScalarToFloat(C)); + + fCachedCenterX = centerX; + fCachedCenterY = centerY; + fCachedA = A; + fCachedB = B; + fCachedC = C; + } +} + +void CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor::GenKey( + const GrProcessor& processor, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); +} + +////////////////////////////////////////////////////////////////////////////// + +class CircleOutside2PtConicalEffect : public GrGradientEffect { +public: + class GLSLCircleOutside2PtConicalProcessor; + + static sk_sp Make(const CreateArgs& args, const CircleConicalInfo& info) { + return sk_sp( + new CircleOutside2PtConicalEffect(args, info)); + } + + ~CircleOutside2PtConicalEffect() override {} + + const char* name() const override { return "Two-Point Conical Gradient Outside"; } + + SkScalar centerX() const { return fInfo.fCenterEnd.fX; } + SkScalar centerY() const { return fInfo.fCenterEnd.fY; } + SkScalar A() const { return fInfo.fA; } + SkScalar B() const { return fInfo.fB; } + SkScalar C() const { return fInfo.fC; } + SkScalar tLimit() const { return fTLimit; } + bool isFlipped() const { return fIsFlipped; } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const CircleOutside2PtConicalEffect& s = sBase.cast(); + return (INHERITED::onIsEqual(sBase) && + this->fInfo.fCenterEnd == s.fInfo.fCenterEnd && + this->fInfo.fA == s.fInfo.fA && + this->fInfo.fB == s.fInfo.fB && + this->fInfo.fC == s.fInfo.fC && + this->fTLimit == s.fTLimit && + this->fIsFlipped == s.fIsFlipped); + } + + CircleOutside2PtConicalEffect(const CreateArgs& args, const CircleConicalInfo& info) + : INHERITED(args, false /* opaque: draws transparent black outside of the cone. */) + , fInfo(info) { + this->initClassID(); + const SkTwoPointConicalGradient& shader = + *static_cast(args.fShader); + if (shader.getStartRadius() != shader.getEndRadius()) { + fTLimit = shader.getStartRadius() / (shader.getStartRadius() - shader.getEndRadius()); + } else { + fTLimit = SK_ScalarMin; + } + + fIsFlipped = shader.isFlippedGrad(); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + const CircleConicalInfo fInfo; + SkScalar fTLimit; + bool fIsFlipped; + + typedef GrGradientEffect INHERITED; +}; + +class CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor + : public GrGradientEffect::GLSLProcessor { +public: + GLSLCircleOutside2PtConicalProcessor(const GrProcessor&); + ~GLSLCircleOutside2PtConicalProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrShaderCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + + UniformHandle fCenterUni; + UniformHandle fParamUni; + + const char* fVSVaryingName; + const char* fFSVaryingName; + + bool fIsFlipped; + + // @{ + /// Values last uploaded as uniforms + + SkScalar fCachedCenterX; + SkScalar fCachedCenterY; + SkScalar fCachedA; + SkScalar fCachedB; + SkScalar fCachedC; + SkScalar fCachedTLimit; + + // @} + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +void CircleOutside2PtConicalEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* CircleOutside2PtConicalEffect::onCreateGLSLInstance() const { + return new CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor(*this); +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(CircleOutside2PtConicalEffect); + +/* + * All Two point conical gradient test create functions may occasionally create edge case shaders + */ +#if GR_TEST_UTILS +sk_sp CircleOutside2PtConicalEffect::TestCreate(GrProcessorTestData* d) { + SkPoint center1 = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius1 = d->fRandom->nextUScalar1() + 0.0001f; // make sure radius1 != 0 + SkPoint center2; + SkScalar radius2; + SkScalar diffLen; + do { + center2.set(d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()); + // If the circles share a center than we can't be in the outside case + } while (center1 == center2); + SkPoint diff = center2 - center1; + diffLen = diff.length(); + // Below makes sure that circle one is not contained within circle two + // and have radius2 >= radius to match sorting on cpu side + radius2 = radius1 + d->fRandom->nextRangeF(0.f, diffLen); + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor + ::GLSLCircleOutside2PtConicalProcessor(const GrProcessor& processor) + : fVSVaryingName(nullptr) + , fFSVaryingName(nullptr) + , fCachedCenterX(SK_ScalarMax) + , fCachedCenterY(SK_ScalarMax) + , fCachedA(SK_ScalarMax) + , fCachedB(SK_ScalarMax) + , fCachedC(SK_ScalarMax) + , fCachedTLimit(SK_ScalarMax) { + const CircleOutside2PtConicalEffect& data = processor.cast(); + fIsFlipped = data.isFlipped(); + } + +void CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor::emitCode(EmitArgs& args) { + const CircleOutside2PtConicalEffect& ge = args.fFp.cast(); + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + this->emitUniforms(uniformHandler, ge); + fCenterUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec2f_GrSLType, kDefault_GrSLPrecision, + "Conical2FSCenter"); + fParamUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec4f_GrSLType, kDefault_GrSLPrecision, + "Conical2FSParams"); + SkString tName("t"); + + GrShaderVar center = uniformHandler->getUniformVariable(fCenterUni); + // params.x = A + // params.y = B + // params.z = C + GrShaderVar params = uniformHandler->getUniformVariable(fParamUni); + + // if we have a vec3 from being in perspective, convert it to a vec2 first + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2DString = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + const char* coords2D = coords2DString.c_str(); + + // output will default to transparent black (we simply won't write anything + // else to it if invalid, instead of discarding or returning prematurely) + fragBuilder->codeAppendf("\t%s = vec4(0.0,0.0,0.0,0.0);\n", args.fOutputColor); + + // p = coords2D + // e = center end + // r = radius end + // A = dot(e, e) - r^2 + 2 * r - 1 + // B = (r -1) / A + // C = 1 / A + // d = dot(e, p) + B + // t = d +/- sqrt(d^2 - A * dot(p, p) + C) + + fragBuilder->codeAppendf("\tfloat pDotp = dot(%s, %s);\n", coords2D, coords2D); + fragBuilder->codeAppendf("\tfloat d = dot(%s, %s) + %s.y;\n", coords2D, center.c_str(), + params.c_str()); + fragBuilder->codeAppendf("\tfloat deter = d * d - %s.x * pDotp + %s.z;\n", params.c_str(), + params.c_str()); + + // Must check to see if we flipped the circle order (to make sure start radius < end radius) + // If so we must also flip sign on sqrt + if (!fIsFlipped) { + fragBuilder->codeAppendf("\tfloat %s = d + sqrt(deter);\n", tName.c_str()); + } else { + fragBuilder->codeAppendf("\tfloat %s = d - sqrt(deter);\n", tName.c_str()); + } + + fragBuilder->codeAppendf("\tif (%s >= %s.w && deter >= 0.0) {\n", + tName.c_str(), params.c_str()); + fragBuilder->codeAppend("\t\t"); + this->emitColor(fragBuilder, + uniformHandler, + args.fShaderCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); + fragBuilder->codeAppend("\t}\n"); +} + +void CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + const CircleOutside2PtConicalEffect& data = processor.cast(); + SkASSERT(data.isFlipped() == fIsFlipped); + SkScalar centerX = data.centerX(); + SkScalar centerY = data.centerY(); + SkScalar A = data.A(); + SkScalar B = data.B(); + SkScalar C = data.C(); + SkScalar tLimit = data.tLimit(); + + if (fCachedCenterX != centerX || fCachedCenterY != centerY || + fCachedA != A || fCachedB != B || fCachedC != C || fCachedTLimit != tLimit) { + + pdman.set2f(fCenterUni, SkScalarToFloat(centerX), SkScalarToFloat(centerY)); + pdman.set4f(fParamUni, SkScalarToFloat(A), SkScalarToFloat(B), SkScalarToFloat(C), + SkScalarToFloat(tLimit)); + + fCachedCenterX = centerX; + fCachedCenterY = centerY; + fCachedA = A; + fCachedB = B; + fCachedC = C; + fCachedTLimit = tLimit; + } +} + +void CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor::GenKey( + const GrProcessor& processor, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + uint32_t* key = b->add32n(2); + key[0] = GenBaseGradientKey(processor); + key[1] = processor.cast().isFlipped(); +} + +////////////////////////////////////////////////////////////////////////////// + +sk_sp Gr2PtConicalGradientEffect::Make( + const GrGradientEffect::CreateArgs& args) { + const SkTwoPointConicalGradient& shader = + *static_cast(args.fShader); + + SkMatrix matrix; + if (!shader.getLocalMatrix().invert(&matrix)) { + return nullptr; + } + if (args.fMatrix) { + SkMatrix inv; + if (!args.fMatrix->invert(&inv)) { + return nullptr; + } + matrix.postConcat(inv); + } + + GrGradientEffect::CreateArgs newArgs(args.fContext, args.fShader, &matrix, args.fTileMode, + std::move(args.fColorSpaceXform), args.fGammaCorrect); + + if (shader.getStartRadius() < kErrorTol) { + SkScalar focalX; + ConicalType type = set_matrix_focal_conical(shader, &matrix, &focalX); + if (type == kInside_ConicalType) { + return FocalInside2PtConicalEffect::Make(newArgs, focalX); + } else if(type == kEdge_ConicalType) { + set_matrix_edge_conical(shader, &matrix); + return Edge2PtConicalEffect::Make(newArgs); + } else { + return FocalOutside2PtConicalEffect::Make(newArgs, focalX); + } + } + + CircleConicalInfo info; + ConicalType type = set_matrix_circle_conical(shader, &matrix, &info); + + if (type == kInside_ConicalType) { + return CircleInside2PtConicalEffect::Make(newArgs, info); + } else if (type == kEdge_ConicalType) { + set_matrix_edge_conical(shader, &matrix); + return Edge2PtConicalEffect::Make(newArgs); + } else { + return CircleOutside2PtConicalEffect::Make(newArgs, info); + } +} + +#endif diff --git a/src/effects/gradients/SkTwoPointConicalGradient_gpu.h b/src/effects/gradients/SkTwoPointConicalGradient_gpu.h new file mode 100644 index 0000000000..46edb1f7d1 --- /dev/null +++ b/src/effects/gradients/SkTwoPointConicalGradient_gpu.h @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTwoPointConicalGradient_gpu_DEFINED +#define SkTwoPointConicalGradient_gpu_DEFINED + +#include "SkGradientShaderPriv.h" + +class GrProcessor; +class SkTwoPointConicalGradient; + +namespace Gr2PtConicalGradientEffect { + /** + * Creates an effect that produces a two point conical gradient based on the + * shader passed in. + */ + sk_sp Make(const GrGradientEffect::CreateArgs& args); +}; + +#endif -- cgit v1.2.3