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)? Readcore/resource-modelfirst.
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_typelink, and - a material that references a texture records a real
.refsedge 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::Texturethe 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 (TextureRefCounts→drop_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:
| key | values | meaning |
|---|---|---|
format | srgb (default) / rgba8 / bc7 / bc7_srgb | output format. srgb for albedo/emissive/base-color; rgba8 (linear) for normal/RM/AO/height. bc7* reserved (not yet produced by the CPU encoder). |
generateMipmaps | true (default) / false | bake the full mip chain into data.tex at import time. |
srgb | true / false | optional override applied when format is omitted or non-srgb. |
maxDimension | integer, 0 = no cap | downsample 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.