1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2018 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/gradients/GrGradientShader.h"
8*c8dee2aaSAndroid Build Coastguard Worker
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkAlphaType.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkBitmap.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColorSpace.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColorType.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkMatrix.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRect.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSamplingOptions.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkString.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTileMode.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "include/effects/SkGradientShader.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "include/effects/SkRuntimeEffect.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "include/gpu/GpuTypes.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "include/gpu/ganesh/GrBackendSurface.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "include/gpu/ganesh/GrRecordingContext.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "include/gpu/ganesh/GrTypes.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/SkColorData.h"
25*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
26*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkDebug.h"
27*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkMath.h"
28*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkOnce.h"
29*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkSpan_impl.h"
30*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTemplates.h"
31*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/gpu/ganesh/GrTypesPriv.h"
32*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkArenaAlloc.h"
33*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkMathPriv.h"
34*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkVx.h"
35*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkColorSpacePriv.h"
36*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkRasterPipeline.h"
37*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkRasterPipelineOpContexts.h"
38*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkRasterPipelineOpList.h"
39*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkRuntimeEffectPriv.h"
40*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/GrCaps.h"
41*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/GrColorInfo.h"
42*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/GrColorSpaceXform.h"
43*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/GrFPArgs.h"
44*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/GrFragmentProcessor.h"
45*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/GrRecordingContextPriv.h"
46*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/GrSamplerState.h"
47*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/GrShaderCaps.h"
48*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/SkGr.h"
49*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/effects/GrMatrixEffect.h"
50*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/effects/GrSkSLFP.h"
51*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/effects/GrTextureEffect.h"
52*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/gradients/GrGradientBitmapCache.h"
53*c8dee2aaSAndroid Build Coastguard Worker #include "src/shaders/SkShaderBase.h"
54*c8dee2aaSAndroid Build Coastguard Worker #include "src/shaders/gradients/SkGradientBaseShader.h"
55*c8dee2aaSAndroid Build Coastguard Worker
56*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
57*c8dee2aaSAndroid Build Coastguard Worker #include <array>
58*c8dee2aaSAndroid Build Coastguard Worker #include <cstddef>
59*c8dee2aaSAndroid Build Coastguard Worker #include <tuple>
60*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
61*c8dee2aaSAndroid Build Coastguard Worker
62*c8dee2aaSAndroid Build Coastguard Worker struct SkV4;
63*c8dee2aaSAndroid Build Coastguard Worker
64*c8dee2aaSAndroid Build Coastguard Worker #if defined(GPU_TEST_UTILS)
65*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkRandom.h"
66*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/GrTestUtils.h"
67*c8dee2aaSAndroid Build Coastguard Worker #endif
68*c8dee2aaSAndroid Build Coastguard Worker
69*c8dee2aaSAndroid Build Coastguard Worker using namespace skia_private;
70*c8dee2aaSAndroid Build Coastguard Worker
71*c8dee2aaSAndroid Build Coastguard Worker using Vec4 = skvx::Vec<4, float>;
72*c8dee2aaSAndroid Build Coastguard Worker
73*c8dee2aaSAndroid Build Coastguard Worker // Intervals smaller than this (that aren't hard stops) on low-precision-only devices force us to
74*c8dee2aaSAndroid Build Coastguard Worker // use the textured gradient
75*c8dee2aaSAndroid Build Coastguard Worker static const SkScalar kLowPrecisionIntervalLimit = 0.01f;
76*c8dee2aaSAndroid Build Coastguard Worker
77*c8dee2aaSAndroid Build Coastguard Worker // Each cache entry costs 1K or 2K of RAM. Each bitmap will be 1x256 at either 32bpp or 64bpp.
78*c8dee2aaSAndroid Build Coastguard Worker static const int kMaxNumCachedGradientBitmaps = 32;
79*c8dee2aaSAndroid Build Coastguard Worker static const int kGradientTextureSize = 256;
80*c8dee2aaSAndroid Build Coastguard Worker
81*c8dee2aaSAndroid Build Coastguard Worker // NOTE: signature takes raw pointers to the color/pos arrays and a count to make it easy for
82*c8dee2aaSAndroid Build Coastguard Worker // MakeColorizer to transparently take care of hard stops at the end points of the gradient.
make_textured_colorizer(const SkPMColor4f * colors,const SkScalar * positions,int count,bool colorsAreOpaque,const SkGradientShader::Interpolation & interpolation,const SkColorSpace * intermediateColorSpace,const SkColorSpace * dstColorSpace,const GrFPArgs & args)83*c8dee2aaSAndroid Build Coastguard Worker static std::unique_ptr<GrFragmentProcessor> make_textured_colorizer(
84*c8dee2aaSAndroid Build Coastguard Worker const SkPMColor4f* colors,
85*c8dee2aaSAndroid Build Coastguard Worker const SkScalar* positions,
86*c8dee2aaSAndroid Build Coastguard Worker int count,
87*c8dee2aaSAndroid Build Coastguard Worker bool colorsAreOpaque,
88*c8dee2aaSAndroid Build Coastguard Worker const SkGradientShader::Interpolation& interpolation,
89*c8dee2aaSAndroid Build Coastguard Worker const SkColorSpace* intermediateColorSpace,
90*c8dee2aaSAndroid Build Coastguard Worker const SkColorSpace* dstColorSpace,
91*c8dee2aaSAndroid Build Coastguard Worker const GrFPArgs& args) {
92*c8dee2aaSAndroid Build Coastguard Worker static GrGradientBitmapCache gCache(kMaxNumCachedGradientBitmaps, kGradientTextureSize);
93*c8dee2aaSAndroid Build Coastguard Worker
94*c8dee2aaSAndroid Build Coastguard Worker // Use 8888 or F16, depending on the destination config.
95*c8dee2aaSAndroid Build Coastguard Worker // TODO: Use 1010102 for opaque gradients, at least if destination is 1010102?
96*c8dee2aaSAndroid Build Coastguard Worker SkColorType colorType = kRGBA_8888_SkColorType;
97*c8dee2aaSAndroid Build Coastguard Worker if (GrColorTypeIsWiderThan(args.fDstColorInfo->colorType(), 8)) {
98*c8dee2aaSAndroid Build Coastguard Worker auto f16Format = args.fContext->priv().caps()->getDefaultBackendFormat(
99*c8dee2aaSAndroid Build Coastguard Worker GrColorType::kRGBA_F16, GrRenderable::kNo);
100*c8dee2aaSAndroid Build Coastguard Worker if (f16Format.isValid()) {
101*c8dee2aaSAndroid Build Coastguard Worker colorType = kRGBA_F16_SkColorType;
102*c8dee2aaSAndroid Build Coastguard Worker }
103*c8dee2aaSAndroid Build Coastguard Worker }
104*c8dee2aaSAndroid Build Coastguard Worker SkAlphaType alphaType = static_cast<bool>(interpolation.fInPremul) ? kPremul_SkAlphaType
105*c8dee2aaSAndroid Build Coastguard Worker : kUnpremul_SkAlphaType;
106*c8dee2aaSAndroid Build Coastguard Worker
107*c8dee2aaSAndroid Build Coastguard Worker SkBitmap bitmap;
108*c8dee2aaSAndroid Build Coastguard Worker gCache.getGradient(colors,
109*c8dee2aaSAndroid Build Coastguard Worker positions,
110*c8dee2aaSAndroid Build Coastguard Worker count,
111*c8dee2aaSAndroid Build Coastguard Worker colorsAreOpaque,
112*c8dee2aaSAndroid Build Coastguard Worker interpolation,
113*c8dee2aaSAndroid Build Coastguard Worker intermediateColorSpace,
114*c8dee2aaSAndroid Build Coastguard Worker dstColorSpace,
115*c8dee2aaSAndroid Build Coastguard Worker colorType,
116*c8dee2aaSAndroid Build Coastguard Worker alphaType,
117*c8dee2aaSAndroid Build Coastguard Worker &bitmap);
118*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(1 == bitmap.height() && SkIsPow2(bitmap.width()));
119*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(bitmap.isImmutable());
120*c8dee2aaSAndroid Build Coastguard Worker
121*c8dee2aaSAndroid Build Coastguard Worker auto view = std::get<0>(GrMakeCachedBitmapProxyView(
122*c8dee2aaSAndroid Build Coastguard Worker args.fContext, bitmap, /*label=*/"MakeTexturedColorizer", skgpu::Mipmapped::kNo));
123*c8dee2aaSAndroid Build Coastguard Worker if (!view) {
124*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Gradient won't draw. Could not create texture.");
125*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
126*c8dee2aaSAndroid Build Coastguard Worker }
127*c8dee2aaSAndroid Build Coastguard Worker
128*c8dee2aaSAndroid Build Coastguard Worker auto m = SkMatrix::Scale(view.width(), 1.f);
129*c8dee2aaSAndroid Build Coastguard Worker return GrTextureEffect::Make(std::move(view), alphaType, m, GrSamplerState::Filter::kLinear);
130*c8dee2aaSAndroid Build Coastguard Worker }
131*c8dee2aaSAndroid Build Coastguard Worker
make_single_interval_colorizer(const SkPMColor4f & start,const SkPMColor4f & end)132*c8dee2aaSAndroid Build Coastguard Worker static std::unique_ptr<GrFragmentProcessor> make_single_interval_colorizer(const SkPMColor4f& start,
133*c8dee2aaSAndroid Build Coastguard Worker const SkPMColor4f& end) {
134*c8dee2aaSAndroid Build Coastguard Worker static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
135*c8dee2aaSAndroid Build Coastguard Worker "uniform half4 start;"
136*c8dee2aaSAndroid Build Coastguard Worker "uniform half4 end;"
137*c8dee2aaSAndroid Build Coastguard Worker "half4 main(float2 coord) {"
138*c8dee2aaSAndroid Build Coastguard Worker // Clamping and/or wrapping was already handled by the parent shader so the output
139*c8dee2aaSAndroid Build Coastguard Worker // color is a simple lerp.
140*c8dee2aaSAndroid Build Coastguard Worker "return mix(start, end, half(coord.x));"
141*c8dee2aaSAndroid Build Coastguard Worker "}"
142*c8dee2aaSAndroid Build Coastguard Worker );
143*c8dee2aaSAndroid Build Coastguard Worker return GrSkSLFP::Make(effect, "SingleIntervalColorizer", /*inputFP=*/nullptr,
144*c8dee2aaSAndroid Build Coastguard Worker GrSkSLFP::OptFlags::kNone,
145*c8dee2aaSAndroid Build Coastguard Worker "start", start,
146*c8dee2aaSAndroid Build Coastguard Worker "end", end);
147*c8dee2aaSAndroid Build Coastguard Worker }
148*c8dee2aaSAndroid Build Coastguard Worker
make_dual_interval_colorizer(const SkPMColor4f & c0,const SkPMColor4f & c1,const SkPMColor4f & c2,const SkPMColor4f & c3,float threshold)149*c8dee2aaSAndroid Build Coastguard Worker static std::unique_ptr<GrFragmentProcessor> make_dual_interval_colorizer(const SkPMColor4f& c0,
150*c8dee2aaSAndroid Build Coastguard Worker const SkPMColor4f& c1,
151*c8dee2aaSAndroid Build Coastguard Worker const SkPMColor4f& c2,
152*c8dee2aaSAndroid Build Coastguard Worker const SkPMColor4f& c3,
153*c8dee2aaSAndroid Build Coastguard Worker float threshold) {
154*c8dee2aaSAndroid Build Coastguard Worker static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
155*c8dee2aaSAndroid Build Coastguard Worker "uniform float4 scale[2];"
156*c8dee2aaSAndroid Build Coastguard Worker "uniform float4 bias[2];"
157*c8dee2aaSAndroid Build Coastguard Worker "uniform half threshold;"
158*c8dee2aaSAndroid Build Coastguard Worker
159*c8dee2aaSAndroid Build Coastguard Worker "half4 main(float2 coord) {"
160*c8dee2aaSAndroid Build Coastguard Worker "half t = half(coord.x);"
161*c8dee2aaSAndroid Build Coastguard Worker
162*c8dee2aaSAndroid Build Coastguard Worker "float4 s, b;"
163*c8dee2aaSAndroid Build Coastguard Worker "if (t < threshold) {"
164*c8dee2aaSAndroid Build Coastguard Worker "s = scale[0];"
165*c8dee2aaSAndroid Build Coastguard Worker "b = bias[0];"
166*c8dee2aaSAndroid Build Coastguard Worker "} else {"
167*c8dee2aaSAndroid Build Coastguard Worker "s = scale[1];"
168*c8dee2aaSAndroid Build Coastguard Worker "b = bias[1];"
169*c8dee2aaSAndroid Build Coastguard Worker "}"
170*c8dee2aaSAndroid Build Coastguard Worker
171*c8dee2aaSAndroid Build Coastguard Worker "return half4(t * s + b);"
172*c8dee2aaSAndroid Build Coastguard Worker "}"
173*c8dee2aaSAndroid Build Coastguard Worker );
174*c8dee2aaSAndroid Build Coastguard Worker
175*c8dee2aaSAndroid Build Coastguard Worker // Derive scale and biases from the 4 colors and threshold
176*c8dee2aaSAndroid Build Coastguard Worker Vec4 vc0 = Vec4::Load(c0.vec());
177*c8dee2aaSAndroid Build Coastguard Worker Vec4 vc1 = Vec4::Load(c1.vec());
178*c8dee2aaSAndroid Build Coastguard Worker Vec4 vc2 = Vec4::Load(c2.vec());
179*c8dee2aaSAndroid Build Coastguard Worker Vec4 vc3 = Vec4::Load(c3.vec());
180*c8dee2aaSAndroid Build Coastguard Worker
181*c8dee2aaSAndroid Build Coastguard Worker const Vec4 scale[2] = {(vc1 - vc0) / threshold,
182*c8dee2aaSAndroid Build Coastguard Worker (vc3 - vc2) / (1 - threshold)};
183*c8dee2aaSAndroid Build Coastguard Worker const Vec4 bias[2] = {vc0,
184*c8dee2aaSAndroid Build Coastguard Worker vc2 - threshold * scale[1]};
185*c8dee2aaSAndroid Build Coastguard Worker return GrSkSLFP::Make(effect, "DualIntervalColorizer", /*inputFP=*/nullptr,
186*c8dee2aaSAndroid Build Coastguard Worker GrSkSLFP::OptFlags::kNone,
187*c8dee2aaSAndroid Build Coastguard Worker "scale", SkSpan(scale),
188*c8dee2aaSAndroid Build Coastguard Worker "bias", SkSpan(bias),
189*c8dee2aaSAndroid Build Coastguard Worker "threshold", threshold);
190*c8dee2aaSAndroid Build Coastguard Worker }
191*c8dee2aaSAndroid Build Coastguard Worker
192*c8dee2aaSAndroid Build Coastguard Worker // The "unrolled" colorizer contains hand-written nested ifs which perform a binary search.
193*c8dee2aaSAndroid Build Coastguard Worker // This works on ES2 hardware that doesn't support non-constant array indexes.
194*c8dee2aaSAndroid Build Coastguard Worker // However, to keep code size under control, we are limited to a small number of stops.
195*c8dee2aaSAndroid Build Coastguard Worker static constexpr int kMaxUnrolledColorCount = 16;
196*c8dee2aaSAndroid Build Coastguard Worker static constexpr int kMaxUnrolledIntervalCount = kMaxUnrolledColorCount / 2;
197*c8dee2aaSAndroid Build Coastguard Worker
make_unrolled_colorizer(int intervalCount,const SkPMColor4f * scale,const SkPMColor4f * bias,SkRect thresholds1_7,SkRect thresholds9_13)198*c8dee2aaSAndroid Build Coastguard Worker static std::unique_ptr<GrFragmentProcessor> make_unrolled_colorizer(int intervalCount,
199*c8dee2aaSAndroid Build Coastguard Worker const SkPMColor4f* scale,
200*c8dee2aaSAndroid Build Coastguard Worker const SkPMColor4f* bias,
201*c8dee2aaSAndroid Build Coastguard Worker SkRect thresholds1_7,
202*c8dee2aaSAndroid Build Coastguard Worker SkRect thresholds9_13) {
203*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(intervalCount >= 1 && intervalCount <= 8);
204*c8dee2aaSAndroid Build Coastguard Worker
205*c8dee2aaSAndroid Build Coastguard Worker static SkOnce once[kMaxUnrolledIntervalCount];
206*c8dee2aaSAndroid Build Coastguard Worker static const SkRuntimeEffect* effects[kMaxUnrolledIntervalCount];
207*c8dee2aaSAndroid Build Coastguard Worker
208*c8dee2aaSAndroid Build Coastguard Worker once[intervalCount - 1]([intervalCount] {
209*c8dee2aaSAndroid Build Coastguard Worker SkString sksl;
210*c8dee2aaSAndroid Build Coastguard Worker
211*c8dee2aaSAndroid Build Coastguard Worker // The 7 threshold positions that define the boundaries of the 8 intervals (excluding t = 0,
212*c8dee2aaSAndroid Build Coastguard Worker // and t = 1) are packed into two half4s instead of having up to 7 separate scalar uniforms.
213*c8dee2aaSAndroid Build Coastguard Worker // For low interval counts, the extra components are ignored in the shader, but the uniform
214*c8dee2aaSAndroid Build Coastguard Worker // simplification is worth it. It is assumed thresholds are provided in increasing value,
215*c8dee2aaSAndroid Build Coastguard Worker // mapped as:
216*c8dee2aaSAndroid Build Coastguard Worker // - thresholds1_7.x = boundary between (0,1) and (2,3) -> 1_2
217*c8dee2aaSAndroid Build Coastguard Worker // - .y = boundary between (2,3) and (4,5) -> 3_4
218*c8dee2aaSAndroid Build Coastguard Worker // - .z = boundary between (4,5) and (6,7) -> 5_6
219*c8dee2aaSAndroid Build Coastguard Worker // - .w = boundary between (6,7) and (8,9) -> 7_8
220*c8dee2aaSAndroid Build Coastguard Worker // - thresholds9_13.x = boundary between (8,9) and (10,11) -> 9_10
221*c8dee2aaSAndroid Build Coastguard Worker // - .y = boundary between (10,11) and (12,13) -> 11_12
222*c8dee2aaSAndroid Build Coastguard Worker // - .z = boundary between (12,13) and (14,15) -> 13_14
223*c8dee2aaSAndroid Build Coastguard Worker // - .w = unused
224*c8dee2aaSAndroid Build Coastguard Worker sksl.append("uniform half4 thresholds1_7, thresholds9_13;");
225*c8dee2aaSAndroid Build Coastguard Worker
226*c8dee2aaSAndroid Build Coastguard Worker // With the current hardstop detection threshold of 0.00024, the maximum scale and bias
227*c8dee2aaSAndroid Build Coastguard Worker // values will be on the order of 4k (since they divide by dt). That is well outside the
228*c8dee2aaSAndroid Build Coastguard Worker // precision capabilities of half floats, which can lead to inaccurate gradient calculations
229*c8dee2aaSAndroid Build Coastguard Worker sksl.appendf("uniform float4 scale[%d];", intervalCount);
230*c8dee2aaSAndroid Build Coastguard Worker sksl.appendf("uniform float4 bias[%d];", intervalCount);
231*c8dee2aaSAndroid Build Coastguard Worker
232*c8dee2aaSAndroid Build Coastguard Worker // Explicit binary search for the proper interval that t falls within. The interval
233*c8dee2aaSAndroid Build Coastguard Worker // count checks are constant expressions, which are then optimized to the minimal number
234*c8dee2aaSAndroid Build Coastguard Worker // of branches for the specific interval count.
235*c8dee2aaSAndroid Build Coastguard Worker sksl.appendf(
236*c8dee2aaSAndroid Build Coastguard Worker "half4 main(float2 coord) {"
237*c8dee2aaSAndroid Build Coastguard Worker "half t = half(coord.x);"
238*c8dee2aaSAndroid Build Coastguard Worker "float4 s, b;"
239*c8dee2aaSAndroid Build Coastguard Worker // thresholds1_7.w is mid point for intervals (0,7) and (8,15)
240*c8dee2aaSAndroid Build Coastguard Worker "if (%d <= 4 || t < thresholds1_7.w) {"
241*c8dee2aaSAndroid Build Coastguard Worker // thresholds1_7.y is mid point for intervals (0,3) and (4,7)
242*c8dee2aaSAndroid Build Coastguard Worker "if (%d <= 2 || t < thresholds1_7.y) {"
243*c8dee2aaSAndroid Build Coastguard Worker // thresholds1_7.x is mid point for intervals (0,1) and (2,3)
244*c8dee2aaSAndroid Build Coastguard Worker "if (%d <= 1 || t < thresholds1_7.x) {"
245*c8dee2aaSAndroid Build Coastguard Worker "%s" // s = scale[0]; b = bias[0];
246*c8dee2aaSAndroid Build Coastguard Worker "} else {"
247*c8dee2aaSAndroid Build Coastguard Worker "%s" // s = scale[1]; b = bias[1];
248*c8dee2aaSAndroid Build Coastguard Worker "}"
249*c8dee2aaSAndroid Build Coastguard Worker "} else {"
250*c8dee2aaSAndroid Build Coastguard Worker // thresholds1_7.z is mid point for intervals (4,5) and (6,7)
251*c8dee2aaSAndroid Build Coastguard Worker "if (%d <= 3 || t < thresholds1_7.z) {"
252*c8dee2aaSAndroid Build Coastguard Worker "%s" // s = scale[2]; b = bias[2];
253*c8dee2aaSAndroid Build Coastguard Worker "} else {"
254*c8dee2aaSAndroid Build Coastguard Worker "%s" // s = scale[3]; b = bias[3];
255*c8dee2aaSAndroid Build Coastguard Worker "}"
256*c8dee2aaSAndroid Build Coastguard Worker "}"
257*c8dee2aaSAndroid Build Coastguard Worker "} else {"
258*c8dee2aaSAndroid Build Coastguard Worker // thresholds9_13.y is mid point for intervals (8,11) and (12,15)
259*c8dee2aaSAndroid Build Coastguard Worker "if (%d <= 6 || t < thresholds9_13.y) {"
260*c8dee2aaSAndroid Build Coastguard Worker // thresholds9_13.x is mid point for intervals (8,9) and (10,11)
261*c8dee2aaSAndroid Build Coastguard Worker "if (%d <= 5 || t < thresholds9_13.x) {"
262*c8dee2aaSAndroid Build Coastguard Worker "%s"
263*c8dee2aaSAndroid Build Coastguard Worker "} else {"
264*c8dee2aaSAndroid Build Coastguard Worker "%s" // s = scale[5]; b = bias[5];
265*c8dee2aaSAndroid Build Coastguard Worker "}"
266*c8dee2aaSAndroid Build Coastguard Worker "} else {"
267*c8dee2aaSAndroid Build Coastguard Worker // thresholds9_13.z is mid point for intervals (12,13) and (14,15)
268*c8dee2aaSAndroid Build Coastguard Worker "if (%d <= 7 || t < thresholds9_13.z) {"
269*c8dee2aaSAndroid Build Coastguard Worker "%s" // s = scale[6]; b = bias[6];
270*c8dee2aaSAndroid Build Coastguard Worker "} else {"
271*c8dee2aaSAndroid Build Coastguard Worker "%s" // s = scale[7]; b = bias[7];
272*c8dee2aaSAndroid Build Coastguard Worker "}"
273*c8dee2aaSAndroid Build Coastguard Worker "}"
274*c8dee2aaSAndroid Build Coastguard Worker "}"
275*c8dee2aaSAndroid Build Coastguard Worker "return t * s + b;"
276*c8dee2aaSAndroid Build Coastguard Worker "}"
277*c8dee2aaSAndroid Build Coastguard Worker , intervalCount,
278*c8dee2aaSAndroid Build Coastguard Worker intervalCount,
279*c8dee2aaSAndroid Build Coastguard Worker intervalCount,
280*c8dee2aaSAndroid Build Coastguard Worker (intervalCount <= 0) ? "" : "s = scale[0]; b = bias[0];",
281*c8dee2aaSAndroid Build Coastguard Worker (intervalCount <= 1) ? "" : "s = scale[1]; b = bias[1];",
282*c8dee2aaSAndroid Build Coastguard Worker intervalCount,
283*c8dee2aaSAndroid Build Coastguard Worker (intervalCount <= 2) ? "" : "s = scale[2]; b = bias[2];",
284*c8dee2aaSAndroid Build Coastguard Worker (intervalCount <= 3) ? "" : "s = scale[3]; b = bias[3];",
285*c8dee2aaSAndroid Build Coastguard Worker intervalCount,
286*c8dee2aaSAndroid Build Coastguard Worker intervalCount,
287*c8dee2aaSAndroid Build Coastguard Worker (intervalCount <= 4) ? "" : "s = scale[4]; b = bias[4];",
288*c8dee2aaSAndroid Build Coastguard Worker (intervalCount <= 5) ? "" : "s = scale[5]; b = bias[5];",
289*c8dee2aaSAndroid Build Coastguard Worker intervalCount,
290*c8dee2aaSAndroid Build Coastguard Worker (intervalCount <= 6) ? "" : "s = scale[6]; b = bias[6];",
291*c8dee2aaSAndroid Build Coastguard Worker (intervalCount <= 7) ? "" : "s = scale[7]; b = bias[7];");
292*c8dee2aaSAndroid Build Coastguard Worker
293*c8dee2aaSAndroid Build Coastguard Worker auto result = SkRuntimeEffect::MakeForShader(std::move(sksl));
294*c8dee2aaSAndroid Build Coastguard Worker SkASSERTF(result.effect, "%s", result.errorText.c_str());
295*c8dee2aaSAndroid Build Coastguard Worker effects[intervalCount - 1] = result.effect.release();
296*c8dee2aaSAndroid Build Coastguard Worker });
297*c8dee2aaSAndroid Build Coastguard Worker
298*c8dee2aaSAndroid Build Coastguard Worker return GrSkSLFP::Make(effects[intervalCount - 1], "UnrolledBinaryColorizer",
299*c8dee2aaSAndroid Build Coastguard Worker /*inputFP=*/nullptr, GrSkSLFP::OptFlags::kNone,
300*c8dee2aaSAndroid Build Coastguard Worker "thresholds1_7", thresholds1_7,
301*c8dee2aaSAndroid Build Coastguard Worker "thresholds9_13", thresholds9_13,
302*c8dee2aaSAndroid Build Coastguard Worker "scale", SkSpan(scale, intervalCount),
303*c8dee2aaSAndroid Build Coastguard Worker "bias", SkSpan(bias, intervalCount));
304*c8dee2aaSAndroid Build Coastguard Worker }
305*c8dee2aaSAndroid Build Coastguard Worker
306*c8dee2aaSAndroid Build Coastguard Worker // The "looping" colorizer uses a real loop to binary-search the array of gradient stops.
307*c8dee2aaSAndroid Build Coastguard Worker static constexpr int kMaxLoopingColorCount = 128;
308*c8dee2aaSAndroid Build Coastguard Worker static constexpr int kMaxLoopingIntervalCount = kMaxLoopingColorCount / 2;
309*c8dee2aaSAndroid Build Coastguard Worker
make_looping_colorizer(int intervalCount,const SkPMColor4f * scale,const SkPMColor4f * bias,const SkScalar * thresholds)310*c8dee2aaSAndroid Build Coastguard Worker static std::unique_ptr<GrFragmentProcessor> make_looping_colorizer(int intervalCount,
311*c8dee2aaSAndroid Build Coastguard Worker const SkPMColor4f* scale,
312*c8dee2aaSAndroid Build Coastguard Worker const SkPMColor4f* bias,
313*c8dee2aaSAndroid Build Coastguard Worker const SkScalar* thresholds) {
314*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(intervalCount >= 1 && intervalCount <= kMaxLoopingIntervalCount);
315*c8dee2aaSAndroid Build Coastguard Worker SkASSERT((intervalCount & 3) == 0); // intervals are required to come in groups of four
316*c8dee2aaSAndroid Build Coastguard Worker int intervalChunks = intervalCount / 4;
317*c8dee2aaSAndroid Build Coastguard Worker int cacheIndex = (size_t)intervalChunks - 1;
318*c8dee2aaSAndroid Build Coastguard Worker
319*c8dee2aaSAndroid Build Coastguard Worker struct EffectCacheEntry {
320*c8dee2aaSAndroid Build Coastguard Worker SkOnce once;
321*c8dee2aaSAndroid Build Coastguard Worker const SkRuntimeEffect* effect;
322*c8dee2aaSAndroid Build Coastguard Worker };
323*c8dee2aaSAndroid Build Coastguard Worker
324*c8dee2aaSAndroid Build Coastguard Worker static EffectCacheEntry effectCache[kMaxLoopingIntervalCount / 4];
325*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(cacheIndex >= 0 && cacheIndex < (int)std::size(effectCache));
326*c8dee2aaSAndroid Build Coastguard Worker EffectCacheEntry* cacheEntry = &effectCache[cacheIndex];
327*c8dee2aaSAndroid Build Coastguard Worker
328*c8dee2aaSAndroid Build Coastguard Worker cacheEntry->once([intervalCount, intervalChunks, cacheEntry] {
329*c8dee2aaSAndroid Build Coastguard Worker SkString sksl;
330*c8dee2aaSAndroid Build Coastguard Worker
331*c8dee2aaSAndroid Build Coastguard Worker // Binary search for the interval that `t` falls within. We can precalculate the number of
332*c8dee2aaSAndroid Build Coastguard Worker // loop iterations we need, and we know `t` will always be in range, so we can just loop a
333*c8dee2aaSAndroid Build Coastguard Worker // fixed number of times and can be guaranteed to have found the proper element.
334*c8dee2aaSAndroid Build Coastguard Worker //
335*c8dee2aaSAndroid Build Coastguard Worker // Threshold values are stored in half4s to keep them compact, so the last two rounds of
336*c8dee2aaSAndroid Build Coastguard Worker // binary search are hand-unrolled to allow them to use swizzles.
337*c8dee2aaSAndroid Build Coastguard Worker //
338*c8dee2aaSAndroid Build Coastguard Worker // Note that this colorizer is also designed to handle the case of exactly 4 intervals (a
339*c8dee2aaSAndroid Build Coastguard Worker // single chunk). In this case, the binary search for-loop will optimize away entirely, as
340*c8dee2aaSAndroid Build Coastguard Worker // it can be proven to execute zero times. We also optimize away the calculation of `4 *
341*c8dee2aaSAndroid Build Coastguard Worker // chunk` near the end via an if statement, as the result will always be in chunk 0.
342*c8dee2aaSAndroid Build Coastguard Worker int loopCount = SkNextLog2(intervalChunks);
343*c8dee2aaSAndroid Build Coastguard Worker sksl.appendf(
344*c8dee2aaSAndroid Build Coastguard Worker "#version 300\n" // important space to separate token.
345*c8dee2aaSAndroid Build Coastguard Worker "uniform float4 thresholds[%d];"
346*c8dee2aaSAndroid Build Coastguard Worker "uniform float4 scale[%d];"
347*c8dee2aaSAndroid Build Coastguard Worker "uniform float4 bias[%d];"
348*c8dee2aaSAndroid Build Coastguard Worker
349*c8dee2aaSAndroid Build Coastguard Worker "half4 main(float2 coord) {"
350*c8dee2aaSAndroid Build Coastguard Worker "float t = coord.x;"
351*c8dee2aaSAndroid Build Coastguard Worker
352*c8dee2aaSAndroid Build Coastguard Worker // Choose a chunk from thresholds via binary search in a loop.
353*c8dee2aaSAndroid Build Coastguard Worker "int low = 0;"
354*c8dee2aaSAndroid Build Coastguard Worker "int high = %d;"
355*c8dee2aaSAndroid Build Coastguard Worker "int chunk = %d;"
356*c8dee2aaSAndroid Build Coastguard Worker "for (int loop = 0; loop < %d; ++loop) {"
357*c8dee2aaSAndroid Build Coastguard Worker "if (t < thresholds[chunk].w) {"
358*c8dee2aaSAndroid Build Coastguard Worker "high = chunk;"
359*c8dee2aaSAndroid Build Coastguard Worker "} else {"
360*c8dee2aaSAndroid Build Coastguard Worker "low = chunk + 1;"
361*c8dee2aaSAndroid Build Coastguard Worker "}"
362*c8dee2aaSAndroid Build Coastguard Worker "chunk = (low + high) / 2;"
363*c8dee2aaSAndroid Build Coastguard Worker "}"
364*c8dee2aaSAndroid Build Coastguard Worker
365*c8dee2aaSAndroid Build Coastguard Worker // Choose the final position via explicit 4-way binary search.
366*c8dee2aaSAndroid Build Coastguard Worker "int pos;"
367*c8dee2aaSAndroid Build Coastguard Worker "if (t < thresholds[chunk].y) {"
368*c8dee2aaSAndroid Build Coastguard Worker "pos = (t < thresholds[chunk].x) ? 0 : 1;"
369*c8dee2aaSAndroid Build Coastguard Worker "} else {"
370*c8dee2aaSAndroid Build Coastguard Worker "pos = (t < thresholds[chunk].z) ? 2 : 3;"
371*c8dee2aaSAndroid Build Coastguard Worker "}"
372*c8dee2aaSAndroid Build Coastguard Worker "if (%d > 0) {"
373*c8dee2aaSAndroid Build Coastguard Worker "pos += 4 * chunk;"
374*c8dee2aaSAndroid Build Coastguard Worker "}"
375*c8dee2aaSAndroid Build Coastguard Worker "return t * scale[pos] + bias[pos];"
376*c8dee2aaSAndroid Build Coastguard Worker "}"
377*c8dee2aaSAndroid Build Coastguard Worker , /* thresholds: */ intervalChunks,
378*c8dee2aaSAndroid Build Coastguard Worker /* scale: */ intervalCount,
379*c8dee2aaSAndroid Build Coastguard Worker /* bias: */ intervalCount,
380*c8dee2aaSAndroid Build Coastguard Worker /* high: */ intervalChunks - 1,
381*c8dee2aaSAndroid Build Coastguard Worker /* chunk: */ (intervalChunks - 1) / 2,
382*c8dee2aaSAndroid Build Coastguard Worker /* loopCount: */ loopCount,
383*c8dee2aaSAndroid Build Coastguard Worker /* if (loopCount > 0): */ loopCount);
384*c8dee2aaSAndroid Build Coastguard Worker
385*c8dee2aaSAndroid Build Coastguard Worker auto result = SkRuntimeEffect::MakeForShader(std::move(sksl));
386*c8dee2aaSAndroid Build Coastguard Worker SkASSERTF(result.effect, "%s", result.errorText.c_str());
387*c8dee2aaSAndroid Build Coastguard Worker cacheEntry->effect = result.effect.release();
388*c8dee2aaSAndroid Build Coastguard Worker });
389*c8dee2aaSAndroid Build Coastguard Worker
390*c8dee2aaSAndroid Build Coastguard Worker return GrSkSLFP::Make(cacheEntry->effect, "LoopingBinaryColorizer",
391*c8dee2aaSAndroid Build Coastguard Worker /*inputFP=*/nullptr, GrSkSLFP::OptFlags::kNone,
392*c8dee2aaSAndroid Build Coastguard Worker "thresholds", SkSpan((const SkV4*)thresholds, intervalChunks),
393*c8dee2aaSAndroid Build Coastguard Worker "scale", SkSpan(scale, intervalCount),
394*c8dee2aaSAndroid Build Coastguard Worker "bias", SkSpan(bias, intervalCount));
395*c8dee2aaSAndroid Build Coastguard Worker }
396*c8dee2aaSAndroid Build Coastguard Worker
397*c8dee2aaSAndroid Build Coastguard Worker // Converts an input array of {colors, positions} into an array of {scales, biases, thresholds}.
398*c8dee2aaSAndroid Build Coastguard Worker // The length of the result array may differ from the input due to hard-stops or empty intervals.
build_intervals(int inputLength,const SkPMColor4f * inColors,const SkScalar * inPositions,int outputLength,SkPMColor4f * outScales,SkPMColor4f * outBiases,SkScalar * outThresholds)399*c8dee2aaSAndroid Build Coastguard Worker int build_intervals(int inputLength,
400*c8dee2aaSAndroid Build Coastguard Worker const SkPMColor4f* inColors,
401*c8dee2aaSAndroid Build Coastguard Worker const SkScalar* inPositions,
402*c8dee2aaSAndroid Build Coastguard Worker int outputLength,
403*c8dee2aaSAndroid Build Coastguard Worker SkPMColor4f* outScales,
404*c8dee2aaSAndroid Build Coastguard Worker SkPMColor4f* outBiases,
405*c8dee2aaSAndroid Build Coastguard Worker SkScalar* outThresholds) {
406*c8dee2aaSAndroid Build Coastguard Worker // Depending on how the positions resolve into hard stops or regular stops, the number of
407*c8dee2aaSAndroid Build Coastguard Worker // intervals specified by the number of colors/positions can change. For instance, a plain
408*c8dee2aaSAndroid Build Coastguard Worker // 3 color gradient is two intervals, but a 4 color gradient with a hard stop is also
409*c8dee2aaSAndroid Build Coastguard Worker // two intervals. At the most extreme end, an 8 interval gradient made entirely of hard
410*c8dee2aaSAndroid Build Coastguard Worker // stops has 16 colors.
411*c8dee2aaSAndroid Build Coastguard Worker int intervalCount = 0;
412*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < inputLength - 1; i++) {
413*c8dee2aaSAndroid Build Coastguard Worker if (intervalCount >= outputLength) {
414*c8dee2aaSAndroid Build Coastguard Worker // Already reached our output limit, and haven't run out of color stops. This gradient
415*c8dee2aaSAndroid Build Coastguard Worker // cannot be represented without more intervals.
416*c8dee2aaSAndroid Build Coastguard Worker return 0;
417*c8dee2aaSAndroid Build Coastguard Worker }
418*c8dee2aaSAndroid Build Coastguard Worker
419*c8dee2aaSAndroid Build Coastguard Worker SkScalar t0 = inPositions[i];
420*c8dee2aaSAndroid Build Coastguard Worker SkScalar t1 = inPositions[i + 1];
421*c8dee2aaSAndroid Build Coastguard Worker SkScalar dt = t1 - t0;
422*c8dee2aaSAndroid Build Coastguard Worker // If the interval is empty, skip to the next interval. This will automatically create
423*c8dee2aaSAndroid Build Coastguard Worker // distinct hard stop intervals as needed. It also protects against malformed gradients
424*c8dee2aaSAndroid Build Coastguard Worker // that have repeated hard stops at the very beginning that are effectively unreachable.
425*c8dee2aaSAndroid Build Coastguard Worker if (SkScalarNearlyZero(dt)) {
426*c8dee2aaSAndroid Build Coastguard Worker continue;
427*c8dee2aaSAndroid Build Coastguard Worker }
428*c8dee2aaSAndroid Build Coastguard Worker
429*c8dee2aaSAndroid Build Coastguard Worker Vec4 c0 = Vec4::Load(inColors[i].vec());
430*c8dee2aaSAndroid Build Coastguard Worker Vec4 c1 = Vec4::Load(inColors[i + 1].vec());
431*c8dee2aaSAndroid Build Coastguard Worker Vec4 scale = (c1 - c0) / dt;
432*c8dee2aaSAndroid Build Coastguard Worker Vec4 bias = c0 - t0 * scale;
433*c8dee2aaSAndroid Build Coastguard Worker
434*c8dee2aaSAndroid Build Coastguard Worker scale.store(outScales + intervalCount);
435*c8dee2aaSAndroid Build Coastguard Worker bias.store(outBiases + intervalCount);
436*c8dee2aaSAndroid Build Coastguard Worker outThresholds[intervalCount] = t1;
437*c8dee2aaSAndroid Build Coastguard Worker intervalCount++;
438*c8dee2aaSAndroid Build Coastguard Worker }
439*c8dee2aaSAndroid Build Coastguard Worker return intervalCount;
440*c8dee2aaSAndroid Build Coastguard Worker }
441*c8dee2aaSAndroid Build Coastguard Worker
make_unrolled_binary_colorizer(const SkPMColor4f * colors,const SkScalar * positions,int count)442*c8dee2aaSAndroid Build Coastguard Worker static std::unique_ptr<GrFragmentProcessor> make_unrolled_binary_colorizer(
443*c8dee2aaSAndroid Build Coastguard Worker const SkPMColor4f* colors, const SkScalar* positions, int count) {
444*c8dee2aaSAndroid Build Coastguard Worker if (count > kMaxUnrolledColorCount) {
445*c8dee2aaSAndroid Build Coastguard Worker // Definitely cannot represent this gradient configuration
446*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
447*c8dee2aaSAndroid Build Coastguard Worker }
448*c8dee2aaSAndroid Build Coastguard Worker
449*c8dee2aaSAndroid Build Coastguard Worker SkPMColor4f scales[kMaxUnrolledIntervalCount];
450*c8dee2aaSAndroid Build Coastguard Worker SkPMColor4f biases[kMaxUnrolledIntervalCount];
451*c8dee2aaSAndroid Build Coastguard Worker SkScalar thresholds[kMaxUnrolledIntervalCount] = {};
452*c8dee2aaSAndroid Build Coastguard Worker int intervalCount = build_intervals(count, colors, positions,
453*c8dee2aaSAndroid Build Coastguard Worker kMaxUnrolledIntervalCount, scales, biases, thresholds);
454*c8dee2aaSAndroid Build Coastguard Worker if (intervalCount <= 0) {
455*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
456*c8dee2aaSAndroid Build Coastguard Worker }
457*c8dee2aaSAndroid Build Coastguard Worker
458*c8dee2aaSAndroid Build Coastguard Worker SkRect thresholds1_7 = {thresholds[0], thresholds[1], thresholds[2], thresholds[3]},
459*c8dee2aaSAndroid Build Coastguard Worker thresholds9_13 = {thresholds[4], thresholds[5], thresholds[6], 0.0};
460*c8dee2aaSAndroid Build Coastguard Worker
461*c8dee2aaSAndroid Build Coastguard Worker return make_unrolled_colorizer(intervalCount, scales, biases, thresholds1_7, thresholds9_13);
462*c8dee2aaSAndroid Build Coastguard Worker }
463*c8dee2aaSAndroid Build Coastguard Worker
make_looping_binary_colorizer(const SkPMColor4f * colors,const SkScalar * positions,int count)464*c8dee2aaSAndroid Build Coastguard Worker static std::unique_ptr<GrFragmentProcessor> make_looping_binary_colorizer(const SkPMColor4f* colors,
465*c8dee2aaSAndroid Build Coastguard Worker const SkScalar* positions,
466*c8dee2aaSAndroid Build Coastguard Worker int count) {
467*c8dee2aaSAndroid Build Coastguard Worker if (count > kMaxLoopingColorCount) {
468*c8dee2aaSAndroid Build Coastguard Worker // Definitely cannot represent this gradient configuration
469*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
470*c8dee2aaSAndroid Build Coastguard Worker }
471*c8dee2aaSAndroid Build Coastguard Worker
472*c8dee2aaSAndroid Build Coastguard Worker SkPMColor4f scales[kMaxLoopingIntervalCount];
473*c8dee2aaSAndroid Build Coastguard Worker SkPMColor4f biases[kMaxLoopingIntervalCount];
474*c8dee2aaSAndroid Build Coastguard Worker SkScalar thresholds[kMaxLoopingIntervalCount] = {};
475*c8dee2aaSAndroid Build Coastguard Worker int intervalCount = build_intervals(count, colors, positions,
476*c8dee2aaSAndroid Build Coastguard Worker kMaxLoopingIntervalCount, scales, biases, thresholds);
477*c8dee2aaSAndroid Build Coastguard Worker if (intervalCount <= 0) {
478*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
479*c8dee2aaSAndroid Build Coastguard Worker }
480*c8dee2aaSAndroid Build Coastguard Worker
481*c8dee2aaSAndroid Build Coastguard Worker // We round up the number of intervals to the next power of two. This reduces the number of
482*c8dee2aaSAndroid Build Coastguard Worker // unique shaders and doesn't require any additional GPU processing power, but this does waste a
483*c8dee2aaSAndroid Build Coastguard Worker // handful of uniforms.
484*c8dee2aaSAndroid Build Coastguard Worker int roundedSize = std::max(4, SkNextPow2(intervalCount));
485*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(roundedSize <= kMaxLoopingIntervalCount);
486*c8dee2aaSAndroid Build Coastguard Worker for (; intervalCount < roundedSize; ++intervalCount) {
487*c8dee2aaSAndroid Build Coastguard Worker thresholds[intervalCount] = thresholds[intervalCount - 1];
488*c8dee2aaSAndroid Build Coastguard Worker scales[intervalCount] = scales[intervalCount - 1];
489*c8dee2aaSAndroid Build Coastguard Worker biases[intervalCount] = biases[intervalCount - 1];
490*c8dee2aaSAndroid Build Coastguard Worker }
491*c8dee2aaSAndroid Build Coastguard Worker
492*c8dee2aaSAndroid Build Coastguard Worker return make_looping_colorizer(intervalCount, scales, biases, thresholds);
493*c8dee2aaSAndroid Build Coastguard Worker }
494*c8dee2aaSAndroid Build Coastguard Worker
495*c8dee2aaSAndroid Build Coastguard Worker // Analyze the shader's color stops and positions and chooses an appropriate colorizer to represent
496*c8dee2aaSAndroid Build Coastguard Worker // the gradient.
make_uniform_colorizer(const SkPMColor4f * colors,const SkScalar * positions,int count,bool premul,const GrFPArgs & args)497*c8dee2aaSAndroid Build Coastguard Worker static std::unique_ptr<GrFragmentProcessor> make_uniform_colorizer(const SkPMColor4f* colors,
498*c8dee2aaSAndroid Build Coastguard Worker const SkScalar* positions,
499*c8dee2aaSAndroid Build Coastguard Worker int count,
500*c8dee2aaSAndroid Build Coastguard Worker bool premul,
501*c8dee2aaSAndroid Build Coastguard Worker const GrFPArgs& args) {
502*c8dee2aaSAndroid Build Coastguard Worker // If there are hard stops at the beginning or end, the first and/or last color should be
503*c8dee2aaSAndroid Build Coastguard Worker // ignored by the colorizer since it should only be used in a clamped border color. By detecting
504*c8dee2aaSAndroid Build Coastguard Worker // and removing these stops at the beginning, it makes optimizing the remaining color stops
505*c8dee2aaSAndroid Build Coastguard Worker // simpler.
506*c8dee2aaSAndroid Build Coastguard Worker
507*c8dee2aaSAndroid Build Coastguard Worker // SkGradientBaseShader guarantees that pos[0] == 0 by adding a default value.
508*c8dee2aaSAndroid Build Coastguard Worker bool bottomHardStop = SkScalarNearlyEqual(positions[0], positions[1]);
509*c8dee2aaSAndroid Build Coastguard Worker // The same is true for pos[end] == 1
510*c8dee2aaSAndroid Build Coastguard Worker bool topHardStop = SkScalarNearlyEqual(positions[count - 2], positions[count - 1]);
511*c8dee2aaSAndroid Build Coastguard Worker
512*c8dee2aaSAndroid Build Coastguard Worker if (bottomHardStop) {
513*c8dee2aaSAndroid Build Coastguard Worker colors++;
514*c8dee2aaSAndroid Build Coastguard Worker positions++;
515*c8dee2aaSAndroid Build Coastguard Worker count--;
516*c8dee2aaSAndroid Build Coastguard Worker }
517*c8dee2aaSAndroid Build Coastguard Worker if (topHardStop) {
518*c8dee2aaSAndroid Build Coastguard Worker count--;
519*c8dee2aaSAndroid Build Coastguard Worker }
520*c8dee2aaSAndroid Build Coastguard Worker
521*c8dee2aaSAndroid Build Coastguard Worker // Two remaining colors means a single interval from 0 to 1
522*c8dee2aaSAndroid Build Coastguard Worker // (but it may have originally been a 3 or 4 color gradient with 1-2 hard stops at the ends)
523*c8dee2aaSAndroid Build Coastguard Worker if (count == 2) {
524*c8dee2aaSAndroid Build Coastguard Worker return make_single_interval_colorizer(colors[0], colors[1]);
525*c8dee2aaSAndroid Build Coastguard Worker }
526*c8dee2aaSAndroid Build Coastguard Worker
527*c8dee2aaSAndroid Build Coastguard Worker const GrShaderCaps* caps = args.fContext->priv().caps()->shaderCaps();
528*c8dee2aaSAndroid Build Coastguard Worker auto intervalsExceedPrecisionLimit = [&]() -> bool {
529*c8dee2aaSAndroid Build Coastguard Worker // The remaining analytic colorizers use scale*t+bias, and the scale/bias values can become
530*c8dee2aaSAndroid Build Coastguard Worker // quite large when thresholds are close (but still outside the hardstop limit). If float
531*c8dee2aaSAndroid Build Coastguard Worker // isn't 32-bit, output can be incorrect if the thresholds are too close together. However,
532*c8dee2aaSAndroid Build Coastguard Worker // the analytic shaders are higher quality, so they can be used with lower precision
533*c8dee2aaSAndroid Build Coastguard Worker // hardware when the thresholds are not ill-conditioned.
534*c8dee2aaSAndroid Build Coastguard Worker if (!caps->fFloatIs32Bits) {
535*c8dee2aaSAndroid Build Coastguard Worker // Could run into problems. Check if thresholds are close together (with a limit of .01,
536*c8dee2aaSAndroid Build Coastguard Worker // so that scales will be less than 100, which leaves 4 decimals of precision on
537*c8dee2aaSAndroid Build Coastguard Worker // 16-bit).
538*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < count - 1; i++) {
539*c8dee2aaSAndroid Build Coastguard Worker SkScalar dt = SkScalarAbs(positions[i] - positions[i + 1]);
540*c8dee2aaSAndroid Build Coastguard Worker if (dt <= kLowPrecisionIntervalLimit && dt > SK_ScalarNearlyZero) {
541*c8dee2aaSAndroid Build Coastguard Worker return true;
542*c8dee2aaSAndroid Build Coastguard Worker }
543*c8dee2aaSAndroid Build Coastguard Worker }
544*c8dee2aaSAndroid Build Coastguard Worker }
545*c8dee2aaSAndroid Build Coastguard Worker return false;
546*c8dee2aaSAndroid Build Coastguard Worker };
547*c8dee2aaSAndroid Build Coastguard Worker
548*c8dee2aaSAndroid Build Coastguard Worker auto makeDualIntervalColorizer = [&]() -> std::unique_ptr<GrFragmentProcessor> {
549*c8dee2aaSAndroid Build Coastguard Worker // The dual-interval colorizer uses the same principles as the binary-search colorizer, but
550*c8dee2aaSAndroid Build Coastguard Worker // is limited to exactly 2 intervals.
551*c8dee2aaSAndroid Build Coastguard Worker if (count == 3) {
552*c8dee2aaSAndroid Build Coastguard Worker // Must be a dual interval gradient, where the middle point is at 1 and the
553*c8dee2aaSAndroid Build Coastguard Worker // two intervals share the middle color stop.
554*c8dee2aaSAndroid Build Coastguard Worker return make_dual_interval_colorizer(colors[0], colors[1],
555*c8dee2aaSAndroid Build Coastguard Worker colors[1], colors[2],
556*c8dee2aaSAndroid Build Coastguard Worker positions[1]);
557*c8dee2aaSAndroid Build Coastguard Worker }
558*c8dee2aaSAndroid Build Coastguard Worker if (count == 4 && SkScalarNearlyEqual(positions[1], positions[2])) {
559*c8dee2aaSAndroid Build Coastguard Worker // Two separate intervals that join at the same threshold position
560*c8dee2aaSAndroid Build Coastguard Worker return make_dual_interval_colorizer(colors[0], colors[1],
561*c8dee2aaSAndroid Build Coastguard Worker colors[2], colors[3],
562*c8dee2aaSAndroid Build Coastguard Worker positions[1]);
563*c8dee2aaSAndroid Build Coastguard Worker }
564*c8dee2aaSAndroid Build Coastguard Worker // The gradient can't be represented in only two intervals.
565*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
566*c8dee2aaSAndroid Build Coastguard Worker };
567*c8dee2aaSAndroid Build Coastguard Worker
568*c8dee2aaSAndroid Build Coastguard Worker int binaryColorizerLimit = caps->fNonconstantArrayIndexSupport ? kMaxLoopingColorCount
569*c8dee2aaSAndroid Build Coastguard Worker : kMaxUnrolledColorCount;
570*c8dee2aaSAndroid Build Coastguard Worker if ((count <= binaryColorizerLimit) && !intervalsExceedPrecisionLimit()) {
571*c8dee2aaSAndroid Build Coastguard Worker // The dual-interval colorizer uses the same principles as the binary-search colorizer, but
572*c8dee2aaSAndroid Build Coastguard Worker // is limited to exactly 2 intervals.
573*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GrFragmentProcessor> colorizer = makeDualIntervalColorizer();
574*c8dee2aaSAndroid Build Coastguard Worker if (colorizer) {
575*c8dee2aaSAndroid Build Coastguard Worker return colorizer;
576*c8dee2aaSAndroid Build Coastguard Worker }
577*c8dee2aaSAndroid Build Coastguard Worker // Attempt to create an analytic colorizer that uses a binary-search loop.
578*c8dee2aaSAndroid Build Coastguard Worker colorizer = caps->fNonconstantArrayIndexSupport
579*c8dee2aaSAndroid Build Coastguard Worker ? make_looping_binary_colorizer(colors, positions, count)
580*c8dee2aaSAndroid Build Coastguard Worker : make_unrolled_binary_colorizer(colors, positions, count);
581*c8dee2aaSAndroid Build Coastguard Worker if (colorizer) {
582*c8dee2aaSAndroid Build Coastguard Worker return colorizer;
583*c8dee2aaSAndroid Build Coastguard Worker }
584*c8dee2aaSAndroid Build Coastguard Worker }
585*c8dee2aaSAndroid Build Coastguard Worker
586*c8dee2aaSAndroid Build Coastguard Worker // This gradient is too complex for our uniform colorizers. The calling code will fall back to
587*c8dee2aaSAndroid Build Coastguard Worker // creating a textured colorizer, instead.
588*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
589*c8dee2aaSAndroid Build Coastguard Worker }
590*c8dee2aaSAndroid Build Coastguard Worker
591*c8dee2aaSAndroid Build Coastguard Worker // This top-level effect implements clamping on the layout coordinate and requires specifying the
592*c8dee2aaSAndroid Build Coastguard Worker // border colors that are used when outside the clamped boundary. Gradients with the
593*c8dee2aaSAndroid Build Coastguard Worker // SkTileMode::kClamp should use the colors at their first and last stop (after adding default stops
594*c8dee2aaSAndroid Build Coastguard Worker // for t=0,t=1) as the border color. This will automatically replicate the edge color, even when
595*c8dee2aaSAndroid Build Coastguard Worker // there is a hard stop.
596*c8dee2aaSAndroid Build Coastguard Worker //
597*c8dee2aaSAndroid Build Coastguard Worker // The SkTileMode::kDecal can be produced by specifying transparent black as the border colors,
598*c8dee2aaSAndroid Build Coastguard Worker // regardless of the gradient's stop colors.
make_clamped_gradient(std::unique_ptr<GrFragmentProcessor> colorizer,std::unique_ptr<GrFragmentProcessor> gradLayout,SkPMColor4f leftBorderColor,SkPMColor4f rightBorderColor,bool colorsAreOpaque)599*c8dee2aaSAndroid Build Coastguard Worker static std::unique_ptr<GrFragmentProcessor> make_clamped_gradient(
600*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GrFragmentProcessor> colorizer,
601*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GrFragmentProcessor> gradLayout,
602*c8dee2aaSAndroid Build Coastguard Worker SkPMColor4f leftBorderColor,
603*c8dee2aaSAndroid Build Coastguard Worker SkPMColor4f rightBorderColor,
604*c8dee2aaSAndroid Build Coastguard Worker bool colorsAreOpaque) {
605*c8dee2aaSAndroid Build Coastguard Worker static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
606*c8dee2aaSAndroid Build Coastguard Worker "uniform shader colorizer;"
607*c8dee2aaSAndroid Build Coastguard Worker "uniform shader gradLayout;"
608*c8dee2aaSAndroid Build Coastguard Worker
609*c8dee2aaSAndroid Build Coastguard Worker "uniform half4 leftBorderColor;" // t < 0.0
610*c8dee2aaSAndroid Build Coastguard Worker "uniform half4 rightBorderColor;" // t > 1.0
611*c8dee2aaSAndroid Build Coastguard Worker
612*c8dee2aaSAndroid Build Coastguard Worker "uniform int layoutPreservesOpacity;" // specialized
613*c8dee2aaSAndroid Build Coastguard Worker
614*c8dee2aaSAndroid Build Coastguard Worker "half4 main(float2 coord) {"
615*c8dee2aaSAndroid Build Coastguard Worker "half4 t = gradLayout.eval(coord);"
616*c8dee2aaSAndroid Build Coastguard Worker "half4 outColor;"
617*c8dee2aaSAndroid Build Coastguard Worker
618*c8dee2aaSAndroid Build Coastguard Worker // If t.x is below 0, use the left border color without invoking the child processor.
619*c8dee2aaSAndroid Build Coastguard Worker // If any t.x is above 1, use the right border color. Otherwise, t is in the [0, 1]
620*c8dee2aaSAndroid Build Coastguard Worker // range assumed by the colorizer FP, so delegate to the child processor.
621*c8dee2aaSAndroid Build Coastguard Worker "if (!bool(layoutPreservesOpacity) && t.y < 0) {"
622*c8dee2aaSAndroid Build Coastguard Worker // layout has rejected this fragment (rely on sksl to remove this branch if the
623*c8dee2aaSAndroid Build Coastguard Worker // layout FP preserves opacity is false)
624*c8dee2aaSAndroid Build Coastguard Worker "outColor = half4(0);"
625*c8dee2aaSAndroid Build Coastguard Worker "} else if (t.x < 0) {"
626*c8dee2aaSAndroid Build Coastguard Worker "outColor = leftBorderColor;"
627*c8dee2aaSAndroid Build Coastguard Worker "} else if (t.x > 1.0) {"
628*c8dee2aaSAndroid Build Coastguard Worker "outColor = rightBorderColor;"
629*c8dee2aaSAndroid Build Coastguard Worker "} else {"
630*c8dee2aaSAndroid Build Coastguard Worker // Always sample from (x, 0), discarding y, since the layout FP can use y as a
631*c8dee2aaSAndroid Build Coastguard Worker // side-channel.
632*c8dee2aaSAndroid Build Coastguard Worker "outColor = colorizer.eval(t.x0);"
633*c8dee2aaSAndroid Build Coastguard Worker "}"
634*c8dee2aaSAndroid Build Coastguard Worker "return outColor;"
635*c8dee2aaSAndroid Build Coastguard Worker "}"
636*c8dee2aaSAndroid Build Coastguard Worker );
637*c8dee2aaSAndroid Build Coastguard Worker
638*c8dee2aaSAndroid Build Coastguard Worker // If the layout does not preserve opacity, remove the opaque optimization,
639*c8dee2aaSAndroid Build Coastguard Worker // but otherwise respect the provided color opacity state (which should take
640*c8dee2aaSAndroid Build Coastguard Worker // into account the opacity of the border colors).
641*c8dee2aaSAndroid Build Coastguard Worker bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
642*c8dee2aaSAndroid Build Coastguard Worker GrSkSLFP::OptFlags optFlags = GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
643*c8dee2aaSAndroid Build Coastguard Worker if (colorsAreOpaque && layoutPreservesOpacity) {
644*c8dee2aaSAndroid Build Coastguard Worker optFlags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
645*c8dee2aaSAndroid Build Coastguard Worker }
646*c8dee2aaSAndroid Build Coastguard Worker
647*c8dee2aaSAndroid Build Coastguard Worker return GrSkSLFP::Make(effect, "ClampedGradient", /*inputFP=*/nullptr, optFlags,
648*c8dee2aaSAndroid Build Coastguard Worker "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
649*c8dee2aaSAndroid Build Coastguard Worker "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
650*c8dee2aaSAndroid Build Coastguard Worker "leftBorderColor", leftBorderColor,
651*c8dee2aaSAndroid Build Coastguard Worker "rightBorderColor", rightBorderColor,
652*c8dee2aaSAndroid Build Coastguard Worker "layoutPreservesOpacity",
653*c8dee2aaSAndroid Build Coastguard Worker GrSkSLFP::Specialize<int>(layoutPreservesOpacity));
654*c8dee2aaSAndroid Build Coastguard Worker }
655*c8dee2aaSAndroid Build Coastguard Worker
make_tiled_gradient(const GrFPArgs & args,std::unique_ptr<GrFragmentProcessor> colorizer,std::unique_ptr<GrFragmentProcessor> gradLayout,bool mirror,bool colorsAreOpaque)656*c8dee2aaSAndroid Build Coastguard Worker static std::unique_ptr<GrFragmentProcessor> make_tiled_gradient(
657*c8dee2aaSAndroid Build Coastguard Worker const GrFPArgs& args,
658*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GrFragmentProcessor> colorizer,
659*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GrFragmentProcessor> gradLayout,
660*c8dee2aaSAndroid Build Coastguard Worker bool mirror,
661*c8dee2aaSAndroid Build Coastguard Worker bool colorsAreOpaque) {
662*c8dee2aaSAndroid Build Coastguard Worker static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
663*c8dee2aaSAndroid Build Coastguard Worker "uniform shader colorizer;"
664*c8dee2aaSAndroid Build Coastguard Worker "uniform shader gradLayout;"
665*c8dee2aaSAndroid Build Coastguard Worker
666*c8dee2aaSAndroid Build Coastguard Worker "uniform int mirror;" // specialized
667*c8dee2aaSAndroid Build Coastguard Worker "uniform int layoutPreservesOpacity;" // specialized
668*c8dee2aaSAndroid Build Coastguard Worker "uniform int useFloorAbsWorkaround;" // specialized
669*c8dee2aaSAndroid Build Coastguard Worker
670*c8dee2aaSAndroid Build Coastguard Worker "half4 main(float2 coord) {"
671*c8dee2aaSAndroid Build Coastguard Worker "float4 t = gradLayout.eval(coord);"
672*c8dee2aaSAndroid Build Coastguard Worker
673*c8dee2aaSAndroid Build Coastguard Worker "if (!bool(layoutPreservesOpacity) && t.y < 0) {"
674*c8dee2aaSAndroid Build Coastguard Worker // layout has rejected this fragment (rely on sksl to remove this branch if the
675*c8dee2aaSAndroid Build Coastguard Worker // layout FP preserves opacity is false)
676*c8dee2aaSAndroid Build Coastguard Worker "return half4(0);"
677*c8dee2aaSAndroid Build Coastguard Worker "} else {"
678*c8dee2aaSAndroid Build Coastguard Worker "if (bool(mirror)) {"
679*c8dee2aaSAndroid Build Coastguard Worker "float t_1 = t.x - 1;"
680*c8dee2aaSAndroid Build Coastguard Worker "float tiled_t = t_1 - 2 * floor(t_1 * 0.5) - 1;"
681*c8dee2aaSAndroid Build Coastguard Worker "if (bool(useFloorAbsWorkaround)) {"
682*c8dee2aaSAndroid Build Coastguard Worker // At this point the expected value of tiled_t should between -1 and 1, so
683*c8dee2aaSAndroid Build Coastguard Worker // this clamp has no effect other than to break up the floor and abs calls
684*c8dee2aaSAndroid Build Coastguard Worker // and make sure the compiler doesn't merge them back together.
685*c8dee2aaSAndroid Build Coastguard Worker "tiled_t = clamp(tiled_t, -1, 1);"
686*c8dee2aaSAndroid Build Coastguard Worker "}"
687*c8dee2aaSAndroid Build Coastguard Worker "t.x = abs(tiled_t);"
688*c8dee2aaSAndroid Build Coastguard Worker "} else {"
689*c8dee2aaSAndroid Build Coastguard Worker // Simple repeat mode
690*c8dee2aaSAndroid Build Coastguard Worker "t.x = fract(t.x);"
691*c8dee2aaSAndroid Build Coastguard Worker "}"
692*c8dee2aaSAndroid Build Coastguard Worker
693*c8dee2aaSAndroid Build Coastguard Worker // Always sample from (x, 0), discarding y, since the layout FP can use y as a
694*c8dee2aaSAndroid Build Coastguard Worker // side-channel.
695*c8dee2aaSAndroid Build Coastguard Worker "half4 outColor = colorizer.eval(t.x0);"
696*c8dee2aaSAndroid Build Coastguard Worker "return outColor;"
697*c8dee2aaSAndroid Build Coastguard Worker "}"
698*c8dee2aaSAndroid Build Coastguard Worker "}"
699*c8dee2aaSAndroid Build Coastguard Worker );
700*c8dee2aaSAndroid Build Coastguard Worker
701*c8dee2aaSAndroid Build Coastguard Worker // If the layout does not preserve opacity, remove the opaque optimization,
702*c8dee2aaSAndroid Build Coastguard Worker // but otherwise respect the provided color opacity state (which should take
703*c8dee2aaSAndroid Build Coastguard Worker // into account the opacity of the border colors).
704*c8dee2aaSAndroid Build Coastguard Worker bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
705*c8dee2aaSAndroid Build Coastguard Worker GrSkSLFP::OptFlags optFlags = GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
706*c8dee2aaSAndroid Build Coastguard Worker if (colorsAreOpaque && layoutPreservesOpacity) {
707*c8dee2aaSAndroid Build Coastguard Worker optFlags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
708*c8dee2aaSAndroid Build Coastguard Worker }
709*c8dee2aaSAndroid Build Coastguard Worker const bool useFloorAbsWorkaround =
710*c8dee2aaSAndroid Build Coastguard Worker args.fContext->priv().caps()->shaderCaps()->fMustDoOpBetweenFloorAndAbs;
711*c8dee2aaSAndroid Build Coastguard Worker
712*c8dee2aaSAndroid Build Coastguard Worker return GrSkSLFP::Make(effect, "TiledGradient", /*inputFP=*/nullptr, optFlags,
713*c8dee2aaSAndroid Build Coastguard Worker "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
714*c8dee2aaSAndroid Build Coastguard Worker "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
715*c8dee2aaSAndroid Build Coastguard Worker "mirror", GrSkSLFP::Specialize<int>(mirror),
716*c8dee2aaSAndroid Build Coastguard Worker "layoutPreservesOpacity",
717*c8dee2aaSAndroid Build Coastguard Worker GrSkSLFP::Specialize<int>(layoutPreservesOpacity),
718*c8dee2aaSAndroid Build Coastguard Worker "useFloorAbsWorkaround",
719*c8dee2aaSAndroid Build Coastguard Worker GrSkSLFP::Specialize<int>(useFloorAbsWorkaround));
720*c8dee2aaSAndroid Build Coastguard Worker }
721*c8dee2aaSAndroid Build Coastguard Worker
make_interpolated_to_dst(std::unique_ptr<GrFragmentProcessor> gradient,const SkGradientShader::Interpolation & interpolation,SkColorSpace * intermediateColorSpace,const GrColorInfo & dstInfo,bool allOpaque)722*c8dee2aaSAndroid Build Coastguard Worker static std::unique_ptr<GrFragmentProcessor> make_interpolated_to_dst(
723*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GrFragmentProcessor> gradient,
724*c8dee2aaSAndroid Build Coastguard Worker const SkGradientShader::Interpolation& interpolation,
725*c8dee2aaSAndroid Build Coastguard Worker SkColorSpace* intermediateColorSpace,
726*c8dee2aaSAndroid Build Coastguard Worker const GrColorInfo& dstInfo,
727*c8dee2aaSAndroid Build Coastguard Worker bool allOpaque) {
728*c8dee2aaSAndroid Build Coastguard Worker using ColorSpace = SkGradientShader::Interpolation::ColorSpace;
729*c8dee2aaSAndroid Build Coastguard Worker
730*c8dee2aaSAndroid Build Coastguard Worker // If these values change, you will need to edit sksl_shared
731*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<int>(ColorSpace::kDestination) == 0);
732*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<int>(ColorSpace::kSRGBLinear) == 1);
733*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<int>(ColorSpace::kLab) == 2);
734*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<int>(ColorSpace::kOKLab) == 3);
735*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<int>(ColorSpace::kOKLabGamutMap) == 4);
736*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<int>(ColorSpace::kLCH) == 5);
737*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<int>(ColorSpace::kOKLCH) == 6);
738*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<int>(ColorSpace::kOKLCHGamutMap) == 7);
739*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<int>(ColorSpace::kSRGB) == 8);
740*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<int>(ColorSpace::kHSL) == 9);
741*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<int>(ColorSpace::kHWB) == 10);
742*c8dee2aaSAndroid Build Coastguard Worker
743*c8dee2aaSAndroid Build Coastguard Worker static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
744*c8dee2aaSAndroid Build Coastguard Worker "uniform int colorSpace;" // specialized
745*c8dee2aaSAndroid Build Coastguard Worker "uniform int do_unpremul;" // specialized
746*c8dee2aaSAndroid Build Coastguard Worker
747*c8dee2aaSAndroid Build Coastguard Worker "half4 main(half4 color) {"
748*c8dee2aaSAndroid Build Coastguard Worker "return $interpolated_to_rgb_unpremul(color, colorSpace, do_unpremul);"
749*c8dee2aaSAndroid Build Coastguard Worker "}"
750*c8dee2aaSAndroid Build Coastguard Worker );
751*c8dee2aaSAndroid Build Coastguard Worker
752*c8dee2aaSAndroid Build Coastguard Worker // Are we interpreting premul colors? We use this later to decide if we need to inject a final
753*c8dee2aaSAndroid Build Coastguard Worker // premultiplication step.
754*c8dee2aaSAndroid Build Coastguard Worker bool inputPremul = static_cast<bool>(interpolation.fInPremul);
755*c8dee2aaSAndroid Build Coastguard Worker
756*c8dee2aaSAndroid Build Coastguard Worker switch (interpolation.fColorSpace) {
757*c8dee2aaSAndroid Build Coastguard Worker case ColorSpace::kLab:
758*c8dee2aaSAndroid Build Coastguard Worker case ColorSpace::kOKLab:
759*c8dee2aaSAndroid Build Coastguard Worker case ColorSpace::kOKLabGamutMap:
760*c8dee2aaSAndroid Build Coastguard Worker case ColorSpace::kLCH:
761*c8dee2aaSAndroid Build Coastguard Worker case ColorSpace::kOKLCH:
762*c8dee2aaSAndroid Build Coastguard Worker case ColorSpace::kOKLCHGamutMap:
763*c8dee2aaSAndroid Build Coastguard Worker case ColorSpace::kHSL:
764*c8dee2aaSAndroid Build Coastguard Worker case ColorSpace::kHWB:
765*c8dee2aaSAndroid Build Coastguard Worker // In these exotic spaces, unpremul the colors if necessary (no need to do this if
766*c8dee2aaSAndroid Build Coastguard Worker // they're all opaque), and then convert them to the intermediate SkColorSpace
767*c8dee2aaSAndroid Build Coastguard Worker gradient = GrSkSLFP::Make(effect, "GradientCS", std::move(gradient),
768*c8dee2aaSAndroid Build Coastguard Worker GrSkSLFP::OptFlags::kAll,
769*c8dee2aaSAndroid Build Coastguard Worker "colorSpace", GrSkSLFP::Specialize<int>(
770*c8dee2aaSAndroid Build Coastguard Worker static_cast<int>(interpolation.fColorSpace)),
771*c8dee2aaSAndroid Build Coastguard Worker "do_unpremul", GrSkSLFP::Specialize<int>(
772*c8dee2aaSAndroid Build Coastguard Worker inputPremul && !allOpaque));
773*c8dee2aaSAndroid Build Coastguard Worker // We just forced the colors back to unpremul. Remember that for below
774*c8dee2aaSAndroid Build Coastguard Worker inputPremul = false;
775*c8dee2aaSAndroid Build Coastguard Worker break;
776*c8dee2aaSAndroid Build Coastguard Worker default:
777*c8dee2aaSAndroid Build Coastguard Worker break;
778*c8dee2aaSAndroid Build Coastguard Worker }
779*c8dee2aaSAndroid Build Coastguard Worker
780*c8dee2aaSAndroid Build Coastguard Worker // Now transform from intermediate to destination color space. There are two tricky things here:
781*c8dee2aaSAndroid Build Coastguard Worker // 1) Normally, we'd pass dstInfo to the transform effect. However, if someone is rendering to
782*c8dee2aaSAndroid Build Coastguard Worker // a non-color managed surface (nullptr dst color space), and they chose to interpolate in
783*c8dee2aaSAndroid Build Coastguard Worker // any of the exotic spaces, that transform would do nothing, and leave the colors in
784*c8dee2aaSAndroid Build Coastguard Worker // whatever intermediate space we chose. That could even be something like XYZ, which will
785*c8dee2aaSAndroid Build Coastguard Worker // produce nonsense. So, in this particular case, we break Skia's rules, and treat a null
786*c8dee2aaSAndroid Build Coastguard Worker // destination as sRGB.
787*c8dee2aaSAndroid Build Coastguard Worker SkColorSpace* dstColorSpace = dstInfo.colorSpace() ? dstInfo.colorSpace() : sk_srgb_singleton();
788*c8dee2aaSAndroid Build Coastguard Worker
789*c8dee2aaSAndroid Build Coastguard Worker // 2) Alpha type: We already tweaked our idea of "inputPremul" above -- if we interpolated in a
790*c8dee2aaSAndroid Build Coastguard Worker // non-RGB space, then we had to unpremul the colors to get proper conversion back to RGB.
791*c8dee2aaSAndroid Build Coastguard Worker // Our final goal is to emit premul colors, but under certain conditions we don't need to do
792*c8dee2aaSAndroid Build Coastguard Worker // anything to achieve that: i.e. its interpolating already premul colors (inputPremul) or
793*c8dee2aaSAndroid Build Coastguard Worker // all the colors have a = 1, in which case premul is a no op. Note that this allOpaque check
794*c8dee2aaSAndroid Build Coastguard Worker // is more permissive than SkGradientBaseShader's isOpaque(), since we can optimize away the
795*c8dee2aaSAndroid Build Coastguard Worker // make-premul op for two point conical gradients (which report false for isOpaque).
796*c8dee2aaSAndroid Build Coastguard Worker SkAlphaType intermediateAlphaType = inputPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
797*c8dee2aaSAndroid Build Coastguard Worker SkAlphaType dstAlphaType = kPremul_SkAlphaType;
798*c8dee2aaSAndroid Build Coastguard Worker
799*c8dee2aaSAndroid Build Coastguard Worker // If all the colors were opaque, then we don't need to do any premultiplication. We describe
800*c8dee2aaSAndroid Build Coastguard Worker // all the colors as *unpremul*, though. That will eliminate any extra unpremul/premul pair
801*c8dee2aaSAndroid Build Coastguard Worker // that would be injected if we have to do a color-space conversion here.
802*c8dee2aaSAndroid Build Coastguard Worker if (allOpaque) {
803*c8dee2aaSAndroid Build Coastguard Worker intermediateAlphaType = dstAlphaType = kUnpremul_SkAlphaType;
804*c8dee2aaSAndroid Build Coastguard Worker }
805*c8dee2aaSAndroid Build Coastguard Worker
806*c8dee2aaSAndroid Build Coastguard Worker return GrColorSpaceXformEffect::Make(std::move(gradient),
807*c8dee2aaSAndroid Build Coastguard Worker intermediateColorSpace, intermediateAlphaType,
808*c8dee2aaSAndroid Build Coastguard Worker dstColorSpace, dstAlphaType);
809*c8dee2aaSAndroid Build Coastguard Worker }
810*c8dee2aaSAndroid Build Coastguard Worker
811*c8dee2aaSAndroid Build Coastguard Worker namespace GrGradientShader {
812*c8dee2aaSAndroid Build Coastguard Worker
813*c8dee2aaSAndroid Build Coastguard Worker /**
814*c8dee2aaSAndroid Build Coastguard Worker * Produces an FP that muls its input coords by the inverse of the pending matrix and then
815*c8dee2aaSAndroid Build Coastguard Worker * samples the passed FP with those coordinates. 'postInv' is an additional matrix to
816*c8dee2aaSAndroid Build Coastguard Worker * post-apply to the inverted pending matrix. If the pending matrix is not invertible the
817*c8dee2aaSAndroid Build Coastguard Worker * GrFPResult's bool will be false and the passed FP will be returned to the caller in the
818*c8dee2aaSAndroid Build Coastguard Worker * GrFPResult.
819*c8dee2aaSAndroid Build Coastguard Worker */
apply_matrix(std::unique_ptr<GrFragmentProcessor> fp,const SkShaders::MatrixRec & rec,const SkMatrix & postInv)820*c8dee2aaSAndroid Build Coastguard Worker static GrFPResult apply_matrix(std::unique_ptr<GrFragmentProcessor> fp,
821*c8dee2aaSAndroid Build Coastguard Worker const SkShaders::MatrixRec& rec,
822*c8dee2aaSAndroid Build Coastguard Worker const SkMatrix& postInv) {
823*c8dee2aaSAndroid Build Coastguard Worker auto [total, ok] = rec.applyForFragmentProcessor(postInv);
824*c8dee2aaSAndroid Build Coastguard Worker if (!ok) {
825*c8dee2aaSAndroid Build Coastguard Worker return {false, std::move(fp)};
826*c8dee2aaSAndroid Build Coastguard Worker }
827*c8dee2aaSAndroid Build Coastguard Worker // GrMatrixEffect returns 'fp' if total worked out to identity.
828*c8dee2aaSAndroid Build Coastguard Worker return {true, GrMatrixEffect::Make(total, std::move(fp))};
829*c8dee2aaSAndroid Build Coastguard Worker }
830*c8dee2aaSAndroid Build Coastguard Worker
831*c8dee2aaSAndroid Build Coastguard Worker // Combines the colorizer and layout with an appropriately configured top-level effect based on the
832*c8dee2aaSAndroid Build Coastguard Worker // gradient's tile mode
MakeGradientFP(const SkGradientBaseShader & shader,const GrFPArgs & args,const SkShaders::MatrixRec & mRec,std::unique_ptr<GrFragmentProcessor> layout,const SkMatrix * overrideMatrix)833*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GrFragmentProcessor> MakeGradientFP(const SkGradientBaseShader& shader,
834*c8dee2aaSAndroid Build Coastguard Worker const GrFPArgs& args,
835*c8dee2aaSAndroid Build Coastguard Worker const SkShaders::MatrixRec& mRec,
836*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GrFragmentProcessor> layout,
837*c8dee2aaSAndroid Build Coastguard Worker const SkMatrix* overrideMatrix) {
838*c8dee2aaSAndroid Build Coastguard Worker // No shader is possible if a layout couldn't be created, e.g. a layout-specific Make() returned
839*c8dee2aaSAndroid Build Coastguard Worker // null.
840*c8dee2aaSAndroid Build Coastguard Worker if (layout == nullptr) {
841*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
842*c8dee2aaSAndroid Build Coastguard Worker }
843*c8dee2aaSAndroid Build Coastguard Worker
844*c8dee2aaSAndroid Build Coastguard Worker // Some two-point conical gradients use a custom matrix here. Otherwise, use
845*c8dee2aaSAndroid Build Coastguard Worker // SkGradientBaseShader's matrix;
846*c8dee2aaSAndroid Build Coastguard Worker if (!overrideMatrix) {
847*c8dee2aaSAndroid Build Coastguard Worker overrideMatrix = &shader.getGradientMatrix();
848*c8dee2aaSAndroid Build Coastguard Worker }
849*c8dee2aaSAndroid Build Coastguard Worker bool success;
850*c8dee2aaSAndroid Build Coastguard Worker std::tie(success, layout) = apply_matrix(std::move(layout), mRec, *overrideMatrix);
851*c8dee2aaSAndroid Build Coastguard Worker if (!success) {
852*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
853*c8dee2aaSAndroid Build Coastguard Worker }
854*c8dee2aaSAndroid Build Coastguard Worker
855*c8dee2aaSAndroid Build Coastguard Worker // Convert all colors into destination space and into SkPMColor4fs, and handle
856*c8dee2aaSAndroid Build Coastguard Worker // premul issues depending on the interpolation mode.
857*c8dee2aaSAndroid Build Coastguard Worker //
858*c8dee2aaSAndroid Build Coastguard Worker // SkGradientShader stores positions implicitly when they are evenly spaced, but the getPos()
859*c8dee2aaSAndroid Build Coastguard Worker // implementation performs a branch for every position index. Since the shader conversion
860*c8dee2aaSAndroid Build Coastguard Worker // requires lots of position tests, instruct the xformer to calculate all of the positions up
861*c8dee2aaSAndroid Build Coastguard Worker // front if needed.
862*c8dee2aaSAndroid Build Coastguard Worker SkColor4fXformer xformedColors(
863*c8dee2aaSAndroid Build Coastguard Worker &shader, args.fDstColorInfo->colorSpace(), /*forceExplicitPositions=*/true);
864*c8dee2aaSAndroid Build Coastguard Worker const SkPMColor4f* colors = xformedColors.fColors.begin();
865*c8dee2aaSAndroid Build Coastguard Worker const SkScalar* positions = xformedColors.fPositions;
866*c8dee2aaSAndroid Build Coastguard Worker const int colorCount = xformedColors.fColors.size();
867*c8dee2aaSAndroid Build Coastguard Worker
868*c8dee2aaSAndroid Build Coastguard Worker bool allOpaque = true;
869*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < colorCount; i++) {
870*c8dee2aaSAndroid Build Coastguard Worker if (allOpaque && !SkScalarNearlyEqual(colors[i].fA, 1.0)) {
871*c8dee2aaSAndroid Build Coastguard Worker allOpaque = false;
872*c8dee2aaSAndroid Build Coastguard Worker }
873*c8dee2aaSAndroid Build Coastguard Worker }
874*c8dee2aaSAndroid Build Coastguard Worker
875*c8dee2aaSAndroid Build Coastguard Worker // All gradients are colorized the same way, regardless of layout
876*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GrFragmentProcessor> colorizer = make_uniform_colorizer(
877*c8dee2aaSAndroid Build Coastguard Worker colors, positions, colorCount, shader.interpolateInPremul(), args);
878*c8dee2aaSAndroid Build Coastguard Worker
879*c8dee2aaSAndroid Build Coastguard Worker if (colorizer) {
880*c8dee2aaSAndroid Build Coastguard Worker // If we made a uniform colorizer, wrap it in a conversion from interpolated space to
881*c8dee2aaSAndroid Build Coastguard Worker // destination. This also applies any final premultiplication.
882*c8dee2aaSAndroid Build Coastguard Worker colorizer = make_interpolated_to_dst(std::move(colorizer),
883*c8dee2aaSAndroid Build Coastguard Worker shader.fInterpolation,
884*c8dee2aaSAndroid Build Coastguard Worker xformedColors.fIntermediateColorSpace.get(),
885*c8dee2aaSAndroid Build Coastguard Worker *args.fDstColorInfo,
886*c8dee2aaSAndroid Build Coastguard Worker allOpaque);
887*c8dee2aaSAndroid Build Coastguard Worker } else {
888*c8dee2aaSAndroid Build Coastguard Worker // If we failed to make a uniform colorizer, we need to rasterize the gradient to a
889*c8dee2aaSAndroid Build Coastguard Worker // texture (which can handle arbitrarily complex gradients). This method directly encodes
890*c8dee2aaSAndroid Build Coastguard Worker // the result of the interpolated-to-dst into the texture, so we skip the wrapper FP above.
891*c8dee2aaSAndroid Build Coastguard Worker //
892*c8dee2aaSAndroid Build Coastguard Worker // Also, note that the texture technique has limited sampling resolution, and always blurs
893*c8dee2aaSAndroid Build Coastguard Worker // hard-stops.)
894*c8dee2aaSAndroid Build Coastguard Worker colorizer = make_textured_colorizer(colors,
895*c8dee2aaSAndroid Build Coastguard Worker positions,
896*c8dee2aaSAndroid Build Coastguard Worker colorCount,
897*c8dee2aaSAndroid Build Coastguard Worker allOpaque,
898*c8dee2aaSAndroid Build Coastguard Worker shader.fInterpolation,
899*c8dee2aaSAndroid Build Coastguard Worker xformedColors.fIntermediateColorSpace.get(),
900*c8dee2aaSAndroid Build Coastguard Worker args.fDstColorInfo->colorSpace(),
901*c8dee2aaSAndroid Build Coastguard Worker args);
902*c8dee2aaSAndroid Build Coastguard Worker }
903*c8dee2aaSAndroid Build Coastguard Worker
904*c8dee2aaSAndroid Build Coastguard Worker if (colorizer == nullptr) {
905*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
906*c8dee2aaSAndroid Build Coastguard Worker }
907*c8dee2aaSAndroid Build Coastguard Worker
908*c8dee2aaSAndroid Build Coastguard Worker // If interpolation space is different than destination, wrap the colorizer in a conversion.
909*c8dee2aaSAndroid Build Coastguard Worker // This also handles any final premultiplication, etc.
910*c8dee2aaSAndroid Build Coastguard Worker
911*c8dee2aaSAndroid Build Coastguard Worker // All tile modes are supported (unless something was added to SkShader)
912*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GrFragmentProcessor> gradient;
913*c8dee2aaSAndroid Build Coastguard Worker switch(shader.getTileMode()) {
914*c8dee2aaSAndroid Build Coastguard Worker case SkTileMode::kRepeat:
915*c8dee2aaSAndroid Build Coastguard Worker gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
916*c8dee2aaSAndroid Build Coastguard Worker /* mirror */ false, allOpaque);
917*c8dee2aaSAndroid Build Coastguard Worker break;
918*c8dee2aaSAndroid Build Coastguard Worker case SkTileMode::kMirror:
919*c8dee2aaSAndroid Build Coastguard Worker gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
920*c8dee2aaSAndroid Build Coastguard Worker /* mirror */ true, allOpaque);
921*c8dee2aaSAndroid Build Coastguard Worker break;
922*c8dee2aaSAndroid Build Coastguard Worker case SkTileMode::kClamp: {
923*c8dee2aaSAndroid Build Coastguard Worker // For the clamped mode, the border colors are the first and last colors, corresponding
924*c8dee2aaSAndroid Build Coastguard Worker // to t=0 and t=1, because SkGradientBaseShader enforces that by adding color stops as
925*c8dee2aaSAndroid Build Coastguard Worker // appropriate. If there is a hard stop, this grabs the expected outer colors for the
926*c8dee2aaSAndroid Build Coastguard Worker // border.
927*c8dee2aaSAndroid Build Coastguard Worker
928*c8dee2aaSAndroid Build Coastguard Worker // However, we need to finish converting to destination color space. (These are still
929*c8dee2aaSAndroid Build Coastguard Worker // in the interpolated color space).
930*c8dee2aaSAndroid Build Coastguard Worker SkPMColor4f borderColors[2] = { colors[0], colors[colorCount - 1] };
931*c8dee2aaSAndroid Build Coastguard Worker SkArenaAlloc alloc(/*firstHeapAllocation=*/0);
932*c8dee2aaSAndroid Build Coastguard Worker SkRasterPipeline p(&alloc);
933*c8dee2aaSAndroid Build Coastguard Worker SkRasterPipeline_MemoryCtx ctx = { borderColors, 0 };
934*c8dee2aaSAndroid Build Coastguard Worker
935*c8dee2aaSAndroid Build Coastguard Worker p.append(SkRasterPipelineOp::load_f32, &ctx);
936*c8dee2aaSAndroid Build Coastguard Worker SkGradientBaseShader::AppendInterpolatedToDstStages(
937*c8dee2aaSAndroid Build Coastguard Worker &p,
938*c8dee2aaSAndroid Build Coastguard Worker &alloc,
939*c8dee2aaSAndroid Build Coastguard Worker allOpaque,
940*c8dee2aaSAndroid Build Coastguard Worker shader.fInterpolation,
941*c8dee2aaSAndroid Build Coastguard Worker xformedColors.fIntermediateColorSpace.get(),
942*c8dee2aaSAndroid Build Coastguard Worker args.fDstColorInfo->colorSpace());
943*c8dee2aaSAndroid Build Coastguard Worker p.append(SkRasterPipelineOp::store_f32, &ctx);
944*c8dee2aaSAndroid Build Coastguard Worker p.run(0, 0, 2, 1);
945*c8dee2aaSAndroid Build Coastguard Worker
946*c8dee2aaSAndroid Build Coastguard Worker gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
947*c8dee2aaSAndroid Build Coastguard Worker borderColors[0], borderColors[1], allOpaque);
948*c8dee2aaSAndroid Build Coastguard Worker break;
949*c8dee2aaSAndroid Build Coastguard Worker }
950*c8dee2aaSAndroid Build Coastguard Worker case SkTileMode::kDecal:
951*c8dee2aaSAndroid Build Coastguard Worker // Even if the gradient colors are opaque, the decal borders are transparent so
952*c8dee2aaSAndroid Build Coastguard Worker // disable that optimization
953*c8dee2aaSAndroid Build Coastguard Worker gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
954*c8dee2aaSAndroid Build Coastguard Worker SK_PMColor4fTRANSPARENT, SK_PMColor4fTRANSPARENT,
955*c8dee2aaSAndroid Build Coastguard Worker /* colorsAreOpaque */ false);
956*c8dee2aaSAndroid Build Coastguard Worker break;
957*c8dee2aaSAndroid Build Coastguard Worker }
958*c8dee2aaSAndroid Build Coastguard Worker
959*c8dee2aaSAndroid Build Coastguard Worker return gradient;
960*c8dee2aaSAndroid Build Coastguard Worker }
961*c8dee2aaSAndroid Build Coastguard Worker
MakeLinear(const SkLinearGradient & shader,const GrFPArgs & args,const SkShaders::MatrixRec & mRec)962*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GrFragmentProcessor> MakeLinear(const SkLinearGradient& shader,
963*c8dee2aaSAndroid Build Coastguard Worker const GrFPArgs& args,
964*c8dee2aaSAndroid Build Coastguard Worker const SkShaders::MatrixRec& mRec) {
965*c8dee2aaSAndroid Build Coastguard Worker // We add a tiny delta to t. When gradient stops are set up so that a hard stop in a vertically
966*c8dee2aaSAndroid Build Coastguard Worker // or horizontally oriented gradient falls exactly at a column or row of pixel centers we can
967*c8dee2aaSAndroid Build Coastguard Worker // get slightly different interpolated t values along the column/row. By adding the delta
968*c8dee2aaSAndroid Build Coastguard Worker // we will consistently get the color to the "right" of the stop. Of course if the hard stop
969*c8dee2aaSAndroid Build Coastguard Worker // falls at X.5 - delta then we still could get inconsistent results, but that is much less
970*c8dee2aaSAndroid Build Coastguard Worker // likely. crbug.com/938592
971*c8dee2aaSAndroid Build Coastguard Worker // If/when we add filtering of the gradient this can be removed.
972*c8dee2aaSAndroid Build Coastguard Worker static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
973*c8dee2aaSAndroid Build Coastguard Worker "half4 main(float2 coord) {"
974*c8dee2aaSAndroid Build Coastguard Worker "return half4(half(coord.x) + 0.00001, 1, 0, 0);" // y = 1 for always valid
975*c8dee2aaSAndroid Build Coastguard Worker "}"
976*c8dee2aaSAndroid Build Coastguard Worker );
977*c8dee2aaSAndroid Build Coastguard Worker // The linear gradient never rejects a pixel so it doesn't change opacity
978*c8dee2aaSAndroid Build Coastguard Worker auto fp = GrSkSLFP::Make(effect, "LinearLayout", /*inputFP=*/nullptr,
979*c8dee2aaSAndroid Build Coastguard Worker GrSkSLFP::OptFlags::kPreservesOpaqueInput);
980*c8dee2aaSAndroid Build Coastguard Worker return MakeGradientFP(shader, args, mRec, std::move(fp));
981*c8dee2aaSAndroid Build Coastguard Worker }
982*c8dee2aaSAndroid Build Coastguard Worker
983*c8dee2aaSAndroid Build Coastguard Worker #if defined(GPU_TEST_UTILS)
RandomParams(SkRandom * random)984*c8dee2aaSAndroid Build Coastguard Worker RandomParams::RandomParams(SkRandom* random) {
985*c8dee2aaSAndroid Build Coastguard Worker // Set color count to min of 2 so that we don't trigger the const color optimization and make
986*c8dee2aaSAndroid Build Coastguard Worker // a non-gradient processor.
987*c8dee2aaSAndroid Build Coastguard Worker fColorCount = random->nextRangeU(2, kMaxRandomGradientColors);
988*c8dee2aaSAndroid Build Coastguard Worker fUseColors4f = random->nextBool();
989*c8dee2aaSAndroid Build Coastguard Worker
990*c8dee2aaSAndroid Build Coastguard Worker // if one color, omit stops, otherwise randomly decide whether or not to
991*c8dee2aaSAndroid Build Coastguard Worker if (fColorCount == 1 || (fColorCount >= 2 && random->nextBool())) {
992*c8dee2aaSAndroid Build Coastguard Worker fStops = nullptr;
993*c8dee2aaSAndroid Build Coastguard Worker } else {
994*c8dee2aaSAndroid Build Coastguard Worker fStops = fStopStorage;
995*c8dee2aaSAndroid Build Coastguard Worker }
996*c8dee2aaSAndroid Build Coastguard Worker
997*c8dee2aaSAndroid Build Coastguard Worker // if using SkColor4f, attach a random (possibly null) color space (with linear gamma)
998*c8dee2aaSAndroid Build Coastguard Worker if (fUseColors4f) {
999*c8dee2aaSAndroid Build Coastguard Worker fColorSpace = GrTest::TestColorSpace(random);
1000*c8dee2aaSAndroid Build Coastguard Worker }
1001*c8dee2aaSAndroid Build Coastguard Worker
1002*c8dee2aaSAndroid Build Coastguard Worker SkScalar stop = 0.f;
1003*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < fColorCount; ++i) {
1004*c8dee2aaSAndroid Build Coastguard Worker if (fUseColors4f) {
1005*c8dee2aaSAndroid Build Coastguard Worker fColors4f[i].fR = random->nextUScalar1();
1006*c8dee2aaSAndroid Build Coastguard Worker fColors4f[i].fG = random->nextUScalar1();
1007*c8dee2aaSAndroid Build Coastguard Worker fColors4f[i].fB = random->nextUScalar1();
1008*c8dee2aaSAndroid Build Coastguard Worker fColors4f[i].fA = random->nextUScalar1();
1009*c8dee2aaSAndroid Build Coastguard Worker } else {
1010*c8dee2aaSAndroid Build Coastguard Worker fColors[i] = random->nextU();
1011*c8dee2aaSAndroid Build Coastguard Worker }
1012*c8dee2aaSAndroid Build Coastguard Worker if (fStops) {
1013*c8dee2aaSAndroid Build Coastguard Worker fStops[i] = stop;
1014*c8dee2aaSAndroid Build Coastguard Worker stop = i < fColorCount - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f;
1015*c8dee2aaSAndroid Build Coastguard Worker }
1016*c8dee2aaSAndroid Build Coastguard Worker }
1017*c8dee2aaSAndroid Build Coastguard Worker fTileMode = static_cast<SkTileMode>(random->nextULessThan(kSkTileModeCount));
1018*c8dee2aaSAndroid Build Coastguard Worker }
1019*c8dee2aaSAndroid Build Coastguard Worker #endif
1020*c8dee2aaSAndroid Build Coastguard Worker
1021*c8dee2aaSAndroid Build Coastguard Worker } // namespace GrGradientShader
1022