diff options
Diffstat (limited to 'gfx/skia/skia/src/effects/gradients')
21 files changed, 7395 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/effects/gradients/Sk4fGradientBase.cpp b/gfx/skia/skia/src/effects/gradients/Sk4fGradientBase.cpp new file mode 100644 index 000000000..fa9364a60 --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/Sk4fGradientBase.cpp @@ -0,0 +1,444 @@ +/* + * 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; +} + +template<SkShader::TileMode> +SkScalar tileProc(SkScalar t); + +template<> +SkScalar tileProc<SkShader::kClamp_TileMode>(SkScalar t) { + // synthetic clamp-mode edge intervals allow for a free-floating t: + // [-inf..0)[0..1)[1..+inf) + return t; +} + +template<> +SkScalar tileProc<SkShader::kRepeat_TileMode>(SkScalar t) { + // t % 1 (intervals range: [0..1)) + return t - SkScalarFloorToScalar(t); +} + +template<> +SkScalar tileProc<SkShader::kMirror_TileMode>(SkScalar t) { + // t % 2 (synthetic mirror intervals expand the range to [0..2) + return t - SkScalarFloorToScalar(t / 2) * 2; +} + +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; +}; + +} // anonymous namespace + +SkGradientShaderBase::GradientShaderBase4fContext:: +Interval::Interval(const Sk4f& c0, SkScalar p0, + const Sk4f& c1, SkScalar p1) + : fP0(p0) + , fP1(p1) + , fZeroRamp((c0 == c1).allTrue()) { + + SkASSERT(p0 != p1); + // Either p0 or p1 can be (-)inf for synthetic clamp edge intervals. + SkASSERT(SkScalarIsFinite(p0) || SkScalarIsFinite(p1)); + + const auto dp = p1 - p0; + + // Clamp edge intervals are always zero-ramp. + SkASSERT(SkScalarIsFinite(dp) || fZeroRamp); + const Sk4f dc = SkScalarIsFinite(dp) ? (c1 - c0) / dp : 0; + + c0.store(&fC0.fVec); + dc.store(&fDc.fVec); +} + +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::buildIntervals(const SkGradientShaderBase& shader, + const ContextRec& rec, 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(shader.fColorCount > 0); + SkASSERT(shader.fOrigColors); + + const float paintAlpha = rec.fPaint->getAlpha() * (1.0f / 255); + const Sk4f componentScale = fColorsArePremul + ? Sk4f(paintAlpha) + : Sk4f(1.0f, 1.0f, 1.0f, paintAlpha); + const int first_index = reverse ? shader.fColorCount - 1 : 0; + const int last_index = shader.fColorCount - 1 - first_index; + const SkScalar first_pos = reverse ? SK_Scalar1 : 0; + const SkScalar last_pos = SK_Scalar1 - first_pos; + + if (shader.fTileMode == SkShader::kClamp_TileMode) { + // synthetic edge interval: -/+inf .. P0 + const Sk4f clamp_color = pack_color(shader.fOrigColors[first_index], + fColorsArePremul, componentScale); + const SkScalar clamp_pos = reverse ? SK_ScalarInfinity : SK_ScalarNegativeInfinity; + fIntervals.emplace_back(clamp_color, clamp_pos, + clamp_color, first_pos); + } else if (shader.fTileMode == SkShader::kMirror_TileMode && reverse) { + // synthetic mirror intervals injected before main intervals: (2 .. 1] + addMirrorIntervals(shader, componentScale, false); + } + + const IntervalIterator iter(shader.fOrigColors, + shader.fOrigPos, + shader.fColorCount, + reverse); + iter.iterate([this, &componentScale] (SkColor c0, SkColor c1, SkScalar p0, SkScalar p1) { + SkASSERT(fIntervals.empty() || fIntervals.back().fP1 == p0); + + fIntervals.emplace_back(pack_color(c0, fColorsArePremul, componentScale), + p0, + pack_color(c1, fColorsArePremul, componentScale), + p1); + }); + + if (shader.fTileMode == SkShader::kClamp_TileMode) { + // synthetic edge interval: Pn .. +/-inf + const Sk4f clamp_color = pack_color(shader.fOrigColors[last_index], + fColorsArePremul, componentScale); + const SkScalar clamp_pos = reverse ? SK_ScalarNegativeInfinity : SK_ScalarInfinity; + fIntervals.emplace_back(clamp_color, last_pos, + clamp_color, clamp_pos); + } else if (shader.fTileMode == SkShader::kMirror_TileMode && !reverse) { + // synthetic mirror intervals injected after main intervals: [1 .. 2) + addMirrorIntervals(shader, componentScale, true); + } +} + +void SkGradientShaderBase:: +GradientShaderBase4fContext::addMirrorIntervals(const SkGradientShaderBase& shader, + const Sk4f& componentScale, bool reverse) { + const IntervalIterator iter(shader.fOrigColors, + shader.fOrigPos, + shader.fColorCount, + reverse); + iter.iterate([this, &componentScale] (SkColor c0, SkColor c1, SkScalar p0, SkScalar p1) { + SkASSERT(fIntervals.empty() || fIntervals.back().fP1 == 2 - p0); + + fIntervals.emplace_back(pack_color(c0, fColorsArePremul, componentScale), + 2 - p0, + pack_color(c1, fColorsArePremul, componentScale), + 2 - p1); + }); +} + +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, 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, SkShader::TileMode tileMode> +class SkGradientShaderBase::GradientShaderBase4fContext::TSampler { +public: + TSampler(const GradientShaderBase4fContext& ctx) + : fFirstInterval(ctx.fIntervals.begin()) + , fLastInterval(ctx.fIntervals.end() - 1) + , fInterval(nullptr) { + SkASSERT(fLastInterval >= fFirstInterval); + } + + Sk4f sample(SkScalar t) { + const SkScalar tiled_t = tileProc<tileMode>(t); + + if (!fInterval) { + // Very first sample => locate the initial interval. + // TODO: maybe do this in ctor to remove a branch? + fInterval = this->findFirstInterval(tiled_t); + this->loadIntervalData(fInterval); + } else if (tiled_t < fInterval->fP0 || tiled_t >= fInterval->fP1) { + fInterval = this->findNextInterval(t, tiled_t); + this->loadIntervalData(fInterval); + } + + fPrevT = t; + return lerp(tiled_t); + } + +private: + Sk4f lerp(SkScalar t) { + SkASSERT(t >= fInterval->fP0 && t < fInterval->fP1); + return fCc + fDc * (t - fInterval->fP0); + } + + const Interval* findFirstInterval(SkScalar t) const { + // Binary search. + const Interval* i0 = fFirstInterval; + const Interval* i1 = fLastInterval; + + while (i0 != i1) { + SkASSERT(i0 < i1); + SkASSERT(t >= i0->fP0 && t < i1->fP1); + + const Interval* i = i0 + ((i1 - i0) >> 1); + + if (t >= i->fP1) { + i0 = i + 1; + } else { + i1 = i; + } + } + + SkASSERT(t >= i0->fP0 && t <= i0->fP1); + return i0; + } + + const Interval* findNextInterval(SkScalar t, SkScalar tiled_t) const { + SkASSERT(tiled_t < fInterval->fP0 || tiled_t >= fInterval->fP1); + SkASSERT(tiled_t >= fFirstInterval->fP0 && tiled_t < fLastInterval->fP1); + + const Interval* i = fInterval; + + // Use the t vs. prev_t signal to figure which direction we should search for + // the next interval, then perform a linear search. + if (t >= fPrevT) { + do { + i += 1; + if (i > fLastInterval) { + i = fFirstInterval; + } + } while (tiled_t < i->fP0 || tiled_t >= i->fP1); + } else { + do { + i -= 1; + if (i < fFirstInterval) { + i = fLastInterval; + } + } while (tiled_t < i->fP0 || tiled_t >= i->fP1); + } + + return i; + } + + void loadIntervalData(const Interval* i) { + fCc = DstTraits<dstType>::load(i->fC0); + fDc = DstTraits<dstType>::load(i->fDc); + } + + const Interval* fFirstInterval; + const Interval* fLastInterval; + const Interval* fInterval; + SkScalar fPrevT; + Sk4f fCc; + Sk4f fDc; +}; diff --git a/gfx/skia/skia/src/effects/gradients/Sk4fGradientBase.h b/gfx/skia/skia/src/effects/gradients/Sk4fGradientBase.h new file mode 100644 index 000000000..fd6d6563a --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/Sk4fGradientBase.h @@ -0,0 +1,75 @@ +/* + * 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 "SkShader.h" +#include "SkTArray.h" + +class SkGradientShaderBase:: +GradientShaderBase4fContext : public SkShader::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: + struct Interval { + Interval(const Sk4f& c0, SkScalar p0, + const Sk4f& c1, SkScalar p1); + + bool isZeroRamp() const { return fZeroRamp; } + + SkPM4f fC0, fDc; + SkScalar fP0, fP1; + bool fZeroRamp; + }; + + virtual void mapTs(int x, int y, SkScalar ts[], int count) const = 0; + + void buildIntervals(const SkGradientShaderBase&, const ContextRec&, bool reverse); + + SkSTArray<8, Interval, true> fIntervals; + SkMatrix fDstToPos; + SkMatrix::MapXYProc fDstToPosProc; + uint8_t fDstToPosClass; + uint8_t fFlags; + bool fDither; + bool fColorsArePremul; + +private: + using INHERITED = SkShader::Context; + + void addMirrorIntervals(const SkGradientShaderBase&, + const Sk4f& componentScale, bool reverse); + + template<DstType, 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/gfx/skia/skia/src/effects/gradients/Sk4fGradientPriv.h b/gfx/skia/skia/src/effects/gradients/Sk4fGradientPriv.h new file mode 100644 index 000000000..65fa821e8 --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/Sk4fGradientPriv.h @@ -0,0 +1,195 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4fGradientPriv_DEFINED +#define Sk4fGradientPriv_DEFINED + +#include "SkColor.h" +#include "SkHalf.h" +#include "SkImageInfo.h" +#include "SkNx.h" +#include "SkPM4f.h" +#include "SkPM4fPriv.h" +#include "SkUtils.h" + +// Templates shared by various 4f gradient flavors. + +namespace { + +enum class ApplyPremul { True, False }; + +enum class DstType { + L32, // Linear 32bit. Used for both shader/blitter paths. + S32, // SRGB 32bit. Used for the blitter path only. + F16, // Linear half-float. Used for blitters only. + F32, // Linear float. Used for shaders only. +}; + +template <ApplyPremul premul> +inline SkPMColor trunc_from_4f_255(const Sk4f& c) { + SkPMColor pmc; + SkNx_cast<uint8_t>(c).store(&pmc); + if (premul == ApplyPremul::True) { + pmc = SkPreMultiplyARGB(SkGetPackedA32(pmc), SkGetPackedR32(pmc), + SkGetPackedG32(pmc), SkGetPackedB32(pmc)); + } + return pmc; +} + +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 = ApplyPremul::False> +struct DstTraits; + +template <ApplyPremul premul> +struct DstTraits<DstType::L32, premul> { + using Type = SkPMColor; + + // For L32, we prescale the values by 255 to save a per-pixel multiplication. + static Sk4f load(const SkPM4f& c) { + return c.to4f_pmorder() * Sk4f(255); + } + + static void store(const Sk4f& c, Type* dst) { + *dst = trunc_from_4f_255<premul>(c); + } + + static void store(const Sk4f& c, Type* dst, int n) { + sk_memset32(dst, trunc_from_4f_255<premul>(c), 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::S32, premul> { + using PM = PremulTraits<premul>; + using Type = SkPMColor; + + static Sk4f load(const SkPM4f& c) { + return c.to4f_pmorder(); + } + + static void store(const Sk4f& c, Type* dst) { + // FIXME: this assumes opaque colors. Handle unpremultiplication. + *dst = Sk4f_toS32(PM::apply(c)); + } + + static void store(const Sk4f& c, Type* dst, int n) { + sk_memset32(dst, Sk4f_toS32(PM::apply(c)), n); + } + + static void store4x(const Sk4f& c0, const Sk4f& c1, + const Sk4f& c2, const Sk4f& c3, + Type* dst) { + store(c0, dst + 0); + store(c1, dst + 1); + store(c2, dst + 2); + store(c3, dst + 3); + } +}; + +template <ApplyPremul premul> +struct DstTraits<DstType::F16, premul> { + using PM = PremulTraits<premul>; + using Type = uint64_t; + + static Sk4f load(const SkPM4f& c) { + return c.to4f(); + } + + static void store(const Sk4f& c, Type* dst) { + SkFloatToHalf_finite_ftz(PM::apply(c)).store(dst); + } + + static void store(const Sk4f& c, Type* dst, int n) { + uint64_t color; + SkFloatToHalf_finite_ftz(PM::apply(c)).store(&color); + sk_memset64(dst, color, n); + } + + static void store4x(const Sk4f& c0, const Sk4f& c1, + const Sk4f& c2, const Sk4f& c3, + Type* dst) { + store(c0, dst + 0); + store(c1, dst + 1); + store(c2, dst + 2); + store(c3, dst + 3); + } +}; + +template <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/gfx/skia/skia/src/effects/gradients/Sk4fLinearGradient.cpp b/gfx/skia/skia/src/effects/gradients/Sk4fLinearGradient.cpp new file mode 100644 index 000000000..f9618dd1b --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/Sk4fLinearGradient.cpp @@ -0,0 +1,488 @@ +/* + * 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 "SkXfermode.h" + +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); + } +} + +// Planar version of ramp (S32 no-premul only). +template<> +void ramp<DstType::S32, ApplyPremul::False>(const Sk4f& c, const Sk4f& dc, SkPMColor dst[], int n) { + SkASSERT(n > 0); + + const Sk4f dc4 = dc * 4; + const Sk4x4f dc4x = { Sk4f(dc4[0]), Sk4f(dc4[1]), Sk4f(dc4[2]), Sk4f(dc4[3]) }; + Sk4x4f c4x = Sk4x4f::Transpose(c, c + dc, c + dc * 2, c + dc * 3); + + while (n >= 4) { + ( sk_linear_to_srgb(c4x.r) << 0 + | sk_linear_to_srgb(c4x.g) << 8 + | sk_linear_to_srgb(c4x.b) << 16 + | Sk4f_round(255.0f*c4x.a) << 24).store(dst); + + c4x.r += dc4x.r; + c4x.g += dc4x.g; + c4x.b += dc4x.b; + c4x.a += dc4x.a; + + dst += 4; + n -= 4; + } + + if (n & 2) { + DstTraits<DstType::S32, ApplyPremul::False> + ::store(Sk4f(c4x.r[0], c4x.g[0], c4x.b[0], c4x.a[0]), dst++); + DstTraits<DstType::S32, ApplyPremul::False> + ::store(Sk4f(c4x.r[1], c4x.g[1], c4x.b[1], c4x.a[1]), dst++); + } + + if (n & 1) { + DstTraits<DstType::S32, ApplyPremul::False> + ::store(Sk4f(c4x.r[n & 2], c4x.g[n & 2], c4x.b[n & 2], c4x.a[n & 2]), 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) { + const SkScalar f = SkScalarFraction(fx); + return f < 0 ? f + 1 : f; +} + +template<> +SkScalar pinFx<SkShader::kMirror_TileMode>(SkScalar fx) { + const SkScalar f = SkScalarMod(fx, 2.0f); + return f < 0 ? f + 2 : 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() && signbit(fDstToPos.getScaleX()); + this->buildIntervals(shader, rec, reverseIntervals); + + SkASSERT(fIntervals.count() > 0); + fCachedInterval = fIntervals.begin(); +} + +const SkGradientShaderBase::GradientShaderBase4fContext::Interval* +SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const { + SkASSERT(in_range(fx, fIntervals.front().fP0, fIntervals.back().fP1)); + + 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->fP0, fCachedInterval->fP1)) { + 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 Interval* i0 = fIntervals.begin(); + const Interval* i1 = fIntervals.end() - 1; + + while (i0 != i1) { + SkASSERT(i0 < i1); + SkASSERT(in_range(fx, i0->fP0, i1->fP1)); + + const Interval* i = i0 + ((i1 - i0) >> 1); + + if (in_range(fx, i0->fP0, i->fP1)) { + i1 = i; + } else { + SkASSERT(in_range(fx, i->fP1, i1->fP1)); + i0 = i + 1; + } + } + + SkASSERT(in_range(fx, i0->fP0, i0->fP1)); + 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, 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, SkShader::TileMode tileMode> +class SkLinearGradient:: +LinearGradient4fContext::LinearIntervalProcessor { +public: + LinearIntervalProcessor(const Interval* firstInterval, + const Interval* lastInterval, + const Interval* i, + SkScalar fx, + SkScalar dx, + bool is_vertical) + : fAdvX((i->fP1 - fx) / dx) + , fFirstInterval(firstInterval) + , fLastInterval(lastInterval) + , fInterval(i) + , fDx(dx) + , fIsVertical(is_vertical) + { + SkASSERT(fAdvX >= 0); + SkASSERT(firstInterval <= lastInterval); + SkASSERT(in_range(fx, i->fP0, i->fP1)); + this->compute_interval_props(fx - i->fP0); + } + + SkScalar currentAdvance() const { + SkASSERT(fAdvX >= 0); + SkASSERT(fAdvX <= (fInterval->fP1 - fInterval->fP0) / fDx); + 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) { + fZeroRamp = fIsVertical || fInterval->isZeroRamp(); + fCc = DstTraits<dstType>::load(fInterval->fC0); + + if (fInterval->isZeroRamp()) { + fDcDx = 0; + } else { + const Sk4f dC = DstTraits<dstType>::load(fInterval->fDc); + fCc = fCc + dC * Sk4f(t); + fDcDx = dC * fDx; + } + } + + const Interval* next_interval(const Interval* 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->fP1 - fInterval->fP0) / fDx; + SkASSERT(fAdvX > 0); + } while (advX >= fAdvX); + + compute_interval_props(0); + + 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 Interval* fFirstInterval; + const Interval* fLastInterval; + const Interval* 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); + ts[i] = pt.x(); + sx += SK_Scalar1; + } + } +} + +bool SkLinearGradient::LinearGradient4fContext::onChooseBlitProcs(const SkImageInfo& info, + BlitState* state) { + SkXfermode::Mode mode; + if (!SkXfermode::AsMode(state->fXfer, &mode)) { + return false; + } + + if (mode != SkXfermode::kSrc_Mode && + !(mode == SkXfermode::kSrcOver_Mode && (fFlags & kOpaqueAlpha_Flag))) { + return false; + } + + switch (info.colorType()) { + case kN32_SkColorType: + state->fBlitBW = D32_BlitBW; + return true; + case kRGBA_F16_SkColorType: + state->fBlitBW = D64_BlitBW; + return true; + default: + return false; + } +} + +void SkLinearGradient:: +LinearGradient4fContext::D32_BlitBW(BlitState* state, int x, int y, const SkPixmap& dst, + int count) { + // FIXME: ignoring coverage for now + const LinearGradient4fContext* ctx = + static_cast<const LinearGradient4fContext*>(state->fCtx); + + if (!dst.info().gammaCloseToSRGB()) { + if (ctx->fColorsArePremul) { + ctx->shadePremulSpan<DstType::L32, ApplyPremul::False>( + x, y, dst.writable_addr32(x, y), count); + } else { + ctx->shadePremulSpan<DstType::L32, ApplyPremul::True>( + x, y, dst.writable_addr32(x, y), count); + } + } else { + if (ctx->fColorsArePremul) { + ctx->shadePremulSpan<DstType::S32, ApplyPremul::False>( + x, y, dst.writable_addr32(x, y), count); + } else { + ctx->shadePremulSpan<DstType::S32, ApplyPremul::True>( + x, y, dst.writable_addr32(x, y), count); + } + } +} + +void SkLinearGradient:: +LinearGradient4fContext::D64_BlitBW(BlitState* state, int x, int y, const SkPixmap& dst, + int count) { + // FIXME: ignoring coverage for now + const LinearGradient4fContext* ctx = + static_cast<const LinearGradient4fContext*>(state->fCtx); + + if (ctx->fColorsArePremul) { + ctx->shadePremulSpan<DstType::F16, ApplyPremul::False>( + x, y, dst.writable_addr64(x, y), count); + } else { + ctx->shadePremulSpan<DstType::F16, ApplyPremul::True>( + x, y, dst.writable_addr64(x, y), count); + } +} diff --git a/gfx/skia/skia/src/effects/gradients/Sk4fLinearGradient.h b/gfx/skia/skia/src/effects/gradients/Sk4fLinearGradient.h new file mode 100644 index 000000000..dc7a17958 --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/Sk4fLinearGradient.h @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4fLinearGradient_DEFINED +#define Sk4fLinearGradient_DEFINED + +#include "Sk4fGradientBase.h" +#include "SkLinearGradient.h" + +class SkLinearGradient:: +LinearGradient4fContext final : public GradientShaderBase4fContext { +public: + LinearGradient4fContext(const SkLinearGradient&, const ContextRec&); + + void shadeSpan(int x, int y, SkPMColor dst[], int count) override; + void shadeSpan4f(int x, int y, SkPM4f dst[], int count) override; + +protected: + void mapTs(int x, int y, SkScalar ts[], int count) const override; + + bool onChooseBlitProcs(const SkImageInfo&, BlitState*) override; + +private: + using INHERITED = GradientShaderBase4fContext; + + template<DstType, 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 Interval* findInterval(SkScalar fx) const; + + bool isFast() const { return fDstToPosClass == kLinear_MatrixClass; } + + static void D32_BlitBW(BlitState*, int x, int y, const SkPixmap& dst, int count); + static void D64_BlitBW(BlitState*, int x, int y, const SkPixmap& dst, int count); + + mutable const Interval* fCachedInterval; +}; + +#endif // Sk4fLinearGradient_DEFINED diff --git a/gfx/skia/skia/src/effects/gradients/SkClampRange.cpp b/gfx/skia/skia/src/effects/gradients/SkClampRange.cpp new file mode 100644 index 000000000..5fd1c0369 --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkClampRange.cpp @@ -0,0 +1,158 @@ +/* + * 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 a, int64_t b, int64_t* result) { + // Do it the slow way until we have some assembly. + int64_t ua = SkTAbs(a); + int64_t ub = SkTAbs(b); + int zeros = SkCLZ64(ua) + SkCLZ64(ub); + // 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 = 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; + if (!sk_64_smul_check(count - 1, dx, &count_times_dx)) { + // we can't represent the computed end in 32.32, so just draw something (first color) + fCount1 = fCount2 = 0; + fCount0 = count; + return; + } + + int64_t ex = fx + (count - 1) * dx; + + 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/gfx/skia/skia/src/effects/gradients/SkClampRange.h b/gfx/skia/skia/src/effects/gradients/SkClampRange.h new file mode 100644 index 000000000..d3d2d08c8 --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkClampRange.h @@ -0,0 +1,51 @@ +/* + * 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 +#define SkScalarToGradFixed(x) SkScalarToFixed3232(x) +#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/gfx/skia/skia/src/effects/gradients/SkGradientBitmapCache.cpp b/gfx/skia/skia/src/effects/gradients/SkGradientBitmapCache.cpp new file mode 100644 index 000000000..20b87e02c --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkGradientBitmapCache.cpp @@ -0,0 +1,152 @@ +/* + * 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" + +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/gfx/skia/skia/src/effects/gradients/SkGradientBitmapCache.h b/gfx/skia/skia/src/effects/gradients/SkGradientBitmapCache.h new file mode 100644 index 000000000..0dcd32272 --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkGradientBitmapCache.h @@ -0,0 +1,48 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkGradientBitmapCache_DEFINED +#define SkGradientBitmapCache_DEFINED + +#include "SkBitmap.h" + +class SkGradientBitmapCache : SkNoncopyable { +public: + SkGradientBitmapCache(int maxEntries); + ~SkGradientBitmapCache(); + + bool find(const void* buffer, size_t len, SkBitmap*) const; + void add(const void* buffer, size_t len, const SkBitmap&); + +private: + int fEntryCount; + const int fMaxEntries; + + struct Entry; + mutable Entry* fHead; + mutable Entry* fTail; + + inline Entry* release(Entry*) const; + inline void attachToHead(Entry*) const; + +#ifdef SK_DEBUG + void validate() const; +#else + void validate() const {} +#endif + + class AutoValidate : SkNoncopyable { + public: + AutoValidate(const SkGradientBitmapCache* bc) : fBC(bc) { bc->validate(); } + ~AutoValidate() { fBC->validate(); } + private: + const SkGradientBitmapCache* fBC; + }; +}; + +#endif diff --git a/gfx/skia/skia/src/effects/gradients/SkGradientShader.cpp b/gfx/skia/skia/src/effects/gradients/SkGradientShader.cpp new file mode 100644 index 000000000..17302d9d4 --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkGradientShader.cpp @@ -0,0 +1,1750 @@ +/* + * 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 "Sk4fLinearGradient.h" +#include "SkGradientShaderPriv.h" +#include "SkHalf.h" +#include "SkLinearGradient.h" +#include "SkRadialGradient.h" +#include "SkTwoPointConicalGradient.h" +#include "SkSweepGradient.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::NewNamed(SkColorSpace::kSRGBLinear_Named); + } 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)); +} + +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)SkShader::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; + fCache32PixelRef = nullptr; +} + +SkGradientShaderBase::GradientShaderCache::~GradientShaderCache() { + SkSafeUnref(fCache32PixelRef); +} + +/* + * 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::NewAllocate(info, 0, nullptr); + cache->fCache32 = (SkPMColor*)cache->fCache32PixelRef->getAddr(); + 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); + bitmap->lockPixels(); + 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); + bitmap->unlockPixels(); +} + +/* + * The gradient holds a cache for the most recent value of alpha. Successive + * callers with the same alpha value will share the same cache. + */ +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. + fCache.get()->ref(); + 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 + SkAutoTUnref<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(cache->getCache32PixelRef()); + } 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::NewNamed(SkColorSpace::kSRGB_Named)); + break; + case GradientBitmapType::kHalfFloat: + info = SkImageInfo::Make( + kCache32Count, 1, kRGBA_F16_SkColorType, kPremul_SkAlphaType, + SkColorSpace::NewNamed(SkColorSpace::kSRGBLinear_Named)); + 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; + if (flipGrad && (info->fColors || info->fColorOffsets)) { + SkAutoSTArray<8, SkColor> colorStorage(fColorCount); + SkAutoSTArray<8, Rec> recStorage(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)); + } + + 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)); + } + + 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(); + } + } + 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)); + } + + 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 "GrInvariantOutput.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::kHardStopCentered_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], 0.5f) && + SkScalarNearlyEqual(shader.fOrigPos[2], 0.5f) && + SkScalarNearlyEqual(shader.fOrigPos[3], 1.0f)) { + + return kHardStopCentered_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); + } 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 GrProcessor& processor) { + const GrGradientEffect& e = processor.cast<GrGradientEffect>(); + + switch (e.getColorType()) { +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + case GrGradientEffect::kHardStopCentered_ColorType: + 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)) { + pdman.setSkMatrix44(fColorSpaceXformUni, e.fColorSpaceXform->srcToDst()); + } + 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::kHardStopCentered_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 GrGLSLCaps* glslCaps, + 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 kHardStopCentered_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("}"); + } + + // Calculate color + fragBuilder->codeAppendf("float relative_t = fract(2.0 * clamp_t);"); + if (SkShader::kClamp_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("relative_t += step(1.0, %s);", t); + } + + fragBuilder->codeAppendf("vec4 start = %s[0];", colors); + fragBuilder->codeAppendf("vec4 end = %s[1];", colors); + fragBuilder->codeAppendf("if (clamp_t >= 0.5) {"); + fragBuilder->codeAppendf(" start = %s[2];", colors); + fragBuilder->codeAppendf(" end = %s[3];", colors); + fragBuilder->codeAppendf("}"); + fragBuilder->codeAppendf("vec4 colorTemp = mix(start, end, relative_t);"); + + if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) { + fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;"); + } + fragBuilder->codeAppendf("%s = %s;", outputColor, + (GrGLSLExpr4(inputColor) * GrGLSLExpr4("colorTemp")).c_str()); + + 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;"); + } + fragBuilder->codeAppendf("%s = %s;", outputColor, + (GrGLSLExpr4(inputColor) * GrGLSLExpr4("colorTemp")).c_str()); + + 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;"); + } + fragBuilder->codeAppendf("%s = %s;", outputColor, + (GrGLSLExpr4(inputColor) * GrGLSLExpr4("colorTemp")).c_str()); + + 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;"); + } + + fragBuilder->codeAppendf("%s = %s;", outputColor, + (GrGLSLExpr4(inputColor) * GrGLSLExpr4("colorTemp")).c_str()); + + 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 (!glslCaps->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;"); + } + + fragBuilder->codeAppendf("%s = %s;", outputColor, + (GrGLSLExpr4(inputColor) * GrGLSLExpr4("colorTemp")).c_str()); + + break; + } + + case kTexture_ColorType: { + GrGLSLColorSpaceXformHelper colorSpaceHelper(uniformHandler, ge.fColorSpaceXform.get(), + &fColorSpaceXformUni); + + 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, &colorSpaceHelper); + fragBuilder->codeAppend(";"); + + break; + } + } +} + +///////////////////////////////////////////////////////////////////// + +GrGradientEffect::GrGradientEffect(const CreateArgs& args) { + 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 kHardStopCentered_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); + + 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. + GrTextureParams params; + params.setFilterMode(GrTextureParams::kBilerp_FilterMode); + params.setTileModeX(args.fTileMode); + + fRow = fAtlas->lockRow(bitmap); + if (-1 != fRow) { + fYCoord = fAtlas->getYOffset(fRow)+SK_ScalarHalf*fAtlas->getNormalizedTexelHeight(); + fCoordTransform.reset(*args.fMatrix, fAtlas->getTexture(), params.filterMode()); + fTextureAccess.reset(fAtlas->getTexture(), params); + } else { + SkAutoTUnref<GrTexture> texture( + GrRefCachedBitmapTexture(args.fContext, bitmap, params, + SkSourceGammaTreatment::kRespect)); + if (!texture) { + return; + } + fCoordTransform.reset(*args.fMatrix, texture, params.filterMode()); + fTextureAccess.reset(texture, params); + fYCoord = SK_ScalarHalf; + } + + this->addTextureAccess(&fTextureAccess); + + 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()) { + if (kTexture_ColorType == fColorType) { + if (fYCoord != ge.getYCoord()) { + return false; + } + } else { + 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; + } + } + } + + + SkASSERT(this->useAtlas() == ge.useAtlas()); + return GrColorSpaceXform::Equals(this->fColorSpaceXform.get(), ge.fColorSpaceXform.get()); + } + + return false; +} + +void GrGradientEffect::onComputeInvariantOutput(GrInvariantOutput* inout) const { + if (fIsOpaque) { + inout->mulByUnknownOpaqueFourComponents(); + } else { + inout->mulByUnknownFourComponents(); + } +} + +int GrGradientEffect::RandomGradientParams(SkRandom* random, + SkColor colors[], + SkScalar** stops, + SkShader::TileMode* tm) { + int outColors = random->nextRangeU(1, kMaxRandomGradientColors); + + // if one color, omit stops, otherwise randomly decide whether or not to + if (outColors == 1 || (outColors >= 2 && random->nextBool())) { + *stops = nullptr; + } + + SkScalar stop = 0.f; + for (int i = 0; i < outColors; ++i) { + colors[i] = random->nextU(); + if (*stops) { + (*stops)[i] = stop; + stop = i < outColors - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f; + } + } + *tm = static_cast<SkShader::TileMode>(random->nextULessThan(SkShader::kTileModeCount)); + + return outColors; +} + +#endif diff --git a/gfx/skia/skia/src/effects/gradients/SkGradientShaderPriv.h b/gfx/skia/skia/src/effects/gradients/SkGradientShaderPriv.h new file mode 100644 index 000000000..61a44184f --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkGradientShaderPriv.h @@ -0,0 +1,517 @@ +/* + * 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 "SkClampRange.h" +#include "SkColorPriv.h" +#include "SkColorSpace.h" +#include "SkReadBuffer.h" +#include "SkWriteBuffer.h" +#include "SkMallocPixelRef.h" +#include "SkUtils.h" +#include "SkShader.h" +#include "SkOnce.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 SkShader { +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); + virtual ~SkGradientShaderBase(); + + // 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(); + + SkMallocPixelRef* getCache32PixelRef() const { return fCache32PixelRef; } + + 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; + + SkMallocPixelRef* 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 SkShader::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; + + SkAutoTUnref<GradientShaderCache> fCache; + + private: + typedef SkShader::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: + class GradientShaderBase4fContext; + + SkGradientShaderBase(SkReadBuffer& ); + void flatten(SkWriteBuffer&) const override; + SK_TO_STRING_OVERRIDE() + + const SkMatrix fPtsToUnit; + TileMode fTileMode; + TileProc fTileProc; + uint8_t fGradFlags; + + struct Rec { + SkFixed fPos; // 0...1 + uint32_t fScale; // (1 << 24) / range + }; + Rec* fRecs; + + 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); + + template <typename T, typename... Args> + static Context* CheckedCreateContext(void* storage, Args&&... args) { + auto* ctx = new (storage) T(std::forward<Args>(args)...); + if (!ctx->isValid()) { + ctx->~T(); + return nullptr; + } + return ctx; + } + +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; + + GradientShaderCache* refCache(U8CPU alpha, bool dither) const; + mutable SkMutex fCacheMutex; + mutable SkAutoTUnref<GradientShaderCache> fCache; + + void initCommon(); + + typedef SkShader 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/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(const CreateArgs&); + + virtual ~GrGradientEffect(); + + 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 + kHardStopCentered_ColorType, // 0, 0.5, 0.5, 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: + /** Populates a pair of arrays with colors and stop info to construct a random gradient. + The function decides whether stop values should be used or not. The return value indicates + the number of colors, which will be capped by kMaxRandomGradientColors. colors should be + sized to be at least kMaxRandomGradientColors. stops is a pointer to an array of at least + size kMaxRandomGradientColors. It may be updated to nullptr, indicating that nullptr should + be passed to the gradient factory rather than the array. + */ + static const int kMaxRandomGradientColors = 4; + static int RandomGradientParams(SkRandom* r, + SkColor colors[kMaxRandomGradientColors], + SkScalar** stops, + SkShader::TileMode* tm); + + bool onIsEqual(const GrFragmentProcessor&) const override; + + void onComputeInvariantOutput(GrInvariantOutput* inout) const override; + + const GrCoordTransform& getCoordTransform() const { return fCoordTransform; } + +private: + // 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; + GrTextureAccess fTextureAccess; + 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 GrProcessor&) 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 GrGLSLCaps* caps, + 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 fFSYUni; + GrGLSLProgramDataManager::UniformHandle fColorSpaceXformUni; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +#endif + +#endif diff --git a/gfx/skia/skia/src/effects/gradients/SkLinearGradient.cpp b/gfx/skia/skia/src/effects/gradients/SkLinearGradient.cpp new file mode 100644 index 000000000..df2765b46 --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkLinearGradient.cpp @@ -0,0 +1,769 @@ +/* + * 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 "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 SkShader::ContextRec& rec, uint32_t flags) { +#ifdef FORCE_4F_CONTEXT + return true; +#else + return rec.fPreferredDstType == SkShader::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); +} + +size_t SkLinearGradient::onContextSize(const ContextRec& rec) const { + return use_4f_context(rec, fGradFlags) + ? sizeof(LinearGradient4fContext) + : sizeof(LinearGradientContext); +} + +SkShader::Context* SkLinearGradient::onCreateContext(const ContextRec& rec, void* storage) const { + return use_4f_context(rec, fGradFlags) + ? CheckedCreateContext<LinearGradient4fContext>(storage, *this, rec) + : CheckedCreateContext< LinearGradientContext>(storage, *this, rec); +} + +// 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) - (SK_FixedHalf >> SkGradientShaderBase::kCache32Bits)); + 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); + +// Only use the Sk4f impl when known to be fast. +#if defined(SKNX_IS_FAST) + if (SkShader::kClamp_TileMode == linearGradient.fTileMode && + kLinear_MatrixClass == fDstToIndexClass) + { + this->shade4_clamp(x, y, dstC, count); + return; + } +#endif + + 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 = SkScalarToGradFixed(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 = SkScalarToGradFixed(step.fX); + } else { + SkASSERT(fDstToIndexClass == kLinear_MatrixClass); + dx = SkScalarToGradFixed(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 "glsl/GrGLSLCaps.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)); + } + + virtual ~GrLinearGradient() { } + + const char* name() const override { return "Linear Gradient"; } + +private: + GrLinearGradient(const CreateArgs& args) + : INHERITED(args) { + this->initClassID<GrLinearGradient>(); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + virtual void onGetGLSLProcessorKey(const GrGLSLCaps& caps, + GrProcessorKeyBuilder* b) const override; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + typedef GrGradientEffect INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrLinearGradient::GLSLLinearProcessor : public GrGradientEffect::GLSLProcessor { +public: + GLSLLinearProcessor(const GrProcessor&) {} + + virtual ~GLSLLinearProcessor() { } + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor& processor, const GrGLSLCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); + } + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +GrGLSLFragmentProcessor* GrLinearGradient::onCreateGLSLInstance() const { + return new GrLinearGradient::GLSLLinearProcessor(*this); +} + +void GrLinearGradient::onGetGLSLProcessorKey(const GrGLSLCaps& caps, + GrProcessorKeyBuilder* b) const { + GrLinearGradient::GLSLLinearProcessor::GenKey(*this, caps, b); +} + +///////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrLinearGradient); + +sk_sp<GrFragmentProcessor> GrLinearGradient::TestCreate(GrProcessorTestData* d) { + SkPoint points[] = {{d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}, + {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}}; + + SkColor colors[kMaxRandomGradientColors]; + SkScalar stopsArray[kMaxRandomGradientColors]; + SkScalar* stops = stopsArray; + SkShader::TileMode tm; + int colorCount = RandomGradientParams(d->fRandom, colors, &stops, &tm); + auto shader = SkGradientShader::MakeLinear(points, colors, stops, colorCount, tm); + SkMatrix viewMatrix = GrTest::TestMatrix(d->fRandom); + auto dstColorSpace = GrTest::TestColorSpace(d->fRandom); + sk_sp<GrFragmentProcessor> fp = shader->asFragmentProcessor(SkShader::AsFPArgs( + d->fContext, &viewMatrix, NULL, kNone_SkFilterQuality, dstColorSpace.get(), + SkSourceGammaTreatment::kRespect)); + GrAlwaysAssert(fp); + return fp; +} + +///////////////////////////////////////////////////////////////////// + +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.fGLSLCaps, + 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; +} + +template <bool apply_alpha> SkPMColor trunc_from_255(const Sk4f& x) { + SkPMColor c; + SkNx_cast<uint8_t>(x).store(&c); + if (apply_alpha) { + c = SkPreMultiplyARGB(SkGetPackedA32(c), SkGetPackedR32(c), + SkGetPackedG32(c), SkGetPackedB32(c)); + } + return c; +} + +template <bool apply_alpha> void fill(SkPMColor dst[], int count, + const Sk4f& c4, const Sk4f& c4other) { + sk_memset32_dither(dst, trunc_from_255<apply_alpha>(c4), + trunc_from_255<apply_alpha>(c4other), 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), 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 = c + dither0; + Sk4f cd1 = 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); + *dstC++ = trunc_from_255<apply_alpha>(cd1); + *dstC++ = trunc_from_255<apply_alpha>(cd2); + *dstC++ = trunc_from_255<apply_alpha>(cd3); + } + cd0 = cd0 + dc4; + cd1 = cd1 + dc4; + cd2 = cd2 + dc4; + cd3 = cd3 + dc4; + n -= 4; + } + if (n & 2) { + *dstC++ = trunc_from_255<apply_alpha>(cd0); + *dstC++ = trunc_from_255<apply_alpha>(cd1); + cd0 = cd0 + dc2; + } + if (n & 1) { + *dstC++ = trunc_from_255<apply_alpha>(cd0); + } +} + +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 }; + const float invDx = 1 / dx; + + 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, c + dither1); + } else { + fill<false>(dstC, count, c + dither0, c + dither1); + } + return; + } + + 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/gfx/skia/skia/src/effects/gradients/SkLinearGradient.h b/gfx/skia/skia/src/effects/gradients/SkLinearGradient.h new file mode 100644 index 000000000..7a85b88cb --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkLinearGradient.h @@ -0,0 +1,81 @@ +/* + * 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; + size_t onContextSize(const ContextRec&) const override; + Context* onCreateContext(const ContextRec&, void* storage) const override; + +private: + class LinearGradient4fContext; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; + const SkPoint fStart; + const SkPoint fEnd; +}; + +#endif diff --git a/gfx/skia/skia/src/effects/gradients/SkRadialGradient.cpp b/gfx/skia/skia/src/effects/gradients/SkRadialGradient.cpp new file mode 100644 index 000000000..18ef37686 --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkRadialGradient.cpp @@ -0,0 +1,386 @@ +/* + * 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 "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) { +} + +size_t SkRadialGradient::onContextSize(const ContextRec&) const { + return sizeof(RadialGradientContext); +} + +SkShader::Context* SkRadialGradient::onCreateContext(const ContextRec& rec, void* storage) const { + return CheckedCreateContext<RadialGradientContext>(storage, *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 "glsl/GrGLSLCaps.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)); + } + + virtual ~GrRadialGradient() { } + + const char* name() const override { return "Radial Gradient"; } + +private: + GrRadialGradient(const CreateArgs& args) + : INHERITED(args) { + this->initClassID<GrRadialGradient>(); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + virtual void onGetGLSLProcessorKey(const GrGLSLCaps& caps, + GrProcessorKeyBuilder* b) const override; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + typedef GrGradientEffect INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrRadialGradient::GLSLRadialProcessor : public GrGradientEffect::GLSLProcessor { +public: + GLSLRadialProcessor(const GrProcessor&) {} + virtual ~GLSLRadialProcessor() { } + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor& processor, const GrGLSLCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); + } + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +///////////////////////////////////////////////////////////////////// + +GrGLSLFragmentProcessor* GrRadialGradient::onCreateGLSLInstance() const { + return new GrRadialGradient::GLSLRadialProcessor(*this); +} + +void GrRadialGradient::onGetGLSLProcessorKey(const GrGLSLCaps& caps, + GrProcessorKeyBuilder* b) const { + GrRadialGradient::GLSLRadialProcessor::GenKey(*this, caps, b); +} + +///////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRadialGradient); + +sk_sp<GrFragmentProcessor> GrRadialGradient::TestCreate(GrProcessorTestData* d) { + SkPoint center = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius = d->fRandom->nextUScalar1(); + + SkColor colors[kMaxRandomGradientColors]; + SkScalar stopsArray[kMaxRandomGradientColors]; + SkScalar* stops = stopsArray; + SkShader::TileMode tm; + int colorCount = RandomGradientParams(d->fRandom, colors, &stops, &tm); + auto shader = SkGradientShader::MakeRadial(center, radius, colors, stops, colorCount, tm); + SkMatrix viewMatrix = GrTest::TestMatrix(d->fRandom); + auto dstColorSpace = GrTest::TestColorSpace(d->fRandom); + sk_sp<GrFragmentProcessor> fp = shader->asFragmentProcessor(SkShader::AsFPArgs( + d->fContext, &viewMatrix, NULL, kNone_SkFilterQuality, dstColorSpace.get(), + SkSourceGammaTreatment::kRespect)); + GrAlwaysAssert(fp); + return fp; +} + +///////////////////////////////////////////////////////////////////// + +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.fGLSLCaps, + 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 + +#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/gfx/skia/skia/src/effects/gradients/SkRadialGradient.h b/gfx/skia/skia/src/effects/gradients/SkRadialGradient.h new file mode 100644 index 000000000..0b239039e --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkRadialGradient.h @@ -0,0 +1,49 @@ +/* + * 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; + size_t onContextSize(const ContextRec&) const override; + Context* onCreateContext(const ContextRec&, void* storage) const override; + +private: + const SkPoint fCenter; + const SkScalar fRadius; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/effects/gradients/SkSweepGradient.cpp b/gfx/skia/skia/src/effects/gradients/SkSweepGradient.cpp new file mode 100644 index 000000000..d1fe269b9 --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkSweepGradient.cpp @@ -0,0 +1,276 @@ +/* + * 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 "SkSweepGradient.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); +} + +size_t SkSweepGradient::onContextSize(const ContextRec&) const { + return sizeof(SweepGradientContext); +} + +SkShader::Context* SkSweepGradient::onCreateContext(const ContextRec& rec, void* storage) const { + return CheckedCreateContext<SweepGradientContext>(storage, *this, rec); +} + +SkSweepGradient::SweepGradientContext::SweepGradientContext( + const SkSweepGradient& shader, const ContextRec& rec) + : INHERITED(shader, rec) {} + +// 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 "gl/GrGLContext.h" +#include "glsl/GrGLSLCaps.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)); + } + virtual ~GrSweepGradient() { } + + const char* name() const override { return "Sweep Gradient"; } + +private: + GrSweepGradient(const CreateArgs& args) + : INHERITED(args) { + this->initClassID<GrSweepGradient>(); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + virtual void onGetGLSLProcessorKey(const GrGLSLCaps& caps, + GrProcessorKeyBuilder* b) const override; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + typedef GrGradientEffect INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrSweepGradient::GLSLSweepProcessor : public GrGradientEffect::GLSLProcessor { +public: + GLSLSweepProcessor(const GrProcessor&) {} + virtual ~GLSLSweepProcessor() { } + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor& processor, const GrGLSLCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); + } + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +///////////////////////////////////////////////////////////////////// + +GrGLSLFragmentProcessor* GrSweepGradient::onCreateGLSLInstance() const { + return new GrSweepGradient::GLSLSweepProcessor(*this); +} + +void GrSweepGradient::onGetGLSLProcessorKey(const GrGLSLCaps& caps, + GrProcessorKeyBuilder* b) const { + GrSweepGradient::GLSLSweepProcessor::GenKey(*this, caps, b); +} + + +///////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSweepGradient); + +sk_sp<GrFragmentProcessor> GrSweepGradient::TestCreate(GrProcessorTestData* d) { + SkPoint center = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + + SkColor colors[kMaxRandomGradientColors]; + SkScalar stopsArray[kMaxRandomGradientColors]; + SkScalar* stops = stopsArray; + SkShader::TileMode tmIgnored; + int colorCount = RandomGradientParams(d->fRandom, colors, &stops, &tmIgnored); + sk_sp<SkShader> shader(SkGradientShader::MakeSweep(center.fX, center.fY, colors, stops, + colorCount)); + SkMatrix viewMatrix = GrTest::TestMatrix(d->fRandom); + auto dstColorSpace = GrTest::TestColorSpace(d->fRandom); + sk_sp<GrFragmentProcessor> fp = shader->asFragmentProcessor(SkShader::AsFPArgs( + d->fContext, &viewMatrix, NULL, kNone_SkFilterQuality, dstColorSpace.get(), + SkSourceGammaTreatment::kRespect)); + GrAlwaysAssert(fp); + return fp; +} + +///////////////////////////////////////////////////////////////////// + +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] + // On Intel GPU there is an issue where it reads the second arguement to atan "- %s.x" as an int + // thus must us -1.0 * %s.x to work correctly + if (args.fGLSLCaps->mustForceNegatedAtanParamToFloat()){ + t.printf("(atan(- %s.y, -1.0 * %s.x) * 0.1591549430918 + 0.5)", + 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.fGLSLCaps, + 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 + +#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(")"); +} +#endif diff --git a/gfx/skia/skia/src/effects/gradients/SkSweepGradient.h b/gfx/skia/skia/src/effects/gradients/SkSweepGradient.h new file mode 100644 index 000000000..f132118b3 --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkSweepGradient.h @@ -0,0 +1,48 @@ +/* + * 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; + size_t onContextSize(const ContextRec&) const override; + Context* onCreateContext(const ContextRec&, void* storage) const override; + +private: + const SkPoint fCenter; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/effects/gradients/SkTwoPointConicalGradient.cpp b/gfx/skia/skia/src/effects/gradients/SkTwoPointConicalGradient.cpp new file mode 100644 index 000000000..599fd4c9f --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkTwoPointConicalGradient.cpp @@ -0,0 +1,398 @@ +/* + * 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; +} + +size_t SkTwoPointConicalGradient::onContextSize(const ContextRec&) const { + return sizeof(TwoPointConicalGradientContext); +} + +SkShader::Context* SkTwoPointConicalGradient::onCreateContext(const ContextRec& rec, + void* storage) const { + return CheckedCreateContext<TwoPointConicalGradientContext>(storage, *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 + +#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/gfx/skia/skia/src/effects/gradients/SkTwoPointConicalGradient.h b/gfx/skia/skia/src/effects/gradients/SkTwoPointConicalGradient.h new file mode 100644 index 000000000..d16e4bc3c --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkTwoPointConicalGradient.h @@ -0,0 +1,92 @@ +/* + * 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 "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() {} + + 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; + size_t onContextSize(const ContextRec&) const override; + Context* onCreateContext(const ContextRec&, void* storage) const override; + +private: + SkPoint fCenter1; + SkPoint fCenter2; + SkScalar fRadius1; + SkScalar fRadius2; + bool fFlippedGrad; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/effects/gradients/SkTwoPointConicalGradient_gpu.cpp b/gfx/skia/skia/src/effects/gradients/SkTwoPointConicalGradient_gpu.cpp new file mode 100644 index 000000000..a8df3b50c --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkTwoPointConicalGradient_gpu.cpp @@ -0,0 +1,1343 @@ +/* + * 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 "GrInvariantOutput.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(-SkScalarMul(invDiffLen, diff.fY), + SkScalarMul(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)); + } + + virtual ~Edge2PtConicalEffect() {} + + 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 GrGLSLCaps&, 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) { + 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 = SkScalarMul(fRadius0, fDiffRadius); + bMatrix[SkMatrix::kMScaleX] = -2 * (SkScalarMul(fCenterX1, bMatrix[SkMatrix::kMScaleX]) + + SkScalarMul(r0dr, bMatrix[SkMatrix::kMPersp0])); + bMatrix[SkMatrix::kMSkewX] = -2 * (SkScalarMul(fCenterX1, bMatrix[SkMatrix::kMSkewX]) + + SkScalarMul(r0dr, bMatrix[SkMatrix::kMPersp1])); + bMatrix[SkMatrix::kMTransX] = -2 * (SkScalarMul(fCenterX1, bMatrix[SkMatrix::kMTransX]) + + SkScalarMul(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&); + virtual ~GLSLEdge2PtConicalProcessor() { } + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrGLSLCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) 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 GrGLSLCaps& 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 + */ +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; + + SkColor colors[kMaxRandomGradientColors]; + SkScalar stopsArray[kMaxRandomGradientColors]; + SkScalar* stops = stopsArray; + SkShader::TileMode tm; + int colorCount = RandomGradientParams(d->fRandom, colors, &stops, &tm); + auto shader = SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + colors, stops, colorCount, tm); + SkMatrix viewMatrix = GrTest::TestMatrix(d->fRandom); + auto dstColorSpace = GrTest::TestColorSpace(d->fRandom); + sk_sp<GrFragmentProcessor> fp = shader->asFragmentProcessor(SkShader::AsFPArgs( + d->fContext, &viewMatrix, NULL, kNone_SkFilterQuality, dstColorSpace.get(), + SkSourceGammaTreatment::kRespect)); + GrAlwaysAssert(fp); + return fp; +} + +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.fGLSLCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); + fragBuilder->codeAppend("\t}\n"); +} + +void Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, + const GrProcessor& 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, SkScalarToFloat(radius0), + SkScalarToFloat(SkScalarMul(radius0, radius0)), SkScalarToFloat(diffRadius)); + fCachedRadius = radius0; + fCachedDiffRadius = diffRadius; + } +} + +void Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor::GenKey(const GrProcessor& processor, + const GrGLSLCaps&, 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(-SkScalarMul(invFocalX, focalTrans.fY), + SkScalarMul(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 - SkScalarMul(*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)); + } + + virtual ~FocalOutside2PtConicalEffect() { } + + 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 GrGLSLCaps&, 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); + } + + FocalOutside2PtConicalEffect(const CreateArgs& args, SkScalar focalX) + : INHERITED(args) + , fFocalX(focalX) + , fIsFlipped(static_cast<const SkTwoPointConicalGradient*>(args.fShader)->isFlippedGrad()) { + 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&); + virtual ~GLSLFocalOutside2PtConicalProcessor() { } + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrGLSLCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) 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 GrGLSLCaps& 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 + */ +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); + + SkColor colors[kMaxRandomGradientColors]; + SkScalar stopsArray[kMaxRandomGradientColors]; + SkScalar* stops = stopsArray; + SkShader::TileMode tm; + int colorCount = RandomGradientParams(d->fRandom, colors, &stops, &tm); + auto shader = SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + colors, stops, colorCount, tm); + SkMatrix viewMatrix = GrTest::TestMatrix(d->fRandom); + auto dstColorSpace = GrTest::TestColorSpace(d->fRandom); + sk_sp<GrFragmentProcessor> fp = shader->asFragmentProcessor(SkShader::AsFPArgs( + d->fContext, &viewMatrix, NULL, kNone_SkFilterQuality, dstColorSpace.get(), + SkSourceGammaTreatment::kRespect)); + GrAlwaysAssert(fp); + return fp; +} + +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.fGLSLCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); + fragBuilder->codeAppend("\t}\n"); +} + +void FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, + const GrProcessor& 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 - SkScalarMul(focal, focal); + + pdman.set2f(fParamUni, SkScalarToFloat(focal), SkScalarToFloat(oneMinus2F)); + fCachedFocal = focal; + } +} + +void FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor::GenKey( + const GrProcessor& processor, + const GrGLSLCaps&, 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)); + } + + virtual ~FocalInside2PtConicalEffect() {} + + 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 GrGLSLCaps&, 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), 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&); + virtual ~GLSLFocalInside2PtConicalProcessor() {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrGLSLCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) 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 GrGLSLCaps& 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 + */ +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); + + SkColor colors[kMaxRandomGradientColors]; + SkScalar stopsArray[kMaxRandomGradientColors]; + SkScalar* stops = stopsArray; + SkShader::TileMode tm; + int colorCount = RandomGradientParams(d->fRandom, colors, &stops, &tm); + auto shader = SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + colors, stops, colorCount, tm); + SkMatrix viewMatrix = GrTest::TestMatrix(d->fRandom); + auto dstColorSpace = GrTest::TestColorSpace(d->fRandom); + sk_sp<GrFragmentProcessor> fp = shader->asFragmentProcessor(SkShader::AsFPArgs( + d->fContext, &viewMatrix, NULL, kNone_SkFilterQuality, dstColorSpace.get(), + SkSourceGammaTreatment::kRespect)); + GrAlwaysAssert(fp); + return fp; +} + +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 + GrGLSLShaderVar 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.fGLSLCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); +} + +void FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, + const GrProcessor& 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 GrGLSLCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); +} + +////////////////////////////////////////////////////////////////////////////// +// Circle Conical Gradients +////////////////////////////////////////////////////////////////////////////// + +struct CircleConicalInfo { + SkPoint fCenterEnd; + SkScalar fA; + SkScalar fB; + SkScalar fC; +}; + +// Returns focal distance along x-axis in transformed coords +static ConicalType set_matrix_circle_conical(const SkTwoPointConicalGradient& shader, + SkMatrix* invLMatrix, CircleConicalInfo* info) { + // Inverse of the current local matrix is passed in then, + // translate and scale such that start circle is on the origin and has radius 1 + const SkPoint& centerStart = shader.getStartCenter(); + const SkPoint& centerEnd = shader.getEndCenter(); + SkScalar radiusStart = shader.getStartRadius(); + SkScalar radiusEnd = shader.getEndRadius(); + + SkMatrix matrix; + + matrix.setTranslate(-centerStart.fX, -centerStart.fY); + + SkScalar invStartRad = 1.f / radiusStart; + matrix.postScale(invStartRad, invStartRad); + + radiusEnd /= radiusStart; + + SkPoint centerEndTrans; + matrix.mapPoints(¢erEndTrans, ¢erEnd, 1); + + SkScalar A = centerEndTrans.fX * centerEndTrans.fX + centerEndTrans.fY * centerEndTrans.fY + - radiusEnd * radiusEnd + 2 * radiusEnd - 1; + + // Check to see if start circle is inside end circle with edges touching. + // If touching we return that it is of kEdge_ConicalType, and leave the matrix setting + // to the edge shader. kEdgeErrorTol = 5 * kErrorTol was picked after manual testing + // so that C = 1 / A is stable, and the linear approximation used in the Edge shader is + // still accurate. + if (SkScalarAbs(A) < kEdgeErrorTol) { + return kEdge_ConicalType; + } + + SkScalar C = 1.f / A; + SkScalar B = (radiusEnd - 1.f) * C; + + matrix.postScale(C, C); + + invLMatrix->postConcat(matrix); + + info->fCenterEnd = centerEndTrans; + info->fA = A; + info->fB = B; + info->fC = C; + + // if A ends up being negative, the start circle is contained completely inside the end cirlce + if (A < 0.f) { + return kInside_ConicalType; + } + return kOutside_ConicalType; +} + +class CircleInside2PtConicalEffect : public GrGradientEffect { +public: + class GLSLCircleInside2PtConicalProcessor; + + static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args, const CircleConicalInfo& info) { + return sk_sp<GrFragmentProcessor>( + new CircleInside2PtConicalEffect(args, info)); + } + + virtual ~CircleInside2PtConicalEffect() {} + + 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 GrGLSLCaps& 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), 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&); + virtual ~GLSLCircleInside2PtConicalProcessor() {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrGLSLCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) 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 GrGLSLCaps& 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 + */ +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); + + SkColor colors[kMaxRandomGradientColors]; + SkScalar stopsArray[kMaxRandomGradientColors]; + SkScalar* stops = stopsArray; + SkShader::TileMode tm; + int colorCount = RandomGradientParams(d->fRandom, colors, &stops, &tm); + auto shader = SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + colors, stops, colorCount, tm); + SkMatrix viewMatrix = GrTest::TestMatrix(d->fRandom); + auto dstColorSpace = GrTest::TestColorSpace(d->fRandom); + sk_sp<GrFragmentProcessor> fp = shader->asFragmentProcessor(SkShader::AsFPArgs( + d->fContext, &viewMatrix, NULL, kNone_SkFilterQuality, dstColorSpace.get(), + SkSourceGammaTreatment::kRespect)); + GrAlwaysAssert(fp); + return fp; +} + +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"); + + GrGLSLShaderVar center = uniformHandler->getUniformVariable(fCenterUni); + // params.x = A + // params.y = B + // params.z = C + GrGLSLShaderVar 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.fGLSLCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); +} + +void CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, + const GrProcessor& 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 GrGLSLCaps&, 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)); + } + + virtual ~CircleOutside2PtConicalEffect() {} + + 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 GrGLSLCaps&, 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), 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&); + virtual ~GLSLCircleOutside2PtConicalProcessor() {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrGLSLCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) 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 GrGLSLCaps& 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 + */ +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); + + SkColor colors[kMaxRandomGradientColors]; + SkScalar stopsArray[kMaxRandomGradientColors]; + SkScalar* stops = stopsArray; + SkShader::TileMode tm; + int colorCount = RandomGradientParams(d->fRandom, colors, &stops, &tm); + auto shader = SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + colors, stops, colorCount, tm); + SkMatrix viewMatrix = GrTest::TestMatrix(d->fRandom); + auto dstColorSpace = GrTest::TestColorSpace(d->fRandom); + sk_sp<GrFragmentProcessor> fp = shader->asFragmentProcessor(SkShader::AsFPArgs( + d->fContext, &viewMatrix, NULL, kNone_SkFilterQuality, dstColorSpace.get(), + SkSourceGammaTreatment::kRespect)); + GrAlwaysAssert(fp); + return fp; +} + +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"); + + GrGLSLShaderVar center = uniformHandler->getUniformVariable(fCenterUni); + // params.x = A + // params.y = B + // params.z = C + GrGLSLShaderVar 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.fGLSLCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); + fragBuilder->codeAppend("\t}\n"); +} + +void CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, + const GrProcessor& 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 GrGLSLCaps&, 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/gfx/skia/skia/src/effects/gradients/SkTwoPointConicalGradient_gpu.h b/gfx/skia/skia/src/effects/gradients/SkTwoPointConicalGradient_gpu.h new file mode 100644 index 000000000..46edb1f7d --- /dev/null +++ b/gfx/skia/skia/src/effects/gradients/SkTwoPointConicalGradient_gpu.h @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTwoPointConicalGradient_gpu_DEFINED +#define SkTwoPointConicalGradient_gpu_DEFINED + +#include "SkGradientShaderPriv.h" + +class GrProcessor; +class SkTwoPointConicalGradient; + +namespace Gr2PtConicalGradientEffect { + /** + * Creates an effect that produces a two point conical gradient based on the + * shader passed in. + */ + sk_sp<GrFragmentProcessor> Make(const GrGradientEffect::CreateArgs& args); +}; + +#endif |