# Compressed Textures (BC / ASTC / ETC2) SimVX loads GPU block-compressed textures from `.ktx2` (KTX 2.0) and `.dds` containers, transcoding the universal UASTC interchange format to whatever block family the current GPU prefers. Block-compressed textures use a fraction of the VRAM of RGBA8 and stay compressed on the GPU, so they are the right choice for shipped game assets. A runnable example is at `examples/features/3d/compressed_texture.py` (`uv run python examples/features/3d/compressed_texture.py`). ## Authoring Encode offline once, ship the compressed file: - **UASTC `.ktx2` (recommended interchange).** Transcodes to any GPU family at load time: ```sh toktx --uastc --genmipmap --t2 out.ktx2 in.png ``` `--uastc` selects UASTC LDR 4x4, `--genmipmap` builds the mip chain, `--t2` writes a KTX2 container. - **Explicit-BC `.dds`** (for BC-only desktop targets) via `basisu`, DirectX `texconv`, or AMD Compressonator. The VkFormat is carried in the file; no transcode happens. Prefer UASTC `.ktx2`: one asset transcodes to BC7 on desktop, ASTC-4x4 on mobile/tablet, and ETC2 on older mobile, with no per-platform re-encode. ## Loading There is no special API. `.ktx2` / `.dds` are accepted transparently anywhere a texture source is accepted; the texture manager dispatches on the file suffix (or the magic bytes for in-memory sources): ```python mat = Material(albedo_map="hero.ktx2") # UASTC or explicit-BC KTX2 spr = Sprite2D(texture="tiles.dds") # explicit-BC DDS ``` ## Fallback chain Loading never crashes; it degrades step by step: 1. **Native transcode.** A UASTC `.ktx2` is transcoded to the device's chosen block target via the native `basis_universal` transcoder. The target is picked by a device probe in this order, each gated by BOTH the coarse compression-family feature AND a per-format SAMPLED check: **BC7 -> ASTC-4x4 -> ETC2**. Explicit-BC files skip the probe (they carry their own VkFormat). 2. **CPU decode.** If the GPU exposes no usable block family (or the native transcoder is not built), mip 0 is decoded to RGBA8 on the CPU via the optional `texture2ddecoder` package and uploaded as `R8G8B8A8_UNORM`. 3. **Skip.** If even the CPU decoder/dependency is absent, a one-time WARNING is logged and the texture resolves to the `-1` "couldn't resolve" sentinel. ## Mip sampling When a `.ktx2` / `.dds` carries a mip chain, the whole chain is uploaded and the bound sampler's `maxLod` is set to `mip_count - 1`, so minified surfaces sample the smaller levels (no aliasing). Single-mip textures keep `maxLod = 0` unchanged. Generate the chain at authoring time with `--genmipmap`. ## Optional dependency + build The native transcoder is an opt-in C++ extension built once after install: ```sh simvx build-textures # requires a C++ compiler (g++ / clang / MSVC) ``` `texture2ddecoder` is the pip-installable CPU-decode fallback (`uv pip install texture2ddecoder`). Both are optional: with neither present, compressed textures degrade gracefully (see the fallback chain). See {doc}`../install` for the dependency matrix. Re-enabling the ASTC and ETC2 transcode targets grows the built `.so` and adds the ASTC table-generation cost at compile time (acceptable for the full platform matrix). **Changing the transcoder's define-set requires a forced rebuild**: delete the cached `_simvx_basis_transcoder*.so` (or re-run `simvx build-textures`), because `uv` reuses the cached/installed binary otherwise. ## Per-platform target matrix | Platform | Block family | Notes | | --- | --- | --- | | Desktop (PC) | **BC7** | Near-universal; the desktop probe picks it first. | | Mobile / tablet | **ASTC-4x4** | Modern GPUs (Adreno, Mali, Apple). | | Older mobile / WebGL-class | **ETC2** | Widely supported baseline. | | Web runtime | **UASTC (in-browser)** | The web export ships raw UASTC and the browser/WebGPU transcodes per-device via the `basis_universal` WebAssembly transcoder. | The desktop probe order is **BC7 -> ASTC-4x4 -> ETC2 -> CPU-decode**. ASTC sizes other than 4x4 are out of scope (their larger texel footprint would break the 4-texel block-row math). ## sRGB vs UNORM authoring - **Colour maps (albedo)**: author as **sRGB**. The KTX2 DFD transfer function sets the flag; the loader reads it and picks the `_SRGB_BLOCK` VkFormat so the texture is gamma-decoded on sample. - **Data maps (normal / roughness / metallic / AO)**: author as **UNORM / linear** (no sRGB flag) so they are sampled raw, not gamma-decoded. Note: the CPU-decode fallback uploads `R8G8B8A8_UNORM`, so an sRGB source loses sRGB-on-sample in that degraded path. This is acceptable; the GPU-native transcode path is colour-correct.