xref: /aosp_15_r20/external/skia/src/sksl/sksl_graphite_frag.sksl (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1// Graphite-specific fragment shader code
2
3const int $kTileModeClamp  = 0;
4const int $kTileModeRepeat = 1;
5const int $kTileModeMirror = 2;
6const int $kTileModeDecal  = 3;
7
8const int $kFilterModeNearest = 0;
9const int $kFilterModeLinear  = 1;
10
11const int $kTFTypeSRGB = 1;
12const int $kTFTypePQ = 2;
13const int $kTFTypeHLG = 3;
14const int $kTFTypeHLGinv = 4;
15
16const int $kColorSpaceXformFlagUnpremul = 0x1;
17const int $kColorSpaceXformFlagLinearize = 0x2;
18const int $kColorSpaceXformFlagGamutTransform = 0x4;
19const int $kColorSpaceXformFlagEncode = 0x8;
20const int $kColorSpaceXformFlagPremul = 0x10;
21const int $kColorSpaceXformFlagAlphaSwizzle = 0x20;
22
23const int $kMaskFormatA8   = 0;
24const int $kMaskFormatA565 = 1;
25const int $kMaskFormatARGB = 2;
26
27const int $kShapeTypeRect = 0;
28const int $kShapeTypeRRect = 1;
29const int $kShapeTypeCircle = 2;
30
31// Matches GrTextureEffect::kLinearInset, to make sure we don't touch an outer
32// row or column with a weight of 0 when linear filtering.
33const float $kLinearInset = 0.5 + 0.00001;
34
35$pure half4 sk_error() {
36    return half4(1.0, 0.0, 0.0, 1.0);
37}
38
39$pure half4 sk_passthrough(half4 color) {
40    return color;
41}
42
43$pure half4 sk_solid_shader(float4 colorParam) {
44    return half4(colorParam);
45}
46
47$pure half4 sk_rgb_opaque(float4 colorParam) {
48    return half4(colorParam.rgb, 1.0);
49}
50
51$pure half4 sk_alpha_only(float4 colorParam) {
52    return half4(0.0, 0.0, 0.0, colorParam.a);
53}
54
55$pure float $apply_xfer_fn(int kind, float x, half4 cs[2]) {
56    float G = cs[0][0], A = cs[0][1], B = cs[0][2], C = cs[0][3],
57          D = cs[1][0], E = cs[1][1], F = cs[1][2];
58    float s = sign(x);
59    x = abs(x);
60    switch (kind) {
61        case $kTFTypeSRGB:
62            x = (x < D) ? (C * x) + F
63                        : pow(A * x + B, G) + E;
64            break;
65        case $kTFTypePQ:
66            float x_C = pow(x, C);
67            x = pow(max(A + B * x_C, 0) / (D + E * x_C), F);
68            break;
69        case $kTFTypeHLG:
70            x = (x * A <= 1) ? pow(x * A, B)
71                             : exp((x - E) * C) + D;
72            x *= (F + 1);
73            break;
74        case $kTFTypeHLGinv:
75            x /= (F + 1);
76            x = (x <= 1) ? A * pow(x, B)
77                         : C * log(x - D) + E;
78            break;
79    }
80    return s * x;
81}
82
83$pure half4 sk_premul_alpha(float4 color) {
84    return half4(color.rgb*color.a, color.a);
85}
86
87$pure half4 sk_color_space_transform(half4 halfColor,
88                                     int flags,
89                                     int srcKind,
90                                     half3x3 gamutTransform,
91                                     int dstKind,
92                                     half4x4 coeffs) {
93    if (flags != 0) {
94        float4 color = float4(halfColor);
95
96        if (bool(flags & $kColorSpaceXformFlagAlphaSwizzle)) {
97            color.a = dot(color.r1, float2(coeffs[1][3], coeffs[3][3]));
98        }
99        if (bool(flags & $kColorSpaceXformFlagUnpremul)) {
100            color = unpremul(color);
101        }
102        if (bool(flags & $kColorSpaceXformFlagLinearize)) {
103            half4 srcCoeffs[2];
104            srcCoeffs[0] = coeffs[0];
105            srcCoeffs[1] = coeffs[1];
106            color.r = $apply_xfer_fn(srcKind, color.r, srcCoeffs);
107            color.g = $apply_xfer_fn(srcKind, color.g, srcCoeffs);
108            color.b = $apply_xfer_fn(srcKind, color.b, srcCoeffs);
109        }
110        if (bool(flags & $kColorSpaceXformFlagGamutTransform)) {
111            color.rgb = gamutTransform * color.rgb;
112        }
113        if (bool(flags & $kColorSpaceXformFlagEncode)) {
114            half4 dstCoeffs[2];
115            dstCoeffs[0] = coeffs[2];
116            dstCoeffs[1] = coeffs[3];
117            color.r = $apply_xfer_fn(dstKind, color.r, dstCoeffs);
118            color.g = $apply_xfer_fn(dstKind, color.g, dstCoeffs);
119            color.b = $apply_xfer_fn(dstKind, color.b, dstCoeffs);
120        }
121
122        halfColor = bool(flags & $kColorSpaceXformFlagPremul) ? sk_premul_alpha(color)
123                                                              : half4(color);
124    }
125    return halfColor;
126}
127
128$pure half4 sk_circular_rrect_clip(float4 rect,
129                                   float2 radiusPlusHalf,
130                                   half4 edgeSelect) {
131    float2 fragCoord = sk_FragCoord.xy;
132
133    // Negative x indicates inverse fill
134    float2 radius = float2(abs(radiusPlusHalf.x));
135    float2 dxy0 = edgeSelect.LT*((rect.LT + radius) - fragCoord);
136    float2 dxy1 = edgeSelect.RB*(fragCoord - (rect.RB - radius));
137    float2 dxy = max(max(dxy0, dxy1), 0.0);
138    // Use more complex length calculation to manage precision issues
139    half circleCornerAlpha = half(saturate(radius.x*(1.0 - length(dxy*radiusPlusHalf.y))));
140
141    half4 rectEdgeAlphas = saturate(half4(fragCoord - rect.LT, rect.RB - fragCoord));
142    rectEdgeAlphas = mix(rectEdgeAlphas, half4(1), edgeSelect);
143
144    half alpha = circleCornerAlpha * rectEdgeAlphas.x * rectEdgeAlphas.y *
145                 rectEdgeAlphas.z * rectEdgeAlphas.w;
146    // Check for inverse fill
147    alpha = radiusPlusHalf.x < 0 ? (1-alpha) : alpha;
148
149    return half4(alpha);
150}
151
152$pure float $tile(int tileMode, float f, float low, float high) {
153    switch (tileMode) {
154        case $kTileModeClamp:
155            return clamp(f, low, high);
156
157        case $kTileModeRepeat: {
158            float length = high - low;
159            return (mod(f - low, length) + low);
160        }
161        case $kTileModeMirror: {
162            float length = high - low;
163            float length2 = 2 * length;
164            float tmp = mod(f - low, length2);
165            return (mix(tmp, length2 - tmp, step(length, tmp)) + low);
166        }
167        default:  // $kTileModeDecal
168            // Decal is handled later.
169            return f;
170    }
171}
172
173$pure half4 $sample_image(float2 pos, float2 invImgSize, sampler2D s) {
174    return sample(s, pos * invImgSize);
175}
176
177$pure half4 $sample_image_subset(float2 pos,
178                                 float2 invImgSize,
179                                 float4 subset,
180                                 int tileModeX,
181                                 int tileModeY,
182                                 int filterMode,
183                                 float2 linearFilterInset,
184                                 sampler2D s) {
185    // Do hard-edge shader transitions to the border color for nearest-neighbor decal tiling at the
186    // subset boundaries. Snap the input coordinates to nearest neighbor before comparing to the
187    // subset rect, to avoid GPU interpolation errors. See https://crbug.com/skia/10403.
188    if (tileModeX == $kTileModeDecal && filterMode == $kFilterModeNearest) {
189        float snappedX = floor(pos.x) + 0.5;
190        if (snappedX < subset.x || snappedX > subset.z) {
191            return half4(0);
192        }
193    }
194    if (tileModeY == $kTileModeDecal && filterMode == $kFilterModeNearest) {
195        float snappedY = floor(pos.y) + 0.5;
196        if (snappedY < subset.y || snappedY > subset.w) {
197            return half4(0);
198        }
199    }
200
201    pos.x = $tile(tileModeX, pos.x, subset.x, subset.z);
202    pos.y = $tile(tileModeY, pos.y, subset.y, subset.w);
203
204    // Clamp to an inset subset to prevent sampling neighboring texels when coords fall exactly at
205    // texel boundaries.
206    float4 insetClamp;
207    if (filterMode == $kFilterModeNearest) {
208        insetClamp = float4(floor(subset.xy) + $kLinearInset, ceil(subset.zw) - $kLinearInset);
209    } else {
210        insetClamp = float4(subset.xy + linearFilterInset.x, subset.zw - linearFilterInset.y);
211    }
212    float2 clampedPos = clamp(pos, insetClamp.xy, insetClamp.zw);
213    half4 color = $sample_image(clampedPos, invImgSize, s);
214
215    if (filterMode == $kFilterModeLinear) {
216        // Remember the amount the coord moved for clamping. This is used to implement shader-based
217        // filtering for repeat and decal tiling.
218        half2 error = half2(pos - clampedPos);
219        half2 absError = abs(error);
220
221        // Do 1 or 3 more texture reads depending on whether both x and y tiling modes are repeat
222        // and whether we're near a single subset edge or a corner. Then blend the multiple reads
223        // using the error values calculated above.
224        bool sampleExtraX = tileModeX == $kTileModeRepeat;
225        bool sampleExtraY = tileModeY == $kTileModeRepeat;
226        if (sampleExtraX || sampleExtraY) {
227            float extraCoordX;
228            float extraCoordY;
229            half4 extraColorX;
230            half4 extraColorY;
231            if (sampleExtraX) {
232                extraCoordX = error.x > 0 ? insetClamp.x : insetClamp.z;
233                extraColorX = $sample_image(float2(extraCoordX, clampedPos.y),
234                                            invImgSize, s);
235            }
236            if (sampleExtraY) {
237                extraCoordY = error.y > 0 ? insetClamp.y : insetClamp.w;
238                extraColorY = $sample_image(float2(clampedPos.x, extraCoordY),
239                                            invImgSize, s);
240            }
241            if (sampleExtraX && sampleExtraY) {
242                half4 extraColorXY = $sample_image(float2(extraCoordX, extraCoordY),
243                                                   invImgSize, s);
244                color = mix(mix(color, extraColorX, absError.x),
245                            mix(extraColorY, extraColorXY, absError.x),
246                            absError.y);
247            } else if (sampleExtraX) {
248                color = mix(color, extraColorX, absError.x);
249            } else if (sampleExtraY) {
250                color = mix(color, extraColorY, absError.y);
251            }
252        }
253
254        // Do soft edge shader filtering for decal tiling and linear filtering using the error
255        // values calculated above.
256        if (tileModeX == $kTileModeDecal) {
257            color *= max(1 - absError.x, 0);
258        }
259        if (tileModeY == $kTileModeDecal) {
260            color *= max(1 - absError.y, 0);
261        }
262    }
263
264    return color;
265}
266
267$pure half4 $cubic_filter_image(float2 pos,
268                                float2 invImgSize,
269                                float4 subset,
270                                int tileModeX,
271                                int tileModeY,
272                                half4x4 coeffs,
273                                sampler2D s) {
274    // Determine pos's fractional offset f between texel centers.
275    float2 f = fract(pos - 0.5);
276    // Sample 16 points at 1-pixel intervals from [p - 1.5 ... p + 1.5].
277    pos -= 1.5;
278    // Snap to texel centers to prevent sampling neighboring texels.
279    pos = floor(pos) + 0.5;
280
281    half4 wx = coeffs * half4(1.0, f.x, f.x * f.x, f.x * f.x * f.x);
282    half4 wy = coeffs * half4(1.0, f.y, f.y * f.y, f.y * f.y * f.y);
283    half4 color = half4(0);
284    for (int y = 0; y < 4; ++y) {
285        half4 rowColor = half4(0);
286        for (int x = 0; x < 4; ++x) {
287            rowColor += wx[x] * $sample_image_subset(pos + float2(x, y), invImgSize, subset,
288                                                     tileModeX, tileModeY, $kFilterModeNearest,
289                                                     float2($kLinearInset), s);
290        }
291        color += wy[y] * rowColor;
292    }
293    // Bicubic can send colors out of range, so clamp to get them back in gamut, assuming premul.
294    color.a = saturate(color.a);
295    color.rgb = clamp(color.rgb, half3(0.0), color.aaa);
296    return color;
297}
298
299$pure half4 sk_image_shader(float2 coords,
300                            float2 invImgSize,
301                            float4 subset,
302                            int tileModeX,
303                            int tileModeY,
304                            int filterMode,
305                            sampler2D s) {
306    return $sample_image_subset(coords, invImgSize, subset, tileModeX, tileModeY,
307                                filterMode, float2($kLinearInset), s);
308}
309
310$pure half4 sk_cubic_image_shader(float2 coords,
311                                  float2 invImgSize,
312                                  float4 subset,
313                                  int tileModeX,
314                                  int tileModeY,
315                                  half4x4 cubicCoeffs,
316                                  sampler2D s) {
317    return $cubic_filter_image(coords, invImgSize, subset, tileModeX, tileModeY, cubicCoeffs, s);
318}
319
320$pure half4 sk_hw_image_shader(float2 coords,
321                               float2 invImgSize,
322                               sampler2D s) {
323    return $sample_image(coords, invImgSize, s);
324}
325
326$pure half4 $yuv_to_rgb_no_swizzle(half Y,
327                                   half U,
328                                   half V,
329                                   half alpha,
330                                   half3x3 yuvToRGBMatrix,
331                                   half3 yuvToRGBTranslate) {
332    half3 preColor = half3(Y, U, V);
333    half4 sampleColor;
334    sampleColor.rgb = saturate(yuvToRGBMatrix * preColor.rgb + yuvToRGBTranslate);
335    sampleColor.a = alpha;
336
337    // We always return an unpremul color here to skip work when transforming colorspaces
338    return sampleColor;
339}
340
341$pure half4 $yuv_to_rgb(half4 sampleColorY,
342                        half4 sampleColorU,
343                        half4 sampleColorV,
344                        half alpha,
345                        half4 channelSelectY,
346                        half4 channelSelectU,
347                        half4 channelSelectV,
348                        half3x3 yuvToRGBMatrix,
349                        half3 yuvToRGBTranslate) {
350    half Y = dot(channelSelectY, sampleColorY);
351    half U = dot(channelSelectU, sampleColorU);
352    half V = dot(channelSelectV, sampleColorV);
353    return $yuv_to_rgb_no_swizzle(Y, U, V, alpha, yuvToRGBMatrix, yuvToRGBTranslate);
354}
355
356$pure half4 sk_yuv_image_shader(float2 coords,
357                                float2 invImgSizeY,
358                                float2 invImgSizeUV,  // Relative to Y's coordinate space
359                                float4 subset,
360                                float2 linearFilterUVInset,
361                                int tileModeX,
362                                int tileModeY,
363                                int filterModeY,
364                                int filterModeUV,
365                                half4 channelSelectY,
366                                half4 channelSelectU,
367                                half4 channelSelectV,
368                                half4 channelSelectA,
369                                half3x3 yuvToRGBMatrix,
370                                half3 yuvToRGBTranslate,
371                                sampler2D sY,
372                                sampler2D sU,
373                                sampler2D sV,
374                                sampler2D sA) {
375    // If the filter modes are different between Y and UV, this means that
376    // the base filtermode is nearest and we have to snap the coords to Y's
377    // texel centers to get the correct positions for UV.
378    if (filterModeY != filterModeUV) {
379        coords = floor(coords) + 0.5;
380    }
381
382    int tileModeX_UV = tileModeX == $kTileModeDecal ? $kTileModeClamp : tileModeX;
383    int tileModeY_UV = tileModeY == $kTileModeDecal ? $kTileModeClamp : tileModeY;
384
385    half4 sampleColorY, sampleColorU, sampleColorV;
386    sampleColorY = $sample_image_subset(coords, invImgSizeY, subset, tileModeX, tileModeY,
387                                        filterModeY, float2($kLinearInset), sY);
388    sampleColorU = $sample_image_subset(coords, invImgSizeUV, subset, tileModeX_UV, tileModeY_UV,
389                                        filterModeUV, linearFilterUVInset, sU);
390    sampleColorV = $sample_image_subset(coords, invImgSizeUV, subset, tileModeX_UV, tileModeY_UV,
391                                        filterModeUV, linearFilterUVInset, sV);
392    half alpha;
393    if (channelSelectA == half4(1)) {
394        alpha = 1;
395    } else {
396        half4 sampleColorA = $sample_image_subset(coords, invImgSizeY, subset, tileModeX, tileModeY,
397                                                  filterModeY, float2($kLinearInset), sA);
398        alpha = dot(channelSelectA, sampleColorA);
399    }
400
401    return $yuv_to_rgb(sampleColorY, sampleColorU, sampleColorV, alpha,
402                       channelSelectY, channelSelectU, channelSelectV,
403                       yuvToRGBMatrix, yuvToRGBTranslate);
404}
405
406$pure half4 sk_cubic_yuv_image_shader(float2 coords,
407                                      float2 invImgSizeY,
408                                      float2 invImgSizeUV,  // Relative to Y's coordinate space
409                                      float4 subset,
410                                      int tileModeX,
411                                      int tileModeY,
412                                      half4x4 cubicCoeffs,
413                                      half4 channelSelectY,
414                                      half4 channelSelectU,
415                                      half4 channelSelectV,
416                                      half4 channelSelectA,
417                                      half3x3 yuvToRGBMatrix,
418                                      half3 yuvToRGBTranslate,
419                                      sampler2D sY,
420                                      sampler2D sU,
421                                      sampler2D sV,
422                                      sampler2D sA) {
423    int tileModeX_UV = tileModeX == $kTileModeDecal ? $kTileModeClamp : tileModeX;
424    int tileModeY_UV = tileModeY == $kTileModeDecal ? $kTileModeClamp : tileModeY;
425
426    half4 sampleColorY, sampleColorU, sampleColorV;
427    sampleColorY = $cubic_filter_image(coords, invImgSizeY, subset, tileModeX, tileModeY,
428                                       cubicCoeffs, sY);
429    sampleColorU = $cubic_filter_image(coords, invImgSizeUV, subset, tileModeX_UV, tileModeY_UV,
430                                       cubicCoeffs, sU);
431    sampleColorV = $cubic_filter_image(coords, invImgSizeUV, subset, tileModeX_UV, tileModeY_UV,
432                                       cubicCoeffs, sV);
433    half alpha;
434    if (channelSelectA == half4(1)) {
435        alpha = 1;
436    } else {
437        half4 sampleColorA = $cubic_filter_image(coords, invImgSizeY, subset, tileModeX, tileModeY,
438                                                 cubicCoeffs, sA);
439        alpha = dot(channelSelectA, sampleColorA);
440    }
441
442    return $yuv_to_rgb(sampleColorY, sampleColorU, sampleColorV, alpha,
443                       channelSelectY, channelSelectU, channelSelectV,
444                       yuvToRGBMatrix, yuvToRGBTranslate);
445}
446
447$pure half4 sk_hw_yuv_image_shader(float2 coords,
448                                   float2 invImgSizeY,
449                                   float2 invImgSizeUV,  // Relative to Y's coordinate space
450                                   half4 channelSelectY,
451                                   half4 channelSelectU,
452                                   half4 channelSelectV,
453                                   half4 channelSelectA,
454                                   half3x3 yuvToRGBMatrix,
455                                   half3 yuvToRGBTranslate,
456                                   sampler2D sY,
457                                   sampler2D sU,
458                                   sampler2D sV,
459                                   sampler2D sA) {
460    half4 sampleColorY, sampleColorU, sampleColorV, sampleColorA;
461    sampleColorY = $sample_image(coords, invImgSizeY, sY);
462    sampleColorU = $sample_image(coords, invImgSizeUV, sU);
463    sampleColorV = $sample_image(coords, invImgSizeUV, sV);
464    half alpha;
465    if (channelSelectA == half4(1)) {
466        alpha = 1;
467    } else {
468        half4 sampleColorA = $sample_image(coords, invImgSizeY, sA);
469        alpha = dot(channelSelectA, sampleColorA);
470    }
471
472    return $yuv_to_rgb(sampleColorY, sampleColorU, sampleColorV, alpha,
473                       channelSelectY, channelSelectU, channelSelectV,
474                       yuvToRGBMatrix, yuvToRGBTranslate);
475}
476
477$pure half4 sk_hw_yuv_no_swizzle_image_shader(float2 coords,
478                                              float2 invImgSizeY,
479                                              float2 invImgSizeUV,  // Relative to Y's coord space
480                                              half3x3 yuvToRGBMatrix,
481                                              half4 yuvToRGBXlateAlphaParam,
482                                              sampler2D sY,
483                                              sampler2D sU,
484                                              sampler2D sV,
485                                              sampler2D sA) {
486    half4 sampleColorY, sampleColorU, sampleColorV, sampleColorA;
487    half Y = $sample_image(coords, invImgSizeY, sY).r;
488    half U = $sample_image(coords, invImgSizeUV, sU).r;
489    half V = $sample_image(coords, invImgSizeUV, sV).r;
490    // When it's a Y_U_V_A texture, yuvToRGBXlateAlphaParam.w is 0 and we have a real A sampler so
491    // alpha is just that saturated value (should be a no-op). For Y_U_V, we set
492    // yuvToRGBXlateAlphaParam.w to 1 and sample from Y, which after the saturate is always 1.
493    half alpha = saturate($sample_image(coords, invImgSizeY, sA).r + yuvToRGBXlateAlphaParam.w);
494
495    return $yuv_to_rgb_no_swizzle(Y, U, V, alpha,
496                                  yuvToRGBMatrix, yuvToRGBXlateAlphaParam.xyz);
497}
498
499$pure half4 sk_dither(half4 colorIn, half range, sampler2D lut) {
500    const float kImgSize = 8;
501
502    half value = sample(lut, sk_FragCoord.xy / kImgSize).r - 0.5; // undo the bias in the table
503    // For each color channel, add the random offset to the channel value and then clamp
504    // between 0 and alpha to keep the color premultiplied.
505    return half4(clamp(colorIn.rgb + value * range, 0.0, colorIn.a), colorIn.a);
506}
507
508$pure float2 $tile_grad(int tileMode, float2 t) {
509    switch (tileMode) {
510        case $kTileModeClamp:
511            t.x = saturate(t.x);
512            break;
513
514        case $kTileModeRepeat:
515            t.x = fract(t.x);
516            break;
517
518        case $kTileModeMirror: {
519            float t_1 = t.x - 1;
520            t.x = t_1 - 2 * floor(t_1 * 0.5) - 1;
521            if (sk_Caps.mustDoOpBetweenFloorAndAbs) {
522                // At this point the expected value of tiled_t should between -1 and 1, so this
523                // clamp has no effect other than to break up the floor and abs calls and make sure
524                // the compiler doesn't merge them back together.
525                t.x = clamp(t.x, -1, 1);
526            }
527            t.x = abs(t.x);
528            break;
529        }
530
531        case $kTileModeDecal:
532            if (t.x < 0 || t.x > 1) {
533                return float2(0, -1);
534            }
535            break;
536    }
537
538    return t;
539}
540
541$pure half4 $colorize_grad_4(float4 colorsParam[4], float4 offsetsParam, float2 t) {
542    if (t.y < 0) {
543        return half4(0);
544
545    } else if (t.x <= offsetsParam[0]) {
546        return half4(colorsParam[0]);
547    } else if (t.x < offsetsParam[1]) {
548        return half4(mix(colorsParam[0], colorsParam[1], (t.x             - offsetsParam[0]) /
549                                                         (offsetsParam[1] - offsetsParam[0])));
550    } else if (t.x < offsetsParam[2]) {
551        return half4(mix(colorsParam[1], colorsParam[2], (t.x             - offsetsParam[1]) /
552                                                         (offsetsParam[2] - offsetsParam[1])));
553    } else if (t.x < offsetsParam[3]) {
554        return half4(mix(colorsParam[2], colorsParam[3], (t.x             - offsetsParam[2]) /
555                                                         (offsetsParam[3] - offsetsParam[2])));
556    } else {
557        return half4(colorsParam[3]);
558    }
559}
560
561$pure half4 $colorize_grad_8(float4 colorsParam[8], float4 offsetsParam[2], float2 t) {
562    if (t.y < 0) {
563        return half4(0);
564
565    // Unrolled binary search through intervals
566    // ( .. 0), (0 .. 1), (1 .. 2), (2 .. 3), (3 .. 4), (4 .. 5), (5 .. 6), (6 .. 7), (7 .. ).
567    } else if (t.x < offsetsParam[1][0]) {
568        if (t.x < offsetsParam[0][2]) {
569            if (t.x <= offsetsParam[0][0]) {
570                return half4(colorsParam[0]);
571            } else if (t.x < offsetsParam[0][1]) {
572                return half4(mix(colorsParam[0], colorsParam[1],
573                                 (t.x                - offsetsParam[0][0]) /
574                                 (offsetsParam[0][1] - offsetsParam[0][0])));
575            } else {
576                return half4(mix(colorsParam[1], colorsParam[2],
577                                 (t.x                - offsetsParam[0][1]) /
578                                 (offsetsParam[0][2] - offsetsParam[0][1])));
579            }
580        } else {
581            if (t.x < offsetsParam[0][3]) {
582                return half4(mix(colorsParam[2], colorsParam[3],
583                                 (t.x                - offsetsParam[0][2]) /
584                                 (offsetsParam[0][3] - offsetsParam[0][2])));
585            } else {
586                return half4(mix(colorsParam[3], colorsParam[4],
587                                 (t.x                - offsetsParam[0][3]) /
588                                 (offsetsParam[1][0] - offsetsParam[0][3])));
589            }
590        }
591    } else {
592        if (t.x < offsetsParam[1][2]) {
593            if (t.x < offsetsParam[1][1]) {
594                return half4(mix(colorsParam[4], colorsParam[5],
595                                 (t.x                - offsetsParam[1][0]) /
596                                 (offsetsParam[1][1] - offsetsParam[1][0])));
597            } else {
598                return half4(mix(colorsParam[5], colorsParam[6],
599                                 (t.x                - offsetsParam[1][1]) /
600                                 (offsetsParam[1][2] - offsetsParam[1][1])));
601            }
602        } else {
603            if (t.x < offsetsParam[1][3]) {
604                return half4(mix(colorsParam[6], colorsParam[7],
605                                 (t.x                - offsetsParam[1][2]) /
606                                 (offsetsParam[1][3] - offsetsParam[1][2])));
607            } else {
608                return half4(colorsParam[7]);
609            }
610        }
611    }
612}
613
614$pure half4 $colorize_grad_tex(sampler2D colorsAndOffsetsSampler, int numStops, float2 t) {
615    const float kColorCoord = 0.25;
616    const float kOffsetCoord = 0.75;
617
618    if (t.y < 0) {
619        return half4(0);
620    } else if (t.x == 0) {
621        return sampleLod(colorsAndOffsetsSampler, float2(0, kColorCoord), 0);
622    } else if (t.x == 1) {
623        return sampleLod(colorsAndOffsetsSampler, float2(1, kColorCoord), 0);
624    } else {
625        float low = 0;
626        float high = float(numStops);
627        float invNumStops = 1.0 / high;
628        for (int loop = 1; loop < numStops; loop += loop) {
629            float mid = floor((low + high) * 0.5);
630            float samplePos = (mid + 0.5) * invNumStops;
631
632            float2 tmp = sampleLod(colorsAndOffsetsSampler, float2(samplePos, kOffsetCoord), 0).xy;
633            float offset = ldexp(tmp.x, int(tmp.y));
634
635            if (t.x < offset) {
636                high = mid;
637            } else {
638                low = mid;
639            }
640        }
641
642        high = (low + 1.5) * invNumStops;
643        low = (low + 0.5) * invNumStops;
644        half4 color0 = sampleLod(colorsAndOffsetsSampler, float2(low, kColorCoord), 0);
645        half4 color1 = sampleLod(colorsAndOffsetsSampler, float2(high, kColorCoord), 0);
646
647        float2 tmp = sampleLod(colorsAndOffsetsSampler, float2(low, kOffsetCoord), 0).xy;
648        float offset0 = ldexp(tmp.x, int(tmp.y));
649
650        tmp = sampleLod(colorsAndOffsetsSampler, float2(high, kOffsetCoord), 0).xy;
651        float offset1 = ldexp(tmp.x, int(tmp.y));
652
653        return half4(mix(color0, color1,
654                         (t.x     - offset0) /
655                         (offset1 - offset0)));
656    }
657}
658
659$pure half4 $half4_from_array(float[] arr, int offset) {
660    return half4(arr[offset + 0],
661                 arr[offset + 1],
662                 arr[offset + 2],
663                 arr[offset + 3]);
664}
665
666$pure half4 $colorize_grad_buf(float[] colorAndOffsetData,
667                               int offsetsBaseIndex,
668                               int numStops,
669                               float2 t) {
670    int colorsBaseIndex = offsetsBaseIndex + numStops;
671    if (t.y < 0) {
672        return half4(0);
673    } else if (t.x == 0) {
674        return $half4_from_array(colorAndOffsetData, colorsBaseIndex);
675    } else if (t.x == 1) {
676        int lastColorIndex = colorsBaseIndex + (numStops - 1) * 4;
677        return $half4_from_array(colorAndOffsetData, lastColorIndex);
678    } else {
679        // Binary search for the matching adjacent stop offsets running log2(numStops).
680        int lowOffsetIndex = offsetsBaseIndex;
681        int highOffsetIndex = lowOffsetIndex + numStops - 1;
682        for (int i = 1; i < numStops; i += i) {
683            int middleOffsetIndex = (lowOffsetIndex + highOffsetIndex) / 2;
684            if (t.x < colorAndOffsetData[middleOffsetIndex]) {
685                highOffsetIndex = middleOffsetIndex;
686            } else {
687                lowOffsetIndex = middleOffsetIndex;
688            }
689        }
690        int lowColorIndex = colorsBaseIndex + (lowOffsetIndex - offsetsBaseIndex) * 4;
691        float lowOffset = colorAndOffsetData[lowOffsetIndex];
692        half4 lowColor = $half4_from_array(colorAndOffsetData, lowColorIndex);
693
694        int highColorIndex = colorsBaseIndex + (highOffsetIndex - offsetsBaseIndex) * 4;
695        float highOffset = colorAndOffsetData[highOffsetIndex];
696        if (highOffset == lowOffset) {
697            // If the t value falls exactly on a color stop, both lowOffset
698            // and highOffset will be exactly the same so we avoid having
699            // 0/0=NaN as our mix value.
700            return lowColor;
701        } else {
702            half4 highColor = $half4_from_array(colorAndOffsetData, highColorIndex);
703
704            return half4(mix(lowColor, highColor, (t.x - lowOffset) / (highOffset - lowOffset)));
705        }
706    }
707}
708
709$pure float2 $linear_grad_layout(float2 pos) {
710    // Add small epsilon since when the gradient is horizontally or vertically aligned,
711    // pixels along the same column or row can have slightly different interpolated t values
712    // causing pixels to choose the wrong offset when colorizing. This helps ensure pixels
713    // along the same column or row choose the same gradient offsets.
714    return float2(pos.x + 0.00001, 1);
715}
716
717$pure float2 $radial_grad_layout(float2 pos) {
718    float t = length(pos);
719    return float2(t, 1);
720}
721
722$pure float2 $sweep_grad_layout(float biasParam, float scaleParam, float2 pos) {
723    // Some devices incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
724    // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). To work around this we pass in
725    // (sqrt(x^2 + y^2) + x) as the second parameter to atan2 in these cases. We let the device
726    // handle the undefined behavior if the second parameter is 0, instead of doing the divide
727    // ourselves and calling atan with the quotient.
728    float angle;
729    if (sk_Caps.atan2ImplementedAsAtanYOverX) {
730        angle = 2 * atan(-pos.y, length(pos) - pos.x);
731    } else {
732        // Hardcode pi/2 for the angle when x == 0, to avoid undefined behavior in this
733        // case. This hasn't proven to be necessary in the atan workaround case.
734        angle = pos.x != 0.0 ? atan(-pos.y, -pos.x) : sign(pos.y) * -1.5707963267949;
735    }
736
737    // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi]
738    float t = (angle * 0.1591549430918 + 0.5 + biasParam) * scaleParam;
739    return float2(t, 1);
740}
741
742$pure float2 $conical_grad_layout(float radius0,
743                                  float dRadius,
744                                  float a,
745                                  float invA,
746                                  float2 pos) {
747    // When writing uniform values, if the gradient is radial, we encode a == 0 and since
748    // the linear edge case is when a == 0, we differentiate the radial case with invA == 1.
749    if (a == 0 && invA == 1) {
750        // Radial case
751        float t = length(pos) * dRadius - radius0;
752
753        return float2(t, 1);
754    } else {
755        // Focal/strip case.
756        float c = dot(pos, pos) - radius0 * radius0;
757        float negB = 2 * (dRadius * radius0 + pos.x);
758
759        float t;
760        if (a == 0) {
761            // Linear case, both circles intersect as exactly one point
762            // with the focal point sitting on that point.
763
764            // It is highly unlikely that b and c would be 0 resulting in NaN, if b == 0 due to
765            // a specific translation, it would result is +/-Inf which propogates the sign to
766            // isValid resulting in how the pixel is expected to look.
767            t = c / negB;
768        } else {
769            // Quadratic case
770            float d = negB*negB - 4*a*c;
771            if (d < 0) {
772                return float2(0, -1);
773            }
774
775            // T should be as large as possible, so when one circle fully encloses the other,
776            // the sign will be positive or negative depending on the sign of dRadius.
777            // When this isn't the case and they form a cone, the sign will always be positive.
778            float quadSign = sign(1 - dRadius);
779            t = invA * (negB + quadSign * sqrt(d));
780        }
781
782        // Interpolated radius must be positive.
783        float isValid = sign(t * dRadius + radius0);
784        return float2(t, isValid);
785    }
786}
787
788$pure half4 sk_linear_grad_4_shader(float2 coords,
789                                    float4 colorsParam[4],
790                                    float4 offsetsParam,
791                                    int tileMode,
792                                    int colorSpace,
793                                    int doUnpremul) {
794    float2 t = $linear_grad_layout(coords);
795    t = $tile_grad(tileMode, t);
796    half4 color = $colorize_grad_4(colorsParam, offsetsParam, t);
797    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
798}
799
800$pure half4 sk_linear_grad_8_shader(float2 coords,
801                                    float4 colorsParam[8],
802                                    float4 offsetsParam[2],
803                                    int tileMode,
804                                    int colorSpace,
805                                    int doUnpremul) {
806    float2 t = $linear_grad_layout(coords);
807    t = $tile_grad(tileMode, t);
808    half4 color = $colorize_grad_8(colorsParam, offsetsParam, t);
809    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
810}
811
812$pure half4 sk_linear_grad_tex_shader(float2 coords,
813                                      int numStops,
814                                      int tileMode,
815                                      int colorSpace,
816                                      int doUnpremul,
817                                      sampler2D colorAndOffsetSampler) {
818    float2 t = $linear_grad_layout(coords);
819    t = $tile_grad(tileMode, t);
820    half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t);
821    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
822}
823
824$pure half4 sk_linear_grad_buf_shader(float2 coords,
825                                      int numStops,
826                                      int bufferOffset,
827                                      int tileMode,
828                                      int colorSpace,
829                                      int doUnpremul,
830                                      float[] colorAndOffsetData) {
831    float2 t = $linear_grad_layout(coords);
832    t = $tile_grad(tileMode, t);
833    half4 color = $colorize_grad_buf(colorAndOffsetData, bufferOffset, numStops, t);
834    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
835}
836
837$pure half4 sk_radial_grad_4_shader(float2 coords,
838                                    float4 colorsParam[4],
839                                    float4 offsetsParam,
840                                    int tileMode,
841                                    int colorSpace,
842                                    int doUnpremul) {
843    float2 t = $radial_grad_layout(coords);
844    t = $tile_grad(tileMode, t);
845    half4 color = $colorize_grad_4(colorsParam, offsetsParam, t);
846    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
847}
848
849$pure half4 sk_radial_grad_8_shader(float2 coords,
850                                    float4 colorsParam[8],
851                                    float4 offsetsParam[2],
852                                    int tileMode,
853                                    int colorSpace,
854                                    int doUnpremul) {
855    float2 t = $radial_grad_layout(coords);
856    t = $tile_grad(tileMode, t);
857    half4 color = $colorize_grad_8(colorsParam, offsetsParam, t);
858    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
859}
860
861$pure half4 sk_radial_grad_tex_shader(float2 coords,
862                                      int numStops,
863                                      int tileMode,
864                                      int colorSpace,
865                                      int doUnpremul,
866                                      sampler2D colorAndOffsetSampler) {
867    float2 t = $radial_grad_layout(coords);
868    t = $tile_grad(tileMode, t);
869    half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t);
870    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
871}
872
873$pure half4 sk_radial_grad_buf_shader(float2 coords,
874                                      int numStops,
875                                      int bufferOffset,
876                                      int tileMode,
877                                      int colorSpace,
878                                      int doUnpremul,
879                                      float[] colorAndOffsetData) {
880    float2 t = $radial_grad_layout(coords);
881    t = $tile_grad(tileMode, t);
882    half4 color = $colorize_grad_buf(colorAndOffsetData, bufferOffset, numStops, t);
883    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
884}
885
886$pure half4 sk_sweep_grad_4_shader(float2 coords,
887                                   float4 colorsParam[4],
888                                   float4 offsetsParam,
889                                   float biasParam,
890                                   float scaleParam,
891                                   int tileMode,
892                                   int colorSpace,
893                                   int doUnpremul) {
894    float2 t = $sweep_grad_layout(biasParam, scaleParam, coords);
895    t = $tile_grad(tileMode, t);
896    half4 color = $colorize_grad_4(colorsParam, offsetsParam, t);
897    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
898}
899
900$pure half4 sk_sweep_grad_8_shader(float2 coords,
901                                   float4 colorsParam[8],
902                                   float4 offsetsParam[2],
903                                   float biasParam,
904                                   float scaleParam,
905                                   int tileMode,
906                                   int colorSpace,
907                                   int doUnpremul) {
908    float2 t = $sweep_grad_layout(biasParam, scaleParam, coords);
909    t = $tile_grad(tileMode, t);
910    half4 color = $colorize_grad_8(colorsParam, offsetsParam, t);
911    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
912}
913
914$pure half4 sk_sweep_grad_tex_shader(float2 coords,
915                                     float biasParam,
916                                     float scaleParam,
917                                     int numStops,
918                                     int tileMode,
919                                     int colorSpace,
920                                     int doUnpremul,
921                                     sampler2D colorAndOffsetSampler) {
922    float2 t = $sweep_grad_layout(biasParam, scaleParam, coords);
923    t = $tile_grad(tileMode, t);
924    half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t);
925    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
926}
927
928$pure half4 sk_sweep_grad_buf_shader(float2 coords,
929                                     float biasParam,
930                                     float scaleParam,
931                                     int numStops,
932                                     int bufferOffset,
933                                     int tileMode,
934                                     int colorSpace,
935                                     int doUnpremul,
936                                     float[] colorAndOffsetData) {
937    float2 t = $sweep_grad_layout(biasParam, scaleParam, coords);
938    t = $tile_grad(tileMode, t);
939    half4 color = $colorize_grad_buf(colorAndOffsetData, bufferOffset, numStops, t);
940    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
941}
942
943$pure half4 sk_conical_grad_4_shader(float2 coords,
944                                     float4 colorsParam[4],
945                                     float4 offsetsParam,
946                                     float radius0Param,
947                                     float dRadiusParam,
948                                     float aParam,
949                                     float invAParam,
950                                     int tileMode,
951                                     int colorSpace,
952                                     int doUnpremul) {
953    float2 t = $conical_grad_layout(radius0Param, dRadiusParam, aParam, invAParam, coords);
954    t = $tile_grad(tileMode, t);
955    half4 color = $colorize_grad_4(colorsParam, offsetsParam, t);
956    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
957}
958
959$pure half4 sk_conical_grad_8_shader(float2 coords,
960                                     float4 colorsParam[8],
961                                     float4 offsetsParam[2],
962                                     float radius0Param,
963                                     float dRadiusParam,
964                                     float aParam,
965                                     float invAParam,
966                                     int tileMode,
967                                     int colorSpace,
968                                     int doUnpremul) {
969    float2 t = $conical_grad_layout(radius0Param, dRadiusParam, aParam, invAParam, coords);
970    t = $tile_grad(tileMode, t);
971    half4 color = $colorize_grad_8(colorsParam, offsetsParam, t);
972    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
973}
974
975$pure half4 sk_conical_grad_tex_shader(float2 coords,
976                                       float radius0Param,
977                                       float dRadiusParam,
978                                       float aParam,
979                                       float invAParam,
980                                       int numStops,
981                                       int tileMode,
982                                       int colorSpace,
983                                       int doUnpremul,
984                                       sampler2D colorAndOffsetSampler) {
985    float2 t = $conical_grad_layout(radius0Param, dRadiusParam, aParam, invAParam, coords);
986    t = $tile_grad(tileMode, t);
987    half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t);
988    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
989}
990
991$pure half4 sk_conical_grad_buf_shader(float2 coords,
992                                       float radius0Param,
993                                       float dRadiusParam,
994                                       float aParam,
995                                       float invAParam,
996                                       int numStops,
997                                       int bufferOffset,
998                                       int tileMode,
999                                       int colorSpace,
1000                                       int doUnpremul,
1001                                       float[] colorAndOffsetData) {
1002    float2 t = $conical_grad_layout(radius0Param, dRadiusParam, aParam, invAParam, coords);
1003    t = $tile_grad(tileMode, t);
1004    half4 color = $colorize_grad_buf(colorAndOffsetData, bufferOffset, numStops, t);
1005    return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
1006}
1007
1008$pure half4 sk_matrix_colorfilter(half4 colorIn, float4x4 m, float4 v, int inHSLA, int clampRGB) {
1009    if (bool(inHSLA)) {
1010        colorIn = $rgb_to_hsl(colorIn.rgb, colorIn.a); // includes unpremul
1011    } else {
1012        colorIn = unpremul(colorIn);
1013    }
1014
1015    half4 colorOut = half4((m * colorIn) + v);
1016
1017    if (bool(inHSLA)) {
1018        colorOut = $hsl_to_rgb(colorOut.rgb, colorOut.a); // includes clamp and premul
1019    } else {
1020        if (bool(clampRGB)) {
1021            colorOut = saturate(colorOut);
1022        } else {
1023            colorOut.a = saturate(colorOut.a);
1024        }
1025        colorOut.rgb *= colorOut.a;
1026    }
1027
1028    return colorOut;
1029}
1030
1031// This method computes the 4 x-coodinates ([0..1]) that should be used to look
1032// up in the Perlin noise shader's noise table.
1033$pure half4 $noise_helper(half2 noiseVec,
1034                          half2 stitchData,
1035                          int stitching,
1036                          sampler2D permutationSampler) {
1037    const half kBlockSize = 256.0;
1038
1039    half4 floorVal;
1040    floorVal.xy = floor(noiseVec);
1041    floorVal.zw = floorVal.xy + half2(1);
1042
1043    // Adjust frequencies if we're stitching tiles
1044    if (bool(stitching)) {
1045        floorVal -= step(stitchData.xyxy, floorVal) * stitchData.xyxy;
1046    }
1047
1048    half sampleX = sample(permutationSampler, half2((floorVal.x + 0.5) / kBlockSize, 0.5)).r;
1049    half sampleY = sample(permutationSampler, half2((floorVal.z + 0.5) / kBlockSize, 0.5)).r;
1050
1051    half2 latticeIdx = half2(sampleX, sampleY);
1052
1053    if (sk_Caps.PerlinNoiseRoundingFix) {
1054        // Aggressively round to the nearest exact (N / 255) floating point values.
1055        // This prevents rounding errors on some platforms (e.g., Tegras)
1056        const half kInv255 = 1.0 / 255.0;
1057
1058        latticeIdx = floor(latticeIdx * half2(255.0) + half2(0.5)) * half2(kInv255);
1059    }
1060
1061    // Get (x,y) coordinates with the permuted x
1062    half4 noiseXCoords = kBlockSize*latticeIdx.xyxy + floorVal.yyww;
1063
1064    noiseXCoords /= half4(kBlockSize);
1065    return noiseXCoords;
1066}
1067
1068$pure half4 $noise_function(half2 noiseVec,
1069                            half4 noiseXCoords,
1070                            sampler2D noiseSampler) {
1071    half2 fractVal = fract(noiseVec);
1072
1073    // Hermite interpolation : t^2*(3 - 2*t)
1074    half2 noiseSmooth = smoothstep(0, 1, fractVal);
1075
1076    // This is used to convert the two 16bit integers packed into rgba 8 bit input into
1077    // a [-1,1] vector
1078    const half kInv256 = 0.00390625;  // 1.0 / 256.0
1079
1080    half4 result;
1081
1082    for (int channel = 0; channel < 4; channel++) {
1083
1084        // There are 4 lines in the noise texture, put y coords at center of each.
1085        half chanCoord = (half(channel) + 0.5) / 4.0;
1086
1087        half4 sampleA = sample(noiseSampler, float2(noiseXCoords.x, chanCoord));
1088        half4 sampleB = sample(noiseSampler, float2(noiseXCoords.y, chanCoord));
1089        half4 sampleC = sample(noiseSampler, float2(noiseXCoords.w, chanCoord));
1090        half4 sampleD = sample(noiseSampler, float2(noiseXCoords.z, chanCoord));
1091
1092        half2 tmpFractVal = fractVal;
1093
1094        // Compute u, at offset (0,0)
1095        half u = dot((sampleA.ga + sampleA.rb*kInv256)*2 - 1, tmpFractVal);
1096
1097        // Compute v, at offset (-1,0)
1098        tmpFractVal.x -= 1.0;
1099        half v = dot((sampleB.ga + sampleB.rb*kInv256)*2 - 1, tmpFractVal);
1100
1101        // Compute 'a' as a linear interpolation of 'u' and 'v'
1102        half a = mix(u, v, noiseSmooth.x);
1103
1104        // Compute v, at offset (-1,-1)
1105        tmpFractVal.y -= 1.0;
1106        v = dot((sampleC.ga + sampleC.rb*kInv256)*2 - 1, tmpFractVal);
1107
1108        // Compute u, at offset (0,-1)
1109        tmpFractVal.x += 1.0;
1110        u = dot((sampleD.ga + sampleD.rb*kInv256)*2 - 1, tmpFractVal);
1111
1112        // Compute 'b' as a linear interpolation of 'u' and 'v'
1113        half b = mix(u, v, noiseSmooth.x);
1114
1115        // Compute the noise as a linear interpolation of 'a' and 'b'
1116        result[channel] = mix(a, b, noiseSmooth.y);
1117    }
1118
1119    return result;
1120}
1121
1122// permutationSampler is [kBlockSize x 1] A8
1123// noiseSampler is [kBlockSize x 4] RGBA8 premul
1124$pure half4 sk_perlin_noise_shader(float2 coords,
1125                                   float2 baseFrequency,
1126                                   float2 stitchDataIn,
1127                                   int noiseType,
1128                                   int numOctaves,
1129                                   int stitching,
1130                                   sampler2D permutationSampler,
1131                                   sampler2D noiseSampler) {
1132    const int kFractalNoise = 0;
1133    const int kTurbulence = 1;
1134
1135    // In the past, Perlin noise handled coordinates a bit differently than most shaders.
1136    // It operated in device space, floored; it also had a one-pixel transform matrix applied to
1137    // both the X and Y coordinates. This is roughly equivalent to adding 0.5 to the coordinates.
1138    // This was originally done in order to better match preexisting golden images from WebKit.
1139    // Perlin noise now operates in local space, which allows rotation to work correctly. To better
1140    // approximate past behavior, we add 0.5 to the coordinates here. This is _not_ exactly the same
1141    // because this adjustment is occurring in local space, not device space.
1142    half2 noiseVec = half2((coords + 0.5) * baseFrequency);
1143
1144    // Clear the color accumulator
1145    half4 color = half4(0);
1146
1147    half2 stitchData = half2(stitchDataIn);
1148
1149    half ratio = 1.0;
1150
1151    // Loop over all octaves
1152    for (int octave = 0; octave < numOctaves; ++octave) {
1153        half4 noiseXCoords = $noise_helper(noiseVec, stitchData, stitching, permutationSampler);
1154
1155        half4 tmp = $noise_function(noiseVec, noiseXCoords, noiseSampler);
1156
1157        if (noiseType != kFractalNoise) {
1158            // For kTurbulence the result is: abs(noise[-1,1])
1159            tmp = abs(tmp);
1160        }
1161
1162        color += tmp * ratio;
1163
1164        noiseVec *= half2(2.0);
1165        ratio *= 0.5;
1166        stitchData *= half2(2.0);
1167    }
1168
1169    if (noiseType == kFractalNoise) {
1170        // For kFractalNoise the result is: noise[-1,1] * 0.5 + 0.5
1171        color = color * half4(0.5) + half4(0.5);
1172    }
1173
1174    // Clamp values
1175    color = saturate(color);
1176
1177    // Pre-multiply the result
1178    return sk_premul_alpha(color);
1179}
1180
1181$pure half4 sk_porter_duff_blend(half4 src, half4 dst, half4 coeffs) {
1182    return blend_porter_duff(coeffs, src, dst);
1183}
1184
1185$pure half4 sk_hslc_blend(half4 src, half4 dst, half2 flipSat) {
1186    return blend_hslc(flipSat, src, dst);
1187}
1188
1189$pure half4 sk_table_colorfilter(half4 inColor, sampler2D s) {
1190    half4 coords = unpremul(inColor) * (255.0/256.0) + (0.5/256.0);
1191    half4 color = half4(sample(s, half2(coords.r, 3.0/8.0)).r,
1192                        sample(s, half2(coords.g, 5.0/8.0)).r,
1193                        sample(s, half2(coords.b, 7.0/8.0)).r,
1194                        1);
1195    return color * sample(s, half2(coords.a, 1.0/8.0)).r;
1196}
1197
1198$pure half4 sk_gaussian_colorfilter(half4 inColor) {
1199    half factor = 1 - inColor.a;
1200    factor = exp(-factor * factor * 4) - 0.018;
1201    return half4(factor);
1202}
1203
1204$pure half4 sample_indexed_atlas(float2 textureCoords,
1205                                 int atlasIndex,
1206                                 sampler2D atlas0,
1207                                 sampler2D atlas1,
1208                                 sampler2D atlas2,
1209                                 sampler2D atlas3) {
1210    switch (atlasIndex) {
1211        case 1:
1212            return sample(atlas1, textureCoords);
1213        case 2:
1214            return sample(atlas2, textureCoords);
1215        case 3:
1216            return sample(atlas3, textureCoords);
1217        default:
1218            return sample(atlas0, textureCoords);
1219    }
1220}
1221
1222$pure half3 $sample_indexed_atlas_lcd(float2 textureCoords,
1223                                      int atlasIndex,
1224                                      half2 offset,
1225                                      sampler2D atlas0,
1226                                      sampler2D atlas1,
1227                                      sampler2D atlas2,
1228                                      sampler2D atlas3) {
1229    half3 distance = half3(1);
1230    switch (atlasIndex) {
1231        case 1:
1232            distance.x = sample(atlas1, half2(textureCoords) - offset).r;
1233            distance.y = sample(atlas1, textureCoords).r;
1234            distance.z = sample(atlas1, half2(textureCoords) + offset).r;
1235        case 2:
1236            distance.x = sample(atlas2, half2(textureCoords) - offset).r;
1237            distance.y = sample(atlas2, textureCoords).r;
1238            distance.z = sample(atlas2, half2(textureCoords) + offset).r;
1239        case 3:
1240            distance.x = sample(atlas3, half2(textureCoords) - offset).r;
1241            distance.y = sample(atlas3, textureCoords).r;
1242            distance.z = sample(atlas3, half2(textureCoords) + offset).r;
1243        default:
1244            distance.x = sample(atlas0, half2(textureCoords) - offset).r;
1245            distance.y = sample(atlas0, textureCoords).r;
1246            distance.z = sample(atlas0, half2(textureCoords) + offset).r;
1247    }
1248    return distance;
1249}
1250
1251$pure half4 bitmap_text_coverage_fn(half4 texColor, int maskFormat) {
1252    return (maskFormat == $kMaskFormatA8) ? texColor.rrrr
1253                                          : texColor;
1254}
1255
1256$pure half4 sdf_text_coverage_fn(half texColor,
1257                                 half2 gammaParams,
1258                                 float2 unormTexCoords) {
1259    // TODO: To minimize the number of shaders generated this is the full affine shader.
1260    // For best performance it may be worth creating the uniform scale shader as well,
1261    // as that's the most common case.
1262    // TODO: Need to add 565 support.
1263
1264    // The distance field is constructed as uchar8_t values, so that the zero value is at 128,
1265    // and the supported range of distances is [-4 * 127/128, 4].
1266    // Hence to convert to floats our multiplier (width of the range) is 4 * 255/128 = 7.96875
1267    // and zero threshold is 128/255 = 0.50196078431.
1268    half dist = 7.96875 * (texColor - 0.50196078431);
1269
1270    // We may further adjust the distance for gamma correction.
1271    dist -= gammaParams.x;
1272
1273    // After the distance is unpacked, we need to correct it by a factor dependent on the
1274    // current transformation. For general transforms, to determine the amount of correction
1275    // we multiply a unit vector pointing along the SDF gradient direction by the Jacobian of
1276    // unormTexCoords (which is the inverse transform for this fragment) and take the length of
1277    // the result.
1278    half2 dist_grad = half2(dFdx(dist), dFdy(dist));
1279    half dg_len2 = dot(dist_grad, dist_grad);
1280
1281    // The length of the gradient may be near 0, so we need to check for that. This also
1282    // compensates for the Adreno, which likes to drop tiles on division by 0.
1283    dist_grad = (dg_len2 >= 0.0001) ? dist_grad * inversesqrt(dg_len2)
1284                                    : half2(0.7071);
1285
1286    // Computing the Jacobian and multiplying by the gradient.
1287    float2x2 jacobian = float2x2(dFdx(unormTexCoords), dFdy(unormTexCoords));
1288    half2 grad = half2(jacobian * dist_grad);
1289
1290    // This gives us a smooth step across approximately one fragment.
1291    half approxFragWidth = 0.65 * length(grad);
1292
1293    // TODO: handle aliased rendering
1294    if (gammaParams.y > 0) {
1295        // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
1296        // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want
1297        // distance mapped linearly to coverage, so use a linear step:
1298        return half4(saturate((dist + approxFragWidth) / (2.0 * approxFragWidth)));
1299    } else {
1300        return half4(smoothstep(-approxFragWidth, approxFragWidth, dist));
1301    }
1302}
1303
1304$pure half4 sdf_text_lcd_coverage_fn(float2 textureCoords,
1305                                     half2 pixelGeometryDelta,
1306                                     half4 gammaParams,
1307                                     float2 unormTexCoords,
1308                                     float texIndex,
1309                                     sampler2D atlas0,
1310                                     sampler2D atlas1,
1311                                     sampler2D atlas2,
1312                                     sampler2D atlas3) {
1313    // TODO: To minimize the number of shaders generated this is the full affine shader.
1314    // For best performance it may be worth creating the uniform scale shader as well,
1315    // as that's the most common case.
1316
1317    float2x2 jacobian = float2x2(dFdx(unormTexCoords), dFdy(unormTexCoords));
1318    half2 offset = half2(jacobian * pixelGeometryDelta);
1319
1320    half3 distance = $sample_indexed_atlas_lcd(textureCoords,
1321                                               int(texIndex),
1322                                               offset,
1323                                               atlas0,
1324                                               atlas1,
1325                                               atlas2,
1326                                               atlas3);
1327    // The distance field is constructed as uchar8_t values, so that the zero value is at 128,
1328    // and the supported range of distances is [-4 * 127/128, 4].
1329    // Hence to convert to floats our multiplier (width of the range) is 4 * 255/128 = 7.96875
1330    // and zero threshold is 128/255 = 0.50196078431.
1331    half3 dist = half3(7.96875) * (distance - half3(0.50196078431));
1332
1333    // We may further adjust the distance for gamma correction.
1334    dist -= gammaParams.xyz;
1335
1336    // After the distance is unpacked, we need to correct it by a factor dependent on the
1337    // current transformation. For general transforms, to determine the amount of correction
1338    // we multiply a unit vector pointing along the SDF gradient direction by the Jacobian of
1339    // unormTexCoords (which is the inverse transform for this fragment) and take the length of
1340    // the result.
1341    half2 dist_grad = half2(dFdx(dist.g), dFdy(dist.g));
1342    half dg_len2 = dot(dist_grad, dist_grad);
1343
1344    // The length of the gradient may be near 0, so we need to check for that. This also
1345    // compensates for the Adreno, which likes to drop tiles on division by 0.
1346    dist_grad = (dg_len2 >= 0.0001) ? dist_grad * inversesqrt(dg_len2)
1347                                    : half2(0.7071);
1348
1349    // Multiplying the Jacobian by the gradient.
1350    half2 grad = half2(jacobian * dist_grad);
1351
1352    // This gives us a smooth step across approximately one fragment.
1353    half3 approxFragWidth = half3(0.65 * length(grad));
1354
1355    // TODO: handle aliased rendering
1356    if (gammaParams.w > 0) {
1357        // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
1358        // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want
1359        // distance mapped linearly to coverage, so use a linear step:
1360        return half4(saturate((dist + approxFragWidth / (2.0 * approxFragWidth))), 1);
1361    } else {
1362        return half4(smoothstep(half3(-approxFragWidth), half3(approxFragWidth), dist), 1);
1363    }
1364}
1365
1366///////////////////////////////////////////////////////////////////////////////////////////////////
1367// Support functions for analytic round rectangles
1368
1369// Calculates 1/|∇| in device space by applying the chain rule to a local gradient vector and the
1370// 2x2 Jacobian describing the transform from local-to-device space. For non-perspective, this is
1371// equivalent to the "normal matrix", or the inverse transpose. For perspective, J should be
1372//    W(u,v) [m00' - m20'u  m01' - m21'u] derived from the first two columns of the 3x3 inverse.
1373//           [m10' - m20'v  m11' - m21'v]
1374$pure float $inverse_grad_len(float2 localGrad, float2x2 jacobian) {
1375    // NOTE: By chain rule, the local gradient is on the left side of the Jacobian matrix
1376    float2 devGrad = localGrad * jacobian;
1377    // NOTE: This uses the L2 norm, which is more accurate than the L1 norm used by fwidth().
1378    // TODO: Switch to L1 since it is a 2x perf improvement according to Xcode with little visual
1379    // impact, but start with L2 to measure the change separately from the algorithmic update.
1380    // return 1.0 / (abs(devGrad.x) + abs(devGrad.y));
1381    return inversesqrt(dot(devGrad, devGrad));
1382}
1383
1384// Returns distance from both sides of a stroked circle or ellipse. Elliptical coverage is
1385// only accurate if strokeRadius = 0. A positive value represents the interior of the stroke.
1386$pure float2 $elliptical_distance(float2 uv, float2 radii, float strokeRadius, float2x2 jacobian) {
1387    // We do need to evaluate up to two circle equations: one with
1388    //    R = cornerRadius(r)+strokeRadius(s), and another with R = r-s.
1389    // This can be consolidated into a common evaluation against a circle of radius sqrt(r^2+s^2):
1390    //    (x/(r+/-s))^2 + (y/(r+/-s))^2 = 1
1391    //    x^2 + y^2 = (r+/-s)^2
1392    //    x^2 + y^2 = r^2 + s^2 +/- 2rs
1393    //    (x/sqrt(r^2+s^2))^2 + (y/sqrt(r^2+s^2)) = 1 +/- 2rs/(r^2+s^2)
1394    // The 2rs/(r^2+s^2) is the "width" that adjusts the implicit function to the outer or inner
1395    // edge of the stroke. For fills and hairlines, s = 0, which means these operations remain valid
1396    // for elliptical corners where radii holds the different X and Y corner radii.
1397    float2 invR2 = 1.0 / (radii * radii + strokeRadius*strokeRadius);
1398    float2 normUV = invR2 * uv;
1399    float invGradLength = $inverse_grad_len(normUV, jacobian);
1400
1401    // Since normUV already includes 1/r^2 in the denominator, dot with just 'uv' instead.
1402    float f = 0.5 * invGradLength * (dot(uv, normUV) - 1.0);
1403
1404    // This is 0 for fills/hairlines, which are the only types that allow
1405    // elliptical corners (strokeRadius == 0). For regular strokes just use X.
1406    float width = radii.x * strokeRadius * invR2.x * invGradLength;
1407    return float2(width - f, width + f);
1408}
1409
1410// Accumulates the minimum (and negative maximum) of the outer and inner corner distances in 'dist'
1411// for a possibly elliptical corner with 'radii' and relative pixel location specified by
1412// 'cornerEdgeDist'. The corner's basis relative to the jacobian is defined in 'xyFlip'.
1413void $corner_distance(inout float2 dist,
1414                      float2x2 jacobian,
1415                      float2 strokeParams,
1416                      float2 cornerEdgeDist,
1417                      float2 xyFlip,
1418                      float2 radii) {
1419    float2 uv = radii - cornerEdgeDist;
1420    // NOTE: For mitered corners uv > 0 only if it's stroked, and in that case the
1421    // subsequent conditions skip calculating anything.
1422    if (all(greaterThan(uv, float2(0.0)))) {
1423        if (all(greaterThan(radii, float2(0.0))) ||
1424            (strokeParams.x > 0.0 && strokeParams.y < 0.0 /* round-join */)) {
1425            // A rounded corner so incorporate outer elliptical distance if we're within the
1426            // quarter circle.
1427            float2 d = $elliptical_distance(uv * xyFlip, radii, strokeParams.x, jacobian);
1428            d.y = (radii.x - strokeParams.x <= 0.0)
1429                      ? 1.0    // Disregard inner curve since it's collapsed into an inner miter.
1430                      : -d.y;  // Negate so that "min" accumulates the maximum value instead.
1431            dist = min(dist, d);
1432        } else if (strokeParams.y == 0.0 /* bevel-join */) {
1433            // Bevels are--by construction--interior mitered, so inner distance is based
1434            // purely on the edge distance calculations, but the outer distance is to a 45-degree
1435            // line and not the vertical/horizontal lines of the other edges.
1436            float bevelDist = (strokeParams.x - uv.x - uv.y) * $inverse_grad_len(xyFlip, jacobian);
1437            dist.x = min(dist.x, bevelDist);
1438        } // Else it's a miter so both inner and outer distances are unmodified
1439    } // Else we're not affected by the corner so leave distances unmodified
1440}
1441
1442// Accumulates the minimum (and negative maximum) of the outer and inner corner distances into 'd',
1443// for all four corners of a [round] rectangle. 'edgeDists' should be ordered LTRB with positive
1444// distance representing the interior of the edge. 'xRadii' and 'yRadii' should hold the per-corner
1445// elliptical radii, ordered TL, TR, BR, BL.
1446void $corner_distances(inout float2 d,
1447                       float2x2 J,
1448                       float2 stroke, // {radii, joinStyle}, see StrokeStyle struct definition
1449                       float4 edgeDists,
1450                       float4 xRadii,
1451                       float4 yRadii) {
1452    $corner_distance(d, J, stroke, edgeDists.xy, float2(-1.0, -1.0), float2(xRadii[0], yRadii[0]));
1453    $corner_distance(d, J, stroke, edgeDists.zy, float2( 1.0, -1.0), float2(xRadii[1], yRadii[1]));
1454    $corner_distance(d, J, stroke, edgeDists.zw, float2( 1.0,  1.0), float2(xRadii[2], yRadii[2]));
1455    $corner_distance(d, J, stroke, edgeDists.xw, float2(-1.0,  1.0), float2(xRadii[3], yRadii[3]));
1456}
1457
1458$pure half4 analytic_rrect_coverage_fn(float4 coords,
1459                                       float4 jacobian,
1460                                       float4 edgeDistances,
1461                                       float4 xRadii,
1462                                       float4 yRadii,
1463                                       float2 strokeParams,
1464                                       float2 perPixelControl) {
1465    if (perPixelControl.x > 0.0) {
1466        // A trivially solid interior pixel, either from a filled rect or round rect, or a
1467        // stroke with sufficiently large width that the interior completely overlaps itself.
1468        return half4(1.0);
1469    } else if (perPixelControl.y > 1.0) {
1470        // This represents a filled rectangle or quadrilateral, where the distances have already
1471        // been converted to device space. Mitered strokes cannot use this optimization because
1472        // their scale and bias is not uniform over the shape; Rounded shapes cannot use this
1473        // because they rely on the edge distances being in local space to reconstruct the
1474        // per-corner positions for the elliptical implicit functions.
1475        float2 outerDist = min(edgeDistances.xy, edgeDistances.zw);
1476        float c = min(outerDist.x, outerDist.y) * coords.w;
1477        float scale = (perPixelControl.y - 1.0) * coords.w;
1478        float bias = coverage_bias(scale);
1479        return half4(saturate(scale * (c + bias)));
1480    } else {
1481        // Compute per-pixel coverage, mixing four outer edge distances, possibly four inner
1482        // edge distances, and per-corner elliptical distances into a final coverage value.
1483        // The Jacobian needs to be multiplied by W, but coords.w stores 1/w.
1484        float2x2 J = float2x2(jacobian) / coords.w;
1485
1486        float2 invGradLen = float2($inverse_grad_len(float2(1.0, 0.0), J),
1487                                   $inverse_grad_len(float2(0.0, 1.0), J));
1488        float2 outerDist = invGradLen * (strokeParams.x + min(edgeDistances.xy,
1489                                                              edgeDistances.zw));
1490
1491        // d.x tracks minimum outer distance (pre scale-and-biasing to a coverage value).
1492        // d.y tracks negative maximum inner distance (so min() over c accumulates min and outer
1493        // and max inner simultaneously).)
1494        float2 d = float2(min(outerDist.x, outerDist.y), -1.0);
1495        float scale, bias;
1496
1497        // Check for bidirectional coverage, which is is marked as a -1 from the vertex shader.
1498        // We don't just check for < 0 since extrapolated fill triangle samples can have small
1499        // negative values.
1500        if (perPixelControl.x > -0.95) {
1501            // A solid interior, so update scale and bias based on full width and height
1502            float2 dim = invGradLen * (edgeDistances.xy + edgeDistances.zw + 2*strokeParams.xx);
1503            scale = min(min(dim.x, dim.y), 1.0);
1504            bias = coverage_bias(scale);
1505            // Since we leave d.y = -1.0, no inner curve coverage will adjust it closer to 0,
1506            // so 'finalCoverage' is based solely on outer edges and curves.
1507        } else {
1508            // Bidirectional coverage, so we modify c.y to hold the negative of the maximum
1509            // interior coverage, and update scale and bias based on stroke width.
1510            float2 strokeWidth = 2.0 * strokeParams.x * invGradLen;
1511            float2 innerDist = strokeWidth - outerDist;
1512
1513            d.y = -max(innerDist.x, innerDist.y);
1514            if (strokeParams.x > 0.0) {
1515                float narrowStroke = min(strokeWidth.x, strokeWidth.y);
1516                // On an axis where innerDist >= -0.5, allow strokeWidth.x/y to be preserved as-is.
1517                // On an axis where innerDist < -0.5, use the smaller of strokeWidth.x/y.
1518                float2 strokeDim = mix(float2(narrowStroke), strokeWidth,
1519                                       greaterThanEqual(innerDist, float2(-0.5)));
1520                // Preserve the wider axis from the above calculation.
1521                scale = saturate(max(strokeDim.x, strokeDim.y));
1522                bias = coverage_bias(scale);
1523            } else {
1524                // A hairline, so scale and bias should both be 1
1525                 scale = bias = 1.0;
1526            }
1527        }
1528
1529        // Check all corners, although most pixels should only be influenced by 1.
1530        $corner_distances(d, J, strokeParams, edgeDistances, xRadii, yRadii);
1531
1532        float outsetDist = min(perPixelControl.y, 0.0) * coords.w;
1533        float finalCoverage = scale * (min(d.x + outsetDist, -d.y) + bias);
1534
1535        return half4(saturate(finalCoverage));
1536    }
1537}
1538
1539$pure half4 per_edge_aa_quad_coverage_fn(float4 coords,
1540                                         float4 edgeDistances) {
1541    // This represents a filled rectangle or quadrilateral, where the distances have already
1542    // been converted to device space.
1543    float2 outerDist = min(edgeDistances.xy, edgeDistances.zw);
1544    float c = min(outerDist.x, outerDist.y) * coords.w;
1545    return half4(saturate(c));
1546}
1547
1548$pure half4 circular_arc_coverage_fn(float4 circleEdge,
1549                                     float3 clipPlane,
1550                                     float3 isectPlane,
1551                                     float3 unionPlane,
1552                                     float roundCapRadius,
1553                                     float4 roundCapPos) {
1554    float d = length(circleEdge.xy);
1555    half distanceToOuterEdge = half(circleEdge.z * (1.0 - d));
1556    half edgeAlpha = saturate(distanceToOuterEdge);
1557    half distanceToInnerEdge = half(circleEdge.z * (d - circleEdge.w));
1558    half innerAlpha = saturate(distanceToInnerEdge);
1559    edgeAlpha *= innerAlpha;
1560
1561    half clip = half(saturate(circleEdge.z * dot(circleEdge.xy, clipPlane.xy) + clipPlane.z));
1562    clip *= half(saturate(circleEdge.z * dot(circleEdge.xy, isectPlane.xy) + isectPlane.z));
1563    clip = clip + half(saturate(circleEdge.z * dot(circleEdge.xy, unionPlane.xy) + unionPlane.z));
1564
1565    // We compute coverage of the round caps as circles at the butt caps produced
1566    // by the clip planes, and union it with the current clip.
1567    half dcap1 = half(circleEdge.z * (roundCapRadius - length(circleEdge.xy - roundCapPos.xy)));
1568    half dcap2 = half(circleEdge.z * (roundCapRadius - length(circleEdge.xy - roundCapPos.zw)));
1569    half capAlpha = max(dcap1, 0) + max(dcap2, 0);
1570    clip = saturate(clip + capAlpha);
1571
1572    return half4(clip*edgeAlpha);
1573}
1574
1575$pure half4 $rect_blur_coverage_fn(float2 coords,
1576                                   float4 rect,
1577                                   half isFast,
1578                                   half invSixSigma,
1579                                   sampler2D integral) {
1580    half xCoverage;
1581    half yCoverage;
1582
1583    if (isFast != 0.0) {
1584        // Get the smaller of the signed distance from the frag coord to the left and right
1585        // edges and similar for y.
1586        // The integral texture goes "backwards" (from 3*sigma to -3*sigma), So, the below
1587        // computations align the left edge of the integral texture with the inset rect's
1588        // edge extending outward 6 * sigma from the inset rect.
1589        half2 pos = max(half2(rect.LT - coords), half2(coords - rect.RB));
1590        xCoverage = sample(integral, float2(invSixSigma * pos.x, 0.5)).r;
1591        yCoverage = sample(integral, float2(invSixSigma * pos.y, 0.5)).r;
1592
1593    } else {
1594        // We just consider just the x direction here. In practice we compute x and y
1595        // separately and multiply them together.
1596        // We define our coord system so that the point at which we're evaluating a kernel
1597        // defined by the normal distribution (K) at 0. In this coord system let L be left
1598        // edge and R be the right edge of the rectangle.
1599        // We can calculate C by integrating K with the half infinite ranges outside the
1600        // L to R range and subtracting from 1:
1601        //   C = 1 - <integral of K from from -inf to  L> - <integral of K from R to inf>
1602        // K is symmetric about x=0 so:
1603        //   C = 1 - <integral of K from from -inf to  L> - <integral of K from -inf to -R>
1604
1605        // The integral texture goes "backwards" (from 3*sigma to -3*sigma) which is
1606        // factored in to the below calculations.
1607        // Also, our rect uniform was pre-inset by 3 sigma from the actual rect being
1608        // blurred, also factored in.
1609        half4 rect = half4(half2(rect.LT - coords), half2(coords - rect.RB));
1610        xCoverage = 1 - sample(integral, float2(invSixSigma * rect.L, 0.5)).r
1611                      - sample(integral, float2(invSixSigma * rect.R, 0.5)).r;
1612        yCoverage = 1 - sample(integral, float2(invSixSigma * rect.T, 0.5)).r
1613                      - sample(integral, float2(invSixSigma * rect.B, 0.5)).r;
1614    }
1615
1616    return half4(xCoverage * yCoverage);
1617}
1618
1619$pure half4 $circle_blur_coverage_fn(float2 coords, float4 circle, sampler2D blurProfile) {
1620    // We just want to compute "(length(vec) - solidRadius + 0.5) / textureRadius" but need to
1621    // rearrange to avoid passing large values to length() that would overflow. We've precalculated
1622    // "1 / textureRadius" and "(solidRadius - 0.5) / textureRadius" on the CPU as circle.z and
1623    // circle.w, respectively.
1624    float invTextureRadius = circle.z;
1625    float normSolidRadius = circle.w;
1626
1627    half2 vec = half2((coords - circle.xy) * invTextureRadius);
1628    float dist = length(vec) - normSolidRadius;
1629    return sample(blurProfile, float2(dist, 0.5)).rrrr;
1630}
1631
1632$pure half4 $rrect_blur_coverage_fn(float2 coords,
1633                                    float4 proxyRect,
1634                                    half edgeSize,
1635                                    sampler2D ninePatch) {
1636    // Warp the fragment position to the appropriate part of the 9-patch blur texture by
1637    // snipping out the middle section of the proxy rect.
1638    float2 translatedFragPosFloat = coords - proxyRect.LT;
1639    float2 proxyCenter = (proxyRect.RB - proxyRect.LT) * 0.5;
1640
1641    // Position the fragment so that (0, 0) marks the center of the proxy rectangle.
1642    // Negative coordinates are on the left/top side and positive numbers are on the
1643    // right/bottom.
1644    translatedFragPosFloat -= proxyCenter;
1645
1646    // Temporarily strip off the fragment's sign. x/y are now strictly increasing as we
1647    // move away from the center.
1648    half2 fragDirection = half2(sign(translatedFragPosFloat));
1649    translatedFragPosFloat = abs(translatedFragPosFloat);
1650
1651    // Our goal is to snip out the "middle section" of the proxy rect (everything but the
1652    // edge). We've repositioned our fragment position so that (0, 0) is the centerpoint
1653    // and x/y are always positive, so we can subtract here and interpret negative results
1654    // as being within the middle section.
1655    half2 translatedFragPosHalf = half2(translatedFragPosFloat - (proxyCenter - edgeSize));
1656
1657    // Remove the middle section by clamping to zero.
1658    translatedFragPosHalf = max(translatedFragPosHalf, 0);
1659
1660    // Reapply the fragment's sign, so that negative coordinates once again mean left/top
1661    // side and positive means bottom/right side.
1662    translatedFragPosHalf *= fragDirection;
1663
1664    // Offset the fragment so that (0, 0) marks the upper-left again, instead of the center
1665    // point.
1666    translatedFragPosHalf += half2(edgeSize);
1667
1668    half2 proxyDims = half2(2.0 * edgeSize);
1669    half2 texCoord = translatedFragPosHalf / proxyDims;
1670
1671    return sample(ninePatch, texCoord).rrrr;
1672}
1673
1674$pure half4 blur_coverage_fn(float2 coords,
1675                             float4 shapeData,
1676                             half2 blurData,
1677                             int shapeType,
1678                             sampler2D s) {
1679    switch (shapeType) {
1680        case $kShapeTypeRect: {
1681            return $rect_blur_coverage_fn(coords, shapeData, blurData.x, blurData.y, s);
1682        }
1683        case $kShapeTypeCircle: {
1684            return $circle_blur_coverage_fn(coords, shapeData, s);
1685        }
1686        case $kShapeTypeRRect: {
1687            return $rrect_blur_coverage_fn(coords, shapeData, blurData.x, s);
1688        }
1689    }
1690    return half4(0);
1691}
1692