Coverage Report

Created: 2021-08-22 09:07

/src/skia/src/gpu/gradients/GrGradientShader.cpp
Line
Count
Source (jump to first uncovered line)
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/gradients/GrGradientShader.h"
9
10
#include "src/gpu/gradients/GrGradientBitmapCache.h"
11
12
#include "include/gpu/GrRecordingContext.h"
13
#include "src/core/SkRuntimeEffectPriv.h"
14
#include "src/gpu/GrCaps.h"
15
#include "src/gpu/GrColor.h"
16
#include "src/gpu/GrColorInfo.h"
17
#include "src/gpu/GrRecordingContextPriv.h"
18
#include "src/gpu/SkGr.h"
19
#include "src/gpu/effects/GrMatrixEffect.h"
20
#include "src/gpu/effects/GrSkSLFP.h"
21
#include "src/gpu/effects/GrTextureEffect.h"
22
23
// Intervals smaller than this (that aren't hard stops) on low-precision-only devices force us to
24
// use the textured gradient
25
static const SkScalar kLowPrecisionIntervalLimit = 0.01f;
26
27
// Each cache entry costs 1K or 2K of RAM. Each bitmap will be 1x256 at either 32bpp or 64bpp.
28
static const int kMaxNumCachedGradientBitmaps = 32;
29
static const int kGradientTextureSize = 256;
30
31
// NOTE: signature takes raw pointers to the color/pos arrays and a count to make it easy for
32
// MakeColorizer to transparently take care of hard stops at the end points of the gradient.
33
static std::unique_ptr<GrFragmentProcessor> make_textured_colorizer(const SkPMColor4f* colors,
34
898
        const SkScalar* positions, int count, bool premul, const GrFPArgs& args) {
35
898
    static GrGradientBitmapCache gCache(kMaxNumCachedGradientBitmaps, kGradientTextureSize);
36
37
    // Use 8888 or F16, depending on the destination config.
38
    // TODO: Use 1010102 for opaque gradients, at least if destination is 1010102?
39
898
    SkColorType colorType = kRGBA_8888_SkColorType;
40
898
    if (GrColorTypeIsWiderThan(args.fDstColorInfo->colorType(), 8)) {
41
0
        auto f16Format = args.fContext->priv().caps()->getDefaultBackendFormat(
42
0
                GrColorType::kRGBA_F16, GrRenderable::kNo);
43
0
        if (f16Format.isValid()) {
44
0
            colorType = kRGBA_F16_SkColorType;
45
0
        }
46
0
    }
47
898
    SkAlphaType alphaType = premul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
48
49
898
    SkBitmap bitmap;
50
898
    gCache.getGradient(colors, positions, count, colorType, alphaType, &bitmap);
51
898
    SkASSERT(1 == bitmap.height() && SkIsPow2(bitmap.width()));
52
898
    SkASSERT(bitmap.isImmutable());
53
54
898
    auto view = std::get<0>(GrMakeCachedBitmapProxyView(args.fContext, bitmap, GrMipmapped::kNo));
55
898
    if (!view) {
56
0
        SkDebugf("Gradient won't draw. Could not create texture.");
57
0
        return nullptr;
58
0
    }
59
60
898
    auto m = SkMatrix::Scale(view.width(), 1.f);
61
898
    return GrTextureEffect::Make(std::move(view), alphaType, m, GrSamplerState::Filter::kLinear);
62
898
}
63
64
65
static std::unique_ptr<GrFragmentProcessor> make_single_interval_colorizer(const SkPMColor4f& start,
66
251
                                                                           const SkPMColor4f& end) {
67
251
    static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
68
251
        uniform half4 start;
69
251
        uniform half4 end;
70
251
        half4 main(float2 coord) {
71
251
            // Clamping and/or wrapping was already handled by the parent shader so the output
72
251
            // color is a simple lerp.
73
251
            return mix(start, end, half(coord.x));
74
251
        }
75
251
    )");
76
251
    return GrSkSLFP::Make(effect, "SingleIntervalColorizer", /*inputFP=*/nullptr,
77
251
                          GrSkSLFP::OptFlags::kNone,
78
251
                          "start", start,
79
251
                          "end", end);
80
251
}
81
82
static std::unique_ptr<GrFragmentProcessor> make_dual_interval_colorizer(const SkPMColor4f& c0,
83
                                                                         const SkPMColor4f& c1,
84
                                                                         const SkPMColor4f& c2,
85
                                                                         const SkPMColor4f& c3,
86
6
                                                                         float threshold) {
87
6
    static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
88
6
        uniform float4 scale01;
89
6
        uniform float4 bias01;
90
6
        uniform float4 scale23;
91
6
        uniform float4 bias23;
92
6
        uniform half threshold;
93
6
94
6
        half4 main(float2 coord) {
95
6
            half t = half(coord.x);
96
6
97
6
            float4 scale, bias;
98
6
            if (t < threshold) {
99
6
                scale = scale01;
100
6
                bias = bias01;
101
6
            } else {
102
6
                scale = scale23;
103
6
                bias = bias23;
104
6
            }
105
6
106
6
            return half4(t * scale + bias);
107
6
        }
108
6
    )");
109
110
6
    using sk4f = skvx::Vec<4, float>;
111
112
    // Derive scale and biases from the 4 colors and threshold
113
6
    auto vc0 = sk4f::Load(c0.vec());
114
6
    auto vc1 = sk4f::Load(c1.vec());
115
6
    auto scale01 = (vc1 - vc0) / threshold;
116
    // bias01 = c0
117
118
6
    auto vc2 = sk4f::Load(c2.vec());
119
6
    auto vc3 = sk4f::Load(c3.vec());
120
6
    auto scale23 = (vc3 - vc2) / (1 - threshold);
121
6
    auto bias23 = vc2 - threshold * scale23;
122
123
6
    return GrSkSLFP::Make(effect, "DualIntervalColorizer", /*inputFP=*/nullptr,
124
6
                          GrSkSLFP::OptFlags::kNone,
125
6
                          "scale01", scale01,
126
6
                          "bias01", c0,
127
6
                          "scale23", scale23,
128
6
                          "bias23", bias23,
129
6
                          "threshold", threshold);
