Implement SkBlender support in Ganesh.
SkBlenderBase::asFragmentProcessor now returns a working runtime-blender
FP. This FP is appended to the end of the paintFP chain by
skpaint_to_grpaint_impl when an SkPaint contains an SkBlender, and the
GrPaint's XferProcessor factory is set to kSrc.
Unit tests have been added to verify basic functionality is working as
expected; more thorough drawing tests will be added once the CPU side is
running as well (since we don't want GMs that render differently on CPU
and GPU).
Change-Id: I255abd057fa75d638a9f2612c1a353be4de9e24c
Bug: skia:12080
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/419358
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
diff --git a/tests/SkRuntimeEffectTest.cpp b/tests/SkRuntimeEffectTest.cpp
index 6fccf5d..d0a6bb8 100644
--- a/tests/SkRuntimeEffectTest.cpp
+++ b/tests/SkRuntimeEffectTest.cpp
@@ -304,6 +304,40 @@
"sample(colorFilter, float2, half4)");
}
+using PreTestFn = std::function<void(SkCanvas*, SkPaint*)>;
+
+void paint_canvas(SkCanvas* canvas, SkPaint* paint, const PreTestFn& preTestCallback) {
+ canvas->save();
+ if (preTestCallback) {
+ preTestCallback(canvas, paint);
+ }
+ canvas->drawPaint(*paint);
+ canvas->restore();
+}
+
+static void verify_2x2_surface_results(skiatest::Reporter* r,
+ const SkRuntimeEffect* effect,
+ SkSurface* surface,
+ std::array<GrColor, 4> expected) {
+ std::array<GrColor, 4> actual;
+ SkImageInfo info = surface->imageInfo();
+ if (!surface->readPixels(info, actual.data(), info.minRowBytes(), /*srcX=*/0, /*srcY=*/0)) {
+ REPORT_FAILURE(r, "readPixels", SkString("readPixels failed"));
+ return;
+ }
+
+ if (actual != expected) {
+ REPORT_FAILURE(r, "Runtime effect didn't match expectations",
+ SkStringPrintf("\n"
+ "Expected: [ %08x %08x %08x %08x ]\n"
+ "Got : [ %08x %08x %08x %08x ]\n"
+ "SkSL:\n%s\n",
+ expected[0], expected[1], expected[2], expected[3],
+ actual[0], actual[1], actual[2], actual[3],
+ effect->source().c_str()));
+ }
+}
+
class TestEffect {
public:
TestEffect(skiatest::Reporter* r, sk_sp<SkSurface> surface)
@@ -322,14 +356,12 @@
SkRuntimeShaderBuilder::BuilderUniform uniform(const char* name) {
return fBuilder->uniform(name);
}
+
SkRuntimeShaderBuilder::BuilderChild child(const char* name) {
return fBuilder->child(name);
}
- using PreTestFn = std::function<void(SkCanvas*, SkPaint*)>;
-
- void test(GrColor TL, GrColor TR, GrColor BL, GrColor BR,
- PreTestFn preTestCallback = nullptr) {
+ void test(std::array<GrColor, 4> expected, PreTestFn preTestCallback = nullptr) {
auto shader = fBuilder->makeShader(/*localMatrix=*/nullptr, /*isOpaque=*/false);
if (!shader) {
REPORT_FAILURE(fReporter, "shader", SkString("Effect didn't produce a shader"));
@@ -341,34 +373,13 @@
paint.setShader(std::move(shader));
paint.setBlendMode(SkBlendMode::kSrc);
- canvas->save();
- if (preTestCallback) {
- preTestCallback(canvas, &paint);
- }
- canvas->drawPaint(paint);
- canvas->restore();
+ paint_canvas(canvas, &paint, preTestCallback);
- GrColor actual[4];
- SkImageInfo info = fSurface->imageInfo();
- if (!fSurface->readPixels(info, actual, info.minRowBytes(), 0, 0)) {
- REPORT_FAILURE(fReporter, "readPixels", SkString("readPixels failed"));
- return;
- }
-
- GrColor expected[4] = {TL, TR, BL, BR};
- if (0 != memcmp(actual, expected, sizeof(actual))) {
- REPORT_FAILURE(fReporter, "Runtime effect didn't match expectations",
- SkStringPrintf("\n"
- "Expected: [ %08x %08x %08x %08x ]\n"
- "Got : [ %08x %08x %08x %08x ]\n"
- "SkSL:\n%s\n",
- TL, TR, BL, BR, actual[0], actual[1], actual[2],
- actual[3], fBuilder->effect()->source().c_str()));
- }
+ verify_2x2_surface_results(fReporter, fBuilder->effect(), fSurface.get(), expected);
}
void test(GrColor expected, PreTestFn preTestCallback = nullptr) {
- this->test(expected, expected, expected, expected, preTestCallback);
+ this->test({expected, expected, expected, expected}, preTestCallback);
}
private:
@@ -377,6 +388,52 @@
SkTLazy<SkRuntimeShaderBuilder> fBuilder;
};
+class TestBlend {
+public:
+ TestBlend(skiatest::Reporter* r, sk_sp<SkSurface> surface)
+ : fReporter(r), fSurface(std::move(surface)) {}
+
+ void build(const char* src) {
+ auto [effect, errorText] = SkRuntimeEffect::MakeForBlender(SkString(src));
+ if (!effect) {
+ REPORT_FAILURE(fReporter, "effect",
+ SkStringPrintf("Effect didn't compile: %s", errorText.c_str()));
+ return;
+ }
+ fBuilder.init(std::move(effect));
+ }
+
+ SkRuntimeBlendBuilder::BuilderUniform uniform(const char* name) {
+ return fBuilder->uniform(name);
+ }
+
+ void test(std::array<GrColor, 4> expected, PreTestFn preTestCallback = nullptr) {
+ auto blender = fBuilder->makeBlender();
+ if (!blender) {
+ REPORT_FAILURE(fReporter, "blender", SkString("Effect didn't produce a blender"));
+ return;
+ }
+
+ SkCanvas* canvas = fSurface->getCanvas();
+ SkPaint paint;
+ paint.experimental_setBlender(std::move(blender));
+ paint.setColor(SK_ColorGRAY);
+
+ paint_canvas(canvas, &paint, preTestCallback);
+
+ verify_2x2_surface_results(fReporter, fBuilder->effect(), fSurface.get(), expected);
+ }
+
+ void test(GrColor expected, PreTestFn preTestCallback = nullptr) {
+ this->test({expected, expected, expected, expected}, preTestCallback);
+ }
+
+private:
+ skiatest::Reporter* fReporter;
+ sk_sp<SkSurface> fSurface;
+ SkTLazy<SkRuntimeBlendBuilder> fBuilder;
+};
+
// Produces a 2x2 bitmap shader, with opaque colors:
// [ Red, Green ]
// [ Blue, White ]
@@ -404,7 +461,7 @@
// Local coords
effect.build("half4 main(float2 p) { return half4(half2(p - 0.5), 0, 1); }");
- effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+ effect.test({0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF});
// Use of a simple uniform. (Draw twice with two values to ensure it's updated).
effect.build("uniform float4 gColor; half4 main(float2 p) { return half4(gColor); }");
@@ -425,25 +482,25 @@
// make sure we're not saturating unexpectedly.
effect.build(
"half4 main(float2 p) { return half4(0.498 * (half2(sk_FragCoord.xy) - 0.5), 0, 1); }");
- effect.test(0xFF000000, 0xFF00007F, 0xFF007F00, 0xFF007F7F,
+ effect.test({0xFF000000, 0xFF00007F, 0xFF007F00, 0xFF007F7F},
[](SkCanvas* canvas, SkPaint*) { canvas->rotate(45.0f); });
// Runtime effects should use relaxed precision rules by default
effect.build("half4 main(float2 p) { return float4(p - 0.5, 0, 1); }");
- effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+ effect.test({0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF});
// ... and support *returning* float4 (aka vec4), not just half4
effect.build("float4 main(float2 p) { return float4(p - 0.5, 0, 1); }");
- effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+ effect.test({0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF});
effect.build("vec4 main(float2 p) { return float4(p - 0.5, 0, 1); }");
- effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+ effect.test({0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF});
// Mutating coords should work. (skbug.com/10918)
effect.build("vec4 main(vec2 p) { p -= 0.5; return vec4(p, 0, 1); }");
- effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+ effect.test({0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF});
effect.build("void moveCoords(inout vec2 p) { p -= 0.5; }"
"vec4 main(vec2 p) { moveCoords(p); return vec4(p, 0, 1); }");
- effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
+ effect.test({0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF});
//
// Sampling children
@@ -462,13 +519,13 @@
effect.build("uniform shader child;"
"half4 main(float2 p) { return sample(child, p); }");
effect.child("child") = rgbwShader;
- effect.test(0xFF0000FF, 0xFF00FF00, 0xFFFF0000, 0xFFFFFFFF);
+ effect.test({0xFF0000FF, 0xFF00FF00, 0xFFFF0000, 0xFFFFFFFF});
// Sampling with explicit coordinates (reflecting about the diagonal)
effect.build("uniform shader child;"
"half4 main(float2 p) { return sample(child, p.yx); }");
effect.child("child") = rgbwShader;
- effect.test(0xFF0000FF, 0xFFFF0000, 0xFF00FF00, 0xFFFFFFFF);
+ effect.test({0xFF0000FF, 0xFFFF0000, 0xFF00FF00, 0xFFFFFFFF});
//
// Helper functions
@@ -488,6 +545,73 @@
test_RuntimeEffect_Shaders(r, ctxInfo.directContext());
}
+static void test_RuntimeEffect_Blenders(skiatest::Reporter* r, GrRecordingContext* rContext) {
+ SkImageInfo info = SkImageInfo::Make(2, 2, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+ sk_sp<SkSurface> surface = rContext
+ ? SkSurface::MakeRenderTarget(rContext, SkBudgeted::kNo, info)
+ : SkSurface::MakeRaster(info);
+ REPORTER_ASSERT(r, surface);
+ TestBlend effect(r, surface);
+
+ using float4 = std::array<float, 4>;
+ using int4 = std::array<int, 4>;
+
+ // Use of a simple uniform. (Draw twice with two values to ensure it's updated).
+ effect.build("uniform float4 gColor; half4 main(half4 s, half4 d) { return half4(gColor); }");
+ effect.uniform("gColor") = float4{ 0.0f, 0.25f, 0.75f, 1.0f };
+ effect.test(0xFFBF4000);
+ effect.uniform("gColor") = float4{ 1.0f, 0.0f, 0.0f, 0.498f };
+ effect.test(0x7F0000FF); // Unlike SkShaders, we don't clamp here
+
+ // Same, with integer uniforms
+ effect.build("uniform int4 gColor;"
+ "half4 main(half4 s, half4 d) { return half4(gColor) / 255.0; }");
+ effect.uniform("gColor") = int4{ 0x00, 0x40, 0xBF, 0xFF };
+ effect.test(0xFFBF4000);
+ effect.uniform("gColor") = int4{ 0xFF, 0x00, 0x00, 0x7F };
+ effect.test(0x7F0000FF); // Unlike SkShaders, we don't clamp here
+
+ // Verify that mutating the source and destination colors is allowed
+ effect.build("half4 main(half4 s, half4 d) { s += d; d += s; return half4(1); }");
+ effect.test(0xFFFFFFFF);
+
+ // Verify that we can write out the source color (ignoring the dest color)
+ // This is equivalent to the kSrc blend mode.
+ effect.build("half4 main(half4 s, half4 d) { return s; }");
+ effect.test(0xFF888888);
+
+ // Fill the destination with a variety of colors (using the RGBW shader)
+ SkPaint paint;
+ paint.setShader(make_RGBW_shader());
+ paint.setBlendMode(SkBlendMode::kSrc);
+ surface->getCanvas()->drawPaint(paint);
+
+ // Verify that we can read back the dest color exactly as-is (ignoring the source color)
+ // This is equivalent to the kDst blend mode.
+ effect.build("half4 main(half4 s, half4 d) { return d; }");
+ effect.test({0xFF0000FF, 0xFF00FF00, 0xFFFF0000, 0xFFFFFFFF});
+
+ // Verify that we can invert the destination color (including the alpha channel).
+ // The expected outputs are the exact inverse of the previous test.
+ effect.build("half4 main(half4 s, half4 d) { return half4(1) - d; }");
+ effect.test({0x00FFFF00, 0x00FF00FF, 0x0000FFFF, 0x00000000});
+
+ // Verify that color values are clamped to 0 and 1.
+ effect.build("half4 main(half4 s, half4 d) { return half4(-1); }");
+ effect.test(0x00000000);
+ effect.build("half4 main(half4 s, half4 d) { return half4(2); }");
+ effect.test(0xFFFFFFFF);
+}
+
+DEF_TEST(SkRuntimeEffect_Blender_CPU, r) {
+ // TODO(skia:12080): add CPU support for SkBlender
+// test_RuntimeEffect_Blenders(r, /*rContext=*/nullptr);
+}
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRuntimeEffect_Blender_GPU, r, ctxInfo) {
+ test_RuntimeEffect_Blenders(r, ctxInfo.directContext());
+}
+
DEF_TEST(SkRuntimeShaderBuilderReuse, r) {
const char* kSource = R"(
uniform half x;