skia2/tests/SkRuntimeEffectTest.cpp
Michael Ludwig 22534f2098 Expose sk_FragCoord for runtime effects
This is needed for the inline dither effect to use device coordinates
instead of local coords for dithering.

The builtin is not "in float4" because that trips up the runtime effect
assertions about what types of in variables are allowed.

Change-Id: I580fc461fdc9cbd812592b2571f51868a7a3ea4b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/292262
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
2020-05-27 23:06:09 +00:00

170 lines
6.5 KiB
C++

/*
* Copyright 2019 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkCanvas.h"
#include "include/core/SkPaint.h"
#include "include/core/SkSurface.h"
#include "include/effects/SkRuntimeEffect.h"
#include "include/gpu/GrContext.h"
#include "src/core/SkTLazy.h"
#include "tests/Test.h"
#include <algorithm>
DEF_TEST(SkRuntimeEffectInvalid, r) {
auto test = [r](const char* hdr, const char* body, const char* expected) {
SkString src = SkStringPrintf("%s void main(float2 p, inout half4 color) { %s }",
hdr, body);
auto[effect, errorText] = SkRuntimeEffect::Make(src);
REPORTER_ASSERT(r, !effect);
REPORTER_ASSERT(r, errorText.contains(expected),
"Expected error message to contain \"%s\". Actual message: \"%s\"",
expected, errorText.c_str());
};
// Features that are only allowed in .fp files (key, in uniform, ctype, when, tracked).
// Ensure that these fail, and the error messages contain the relevant keyword.
test("layout(key) in bool Input;", "", "key");
test("in uniform float Input;", "", "in uniform");
test("layout(ctype=SkRect) float4 Input;", "", "ctype");
test("in bool Flag; layout(when=Flag) uniform float Input;", "", "when");
test("layout(tracked) uniform float Input;", "", "tracked");
// Runtime SkSL supports a limited set of uniform types. No samplers, for example:
test("uniform sampler2D s;", "", "sampler2D");
// 'in' variables can't be arrays
test("in int Input[2];", "", "array");
// Type specific restrictions:
// 'bool', 'int' can't be 'uniform'
test("uniform bool Input;", "", "'uniform'");
test("uniform int Input;", "", "'uniform'");
// vector and matrix types can't be 'in'
test("in float2 Input;", "", "'in'");
test("in half3x3 Input;", "", "'in'");
test("half missing();", "color.r = missing();", "undefined function");
}
// Our packing rules and unit test code here relies on this:
static_assert(sizeof(bool) == 1);
class TestEffect {
public:
TestEffect(skiatest::Reporter* r, const char* hdr, const char* body) {
SkString src = SkStringPrintf("%s void main(float2 p, inout half4 color) { %s }",
hdr, body);
auto[effect, errorText] = SkRuntimeEffect::Make(src);
if (!effect) {
REPORT_FAILURE(r, "effect",
SkStringPrintf("Effect didn't compile: %s", errorText.c_str()));
return;
}
fBuilder.init(std::move(effect));
}
SkRuntimeShaderBuilder::BuilderInput operator[](const char* name) {
return fBuilder->input(name);
}
void test(skiatest::Reporter* r, sk_sp<SkSurface> surface,
uint32_t TL, uint32_t TR, uint32_t BL, uint32_t BR) {
auto shader = fBuilder->makeShader(nullptr, false);
if (!shader) {
REPORT_FAILURE(r, "shader", SkString("Effect didn't produce a shader"));
return;
}
SkPaint paint;
paint.setShader(std::move(shader));
paint.setBlendMode(SkBlendMode::kSrc);
surface->getCanvas()->drawPaint(paint);
uint32_t actual[4];
SkImageInfo info = surface->imageInfo();
if (!surface->readPixels(info, actual, info.minRowBytes(), 0, 0)) {
REPORT_FAILURE(r, "readPixels", SkString("readPixels failed"));
return;
}
uint32_t expected[4] = {TL, TR, BL, BR};
if (memcmp(actual, expected, sizeof(actual)) != 0) {
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",
TL, TR, BL, BR, actual[0], actual[1], actual[2],
actual[3], fBuilder->fEffect->source().c_str()));
}
}
void test(skiatest::Reporter* r, sk_sp<SkSurface> surface, uint32_t expected) {
this->test(r, surface, expected, expected, expected, expected);
}
private:
SkTLazy<SkRuntimeShaderBuilder> fBuilder;
};
static void test_RuntimeEffect_Shaders(skiatest::Reporter* r, GrContext* context) {
SkImageInfo info = SkImageInfo::Make(2, 2, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
sk_sp<SkSurface> surface;
if (context) {
surface = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info);
} else {
surface = SkSurface::MakeRaster(info);
}
REPORTER_ASSERT(r, surface);
TestEffect xy(r, "", "color = half4(half2(p - 0.5), 0, 1);");
xy.test(r, surface, 0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
using float4 = std::array<float, 4>;
// NOTE: For now, we always emit valid premul colors, until CPU and GPU agree on clamping
TestEffect uniformColor(r, "uniform float4 gColor;", "color = half4(gColor);");
uniformColor["gColor"] = float4{ 0.0f, 0.25f, 0.75f, 1.0f };
uniformColor.test(r, surface, 0xFFBF4000);
uniformColor["gColor"] = float4{ 0.75f, 0.25f, 0.0f, 1.0f };
uniformColor.test(r, surface, 0xFF0040BF);
TestEffect pickColor(r, "in int flag; uniform half4 gColors[2];", "color = gColors[flag];");
pickColor["gColors"] =
std::array<float4, 2>{float4{1.0f, 0.0f, 0.0f, 0.498f}, float4{0.0f, 1.0f, 0.0f, 1.0f}};
pickColor["flag"] = 0;
pickColor.test(r, surface, 0x7F00007F); // Tests that we clamp to valid premul
pickColor["flag"] = 1;
pickColor.test(r, surface, 0xFF00FF00);
TestEffect inlineColor(r, "in half c;", "color = half4(c, c, c, 1);");
inlineColor["c"] = 0.498f;
inlineColor.test(r, surface, 0xFF7F7F7F);
// Test sk_FragCoord, which we output to color. Since the surface is 2x2, we should see
// (0,0), (1,0), (0,1), (1,1), multiply by 0.498 to make sure we're not saturating unexpectedly.
// TODO: Remove this when sk_FragCoord is supported by interpreter.
if (context) {
TestEffect fragCoord(r, "", "color = half4(0.498 * (half2(sk_FragCoord.xy) - 0.5), 0, 1);");
fragCoord.test(r, surface, 0xFF000000, 0xFF00007F, 0xFF007F00, 0xFF007F7F);
}
}
DEF_TEST(SkRuntimeEffectSimple, r) {
test_RuntimeEffect_Shaders(r, nullptr);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRuntimeEffectSimple_GPU, r, ctxInfo) {
test_RuntimeEffect_Shaders(r, ctxInfo.grContext());
}