130
6
}
131
132
static constexpr int kMaxUnrolledColorCount    = 16;
133
static constexpr int kMaxUnrolledIntervalCount = 8;
134
135
static std::unique_ptr<GrFragmentProcessor> make_unrolled_colorizer(int intervalCount,
136
                                                                    const SkPMColor4f* scale,
137
                                                                    const SkPMColor4f* bias,
138
                                                                    SkRect thresholds1_7,
139
524
                                                                    SkRect thresholds9_13) {
140
524
    SkASSERT(intervalCount >= 1 && intervalCount <= 8);
141
142
524
    static SkOnce                 once[kMaxUnrolledIntervalCount];
143
524
    static sk_sp<SkRuntimeEffect> effects[kMaxUnrolledIntervalCount];
144
145
3
    once[intervalCount - 1]([intervalCount] {
146
3
        SkString sksl;
147
148
        // The 7 threshold positions that define the boundaries of the 8 intervals (excluding t = 0,
149
        // and t = 1) are packed into two half4s instead of having up to 7 separate scalar uniforms.
150
        // For low interval counts, the extra components are ignored in the shader, but the uniform
151
        // simplification is worth it. It is assumed thresholds are provided in increasing value,
152
        // mapped as:
153
        //  - thresholds1_7.x = boundary between (0,1) and (2,3) -> 1_2
154
        //  -              .y = boundary between (2,3) and (4,5) -> 3_4
155
        //  -              .z = boundary between (4,5) and (6,7) -> 5_6
156
        //  -              .w = boundary between (6,7) and (8,9) -> 7_8
157
        //  - thresholds9_13.x = boundary between (8,9) and (10,11) -> 9_10
158
        //  -               .y = boundary between (10,11) and (12,13) -> 11_12
159
        //  -               .z = boundary between (12,13) and (14,15) -> 13_14
160
        //  -               .w = unused
161
3
        sksl.append("uniform half4 thresholds1_7, thresholds9_13;");
162
163
        // With the current hardstop detection threshold of 0.00024, the maximum scale and bias
164
        // values will be on the order of 4k (since they divide by dt). That is well outside the
165
        // precision capabilities of half floats, which can lead to inaccurate gradient calculations
166
12
        for (int i = 0; i < intervalCount; ++i) {
167
9
            sksl.appendf("uniform float4 scale%d_%d;", 2 * i, 2 * i + 1);
168
9
            sksl.appendf("uniform float4 bias%d_%d;", 2 * i, 2 * i + 1);
169
9
        }
170
171
3
        sksl.append("half4 main(float2 coord) {");
172
3
        sksl.append("  half t = half(coord.x);");
173
3
        sksl.append("  float4 scale, bias;");
174
175
        // To ensure that the code below always compiles, inject local variables with the names of
176
        // the uniforms that we *didn't* emit above. These will all end up unused and removed.
177
18
        for (int i = intervalCount; i < kMaxUnrolledIntervalCount; i++) {
178
15
            sksl.appendf("float4 scale%d_%d, bias%d_%d;", i * 2, i * 2 + 1, i * 2, i * 2 + 1);
179
15
        }
180
181
3
        sksl.appendf(R"(
182
3
            // Explicit binary search for the proper interval that t falls within. The interval
183
3
            // count checks are constant expressions, which are then optimized to the minimal number
184
3
            // of branches for the specific interval count.
185
3
186
3
            // thresholds1_7.w is mid point for intervals (0,7) and (8,15)
187
3
            if (%d <= 4 || t < thresholds1_7.w) {
188
3
                // thresholds1_7.y is mid point for intervals (0,3) and (4,7)
189
3
                if (%d <= 2 || t < thresholds1_7.y) {
190
3
                    // thresholds1_7.x is mid point for intervals (0,1) and (2,3)
191
3
                    if (%d <= 1 || t < thresholds1_7.x) {
192
3
                        scale = scale0_1;
193
3
                        bias = bias0_1;
194
3
                    } else {
195
3
                        scale = scale2_3;
196
3
                        bias = bias2_3;
197
3
                    }
198
3
                } else {
199
3
                    // thresholds1_7.z is mid point for intervals (4,5) and (6,7)
200
3
                    if (%d <= 3 || t < thresholds1_7.z) {
201
3
                        scale = scale4_5;
202
3
                        bias = bias4_5;
203
3
                    } else {
204
3
                        scale = scale6_7;
205
3
                        bias = bias6_7;
206
3
                    }
207
3
                }
208
3
            } else {
209
3
                // thresholds9_13.y is mid point for intervals (8,11) and (12,15)
210
3
                if (%d <= 6 || t < thresholds9_13.y) {
211
3
                    // thresholds9_13.x is mid point for intervals (8,9) and (10,11)
212
3
                    if (%d <= 5 || t < thresholds9_13.x) {
213
3
                        // interval 8-9
214
3
                        scale = scale8_9;
215
3
                        bias = bias8_9;
216
3
                    } else {
217
3
                        // interval 10-11
218
3
                        scale = scale10_11;
219
3
                        bias = bias10_11;
220
3
                    }
221
3
                } else {
222
3
                    // thresholds9_13.z is mid point for intervals (12,13) and (14,15)
223
3
                    if (%d <= 7 || t < thresholds9_13.z) {
224
3
                        // interval 12-13
225
3
                        scale = scale12_13;
226
3
                        bias = bias12_13;
227
3
                    } else {
228
3
                        // interval 14-15
229
3
                        scale = scale14_15;
230
3
                        bias = bias14_15;
231
3
                    }
232
3
                }
233
3
            }
234
3
235
3
        )", intervalCount, intervalCount, intervalCount, intervalCount, intervalCount,
236
3
            intervalCount, intervalCount);
237
238
3
        sksl.append("return half4(t * scale + bias); }");
239
240
3
        auto result = SkRuntimeEffect::MakeForShader(std::move(sksl));
241
3
        SkASSERTF(result.effect, "%s", result.errorText.c_str());
242
3
        effects[intervalCount - 1] = std::move(result.effect);
243
3
    });
