Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tex_codec

A standalone, portable C++ library (with a flat C API and WebAssembly build) for decoding and encoding GPU compressed texture formats, plus platform unswizzling and reswizzling for console texture layouts.

No external dependencies. One header, one static/shared library.

Includes Web Assembly for .wasm, .js and .ts output.

Supported formats

Family Formats Decode Encode
BC / DXT BC1 (DXT1), BC2 (DXT3), BC3 (DXT5), BC4 ± SNORM (ATI1/RGTC1), BC5 ± SNORM (ATI2/3Dc), BC6H UF16/SF16, BC7 ✅ all modes ✅ (BC6H: mode 11, BC7: mode 6)
ETC / EAC ETC1 (with alpha†), ETC2 RGB / RGBA1 (punchthrough) / RGBA8, EAC R11 / RG11 ± signed ✅ ✅
PVRTC PVRTC1 (with alpha†) 2bpp & 4bpp RGB/RGBA, PVRTC2 2bpp & 4bpp ✅ ✅ (PVRTC2: hard-mode)
ASTC LDR + HDR blocks, 2D block sizes 4x4 ... 12x12 ✅ ✅ (valid-bitstream baseline)
ATC ATC RGB, RGBA explicit, RGBA interpolated ✅ ✅
PICA200 (3DS) PICA_ETC1_RGB8 (GPU_ETC1), PICA_ETC1_RGB8A4 (GPU_ETC1A4) ✅ ✅
GameCube / Wii (GX, TPL) WII_I4, WII_I8, WII_IA4, WII_IA8, WII_RGB565, WII_RGB5A3, WII_RGBA8, WII_CMPR; paletted WII_C4, WII_C8, WII_C14X2 ✅ (paletted via texc_decode_paletted) ✅ (not paletted)
Raw RGBA8 passthrough ✅ ✅

Decode target is 8-bit RGBA (texc_decode) or float RGBA (texc_decode_f32, full range for BC6H / ASTC HDR). Encode source is 8-bit RGBA (texc_encode) or float RGBA (texc_encode_f32). Non-block-aligned dimensions are handled (decoders clip, encoders edge-replicate). Encoders favour portability and determinism over ultimate quality; they produce valid bitstreams everywhere.

† See Alpha-atlas formats below.

Alpha-atlas formats

Some alpha-less codecs can hold an alpha by storing the image twice in one double-height texture: RGB on top, the alpha channel as a grayscale map below. The *_A_ATLAS formats reproduce that end to end - you always pass the final (half-height) dimensions:

  • texc_decode decodes the double-height base image and folds the atlas back exactly like the engine (A = R of the bottom-half texel).
  • texc_encode splits your RGBA into the two planes and encodes them as one base-codec image.
  • texc_swizzle / texc_unswizzle / texc_swizzled_size operate on the double-height layout automatically (doubles HEIGHT before untiling).

PICA200 (Nintendo 3DS) ETC1

