From bc590c01b00ef79e1e1f30058e7a70a29419f2a9 Mon Sep 17 00:00:00 2001 From: fmalita Date: Mon, 22 Feb 2016 09:12:33 -0800 Subject: [PATCH] Initial linear gradient 4f impl GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1688543002 Review URL: https://codereview.chromium.org/1688543002 --- gm/gradients.cpp | 51 +++- gyp/effects.gypi | 4 + gyp/gmslides.gypi | 1 + src/effects/gradients/Sk4fGradientBase.cpp | 266 ++++++++++++++++++++ src/effects/gradients/Sk4fGradientBase.h | 61 +++++ src/effects/gradients/Sk4fLinearGradient.cpp | 364 +++++++++++++++++++++++++++ src/effects/gradients/Sk4fLinearGradient.h | 38 +++ src/effects/gradients/SkGradientShader.cpp | 1 + src/effects/gradients/SkGradientShaderPriv.h | 2 + src/effects/gradients/SkLinearGradient.cpp | 20 +- src/effects/gradients/SkLinearGradient.h | 7 + 11 files changed, 809 insertions(+), 6 deletions(-) create mode 100644 src/effects/gradients/Sk4fGradientBase.cpp create mode 100644 src/effects/gradients/Sk4fGradientBase.h create mode 100644 src/effects/gradients/Sk4fLinearGradient.cpp create mode 100644 src/effects/gradients/Sk4fLinearGradient.h diff --git a/gm/gradients.cpp b/gm/gradients.cpp index c1847d4..5fb3465 100644 --- a/gm/gradients.cpp +++ b/gm/gradients.cpp @@ -7,6 +7,7 @@ #include "gm.h" #include "SkGradientShader.h" +#include "SkLinearGradient.h" namespace skiagm { @@ -593,9 +594,16 @@ DEF_GM( return new LinearGradientGM(true); ) DEF_GM( return new LinearGradientGM(false); ) class LinearGradientTinyGM : public GM { +public: + LinearGradientTinyGM(uint32_t flags, const char* suffix = nullptr) + : fName("linear_gradient_tiny") + , fFlags(flags) { + fName.append(suffix); + } + protected: SkString onShortName() override { - return SkString("linear_gradient_tiny"); + return fName; } SkISize onISize() override { @@ -631,7 +639,7 @@ protected: SkAutoCanvasRestore acr(canvas, true); SkAutoTUnref gradient( SkGradientShader::CreateLinear(configs[i].pts, colors, configs[i].pos, kStopCount, - SkShader::kClamp_TileMode)); + SkShader::kClamp_TileMode, fFlags, nullptr)); canvas->translate(kRectSize * ((i % 4) * 1.5f + 0.25f), kRectSize * ((i / 4) * 1.5f + 0.25f)); @@ -642,9 +650,12 @@ protected: private: typedef GM INHERITED; -}; -DEF_GM( return new LinearGradientTinyGM(); ) + SkString fName; + uint32_t fFlags; +}; +DEF_GM( return new LinearGradientTinyGM(0); ) +DEF_GM( return new LinearGradientTinyGM(SkLinearGradient::kForce4fContext_PrivateFlag, "_4f"); ) } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -736,3 +747,35 @@ DEF_SIMPLE_GM(gradients_dup_color_stops, canvas, 704, 564) { } } } + +static void draw_many_stops(SkCanvas* canvas, uint32_t flags) { + const unsigned kStopCount = 200; + const SkPoint pts[] = { {50, 50}, {450, 465}}; + + SkColor colors[kStopCount]; + for (unsigned i = 0; i < kStopCount; i++) { + switch (i % 5) { + case 0: colors[i] = SK_ColorRED; break; + case 1: colors[i] = SK_ColorGREEN; break; + case 2: colors[i] = SK_ColorGREEN; break; + case 3: colors[i] = SK_ColorBLUE; break; + case 4: colors[i] = SK_ColorRED; break; + } + } + + SkAutoTUnref shader(SkGradientShader::CreateLinear( + pts, colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode, flags, nullptr)); + + SkPaint p; + p.setShader(shader); + + canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p); +} + +DEF_SIMPLE_GM(gradient_many_stops, canvas, 500, 500) { + draw_many_stops(canvas, 0); +} + +DEF_SIMPLE_GM(gradient_many_stops_4f, canvas, 500, 500) { + draw_many_stops(canvas, SkLinearGradient::kForce4fContext_PrivateFlag); +} diff --git a/gyp/effects.gypi b/gyp/effects.gypi index bbda38a..347d140 100644 --- a/gyp/effects.gypi +++ b/gyp/effects.gypi @@ -64,6 +64,10 @@ '<(skia_src_path)/effects/SkTileImageFilter.cpp', '<(skia_src_path)/effects/SkXfermodeImageFilter.cpp', + '<(skia_src_path)/effects/gradients/Sk4fGradientBase.cpp', + '<(skia_src_path)/effects/gradients/Sk4fGradientBase.h', + '<(skia_src_path)/effects/gradients/Sk4fLinearGradient.cpp', + '<(skia_src_path)/effects/gradients/Sk4fLinearGradient.h', '<(skia_src_path)/effects/gradients/SkClampRange.cpp', '<(skia_src_path)/effects/gradients/SkClampRange.h', '<(skia_src_path)/effects/gradients/SkGradientBitmapCache.cpp', diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi index 6ad0cca..43d354d 100644 --- a/gyp/gmslides.gypi +++ b/gyp/gmslides.gypi @@ -9,6 +9,7 @@ # include dirs needed by particular GMs '../include/client/android', '../tools/debugger', + '../src/effects/gradients', '../src/images', '../src/lazy', ], diff --git a/src/effects/gradients/Sk4fGradientBase.cpp b/src/effects/gradients/Sk4fGradientBase.cpp new file mode 100644 index 0000000..0840f60 --- /dev/null +++ b/src/effects/gradients/Sk4fGradientBase.cpp @@ -0,0 +1,266 @@ +/* + * 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" + +namespace { + +const float kInv255Float = 1.0f / 255; + +SkPMColor pack_color(SkColor c, bool premul) { + return premul + ? SkPreMultiplyColor(c) + : SkPackARGB32NoCheck(SkColorGetA(c), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c)); +} + +// true when x is in [k1,k2) +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 + +SkGradientShaderBase::GradientShaderBase4fContext:: +Interval::Interval(SkPMColor c0, SkScalar p0, + SkPMColor c1, SkScalar p1, + const Sk4f& componentScale) + : fP0(p0) + , fP1(p1) + , fZeroRamp(c0 == c1) { + SkASSERT(p0 != p1); + + const Sk4f c4f0 = SkNx_cast(Sk4b::Load(&c0)) * componentScale; + const Sk4f c4f1 = SkNx_cast(Sk4b::Load(&c1)) * componentScale; + const Sk4f dc4f = (c4f1 - c4f0) / (p1 - p0); + + c4f0.store(&fC0.fVec); + dc4f.store(&fDc.fVec); +} + +SkGradientShaderBase::GradientShaderBase4fContext:: +Interval::Interval(const Sk4f& c0, const Sk4f& dc, + SkScalar p0, SkScalar p1) + : fP0(p0) + , fP1(p1) + , fZeroRamp((dc == 0).allTrue()) { + c0.store(fC0.fVec); + dc.store(fDc.fVec); +} + +bool SkGradientShaderBase::GradientShaderBase4fContext:: +Interval::contains(SkScalar fx) const { + return in_range(fx, fP0, fP1); +} + +SkGradientShaderBase:: +GradientShaderBase4fContext::GradientShaderBase4fContext(const SkGradientShaderBase& shader, + const ContextRec& rec) + : INHERITED(shader, rec) + , fFlags(this->INHERITED::getFlags() | kSupports4f_Flag) +#ifdef SK_SUPPORT_LEGACY_GRADIENT_DITHERING + , fDither(true) +#else + , fDither(rec.fPaint->isDither()) +#endif +{ + // The main job here is to build an interval list. Intervals are 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 is sorted in increasing dst order, i.e. X(Pk) < X(Pk+1). This + // allows us to always traverse left->right when iterating over a scan line. + // It also means that the interval order matches the color stops when dx >= 0, + // and is the inverse (pos, colors, order are flipped) 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. + + const SkMatrix& inverse = this->getTotalInverse(); + fDstToPos.setConcat(shader.fPtsToUnit, inverse); + fDstToPosProc = fDstToPos.getMapXYProc(); + fDstToPosClass = static_cast(INHERITED::ComputeMatrixClass(fDstToPos)); + + if (shader.fColorsAreOpaque && this->getPaintAlpha() == SK_AlphaOPAQUE) { + fFlags |= kOpaqueAlpha_Flag; + } + + fColorsArePremul = + (shader.fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag) + || shader.fColorsAreOpaque; + + const float paintAlpha = rec.fPaint->getAlpha() * kInv255Float; + const Sk4f componentScale = fColorsArePremul + ? Sk4f(paintAlpha * kInv255Float) + : Sk4f(kInv255Float, kInv255Float, kInv255Float, paintAlpha * kInv255Float); + + SkASSERT(shader.fColorCount > 1); + SkASSERT(shader.fOrigColors); + + int direction = 1; + int first_index = 0; + int last_index = shader.fColorCount - 1; + SkScalar first_pos = 0; + SkScalar last_pos = 1; + const bool dx_is_pos = fDstToPos.getScaleX() >= 0; + if (!dx_is_pos) { + direction = -direction; + SkTSwap(first_index, last_index); + SkTSwap(first_pos, last_pos); + } + + if (shader.fTileMode == SkShader::kClamp_TileMode) { + // synthetic edge interval: -/+inf .. P0) + const SkPMColor clamp_color = pack_color(shader.fOrigColors[first_index], + fColorsArePremul); + const SkScalar clamp_pos = dx_is_pos ? SK_ScalarMin : SK_ScalarMax; + fIntervals.emplace_back(clamp_color, clamp_pos, + clamp_color, first_pos, + componentScale); + } + + int prev = first_index; + int curr = prev + direction; + SkScalar prev_pos = first_pos; + if (shader.fOrigPos) { + // explicit positions + do { + // TODO: this sanitization should be done in SkGradientShaderBase + const SkScalar curr_pos = (dx_is_pos) + ? SkTPin(shader.fOrigPos[curr], prev_pos, last_pos) + : SkTPin(shader.fOrigPos[curr], last_pos, prev_pos); + if (curr_pos != prev_pos) { + fIntervals.emplace_back( + pack_color(shader.fOrigColors[prev], fColorsArePremul), + prev_pos, + pack_color(shader.fOrigColors[curr], fColorsArePremul), + curr_pos, + componentScale); + } + prev = curr; + prev_pos = curr_pos; + curr += direction; + } while (prev != last_index); + } else { + // implicit positions + const SkScalar dt = direction * SK_Scalar1 / (shader.fColorCount - 1); + do { + const SkScalar curr_pos = prev_pos + dt; + fIntervals.emplace_back( + pack_color(shader.fOrigColors[prev], fColorsArePremul), + prev_pos, + pack_color(shader.fOrigColors[curr], fColorsArePremul), + curr_pos, + componentScale); + + prev = curr; + prev_pos = curr_pos; + curr += direction; + } while (prev != last_index); + // pin the last pos to maintain accurate [0,1] pos coverage. + fIntervals.back().fP1 = last_pos; + } + + if (shader.fTileMode == SkShader::kClamp_TileMode) { + // synthetic edge interval: Pn .. +/-inf + const SkPMColor clamp_color = + pack_color(shader.fOrigColors[last_index], fColorsArePremul); + const SkScalar clamp_pos = dx_is_pos ? SK_ScalarMax : SK_ScalarMin; + fIntervals.emplace_back(clamp_color, last_pos, + clamp_color, clamp_pos, + componentScale); + } else if (shader.fTileMode == SkShader::kMirror_TileMode) { + // synthetic flipped intervals in [1 .. 2) + for (int i = fIntervals.count() - 1; i >= 0; --i) { + const Interval& interval = fIntervals[i]; + const SkScalar p0 = interval.fP0; + const SkScalar p1 = interval.fP1; + Sk4f dc = Sk4f::Load(interval.fDc.fVec); + Sk4f c = Sk4f::Load(interval.fC0.fVec) + dc * Sk4f(p1 - p0); + fIntervals.emplace_back(c, dc * Sk4f(-1), 2 - p1, 2 - p0); + } + } + + SkASSERT(fIntervals.count() > 0); + fCachedInterval = fIntervals.begin(); +} + +const SkGradientShaderBase::GradientShaderBase4fContext::Interval* +SkGradientShaderBase:: +GradientShaderBase4fContext::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; + } +} diff --git a/src/effects/gradients/Sk4fGradientBase.h b/src/effects/gradients/Sk4fGradientBase.h new file mode 100644 index 0000000..7870bc3 --- /dev/null +++ b/src/effects/gradients/Sk4fGradientBase.h @@ -0,0 +1,61 @@ +/* + * 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 "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; } + +protected: + struct Interval { + Interval(SkPMColor c0, SkScalar p0, + SkPMColor c1, SkScalar p1, + const Sk4f& componentScale); + Interval(const Sk4f& c0, const Sk4f& dc, + SkScalar p0, SkScalar p1); + + bool isZeroRamp() const { return fZeroRamp; } + + // true when fx is in [p0,p1) + bool contains(SkScalar fx) const; + + SkPM4f fC0, fDc; + SkScalar fP0, fP1; + bool fZeroRamp; + }; + + const Interval* findInterval(SkScalar fx) const; + + 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; + + mutable const Interval* fCachedInterval; +}; + +#endif // Sk4fGradientBase_DEFINED diff --git a/src/effects/gradients/Sk4fLinearGradient.cpp b/src/effects/gradients/Sk4fLinearGradient.cpp new file mode 100644 index 0000000..57c81f6 --- /dev/null +++ b/src/effects/gradients/Sk4fLinearGradient.cpp @@ -0,0 +1,364 @@ +/* + * 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" + +namespace { + +Sk4f premul_4f(const Sk4f& c) { + const float alpha = c[SkPM4f::A]; + // FIXME: portable swizzle? + return c * Sk4f(alpha, alpha, alpha, 1); +} + +template +SkPMColor trunc_from_255(const Sk4f& c) { + SkPMColor pmc; + SkNx_cast(c).store(&pmc); + if (do_premul) { + pmc = SkPreMultiplyARGB(SkGetPackedA32(pmc), SkGetPackedR32(pmc), + SkGetPackedG32(pmc), SkGetPackedB32(pmc)); + } + return pmc; +} + +template +void fill(const Sk4f& c, DstType* dst, int n); + +template<> +void fill(const Sk4f& c, SkPM4f* dst, int n) { + while (n > 0) { + c.store(dst++); + n--; + } +} + +template<> +void fill(const Sk4f& c, SkPM4f* dst, int n) { + fill(premul_4f(c), dst, n); +} + +template<> +void fill(const Sk4f& c, SkPMColor* dst, int n) { + sk_memset32(dst, trunc_from_255(c), n); +} + +template<> +void fill(const Sk4f& c, SkPMColor* dst, int n) { + sk_memset32(dst, trunc_from_255(c), n); +} + +template +void store(const Sk4f& color, DstType* dst); + +template<> +void store(const Sk4f& c, SkPM4f* dst) { + c.store(dst); +} + +template<> +void store(const Sk4f& c, SkPM4f* dst) { + store(premul_4f(c), dst); +} + +template<> +void store(const Sk4f& c, SkPMColor* dst) { + *dst = trunc_from_255(c); +} + +template<> +void store(const Sk4f& c, SkPMColor* dst) { + *dst = trunc_from_255(c); +} + +template +void store4x(const Sk4f& c0, + const Sk4f& c1, + const Sk4f& c2, + const Sk4f& c3, + DstType* dst) { + store(c0, dst++); + store(c1, dst++); + store(c2, dst++); + store(c3, dst++); +} + +template<> +void store4x(const Sk4f& c0, + const Sk4f& c1, + const Sk4f& c2, + const Sk4f& c3, + SkPMColor* dst) { + Sk4f_ToBytes((uint8_t*)dst, c0, c1, c2, c3); +} + +template +void ramp(const Sk4f& c, const Sk4f& dc, DstType* 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) { + 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) { + store(c0, dst++); + store(c1, dst++); + c0 = c0 + dc2; + } + if (n & 1) { + store(c0, dst); + } +} + +template +SkScalar pinFx(SkScalar); + +template<> +SkScalar pinFx(SkScalar fx) { + return fx; +} + +template<> +SkScalar pinFx(SkScalar fx) { + const SkScalar f = SkScalarFraction(fx); + return f < 0 ? f + 1 : f; +} + +template<> +SkScalar pinFx(SkScalar fx) { + const SkScalar f = SkScalarMod(fx, 2.0f); + return f < 0 ? f + 2 : f; +} + +template +float dst_component_scale(); + +template<> +float dst_component_scale() { + return 1; +} + +template<> +float dst_component_scale() { + return 255; +} + +} // anonymous namespace + +SkLinearGradient:: +LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader, + const ContextRec& rec) + : INHERITED(shader, rec) {} + +void SkLinearGradient:: +LinearGradient4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) { + // TODO: plumb dithering + SkASSERT(count > 0); + if (fColorsArePremul) { + this->shadePremulSpan(x, y, dst, count); + } else { + this->shadePremulSpan(x, y, dst, count); + } +} + +void SkLinearGradient:: +LinearGradient4fContext::shadeSpan4f(int x, int y, SkPM4f dst[], int count) { + // TONOTDO: plumb dithering + SkASSERT(count > 0); + if (fColorsArePremul) { + this->shadePremulSpan(x, y, dst, count); + } else { + this->shadePremulSpan(x, y, dst, count); + } +} + +template +void SkLinearGradient:: +LinearGradient4fContext::shadePremulSpan(int x, int y, + DstType dst[], + int count) const { + const SkLinearGradient& shader = + static_cast(fShader); + switch (shader.fTileMode) { + case kClamp_TileMode: + this->shadeSpanInternal(x, y, dst, count); + break; + case kRepeat_TileMode: + this->shadeSpanInternal(x, y, dst, count); + break; + case kMirror_TileMode: + this->shadeSpanInternal(x, y, dst, count); + break; + } +} + +template +void SkLinearGradient:: +LinearGradient4fContext::shadeSpanInternal(int x, int y, + DstType dst[], + int count) const { + SkPoint pt; + fDstToPosProc(fDstToPos, + x + SK_ScalarHalf, + y + SK_ScalarHalf, + &pt); + const SkScalar fx = pinFx(pt.x()); + const SkScalar dx = fDstToPos.getScaleX(); + LinearIntervalProcessor proc(fIntervals.begin(), + fIntervals.end() - 1, + this->findInterval(fx), + fx, + dx, + SkScalarNearlyZero(dx * count)); + while (count > 0) { + // What we really want here is SkTPin(advance, 1, count) + // but that's a significant perf hit for >> stops; investigate. + const int n = SkScalarTruncToInt( + SkTMin(proc.currentAdvance() + 1, SkIntToScalar(count))); + + // The current interval advance can be +inf (e.g. when reaching + // the clamp mode end intervals) - when that happens, we expect to + // a) consume all remaining count in one swoop + // b) return a zero color gradient + SkASSERT(SkScalarIsFinite(proc.currentAdvance()) + || (n == count && proc.currentRampIsZero())); + + if (proc.currentRampIsZero()) { + fill(proc.currentColor(), + dst, n); + } else { + ramp(proc.currentColor(), + proc.currentColorGrad(), + dst, n); + } + + proc.advance(SkIntToScalar(n)); + count -= n; + dst += n; + } +} + +template +class SkLinearGradient:: +LinearGradient4fContext::LinearIntervalProcessor { +public: + LinearIntervalProcessor(const Interval* firstInterval, + const Interval* lastInterval, + const Interval* i, + SkScalar fx, + SkScalar dx, + bool is_vertical) + : fDstComponentScale(dst_component_scale()) + , fAdvX((i->fP1 - fx) / dx) + , fFirstInterval(firstInterval) + , fLastInterval(lastInterval) + , fInterval(i) + , fDx(dx) + , fIsVertical(is_vertical) + { + SkASSERT(firstInterval <= lastInterval); + SkASSERT(i->contains(fx)); + 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) { + fDc = Sk4f::Load(fInterval->fDc.fVec); + fCc = Sk4f::Load(fInterval->fC0.fVec); + fCc = fCc + fDc * Sk4f(t); + fCc = fCc * fDstComponentScale; + fDcDx = fDc * fDstComponentScale * Sk4f(fDx); + fZeroRamp = fIsVertical || fInterval->isZeroRamp(); + } + + 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; + } + + const Sk4f fDstComponentScale; // cached dst scale (PMC: 255, PM4f: 1) + + // Current interval properties. + Sk4f fDc; // local color gradient (dc/dt) + 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; +}; diff --git a/src/effects/gradients/Sk4fLinearGradient.h b/src/effects/gradients/Sk4fLinearGradient.h new file mode 100644 index 0000000..8633760 --- /dev/null +++ b/src/effects/gradients/Sk4fLinearGradient.h @@ -0,0 +1,38 @@ +/* + * 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 : 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; + +private: + using INHERITED = GradientShaderBase4fContext; + + template + class LinearIntervalProcessor; + + template + void shadePremulSpan(int x, int y, DstType[], int count) const; + + template + void shadePremulTileSpan(int x, int y, DstType[], int count) const; + + template + void shadeSpanInternal(int x, int y, DstType[], int count) const; +}; + +#endif // Sk4fLinearGradient_DEFINED diff --git a/src/effects/gradients/SkGradientShader.cpp b/src/effects/gradients/SkGradientShader.cpp index 3a4d0e5..5444c1c 100644 --- a/src/effects/gradients/SkGradientShader.cpp +++ b/src/effects/gradients/SkGradientShader.cpp @@ -5,6 +5,7 @@ * found in the LICENSE file. */ +#include "Sk4fLinearGradient.h" #include "SkGradientShaderPriv.h" #include "SkLinearGradient.h" #include "SkRadialGradient.h" diff --git a/src/effects/gradients/SkGradientShaderPriv.h b/src/effects/gradients/SkGradientShaderPriv.h index 5ab8c0b..5b1b09b 100644 --- a/src/effects/gradients/SkGradientShaderPriv.h +++ b/src/effects/gradients/SkGradientShaderPriv.h @@ -221,6 +221,8 @@ public: uint32_t getGradFlags() const { return fGradFlags; } protected: + class GradientShaderBase4fContext; + SkGradientShaderBase(SkReadBuffer& ); void flatten(SkWriteBuffer&) const override; SK_TO_STRING_OVERRIDE() diff --git a/src/effects/gradients/SkLinearGradient.cpp b/src/effects/gradients/SkLinearGradient.cpp index a1abe54..9a350b8 100644 --- a/src/effects/gradients/SkLinearGradient.cpp +++ b/src/effects/gradients/SkLinearGradient.cpp @@ -5,8 +5,12 @@ * found in the LICENSE file. */ +#include "Sk4fLinearGradient.h" #include "SkLinearGradient.h" +// define to test the 4f gradient path +// #define USE_4fGRADIENTS + static const float kInv255Float = 1.0f / 255; static inline int repeat_8bits(int x) { @@ -43,6 +47,14 @@ static SkMatrix pts_to_unit_matrix(const SkPoint pts[2]) { return matrix; } +static bool use_4f_context(uint32_t flags) { +#ifdef USE_4fGRADIENTS + return true; +#else + return SkToBool(flags & SkLinearGradient::kForce4fContext_PrivateFlag); +#endif +} + /////////////////////////////////////////////////////////////////////////////// SkLinearGradient::SkLinearGradient(const SkPoint pts[2], const Descriptor& desc) @@ -70,11 +82,15 @@ void SkLinearGradient::flatten(SkWriteBuffer& buffer) const { } size_t SkLinearGradient::contextSize() const { - return sizeof(LinearGradientContext); + return use_4f_context(fGradFlags) + ? sizeof(LinearGradient4fContext) + : sizeof(LinearGradientContext); } SkShader::Context* SkLinearGradient::onCreateContext(const ContextRec& rec, void* storage) const { - return new (storage) LinearGradientContext(*this, rec); + return use_4f_context(fGradFlags) + ? static_cast(new (storage) LinearGradient4fContext(*this, rec)) + : static_cast(new (storage) LinearGradientContext(*this, rec)); } // This swizzles SkColor into the same component order as SkPMColor, but does not actually diff --git a/src/effects/gradients/SkLinearGradient.h b/src/effects/gradients/SkLinearGradient.h index c8b91a5..c81eea6 100644 --- a/src/effects/gradients/SkLinearGradient.h +++ b/src/effects/gradients/SkLinearGradient.h @@ -26,6 +26,11 @@ struct Sk4fStorage { class SkLinearGradient : public SkGradientShaderBase { public: + enum { + // Temp flag for testing the 4f impl. + kForce4fContext_PrivateFlag = 1 << 7, + }; + SkLinearGradient(const SkPoint pts[2], const Descriptor&); size_t contextSize() const override; @@ -69,6 +74,8 @@ protected: Context* onCreateContext(const ContextRec&, void* storage) const override; private: + class LinearGradient4fContext; + friend class SkGradientShader; typedef SkGradientShaderBase INHERITED; const SkPoint fStart; -- 2.7.4