1/*
2 * Copyright 2019 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "include/core/SkBitmap.h"
9#include "include/core/SkCanvas.h"
10#include "include/core/SkColorFilter.h"
11#include "include/core/SkData.h"
12#include "include/core/SkPaint.h"
13#include "include/core/SkSurface.h"
14#include "include/effects/SkRuntimeEffect.h"
15#include "include/gpu/GrDirectContext.h"
16#include "include/sksl/DSLRuntimeEffects.h"
17#include "src/core/SkRuntimeEffectPriv.h"
18#include "src/core/SkTLazy.h"
19#include "src/gpu/GrColor.h"
20#include "src/sksl/SkSLCompiler.h"
21#include "tests/Test.h"
22
23#include <algorithm>
24#include <thread>
25
26using namespace SkSL::dsl;
27
28class DSLTestEffect {
29public:
30    DSLTestEffect(skiatest::Reporter* r, sk_sp<SkSurface> surface)
31        : fReporter(r)
32        , fCaps(SkSL::ShaderCapsFactory::Standalone())
33        , fCompiler(std::make_unique<SkSL::Compiler>(fCaps.get()))
34        , fSurface(std::move(surface)) {}
35
36    void start() {
37        StartRuntimeShader(fCompiler.get());
38    }
39
40    void end(bool expectSuccess = true) {
41        SkRuntimeEffect::Options options;
42        SkRuntimeEffectPriv::EnableFragCoord(&options);
43        sk_sp<SkRuntimeEffect> effect = EndRuntimeShader(options);
44        REPORTER_ASSERT(fReporter, effect ? expectSuccess : !expectSuccess);
45        if (effect) {
46            fBuilder.init(std::move(effect));
47        }
48    }
49
50    SkRuntimeShaderBuilder::BuilderUniform uniform(skstd::string_view name) {
51        return fBuilder->uniform(SkString(name).c_str());
52    }
53
54    SkRuntimeShaderBuilder::BuilderChild child(skstd::string_view name) {
55        return fBuilder->child(SkString(name).c_str());
56    }
57
58    using PreTestFn = std::function<void(SkCanvas*, SkPaint*)>;
59
60    void test(GrColor TL, GrColor TR, GrColor BL, GrColor BR,
61              PreTestFn preTestCallback = nullptr) {
62        auto shader = fBuilder->makeShader(nullptr, false);
63        if (!shader) {
64            REPORT_FAILURE(fReporter, "shader", SkString("Effect didn't produce a shader"));
65            return;
66        }
67
68        SkCanvas* canvas = fSurface->getCanvas();
69        SkPaint paint;
70        paint.setShader(std::move(shader));
71        paint.setBlendMode(SkBlendMode::kSrc);
72
73        canvas->save();
74        if (preTestCallback) {
75            preTestCallback(canvas, &paint);
76        }
77        canvas->drawPaint(paint);
78        canvas->restore();
79
80        GrColor actual[4];
81        SkImageInfo info = fSurface->imageInfo();
82        if (!fSurface->readPixels(info, actual, info.minRowBytes(), 0, 0)) {
83            REPORT_FAILURE(fReporter, "readPixels", SkString("readPixels failed"));
84            return;
85        }
86
87        GrColor expected[4] = {TL, TR, BL, BR};
88        if (0 != memcmp(actual, expected, sizeof(actual))) {
89            REPORT_FAILURE(fReporter, "Runtime effect didn't match expectations",
90                           SkStringPrintf("\n"
91                                          "Expected: [ %08x %08x %08x %08x ]\n"
92                                          "Got     : [ %08x %08x %08x %08x ]\n"
93                                          "SkSL:\n%s\n",
94                                          TL, TR, BL, BR, actual[0], actual[1], actual[2],
95                                          actual[3], fBuilder->effect()->source().c_str()));
96        }
97    }
98
99    void test(GrColor expected, PreTestFn preTestCallback = nullptr) {
100        this->test(expected, expected, expected, expected, preTestCallback);
101    }
102
103private:
104    skiatest::Reporter*             fReporter;
105    SkSL::ShaderCapsPointer         fCaps;
106    std::unique_ptr<SkSL::Compiler> fCompiler;
107    sk_sp<SkSurface>                fSurface;
108    SkTLazy<SkRuntimeShaderBuilder> fBuilder;
109};
110
111static void test_RuntimeEffect_Shaders(skiatest::Reporter* r, GrRecordingContext* rContext) {
112    SkImageInfo info = SkImageInfo::Make(2, 2, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
113    sk_sp<SkSurface> surface = rContext
114                                    ? SkSurface::MakeRenderTarget(rContext, SkBudgeted::kNo, info)
115                                    : SkSurface::MakeRaster(info);
116    REPORTER_ASSERT(r, surface);
117    using float4 = std::array<float, 4>;
118    using int4 = std::array<int, 4>;
119    DSLTestEffect effect(r, surface);
120
121    // Local coords
122    {
123        effect.start();
124        Parameter p(kFloat2_Type, "p");
125        Function(kHalf4_Type, "main", p).define(
126            Return(Half4(Half2(p - 0.5), 0, 1))
127        );
128        effect.end();
129        effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
130    }
131
132    // Use of a simple uniform. (Draw twice with two values to ensure it's updated).
133    {
134        effect.start();
135        GlobalVar gColor(kUniform_Modifier, kFloat4_Type);
136        Declare(gColor);
137        Parameter p(kFloat2_Type, "p");
138        Function(kHalf4_Type, "main", p).define(
139            Return(Half4(gColor))
140        );
141        effect.end();
142        effect.uniform(SkString(gColor.name()).c_str()) = float4{ 0.0f, 0.25f, 0.75f, 1.0f };
143        effect.test(0xFFBF4000);
144        effect.uniform(SkString(gColor.name()).c_str()) = float4{ 1.0f, 0.0f, 0.0f, 0.498f };
145        effect.test(0x7F0000FF);  // Tests that we don't clamp to valid premul
146    }
147
148    // Same, with integer uniforms
149    {
150        effect.start();
151        GlobalVar gColor(kUniform_Modifier, kInt4_Type);
152        Declare(gColor);
153        Parameter p(kFloat2_Type, "p");
154        Function(kHalf4_Type, "main", p).define(
155            Return(Half4(gColor) / 255)
156        );
157        effect.end();
158        effect.uniform(SkString(gColor.name()).c_str()) = int4{ 0x00, 0x40, 0xBF, 0xFF };
159        effect.test(0xFFBF4000);
160        effect.uniform(SkString(gColor.name()).c_str()) = int4{ 0xFF, 0x00, 0x00, 0x7F };
161        effect.test(0x7F0000FF);  // Tests that we don't clamp to valid premul
162    }
163
164    // Test sk_FragCoord (device coords). Rotate the canvas to be sure we're seeing device coords.
165    // Since the surface is 2x2, we should see (0,0), (1,0), (0,1), (1,1). Multiply by 0.498 to
166    // make sure we're not saturating unexpectedly.
167    {
168        effect.start();
169        Parameter p(kFloat2_Type, "p");
170        Function(kHalf4_Type, "main", p).define(
171            Return(Half4(0.498 * (Half2(Swizzle(sk_FragCoord(), X, Y)) - 0.5), 0, 1))
172        );
173        effect.end();
174        effect.test(0xFF000000, 0xFF00007F, 0xFF007F00, 0xFF007F7F,
175                    [](SkCanvas* canvas, SkPaint*) { canvas->rotate(45.0f); });
176    }
177
178    // Runtime effects should use relaxed precision rules by default
179    {
180        effect.start();
181        Parameter p(kFloat2_Type, "p");
182        Function(kHalf4_Type, "main", p).define(
183            Return(Float4(p - 0.5, 0, 1))
184        );
185        effect.end();
186        effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
187    }
188
189    // ... and support *returning* float4, not just half4
190    {
191        effect.start();
192        Parameter p(kFloat2_Type, "p");
193        Function(kFloat4_Type, "main", p).define(
194            Return(Float4(p - 0.5, 0, 1))
195        );
196        effect.end();
197        effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
198    }
199
200    // Test error reporting. We put this before a couple of successful tests to ensure that a
201    // failure doesn't leave us in a broken state.
202    {
203        class SimpleErrorReporter : public SkSL::ErrorReporter {
204        public:
205            void handleError(skstd::string_view msg, SkSL::PositionInfo pos) override {
206                fMsg += msg;
207            }
208
209            SkSL::String fMsg;
210        } errorReporter;
211        effect.start();
212        SetErrorReporter(&errorReporter);
213        Parameter p(kFloat2_Type, "p");
214        Function(kHalf4_Type, "main", p).define(
215            Return(1) // Error, type mismatch
216        );
217        effect.end(false);
218        REPORTER_ASSERT(r, errorReporter.fMsg == "expected 'half4', but found 'int'");
219    }
220
221    // Mutating coords should work. (skbug.com/10918)
222    {
223        effect.start();
224        Parameter p(kFloat2_Type, "p");
225        Function(kFloat4_Type, "main", p).define(
226            p -= 0.5,
227            Return(Float4(p, 0, 1))
228        );
229        effect.end();
230        effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
231    }
232    {
233        effect.start();
234        Parameter p1(kInOut_Modifier, kFloat2_Type, "p");
235        Function moveCoords(kVoid_Type, "moveCoords", p1);
236        moveCoords.define(
237            p1 -= 0.5
238        );
239        Parameter p2(kFloat2_Type, "p");
240        Function(kFloat4_Type, "main", p2).define(
241            moveCoords(p2),
242            Return(Float4(p2, 0, 1))
243        );
244        effect.end();
245        effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
246    }
247
248    //
249    // Sampling children
250    //
251
252    // Sampling a null child should return the paint color
253    {
254        effect.start();
255        GlobalVar child(kUniform_Modifier, kShader_Type, "child");
256        Declare(child);
257        Parameter p2(kFloat2_Type, "p");
258        Function(kFloat4_Type, "main", p2).define(
259            Return(child.eval(p2))
260        );
261        effect.end();
262        effect.child(child.name()) = nullptr;
263        effect.test(0xFF00FFFF,
264                    [](SkCanvas*, SkPaint* paint) { paint->setColor4f({1.0f, 1.0f, 0.0f, 1.0f}); });
265    }
266}
267
268DEF_TEST(DSLRuntimeEffectSimple, r) {
269    test_RuntimeEffect_Shaders(r, nullptr);
270}
271
272DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DSLRuntimeEffectSimple_GPU, r, ctxInfo) {
273    test_RuntimeEffect_Shaders(r, ctxInfo.directContext());
274}
275