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