blob: 4220ebea994021b719d109322a0349113ff6e974 [file] [log] [blame]
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);
});
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
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');
});
it('can generate a debug trace', () => {
// We don't support debug tracing on GPU, so we always request a software canvas here.
const surface = CanvasKit.MakeSWCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface');
if (!surface) {
return;
}
const spiral = CanvasKit.RuntimeEffect.Make(spiralSkSL);
expect(spiral).toBeTruthy('could not compile program');
const canvas = surface.getCanvas();
const paint = new CanvasKit.Paint();
const shader = spiral.makeShader([
0.3,
CANVAS_WIDTH/2, CANVAS_HEIGHT/2,
1, 0, 0, 1, // solid red
0, 1, 0, 1]); // solid green
const traced = CanvasKit.RuntimeEffect.MakeTraced(shader, CANVAS_WIDTH/2, CANVAS_HEIGHT/2);
paint.setShader(traced.shader);
canvas.drawRect(CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), paint);
const traceData = traced.debugTrace.writeTrace();
paint.delete();
shader.delete();
spiral.delete();
traced.shader.delete();
traced.debugTrace.delete();
surface.delete();
const parsedTrace = JSON.parse(traceData);
expect(parsedTrace).toBeTruthy('could not parse trace JSON');
expect(parsedTrace.functions).toBeTruthy('debug trace does not include function list');
expect(parsedTrace.slots).toBeTruthy('debug trace does not include slot list');
expect(parsedTrace.trace).toBeTruthy('debug trace does not include trace data');
expect(parsedTrace.nonsense).toBeFalsy('debug trace includes a nonsense key');
expect(parsedTrace.mystery).toBeFalsy('debug trace includes a mystery key');
expect(parsedTrace.source).toEqual([
"",
"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));",
"}"
]);
});
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]);
const blendShader = thresholdEffect.makeShaderWithChildren(
[0.5, 5],
[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));
});
});