diff --git a/packages/shader-transitions/src/webgl.test.ts b/packages/shader-transitions/src/webgl.test.ts new file mode 100644 index 0000000000..6b44fb99e8 --- /dev/null +++ b/packages/shader-transitions/src/webgl.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it, vi } from "vitest"; +import { createProgram } from "./webgl.js"; + +/** Model WebGL's rule that shader handles belong to the context that created them. */ +function createMockContext() { + const shaders = new Set(); + const gl = { + VERTEX_SHADER: 0x8b31, + FRAGMENT_SHADER: 0x8b30, + COMPILE_STATUS: 0x8b81, + LINK_STATUS: 0x8b82, + createShader: vi.fn(() => { + const shader = {}; + shaders.add(shader); + return shader; + }), + shaderSource: vi.fn(), + compileShader: vi.fn(), + getShaderParameter: vi.fn(() => true), + createProgram: vi.fn(() => ({})), + attachShader: vi.fn((_program: WebGLProgram, shader: WebGLShader) => { + if (!shaders.has(shader)) throw new Error("Shader belongs to another WebGL context"); + }), + linkProgram: vi.fn(), + getProgramParameter: vi.fn(() => true), + }; + return gl; +} + +describe("createProgram", () => { + it("uses context-owned shaders across multiple contexts and repeated calls", () => { + const first = createMockContext(); + const second = createMockContext(); + const fragment = "precision mediump float;void main(){gl_FragColor=vec4(1.0);}"; + + for (const gl of [first, second, first, second]) { + // The test double implements only the WebGL methods used to create a program. + expect(() => createProgram(gl as unknown as WebGLRenderingContext, fragment)).not.toThrow(); + } + + expect(first.linkProgram).toHaveBeenCalledTimes(2); + expect(second.linkProgram).toHaveBeenCalledTimes(2); + }); +}); diff --git a/packages/shader-transitions/src/webgl.ts b/packages/shader-transitions/src/webgl.ts index 1a2c3584e6..ed0bf931ad 100644 --- a/packages/shader-transitions/src/webgl.ts +++ b/packages/shader-transitions/src/webgl.ts @@ -23,8 +23,6 @@ export function setupQuad(gl: WebGLRenderingContext): WebGLBuffer { return buf; } -let cachedVertexShader: WebGLShader | null = null; - function compileShader(gl: WebGLRenderingContext, src: string, type: number): WebGLShader { const s = gl.createShader(type); if (!s) throw new Error("[HyperShader] Failed to create shader"); @@ -53,10 +51,7 @@ function linkProgram( } export function createProgram(gl: WebGLRenderingContext, fragSrc: string): WebGLProgram { - if (!cachedVertexShader) { - cachedVertexShader = compileShader(gl, vertSrc, gl.VERTEX_SHADER); - } - return linkProgram(gl, cachedVertexShader, fragSrc); + return createProgramWithVertex(gl, vertSrc, fragSrc); } export function createProgramWithVertex(