Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1d6ad64
texcomp/texpipe: KTX2 reader + transcode-on-load, expose ASTC decoder
syoyo Jul 11, 2026
b113e0e
texpipe: KTX2 Zstd supercompression read (tp_ktx2_read_zstd)
syoyo Jul 11, 2026
14a2c9d
texpipe/texcomp: harden KTX2 reader against crafted headers (PR #258 …
syoyo Jul 11, 2026
b429a67
texpipe: fix heap overflow in the KTX2 Zstd path (uncompressedByteLen…
syoyo Jul 11, 2026
216058f
ci: make the Claude PR review manual (workflow_dispatch) instead of a…
syoyo Jul 11, 2026
20d416d
texcomp/texpipe: BC1/BC3/BC5 decoders, so the whole LDR writer set re…
syoyo Jul 11, 2026
f577123
texpipe: decode cube faces and array layers (tp_ktx2_decode_slice_rgba8)
syoyo Jul 11, 2026
789ea16
texpipe: parse and expose KTX2 key/value data (tp_ktx2_kv_lookup)
syoyo Jul 11, 2026
136ca1d
texcomp: ETC2/EAC decoders + fix transposed blocks in the ETC2/EAC en…
syoyo Jul 11, 2026
950ff31
texcomp/texpipe: BC6H float decode path (tp_ktx2_decode_level_rgbaf)
syoyo Jul 11, 2026
c2ba616
texcomp/texpipe: promote the ASTC HDR decoder, closing the KTX2 reade…
syoyo Jul 11, 2026
c341d73
texcomp: full ASTC HDR conformance (CEM 14, mixed-CEM, LDR-in-HDR, de…
syoyo Jul 11, 2026
ebfff4d
texcomp: ASTC LDR decoder is now bit-exact with astcenc (fix 1-LSB in…
syoyo Jul 11, 2026
d41cc80
texcomp: BC7 decoder conformance on arbitrary blocks (+ fix UB it exp…
syoyo Jul 11, 2026
a6b8327
texcomp: implement BC5_SNORM properly (it was unorm data wearing a si…
syoyo Jul 11, 2026
f2f38f1
texcomp: include <stdlib.h> in test_texcomp.c for abs()
syoyo Jul 11, 2026
d4f6e23
texcomp: fix ETC2 differential mode packing base and delta at overlap…
syoyo Jul 11, 2026
bd70602
web: live texcomp/tir browser demo, plus texcomp docs and README
syoyo Jul 11, 2026
26f2a4e
texcomp: fix ASTC HDR CEM 15 endpoints overlapping their own weight data
syoyo Jul 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions tools/texcomp/include/texcomp.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,16 @@ tc_result tc_astc_hdr_compress_rgbaf(const float *rgba, uint32_t width,
const tc_astc_hdr_options *opt,
uint8_t *out_astc, size_t out_size);

/* Decode an ASTC LDR block stream (footprint block_x x block_y) to RGBA8.
* out_rgba must hold width*height*4 bytes. Rows top-to-bottom, tightly packed.
* Companion to the BC7 (tc_bc7_decompress_rgba8) and uni (tc_uni_decompress_rgba8)
* decoders; together they cover the tinyexr-native transcodable carrier set
* (uni / ASTC 4x4 / BC7). */
tc_result tc_astc_decompress_rgba8(const uint8_t *astc, uint32_t width,
uint32_t height, uint32_t block_x,
uint32_t block_y, uint8_t *out_rgba,
size_t out_size);

size_t tc_dds_bc7_size(uint32_t width, uint32_t height);
size_t tc_dds_bc1_size(uint32_t width, uint32_t height);
size_t tc_dds_bc3_size(uint32_t width, uint32_t height);
Expand Down
20 changes: 20 additions & 0 deletions tools/texcomp/src/texcomp_astc_decode.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* domain, https://github.com/iOrange/bcdec).
*/
#include "texcomp_internal.h"
#include "texcomp.h"
#include <string.h>

/* ---- ISE tables (ASTC spec data) --------------------------------------- */
Expand Down Expand Up @@ -528,3 +529,22 @@ int tc_astc_decode_image_rgba8(const uint8_t *blocks, uint32_t width,
}
return 1;
}

/* Public API wrapper: validate arguments and decode a whole ASTC LDR surface
* to RGBA8. See texcomp.h. */
tc_result tc_astc_decompress_rgba8(const uint8_t *astc, uint32_t width,
uint32_t height, uint32_t block_x,
uint32_t block_y, uint8_t *out_rgba,
size_t out_size) {
size_t need;
if (!astc || !out_rgba || width == 0u || height == 0u) return TC_ERROR_INVALID_ARGUMENT;
if (block_x == 0u || block_y == 0u) return TC_ERROR_INVALID_ARGUMENT;
/* This decoder handles all 2D block modes; the block dimension is bounded
* by the ASTC 2D footprint set (max 12x12). tmp[] holds bx*by*4 bytes. */
if (block_x > 12u || block_y > 12u) return TC_ERROR_UNSUPPORTED;
need = (size_t)width * (size_t)height * 4u;
if (out_size < need) return TC_ERROR_INVALID_ARGUMENT;
Comment on lines +618 to +619
if (!tc_astc_decode_image_rgba8(astc, width, height, block_x, block_y, out_rgba))
return TC_ERROR_CORRUPT;
return TC_SUCCESS;
}
61 changes: 61 additions & 0 deletions tools/texpipe/include/texpipe.h
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,67 @@ tp_result tp_write_ktx2_array(const tp_blocks *layers, int num_layers,
const tp_options *opt, uint8_t *out,
size_t out_size, size_t *written);

/* ===========================================================================
* KTX2 reading / transcode-on-load (the consumer side of the writers above)
* ========================================================================= */

#define TP_KTX2_MAX_LEVELS 32

/* One mip level of a parsed KTX2. `data` points into the caller's source buffer
* (no copy); `size` is the level byte length (all faces/layers of that level).
* `width`/`height` are the level dimensions. */
typedef struct tp_ktx2_level {
const uint8_t *data;
size_t size;
uint32_t width;
uint32_t height;
} tp_ktx2_level;

/* A parsed KTX2 image header + level index. All level `data` pointers alias the
* source buffer passed to tp_ktx2_read (which must outlive this struct). */
typedef struct tp_ktx2_image {
uint32_t vk_format; /* 0 = UNDEFINED (the tinyexr uni intermediate) */
tp_codec codec; /* mapped block codec (valid only when !is_uni) */
int is_uni; /* 1 = uni/UASTC transcodable intermediate */
int is_hdr;
int srgb;
int block_w, block_h, block_bytes;
uint32_t width, height; /* base (level 0) dimensions */
int num_levels;
int num_faces; /* 1 (2D) or 6 (cube) */
int num_layers; /* 0 or 1 = non-array */
uint32_t supercompression; /* only 0 (none) is supported */
tp_ktx2_level levels[TP_KTX2_MAX_LEVELS]; /* level 0 = largest */
} tp_ktx2_image;

/* Parse a KTX2 blob (identifier + header + level index + DFD). Fills `out` with
* pointers into `data` (no allocation). Supports supercompressionScheme 0 only
* (Zstd/BasisLZ return TP_ERROR_UNSUPPORTED). vk_format == 0 is interpreted as
* the uni intermediate (is_uni = 1). Returns TP_ERROR_INVALID_ARGUMENT on a bad
* identifier / out-of-bounds level index, TP_ERROR_UNSUPPORTED for an
* unrecognized vk_format or supercompression. */
tp_result tp_ktx2_read(const uint8_t *data, size_t size, tp_ktx2_image *out);

/* Decode one level of a parsed KTX2 to RGBA8 (width*height*4 bytes, tightly
* packed, top-to-bottom). Handles the tinyexr-native decodable set: uni,
* BC7, and ASTC LDR. Other codecs (BC1/3/5/6H, ETC2/EAC) return
* TP_ERROR_UNSUPPORTED (upload their blocks directly instead, or transcode a uni
* source via texcomp's tc_uni_transcode_*). Single-face/non-array only. */
tp_result tp_ktx2_decode_level_rgba8(const tp_ktx2_image *img, int level,
uint8_t *out_rgba, size_t out_size);

/* Serialize pre-encoded uni (UASTC) mip levels as a KTX2 (vkFormat = UNDEFINED,
* supercompressionScheme = 0, KHR_DF UASTC descriptor). This is the Basis-free
* transcodable carrier: the reader reports is_uni = 1 and a consumer transcodes
* per device with tc_uni_transcode_{bc7,bc1,astc,etc2} or decodes with
* tc_uni_decompress_rgba8. `uni_levels[l]`/`uni_sizes[l]` are the level-l uni
* bytes (from tc_uni_compress_rgba8), level 0 = largest. */
size_t tp_ktx2_uni_size(const size_t *uni_sizes, int num_levels);
tp_result tp_ktx2_write_uni(const uint8_t *const *uni_levels,
const size_t *uni_sizes, const uint32_t *level_w,
const uint32_t *level_h, int num_levels,
uint8_t *out, size_t out_size, size_t *written);

/* ===========================================================================
* Leaf helpers (also the Phase 1 test surface)
* ========================================================================= */
Expand Down
76 changes: 76 additions & 0 deletions tools/texpipe/src/texpipe.c
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,82 @@ tp_result tp_codec_describe(tp_codec codec, const tp_options *opt,
return TP_ERROR_UNSUPPORTED;
}

/* ASTC block dimensions in VkFormat index order (must match tp_astc_vk_format). */
static const uint8_t tp_astc_dims[14][2] = {
{4, 4}, {5, 4}, {5, 5}, {6, 5}, {6, 6}, {8, 5}, {8, 6},
{8, 8}, {10, 5}, {10, 6}, {10, 8}, {10, 10}, {12, 10},{12, 12}};

tp_result tp_vk_format_describe(uint32_t vk, tp_codec_desc *d,
tp_codec *out_codec, int *out_srgb) {
if (!d || !out_codec) return TP_ERROR_INVALID_ARGUMENT;
memset(d, 0, sizeof(*d));
d->block_w = 4;
d->block_h = 4;
if (out_srgb) *out_srgb = 0;
d->vk_format = vk;
switch (vk) {
case TP_VK_BC1_RGBA_UNORM:
case TP_VK_BC1_RGBA_SRGB:
d->name = "bc1"; d->block_bytes = 8; d->channels_in = 4;
if (out_srgb) *out_srgb = (vk == TP_VK_BC1_RGBA_SRGB);
*out_codec = TP_CODEC_BC1; return TP_SUCCESS;
case TP_VK_BC3_UNORM:
case TP_VK_BC3_SRGB:
d->name = "bc3"; d->block_bytes = 16; d->channels_in = 4;
if (out_srgb) *out_srgb = (vk == TP_VK_BC3_SRGB);
*out_codec = TP_CODEC_BC3; return TP_SUCCESS;
case TP_VK_BC5_UNORM:
case TP_VK_BC5_SNORM:
d->name = "bc5"; d->block_bytes = 16; d->channels_in = 4;
*out_codec = TP_CODEC_BC5; return TP_SUCCESS;
case TP_VK_BC6H_UFLOAT:
case TP_VK_BC6H_SFLOAT:
d->name = "bc6h"; d->block_bytes = 16; d->channels_in = 3; d->is_hdr = 1;
*out_codec = TP_CODEC_BC6H; return TP_SUCCESS;
case TP_VK_BC7_UNORM:
case TP_VK_BC7_SRGB:
d->name = "bc7"; d->block_bytes = 16; d->channels_in = 4;
if (out_srgb) *out_srgb = (vk == TP_VK_BC7_SRGB);
*out_codec = TP_CODEC_BC7; return TP_SUCCESS;
case TP_VK_ETC2_RGB_UNORM:
case TP_VK_ETC2_RGB_SRGB:
d->name = "etc2_rgb"; d->block_bytes = 8; d->channels_in = 4;
if (out_srgb) *out_srgb = (vk == TP_VK_ETC2_RGB_SRGB);
*out_codec = TP_CODEC_ETC2_RGB; return TP_SUCCESS;
case TP_VK_ETC2_RGBA_UNORM:
case TP_VK_ETC2_RGBA_SRGB:
d->name = "etc2_rgba"; d->block_bytes = 16; d->channels_in = 4;
if (out_srgb) *out_srgb = (vk == TP_VK_ETC2_RGBA_SRGB);
*out_codec = TP_CODEC_ETC2_RGBA; return TP_SUCCESS;
case TP_VK_EAC_R11_UNORM:
d->name = "eac_r11"; d->block_bytes = 8; d->channels_in = 4;
*out_codec = TP_CODEC_EAC_R11; return TP_SUCCESS;
case TP_VK_EAC_RG11_UNORM:
d->name = "eac_rg11"; d->block_bytes = 16; d->channels_in = 4;
*out_codec = TP_CODEC_EAC_RG11; return TP_SUCCESS;
default: break;
}
/* ASTC LDR: base 157, unorm/srgb interleaved (step 2) over 14 block sizes. */
if (vk >= TP_VK_ASTC_4x4_UNORM && vk <= TP_VK_ASTC_4x4_UNORM + 27u) {
uint32_t rel = vk - TP_VK_ASTC_4x4_UNORM;
uint32_t idx = rel / 2u;
d->name = "astc"; d->block_bytes = 16; d->channels_in = 4;
d->block_w = tp_astc_dims[idx][0];
d->block_h = tp_astc_dims[idx][1];
if (out_srgb) *out_srgb = (int)(rel & 1u);
*out_codec = TP_CODEC_ASTC; return TP_SUCCESS;
}
/* ASTC HDR: base 1000066000, step 1 over 14 block sizes. */
if (vk >= TP_VK_ASTC_4x4_SFLOAT && vk < TP_VK_ASTC_4x4_SFLOAT + 14u) {
uint32_t idx = vk - TP_VK_ASTC_4x4_SFLOAT;
d->name = "astc_hdr"; d->block_bytes = 16; d->channels_in = 3; d->is_hdr = 1;
d->block_w = tp_astc_dims[idx][0];
d->block_h = tp_astc_dims[idx][1];
*out_codec = TP_CODEC_ASTC_HDR; return TP_SUCCESS;
}
return TP_ERROR_UNSUPPORTED;
}

size_t tp_codec_block_size(tp_codec codec, const tp_options *opt, uint32_t w,
uint32_t h) {
tp_codec_desc d;
Expand Down
Loading
Loading