Assets and media

Assets and media

Assets and media are CPU-side host services. Shaders consume the resulting textures/resources, but they do not open files, prompt for permissions, or decode browser/native formats themselves.

Asset paths and loaders

The public loaders are:

let image = loadTexture("assets/photo.png");
let sky = loadCubemap(
  "sky/posx.png", "sky/negx.png",
  "sky/posy.png", "sky/negy.png",
  "sky/posz.png", "sky/negz.png",
);
let model = loadModel("assets/car.glb");
let text = loadText("assets/config.txt");

On native hosts, paths resolve relative to the entry .tfx or .tfxb file, not the current shell directory. PNG/JPEG textures, .glb/.gltf models, and text are loaded by the native asset resolver. External glTF resources such as .bin sidecars and texture files resolve relative to the model through the same resolver.

loadCubemap takes six equal-sized square images in the WebGPU face order +X, -X, +Y, -Y, +Z, -Z. It returns a read-only textureCube<float4> with sample(direction), sampleLevel(direction, lod), and size. Cubemaps can be captured by render or compute shaders, but cannot be constructed as writable targets, indexed, cleared, or used as a parallel for domain. Loaded cubemaps currently have one mip level, so sampleLevel clamps to level zero. This resource is supported by the native and browser WASM runners; generated TypeScript lowering is not yet available.

Browser asset resolution

Browser asset loading is supported, but an embedding must provide the bytes or text. Pass an assets map to tinyfx.run; keys use normalized program-relative paths:

await run(canvas, source, {
  assets: {
    "assets/photo.png": await fetchBytes("/assets/photo.png"),
    "assets/car.glb": await fetchBytes("/assets/car.glb"),
    "assets/config.txt": "settings text",
  },
});

No user program receives browser Blob, Response, DOM, or filesystem objects. Missing or malformed assets become ordinary TinyFX runtime errors.

Model parts

loadModel imports triangle-mode glTF primitives as model parts. A model provides CPU-side part information such as its mesh, base-color texture/factor, and name. The embedded model::parts helper converts it into convenient ModelPart records:

let parts = model::parts(loadModel("assets/car.glb"));
for (let i = 0; i < parts.len(); i++) {
  let texture = parts[i].color;
  let tint = parts[i].colorFactor;
  draw(parts[i].mesh, shader {
    frag { out.color = texture.sample(in.uv) * tint; }
  });
}

Hoist a resource such as texture to a local before a shader captures it. Models and their inspection methods are CPU-only; the mesh/texture values they return can participate in draws and shader captures.

Media acquisition

Webcam, video, and microphone resources are opaque CPU-side handles. They are asynchronous host operations: a request can suspend CPU continuation and later complete, fail, or be cancelled at a frame boundary. The public-facing frame surface is a stable texture2d<float4> so a program never manually uploads a new frame.

let camera: Webcam;
let clip: Video;
 
fn setup() {
  clip = loadVideo("assets/clip.mp4");
  clip.setLoop(true);
  clip.start();
}
 
fn loop() {
  if (ui::button("enable camera")) {
    camera = webcam::acquire();
    camera.start();
  }
}

Webcam and video expose a stable texture plus start/pause/stop controls. Video also exposes looping/readiness/timing controls. New frames are coalesced and published before a later sampling frame; media does not force the renderer to tick faster. The exact method signatures and target availability live in the generated operation catalog.

Platform behavior and limits

Browser media uses the shared browser platform to request permissions, own streams/decoders, and deliver normalized frame data to either the WASM or generated TypeScript core. Session disposal, reload, cancellation, and stale callbacks are handled by the platform boundary so an old session cannot resume a replacement program.

Native webcam acquisition is implemented through the asynchronous nokhwa backend and publishes decoded RGBA frames into the same stable texture model. When a device cannot be opened, the host follows its recoverable/fallback path instead of exposing a platform camera object to TinyFX. Native video decoding currently has a static fallback, and native microphone capture is not currently available; treat those as capability-dependent rather than assuming browser parity.

Permission denial, unavailable devices, bad media, and cancellation are normal host failures. They are intentionally distinct from a compiler error. Do not assume an acquisition stays alive across stop(), reload, or program disposal.

Paint

The paint:: API is another CPU-to-platform rendering service. It represents typed Canvas2D-like operations, then native and browser platforms replay them at the correct point in a frame. Use it for 2D overlay/drawing work, not as a shader substitute. The browser platform owns DOM/Canvas integration while each execution core owns TinyFX ordering and compositing decisions.