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