xref: /aosp_15_r20/external/skia/modules/canvaskit/tests/rtshader_test.js (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1describe('Runtime shader effects', () => {
2    let container;
3
4    beforeEach(async () => {
5        await EverythingLoaded;
6        container = document.createElement('div');
7        container.innerHTML = `
8            <canvas width=600 height=600 id=test></canvas>
9            <canvas width=600 height=600 id=report></canvas>`;
10        document.body.appendChild(container);
11    });
12
13    afterEach(() => {
14        document.body.removeChild(container);
15    });
16
17    const spiralSkSL = `
18uniform float rad_scale;
19uniform int2   in_center;
20uniform float4 in_colors0;
21uniform float4 in_colors1;
22
23half4 main(float2 p) {
24    float2 pp = p - float2(in_center);
25    float radius = sqrt(dot(pp, pp));
26    radius = sqrt(radius);
27    float angle = atan(pp.y / pp.x);
28    float t = (angle + 3.1415926/2) / (3.1415926);
29    t += radius * rad_scale;
30    t = fract(t);
31    return half4(mix(in_colors0, in_colors1, t));
32}`;
33
34    // TODO(kjlubick) rewrite testRTShader and callers to use gm.
35    const testRTShader = (name, done, localMatrix) => {
36        const surface = CanvasKit.MakeCanvasSurface('test');
37        expect(surface).toBeTruthy('Could not make surface');
38        if (!surface) {
39            return;
40        }
41        const spiral = CanvasKit.RuntimeEffect.Make(spiralSkSL);
42        expect(spiral).toBeTruthy('could not compile program');
43
44        expect(spiral.getUniformCount()     ).toEqual(4);
45        expect(spiral.getUniformFloatCount()).toEqual(11);
46        const center = spiral.getUniform(1);
47        expect(center).toBeTruthy('could not fetch numbered uniform');
48        expect(center.slot     ).toEqual(1);
49        expect(center.columns  ).toEqual(2);
50        expect(center.rows     ).toEqual(1);
51        expect(center.isInteger).toEqual(true);
52        const color_0 = spiral.getUniform(2);
53        expect(color_0).toBeTruthy('could not fetch numbered uniform');
54        expect(color_0.slot     ).toEqual(3);
55        expect(color_0.columns  ).toEqual(4);
56        expect(color_0.rows     ).toEqual(1);
57        expect(color_0.isInteger).toEqual(false);
58        expect(spiral.getUniformName(2)).toEqual('in_colors0');
59
60        const canvas = surface.getCanvas();
61        const paint = new CanvasKit.Paint();
62        canvas.clear(CanvasKit.BLACK); // black should not be visible
63        const shader = spiral.makeShader([
64            0.3,
65            CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
66            1, 0, 0, 1, // solid red
67            0, 1, 0, 1], // solid green
68            localMatrix);
69        paint.setShader(shader);
70        canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
71
72        paint.delete();
73        shader.delete();
74        spiral.delete();
75
76        reportSurface(surface, name, done);
77    };
78
79    it('can compile custom shader code', (done) => {
80        testRTShader('rtshader_spiral', done);
81    });
82
83    it('can apply a matrix to the shader', (done) => {
84        testRTShader('rtshader_spiral_translated', done, CanvasKit.Matrix.translated(-200, 100));
85    });
86
87    it('can provide a error handler for compilation errors', () => {
88        let error = '';
89        const spiral = CanvasKit.RuntimeEffect.Make(`invalid sksl code, I hope`, (e) => {
90            error = e;
91        });
92        expect(spiral).toBeFalsy();
93        expect(error).toContain('error');
94    });
95
96    it('can generate a debug trace', () => {
97        // We don't support debug tracing on GPU, so we always request a software canvas here.
98        const surface = CanvasKit.MakeSWCanvasSurface('test');
99        expect(surface).toBeTruthy('Could not make surface');
100        if (!surface) {
101            return;
102        }
103        const spiral = CanvasKit.RuntimeEffect.Make(spiralSkSL);
104        expect(spiral).toBeTruthy('could not compile program');
105
106        const canvas = surface.getCanvas();
107        const paint = new CanvasKit.Paint();
108        const shader = spiral.makeShader([
109            0.3,
110            CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
111            1, 0, 0, 1,   // solid red
112            0, 1, 0, 1]); // solid green
113
114        const traced = CanvasKit.RuntimeEffect.MakeTraced(shader, CANVAS_WIDTH/2, CANVAS_HEIGHT/2);
115        paint.setShader(traced.shader);
116        canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
117
118        const traceData = traced.debugTrace.writeTrace();
119        paint.delete();
120        shader.delete();
121        spiral.delete();
122        traced.shader.delete();
123        traced.debugTrace.delete();
124        surface.delete();
125
126        const parsedTrace = JSON.parse(traceData);
127        expect(parsedTrace).toBeTruthy('could not parse trace JSON');
128        expect(parsedTrace.functions).toBeTruthy('debug trace does not include function list');
129        expect(parsedTrace.slots).toBeTruthy('debug trace does not include slot list');
130        expect(parsedTrace.trace).toBeTruthy('debug trace does not include trace data');
131        expect(parsedTrace.nonsense).toBeFalsy('debug trace includes a nonsense key');
132        expect(parsedTrace.mystery).toBeFalsy('debug trace includes a mystery key');
133        expect(parsedTrace.source).toEqual([
134            "",
135            "uniform float rad_scale;",
136            "uniform int2   in_center;",
137            "uniform float4 in_colors0;",
138            "uniform float4 in_colors1;",
139            "",
140            "half4 main(float2 p) {",
141            "    float2 pp = p - float2(in_center);",
142            "    float radius = sqrt(dot(pp, pp));",
143            "    radius = sqrt(radius);",
144            "    float angle = atan(pp.y / pp.x);",
145            "    float t = (angle + 3.1415926/2) / (3.1415926);",
146            "    t += radius * rad_scale;",
147            "    t = fract(t);",
148            "    return half4(mix(in_colors0, in_colors1, t));",
149            "}"
150        ]);
151    });
152
153    const loadBrick = fetch(
154        '/assets/brickwork-texture.jpg')
155        .then((response) => response.arrayBuffer());
156    const loadMandrill = fetch(
157        '/assets/mandrill_512.png')
158        .then((response) => response.arrayBuffer());
159
160    const thresholdSkSL = `
161uniform shader before_map;
162uniform shader after_map;
163uniform shader threshold_map;
164
165uniform float cutoff;
166uniform float slope;
167
168float smooth_cutoff(float x) {
169    x = x * slope + (0.5 - slope * cutoff);
170    return clamp(x, 0, 1);
171}
172
173half4 main(float2 xy) {
174    half4 before = before_map.eval(xy);
175    half4 after = after_map.eval(xy);
176
177    float m = smooth_cutoff(threshold_map.eval(xy).r);
178    return mix(before, after, half(m));
179}`;
180
181    // TODO(kjlubick) rewrite testChildrenShader and callers to use gm.
182    const testChildrenShader = (name, done, localMatrix) => {
183        Promise.all([loadBrick, loadMandrill]).then((values) => {
184            catchException(done, () => {
185                const [brickData, mandrillData] = values;
186                const brickImg = CanvasKit.MakeImageFromEncoded(brickData);
187                expect(brickImg).toBeTruthy('brick image could not be loaded');
188                const mandrillImg = CanvasKit.MakeImageFromEncoded(mandrillData);
189                expect(mandrillImg).toBeTruthy('mandrill image could not be loaded');
190
191                const thresholdEffect = CanvasKit.RuntimeEffect.Make(thresholdSkSL);
192                expect(thresholdEffect).toBeTruthy('threshold did not compile');
193                const spiralEffect = CanvasKit.RuntimeEffect.Make(spiralSkSL);
194                expect(spiralEffect).toBeTruthy('spiral did not compile');
195
196                const brickShader = brickImg.makeShaderCubic(
197                    CanvasKit.TileMode.Decal, CanvasKit.TileMode.Decal,
198                    1/3 /*B*/, 1/3 /*C*/,
199                    CanvasKit.Matrix.scaled(CANVAS_WIDTH/brickImg.width(),
200                                            CANVAS_HEIGHT/brickImg.height()));
201                const mandrillShader = mandrillImg.makeShaderCubic(
202                    CanvasKit.TileMode.Decal, CanvasKit.TileMode.Decal,
203                    1/3 /*B*/, 1/3 /*C*/,
204                    CanvasKit.Matrix.scaled(CANVAS_WIDTH/mandrillImg.width(),
205                                            CANVAS_HEIGHT/mandrillImg.height()));
206                const spiralShader = spiralEffect.makeShader([
207                    0.8,
208                    CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
209                    1, 1, 1, 1,
210                    0, 0, 0, 1]);
211
212                const blendShader = thresholdEffect.makeShaderWithChildren(
213                    [0.5, 5],
214                    [brickShader, mandrillShader, spiralShader], localMatrix);
215
216                const surface = CanvasKit.MakeCanvasSurface('test');
217                expect(surface).toBeTruthy('Could not make surface');
218                const canvas = surface.getCanvas();
219                const paint = new CanvasKit.Paint();
220
221                paint.setShader(blendShader);
222                canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
223
224                brickImg.delete();
225                mandrillImg.delete();
226                thresholdEffect.delete();
227                spiralEffect.delete();
228                brickShader.delete();
229                mandrillShader.delete();
230                spiralShader.delete();
231                blendShader.delete();
232                paint.delete();
233
234                reportSurface(surface, name, done);
235            })();
236        });
237    }
238
239    it('take other shaders as fragment processors', (done) => {
240        testChildrenShader('rtshader_children', done);
241    });
242
243    it('apply a local matrix to the children-based shader', (done) => {
244        testChildrenShader('rtshader_children_rotated', done, CanvasKit.Matrix.rotated(Math.PI/12));
245    });
246
247    it('can generate runtime blender', (done) => {
248        const loadBrick = fetch(
249            '/assets/brickwork-texture.jpg')
250            .then((response) => response.arrayBuffer());
251        const loadMandrill = fetch(
252            '/assets/mandrill_512.png')
253            .then((response) => response.arrayBuffer());
254        Promise.all([loadBrick, loadMandrill]).then((values) => {
255            catchException(done, () => {
256                const screenSkSL = `
257                    vec4 main(vec4 src, vec4 dst) {
258                        return src + dst - src * dst;
259                    }
260                `;
261
262                const [brickData, mandrillData] = values;
263                const brickImg = CanvasKit.MakeImageFromEncoded(brickData);
264                expect(brickImg)
265                    .withContext('brick image could not be loaded')
266                    .toBeTruthy();
267                const mandrillImg = CanvasKit.MakeImageFromEncoded(mandrillData);
268                expect(mandrillImg)
269                    .withContext('mandrill image could not be loaded')
270                    .toBeTruthy();
271
272                const brickShader = brickImg.makeShaderCubic(
273                    CanvasKit.TileMode.Decal, CanvasKit.TileMode.Decal,
274                    1/3 /*B*/, 1/3 /*C*/,
275                    CanvasKit.Matrix.scaled(CANVAS_WIDTH/brickImg.width(),
276                                            CANVAS_HEIGHT/brickImg.height()));
277                const mandrillShader = mandrillImg.makeShaderCubic(
278                    CanvasKit.TileMode.Decal, CanvasKit.TileMode.Decal,
279                    1/3 /*B*/, 1/3 /*C*/,
280                    CanvasKit.Matrix.scaled(CANVAS_WIDTH/mandrillImg.width(),
281                                            CANVAS_HEIGHT/mandrillImg.height()));
282
283                const surface = CanvasKit.MakeCanvasSurface('test');
284                expect(surface)
285                    .withContext('Could not make surface')
286                    .toBeTruthy();
287                const canvas = surface.getCanvas();
288                const paint = new CanvasKit.Paint();
289
290                const screenEffect = CanvasKit.RuntimeEffect.MakeForBlender(screenSkSL);
291                expect(screenEffect)
292                    .withContext('could not compile program')
293                    .toBeTruthy();
294                expect(screenEffect.getUniformCount()     ).toEqual(0);
295                expect(screenEffect.getUniformFloatCount()).toEqual(0);
296                const screenBlender = screenEffect.makeBlender([]);
297
298                paint.setShader(brickShader);
299                canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
300                paint.setShader(mandrillShader);
301                paint.setBlender(screenBlender);
302                canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
303
304                brickImg.delete();
305                mandrillImg.delete();
306                brickShader.delete();
307                mandrillShader.delete();
308                paint.delete();
309                screenBlender.delete();
310                screenEffect.delete();
311
312                reportSurface(surface, 'rtblender', done);
313            })();
314        });
315    });
316});
317