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 SkRuntimeEffectPriv::AllowPrivateAccess(&options);
208
209 switch (stableKey) {
210 case StableKey::kInvalid:
211 return nullptr;
212
213 // Shaders
214 case StableKey::k1DBlur4: {
215 static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(4, options);
216 return s1DBlurEffect;
217 }
218 case StableKey::k1DBlur8: {
219 static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(8, options);
220 return s1DBlurEffect;
221 }
222 case StableKey::k1DBlur12: {
223 static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(12, options);
224 return s1DBlurEffect;
225 }
226 case StableKey::k1DBlur16: {
227 static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(16, options);
228 return s1DBlurEffect;
229 }
230 case StableKey::k1DBlur20: {
231 static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(20, options);
232 return s1DBlurEffect;
233 }
234 case StableKey::k1DBlur28: {
235 static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(28, options);
236 return s1DBlurEffect;
237 }
238 case StableKey::k2DBlur4: {
239 static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(4, options);
240 return s2DBlurEffect;
241 }
242 case StableKey::k2DBlur8: {
243 static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(8, options);
244 return s2DBlurEffect;
245 }
246 case StableKey::k2DBlur12: {
247 static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(12, options);
248 return s2DBlurEffect;
249 }
250 case StableKey::k2DBlur16: {
251 static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(16, options);
252 return s2DBlurEffect;
253 }
254 case StableKey::k2DBlur20: {
255 static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(20, options);
256 return s2DBlurEffect;
257 }
258 case StableKey::k2DBlur28: {
259 static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(28, options);
260 return s2DBlurEffect;
261 }
262 case StableKey::kBlend: {
263 static constexpr char kBlendShaderCode[] =
264 "uniform shader s, d;"
265 "uniform blender b;"
266 "half4 main(float2 xy) {"
267 "return b.eval(s.eval(xy), d.eval(xy));"
268 "}";
269
270 static const SkRuntimeEffect* sBlendEffect =
271 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
272 kBlendShaderCode,
273 options);
274 return sBlendEffect;
275 }
276 case StableKey::kLerp: {
277 static constexpr char kLerpFilterCode[] =
278 "uniform colorFilter cf0;"
279 "uniform colorFilter cf1;"
280 "uniform half weight;"
281
282 "half4 main(half4 color) {"
283 "return mix(cf0.eval(color), cf1.eval(color), weight);"
284 "}";
285
286 static const SkRuntimeEffect* sLerpEffect =
287 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
288 kLerpFilterCode,
289 options);
290 return sLerpEffect;
291 }
292 case StableKey::kMatrixConvUniforms: {
293 static const SkRuntimeEffect* sMatrixConvUniformsEffect =
294 make_matrix_conv_effect(MatrixConvolutionImpl::kUniformBased, options);
295 return sMatrixConvUniformsEffect;
296 }
297
298 case StableKey::kMatrixConvTexSm: {
299 static const SkRuntimeEffect* sMatrixConvTexSmEffect =
300 make_matrix_conv_effect(MatrixConvolutionImpl::kTextureBasedSm, options);
301 return sMatrixConvTexSmEffect;
302 }
303
304 case StableKey::kMatrixConvTexLg: {
305 static const SkRuntimeEffect* sMatrixConvTexMaxEffect =
306 make_matrix_conv_effect(MatrixConvolutionImpl::kTextureBasedLg, options);
307 return sMatrixConvTexMaxEffect;
308 }
309 case StableKey::kDecal: {
310 static constexpr char kDecalShaderCode[] =
311 "uniform shader image;"
312 "uniform float4 decalBounds;"
313
314 "half4 main(float2 coord) {"
315 "return sk_decal(image, coord, decalBounds);"
316 "}";
317
318 static const SkRuntimeEffect* sDecalEffect =
319 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
320 kDecalShaderCode,
321 options);
322 return sDecalEffect;
323 }
324 case StableKey::kDisplacement: {
325 // NOTE: This uses dot product selection to work on all GLES2 hardware (enforced by
326 // public runtime effect restrictions). Otherwise, this would use a "uniform ivec2"
327 // and component indexing to convert the displacement color into a vector.
328 static constexpr char kDisplacementShaderCode[] =
329 "uniform shader displMap;"
330 "uniform shader colorMap;"
331 "uniform half2 scale;"
332 "uniform half4 xSelect;" // Only one of RGBA will be 1, the rest are 0
333 "uniform half4 ySelect;"
334
335 "half4 main(float2 coord) {"
336 "return sk_displacement(displMap, colorMap, coord, scale, xSelect, ySelect);"
337 "}";
338
339 static const SkRuntimeEffect* sDisplacementEffect =
340 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
341 kDisplacementShaderCode,
342 options);
343 return sDisplacementEffect;
344 }
345 case StableKey::kLighting: {
346 static constexpr char kLightingShaderCode[] =
347 "uniform shader normalMap;"
348
349 // Packs surface depth, shininess, material type (0 == diffuse) and light type
350 // (< 0 = distant, 0 = point, > 0 = spot)
351 "uniform half4 materialAndLightType;"
352
353 "uniform half4 lightPosAndSpotFalloff;" // (x,y,z) are lightPos, w is spot falloff
354 // exponent
355 "uniform half4 lightDirAndSpotCutoff;" // (x,y,z) are lightDir,
356 // w is spot cos(cutoffAngle)
357 "uniform half3 lightColor;" // Material's k has already been multiplied in
358
359 "half4 main(float2 coord) {"
360 "return sk_lighting(normalMap, coord,"
361 /*depth=*/"materialAndLightType.x,"
362 /*shininess=*/"materialAndLightType.y,"
363 /*materialType=*/"materialAndLightType.z,"
364 /*lightType=*/"materialAndLightType.w,"
365 /*lightPos=*/"lightPosAndSpotFalloff.xyz,"
366 /*spotFalloff=*/"lightPosAndSpotFalloff.w,"
367 /*lightDir=*/"lightDirAndSpotCutoff.xyz,"
368 /*cosCutoffAngle=*/"lightDirAndSpotCutoff.w,"
369 "lightColor);"
370 "}";
371
372 static const SkRuntimeEffect* sLightingEffect =
373 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
374 kLightingShaderCode,
375 options);
376 return sLightingEffect;
377 }
378 case StableKey::kLinearMorphology: {
379 static constexpr char kLinearMorphologyShaderCode[] =
380 "uniform shader child;"
381 "uniform half2 offset;"
382 "uniform half flip;" // -1 converts the max() calls to min()
383 "uniform int radius;"
384
385 "half4 main(float2 coord) {"
386 "return sk_linear_morphology(child, coord, offset, flip, radius);"
387 "}";
388
389 static const SkRuntimeEffect* sLinearMorphologyEffect =
390 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
391 kLinearMorphologyShaderCode,
392 options);
393 return sLinearMorphologyEffect;
394 }
395
396 case StableKey::kMagnifier: {
397 static constexpr char kMagnifierShaderCode[] =
398 "uniform shader src;"
399 "uniform float4 lensBounds;"
400 "uniform float4 zoomXform;"
401 "uniform float2 invInset;"
402
403 "half4 main(float2 coord) {"
404 "return sk_magnifier(src, coord, lensBounds, zoomXform, invInset);"
405 "}";
406
407 static const SkRuntimeEffect* sMagnifierEffect =
408 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
409 kMagnifierShaderCode,
410 options);
411 return sMagnifierEffect;
412 }
413 case StableKey::kNormal: {
414 static constexpr char kNormalShaderCode[] =
415 "uniform shader alphaMap;"
416 "uniform float4 edgeBounds;"
417 "uniform half negSurfaceDepth;"
418
419 "half4 main(float2 coord) {"
420 "return sk_normal(alphaMap, coord, edgeBounds, negSurfaceDepth);"
421 "}";
422
423 static const SkRuntimeEffect* sNormalEffect =
424 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
425 kNormalShaderCode,
426 options);
427 return sNormalEffect;
428 }
429 case StableKey::kSparseMorphology: {
430 static constexpr char kSparseMorphologyShaderCode[] =
431 "uniform shader child;"
432 "uniform half2 offset;"
433 "uniform half flip;"
434
435 "half4 main(float2 coord) {"
436 "return sk_sparse_morphology(child, coord, offset, flip);"
437 "}";
438
439 static const SkRuntimeEffect* sSparseMorphologyEffect =
440 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
441 kSparseMorphologyShaderCode,
442 options);
443 return sSparseMorphologyEffect;
444 }
445
446 // Blenders
447 case StableKey::kArithmetic: {
448 static constexpr char kArithmeticBlenderCode[] =
449 "uniform half4 k;"
450 "uniform half pmClamp;"
451
452 "half4 main(half4 src, half4 dst) {"
453 "return sk_arithmetic_blend(src, dst, k, pmClamp);"
454 "}";
455
456 static const SkRuntimeEffect* sArithmeticEffect =
457 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForBlender,
458 kArithmeticBlenderCode,
459 options);
460 return sArithmeticEffect;
461 }
462
463 // Color Filters
464 case StableKey::kHighContrast: {
465 static constexpr char kHighContrastFilterCode[] =
466 "uniform half grayscale, invertStyle, contrast;"
467 "half4 main(half4 color) {"
468 "return half4(sk_high_contrast(color.rgb, grayscale, invertStyle, contrast),"
469 "color.a);"
470 "}";
471
472 static const SkRuntimeEffect* sHighContrastEffect =
473 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
474 kHighContrastFilterCode,
475 options);
476 return sHighContrastEffect;
477 }
478
479 case StableKey::kLuma: {
480 static constexpr char kLumaFilterCode[] =
481 "half4 main(half4 color) {"
482 "return sk_luma(color.rgb);"
483 "}";
484
485 static const SkRuntimeEffect* sLumaEffect =
486 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
487 kLumaFilterCode,
488 options);
489 return sLumaEffect;
490 }
491
492 case StableKey::kOverdraw: {
493 static constexpr char kOverdrawFilterCode[] =
494 "uniform half4 color0, color1, color2, color3, color4, color5;"
495
496 "half4 main(half4 color) {"
497 "return sk_overdraw(color.a, color0, color1, color2, color3, color4, color5);"
498 "}";
499
500 static const SkRuntimeEffect* sOverdrawEffect =
501 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
502 kOverdrawFilterCode,
503 options);
504 return sOverdrawEffect;
505 }
506 }
507
508 SkUNREACHABLE;
509 }
510
511 } // namespace SkKnownRuntimeEffects
512