aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/shaders
diff options
context:
space:
mode:
authorGravatar Florin Malita <fmalita@chromium.org>2017-05-30 16:39:47 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-05-30 21:01:46 +0000
commit5edba45dca995baed5e66dfaaa7859132e716314 (patch)
tree0333422088e6ed781db5c6e2cf4ed330b69a0957 /src/shaders
parent64790a3714467300848971aa153aca8cea91cf7b (diff)
[Reland] Relocate shaders to own dir
Consolidate all shader impls under src/shaders/. (reland of https://skia-review.googlesource.com/c/17927/) Change-Id: I7918bdc1aafe842ed194412ba95b9ae53a2ec1d7 Reviewed-on: https://skia-review.googlesource.com/18146 Reviewed-by: Florin Malita <fmalita@chromium.org> Commit-Queue: Florin Malita <fmalita@chromium.org>
Diffstat (limited to 'src/shaders')
-rw-r--r--src/shaders/SkBitmapProcShader.cpp206
-rw-r--r--src/shaders/SkBitmapProcShader.h26
-rw-r--r--src/shaders/SkColorFilterShader.cpp138
-rw-r--r--src/shaders/SkColorFilterShader.h60
-rw-r--r--src/shaders/SkColorShader.cpp274
-rw-r--r--src/shaders/SkColorShader.h136
-rw-r--r--src/shaders/SkComposeShader.cpp311
-rw-r--r--src/shaders/SkComposeShader.h88
-rw-r--r--src/shaders/SkEmptyShader.h41
-rw-r--r--src/shaders/SkImageShader.cpp391
-rw-r--r--src/shaders/SkImageShader.h58
-rw-r--r--src/shaders/SkLightingShader.cpp488
-rw-r--r--src/shaders/SkLightingShader.h39
-rw-r--r--src/shaders/SkLocalMatrixShader.cpp110
-rw-r--r--src/shaders/SkLocalMatrixShader.h75
-rw-r--r--src/shaders/SkPerlinNoiseShader.cpp1496
-rw-r--r--src/shaders/SkPictureShader.cpp364
-rw-r--r--src/shaders/SkPictureShader.h79
-rw-r--r--src/shaders/SkShader.cpp305
-rw-r--r--src/shaders/SkShaderBase.h272
-rw-r--r--src/shaders/gradients/Sk4fGradientBase.cpp451
-rw-r--r--src/shaders/gradients/Sk4fGradientBase.h97
-rw-r--r--src/shaders/gradients/Sk4fGradientPriv.h135
-rw-r--r--src/shaders/gradients/Sk4fLinearGradient.cpp430
-rw-r--r--src/shaders/gradients/Sk4fLinearGradient.h46
-rw-r--r--src/shaders/gradients/SkClampRange.cpp178
-rw-r--r--src/shaders/gradients/SkClampRange.h62
-rw-r--r--src/shaders/gradients/SkGradientBitmapCache.cpp154
-rw-r--r--src/shaders/gradients/SkGradientBitmapCache.h48
-rw-r--r--src/shaders/gradients/SkGradientShader.cpp2004
-rw-r--r--src/shaders/gradients/SkGradientShaderPriv.h540
-rw-r--r--src/shaders/gradients/SkLinearGradient.cpp804
-rw-r--r--src/shaders/gradients/SkLinearGradient.h87
-rw-r--r--src/shaders/gradients/SkRadialGradient.cpp405
-rw-r--r--src/shaders/gradients/SkRadialGradient.h53
-rw-r--r--src/shaders/gradients/SkSweepGradient.cpp306
-rw-r--r--src/shaders/gradients/SkSweepGradient.h54
-rw-r--r--src/shaders/gradients/SkTwoPointConicalGradient.cpp421
-rw-r--r--src/shaders/gradients/SkTwoPointConicalGradient.h93
-rw-r--r--src/shaders/gradients/SkTwoPointConicalGradient_gpu.cpp1341
-rw-r--r--src/shaders/gradients/SkTwoPointConicalGradient_gpu.h24
41 files changed, 12690 insertions, 0 deletions
diff --git a/src/shaders/SkBitmapProcShader.cpp b/src/shaders/SkBitmapProcShader.cpp
new file mode 100644
index 0000000000..5410447d6d
--- /dev/null
+++ b/src/shaders/SkBitmapProcShader.cpp
@@ -0,0 +1,206 @@
+/*
+ * 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 "SkBitmapProcShader.h"
+
+#include "SkArenaAlloc.h"
+#include "SkBitmapProcState.h"
+#include "SkBitmapProvider.h"
+#include "SkXfermodePriv.h"
+
+static bool only_scale_and_translate(const SkMatrix& matrix) {
+ unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
+ return (matrix.getType() & ~mask) == 0;
+}
+
+class BitmapProcInfoContext : public SkShaderBase::Context {
+public:
+ // The info has been allocated elsewhere, but we are responsible for calling its destructor.
+ BitmapProcInfoContext(const SkShaderBase& shader, const SkShaderBase::ContextRec& rec,
+ SkBitmapProcInfo* info)
+ : INHERITED(shader, rec)
+ , fInfo(info)
+ {
+ fFlags = 0;
+ if (fInfo->fPixmap.isOpaque() && (255 == this->getPaintAlpha())) {
+ fFlags |= SkShaderBase::kOpaqueAlpha_Flag;
+ }
+
+ if (1 == fInfo->fPixmap.height() && only_scale_and_translate(this->getTotalInverse())) {
+ fFlags |= SkShaderBase::kConstInY32_Flag;
+ }
+ }
+
+ uint32_t getFlags() const override { return fFlags; }
+
+private:
+ SkBitmapProcInfo* fInfo;
+ uint32_t fFlags;
+
+ typedef SkShaderBase::Context INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class BitmapProcShaderContext : public BitmapProcInfoContext {
+public:
+ BitmapProcShaderContext(const SkShaderBase& shader, const SkShaderBase::ContextRec& rec,
+ SkBitmapProcState* state)
+ : INHERITED(shader, rec, state)
+ , fState(state)
+ {}
+
+ void shadeSpan(int x, int y, SkPMColor dstC[], int count) override {
+ const SkBitmapProcState& state = *fState;
+ if (state.getShaderProc32()) {
+ state.getShaderProc32()(&state, x, y, dstC, count);
+ return;
+ }
+
+ const int BUF_MAX = 128;
+ uint32_t buffer[BUF_MAX];
+ SkBitmapProcState::MatrixProc mproc = state.getMatrixProc();
+ SkBitmapProcState::SampleProc32 sproc = state.getSampleProc32();
+ const int max = state.maxCountForBufferSize(sizeof(buffer[0]) * BUF_MAX);
+
+ SkASSERT(state.fPixmap.addr());
+
+ for (;;) {
+ int n = SkTMin(count, max);
+ SkASSERT(n > 0 && n < BUF_MAX*2);
+ mproc(state, buffer, n, x, y);
+ sproc(state, buffer, n, dstC);
+
+ if ((count -= n) == 0) {
+ break;
+ }
+ SkASSERT(count > 0);
+ x += n;
+ dstC += n;
+ }
+ }
+
+ ShadeProc asAShadeProc(void** ctx) override {
+ if (fState->getShaderProc32()) {
+ *ctx = fState;
+ return (ShadeProc)fState->getShaderProc32();
+ }
+ return nullptr;
+ }
+
+private:
+ SkBitmapProcState* fState;
+
+ typedef BitmapProcInfoContext INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+#include "SkLinearBitmapPipeline.h"
+#include "SkPM4f.h"
+
+class LinearPipelineContext : public BitmapProcInfoContext {
+public:
+ LinearPipelineContext(const SkShaderBase& shader, const SkShaderBase::ContextRec& rec,
+ SkBitmapProcInfo* info, SkArenaAlloc* alloc)
+ : INHERITED(shader, rec, info), fAllocator{alloc}
+ {
+ // Save things off in case we need to build a blitter pipeline.
+ fSrcPixmap = info->fPixmap;
+ fAlpha = SkColorGetA(info->fPaintColor) / 255.0f;
+ fFilterQuality = info->fFilterQuality;
+ fMatrixTypeMask = info->fRealInvMatrix.getType();
+
+ fShaderPipeline = alloc->make<SkLinearBitmapPipeline>(
+ info->fRealInvMatrix, info->fFilterQuality,
+ info->fTileModeX, info->fTileModeY,
+ info->fPaintColor,
+ info->fPixmap,
+ fAllocator);
+
+ // To implement the old shadeSpan entry-point, we need to efficiently convert our native
+ // floats into SkPMColor. The SkXfermode::D32Procs do exactly that.
+ //
+ fSrcModeProc = SkXfermode::GetD32Proc(SkBlendMode::kSrc, 0);
+ }
+
+ void shadeSpan4f(int x, int y, SkPM4f dstC[], int count) override {
+ fShaderPipeline->shadeSpan4f(x, y, dstC, count);
+ }
+
+ void shadeSpan(int x, int y, SkPMColor dstC[], int count) override {
+ const int N = 128;
+ SkPM4f tmp[N];
+
+ while (count > 0) {
+ const int n = SkTMin(count, N);
+ fShaderPipeline->shadeSpan4f(x, y, tmp, n);
+ fSrcModeProc(SkBlendMode::kSrc, dstC, tmp, n, nullptr);
+ dstC += n;
+ x += n;
+ count -= n;
+ }
+ }
+
+private:
+ // Store the allocator from the context creation incase we are asked to build a blitter.
+ SkArenaAlloc* fAllocator;
+ SkLinearBitmapPipeline* fShaderPipeline;
+ SkXfermode::D32Proc fSrcModeProc;
+ SkPixmap fSrcPixmap;
+ float fAlpha;
+ SkMatrix::TypeMask fMatrixTypeMask;
+ SkFilterQuality fFilterQuality;
+
+ typedef BitmapProcInfoContext INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool choose_linear_pipeline(const SkShaderBase::ContextRec& rec, const SkImageInfo& srcInfo) {
+ // If we get here, we can reasonably use either context, respect the caller's preference
+ //
+ bool needsPremul = srcInfo.alphaType() == kUnpremul_SkAlphaType;
+ bool needsSwizzle = srcInfo.bytesPerPixel() == 4 && srcInfo.colorType() != kN32_SkColorType;
+ return SkShaderBase::ContextRec::kPM4f_DstType == rec.fPreferredDstType
+ || needsPremul || needsSwizzle;
+}
+
+size_t SkBitmapProcLegacyShader::ContextSize(const ContextRec& rec, const SkImageInfo& srcInfo) {
+ size_t size0 = sizeof(BitmapProcShaderContext) + sizeof(SkBitmapProcState);
+ size_t size1 = sizeof(LinearPipelineContext) + sizeof(SkBitmapProcInfo);
+ size_t s = SkTMax(size0, size1);
+ return s;
+}
+
+SkShaderBase::Context* SkBitmapProcLegacyShader::MakeContext(
+ const SkShaderBase& shader, TileMode tmx, TileMode tmy,
+ const SkBitmapProvider& provider, const ContextRec& rec, SkArenaAlloc* alloc)
+{
+ SkMatrix totalInverse;
+ // Do this first, so we know the matrix can be inverted.
+ if (!shader.computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &totalInverse)) {
+ return nullptr;
+ }
+
+ // Decide if we can/want to use the new linear pipeline
+ bool useLinearPipeline = choose_linear_pipeline(rec, provider.info());
+
+ if (useLinearPipeline) {
+ SkBitmapProcInfo* info = alloc->make<SkBitmapProcInfo>(provider, tmx, tmy);
+ if (!info->init(totalInverse, *rec.fPaint)) {
+ return nullptr;
+ }
+
+ return alloc->make<LinearPipelineContext>(shader, rec, info, alloc);
+ } else {
+ SkBitmapProcState* state = alloc->make<SkBitmapProcState>(provider, tmx, tmy);
+ if (!state->setup(totalInverse, *rec.fPaint)) {
+ return nullptr;
+ }
+ return alloc->make<BitmapProcShaderContext>(shader, rec, state);
+ }
+}
diff --git a/src/shaders/SkBitmapProcShader.h b/src/shaders/SkBitmapProcShader.h
new file mode 100644
index 0000000000..2a2599cb1d
--- /dev/null
+++ b/src/shaders/SkBitmapProcShader.h
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+#ifndef SkBitmapProcShader_DEFINED
+#define SkBitmapProcShader_DEFINED
+
+#include "SkImagePriv.h"
+#include "SkShaderBase.h"
+
+class SkBitmapProvider;
+
+class SkBitmapProcLegacyShader : public SkShaderBase {
+private:
+ friend class SkImageShader;
+
+ static size_t ContextSize(const ContextRec&, const SkImageInfo& srcInfo);
+ static Context* MakeContext(const SkShaderBase&, TileMode tmx, TileMode tmy,
+ const SkBitmapProvider&, const ContextRec&, SkArenaAlloc* alloc);
+
+ typedef SkShaderBase INHERITED;
+};
+
+#endif
diff --git a/src/shaders/SkColorFilterShader.cpp b/src/shaders/SkColorFilterShader.cpp
new file mode 100644
index 0000000000..4798422dfa
--- /dev/null
+++ b/src/shaders/SkColorFilterShader.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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 "SkArenaAlloc.h"
+#include "SkColorFilterShader.h"
+#include "SkColorSpaceXformer.h"
+#include "SkReadBuffer.h"
+#include "SkWriteBuffer.h"
+#include "SkShader.h"
+#include "SkString.h"
+
+#if SK_SUPPORT_GPU
+#include "GrFragmentProcessor.h"
+#endif
+
+SkColorFilterShader::SkColorFilterShader(sk_sp<SkShader> shader, sk_sp<SkColorFilter> filter)
+ : fShader(std::move(shader))
+ , fFilter(std::move(filter))
+{
+ SkASSERT(fShader);
+ SkASSERT(fFilter);
+}
+
+sk_sp<SkFlattenable> SkColorFilterShader::CreateProc(SkReadBuffer& buffer) {
+ auto shader = buffer.readShader();
+ auto filter = buffer.readColorFilter();
+ if (!shader || !filter) {
+ return nullptr;
+ }
+ return sk_make_sp<SkColorFilterShader>(shader, filter);
+}
+
+void SkColorFilterShader::flatten(SkWriteBuffer& buffer) const {
+ buffer.writeFlattenable(fShader.get());
+ buffer.writeFlattenable(fFilter.get());
+}
+
+uint32_t SkColorFilterShader::FilterShaderContext::getFlags() const {
+ const SkColorFilterShader& filterShader = static_cast<const SkColorFilterShader&>(fShader);
+
+ uint32_t shaderF = fShaderContext->getFlags();
+ uint32_t filterF = filterShader.fFilter->getFlags();
+
+ // If the filter does not support a given feature, but sure to clear the corresponding flag
+ // in the shader flags.
+ //
+ if (!(filterF & SkColorFilter::kAlphaUnchanged_Flag)) {
+ shaderF &= ~kOpaqueAlpha_Flag;
+ }
+ return shaderF;
+}
+
+SkShaderBase::Context* SkColorFilterShader::onMakeContext(const ContextRec& rec,
+ SkArenaAlloc* alloc) const {
+ auto* shaderContext = as_SB(fShader)->makeContext(rec, alloc);
+ if (nullptr == shaderContext) {
+ return nullptr;
+ }
+ return alloc->make<FilterShaderContext>(*this, shaderContext, rec);
+}
+
+sk_sp<SkShader> SkColorFilterShader::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
+ return xformer->apply(fShader.get())->makeWithColorFilter(xformer->apply(fFilter.get()));
+}
+
+SkColorFilterShader::FilterShaderContext::FilterShaderContext(
+ const SkColorFilterShader& filterShader,
+ SkShaderBase::Context* shaderContext,
+ const ContextRec& rec)
+ : INHERITED(filterShader, rec)
+ , fShaderContext(shaderContext)
+{}
+
+void SkColorFilterShader::FilterShaderContext::shadeSpan(int x, int y, SkPMColor result[],
+ int count) {
+ const SkColorFilterShader& filterShader = static_cast<const SkColorFilterShader&>(fShader);
+
+ fShaderContext->shadeSpan(x, y, result, count);
+ filterShader.fFilter->filterSpan(result, count, result);
+}
+
+void SkColorFilterShader::FilterShaderContext::shadeSpan4f(int x, int y, SkPM4f result[],
+ int count) {
+ const SkColorFilterShader& filterShader = static_cast<const SkColorFilterShader&>(fShader);
+
+ fShaderContext->shadeSpan4f(x, y, result, count);
+ filterShader.fFilter->filterSpan4f(result, count, result);
+}
+
+#if SK_SUPPORT_GPU
+/////////////////////////////////////////////////////////////////////
+
+sk_sp<GrFragmentProcessor> SkColorFilterShader::asFragmentProcessor(const AsFPArgs& args) const {
+
+ sk_sp<GrFragmentProcessor> fp1(as_SB(fShader)->asFragmentProcessor(args));
+ if (!fp1) {
+ return nullptr;
+ }
+
+ sk_sp<GrFragmentProcessor> fp2(fFilter->asFragmentProcessor(args.fContext,
+ args.fDstColorSpace));
+ if (!fp2) {
+ return fp1;
+ }
+
+ sk_sp<GrFragmentProcessor> fpSeries[] = { std::move(fp1), std::move(fp2) };
+ return GrFragmentProcessor::RunInSeries(fpSeries, 2);
+}
+#endif
+
+#ifndef SK_IGNORE_TO_STRING
+void SkColorFilterShader::toString(SkString* str) const {
+ str->append("SkColorFilterShader: (");
+
+ str->append("Shader: ");
+ as_SB(fShader)->toString(str);
+ str->append(" Filter: ");
+ // TODO: add "fFilter->toString(str);" once SkColorFilter::toString is added
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+sk_sp<SkShader> SkShader::makeWithColorFilter(sk_sp<SkColorFilter> filter) const {
+ SkShader* base = const_cast<SkShader*>(this);
+ if (!filter) {
+ return sk_ref_sp(base);
+ }
+ return sk_make_sp<SkColorFilterShader>(sk_ref_sp(base), filter);
+}
diff --git a/src/shaders/SkColorFilterShader.h b/src/shaders/SkColorFilterShader.h
new file mode 100644
index 0000000000..7f4202158a
--- /dev/null
+++ b/src/shaders/SkColorFilterShader.h
@@ -0,0 +1,60 @@
+/*
+ * 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 SkColorFilterShader_DEFINED
+#define SkColorFilterShader_DEFINED
+
+#include "SkColorFilter.h"
+#include "SkShaderBase.h"
+
+class SkArenaAlloc;
+
+class SkColorFilterShader : public SkShaderBase {
+public:
+ SkColorFilterShader(sk_sp<SkShader> shader, sk_sp<SkColorFilter> filter);
+
+#if SK_SUPPORT_GPU
+ sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override;
+#endif
+
+ class FilterShaderContext : public Context {
+ public:
+ // Takes ownership of shaderContext and calls its destructor.
+ FilterShaderContext(const SkColorFilterShader&, SkShaderBase::Context*, const ContextRec&);
+
+ uint32_t getFlags() const override;
+
+ void shadeSpan(int x, int y, SkPMColor[], int count) override;
+ void shadeSpan4f(int x, int y, SkPM4f[], int count) override;
+
+ void set3DMask(const SkMask* mask) override {
+ // forward to our proxy
+ fShaderContext->set3DMask(mask);
+ }
+
+ private:
+ SkShaderBase::Context* fShaderContext;
+
+ typedef Context INHERITED;
+ };
+
+ SK_TO_STRING_OVERRIDE()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorFilterShader)
+
+protected:
+ void flatten(SkWriteBuffer&) const override;
+ Context* onMakeContext(const ContextRec&, SkArenaAlloc* alloc) const override;
+ sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
+
+private:
+ sk_sp<SkShader> fShader;
+ sk_sp<SkColorFilter> fFilter;
+
+ typedef SkShaderBase INHERITED;
+};
+
+#endif
diff --git a/src/shaders/SkColorShader.cpp b/src/shaders/SkColorShader.cpp
new file mode 100644
index 0000000000..7b6d0a9068
--- /dev/null
+++ b/src/shaders/SkColorShader.cpp
@@ -0,0 +1,274 @@
+/*
+ * 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 "SkArenaAlloc.h"
+#include "SkColorShader.h"
+#include "SkColorSpace.h"
+#include "SkPM4fPriv.h"
+#include "SkRasterPipeline.h"
+#include "SkReadBuffer.h"
+#include "SkUtils.h"
+
+SkColorShader::SkColorShader(SkColor c) : fColor(c) {}
+
+bool SkColorShader::isOpaque() const {
+ return SkColorGetA(fColor) == 255;
+}
+
+sk_sp<SkFlattenable> SkColorShader::CreateProc(SkReadBuffer& buffer) {
+ return sk_make_sp<SkColorShader>(buffer.readColor());
+}
+
+void SkColorShader::flatten(SkWriteBuffer& buffer) const {
+ buffer.writeColor(fColor);
+}
+
+uint32_t SkColorShader::ColorShaderContext::getFlags() const {
+ return fFlags;
+}
+
+SkShaderBase::Context* SkColorShader::onMakeContext(const ContextRec& rec,
+ SkArenaAlloc* alloc) const {
+ return alloc->make<ColorShaderContext>(*this, rec);
+}
+
+SkColorShader::ColorShaderContext::ColorShaderContext(const SkColorShader& shader,
+ const ContextRec& rec)
+ : INHERITED(shader, rec)
+{
+ SkColor color = shader.fColor;
+ unsigned a = SkAlphaMul(SkColorGetA(color), SkAlpha255To256(rec.fPaint->getAlpha()));
+
+ unsigned r = SkColorGetR(color);
+ unsigned g = SkColorGetG(color);
+ unsigned b = SkColorGetB(color);
+
+ if (a != 255) {
+ r = SkMulDiv255Round(r, a);
+ g = SkMulDiv255Round(g, a);
+ b = SkMulDiv255Round(b, a);
+ }
+ fPMColor = SkPackARGB32(a, r, g, b);
+
+ SkColor4f c4 = SkColor4f::FromColor(shader.fColor);
+ c4.fA *= rec.fPaint->getAlpha() / 255.0f;
+ fPM4f = c4.premul();
+
+ fFlags = kConstInY32_Flag;
+ if (255 == a) {
+ fFlags |= kOpaqueAlpha_Flag;
+ }
+}
+
+void SkColorShader::ColorShaderContext::shadeSpan(int x, int y, SkPMColor span[], int count) {
+ sk_memset32(span, fPMColor, count);
+}
+
+void SkColorShader::ColorShaderContext::shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) {
+ memset(alpha, SkGetPackedA32(fPMColor), count);
+}
+
+void SkColorShader::ColorShaderContext::shadeSpan4f(int x, int y, SkPM4f span[], int count) {
+ for (int i = 0; i < count; ++i) {
+ span[i] = fPM4f;
+ }
+}
+
+SkShader::GradientType SkColorShader::asAGradient(GradientInfo* info) const {
+ if (info) {
+ if (info->fColors && info->fColorCount >= 1) {
+ info->fColors[0] = fColor;
+ }
+ info->fColorCount = 1;
+ info->fTileMode = SkShader::kRepeat_TileMode;
+ }
+ return kColor_GradientType;
+}
+
+#if SK_SUPPORT_GPU
+
+#include "SkGr.h"
+#include "effects/GrConstColorProcessor.h"
+sk_sp<GrFragmentProcessor> SkColorShader::asFragmentProcessor(const AsFPArgs& args) const {
+ GrColor4f color = SkColorToPremulGrColor4f(fColor, args.fDstColorSpace);
+ return GrConstColorProcessor::Make(color, GrConstColorProcessor::kModulateA_InputMode);
+}
+
+#endif
+
+#ifndef SK_IGNORE_TO_STRING
+void SkColorShader::toString(SkString* str) const {
+ str->append("SkColorShader: (");
+
+ str->append("Color: ");
+ str->appendHex(fColor);
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+static unsigned unit_to_byte(float unit) {
+ SkASSERT(unit >= 0 && unit <= 1);
+ return (unsigned)(unit * 255 + 0.5);
+}
+
+static SkColor unit_to_skcolor(const SkColor4f& unit, SkColorSpace* cs) {
+ return SkColorSetARGB(unit_to_byte(unit.fA), unit_to_byte(unit.fR),
+ unit_to_byte(unit.fG), unit_to_byte(unit.fB));
+}
+
+SkColor4Shader::SkColor4Shader(const SkColor4f& color, sk_sp<SkColorSpace> space)
+ : fColorSpace(std::move(space))
+ , fColor4(color)
+ , fCachedByteColor(unit_to_skcolor(color.pin(), space.get()))
+{}
+
+sk_sp<SkFlattenable> SkColor4Shader::CreateProc(SkReadBuffer& buffer) {
+ SkColor4f color;
+ buffer.readColor4f(&color);
+ if (buffer.readBool()) {
+ // TODO how do we unflatten colorspaces
+ }
+ return SkShader::MakeColorShader(color, nullptr);
+}
+
+void SkColor4Shader::flatten(SkWriteBuffer& buffer) const {
+ buffer.writeColor4f(fColor4);
+ buffer.writeBool(false); // TODO how do we flatten colorspaces?
+}
+
+uint32_t SkColor4Shader::Color4Context::getFlags() const {
+ return fFlags;
+}
+
+SkShaderBase::Context* SkColor4Shader::onMakeContext(const ContextRec& rec,
+ SkArenaAlloc* alloc) const {
+ return alloc->make<Color4Context>(*this, rec);
+}
+
+SkColor4Shader::Color4Context::Color4Context(const SkColor4Shader& shader,
+ const ContextRec& rec)
+: INHERITED(shader, rec)
+{
+ SkColor color = shader.fCachedByteColor;
+ unsigned a = SkAlphaMul(SkColorGetA(color), SkAlpha255To256(rec.fPaint->getAlpha()));
+
+ unsigned r = SkColorGetR(color);
+ unsigned g = SkColorGetG(color);
+ unsigned b = SkColorGetB(color);
+
+ if (a != 255) {
+ r = SkMulDiv255Round(r, a);
+ g = SkMulDiv255Round(g, a);
+ b = SkMulDiv255Round(b, a);
+ }
+ fPMColor = SkPackARGB32(a, r, g, b);
+
+ SkColor4f c4 = shader.fColor4;
+ c4.fA *= rec.fPaint->getAlpha() * (1 / 255.0f);
+ fPM4f = c4.premul();
+
+ fFlags = kConstInY32_Flag;
+ if (255 == a) {
+ fFlags |= kOpaqueAlpha_Flag;
+ }
+}
+
+void SkColor4Shader::Color4Context::shadeSpan(int x, int y, SkPMColor span[], int count) {
+ sk_memset32(span, fPMColor, count);
+}
+
+void SkColor4Shader::Color4Context::shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) {
+ memset(alpha, SkGetPackedA32(fPMColor), count);
+}
+
+void SkColor4Shader::Color4Context::shadeSpan4f(int x, int y, SkPM4f span[], int count) {
+ for (int i = 0; i < count; ++i) {
+ span[i] = fPM4f;
+ }
+}
+
+// TODO: do we need an updated version of this method for color4+colorspace?
+SkShader::GradientType SkColor4Shader::asAGradient(GradientInfo* info) const {
+ if (info) {
+ if (info->fColors && info->fColorCount >= 1) {
+ info->fColors[0] = fCachedByteColor;
+ }
+ info->fColorCount = 1;
+ info->fTileMode = SkShader::kRepeat_TileMode;
+ }
+ return kColor_GradientType;
+}
+
+#if SK_SUPPORT_GPU
+
+#include "SkGr.h"
+#include "effects/GrConstColorProcessor.h"
+#include "GrColorSpaceXform.h"
+sk_sp<GrFragmentProcessor> SkColor4Shader::asFragmentProcessor(const AsFPArgs& args) const {
+ sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(),
+ args.fDstColorSpace);
+ GrColor4f color = GrColor4f::FromSkColor4f(fColor4);
+ if (colorSpaceXform) {
+ color = colorSpaceXform->apply(color);
+ }
+ return GrConstColorProcessor::Make(color.premul(), GrConstColorProcessor::kModulateA_InputMode);
+}
+
+#endif
+
+#ifndef SK_IGNORE_TO_STRING
+void SkColor4Shader::toString(SkString* str) const {
+ str->append("SkColor4Shader: (");
+
+ str->append("RGBA:");
+ for (int i = 0; i < 4; ++i) {
+ str->appendf(" %g", fColor4.vec()[i]);
+ }
+ str->append(" )");
+}
+#endif
+
+sk_sp<SkShader> SkColor4Shader::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
+ return SkShader::MakeColorShader(xformer->apply(fCachedByteColor));
+}
+
+sk_sp<SkShader> SkShader::MakeColorShader(const SkColor4f& color, sk_sp<SkColorSpace> space) {
+ if (!SkScalarsAreFinite(color.vec(), 4)) {
+ return nullptr;
+ }
+ return sk_make_sp<SkColor4Shader>(color, std::move(space));
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool SkColorShader::onAppendStages(SkRasterPipeline* p,
+ SkColorSpace* dst,
+ SkArenaAlloc* scratch,
+ const SkMatrix&,
+ const SkPaint&,
+ const SkMatrix*) const {
+ auto color = scratch->make<SkPM4f>(SkPM4f_from_SkColor(fColor, dst));
+ p->append(SkRasterPipeline::constant_color, color);
+ return true;
+}
+
+bool SkColor4Shader::onAppendStages(SkRasterPipeline* p,
+ SkColorSpace* dst,
+ SkArenaAlloc* scratch,
+ const SkMatrix&,
+ const SkPaint&,
+ const SkMatrix*) const {
+ auto color = scratch->make<SkPM4f>(to_colorspace(fColor4, fColorSpace.get(), dst).premul());
+ p->append(SkRasterPipeline::constant_color, color);
+ return true;
+}
diff --git a/src/shaders/SkColorShader.h b/src/shaders/SkColorShader.h
new file mode 100644
index 0000000000..969064946e
--- /dev/null
+++ b/src/shaders/SkColorShader.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2007 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.
+ */
+
+#ifndef SkColorShader_DEFINED
+#define SkColorShader_DEFINED
+
+#include "SkColorSpaceXformer.h"
+#include "SkShaderBase.h"
+#include "SkPM4f.h"
+
+/** \class SkColorShader
+ A Shader that represents a single color. In general, this effect can be
+ accomplished by just using the color field on the paint, but if an
+ actual shader object is needed, this provides that feature.
+*/
+class SkColorShader : public SkShaderBase {
+public:
+ /** Create a ColorShader that ignores the color in the paint, and uses the
+ specified color. Note: like all shaders, at draw time the paint's alpha
+ will be respected, and is applied to the specified color.
+ */
+ explicit SkColorShader(SkColor c);
+
+ bool isOpaque() const override;
+ bool isConstant() const override { return true; }
+
+ class ColorShaderContext : public Context {
+ public:
+ ColorShaderContext(const SkColorShader& shader, const ContextRec&);
+
+ uint32_t getFlags() const override;
+ void shadeSpan(int x, int y, SkPMColor span[], int count) override;
+ void shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) override;
+ void shadeSpan4f(int x, int y, SkPM4f[], int count) override;
+
+ private:
+ SkPM4f fPM4f;
+ SkPMColor fPMColor;
+ uint32_t fFlags;
+
+ typedef Context INHERITED;
+ };
+
+ GradientType asAGradient(GradientInfo* info) const override;
+
+#if SK_SUPPORT_GPU
+ sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override;
+#endif
+
+ SK_TO_STRING_OVERRIDE()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorShader)
+
+protected:
+ SkColorShader(SkReadBuffer&);
+ void flatten(SkWriteBuffer&) const override;
+ Context* onMakeContext(const ContextRec&, SkArenaAlloc* storage) const override;
+
+ bool onAsLuminanceColor(SkColor* lum) const override {
+ *lum = fColor;
+ return true;
+ }
+
+ bool onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*,
+ const SkMatrix& ctm, const SkPaint&, const SkMatrix*) const override;
+
+ sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override {
+ return SkShader::MakeColorShader(xformer->apply(fColor));
+ }
+
+private:
+ SkColor fColor;
+
+ typedef SkShaderBase INHERITED;
+};
+
+class SkColor4Shader : public SkShaderBase {
+public:
+ SkColor4Shader(const SkColor4f&, sk_sp<SkColorSpace>);
+
+ bool isOpaque() const override {
+ return SkColorGetA(fCachedByteColor) == 255;
+ }
+ bool isConstant() const override { return true; }
+
+ class Color4Context : public Context {
+ public:
+ Color4Context(const SkColor4Shader& shader, const ContextRec&);
+
+ uint32_t getFlags() const override;
+ void shadeSpan(int x, int y, SkPMColor span[], int count) override;
+ void shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) override;
+ void shadeSpan4f(int x, int y, SkPM4f[], int count) override;
+
+ private:
+ SkPM4f fPM4f;
+ SkPMColor fPMColor;
+ uint32_t fFlags;
+
+ typedef Context INHERITED;
+ };
+
+ GradientType asAGradient(GradientInfo* info) const override;
+
+#if SK_SUPPORT_GPU
+ sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override;
+#endif
+
+ SK_TO_STRING_OVERRIDE()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorShader)
+
+protected:
+ SkColor4Shader(SkReadBuffer&);
+ void flatten(SkWriteBuffer&) const override;
+ Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
+ bool onAsLuminanceColor(SkColor* lum) const override {
+ *lum = fCachedByteColor;
+ return true;
+ }
+ bool onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*,
+ const SkMatrix& ctm, const SkPaint&, const SkMatrix*) const override;
+
+ sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
+
+private:
+ sk_sp<SkColorSpace> fColorSpace;
+ const SkColor4f fColor4;
+ const SkColor fCachedByteColor;
+
+ typedef SkShaderBase INHERITED;
+};
+
+#endif
diff --git a/src/shaders/SkComposeShader.cpp b/src/shaders/SkComposeShader.cpp
new file mode 100644
index 0000000000..7735494291
--- /dev/null
+++ b/src/shaders/SkComposeShader.cpp
@@ -0,0 +1,311 @@
+/*
+ * 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 "SkArenaAlloc.h"
+#include "SkBlendModePriv.h"
+#include "SkComposeShader.h"
+#include "SkColorFilter.h"
+#include "SkColorPriv.h"
+#include "SkColorShader.h"
+#include "SkRasterPipeline.h"
+#include "SkReadBuffer.h"
+#include "SkWriteBuffer.h"
+#include "SkString.h"
+#include "../jumper/SkJumper.h"
+
+sk_sp<SkShader> SkShader::MakeComposeShader(sk_sp<SkShader> dst, sk_sp<SkShader> src,
+ SkBlendMode mode) {
+ if (!src || !dst) {
+ return nullptr;
+ }
+ if (SkBlendMode::kSrc == mode) {
+ return src;
+ }
+ if (SkBlendMode::kDst == mode) {
+ return dst;
+ }
+ return sk_sp<SkShader>(new SkComposeShader(std::move(dst), std::move(src), mode));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkAutoAlphaRestore {
+public:
+ SkAutoAlphaRestore(SkPaint* paint, uint8_t newAlpha) {
+ fAlpha = paint->getAlpha();
+ fPaint = paint;
+ paint->setAlpha(newAlpha);
+ }
+
+ ~SkAutoAlphaRestore() {
+ fPaint->setAlpha(fAlpha);
+ }
+private:
+ SkPaint* fPaint;
+ uint8_t fAlpha;
+};
+#define SkAutoAlphaRestore(...) SK_REQUIRE_LOCAL_VAR(SkAutoAlphaRestore)
+
+sk_sp<SkFlattenable> SkComposeShader::CreateProc(SkReadBuffer& buffer) {
+ sk_sp<SkShader> shaderA(buffer.readShader());
+ sk_sp<SkShader> shaderB(buffer.readShader());
+ SkBlendMode mode;
+ if (buffer.isVersionLT(SkReadBuffer::kXfermodeToBlendMode2_Version)) {
+ sk_sp<SkXfermode> xfer = buffer.readXfermode();
+ mode = xfer ? xfer->blend() : SkBlendMode::kSrcOver;
+ } else {
+ mode = (SkBlendMode)buffer.read32();
+ }
+ if (!shaderA || !shaderB) {
+ return nullptr;
+ }
+ return sk_make_sp<SkComposeShader>(std::move(shaderA), std::move(shaderB), mode);
+}
+
+void SkComposeShader::flatten(SkWriteBuffer& buffer) const {
+ buffer.writeFlattenable(fShaderA.get());
+ buffer.writeFlattenable(fShaderB.get());
+ buffer.write32((int)fMode);
+}
+
+SkShaderBase::Context* SkComposeShader::onMakeContext(
+ const ContextRec& rec, SkArenaAlloc* alloc) const
+{
+ // we preconcat our localMatrix (if any) with the device matrix
+ // before calling our sub-shaders
+ SkMatrix tmpM;
+ tmpM.setConcat(*rec.fMatrix, this->getLocalMatrix());
+
+ // Our sub-shaders need to see opaque, so by combining them we don't double-alphatize the
+ // result. ComposeShader itself will respect the alpha, and post-apply it after calling the
+ // sub-shaders.
+ SkPaint opaquePaint(*rec.fPaint);
+ opaquePaint.setAlpha(0xFF);
+
+ ContextRec newRec(rec);
+ newRec.fMatrix = &tmpM;
+ newRec.fPaint = &opaquePaint;
+
+ SkShaderBase::Context* contextA = as_SB(fShaderA)->makeContext(newRec, alloc);
+ SkShaderBase::Context* contextB = as_SB(fShaderB)->makeContext(newRec, alloc);
+ if (!contextA || !contextB) {
+ return nullptr;
+ }
+
+ return alloc->make<ComposeShaderContext>(*this, rec, contextA, contextB);
+}
+
+sk_sp<SkShader> SkComposeShader::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
+ return SkShader::MakeComposeShader(xformer->apply(fShaderA.get()),
+ xformer->apply(fShaderB.get()), fMode);
+}
+
+SkComposeShader::ComposeShaderContext::ComposeShaderContext(
+ const SkComposeShader& shader, const ContextRec& rec,
+ SkShaderBase::Context* contextA, SkShaderBase::Context* contextB)
+ : INHERITED(shader, rec)
+ , fShaderContextA(contextA)
+ , fShaderContextB(contextB) {}
+
+bool SkComposeShader::asACompose(ComposeRec* rec) const {
+ if (rec) {
+ rec->fShaderA = fShaderA.get();
+ rec->fShaderB = fShaderB.get();
+ rec->fBlendMode = fMode;
+ }
+ return true;
+}
+
+bool SkComposeShader::isRasterPipelineOnly() const {
+ return as_SB(fShaderA)->isRasterPipelineOnly() || as_SB(fShaderB)->isRasterPipelineOnly();
+}
+
+bool SkComposeShader::onAppendStages(SkRasterPipeline* pipeline, SkColorSpace* dstCS,
+ SkArenaAlloc* alloc, const SkMatrix& ctm,
+ const SkPaint& paint, const SkMatrix* localM) const {
+ struct Storage {
+ float fXY[4 * SkJumper_kMaxStride];
+ float fRGBA[4 * SkJumper_kMaxStride];
+ float fAlpha;
+ };
+ auto storage = alloc->make<Storage>();
+
+ // We need to save off device x,y (inputs to shader), since after calling fShaderA they
+ // will be smashed, and I'll need them again for fShaderB. store_rgba saves off 4 registers
+ // even though we only need to save r,g.
+ pipeline->append(SkRasterPipeline::store_rgba, storage->fXY);
+ if (!as_SB(fShaderB)->appendStages(pipeline, dstCS, alloc, ctm, paint, localM)) { // SRC
+ return false;
+ }
+ // This outputs r,g,b,a, which we'll need later when we apply the mode, but we save it off now
+ // since fShaderB will overwrite them.
+ pipeline->append(SkRasterPipeline::store_rgba, storage->fRGBA);
+ // Now we restore the device x,y for the next shader
+ pipeline->append(SkRasterPipeline::load_rgba, storage->fXY);
+ if (!as_SB(fShaderA)->appendStages(pipeline, dstCS, alloc, ctm, paint, localM)) { // DST
+ return false;
+ }
+ // We now have our logical 'dst' in r,g,b,a, but we need it in dr,dg,db,da for the mode
+ // so we have to shuttle them. If we had a stage the would load_into_dst, then we could
+ // reverse the two shader invocations, and avoid this move...
+ pipeline->append(SkRasterPipeline::move_src_dst);
+ pipeline->append(SkRasterPipeline::load_rgba, storage->fRGBA);
+
+ // Idea: should time this, and see if it helps to have custom versions of the overflow modes
+ // that do their own clamping, avoiding the overhead of an extra stage.
+ SkBlendMode_AppendStages(fMode, pipeline);
+ if (SkBlendMode_CanOverflow(fMode)) {
+ pipeline->append(SkRasterPipeline::clamp_a);
+ }
+ return true;
+}
+
+// larger is better (fewer times we have to loop), but we shouldn't
+// take up too much stack-space (each element is 4 bytes)
+#define TMP_COLOR_COUNT 64
+
+void SkComposeShader::ComposeShaderContext::shadeSpan(int x, int y, SkPMColor result[], int count) {
+ auto* shaderContextA = fShaderContextA;
+ auto* shaderContextB = fShaderContextB;
+ SkBlendMode mode = static_cast<const SkComposeShader&>(fShader).fMode;
+ unsigned scale = SkAlpha255To256(this->getPaintAlpha());
+
+ SkPMColor tmp[TMP_COLOR_COUNT];
+
+ SkXfermode* xfer = SkXfermode::Peek(mode);
+ if (nullptr == xfer) { // implied SRC_OVER
+ // TODO: when we have a good test-case, should use SkBlitRow::Proc32
+ // for these loops
+ do {
+ int n = count;
+ if (n > TMP_COLOR_COUNT) {
+ n = TMP_COLOR_COUNT;
+ }
+
+ shaderContextA->shadeSpan(x, y, result, n);
+ shaderContextB->shadeSpan(x, y, tmp, n);
+
+ if (256 == scale) {
+ for (int i = 0; i < n; i++) {
+ result[i] = SkPMSrcOver(tmp[i], result[i]);
+ }
+ } else {
+ for (int i = 0; i < n; i++) {
+ result[i] = SkAlphaMulQ(SkPMSrcOver(tmp[i], result[i]),
+ scale);
+ }
+ }
+
+ result += n;
+ x += n;
+ count -= n;
+ } while (count > 0);
+ } else { // use mode for the composition
+ do {
+ int n = count;
+ if (n > TMP_COLOR_COUNT) {
+ n = TMP_COLOR_COUNT;
+ }
+
+ shaderContextA->shadeSpan(x, y, result, n);
+ shaderContextB->shadeSpan(x, y, tmp, n);
+ xfer->xfer32(result, tmp, n, nullptr);
+
+ if (256 != scale) {
+ for (int i = 0; i < n; i++) {
+ result[i] = SkAlphaMulQ(result[i], scale);
+ }
+ }
+
+ result += n;
+ x += n;
+ count -= n;
+ } while (count > 0);
+ }
+}
+
+void SkComposeShader::ComposeShaderContext::shadeSpan4f(int x, int y, SkPM4f result[], int count) {
+ auto* shaderContextA = fShaderContextA;
+ auto* shaderContextB = fShaderContextB;
+ SkBlendMode mode = static_cast<const SkComposeShader&>(fShader).fMode;
+ unsigned alpha = this->getPaintAlpha();
+ Sk4f scale(alpha * (1.0f / 255));
+
+ SkPM4f tmp[TMP_COLOR_COUNT];
+
+ SkXfermodeProc4f xfer = SkXfermode::GetProc4f(mode);
+ do {
+ int n = SkTMin(count, TMP_COLOR_COUNT);
+
+ shaderContextA->shadeSpan4f(x, y, result, n);
+ shaderContextB->shadeSpan4f(x, y, tmp, n);
+ if (255 == alpha) {
+ for (int i = 0; i < n; ++i) {
+ result[i] = xfer(tmp[i], result[i]);
+ }
+ } else {
+ for (int i = 0; i < n; ++i) {
+ (xfer(tmp[i], result[i]).to4f() * scale).store(result + i);
+ }
+ }
+ result += n;
+ x += n;
+ count -= n;
+ } while (count > 0);
+}
+
+#if SK_SUPPORT_GPU
+
+#include "effects/GrConstColorProcessor.h"
+#include "effects/GrXfermodeFragmentProcessor.h"
+
+/////////////////////////////////////////////////////////////////////
+
+sk_sp<GrFragmentProcessor> SkComposeShader::asFragmentProcessor(const AsFPArgs& args) const {
+ switch (fMode) {
+ case SkBlendMode::kClear:
+ return GrConstColorProcessor::Make(GrColor4f::TransparentBlack(),
+ GrConstColorProcessor::kIgnore_InputMode);
+ break;
+ case SkBlendMode::kSrc:
+ return as_SB(fShaderB)->asFragmentProcessor(args);
+ break;
+ case SkBlendMode::kDst:
+ return as_SB(fShaderA)->asFragmentProcessor(args);
+ break;
+ default:
+ sk_sp<GrFragmentProcessor> fpA(as_SB(fShaderA)->asFragmentProcessor(args));
+ if (!fpA) {
+ return nullptr;
+ }
+ sk_sp<GrFragmentProcessor> fpB(as_SB(fShaderB)->asFragmentProcessor(args));
+ if (!fpB) {
+ return nullptr;
+ }
+ return GrXfermodeFragmentProcessor::MakeFromTwoProcessors(std::move(fpB),
+ std::move(fpA), fMode);
+ }
+}
+#endif
+
+#ifndef SK_IGNORE_TO_STRING
+void SkComposeShader::toString(SkString* str) const {
+ str->append("SkComposeShader: (");
+
+ str->append("ShaderA: ");
+ as_SB(fShaderA)->toString(str);
+ str->append(" ShaderB: ");
+ as_SB(fShaderB)->toString(str);
+ if (SkBlendMode::kSrcOver != fMode) {
+ str->appendf(" Xfermode: %s", SkXfermode::ModeName(fMode));
+ }
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/src/shaders/SkComposeShader.h b/src/shaders/SkComposeShader.h
new file mode 100644
index 0000000000..c7bb4b9eb8
--- /dev/null
+++ b/src/shaders/SkComposeShader.h
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+#ifndef SkComposeShader_DEFINED
+#define SkComposeShader_DEFINED
+
+#include "SkShaderBase.h"
+#include "SkBlendMode.h"
+
+class SkColorSpacXformer;
+
+///////////////////////////////////////////////////////////////////////////////////////////
+
+/** \class SkComposeShader
+ This subclass of shader returns the composition of two other shaders, combined by
+ a xfermode.
+*/
+class SkComposeShader : public SkShaderBase {
+public:
+ /** Create a new compose shader, given shaders A, B, and a combining xfermode mode.
+ When the xfermode is called, it will be given the result from shader A as its
+ "dst", and the result from shader B as its "src".
+ mode->xfer32(sA_result, sB_result, ...)
+ @param shaderA The colors from this shader are seen as the "dst" by the xfermode
+ @param shaderB The colors from this shader are seen as the "src" by the xfermode
+ @param mode The xfermode that combines the colors from the two shaders. If mode
+ is null, then SRC_OVER is assumed.
+ */
+ SkComposeShader(sk_sp<SkShader> sA, sk_sp<SkShader> sB, SkBlendMode mode)
+ : fShaderA(std::move(sA))
+ , fShaderB(std::move(sB))
+ , fMode(mode)
+ {}
+
+#if SK_SUPPORT_GPU
+ sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override;
+#endif
+
+ class ComposeShaderContext : public Context {
+ public:
+ // When this object gets destroyed, it will call contextA and contextB's destructor
+ // but it will NOT free the memory.
+ ComposeShaderContext(const SkComposeShader&, const ContextRec&,
+ SkShaderBase::Context* contextA, SkShaderBase::Context* contextB);
+
+ void shadeSpan(int x, int y, SkPMColor[], int count) override;
+ void shadeSpan4f(int x, int y, SkPM4f[], int count) override;
+
+ private:
+ SkShaderBase::Context* fShaderContextA;
+ SkShaderBase::Context* fShaderContextB;
+
+ typedef Context INHERITED;
+ };
+
+#ifdef SK_DEBUG
+ SkShader* getShaderA() { return fShaderA.get(); }
+ SkShader* getShaderB() { return fShaderB.get(); }
+#endif
+
+ bool asACompose(ComposeRec* rec) const override;
+
+ SK_TO_STRING_OVERRIDE()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkComposeShader)
+
+protected:
+ SkComposeShader(SkReadBuffer&);
+ void flatten(SkWriteBuffer&) const override;
+ Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
+ sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
+ bool onAppendStages(SkRasterPipeline*, SkColorSpace* dstCS, SkArenaAlloc*,
+ const SkMatrix&, const SkPaint&, const SkMatrix* localM) const override;
+
+ bool isRasterPipelineOnly() const final;
+
+private:
+ sk_sp<SkShader> fShaderA;
+ sk_sp<SkShader> fShaderB;
+ SkBlendMode fMode;
+
+ typedef SkShaderBase INHERITED;
+};
+
+#endif
diff --git a/src/shaders/SkEmptyShader.h b/src/shaders/SkEmptyShader.h
new file mode 100644
index 0000000000..d7187c4edf
--- /dev/null
+++ b/src/shaders/SkEmptyShader.h
@@ -0,0 +1,41 @@
+/*
+ * 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 SkEmptyShader_DEFINED
+#define SkEmptyShader_DEFINED
+
+#include "SkShaderBase.h"
+
+// TODO: move this to private, as there is a public factory on SkShader
+
+/**
+ * \class SkEmptyShader
+ * A Shader that always draws nothing. Its createContext always returns nullptr.
+ */
+class SkEmptyShader : public SkShaderBase {
+public:
+ SkEmptyShader() {}
+
+ SK_TO_STRING_OVERRIDE()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkEmptyShader)
+
+protected:
+ Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override {
+ return nullptr;
+ }
+
+ void flatten(SkWriteBuffer& buffer) const override {
+ // Do nothing.
+ // We just don't want to fall through to SkShader::flatten(),
+ // which will write data we don't care to serialize or decode.
+ }
+
+private:
+ typedef SkShaderBase INHERITED;
+};
+
+#endif
diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp
new file mode 100644
index 0000000000..751300e951
--- /dev/null
+++ b/src/shaders/SkImageShader.cpp
@@ -0,0 +1,391 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkArenaAlloc.h"
+#include "SkBitmapController.h"
+#include "SkBitmapProcShader.h"
+#include "SkBitmapProvider.h"
+#include "SkColorTable.h"
+#include "SkEmptyShader.h"
+#include "SkImage_Base.h"
+#include "SkImageShader.h"
+#include "SkPM4fPriv.h"
+#include "SkReadBuffer.h"
+#include "SkWriteBuffer.h"
+#include "../jumper/SkJumper.h"
+
+SkImageShader::SkImageShader(sk_sp<SkImage> img, TileMode tmx, TileMode tmy, const SkMatrix* matrix)
+ : INHERITED(matrix)
+ , fImage(std::move(img))
+ , fTileModeX(tmx)
+ , fTileModeY(tmy)
+{}
+
+sk_sp<SkFlattenable> SkImageShader::CreateProc(SkReadBuffer& buffer) {
+ const TileMode tx = (TileMode)buffer.readUInt();
+ const TileMode ty = (TileMode)buffer.readUInt();
+ SkMatrix matrix;
+ buffer.readMatrix(&matrix);
+ sk_sp<SkImage> img = buffer.readImage();
+ if (!img) {
+ return nullptr;
+ }
+ return SkImageShader::Make(std::move(img), tx, ty, &matrix);
+}
+
+void SkImageShader::flatten(SkWriteBuffer& buffer) const {
+ buffer.writeUInt(fTileModeX);
+ buffer.writeUInt(fTileModeY);
+ buffer.writeMatrix(this->getLocalMatrix());
+ buffer.writeImage(fImage.get());
+}
+
+bool SkImageShader::isOpaque() const {
+ return fImage->isOpaque();
+}
+
+SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec,
+ SkArenaAlloc* alloc) const {
+ return SkBitmapProcLegacyShader::MakeContext(*this, fTileModeX, fTileModeY,
+ SkBitmapProvider(fImage.get(), rec.fDstColorSpace),
+ rec, alloc);
+}
+
+SkImage* SkImageShader::onIsAImage(SkMatrix* texM, TileMode xy[]) const {
+ if (texM) {
+ *texM = this->getLocalMatrix();
+ }
+ if (xy) {
+ xy[0] = (TileMode)fTileModeX;
+ xy[1] = (TileMode)fTileModeY;
+ }
+ return const_cast<SkImage*>(fImage.get());
+}
+
+#ifdef SK_SUPPORT_LEGACY_SHADER_ISABITMAP
+bool SkImageShader::onIsABitmap(SkBitmap* texture, SkMatrix* texM, TileMode xy[]) const {
+ const SkBitmap* bm = as_IB(fImage)->onPeekBitmap();
+ if (!bm) {
+ return false;
+ }
+
+ if (texture) {
+ *texture = *bm;
+ }
+ if (texM) {
+ *texM = this->getLocalMatrix();
+ }
+ if (xy) {
+ xy[0] = (TileMode)fTileModeX;
+ xy[1] = (TileMode)fTileModeY;
+ }
+ return true;
+}
+#endif
+
+static bool bitmap_is_too_big(int w, int h) {
+ // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer, as it
+ // communicates between its matrix-proc and its sampler-proc. Until we can
+ // widen that, we have to reject bitmaps that are larger.
+ //
+ static const int kMaxSize = 65535;
+
+ return w > kMaxSize || h > kMaxSize;
+}
+
+sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image, TileMode tx, TileMode ty,
+ const SkMatrix* localMatrix) {
+ if (!image || bitmap_is_too_big(image->width(), image->height())) {
+ return sk_make_sp<SkEmptyShader>();
+ } else {
+ return sk_make_sp<SkImageShader>(image, tx, ty, localMatrix);
+ }
+}
+
+#ifndef SK_IGNORE_TO_STRING
+void SkImageShader::toString(SkString* str) const {
+ const char* gTileModeName[SkShader::kTileModeCount] = {
+ "clamp", "repeat", "mirror"
+ };
+
+ str->appendf("ImageShader: ((%s %s) ", gTileModeName[fTileModeX], gTileModeName[fTileModeY]);
+ fImage->toString(str);
+ this->INHERITED::toString(str);
+ str->append(")");
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "SkGr.h"
+#include "GrContext.h"
+#include "effects/GrSimpleTextureEffect.h"
+#include "effects/GrBicubicEffect.h"
+#include "effects/GrSimpleTextureEffect.h"
+
+sk_sp<GrFragmentProcessor> SkImageShader::asFragmentProcessor(const AsFPArgs& args) const {
+
+ SkMatrix lmInverse;
+ if (!this->getLocalMatrix().invert(&lmInverse)) {
+ return nullptr;
+ }
+ if (args.fLocalMatrix) {
+ SkMatrix inv;
+ if (!args.fLocalMatrix->invert(&inv)) {
+ return nullptr;
+ }
+ lmInverse.postConcat(inv);
+ }
+
+ SkShader::TileMode tm[] = { fTileModeX, fTileModeY };
+
+ // Must set wrap and filter on the sampler before requesting a texture. In two places below
+ // we check the matrix scale factors to determine how to interpret the filter quality setting.
+ // This completely ignores the complexity of the drawVertices case where explicit local coords
+ // are provided by the caller.
+ bool doBicubic;
+ GrSamplerParams::FilterMode textureFilterMode =
+ GrSkFilterQualityToGrFilterMode(args.fFilterQuality, *args.fViewMatrix, this->getLocalMatrix(),
+ &doBicubic);
+ GrSamplerParams params(tm, textureFilterMode);
+ sk_sp<SkColorSpace> texColorSpace;
+ SkScalar scaleAdjust[2] = { 1.0f, 1.0f };
+ sk_sp<GrTextureProxy> proxy(as_IB(fImage)->asTextureProxyRef(args.fContext, params,
+ args.fDstColorSpace,
+ &texColorSpace, scaleAdjust));
+ if (!proxy) {
+ return nullptr;
+ }
+
+ bool isAlphaOnly = GrPixelConfigIsAlphaOnly(proxy->config());
+
+ lmInverse.postScale(scaleAdjust[0], scaleAdjust[1]);
+
+ sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(texColorSpace.get(),
+ args.fDstColorSpace);
+ sk_sp<GrFragmentProcessor> inner;
+ if (doBicubic) {
+ inner = GrBicubicEffect::Make(args.fContext->resourceProvider(), std::move(proxy),
+ std::move(colorSpaceXform), lmInverse, tm);
+ } else {
+ inner = GrSimpleTextureEffect::Make(args.fContext->resourceProvider(), std::move(proxy),
+ std::move(colorSpaceXform), lmInverse, params);
+ }
+
+ if (isAlphaOnly) {
+ return inner;
+ }
+ return sk_sp<GrFragmentProcessor>(GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)));
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+#include "SkImagePriv.h"
+
+sk_sp<SkShader> SkMakeBitmapShader(const SkBitmap& src, SkShader::TileMode tmx,
+ SkShader::TileMode tmy, const SkMatrix* localMatrix,
+ SkCopyPixelsMode cpm) {
+ return SkImageShader::Make(SkMakeImageFromRasterBitmap(src, cpm),
+ tmx, tmy, localMatrix);
+}
+
+static sk_sp<SkFlattenable> SkBitmapProcShader_CreateProc(SkReadBuffer& buffer) {
+ SkMatrix lm;
+ buffer.readMatrix(&lm);
+ sk_sp<SkImage> image = buffer.readBitmapAsImage();
+ SkShader::TileMode mx = (SkShader::TileMode)buffer.readUInt();
+ SkShader::TileMode my = (SkShader::TileMode)buffer.readUInt();
+ return image ? image->makeShader(mx, my, &lm) : nullptr;
+}
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkShaderBase)
+SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkImageShader)
+SkFlattenable::Register("SkBitmapProcShader", SkBitmapProcShader_CreateProc, kSkShaderBase_Type);
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
+
+
+bool SkImageShader::onAppendStages(SkRasterPipeline* p, SkColorSpace* dstCS, SkArenaAlloc* alloc,
+ const SkMatrix& ctm, const SkPaint& paint,
+ const SkMatrix* localM) const {
+ auto matrix = SkMatrix::Concat(ctm, this->getLocalMatrix());
+ if (localM) {
+ matrix.preConcat(*localM);
+ }
+
+ if (!matrix.invert(&matrix)) {
+ return false;
+ }
+ auto quality = paint.getFilterQuality();
+
+ SkBitmapProvider provider(fImage.get(), dstCS);
+ SkDefaultBitmapController controller(SkDefaultBitmapController::CanShadeHQ::kYes);
+ std::unique_ptr<SkBitmapController::State> state {
+ controller.requestBitmap(provider, matrix, quality)
+ };
+ if (!state) {
+ return false;
+ }
+
+ const SkPixmap& pm = state->pixmap();
+ matrix = state->invMatrix();
+ quality = state->quality();
+ auto info = pm.info();
+
+ // When the matrix is just an integer translate, bilerp == nearest neighbor.
+ if (quality == kLow_SkFilterQuality &&
+ matrix.getType() <= SkMatrix::kTranslate_Mask &&
+ matrix.getTranslateX() == (int)matrix.getTranslateX() &&
+ matrix.getTranslateY() == (int)matrix.getTranslateY()) {
+ quality = kNone_SkFilterQuality;
+ }
+
+ // See skia:4649 and the GM image_scale_aligned.
+ if (quality == kNone_SkFilterQuality) {
+ if (matrix.getScaleX() >= 0) {
+ matrix.setTranslateX(nextafterf(matrix.getTranslateX(),
+ floorf(matrix.getTranslateX())));
+ }
+ if (matrix.getScaleY() >= 0) {
+ matrix.setTranslateY(nextafterf(matrix.getTranslateY(),
+ floorf(matrix.getTranslateY())));
+ }
+ }
+
+
+ struct MiscCtx {
+ std::unique_ptr<SkBitmapController::State> state;
+ SkColor4f paint_color;
+ float matrix[9];
+ };
+ auto misc = alloc->make<MiscCtx>();
+ misc->state = std::move(state); // Extend lifetime to match the pipeline's.
+ misc->paint_color = SkColor4f_from_SkColor(paint.getColor(), dstCS);
+ if (matrix.asAffine(misc->matrix)) {
+ p->append(SkRasterPipeline::matrix_2x3, misc->matrix);
+ } else {
+ matrix.get9(misc->matrix);
+ p->append(SkRasterPipeline::matrix_perspective, misc->matrix);
+ }
+
+ auto gather = alloc->make<SkJumper_GatherCtx>();
+ gather->pixels = pm.addr();
+ gather->ctable = pm.ctable() ? pm.ctable()->readColors() : nullptr;
+ gather->stride = pm.rowBytesAsPixels();
+
+ // Tiling stages (clamp_x, mirror_y, etc.) are inclusive of their limit,
+ // so we tick down our width and height by one float to make them exclusive.
+ auto ulp_before = [](float f) {
+ uint32_t bits;
+ memcpy(&bits, &f, 4);
+ bits--;
+ memcpy(&f, &bits, 4);
+ return f;
+ };
+ auto limit_x = alloc->make<float>(ulp_before((float)pm. width())),
+ limit_y = alloc->make<float>(ulp_before((float)pm.height()));
+
+ auto append_tiling_and_gather = [&] {
+ switch (fTileModeX) {
+ case kClamp_TileMode: p->append(SkRasterPipeline::clamp_x, limit_x); break;
+ case kMirror_TileMode: p->append(SkRasterPipeline::mirror_x, limit_x); break;
+ case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_x, limit_x); break;
+ }
+ switch (fTileModeY) {
+ case kClamp_TileMode: p->append(SkRasterPipeline::clamp_y, limit_y); break;
+ case kMirror_TileMode: p->append(SkRasterPipeline::mirror_y, limit_y); break;
+ case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_y, limit_y); break;
+ }
+ switch (info.colorType()) {
+ case kAlpha_8_SkColorType: p->append(SkRasterPipeline::gather_a8, gather); break;
+ case kIndex_8_SkColorType: p->append(SkRasterPipeline::gather_i8, gather); break;
+ case kGray_8_SkColorType: p->append(SkRasterPipeline::gather_g8, gather); break;
+ case kRGB_565_SkColorType: p->append(SkRasterPipeline::gather_565, gather); break;
+ case kARGB_4444_SkColorType: p->append(SkRasterPipeline::gather_4444, gather); break;
+ case kRGBA_8888_SkColorType:
+ case kBGRA_8888_SkColorType: p->append(SkRasterPipeline::gather_8888, gather); break;
+ case kRGBA_F16_SkColorType: p->append(SkRasterPipeline::gather_f16, gather); break;
+ default: SkASSERT(false);
+ }
+ if (info.gammaCloseToSRGB() && dstCS != nullptr) {
+ p->append_from_srgb(info.alphaType());
+ }
+ };
+
+ SkJumper_SamplerCtx* sampler = nullptr;
+ if (quality != kNone_SkFilterQuality) {
+ sampler = alloc->make<SkJumper_SamplerCtx>();
+ }
+
+ auto sample = [&](SkRasterPipeline::StockStage setup_x,
+ SkRasterPipeline::StockStage setup_y) {
+ p->append(setup_x, sampler);
+ p->append(setup_y, sampler);
+ append_tiling_and_gather();
+ p->append(SkRasterPipeline::accumulate, sampler);
+ };
+
+ if (quality == kNone_SkFilterQuality) {
+ append_tiling_and_gather();
+ } else if (quality == kLow_SkFilterQuality) {
+ p->append(SkRasterPipeline::save_xy, sampler);
+
+ sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_ny);
+ sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_ny);
+ sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_py);
+ sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_py);
+
+ p->append(SkRasterPipeline::move_dst_src);
+ } else {
+ p->append(SkRasterPipeline::save_xy, sampler);
+
+ sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n3y);
+ sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n3y);
+ sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n3y);
+ sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n3y);
+
+ sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n1y);
+ sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n1y);
+ sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n1y);
+ sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n1y);
+
+ sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p1y);
+ sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p1y);
+ sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p1y);
+ sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p1y);
+
+ sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p3y);
+ sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p3y);
+ sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p3y);
+ sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p3y);
+
+ p->append(SkRasterPipeline::move_dst_src);
+ }
+
+ auto effective_color_type = [](SkColorType ct) {
+ return ct == kIndex_8_SkColorType ? kN32_SkColorType : ct;
+ };
+
+ if (effective_color_type(info.colorType()) == kBGRA_8888_SkColorType) {
+ p->append(SkRasterPipeline::swap_rb);
+ }
+ if (info.colorType() == kAlpha_8_SkColorType) {
+ p->append(SkRasterPipeline::set_rgb, &misc->paint_color);
+ }
+ if (info.colorType() == kAlpha_8_SkColorType || info.alphaType() == kUnpremul_SkAlphaType) {
+ p->append(SkRasterPipeline::premul);
+ }
+ if (quality > kLow_SkFilterQuality) {
+ // Bicubic filtering naturally produces out of range values on both sides.
+ p->append(SkRasterPipeline::clamp_0);
+ p->append(SkRasterPipeline::clamp_a);
+ }
+ append_gamut_transform(p, alloc, info.colorSpace(), dstCS, kPremul_SkAlphaType);
+ return true;
+}
diff --git a/src/shaders/SkImageShader.h b/src/shaders/SkImageShader.h
new file mode 100644
index 0000000000..7be982c5c6
--- /dev/null
+++ b/src/shaders/SkImageShader.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkImageShader_DEFINED
+#define SkImageShader_DEFINED
+
+#include "SkBitmapProcShader.h"
+#include "SkColorSpaceXformer.h"
+#include "SkImage.h"
+#include "SkShaderBase.h"
+
+class SkImageShader : public SkShaderBase {
+public:
+ static sk_sp<SkShader> Make(sk_sp<SkImage>, TileMode tx, TileMode ty,
+ const SkMatrix* localMatrix);
+
+ bool isOpaque() const override;
+
+ SK_TO_STRING_OVERRIDE()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkImageShader)
+
+#if SK_SUPPORT_GPU
+ sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override;
+#endif
+
+ SkImageShader(sk_sp<SkImage>, TileMode tx, TileMode ty, const SkMatrix* localMatrix);
+
+protected:
+ void flatten(SkWriteBuffer&) const override;
+ Context* onMakeContext(const ContextRec&, SkArenaAlloc* storage) const override;
+#ifdef SK_SUPPORT_LEGACY_SHADER_ISABITMAP
+ bool onIsABitmap(SkBitmap*, SkMatrix*, TileMode*) const override;
+#endif
+ SkImage* onIsAImage(SkMatrix*, TileMode*) const override;
+
+ bool onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*,
+ const SkMatrix& ctm, const SkPaint&, const SkMatrix*) const override;
+
+ sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override {
+ return xformer->apply(fImage.get())->makeShader(fTileModeX, fTileModeY,
+ &this->getLocalMatrix());
+ }
+
+ sk_sp<SkImage> fImage;
+ const TileMode fTileModeX;
+ const TileMode fTileModeY;
+
+private:
+ friend class SkShaderBase;
+
+ typedef SkShaderBase INHERITED;
+};
+
+#endif
diff --git a/src/shaders/SkLightingShader.cpp b/src/shaders/SkLightingShader.cpp
new file mode 100644
index 0000000000..cdfa528e1e
--- /dev/null
+++ b/src/shaders/SkLightingShader.cpp
@@ -0,0 +1,488 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkArenaAlloc.h"
+#include "SkBitmapProcShader.h"
+#include "SkBitmapProcState.h"
+#include "SkColor.h"
+#include "SkColorSpaceXformer.h"
+#include "SkEmptyShader.h"
+#include "SkLightingShader.h"
+#include "SkMathPriv.h"
+#include "SkNormalSource.h"
+#include "SkPoint3.h"
+#include "SkReadBuffer.h"
+#include "SkShaderBase.h"
+#include "SkWriteBuffer.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+/*
+ SkLightingShader TODOs:
+ support different light types
+ support multiple lights
+ fix non-opaque diffuse textures
+
+ To Test:
+ A8 diffuse textures
+ down & upsampled draws
+*/
+
+
+
+/** \class SkLightingShaderImpl
+ This subclass of shader applies lighting.
+*/
+class SkLightingShaderImpl : public SkShaderBase {
+public:
+ /** Create a new lighting shader that uses the provided normal map and
+ lights to light the diffuse bitmap.
+ @param diffuseShader the shader that provides the diffuse colors
+ @param normalSource the source of normals for lighting computation
+ @param lights the lights applied to the geometry
+ */
+ SkLightingShaderImpl(sk_sp<SkShader> diffuseShader,
+ sk_sp<SkNormalSource> normalSource,
+ sk_sp<SkLights> lights)
+ : fDiffuseShader(std::move(diffuseShader))
+ , fNormalSource(std::move(normalSource))
+ , fLights(std::move(lights)) {}
+
+ bool isOpaque() const override;
+
+#if SK_SUPPORT_GPU
+ sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override;
+#endif
+
+ class LightingShaderContext : public Context {
+ public:
+ // The context takes ownership of the context and provider. It will call their destructors
+ // and then indirectly free their memory by calling free() on heapAllocated
+ LightingShaderContext(const SkLightingShaderImpl&, const ContextRec&,
+ SkShaderBase::Context* diffuseContext, SkNormalSource::Provider*,
+ void* heapAllocated);
+
+ void shadeSpan(int x, int y, SkPMColor[], int count) override;
+
+ uint32_t getFlags() const override { return fFlags; }
+
+ private:
+ SkShaderBase::Context* fDiffuseContext;
+ SkNormalSource::Provider* fNormalProvider;
+ SkColor fPaintColor;
+ uint32_t fFlags;
+
+ typedef Context INHERITED;
+ };
+
+ SK_TO_STRING_OVERRIDE()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingShaderImpl)
+
+protected:
+ void flatten(SkWriteBuffer&) const override;
+ Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
+ sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
+
+private:
+ sk_sp<SkShader> fDiffuseShader;
+ sk_sp<SkNormalSource> fNormalSource;
+ sk_sp<SkLights> fLights;
+
+ friend class SkLightingShader;
+
+ typedef SkShaderBase INHERITED;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrCoordTransform.h"
+#include "GrFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramDataManager.h"
+#include "glsl/GrGLSLUniformHandler.h"
+#include "SkGr.h"
+
+// This FP expects a premul'd color input for its diffuse color. Premul'ing of the paint's color is
+// handled by the asFragmentProcessor() factory, but shaders providing diffuse color must output it
+// premul'd.
+class LightingFP : public GrFragmentProcessor {
+public:
+ LightingFP(sk_sp<GrFragmentProcessor> normalFP, sk_sp<SkLights> lights)
+ : INHERITED(kPreservesOpaqueInput_OptimizationFlag) {
+ // fuse all ambient lights into a single one
+ fAmbientColor = lights->ambientLightColor();
+ for (int i = 0; i < lights->numLights(); ++i) {
+ if (SkLights::Light::kDirectional_LightType == lights->light(i).type()) {
+ fDirectionalLights.push_back(lights->light(i));
+ // TODO get the handle to the shadow map if there is one
+ } else {
+ SkDEBUGFAIL("Unimplemented Light Type passed to LightingFP");
+ }
+ }
+
+ this->registerChildProcessor(std::move(normalFP));
+ this->initClassID<LightingFP>();
+ }
+
+ class GLSLLightingFP : public GrGLSLFragmentProcessor {
+ public:
+ GLSLLightingFP() {
+ fAmbientColor.fX = 0.0f;
+ }
+
+ void emitCode(EmitArgs& args) override {
+
+ GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+ GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
+ const LightingFP& lightingFP = args.fFp.cast<LightingFP>();
+
+ const char *lightDirsUniName = nullptr;
+ const char *lightColorsUniName = nullptr;
+ if (lightingFP.fDirectionalLights.count() != 0) {
+ fLightDirsUni = uniformHandler->addUniformArray(
+ kFragment_GrShaderFlag,
+ kVec3f_GrSLType,
+ kDefault_GrSLPrecision,
+ "LightDir",
+ lightingFP.fDirectionalLights.count(),
+ &lightDirsUniName);
+ fLightColorsUni = uniformHandler->addUniformArray(
+ kFragment_GrShaderFlag,
+ kVec3f_GrSLType,
+ kDefault_GrSLPrecision,
+ "LightColor",
+ lightingFP.fDirectionalLights.count(),
+ &lightColorsUniName);
+ }
+
+ const char* ambientColorUniName = nullptr;
+ fAmbientColorUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
+ kVec3f_GrSLType, kDefault_GrSLPrecision,
+ "AmbientColor", &ambientColorUniName);
+
+ fragBuilder->codeAppendf("vec4 diffuseColor = %s;", args.fInputColor);
+
+ SkString dstNormalName("dstNormal");
+ this->emitChild(0, &dstNormalName, args);
+
+ fragBuilder->codeAppendf("vec3 normal = %s.xyz;", dstNormalName.c_str());
+
+ fragBuilder->codeAppend( "vec3 result = vec3(0.0);");
+
+ // diffuse light
+ if (lightingFP.fDirectionalLights.count() != 0) {
+ fragBuilder->codeAppendf("for (int i = 0; i < %d; i++) {",
+ lightingFP.fDirectionalLights.count());
+ // TODO: modulate the contribution from each light based on the shadow map
+ fragBuilder->codeAppendf(" float NdotL = clamp(dot(normal, %s[i]), 0.0, 1.0);",
+ lightDirsUniName);
+ fragBuilder->codeAppendf(" result += %s[i]*diffuseColor.rgb*NdotL;",
+ lightColorsUniName);
+ fragBuilder->codeAppend("}");
+ }
+
+ // ambient light
+ fragBuilder->codeAppendf("result += %s * diffuseColor.rgb;", ambientColorUniName);
+
+ // Clamping to alpha (equivalent to an unpremul'd clamp to 1.0)
+ fragBuilder->codeAppendf("%s = vec4(clamp(result.rgb, 0.0, diffuseColor.a), "
+ "diffuseColor.a);", args.fOutputColor);
+ }
+
+ static void GenKey(const GrProcessor& proc, const GrShaderCaps&, GrProcessorKeyBuilder* b) {
+ const LightingFP& lightingFP = proc.cast<LightingFP>();
+ b->add32(lightingFP.fDirectionalLights.count());
+ }
+
+ protected:
+ void onSetData(const GrGLSLProgramDataManager& pdman,
+ const GrFragmentProcessor& proc) override {
+ const LightingFP& lightingFP = proc.cast<LightingFP>();
+
+ const SkTArray<SkLights::Light>& directionalLights = lightingFP.directionalLights();
+ if (directionalLights != fDirectionalLights) {
+ SkTArray<SkColor3f> lightDirs(directionalLights.count());
+ SkTArray<SkVector3> lightColors(directionalLights.count());
+ for (const SkLights::Light& light : directionalLights) {
+ lightDirs.push_back(light.dir());
+ lightColors.push_back(light.color());
+ }
+
+ pdman.set3fv(fLightDirsUni, directionalLights.count(), &(lightDirs[0].fX));
+ pdman.set3fv(fLightColorsUni, directionalLights.count(), &(lightColors[0].fX));
+
+ fDirectionalLights = directionalLights;
+ }
+
+ const SkColor3f& ambientColor = lightingFP.ambientColor();
+ if (ambientColor != fAmbientColor) {
+ pdman.set3fv(fAmbientColorUni, 1, &ambientColor.fX);
+ fAmbientColor = ambientColor;
+ }
+ }
+
+ private:
+ SkTArray<SkLights::Light> fDirectionalLights;
+ GrGLSLProgramDataManager::UniformHandle fLightDirsUni;
+ GrGLSLProgramDataManager::UniformHandle fLightColorsUni;
+
+ SkColor3f fAmbientColor;
+ GrGLSLProgramDataManager::UniformHandle fAmbientColorUni;
+ };
+
+ void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
+ GLSLLightingFP::GenKey(*this, caps, b);
+ }
+
+ const char* name() const override { return "LightingFP"; }
+
+ const SkTArray<SkLights::Light>& directionalLights() const { return fDirectionalLights; }
+ const SkColor3f& ambientColor() const { return fAmbientColor; }
+
+private:
+ GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLLightingFP; }
+
+ bool onIsEqual(const GrFragmentProcessor& proc) const override {
+ const LightingFP& lightingFP = proc.cast<LightingFP>();
+ return fDirectionalLights == lightingFP.fDirectionalLights &&
+ fAmbientColor == lightingFP.fAmbientColor;
+ }
+
+ SkTArray<SkLights::Light> fDirectionalLights;
+ SkColor3f fAmbientColor;
+
+ typedef GrFragmentProcessor INHERITED;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+sk_sp<GrFragmentProcessor> SkLightingShaderImpl::asFragmentProcessor(const AsFPArgs& args) const {
+ sk_sp<GrFragmentProcessor> normalFP(fNormalSource->asFragmentProcessor(args));
+ if (!normalFP) {
+ return nullptr;
+ }
+
+ if (fDiffuseShader) {
+ sk_sp<GrFragmentProcessor> fpPipeline[] = {
+ as_SB(fDiffuseShader)->asFragmentProcessor(args),
+ sk_make_sp<LightingFP>(std::move(normalFP), fLights)
+ };
+ if(!fpPipeline[0]) {
+ return nullptr;
+ }
+
+ sk_sp<GrFragmentProcessor> innerLightFP = GrFragmentProcessor::RunInSeries(fpPipeline, 2);
+ // FP is wrapped because paint's alpha needs to be applied to output
+ return GrFragmentProcessor::MulOutputByInputAlpha(std::move(innerLightFP));
+ } else {
+ // FP is wrapped because paint comes in unpremul'd to fragment shader, but LightingFP
+ // expects premul'd color.
+ return GrFragmentProcessor::PremulInput(sk_make_sp<LightingFP>(std::move(normalFP),
+ fLights));
+ }
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////
+
+bool SkLightingShaderImpl::isOpaque() const {
+ return (fDiffuseShader ? fDiffuseShader->isOpaque() : false);
+}
+
+SkLightingShaderImpl::LightingShaderContext::LightingShaderContext(
+ const SkLightingShaderImpl& shader, const ContextRec& rec,
+ SkShaderBase::Context* diffuseContext, SkNormalSource::Provider* normalProvider,
+ void* heapAllocated)
+ : INHERITED(shader, rec)
+ , fDiffuseContext(diffuseContext)
+ , fNormalProvider(normalProvider) {
+ bool isOpaque = shader.isOpaque();
+
+ // update fFlags
+ uint32_t flags = 0;
+ if (isOpaque && (255 == this->getPaintAlpha())) {
+ flags |= kOpaqueAlpha_Flag;
+ }
+
+ fPaintColor = rec.fPaint->getColor();
+ fFlags = flags;
+}
+
+static inline SkPMColor convert(SkColor3f color, U8CPU a) {
+ if (color.fX <= 0.0f) {
+ color.fX = 0.0f;
+ } else if (color.fX >= 255.0f) {
+ color.fX = 255.0f;
+ }
+
+ if (color.fY <= 0.0f) {
+ color.fY = 0.0f;
+ } else if (color.fY >= 255.0f) {
+ color.fY = 255.0f;
+ }
+
+ if (color.fZ <= 0.0f) {
+ color.fZ = 0.0f;
+ } else if (color.fZ >= 255.0f) {
+ color.fZ = 255.0f;
+ }
+
+ return SkPreMultiplyARGB(a, (int) color.fX, (int) color.fY, (int) color.fZ);
+}
+
+// larger is better (fewer times we have to loop), but we shouldn't
+// take up too much stack-space (each one here costs 16 bytes)
+#define BUFFER_MAX 16
+void SkLightingShaderImpl::LightingShaderContext::shadeSpan(int x, int y,
+ SkPMColor result[], int count) {
+ const SkLightingShaderImpl& lightShader = static_cast<const SkLightingShaderImpl&>(fShader);
+
+ SkPMColor diffuse[BUFFER_MAX];
+ SkPoint3 normals[BUFFER_MAX];
+
+ SkColor diffColor = fPaintColor;
+
+ do {
+ int n = SkTMin(count, BUFFER_MAX);
+
+ fNormalProvider->fillScanLine(x, y, normals, n);
+
+ if (fDiffuseContext) {
+ fDiffuseContext->shadeSpan(x, y, diffuse, n);
+ }
+
+ for (int i = 0; i < n; ++i) {
+ if (fDiffuseContext) {
+ diffColor = SkUnPreMultiply::PMColorToColor(diffuse[i]);
+ }
+
+ SkColor3f accum = SkColor3f::Make(0.0f, 0.0f, 0.0f);
+
+ // Adding ambient light
+ accum.fX += lightShader.fLights->ambientLightColor().fX * SkColorGetR(diffColor);
+ accum.fY += lightShader.fLights->ambientLightColor().fY * SkColorGetG(diffColor);
+ accum.fZ += lightShader.fLights->ambientLightColor().fZ * SkColorGetB(diffColor);
+
+ // This is all done in linear unpremul color space (each component 0..255.0f though)
+ for (int l = 0; l < lightShader.fLights->numLights(); ++l) {
+ const SkLights::Light& light = lightShader.fLights->light(l);
+
+ SkScalar illuminanceScalingFactor = 1.0f;
+
+ if (SkLights::Light::kDirectional_LightType == light.type()) {
+ illuminanceScalingFactor = normals[i].dot(light.dir());
+ if (illuminanceScalingFactor < 0.0f) {
+ illuminanceScalingFactor = 0.0f;
+ }
+ }
+
+ accum.fX += light.color().fX * SkColorGetR(diffColor) * illuminanceScalingFactor;
+ accum.fY += light.color().fY * SkColorGetG(diffColor) * illuminanceScalingFactor;
+ accum.fZ += light.color().fZ * SkColorGetB(diffColor) * illuminanceScalingFactor;
+ }
+
+ // convert() premultiplies the accumulate color with alpha
+ result[i] = convert(accum, SkColorGetA(diffColor));
+ }
+
+ result += n;
+ x += n;
+ count -= n;
+ } while (count > 0);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+#ifndef SK_IGNORE_TO_STRING
+void SkLightingShaderImpl::toString(SkString* str) const {
+ str->appendf("LightingShader: ()");
+}
+#endif
+
+sk_sp<SkFlattenable> SkLightingShaderImpl::CreateProc(SkReadBuffer& buf) {
+
+ // Discarding SkShader flattenable params
+ bool hasLocalMatrix = buf.readBool();
+ SkAssertResult(!hasLocalMatrix);
+
+ sk_sp<SkLights> lights = SkLights::MakeFromBuffer(buf);
+
+ sk_sp<SkNormalSource> normalSource(buf.readFlattenable<SkNormalSource>());
+
+ bool hasDiffuse = buf.readBool();
+ sk_sp<SkShader> diffuseShader = nullptr;
+ if (hasDiffuse) {
+ diffuseShader = buf.readFlattenable<SkShaderBase>();
+ }
+
+ return sk_make_sp<SkLightingShaderImpl>(std::move(diffuseShader), std::move(normalSource),
+ std::move(lights));
+}
+
+void SkLightingShaderImpl::flatten(SkWriteBuffer& buf) const {
+ this->INHERITED::flatten(buf);
+
+ fLights->flatten(buf);
+
+ buf.writeFlattenable(fNormalSource.get());
+ buf.writeBool(fDiffuseShader);
+ if (fDiffuseShader) {
+ buf.writeFlattenable(fDiffuseShader.get());
+ }
+}
+
+SkShaderBase::Context* SkLightingShaderImpl::onMakeContext(
+ const ContextRec& rec, SkArenaAlloc* alloc) const
+{
+ SkShaderBase::Context *diffuseContext = nullptr;
+ if (fDiffuseShader) {
+ diffuseContext = as_SB(fDiffuseShader)->makeContext(rec, alloc);
+ if (!diffuseContext) {
+ return nullptr;
+ }
+ }
+
+ SkNormalSource::Provider* normalProvider = fNormalSource->asProvider(rec, alloc);
+ if (!normalProvider) {
+ return nullptr;
+ }
+
+ return alloc->make<LightingShaderContext>(*this, rec, diffuseContext, normalProvider, nullptr);
+}
+
+sk_sp<SkShader> SkLightingShaderImpl::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
+ sk_sp<SkShader> xformedDiffuseShader =
+ fDiffuseShader ? xformer->apply(fDiffuseShader.get()) : nullptr;
+ return SkLightingShader::Make(std::move(xformedDiffuseShader), fNormalSource,
+ fLights->makeColorSpace(xformer));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+sk_sp<SkShader> SkLightingShader::Make(sk_sp<SkShader> diffuseShader,
+ sk_sp<SkNormalSource> normalSource,
+ sk_sp<SkLights> lights) {
+ SkASSERT(lights);
+ if (!normalSource) {
+ normalSource = SkNormalSource::MakeFlat();
+ }
+
+ return sk_make_sp<SkLightingShaderImpl>(std::move(diffuseShader), std::move(normalSource),
+ std::move(lights));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkLightingShader)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingShaderImpl)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
+
+///////////////////////////////////////////////////////////////////////////////
diff --git a/src/shaders/SkLightingShader.h b/src/shaders/SkLightingShader.h
new file mode 100644
index 0000000000..aa90710aa4
--- /dev/null
+++ b/src/shaders/SkLightingShader.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkLightingShader_DEFINED
+#define SkLightingShader_DEFINED
+
+#include "SkLights.h"
+#include "SkShader.h"
+
+class SkBitmap;
+class SkMatrix;
+class SkNormalSource;
+
+class SK_API SkLightingShader {
+public:
+ /** Returns a shader that lights the shape, colored by the diffuseShader, using the
+ normals from normalSource, with the set of lights provided.
+
+ @param diffuseShader the shader that provides the colors. If nullptr, uses the paint's
+ color.
+ @param normalSource the source for the shape's normals. If nullptr, assumes straight
+ up normals (<0,0,1>).
+ @param lights the lights applied to the normals
+
+ The lighting equation is currently:
+ result = (LightColor * dot(Normal, LightDir) + AmbientColor) * DiffuseColor
+
+ */
+ static sk_sp<SkShader> Make(sk_sp<SkShader> diffuseShader, sk_sp<SkNormalSource> normalSource,
+ sk_sp<SkLights> lights);
+
+ SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
+};
+
+#endif
diff --git a/src/shaders/SkLocalMatrixShader.cpp b/src/shaders/SkLocalMatrixShader.cpp
new file mode 100644
index 0000000000..e21e4a84b7
--- /dev/null
+++ b/src/shaders/SkLocalMatrixShader.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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 "SkLocalMatrixShader.h"
+
+#if SK_SUPPORT_GPU
+#include "GrFragmentProcessor.h"
+#endif
+
+#if SK_SUPPORT_GPU
+sk_sp<GrFragmentProcessor> SkLocalMatrixShader::asFragmentProcessor(const AsFPArgs& args) const {
+ SkMatrix tmp = this->getLocalMatrix();
+ if (args.fLocalMatrix) {
+ tmp.preConcat(*args.fLocalMatrix);
+ }
+ return as_SB(fProxyShader)->asFragmentProcessor(AsFPArgs(
+ args.fContext, args.fViewMatrix, &tmp, args.fFilterQuality, args.fDstColorSpace));
+}
+#endif
+
+sk_sp<SkFlattenable> SkLocalMatrixShader::CreateProc(SkReadBuffer& buffer) {
+ SkMatrix lm;
+ buffer.readMatrix(&lm);
+ auto baseShader(buffer.readShader());
+ if (!baseShader) {
+ return nullptr;
+ }
+ return baseShader->makeWithLocalMatrix(lm);
+}
+
+void SkLocalMatrixShader::flatten(SkWriteBuffer& buffer) const {
+ buffer.writeMatrix(this->getLocalMatrix());
+ buffer.writeFlattenable(fProxyShader.get());
+}
+
+SkShaderBase::Context* SkLocalMatrixShader::onMakeContext(
+ const ContextRec& rec, SkArenaAlloc* alloc) const
+{
+ ContextRec newRec(rec);
+ SkMatrix tmp;
+ if (rec.fLocalMatrix) {
+ tmp.setConcat(*rec.fLocalMatrix, this->getLocalMatrix());
+ newRec.fLocalMatrix = &tmp;
+ } else {
+ newRec.fLocalMatrix = &this->getLocalMatrix();
+ }
+ return as_SB(fProxyShader)->makeContext(newRec, alloc);
+}
+
+SkImage* SkLocalMatrixShader::onIsAImage(SkMatrix* outMatrix, enum TileMode* mode) const {
+ SkMatrix imageMatrix;
+ SkImage* image = fProxyShader->isAImage(&imageMatrix, mode);
+ if (image && outMatrix) {
+ // Local matrix must be applied first so it is on the right side of the concat.
+ *outMatrix = SkMatrix::Concat(imageMatrix, this->getLocalMatrix());
+ }
+
+ return image;
+}
+
+bool SkLocalMatrixShader::onAppendStages(SkRasterPipeline* p,
+ SkColorSpace* dst,
+ SkArenaAlloc* scratch,
+ const SkMatrix& ctm,
+ const SkPaint& paint,
+ const SkMatrix* localM) const {
+ SkMatrix tmp;
+ if (localM) {
+ tmp.setConcat(*localM, this->getLocalMatrix());
+ }
+ return as_SB(fProxyShader)->appendStages(p, dst, scratch, ctm, paint,
+ localM ? &tmp : &this->getLocalMatrix());
+}
+
+#ifndef SK_IGNORE_TO_STRING
+void SkLocalMatrixShader::toString(SkString* str) const {
+ str->append("SkLocalMatrixShader: (");
+
+ as_SB(fProxyShader)->toString(str);
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
+
+sk_sp<SkShader> SkShader::makeWithLocalMatrix(const SkMatrix& localMatrix) const {
+ if (localMatrix.isIdentity()) {
+ return sk_ref_sp(const_cast<SkShader*>(this));
+ }
+
+ const SkMatrix* lm = &localMatrix;
+
+ sk_sp<SkShader> baseShader;
+ SkMatrix otherLocalMatrix;
+ sk_sp<SkShader> proxy(as_SB(this)->makeAsALocalMatrixShader(&otherLocalMatrix));
+ if (proxy) {
+ otherLocalMatrix.preConcat(localMatrix);
+ lm = &otherLocalMatrix;
+ baseShader = proxy;
+ } else {
+ baseShader = sk_ref_sp(const_cast<SkShader*>(this));
+ }
+
+ return sk_make_sp<SkLocalMatrixShader>(std::move(baseShader), *lm);
+}
diff --git a/src/shaders/SkLocalMatrixShader.h b/src/shaders/SkLocalMatrixShader.h
new file mode 100644
index 0000000000..4572e9fe2e
--- /dev/null
+++ b/src/shaders/SkLocalMatrixShader.h
@@ -0,0 +1,75 @@
+/*
+ * 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 SkLocalMatrixShader_DEFINED
+#define SkLocalMatrixShader_DEFINED
+
+#include "SkShaderBase.h"
+#include "SkReadBuffer.h"
+#include "SkWriteBuffer.h"
+
+class GrFragmentProcessor;
+class SkArenaAlloc;
+class SkColorSpaceXformer;
+
+class SkLocalMatrixShader : public SkShaderBase {
+public:
+ SkLocalMatrixShader(sk_sp<SkShader> proxy, const SkMatrix& localMatrix)
+ : INHERITED(&localMatrix)
+ , fProxyShader(std::move(proxy))
+ {}
+
+ GradientType asAGradient(GradientInfo* info) const override {
+ return fProxyShader->asAGradient(info);
+ }
+
+#if SK_SUPPORT_GPU
+ sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override;
+#endif
+
+ sk_sp<SkShader> makeAsALocalMatrixShader(SkMatrix* localMatrix) const override {
+ if (localMatrix) {
+ *localMatrix = this->getLocalMatrix();
+ }
+ return fProxyShader;
+ }
+
+ SK_TO_STRING_OVERRIDE()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLocalMatrixShader)
+
+protected:
+ void flatten(SkWriteBuffer&) const override;
+
+ Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
+
+ SkImage* onIsAImage(SkMatrix* matrix, TileMode* mode) const override;
+
+ bool onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*,
+ const SkMatrix&, const SkPaint&, const SkMatrix*) const override;
+
+ sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override {
+ return as_SB(fProxyShader)->makeColorSpace(xformer)->makeWithLocalMatrix(
+ this->getLocalMatrix());
+ }
+
+#ifdef SK_SUPPORT_LEGACY_SHADER_ISABITMAP
+ bool onIsABitmap(SkBitmap* bitmap, SkMatrix* matrix, TileMode* mode) const override {
+ return fProxyShader->isABitmap(bitmap, matrix, mode);
+ }
+#endif
+
+ bool isRasterPipelineOnly() const final {
+ return as_SB(fProxyShader)->isRasterPipelineOnly();
+ }
+
+private:
+ sk_sp<SkShader> fProxyShader;
+
+ typedef SkShaderBase INHERITED;
+};
+
+#endif
diff --git a/src/shaders/SkPerlinNoiseShader.cpp b/src/shaders/SkPerlinNoiseShader.cpp
new file mode 100644
index 0000000000..9d8d030002
--- /dev/null
+++ b/src/shaders/SkPerlinNoiseShader.cpp
@@ -0,0 +1,1496 @@
+/*
+ * 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 "SkDither.h"
+#include "SkColorFilter.h"
+#include "SkReadBuffer.h"
+#include "SkWriteBuffer.h"
+#include "SkShader.h"
+#include "SkUnPreMultiply.h"
+#include "SkString.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
+
+static uint8_t improved_noise_permutations[] = {
+ 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103,
+ 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26,
+ 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174,
+ 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231,
+ 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143,
+ 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
+ 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124,
+ 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17,
+ 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101,
+ 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185,
+ 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81,
+ 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176,
+ 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243,
+ 141, 128, 195, 78, 66, 215, 61, 156, 180,
+ 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103,
+ 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26,
+ 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174,
+ 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231,
+ 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143,
+ 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
+ 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124,
+ 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17,
+ 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101,
+ 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185,
+ 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81,
+ 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176,
+ 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243,
+ 141, 128, 195, 78, 66, 215, 61, 156, 180
+};
+
+class SkPerlinNoiseShaderImpl : public SkShaderBase {
+public:
+ struct StitchData;
+ struct PaintingData;
+
+ /**
+ * About the noise types : the difference between the first 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.
+ * "Improved" is based on the Improved Perlin Noise algorithm described at
+ * http://mrl.nyu.edu/~perlin/noise/. It is quite distinct from the other two, and the noise is
+ * a 2D slice of a 3D noise texture. Minor changes to the Z coordinate will result in minor
+ * changes to the noise, making it suitable for animated noise.
+ */
+ enum Type {
+ kFractalNoise_Type,
+ kTurbulence_Type,
+ kImprovedNoise_Type,
+ kFirstType = kFractalNoise_Type,
+ kLastType = kImprovedNoise_Type
+ };
+
+ SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::Type type, SkScalar baseFrequencyX,
+ SkScalar baseFrequencyY, int numOctaves, SkScalar seed,
+ const SkISize* tileSize);
+ ~SkPerlinNoiseShaderImpl() override;
+
+
+ class PerlinNoiseShaderContext : 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 calculateImprovedNoiseValueForPoint(int channel, const SkPoint& point) const;
+ SkScalar noise2D(int channel,
+ const StitchData& stitchData, const SkPoint& noiseVector) const;
+
+ SkMatrix fMatrix;
+ PaintingData* fPaintingData;
+
+ typedef Context INHERITED;
+ };
+
+#if SK_SUPPORT_GPU
+ sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override;
+#endif
+
+ SK_TO_STRING_OVERRIDE()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPerlinNoiseShaderImpl)
+
+protected:
+ void flatten(SkWriteBuffer&) const override;
+ Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
+
+private:
+ const SkPerlinNoiseShaderImpl::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;
+};
+
+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);
+}
+
+} // 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]);
+
+ fImprovedPermutationsBitmap.setInfo(SkImageInfo::MakeA8(256, 1));
+ fImprovedPermutationsBitmap.setPixels(improved_noise_permutations);
+
+ fGradientBitmap.setInfo(SkImageInfo::MakeN32Premul(16, 1));
+ static uint8_t gradients[] = { 2, 2, 1, 0,
+ 0, 2, 1, 0,
+ 2, 0, 1, 0,
+ 0, 0, 1, 0,
+ 2, 1, 2, 0,
+ 0, 1, 2, 0,
+ 2, 1, 0, 0,
+ 0, 1, 0, 0,
+ 1, 2, 2, 0,
+ 1, 0, 2, 0,
+ 1, 2, 0, 0,
+ 1, 0, 0, 0,
+ 2, 2, 1, 0,
+ 1, 0, 2, 0,
+ 0, 2, 1, 0,
+ 1, 0, 0, 0 };
+ fGradientBitmap.setPixels(gradients);
+#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;
+ SkBitmap fImprovedPermutationsBitmap;
+ SkBitmap fGradientBitmap;
+#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; }
+
+ const SkBitmap& getImprovedPermutationsBitmap() const { return fImprovedPermutationsBitmap; }
+
+ const SkBitmap& getGradientBitmap() const { return fGradientBitmap; }
+#endif
+};
+
+SkPerlinNoiseShaderImpl::SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::Type type,
+ SkScalar baseFrequencyX,
+ SkScalar baseFrequencyY,
+ int numOctaves,
+ SkScalar seed,
+ const SkISize* tileSize)
+ : fType(type)
+ , fBaseFrequencyX(baseFrequencyX)
+ , fBaseFrequencyY(baseFrequencyY)
+ , fNumOctaves(numOctaves > 255 ? 255 : numOctaves/*[0,255] octaves allowed*/)
+ , fSeed(seed)
+ , fTileSize(nullptr == tileSize ? SkISize::Make(0, 0) : *tileSize)
+ , fStitchTiles(!fTileSize.isEmpty())
+{
+ SkASSERT(numOctaves >= 0 && numOctaves < 256);
+}
+
+SkPerlinNoiseShaderImpl::~SkPerlinNoiseShaderImpl() {
+}
+
+sk_sp<SkFlattenable> 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);
+ case kImprovedNoise_Type:
+ return SkPerlinNoiseShader::MakeImprovedNoise(freqX, freqY, octaves, seed);
+ 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<const SkPerlinNoiseShaderImpl&>(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<const SkPerlinNoiseShaderImpl&>(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 = SkScalarHalf(turbulenceFunctionResult + 1);
+ }
+
+ if (channel == 3) { // Scale alpha by paint value
+ turbulenceFunctionResult *= SkIntToScalar(getPaintAlpha()) / 255;
+ }
+
+ // Clamp result
+ return SkScalarPin(turbulenceFunctionResult, 0, SK_Scalar1);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Improved Perlin Noise based on Java implementation found at http://mrl.nyu.edu/~perlin/noise/
+static SkScalar fade(SkScalar t) {
+ return t * t * t * (t * (t * 6 - 15) + 10);
+}
+
+static SkScalar lerp(SkScalar t, SkScalar a, SkScalar b) {
+ return a + t * (b - a);
+}
+
+static SkScalar grad(int hash, SkScalar x, SkScalar y, SkScalar z) {
+ int h = hash & 15;
+ SkScalar u = h < 8 ? x : y;
+ SkScalar v = h < 4 ? y : h == 12 || h == 14 ? x : z;
+ return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
+}
+
+SkScalar SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::calculateImprovedNoiseValueForPoint(
+ int channel, const SkPoint& point) const {
+ const SkPerlinNoiseShaderImpl& perlinNoiseShader = static_cast<const SkPerlinNoiseShaderImpl&>(fShader);
+ SkScalar x = point.fX * perlinNoiseShader.fBaseFrequencyX;
+ SkScalar y = point.fY * perlinNoiseShader.fBaseFrequencyY;
+ // z offset between different channels, chosen arbitrarily
+ static const SkScalar CHANNEL_DELTA = 1000.0f;
+ SkScalar z = channel * CHANNEL_DELTA + perlinNoiseShader.fSeed;
+ SkScalar result = 0;
+ SkScalar ratio = SK_Scalar1;
+ for (int i = 0; i < perlinNoiseShader.fNumOctaves; i++) {
+ int X = SkScalarFloorToInt(x) & 255;
+ int Y = SkScalarFloorToInt(y) & 255;
+ int Z = SkScalarFloorToInt(z) & 255;
+ SkScalar px = x - SkScalarFloorToScalar(x);
+ SkScalar py = y - SkScalarFloorToScalar(y);
+ SkScalar pz = z - SkScalarFloorToScalar(z);
+ SkScalar u = fade(px);
+ SkScalar v = fade(py);
+ SkScalar w = fade(pz);
+ uint8_t* permutations = improved_noise_permutations;
+ int A = permutations[X] + Y;
+ int AA = permutations[A] + Z;
+ int AB = permutations[A + 1] + Z;
+ int B = permutations[X + 1] + Y;
+ int BA = permutations[B] + Z;
+ int BB = permutations[B + 1] + Z;
+ result += lerp(w, lerp(v, lerp(u, grad(permutations[AA ], px , py , pz ),
+ grad(permutations[BA ], px - 1, py , pz )),
+ lerp(u, grad(permutations[AB ], px , py - 1, pz ),
+ grad(permutations[BB ], px - 1, py - 1, pz ))),
+ lerp(v, lerp(u, grad(permutations[AA + 1], px , py , pz - 1),
+ grad(permutations[BA + 1], px - 1, py , pz - 1)),
+ lerp(u, grad(permutations[AB + 1], px , py - 1, pz - 1),
+ grad(permutations[BB + 1], px - 1, py - 1, pz - 1)))) /
+ ratio;
+ x *= 2;
+ y *= 2;
+ ratio *= 2;
+ }
+ result = SkScalarClampMax((result + 1.0f) / 2.0f, 1.0f);
+ return result;
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+SkPMColor SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::shade(
+ const SkPoint& point, StitchData& stitchData) const {
+ const SkPerlinNoiseShaderImpl& perlinNoiseShader = static_cast<const SkPerlinNoiseShaderImpl&>(fShader);
+ 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) {
+ SkScalar value;
+ if (perlinNoiseShader.fType == kImprovedNoise_Type) {
+ value = calculateImprovedNoiseValueForPoint(channel, newPoint);
+ }
+ else {
+ value = calculateTurbulenceValueForPoint(channel, stitchData, newPoint);
+ }
+ rgba[channel] = SkScalarFloorToInt(255 * value);
+ }
+ return SkPreMultiplyARGB(rgba[3], rgba[0], rgba[1], rgba[2]);
+}
+
+SkShaderBase::Context* SkPerlinNoiseShaderImpl::onMakeContext(const ContextRec& rec,
+ SkArenaAlloc* alloc) const {
+ return alloc->make<PerlinNoiseShaderContext>(*this, rec);
+}
+
+SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::PerlinNoiseShaderContext(
+ const SkPerlinNoiseShaderImpl& shader, const ContextRec& rec)
+ : INHERITED(shader, rec)
+{
+ SkMatrix newMatrix = *rec.fMatrix;
+ newMatrix.preConcat(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* b);
+
+protected:
+ void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override;
+
+private:
+ GrGLSLProgramDataManager::UniformHandle fStitchDataUni;
+ GrGLSLProgramDataManager::UniformHandle fBaseFrequencyUni;
+
+ typedef GrGLSLFragmentProcessor INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrPerlinNoise2Effect : public GrFragmentProcessor {
+public:
+ static sk_sp<GrFragmentProcessor> Make(GrResourceProvider* resourceProvider,
+ SkPerlinNoiseShaderImpl::Type type,
+ int numOctaves, bool stitchTiles,
+ SkPerlinNoiseShaderImpl::PaintingData* paintingData,
+ sk_sp<GrTextureProxy> permutationsProxy,
+ sk_sp<GrTextureProxy> noiseProxy,
+ const SkMatrix& matrix) {
+ return sk_sp<GrFragmentProcessor>(
+ new GrPerlinNoise2Effect(resourceProvider, type, numOctaves, stitchTiles, paintingData,
+ std::move(permutationsProxy), std::move(noiseProxy), matrix));
+ }
+
+ ~GrPerlinNoise2Effect() 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; }
+ const SkMatrix& matrix() const { return fCoordTransform.getMatrix(); }
+
+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 GrPerlinNoise2Effect& s = sBase.cast<GrPerlinNoise2Effect>();
+ return fType == s.fType &&
+ fPaintingData->fBaseFrequency == s.fPaintingData->fBaseFrequency &&
+ fNumOctaves == s.fNumOctaves &&
+ fStitchTiles == s.fStitchTiles &&
+ fPaintingData->fStitchDataInit == s.fPaintingData->fStitchDataInit;
+ }
+
+ GrPerlinNoise2Effect(GrResourceProvider* resourceProvider,
+ SkPerlinNoiseShaderImpl::Type type, int numOctaves, bool stitchTiles,
+ SkPerlinNoiseShaderImpl::PaintingData* paintingData,
+ sk_sp<GrTextureProxy> permutationsProxy,
+ sk_sp<GrTextureProxy> noiseProxy,
+ const SkMatrix& matrix)
+ : INHERITED(kNone_OptimizationFlags)
+ , fType(type)
+ , fNumOctaves(numOctaves)
+ , fStitchTiles(stitchTiles)
+ , fPermutationsSampler(resourceProvider, std::move(permutationsProxy))
+ , fNoiseSampler(resourceProvider, std::move(noiseProxy))
+ , fPaintingData(paintingData) {
+ this->initClassID<GrPerlinNoise2Effect>();
+ this->addTextureSampler(&fPermutationsSampler);
+ this->addTextureSampler(&fNoiseSampler);
+ fCoordTransform.reset(matrix);
+ this->addCoordTransform(&fCoordTransform);
+ }
+
+ GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
+
+ SkPerlinNoiseShaderImpl::Type fType;
+ GrCoordTransform fCoordTransform;
+ int fNumOctaves;
+ bool fStitchTiles;
+ TextureSampler fPermutationsSampler;
+ TextureSampler fNoiseSampler;
+ SkPerlinNoiseShaderImpl::PaintingData* fPaintingData;
+
+ typedef GrFragmentProcessor INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrPerlinNoise2Effect);
+
+#if GR_TEST_UTILS
+sk_sp<GrFragmentProcessor> GrPerlinNoise2Effect::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<SkShader> 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 GrPerlinNoise2Effect& pne = args.fFp.cast<GrPerlinNoise2Effect>();
+
+ GrGLSLFragmentBuilder* 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("\n\t\tvec2 %s = %s;", stitchData, stitchDataUni);
+ }
+
+ fragBuilder->codeAppendf("\n\t\tfloat %s = 1.0;", ratio);
+
+ // Loop over all octaves
+ fragBuilder->codeAppendf("for (int octave = 0; octave < %d; ++octave) {", pne.numOctaves());
+
+ fragBuilder->codeAppendf("\n\t\t\t%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 GrPerlinNoise2Effect& turbulence = processor.cast<GrPerlinNoise2Effect>();
+
+ 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 GrPerlinNoise2Effect& turbulence = processor.cast<GrPerlinNoise2Effect>();
+
+ 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));
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+
+class GrGLImprovedPerlinNoise : 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 fZUni;
+ GrGLSLProgramDataManager::UniformHandle fOctavesUni;
+ GrGLSLProgramDataManager::UniformHandle fBaseFrequencyUni;
+
+ typedef GrGLSLFragmentProcessor INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrImprovedPerlinNoiseEffect : public GrFragmentProcessor {
+public:
+ static sk_sp<GrFragmentProcessor> Make(GrResourceProvider* resourceProvider,
+ int octaves, SkScalar z,
+ SkPerlinNoiseShaderImpl::PaintingData* paintingData,
+ sk_sp<GrTextureProxy> permutationsProxy,
+ sk_sp<GrTextureProxy> gradientProxy,
+ const SkMatrix& matrix) {
+ return sk_sp<GrFragmentProcessor>(
+ new GrImprovedPerlinNoiseEffect(resourceProvider, octaves, z, paintingData,
+ std::move(permutationsProxy),
+ std::move(gradientProxy), matrix));
+ }
+
+ ~GrImprovedPerlinNoiseEffect() override { delete fPaintingData; }
+
+ const char* name() const override { return "ImprovedPerlinNoise"; }
+
+ const SkVector& baseFrequency() const { return fPaintingData->fBaseFrequency; }
+ SkScalar z() const { return fZ; }
+ int octaves() const { return fOctaves; }
+ const SkMatrix& matrix() const { return fCoordTransform.getMatrix(); }
+
+private:
+ GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
+ return new GrGLImprovedPerlinNoise;
+ }
+
+ void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
+ GrGLImprovedPerlinNoise::GenKey(*this, caps, b);
+ }
+
+ bool onIsEqual(const GrFragmentProcessor& sBase) const override {
+ const GrImprovedPerlinNoiseEffect& s = sBase.cast<GrImprovedPerlinNoiseEffect>();
+ return fZ == fZ &&
+ fPaintingData->fBaseFrequency == s.fPaintingData->fBaseFrequency;
+ }
+
+ GrImprovedPerlinNoiseEffect(GrResourceProvider* resourceProvider,
+ int octaves, SkScalar z,
+ SkPerlinNoiseShaderImpl::PaintingData* paintingData,
+ sk_sp<GrTextureProxy> permutationsProxy,
+ sk_sp<GrTextureProxy> gradientProxy,
+ const SkMatrix& matrix)
+ : INHERITED(kNone_OptimizationFlags)
+ , fOctaves(octaves)
+ , fZ(z)
+ , fPermutationsSampler(resourceProvider, std::move(permutationsProxy))
+ , fGradientSampler(resourceProvider, std::move(gradientProxy))
+ , fPaintingData(paintingData) {
+ this->initClassID<GrImprovedPerlinNoiseEffect>();
+ this->addTextureSampler(&fPermutationsSampler);
+ this->addTextureSampler(&fGradientSampler);
+ fCoordTransform.reset(matrix);
+ this->addCoordTransform(&fCoordTransform);
+ }
+
+ GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
+
+ GrCoordTransform fCoordTransform;
+ int fOctaves;
+ SkScalar fZ;
+ TextureSampler fPermutationsSampler;
+ TextureSampler fGradientSampler;
+ SkPerlinNoiseShaderImpl::PaintingData* fPaintingData;
+
+ typedef GrFragmentProcessor INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrImprovedPerlinNoiseEffect);
+
+#if GR_TEST_UTILS
+sk_sp<GrFragmentProcessor> GrImprovedPerlinNoiseEffect::TestCreate(GrProcessorTestData* d) {
+ SkScalar baseFrequencyX = d->fRandom->nextRangeScalar(0.01f,
+ 0.99f);
+ SkScalar baseFrequencyY = d->fRandom->nextRangeScalar(0.01f,
+ 0.99f);
+ int numOctaves = d->fRandom->nextRangeU(2, 10);
+ SkScalar z = SkIntToScalar(d->fRandom->nextU());
+
+ sk_sp<SkShader> shader(SkPerlinNoiseShader::MakeImprovedNoise(baseFrequencyX,
+ baseFrequencyY,
+ numOctaves,
+ z));
+
+ GrTest::TestAsFPArgs asFPArgs(d);
+ return as_SB(shader)->asFragmentProcessor(asFPArgs.args());
+}
+#endif
+
+void GrGLImprovedPerlinNoise::emitCode(EmitArgs& args) {
+ GrGLSLFragmentBuilder* 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);
+
+ fOctavesUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
+ kFloat_GrSLType, kDefault_GrSLPrecision,
+ "octaves");
+ const char* octavesUni = uniformHandler->getUniformCStr(fOctavesUni);
+
+ fZUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
+ kFloat_GrSLType, kDefault_GrSLPrecision,
+ "z");
+ const char* zUni = uniformHandler->getUniformCStr(fZUni);
+
+ // fade function
+ static const GrShaderVar fadeArgs[] = {
+ GrShaderVar("t", kVec3f_GrSLType)
+ };
+ SkString fadeFuncName;
+ fragBuilder->emitFunction(kVec3f_GrSLType, "fade", SK_ARRAY_COUNT(fadeArgs),
+ fadeArgs,
+ "return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);",
+ &fadeFuncName);
+
+ // perm function
+ static const GrShaderVar permArgs[] = {
+ GrShaderVar("x", kFloat_GrSLType)
+ };
+ SkString permFuncName;
+ SkString permCode("return ");
+ // FIXME even though I'm creating these textures with kRepeat_TileMode, they're clamped. Not
+ // sure why. Using fract() (here and the next texture lookup) as a workaround.
+ fragBuilder->appendTextureLookup(&permCode, args.fTexSamplers[0], "vec2(fract(x / 256.0), 0.0)",
+ kVec2f_GrSLType);
+ permCode.append(".r * 255.0;");
+ fragBuilder->emitFunction(kFloat_GrSLType, "perm", SK_ARRAY_COUNT(permArgs), permArgs,
+ permCode.c_str(), &permFuncName);
+
+ // grad function
+ static const GrShaderVar gradArgs[] = {
+ GrShaderVar("x", kFloat_GrSLType),
+ GrShaderVar("p", kVec3f_GrSLType)
+ };
+ SkString gradFuncName;
+ SkString gradCode("return dot(");
+ fragBuilder->appendTextureLookup(&gradCode, args.fTexSamplers[1], "vec2(fract(x / 16.0), 0.0)",
+ kVec2f_GrSLType);
+ gradCode.append(".rgb * 255.0 - vec3(1.0), p);");
+ fragBuilder->emitFunction(kFloat_GrSLType, "grad", SK_ARRAY_COUNT(gradArgs), gradArgs,
+ gradCode.c_str(), &gradFuncName);
+
+ // lerp function
+ static const GrShaderVar lerpArgs[] = {
+ GrShaderVar("a", kFloat_GrSLType),
+ GrShaderVar("b", kFloat_GrSLType),
+ GrShaderVar("w", kFloat_GrSLType)
+ };
+ SkString lerpFuncName;
+ fragBuilder->emitFunction(kFloat_GrSLType, "lerp", SK_ARRAY_COUNT(lerpArgs), lerpArgs,
+ "return a + w * (b - a);", &lerpFuncName);
+
+ // noise function
+ static const GrShaderVar noiseArgs[] = {
+ GrShaderVar("p", kVec3f_GrSLType),
+ };
+ SkString noiseFuncName;
+ SkString noiseCode;
+ noiseCode.append("vec3 P = mod(floor(p), 256.0);");
+ noiseCode.append("p -= floor(p);");
+ noiseCode.appendf("vec3 f = %s(p);", fadeFuncName.c_str());
+ noiseCode.appendf("float A = %s(P.x) + P.y;", permFuncName.c_str());
+ noiseCode.appendf("float AA = %s(A) + P.z;", permFuncName.c_str());
+ noiseCode.appendf("float AB = %s(A + 1.0) + P.z;", permFuncName.c_str());
+ noiseCode.appendf("float B = %s(P.x + 1.0) + P.y;", permFuncName.c_str());
+ noiseCode.appendf("float BA = %s(B) + P.z;", permFuncName.c_str());
+ noiseCode.appendf("float BB = %s(B + 1.0) + P.z;", permFuncName.c_str());
+ noiseCode.appendf("float result = %s(", lerpFuncName.c_str());
+ noiseCode.appendf("%s(%s(%s(%s(AA), p),", lerpFuncName.c_str(), lerpFuncName.c_str(),
+ gradFuncName.c_str(), permFuncName.c_str());
+ noiseCode.appendf("%s(%s(BA), p + vec3(-1.0, 0.0, 0.0)), f.x),", gradFuncName.c_str(),
+ permFuncName.c_str());
+ noiseCode.appendf("%s(%s(%s(AB), p + vec3(0.0, -1.0, 0.0)),", lerpFuncName.c_str(),
+ gradFuncName.c_str(), permFuncName.c_str());
+ noiseCode.appendf("%s(%s(BB), p + vec3(-1.0, -1.0, 0.0)), f.x), f.y),",
+ gradFuncName.c_str(), permFuncName.c_str());
+ noiseCode.appendf("%s(%s(%s(%s(AA + 1.0), p + vec3(0.0, 0.0, -1.0)),",
+ lerpFuncName.c_str(), lerpFuncName.c_str(), gradFuncName.c_str(),
+ permFuncName.c_str());
+ noiseCode.appendf("%s(%s(BA + 1.0), p + vec3(-1.0, 0.0, -1.0)), f.x),",
+ gradFuncName.c_str(), permFuncName.c_str());
+ noiseCode.appendf("%s(%s(%s(AB + 1.0), p + vec3(0.0, -1.0, -1.0)),",
+ lerpFuncName.c_str(), gradFuncName.c_str(), permFuncName.c_str());
+ noiseCode.appendf("%s(%s(BB + 1.0), p + vec3(-1.0, -1.0, -1.0)), f.x), f.y), f.z);",
+ gradFuncName.c_str(), permFuncName.c_str());
+ noiseCode.append("return result;");
+ fragBuilder->emitFunction(kFloat_GrSLType, "noise", SK_ARRAY_COUNT(noiseArgs), noiseArgs,
+ noiseCode.c_str(), &noiseFuncName);
+
+ // noiseOctaves function
+ static const GrShaderVar noiseOctavesArgs[] = {
+ GrShaderVar("p", kVec3f_GrSLType),
+ GrShaderVar("octaves", kFloat_GrSLType),
+ };
+ SkString noiseOctavesFuncName;
+ SkString noiseOctavesCode;
+ noiseOctavesCode.append("float result = 0.0;");
+ noiseOctavesCode.append("float ratio = 1.0;");
+ noiseOctavesCode.append("for (float i = 0.0; i < octaves; i++) {");
+ noiseOctavesCode.appendf("result += %s(p) / ratio;", noiseFuncName.c_str());
+ noiseOctavesCode.append("p *= 2.0;");
+ noiseOctavesCode.append("ratio *= 2.0;");
+ noiseOctavesCode.append("}");
+ noiseOctavesCode.append("return (result + 1.0) / 2.0;");
+ fragBuilder->emitFunction(kFloat_GrSLType, "noiseOctaves", SK_ARRAY_COUNT(noiseOctavesArgs),
+ noiseOctavesArgs, noiseOctavesCode.c_str(), &noiseOctavesFuncName);
+
+ fragBuilder->codeAppendf("vec2 coords = %s * %s;", vCoords.c_str(), baseFrequencyUni);
+ fragBuilder->codeAppendf("float r = %s(vec3(coords, %s), %s);", noiseOctavesFuncName.c_str(),
+ zUni, octavesUni);
+ fragBuilder->codeAppendf("float g = %s(vec3(coords, %s + 0000.0), %s);",
+ noiseOctavesFuncName.c_str(), zUni, octavesUni);
+ fragBuilder->codeAppendf("float b = %s(vec3(coords, %s + 0000.0), %s);",
+ noiseOctavesFuncName.c_str(), zUni, octavesUni);
+ fragBuilder->codeAppendf("float a = %s(vec3(coords, %s + 0000.0), %s);",
+ noiseOctavesFuncName.c_str(), zUni, octavesUni);
+ fragBuilder->codeAppendf("%s = vec4(r, g, b, a);", args.fOutputColor);
+
+ // Clamp values
+ fragBuilder->codeAppendf("%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 GrGLImprovedPerlinNoise::GenKey(const GrProcessor& processor, const GrShaderCaps&,
+ GrProcessorKeyBuilder* b) {
+}
+
+void GrGLImprovedPerlinNoise::onSetData(const GrGLSLProgramDataManager& pdman,
+ const GrFragmentProcessor& processor) {
+ INHERITED::onSetData(pdman, processor);
+
+ const GrImprovedPerlinNoiseEffect& noise = processor.cast<GrImprovedPerlinNoiseEffect>();
+
+ const SkVector& baseFrequency = noise.baseFrequency();
+ pdman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY);
+
+ pdman.set1f(fOctavesUni, SkIntToScalar(noise.octaves()));
+
+ pdman.set1f(fZUni, noise.z());
+}
+
+/////////////////////////////////////////////////////////////////////
+sk_sp<GrFragmentProcessor> 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);
+
+ // 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);
+
+ SkMatrix m = *args.fViewMatrix;
+ m.setTranslateX(-localMatrix.getTranslateX() + SK_Scalar1);
+ m.setTranslateY(-localMatrix.getTranslateY() + SK_Scalar1);
+
+ if (fType == kImprovedNoise_Type) {
+ GrSamplerParams textureParams(SkShader::TileMode::kRepeat_TileMode,
+ GrSamplerParams::FilterMode::kNone_FilterMode);
+ sk_sp<GrTextureProxy> permutationsTexture(
+ GrRefCachedBitmapTextureProxy(args.fContext,
+ paintingData->getImprovedPermutationsBitmap(),
+ textureParams, nullptr));
+ sk_sp<GrTextureProxy> gradientTexture(
+ GrRefCachedBitmapTextureProxy(args.fContext,
+ paintingData->getGradientBitmap(),
+ textureParams, nullptr));
+ return GrImprovedPerlinNoiseEffect::Make(args.fContext->resourceProvider(),
+ fNumOctaves, fSeed, paintingData,
+ std::move(permutationsTexture),
+ std::move(gradientTexture), m);
+ }
+
+ 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<GrFragmentProcessor> 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);
+ }
+
+ sk_sp<GrTextureProxy> permutationsProxy = GrMakeCachedBitmapProxy(
+ args.fContext->resourceProvider(),
+ paintingData->getPermutationsBitmap());
+ sk_sp<GrTextureProxy> noiseProxy = GrMakeCachedBitmapProxy(args.fContext->resourceProvider(),
+ paintingData->getNoiseBitmap());
+
+ if (permutationsProxy && noiseProxy) {
+ sk_sp<GrFragmentProcessor> inner(
+ GrPerlinNoise2Effect::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("SkPerlinNoiseShaderImpl: (");
+
+ 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_sp<SkShader> SkPerlinNoiseShader::MakeFractalNoise(SkScalar baseFrequencyX,
+ SkScalar baseFrequencyY,
+ int numOctaves, SkScalar seed,
+ const SkISize* tileSize) {
+ return sk_sp<SkShader>(new SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::kFractalNoise_Type,
+ baseFrequencyX, baseFrequencyY, numOctaves, seed,
+ tileSize));
+}
+
+sk_sp<SkShader> SkPerlinNoiseShader::MakeTurbulence(SkScalar baseFrequencyX,
+ SkScalar baseFrequencyY,
+ int numOctaves, SkScalar seed,
+ const SkISize* tileSize) {
+ return sk_sp<SkShader>(new SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::kTurbulence_Type,
+ baseFrequencyX, baseFrequencyY, numOctaves, seed,
+ tileSize));
+}
+
+sk_sp<SkShader> SkPerlinNoiseShader::MakeImprovedNoise(SkScalar baseFrequencyX,
+ SkScalar baseFrequencyY,
+ int numOctaves, SkScalar z) {
+ return sk_sp<SkShader>(new SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::kImprovedNoise_Type,
+ baseFrequencyX, baseFrequencyY, numOctaves, z,
+ nullptr));
+}
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkPerlinNoiseShader)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkPerlinNoiseShaderImpl)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/src/shaders/SkPictureShader.cpp b/src/shaders/SkPictureShader.cpp
new file mode 100644
index 0000000000..d6ee941251
--- /dev/null
+++ b/src/shaders/SkPictureShader.cpp
@@ -0,0 +1,364 @@
+/*
+ * 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 "SkPictureShader.h"
+
+#include "SkArenaAlloc.h"
+#include "SkBitmap.h"
+#include "SkBitmapProcShader.h"
+#include "SkCanvas.h"
+#include "SkColorSpaceXformCanvas.h"
+#include "SkImage.h"
+#include "SkImageShader.h"
+#include "SkMatrixUtils.h"
+#include "SkPicture.h"
+#include "SkPictureImageGenerator.h"
+#include "SkReadBuffer.h"
+#include "SkResourceCache.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "GrCaps.h"
+#include "GrFragmentProcessor.h"
+#endif
+
+namespace {
+static unsigned gBitmapSkaderKeyNamespaceLabel;
+
+struct BitmapShaderKey : public SkResourceCache::Key {
+public:
+ BitmapShaderKey(sk_sp<SkColorSpace> colorSpace,
+ uint32_t pictureID,
+ const SkRect& tile,
+ SkShader::TileMode tmx,
+ SkShader::TileMode tmy,
+ const SkSize& scale,
+ const SkMatrix& localMatrix,
+ SkTransferFunctionBehavior blendBehavior)
+ : fColorSpace(std::move(colorSpace))
+ , fPictureID(pictureID)
+ , fTile(tile)
+ , fTmx(tmx)
+ , fTmy(tmy)
+ , fScale(scale)
+ , fBlendBehavior(blendBehavior) {
+
+ for (int i = 0; i < 9; ++i) {
+ fLocalMatrixStorage[i] = localMatrix[i];
+ }
+
+ static const size_t keySize = sizeof(fColorSpace) +
+ sizeof(fPictureID) +
+ sizeof(fTile) +
+ sizeof(fTmx) + sizeof(fTmy) +
+ sizeof(fScale) +
+ sizeof(fLocalMatrixStorage) +
+ sizeof(fBlendBehavior);
+ // This better be packed.
+ SkASSERT(sizeof(uint32_t) * (&fEndOfStruct - (uint32_t*)&fColorSpace) == keySize);
+ this->init(&gBitmapSkaderKeyNamespaceLabel, 0, keySize);
+ }
+
+private:
+ sk_sp<SkColorSpace> fColorSpace;
+ uint32_t fPictureID;
+ SkRect fTile;
+ SkShader::TileMode fTmx, fTmy;
+ SkSize fScale;
+ SkScalar fLocalMatrixStorage[9];
+ SkTransferFunctionBehavior fBlendBehavior;
+
+ SkDEBUGCODE(uint32_t fEndOfStruct;)
+};
+
+struct BitmapShaderRec : public SkResourceCache::Rec {
+ BitmapShaderRec(const BitmapShaderKey& key, SkShader* tileShader)
+ : fKey(key)
+ , fShader(SkRef(tileShader)) {}
+
+ BitmapShaderKey fKey;
+ sk_sp<SkShader> fShader;
+ size_t fBitmapBytes;
+
+ const Key& getKey() const override { return fKey; }
+ size_t bytesUsed() const override {
+ // Just the record overhead -- the actual pixels are accounted by SkImageCacherator.
+ return sizeof(fKey) + sizeof(SkImageShader);
+ }
+ const char* getCategory() const override { return "bitmap-shader"; }
+ SkDiscardableMemory* diagnostic_only_getDiscardable() const override { return nullptr; }
+
+ static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextShader) {
+ const BitmapShaderRec& rec = static_cast<const BitmapShaderRec&>(baseRec);
+ sk_sp<SkShader>* result = reinterpret_cast<sk_sp<SkShader>*>(contextShader);
+
+ *result = rec.fShader;
+
+ // The bitmap shader is backed by an image generator, thus it can always re-generate its
+ // pixels if discarded.
+ return true;
+ }
+};
+
+} // namespace
+
+SkPictureShader::SkPictureShader(sk_sp<SkPicture> picture, TileMode tmx, TileMode tmy,
+ const SkMatrix* localMatrix, const SkRect* tile,
+ sk_sp<SkColorSpace> colorSpace)
+ : INHERITED(localMatrix)
+ , fPicture(std::move(picture))
+ , fTile(tile ? *tile : fPicture->cullRect())
+ , fTmx(tmx)
+ , fTmy(tmy)
+ , fColorSpace(std::move(colorSpace))
+{}
+
+sk_sp<SkShader> SkPictureShader::Make(sk_sp<SkPicture> picture, TileMode tmx, TileMode tmy,
+ const SkMatrix* localMatrix, const SkRect* tile) {
+ if (!picture || picture->cullRect().isEmpty() || (tile && tile->isEmpty())) {
+ return SkShader::MakeEmptyShader();
+ }
+ return sk_sp<SkShader>(new SkPictureShader(std::move(picture), tmx, tmy, localMatrix, tile,
+ nullptr));
+}
+
+sk_sp<SkFlattenable> SkPictureShader::CreateProc(SkReadBuffer& buffer) {
+ SkMatrix lm;
+ buffer.readMatrix(&lm);
+ TileMode mx = (TileMode)buffer.read32();
+ TileMode my = (TileMode)buffer.read32();
+ SkRect tile;
+ buffer.readRect(&tile);
+
+ sk_sp<SkPicture> picture;
+
+ if (buffer.isCrossProcess() && SkPicture::PictureIOSecurityPrecautionsEnabled()) {
+ if (buffer.isVersionLT(SkReadBuffer::kPictureShaderHasPictureBool_Version)) {
+ // Older code blindly serialized pictures. We don't trust them.
+ buffer.validate(false);
+ return nullptr;
+ }
+ // Newer code won't serialize pictures in disallow-cross-process-picture mode.
+ // Assert that they didn't serialize anything except a false here.
+ buffer.validate(!buffer.readBool());
+ } else {
+ // Old code always serialized the picture. New code writes a 'true' first if it did.
+ if (buffer.isVersionLT(SkReadBuffer::kPictureShaderHasPictureBool_Version) ||
+ buffer.readBool()) {
+ picture = SkPicture::MakeFromBuffer(buffer);
+ }
+ }
+ return SkPictureShader::Make(picture, mx, my, &lm, &tile);
+}
+
+void SkPictureShader::flatten(SkWriteBuffer& buffer) const {
+ buffer.writeMatrix(this->getLocalMatrix());
+ buffer.write32(fTmx);
+ buffer.write32(fTmy);
+ buffer.writeRect(fTile);
+
+ // The deserialization code won't trust that our serialized picture is safe to deserialize.
+ // So write a 'false' telling it that we're not serializing a picture.
+ if (buffer.isCrossProcess() && SkPicture::PictureIOSecurityPrecautionsEnabled()) {
+ buffer.writeBool(false);
+ } else {
+ buffer.writeBool(true);
+ fPicture->flatten(buffer);
+ }
+}
+
+sk_sp<SkShader> SkPictureShader::refBitmapShader(const SkMatrix& viewMatrix, const SkMatrix* localM,
+ SkColorSpace* dstColorSpace,
+ const int maxTextureSize) const {
+ SkASSERT(fPicture && !fPicture->cullRect().isEmpty());
+
+ SkMatrix m;
+ m.setConcat(viewMatrix, this->getLocalMatrix());
+ if (localM) {
+ m.preConcat(*localM);
+ }
+
+ // Use a rotation-invariant scale
+ SkPoint scale;
+ //
+ // TODO: replace this with decomposeScale() -- but beware LayoutTest rebaselines!
+ //
+ if (!SkDecomposeUpper2x2(m, nullptr, &scale, nullptr)) {
+ // Decomposition failed, use an approximation.
+ scale.set(SkScalarSqrt(m.getScaleX() * m.getScaleX() + m.getSkewX() * m.getSkewX()),
+ SkScalarSqrt(m.getScaleY() * m.getScaleY() + m.getSkewY() * m.getSkewY()));
+ }
+ SkSize scaledSize = SkSize::Make(SkScalarAbs(scale.x() * fTile.width()),
+ SkScalarAbs(scale.y() * fTile.height()));
+
+ // Clamp the tile size to about 4M pixels
+ static const SkScalar kMaxTileArea = 2048 * 2048;
+ SkScalar tileArea = scaledSize.width() * scaledSize.height();
+ if (tileArea > kMaxTileArea) {
+ SkScalar clampScale = SkScalarSqrt(kMaxTileArea / tileArea);
+ scaledSize.set(scaledSize.width() * clampScale,
+ scaledSize.height() * clampScale);
+ }
+#if SK_SUPPORT_GPU
+ // Scale down the tile size if larger than maxTextureSize for GPU Path or it should fail on create texture
+ if (maxTextureSize) {
+ if (scaledSize.width() > maxTextureSize || scaledSize.height() > maxTextureSize) {
+ SkScalar downScale = maxTextureSize / SkMaxScalar(scaledSize.width(), scaledSize.height());
+ scaledSize.set(SkScalarFloorToScalar(scaledSize.width() * downScale),
+ SkScalarFloorToScalar(scaledSize.height() * downScale));
+ }
+ }
+#endif
+
+#ifdef SK_SUPPORT_LEGACY_PICTURESHADER_ROUNDING
+ const SkISize tileSize = scaledSize.toRound();
+#else
+ const SkISize tileSize = scaledSize.toCeil();
+#endif
+ if (tileSize.isEmpty()) {
+ return SkShader::MakeEmptyShader();
+ }
+
+ // The actual scale, compensating for rounding & clamping.
+ const SkSize tileScale = SkSize::Make(SkIntToScalar(tileSize.width()) / fTile.width(),
+ SkIntToScalar(tileSize.height()) / fTile.height());
+
+ // |fColorSpace| will only be set when using an SkColorSpaceXformCanvas to do pre-draw xforms.
+ // This canvas is strictly for legacy mode. A non-null |dstColorSpace| indicates that we
+ // should perform color correct rendering and xform at draw time.
+ SkASSERT(!fColorSpace || !dstColorSpace);
+ sk_sp<SkColorSpace> keyCS = dstColorSpace ? sk_ref_sp(dstColorSpace) : fColorSpace;
+ SkTransferFunctionBehavior blendBehavior = dstColorSpace ? SkTransferFunctionBehavior::kRespect
+ : SkTransferFunctionBehavior::kIgnore;
+
+ sk_sp<SkShader> tileShader;
+ BitmapShaderKey key(std::move(keyCS),
+ fPicture->uniqueID(),
+ fTile,
+ fTmx,
+ fTmy,
+ tileScale,
+ this->getLocalMatrix(),
+ blendBehavior);
+
+ if (!SkResourceCache::Find(key, BitmapShaderRec::Visitor, &tileShader)) {
+ SkMatrix tileMatrix;
+ tileMatrix.setRectToRect(fTile, SkRect::MakeIWH(tileSize.width(), tileSize.height()),
+ SkMatrix::kFill_ScaleToFit);
+
+ sk_sp<SkImage> tileImage = SkImage::MakeFromGenerator(
+ SkPictureImageGenerator::Make(tileSize, fPicture, &tileMatrix, nullptr,
+ SkImage::BitDepth::kU8, sk_ref_sp(dstColorSpace)));
+ if (!tileImage) {
+ return nullptr;
+ }
+
+ if (fColorSpace) {
+ tileImage = tileImage->makeColorSpace(fColorSpace, SkTransferFunctionBehavior::kIgnore);
+ }
+
+ SkMatrix shaderMatrix = this->getLocalMatrix();
+ shaderMatrix.preScale(1 / tileScale.width(), 1 / tileScale.height());
+ tileShader = tileImage->makeShader(fTmx, fTmy, &shaderMatrix);
+
+ SkResourceCache::Add(new BitmapShaderRec(key, tileShader.get()));
+ }
+
+ return tileShader;
+}
+
+bool SkPictureShader::onAppendStages(SkRasterPipeline* p, SkColorSpace* cs, SkArenaAlloc* alloc,
+ const SkMatrix& ctm, const SkPaint& paint,
+ const SkMatrix* localMatrix) const {
+ // Keep bitmapShader alive by using alloc instead of stack memory
+ auto& bitmapShader = *alloc->make<sk_sp<SkShader>>();
+ bitmapShader = this->refBitmapShader(ctm, localMatrix, cs);
+ return bitmapShader && as_SB(bitmapShader)->appendStages(p, cs, alloc, ctm, paint);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+SkShaderBase::Context* SkPictureShader::onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc)
+const {
+ sk_sp<SkShader> bitmapShader(this->refBitmapShader(*rec.fMatrix, rec.fLocalMatrix,
+ rec.fDstColorSpace));
+ if (!bitmapShader) {
+ return nullptr;
+ }
+
+ PictureShaderContext* ctx =
+ alloc->make<PictureShaderContext>(*this, rec, std::move(bitmapShader), alloc);
+ if (nullptr == ctx->fBitmapShaderContext) {
+ ctx = nullptr;
+ }
+ return ctx;
+}
+
+sk_sp<SkShader> SkPictureShader::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
+ return sk_sp<SkPictureShader>(new SkPictureShader(fPicture, fTmx, fTmy, &this->getLocalMatrix(),
+ &fTile, xformer->dst()));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+SkPictureShader::PictureShaderContext::PictureShaderContext(
+ const SkPictureShader& shader, const ContextRec& rec, sk_sp<SkShader> bitmapShader,
+ SkArenaAlloc* alloc)
+ : INHERITED(shader, rec)
+ , fBitmapShader(std::move(bitmapShader))
+{
+ fBitmapShaderContext = as_SB(fBitmapShader)->makeContext(rec, alloc);
+ //if fBitmapShaderContext is null, we are invalid
+}
+
+uint32_t SkPictureShader::PictureShaderContext::getFlags() const {
+ SkASSERT(fBitmapShaderContext);
+ return fBitmapShaderContext->getFlags();
+}
+
+SkShaderBase::Context::ShadeProc SkPictureShader::PictureShaderContext::asAShadeProc(void** ctx) {
+ SkASSERT(fBitmapShaderContext);
+ return fBitmapShaderContext->asAShadeProc(ctx);
+}
+
+void SkPictureShader::PictureShaderContext::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
+ SkASSERT(fBitmapShaderContext);
+ fBitmapShaderContext->shadeSpan(x, y, dstC, count);
+}
+
+#ifndef SK_IGNORE_TO_STRING
+void SkPictureShader::toString(SkString* str) const {
+ static const char* gTileModeName[SkShader::kTileModeCount] = {
+ "clamp", "repeat", "mirror"
+ };
+
+ str->appendf("PictureShader: [%f:%f:%f:%f] ",
+ fPicture->cullRect().fLeft,
+ fPicture->cullRect().fTop,
+ fPicture->cullRect().fRight,
+ fPicture->cullRect().fBottom);
+
+ str->appendf("(%s, %s)", gTileModeName[fTmx], gTileModeName[fTmy]);
+
+ this->INHERITED::toString(str);
+}
+#endif
+
+#if SK_SUPPORT_GPU
+sk_sp<GrFragmentProcessor> SkPictureShader::asFragmentProcessor(const AsFPArgs& args) const {
+ int maxTextureSize = 0;
+ if (args.fContext) {
+ maxTextureSize = args.fContext->caps()->maxTextureSize();
+ }
+ sk_sp<SkShader> bitmapShader(this->refBitmapShader(*args.fViewMatrix, args.fLocalMatrix,
+ args.fDstColorSpace, maxTextureSize));
+ if (!bitmapShader) {
+ return nullptr;
+ }
+ return as_SB(bitmapShader)->asFragmentProcessor(SkShaderBase::AsFPArgs(
+ args.fContext, args.fViewMatrix, nullptr, args.fFilterQuality, args.fDstColorSpace));
+}
+#endif
diff --git a/src/shaders/SkPictureShader.h b/src/shaders/SkPictureShader.h
new file mode 100644
index 0000000000..f7a509f181
--- /dev/null
+++ b/src/shaders/SkPictureShader.h
@@ -0,0 +1,79 @@
+/*
+ * 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 SkPictureShader_DEFINED
+#define SkPictureShader_DEFINED
+
+#include "SkShaderBase.h"
+
+class SkArenaAlloc;
+class SkBitmap;
+class SkPicture;
+
+/*
+ * An SkPictureShader can be used to draw SkPicture-based patterns.
+ *
+ * The SkPicture is first rendered into a tile, which is then used to shade the area according
+ * to specified tiling rules.
+ */
+class SkPictureShader : public SkShaderBase {
+public:
+ static sk_sp<SkShader> Make(sk_sp<SkPicture>, TileMode, TileMode, const SkMatrix*,
+ const SkRect*);
+
+ SK_TO_STRING_OVERRIDE()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPictureShader)
+
+#if SK_SUPPORT_GPU
+ sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override;
+#endif
+
+protected:
+ SkPictureShader(SkReadBuffer&);
+ void flatten(SkWriteBuffer&) const override;
+ bool onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*,
+ const SkMatrix&, const SkPaint&, const SkMatrix*) const override;
+ Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
+ sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
+
+private:
+ SkPictureShader(sk_sp<SkPicture>, TileMode, TileMode, const SkMatrix*, const SkRect*,
+ sk_sp<SkColorSpace>);
+
+ sk_sp<SkShader> refBitmapShader(const SkMatrix&, const SkMatrix* localMatrix,
+ SkColorSpace* dstColorSpace,
+ const int maxTextureSize = 0) const;
+
+ sk_sp<SkPicture> fPicture;
+ SkRect fTile;
+ TileMode fTmx, fTmy;
+
+ class PictureShaderContext : public Context {
+ public:
+ PictureShaderContext(
+ const SkPictureShader&, const ContextRec&, sk_sp<SkShader> bitmapShader, SkArenaAlloc*);
+
+ uint32_t getFlags() const override;
+
+ ShadeProc asAShadeProc(void** ctx) override;
+ void shadeSpan(int x, int y, SkPMColor dstC[], int count) override;
+
+ sk_sp<SkShader> fBitmapShader;
+ SkShaderBase::Context* fBitmapShaderContext;
+ void* fBitmapShaderContextStorage;
+
+ typedef Context INHERITED;
+ };
+
+ // Should never be set by a public constructor. This is only used when onMakeColorSpace()
+ // forces a deferred color space xform.
+ sk_sp<SkColorSpace> fColorSpace;
+
+ typedef SkShaderBase INHERITED;
+};
+
+#endif // SkPictureShader_DEFINED
diff --git a/src/shaders/SkShader.cpp b/src/shaders/SkShader.cpp
new file mode 100644
index 0000000000..d04fbfe4df
--- /dev/null
+++ b/src/shaders/SkShader.cpp
@@ -0,0 +1,305 @@
+/*
+ * 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 "SkArenaAlloc.h"
+#include "SkAtomics.h"
+#include "SkBitmapProcShader.h"
+#include "SkColorShader.h"
+#include "SkEmptyShader.h"
+#include "SkMallocPixelRef.h"
+#include "SkPaint.h"
+#include "SkPicture.h"
+#include "SkPictureShader.h"
+#include "SkPM4fPriv.h"
+#include "SkRasterPipeline.h"
+#include "SkReadBuffer.h"
+#include "SkScalar.h"
+#include "SkShaderBase.h"
+#include "SkTLazy.h"
+#include "SkWriteBuffer.h"
+#include "../jumper/SkJumper.h"
+
+#if SK_SUPPORT_GPU
+#include "GrFragmentProcessor.h"
+#endif
+
+//#define SK_TRACK_SHADER_LIFETIME
+
+#ifdef SK_TRACK_SHADER_LIFETIME
+ static int32_t gShaderCounter;
+#endif
+
+static inline void inc_shader_counter() {
+#ifdef SK_TRACK_SHADER_LIFETIME
+ int32_t prev = sk_atomic_inc(&gShaderCounter);
+ SkDebugf("+++ shader counter %d\n", prev + 1);
+#endif
+}
+static inline void dec_shader_counter() {
+#ifdef SK_TRACK_SHADER_LIFETIME
+ int32_t prev = sk_atomic_dec(&gShaderCounter);
+ SkDebugf("--- shader counter %d\n", prev - 1);
+#endif
+}
+
+SkShaderBase::SkShaderBase(const SkMatrix* localMatrix)
+ : fLocalMatrix(localMatrix ? *localMatrix : SkMatrix::I()) {
+ inc_shader_counter();
+ // Pre-cache so future calls to fLocalMatrix.getType() are threadsafe.
+ (void)fLocalMatrix.getType();
+}
+
+SkShaderBase::~SkShaderBase() {
+ dec_shader_counter();
+}
+
+void SkShaderBase::flatten(SkWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ bool hasLocalM = !fLocalMatrix.isIdentity();
+ buffer.writeBool(hasLocalM);
+ if (hasLocalM) {
+ buffer.writeMatrix(fLocalMatrix);
+ }
+}
+
+bool SkShaderBase::computeTotalInverse(const SkMatrix& ctm,
+ const SkMatrix* outerLocalMatrix,
+ SkMatrix* totalInverse) const {
+ SkMatrix total = SkMatrix::Concat(ctm, fLocalMatrix);
+ if (outerLocalMatrix) {
+ total.preConcat(*outerLocalMatrix);
+ }
+
+ return total.invert(totalInverse);
+}
+
+bool SkShaderBase::asLuminanceColor(SkColor* colorPtr) const {
+ SkColor storage;
+ if (nullptr == colorPtr) {
+ colorPtr = &storage;
+ }
+ if (this->onAsLuminanceColor(colorPtr)) {
+ *colorPtr = SkColorSetA(*colorPtr, 0xFF); // we only return opaque
+ return true;
+ }
+ return false;
+}
+
+SkShaderBase::Context* SkShaderBase::makeContext(const ContextRec& rec, SkArenaAlloc* alloc) const {
+ if (!this->computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, nullptr)) {
+ return nullptr;
+ }
+ return this->onMakeContext(rec, alloc);
+}
+
+SkShaderBase::Context::Context(const SkShaderBase& shader, const ContextRec& rec)
+ : fShader(shader), fCTM(*rec.fMatrix)
+{
+ // We should never use a context for RP-only shaders.
+ SkASSERT(!shader.isRasterPipelineOnly());
+
+ // Because the context parameters must be valid at this point, we know that the matrix is
+ // invertible.
+ SkAssertResult(fShader.computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &fTotalInverse));
+ fTotalInverseClass = (uint8_t)ComputeMatrixClass(fTotalInverse);
+
+ fPaintAlpha = rec.fPaint->getAlpha();
+}
+
+SkShaderBase::Context::~Context() {}
+
+SkShaderBase::Context::ShadeProc SkShaderBase::Context::asAShadeProc(void** ctx) {
+ return nullptr;
+}
+
+void SkShaderBase::Context::shadeSpan4f(int x, int y, SkPM4f dst[], int count) {
+ const int N = 128;
+ SkPMColor tmp[N];
+ while (count > 0) {
+ int n = SkTMin(count, N);
+ this->shadeSpan(x, y, tmp, n);
+ for (int i = 0; i < n; ++i) {
+ dst[i] = SkPM4f::FromPMColor(tmp[i]);
+ }
+ dst += n;
+ x += n;
+ count -= n;
+ }
+}
+
+#include "SkColorPriv.h"
+
+#define kTempColorQuadCount 6 // balance between speed (larger) and saving stack-space
+#define kTempColorCount (kTempColorQuadCount << 2)
+
+#ifdef SK_CPU_BENDIAN
+ #define SkU32BitShiftToByteOffset(shift) (3 - ((shift) >> 3))
+#else
+ #define SkU32BitShiftToByteOffset(shift) ((shift) >> 3)
+#endif
+
+void SkShaderBase::Context::shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) {
+ SkASSERT(count > 0);
+
+ SkPMColor colors[kTempColorCount];
+
+ while ((count -= kTempColorCount) >= 0) {
+ this->shadeSpan(x, y, colors, kTempColorCount);
+ x += kTempColorCount;
+
+ const uint8_t* srcA = (const uint8_t*)colors + SkU32BitShiftToByteOffset(SK_A32_SHIFT);
+ int quads = kTempColorQuadCount;
+ do {
+ U8CPU a0 = srcA[0];
+ U8CPU a1 = srcA[4];
+ U8CPU a2 = srcA[8];
+ U8CPU a3 = srcA[12];
+ srcA += 4*4;
+ *alpha++ = SkToU8(a0);
+ *alpha++ = SkToU8(a1);
+ *alpha++ = SkToU8(a2);
+ *alpha++ = SkToU8(a3);
+ } while (--quads != 0);
+ }
+ SkASSERT(count < 0);
+ SkASSERT(count + kTempColorCount >= 0);
+ if (count += kTempColorCount) {
+ this->shadeSpan(x, y, colors, count);
+
+ const uint8_t* srcA = (const uint8_t*)colors + SkU32BitShiftToByteOffset(SK_A32_SHIFT);
+ do {
+ *alpha++ = *srcA;
+ srcA += 4;
+ } while (--count != 0);
+ }
+#if 0
+ do {
+ int n = count;
+ if (n > kTempColorCount)
+ n = kTempColorCount;
+ SkASSERT(n > 0);
+
+ this->shadeSpan(x, y, colors, n);
+ x += n;
+ count -= n;
+
+ const uint8_t* srcA = (const uint8_t*)colors + SkU32BitShiftToByteOffset(SK_A32_SHIFT);
+ do {
+ *alpha++ = *srcA;
+ srcA += 4;
+ } while (--n != 0);
+ } while (count > 0);
+#endif
+}
+
+SkShaderBase::Context::MatrixClass SkShaderBase::Context::ComputeMatrixClass(const SkMatrix& mat) {
+ MatrixClass mc = kLinear_MatrixClass;
+
+ if (mat.hasPerspective()) {
+ if (mat.isFixedStepInX()) {
+ mc = kFixedStepInX_MatrixClass;
+ } else {
+ mc = kPerspective_MatrixClass;
+ }
+ }
+ return mc;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+const SkMatrix& SkShader::getLocalMatrix() const {
+ return as_SB(this)->getLocalMatrix();
+}
+
+#ifdef SK_SUPPORT_LEGACY_SHADER_ISABITMAP
+bool SkShader::isABitmap(SkBitmap* outTexture, SkMatrix* outMatrix, TileMode xy[2]) const {
+ return as_SB(this)->onIsABitmap(outTexture, outMatrix, xy);
+}
+#endif
+
+SkImage* SkShader::isAImage(SkMatrix* localMatrix, TileMode xy[2]) const {
+ return as_SB(this)->onIsAImage(localMatrix, xy);
+}
+
+SkShader::GradientType SkShader::asAGradient(GradientInfo* info) const {
+ return kNone_GradientType;
+}
+
+#if SK_SUPPORT_GPU
+sk_sp<GrFragmentProcessor> SkShaderBase::asFragmentProcessor(const AsFPArgs&) const {
+ return nullptr;
+}
+#endif
+
+sk_sp<SkShader> SkShader::makeAsALocalMatrixShader(SkMatrix*) const {
+ return nullptr;
+}
+
+sk_sp<SkShader> SkShader::MakeEmptyShader() { return sk_make_sp<SkEmptyShader>(); }
+
+sk_sp<SkShader> SkShader::MakeColorShader(SkColor color) { return sk_make_sp<SkColorShader>(color); }
+
+sk_sp<SkShader> SkShader::MakeBitmapShader(const SkBitmap& src, TileMode tmx, TileMode tmy,
+ const SkMatrix* localMatrix) {
+ if (localMatrix && !localMatrix->invert(nullptr)) {
+ return nullptr;
+ }
+ return SkMakeBitmapShader(src, tmx, tmy, localMatrix, kIfMutable_SkCopyPixelsMode);
+}
+
+sk_sp<SkShader> SkShader::MakePictureShader(sk_sp<SkPicture> src, TileMode tmx, TileMode tmy,
+ const SkMatrix* localMatrix, const SkRect* tile) {
+ if (localMatrix && !localMatrix->invert(nullptr)) {
+ return nullptr;
+ }
+ return SkPictureShader::Make(std::move(src), tmx, tmy, localMatrix, tile);
+}
+
+#ifndef SK_IGNORE_TO_STRING
+void SkShaderBase::toString(SkString* str) const {
+ if (!fLocalMatrix.isIdentity()) {
+ str->append(" ");
+ fLocalMatrix.toString(str);
+ }
+}
+#endif
+
+bool SkShaderBase::appendStages(SkRasterPipeline* p,
+ SkColorSpace* dstCS,
+ SkArenaAlloc* alloc,
+ const SkMatrix& ctm,
+ const SkPaint& paint,
+ const SkMatrix* localM) const {
+ return this->onAppendStages(p, dstCS, alloc, ctm, paint, localM);
+}
+
+bool SkShaderBase::onAppendStages(SkRasterPipeline* p,
+ SkColorSpace* dstCS,
+ SkArenaAlloc* alloc,
+ const SkMatrix& ctm,
+ const SkPaint& paint,
+ const SkMatrix* localM) const {
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+sk_sp<SkFlattenable> SkEmptyShader::CreateProc(SkReadBuffer&) {
+ return SkShader::MakeEmptyShader();
+}
+
+#ifndef SK_IGNORE_TO_STRING
+#include "SkEmptyShader.h"
+
+void SkEmptyShader::toString(SkString* str) const {
+ str->append("SkEmptyShader: (");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/src/shaders/SkShaderBase.h b/src/shaders/SkShaderBase.h
new file mode 100644
index 0000000000..94542321a3
--- /dev/null
+++ b/src/shaders/SkShaderBase.h
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkShaderBase_DEFINED
+#define SkShaderBase_DEFINED
+
+#include "SkFilterQuality.h"
+#include "SkMatrix.h"
+#include "SkShader.h"
+
+class GrContext;
+class GrFragmentProcessor;
+class SkArenaAlloc;
+class SkColorSpace;
+class SkColorSpaceXformer;
+class SkImage;
+struct SkImageInfo;
+class SkPaint;
+class SkRasterPipeline;
+
+class SkShaderBase : public SkShader {
+public:
+ ~SkShaderBase() override;
+
+ /**
+ * Returns true if the shader is guaranteed to produce only a single color.
+ * Subclasses can override this to allow loop-hoisting optimization.
+ */
+ virtual bool isConstant() const { return false; }
+
+ const SkMatrix& getLocalMatrix() const { return fLocalMatrix; }
+
+ enum Flags {
+ //!< set if all of the colors will be opaque
+ kOpaqueAlpha_Flag = 1 << 0,
+
+ /** set if the spans only vary in X (const in Y).
+ e.g. an Nx1 bitmap that is being tiled in Y, or a linear-gradient
+ that varies from left-to-right. This flag specifies this for
+ shadeSpan().
+ */
+ kConstInY32_Flag = 1 << 1,
+
+ /** hint for the blitter that 4f is the preferred shading mode.
+ */
+ kPrefers4f_Flag = 1 << 2,
+ };
+
+ /**
+ * ContextRec acts as a parameter bundle for creating Contexts.
+ */
+ struct ContextRec {
+ enum DstType {
+ kPMColor_DstType, // clients prefer shading into PMColor dest
+ kPM4f_DstType, // clients prefer shading into PM4f dest
+ };
+
+ ContextRec(const SkPaint& paint, const SkMatrix& matrix, const SkMatrix* localM,
+ DstType dstType, SkColorSpace* dstColorSpace)
+ : fPaint(&paint)
+ , fMatrix(&matrix)
+ , fLocalMatrix(localM)
+ , fPreferredDstType(dstType)
+ , fDstColorSpace(dstColorSpace) {}
+
+ const SkPaint* fPaint; // the current paint associated with the draw
+ const SkMatrix* fMatrix; // the current matrix in the canvas
+ const SkMatrix* fLocalMatrix; // optional local matrix
+ const DstType fPreferredDstType; // the "natural" client dest type
+ SkColorSpace* fDstColorSpace; // the color space of the dest surface (if any)
+ };
+
+ class Context : public ::SkNoncopyable {
+ public:
+ Context(const SkShaderBase& shader, const ContextRec&);
+
+ virtual ~Context();
+
+ /**
+ * Called sometimes before drawing with this shader. Return the type of
+ * alpha your shader will return. The default implementation returns 0.
+ * Your subclass should override if it can (even sometimes) report a
+ * non-zero value, since that will enable various blitters to perform
+ * faster.
+ */
+ virtual uint32_t getFlags() const { return 0; }
+
+ /**
+ * Called for each span of the object being drawn. Your subclass should
+ * set the appropriate colors (with premultiplied alpha) that correspond
+ * to the specified device coordinates.
+ */
+ virtual void shadeSpan(int x, int y, SkPMColor[], int count) = 0;
+
+ virtual void shadeSpan4f(int x, int y, SkPM4f[], int count);
+
+ /**
+ * The const void* ctx is only const because all the implementations are const.
+ * This can be changed to non-const if a new shade proc needs to change the ctx.
+ */
+ typedef void (*ShadeProc)(const void* ctx, int x, int y, SkPMColor[], int count);
+ virtual ShadeProc asAShadeProc(void** ctx);
+
+ /**
+ * Similar to shadeSpan, but only returns the alpha-channel for a span.
+ * The default implementation calls shadeSpan() and then extracts the alpha
+ * values from the returned colors.
+ */
+ virtual void shadeSpanAlpha(int x, int y, uint8_t alpha[], int count);
+
+ // Notification from blitter::blitMask in case we need to see the non-alpha channels
+ virtual void set3DMask(const SkMask*) {}
+
+ protected:
+ // Reference to shader, so we don't have to dupe information.
+ const SkShaderBase& fShader;
+
+ enum MatrixClass {
+ kLinear_MatrixClass, // no perspective
+ kFixedStepInX_MatrixClass, // fast perspective, need to call fixedStepInX() each
+ // scanline
+ kPerspective_MatrixClass // slow perspective, need to mappoints each pixel
+ };
+ static MatrixClass ComputeMatrixClass(const SkMatrix&);
+
+ uint8_t getPaintAlpha() const { return fPaintAlpha; }
+ const SkMatrix& getTotalInverse() const { return fTotalInverse; }
+ MatrixClass getInverseClass() const { return (MatrixClass)fTotalInverseClass; }
+ const SkMatrix& getCTM() const { return fCTM; }
+
+ private:
+ SkMatrix fCTM;
+ SkMatrix fTotalInverse;
+ uint8_t fPaintAlpha;
+ uint8_t fTotalInverseClass;
+
+ typedef SkNoncopyable INHERITED;
+ };
+
+ /**
+ * Make a context using the memory provided by the arena.
+ *
+ * @return pointer to context or nullptr if can't be created
+ */
+ Context* makeContext(const ContextRec&, SkArenaAlloc*) const;
+
+#if SK_SUPPORT_GPU
+ struct AsFPArgs {
+ AsFPArgs() {}
+ AsFPArgs(GrContext* context,
+ const SkMatrix* viewMatrix,
+ const SkMatrix* localMatrix,
+ SkFilterQuality filterQuality,
+ SkColorSpace* dstColorSpace)
+ : fContext(context)
+ , fViewMatrix(viewMatrix)
+ , fLocalMatrix(localMatrix)
+ , fFilterQuality(filterQuality)
+ , fDstColorSpace(dstColorSpace) {}
+
+ GrContext* fContext;
+ const SkMatrix* fViewMatrix;
+ const SkMatrix* fLocalMatrix;
+ SkFilterQuality fFilterQuality;
+ SkColorSpace* fDstColorSpace;
+ };
+
+ /**
+ * Returns a GrFragmentProcessor that implements the shader for the GPU backend. NULL is
+ * returned if there is no GPU implementation.
+ *
+ * The GPU device does not call SkShader::createContext(), instead we pass the view matrix,
+ * local matrix, and filter quality directly.
+ *
+ * The GrContext may be used by the to create textures that are required by the returned
+ * processor.
+ *
+ * The returned GrFragmentProcessor should expect an unpremultiplied input color and
+ * produce a premultiplied output.
+ */
+ virtual sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const;
+#endif
+
+ /**
+ * If the shader can represent its "average" luminance in a single color, return true and
+ * if color is not NULL, return that color. If it cannot, return false and ignore the color
+ * parameter.
+ *
+ * Note: if this returns true, the returned color will always be opaque, as only the RGB
+ * components are used to compute luminance.
+ */
+ bool asLuminanceColor(SkColor*) const;
+
+ /**
+ * Returns a shader transformed into a new color space via the |xformer|.
+ */
+ sk_sp<SkShader> makeColorSpace(SkColorSpaceXformer* xformer) const {
+ return this->onMakeColorSpace(xformer);
+ }
+
+ virtual bool isRasterPipelineOnly() const { return false; }
+
+ bool appendStages(SkRasterPipeline*, SkColorSpace* dstCS, SkArenaAlloc*,
+ const SkMatrix& ctm, const SkPaint&, const SkMatrix* localM=nullptr) const;
+
+ bool computeTotalInverse(const SkMatrix& ctm,
+ const SkMatrix* outerLocalMatrix,
+ SkMatrix* totalInverse) const;
+
+#ifdef SK_SUPPORT_LEGACY_SHADER_ISABITMAP
+ virtual bool onIsABitmap(SkBitmap*, SkMatrix*, TileMode[2]) const {
+ return false;
+ }
+#endif
+
+ virtual SkImage* onIsAImage(SkMatrix*, TileMode[2]) const {
+ return nullptr;
+ }
+
+ SK_TO_STRING_VIRT()
+
+ SK_DEFINE_FLATTENABLE_TYPE(SkShaderBase)
+ SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
+
+protected:
+ SkShaderBase(const SkMatrix* localMatrix = nullptr);
+
+ void flatten(SkWriteBuffer&) const override;
+
+ /**
+ * Specialize creating a SkShader context using the supplied allocator.
+ * @return pointer to context owned by the arena allocator.
+ */
+ virtual Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const {
+ return nullptr;
+ }
+
+ virtual bool onAsLuminanceColor(SkColor*) const {
+ return false;
+ }
+
+ virtual sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer*) const {
+ return sk_ref_sp(const_cast<SkShaderBase*>(this));
+ }
+
+ virtual bool onAppendStages(SkRasterPipeline*, SkColorSpace* dstCS, SkArenaAlloc*,
+ const SkMatrix&, const SkPaint&, const SkMatrix* localM) const;
+
+private:
+ // This is essentially const, but not officially so it can be modified in constructors.
+ SkMatrix fLocalMatrix;
+
+ typedef SkShader INHERITED;
+};
+
+inline SkShaderBase* as_SB(SkShader* shader) {
+ return static_cast<SkShaderBase*>(shader);
+}
+
+inline const SkShaderBase* as_SB(const SkShader* shader) {
+ return static_cast<const SkShaderBase*>(shader);
+}
+
+inline const SkShaderBase* as_SB(const sk_sp<SkShader>& shader) {
+ return static_cast<SkShaderBase*>(shader.get());
+}
+
+#endif // SkShaderBase_DEFINED
diff --git a/src/shaders/gradients/Sk4fGradientBase.cpp b/src/shaders/gradients/Sk4fGradientBase.cpp
new file mode 100644
index 0000000000..e20f5f4702
--- /dev/null
+++ b/src/shaders/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 <functional>
+
+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<void(SkColor, SkColor, SkScalar, SkScalar)> 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<void(SkColor, SkColor, SkScalar, SkScalar)> 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<uint8_t>(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<DstType::L32, ApplyPremul::False>(x, y, dst, count);
+ } else {
+ this->shadePremulSpan<DstType::L32, ApplyPremul::True>(x, y, dst, count);
+ }
+}
+
+void SkGradientShaderBase::
+GradientShaderBase4fContext::shadeSpan4f(int x, int y, SkPM4f dst[], int count) {
+ if (fColorsArePremul) {
+ this->shadePremulSpan<DstType::F32, ApplyPremul::False>(x, y, dst, count);
+ } else {
+ this->shadePremulSpan<DstType::F32, ApplyPremul::True>(x, y, dst, count);
+ }
+}
+
+template<DstType dstType, ApplyPremul premul>
+void SkGradientShaderBase::
+GradientShaderBase4fContext::shadePremulSpan(int x, int y,
+ typename DstTraits<dstType, premul>::Type dst[],
+ int count) const {
+ const SkGradientShaderBase& shader =
+ static_cast<const SkGradientShaderBase&>(fShader);
+
+ switch (shader.fTileMode) {
+ case kClamp_TileMode:
+ this->shadeSpanInternal<dstType,
+ premul,
+ kClamp_TileMode>(x, y, dst, count);
+ break;
+ case kRepeat_TileMode:
+ this->shadeSpanInternal<dstType,
+ premul,
+ kRepeat_TileMode>(x, y, dst, count);
+ break;
+ case kMirror_TileMode:
+ this->shadeSpanInternal<dstType,
+ premul,
+ kMirror_TileMode>(x, y, dst, count);
+ break;
+ }
+}
+
+template<DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode>
+void SkGradientShaderBase::
+GradientShaderBase4fContext::shadeSpanInternal(int x, int y,
+ typename DstTraits<dstType, premul>::Type dst[],
+ int count) const {
+ static const int kBufSize = 128;
+ SkScalar ts[kBufSize];
+ TSampler<dstType, premul, tileMode> 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<dstType, premul>::store(c, dst++);
+ }
+ x += n;
+ count -= n;
+ } while (count > 0);
+}
+
+template<DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode>
+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<dstType, premul>::load(i->fCb);
+ fCg = DstTraits<dstType, premul>::load(i->fCg);
+ }
+
+ const GradientShaderBase4fContext& fCtx;
+ const Sk4fGradientInterval* fInterval;
+ SkScalar fPrevT;
+ SkScalar fLargestIntervalValue;
+ Sk4f fCb;
+ Sk4f fCg;
+};
diff --git a/src/shaders/gradients/Sk4fGradientBase.h b/src/shaders/gradients/Sk4fGradientBase.h
new file mode 100644
index 0000000000..a660d6bde5
--- /dev/null
+++ b/src/shaders/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<DstType, ApplyPremul, SkShader::TileMode tileMode>
+ class TSampler;
+
+ template <DstType dstType, ApplyPremul premul>
+ void shadePremulSpan(int x, int y, typename DstTraits<dstType, premul>::Type[],
+ int count) const;
+
+ template <DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode>
+ void shadeSpanInternal(int x, int y, typename DstTraits<dstType, premul>::Type[],
+ int count) const;
+};
+
+#endif // Sk4fGradientBase_DEFINED
diff --git a/src/shaders/gradients/Sk4fGradientPriv.h b/src/shaders/gradients/Sk4fGradientPriv.h
new file mode 100644
index 0000000000..f18d6ced7b
--- /dev/null
+++ b/src/shaders/gradients/Sk4fGradientPriv.h
@@ -0,0 +1,135 @@
+/*
+ * 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.
+ F32, // Linear float.
+};
+
+template <ApplyPremul>
+struct PremulTraits;
+
+template <>
+struct PremulTraits<ApplyPremul::False> {
+ static Sk4f apply(const Sk4f& c) { return c; }
+};
+
+template <>
+struct PremulTraits<ApplyPremul::True> {
+ 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 <DstType, ApplyPremul premul>
+struct DstTraits;
+
+template <ApplyPremul premul>
+struct DstTraits<DstType::L32, premul> {
+ using PM = PremulTraits<premul>;
+ 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<uint8_t>(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 <ApplyPremul premul>
+struct DstTraits<DstType::F32, premul> {
+ using PM = PremulTraits<premul>;
+ 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/shaders/gradients/Sk4fLinearGradient.cpp b/src/shaders/gradients/Sk4fLinearGradient.cpp
new file mode 100644
index 0000000000..7b7498eaef
--- /dev/null
+++ b/src/shaders/gradients/Sk4fLinearGradient.cpp
@@ -0,0 +1,430 @@
+/*
+ * 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 <cmath>
+
+namespace {
+
+template<DstType dstType, ApplyPremul premul>
+void ramp(const Sk4f& c, const Sk4f& dc, typename DstTraits<dstType, premul>::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<dstType, premul>::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<dstType, premul>::store(c0, dst++);
+ DstTraits<dstType, premul>::store(c1, dst++);
+ c0 = c0 + dc2;
+ }
+ if (n & 1) {
+ DstTraits<dstType, premul>::store(c0, dst);
+ }
+}
+
+template<SkShader::TileMode>
+SkScalar pinFx(SkScalar);
+
+template<>
+SkScalar pinFx<SkShader::kClamp_TileMode>(SkScalar fx) {
+ return fx;
+}
+
+template<>
+SkScalar pinFx<SkShader::kRepeat_TileMode>(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<SkShader::kMirror_TileMode>(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<DstType::L32,
+ ApplyPremul::False>(x, y, dst, count);
+ } else {
+ this->shadePremulSpan<DstType::L32,
+ ApplyPremul::True>(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<DstType::F32,
+ ApplyPremul::False>(x, y, dst, count);
+ } else {
+ this->shadePremulSpan<DstType::F32,
+ ApplyPremul::True>(x, y, dst, count);
+ }
+}
+
+template<DstType dstType, ApplyPremul premul>
+void SkLinearGradient::
+LinearGradient4fContext::shadePremulSpan(int x, int y,
+ typename DstTraits<dstType, premul>::Type dst[],
+ int count) const {
+ const SkLinearGradient& shader =
+ static_cast<const SkLinearGradient&>(fShader);
+ switch (shader.fTileMode) {
+ case kClamp_TileMode:
+ this->shadeSpanInternal<dstType,
+ premul,
+ kClamp_TileMode>(x, y, dst, count);
+ break;
+ case kRepeat_TileMode:
+ this->shadeSpanInternal<dstType,
+ premul,
+ kRepeat_TileMode>(x, y, dst, count);
+ break;
+ case kMirror_TileMode:
+ this->shadeSpanInternal<dstType,
+ premul,
+ kMirror_TileMode>(x, y, dst, count);
+ break;
+ }
+}
+
+template<DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode>
+void SkLinearGradient::
+LinearGradient4fContext::shadeSpanInternal(int x, int y,
+ typename DstTraits<dstType, premul>::Type dst[],
+ int count) const {
+ SkPoint pt;
+ fDstToPosProc(fDstToPos,
+ x + SK_ScalarHalf,
+ y + SK_ScalarHalf,
+ &pt);
+ const SkScalar fx = pinFx<tileMode>(pt.x());
+ const SkScalar dx = fDstToPos.getScaleX();
+ LinearIntervalProcessor<dstType, premul, tileMode> 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<SkScalar>(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<dstType, premul>::store(proc.currentColor(),
+ dst, n);
+ } else {
+ ramp<dstType, premul>(proc.currentColor(),
+ proc.currentColorGrad(),
+ dst, n);
+ }
+
+ proc.advance(SkIntToScalar(n));
+ count -= n;
+ dst += n;
+ }
+}
+
+template<DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode>
+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<dstType, premul>::load(fInterval->fCg);
+ fCc = DstTraits<dstType, premul>::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<dstType, premul>::load(i->fCb)
+ + DstTraits<dstType, premul>::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;
+ }
+ }
+}
diff --git a/src/shaders/gradients/Sk4fLinearGradient.h b/src/shaders/gradients/Sk4fLinearGradient.h
new file mode 100644
index 0000000000..f1c0bb5a30
--- /dev/null
+++ b/src/shaders/gradients/Sk4fLinearGradient.h
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+private:
+ using INHERITED = GradientShaderBase4fContext;
+
+ template<DstType, ApplyPremul, TileMode>
+ class LinearIntervalProcessor;
+
+ template <DstType dstType, ApplyPremul premul>
+ void shadePremulSpan(int x, int y, typename DstTraits<dstType, premul>::Type[],
+ int count) const;
+
+ template <DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode>
+ void shadeSpanInternal(int x, int y, typename DstTraits<dstType, premul>::Type[],
+ int count) const;
+
+ const Sk4fGradientInterval* findInterval(SkScalar fx) const;
+
+ bool isFast() const { return fDstToPosClass == kLinear_MatrixClass; }
+
+ mutable const Sk4fGradientInterval* fCachedInterval;
+};
+
+#endif // Sk4fLinearGradient_DEFINED
diff --git a/src/shaders/gradients/SkClampRange.cpp b/src/shaders/gradients/SkClampRange.cpp
new file mode 100644
index 0000000000..efc93959d1
--- /dev/null
+++ b/src/shaders/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<int64_t>::min()) {
+ return false; // SkTAbs overflow
+ }
+
+ SkASSERT(count >= 0);
+ uint64_t ucount = static_cast<uint64_t>(count);
+ uint64_t udx = static_cast<uint64_t>(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<int64_t>::max() - a) {
+ return false;
+ }
+ } else {
+ if (b < std::numeric_limits<int64_t>::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/shaders/gradients/SkClampRange.h b/src/shaders/gradients/SkClampRange.h
new file mode 100644
index 0000000000..8a22e72d38
--- /dev/null
+++ b/src/shaders/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/shaders/gradients/SkGradientBitmapCache.cpp b/src/shaders/gradients/SkGradientBitmapCache.cpp
new file mode 100644
index 0000000000..06b2d8c3fe
--- /dev/null
+++ b/src/shaders/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/shaders/gradients/SkGradientBitmapCache.h b/src/shaders/gradients/SkGradientBitmapCache.h
new file mode 100644
index 0000000000..0dcd32272e
--- /dev/null
+++ b/src/shaders/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/shaders/gradients/SkGradientShader.cpp b/src/shaders/gradients/SkGradientShader.cpp
new file mode 100644
index 0000000000..137da84d0c
--- /dev/null
+++ b/src/shaders/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 <algorithm>
+#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<SkData> colorSpaceData = fColorSpace ? fColorSpace->serialize() : nullptr;
+ if (colorSpaceData) {
+ flags |= kHasColorSpace_GSF;
+ }
+ SkASSERT(static_cast<uint32_t>(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<SkScalar*>(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<SkData> 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<uint8_t>(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<SkColor*>(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<float>(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<SkPM4f>(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<SkJumper_GradientCtx>();
+
+ // 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<float>(std::max(fColorCount+1, 8));
+ ctx->bs[i] = alloc->makeArray<float>(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<float>(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<SkHalf*>(bitmap->getPixels());
+ uint32_t* pixelsS32 = reinterpret_cast<uint32_t*>(bitmap->getPixels());
+
+ typedef std::function<void(const Sk4f&, int)> 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<float>(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::GradientShaderCache> 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<GradientShaderCache> 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<int32_t>(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<SkColorSpace> 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<SkShader> 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<SkShader> SkGradientShader::MakeLinear(const SkPoint pts[2],
+ const SkColor4f colors[],
+ sk_sp<SkColorSpace> 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<SkLinearGradient>(pts, desc);
+}
+
+sk_sp<SkShader> 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<SkShader> SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius,
+ const SkColor4f colors[],
+ sk_sp<SkColorSpace> 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<SkRadialGradient>(center, radius, desc);
+}
+
+sk_sp<SkShader> 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<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
+ SkScalar startRadius,
+ const SkPoint& end,
+ SkScalar endRadius,
+ const SkColor4f colors[],
+ sk_sp<SkColorSpace> 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<SkTwoPointConicalGradient>(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<SkTwoPointConicalGradient>(end, endRadius, start, startRadius,
+ flipGradient, desc);
+ }
+}
+
+sk_sp<SkShader> 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<SkShader> SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy,
+ const SkColor4f colors[],
+ sk_sp<SkColorSpace> 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<SkSweepGradient>(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<SkColor4f>& 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<SkColor4f>& 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<SkColor>& 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<SkColor>& 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<GrGradientEffect>();
+
+ 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<GrGradientEffect>();
+
+ 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<SkColor4f>(shader.fOrigColors4f, shader.fColorCount);
+ } else {
+ fColors = SkTDArray<SkColor>(shader.fOrigColors, shader.fColorCount);
+ }
+
+#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS
+ if (shader.fOrigPos) {
+ fPositions = SkTDArray<SkScalar>(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<GrTextureProxy> 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<GrGradientEffect>();
+
+ 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<SkColorSpace_XYZ*>(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<SkShader::TileMode>(random->nextULessThan(SkShader::kTileModeCount));
+}
+#endif
+
+#endif
diff --git a/src/shaders/gradients/SkGradientShaderPriv.h b/src/shaders/gradients/SkGradientShaderPriv.h
new file mode 100644
index 0000000000..7a66edaffc
--- /dev/null
+++ b/src/shaders/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<SkColorSpace> 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<SkColor4f*>(fColors); }
+ SkScalar* mutablePos() { return const_cast<SkScalar*>(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<SkPixelRef> 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<GradientShaderCache> 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 <typename T, typename... Args>
+ static Context* CheckedMakeContext(SkArenaAlloc* alloc, Args&&... args) {
+ auto* ctx = alloc->make<T>(std::forward<Args>(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<SkColorSpace> 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<GradientShaderCache> refCache(U8CPU alpha, bool dither) const;
+ mutable SkMutex fCacheMutex;
+ mutable sk_sp<GradientShaderCache> 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<GrColorSpaceXform> 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<GrColorSpaceXform> 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<SkColorSpace> 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<SkColor> fColors;
+
+ SkTDArray<SkColor4f> fColors4f;
+ sk_sp<GrColorSpaceXform> fColorSpaceXform;
+
+ SkTDArray<SkScalar> 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/shaders/gradients/SkLinearGradient.cpp b/src/shaders/gradients/SkLinearGradient.cpp
new file mode 100644
index 0000000000..17c4fd36a4
--- /dev/null
+++ b/src/shaders/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<SkFlattenable> 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<LinearGradient4fContext>(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<SkShader> 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<float>(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<float>(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<const SkLinearGradient&>(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<GrFragmentProcessor> Make(const CreateArgs& args) {
+ return sk_sp<GrFragmentProcessor>(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<GrLinearGradient>();
+ }
+
+ 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<GrFragmentProcessor> 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<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
+ GrAlwaysAssert(fp);
+ return fp;
+}
+#endif
+
+/////////////////////////////////////////////////////////////////////
+
+void GrLinearGradient::GLSLLinearProcessor::emitCode(EmitArgs& args) {
+ const GrLinearGradient& ge = args.fFp.cast<GrLinearGradient>();
+ 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<GrFragmentProcessor> 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<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(),
+ args.fDstColorSpace);
+ sk_sp<GrFragmentProcessor> 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 <bool apply_alpha>
+Sk4f pre_bias(const Sk4f& x, const Sk4f& bias) {
+ return apply_alpha ? x : x + bias;
+}
+
+template <bool apply_alpha>
+Sk4f post_bias(const Sk4f& x, const Sk4f& bias) {
+ return apply_alpha ? x + bias : x;
+}
+
+template <bool apply_alpha> 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<uint8_t>(post_bias<apply_alpha>(c4f255, bias)).store(&c);
+
+ return c;
+}
+
+template <bool apply_alpha> void fill(SkPMColor dst[], int count,
+ const Sk4f& c4, const Sk4f& bias0, const Sk4f& bias1) {
+ const SkPMColor c0 = trunc_from_255<apply_alpha>(pre_bias<apply_alpha>(c4, bias0), bias0);
+ const SkPMColor c1 = trunc_from_255<apply_alpha>(pre_bias<apply_alpha>(c4, bias1), bias1);
+ sk_memset32_dither(dst, c0, c1, count);
+}
+
+template <bool apply_alpha> void fill(SkPMColor dst[], int count, const Sk4f& c4) {
+ // Assumes that c4 does not need to be dithered.
+ sk_memset32(dst, trunc_from_255<apply_alpha>(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 <bool apply_alpha> 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<apply_alpha>(c , dither0);
+ Sk4f cd1 = pre_bias<apply_alpha>(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<apply_alpha>(cd0, dither0);
+ *dstC++ = trunc_from_255<apply_alpha>(cd1, dither1);
+ *dstC++ = trunc_from_255<apply_alpha>(cd2, dither0);
+ *dstC++ = trunc_from_255<apply_alpha>(cd3, dither1);
+ }
+ cd0 = cd0 + dc4;
+ cd1 = cd1 + dc4;
+ cd2 = cd2 + dc4;
+ cd3 = cd3 + dc4;
+ n -= 4;
+ }
+ if (n & 2) {
+ *dstC++ = trunc_from_255<apply_alpha>(cd0, dither0);
+ *dstC++ = trunc_from_255<apply_alpha>(cd1, dither1);
+ cd0 = cd0 + dc2;
+ }
+ if (n & 1) {
+ *dstC++ = trunc_from_255<apply_alpha>(cd0, dither0);
+ }
+}
+
+template <bool apply_alpha, bool dx_is_pos>
+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<uint32_t>(static_cast<uint32_t>(SkFloatToIntFloor(-fx * invDx)) + 1,
+ count);
+ SkASSERT(n > 0);
+ fill<apply_alpha>(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<uint32_t>(static_cast<uint32_t>(SkFloatToIntFloor((1 - fx) * invDx)) + 1,
+ count);
+ SkASSERT(n > 0);
+ fill<apply_alpha>(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<apply_alpha>(dstC, count, rec[fRecs.count() - 1].fColor);
+ return;
+ }
+ } else { // dx < 0
+ if (fx <= 0) {
+ fill<apply_alpha>(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<apply_alpha>(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<true>(dstC, count, c, dither0, dither1);
+ } else {
+ fill<false>(dstC, count, c, dither0, dither1);
+ }
+ return;
+ }
+
+ SkASSERT(0.f != dx);
+ const float invDx = 1 / dx;
+ if (dx > 0) {
+ if (fApplyAlphaAfterInterp) {
+ this->shade4_dx_clamp<true, true>(dstC, count, fx, dx, invDx, dither);
+ } else {
+ this->shade4_dx_clamp<false, true>(dstC, count, fx, dx, invDx, dither);
+ }
+ } else {
+ if (fApplyAlphaAfterInterp) {
+ this->shade4_dx_clamp<true, false>(dstC, count, fx, dx, invDx, dither);
+ } else {
+ this->shade4_dx_clamp<false, false>(dstC, count, fx, dx, invDx, dither);
+ }
+ }
+}
diff --git a/src/shaders/gradients/SkLinearGradient.h b/src/shaders/gradients/SkLinearGradient.h
new file mode 100644
index 0000000000..19a965c7bb
--- /dev/null
+++ b/src/shaders/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<Rec> fRecs;
+ bool fApplyAlphaAfterInterp;
+
+ void shade4_clamp(int x, int y, SkPMColor dstC[], int count);
+ template <bool, bool> 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<GrFragmentProcessor> 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<SkShader> 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/shaders/gradients/SkRadialGradient.cpp b/src/shaders/gradients/SkRadialGradient.cpp
new file mode 100644
index 0000000000..d49b3dd8e1
--- /dev/null
+++ b/src/shaders/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<RadialGradientContext>(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<SkFlattenable> 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<uint8_t>(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<uint8_t>(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 <SkFixed (*TileProc)(SkFixed)>
+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<mirror_tileproc_nonstatic>(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<repeat_tileproc_nonstatic>(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<const SkRadialGradient&>(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<GrFragmentProcessor> Make(const CreateArgs& args) {
+ return sk_sp<GrFragmentProcessor>(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<GrRadialGradient>();
+ }
+
+ 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<GrFragmentProcessor> GrRadialGradient::TestCreate(GrProcessorTestData* d) {
+ sk_sp<SkShader> 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<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
+ GrAlwaysAssert(fp);
+ return fp;
+}
+#endif
+
+/////////////////////////////////////////////////////////////////////
+
+void GrRadialGradient::GLSLRadialProcessor::emitCode(EmitArgs& args) {
+ const GrRadialGradient& ge = args.fFp.cast<GrRadialGradient>();
+ 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<GrFragmentProcessor> 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<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(),
+ args.fDstColorSpace);
+ sk_sp<GrFragmentProcessor> 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<SkShader> 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/shaders/gradients/SkRadialGradient.h b/src/shaders/gradients/SkRadialGradient.h
new file mode 100644
index 0000000000..69ec4b1285
--- /dev/null
+++ b/src/shaders/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<GrFragmentProcessor> 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<SkShader> 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/shaders/gradients/SkSweepGradient.cpp b/src/shaders/gradients/SkSweepGradient.cpp
new file mode 100644
index 0000000000..1e583c2ee0
--- /dev/null
+++ b/src/shaders/gradients/SkSweepGradient.cpp
@@ -0,0 +1,306 @@
+/*
+ * 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 <algorithm>
+#include <cmath>
+
+#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<SkFlattenable> 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);
+}
+
+SkShaderBase::Context* SkSweepGradient::onMakeContext(
+ const ContextRec& rec, SkArenaAlloc* alloc) const
+{
+ return CheckedMakeContext<SweepGradientContext>(alloc, *this, rec);
+}
+
+SkSweepGradient::SweepGradientContext::SweepGradientContext(
+ const SkSweepGradient& shader, const ContextRec& rec)
+ : INHERITED(shader, rec) {}
+
+bool SkSweepGradient::isRasterPipelineOnly() const {
+#ifdef SK_LEGACY_SWEEP_GRADIENT
+ return false;
+#else
+ return true;
+#endif
+}
+
+// returns angle in a circle [0..2PI) -> [0..255]
+static unsigned SkATan2_255(float y, float x) {
+ // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
+ static const float g255Over2PI = 40.584510488433314f;
+
+ float result = sk_float_atan2(y, x);
+ if (!SkScalarIsFinite(result)) {
+ return 0;
+ }
+ if (result < 0) {
+ result += 2 * SK_ScalarPI;
+ }
+ SkASSERT(result >= 0);
+ // since our value is always >= 0, we can cast to int, which is faster than
+ // calling floorf()
+ int ir = (int)(result * g255Over2PI);
+ SkASSERT(ir >= 0 && ir <= 255);
+ return ir;
+}
+
+void SkSweepGradient::SweepGradientContext::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
+ int count) {
+ SkMatrix::MapXYProc proc = fDstToIndexProc;
+ const SkMatrix& matrix = fDstToIndex;
+ const SkPMColor* SK_RESTRICT cache = fCache->getCache32();
+ int toggle = init_dither_toggle(x, y);
+ SkPoint srcPt;
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ proc(matrix, 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 = matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf);
+ dx = step.fX;
+ dy = step.fY;
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = matrix.getScaleX();
+ dy = matrix.getSkewY();
+ }
+
+ for (; count > 0; --count) {
+ *dstC++ = cache[toggle + SkATan2_255(fy, fx)];
+ fx += dx;
+ fy += dy;
+ toggle = next_dither_toggle(toggle);
+ }
+ } else { // perspective case
+ for (int stop = x + count; x < stop; x++) {
+ proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ *dstC++ = cache[toggle + SkATan2_255(srcPt.fY, srcPt.fX)];
+ toggle = next_dither_toggle(toggle);
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#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<GrFragmentProcessor> Make(const CreateArgs& args) {
+ return sk_sp<GrFragmentProcessor>(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<GrSweepGradient>();
+ }
+
+ 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<GrFragmentProcessor> 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<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
+ GrAlwaysAssert(fp);
+ return fp;
+}
+#endif
+
+/////////////////////////////////////////////////////////////////////
+
+void GrSweepGradient::GLSLSweepProcessor::emitCode(EmitArgs& args) {
+ const GrSweepGradient& ge = args.fFp.cast<GrSweepGradient>();
+ 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<GrFragmentProcessor> 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<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(),
+ args.fDstColorSpace);
+ sk_sp<GrFragmentProcessor> 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<SkShader> 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/shaders/gradients/SkSweepGradient.h b/src/shaders/gradients/SkSweepGradient.h
new file mode 100644
index 0000000000..b7ed7e5bb9
--- /dev/null
+++ b/src/shaders/gradients/SkSweepGradient.h
@@ -0,0 +1,54 @@
+/*
+ * 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 : public SkGradientShaderBase {
+public:
+ SkSweepGradient(SkScalar cx, SkScalar cy, const Descriptor&);
+
+ class SweepGradientContext : public SkGradientShaderBase::GradientShaderBaseContext {
+ public:
+ SweepGradientContext(const SkSweepGradient& shader, 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<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override;
+#endif
+
+ SK_TO_STRING_OVERRIDE()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSweepGradient)
+
+protected:
+ void flatten(SkWriteBuffer& buffer) const override;
+ Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
+ sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
+
+ bool adjustMatrixAndAppendStages(SkArenaAlloc* alloc,
+ SkMatrix* matrix,
+ SkRasterPipeline* p) const final;
+
+ bool isRasterPipelineOnly() const final;
+
+private:
+ const SkPoint fCenter;
+
+ friend class SkGradientShader;
+ typedef SkGradientShaderBase INHERITED;
+};
+
+#endif
diff --git a/src/shaders/gradients/SkTwoPointConicalGradient.cpp b/src/shaders/gradients/SkTwoPointConicalGradient.cpp
new file mode 100644
index 0000000000..4549527d51
--- /dev/null
+++ b/src/shaders/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<TwoPointConicalGradientContext>(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<const SkTwoPointConicalGradient&>(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<SkFlattenable> 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<GrFragmentProcessor> SkTwoPointConicalGradient::asFragmentProcessor(
+ const AsFPArgs& args) const {
+ SkASSERT(args.fContext);
+ SkASSERT(fPtsToUnit.isIdentity());
+ sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(),
+ args.fDstColorSpace);
+ sk_sp<GrFragmentProcessor> 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<SkShader> 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/shaders/gradients/SkTwoPointConicalGradient.h b/src/shaders/gradients/SkTwoPointConicalGradient.h
new file mode 100644
index 0000000000..b32f52c1e0
--- /dev/null
+++ b/src/shaders/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<GrFragmentProcessor> 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<SkShader> 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/shaders/gradients/SkTwoPointConicalGradient_gpu.cpp b/src/shaders/gradients/SkTwoPointConicalGradient_gpu.cpp
new file mode 100644
index 0000000000..8402199362
--- /dev/null
+++ b/src/shaders/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<GrFragmentProcessor> Make(const CreateArgs& args) {
+ return sk_sp<GrFragmentProcessor>(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<Edge2PtConicalEffect>();
+ 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<const SkTwoPointConicalGradient*>(args.fShader);
+ fCenterX1 = shader.getCenterX1();
+ fRadius0 = shader.getStartRadius();
+ fDiffRadius = shader.getDiffRadius();
+ this->initClassID<Edge2PtConicalEffect>();
+ // 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<GrFragmentProcessor> 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<GrFragmentProcessor> 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<Edge2PtConicalEffect>();
+ 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<Edge2PtConicalEffect>();
+ 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<GrFragmentProcessor> Make(const CreateArgs& args, SkScalar focalX) {
+ return sk_sp<GrFragmentProcessor>(
+ 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<FocalOutside2PtConicalEffect>();
+ return (INHERITED::onIsEqual(sBase) &&
+ this->fFocalX == s.fFocalX &&
+ this->fIsFlipped == s.fIsFlipped);
+ }
+
+ static bool IsFlipped(const CreateArgs& args) {
+ // eww.
+ return static_cast<const SkTwoPointConicalGradient*>(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<FocalOutside2PtConicalEffect>();
+ }
+
+ 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<GrFragmentProcessor> 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<GrFragmentProcessor> 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<FocalOutside2PtConicalEffect>();
+ fIsFlipped = data.isFlipped();
+}
+
+void FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor::emitCode(EmitArgs& args) {
+ const FocalOutside2PtConicalEffect& ge = args.fFp.cast<FocalOutside2PtConicalEffect>();
+ 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<FocalOutside2PtConicalEffect>();
+ 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<FocalOutside2PtConicalEffect>().isFlipped();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+class FocalInside2PtConicalEffect : public GrGradientEffect {
+public:
+ class GLSLFocalInside2PtConicalProcessor;
+
+ static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args, SkScalar focalX) {
+ return sk_sp<GrFragmentProcessor>(
+ 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<FocalInside2PtConicalEffect>();
+ return (INHERITED::onIsEqual(sBase) &&
+ this->fFocalX == s.fFocalX);
+ }
+
+ FocalInside2PtConicalEffect(const CreateArgs& args, SkScalar focalX)
+ : INHERITED(args, args.fShader->colorsAreOpaque()), fFocalX(focalX) {
+ this->initClassID<FocalInside2PtConicalEffect>();
+ }
+
+ 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<GrFragmentProcessor> 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<GrFragmentProcessor> 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<FocalInside2PtConicalEffect>();
+ 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<FocalInside2PtConicalEffect>();
+ 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(&centerEndTrans, &centerEnd, 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<GrFragmentProcessor> Make(const CreateArgs& args, const CircleConicalInfo& info) {
+ return sk_sp<GrFragmentProcessor>(
+ 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<CircleInside2PtConicalEffect>();
+ 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<CircleInside2PtConicalEffect>();
+ }
+
+ 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<GrFragmentProcessor> 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<GrFragmentProcessor> 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<CircleInside2PtConicalEffect>();
+ 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<CircleInside2PtConicalEffect>();
+ 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<GrFragmentProcessor> Make(const CreateArgs& args, const CircleConicalInfo& info) {
+ return sk_sp<GrFragmentProcessor>(
+ 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<CircleOutside2PtConicalEffect>();
+ 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<CircleOutside2PtConicalEffect>();
+ const SkTwoPointConicalGradient& shader =
+ *static_cast<const SkTwoPointConicalGradient*>(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<GrFragmentProcessor> 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<GrFragmentProcessor> 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<CircleOutside2PtConicalEffect>();
+ fIsFlipped = data.isFlipped();
+ }
+
+void CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor::emitCode(EmitArgs& args) {
+ const CircleOutside2PtConicalEffect& ge = args.fFp.cast<CircleOutside2PtConicalEffect>();
+ 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<CircleOutside2PtConicalEffect>();
+ 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<CircleOutside2PtConicalEffect>().isFlipped();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+sk_sp<GrFragmentProcessor> Gr2PtConicalGradientEffect::Make(
+ const GrGradientEffect::CreateArgs& args) {
+ const SkTwoPointConicalGradient& shader =
+ *static_cast<const SkTwoPointConicalGradient*>(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/shaders/gradients/SkTwoPointConicalGradient_gpu.h b/src/shaders/gradients/SkTwoPointConicalGradient_gpu.h
new file mode 100644
index 0000000000..46edb1f7d1
--- /dev/null
+++ b/src/shaders/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<GrFragmentProcessor> Make(const GrGradientEffect::CreateArgs& args);
+};
+
+#endif