Shaders & Textures

Texture Loading

Reactive hook for loading textures from URLs with automatic cleanup and error handling.


useTexture is an adapter runtime hook that loads textures from URLs, exposes reactive loading and both raw/normalized error state, and handles cancellation and resource cleanup automatically.

For texture declaration and configuration in materials, see Texture Definitions.

Basic usage

<script lang="ts"> import { useFrame, useTexture } from '@motion-core/motion-gpu/svelte'; const loaded = useTexture(['/assets/albedo.png']); useFrame((state) => { const tex = loaded.textures.current?.[0]; state.setTexture('uAlbedo', tex ? { source: tex.source } : null); }); </script>

useTexture starts loading immediately when called. It returns reactive stores that update as the load progresses.

URL input forms

Form Description
string[] Static list of URLs
() => string[] Lazy URL provider (evaluated on each load/reload)
// Static
const tex = useTexture(['/assets/a.png', '/assets/b.png']);

// Dynamic
const tex = useTexture(() => [
  `/assets/textures/${currentSet}/diffuse.png`,
  `/assets/textures/${currentSet}/normal.png`
]);
// Static
const tex = useTexture(['/assets/a.png', '/assets/b.png']);

// Dynamic
const tex = useTexture(() => [
  `/assets/textures/${currentSet}/diffuse.png`,
  `/assets/textures/${currentSet}/normal.png`
]);

Return value (UseTextureResult)

Field Type Description
textures CurrentReadable<LoadedTexture[] null> Loaded textures in input order, or null on failure
loading CurrentReadable<boolean> true while an active load request is running
error CurrentReadable<Error null> Last loading error
errorReport CurrentReadable<MotionGPUErrorReport null> Last loading error normalized to MotionGPU diagnostics shape
reload () => Promise<void> Triggers a fresh load with current URL input

Using .current for synchronous access

All stores expose a .current getter for zero-subscription reads — ideal for useFrame callbacks:

useFrame((state) => {
  if (loaded.loading.current) return; // Skip while loading
  if (loaded.error.current) return;   // Skip on error

  const textures = loaded.textures.current;
  if (textures) {
    state.setTexture('uDiffuse', { source: textures[0].source });
    state.setTexture('uNormal', { source: textures[1].source });
  }
});
useFrame((state) => {
  if (loaded.loading.current) return; // Skip while loading
  if (loaded.error.current) return;   // Skip on error

  const textures = loaded.textures.current;
  if (textures) {
    state.setTexture('uDiffuse', { source: textures[0].source });
    state.setTexture('uNormal', { source: textures[1].source });
  }
});

Using reactive subscriptions for UI

<script lang="ts"> const { textures, loading, error, errorReport } = useTexture(['/assets/albedo.png']); </script> {#if $loading} <p>Loading textures...</p> {:else if $error} <p>Error: {$error.message}</p> {#if $errorReport} <p>Code: {$errorReport.code}</p> <p>Hint: {$errorReport.hint}</p> {/if} {:else} <p>Loaded {$textures?.length ?? 0} textures</p> {/if}

TextureLoadOptions

Field Type Default Description
colorSpace 'srgb' | 'linear' 'srgb' Affects colorSpaceConversion during decode
requestInit RequestInit undefined Forwarded to fetch() for custom headers, credentials, etc.
decode TextureDecodeOptions See below Bitmap decode settings
signal AbortSignal undefined External cancellation signal
update TextureUpdateMode undefined Metadata attached to loaded textures
flipY boolean undefined Metadata attached to loaded textures
premultipliedAlpha boolean undefined Metadata attached to loaded textures
generateMipmaps boolean undefined Metadata attached to loaded textures

TextureDecodeOptions

Field Default Description
colorSpaceConversion 'none' (linear) / 'default' (srgb) Colour space handling during bitmap decode
premultiplyAlpha 'default' Alpha premultiplication during decode
imageOrientation 'none' Bitmap orientation ('none' or 'flipY')

Example with options

const loaded = useTexture(
  ['/assets/hdr-env.png'],
  {
    colorSpace: 'linear',
    generateMipmaps: true,
    decode: {
      premultiplyAlpha: 'none'
    }
  }
);
const loaded = useTexture(
  ['/assets/hdr-env.png'],
  {
    colorSpace: 'linear',
    generateMipmaps: true,
    decode: {
      premultiplyAlpha: 'none'
    }
  }
);

LoadedTexture shape

Each loaded texture provides:

Field Type Description
url string Source URL
source ImageBitmap Decoded bitmap — pass to state.setTexture
width number Bitmap width in pixels
height number Bitmap height in pixels
colorSpace 'srgb' | 'linear' Effective color space
update 'once' 'onInvalidate' 'perFrame' Effective update mode metadata
flipY boolean Effective flip-y metadata
premultipliedAlpha boolean Effective premultiplied alpha metadata
generateMipmaps boolean Effective mipmap metadata
dispose () => void Releases the bitmap resources (idempotent: safe to call more than once)

Abort, reload, and race safety

useTexture is designed for safe async operation in a reactive environment:

Cancellation on reload

When reload() is called, any in-flight request is aborted before the new one starts. This prevents stale results from overwriting fresh data.

Request versioning

Each load increments an internal version counter. When a response arrives, it is only accepted if its version matches the current counter. This eliminates race conditions from overlapping loads.

Dispose on replacement

When new textures are loaded successfully, the previous bitmaps are disposed. When the component is destroyed, all current bitmaps are disposed.

Abort error suppression

AbortError exceptions are treated as expected cancellation and are not surfaced in the error or errorReport stores. Only genuine failures (network errors, decode errors, etc.) appear.

Blob cache

Under the hood, texture-loader.ts maintains a reference-counted blob cache. This means:

  • If two useTexture calls request the same URL with the same options, only one network request is made.
  • The cache key is deterministic: JSON.stringify({ url, colorSpace, requestInit, decode }).
  • Entries are evicted when their reference count drops to zero (e.g., all components using that URL are destroyed).

Component lifecycle

useTexture performs adapter lifecycle cleanup automatically:

  1. On destroy/unmount, disposed flag is set.
  2. The internal requestVersion is incremented (invalidating any in-flight result).
  3. The active AbortController is aborted.
  4. All current bitmap textures are disposed.

This means you don’t need to manually clean up — the hook handles its own lifecycle.

Error handling

Failure Source errorReport.code
createImageBitmap unavailable Runtime capability check TEXTURE_DECODE_UNAVAILABLE
Non-OK HTTP response (4xx, 5xx) URL fetch TEXTURE_REQUEST_FAILED
Fetch failure (network/CORS) URL fetch TEXTURE_REQUEST_FAILED (when message matches loader pattern) or fallback
Invalid image data Bitmap decode Usually MOTIONGPU_RUNTIME_ERROR unless message matches a known classifier
Abort / cancellation AbortError Suppressed (error and errorReport stay null)