66 lines
3.1 KiB
TypeScript
66 lines
3.1 KiB
TypeScript
import { blockedGate, capabilityIssue, readyGate, type CapabilityGateResult } from "./capability-gates";
|
|
import type { ShaderNodeType } from "./shader-graph";
|
|
|
|
export type RenderBackend = "WEBGL2" | "WEBGPU";
|
|
export type PostProcessPass = "FXAA" | "BLOOM" | "SSAO" | "SSR" | "TAA" | "DOF" | "MOTION_BLUR";
|
|
|
|
export type RenderCapabilityRequest =
|
|
| { kind: "ARBITRARY_SHADER"; nodeTypes: Array<ShaderNodeType | "UNSUPPORTED"> }
|
|
| { kind: "VOLUME" }
|
|
| { kind: "SUBSURFACE" }
|
|
| { kind: "WEBGPU_BACKEND" }
|
|
| { kind: "POSTPROCESS"; passes: PostProcessPass[] };
|
|
|
|
export interface RenderCapabilityContext {
|
|
webgpuAvailable?: boolean;
|
|
webgpuRendererBundled?: boolean;
|
|
supportedShaderNodes?: ReadonlySet<ShaderNodeType>;
|
|
supportedPostProcessPasses?: ReadonlySet<PostProcessPass>;
|
|
volumeRendererAvailable?: boolean;
|
|
subsurfaceRendererAvailable?: boolean;
|
|
}
|
|
|
|
const boundedShaderNodes = new Set<ShaderNodeType>([
|
|
"RGB",
|
|
"VALUE",
|
|
"MATH",
|
|
"IMAGE_TEXTURE",
|
|
"NORMAL_MAP",
|
|
"PRINCIPLED",
|
|
"MATERIAL_OUTPUT",
|
|
]);
|
|
|
|
export function gateRenderCapability(
|
|
request: RenderCapabilityRequest,
|
|
context: RenderCapabilityContext = {},
|
|
): CapabilityGateResult {
|
|
if (request.kind === "ARBITRARY_SHADER") {
|
|
const supported = context.supportedShaderNodes ?? boundedShaderNodes;
|
|
const unsupported = [...new Set(request.nodeTypes.filter((type) => type === "UNSUPPORTED" || !supported.has(type as ShaderNodeType)))];
|
|
if (unsupported.length === 0) return readyGate("PBR-012", "BOUNDED_SHADER_GRAPH");
|
|
return blockedGate("PBR-012", "ARBITRARY_SHADER", [
|
|
capabilityIssue("SHADER_NODE_UNSUPPORTED", `Web shader compiler does not support: ${unsupported.join(", ")}`, "nodeTypes"),
|
|
]);
|
|
}
|
|
if (request.kind === "VOLUME") {
|
|
return context.volumeRendererAvailable
|
|
? readyGate("PBR-012", "VOLUME")
|
|
: blockedGate("PBR-012", "VOLUME", [capabilityIssue("VOLUME_SHADER_UNAVAILABLE", "Volume transport is not available in the Web renderer")]);
|
|
}
|
|
if (request.kind === "SUBSURFACE") {
|
|
return context.subsurfaceRendererAvailable
|
|
? readyGate("PBR-012", "SUBSURFACE")
|
|
: blockedGate("PBR-012", "SUBSURFACE", [capabilityIssue("SUBSURFACE_SHADER_UNAVAILABLE", "Blender-compatible subsurface scattering is not available in the Web renderer")]);
|
|
}
|
|
if (request.kind === "WEBGPU_BACKEND") {
|
|
if (context.webgpuAvailable && context.webgpuRendererBundled) return readyGate("PBR-012", "WEBGPU_BACKEND");
|
|
const reason = context.webgpuAvailable ? "The WebGPU renderer bundle is not installed" : "WebGPU is unavailable in this browser or device";
|
|
return blockedGate("PBR-012", "WEBGPU_BACKEND", [capabilityIssue("WEBGPU_RENDERER_UNAVAILABLE", reason)]);
|
|
}
|
|
const supported = context.supportedPostProcessPasses ?? new Set<PostProcessPass>();
|
|
const unsupported = [...new Set(request.passes.filter((pass) => !supported.has(pass)))];
|
|
return unsupported.length === 0
|
|
? readyGate("PBR-012", "POSTPROCESS")
|
|
: blockedGate("PBR-012", "POSTPROCESS", [capabilityIssue("POSTPROCESS_PASS_UNAVAILABLE", `Unsupported post-process passes: ${unsupported.join(", ")}`, "passes")]);
|
|
}
|