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.
| 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.
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_decodedecodes the double-height base image and folds the atlas back exactly like the engine (A = Rof the bottom-half texel).texc_encodesplits your RGBA into the two planes and encodes them as one base-codec image.texc_swizzle/texc_unswizzle/texc_swizzled_sizeoperate on the double-height layout automatically (doublesHEIGHTbefore untiling).
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;
RGB8A4additionally prefixes every colour block with 8 bytes of 4-bit alpha, nibble indexx*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.pngPorted 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_dimsreports, 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 <= c1selects 3-colour mode with a transparent 4th entry. Encoding reuses the BC1 encoder, soalpha_thresholdcontrols 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.pngtexc_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);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_swizzleis 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.
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.
Ported from tex-decoder's
profiler.ts and flipper.ts:
- Colour profile converter -
texc_convert_profileconverts 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_namegive metadata. - Flipper -
texc_flip_y(row order, tex-decoderflipImage) andtexc_flip_x(horizontal mirror), any bytes-per-pixel, in-place capable. - Cropper -
texc_crop(tex-decodercropImage): copy a rectangle out of a raw image with bounds validation.
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.pyThe 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 firstPublishing 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.
The version is declared once, in include/tex_codec.h:
#define TEXC_VERSION_MAJOR 1
#define TEXC_VERSION_MINOR 5
#define TEXC_VERSION_PATCH 0Everything 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"
#endifCMake 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.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
ctest --test-dir build -C ReleaseOptions: -DTEXC_BUILD_SHARED=ON (DLL/.so with exported symbols),
-DTEXC_BUILD_TESTS=OFF, -DTEXC_BUILD_CLI=OFF.
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 autoOutput 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>.
With an activated emsdk:
cd wasm && ./build_wasm.sh ../distor 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).
#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);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);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 }).
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.
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.
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
- 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