244
245
524
#define ADD_STOP(N, suffix) \
246
3.66k
    "scale" #suffix, GrSkSLFP::When(intervalCount > N, scale[N]), \
247
3.66k
    "bias"  #suffix, GrSkSLFP::When(intervalCount > N, bias[N])
248
249
524
    return GrSkSLFP::Make(effects[intervalCount - 1], "UnrolledBinaryColorizer",
250
524
                          /*inputFP=*/nullptr, GrSkSLFP::OptFlags::kNone,
251
524
                          "thresholds1_7", thresholds1_7,
252
524
                          "thresholds9_13", thresholds9_13,
253
524
                          "scale0_1", scale[0], "bias0_1", bias[0],
254
524
                          ADD_STOP(1, 2_3),
255
524
                          ADD_STOP(2, 4_5),
256
524
                          ADD_STOP(3, 6_7),
257
524
                          ADD_STOP(4, 8_9),
258
524
                          ADD_STOP(5, 10_11),
259
524
                          ADD_STOP(6, 12_13),
260
524
                          ADD_STOP(7, 14_15));
261
524
#undef ADD_STOP
262
524
}
263
264
static std::unique_ptr<GrFragmentProcessor> make_unrolled_binary_colorizer(
265
1.42k
        const SkPMColor4f* colors, const SkScalar* positions, int count) {
266
    // Depending on how the positions resolve into hard stops or regular stops, the number of
267
    // intervals specified by the number of colors/positions can change. For instance, a plain
268
    // 3 color gradient is two intervals, but a 4 color gradient with a hard stop is also
269
    // two intervals. At the most extreme end, an 8 interval gradient made entirely of hard
270
    // stops has 16 colors.
271
272
1.42k
    if (count > kMaxUnrolledColorCount) {
273
        // Definitely cannot represent this gradient configuration
274
0
        return nullptr;
275
0
    }
276
277
    // The raster implementation also uses scales and biases, but since they must be calculated
278
    // after the dst color space is applied, it limits our ability to cache their values.
279
1.42k
    SkPMColor4f scales[kMaxUnrolledIntervalCount];
280
1.42k
    SkPMColor4f biases[kMaxUnrolledIntervalCount];
281
1.42k
    SkScalar thresholds[kMaxUnrolledIntervalCount] = { 0 };
282
283
1.42k
    int intervalCount = 0;
284
285
14.8k
    for (int i = 0; i < count - 1; i++) {
286
14.3k
        if (intervalCount >= kMaxUnrolledIntervalCount) {
287
            // Already reached kMaxUnrolledIntervalCount, and haven't run out of color stops so this
288
            // gradient cannot be represented by this shader.
289
898
            return nullptr;
290
898
        }
291
292
13.4k
        SkScalar t0 = positions[i];
293
13.4k
        SkScalar t1 = positions[i + 1];
294
13.4k
        SkScalar dt = t1 - t0;
295
        // If the interval is empty, skip to the next interval. This will automatically create
296
        // distinct hard stop intervals as needed. It also protects against malformed gradients
297
        // that have repeated hard stops at the very beginning that are effectively unreachable.
298
13.4k
        if (SkScalarNearlyZero(dt)) {
299
5.08k
            continue;
300
5.08k
        }
301
302
8.35k
        auto c0 = Sk4f::Load(colors[i].vec());
303
8.35k
        auto c1 = Sk4f::Load(colors[i + 1].vec());
304
305
8.35k
        auto scale = (c1 - c0) / dt;
306
8.35k
        auto bias = c0 - t0 * scale;
307
308
8.35k
        scale.store(scales + intervalCount);
309
8.35k
        bias.store(biases + intervalCount);
310
8.35k
        thresholds[intervalCount] = t1;
311
8.35k
        intervalCount++;
312
8.35k
    }
313
314
524
    SkRect thresholds1_7  = {thresholds[0], thresholds[1], thresholds[2], thresholds[3]},
315
524
           thresholds9_13 = {thresholds[4], thresholds[5], thresholds[6], 0.0};
316
317
524
    return make_unrolled_colorizer(intervalCount, scales, biases, thresholds1_7, thresholds9_13);
318
1.42k
}
319
320
// Analyze the shader's color stops and positions and chooses an appropriate colorizer to represent
321
// the gradient.
322
static std::unique_ptr<GrFragmentProcessor> make_colorizer(const SkPMColor4f* colors,
323
1.67k
        const SkScalar* positions, int count, bool premul, const GrFPArgs& args) {
324
    // If there are hard stops at the beginning or end, the first and/or last color should be
325
    // ignored by the colorizer since it should only be used in a clamped border color. By detecting
326
    // and removing these stops at the beginning, it makes optimizing the remaining color stops
327
    // simpler.
328
329
    // SkGradientShaderBase guarantees that pos[0] == 0 by adding a default value.
330
1.67k
    bool bottomHardStop = SkScalarNearlyEqual(positions[0], positions[1]);
331
    // The same is true for pos[end] == 1
332
1.67k
    bool topHardStop = SkScalarNearlyEqual(positions[count - 2], positions[count - 1]);
333
334
1.67k
    int offset = 0;
335
1.67k
    if (bottomHardStop) {
336
0
        offset += 1;
337
0
        count--;
338
0
    }
339
1.67k
    if (topHardStop) {
340
1
        count--;
341
1
    }
342
343
    // Two remaining colors means a single interval from 0 to 1
344
    // (but it may have originally been a 3 or 4 color gradient with 1-2 hard stops at the ends)
345
1.67k
    if (count == 2) {
346
251
        return make_single_interval_colorizer(colors[offset], colors[offset + 1]);
347
251
    }
348
349
    // Do an early test for the texture fallback to skip all of the other tests for specific
350
    // analytic support of the gradient (and compatibility with the hardware), when it's definitely
351
    // impossible to use an analytic solution.
352
1.42k
    bool tryAnalyticColorizer = count <= kMaxUnrolledColorCount;
353
354
    // The remaining analytic colorizers use scale*t+bias, and the scale/bias values can become
355
    // quite large when thresholds are close (but still outside the hardstop limit). If float isn't
356
    // 32-bit, output can be incorrect if the thresholds are too close together. However, the
357
    // analytic shaders are higher quality, so they can be used with lower precision hardware when
358
    // the thresholds are not ill-conditioned.
359
1.42k
    const GrShaderCaps* caps = args.fContext->priv().caps()->shaderCaps();
360
1.42k
    if (!caps->floatIs32Bits() && tryAnalyticColorizer) {
361
        // Could run into problems, check if thresholds are close together (with a limit of .01, so
362
        // that scales will be less than 100, which leaves 4 decimals of precision on 16-bit).
363
0
        for (int i = offset; i < count - 1; i++) {
364
0
            SkScalar dt = SkScalarAbs(positions[i] - positions[i + 1]);
365
0
            if (dt <= kLowPrecisionIntervalLimit && dt > SK_ScalarNearlyZero) {
366
0
                tryAnalyticColorizer = false;
367
0
                break;
368
0
            }
369
0
        }
370
0
    }
371
372
1.42k
    if (tryAnalyticColorizer) {
373
1.42k
        if (count == 3) {
374
            // Must be a dual interval gradient, where the middle point is at offset+1 and the two
375
            // intervals share the middle color stop.
376
6
            return make_dual_interval_colorizer(colors[offset], colors[offset + 1],
377
6
                                                colors[offset + 1], colors[offset + 2],
378
6
                                                positions[offset + 1]);
379
1.42k
        } else if (count == 4 && SkScalarNearlyEqual(positions[offset + 1],
380
0
                                                     positions[offset + 2])) {
381
            // Two separate intervals that join at the same threshold position
382
0
            return make_dual_interval_colorizer(colors[offset], colors[offset + 1],
383
0
                                                colors[offset + 2], colors[offset + 3],
384
0
                                                positions[offset + 1]);
385
0
        }
386
387
        // The single and dual intervals are a specialized case of the unrolled binary search
388
        // colorizer which can analytically render gradients of up to 8 intervals (up to 9 or 16
389
        // colors depending on how many hard stops are inserted).
390
1.42k
        std::unique_ptr<GrFragmentProcessor> unrolled =
391
1.42k
                make_unrolled_binary_colorizer(colors + offset, positions + offset, count);
392
1.42k
        if (unrolled) {
393
524
            return unrolled;
394
524
        }
395
898
    }
396
397
    // Otherwise fall back to a rasterized gradient sampled by a texture, which can handle
398
    // arbitrary gradients (the only downside being sampling resolution).
399
898
    return make_textured_colorizer(colors + offset, positions + offset, count, premul, args);
400
898
}
401
402
// This top-level effect implements clamping on the layout coordinate and requires specifying the
403
// border colors that are used when outside the clamped boundary. Gradients with the
404
// SkTileMode::kClamp should use the colors at their first and last stop (after adding default stops
405
// for t=0,t=1) as the border color. This will automatically replicate the edge color, even when
406
// there is a hard stop.
407
//
408
// The SkTileMode::kDecal can be produced by specifying transparent black as the border colors,
409
// regardless of the gradient's stop colors.
410
static std::unique_ptr<GrFragmentProcessor> make_clamped_gradient(
411
        std::unique_ptr<GrFragmentProcessor> colorizer,
412
        std::unique_ptr<GrFragmentProcessor> gradLayout,
413
        SkPMColor4f leftBorderColor,
414
        SkPMColor4f rightBorderColor,
415
        bool makePremul,
416
1.67k
        bool colorsAreOpaque) {
417
1.67k
    static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
418
1.67k
        uniform shader colorizer;
419
1.67k
        uniform shader gradLayout;
420
1.67k
421
1.67k
        uniform half4 leftBorderColor;  // t < 0.0
422
1.67k
        uniform half4 rightBorderColor; // t > 1.0
423
1.67k
424
1.67k
        uniform int makePremul;              // specialized
425
1.67k
        uniform int layoutPreservesOpacity;  // specialized
426
1.67k
427
1.67k
        half4 main(float2 coord) {
428
1.67k
            half4 t = sample(gradLayout, coord);
429
1.67k
            half4 outColor;
430
1.67k
431
1.67k
            // If t.x is below 0, use the left border color without invoking the child processor.
432
1.67k
            // If any t.x is above 1, use the right border color. Otherwise, t is in the [0, 1]
433
1.67k
            // range assumed by the colorizer FP, so delegate to the child processor.
434
1.67k
            if (!bool(layoutPreservesOpacity) && t.y < 0) {
435
1.67k
                // layout has rejected this fragment (rely on sksl to remove this branch if the
436
1.67k
                // layout FP preserves opacity is false)
437
1.67k
                outColor = half4(0);
438
1.67k
            } else if (t.x < 0) {
439
1.67k
                outColor = leftBorderColor;
440
1.67k
            } else if (t.x > 1.0) {
441
1.67k
                outColor = rightBorderColor;
442
1.67k
            } else {
443
1.67k
                // Always sample from (x, 0), discarding y, since the layout FP can use y as a
444
1.67k
                // side-channel.
445
1.67k
                outColor = sample(colorizer, t.x0);
446
1.67k
            }
447
1.67k
            if (bool(makePremul)) {
448
1.67k
                outColor.rgb *= outColor.a;
449
1.67k
            }
450
1.67k
            return outColor;
451
1.67k
        }
452
1.67k
    )");
