135 lines
4.5 KiB
TypeScript
135 lines
4.5 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { createCheckerboardRenderer } from "./checkerboard";
|
|
import type { WebGlRendererContext } from "./types";
|
|
|
|
type FakeGlCall = {
|
|
name: string;
|
|
args: unknown[];
|
|
};
|
|
|
|
type FakeUniformLocation = {
|
|
name: string;
|
|
};
|
|
|
|
describe("checkerboard renderer", () => {
|
|
test("renders a clipped checkerboard with one draw call", () => {
|
|
const fake = createFakeGl();
|
|
const renderer = createCheckerboardRenderer(createFakeContext(fake.gl, 100, 80));
|
|
|
|
renderer.render({ x: -5, y: 3, w: 30, h: 20 }, 12);
|
|
|
|
expect(fake.calls.filter((call) => call.name === "drawArrays")).toHaveLength(1);
|
|
expect(fake.calls.find((call) => call.name === "scissor")?.args).toEqual([0, 57, 25, 20]);
|
|
expect(fake.uniforms.get("u_origin")).toEqual([-5, 3]);
|
|
expect(fake.uniforms.get("u_squareSize")).toEqual([12]);
|
|
expect(Array.from(fake.bufferData.at(-1) ?? [])).toEqual([0, 3, 25, 3, 0, 23, 0, 23, 25, 3, 25, 23]);
|
|
});
|
|
|
|
test("skips fully clipped checkerboards", () => {
|
|
const fake = createFakeGl();
|
|
const renderer = createCheckerboardRenderer(createFakeContext(fake.gl, 100, 80));
|
|
|
|
renderer.render({ x: 120, y: 0, w: 20, h: 20 }, 8);
|
|
|
|
expect(fake.calls.filter((call) => call.name === "drawArrays")).toHaveLength(0);
|
|
});
|
|
|
|
test("disposes WebGL resources", () => {
|
|
const fake = createFakeGl();
|
|
const renderer = createCheckerboardRenderer(createFakeContext(fake.gl, 100, 80));
|
|
|
|
renderer.dispose();
|
|
|
|
expect(fake.calls.filter((call) => call.name === "deleteBuffer")).toHaveLength(1);
|
|
expect(fake.calls.filter((call) => call.name === "deleteProgram")).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
function createFakeContext(gl: WebGL2RenderingContext, width: number, height: number): WebGlRendererContext {
|
|
return {
|
|
gl,
|
|
canvas: { width, height } as HTMLCanvasElement,
|
|
};
|
|
}
|
|
|
|
function createFakeGl() {
|
|
const calls: FakeGlCall[] = [];
|
|
const uniforms = new Map<string, number[]>();
|
|
const bufferData: Float32Array[] = [];
|
|
const shader = {};
|
|
const program = {};
|
|
const buffer = {};
|
|
const noop = () => {};
|
|
|
|
const gl = {
|
|
ARRAY_BUFFER: 0x8892,
|
|
BLEND: 0x0be2,
|
|
COMPILE_STATUS: 0x8b81,
|
|
DYNAMIC_DRAW: 0x88e8,
|
|
FLOAT: 0x1406,
|
|
FRAGMENT_SHADER: 0x8b30,
|
|
LINK_STATUS: 0x8b82,
|
|
SCISSOR_TEST: 0x0c11,
|
|
TRIANGLES: 0x0004,
|
|
VERTEX_SHADER: 0x8b31,
|
|
attachShader: noop,
|
|
bindBuffer: noop,
|
|
bufferData: (...args: [number, BufferSource, number]) => {
|
|
const data = args[1];
|
|
if (data instanceof Float32Array) bufferData.push(data);
|
|
},
|
|
compileShader: noop,
|
|
createBuffer: () => buffer,
|
|
createProgram: () => program,
|
|
createShader: () => shader,
|
|
deleteBuffer: () => {
|
|
calls.push({ name: "deleteBuffer", args: [] });
|
|
},
|
|
deleteProgram: () => {
|
|
calls.push({ name: "deleteProgram", args: [] });
|
|
},
|
|
deleteShader: noop,
|
|
disable: (capability: number) => {
|
|
calls.push({ name: "disable", args: [capability] });
|
|
},
|
|
disableVertexAttribArray: (index: number) => {
|
|
calls.push({ name: "disableVertexAttribArray", args: [index] });
|
|
},
|
|
drawArrays: (mode: number, first: number, count: number) => {
|
|
calls.push({ name: "drawArrays", args: [mode, first, count] });
|
|
},
|
|
enable: (capability: number) => {
|
|
calls.push({ name: "enable", args: [capability] });
|
|
},
|
|
enableVertexAttribArray: (index: number) => {
|
|
calls.push({ name: "enableVertexAttribArray", args: [index] });
|
|
},
|
|
getAttribLocation: () => 0,
|
|
getProgramInfoLog: () => null,
|
|
getProgramParameter: () => true,
|
|
getShaderInfoLog: () => null,
|
|
getShaderParameter: () => true,
|
|
getUniformLocation: (...args: [unknown, string]) => ({ name: args[1] }),
|
|
linkProgram: noop,
|
|
scissor: (x: number, y: number, w: number, h: number) => {
|
|
calls.push({ name: "scissor", args: [x, y, w, h] });
|
|
},
|
|
shaderSource: noop,
|
|
uniform1f: (location: FakeUniformLocation, value: number) => {
|
|
uniforms.set(location.name, [value]);
|
|
},
|
|
uniform2f: (location: FakeUniformLocation, x: number, y: number) => {
|
|
uniforms.set(location.name, [x, y]);
|
|
},
|
|
uniform4fv: (location: FakeUniformLocation, value: Iterable<number>) => {
|
|
uniforms.set(location.name, Array.from(value));
|
|
},
|
|
useProgram: noop,
|
|
vertexAttribPointer: (index: number, size: number, type: number, normalized: boolean, stride: number, offset: number) => {
|
|
calls.push({ name: "vertexAttribPointer", args: [index, size, type, normalized, stride, offset] });
|
|
},
|
|
} as unknown as WebGL2RenderingContext;
|
|
|
|
return { gl, calls, uniforms, bufferData };
|
|
}
|