• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2024 Google LLC
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/core/SkKnownRuntimeEffects.h"
9 
10 #include "include/core/SkString.h"
11 #include "include/effects/SkRuntimeEffect.h"
12 #include "src/core/SkRuntimeEffectPriv.h"
13 #include "src/effects/imagefilters/SkMatrixConvolutionImageFilter.h"
14 
15 namespace SkKnownRuntimeEffects {
16 
17 namespace {
18 
19 // This must be kept in sync w/ the version in BlurUtils.h
20 static constexpr int kMaxBlurSamples = 28;
21 
make_blur_1D_effect(int kernelWidth,const SkRuntimeEffect::Options & options)22 SkRuntimeEffect* make_blur_1D_effect(int kernelWidth, const SkRuntimeEffect::Options& options) {
23     SkASSERT(kernelWidth <= kMaxBlurSamples);
24     // The SkSL structure performs two kernel taps; if the kernel has an odd width the last
25     // sample will be skipped with the current loop limit calculation.
26     SkASSERT(kernelWidth % 2 == 0);
27     return SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
28             SkStringPrintf(
29                     // The coefficients are always stored for the max radius to keep the
30                     // uniform block consistent across all effects.
31                     "const int kMaxUniformKernelSize = %d / 2;"
32                     // But we generate an exact loop over the kernel size. Note that this
33                     // program can be used for kernels smaller than the constructed max as long
34                     // as the kernel weights for excess entries are set to 0.
35                     "const int kMaxLoopLimit = %d / 2;"
36 
37                     "uniform half4 offsetsAndKernel[kMaxUniformKernelSize];"
38                     "uniform half2 dir;"
39 
40                     "uniform shader child;"
41 
42                     "half4 main(float2 coord) {"
43                         "half4 sum = half4(0);"
44                         "for (int i = 0; i < kMaxLoopLimit; ++i) {"
45                             "half4 s = offsetsAndKernel[i];"
46                             "sum += s.y * child.eval(coord + s.x*dir);"
47                             "sum += s.w * child.eval(coord + s.z*dir);"
48                         "}"
49                         "return sum;"
50                     "}", kMaxBlurSamples, kernelWidth).c_str(),
51                     options);
52 }
53 
make_blur_2D_effect(int maxKernelSize,const SkRuntimeEffect::Options & options)54 SkRuntimeEffect* make_blur_2D_effect(int maxKernelSize, const SkRuntimeEffect::Options& options) {
55     SkASSERT(maxKernelSize % 4 == 0);
56     return SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
57             SkStringPrintf(
58                     // The coefficients are always stored for the max radius to keep the
59                     // uniform block consistent across all effects.
60                     "const int kMaxUniformKernelSize = %d / 4;"
61                     "const int kMaxUniformOffsetsSize = 2*kMaxUniformKernelSize;"
62                     // But we generate an exact loop over the kernel size. Note that this
63                     // program can be used for kernels smaller than the constructed max as long
64                     // as the kernel weights for excess entries are set to 0.
65                     "const int kMaxLoopLimit = %d / 4;"
66 
67                     // Pack scalar coefficients into half4 for better packing on std140, and
68                     // upload offsets to avoid having to transform the 1D index into a 2D coord
69                     "uniform half4 kernel[kMaxUniformKernelSize];"
70                     "uniform half4 offsets[kMaxUniformOffsetsSize];"
71 
72                     "uniform shader child;"
73 
74                     "half4 main(float2 coord) {"
75                         "half4 sum = half4(0);"
76 
77                         "for (int i = 0; i < kMaxLoopLimit; ++i) {"
78                             "half4 k = kernel[i];"
79                             "half4 o = offsets[2*i];"
80                             "sum += k.x * child.eval(coord + o.xy);"
81                             "sum += k.y * child.eval(coord + o.zw);"
82                             "o = offsets[2*i + 1];"
83                             "sum += k.z * child.eval(coord + o.xy);"
84                             "sum += k.w * child.eval(coord + o.zw);"
85                         "}"
86                         "return sum;"
87                     "}", kMaxBlurSamples, maxKernelSize).c_str(),
88                     options);
89 }
90 
91 enum class MatrixConvolutionImpl {
92     kUniformBased,
93     kTextureBasedSm,
94     kTextureBasedLg,
95 };
96 
97 // There are three shader variants:
98 //    a smaller kernel version that stores the matrix in uniforms and iterates in 1D
99 //    a larger kernel version that stores the matrix in a 1D texture. The texture version has small
100 //    and large variants w/ the actual kernel size uploaded as a uniform.
make_matrix_conv_effect(MatrixConvolutionImpl impl,const SkRuntimeEffect::Options & options)101 SkRuntimeEffect* make_matrix_conv_effect(MatrixConvolutionImpl impl,
102                                          const SkRuntimeEffect::Options& options) {
103     // While the uniforms and kernel access are different, pieces of the algorithm are common and
104     // defined statically for re-use in the two shaders:
105     static const char* kHeaderAndBeginLoopSkSL =
106         "uniform int2 size;"
107         "uniform int2 offset;"
108         "uniform half2 gainAndBias;"
109         "uniform int convolveAlpha;" // FIXME not a full  int? Put in a half3 w/ gainAndBias?
110 
111         "uniform shader child;"
112 
113         "half4 main(float2 coord) {"
114             "half4 sum = half4(0);"
115             "half origAlpha = 0;"
116             "int2 kernelPos = int2(0);"
117             "for (int i = 0; i < kMaxKernelSize; ++i) {"
118                 "if (kernelPos.y >= size.y) { break; }";
119 
120     // Used in the inner loop to accumulate convolution sum and increment the kernel position
121     static const char* kAccumulateAndIncrementSkSL =
122                 "half4 c = child.eval(coord + half2(kernelPos) - half2(offset));"
123                 "if (convolveAlpha == 0) {"
124                     // When not convolving alpha, remember the original alpha for actual sample
125                     // coord, and perform accumulation on unpremul colors.
126                     "if (kernelPos == offset) {"
127                         "origAlpha = c.a;"
128                     "}"
129                     "c = unpremul(c);"
130                 "}"
131                 "sum += c*k;"
132                 "kernelPos.x += 1;"
133                 "if (kernelPos.x >= size.x) {"
134                     "kernelPos.x = 0;"
135                     "kernelPos.y += 1;"
136                 "}";
137 
138     // Closes the loop and calculates final color
139     static const char* kCloseLoopAndFooterSkSL =
140             "}"
141             "half4 color = sum*gainAndBias.x + gainAndBias.y;"
142             "if (convolveAlpha == 0) {"
143                 // Reset the alpha to the original and convert to premul RGB
144                 "color = half4(color.rgb*origAlpha, origAlpha);"
145             "} else {"
146                 // Ensure convolved alpha is within [0, 1]
147                 "color.a = saturate(color.a);"
148             "}"
149             // Make RGB valid premul w/ respect to the alpha (either original or convolved)
150             "color.rgb = clamp(color.rgb, 0, color.a);"
151             "return color;"
152         "}";
153 
154     static const auto makeTextureEffect = [](int maxTextureKernelSize,
155                                              const SkRuntimeEffect::Options& options) {
156         return SkMakeRuntimeEffect(
157                         SkRuntimeEffect::MakeForShader,
158                         SkStringPrintf("const int kMaxKernelSize = %d;"
159                                        "uniform shader kernel;"
160                                        "uniform half2 innerGainAndBias;"
161                                        "%s" // kHeaderAndBeginLoopSkSL
162                                                "half k = kernel.eval(half2(half(i) + 0.5, 0.5)).a;"
163                                                "k = k * innerGainAndBias.x + innerGainAndBias.y;"
164                                                "%s" // kAccumulateAndIncrementSkSL
165                                        "%s", // kCloseLoopAndFooterSkSL
166                                        maxTextureKernelSize,
167                                        kHeaderAndBeginLoopSkSL,
168                                        kAccumulateAndIncrementSkSL,
169                                        kCloseLoopAndFooterSkSL).c_str(),
170                         options);
171     };
172 
173     switch (impl) {
174         case MatrixConvolutionImpl::kUniformBased: {
175             return SkMakeRuntimeEffect(
176                         SkRuntimeEffect::MakeForShader,
177                         SkStringPrintf("const int kMaxKernelSize = %d / 4;"
178                                        "uniform half4 kernel[kMaxKernelSize];"
179                                        "%s" // kHeaderAndBeginLoopSkSL
180                                                 "half4 k4 = kernel[i];"
181                                                 "for (int j = 0; j < 4; ++j) {"
182                                                     "if (kernelPos.y >= size.y) { break; }"
183                                                     "half k = k4[j];"
184                                                     "%s" // kAccumulateAndIncrementSkSL
185                                                 "}"
186                                        "%s", // kCloseLoopAndFooterSkSL
187                                        MatrixConvolutionImageFilter::kMaxUniformKernelSize,
188                                        kHeaderAndBeginLoopSkSL,
189                                        kAccumulateAndIncrementSkSL,
190                                        kCloseLoopAndFooterSkSL).c_str(),
191                         options);
192         }
193         case MatrixConvolutionImpl::kTextureBasedSm:
194             return makeTextureEffect(MatrixConvolutionImageFilter::kSmallKernelSize, options);
195         case MatrixConvolutionImpl::kTextureBasedLg:
196             return makeTextureEffect(MatrixConvolutionImageFilter::kLargeKernelSize, options);
197     }
198 
199     SkUNREACHABLE;
200 }
201 
202 } // anonymous namespace
203 
GetKnownRuntimeEffect(StableKey stableKey)204 const SkRuntimeEffect* GetKnownRuntimeEffect(StableKey stableKey) {
205     SkRuntimeEffect::Options options;
206     SkRuntimeEffectPriv::SetStableKey(&options, static_cast<uint32_t>(stableKey));
207 
208     switch (stableKey) {
209         case StableKey::kInvalid:
210             return nullptr;
211 
212         // Shaders
213         case StableKey::k1DBlur4: {
214             static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(4, options);
215             return s1DBlurEffect;
216         }
217         case StableKey::k1DBlur8: {
218             static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(8, options);
219             return s1DBlurEffect;
220         }
221         case StableKey::k1DBlur12: {
222             static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(12, options);
223             return s1DBlurEffect;
224         }
225         case StableKey::k1DBlur16: {
226             static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(16, options);
227             return s1DBlurEffect;
228         }
229         case StableKey::k1DBlur20: {
230             static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(20, options);
231             return s1DBlurEffect;
232         }
233         case StableKey::k1DBlur28: {
234             static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(28, options);
235             return s1DBlurEffect;
236         }
237         case StableKey::k2DBlur4: {
238             static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(4, options);
239             return s2DBlurEffect;
240         }
241         case StableKey::k2DBlur8: {
242             static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(8, options);
243             return s2DBlurEffect;
244         }
245         case StableKey::k2DBlur12: {
246             static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(12, options);
247             return s2DBlurEffect;
248         }
249         case StableKey::k2DBlur16: {
250             static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(16, options);
251             return s2DBlurEffect;
252         }
253         case StableKey::k2DBlur20: {
254             static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(20, options);
255             return s2DBlurEffect;
256         }
257         case StableKey::k2DBlur28: {
258             static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(28, options);
259             return s2DBlurEffect;
260         }
261         case StableKey::kBlend: {
262             static constexpr char kBlendShaderCode[] =
263                 "uniform shader s, d;"
264                 "uniform blender b;"
265                 "half4 main(float2 xy) {"
266                     "return b.eval(s.eval(xy), d.eval(xy));"
267                 "}";
268 
269             static const SkRuntimeEffect* sBlendEffect =
270                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
271                                         kBlendShaderCode,
272                                         options);
273             return sBlendEffect;
274         }
275         case StableKey::kDecal: {
276             static constexpr char kDecalShaderCode[] =
277                 "uniform shader image;"
278                 "uniform float4 decalBounds;"
279 
280                 "half4 main(float2 coord) {"
281                     "half4 d = half4(decalBounds - coord.xyxy) * half4(-1, -1, 1, 1);"
282                     "d = saturate(d + 0.5);"
283                     "return (d.x*d.y*d.z*d.w) * image.eval(coord);"
284                 "}";
285 
286             static const SkRuntimeEffect* sDecalEffect =
287                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
288                                         kDecalShaderCode,
289                                         options);
290             return sDecalEffect;
291         }
292         case StableKey::kDisplacement: {
293             // NOTE: This uses dot product selection to work on all GLES2 hardware (enforced by
294             // public runtime effect restrictions). Otherwise, this would use a "uniform ivec2"
295             // and component indexing to convert the displacement color into a vector.
296             static constexpr char kDisplacementShaderCode[] =
297                 "uniform shader displMap;"
298                 "uniform shader colorMap;"
299                 "uniform half2 scale;"
300                 "uniform half4 xSelect;" // Only one of RGBA will be 1, the rest are 0
301                 "uniform half4 ySelect;"
302 
303                 "half4 main(float2 coord) {"
304                     "half4 displColor = unpremul(displMap.eval(coord));"
305                     "half2 displ = half2(dot(displColor, xSelect), dot(displColor, ySelect));"
306                     "displ = scale * (displ - 0.5);"
307                     "return colorMap.eval(coord + displ);"
308                 "}";
309 
310             static const SkRuntimeEffect* sDisplacementEffect =
311                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
312                                         kDisplacementShaderCode,
313                                         options);
314             return sDisplacementEffect;
315         }
316         case StableKey::kLighting: {
317             static constexpr char kLightingShaderCode[] =
318                 "const half kConeAAThreshold = 0.016;"
319                 "const half kConeScale = 1.0 / kConeAAThreshold;"
320 
321                 "uniform shader normalMap;"
322 
323                 // Packs surface depth, shininess, material type (0 == diffuse) and light type
324                 // (< 0 = distant, 0 = point, > 0 = spot)
325                 "uniform half4 materialAndLightType;"
326 
327                 "uniform half4 lightPosAndSpotFalloff;" // (x,y,z) are lightPos, w is spot falloff
328                                                         // exponent
329                 "uniform half4 lightDirAndSpotCutoff;" // (x,y,z) are lightDir,
330                                                        // w is spot cos(cutoffAngle)
331                 "uniform half3 lightColor;" // Material's k has already been multiplied in
332 
333                 "half3 surface_to_light(half3 coord) {"
334                     "if (materialAndLightType.w < 0) {"
335                         "return lightDirAndSpotCutoff.xyz;"
336                     "} else {"
337                         // Spot and point have the same equation
338                         "return normalize(lightPosAndSpotFalloff.xyz - coord);"
339                     "}"
340                 "}"
341 
342                 "half spotlight_scale(half3 surfaceToLight) {"
343                     "half cosCutoffAngle = lightDirAndSpotCutoff.w;"
344                     "half cosAngle = -dot(surfaceToLight, lightDirAndSpotCutoff.xyz);"
345                     "if (cosAngle < cosCutoffAngle) {"
346                         "return 0.0;"
347                     "}"
348                     "half scale = pow(cosAngle, lightPosAndSpotFalloff.w);"
349                     "if (cosAngle < cosCutoffAngle + kConeAAThreshold) {"
350                         "return scale * (cosAngle - cosCutoffAngle) * kConeScale;"
351                     "} else {"
352                         "return scale;"
353                     "}"
354                 "}"
355 
356                 "half4 compute_lighting(half3 normal, half3 surfaceToLight) {"
357                     // Point and distant light color contributions are constant
358                     "half3 color = lightColor;"
359                     // Spotlights fade based on the angle away from its direction
360                     "if (materialAndLightType.w > 0) {"
361                         "color *= spotlight_scale(surfaceToLight);"
362                     "}"
363 
364                     // Diffuse and specular reflections scale the light's "color" differently
365                     "if (materialAndLightType.z == 0) {"
366                         "half coeff = dot(normal, surfaceToLight);"
367                         "color = saturate(coeff * color);"
368                         "return half4(color, 1.0);"
369                     "} else {"
370                         "half3 halfDir = normalize(surfaceToLight + half3(0, 0, 1));"
371                         "half shininess = materialAndLightType.y;"
372                         "half coeff = pow(dot(normal, halfDir), shininess);"
373                         "color = saturate(coeff * color);"
374                         "return half4(color, max(max(color.r, color.g), color.b));"
375                     "}"
376                 "}"
377 
378                 "half4 main(float2 coord) {"
379                     "half4 normalAndA = normalMap.eval(coord);"
380                     "half depth = materialAndLightType.x;"
381                     "half3 surfaceToLight = surface_to_light(half3(half2(coord),"
382                                                                   "depth*normalAndA.a));"
383                     "return compute_lighting(normalAndA.xyz, surfaceToLight);"
384                 "}";
385 
386             static const SkRuntimeEffect* sLightingEffect =
387                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
388                                         kLightingShaderCode,
389                                         options);
390             return sLightingEffect;
391         }
392         case StableKey::kLinearMorphology: {
393             static constexpr char kLinearMorphologyShaderCode[] =
394                 // KEEP IN SYNC WITH SkMorphologyImageFilter.cpp DEFINITION
395                 "const int kMaxLinearRadius = 14;"
396 
397                 "uniform shader child;"
398                 "uniform half2 offset;"
399                 "uniform half flip;" // -1 converts the max() calls to min()
400                 "uniform int radius;"
401 
402                 "half4 main(float2 coord) {"
403                     "half4 aggregate = flip*child.eval(coord);" // case 0 only samples once
404                     "for (int i = 1; i <= kMaxLinearRadius; ++i) {"
405                         "if (i > radius) break;"
406                         "half2 delta = half(i) * offset;"
407                         "aggregate = max(aggregate, max(flip*child.eval(coord + delta),"
408                                                        "flip*child.eval(coord - delta)));"
409                     "}"
410                     "return flip*aggregate;"
411                 "}";
412 
413             static const SkRuntimeEffect* sLinearMorphologyEffect =
414                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
415                                         kLinearMorphologyShaderCode,
416                                         options);
417             return sLinearMorphologyEffect;
418         }
419 
420         case StableKey::kMagnifier: {
421             static constexpr char kMagnifierShaderCode[] =
422                 "uniform shader src;"
423                 "uniform float4 lensBounds;"
424                 "uniform float4 zoomXform;"
425                 "uniform float2 invInset;"
426 
427                 "half4 main(float2 coord) {"
428                     "float2 zoomCoord = zoomXform.xy + zoomXform.zw*coord;"
429                     // edgeInset is the smallest distance to the lens bounds edges,
430                     // in units of "insets".
431                     "float2 edgeInset = min(coord - lensBounds.xy, lensBounds.zw - coord) *"
432                                        "invInset;"
433 
434                     // The equations for 'weight' ensure that it is 0 along the outside of
435                     // lensBounds so it seams with any un-zoomed, un-filtered content. The zoomed
436                     // content fills a rounded rectangle that is 1 "inset" in from lensBounds with
437                     // circular corners with radii equal to the inset distance. Outside of this
438                     // region, there is a non-linear weighting to compress the un-zoomed content
439                     // to the zoomed content. The critical zone about each corner is limited
440                     // to 2x"inset" square.
441                     "float weight = (edgeInset.x < 2.0 && edgeInset.y < 2.0)"
442                         // Circular distortion weighted by distance to inset corner
443                         "? (2.0 - length(2.0 - edgeInset))"
444                         // Linear zoom, or single-axis compression outside of the inset
445                         // area (if delta < 1)
446                         ": min(edgeInset.x, edgeInset.y);"
447 
448                     // Saturate before squaring so that negative weights are clamped to 0
449                     // before squaring
450                     "weight = saturate(weight);"
451                     "return src.eval(mix(coord, zoomCoord, weight*weight));"
452                 "}";
453 
454             static const SkRuntimeEffect* sMagnifierEffect =
455                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
456                                         kMagnifierShaderCode,
457                                         options);
458             return sMagnifierEffect;
459         }
460 
461         case StableKey::kMatrixConvUniforms: {
462             static const SkRuntimeEffect* sMatrixConvUniformsEffect =
463                     make_matrix_conv_effect(MatrixConvolutionImpl::kUniformBased, options);
464             return sMatrixConvUniformsEffect;
465         }
466 
467         case StableKey::kMatrixConvTexSm: {
468             static const SkRuntimeEffect* sMatrixConvTexSmEffect =
469                     make_matrix_conv_effect(MatrixConvolutionImpl::kTextureBasedSm, options);
470             return sMatrixConvTexSmEffect;
471         }
472 
473         case StableKey::kMatrixConvTexLg: {
474             static const SkRuntimeEffect* sMatrixConvTexMaxEffect =
475                     make_matrix_conv_effect(MatrixConvolutionImpl::kTextureBasedLg, options);
476             return sMatrixConvTexMaxEffect;
477         }
478 
479         case StableKey::kNormal: {
480             static constexpr char kNormalShaderCode[] =
481                 "uniform shader alphaMap;"
482                 "uniform float4 edgeBounds;"
483                 "uniform half negSurfaceDepth;"
484 
485                 "half3 normal(half3 alphaC0, half3 alphaC1, half3 alphaC2) {"
486                     // The right column (or bottom row) terms of the Sobel filter. The left/top is
487                     // just the negative, and the middle row/column is all 0s so those instructions
488                     // are skipped.
489                     "const half3 kSobel = 0.25 * half3(1,2,1);"
490                     "half3 alphaR0 = half3(alphaC0.x, alphaC1.x, alphaC2.x);"
491                     "half3 alphaR2 = half3(alphaC0.z, alphaC1.z, alphaC2.z);"
492                     "half nx = dot(kSobel, alphaC2) - dot(kSobel, alphaC0);"
493                     "half ny = dot(kSobel, alphaR2) - dot(kSobel, alphaR0);"
494                     "return normalize(half3(negSurfaceDepth * half2(nx, ny), 1));"
495                 "}"
496 
497                 "half4 main(float2 coord) {"
498                    "half3 alphaC0 = half3("
499                      "alphaMap.eval(clamp(coord + float2(-1,-1), edgeBounds.LT, edgeBounds.RB)).a,"
500                      "alphaMap.eval(clamp(coord + float2(-1, 0), edgeBounds.LT, edgeBounds.RB)).a,"
501                      "alphaMap.eval(clamp(coord + float2(-1, 1), edgeBounds.LT, edgeBounds.RB)).a);"
502                    "half3 alphaC1 = half3("
503                      "alphaMap.eval(clamp(coord + float2( 0,-1), edgeBounds.LT, edgeBounds.RB)).a,"
504                      "alphaMap.eval(clamp(coord + float2( 0, 0), edgeBounds.LT, edgeBounds.RB)).a,"
505                      "alphaMap.eval(clamp(coord + float2( 0, 1), edgeBounds.LT, edgeBounds.RB)).a);"
506                    "half3 alphaC2 = half3("
507                      "alphaMap.eval(clamp(coord + float2( 1,-1), edgeBounds.LT, edgeBounds.RB)).a,"
508                      "alphaMap.eval(clamp(coord + float2( 1, 0), edgeBounds.LT, edgeBounds.RB)).a,"
509                      "alphaMap.eval(clamp(coord + float2( 1, 1), edgeBounds.LT, edgeBounds.RB)).a);"
510 
511                    "half mainAlpha = alphaC1.y;" // offset = (0,0)
512                    "return half4(normal(alphaC0, alphaC1, alphaC2), mainAlpha);"
513                 "}";
514 
515             static const SkRuntimeEffect* sNormalEffect =
516                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
517                                         kNormalShaderCode,
518                                         options);
519             return sNormalEffect;
520         }
521         case StableKey::kSparseMorphology: {
522             static constexpr char kSparseMorphologyShaderCode[] =
523                 "uniform shader child;"
524                 "uniform half2 offset;"
525                 "uniform half flip;"
526 
527                 "half4 main(float2 coord) {"
528                     "half4 aggregate = max(flip*child.eval(coord + offset),"
529                                           "flip*child.eval(coord - offset));"
530                     "return flip*aggregate;"
531                 "}";
532 
533             static const SkRuntimeEffect* sSparseMorphologyEffect =
534                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
535                                         kSparseMorphologyShaderCode,
536                                         options);
537             return sSparseMorphologyEffect;
538         }
539 
540         // Blenders
541         case StableKey::kArithmetic: {
542             static constexpr char kArithmeticBlenderCode[] =
543                 "uniform half4 k;"
544                 "uniform half pmClamp;"
545 
546                 "half4 main(half4 src, half4 dst) {"
547                     "half4 c = saturate(k.x * src * dst + k.y * src + k.z * dst + k.w);"
548                     "c.rgb = min(c.rgb, max(c.a, pmClamp));"
549                     "return c;"
550                 "}";
551 
552             static const SkRuntimeEffect* sArithmeticEffect =
553                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForBlender,
554                                         kArithmeticBlenderCode,
555                                         options);
556             return sArithmeticEffect;
557         }
558 
559         // Color Filters
560         case StableKey::kHighContrast: {
561             static constexpr char kHighContrastFilterCode[] =
562                 "uniform half grayscale, invertStyle, contrast;"
563 
564                 "half3 rgb_to_hsl(half3 c) {"
565                     "half mx = max(max(c.r,c.g),c.b),"
566                          "mn = min(min(c.r,c.g),c.b),"
567                           "d = mx-mn,"
568                        "invd = 1.0 / d,"
569                      "g_lt_b = c.g < c.b ? 6.0 : 0.0;"
570 
571                 // We'd prefer to write these tests like `mx == c.r`, but on some GPUs max(x,y) is
572                 // not always equal to either x or y. So we use long form, c.r >= c.g && c.r >= c.b.
573                     "half h = (1/6.0) * (mx == mn"               "? 0.0 :"
574                          /*mx==c.r*/    "c.r >= c.g && c.r >= c.b ? invd * (c.g - c.b) + g_lt_b :"
575                          /*mx==c.g*/    "c.g >= c.b"             "? invd * (c.b - c.r) + 2.0"
576                          /*mx==c.b*/                             ": invd * (c.r - c.g) + 4.0);"
577                     "half sum = mx+mn,"
578                            "l = sum * 0.5,"
579                            "s = mx == mn ? 0.0"
580                                         ": d / (l > 0.5 ? 2.0 - sum : sum);"
581                     "return half3(h,s,l);"
582                 "}"
583                 "half4 main(half4 inColor) {"
584                     "half3 c = inColor.rgb;"
585                     "if (grayscale == 1) {"
586                         "c = dot(half3(0.2126, 0.7152, 0.0722), c).rrr;"
587                     "}"
588                     "if (invertStyle == 1) {"  // brightness
589                         "c = 1 - c;"
590                     "} else if (invertStyle == 2) {"  // lightness
591                         "c = rgb_to_hsl(c);"
592                         "c.b = 1 - c.b;"
593                         "c = $hsl_to_rgb(c);"
594                     "}"
595                     "c = mix(half3(0.5), c, contrast);"
596                     "return half4(saturate(c), inColor.a);"
597                 "}";
598 
599             static const SkRuntimeEffect* sHighContrastEffect =
600                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
601                                         kHighContrastFilterCode,
602                                         options);
603             return sHighContrastEffect;
604         }
605 
606         case StableKey::kLerp: {
607             static constexpr char kLerpFilterCode[] =
608                 "uniform colorFilter cf0;"
609                 "uniform colorFilter cf1;"
610                 "uniform half weight;"
611 
612                 "half4 main(half4 color) {"
613                     "return mix(cf0.eval(color), cf1.eval(color), weight);"
614                 "}";
615 
616             static const SkRuntimeEffect* sLerpEffect =
617                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
618                                         kLerpFilterCode,
619                                         options);
620             return sLerpEffect;
621         }
622 
623         case StableKey::kLuma: {
624             static constexpr char kLumaFilterCode[] =
625                 "half4 main(half4 inColor) {"
626                     "return saturate(dot(half3(0.2126, 0.7152, 0.0722), inColor.rgb)).000r;"
627                 "}";
628 
629             static const SkRuntimeEffect* sLumaEffect =
630                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
631                                         kLumaFilterCode,
632                                         options);
633             return sLumaEffect;
634         }
635 
636         case StableKey::kOverdraw: {
637             static constexpr char kOverdrawFilterCode[] =
638                 "uniform half4 color0, color1, color2, color3, color4, color5;"
639 
640                 "half4 main(half4 color) {"
641                     "half alpha = 255.0 * color.a;"
642                     "return alpha < 0.5 ? color0"
643                          ": alpha < 1.5 ? color1"
644                          ": alpha < 2.5 ? color2"
645                          ": alpha < 3.5 ? color3"
646                          ": alpha < 4.5 ? color4 : color5;"
647                 "}";
648 
649             static const SkRuntimeEffect* sOverdrawEffect =
650                     SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
651                                         kOverdrawFilterCode,
652                                         options);
653             return sOverdrawEffect;
654         }
655     }
656 
657     SkUNREACHABLE;
658 }
659 
660 } // namespace SkKnownRuntimeEffects
661