453
454
    // If the layout does not preserve opacity, remove the opaque optimization,
455
    // but otherwise respect the provided color opacity state (which should take
456
    // into account the opacity of the border colors).
457
1.67k
    bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
458
1.67k
    GrSkSLFP::OptFlags optFlags = GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
459
1.67k
    if (colorsAreOpaque && layoutPreservesOpacity) {
460
0
        optFlags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
461
0
    }
462
463
1.67k
    return GrSkSLFP::Make(effect, "ClampedGradient", /*inputFP=*/nullptr, optFlags,
464
1.67k
                          "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
465
1.67k
                          "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
466
1.67k
                          "leftBorderColor", leftBorderColor,
467
1.67k
                          "rightBorderColor", rightBorderColor,
468
1.67k
                          "makePremul", GrSkSLFP::Specialize<int>(makePremul),
469
1.67k
                          "layoutPreservesOpacity",
470
1.67k
                              GrSkSLFP::Specialize<int>(layoutPreservesOpacity));
471
1.67k
}
472
473
static std::unique_ptr<GrFragmentProcessor> make_tiled_gradient(
474
        const GrFPArgs& args,
475
        std::unique_ptr<GrFragmentProcessor> colorizer,
476
        std::unique_ptr<GrFragmentProcessor> gradLayout,
477
        bool mirror,
478
        bool makePremul,
479
0
        bool colorsAreOpaque) {
480
0
    static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
481
0
        uniform shader colorizer;
482
0
        uniform shader gradLayout;
483
0
484
0
        uniform int mirror;                  // specialized
485
0
        uniform int makePremul;              // specialized
486
0
        uniform int layoutPreservesOpacity;  // specialized
487
0
        uniform int useFloorAbsWorkaround;   // specialized
488
0
489
0
        half4 main(float2 coord) {
490
0
            half4 t = sample(gradLayout, coord);
491
0
492
0
            if (!bool(layoutPreservesOpacity) && t.y < 0) {
493
0
                // layout has rejected this fragment (rely on sksl to remove this branch if the
494
0
                // layout FP preserves opacity is false)
495
0
                return half4(0);
496
0
            } else {
497
0
                if (bool(mirror)) {
498
0
                    half t_1 = t.x - 1;
499
0
                    half tiled_t = t_1 - 2 * floor(t_1 * 0.5) - 1;
500
0
                    if (bool(useFloorAbsWorkaround)) {
501
0
                        // At this point the expected value of tiled_t should between -1 and 1, so
502
0
                        // this clamp has no effect other than to break up the floor and abs calls
503
0
                        // and make sure the compiler doesn't merge them back together.
504
0
                        tiled_t = clamp(tiled_t, -1, 1);
505
0
                    }
506
0
                    t.x = abs(tiled_t);
507
0
                } else {
508
0
                    // Simple repeat mode
509
0
                    t.x = fract(t.x);
510
0
                }
511
0
512
0
                // Always sample from (x, 0), discarding y, since the layout FP can use y as a
513
0
                // side-channel.
514
0
                half4 outColor = sample(colorizer, t.x0);
515
0
                if (bool(makePremul)) {
516
0
                    outColor.rgb *= outColor.a;
517
0
                }
518
0
                return outColor;
519
0
            }
520
0
        }
521
0
    )");
