Log inGet started

texture

An image asset — the raw bytes of a png / jpg / jpeg / hdr / ktx2 / webp / bmp / tga file. Loose image files under a textures/ directory classify as texture.

New to how a texture exists as an asset, as CPU-side pixels, and as a live GPU texture (and how renderer.texture.* and render targets move between them)? Read core/resource-model first.

texture is a first-class assetType (not a bare plural-dir category) so that:

  • a texture has a stable asset identity and an explicit asset_type link, and
  • a material that references a texture records a real .refs edge to it — which is what lets the CPU-residency system cascade from a material to the textures it actually uses.

CPU residency vs GPU residency

A texture can be resident two fundamentally different ways, and they must not be conflated:

  • CPU residency (this assetType): the raw image bytes in memory — readable, hashable, decodable. You can operate on the bytes.
  • GPU residency: those bytes uploaded to the device as a wgpu::Texture the renderer binds/samples. Byte-level reads are not possible on the GPU variant — that is simply how the device works. GPU residency is managed separately (TextureRefCountsdrop_texture) and is not part of this assetType.

Same texture identity, two separate residency states, two separate operation sets, two separate reference counts. See docs/specs/runtime-asset-copying.md.

Internal layout

A .texture/ is permissive (allow_unlisted: true) — the conversion step decides its shape. Two shapes are produced today:

Plain image container

<name>.texture/
  <name>.png          the primary image (or .jpg / .webp / .hdr / …)

Produced by asset.create("texture", name, { bytes, ext }) and the svg→texture importer. The renderer decodes the image and generates mips on upload.

Managed (compressed) container

Produced by the texture importer when a loose raster image lands in /source/:

<name>.texture/
  source.<ext>   the original raster, MOVED in (never lost)
  meta.yaml      compression settings — see below
  data.tex       canonical pre-encoded + pre-mipped payload (ZTEX magic)
  README.md

data.tex is the preferred primary: when present the renderer resolves and uploads it verbatim — no decode, no re-mip. When absent the primary falls through to the raster image, so plain containers keep working.

meta.yaml settings

Editing any of these re-runs compression against the sibling source.<ext> and rewrites data.tex:

keyvaluesmeaning
formatsrgb (default) / rgba8 / bc7 / bc7_srgboutput format. srgb for albedo/emissive/base-color; rgba8 (linear) for normal/RM/AO/height. bc7* reserved (not yet produced by the CPU encoder).
generateMipmapstrue (default) / falsebake the full mip chain into data.tex at import time.
srgbtrue / falseoptional override applied when format is omitted or non-srgb.
maxDimensioninteger, 0 = no capdownsample so neither axis exceeds this.

The data.tex payload format lives in crates/zero_texture/src/blob.rs (ZTEX magic + 32-byte header + mip levels); the CPU encoder is exposed to Luau as __texture.* and consumed by the texture importer and this assetType.

  • asset-type
  • reference