import { describe, expect, it } from "vitest"; import type { ToolcraftGpuPassExecution } from "../rendering"; import { createToolcraftEnvelopeValidationContext } from "./performance-envelope-validation-context"; import { deriveToolcraftGpuPressure, getToolcraftRendererGpuErrors, } from "./performance-renderer-gpu-validation"; import type { ToolcraftEnvelopePerformanceConfig, ToolcraftRendererTechnique, } from "./performance-types"; import { createSchema } from "./performance-workload-envelope-test-fixtures"; const gpuPass = { resources: "storage-buffers", stage: "compute", state: "stateless", surfaces: ["preview"], } as const satisfies ToolcraftGpuPassExecution; const vgpu = { backend: "webgpu", capability: "shader-webgpu-vgpu", provider: "vgpu", versionPolicy: "app-pinned", } as const; const webglCompatibility = { backend: "webgl", exception: { evidence: "Required browser support excludes WebGPU.", kind: "browser-compatibility", }, provider: "native", } as const; const webglReference = { backend: "webgl", exception: { evidence: "The inspected reference runtime requires WebGL.", kind: "reference-parity", }, provider: "reference-runtime", } as const; const statelessFragmentGpu = { resources: "uniforms-only", stage: "render", state: "stateless", surfaces: ["preview"], } as const satisfies ToolcraftGpuPassExecution; function createPipeline(gpu: ToolcraftGpuPassExecution) { return { interactionInvalidation: [], passes: [ { gpu, id: "effect", inputs: [], invalidatedBy: [], kind: "pixel-transform", output: (gpu.surfaces as readonly string[]).includes("preview") ? "preview" : "export", quality: (gpu.surfaces as readonly string[]).includes("preview") ? "full" : "export", runsOn: "gpu", }, ], }; } function createTechnique( gpu: unknown, overrides: Partial = {}, ): ToolcraftRendererTechnique { return { exportRenderer: "webgpu", fidelityRisks: ["GPU precision can differ between devices."], gpu, performanceRisks: ["Shader work can exceed the frame budget."], previewRenderer: "webgpu", productRepresentation: "pixel", rendererStrategy: "webgpu", sourceRepresentation: "procedural-data", whyNotAlternativeStrategies: ["The workload requires GPU execution."], ...overrides, } as ToolcraftRendererTechnique; } function getErrors(options: { gpu: unknown; passGpu?: ToolcraftGpuPassExecution; technique?: Partial; }): string[] { const technique = createTechnique(options.gpu, options.technique); const config = { rendererPipeline: createPipeline(options.passGpu ?? gpuPass), rendererStrategy: technique.rendererStrategy, rendererTechnique: technique, scenarios: [], usesCustomRenderer: true, workloadEnvelope: { dimensions: [] }, } as ToolcraftEnvelopePerformanceConfig; return getToolcraftRendererGpuErrors( createToolcraftEnvelopeValidationContext(createSchema(), config), ); } describe("renderer GPU pressure and provider policy", () => { it.each([ [ "compute defaults to WebGPU", { ...gpuPass, state: "stateless", stage: "compute" }, { preview: { backend: "webgl", provider: "three" } }, "creates WebGPU pressure", ], [ "feedback defaults to WebGPU", { ...gpuPass, resources: "sampled-textures", stage: "render", state: "feedback", }, { preview: { backend: "webgl", provider: "three" } }, "creates WebGPU pressure", ], [ "storage defaults to WebGPU", { ...gpuPass, resources: "storage-textures", stage: "render", state: "stateless", }, { preview: { backend: "webgl", provider: "three" } }, "creates WebGPU pressure", ], [ "compatibility evidence permits WebGL", gpuPass, { preview: webglCompatibility }, null, ], [ "reference evidence permits WebGL", gpuPass, { preview: webglReference }, null, ], ["new WebGPU uses VGPU", gpuPass, { preview: vgpu }, null], [ "native WebGPU needs API-gap evidence", gpuPass, { preview: { backend: "webgpu", provider: "native" } }, "must use the approved VGPU provider", ], [ "runtime WebGPU names its owner", gpuPass, { preview: { backend: "webgpu", provider: "toolcraft-runtime" } }, "must name its runtime owner", ], [ "stateless fragment may remain WebGL", { ...gpuPass, resources: "uniforms-only", stage: "render", state: "stateless", }, { preview: webglCompatibility }, null, ], ] as const)("%s", (_name, passGpu, gpu, expectedError) => { const previewRenderer = gpu.preview.backend; const errors = getErrors({ gpu, passGpu: passGpu as ToolcraftGpuPassExecution, technique: { exportRenderer: "none", previewRenderer, rendererStrategy: previewRenderer, }, }); if (expectedError === null) { expect(errors).toEqual([]); } else { expect(errors.some((error) => error.includes(expectedError))).toBe(true); } }); it("does not let export-only compute pressure a WebGL preview", () => { expect( getErrors({ gpu: { export: vgpu, preview: webglCompatibility }, passGpu: { ...gpuPass, surfaces: ["export"] }, technique: { exportRenderer: "webgpu", previewRenderer: "webgl", rendererStrategy: "webgl", }, }), ).toEqual([]); }); it("derives and freezes canonical pressure without leaking other surfaces", () => { const config = { rendererPipeline: createPipeline({ ...gpuPass, state: "feedback", surfaces: ["preview", "export"], }), rendererStrategy: "webgpu", scenarios: [], usesCustomRenderer: true, workloadEnvelope: { dimensions: [] }, } as ToolcraftEnvelopePerformanceConfig; const context = createToolcraftEnvelopeValidationContext(createSchema(), config); const pressure = deriveToolcraftGpuPressure(context.parsedPipeline, "preview"); expect(pressure).toEqual({ passIds: ["effect"], reasons: ["compute", "feedback", "storage"], surface: "preview", }); expect(Object.isFrozen(pressure)).toBe(true); expect(Object.isFrozen(pressure.passIds)).toBe(true); expect(Object.isFrozen(pressure.reasons)).toBe(true); }); }); describe("renderer GPU surface ownership", () => { it.each(["webgl", "webgpu"] as const)( "requires rendererTechnique.gpu for the %s renderer strategy", (rendererStrategy) => { expect( getErrors({ gpu: undefined, technique: { exportRenderer: "none", previewRenderer: rendererStrategy, rendererStrategy, }, }), ).toContain( "GPU renderer strategies must declare rendererTechnique.gpu.", ); }, ); it.each(["dom", "svg", "canvas-2d"] as const)( "allows an offscreen GPU pass for a %s final renderer", (rendererStrategy) => { expect( getErrors({ gpu: { preview: vgpu }, technique: { exportRenderer: "none", previewRenderer: rendererStrategy, rendererStrategy, }, }), ).toEqual([]); }, ); it("forbids an unused GPU surface entry", () => { expect( getErrors({ gpu: { export: vgpu, preview: vgpu }, technique: { exportRenderer: "none" }, }).some((error) => error.includes("does not target a GPU pass or GPU final renderer")), ).toBe(true); }); it("matches a GPU final renderer backend", () => { expect( getErrors({ gpu: { preview: webglCompatibility }, technique: { exportRenderer: "none" }, }).some((error) => error.includes('must match final renderer "webgpu"')), ).toBe(true); }); it("rejects an empty GPU map and a multi-owner surface", () => { expect( getErrors({ gpu: {}, technique: { exportRenderer: "none" } }).some((error) => error.includes("must be non-empty"), ), ).toBe(true); expect( getErrors({ gpu: { preview: [vgpu, webglCompatibility] }, technique: { exportRenderer: "none" }, }).some((error) => error.includes("exactly one backend/provider")), ).toBe(true); }); it.each(["short", " evidence long enough "])( "requires trimmed evidence of at least twelve characters: %s", (evidence) => { expect( getErrors({ gpu: { preview: { ...webglCompatibility, exception: { ...webglCompatibility.exception, evidence }, }, }, technique: { exportRenderer: "none", previewRenderer: "webgl", rendererStrategy: "webgl", }, }).some((error) => error.includes("trimmed evidence of at least 12 characters")), ).toBe(true); }, ); it("enforces the exact VGPU capability and version policy", () => { expect( getErrors({ gpu: { preview: { ...vgpu, capability: "custom", versionPolicy: "latest" }, }, technique: { exportRenderer: "none" }, }).some((error) => error.includes("fixed VGPU capability and version policy")), ).toBe(true); }); it("never accepts a fallback as the primary backend", () => { const errors = getErrors({ gpu: { preview: { fallback: vgpu } }, technique: { exportRenderer: "none" }, }); expect(errors.some((error) => error.includes("fallback cannot satisfy or replace"))).toBe( true, ); expect(errors.some((error) => error.includes("exactly one backend/provider"))).toBe(true); }); it.each([ [ "mixed Three and VGPU fields", { backend: "webgl", capability: "shader-webgpu-vgpu", provider: "three", versionPolicy: "app-pinned", }, ], ["mixed VGPU and runtime fields", { ...vgpu, owner: "product-renderer" }], [ "mixed runtime and VGPU fields", { backend: "webgpu", capability: "shader-webgpu-vgpu", owner: "product-renderer", provider: "toolcraft-runtime", }, ], ["native WebGL extra ownership", { ...webglCompatibility, owner: "app" }], [ "native WebGPU extra version policy", { backend: "webgpu", exception: { evidence: "VGPU lacks a required inspected API surface.", kind: "vgpu-api-gap", }, provider: "native", versionPolicy: "app-pinned", }, ], ["unknown provider key", { ...webglReference, debug: true }], [ "unknown exception key", { ...webglCompatibility, exception: { ...webglCompatibility.exception, source: "worklog" }, }, ], ] as const)("runtime-closes %s", (_name, preview) => { expect( getErrors({ gpu: { preview }, passGpu: statelessFragmentGpu, technique: { exportRenderer: "none", previewRenderer: preview.backend, rendererStrategy: preview.backend, }, }).some((error) => error.includes("must contain exactly")), ).toBe(true); }); it.each([ ["mixed Three.js exception", { ...webglCompatibility, provider: "three" }], ["non-canonical native exception", { ...webglCompatibility, owner: "app" }], ] as const)("does not let a %s suppress WebGPU pressure", (_name, preview) => { const errors = getErrors({ gpu: { preview }, technique: { exportRenderer: "none", previewRenderer: "webgl", rendererStrategy: "webgl", }, }); expect(errors.some((error) => error.includes("must contain exactly"))).toBe(true); expect(errors.some((error) => error.includes("creates WebGPU pressure"))).toBe(true); }); it("does not infer unused GPU entries from a pipeline parse failure", () => { const config = { rendererPipeline: { interactionInvalidation: [], passes: "invalid" }, rendererStrategy: "dom", rendererTechnique: createTechnique( { preview: vgpu }, { exportRenderer: "none", previewRenderer: "dom", rendererStrategy: "dom", }, ), scenarios: [], usesCustomRenderer: true, workloadEnvelope: { dimensions: [] }, } as unknown as ToolcraftEnvelopePerformanceConfig; const context = createToolcraftEnvelopeValidationContext(createSchema(), config); expect(context.parsedPipeline.errors.length).toBeGreaterThan(0); expect(getToolcraftRendererGpuErrors(context)).not.toContain( "rendererTechnique.gpu.preview does not target a GPU pass or GPU final renderer.", ); const finalRendererConfig = { ...config, rendererStrategy: "webgpu", rendererTechnique: createTechnique(undefined, { exportRenderer: "none" }), } as ToolcraftEnvelopePerformanceConfig; expect( getToolcraftRendererGpuErrors( createToolcraftEnvelopeValidationContext(createSchema(), finalRendererConfig), ), ).toContain( "rendererTechnique.gpu.preview is required for the selected GPU renderer or targeted GPU pass.", ); }); });