xref: /aosp_15_r20/external/skia/src/gpu/ganesh/gradients/GrGradientShader.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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