import type { RawPixels, SizedTexture, TextureSource } from "./types.js"; export interface TextureCacheOptions { /** * GL_TEXTURE_MIN_FILTER. Defaults to `LINEAR_MIPMAP_LINEAR` when * `generateMipmaps` is true, else `LINEAR`. Effects libraries that * sample neighbours (not LOD) typically want `LINEAR` without mipmaps. */ minFilter?: GLenum; /** GL_TEXTURE_MAG_FILTER. Defaults to `LINEAR`. */ magFilter?: GLenum; /** GL_TEXTURE_WRAP_S. Defaults to `CLAMP_TO_EDGE`. */ wrapS?: GLenum; /** GL_TEXTURE_WRAP_T. Defaults to `CLAMP_TO_EDGE`. */ wrapT?: GLenum; /** * Whether to call `gl.generateMipmap` after each upload. Required when * `minFilter` is a mipmapped enum. Defaults to true. */ generateMipmaps?: boolean; /** UNPACK_FLIP_Y_WEBGL. Defaults to true. */ flipY?: boolean; /** UNPACK_PREMULTIPLY_ALPHA_WEBGL. Defaults to false. */ premultiplyAlpha?: boolean; /** * Maximum number of URL-keyed entries (from `resolveAsync(url)`) to * retain. When exceeded, least-recently-used entries are evicted: the * GL texture is deleted and the cache entry dropped. Defaults to * `Infinity` (no eviction — preserves prior behavior). * * Only applies to URL inputs. DOM-source entries * (`HTMLImageElement`, `HTMLCanvasElement`, etc.) are not subject to * LRU eviction — they're cleaned up naturally when the source object * is garbage collected (entries are stored in a WeakMap). * * Typical use: lazy-loading slideshows / flipbooks that page through * many images and only need a small window resident on the GPU. */ maxUrlEntries?: number; } interface CachedTexture { texture: WebGLTexture; owned: boolean; /** True once the source's pixels have been uploaded at least once. */ uploaded: boolean; } interface UrlCacheEntry { /** * Resolves to the GL texture once the URL has been fetched + decoded * + uploaded. Concurrent callers for the same URL await the same * promise (request deduplication). */ promise: Promise; /** * Set once the promise fulfills. Used by `release()` and eviction to * free the GL-side resource without having to await the promise. */ texture: WebGLTexture | null; } function isWebGLTexture(source: TextureSource): source is WebGLTexture { return ( typeof WebGLTexture !== "undefined" && source instanceof WebGLTexture ); } /** * Structural detection for the `SizedTexture` variant — a wrapper that * pairs an existing `WebGLTexture` with its dimensions. We dispatch on * the `texture` field being a `WebGLTexture` instance to avoid mistaking * other plain objects (e.g. `RawPixels`, which has `pixels` instead). */ function isSizedTexture(source: TextureSource): source is SizedTexture { if (typeof source !== "object" || source === null) return false; if (typeof WebGLTexture !== "undefined" && source instanceof WebGLTexture) { return false; } const candidate = source as Partial; return ( "texture" in candidate && typeof WebGLTexture !== "undefined" && candidate.texture instanceof WebGLTexture && typeof candidate.width === "number" && typeof candidate.height === "number" ); } /** * Structural detection for the `RawPixels` variant of TextureSource. * A plain object with `pixels` (a TypedArray), `width` and `height` — * none of the other source variants have a `pixels` field, so this key * is unambiguous. We check `ArrayBuffer.isView(pixels)` defensively so * that a stray `{ pixels: "abc", width: 1, height: 1 }` doesn't slip * past structural narrowing at runtime. */ function isRawPixels(source: TextureSource): source is RawPixels { if (typeof source !== "object" || source === null) return false; if (typeof WebGLTexture !== "undefined" && source instanceof WebGLTexture) { return false; } const candidate = source as Partial; return ( "pixels" in candidate && typeof candidate.width === "number" && typeof candidate.height === "number" && ArrayBuffer.isView(candidate.pixels as ArrayBufferView) ); } /** * Returns true if the source's pixels can never change after construction — * so we can upload to the GPU once and skip re-uploads on subsequent * `resolve()` calls. This is a major win for static-image transitions, * where re-uploading a 1920×1080 RGBA texture (~8 MB) plus regenerating its * mipmap chain every frame causes visible GPU stalls. * * Conservatively immutable: HTMLImageElement (.src can be reassigned but * that's rare and the caller can drop their reference and create a new * Image) and ImageBitmap (no public mutation API). Everything else — *