blob: 1597da304f1add140733d96a77c2f5178dde7256 [file] [log] [blame]
/*
* 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));
SkRuntimeEffectPriv::AllowPrivateAccess(&options);
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::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::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::kDecal: {
static constexpr char kDecalShaderCode[] =
"uniform shader image;"
"uniform float4 decalBounds;"
"half4 main(float2 coord) {"
"return sk_decal(image, coord, decalBounds);"
"}";
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) {"
"return sk_displacement(displMap, colorMap, coord, scale, xSelect, ySelect);"
"}";
static const SkRuntimeEffect* sDisplacementEffect =
SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
kDisplacementShaderCode,
options);
return sDisplacementEffect;
}
case StableKey::kLighting: {
static constexpr char kLightingShaderCode[] =
"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
"half4 main(float2 coord) {"
"return sk_lighting(normalMap, coord,"
/*depth=*/"materialAndLightType.x,"
/*shininess=*/"materialAndLightType.y,"
/*materialType=*/"materialAndLightType.z,"
/*lightType=*/"materialAndLightType.w,"
/*lightPos=*/"lightPosAndSpotFalloff.xyz,"
/*spotFalloff=*/"lightPosAndSpotFalloff.w,"
/*lightDir=*/"lightDirAndSpotCutoff.xyz,"
/*cosCutoffAngle=*/"lightDirAndSpotCutoff.w,"
"lightColor);"
"}";
static const SkRuntimeEffect* sLightingEffect =
SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
kLightingShaderCode,
options);
return sLightingEffect;
}
case StableKey::kLinearMorphology: {
static constexpr char kLinearMorphologyShaderCode[] =
"uniform shader child;"
"uniform half2 offset;"
"uniform half flip;" // -1 converts the max() calls to min()
"uniform int radius;"
"half4 main(float2 coord) {"
"return sk_linear_morphology(child, coord, offset, flip, radius);"
"}";
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) {"
"return sk_magnifier(src, coord, lensBounds, zoomXform, invInset);"
"}";
static const SkRuntimeEffect* sMagnifierEffect =
SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
kMagnifierShaderCode,
options);
return sMagnifierEffect;
}
case StableKey::kNormal: {
static constexpr char kNormalShaderCode[] =
"uniform shader alphaMap;"
"uniform float4 edgeBounds;"
"uniform half negSurfaceDepth;"
"half4 main(float2 coord) {"
"return sk_normal(alphaMap, coord, edgeBounds, negSurfaceDepth);"
"}";
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) {"
"return sk_sparse_morphology(child, coord, offset, flip);"
"}";
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) {"
"return sk_arithmetic_blend(src, dst, k, pmClamp);"
"}";
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;"
"half4 main(half4 color) {"
"return half4(sk_high_contrast(color.rgb, grayscale, invertStyle, contrast),"
"color.a);"
"}";
static const SkRuntimeEffect* sHighContrastEffect =
SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
kHighContrastFilterCode,
options);
return sHighContrastEffect;
}
case StableKey::kLuma: {
static constexpr char kLumaFilterCode[] =
"half4 main(half4 color) {"
"return sk_luma(color.rgb);"
"}";
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) {"
"return sk_overdraw(color.a, color0, color1, color2, color3, color4, color5);"
"}";
static const SkRuntimeEffect* sOverdrawEffect =
SkMakeRuntimeEffect(SkRuntimeEffect::MakeForColorFilter,
kOverdrawFilterCode,
options);
return sOverdrawEffect;
}
}
SkUNREACHABLE;
}
} // namespace SkKnownRuntimeEffects