• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1describe('Runtime shader effects', () => {
2    let container;
3
4    beforeEach(async () => {
5        await LoadCanvasKit;
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    // On the SW backend, atan is not supported - a shader is returned, but
18    // it will draw blank.
19    const spiralSkSL = `
20uniform float rad_scale;
21uniform int2   in_center;
22uniform float4 in_colors0;
23uniform float4 in_colors1;
24
25half4 main(float2 p) {
26    float2 pp = p - float2(in_center);
27    float radius = sqrt(dot(pp, pp));
28    radius = sqrt(radius);
29    float angle = atan(pp.y / pp.x);
30    float t = (angle + 3.1415926/2) / (3.1415926);
31    t += radius * rad_scale;
32    t = fract(t);
33    return half4(mix(in_colors0, in_colors1, t));
34}`;
35
36    // TODO(kjlubick) rewrite testRTShader and callers to use gm.
37    const testRTShader = (name, done, localMatrix) => {
38        const surface = CanvasKit.MakeCanvasSurface('test');
39        expect(surface).toBeTruthy('Could not make surface');
40        if (!surface) {
41            return;
42        }
43        const spiral = CanvasKit.RuntimeEffect.Make(spiralSkSL);
44        expect(spiral).toBeTruthy('could not compile program');
45
46        expect(spiral.getUniformCount()     ).toEqual(4);
47        expect(spiral.getUniformFloatCount()).toEqual(11);
48        const center = spiral.getUniform(1);
49        expect(center).toBeTruthy('could not fetch numbered uniform');
50        expect(center.slot     ).toEqual(1);
51        expect(center.columns  ).toEqual(2);
52        expect(center.rows     ).toEqual(1);
53        expect(center.isInteger).toEqual(true);
54        const color_0 = spiral.getUniform(2);
55        expect(color_0).toBeTruthy('could not fetch numbered uniform');
56        expect(color_0.slot     ).toEqual(3);
57        expect(color_0.columns  ).toEqual(4);
58        expect(color_0.rows     ).toEqual(1);
59        expect(color_0.isInteger).toEqual(false);
60        expect(spiral.getUniformName(2)).toEqual('in_colors0');
61
62        const canvas = surface.getCanvas();
63        const paint = new CanvasKit.Paint();
64        canvas.clear(CanvasKit.BLACK); // black should not be visible
65        const shader = spiral.makeShader([
66            0.3,
67            CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
68            1, 0, 0, 1, // solid red
69            0, 1, 0, 1], // solid green
70            true, /*=opaque*/
71            localMatrix);
72        paint.setShader(shader);
73        canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
74
75        paint.delete();
76        shader.delete();
77        spiral.delete();
78
79        reportSurface(surface, name, done);
80    };
81
82    it('can compile custom shader code', (done) => {
83        testRTShader('rtshader_spiral', done);
84    });
85
86    it('can apply a matrix to the shader', (done) => {
87        testRTShader('rtshader_spiral_translated', done, CanvasKit.Matrix.translated(-200, 100));
88    });
89
90    it('can provide a error handler for compilation errors', () => {
91        let error = '';
92        const spiral = CanvasKit.RuntimeEffect.Make(`invalid sksl code, I hope`, (e) => {
93            error = e;
94        });
95        expect(spiral).toBeFalsy();
96        expect(error).toContain('error');
97    });
98
99    const loadBrick = fetch(
100        '/assets/brickwork-texture.jpg')
101        .then((response) => response.arrayBuffer());
102    const loadMandrill = fetch(
103        '/assets/mandrill_512.png')
104        .then((response) => response.arrayBuffer());
105
106    const thresholdSkSL = `
107uniform shader before_map;
108uniform shader after_map;
109uniform shader threshold_map;
110
111uniform float cutoff;
112uniform float slope;
113
114float smooth_cutoff(float x) {
115    x = x * slope + (0.5 - slope * cutoff);
116    return clamp(x, 0, 1);
117}
118
119half4 main(float2 xy) {
120    half4 before = before_map.eval(xy);
121    half4 after = after_map.eval(xy);
122
123    float m = smooth_cutoff(threshold_map.eval(xy).r);
124    return mix(before, after, half(m));
125}`;
126
127    // TODO(kjlubick) rewrite testChildrenShader and callers to use gm.
128    const testChildrenShader = (name, done, localMatrix) => {
129        Promise.all([loadBrick, loadMandrill]).then((values) => {
130            catchException(done, () => {
131                const [brickData, mandrillData] = values;
132                const brickImg = CanvasKit.MakeImageFromEncoded(brickData);
133                expect(brickImg).toBeTruthy('brick image could not be loaded');
134                const mandrillImg = CanvasKit.MakeImageFromEncoded(mandrillData);
135                expect(mandrillImg).toBeTruthy('mandrill image could not be loaded');
136
137                const thresholdEffect = CanvasKit.RuntimeEffect.Make(thresholdSkSL);
138                expect(thresholdEffect).toBeTruthy('threshold did not compile');
139                const spiralEffect = CanvasKit.RuntimeEffect.Make(spiralSkSL);
140                expect(spiralEffect).toBeTruthy('spiral did not compile');
141
142                const brickShader = brickImg.makeShaderCubic(
143                    CanvasKit.TileMode.Decal, CanvasKit.TileMode.Decal,
144                    1/3 /*B*/, 1/3 /*C*/,
145                    CanvasKit.Matrix.scaled(CANVAS_WIDTH/brickImg.width(),
146                                              CANVAS_HEIGHT/brickImg.height()));
147                const mandrillShader = mandrillImg.makeShaderCubic(
148                    CanvasKit.TileMode.Decal, CanvasKit.TileMode.Decal,
149                    1/3 /*B*/, 1/3 /*C*/,
150                    CanvasKit.Matrix.scaled(CANVAS_WIDTH/mandrillImg.width(),
151                                              CANVAS_HEIGHT/mandrillImg.height()));
152                const spiralShader = spiralEffect.makeShader([
153                    0.8,
154                    CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
155                    1, 1, 1, 1,
156                    0, 0, 0, 1], true);
157
158                const blendShader = thresholdEffect.makeShaderWithChildren(
159                    [0.5, 5],
160                    true, [brickShader, mandrillShader, spiralShader], localMatrix);
161
162                const surface = CanvasKit.MakeCanvasSurface('test');
163                expect(surface).toBeTruthy('Could not make surface');
164                const canvas = surface.getCanvas();
165                const paint = new CanvasKit.Paint();
166                canvas.clear(CanvasKit.WHITE);
167
168                paint.setShader(blendShader);
169                canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
170
171                brickImg.delete();
172                mandrillImg.delete();
173                thresholdEffect.delete();
174                spiralEffect.delete();
175                brickShader.delete();
176                mandrillShader.delete();
177                spiralShader.delete();
178                blendShader.delete();
179                paint.delete();
180
181                reportSurface(surface, name, done);
182            })();
183        });
184    }
185
186    it('take other shaders as fragment processors', (done) => {
187        testChildrenShader('rtshader_children', done);
188    });
189
190    it('apply a local matrix to the children-based shader', (done) => {
191        testChildrenShader('rtshader_children_rotated', done, CanvasKit.Matrix.rotated(Math.PI/12));
192    });
193});
194