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