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