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