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 RenderPassName = "COMBINED" | "Z" | "MIST" | "NORMAL" | "VECTOR" | "UV" | "OBJECT_INDEX" | "MATERIAL_INDEX" | "SHADOW" | "AO" | "EMISSION" | "ENVIRONMENT" | "DIFFUSE_DIRECT" | "DIFFUSE_INDIRECT" | "GLOSSY_DIRECT" | "GLOSSY_INDIRECT" | "TRANSMISSION_DIRECT" | "TRANSMISSION_INDIRECT" | "VOLUME_DIRECT" | "VOLUME_INDIRECT" | "CRYPTOMATTE_OBJECT" | "CRYPTOMATTE_MATERIAL" | "CRYPTOMATTE_ASSET"; export type RenderCapabilityRequest = | { kind: "ARBITRARY_SHADER"; nodeTypes: string[] } | { kind: "VOLUME" } | { kind: "SUBSURFACE" } | { kind: "WEBGPU_BACKEND" } | { kind: "POSTPROCESS"; passes: PostProcessPass[] } | { kind: "RENDER_PASSES"; passes: RenderPassName[] }; export interface RenderCapabilityContext { webgpuAvailable?: boolean; webgpuRendererBundled?: boolean; supportedShaderNodes?: ReadonlySet; supportedPostProcessPasses?: ReadonlySet; supportedRenderPasses?: ReadonlySet; volumeRendererAvailable?: boolean; subsurfaceRendererAvailable?: boolean; } const boundedShaderNodes = new Set([ "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)))].sort(); 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)]); } if (request.kind === "RENDER_PASSES") { const supported = context.supportedRenderPasses ?? new Set(["COMBINED", "Z", "MIST", "NORMAL", "VECTOR", "UV", "OBJECT_INDEX", "MATERIAL_INDEX", "SHADOW", "AO", "EMISSION", "ENVIRONMENT", "DIFFUSE_DIRECT", "DIFFUSE_INDIRECT", "GLOSSY_DIRECT", "GLOSSY_INDIRECT", "TRANSMISSION_DIRECT", "TRANSMISSION_INDIRECT", "VOLUME_DIRECT", "VOLUME_INDIRECT", "CRYPTOMATTE_OBJECT", "CRYPTOMATTE_MATERIAL", "CRYPTOMATTE_ASSET"]); const unsupported = [...new Set(request.passes.filter((pass) => !supported.has(pass)))]; return unsupported.length === 0 ? readyGate("PBR-012", "RENDER_PASSES") : blockedGate("PBR-012", "RENDER_PASSES", [capabilityIssue("RENDER_PASS_UNAVAILABLE", `Unsupported render passes: ${unsupported.join(", ")}`, "passes")]); } const supported = context.supportedPostProcessPasses ?? new Set(); 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")]); }