Texture Conversion - limztudio/NWBLot GitHub Wiki

Texture Conversion

tex_conv is the offline authoring utility for NWBLot's typed Texture assets. It converts LDR and HDR images into prepared linear UASTC texture assets — a descriptive .nwb metadata file plus a raw .tex linear UASTC block sidecar; the impl/assets_texture cooker validates that pair without re-encoding and asset_gatherer publishes a cooked texture payload in the graphics volume.

Commands

From the repository root, build the converter without starting it (python3 on Linux):

python -m launcher tex-conv --build-only --config dbg

The utility owns utilities/tex_conv/launch.py. Its discovered shortcut selects the matching preset, configures when needed, and builds its dependencies; no manual CMake command is needed. The run examples pass --working-directory . before -- so relative input and output paths resolve from the repository root.

Build target nwb_tex_conv produces tex_conv. Use the discovered tex-conv repository command; a single input creates a 2D texture:

python -m launcher tex-conv --working-directory . -- --help
python -m launcher tex-conv --working-directory . -- assets/textures/foobar.png

Cubemaps and volumes use explicitly ordered image lists:

python -m launcher tex-conv --working-directory . -- --cube posx.png negx.png posy.png negy.png posz.png negz.png --output sky
python -m launcher tex-conv --working-directory . -- --volume z0.png z1.png z2.png z3.png --output fog

--cube requires exactly six square faces in +X, -X, +Y, -Y, +Z, -Z order. --volume accepts one or more equal-sized images in ascending-Z order. All modes accept PNG, JPEG/JFIF, TGA, and QOI LDR images, plus OpenEXR (.exr) and Radiance (.hdr) HDR images. A conversion's primary images must all be LDR or all be HDR; mixed source classes fail rather than selecting a different texture type.

The default output is <first-input-stem>.nwb plus <first-input-stem>.tex beside the first input. --output <path-or-name> selects another pair. --force is required before either existing output can be replaced.

Ordinary LDR input is interpreted as sRGB; --linear declares LDR input to be linear data instead, and HDR input is always linear. The decoder expands input to float RGBA, the converter applies the sRGB transfer function to RGB before filtering or encoding, and alpha always represents linear coverage. LDR RGB must be finite and in [0, 1]; HDR RGB must be finite and in [0, 65216]; alpha must be finite and in [0, 1]. The converter generates the complete mip chain in linear float space (cube faces filter independently; volume mips reduce all three dimensions), quantizes LDR to UNORM8 after filtering, and encodes with one Basis worker for reproducible output. LDR UASTC is an 8-bit normalized target, so linearized dark colors carry less precision than an sRGB-encoded 8-bit representation; UI artwork follows the same conversion and storage rules as other color textures.

--alpha selects an alpha source independently of RGB:

python -m launcher tex-conv --working-directory . -- rgb.png --alpha opacity.png
python -m launcher tex-conv --working-directory . -- rgb.exr --alpha black
python -m launcher tex-conv --working-directory . -- rgb.hdr --alpha white

An image mask uses its red channel clamped to [0, 1] and must match the source width and height. For cube and volume conversions, one explicitly supplied mask is applied to every face or slice. white and black select constant 1.0 and 0.0 alpha. Without --alpha, the converter preserves input alpha when present and otherwise uses opaque alpha.

Source File Contract

The generated metadata declares texture asset;; asset.format selects the supported payload contract:

  • LDR uses asset.format = "uastc_ldr_4x4"; with opaque or embedded_ldr alpha.
  • HDR uses asset.format = "uastc_hdr_4x4";. HDR is linear and records one alpha mode: opaque, constant_unorm8, or separate_uastc_ldr_4x4.

Authors do not maintain metadata version or specification-revision fields. The cooker rejects unknown fields, including version, schema_version, uastc_spec_revision, uastc_hdr_spec_revision, and alpha_uastc_spec_revision. Internal cooked payload versions remain owned by the codec.

The source fields describe only asset content and author choices:

Applies to Required fields
Every texture format, dimension (2d, cube, or volume), resolution (positive [width, height] for 2D/cube, [width, height, depth] for volumes), and alpha_mode. The .tex file is the implicit same-directory, same-basename sidecar; metadata carries no filename.
Volumes Three-element resolution with positive depth. 2D and cube textures use two elements; cube faces must be square.
LDR alpha_mode opaque or embedded_ldr.
HDR alpha_mode (opaque, constant_unorm8, or separate_uastc_ldr_4x4). Only constant_unorm8 permits and requires integer alpha_constant_unorm8 from 0 through 254; use opaque for 255.

For example, a 7x5 opaque LDR texture uses:

texture asset;
asset.format = "uastc_ldr_4x4";
asset.dimension = "2d";
asset.resolution = [7, 5];
asset.alpha_mode = "opaque";

