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