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