Files
basis_universal/transcoder/basisu_xbc7_decoder.inl
Richard Geldreich 069e065454 formatting fix
2026-07-01 23:43:00 -04:00

1516 lines
53 KiB
C++

// File: basisu_xbc7_decoder.inl
// XBC7 decoder implementation. #included at the END of basisu_transcoder.cpp
// (the transcoder stays a single .cpp). Declarations live in
// basisu_xbc7_decoder.h. Decoding REQUIRES zstd (BASISD_SUPPORT_KTX2_ZSTD); when
// that is disabled the public entry points fail with an error.
namespace basist {
namespace xbc7 {
bool eval_weight_predictor(
uint32_t cand_index, uint32_t amp_code,
uint32_t bx, uint32_t by, uint32_t num_blocks_x,
const tile_bounds& tile, // every block reference is clamped to this AABB (encoder: the job's tile; decoder: whole image)
const vector2D<basist::bc7u::log_bc7_block>& log_blks,
uint32_t p, int pOut_preds[16])
{
BASISU_NOTE_UNUSED(num_blocks_x); // bounds now come from the tile
assert(cand_index != cCandAbsolute);
assert(cand_index < cTotalCandidates);
assert(amp_code < 4);
assert(tile.contains((int)bx, (int)by));
auto fetch_w = [](const basist::bc7u::log_bc7_block* pBlk, uint32_t plane, uint32_t w) -> int
{
const uint32_t sp = pBlk->is_dual_plane() ? plane : 0;
return basist::bc7u::dequant_weight(pBlk->m_weights[sp][w], pBlk->m_weight_bits[sp]);
};
const basist::bc7u::log_bc7_block* pLeft_diag_log_blk = tile.contains((int)bx - 1, (int)by - 1) ? &log_blks(bx - 1, by - 1) : nullptr;
const basist::bc7u::log_bc7_block* pRight_diag_log_blk = tile.contains((int)bx + 1, (int)by - 1) ? &log_blks(bx + 1, by - 1) : nullptr;
const basist::bc7u::log_bc7_block* pUp_log_blk = tile.contains((int)bx, (int)by - 1) ? &log_blks(bx, by - 1) : nullptr;
const basist::bc7u::log_bc7_block* pLeft_log_blk = tile.contains((int)bx - 1, (int)by) ? &log_blks(bx - 1, by) : nullptr;
const basist::bc7u::log_bc7_block* pCand_log_blk = nullptr;
if (cand_index == cCandAbsolute)
{
}
else if (cand_index >= cCandFirstXYDelta)
{
// generic causal block reference (copy); subsumes the old
// left/up/left-diag/right-diag copy candidates
const xbc7_xy_delta& delta = g_xbc7_xy_deltas[cand_index - cCandFirstXYDelta];
const int nx = (int)bx + delta.m_dx;
const int ny = (int)by + delta.m_dy;
if (tile.contains(nx, ny))
pCand_log_blk = &log_blks(nx, ny);
if (!pCand_log_blk)
return false;
}
else
{
if (cand_index == cCandLeftEdge)
pCand_log_blk = pLeft_log_blk; // left edge
else if (cand_index == cCandUpperEdge)
pCand_log_blk = pUp_log_blk; // upper edge
else if (cand_index == cCandLUBlend)
pCand_log_blk = (pLeft_log_blk && pUp_log_blk) ? pLeft_log_blk : nullptr; // left+upper edge blend
else if (cand_index == cCandReflectLeft)
pCand_log_blk = pLeft_log_blk; // reflect left
else if (cand_index == cCandReflectUpper)
pCand_log_blk = pUp_log_blk; // reflect upper
else if (cand_index == cCandLUAvg)
pCand_log_blk = (pLeft_log_blk && pUp_log_blk) ? pLeft_log_blk : nullptr; // left+upper edge avg
else if (cand_index == cCandLUBlendStrong)
pCand_log_blk = (pLeft_log_blk && pUp_log_blk) ? pLeft_log_blk : nullptr; // left+upper edge stronger distance blend
else if (cand_index == cCandGradient)
pCand_log_blk = (pLeft_log_blk && pUp_log_blk && pLeft_diag_log_blk) ? pLeft_log_blk : nullptr; // gradient
else if (cand_index == cCandGradientDamped)
pCand_log_blk = (pLeft_log_blk && pUp_log_blk && pLeft_diag_log_blk) ? pLeft_log_blk : nullptr; // damped gradient
else if (cand_index == cCandDiagAvg)
pCand_log_blk = (pLeft_diag_log_blk && pRight_diag_log_blk) ? pLeft_diag_log_blk : nullptr; // left/right diagonal avg
else if (cand_index == cCandDiagEdgeBlend)
pCand_log_blk = (pLeft_diag_log_blk && pRight_diag_log_blk) ? pLeft_diag_log_blk : nullptr; // diagonal edge blend
else if (cand_index == cCandUpperDiagEdgeBlend)
pCand_log_blk = (pUp_log_blk && pLeft_diag_log_blk && pRight_diag_log_blk) ? pLeft_diag_log_blk : nullptr; // upper + diagonal edge blend
else if ((cand_index == cCandMED) || (cand_index == cCandGAB))
pCand_log_blk = (pLeft_log_blk && pUp_log_blk && pLeft_diag_log_blk) ? pLeft_log_blk : nullptr; // MED / gradient-adaptive blend
else if (cand_index == cCandPlaneFit)
pCand_log_blk = (pLeft_log_blk && pUp_log_blk) ? pLeft_log_blk : nullptr; // LS plane fit
else if ((cand_index == cCandDDL) && pUp_log_blk && pRight_diag_log_blk)
pCand_log_blk = pUp_log_blk;
else if (cand_index == cCandDDR)
pCand_log_blk = (pLeft_log_blk && pUp_log_blk && pLeft_diag_log_blk) ? pLeft_log_blk : nullptr;
if (!pCand_log_blk)
return false;
}
int* pWeight_predictions = nullptr;
int weight_preds[16];
if (pCand_log_blk)
{
for (uint32_t w = 0; w < 16; w++)
{
weight_preds[w] = fetch_w(pCand_log_blk, p, w);
}
int orig_weight_preds[16];
memcpy(orig_weight_preds, weight_preds, sizeof(orig_weight_preds));
if (cand_index == cCandLeftEdge)
{
// left edge
for (uint32_t y = 0; y < 4; y++)
for (uint32_t x = 0; x < 4; x++)
weight_preds[index_from_xy(x, y)] = orig_weight_preds[index_from_xy(3, y)];
}
else if (cand_index == cCandUpperEdge)
{
// upper edge
for (uint32_t y = 0; y < 4; y++)
for (uint32_t x = 0; x < 4; x++)
weight_preds[index_from_xy(x, y)] = orig_weight_preds[index_from_xy(x, 3)];
}
else if ((cand_index == cCandLUBlend) || (cand_index == cCandLUAvg) || (cand_index == cCandLUBlendStrong))
{
// left+upper edge blend variants.
// pCand_log_blk is pLeft_log_blk here, so orig_weight_preds contains the left block.
// Pull upper edge directly from pUp_log_blk.
int upper_edge[4];
for (uint32_t x = 0; x < 4; x++)
{
const uint32_t w = index_from_xy(x, 3); // upper block's bottom edge
upper_edge[x] = fetch_w(pUp_log_blk, p, w);
}
for (uint32_t y = 0; y < 4; y++)
{
const int left_val = orig_weight_preds[index_from_xy(3, y)]; // left block's right edge
for (uint32_t x = 0; x < 4; x++)
{
const int upper_val = upper_edge[x];
int pred;
if (cand_index == cCandLUBlend)
{
// Existing distance-weighted blend.
const int wl = 4 - static_cast<int>(x); // 4,3,2,1
const int wu = 4 - static_cast<int>(y); // 4,3,2,1
const int den = wl + wu;
pred = (wl * left_val + wu * upper_val + (den >> 1)) / den;
}
else if (cand_index == cCandLUAvg)
{
// Simple average.
pred = (left_val + upper_val + 1) >> 1;
}
else // cCandLUBlendStrong
{
// Stronger distance weighting: trust the nearest edge more.
const int dx = 4 - static_cast<int>(x); // 4,3,2,1
const int dy = 4 - static_cast<int>(y); // 4,3,2,1
const int wl = dx * dx; // 16,9,4,1
const int wu = dy * dy; // 16,9,4,1
const int den = wl + wu;
pred = (wl * left_val + wu * upper_val + (den >> 1)) / den;
}
weight_preds[index_from_xy(x, y)] = pred;
}
}
}
else if (cand_index == cCandReflectLeft)
{
// reflect left
for (uint32_t y = 0; y < 4; y++)
for (uint32_t x = 0; x < 4; x++)
weight_preds[index_from_xy(x, y)] = orig_weight_preds[index_from_xy(3 - x, y)];
}
else if (cand_index == cCandReflectUpper)
{
// reflect upper
for (uint32_t y = 0; y < 4; y++)
for (uint32_t x = 0; x < 4; x++)
weight_preds[index_from_xy(x, y)] = orig_weight_preds[index_from_xy(x, 3 - y)];
}
else if ((cand_index == cCandGradient) || (cand_index == cCandGradientDamped) || (cand_index == cCandMED) || (cand_index == cCandGAB))
{
int upper_edge[4];
for (uint32_t x = 0; x < 4; x++)
{
const uint32_t w = index_from_xy(x, 3); // upper block's bottom edge
upper_edge[x] = fetch_w(pUp_log_blk, p, w);
}
const uint32_t corner_w = index_from_xy(3, 3); // upper-left block's bottom-right
int corner_val;
corner_val = fetch_w(pLeft_diag_log_blk, p, corner_w);
for (uint32_t y = 0; y < 4; y++)
{
const int left_val = orig_weight_preds[index_from_xy(3, y)]; // left block's right edge
for (uint32_t x = 0; x < 4; x++)
{
const int upper_val = upper_edge[x];
if (cand_index == cCandGradient)
{
int grad = left_val + upper_val - corner_val;
grad = basisu::clamp<int>(grad, 0, 64);
weight_preds[index_from_xy(x, y)] = grad;
}
else if (cand_index == cCandGradientDamped)
{
int grad = left_val + upper_val - corner_val;
grad = basisu::clamp<int>(grad, 0, 64);
// Damped gradient: blend gradient with the proven #7 predictor.
const int wl = 4 - static_cast<int>(x);
const int wu = 4 - static_cast<int>(y);
const int den = wl + wu;
const int blend7 = (wl * left_val + wu * upper_val + (den >> 1)) / den;
weight_preds[index_from_xy(x, y)] = (grad + blend7 + 1) >> 1;
}
else if (cand_index == cCandMED)
{
// MED (Median Edge Detector, JPEG-LS / LOCO-I).
// Plane predictor wrapped in a per-sample edge switch:
// if the corner is the local extreme, an edge passes
// between the neighbors -- predict from the neighbor on
// the current sample's side instead of extrapolating
// through the edge (which is #12's overshoot failure).
const int mn = basisu::minimum(left_val, upper_val);
const int mx = basisu::maximum(left_val, upper_val);
int pred;
if (corner_val >= mx)
pred = mn;
else if (corner_val <= mn)
pred = mx;
else
pred = left_val + upper_val - corner_val; // in (mn, mx) here, cannot overshoot
weight_preds[index_from_xy(x, y)] = basisu::clamp<int>(pred, 0, 64);
}
else // cCandGAB
{
// GAB: gradient-adaptive blend (CALIC-spirit, 3 samples).
// |left-corner| large => a horizontal edge crossed between
// the corner row and this row, so the left sample is on the
// current sample's side of it: trust it more. Symmetrically
// for |upper-corner| and vertical edges. A self-normalizing
// soft MED whose weights come from the data, not position.
const int wl = basisu::iabs(left_val - corner_val) + 1;
const int wu = basisu::iabs(upper_val - corner_val) + 1;
const int den = wl + wu;
const int pred = (wl * left_val + wu * upper_val + (den >> 1)) / den;
weight_preds[index_from_xy(x, y)] = pred; // convex blend of in-range values: already in [0,64]
}
}
}
}
else if (cand_index == cCandDiagAvg)
{
// Average upper-left and upper-right diagonal blocks.
// pCand_log_blk is pLeft_diag_log_blk here, so orig_weight_preds contains upper-left.
// Pull upper-right directly from pRight_diag_log_blk.
for (uint32_t w = 0; w < 16; w++)
{
int right_diag_val;
right_diag_val = fetch_w(pRight_diag_log_blk, p, w);
weight_preds[w] = (orig_weight_preds[w] + right_diag_val + 1) >> 1;
}
}
else if (cand_index == cCandDiagEdgeBlend)
{
// Blend upper-left block's right edge with upper-right block's left edge.
// pCand_log_blk is pLeft_diag_log_blk, so orig_weight_preds contains upper-left.
// Pull upper-right left edge directly from pRight_diag_log_blk.
//
// For each row y:
// L = upper-left[3,y]
// R = upper-right[0,y]
// Then interpolate across x.
int right_diag_left_edge[4];
for (uint32_t y = 0; y < 4; y++)
{
const uint32_t w = index_from_xy(0, y); // upper-right block's left edge
right_diag_left_edge[y] = fetch_w(pRight_diag_log_blk, p, w);
}
for (uint32_t y = 0; y < 4; y++)
{
const int left_val = orig_weight_preds[index_from_xy(3, y)]; // upper-left right edge
const int right_val = right_diag_left_edge[y]; // upper-right left edge
for (uint32_t x = 0; x < 4; x++)
{
// x=0 mostly left_val, x=3 mostly right_val.
// Use 4-sample interpolation: 3/0, 2/1, 1/2, 0/3.
const int pred = ((3 - static_cast<int>(x)) * left_val +
static_cast<int>(x) * right_val + 1) / 3;
weight_preds[index_from_xy(x, y)] = pred;
}
}
}
else if (cand_index == cCandUpperDiagEdgeBlend)
{
// Blend upper edge predictor with diagonal edge blend.
//
// upper_edge[x] = upper block's bottom edge
// diag_blend[x,y] = horizontal interpolation between:
// upper-left block's right edge and upper-right block's left edge
//
// This combines direct top continuation with previous-row lateral structure.
int upper_edge[4];
int right_diag_left_edge[4];
for (uint32_t x = 0; x < 4; x++)
{
const uint32_t up_w = index_from_xy(x, 3); // upper block's bottom edge
upper_edge[x] = fetch_w(pUp_log_blk, p, up_w);
}
for (uint32_t y = 0; y < 4; y++)
{
const uint32_t rd_w = index_from_xy(0, y); // upper-right block's left edge
right_diag_left_edge[y] = fetch_w(pRight_diag_log_blk, p, rd_w);
}
for (uint32_t y = 0; y < 4; y++)
{
const int left_diag_right_val = orig_weight_preds[index_from_xy(3, y)]; // upper-left right edge
const int right_diag_left_val = right_diag_left_edge[y];
for (uint32_t x = 0; x < 4; x++)
{
// Same as #15: lateral predictor from upper-left/right diagonal edges.
const int diag_blend =
((3 - static_cast<int>(x)) * left_diag_right_val +
static_cast<int>(x) * right_diag_left_val + 1) / 3;
// Same as #6: direct upper edge replicated downward.
const int up_val = upper_edge[x];
// Trust upper edge more near y=0, trust diagonal lateral structure more lower in the block.
const int wu = 4 - static_cast<int>(y); // 4,3,2,1
const int wd = 1 + static_cast<int>(y); // 1,2,3,4
const int den = wu + wd; // always 5
weight_preds[index_from_xy(x, y)] =
(wu * up_val + wd * diag_blend + (den >> 1)) / den;
}
}
}
else if (cand_index == cCandPlaneFit)
{
// LS plane fit through left + upper edges.
// pCand_log_blk is pLeft_log_blk, so orig_weight_preds holds the left block.
int upper_edge[4];
for (uint32_t x = 0; x < 4; x++)
{
const uint32_t w = index_from_xy(x, 3); // upper block's bottom edge
upper_edge[x] = fetch_w(pUp_log_blk, p, w);
}
int left_edge[4];
for (uint32_t y = 0; y < 4; y++)
left_edge[y] = orig_weight_preds[index_from_xy(3, y)]; // left block's right edge
const int sum_u = upper_edge[0] + upper_edge[1] + upper_edge[2] + upper_edge[3];
const int sum_l = left_edge[0] + left_edge[1] + left_edge[2] + left_edge[3];
// LS slopes * 10
const int gx10 = -3 * upper_edge[0] - upper_edge[1] + upper_edge[2] + 3 * upper_edge[3];
const int gy10 = -3 * left_edge[0] - left_edge[1] + left_edge[2] + 3 * left_edge[3];
const int base = 5 * (sum_u + sum_l); // mean*40, anchored at edge centroid (.25,.25)
for (uint32_t y = 0; y < 4; y++)
{
for (uint32_t x = 0; x < 4; x++)
{
const int num = base + gx10 * (4 * (int)x - 1) + gy10 * (4 * (int)y - 1);
// round-half-up = floor((num + 20) / 40); numerator can be negative
// (negative slopes), and C++ '/' truncates toward zero, so force floor.
const int t = num + 20;
const int pred_unclamped = (t >= 0) ? (t / 40) : -((-t + 39) / 40);
weight_preds[index_from_xy(x, y)] = basisu::clamp<int>(pred_unclamped, 0, 64);
}
}
}
else if (cand_index == cCandDDL)
{
// Diagonal-down-left (H.264 4x4 intra mode 3 analog): propagate the
// extended top row (upper block's bottom edge + upper-RIGHT block's
// bottom edge) down-left at 45 degrees, with 1-2-1 smoothing along the
// diagonal. Captures 45-degree edges/stripes entering from the top-right
// quadrant -- a direction nothing else in the bank can represent.
// pCand_log_blk is pUp_log_blk; requires pRight_diag_log_blk.
int T[8];
for (uint32_t x = 0; x < 4; x++)
{
const uint32_t w = index_from_xy(x, 3);
T[x] = fetch_w(pUp_log_blk, p, w);
T[4 + x] = fetch_w(pRight_diag_log_blk, p, w);
}
for (uint32_t y = 0; y < 4; y++)
{
for (uint32_t x = 0; x < 4; x++)
{
const uint32_t d = x + y; // 0..6
int pred;
if (d == 6)
pred = (T[6] + 3 * T[7] + 2) >> 2;
else
pred = (T[d] + 2 * T[d + 1] + T[d + 2] + 2) >> 2;
weight_preds[index_from_xy(x, y)] = pred; // smoothed avg of [0,64] values: in range
}
}
}
else if (cand_index == cCandDDR)
{
// Diagonal-down-right (H.264 4x4 intra mode 4 analog): propagate at 45
// degrees from the top-left, sourcing the left column, the corner, and
// the top row, 1-2-1 smoothed. Captures 45-degree structure entering
// from the top-left quadrant.
// pCand_log_blk is pLeft_log_blk; requires pUp_log_blk and pLeft_diag_log_blk.
// A[0..8]: A[0..3] = left column bottom-to-top, A[4] = corner, A[5..8] = top row.
int A[9];
for (uint32_t y = 0; y < 4; y++)
A[3 - y] = orig_weight_preds[index_from_xy(3, y)]; // left block's right edge, reversed
{
const uint32_t cw = index_from_xy(3, 3);
A[4] = fetch_w(pLeft_diag_log_blk, p, cw);
}
for (uint32_t x = 0; x < 4; x++)
{
const uint32_t w = index_from_xy(x, 3);
A[5 + x] = fetch_w(pUp_log_blk, p, w);
}
for (uint32_t y = 0; y < 4; y++)
{
for (uint32_t x = 0; x < 4; x++)
{
const int d = 4 + (int)x - (int)y; // 1..7; texels on diagonal k share A-index k
const int pred = (A[d - 1] + 2 * A[d] + A[d + 1] + 2) >> 2;
weight_preds[index_from_xy(x, y)] = pred;
}
}
}
pWeight_predictions = weight_preds;
}
if ((amp_code) && (pWeight_predictions))
{
// Amplitude code: generalization of the old inversion flag.
// All transforms are about the prediction's own mean (per
// plane), so they negate/scale the ACs while leaving the DC
// for the DC coefficient -- the old 64-w flip also
// complemented the DC, forcing a DC correction symbol
// whenever the content mean wasn't complementary.
int sum = 0;
for (uint32_t i = 0; i < 16; i++)
sum += pWeight_predictions[i];
const int mean = (sum + 8) >> 4;
for (uint32_t i = 0; i < 16; i++)
{
const int w = pWeight_predictions[i];
int v;
if (amp_code == 1)
{
// flip about mean (s = -1): pure AC negation
v = basisu::clamp<int>(2 * mean - w, 0, 64);
//v = 64 - w;
}
else if (amp_code == 2)
{
// half contrast (s = +1/2): mean + (w - mean)/2, half-up
v = (w + mean + 1) >> 1; // closed in [0,64], no clamp needed
}
else // amp_code == 3
{
// half contrast of the flip (s = -1/2): compose 1 then 2
const int f = basisu::clamp<int>(2 * mean - w, 0, 64);
v = (f + mean + 1) >> 1;
}
pWeight_predictions[i] = v;
}
}
if (!pWeight_predictions)
return false;
memcpy(pOut_preds, pWeight_predictions, 16 * sizeof(int));
return true;
}
static inline int unpack_coeff_b(uint32_t v)
{
assert(v <= 255);
return (int)v;
}
// LSB-first bit reader matching basisu::bitwise_coder's packing exactly
// (first bit written lands in bit 0 of byte 0; values may cross byte
// boundaries). Total: reads past the end return false.
struct lsb_bit_reader
{
const uint8_t* m_p = nullptr;
uint64_t m_total_bits = 0;
uint64_t m_bit_ofs = 0;
void init(const uint8_t* p, size_t size_bytes) { m_p = p; m_total_bits = (uint64_t)size_bytes * 8; m_bit_ofs = 0; }
// seek to a bit range [start_bit, end_bit) within the blob -- used for
// per-stripe decoding (the stripe's bits occupy exactly this range)
void init_range(const uint8_t* p, uint64_t start_bit, uint64_t end_bit) { m_p = p; m_bit_ofs = start_bit; m_total_bits = end_bit; }
bool get_bits(uint32_t n, uint32_t& v)
{
assert(n <= 32);
v = 0;
if (n > (m_total_bits - m_bit_ofs))
return false;
if (!n)
return true;
const uint64_t byte_idx = m_bit_ofs >> 3;
const uint32_t bit_idx = (uint32_t)(m_bit_ofs & 7);
const uint32_t num_bytes = (bit_idx + n + 7) >> 3; // 1..5
uint64_t bits = 0;
for (uint32_t i = 0; i < num_bytes; i++)
bits |= ((uint64_t)m_p[byte_idx + i]) << (i * 8);
v = (uint32_t)((bits >> bit_idx) & ((1ull << n) - 1ull));
m_bit_ofs += n;
return true;
}
// at most 7 zero pad bits may remain
bool is_fully_consumed() const { return (m_total_bits - m_bit_ofs) < 8; }
};
// forward byte cursor over one blob; total
struct byte_cursor
{
const uint8_t* m_p = nullptr;
uint32_t m_size = 0, m_ofs = 0;
void init(const blob_stream_reader& rdr, uint32_t id) { m_p = rdr.get_ptr(id); m_size = rdr.get_size(id); m_ofs = 0; }
// seek to a byte range [start, end) within the blob -- per-stripe decode
void init_range(const blob_stream_reader& rdr, uint32_t id, uint32_t start, uint32_t end) { m_p = rdr.get_ptr(id); m_ofs = start; m_size = end; }
bool get(uint8_t& b) { if (m_ofs >= m_size) return false; b = m_p[m_ofs++]; return true; }
bool is_fully_consumed() const { return m_ofs == m_size; }
};
bool blob_stream_reader::init_internal(const void* pData, size_t data_size, uint64_t max_total_uncomp)
{
clear();
const uint8_t* pBytes = static_cast<const uint8_t*>(pData);
if ((!pBytes) || (data_size < 3)) // magic + count + magic minimum
return false;
uint64_t ofs = 0;
if (pBytes[ofs++] != BLOB_STREAM_MAGIC_BEGIN)
return false;
const uint32_t num_blobs = pBytes[ofs++];
// pass 1: walk + validate the directory, total the arena size
struct entry { uint32_t id, uncomp_size, stored_size; uint64_t data_ofs; };
entry entries[255];
uint64_t total_arena = 0;
for (uint32_t i = 0; i < num_blobs; i++)
{
if (ofs >= data_size)
return false;
entry& e = entries[i];
const uint8_t id_flag = pBytes[ofs++];
e.id = id_flag & 0x7Fu;
const bool compressed = (id_flag & 0x80u) != 0;
if (!read_varint(pBytes, data_size, ofs, e.uncomp_size))
return false;
e.stored_size = 0;
if (compressed)
{
if (!read_varint(pBytes, data_size, ofs, e.stored_size))
return false;
if (!e.stored_size)
return false;
}
e.data_ofs = ofs;
if (!e.uncomp_size)
return false; // empty blobs are never stored
if (m_sizes[e.id] || m_ptrs[e.id])
return false; // duplicate id
const uint64_t stored_bytes = e.stored_size ? e.stored_size : e.uncomp_size;
if ((ofs + stored_bytes) > data_size)
return false;
ofs += stored_bytes;
if (e.stored_size)
{
if (e.stored_size >= e.uncomp_size)
return false; // compressed must be strictly smaller (writer guarantees it)
total_arena += e.uncomp_size;
if (total_arena > max_total_uncomp)
return false;
}
// mark id as seen (real ptr/size set in pass 2)
m_sizes[e.id] = e.uncomp_size;
m_ptrs[e.id] = pBytes; // placeholder, nonzero for dup detection
}
// end marker must be the exact final byte: rejects truncation,
// trailing garbage, and directory/data length disagreements
if ((ofs != (data_size - 1)) || (pBytes[ofs] != BLOB_STREAM_MAGIC_END))
return false;
// pass 2: single arena allocation, decompress, wire up pointers
if (!m_arena.try_resize((size_t)total_arena))
return false;
uint64_t arena_ofs = 0;
for (uint32_t i = 0; i < num_blobs; i++)
{
const entry& e = entries[i];
if (!e.stored_size)
{
m_ptrs[e.id] = pBytes + e.data_ofs; // raw: zero copy into input
}
else
{
uint8_t* pDst = m_arena.data() + arena_ofs;
#if BASISD_SUPPORT_KTX2_ZSTD
const size_t res = ZSTD_decompress(pDst, e.uncomp_size, pBytes + e.data_ofs, e.stored_size);
if (ZSTD_isError(res) || (res != e.uncomp_size))
return false;
#else
BASISU_NOTE_UNUSED(pDst);
return false; // zstd disabled at compile time
#endif
m_ptrs[e.id] = pDst;
arena_ofs += e.uncomp_size;
}
}
return true;
}
bool image_unpacker::init(
const byte_span& comp,
decode_init_callback_ptr pInit_callback, void* pInit_callback_data,
decode_block_callback_ptr pBlock_callback, void* pBlock_callback_data)
{
m_initialized = false; // set true ONLY on full success below; every failure path leaves it false
m_tiny_mip = false;
m_block_cb = pBlock_callback;
m_block_data = pBlock_callback_data;
#if !BASISD_SUPPORT_KTX2_ZSTD
BASISU_DEVEL_ERROR("xbc7::unpack_image: XBC7 decoding requires zstd (BASISD_SUPPORT_KTX2_ZSTD is 0)");
return false;
#endif
// ---- format dispatch on the leading byte ----
// 0xB7 = blob container (blob_stream_reader validates this marker itself).
// 0xB8/0xB9 = tiny-mip ([marker][num_blocks_x:u8][num_blocks_y:u8] then 16
// bytes per packed BC7 block); 0xB8 = no alpha, 0xB9 = has alpha
// (the has_alpha bit rides in the marker). Anything else: not XBC7.
if (comp.size() < 1)
return false;
#ifndef NDEBUG
// Debug builds: report the input buffer size and the stream type.
{
const char* pType = "regular (blob)";
if (comp[0] == 0xB8)
pType = "tiny-mip (no alpha)";
else if (comp[0] == 0xB9)
pType = "tiny-mip (alpha)";
else if (comp[0] != BLOB_STREAM_MAGIC_BEGIN)
pType = "unknown / invalid marker";
fmt_debug_printf("XBC7 decode: input {} bytes, type: {}\n", (uint64_t)comp.size(), pType);
}
#endif
if ((comp[0] == 0xB8) || (comp[0] == 0xB9))
return init_tiny_mip(comp, comp[0] == 0xB9, pInit_callback, pInit_callback_data);
if (comp[0] != BLOB_STREAM_MAGIC_BEGIN)
return false; // unknown format marker
if (!m_rdr.init(comp.data(), comp.size()))
return false;
// ---- header ----
if (m_rdr.get_size(cBlobHeader) != sizeof(xbc7_header))
return false;
xbc7_header hdr;
memcpy((void *)&hdr, m_rdr.get_ptr(cBlobHeader), sizeof(hdr));
const uint32_t width = hdr.m_width_in_texels;
const uint32_t height = hdr.m_height_in_texels;
m_global_q = hdr.m_dct_q;
if ((!width) || (!height))
return false;
const uint32_t XBC7_MAX_SUPPORTED_DIM = 16384;
if ((width > XBC7_MAX_SUPPORTED_DIM) || (height > XBC7_MAX_SUPPORTED_DIM))
return false;
if ((m_global_q < 1) || (m_global_q > 100))
return false;
if (hdr.m_flags & ~XBC7_FLAG_HAS_ALPHA)
return false; // unknown flags
m_width = width;
m_height = height;
m_has_alpha = (hdr.m_flags & XBC7_FLAG_HAS_ALPHA) != 0;
m_num_blocks_x = (width + 3) / 4;
m_num_blocks_y = (height + 3) / 4;
const uint32_t total_blocks = m_num_blocks_x * m_num_blocks_y; // <= 16384^2, no overflow
// ---- stripe geometry (also governs the implicit solid-block clamp) ----
m_num_stripes = hdr.m_num_stripes;
if ((!m_num_stripes) || (m_num_stripes > m_num_blocks_y))
return false;
if (m_num_stripes > XBC7_MAX_ENCODER_STRIPES)
return false;
compute_stripe_ranges(m_num_blocks_y, m_num_stripes, m_stripes);
// ---- paranoia gate: structural size check BEFORE any allocation ----
// One command byte per block makes this a necessary condition and
// transitively bounds every allocation, killing the huge-dims DoS.
if (m_rdr.get_size(cBlobCommands) != total_blocks)
return false;
// ---- per-stripe seek table ----
// m_seek(id, s) = start offset of stripe s in blob id (a BYTE offset,
// or a BIT offset for the three bit blobs); row m_num_stripes holds the
// end sentinel (blob byte size, or total bits). Stripe 0 starts at 0.
// Single-stripe files carry no table -- the whole blob is stripe 0.
const uint32_t SEEK_NUM_STREAMS = (uint32_t)cBlobStripeSeekTable - 1; // ids 1..25
m_seek.resize((uint32_t)cBlobStripeSeekTable, m_num_stripes + 1); // (id) x (stripe)
m_seek.set_all(0);
for (uint32_t id = 1; id < (uint32_t)cBlobStripeSeekTable; id++)
{
const bool bit_blob = (id == cBlobCoeffSigns) || (id == cBlobPBits) || (id == cBlobEPRaw);
m_seek(id, m_num_stripes) = bit_blob ? ((uint64_t)m_rdr.get_size(id) * 8) : (uint64_t)m_rdr.get_size(id);
}
if (m_num_stripes > 1)
{
if (m_rdr.get_size(cBlobStripeSeekTable) != m_num_stripes * SEEK_NUM_STREAMS * 4)
return false;
// Entries are DELTAS from the previous stripe's start, stripe-major,
// stored byte-plane (SoA) transposed: byte b of entry e lives at
// pT[b * num_entries + e] (the encoder's inverse of this layout).
// Reconstruct absolute offsets with a running prefix sum, validating
// as we go (all BEFORE any block decode): stripe 0's delta must be 0,
// and each running offset must stay within [0, blob_end] (blob_end =
// byte size, or total bits for the three bit blobs). Monotonicity is
// automatic -- deltas are unsigned, so the running sum never
// decreases. The range test rejects a corrupted table immediately; a
// wild byte/bit offset can never reach the cursor seek in decode_stripe.
const uint8_t* pT = m_rdr.get_ptr(cBlobStripeSeekTable);
const uint32_t num_entries = m_num_stripes * SEEK_NUM_STREAMS;
for (uint32_t id = 1; id < (uint32_t)cBlobStripeSeekTable; id++)
{
const uint64_t blob_end = m_seek(id, m_num_stripes); // sentinel set above
uint64_t running = 0;
for (uint32_t st = 0; st < m_num_stripes; st++)
{
const uint32_t e = st * SEEK_NUM_STREAMS + (id - 1);
const uint64_t delta = (uint64_t)pT[e] | ((uint64_t)pT[num_entries + e] << 8) |
((uint64_t)pT[2u * num_entries + e] << 16) | ((uint64_t)pT[3u * num_entries + e] << 24);
if ((st == 0) && (delta != 0))
return false; // stripe 0 must start at offset 0
running += delta; // unsigned add: offsets are monotonic by construction
if (running > blob_end)
return false; // offset past end of blob
m_seek(id, st) = running;
}
}
}
// ---- decode state + callbacks ----
// The decoder always maintains its own full-image logical-block buffer:
// causal prediction (endpoint DPCM / repeat / weight predictors) reads
// already-decoded neighbors. Finished blocks are streamed to the caller
// via the block callback; the caller owns any persistent storage/packing.
m_log_blks.resize(m_num_blocks_x, m_num_blocks_y);
// Hand the caller the header geometry/metadata exactly once, before any
// block, so it can validate and allocate. A false return aborts.
if (pInit_callback)
{
if (!pInit_callback(m_num_blocks_x, m_num_blocks_y, m_width, m_height, m_global_q, m_has_alpha, pInit_callback_data))
return false;
}
m_initialized = true;
return true;
}
bool image_unpacker::init_tiny_mip(
const byte_span& comp, bool has_alpha,
decode_init_callback_ptr pInit_callback, void* pInit_callback_data)
{
if (comp.size() < 3)
return false;
const uint32_t nbx = comp[1];
const uint32_t nby = comp[2];
if ((!nbx) || (!nby))
return false;
// exact-size tripwire: the 3-byte header plus exactly the BC7 blocks
const uint64_t expected = 3ull + (uint64_t)nbx * (uint64_t)nby * 16ull;
if ((uint64_t)comp.size() != expected)
return false;
m_tiny_mip = true;
// checked region pointer to all the BC7 blocks (validated once); aliases
// the span's underlying buffer, which must outlive the decoder
m_tiny_blocks = comp.checked_ptr(3, (size_t)nbx * (size_t)nby * 16); // -> first block
m_num_blocks_x = nbx;
m_num_blocks_y = nby;
m_num_stripes = 1;
m_width = nbx * 4; // block-aligned; tiny-mip stores no exact texel dims
m_height = nby * 4;
m_global_q = 0; // no DCT in tiny-mip
m_has_alpha = has_alpha; // from the stream marker (0xB8 = no alpha, 0xB9 = alpha)
if (pInit_callback)
{
if (!pInit_callback(m_num_blocks_x, m_num_blocks_y, m_width, m_height, m_global_q, m_has_alpha, pInit_callback_data))
return false;
}
m_initialized = true;
return true;
}
bool image_unpacker::decode_stripe(uint32_t stripe_index)
{
// must follow a successful init() -- guard in ALL builds (assert traps
// the bug in debug; the return false keeps release total)
assert(m_initialized && "decode_stripe() called without a successful init()");
if (!m_initialized)
return false;
assert(stripe_index < m_num_stripes);
if (stripe_index >= m_num_stripes)
return false;
// tiny-mip is a single conceptual stripe (m_num_stripes == 1): decode all
// its blocks straight from the raw BC7 data, no prediction/blobs involved.
if (m_tiny_mip)
return decode_tiny_mip();
const uint32_t first_row = m_stripes[stripe_index].m_first_block_row;
const uint32_t end_row = first_row + m_stripes[stripe_index].m_num_block_rows;
// every reference is clamped to this stripe AABB (mirrors the encoder)
const tile_bounds stripe_tile = { 0, (int)first_row, (int)m_num_blocks_x - 1, (int)end_row - 1 };
// aliases so the per-block body below resolves to member state
blob_stream_reader& rdr = m_rdr;
const uint32_t num_blocks_x = m_num_blocks_x;
const uint32_t global_q = m_global_q;
const bool has_alpha = m_has_alpha;
vector2D<basist::bc7u::log_bc7_block>& log_blks = m_log_blks;
// cursors seeked to this stripe's range [seek(id,s), seek(id,s+1))
const uint32_t s = stripe_index;
byte_cursor commands; commands.init_range(rdr, cBlobCommands, (uint32_t)m_seek(cBlobCommands, s), (uint32_t)m_seek(cBlobCommands, s + 1));
byte_cursor configs; configs.init_range(rdr, cBlobBC7BlockConfig, (uint32_t)m_seek(cBlobBC7BlockConfig, s), (uint32_t)m_seek(cBlobBC7BlockConfig, s + 1));
byte_cursor partitions2; partitions2.init_range(rdr, cBlobPartition2, (uint32_t)m_seek(cBlobPartition2, s), (uint32_t)m_seek(cBlobPartition2, s + 1));
byte_cursor partitions3; partitions3.init_range(rdr, cBlobPartition3, (uint32_t)m_seek(cBlobPartition3, s), (uint32_t)m_seek(cBlobPartition3, s + 1));
byte_cursor predictors; predictors.init_range(rdr, cBlobWeightPredictors, (uint32_t)m_seek(cBlobWeightPredictors, s), (uint32_t)m_seek(cBlobWeightPredictors, s + 1));
byte_cursor dc_coeffs; dc_coeffs.init_range(rdr, cBlobDCCoeffsSmall, (uint32_t)m_seek(cBlobDCCoeffsSmall, s), (uint32_t)m_seek(cBlobDCCoeffsSmall, s + 1)); // encoder writes ALL DC here
byte_cursor ac_coeffs; ac_coeffs.init_range(rdr, cBlobACCoeffs, (uint32_t)m_seek(cBlobACCoeffs, s), (uint32_t)m_seek(cBlobACCoeffs, s + 1));
byte_cursor solid_deltas; solid_deltas.init_range(rdr, cBlobSolidRGBADeltas, (uint32_t)m_seek(cBlobSolidRGBADeltas, s), (uint32_t)m_seek(cBlobSolidRGBADeltas, s + 1));
byte_cursor ep_block_index; ep_block_index.init_range(rdr, cBlobEPBlockIndex, (uint32_t)m_seek(cBlobEPBlockIndex, s), (uint32_t)m_seek(cBlobEPBlockIndex, s + 1));
byte_cursor raw_weights; raw_weights.init_range(rdr, cBlobRawWeightBits, (uint32_t)m_seek(cBlobRawWeightBits, s), (uint32_t)m_seek(cBlobRawWeightBits, s + 1));
byte_cursor wt_resid[3]; // DPCM weight residuals by bit width: [0]=2, [1]=3, [2]=4
wt_resid[0].init_range(rdr, cBlobDPCMWeightResid2, (uint32_t)m_seek(cBlobDPCMWeightResid2, s), (uint32_t)m_seek(cBlobDPCMWeightResid2, s + 1));
wt_resid[1].init_range(rdr, cBlobDPCMWeightResid3, (uint32_t)m_seek(cBlobDPCMWeightResid3, s), (uint32_t)m_seek(cBlobDPCMWeightResid3, s + 1));
wt_resid[2].init_range(rdr, cBlobDPCMWeightResid4, (uint32_t)m_seek(cBlobDPCMWeightResid4, s), (uint32_t)m_seek(cBlobDPCMWeightResid4, s + 1));
byte_cursor ep_deltas[8]; // [0..3] = Fine R,G,B,A; [4..7] = Coarse R,G,B,A
for (uint32_t i = 0; i < 4; i++)
{
ep_deltas[i].init_range(rdr, cBlobEPDeltaFineR + i, (uint32_t)m_seek(cBlobEPDeltaFineR + i, s), (uint32_t)m_seek(cBlobEPDeltaFineR + i, s + 1));
ep_deltas[4 + i].init_range(rdr, cBlobEPDeltaCoarseR + i, (uint32_t)m_seek(cBlobEPDeltaCoarseR + i, s), (uint32_t)m_seek(cBlobEPDeltaCoarseR + i, s + 1));
}
lsb_bit_reader coeff_signs; coeff_signs.init_range(rdr.get_ptr(cBlobCoeffSigns), m_seek(cBlobCoeffSigns, s), m_seek(cBlobCoeffSigns, s + 1));
lsb_bit_reader pbits; pbits.init_range(rdr.get_ptr(cBlobPBits), m_seek(cBlobPBits, s), m_seek(cBlobPBits, s + 1));
lsb_bit_reader ep_raw; ep_raw.init_range(rdr.get_ptr(cBlobEPRaw), m_seek(cBlobEPRaw, s), m_seek(cBlobEPRaw, s + 1));
xbc7::xbc7_weight_grid_dct_fixed weight_grid_dct_fixed;
weight_grid_dct_fixed.init();
xbc7::fxvec dct_work_fixed;
for (uint32_t by = first_row; by < end_row; by++)
{
for (uint32_t bx = 0; bx < num_blocks_x; bx++)
{
const basist::bc7u::log_bc7_block* pLeft_log_blk = stripe_tile.contains((int)bx - 1, (int)by) ? &log_blks(bx - 1, by) : nullptr;
const basist::bc7u::log_bc7_block* pUp_log_blk = stripe_tile.contains((int)bx, (int)by - 1) ? &log_blks(bx, by - 1) : nullptr;
const basist::bc7u::log_bc7_block* pLeft_diag_log_blk = stripe_tile.contains((int)bx - 1, (int)by - 1) ? &log_blks(bx - 1, by - 1) : nullptr;
const basist::bc7u::log_bc7_block* pRight_diag_log_blk = stripe_tile.contains((int)bx + 1, (int)by - 1) ? &log_blks(bx + 1, by - 1) : nullptr;
basist::bc7u::log_bc7_block& log_blk = log_blks(bx, by);
uint8_t cmd_byte;
if (!commands.get(cmd_byte))
return false;
const uint32_t cmd = cmd_byte & 7;
const uint32_t ep_mode = (cmd_byte >> XBC7_COMMAND_ENDPOINT_MODE_SHIFT) & 7;
const uint32_t wt_mode = (cmd_byte >> XBC7_COMMAND_WEIGHT_MODE_SHIFT) & 1;
if (cmd_byte & 0x80)
return false; // P-frame flag: reserved in v0
// ---- simple commands: the whole byte is the command id ----
if (cmd <= (uint32_t)xbc7_command_id::cCmdSolidDPCM)
{
if (cmd_byte != cmd)
return false; // canonical: simple commands carry no EP/WT bits
if (cmd == (uint32_t)xbc7_command_id::cCmdRepeatLast)
{
if (!pLeft_log_blk)
return false;
log_blk = *pLeft_log_blk;
}
else if (cmd == (uint32_t)xbc7_command_id::cCmdRepeatUpper)
{
if (!pUp_log_blk)
return false;
log_blk = *pUp_log_blk;
}
else // cCmdSolidDPCM
{
// neighbor edge-average prediction in decoded-pixel space;
// mirrors the encoder exactly -- INCLUDING its stripe
// seam clamp, since this prediction is implicit (the
// encoder cannot read the stripe above, so neither may we)
int preds[4] = { 0, 0, 0, 0 };
int num_preds = 0;
if (pLeft_log_blk)
{
for (uint32_t y = 0; y < 4; y++)
{
basist::color_rgba px;
if (!basist::bc7u::unpack_bc7_texel(*pLeft_log_blk, px, 3, y))
return false;
preds[0] += px.r; preds[1] += px.g; preds[2] += px.b; preds[3] += px.a;
}
num_preds += 4;
}
if (pUp_log_blk) // pUp is already null above the stripe's first row
{
for (uint32_t x = 0; x < 4; x++)
{
basist::color_rgba px;
if (!basist::bc7u::unpack_bc7_texel(*pUp_log_blk, px, x, 3))
return false;
preds[0] += px.r; preds[1] += px.g; preds[2] += px.b; preds[3] += px.a;
}
num_preds += 4;
}
if (num_preds)
{
for (uint32_t c = 0; c < 4; c++)
preds[c] = (preds[c] + (num_preds / 2)) / num_preds;
}
basist::color_rgba solid_color;
for (uint32_t c = 0; c < (has_alpha ? 4u : 3u); c++)
{
uint8_t delta;
if (!solid_deltas.get(delta))
return false;
solid_color[c] = (uint8_t)(delta + preds[c]);
}
if (!has_alpha)
solid_color.a = 255;
basist::bc7u::create_solid_blk(log_blk, solid_color);
}
}
else
{
// ---- config ----
if (cmd == (uint32_t)xbc7_command_id::cCmdNewConfig)
{
uint8_t config_byte;
if (!configs.get(config_byte))
return false;
if (config_byte & 0xC0)
return false; // reserved bits
const uint32_t mode = config_byte & 7;
const uint32_t rot = (config_byte >> 3) & 3;
const uint32_t sel = (config_byte >> 5) & 1;
basist::bc7u::init_log_blk(log_blk, mode);
if (log_blk.m_num_planes == 2)
log_blk.m_dp_rotation_index = (uint8_t)rot;
else if (rot)
return false; // rotation on a non-dual-plane mode
if (mode == 4)
log_blk.m_mode4_index_selector = (uint8_t)sel;
else if (sel)
return false; // selector outside mode 4
}
else // reuse config from a neighbor
{
const basist::bc7u::log_bc7_block* pSrc = nullptr;
if (cmd == (uint32_t)xbc7_command_id::cCmdReuseConfigLeft)
pSrc = pLeft_log_blk;
else if (cmd == (uint32_t)xbc7_command_id::cCmdReuseConfigUpper)
pSrc = pUp_log_blk;
else if (cmd == (uint32_t)xbc7_command_id::cCmdReuseConfigLeftDiagonal)
pSrc = pLeft_diag_log_blk;
else // cCmdReuseConfigRightDiagonal
pSrc = pRight_diag_log_blk;
if (!pSrc)
return false;
basist::bc7u::init_log_blk(log_blk, pSrc->m_mode);
log_blk.m_dp_rotation_index = pSrc->m_dp_rotation_index;
log_blk.m_mode4_index_selector = pSrc->m_mode4_index_selector;
}
// ---- partition index (always sent for partitioned modes) ----
if (log_blk.m_num_partitions == 2)
{
uint8_t pat;
if (!partitions2.get(pat))
return false;
if (pat >= 64)
return false;
log_blk.m_pattern_index = pat;
}
else if (log_blk.m_num_partitions == 3)
{
uint8_t pat;
if (!partitions3.get(pat))
return false;
if (pat >= (1u << log_blk.m_pattern_bits))
return false; // mode 0: index < 16
log_blk.m_pattern_index = pat;
}
const basist::bc7u::endpoint_format& fmt = basist::bc7u::g_endpoint_formats[log_blk.m_mode];
const uint32_t num_comps = log_blk.get_num_comps();
// ---- endpoints ----
if (ep_mode == (uint32_t)xbc7_command_endpoint_mode::cCmdEndpointRaw)
{
for (uint32_t subset = 0; subset < log_blk.m_num_partitions; subset++)
{
for (uint32_t c = 0; c < num_comps; c++)
{
for (uint32_t e = 0; e < 2; e++)
{
uint32_t v;
if (!ep_raw.get_bits(log_blk.m_endpoint_bits[c == 3], v))
return false;
log_blk.m_endpoints[subset][e][c] = (uint8_t)v;
}
}
}
for (uint32_t pb = 0; pb < log_blk.m_num_pbits; pb++)
{
uint32_t v;
if (!ep_raw.get_bits(1, v))
return false;
if (pb < 6) // to shut up gcc bogus warning
log_blk.m_pbits[pb] = (uint8_t)v;
}
}
else
{
// resolve the endpoint predictor block (mirrors the encoder
// sweep); ep_mode is 3 bits, and 1..7 are all DPCM modes
const basist::bc7u::log_bc7_block* pEP_pred_blk = nullptr;
uint32_t ep_pred_subset = 0;
if (ep_mode == (uint32_t)xbc7_command_endpoint_mode::cCmdEndpointDPCMLeft)
pEP_pred_blk = pLeft_log_blk;
else if (ep_mode == (uint32_t)xbc7_command_endpoint_mode::cCmdEndpointDPCMUp)
pEP_pred_blk = pUp_log_blk;
else if (ep_mode == (uint32_t)xbc7_command_endpoint_mode::cCmdEndpointDPCMLeftDiagonal)
pEP_pred_blk = pLeft_diag_log_blk;
else if (ep_mode == (uint32_t)xbc7_command_endpoint_mode::cCmdEndpointDPCMRightDiagonal)
pEP_pred_blk = pRight_diag_log_blk;
else if (ep_mode == (uint32_t)xbc7_command_endpoint_mode::cCmdEndpointDPCMLeftSubset1)
{
pEP_pred_blk = pLeft_log_blk;
ep_pred_subset = 1;
}
else if (ep_mode == (uint32_t)xbc7_command_endpoint_mode::cCmdEndpointDPCMUpSubset1)
{
pEP_pred_blk = pUp_log_blk;
ep_pred_subset = 1;
}
else // cCmdEndpointDPCMBlockIndex
{
uint8_t delta_index;
if (!ep_block_index.get(delta_index))
return false;
if (delta_index >= NUM_XY_DELTAS)
return false; // top 3 bits reserved-zero
const xbc7_xy_delta& delta = g_xbc7_xy_deltas[delta_index];
const int nx = (int)bx + delta.m_dx;
const int ny = (int)by + delta.m_dy;
// must reference a block inside this stripe (the encoder
// never emits a cross-stripe reference); also keeps a
// worker from reading another stripe's rows
if (!stripe_tile.contains(nx, ny))
return false; // reference outside the stripe: malformed stream
pEP_pred_blk = &log_blks(nx, ny);
}
if (!pEP_pred_blk)
return false;
// subset-1 references require a partitioned predictor
if ((ep_pred_subset) && (pEP_pred_blk->m_num_partitions < 2))
return false;
const bool fine = (log_blk.m_endpoint_bits[0] >= 6);
for (uint32_t subset = 0; subset < log_blk.m_num_partitions; subset++)
{
uint8_t residuals[8];
uint32_t num_residuals = num_comps * 2;
if ((!has_alpha) && (log_blk.m_mode == 6))
{
assert(num_residuals == 8);
num_residuals = 6;
residuals[6] = 0;
residuals[7] = 0;
}
for (uint32_t i = 0; i < num_residuals; i += 2)
{
const uint32_t chan = i >> 1;
byte_cursor& strm = ep_deltas[(fine ? 0 : 4) + chan];
if (!strm.get(residuals[i + 0]))
return false;
if (!strm.get(residuals[i + 1]))
return false;
}
uint8_t residual_pbits[2] = { 0, 0 };
for (uint32_t pb = 0; pb < fmt.m_num_pbits; pb++)
{
uint32_t v;
if (!pbits.get_bits(1, v))
return false;
residual_pbits[pb] = (uint8_t)v;
}
uint32_t num_residuals_out, num_residual_pbits_out;
basist::bc7u::endpoint_dpcm(true,
*pEP_pred_blk, ep_pred_subset, // mirrors the encoder (subset 1 for EP modes 6/7)
log_blk, subset,
residuals, num_residuals_out, residual_pbits, num_residual_pbits_out);
if ((!has_alpha) && (log_blk.m_mode == 6))
{
log_blk.m_endpoints[0][0][3] = 127;
log_blk.m_endpoints[0][1][3] = 127;
}
}
}
// ---- weights: residual DCT (wt bit 1) or lossless residual DPCM (wt bit 0) ----
uint8_t pred_byte;
if (!predictors.get(pred_byte))
return false;
if (pred_byte >= cTotalCandidates * 4)
return false;
const uint32_t cand_index = pred_byte % cTotalCandidates;
const uint32_t amp_code = pred_byte / cTotalCandidates;
if ((amp_code) && (cand_index == cCandAbsolute))
return false;
int weight_preds[16];
int* pWeight_predictions = nullptr;
for (uint32_t p = 0; p < log_blk.m_num_planes; p++)
{
if (cand_index != cCandAbsolute)
{
// the decoder may reference ANY causal block (whole-image
// tile): the encoder simply never emits a reference
// that crosses one of its tile boundaries
if (!eval_weight_predictor(cand_index, amp_code, bx, by, num_blocks_x, stripe_tile, log_blks, p, weight_preds))
return false; // candidate unavailable here: malformed stream
pWeight_predictions = weight_preds;
}
if (wt_mode != (uint32_t)xbc7_command_weight_mode::cCmdWeightDCT)
{
// ---- lossless residual DPCM weights ----
// Predictions are quantized to the plane's bit depth and
// the wrapped n-bit index residuals (or, for the absolute
// predictor, the raw indices) are read back. Exact.
const uint32_t num_bits = log_blk.m_weight_bits[p];
const uint32_t mask = (1u << num_bits) - 1;
byte_cursor& strm = (cand_index == cCandAbsolute) ? raw_weights : wt_resid[num_bits - 2];
uint8_t syms[16];
if (num_bits == 2)
{
for (uint32_t i = 0; i < 16; i += 4)
{
uint8_t b;
if (!strm.get(b))
return false;
syms[i + 0] = b & 3;
syms[i + 1] = (b >> 2) & 3;
syms[i + 2] = (b >> 4) & 3;
syms[i + 3] = (uint8_t)(b >> 6);
}
}
else
{
for (uint32_t i = 0; i < 16; i += 2)
{
uint8_t b;
if (!strm.get(b))
return false;
const uint8_t lo = b & 0xF, hi = (uint8_t)(b >> 4);
if ((num_bits == 3) && ((lo > 7) || (hi > 7)))
return false; // nibble bit 3 reserved-zero
syms[i + 0] = lo;
syms[i + 1] = hi;
}
}
for (uint32_t i = 0; i < 16; i++)
{
const uint32_t pred_index = pWeight_predictions ?
basist::bc7u::quant_weight(pWeight_predictions[i], num_bits) : 0;
log_blk.m_weights[p][i] = (uint8_t)((syms[i] + pred_index) & mask);
}
continue; // next plane
}
// ---- DC ----
uint8_t dc_byte;
if (!dc_coeffs.get(dc_byte))
return false;
int dc = unpack_coeff_b(dc_byte);
if (pred_byte != cCandAbsolute) // joint index 0 == absolute: DC is unsigned
{
uint32_t sign;
if (!coeff_signs.get_bits(1, sign))
return false;
if (sign)
dc = -dc;
}
// ---- ACs ----
xbc7::dct_syms syms;
syms.clear();
syms.m_dc = (int16_t)dc;
uint32_t zig_idx = 1;
while (zig_idx < 16)
{
uint8_t b;
if (!ac_coeffs.get(b))
return false;
if (b == 0xFF)
{
// trailing zeros to the end of the scan
xbc7::coeff cf;
cf.m_num_zeros = (int16_t)(16 - zig_idx);
cf.m_coeff = INT16_MAX;
syms.m_ac_vals.push_back(cf);
break;
}
const uint32_t run = b;
if ((zig_idx + run) > 15)
return false; // a real coefficient must land at position <= 15
uint8_t mag_byte;
if (!ac_coeffs.get(mag_byte))
return false;
const int mag = unpack_coeff_b(mag_byte);
if (!mag)
return false; // zero coefficients are never coded
uint32_t sign;
if (!coeff_signs.get_bits(1, sign))
return false;
xbc7::coeff cf;
cf.m_num_zeros = (int16_t)run;
cf.m_coeff = (int16_t)(sign ? -mag : mag);
syms.m_ac_vals.push_back(cf);
zig_idx += run + 1;
}
bool status = weight_grid_dct_fixed.inverse(
basist::fixed16_16::from_int(global_q), p, pWeight_predictions, syms, log_blk, dct_work_fixed);
if (!status)
return false;
} // p
}
// ---- single emit point: every block path (repeat-last,
// repeat-upper, solid, config) falls through to here, so the
// caller's block callback is invoked EXACTLY ONCE per block,
// after the logical block is stored in m_log_blks. The caller may
// pack a physical BC7 block, store it, compare it vs a reference,
// transcode it, etc. A false return aborts the decode. ----
if (m_block_cb && !m_block_cb(bx, by, log_blk, m_block_data))
return false;
} // bx
} // by
// ---- per-stripe desync tripwires: each stream consumed exactly its
// range (cursors were bounded to [seek(s), seek(s+1))) ----
if (!commands.is_fully_consumed()) return false;
if (!configs.is_fully_consumed()) return false;
if (!partitions2.is_fully_consumed()) return false;
if (!partitions3.is_fully_consumed()) return false;
if (!predictors.is_fully_consumed()) return false;
if (!dc_coeffs.is_fully_consumed()) return false;
if (!ac_coeffs.is_fully_consumed()) return false;
if (!solid_deltas.is_fully_consumed()) return false;
if (!ep_block_index.is_fully_consumed()) return false;
if (!raw_weights.is_fully_consumed()) return false;
for (uint32_t i = 0; i < 3; i++)
if (!wt_resid[i].is_fully_consumed()) return false;
for (uint32_t i = 0; i < 8; i++)
if (!ep_deltas[i].is_fully_consumed()) return false;
if (!coeff_signs.is_fully_consumed()) return false;
if (!pbits.is_fully_consumed()) return false;
if (!ep_raw.is_fully_consumed()) return false;
return true;
} // decode_stripe
bool image_unpacker::decode_tiny_mip()
{
for (uint32_t by = 0; by < m_num_blocks_y; by++)
{
for (uint32_t bx = 0; bx < m_num_blocks_x; bx++)
{
const uint8_t* p = m_tiny_blocks + ((size_t)by * m_num_blocks_x + bx) * 16;
basist::bc7u::log_bc7_block log_blk;
if (!basist::bc7u::unpack_bc7(p, log_blk))
return false;
if (m_block_cb && !m_block_cb(bx, by, log_blk, m_block_data))
return false;
}
}
return true;
}
bool image_unpacker::decode_all()
{
// guard here too: with no successful init(), m_num_stripes is 0 and the
// loop would otherwise return true (false success) without decoding
assert(m_initialized && "decode_all() called without a successful init()");
if (!m_initialized)
return false;
for (uint32_t s = 0; s < m_num_stripes; s++)
if (!decode_stripe(s))
return false;
return true;
}
// Single-threaded one-shot: prep + serial decode.
bool unpack_image(
const byte_span& comp,
decode_init_callback_ptr pInit_callback, void* pInit_callback_data,
decode_block_callback_ptr pBlock_callback, void* pBlock_callback_data)
{
#if !BASISD_SUPPORT_KTX2_ZSTD
BASISU_NOTE_UNUSED(comp); BASISU_NOTE_UNUSED(pInit_callback); BASISU_NOTE_UNUSED(pInit_callback_data);
BASISU_NOTE_UNUSED(pBlock_callback); BASISU_NOTE_UNUSED(pBlock_callback_data);
BASISU_DEVEL_ERROR("xbc7::unpack_image: XBC7 decoding requires zstd (BASISD_SUPPORT_KTX2_ZSTD is 0)");
return false;
#else
image_unpacker dec;
if (!dec.init(comp, pInit_callback, pInit_callback_data, pBlock_callback, pBlock_callback_data))
return false;
return dec.decode_all();
#endif
}
// Threaded: caller owns `dec` (it must outlive the spawned jobs). init + spawn
// one job per stripe via the spawner; returns false only on init failure and
// does NOT wait. The caller waits on its own pool, then inspects results.
bool unpack_image_threaded(
image_unpacker& dec,
const byte_span& comp,
job_spawner& spawner,
decode_init_callback_ptr pInit_callback, void* pInit_callback_data,
decode_block_callback_ptr pBlock_callback, void* pBlock_callback_data)
{
#if !BASISD_SUPPORT_KTX2_ZSTD
BASISU_NOTE_UNUSED(dec); BASISU_NOTE_UNUSED(comp); BASISU_NOTE_UNUSED(spawner);
BASISU_NOTE_UNUSED(pInit_callback); BASISU_NOTE_UNUSED(pInit_callback_data);
BASISU_NOTE_UNUSED(pBlock_callback); BASISU_NOTE_UNUSED(pBlock_callback_data);
BASISU_DEVEL_ERROR("xbc7::unpack_image_threaded: XBC7 decoding requires zstd (BASISD_SUPPORT_KTX2_ZSTD is 0)");
return false;
#else
if (!dec.init(comp, pInit_callback, pInit_callback_data, pBlock_callback, pBlock_callback_data))
return false;
const uint32_t num_stripes = dec.get_num_stripes();
for (uint32_t stripe_index = 0; stripe_index < num_stripes; stripe_index++)
spawner.spawn_job(dec, stripe_index);
return true;
#endif
}
} // namespace xbc7
} // namespace basist