522
523
    // If the layout does not preserve opacity, remove the opaque optimization,
524
    // but otherwise respect the provided color opacity state (which should take
525
    // into account the opacity of the border colors).
526
0
    bool layoutPreservesOpacity = gradLayout->preservesOpaqueInput();
527
0
    GrSkSLFP::OptFlags optFlags = GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
528
0
    if (colorsAreOpaque && layoutPreservesOpacity) {
529
0
        optFlags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
530
0
    }
531
0
    const bool useFloorAbsWorkaround =
532
0
            args.fContext->priv().caps()->shaderCaps()->mustDoOpBetweenFloorAndAbs();
533
534
0
    return GrSkSLFP::Make(effect, "TiledGradient", /*inputFP=*/nullptr, optFlags,
535
0
                          "colorizer", GrSkSLFP::IgnoreOptFlags(std::move(colorizer)),
536
0
                          "gradLayout", GrSkSLFP::IgnoreOptFlags(std::move(gradLayout)),
537
0
                          "mirror", GrSkSLFP::Specialize<int>(mirror),
538
0
                          "makePremul", GrSkSLFP::Specialize<int>(makePremul),
539
0
                          "layoutPreservesOpacity",
540
0
                                GrSkSLFP::Specialize<int>(layoutPreservesOpacity),
541
0
                          "useFloorAbsWorkaround",
542
0
                                GrSkSLFP::Specialize<int>(useFloorAbsWorkaround));
543
0
}
544
545
// Combines the colorizer and layout with an appropriately configured top-level effect based on the
546
// gradient's tile mode
547
static std::unique_ptr<GrFragmentProcessor> make_gradient(
548
        const SkGradientShaderBase& shader,
549
        const GrFPArgs& args,
550
        std::unique_ptr<GrFragmentProcessor> layout,
551
1.67k
        const SkMatrix* overrideMatrix = nullptr) {
552
    // No shader is possible if a layout couldn't be created, e.g. a layout-specific Make() returned
553
    // null.
554
1.67k
    if (layout == nullptr) {
555
0
        return nullptr;
556
0
    }
557
558
    // Wrap the layout in a matrix effect to apply the gradient's matrix:
559
1.67k
    SkMatrix matrix;
560
1.67k
    if (!shader.totalLocalMatrix(args.fPreLocalMatrix)->invert(&matrix)) {
561
0
        return nullptr;
562
0
    }
563
    // Some two-point conical gradients use a custom matrix here
564
1.67k
    matrix.postConcat(overrideMatrix ? *overrideMatrix : shader.getGradientMatrix());
565
1.67k
    layout = GrMatrixEffect::Make(matrix, std::move(layout));
566
567
    // Convert all colors into destination space and into SkPMColor4fs, and handle
568
    // premul issues depending on the interpolation mode
569
1.67k
    bool inputPremul = shader.getGradFlags() & SkGradientShader::kInterpolateColorsInPremul_Flag;
570
1.67k
    bool allOpaque = true;
571
1.67k
    SkAutoSTMalloc<4, SkPMColor4f> colors(shader.fColorCount);
572
1.67k
    SkColor4fXformer xformedColors(shader.fOrigColors4f, shader.fColorCount,
573
1.67k
                                   shader.fColorSpace.get(), args.fDstColorInfo->colorSpace());
574
19.7k
    for (int i = 0; i < shader.fColorCount; i++) {
575
18.0k
        const SkColor4f& upmColor = xformedColors.fColors[i];
576
0
        colors[i] = inputPremul ? upmColor.premul()
577
18.0k
                                : SkPMColor4f{ upmColor.fR, upmColor.fG, upmColor.fB, upmColor.fA };
578
18.0k
        if (allOpaque && !SkScalarNearlyEqual(colors[i].fA, 1.0)) {
579
1.67k
            allOpaque = false;
580
1.67k
        }
581
18.0k
    }
582
583
    // SkGradientShader stores positions implicitly when they are evenly spaced, but the getPos()
584
    // implementation performs a branch for every position index. Since the shader conversion
585
    // requires lots of position tests, calculate all of the positions up front if needed.
586
1.67k
    SkTArray<SkScalar, true> implicitPos;
587
1.67k
    SkScalar* positions;
588
1.67k
    if (shader.fOrigPos) {
589
521
        positions = shader.fOrigPos;
590
1.15k
    } else {
591
1.15k
        implicitPos.reserve_back(shader.fColorCount);
592
1.15k
        SkScalar posScale = SK_Scalar1 / (shader.fColorCount - 1);
593
12.4k
        for (int i = 0 ; i < shader.fColorCount; i++) {
594
11.3k
            implicitPos.push_back(SkIntToScalar(i) * posScale);
595
11.3k
        }
596
1.15k
        positions = implicitPos.begin();
597
1.15k
    }
598
599
    // All gradients are colorized the same way, regardless of layout
600
1.67k
    std::unique_ptr<GrFragmentProcessor> colorizer = make_colorizer(
601
1.67k
            colors.get(), positions, shader.fColorCount, inputPremul, args);
602
1.67k
    if (colorizer == nullptr) {
603
0
        return nullptr;
604
0
    }
605
606
    // The top-level effect has to export premul colors, but under certain conditions it doesn't
607
    // need to do anything to achieve that: i.e. its interpolating already premul colors
608
    // (inputPremul) or all the colors have a = 1, in which case premul is a no op. Note that this
609
    // allOpaque check is more permissive than SkGradientShaderBase's isOpaque(), since we can
610
    // optimize away the make-premul op for two point conical gradients (which report false for
611
    // isOpaque).
612
1.67k
    bool makePremul = !inputPremul && !allOpaque;
613
614
    // All tile modes are supported (unless something was added to SkShader)
615
1.67k
    std::unique_ptr<GrFragmentProcessor> gradient;
616
1.67k
    switch(shader.getTileMode()) {
617
0
        case SkTileMode::kRepeat:
618
0
            gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
619
0
                                           /* mirror */ false, makePremul, allOpaque);
620
0
            break;
621
0
        case SkTileMode::kMirror:
622
0
            gradient = make_tiled_gradient(args, std::move(colorizer), std::move(layout),
623
0
                                           /* mirror */ true, makePremul, allOpaque);
624
0
            break;
625
1.49k
        case SkTileMode::kClamp:
626
            // For the clamped mode, the border colors are the first and last colors, corresponding
627
            // to t=0 and t=1, because SkGradientShaderBase enforces that by adding color stops as
628
            // appropriate. If there is a hard stop, this grabs the expected outer colors for the
629
            // border.
630
1.49k
            gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
631
1.49k
                                             colors[0], colors[shader.fColorCount - 1],
632
1.49k
                                             makePremul, allOpaque);
633
1.49k
            break;
634
181
        case SkTileMode::kDecal:
635
            // Even if the gradient colors are opaque, the decal borders are transparent so
636
            // disable that optimization
637
181
            gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
638
181
                                             SK_PMColor4fTRANSPARENT, SK_PMColor4fTRANSPARENT,
639
181
                                             makePremul, /* colorsAreOpaque */ false);
640
181
            break;
641
1.67k
    }
642
643
1.67k
    if (gradient == nullptr) {
644
        // Unexpected tile mode
645
0
        return nullptr;
646
0
    }
647
1.67k
    if (args.fInputColorIsOpaque) {
648
        // If the input alpha is known to be 1, we don't need to take the kSrcIn path. This is
649
        // just an optimization. However, we can't just return 'gradient' here. We need to actually
650
        // inhibit the coverage-as-alpha optimization, or we'll fail to incorporate AA correctly.
651
        // The OverrideInput FP happens to do that, so wrap our fp in one of those. The gradient FP
652
        // doesn't actually use the input color at all, so the overridden input is irrelevant.
653
298
        return GrFragmentProcessor::OverrideInput(std::move(gradient), SK_PMColor4fWHITE, false);
654
298
    }
655
1.38k
    return GrFragmentProcessor::MulChildByInputAlpha(std::move(gradient));
656
1.38k
}
657
658
namespace GrGradientShader {
659
660
std::unique_ptr<GrFragmentProcessor> MakeLinear(const SkLinearGradient& shader,
661
79
                                                const GrFPArgs& args) {
662
    // We add a tiny delta to t. When gradient stops are set up so that a hard stop in a vertically
663
    // or horizontally oriented gradient falls exactly at a column or row of pixel centers we can
664
    // we can get slightly different interpolated t values along the column/row. By adding the delta
665
    // we will consistently get the color to the "right" of the stop. Of course if the hard stop
666
    // falls at X.5 - delta then we still could get inconsistent results, but that is much less
667
    // likely. crbug.com/938592
668
    // If/when we add filtering of the gradient this can be removed.
669
79
    static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
670
79
        half4 main(float2 coord) {
671
79
            return half4(half(coord.x) + 0.00001, 1, 0, 0); // y = 1 for always valid
672
79
        }
673
79
    )");
674
    // The linear gradient never rejects a pixel so it doesn't change opacity
675
79
    auto fp = GrSkSLFP::Make(effect, "LinearLayout", /*inputFP=*/nullptr,
676
79
                             GrSkSLFP::OptFlags::kPreservesOpaqueInput);
677
79
    return make_gradient(shader, args, std::move(fp));
678
79
}
679
680
std::unique_ptr<GrFragmentProcessor> MakeRadial(const SkRadialGradient& shader,
681
1
                                                const GrFPArgs& args) {
682
1
    static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
683
1
        half4 main(float2 coord) {
684
1
            return half4(half(length(coord)), 1, 0, 0); // y = 1 for always valid
685
1
        }
686
1
    )");
