UNPKG

@motion-core/motion-gpu

Version:

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

138 lines (137 loc) 5.32 kB
import { storageTextureSampleScalarType } from "./compute-shader.js"; import { assertUniformName } from "./uniforms.js"; //#region src/lib/core/textures.ts /** * Default sampling filter for textures when no explicit value is provided. */ var DEFAULT_TEXTURE_FILTER = "linear"; /** * Default addressing mode for textures when no explicit value is provided. */ var DEFAULT_TEXTURE_ADDRESS_MODE = "clamp-to-edge"; /** * Validates and returns sorted texture keys. * * @param textures - Texture definition map. * @returns Lexicographically sorted texture keys. */ function resolveTextureKeys(textures) { const keys = Object.keys(textures).sort(); for (const key of keys) assertUniformName(key); return keys; } /** * Applies defaults and clamps to a single texture definition. * * @param definition - Optional texture definition. * @returns Normalized definition with deterministic defaults. */ function normalizeTextureDefinition(definition) { const isStorage = definition?.storage === true; const defaultFormat = definition?.colorSpace === "linear" ? "rgba8unorm" : "rgba8unorm-srgb"; const format = definition?.format ?? defaultFormat; const sampleScalar = isStorage ? storageTextureSampleScalarType(format) : "f32"; const explicitFragmentVisible = definition?.fragmentVisible; if (explicitFragmentVisible === true && sampleScalar !== "f32") throw new Error(`Texture with storage format "${format}" cannot be fragmentVisible: fragment shader uses texture_2d<f32>, which is incompatible with ${sampleScalar} sample type. Set fragmentVisible: false or use a float-sampled storage format.`); const fragmentVisible = explicitFragmentVisible ?? sampleScalar === "f32"; const normalized = { source: definition?.source ?? null, colorSpace: definition?.colorSpace ?? "srgb", format, flipY: definition?.flipY ?? true, generateMipmaps: definition?.generateMipmaps ?? false, premultipliedAlpha: definition?.premultipliedAlpha ?? false, anisotropy: Math.max(1, Math.min(16, Math.floor(definition?.anisotropy ?? 1))), filter: definition?.filter ?? DEFAULT_TEXTURE_FILTER, addressModeU: definition?.addressModeU ?? DEFAULT_TEXTURE_ADDRESS_MODE, addressModeV: definition?.addressModeV ?? DEFAULT_TEXTURE_ADDRESS_MODE, storage: isStorage, fragmentVisible }; if (definition?.width !== void 0) normalized.width = definition.width; if (definition?.height !== void 0) normalized.height = definition.height; if (definition?.update !== void 0) normalized.update = definition.update; return normalized; } /** * Normalizes all texture definitions for already-resolved texture keys. * * @param textures - Source texture definitions. * @param textureKeys - Texture keys to normalize. * @returns Normalized map keyed by `textureKeys`. */ function normalizeTextureDefinitions(textures, textureKeys) { const out = {}; for (const key of textureKeys) out[key] = normalizeTextureDefinition(textures[key]); return out; } /** * Checks whether a texture value is a structured `{ source, width?, height? }` object. */ function isTextureData(value) { return typeof value === "object" && value !== null && "source" in value; } /** * Converts supported texture input variants to normalized `TextureData`. * * @param value - Texture value input. * @returns Structured texture data or `null`. */ function toTextureData(value) { if (value === null) return null; if (isTextureData(value)) return value; return { source: value }; } /** * Resolves effective runtime texture update strategy. */ function resolveTextureUpdateMode(input) { if (input.override) return input.override; if (input.defaultMode) return input.defaultMode; if (isVideoTextureSource(input.source)) return "perFrame"; return "once"; } /** * Resolves texture dimensions from explicit values or source metadata. * * @param data - Texture payload. * @returns Positive integer width/height. * @throws {Error} When dimensions cannot be resolved to positive values. */ function resolveTextureSize(data) { const source = data.source; const width = data.width ?? source.naturalWidth ?? source.videoWidth ?? source.width ?? 0; const height = data.height ?? source.naturalHeight ?? source.videoHeight ?? source.height ?? 0; if (width <= 0 || height <= 0) throw new Error("Texture source must have positive width and height"); return { width, height }; } /** * Computes the number of mipmap levels for a base texture size. * * @param width - Base width. * @param height - Base height. * @returns Total mip level count (minimum `1`). */ function getTextureMipLevelCount(width, height) { let levels = 1; let currentWidth = Math.max(1, width); let currentHeight = Math.max(1, height); while (currentWidth > 1 || currentHeight > 1) { currentWidth = Math.max(1, Math.floor(currentWidth / 2)); currentHeight = Math.max(1, Math.floor(currentHeight / 2)); levels += 1; } return levels; } /** * Checks whether the source is an `HTMLVideoElement`. */ function isVideoTextureSource(source) { return typeof HTMLVideoElement !== "undefined" && source instanceof HTMLVideoElement; } //#endregion export { getTextureMipLevelCount, isTextureData, isVideoTextureSource, normalizeTextureDefinition, normalizeTextureDefinitions, resolveTextureKeys, resolveTextureSize, resolveTextureUpdateMode, toTextureData }; //# sourceMappingURL=textures.js.map