1 describe('Runtime shader effects', () => {
4 beforeEach(async () => {
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);
14 document.body.removeChild(container);
18 uniform float rad_scale;
19 uniform int2 in_center;
20 uniform float4 in_colors0;
21 uniform float4 in_colors1;
23 half4 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;
31 return half4(mix(in_colors0, in_colors1, t));
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');
41 const spiral = CanvasKit.RuntimeEffect.Make(spiralSkSL);
42 expect(spiral).toBeTruthy('could not compile program');
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');
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([
65 CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
66 1, 0, 0, 1, // solid red
67 0, 1, 0, 1], // solid green
69 paint.setShader(shader);
70 canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
76 reportSurface(surface, name, done);
79 it('can compile custom shader code', (done) => {
80 testRTShader('rtshader_spiral', done);
83 it('can apply a matrix to the shader', (done) => {
84 testRTShader('rtshader_spiral_translated', done, CanvasKit.Matrix.translated(-200, 100));
87 it('can provide a error handler for compilation errors', () => {
89 const spiral = CanvasKit.RuntimeEffect.Make(`invalid sksl code, I hope`, (e) => {
92 expect(spiral).toBeFalsy();
93 expect(error).toContain('error');
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');
103 const spiral = CanvasKit.RuntimeEffect.Make(spiralSkSL);
104 expect(spiral).toBeTruthy('could not compile program');
106 const canvas = surface.getCanvas();
107 const paint = new CanvasKit.Paint();
108 const shader = spiral.makeShader([
110 CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
111 1, 0, 0, 1, // solid red
112 0, 1, 0, 1]); // solid green
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);
118 const traceData = traced.debugTrace.writeTrace();
122 traced.shader.delete();
123 traced.debugTrace.delete();
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([
135 "uniform float rad_scale;",
136 "uniform int2 in_center;",
137 "uniform float4 in_colors0;",
138 "uniform float4 in_colors1;",
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;",
148 " return half4(mix(in_colors0, in_colors1, t));",
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());
160 const thresholdSkSL = `
161 uniform shader before_map;
162 uniform shader after_map;
163 uniform shader threshold_map;
165 uniform float cutoff;
168 float smooth_cutoff(float x) {
169 x = x * slope + (0.5 - slope * cutoff);
170 return clamp(x, 0, 1);
173 half4 main(float2 xy) {
174 half4 before = before_map.eval(xy);
175 half4 after = after_map.eval(xy);
177 float m = smooth_cutoff(threshold_map.eval(xy).r);
178 return mix(before, after, half(m));
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');
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');
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([
208 CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
212 const blendShader = thresholdEffect.makeShaderWithChildren(
214 [brickShader, mandrillShader, spiralShader], localMatrix);
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 canvas.clear(CanvasKit.WHITE);
222 paint.setShader(blendShader);
223 canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
226 mandrillImg.delete();
227 thresholdEffect.delete();
228 spiralEffect.delete();
229 brickShader.delete();
230 mandrillShader.delete();
231 spiralShader.delete();
232 blendShader.delete();
235 reportSurface(surface, name, done);
240 it('take other shaders as fragment processors', (done) => {
241 testChildrenShader('rtshader_children', done);
244 it('apply a local matrix to the children-based shader', (done) => {
245 testChildrenShader('rtshader_children_rotated', done, CanvasKit.Matrix.rotated(Math.PI/12));