687
    // The radial gradient never rejects a pixel so it doesn't change opacity
688
1
    auto fp = GrSkSLFP::Make(effect, "RadialLayout", /*inputFP=*/nullptr,
689
1
                             GrSkSLFP::OptFlags::kPreservesOpaqueInput);
690
1
    return make_gradient(shader, args, std::move(fp));
691
1
}
692
693
std::unique_ptr<GrFragmentProcessor> MakeSweep(const SkSweepGradient& shader,
694
1.48k
                                               const GrFPArgs& args) {
695
    // On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
696
    // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in (sqrt(x^2
697
    // + y^2) + x) as the second parameter to atan2 in these cases. We let the device handle the
698
    // undefined behavior of the second paramenter being 0 instead of doing the divide ourselves and
699
    // using atan instead.
700
1.48k
    int useAtanWorkaround =
701
1.48k
            args.fContext->priv().caps()->shaderCaps()->atan2ImplementedAsAtanYOverX();
702
1.48k
    static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
703
1.48k
        uniform half bias;
704
1.48k
        uniform half scale;
705
1.48k
        uniform int useAtanWorkaround;  // specialized
706
1.48k
707
1.48k
        half4 main(float2 coord) {
708
1.48k
            half angle = bool(useAtanWorkaround)
709
1.48k
                    ? half(2 * atan(-coord.y, length(coord) - coord.x))
710
1.48k
                    : half(atan(-coord.y, -coord.x));
711
1.48k
712
1.48k
            // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi]
713
1.48k
            half t = (angle * 0.1591549430918 + 0.5 + bias) * scale;
714
1.48k
            return half4(t, 1, 0, 0); // y = 1 for always valid
715
1.48k
        }
716
1.48k
    )");
717
    // The sweep gradient never rejects a pixel so it doesn't change opacity
718
1.48k
    auto fp = GrSkSLFP::Make(effect, "SweepLayout", /*inputFP=*/nullptr,
719
1.48k
                             GrSkSLFP::OptFlags::kPreservesOpaqueInput,
720
1.48k
                             "bias", shader.getTBias(),
721
1.48k
                             "scale", shader.getTScale(),
722
1.48k
                             "useAtanWorkaround", GrSkSLFP::Specialize(useAtanWorkaround));
723
1.48k
    return make_gradient(shader, args, std::move(fp));
724
1.48k
}
725
726
std::unique_ptr<GrFragmentProcessor> MakeConical(const SkTwoPointConicalGradient& shader,
727
110
                                                 const GrFPArgs& args) {
728
    // The 2 point conical gradient can reject a pixel so it does change opacity even if the input
729
    // was opaque. Thus, all of these layout FPs disable that optimization.
730
110
    std::unique_ptr<GrFragmentProcessor> fp;
731
110
    SkTLazy<SkMatrix> matrix;
732
110
    switch (shader.getType()) {
733
109
        case SkTwoPointConicalGradient::Type::kStrip: {
734
109
            static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
735
109
                uniform half r0_2;
736
109
                half4 main(float2 p) {
737
109
                    half v = 1; // validation flag, set to negative to discard fragment later
738
109
                    float t = r0_2 - p.y * p.y;
739
109
                    if (t >= 0) {
740
109
                        t = p.x + sqrt(t);
741
109
                    } else {
742
109
                        v = -1;
743
109
                    }
744
109
                    return half4(half(t), v, 0, 0);
745
109
                }
746
109
            )");
747
109
            float r0 = shader.getStartRadius() / shader.getCenterX1();
748
109
            fp = GrSkSLFP::Make(effect, "TwoPointConicalStripLayout", /*inputFP=*/nullptr,
749
109
                                GrSkSLFP::OptFlags::kNone,
750
109
                                "r0_2", r0 * r0);
751
109
        } break;
752
753
0
        case SkTwoPointConicalGradient::Type::kRadial: {
754
0
            static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
755
0
                uniform half r0;
756
0
                uniform half lengthScale;
757
0
                half4 main(float2 p) {
758
0
                    half v = 1; // validation flag, set to negative to discard fragment later
759
0
                    float t = length(p) * lengthScale - r0;
760
0
                    return half4(half(t), v, 0, 0);
761
0
                }
762
0
            )");
763
0
            float dr = shader.getDiffRadius();
764
0
            float r0 = shader.getStartRadius() / dr;
765
0
            bool isRadiusIncreasing = dr >= 0;
766
0
            fp = GrSkSLFP::Make(effect, "TwoPointConicalRadialLayout", /*inputFP=*/nullptr,
767
0
                                GrSkSLFP::OptFlags::kNone,
768
0
                                "r0", r0,
769
0
                                "lengthScale", isRadiusIncreasing ? 1.0f : -1.0f);
770
771
            // GPU radial matrix is different from the original matrix, since we map the diff radius
772
            // to have |dr| = 1, so manually compute the final gradient matrix here.
773
774
            // Map center to (0, 0)
775
0
            matrix.set(SkMatrix::Translate(-shader.getStartCenter().fX,
776
0
                                           -shader.getStartCenter().fY));
777
            // scale |diffRadius| to 1
778
0
            matrix->postScale(1 / dr, 1 / dr);
779
0
        } break;