The cooker derives the complete mip chain length from the resolution, plus fixed block geometry, block grids, slice counts, contiguous offsets, sizes, and the optional trailing HDR alpha range, then checks the exact sidecar byte count. It performs no image conversion or texture encoding. All prepared RGB values are linear. Do not author source color-space flags, data filenames, separate width/height/depth fields, block/layout/address fields, mip_count, mips, version fields, mip tables, byte offsets, or sampler interpolation settings (mip_filter belongs to the separate Sampler asset). The field validator rejects these redundant transport declarations, and unknown fields fail cooking.

The .tex sibling holds raw linear UASTC blocks starting at byte zero with no container header, mip table, or footer. LDR data is a contiguous UASTC LDR 4x4 block stream. HDR data is a contiguous UASTC HDR 4x4 RGB stream, directly valid ASTC HDR 4x4 data. HDR RGB values must be finite and within [0, 65216], preserving radiance above 1.0. HDR UASTC is RGB-only: opaque HDR has no alpha stream, non-white constant alpha is recorded in metadata, and variable alpha is stored as a trailing, same-layout UASTC LDR mask stream. The mask encodes (a, a, a, 255) and the runtime reads its red channel.

Every UASTC block is 16 bytes and edge blocks use the encoder's normal clamped 4x4 representation. Mips are stored first; within a mip, planes are stored in slice order. A 2D mip has one plane, cube faces retain the command-line face order, and volume planes retain ascending-Z order. Volume mip chains reduce all three dimensions until they reach 1x1x1. When HDR has a variable alpha stream, it starts immediately after RGB and exactly mirrors RGB's mip and slice layout.

The 7x5 example has a derived 7x5, 3x2, and 1x1 mip chain: 64 bytes at offset 0, 16 bytes at offset 64, and 16 bytes at offset 80. Its primary stream is exactly 96 bytes. A variable-alpha HDR texture of those dimensions appends another 96-byte stream. The cooker computes the complete layout from the semantic fields and rejects a missing, malformed, or size-mismatched sidecar. HDR masks must mirror the entire RGB mip/slice layout; no authored offset can relocate them.

tex_conv writes that byte order directly to one temporary .tex sidecar: RGB first, followed by the optional alpha stream. This avoids a second concatenated payload allocation. Both .tmp files are created exclusively, written, flushed, and closed before either destination changes. With --force, existing outputs are preserved as .old backups until both replacements are published. A failure restores the previous destinations and removes only work files created by that invocation; cleanup also runs during exception unwind. Occupied .tmp or .old paths are rejected, including dangling links on Linux.

The rollback contract assumes a single writer for the output pair and covers ordinary operation failure or exception unwind. It does not coordinate concurrent writers or provide a crash-atomic two-file filesystem transaction. If filesystem removal or restoration fails, the converter reports the error and retains the previous output at its backup path for recovery. Successful publication removes the backups; a failed backup removal produces a warning. Existing-output protection and the --force requirement remain.

Cook And Runtime

Place the generated pair below an asset root. The asset pipeline derives the virtual path from the metadata file, loads the implicit same-basename sidecar, and serializes a typed Texture asset into the cooked graphics volume. Runtime consumers load it through AssetManager; they never parse the source .nwb or read the loose .tex.

At Vulkan device initialization, the backend probes each ASTC and BC candidate for 2D sampled-image and upload viability, then caches the usable support. TextureAssetLoader creates 2D, cube, and 3D images with global sampled-image heap entries and selects from that cache:

  • LDR: ASTC 4x4, then BC7, then RGBA8. Every runtime texture uses linear RGB and linear alpha coverage.
  • Opaque HDR: ASTC HDR 4x4, then BC6H, then RGBA16_FLOAT.
  • HDR with constant or variable alpha: RGBA16_FLOAT, with the constant or decoded companion mask merged into its alpha channel.

If creating a selected compressed image still fails, the loader retries the next viable fallback. A material declares a plain texture2d field in its material_constant .bind block and supplies the cooked 2D asset's project/ or engine/ path through the corresponding block/field entry in asset.parameters. Cube and volume assets are supported by the loader but need a consumer with the corresponding bindless descriptor class.

The nwb_tex_conv_prepared_textures integration test (tests/integration/tex_conv/verify_tex_conv.py with the nwb_tex_conv_prepared_decode helper) covers LDR/HDR encoding, linear color conversion, complete cube and volume mip chains, alpha overrides, prepared binary admission, and failed-publication recovery. Its publication regression checks occupied backups, Linux dangling backup links, Windows sharing-denied failure on the second backup after the first was moved, unchanged previous bytes, and successful recovery after the obstruction is removed. The visible nwb_texture_capture_smoke then verifies a cooked texture asset through material binding and rendering.

See Asset Pipeline, Asset Types, and Shader and Material System for the wider cook, runtime, and material-binding contracts.