@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
JavaScript
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