UNPKG

@motion-core/motion-gpu

Version:

Framework-agnostic WebGPU runtime for fullscreen WGSL shaders with explicit Svelte, React, and Vue adapter entrypoints.

142 lines (141 loc) 5.4 kB
import { assertUniformName } from "./uniforms.js"; //#region src/lib/core/material-preprocess.ts var INCLUDE_DIRECTIVE_PATTERN = /^\s*#include\s+<([A-Za-z_][A-Za-z0-9_]*)>\s*$/; function normalizeTypedDefine(name, define) { const value = define.value; if (define.type === "bool") { if (typeof value !== "boolean") throw new Error(`Invalid define value for "${name}". bool define requires boolean value.`); return { type: "bool", value }; } if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`Invalid define value for "${name}". Numeric define must be finite.`); if ((define.type === "i32" || define.type === "u32") && !Number.isInteger(value)) throw new Error(`Invalid define value for "${name}". ${define.type} define requires integer.`); if (define.type === "u32" && value < 0) throw new Error(`Invalid define value for "${name}". u32 define must be >= 0.`); return { type: define.type, value }; } /** * Validates and normalizes define entries. */ function normalizeDefines(defines) { const resolved = {}; for (const [name, value] of Object.entries(defines ?? {})) { assertUniformName(name); if (typeof value === "boolean") { resolved[name] = value; continue; } if (typeof value === "number") { if (!Number.isFinite(value)) throw new Error(`Invalid define value for "${name}". Define numbers must be finite.`); resolved[name] = value; continue; } const normalized = normalizeTypedDefine(name, value); resolved[name] = Object.freeze(normalized); } return resolved; } /** * Validates include map identifiers and source chunks. */ function normalizeIncludes(includes) { const resolved = {}; for (const [name, source] of Object.entries(includes ?? {})) { assertUniformName(name); if (typeof source !== "string" || source.trim().length === 0) throw new Error(`Invalid include "${name}". Include source must be a non-empty WGSL string.`); resolved[name] = source; } return resolved; } /** * Converts one define declaration to WGSL `const`. */ function toDefineLine(key, value) { if (typeof value === "boolean") return `const ${key}: bool = ${value ? "true" : "false"};`; if (typeof value === "number") return `const ${key}: f32 = ${Number.isInteger(value) ? `${value}.0` : `${value}`};`; if (value.type === "bool") return `const ${key}: bool = ${value.value ? "true" : "false"};`; if (value.type === "f32") { const numberValue = value.value; return `const ${key}: f32 = ${Number.isInteger(numberValue) ? `${numberValue}.0` : `${numberValue}`};`; } if (value.type === "i32") return `const ${key}: i32 = ${value.value};`; return `const ${key}: u32 = ${value.value}u;`; } function expandChunk(source, kind, includeName, includes, stack, expandedIncludes) { const sourceLines = source.split("\n"); const lines = []; const mapEntries = []; for (let index = 0; index < sourceLines.length; index += 1) { const sourceLine = sourceLines[index]; if (sourceLine === void 0) continue; const includeMatch = sourceLine.match(INCLUDE_DIRECTIVE_PATTERN); if (!includeMatch) { lines.push(sourceLine); mapEntries.push({ kind, line: index + 1, ...kind === "include" && includeName ? { include: includeName } : {} }); continue; } const includeKey = includeMatch[1]; if (!includeKey) throw new Error("Invalid include directive in fragment shader."); assertUniformName(includeKey); const includeSource = includes[includeKey]; if (!includeSource) throw new Error(`Unknown include "${includeKey}" referenced in fragment shader.`); if (stack.includes(includeKey)) throw new Error(`Circular include detected for "${includeKey}". Include stack: ${[...stack, includeKey].join(" -> ")}.`); if (expandedIncludes.has(includeKey)) continue; expandedIncludes.add(includeKey); const nested = expandChunk(includeSource, "include", includeKey, includes, [...stack, includeKey], expandedIncludes); lines.push(...nested.lines); mapEntries.push(...nested.mapEntries); } return { lines, mapEntries }; } /** * Preprocesses material fragment with deterministic define/include expansion and line mapping. */ function preprocessMaterialFragment(input) { const normalizedDefines = normalizeDefines(input.defines); const normalizedIncludes = normalizeIncludes(input.includes); const fragmentExpanded = expandChunk(input.fragment, "fragment", void 0, normalizedIncludes, [], /* @__PURE__ */ new Set()); const defineEntries = Object.entries(normalizedDefines).sort(([a], [b]) => a.localeCompare(b)); const lines = []; const defineLines = []; const mapEntries = []; for (let index = 0; index < defineEntries.length; index += 1) { const entry = defineEntries[index]; if (!entry) continue; const [name, value] = entry; const defineLine = toDefineLine(name, value); lines.push(defineLine); defineLines.push(defineLine); mapEntries.push({ kind: "define", line: index + 1, define: name }); } if (defineEntries.length > 0) { lines.push(""); mapEntries.push(null); } lines.push(...fragmentExpanded.lines); mapEntries.push(...fragmentExpanded.mapEntries); const lineMap = [null, ...mapEntries]; return { fragment: lines.join("\n"), lineMap, defineBlockSource: defineLines.join("\n") }; } //#endregion export { normalizeDefines, normalizeIncludes, preprocessMaterialFragment, toDefineLine }; //# sourceMappingURL=material-preprocess.js.map