import { GpuFragmentParams, KitTexture } from '../../contract'; export interface SwappableMediaTextureOptions { /** Texture label (shows up in WebGPU error messages / the texture manager's accounting). */ label: string; /** * Size of the initial placeholder. Default 1×1 — a transparent texel, so the pass always has * something valid to bind and the layer renders transparent until content lands. Text and * HTMLInCanvas start larger only because their first write is imminent. */ initial?: { width: number; height: number; }; /** Base format, forwarded to `createMediaTexture`. Default `rgba8unorm`. */ format?: GPUTextureFormat; /** * Allocate a full mip chain and regenerate it on every `write()` — default `true`. Set `false` * for a texture whose upload path never goes through `write()` (HTMLInCanvas uses `unwrap()` + * `copyElementImageToTexture` directly): a mip chain with only level 0 ever populated would * sample garbage/zero data from the unpopulated upper mips on minification. */ mipmaps?: boolean; } export interface SwappableMediaTexture { /** The registered `KitTexture` for the fragment builder. Follows swaps — register it ONCE. */ readonly kit: KitTexture; /** * Ensure the backing texture is exactly `width`×`height` (each floored at 1), allocating and * swapping when it is not. Destroying the old texture is safe: WebGPU keeps it alive for frames * already submitted. No-op after cleanup. */ ensureSize(width: number, height: number): void; /** Upload pixel content (ImageBitmap / HTMLCanvasElement / ImageData / TypedArray). */ write(source: unknown): void; /** The raw `GPUTexture`, for `queue.copyElementImageToTexture` (HTMLInCanvas). */ unwrap(): GPUTexture; readonly width: number; readonly height: number; /** True once the shader has been cleaned up — check it after an await before writing. */ readonly disposed: boolean; } /** * A media texture that can change size: allocate → swap the getter → destroy the previous one, with * the placeholder and the cleanup wired up. * * Media textures are STATIC-sized, so a new image at its native resolution, a re-rastered text run * or a resized DOM capture means a NEW texture. The pass manager notices the backing texture's * identity changed and rebuilds only the sampling pass's bind group — no recompose, no pipeline * rebuild. Shaders whose texture never changes size (Ascii's and ObjectTracker's glyph atlases) use * this too and simply never call `ensureSize`; they get the dispose-guarded `write` for free. */ export declare function createSwappableMediaTexture(params: GpuFragmentParams, opts: SwappableMediaTextureOptions): SwappableMediaTexture; /** What a `load` callback is handed alongside the URL. */ export interface UrlLoadContext { /** * True once the shader has been cleaned up. Check it after EVERY await and bail — the textures * the load would write into are gone. */ isDisposed(): boolean; /** * Mark this URL as loaded, which stops the per-frame watch from re-requesting it. Call it after * the last await that could fail, so a failed load stays retryable (see the module note). */ commit(): void; } export interface UrlSourceLoaderOptions { /** Name of the URL prop, read live via `getCpuValue`. */ prop: string; /** Do the load. Owns its own decode/apply; the loader owns only the state machine. */ load(url: string, ctx: UrlLoadContext): Promise; /** Reported for anything `load` throws. Error TAXONOMY stays per-shader by design. */ onError?(error: unknown, url: string): void; } export interface UrlSourceLoader { /** The URL currently loaded (empty until the first `commit`). */ currentUrl(): string; /** A load is in flight — the per-frame watch stands down while it is. */ isLoading(): boolean; /** Force a load of `url` now, bypassing the per-frame watch. Used by the kickoff and by tests. */ request(url: string): void; } /** * The URL-prop state machine: kick off the initial load, watch the prop for changes each frame, and * keep exactly one load in flight. * * NOTE the `setTimeout(…, 0)` kickoff: the load must not start until the node's uniforms are * populated, because `getCpuValue` reads the live handle. Deferring by a macrotask is the workaround, * not the fix — the fix is an `onUniformsReady` hook on `GpuFragmentParams` (PRIMITIVES_PLAN.md * Phase 12 item 3), at which point this function is the single site that changes. */ export declare function createUrlSourceLoader(params: GpuFragmentParams, opts: UrlSourceLoaderOptions): UrlSourceLoader; /** * A decoded image ready to upload. The two size pairs are NOT interchangeable: * * - `width`/`height` are the RASTER size — what the GPU texture must be. * - `naturalWidth`/`naturalHeight` are the INTRINSIC size — the layout-facing "how big is this * element" answer that goes to the natural-size registry. For a bitmap they are the same; for an * SVG they differ, and conflating them makes a 32px logo slot at 2048px in a layout column. */ export interface DecodedImageSource { source: ImageBitmap | HTMLCanvasElement; width: number; height: number; naturalWidth: number; naturalHeight: number; /** Release transient decode resources (closes an `ImageBitmap`). Safe to call twice. */ close(): void; } /** * Fetch + decode an image URL to something uploadable. SVGs go through a canvas rasterization (with * the intrinsic/raster split above); everything else is a CORS fetch → blob → `createImageBitmap`. */ export declare function decodeImageSource(url: string): Promise; /** Where the video frames come from. */ export type VideoSourceSpec = { kind: 'url'; prop: string; } | { kind: 'webcam'; constraints?: MediaStreamConstraints; }; /** * Which step failed, so a shader's `onError` can keep its own message taxonomy. `'acquire'` covers * everything up to and including metadata (a bad URL, a load timeout, a denied camera) and is * reported ONCE per attempt; `'autoplay'` is the browser refusing to start playback, which is not * necessarily fatal — see `autoplayRequired`. */ export type VideoLoadStage = 'acquire' | 'autoplay'; export interface VideoElementSourceOptions { source: VideoSourceSpec; /** * Keep `video.loop` synced to a boolean prop each frame (URL sources only). The prop packs to an * f32 field (1 = true), and an UNSET value reads as true — both current consumers default their * loop prop to true. */ loop?: { prop: string; }; /** * Key the source's pixel dimensions are published under in the natural-size registry. Defaults * to the URL for a URL source; a webcam has no URL and must pass its own fixed key. */ naturalSizeKey?: string; /** * Milliseconds to wait for `loadedmetadata` before giving up. Default 0 = wait indefinitely * (the webcam's behaviour — a permission prompt can legitimately take a while). */ metadataTimeoutMs?: number; /** * Whether a rejected `play()` aborts the load. False (VideoTexture) reports it and keeps the * element, since a browser autoplay block still leaves a decodable first frame; true * (WebcamTexture) discards it. The two consumers genuinely differ here, so there is no default * winner beyond the more forgiving one. */ autoplayRequired?: boolean; onError?(error: unknown, info: { url: string | null; stage: VideoLoadStage; }): void; } export interface VideoElementSource { /** * The readiness-gated getter to hand `registerExternalTexture`. Returns the element only when it * has a decodable frame; the pass manager skips the pass for a frame where it is null. */ getSource(): HTMLVideoElement | null; /** The live element, or null before the first successful load. */ element(): HTMLVideoElement | null; } /** * The video-element half of VideoTexture and WebcamTexture: acquire a source (a URL or * `getUserMedia`), wait for metadata, start playback, publish the frame size for layout, and gate * the element behind a readiness check so the renderer never imports an undecodable frame. * * The GPU side is identical for both consumers — a per-frame zero-copy `importExternalTexture` — so * only the acquisition differs, and both paths live here rather than in the two shaders. */ export declare function createVideoElementSource(params: GpuFragmentParams, opts: VideoElementSourceOptions): VideoElementSource; //# sourceMappingURL=mediaLifecycle.d.ts.map