780
781
1
        case SkTwoPointConicalGradient::Type::kFocal: {
782
1
            static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
783
1
                // Optimization flags, all specialized:
784
1
                uniform int isRadiusIncreasing;
785
1
                uniform int isFocalOnCircle;
786
1
                uniform int isWellBehaved;
787
1
                uniform int isSwapped;
788
1
                uniform int isNativelyFocal;
789
1
790
1
                uniform half invR1;  // 1/r1
791
1
                uniform half fx;     // focalX = r0/(r0-r1)
792
1
793
1
                half4 main(float2 p) {
794
1
                    float t = -1;
795
1
                    half v = 1; // validation flag, set to negative to discard fragment later
796
1
797
1
                    float x_t = -1;
798
1
                    if (bool(isFocalOnCircle)) {
799
1
                        x_t = dot(p, p) / p.x;
800
1
                    } else if (bool(isWellBehaved)) {
801
1
                        x_t = length(p) - p.x * invR1;
802
1
                    } else {
803
1
                        float temp = p.x * p.x - p.y * p.y;
804
1
805
1
                        // Only do sqrt if temp >= 0; this is significantly slower than checking
806
1
                        // temp >= 0 in the if statement that checks r(t) >= 0. But GPU may break if
807
1
                        // we sqrt a negative float. (Although I havevn't observed that on any
808
1
                        // devices so far, and the old approach also does sqrt negative value
809
1
                        // without a check.) If the performance is really critical, maybe we should
810
1
                        // just compute the area where temp and x_t are always valid and drop all
811
1
                        // these ifs.
812
1
                        if (temp >= 0) {
813
1
                            if (bool(isSwapped) || !bool(isRadiusIncreasing)) {
814
1
                                x_t = -sqrt(temp) - p.x * invR1;
815
1
                            } else {
816
1
                                x_t = sqrt(temp) - p.x * invR1;
817
1
                            }
818
1
                        }
819
1
                    }
820
1
821
1
                    // The final calculation of t from x_t has lots of static optimizations but only
822
1
                    // do them when x_t is positive (which can be assumed true if isWellBehaved is
823
1
                    // true)
824
1
                    if (!bool(isWellBehaved)) {
825
1
                        // This will still calculate t even though it will be ignored later in the
826
1
                        // pipeline to avoid a branch
827
1
                        if (x_t <= 0.0) {
828
1
                            v = -1;
829
1
                        }
830
1
                    }
831
1
                    if (bool(isRadiusIncreasing)) {
832
1
                        if (bool(isNativelyFocal)) {
833
1
                            t = x_t;
834
1
                        } else {
835
1
                            t = x_t + fx;
836
1
                        }
837
1
                    } else {
838
1
                        if (bool(isNativelyFocal)) {
839
1
                            t = -x_t;
840
1
                        } else {
841
1
                            t = -x_t + fx;
842
1
                        }
843
1
                    }
844
1
845
1
                    if (bool(isSwapped)) {
846
1
                        t = 1 - t;
847
1
                    }
848
1
849
1
                    return half4(half(t), v, 0, 0);
850
1
                }
851
1
            )");
852
853
1
            const SkTwoPointConicalGradient::FocalData& focalData = shader.getFocalData();
854
1
            bool isRadiusIncreasing = (1 - focalData.fFocalX) > 0,
855
1
                 isFocalOnCircle    = focalData.isFocalOnCircle(),
856
1
                 isWellBehaved      = focalData.isWellBehaved(),
857
1
                 isSwapped          = focalData.isSwapped(),
858
1
                 isNativelyFocal    = focalData.isNativelyFocal();
859
860
1
            fp = GrSkSLFP::Make(effect, "TwoPointConicalFocalLayout", /*inputFP=*/nullptr,
861
1
                                GrSkSLFP::OptFlags::kNone,
862
1
                                "isRadiusIncreasing", GrSkSLFP::Specialize<int>(isRadiusIncreasing),
863
1
                                "isFocalOnCircle",    GrSkSLFP::Specialize<int>(isFocalOnCircle),
864
1
                                "isWellBehaved",      GrSkSLFP::Specialize<int>(isWellBehaved),
865
1
                                "isSwapped",          GrSkSLFP::Specialize<int>(isSwapped),
866
1
                                "isNativelyFocal",    GrSkSLFP::Specialize<int>(isNativelyFocal),
867
1
                                "invR1", 1.0f / focalData.fR1,
868
1
                                "fx", focalData.fFocalX);
869
1
        } break;
870
110
    }
871
110
    return make_gradient(shader, args, std::move(fp), matrix.getMaybeNull());
872
110
}
873
874
#if GR_TEST_UTILS
875
0
RandomParams::RandomParams(SkRandom* random) {
876
    // Set color count to min of 2 so that we don't trigger the const color optimization and make
877
    // a non-gradient processor.
878
0
    fColorCount = random->nextRangeU(2, kMaxRandomGradientColors);
879
0
    fUseColors4f = random->nextBool();
880
881
    // if one color, omit stops, otherwise randomly decide whether or not to
882
0
    if (fColorCount == 1 || (fColorCount >= 2 && random->nextBool())) {
883
0
        fStops = nullptr;
884
0
    } else {
885
0
        fStops = fStopStorage;
886
0
    }
887
888
    // if using SkColor4f, attach a random (possibly null) color space (with linear gamma)
889
0
    if (fUseColors4f) {
890
0
        fColorSpace = GrTest::TestColorSpace(random);
891
0
    }
892
893
0
    SkScalar stop = 0.f;
894
0
    for (int i = 0; i < fColorCount; ++i) {
895
0
        if (fUseColors4f) {
896
0
            fColors4f[i].fR = random->nextUScalar1();
897
0
            fColors4f[i].fG = random->nextUScalar1();
898
0
            fColors4f[i].fB = random->nextUScalar1();
899
0
            fColors4f[i].fA = random->nextUScalar1();
900
0
        } else {
901
0
            fColors[i] = random->nextU();
902
0
        }
903
0
        if (fStops) {
904
0
            fStops[i] = stop;
905
0
            stop = i < fColorCount - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f;
906
0
        }
907
0
    }
908
0
    fTileMode = static_cast<SkTileMode>(random->nextULessThan(kSkTileModeCount));
909
0
}
910
#endif
911
912
}  // namespace GrGradientShader