Add support for child effects on SkRuntimeBlender.
This will allow runtime blenders to leverage existing color filters or
shaders when computing a blend.
Change-Id: I743d5fc6d83a92cd190cb6bfd1c79789d8be2089
Bug: skia:12249
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/430658
Commit-Queue: John Stiles <johnstiles@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/include/effects/SkRuntimeEffect.h b/include/effects/SkRuntimeEffect.h
index cecfedf..b879e67 100644
--- a/include/effects/SkRuntimeEffect.h
+++ b/include/effects/SkRuntimeEffect.h
@@ -194,7 +194,7 @@
sk_sp<SkColorFilter> makeColorFilter(sk_sp<SkData> uniforms,
SkSpan<ChildPtr> children) const;
- sk_sp<SkBlender> makeBlender(sk_sp<SkData> uniforms) const;
+ sk_sp<SkBlender> makeBlender(sk_sp<SkData> uniforms, SkSpan<ChildPtr> children = {}) const;
const std::string& source() const;
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index 4420576..0bc3d1a 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -264,12 +264,6 @@
// Child effects that can be sampled ('shader' or 'colorFilter')
if (varType.isEffectChild()) {
- // Runtime blends currently don't support child effects.
- if (kind == SkSL::ProgramKind::kRuntimeBlender) {
- RETURN_FAILURE("'%s' is not allowed in runtime blend",
- varType.displayName().c_str());
- }
-
Child c;
c.name = SkString(var.name());
c.type = child_type(varType);
@@ -722,6 +716,7 @@
const char* name,
sk_sp<SkData> uniforms,
std::unique_ptr<GrFragmentProcessor> inputFP,
+ std::unique_ptr<GrFragmentProcessor> destColorFP,
SkSpan<const SkRuntimeEffect::ChildPtr> children,
const GrFPArgs& childArgs) {
SkSTArray<8, std::unique_ptr<GrFragmentProcessor>> childFPs;
@@ -748,7 +743,7 @@
auto fp = GrSkSLFP::MakeWithData(std::move(effect),
name,
std::move(inputFP),
- /*destColorFP=*/nullptr,
+ std::move(destColorFP),
std::move(uniforms),
SkMakeSpan(childFPs));
SkASSERT(fp);
@@ -778,6 +773,7 @@
"runtime_color_filter",
std::move(uniforms),
std::move(inputFP),
+ /*destColorFP=*/nullptr,
SkMakeSpan(fChildren),
childArgs);
}
@@ -938,6 +934,7 @@
"runtime_shader",
std::move(uniforms),
/*inputFP=*/nullptr,
+ /*destColorFP=*/nullptr,
SkMakeSpan(fChildren),
childArgs);
if (!success) {
@@ -1090,9 +1087,12 @@
class SkRuntimeBlender : public SkBlenderBase {
public:
- SkRuntimeBlender(sk_sp<SkRuntimeEffect> effect, sk_sp<SkData> uniforms)
+ SkRuntimeBlender(sk_sp<SkRuntimeEffect> effect,
+ sk_sp<SkData> uniforms,
+ SkSpan<SkRuntimeEffect::ChildPtr> children)
: fEffect(std::move(effect))
- , fUniforms(std::move(uniforms)) {}
+ , fUniforms(std::move(uniforms))
+ , fChildren(children.begin(), children.end()) {}
SkRuntimeEffect* asRuntimeEffect() const override { return fEffect.get(); }
@@ -1103,6 +1103,19 @@
colorInfo.colorSpace());
SkASSERT(inputs);
+ auto sampleChild = [&](int index, skvm::Coord coord, skvm::Color color) {
+ const SkRuntimeEffect::ChildPtr& effect = fChildren[index];
+ if (effect.shader) {
+ SkSimpleMatrixProvider mats{SkMatrix::I()};
+ return as_SB(effect.shader)->program(p, coord, coord, color, mats,
+ /*localM=*/nullptr, colorInfo, uniforms, alloc);
+ }
+ if (effect.colorFilter) {
+ return as_CFB(effect.colorFilter)->program(p, color, colorInfo, uniforms, alloc);
+ }
+ return color;
+ };
+
const size_t uniformCount = fEffect->uniformSize() / 4;
std::vector<skvm::Val> uniform;
uniform.reserve(uniformCount);
@@ -1115,8 +1128,7 @@
// Emit the blend function as an SkVM program.
skvm::Coord zeroCoord = {p->splat(0.0f), p->splat(0.0f)};
return SkSL::ProgramToSkVM(*fEffect->fBaseProgram, fEffect->fMain, p, SkMakeSpan(uniform),
- /*device=*/zeroCoord, /*local=*/zeroCoord,
- src, dst, /*sampleChild=*/nullptr);
+ /*device=*/zeroCoord, /*local=*/zeroCoord, src, dst, sampleChild);
}
#if SK_SUPPORT_GPU
@@ -1127,13 +1139,15 @@
sk_sp<SkData> uniforms = get_xformed_uniforms(fEffect.get(), fUniforms,
args.fDstColorInfo->colorSpace());
SkASSERT(uniforms);
+ auto [success, fp] = make_effect_fp(fEffect,
+ "runtime_blender",
+ std::move(uniforms),
+ std::move(srcFP),
+ std::move(dstFP),
+ SkMakeSpan(fChildren),
+ args);
- return GrSkSLFP::MakeWithData(fEffect,
- "runtime_blender",
- std::move(srcFP),
- std::move(dstFP),
- std::move(uniforms),
- /*childFPs=*/{});
+ return success ? std::move(fp) : nullptr;
}
#endif
@@ -1149,6 +1163,7 @@
sk_sp<SkRuntimeEffect> fEffect;
sk_sp<SkData> fUniforms;
+ std::vector<SkRuntimeEffect::ChildPtr> fChildren;
};
sk_sp<SkFlattenable> SkRuntimeBlender::CreateProc(SkReadBuffer& buffer) {
@@ -1312,17 +1327,21 @@
return this->makeColorFilter(std::move(uniforms), /*children=*/{});
}
-sk_sp<SkBlender> SkRuntimeEffect::makeBlender(sk_sp<SkData> uniforms) const {
+sk_sp<SkBlender> SkRuntimeEffect::makeBlender(sk_sp<SkData> uniforms,
+ SkSpan<ChildPtr> children) const {
if (!this->allowBlender()) {
return nullptr;
}
+ if (!verify_child_effects(fChildren, children)) {
+ return nullptr;
+ }
if (!uniforms) {
uniforms = SkData::MakeEmpty();
}
- if (uniforms->size() != this->uniformSize() || !fChildren.empty()) {
+ if (uniforms->size() != this->uniformSize()) {
return nullptr;
}
- return sk_sp<SkBlender>(new SkRuntimeBlender(sk_ref_sp(this), std::move(uniforms)));
+ return sk_sp<SkBlender>(new SkRuntimeBlender(sk_ref_sp(this), std::move(uniforms), children));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1363,7 +1382,8 @@
SkRuntimeBlendBuilder::~SkRuntimeBlendBuilder() = default;
sk_sp<SkBlender> SkRuntimeBlendBuilder::makeBlender() {
- return this->effect()->makeBlender(this->uniforms());
+ return this->effect()->makeBlender(this->uniforms(),
+ SkMakeSpan(this->children(), this->numChildren()));
}
#endif // SK_ENABLE_SKSL
diff --git a/src/sksl/generated/sksl_rt_blend.dehydrated.sksl b/src/sksl/generated/sksl_rt_blend.dehydrated.sksl
index 530f37f..16fbb2c 100644
--- a/src/sksl/generated/sksl_rt_blend.dehydrated.sksl
+++ b/src/sksl/generated/sksl_rt_blend.dehydrated.sksl
@@ -1,5 +1,36 @@
-static uint8_t SKSL_INCLUDE_sksl_rt_blend[] = {0,0,
-47,0,0,0,0,
+static uint8_t SKSL_INCLUDE_sksl_rt_blend[] = {56,0,
+1,115,
+6,115,104,97,100,101,114,
+6,99,111,111,114,100,115,
+6,102,108,111,97,116,50,
+6,115,97,109,112,108,101,
+5,104,97,108,102,52,
+1,102,
+11,99,111,108,111,114,70,105,108,116,101,114,
+5,99,111,108,111,114,
+47,7,0,
+51,1,0,
+16,2,0,
+48,2,0,4,0,3,
+51,3,0,
+16,11,0,
+48,4,0,18,0,3,
+28,5,0,
+16,25,0,2,1,0,3,0,
+48,6,0,32,0,
+51,7,0,
+16,38,0,
+48,8,0,40,0,3,
+51,9,0,
+16,52,0,
+45,6,0,3,
+50,10,0,2,
+45,5,0,
+28,11,0,
+16,25,0,2,7,0,9,0,
+45,6,0,
+45,11,0,1,0,
+5,0,
19,
20,};
static constexpr size_t SKSL_INCLUDE_sksl_rt_blend_LENGTH = sizeof(SKSL_INCLUDE_sksl_rt_blend);
diff --git a/src/sksl/sksl_rt_blend.sksl b/src/sksl/sksl_rt_blend.sksl
index f6fe915..06b4a42 100644
--- a/src/sksl/sksl_rt_blend.sksl
+++ b/src/sksl/sksl_rt_blend.sksl
@@ -1 +1,2 @@
-// TODO(skia:12080): if any blend-specific program elements are needed, add them here.
+half4 sample(shader s, float2 coords);
+half4 sample(colorFilter f, half4 color);
diff --git a/tests/SkRuntimeEffectTest.cpp b/tests/SkRuntimeEffectTest.cpp
index 205520e..ec784e5 100644
--- a/tests/SkRuntimeEffectTest.cpp
+++ b/tests/SkRuntimeEffectTest.cpp
@@ -176,8 +176,8 @@
errorText.c_str());
};
- // Color filters must use the 'half4 main(half4, half4)' signature. Any mixture of
- // float4/vec4/half4 is allowed.
+ // Blenders must use the 'half4 main(half4, half4)' signature. Any mixture of float4/vec4/half4
+ // is allowed.
test_valid("half4 main(half4 s, half4 d) { return s; }");
test_valid("float4 main(float4 s, float4 d) { return d; }");
test_valid("float4 main(half4 s, float4 d) { return s; }");
@@ -202,11 +202,44 @@
test_invalid("half4 main(half4 s, half4 d) { return sk_FragCoord.xy01; }",
"unknown identifier");
- // Child shaders are currently unsupported in blends
- test_invalid("uniform shader sh; half4 main(half4 s, half4 d) { return s; }",
- "'shader' is not allowed in runtime blend");
- test_invalid("uniform shader sh; half4 main(half4 s, half4 d) { return sample(sh, s.rg); }",
- "unknown identifier 'sample'");
+ // Sampling a child shader requires that we pass explicit coords
+ test_valid("uniform shader child;"
+ "half4 main(half4 s, half4 d) { return sample(child, s.rg); }");
+ // Trying to pass a color as well. (Works internally with FPs, but not in runtime effects).
+ test_invalid("uniform shader child;"
+ "half4 main(half4 s, half4 d) { return sample(child, s.rg, d); }",
+ "no match for sample(shader, half2, half4)");
+
+ // Shader with just a color
+ test_invalid("uniform shader child;"
+ "half4 main(half4 s, half4 d) { return sample(child, s); }",
+ "no match for sample(shader, half4)");
+ // Coords and color in a different order
+ test_invalid("uniform shader child;"
+ "half4 main(half4 s, half4 d) { return sample(child, s, d.rg); }",
+ "no match for sample(shader, half4, half2)");
+
+ // Older variants that are no longer allowed
+ test_invalid("uniform shader child;"
+ "half4 main(half4 s, half4 d) { return sample(child); }",
+ "no match for sample(shader)");
+ test_invalid("uniform shader child;"
+ "half4 main(half4 s, half4 d) { return sample(child, float3x3(1)); }",
+ "no match for sample(shader, float3x3)");
+
+ // Sampling a colorFilter requires a color. No other signatures are valid.
+ test_valid("uniform colorFilter child;"
+ "half4 main(half4 s, half4 d) { return sample(child, d); }");
+
+ test_invalid("uniform colorFilter child;"
+ "half4 main(half4 s, half4 d) { return sample(child); }",
+ "sample(colorFilter)");
+ test_invalid("uniform colorFilter child;"
+ "half4 main(half4 s, half4 d) { return sample(child, d.rg); }",
+ "sample(colorFilter, half2)");
+ test_invalid("uniform colorFilter child;"
+ "half4 main(half4 s, half4 d) { return sample(child, d.rg, s); }",
+ "sample(colorFilter, half2, half4)");
}
DEF_TEST(SkRuntimeEffectForShader, r) {
@@ -406,6 +439,10 @@
return fBuilder->uniform(name);
}
+ SkRuntimeBlendBuilder::BuilderChild child(const char* name) {
+ return fBuilder->child(name);
+ }
+
void test(std::array<GrColor, 4> expected, PreTestFn preTestCallback = nullptr) {
auto blender = fBuilder->makeBlender();
if (!blender) {
@@ -552,6 +589,7 @@
REPORTER_ASSERT(r, surface);
TestBlend effect(r, surface);
+ using float2 = std::array<float, 2>;
using float4 = std::array<float, 4>;
using int4 = std::array<int, 4>;
@@ -600,6 +638,40 @@
effect.test(0x00000000);
effect.build("half4 main(half4 s, half4 d) { return half4(2); }");
effect.test(0xFFFFFFFF);
+
+ //
+ // Sampling children
+ //
+
+ // Sampling a null child should return the paint color
+ effect.build("uniform shader child;"
+ "half4 main(half4 s, half4 d) { return sample(child, s.rg); }");
+ effect.child("child") = nullptr;
+ effect.test(0xFF00FFFF,
+ [](SkCanvas*, SkPaint* paint) { paint->setColor4f({1.0f, 1.0f, 0.0f, 1.0f}); });
+
+ // Sampling a shader at various coordinates
+ effect.build("uniform shader child;"
+ "uniform half2 pos;"
+ "half4 main(half4 s, half4 d) { return sample(child, pos); }");
+ effect.child("child") = make_RGBW_shader();
+ effect.uniform("pos") = float2{0, 0};
+ effect.test(0xFF0000FF);
+
+ effect.uniform("pos") = float2{1, 0};
+ effect.test(0xFF00FF00);
+
+ effect.uniform("pos") = float2{0, 1};
+ effect.test(0xFFFF0000);
+
+ effect.uniform("pos") = float2{1, 1};
+ effect.test(0xFFFFFFFF);
+
+ // Sampling a color filter
+ effect.build("uniform colorFilter child;"
+ "half4 main(half4 s, half4 d) { return sample(child, half4(1)); }");
+ effect.child("child") = SkColorFilters::Blend(0xFF012345, SkBlendMode::kSrc);
+ effect.test(0xFF452301);
}
DEF_TEST(SkRuntimeEffect_Blender_CPU, r) {