| /* |
| * Copyright 2024 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/core/SkKnownRuntimeEffects.h" |
| |
| #include "include/core/SkString.h" |
| #include "include/effects/SkRuntimeEffect.h" |
| #include "src/core/SkRuntimeEffectPriv.h" |
| #include "src/effects/imagefilters/SkMatrixConvolutionImageFilter.h" |
| |
| namespace SkKnownRuntimeEffects { |
| |
| namespace { |
| |
| // This must be kept in sync w/ the version in BlurUtils.h |
| static constexpr int kMaxBlurSamples = 28; |
| |
| SkRuntimeEffect* make_blur_1D_effect(int kernelWidth, const SkRuntimeEffect::Options& options) { |
| SkASSERT(kernelWidth <= kMaxBlurSamples); |
| // The SkSL structure performs two kernel taps; if the kernel has an odd width the last |
| // sample will be skipped with the current loop limit calculation. |
| SkASSERT(kernelWidth % 2 == 0); |
| return SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| SkStringPrintf( |
| // The coefficients are always stored for the max radius to keep the |
| // uniform block consistent across all effects. |
| "const int kMaxUniformKernelSize = %d / 2;" |
| // But we generate an exact loop over the kernel size. Note that this |
| // program can be used for kernels smaller than the constructed max as long |
| // as the kernel weights for excess entries are set to 0. |
| "const int kMaxLoopLimit = %d / 2;" |
| |
| "uniform half4 offsetsAndKernel[kMaxUniformKernelSize];" |
| "uniform half2 dir;" |
| |
| "uniform shader child;" |
| |
| "half4 main(float2 coord) {" |
| "half4 sum = half4(0);" |
| "for (int i = 0; i < kMaxLoopLimit; ++i) {" |
| "half4 s = offsetsAndKernel[i];" |
| "sum += s.y * child.eval(coord + s.x*dir);" |
| "sum += s.w * child.eval(coord + s.z*dir);" |
| "}" |
| "return sum;" |
| "}", kMaxBlurSamples, kernelWidth).c_str(), |
| options); |
| } |
| |
| SkRuntimeEffect* make_blur_2D_effect(int maxKernelSize, const SkRuntimeEffect::Options& options) { |
| SkASSERT(maxKernelSize % 4 == 0); |
| return SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| SkStringPrintf( |
| // The coefficients are always stored for the max radius to keep the |
| // uniform block consistent across all effects. |
| "const int kMaxUniformKernelSize = %d / 4;" |
| "const int kMaxUniformOffsetsSize = 2*kMaxUniformKernelSize;" |
| // But we generate an exact loop over the kernel size. Note that this |
| // program can be used for kernels smaller than the constructed max as long |
| // as the kernel weights for excess entries are set to 0. |
| "const int kMaxLoopLimit = %d / 4;" |
| |
| // Pack scalar coefficients into half4 for better packing on std140, and |
| // upload offsets to avoid having to transform the 1D index into a 2D coord |
| "uniform half4 kernel[kMaxUniformKernelSize];" |
| "uniform half4 offsets[kMaxUniformOffsetsSize];" |
| |
| "uniform shader child;" |
| |
| "half4 main(float2 coord) {" |
| "half4 sum = half4(0);" |
| |
| "for (int i = 0; i < kMaxLoopLimit; ++i) {" |
| "half4 k = kernel[i];" |
| "half4 o = offsets[2*i];" |
| "sum += k.x * child.eval(coord + o.xy);" |
| "sum += k.y * child.eval(coord + o.zw);" |
| "o = offsets[2*i + 1];" |
| "sum += k.z * child.eval(coord + o.xy);" |
| "sum += k.w * child.eval(coord + o.zw);" |
| "}" |
| "return sum;" |
| "}", kMaxBlurSamples, maxKernelSize).c_str(), |
| options); |
| } |
| |
| enum class MatrixConvolutionImpl { |
| kUniformBased, |
| kTextureBasedSm, |
| kTextureBasedLg, |
| }; |
| |
| // There are three shader variants: |
| // a smaller kernel version that stores the matrix in uniforms and iterates in 1D |
| // a larger kernel version that stores the matrix in a 1D texture. The texture version has small |
| // and large variants w/ the actual kernel size uploaded as a uniform. |
| SkRuntimeEffect* make_matrix_conv_effect(MatrixConvolutionImpl impl, |
| const SkRuntimeEffect::Options& options) { |
| // While the uniforms and kernel access are different, pieces of the algorithm are common and |
| // defined statically for re-use in the two shaders: |
| static const char* kHeaderAndBeginLoopSkSL = |
| "uniform int2 size;" |
| "uniform int2 offset;" |
| "uniform half2 gainAndBias;" |
| "uniform int convolveAlpha;" // FIXME not a full int? Put in a half3 w/ gainAndBias? |
| |
| "uniform shader child;" |
| |
| "half4 main(float2 coord) {" |
| "half4 sum = half4(0);" |
| "half origAlpha = 0;" |
| "int2 kernelPos = int2(0);" |
| "for (int i = 0; i < kMaxKernelSize; ++i) {" |
| "if (kernelPos.y >= size.y) { break; }"; |
| |
| // Used in the inner loop to accumulate convolution sum and increment the kernel position |
| static const char* kAccumulateAndIncrementSkSL = |
| "half4 c = child.eval(coord + half2(kernelPos) - half2(offset));" |
| "if (convolveAlpha == 0) {" |
| // When not convolving alpha, remember the original alpha for actual sample |
| // coord, and perform accumulation on unpremul colors. |
| "if (kernelPos == offset) {" |
| "origAlpha = c.a;" |
| "}" |
| "c = unpremul(c);" |
| "}" |
| "sum += c*k;" |
| "kernelPos.x += 1;" |
| "if (kernelPos.x >= size.x) {" |
| "kernelPos.x = 0;" |
| "kernelPos.y += 1;" |
| "}"; |
| |
| // Closes the loop and calculates final color |
| static const char* kCloseLoopAndFooterSkSL = |
| "}" |
| "half4 color = sum*gainAndBias.x + gainAndBias.y;" |
| "if (convolveAlpha == 0) {" |
| // Reset the alpha to the original and convert to premul RGB |
| "color = half4(color.rgb*origAlpha, origAlpha);" |
| "} else {" |
| // Ensure convolved alpha is within [0, 1] |
| "color.a = saturate(color.a);" |
| "}" |
| // Make RGB valid premul w/ respect to the alpha (either original or convolved) |
| "color.rgb = clamp(color.rgb, 0, color.a);" |
| "return color;" |
| "}"; |
| |
| static const auto makeTextureEffect = [](int maxTextureKernelSize, |
| const SkRuntimeEffect::Options& options) { |
| return SkMakeRuntimeEffect( |
| SkRuntimeEffect::MakeForShader, |
| SkStringPrintf("const int kMaxKernelSize = %d;" |
| "uniform shader kernel;" |
| "uniform half2 innerGainAndBias;" |
| "%s" // kHeaderAndBeginLoopSkSL |
| "half k = kernel.eval(half2(half(i) + 0.5, 0.5)).a;" |
| "k = k * innerGainAndBias.x + innerGainAndBias.y;" |
| "%s" // kAccumulateAndIncrementSkSL |
| "%s", // kCloseLoopAndFooterSkSL |
| maxTextureKernelSize, |
| kHeaderAndBeginLoopSkSL, |
| kAccumulateAndIncrementSkSL, |
| kCloseLoopAndFooterSkSL).c_str(), |
| options); |
| }; |
| |
| switch (impl) { |
| case MatrixConvolutionImpl::kUniformBased: { |
| return SkMakeRuntimeEffect( |
| SkRuntimeEffect::MakeForShader, |
| SkStringPrintf("const int kMaxKernelSize = %d / 4;" |
| "uniform half4 kernel[kMaxKernelSize];" |
| "%s" // kHeaderAndBeginLoopSkSL |
| "half4 k4 = kernel[i];" |
| "for (int j = 0; j < 4; ++j) {" |
| "if (kernelPos.y >= size.y) { break; }" |
| "half k = k4[j];" |
| "%s" // kAccumulateAndIncrementSkSL |
| "}" |
| "%s", // kCloseLoopAndFooterSkSL |
| MatrixConvolutionImageFilter::kMaxUniformKernelSize, |
| kHeaderAndBeginLoopSkSL, |
| kAccumulateAndIncrementSkSL, |
| kCloseLoopAndFooterSkSL).c_str(), |
| options); |
| } |
| case MatrixConvolutionImpl::kTextureBasedSm: |
| return makeTextureEffect(MatrixConvolutionImageFilter::kSmallKernelSize, options); |
| case MatrixConvolutionImpl::kTextureBasedLg: |
| return makeTextureEffect(MatrixConvolutionImageFilter::kLargeKernelSize, options); |
| } |
| |
| SkUNREACHABLE; |
| } |
| |
| } // anonymous namespace |
| |
| const SkRuntimeEffect* GetKnownRuntimeEffect(StableKey stableKey) { |
| SkRuntimeEffect::Options options; |
| SkRuntimeEffectPriv::SetStableKey(&options, static_cast<uint32_t>(stableKey)); |
| |
| switch (stableKey) { |
| case StableKey::kInvalid: |
| return nullptr; |
| |
| // Shaders |
| case StableKey::k1DBlur4: { |
| static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(4, options); |
| return s1DBlurEffect; |
| } |
| case StableKey::k1DBlur8: { |
| static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(8, options); |
| return s1DBlurEffect; |
| } |
| case StableKey::k1DBlur12: { |
| static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(12, options); |
| return s1DBlurEffect; |
| } |
| case StableKey::k1DBlur16: { |
| static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(16, options); |
| return s1DBlurEffect; |
| } |
| case StableKey::k1DBlur20: { |
| static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(20, options); |
| return s1DBlurEffect; |
| } |
| case StableKey::k1DBlur28: { |
| static SkRuntimeEffect* s1DBlurEffect = make_blur_1D_effect(28, options); |
| return s1DBlurEffect; |
| } |
| case StableKey::k2DBlur4: { |
| static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(4, options); |
| return s2DBlurEffect; |
| } |
| case StableKey::k2DBlur8: { |
| static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(8, options); |
| return s2DBlurEffect; |
| } |
| case StableKey::k2DBlur12: { |
| static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(12, options); |
| return s2DBlurEffect; |
| } |
| case StableKey::k2DBlur16: { |
| static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(16, options); |
| return s2DBlurEffect; |
| } |
| case StableKey::k2DBlur20: { |
| static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(20, options); |
| return s2DBlurEffect; |
| } |
| case StableKey::k2DBlur28: { |
| static SkRuntimeEffect* s2DBlurEffect = make_blur_2D_effect(28, options); |
| return s2DBlurEffect; |
| } |
| case StableKey::kBlend: { |
| static constexpr char kBlendShaderCode[] = |
| "uniform shader s, d;" |
| "uniform blender b;" |
| "half4 main(float2 xy) {" |
| "return b.eval(s.eval(xy), d.eval(xy));" |
| "}"; |
| |
| static const SkRuntimeEffect* sBlendEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| kBlendShaderCode, |
| options); |
| return sBlendEffect; |
| } |
| case StableKey::kDecal: { |
| static constexpr char kDecalShaderCode[] = |
| "uniform shader image;" |
| "uniform float4 decalBounds;" |
| |
| "half4 main(float2 coord) {" |
| "half4 d = half4(decalBounds - coord.xyxy) * half4(-1, -1, 1, 1);" |
| "d = saturate(d + 0.5);" |
| "return (d.x*d.y*d.z*d.w) * image.eval(coord);" |
| "}"; |
| |
| static const SkRuntimeEffect* sDecalEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| kDecalShaderCode, |
| options); |
| return sDecalEffect; |
| } |
| case StableKey::kDisplacement: { |
| // NOTE: This uses dot product selection to work on all GLES2 hardware (enforced by |
| // public runtime effect restrictions). Otherwise, this would use a "uniform ivec2" |
| // and component indexing to convert the displacement color into a vector. |
| static constexpr char kDisplacementShaderCode[] = |
| "uniform shader displMap;" |
| "uniform shader colorMap;" |
| "uniform half2 scale;" |
| "uniform half4 xSelect;" // Only one of RGBA will be 1, the rest are 0 |
| "uniform half4 ySelect;" |
| |
| "half4 main(float2 coord) {" |
| "half4 displColor = unpremul(displMap.eval(coord));" |
| "half2 displ = half2(dot(displColor, xSelect), dot(displColor, ySelect));" |
| "displ = scale * (displ - 0.5);" |
| "return colorMap.eval(coord + displ);" |
| "}"; |
| |
| static const SkRuntimeEffect* sDisplacementEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| kDisplacementShaderCode, |
| options); |
| return sDisplacementEffect; |
| } |
| case StableKey::kLighting: { |
| static constexpr char kLightingShaderCode[] = |
| "const half kConeAAThreshold = 0.016;" |
| "const half kConeScale = 1.0 / kConeAAThreshold;" |
| |
| "uniform shader normalMap;" |
| |
| // Packs surface depth, shininess, material type (0 == diffuse) and light type |
| // (< 0 = distant, 0 = point, > 0 = spot) |
| "uniform half4 materialAndLightType;" |
| |
| "uniform half4 lightPosAndSpotFalloff;" // (x,y,z) are lightPos, w is spot falloff |
| // exponent |
| "uniform half4 lightDirAndSpotCutoff;" // (x,y,z) are lightDir, |
| // w is spot cos(cutoffAngle) |
| "uniform half3 lightColor;" // Material's k has already been multiplied in |
| |
| "half3 surface_to_light(half3 coord) {" |
| "if (materialAndLightType.w < 0) {" |
| "return lightDirAndSpotCutoff.xyz;" |
| "} else {" |
| // Spot and point have the same equation |
| "return normalize(lightPosAndSpotFalloff.xyz - coord);" |
| "}" |
| "}" |
| |
| "half spotlight_scale(half3 surfaceToLight) {" |
| "half cosCutoffAngle = lightDirAndSpotCutoff.w;" |
| "half cosAngle = -dot(surfaceToLight, lightDirAndSpotCutoff.xyz);" |
| "if (cosAngle < cosCutoffAngle) {" |
| "return 0.0;" |
| "}" |
| "half scale = pow(cosAngle, lightPosAndSpotFalloff.w);" |
| "if (cosAngle < cosCutoffAngle + kConeAAThreshold) {" |
| "return scale * (cosAngle - cosCutoffAngle) * kConeScale;" |
| "} else {" |
| "return scale;" |
| "}" |
| "}" |
| |
| "half4 compute_lighting(half3 normal, half3 surfaceToLight) {" |
| // Point and distant light color contributions are constant |
| "half3 color = lightColor;" |
| // Spotlights fade based on the angle away from its direction |
| "if (materialAndLightType.w > 0) {" |
| "color *= spotlight_scale(surfaceToLight);" |
| "}" |
| |
| // Diffuse and specular reflections scale the light's "color" differently |
| "if (materialAndLightType.z == 0) {" |
| "half coeff = dot(normal, surfaceToLight);" |
| "color = saturate(coeff * color);" |
| "return half4(color, 1.0);" |
| "} else {" |
| "half3 halfDir = normalize(surfaceToLight + half3(0, 0, 1));" |
| "half shininess = materialAndLightType.y;" |
| "half coeff = pow(dot(normal, halfDir), shininess);" |
| "color = saturate(coeff * color);" |
| "return half4(color, max(max(color.r, color.g), color.b));" |
| "}" |
| "}" |
| |
| "half4 main(float2 coord) {" |
| "half4 normalAndA = normalMap.eval(coord);" |
| "half depth = materialAndLightType.x;" |
| "half3 surfaceToLight = surface_to_light(half3(half2(coord)," |
| "depth*normalAndA.a));" |
| "return compute_lighting(normalAndA.xyz, surfaceToLight);" |
| "}"; |
| |
| static const SkRuntimeEffect* sLightingEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| kLightingShaderCode, |
| options); |
| return sLightingEffect; |
| } |
| case StableKey::kLinearMorphology: { |
| static constexpr char kLinearMorphologyShaderCode[] = |
| // KEEP IN SYNC WITH SkMorphologyImageFilter.cpp DEFINITION |
| "const int kMaxLinearRadius = 14;" |
| |
| "uniform shader child;" |
| "uniform half2 offset;" |
| "uniform half flip;" // -1 converts the max() calls to min() |
| "uniform int radius;" |
| |
| "half4 main(float2 coord) {" |
| "half4 aggregate = flip*child.eval(coord);" // case 0 only samples once |
| "for (int i = 1; i <= kMaxLinearRadius; ++i) {" |
| "if (i > radius) break;" |
| "half2 delta = half(i) * offset;" |
| "aggregate = max(aggregate, max(flip*child.eval(coord + delta)," |
| "flip*child.eval(coord - delta)));" |
| "}" |
| "return flip*aggregate;" |
| "}"; |
| |
| static const SkRuntimeEffect* sLinearMorphologyEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| kLinearMorphologyShaderCode, |
| options); |
| return sLinearMorphologyEffect; |
| } |
| |
| case StableKey::kMagnifier: { |
| static constexpr char kMagnifierShaderCode[] = |
| "uniform shader src;" |
| "uniform float4 lensBounds;" |
| "uniform float4 zoomXform;" |
| "uniform float2 invInset;" |
| |
| "half4 main(float2 coord) {" |
| "float2 zoomCoord = zoomXform.xy + zoomXform.zw*coord;" |
| // edgeInset is the smallest distance to the lens bounds edges, |
| // in units of "insets". |
| "float2 edgeInset = min(coord - lensBounds.xy, lensBounds.zw - coord) *" |
| "invInset;" |
| |
| // The equations for 'weight' ensure that it is 0 along the outside of |
| // lensBounds so it seams with any un-zoomed, un-filtered content. The zoomed |
| // content fills a rounded rectangle that is 1 "inset" in from lensBounds with |
| // circular corners with radii equal to the inset distance. Outside of this |
| // region, there is a non-linear weighting to compress the un-zoomed content |
| // to the zoomed content. The critical zone about each corner is limited |
| // to 2x"inset" square. |
| "float weight = (edgeInset.x < 2.0 && edgeInset.y < 2.0)" |
| // Circular distortion weighted by distance to inset corner |
| "? (2.0 - length(2.0 - edgeInset))" |
| // Linear zoom, or single-axis compression outside of the inset |
| // area (if delta < 1) |
| ": min(edgeInset.x, edgeInset.y);" |
| |
| // Saturate before squaring so that negative weights are clamped to 0 |
| // before squaring |
| "weight = saturate(weight);" |
| "return src.eval(mix(coord, zoomCoord, weight*weight));" |
| "}"; |
| |
| static const SkRuntimeEffect* sMagnifierEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| kMagnifierShaderCode, |
| options); |
| return sMagnifierEffect; |
| } |
| |
| case StableKey::kMatrixConvUniforms: { |
| static const SkRuntimeEffect* sMatrixConvUniformsEffect = |
| make_matrix_conv_effect(MatrixConvolutionImpl::kUniformBased, options); |
| return sMatrixConvUniformsEffect; |
| } |
| |
| case StableKey::kMatrixConvTexSm: { |
| static const SkRuntimeEffect* sMatrixConvTexSmEffect = |
| make_matrix_conv_effect(MatrixConvolutionImpl::kTextureBasedSm, options); |
| return sMatrixConvTexSmEffect; |
| } |
| |
| case StableKey::kMatrixConvTexLg: { |
| static const SkRuntimeEffect* sMatrixConvTexMaxEffect = |
| make_matrix_conv_effect(MatrixConvolutionImpl::kTextureBasedLg, options); |
| return sMatrixConvTexMaxEffect; |
| } |
| |
| case StableKey::kNormal: { |
| static constexpr char kNormalShaderCode[] = |
| "uniform shader alphaMap;" |
| "uniform float4 edgeBounds;" |
| "uniform half negSurfaceDepth;" |
| |
| "half3 normal(half3 alphaC0, half3 alphaC1, half3 alphaC2) {" |
| // The right column (or bottom row) terms of the Sobel filter. The left/top is |
| // just the negative, and the middle row/column is all 0s so those instructions |
| // are skipped. |
| "const half3 kSobel = 0.25 * half3(1,2,1);" |
| "half3 alphaR0 = half3(alphaC0.x, alphaC1.x, alphaC2.x);" |
| "half3 alphaR2 = half3(alphaC0.z, alphaC1.z, alphaC2.z);" |
| "half nx = dot(kSobel, alphaC2) - dot(kSobel, alphaC0);" |
| "half ny = dot(kSobel, alphaR2) - dot(kSobel, alphaR0);" |
| "return normalize(half3(negSurfaceDepth * half2(nx, ny), 1));" |
| "}" |
| |
| "half4 main(float2 coord) {" |
| "half3 alphaC0 = half3(" |
| "alphaMap.eval(clamp(coord + float2(-1,-1), edgeBounds.LT, edgeBounds.RB)).a," |
| "alphaMap.eval(clamp(coord + float2(-1, 0), edgeBounds.LT, edgeBounds.RB)).a," |
| "alphaMap.eval(clamp(coord + float2(-1, 1), edgeBounds.LT, edgeBounds.RB)).a);" |
| "half3 alphaC1 = half3(" |
| "alphaMap.eval(clamp(coord + float2( 0,-1), edgeBounds.LT, edgeBounds.RB)).a," |
| "alphaMap.eval(clamp(coord + float2( 0, 0), edgeBounds.LT, edgeBounds.RB)).a," |
| "alphaMap.eval(clamp(coord + float2( 0, 1), edgeBounds.LT, edgeBounds.RB)).a);" |
| "half3 alphaC2 = half3(" |
| "alphaMap.eval(clamp(coord + float2( 1,-1), edgeBounds.LT, edgeBounds.RB)).a," |
| "alphaMap.eval(clamp(coord + float2( 1, 0), edgeBounds.LT, edgeBounds.RB)).a," |
| "alphaMap.eval(clamp(coord + float2( 1, 1), edgeBounds.LT, edgeBounds.RB)).a);" |
| |
| "half mainAlpha = alphaC1.y;" // offset = (0,0) |
| "return half4(normal(alphaC0, alphaC1, alphaC2), mainAlpha);" |
| "}"; |
| |
| static const SkRuntimeEffect* sNormalEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| kNormalShaderCode, |
| options); |
| return sNormalEffect; |
| } |
| case StableKey::kSparseMorphology: { |
| static constexpr char kSparseMorphologyShaderCode[] = |
| "uniform shader child;" |
| "uniform half2 offset;" |
| "uniform half flip;" |
| |
| "half4 main(float2 coord) {" |
| "half4 aggregate = max(flip*child.eval(coord + offset)," |
| "flip*child.eval(coord - offset));" |
| "return flip*aggregate;" |
| "}"; |
| |
| static const SkRuntimeEffect* sSparseMorphologyEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| kSparseMorphologyShaderCode, |
| options); |
| return sSparseMorphologyEffect; |
| } |
| |
| // Blenders |
| case StableKey::kArithmetic: { |
| static constexpr char kArithmeticBlenderCode[] = |
| "uniform half4 k;" |
| "uniform half pmClamp;" |
| |
| "half4 main(half4 src, half4 dst) {" |
| "half4 c = saturate(k.x * src * dst + k.y * src + k.z * dst + k.w);" |
| "c.rgb = min(c.rgb, max(c.a, pmClamp));" |
| "return c;" |
| "}"; |
| |
| static const SkRuntimeEffect* sArithmeticEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForBlender, |
| kArithmeticBlenderCode, |
| options); |
| return sArithmeticEffect; |
| } |
| |
| // Color Filters |
| case StableKey::kHighContrast: { |
| static constexpr char kHighContrastFilterCode[] = |
| "uniform half grayscale, invertStyle, contrast;" |
| |
| "half3 rgb_to_hsl(half3 c) {" |
| "half mx = max(max(c.r,c.g),c.b)," |
| "mn = min(min(c.r,c.g),c.b)," |
| "d = mx-mn," |
| "invd = 1.0 / d," |
| "g_lt_b = c.g < c.b ? 6.0 : 0.0;" |
| |
| // We'd prefer to write these tests like `mx == c.r`, but on some GPUs max(x,y) is |
| // not always equal to either x or y. So we use long form, c.r >= c.g && c.r >= c.b. |
| "half h = (1/6.0) * (mx == mn" "? 0.0 :" |
| /*mx==c.r*/ "c.r >= c.g && c.r >= c.b ? invd * (c.g - c.b) + g_lt_b :" |
| /*mx==c.g*/ "c.g >= c.b" "? invd * (c.b - c.r) + 2.0" |
| /*mx==c.b*/ ": invd * (c.r - c.g) + 4.0);" |
| "half sum = mx+mn," |
| "l = sum * 0.5," |
| "s = mx == mn ? 0.0" |
| ": d / (l > 0.5 ? 2.0 - sum : sum);" |
| "return half3(h,s,l);" |
| "}" |
| "half4 main(half4 inColor) {" |
| "half3 c = inColor.rgb;" |
| "if (grayscale == 1) {" |
| "c = dot(half3(0.2126, 0.7152, 0.0722), c).rrr;" |
| "}" |
| "if (invertStyle == 1) {" // brightness |
| "c = 1 - c;" |
| "} else if (invertStyle == 2) {" // lightness |
| "c = rgb_to_hsl(c);" |
| "c.b = 1 - c.b;" |
| "c = $hsl_to_rgb(c);" |
| "}" |
| "c = mix(half3(0.5), c, contrast);" |
| "return half4(saturate(c), inColor.a);" |
| "}"; |
| |
| static const SkRuntimeEffect* sHighContrastEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter, |
| kHighContrastFilterCode, |
| options); |
| return sHighContrastEffect; |
| } |
| |
| case StableKey::kLerp: { |
| static constexpr char kLerpFilterCode[] = |
| "uniform colorFilter cf0;" |
| "uniform colorFilter cf1;" |
| "uniform half weight;" |
| |
| "half4 main(half4 color) {" |
| "return mix(cf0.eval(color), cf1.eval(color), weight);" |
| "}"; |
| |
| static const SkRuntimeEffect* sLerpEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter, |
| kLerpFilterCode, |
| options); |
| return sLerpEffect; |
| } |
| |
| case StableKey::kLuma: { |
| static constexpr char kLumaFilterCode[] = |
| "half4 main(half4 inColor) {" |
| "return saturate(dot(half3(0.2126, 0.7152, 0.0722), inColor.rgb)).000r;" |
| "}"; |
| |
| static const SkRuntimeEffect* sLumaEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter, |
| kLumaFilterCode, |
| options); |
| return sLumaEffect; |
| } |
| |
| case StableKey::kOverdraw: { |
| static constexpr char kOverdrawFilterCode[] = |
| "uniform half4 color0, color1, color2, color3, color4, color5;" |
| |
| "half4 main(half4 color) {" |
| "half alpha = 255.0 * color.a;" |
| "return alpha < 0.5 ? color0" |
| ": alpha < 1.5 ? color1" |
| ": alpha < 2.5 ? color2" |
| ": alpha < 3.5 ? color3" |
| ": alpha < 4.5 ? color4 : color5;" |
| "}"; |
| |
| static const SkRuntimeEffect* sOverdrawEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter, |
| kOverdrawFilterCode, |
| options); |
| return sOverdrawEffect; |
| } |
| } |
| |
| SkUNREACHABLE; |
| } |
| |
| } // namespace SkKnownRuntimeEffects |