text: describe('Runtime shader effects', () => {
      let container;
  
      beforeEach(async () => {
          await LoadCanvasKit;
          container = document.createElement('div');
          container.innerHTML = `
              <canvas width=600 height=600 id=test></canvas>
              <canvas width=600 height=600 id=report></canvas>`;
          document.body.appendChild(container);
      });
  
      afterEach(() => {
          document.body.removeChild(container);
      });
  
      // On the SW backend, atan is not supported - a shader is returned, but
      // it will draw blank.
      const spiralSkSL = `
  uniform float rad_scale;
  uniform int2   in_center;
  uniform float4 in_colors0;
  uniform float4 in_colors1;
  
  half4 main(float2 p) {
      float2 pp = p - float2(in_center);
      float radius = sqrt(dot(pp, pp));
      radius = sqrt(radius);
      float angle = atan(pp.y / pp.x);
      float t = (angle + 3.1415926/2) / (3.1415926);
      t += radius * rad_scale;
      t = fract(t);
      return half4(mix(in_colors0, in_colors1, t));
  }`;
  
      // TODO(kjlubick) rewrite testRTShader and callers to use gm.
      const testRTShader = (name, done, localMatrix) => {
          const surface = CanvasKit.MakeCanvasSurface('test');
          expect(surface).toBeTruthy('Could not make surface');
          if (!surface) {
              return;
          }
          const spiral = CanvasKit.RuntimeEffect.Make(spiralSkSL);
          expect(spiral).toBeTruthy('could not compile program');
  
          expect(spiral.getUniformCount()     ).toEqual(4);
          expect(spiral.getUniformFloatCount()).toEqual(11);
          const center = spiral.getUniform(1);
          expect(center).toBeTruthy('could not fetch numbered uniform');
          expect(center.slot     ).toEqual(1);
          expect(center.columns  ).toEqual(2);
          expect(center.rows     ).toEqual(1);
          expect(center.isInteger).toEqual(true);
          const color_0 = spiral.getUniform(2);
          expect(color_0).toBeTruthy('could not fetch numbered uniform');
          expect(color_0.slot     ).toEqual(3);
          expect(color_0.columns  ).toEqual(4);
          expect(color_0.rows     ).toEqual(1);
          expect(color_0.isInteger).toEqual(false);
          expect(spiral.getUniformName(2)).toEqual('in_colors0');
  
          const canvas = surface.getCanvas();
          const paint = new CanvasKit.Paint();
          canvas.clear(CanvasKit.BLACK); // black should not be visible
          const shader = spiral.makeShader([
              0.3,
              CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
              1, 0, 0, 1, // solid red
              0, 1, 0, 1], // solid green
              true, /*=opaque*/
              localMatrix);
          paint.setShader(shader);
          canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
  
          paint.delete();
          shader.delete();
          spiral.delete();
  
          reportSurface(surface, name, done);
      };
  
      it('can compile custom shader code', (done) => {
          testRTShader('rtshader_spiral', done);
      });
  
      it('can apply a matrix to the shader', (done) => {
          testRTShader('rtshader_spiral_translated', done, CanvasKit.Matrix.translated(-200, 100));
      });
  
      it('can provide a error handler for compilation errors', () => {
          let error = '';
          const spiral = CanvasKit.RuntimeEffect.Make(`invalid sksl code, I hope`, (e) => {
              error = e;
          });
          expect(spiral).toBeFalsy();
          expect(error).toContain('error');
      });
  
      const loadBrick = fetch(
          '/assets/brickwork-texture.jpg')
          .then((response) => response.arrayBuffer());
      const loadMandrill = fetch(
          '/assets/mandrill_512.png')
          .then((response) => response.arrayBuffer());
  
      const thresholdSkSL = `
  uniform shader before_map;
  uniform shader after_map;
  uniform shader threshold_map;
  
  uniform float cutoff;
  uniform float slope;
  
  float smooth_cutoff(float x) {
      x = x * slope + (0.5 - slope * cutoff);
      return clamp(x, 0, 1);
  }
  
  half4 main(float2 xy) {
      half4 before = before_map.eval(xy);
      half4 after = after_map.eval(xy);
  
      float m = smooth_cutoff(threshold_map.eval(xy).r);
      return mix(before, after, half(m));
  }`;
  
      // TODO(kjlubick) rewrite testChildrenShader and callers to use gm.
      const testChildrenShader = (name, done, localMatrix) => {
          Promise.all([loadBrick, loadMandrill]).then((values) => {
              catchException(done, () => {
                  const [brickData, mandrillData] = values;
                  const brickImg = CanvasKit.MakeImageFromEncoded(brickData);
                  expect(brickImg).toBeTruthy('brick image could not be loaded');
                  const mandrillImg = CanvasKit.MakeImageFromEncoded(mandrillData);
                  expect(mandrillImg).toBeTruthy('mandrill image could not be loaded');
  
                  const thresholdEffect = CanvasKit.RuntimeEffect.Make(thresholdSkSL);
                  expect(thresholdEffect).toBeTruthy('threshold did not compile');
                  const spiralEffect = CanvasKit.RuntimeEffect.Make(spiralSkSL);
                  expect(spiralEffect).toBeTruthy('spiral did not compile');
  
                  const brickShader = brickImg.makeShaderCubic(
                      CanvasKit.TileMode.Decal, CanvasKit.TileMode.Decal,
                      1/3 /*B*/, 1/3 /*C*/,
                      CanvasKit.Matrix.scaled(CANVAS_WIDTH/brickImg.width(),
                                                CANVAS_HEIGHT/brickImg.height()));
                  const mandrillShader = mandrillImg.makeShaderCubic(
                      CanvasKit.TileMode.Decal, CanvasKit.TileMode.Decal,
                      1/3 /*B*/, 1/3 /*C*/,
                      CanvasKit.Matrix.scaled(CANVAS_WIDTH/mandrillImg.width(),
                                                CANVAS_HEIGHT/mandrillImg.height()));
                  const spiralShader = spiralEffect.makeShader([
                      0.8,
                      CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
                      1, 1, 1, 1,
                      0, 0, 0, 1], true);
  
                  const blendShader = thresholdEffect.makeShaderWithChildren(
                      [0.5, 5],
                      true, [brickShader, mandrillShader, spiralShader], localMatrix);
  
                  const surface = CanvasKit.MakeCanvasSurface('test');
                  expect(surface).toBeTruthy('Could not make surface');
                  const canvas = surface.getCanvas();
                  const paint = new CanvasKit.Paint();
                  canvas.clear(CanvasKit.WHITE);
  
                  paint.setShader(blendShader);
                  canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
  
                  brickImg.delete();
                  mandrillImg.delete();
                  thresholdEffect.delete();
                  spiralEffect.delete();
                  brickShader.delete();
                  mandrillShader.delete();
                  spiralShader.delete();
                  blendShader.delete();
                  paint.delete();
  
                  reportSurface(surface, name, done);
              })();
          });
      }
  
      it('take other shaders as fragment processors', (done) => {
          testChildrenShader('rtshader_children', done);
      });
  
      it('apply a local matrix to the children-based shader', (done) => {
          testChildrenShader('rtshader_children_rotated', done, CanvasKit.Matrix.rotated(Math.PI/12));
      });
  });
  