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>
This commit is contained in:
John Stiles 2021-07-22 10:13:30 -04:00 committed by Skia Commit-Bot
parent ff7a4a576f
commit 8e51bad14f
5 changed files with 156 additions and 32 deletions

View File

@ -194,7 +194,7 @@ public:
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;

View File

@ -264,12 +264,6 @@ SkRuntimeEffect::Result SkRuntimeEffect::MakeInternal(std::unique_ptr<SkSL::Prog
// 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 @@ static GrFPResult make_effect_fp(sk_sp<SkRuntimeEffect> effect,
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 @@ static GrFPResult make_effect_fp(sk_sp<SkRuntimeEffect> effect,
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 @@ public:
"runtime_color_filter",
std::move(uniforms),
std::move(inputFP),
/*destColorFP=*/nullptr,
SkMakeSpan(fChildren),
childArgs);
}
@ -938,6 +934,7 @@ public:
"runtime_shader",
std::move(uniforms),
/*inputFP=*/nullptr,
/*destColorFP=*/nullptr,
SkMakeSpan(fChildren),
childArgs);
if (!success) {
@ -1090,9 +1087,12 @@ sk_sp<SkFlattenable> SkRTShader::CreateProc(SkReadBuffer& buffer) {
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 @@ public:
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 @@ public:
// 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 @@ public:
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 @@ private:
sk_sp<SkRuntimeEffect> fEffect;
sk_sp<SkData> fUniforms;
std::vector<SkRuntimeEffect::ChildPtr> fChildren;
};
sk_sp<SkFlattenable> SkRuntimeBlender::CreateProc(SkReadBuffer& buffer) {
@ -1312,17 +1327,21 @@ sk_sp<SkColorFilter> SkRuntimeEffect::makeColorFilter(sk_sp<SkData> uniforms) co
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(sk_sp<SkRuntimeEffect> effect)
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

View File

@ -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);

View File

@ -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);

View File

@ -176,8 +176,8 @@ DEF_TEST(SkRuntimeEffectForBlender, r) {
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 @@ DEF_TEST(SkRuntimeEffectForBlender, r) {
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 @@ public:
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 @@ static void test_RuntimeEffect_Blenders(skiatest::Reporter* r, GrRecordingContex
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 @@ static void test_RuntimeEffect_Blenders(skiatest::Reporter* r, GrRecordingContex
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) {