PICA_ETC1_RGB8 and PICA_ETC1_RGB8A4 are ordinary ETC1 colour data in the 3DS's own container, handled transparently:

  • the image is stored as 8x8 pixel tiles in row-major order, each tile holding four 4x4 ETC1 blocks in the order (0,0), (4,0), (0,4), (4,4);
  • each 64-bit ETC1 block is byte-reversed relative to the standard big-endian layout;
  • RGB8A4 additionally prefixes every colour block with 8 bytes of 4-bit alpha, nibble index x*4 + y (ETC1's own pixel order), expanded as (a << 4) | a.

Block geometry is reported as the 8x8 tile (32 bytes = 4bpp for RGB8, 64 bytes = 8bpp for RGB8A4), so texc_encoded_size rounds up to whole tiles exactly as the hardware stores them. The layout was verified against devkitPro's tex3ds (which encodes via rg-etc1) and gdkchan/SPICA; note that SPICA also flips its output vertically as its own convention - that is not part of the format, so this library keeps its usual top-left origin (use texc_flip_y for the other orientation).

texc decode -i tex.bin -f PICA_ETC1_RGB8A4 -w 128 -h 128 -o out.png

GameCube / Wii (GX "TPL" formats)

Ported from Kerilk's tpl.h (noesis_bayonetta_pc), which is the decoder Project-G1M's PLATFORM::RVL path runs. "Wii swizzling" is really the GX tile layout plus per-format encoding, so it is modelled as a set of formats rather than a swizzle mode - decode/encode handle the tiling:

  • the image is a row-major grid of tiles, every tile 32 bytes (RGBA8: 64); tile size per format: I4 8x8, I8/IA4 8x4, IA8/RGB565/RGB5A3/RGBA8 4x4, CMPR 8x8, C4 8x8, C8 8x4, C14X2 4x4 - this is what texc_block_dims reports, so sizes round up to whole tiles as the hardware stores them;
  • all multi-byte values are big-endian; 4bpp formats put the even column in the high nibble; expansion is v*255/max (integer), as the reference;
  • RGB5A3: bit 15 set = RGB555 opaque, clear = A3+RGB444;
  • RGBA8: each 64-byte tile is a 32-byte AR plane then a 32-byte GB plane;
  • CMPR: an 8x8 tile is four DXT1-style 4x4 sub-blocks (TL, TR, BL, BR) with big-endian colour words and MSB-first selectors; c0 <= c1 selects 3-colour mode with a transparent 4th entry. Encoding reuses the BC1 encoder, so alpha_threshold controls punchthrough here too.

Paletted formats (WII_C4, WII_C8, WII_C14X2) need a palette of big-endian 16-bit IA8 / RGB565 / RGB5A3 entries - for G1T content, an entry from the matching G1TL file (its WiiPALETTE_TYPE maps directly onto texc_palette_format). texc_decode returns TEXC_ERR_NEEDS_PALETTE for them; use texc_decode_paletted, sizing the palette with texc_palette_size (32 / 512 / 32768 bytes). Encoding paletted formats is not supported (no palette generation).

Project-G1M's getNWiiFormat() maps G1T pixel-format IDs like so:

Wii format G1T pixel-format IDs
WII_I4 0x2B, 0x2F
WII_I8 0x18, 0x28, 0x29, 0x2A, 0x2C, 0x30
WII_IA4 0x2D
WII_IA8 0x26, 0x27, 0x2E
WII_RGB565 0x1C
WII_RGB5A3 0x25
WII_RGBA8 0x0A, 0x13, 0x22
WII_CMPR 0x10
WII_C4 / WII_C8 / WII_C14X2 0x31 / 0x32 / 0x15, 0x33

Cube maps and volume textures are simply consecutive 2D faces / slices; decode each with its own dimensions, as the reference does.

texc decode -i tex.bin -f WII_CMPR -w 256 -h 256 -o out.png
texc decode -i tex.bin -f WII_C8 -w 128 -h 128 --palette pal.bin --palette-format rgb5a3 -o out.png

Encoder options

texc_encode_ex takes an optional texc_encode_options (zero-init + texc_encode_options_init for defaults; the struct is versioned by struct_size and may grow):

texc_encode_options opts;
texc_encode_options_init(&opts);
opts.alpha_threshold = 200;   /* punchthrough cutoff for BC1 / ETC2_RGBA1 */
texc_encode_ex(TEXC_FORMAT_BC1, rgba, size, w, h, dst, dst_size, &opts);

Unswizzling / reswizzling (console layouts)

Two clearly named directions (swizzle logic credited to Piken / DwayneR):

  • texc_unswizzle - platform-tiled → linear row-major: the direction you need before decoding console data.
  • texc_reswizzle - linear → platform-tiled: the exact inverse, for putting the platform tiling back when repacking data for the target hardware (e.g. into a G1T). (texc_swizzle is the same function under its legacy name.)

Every mode's reswizzle→unswizzle roundtrip is verified byte-identical in the test suite. Supported layouts:

Mode Layout
TEXC_SWIZZLE_PS4 Orbis 8x8 micro-tile Morton
TEXC_SWIZZLE_PS5 Prospero / AMD GFX10 block tiling (arg = texc_ps5_tile_mode, 0 = standard 4 KB blocks); whole mip chains via the surface-layout API below
TEXC_SWIZZLE_SWITCH Tegra X1 block-linear GOBs (arg = block height log2, 0xFFFFFFFF = auto)
TEXC_SWIZZLE_PSVITA GXM: 32x32 pixel tiles for raw formats (arg = bytes per pixel, 0 = the format's own; G1T ships 8/16/24/32bpp raw Vita textures), Morton / Z-order for block formats
TEXC_SWIZZLE_X360 Xenos macro tiling
TEXC_SWIZZLE_PSP 16-byte x 8-row tiles
TEXC_SWIZZLE_3DS 8x8 Z-order tiles
TEXC_SWIZZLE_WIIU GX2 tiled (arg = GX2 swizzle value)
TEXC_SWIZZLE_DX12_64KB D3D12 64KB standard swizzle (<64KB data is linear)

texc_decode_swizzled does unswizzle + decode in one call. Container parsing is intentionally left to the adopting project - this library is the conversion core.

When you pass a non-zero arg, size the linear side with texc_unswizzled_size(mode, format, w, h, arg) rather than texc_encoded_size() - the format alone cannot describe the PS Vita raw bytes-per-pixel override.

PS Vita raw reproduces DeswizzlePSVitaRaw exactly: the tiled buffer is width * height * bytesPerPixel (the raw width is used, not rounded up to whole 32x32 tiles), and texels whose mapped offset falls outside the image are dropped, as the reference does. That only happens when a dimension is not a multiple of 32, where the mapping is consequently not invertible. A test compares the port against the reference formula across sizes and 8/16/24/32bpp.

PS5 (Prospero) tiling and mip-chain surfaces

The PS5 GPU is an AMD GFX10-class part, and a texture is a row-major raster of fixed-size blocks (256 B, 4 KB or 64 KB) whose interior uses AMD's "standard" address swizzle - the layout AMD's open-source addrlib computes for SW_4KB_S and friends, and what Sony's AgcGpuAddress library calls TileMode::kStandard4KB etc. Every known G1T PS5 texture uses the 4 KB mode (Sony's own texture tool defaults sampled textures to it), which is arg = TEXC_PS5_TILE_DEFAULT (0). TEXC_PS5_TILE_STANDARD_256B (1), _4KB (5) and _64KB (9) match Sony's enum so a header value can be passed straight through; TEXC_PS5_TILE_LEGACY (0x100) keeps the pre-1.6 layout reachable (it equals the 64 KB mode for 16-byte blocks and 32bpp raw data, but is not a hardware layout for anything else).

Koei Tecmo content is not all 4 KB: small textures (mip 0 up to 64 KB in every sample seen) use STANDARD_4KB, large ones (1024x128 and 64x2048 BC7 samples, mip 0 = 128 KB) use STANDARD_64KB, and G1T carries no tile-mode field. The two layouts have different whole-surface sizes, so the mode can be recovered from the texture's data size:

texc_ps5_tile_mode tm;
texc_ps5_detect_tile_mode(TEXC_FORMAT_BC7, 1024, 128, 6, 1,
                          /*data bytes in the G1T*/ 524288, &tm);
/* tm == TEXC_PS5_TILE_STANDARD_64KB (a 4 KB surface would be 196608) */

For the write path, texc_ps5_default_tile_mode(format, w, h) returns the choice observed in Koei Tecmo content (64 KB blocks once mip 0 exceeds 64 KB); it is a heuristic that matches every sample so far. The CLI's --arg auto uses the detector when unswizzling and the heuristic when reswizzling.

A PS5 texture stores its whole mip chain as one surface: the smallest mips that fit in half a block share a single "mip tail" block stored first, then the remaining mips follow in descending order, so mip 0 is last; array slices and cube faces repeat the chain. Because the padding, tail packing and ordering are platform-defined, the library exposes the layout instead of leaving callers to guess offsets:

texc_surface_layout lay;
texc_get_surface_layout(TEXC_SWIZZLE_PS5, TEXC_FORMAT_BC7, 256, 256,
                        /*mips*/ 7, /*slices*/ 1, /*arg*/ 0, &lay);
/* lay.total_size == 90112: 64K (mip 0) + 16K + 4K + one 4K tail block;
   lay.mips[0].offset == 24576, lay.mips[3].in_tail == 1 ... */

/* pull any mip straight out of the surface, ready to decode */
texc_unswizzle_mip(TEXC_SWIZZLE_PS5, TEXC_FORMAT_BC7, 256, 256, 7, 1, 0,
                   /*mip*/ 0, /*slice*/ 0, surface, lay.total_size,
                   linear, texc_encoded_size(TEXC_FORMAT_BC7, 256, 256));
/* and back: zero-fill total_size bytes, then texc_reswizzle_mip per mip */

texc_unswizzle / texc_reswizzle with TEXC_SWIZZLE_PS5 handle a single mip (its block-padded raster, texc_swizzled_size bytes) and are exactly texc_unswizzle_mip on a one-level surface, so mip 0 of a chain can also be converted by pointing them at lay.mips[0].offset. texc_get_surface_layout also works for TEXC_SWIZZLE_NONE (mips back to back, mip 0 first); other modes return TEXC_ERR_UNSUPPORTED.

The implementation is derived from AMD addrlib (MIT). It is verified bit-for-bit against Sony's AgcGpuAddress host library across tile modes, element sizes, odd sizes, mip counts and slices by tools/ps5_oracle (needs the PS5 SDK, never committed); the SDK-generated known-answer table it produces (tests/ps5_kat.h) is checked by the normal test suite, and so are real G1T textures when samples/ps5/textures/ is present. 3D (volume) textures, render-target/depth tile modes and MSAA are not implemented.

Image utilities

Ported from tex-decoder's profiler.ts and flipper.ts:

  • Colour profile converter - texc_convert_profile converts raw pixels between 72 profiles (texc_pixel_profile: RGBA8/BGRA8 and every channel permutation, RGB565, RGBA4, RGBA51, RGB10_A2, signed *I, half-float *16F, float *32F, 16/32-bit integer). Semantics match tex-decoder: proportional range scaling, a missing source alpha becomes opaque, missing colour channels become 0, float 1.0 ≙ integer max. texc_profile_bytes_per_pixel / texc_profile_name give metadata.
  • Flipper - texc_flip_y (row order, tex-decoder flipImage) and texc_flip_x (horizontal mirror), any bytes-per-pixel, in-place capable.
  • Cropper - texc_crop (tex-decoder cropImage): copy a rectangle out of a raw image with bounds validation.

Releasing

tools/make_release.py packages a release zip from the built artifacts:

tex_codec_v<version>.zip
  wasm/    tex_codec.js, tex_codec.wasm, tex_codec_api.mjs, .d.ts
  cli/     texc.exe, tex_codec.lib
  include/ tex_codec.h
  README.md, CHANGELOG.md

Build everything first (the zip needs both the native and the Emscripten output), then package:

cmake --build build --config Release
cd wasm && ./build_wasm.sh ../dist && cd ..
cmake --build build --config Release --target release   # or: python tools/make_release.py

The version comes from include/tex_codec.h, and the script refuses to package if texc.exe or dist/tex_codec_api.mjs report a different one - so a zip can never ship stale artifacts under a new version number.

To also create the GitHub release, using that version's CHANGELOG.md section verbatim as the release notes:

python tools/make_release.py --publish          # add --draft to stage it first

Publishing needs a clean working tree (it refuses otherwise) and either the gh CLI authenticated via gh auth login, or a GITHUB_TOKEN environment variable with repo scope - the token is read from the environment and never printed. It tags v<version> and attaches the zip.

Versioning

The version is declared once, in include/tex_codec.h:

#define TEXC_VERSION_MAJOR 1
#define TEXC_VERSION_MINOR 5
#define TEXC_VERSION_PATCH 0

Everything else derives from it, so nothing can drift: CMake parses those macros for the project/package version, stamps them into the Windows file-version resource of tex_codec.dll and texc.exe (visible under Properties → Details) and into SOVERSION on ELF builds, and the test suite fails if the compiled library, the header or the JS wrapper's VERSION disagree. Bumping a release means editing those three lines and adding a CHANGELOG.md entry.

New formats, profiles and swizzle modes are only ever appended to their enums, so numeric values stay stable across minor versions - which is what lets the JS wrapper and other FFI bindings mirror them by number.

Query it at runtime:

texc_version() packed (major << 16) | (minor << 8) | patch
texc_version_string() "1.5.0"
texc_build_info() tex_codec 1.5.0 (git 3f2a1b8, built Sep 2 2026, MSVC 1944, x64, Release)
texc version the same build line (texc version --short prints just 1.5.0)
await tex.version() / tex.buildInfo() from JavaScript, plus tex.checkVersion() to catch a stale .wasm beside a newer wrapper

Compile-time checks are available too:

#if TEXC_VERSION_NUMBER < TEXC_VERSION_ENCODE(1, 1, 0)
#  error "tex_codec 1.1.0 or newer is required"
#endif

CMake consumers can require a version with find_package(tex_codec 1.1 REQUIRED). The git hash in texc_build_info() is captured at configure time - re-run cmake to refresh it after committing.

Building (native)

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
ctest --test-dir build -C Release

Options: -DTEXC_BUILD_SHARED=ON (DLL/.so with exported symbols), -DTEXC_BUILD_TESTS=OFF, -DTEXC_BUILD_CLI=OFF.

Command line tool (texc)

The native build also produces texc (build/Release/texc.exe), a full front end for the library. --offset / --size open a window into any file, so you can point it straight at texture data inside a container (e.g. a G1T) without extracting it first:

# decode BC7 data at an offset inside a container straight to PNG
texc decode -i file.g1t --offset 0x1234 --size 0x8000 -f BC7 -w 256 -h 256 -o out.png

# Switch-swizzled ASTC: unswizzle + decode in one step ('auto' block height)
texc decode -i tex.bin -f ASTC_8x8 -w 256 -h 256 --swizzle switch --arg auto -o out.tga

# G1T alpha atlas (final half-height dimensions, alpha folded automatically)
texc decode -i tex.bin -f ETC1_RGB_A_ATLAS -w 128 -h 128 -o out.png

# encode a TGA (dimensions come from the file) and reswizzle for PS4
texc encode -i art.tga -f BC1 --alpha-threshold 200 --swizzle ps4 -o tiled.bc1

# pure layout conversions, clearly named per direction
texc unswizzle -i tiled.bin  -f BC7 -w 256 -h 256 -m switch --arg auto -o linear.bin
texc reswizzle -i linear.bin -f BC7 -w 256 -h 256 -m switch --arg auto -o tiled.bin

# PS5: the whole mip chain is one surface - describe it, then pull a mip out
# (--arg auto recovers the 4KB / 64KB tile mode from the input size)
texc info -f BC7 -w 256 -h 256 -m ps5 --mips 7
texc unswizzle -i file.g1t --offset 0x38 -f BC7 -w 256 -h 256 -m ps5 --arg auto --mips 7 --mip 0 -o mip0.bin
texc reswizzle -i mip0.bin -f BC7 -w 256 -h 256 -m ps5 --mips 7 --mip 0 -o surface.bin

# image utilities
texc convert -i in.raw --src-profile RGBA8 --dst-profile BGRA8 -o out.raw
texc flip -i in.raw -w 64 -h 64 --dir y -o out.raw
texc crop -i in.raw -w 64 -h 64 --rect 16,16,32,32 -o out.raw

# sizes without touching data
texc info -f ASTC_8x8 -w 256 -h 256 -m switch --arg auto

Output format follows the extension: .png / .tga for decoded images, anything else raw bytes (--f32 writes float RGBA). Encode input is an uncompressed TGA or raw RGBA8 (with -w/-h). Discovery commands: texc formats, texc modes, texc profiles, texc version, texc help <command>.

Building (WebAssembly)

With an activated emsdk:

cd wasm && ./build_wasm.sh ../dist

or via CMake: emcmake cmake -S . -B build-wasm && cmake --build build-wasm.

Produces tex_codec.js (MODULARIZE loader, export name TexCodec) and tex_codec.wasm, plus copies of the ergonomic wrapper (tex_codec_api.mjs + tex_codec_api.d.ts), usable from web, workers, and Node.

Verify the module with node wasm/smoke_test.mjs dist (wrapper roundtrips, alpha-atlas fold, encoder options, swizzle identity, named exports).

Usage - C

#include "tex_codec.h"

size_t out_size = texc_decoded_size(width, height);
uint8_t *rgba = texc_alloc(out_size);
int rc = texc_decode(TEXC_FORMAT_BC7, data, data_size,
                     width, height, rgba, out_size);
if (rc != TEXC_OK) fprintf(stderr, "%s\n", texc_result_str(rc));

/* Swizzled console data in one step: */
rc = texc_decode_swizzled(TEXC_SWIZZLE_SWITCH, TEXC_FORMAT_ASTC_8x8,
                          width, height, data, data_size,
                          rgba, out_size, 0xFFFFFFFF /* auto block height */);

/* Encoding: */
size_t enc_size = texc_encoded_size(TEXC_FORMAT_ETC2_RGBA8, width, height);
uint8_t *enc = texc_alloc(enc_size);
rc = texc_encode(TEXC_FORMAT_ETC2_RGBA8, rgba, out_size,
                 width, height, enc, enc_size);

texc_free(rgba); texc_free(enc);

Usage - JavaScript / WASM

The recommended entry point is the wrapper in wasm/tex_codec_api.mjs (TypeScript declarations in wasm/tex_codec_api.d.ts; both are static, hand-maintained files that build_wasm.sh copies next to the module). It gives you real enums, async self-initialising methods, automatic WASM memory management, and one class per concern:

import { TexCodec, TexFormat, SwizzleMode, PixelProfile,
         SwitchBlockHeightAuto } from "./dist/tex_codec_api.mjs";

const tex = await TexCodec.load();   // finds ./tex_codec.js next to the file

// --- decoding -----------------------------------------------------------
const rgba  = await tex.decoder.decode(TexFormat.BC7, data, w, h);
const hdr   = await tex.decoder.decodeF32(TexFormat.BC6H_UF16, data, w, h);
const fromSwitch = await tex.decoder.decodeSwizzled(
    SwizzleMode.SWITCH, TexFormat.ASTC_8x8, tiled, w, h,
    SwitchBlockHeightAuto);

// G1T alpha atlas - pass the FINAL (half-height) dimensions:
const folded = await tex.decoder.decode(TexFormat.ETC1_RGB_A_ATLAS,
                                        g1tData, w, h);

// --- encoding -----------------------------------------------------------
const bc1 = await tex.encoder.encode(TexFormat.BC1, rgba, w, h,
                                     { alphaThreshold: 200 });
const atlas = await tex.encoder.encode(TexFormat.ETC1_RGB_A_ATLAS,
                                       rgba, w, h);

// --- unswizzling / reswizzling ------------------------------------------
const linear = await tex.swizzler.unswizzle(SwizzleMode.PS4,       // tiled -> linear
                                            TexFormat.BC7, tiled, w, h);
const tiledAgain = await tex.swizzler.reswizzle(SwizzleMode.PS4,   // linear -> tiled
                                                TexFormat.BC7, linear, w, h);
const bytes = await tex.swizzler.swizzledSize(SwizzleMode.SWITCH,
                                              TexFormat.BC1, w, h, 4);

// --- image utilities (tex.image) ----------------------------------------
const bgra    = await tex.image.convertProfile(PixelProfile.RGBA8,
                                               PixelProfile.BGRA8, rgba);
const flipped = await tex.image.flipY(rgba, w, h);          // 4 bpp default
const region  = await tex.image.crop(rgba, w, h, 4, 16, 16, 64, 64);

Per-format / per-mode helper methods

Every class also carries generated named helpers, so call sites don't need the enums - one per format and one per swizzle mode (fully typed in the .d.ts):

await tex.decoder.decodeBC7(data, w, h);
await tex.decoder.decodeETC1_RGB_A_ATLAS(g1tData, w, h);
await tex.decoder.decodePICA_ETC1_RGB8A4(tex3dsData, w, h);   // 3DS
await tex.decoder.decodeSwizzledSwitch(TexFormat.ASTC_8x8, tiled, w, h,
                                       SwitchBlockHeightAuto);
await tex.encoder.encodeETC2_RGBA8(rgba, w, h);
await tex.encoder.encodeBC1(rgba, w, h, { alphaThreshold: 200 });
await tex.encoder.encodedSizeASTC_4x4(w, h);
await tex.swizzler.unswizzlePS4(TexFormat.BC7, tiled, w, h);   // -> linear
await tex.swizzler.reswizzlePS4(TexFormat.BC7, linear, w, h);  // -> tiled

// PS5: a whole mip chain is one surface (mip tail first, mip 0 last)
const lay = await tex.swizzler.surfaceLayout(SwizzleMode.PS5, TexFormat.BC7,
                                             256, 256, /*mips*/ 7);
// lay.totalSize, lay.mips[0].offset, lay.mips[3].inTail, ...
const mip0 = await tex.swizzler.unswizzleMip(SwizzleMode.PS5, TexFormat.BC7,
                                             surface, 256, 256, 7, /*mip*/ 0);
// G1T has no tile-mode field: recover 4KB vs 64KB blocks from the data size
const tm = await tex.swizzler.detectPS5TileMode(TexFormat.BC7, 1024, 128, 6,
                                                surface.byteLength);

Mode names: PS4, PS5, Switch, PSVita, X360, PSP, N3DS, WiiU, DX12_64KB. surfaceLayout / unswizzleMip / reswizzleMip take { sliceCount, slice, arg } options (arg = a PS5TileMode for PS5).

Every method throws TexCodecError (with a .code from TexResult) on failure. In a bundler, pass the module factory yourself: TexCodec.load({ factory: (await import("./tex_codec.js")).default }).

Raw exports

For callers that manage their own buffers, the full C API is exported on the module (tex.module) - see TexCodecModule in tex_codec_api.d.ts for typed signatures:

  • Generic: _texc_decode, _texc_encode, _texc_encode_ex, _texc_encode_with_options(..., alphaThreshold), _texc_decode_f32, _texc_unswizzle, _texc_swizzle, _texc_swizzled_size, _texc_decode_swizzled, _texc_get_surface_layout, _texc_unswizzle_mip, _texc_reswizzle_mip, sizes/metadata, _malloc / _free.
  • Per format (all 45): _texc_decode_bc7(src, srcSize, w, h, dst, dstSize), _texc_encode_etc2_rgba8(..., alphaThreshold), _texc_encoded_size_astc_12x12(w, h), …
  • Per swizzle mode (ps4, ps5, switch, psvita, x360, psp, n3ds, wiiu, dx12_64kb): _texc_unswizzle_ps4(fmt, w, h, src, srcSize, dst, dstSize, arg) (tiled→linear), _texc_reswizzle_switch(...) (linear→tiled; _texc_swizzle_<mode> is its legacy alias), and _texc_swizzled_size_wiiu(...).
  • Image utilities: _texc_convert_profile, _texc_profile_name, _texc_profile_bytes_per_pixel, _texc_flip_y, _texc_flip_x, _texc_crop.
  • Allocation helpers: _texc_decode_alloc, _texc_decode_f32_alloc, _texc_decode_swizzled_alloc, _texc_encode_alloc, _texc_swizzle_alloc, _texc_unswizzle_mip_alloc, with _texc_last_error() for the failure reason.

FFI notes (C# / Rust / Python)

The entire API is extern "C", uses only scalar types and raw pointers, and never throws or allocates on the caller's behalf (except the documented *_alloc WASM helpers). texc_format / texc_swizzle_mode are plain ints. Build with -DTEXC_BUILD_SHARED=ON and bind with P/Invoke, bindgen, or ctypes directly against include/tex_codec.h.

Layout

include/tex_codec.h      public C API
src/tex_codec.cpp        dispatch, validation, buffer management
src/codecs/              bcn.cpp, etc.cpp, astc.cpp, pvrtc.cpp, atc.cpp
src/unswizzle/           platform tiling conversions
wasm/                    Emscripten exports + build script
tests/                   roundtrip + swizzle-identity test suite

Credits / references

  • swizzle machinery credit: Piken / DwayneR, github.com/fdwr
  • PS5 tiling: derived from AMD addrlib (MIT, GPUOpen-Drivers/pal, gfx10addrlib.cpp / gfx10SwizzlePattern.h); the pre-1.6 legacy layout is a port of id-daemon's RawTex Cooker
  • Format specifications: Khronos Data Format Spec (ASTC, ETC2/EAC), Microsoft D3D BC1-7 specs, AMD ATC extension, PowerVR PVRTC documentation
  • Reference codebases studied: tex-decoder, texture2ddecoder, Basis Universal, bimg, ctt

About

A standalone, portable C++ library (with a flat C API and WebAssembly build) for decoding and encoding GPU compressed texture formats, plus platform unswizzling and reswizzling for console texture layouts.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages