Files
basis_universal/encoder/basisu_bc15_spmd.cpp
Richard Geldreich 3cfdd2240b new files for v2.5
2026-07-01 13:20:12 -04:00

442 lines
22 KiB
C++

// basisu_bc15_spmd.cpp -- standalone opaque 4-color BC1 encoder (see basisu_bc15_spmd.h).
//
// This TU holds the public API, the solid-color omatch tables, and the scalar reference encoder. The SSE4.1
// cppspmd kernel lives in basisu_bc15_spmd_kernels.inl, compiled by basisu_bc15_spmd_sse.cpp; we only declare +
// call its wrapper here (gated on g_cpu_supports_sse41), with the scalar path as the fallback.
#include "basisu_bc15_spmd.h"
#include "basisu_enc.h"
#include <math.h>
namespace basisu
{
extern bool g_cpu_supports_sse41; // set by detect_sse41() in basisu_encoder_init()
namespace bc_spmd
{
// Baked-in benchmarked defaults (not exposed as parameters by design).
static const int kLsRounds = 2; // least-squares alternation rounds
static const int kAvgVar = 16; // try the avg-solid 2nd seed when (rng_r+rng_g+rng_b) < kAvgVar
#if BASISU_SUPPORT_SSE
// Defined in basisu_bc15_spmd_kernels.inl (the SSE4.1 TU). Encodes up to 4 blocks at once, writing num_write
// of them directly at pOut + j*out_stride (no temp buffer).
void encode_bc1_blocks_4_sse41(const color_rgba* pBlocks, uint8_t* pOut,
int* om5lo, int* om5hi, int* om6lo, int* om6hi, int ls_rounds, int avgvar,
uint32_t out_stride, uint32_t num_write);
// BC4: encodes up to 4 single-channel blocks at once, writing num_write of them at pOut + j*out_stride.
// do_ls!=0 adds the 1-D least-squares refit. src_stride = byte stride between texel values in pBlocks (1 or 4).
void encode_bc4_blocks_4_sse41(const uint8_t* pBlocks, uint8_t* pOut, uint32_t out_stride, uint32_t num_write, uint32_t do_ls, uint32_t src_stride);
#endif
// ---- solid-color omatch tables (stb_dxt/basisu 3% span penalty), as int[256] for the kernel's gather ----
static int s_om5_lo[256], s_om5_hi[256], s_om6_lo[256], s_om6_hi[256];
static void build_omatch_tables()
{
for (int bits = 5; bits <= 6; bits++)
{
const int levels = 1 << bits;
int* lo = (bits == 5) ? s_om5_lo : s_om6_lo;
int* hi = (bits == 5) ? s_om5_hi : s_om6_hi;
for (int target = 0; target < 256; target++)
{
int best = 0x7FFFFFFF, bc0 = 0, bc1 = 0;
for (int c0 = 0; c0 < levels; c0++)
{
const int e0 = (bits == 5) ? ((c0 << 3) | (c0 >> 2)) : ((c0 << 2) | (c0 >> 4));
for (int c1 = 0; c1 < levels; c1++) // FULL cross product (matches encode_bc1's prepare_bc1_single_color_table)
{
const int e1 = (bits == 5) ? ((c1 << 3) | (c1 >> 2)) : ((c1 << 2) | (c1 >> 4));
const int interp = (2 * e0 + e1) / 3; // selector-2 (2/3 toward c0), truncated
int err = (interp > target) ? (interp - target) : (target - interp);
err += ((e0 > e1 ? e0 - e1 : e1 - e0) * 3) / 100; // 3% linear span penalty (abs: e0 may be < e1 now)
if (err < best) { best = err; bc0 = c0; bc1 = c1; }
}
}
lo[target] = bc0; hi[target] = bc1;
}
}
}
// Thread-safe one-time table init: C++11 guarantees a function-local static's initializer runs exactly once,
// race-free, even under concurrent first calls (no double-checked-locking bug). init() just forces it.
static inline void ensure_init() { static const bool s_built = (build_omatch_tables(), true); (void)s_built; }
void init() { ensure_init(); }
// ---- scalar reference encoder (the oracle) -- identical algorithm/op-order to the SPMD kernel ----
static inline void unpack565(uint32_t c, int& r, int& g, int& b)
{
r = (c >> 11) & 31; g = (c >> 5) & 63; b = c & 31;
r = (r << 3) | (r >> 2); g = (g << 2) | (g >> 4); b = (b << 3) | (b >> 2);
}
static inline uint32_t f_to_565(float r, float g, float b)
{
const float cr = (r < 0.0f) ? 0.0f : (r > 255.0f ? 255.0f : r);
const float cg = (g < 0.0f) ? 0.0f : (g > 255.0f ? 255.0f : g);
const float cb = (b < 0.0f) ? 0.0f : (b > 255.0f ? 255.0f : b);
return ((int)rintf(cr * (31.0f / 255.0f)) << 11) | ((int)rintf(cg * (63.0f / 255.0f)) << 5) | (int)rintf(cb * (31.0f / 255.0f));
}
static inline int thresh_sel(const int* pr, const int* pg, const int* pb, int r, int g, int b)
{
const int ar = pr[1] - pr[0], ag = pg[1] - pg[0], ab = pb[1] - pb[0];
const int dp0 = pr[0] * ar + pg[0] * ag + pb[0] * ab;
const int dp2 = pr[2] * ar + pg[2] * ag + pb[2] * ab;
const int dp3 = pr[3] * ar + pg[3] * ag + pb[3] * ab;
const int dp1 = pr[1] * ar + pg[1] * ag + pb[1] * ab;
const int t0 = dp0 + dp2, t1 = dp2 + dp3, t2 = dp3 + dp1;
const int d = r * (ar + ar) + g * (ag + ag) + b * (ab + ab);
return (d <= t0) ? 0 : (d < t1) ? 2 : (d < t2) ? 3 : 1;
}
static void ls_round(const color_rgba* blk, uint32_t& c0, uint32_t& c1)
{
int r0, g0, b0, r1, g1, b1;
unpack565(c0, r0, g0, b0); unpack565(c1, r1, g1, b1);
const int pr[4] = { r0, r1, (2 * r0 + r1) / 3, (r0 + 2 * r1) / 3 };
const int pg[4] = { g0, g1, (2 * g0 + g1) / 3, (g0 + 2 * g1) / 3 };
const int pb[4] = { b0, b1, (2 * b0 + b1) / 3, (b0 + 2 * b1) / 3 };
float m00 = 0, m01 = 0, m11 = 0, a0r = 0, a0g = 0, a0b = 0, a1r = 0, a1g = 0, a1b = 0;
for (uint32_t i = 0; i < 16; i++)
{
const int sel = thresh_sel(pr, pg, pb, blk[i].r, blk[i].g, blk[i].b);
const float tb = (sel == 0) ? 0.0f : (sel == 1) ? 1.0f : (sel == 2) ? (1.0f / 3.0f) : (2.0f / 3.0f);
const float ta = 1.0f - tb;
m00 += ta * ta; m01 += ta * tb; m11 += tb * tb;
const float cr = (float)blk[i].r, cg = (float)blk[i].g, cb = (float)blk[i].b;
a0r += ta * cr; a0g += ta * cg; a0b += ta * cb;
a1r += tb * cr; a1g += tb * cg; a1b += tb * cb;
}
const float det = m00 * m11 - m01 * m01;
const float inv = 1.0f / basisu::maximum(det, 1e-6f);
const float e0r = (a0r * m11 - a1r * m01) * inv, e0g = (a0g * m11 - a1g * m01) * inv, e0b = (a0b * m11 - a1b * m01) * inv;
const float e1r = (a1r * m00 - a0r * m01) * inv, e1g = (a1g * m00 - a0g * m01) * inv, e1b = (a1b * m00 - a0b * m01) * inv;
uint32_t nc0 = f_to_565(e0r, e0g, e0b), nc1 = f_to_565(e1r, e1g, e1b);
{ const uint32_t hi = basisu::maximum(nc0, nc1), lo = basisu::minimum(nc0, nc1); nc0 = hi; nc1 = lo; }
if (det > 1e-6f) { c0 = nc0; c1 = nc1; }
}
static int eval_error(const color_rgba* blk, uint32_t c0, uint32_t c1)
{
int r0, g0, b0, r1, g1, b1;
unpack565(c0, r0, g0, b0); unpack565(c1, r1, g1, b1);
const int pr[4] = { r0, r1, (2 * r0 + r1) / 3, (r0 + 2 * r1) / 3 };
const int pg[4] = { g0, g1, (2 * g0 + g1) / 3, (g0 + 2 * g1) / 3 };
const int pb[4] = { b0, b1, (2 * b0 + b1) / 3, (b0 + 2 * b1) / 3 };
int err = 0;
for (uint32_t i = 0; i < 16; i++)
{
const int sel = thresh_sel(pr, pg, pb, blk[i].r, blk[i].g, blk[i].b);
const int dr = blk[i].r - pr[sel], dg = blk[i].g - pg[sel], db = blk[i].b - pb[sel];
err += dr * dr + dg * dg + db * db;
}
return err;
}
static void encode_block_scalar(const color_rgba* blk, uint8_t out[8])
{
int mnr = 255, mng = 255, mnb = 255, mxr = 0, mxg = 0, mxb = 0, sum_r = 0, sum_g = 0, sum_b = 0;
int sum_rr = 0, sum_gg = 0, sum_bb = 0, sum_rg = 0, sum_rb = 0, sum_gb = 0;
for (uint32_t i = 0; i < 16; i++)
{
const int r = blk[i].r, g = blk[i].g, b = blk[i].b;
mnr = basisu::minimum<int>(mnr, r); mng = basisu::minimum<int>(mng, g); mnb = basisu::minimum<int>(mnb, b);
mxr = basisu::maximum<int>(mxr, r); mxg = basisu::maximum<int>(mxg, g); mxb = basisu::maximum<int>(mxb, b);
sum_r += r; sum_g += g; sum_b += b;
sum_rr += r * r; sum_gg += g * g; sum_bb += b * b;
sum_rg += r * g; sum_rb += r * b; sum_gb += g * b;
}
// Solid block: clone of basist::encode_bc1_solid_block -- omatch endpoints from the (full-cross-product)
// single-color tables, then the same swap + degenerate handling that GUARANTEES a 4-color block
// (color0 > color1), never 3-color (required so this is safe for BC3/BC5 color blocks).
if ((mnr == mxr) && (mng == mxg) && (mnb == mxb))
{
const int ar = (sum_r + 8) >> 4, ag = (sum_g + 8) >> 4, ab = (sum_b + 8) >> 4;
uint32_t max16 = (s_om5_lo[ar] << 11) | (s_om6_lo[ag] << 5) | s_om5_lo[ab]; // lo[] = 2x-weighted endpoint (encode_bc1 m_hi)
uint32_t min16 = (s_om5_hi[ar] << 11) | (s_om6_hi[ag] << 5) | s_om5_hi[ab]; // hi[] = 1x endpoint (encode_bc1 m_lo)
uint32_t mask = 0xAA;
if (min16 == max16)
{
mask = 0; // selector 0 = color0 directly; force max16 > min16 so the block stays 4-color
if (min16 > 0) min16--;
else { max16 = 1; min16 = 0; mask = 0x55; } // l == h == 0
}
if (max16 < min16) { const uint32_t t = max16; max16 = min16; min16 = t; mask ^= 0x55; } // selector 2<->3 on swap
out[0] = (uint8_t)max16; out[1] = (uint8_t)(max16 >> 8); out[2] = (uint8_t)min16; out[3] = (uint8_t)(min16 >> 8);
out[4] = out[5] = out[6] = out[7] = (uint8_t)mask;
return;
}
// PCA seed (METHOD 2, on-axis, sqrt-free).
const float cxx = (float)(16 * sum_rr - sum_r * sum_r), cxy = (float)(16 * sum_rg - sum_r * sum_g), cxz = (float)(16 * sum_rb - sum_r * sum_b);
const float cyy = (float)(16 * sum_gg - sum_g * sum_g), cyz = (float)(16 * sum_gb - sum_g * sum_b), czz = (float)(16 * sum_bb - sum_b * sum_b);
float ax = (float)(mxr - mnr), ay = (float)(mxg - mng), az = (float)(mxb - mnb);
for (int it = 0; it < 4; it++)
{
const float nr = ax * cxx + ay * cxy + az * cxz;
const float ng = ax * cxy + ay * cyy + az * cyz;
const float nb = ax * cxz + ay * cyz + az * czz;
ax = nr; ay = ng; az = nb;
}
const float kk = basisu::maximum(basisu::maximum(fabsf(ax), fabsf(ay)), fabsf(az));
const float mm = 1024.0f / basisu::maximum(kk, 1e-3f);
ax *= mm; ay *= mm; az *= mm;
const float inv_len2 = 1.0f / (ax * ax + ay * ay + az * az + 0.0000125f);
const float meanx = (float)sum_r * (1.0f / 16.0f), meany = (float)sum_g * (1.0f / 16.0f), meanz = (float)sum_b * (1.0f / 16.0f);
float minp = 1e30f, maxp = -1e30f;
for (uint32_t i = 0; i < 16; i++)
{
const float prj = ((float)blk[i].r - meanx) * ax + ((float)blk[i].g - meany) * ay + ((float)blk[i].b - meanz) * az;
minp = basisu::minimum(minp, prj); maxp = basisu::maximum(maxp, prj);
}
const float tlo = minp * inv_len2, thi = maxp * inv_len2;
uint32_t color0 = f_to_565(meanx + tlo * ax, meany + tlo * ay, meanz + tlo * az);
uint32_t color1 = f_to_565(meanx + thi * ax, meany + thi * ay, meanz + thi * az);
{ const uint32_t hi = basisu::maximum(color0, color1), lo = basisu::minimum(color0, color1); color0 = hi; color1 = lo; }
if (color0 == color1) { if (color1 != 0) color1--; else color0++; }
for (int r = 0; r < kLsRounds; r++) ls_round(blk, color0, color1);
if (color0 == color1) { if (color1 != 0) color1--; else color0++; }
// Low-variance avg-solid 2nd seed (keep-better).
const int var_proxy = (mxr - mnr) + (mxg - mng) + (mxb - mnb);
if (var_proxy < kAvgVar)
{
const int ar = (sum_r + 8) >> 4, ag = (sum_g + 8) >> 4, ab = (sum_b + 8) >> 4;
uint32_t c0b = (s_om5_lo[ar] << 11) | (s_om6_lo[ag] << 5) | s_om5_lo[ab];
uint32_t c1b = (s_om5_hi[ar] << 11) | (s_om6_hi[ag] << 5) | s_om5_hi[ab];
{ const uint32_t hi = basisu::maximum(c0b, c1b), lo = basisu::minimum(c0b, c1b); c0b = hi; c1b = lo; }
if (c0b == c1b) { if (c1b != 0) c1b--; else c0b++; }
ls_round(blk, c0b, c1b);
if (c0b == c1b) { if (c1b != 0) c1b--; else c0b++; }
if (eval_error(blk, c0b, c1b) < eval_error(blk, color0, color1)) { color0 = c0b; color1 = c1b; }
}
int r0, g0, b0, r1, g1, b1;
unpack565(color0, r0, g0, b0); unpack565(color1, r1, g1, b1);
const int pr[4] = { r0, r1, (2 * r0 + r1) / 3, (r0 + 2 * r1) / 3 };
const int pg[4] = { g0, g1, (2 * g0 + g1) / 3, (g0 + 2 * g1) / 3 };
const int pb[4] = { b0, b1, (2 * b0 + b1) / 3, (b0 + 2 * b1) / 3 };
uint32_t sw = 0;
for (uint32_t i = 0; i < 16; i++)
sw |= ((uint32_t)thresh_sel(pr, pg, pb, blk[i].r, blk[i].g, blk[i].b)) << (i * 2);
out[0] = (uint8_t)color0; out[1] = (uint8_t)(color0 >> 8);
out[2] = (uint8_t)color1; out[3] = (uint8_t)(color1 >> 8);
out[4] = (uint8_t)sw; out[5] = (uint8_t)(sw >> 8); out[6] = (uint8_t)(sw >> 16); out[7] = (uint8_t)(sw >> 24);
}
// ---- public API ----
void encode_bc1_scalar(void* pBlocks, const color_rgba* pSrc_pixels, uint32_t num_blocks, uint32_t block_stride)
{
ensure_init();
uint8_t* pOut = (uint8_t*)pBlocks;
const uint32_t out_stride = 8 * block_stride;
for (uint32_t b = 0; b < num_blocks; b++)
encode_block_scalar(pSrc_pixels + b * 16, pOut + b * out_stride);
}
void encode_bc1_spmd(void* pBlocks, const color_rgba* pSrc_pixels, uint32_t num_blocks, uint32_t block_stride)
{
ensure_init();
uint8_t* pOut = (uint8_t*)pBlocks;
(void)pOut;
const uint32_t out_stride = 8 * block_stride;
(void)out_stride;
#if BASISU_SUPPORT_SSE
if (g_cpu_supports_sse41)
{
// Full groups of 4: the kernel writes all 4 blocks directly at the (possibly strided) output.
uint32_t base = 0;
for (; base + 4 <= num_blocks; base += 4)
encode_bc1_blocks_4_sse41(pSrc_pixels + base * 16, pOut + base * out_stride, s_om5_lo, s_om5_hi, s_om6_lo, s_om6_hi, kLsRounds, kAvgVar, out_stride, 4);
// Final partial group: pad the input to 4, but write only the n valid blocks (still direct, no temp).
const uint32_t n = num_blocks - base; // 0..3
if (n)
{
color_rgba grp[64];
for (uint32_t gi = 0; gi < 4; gi++)
memcpy(grp + gi * 16, pSrc_pixels + (base + ((gi < n) ? gi : (n - 1))) * 16, 16 * sizeof(color_rgba));
encode_bc1_blocks_4_sse41(grp, pOut + base * out_stride, s_om5_lo, s_om5_hi, s_om6_lo, s_om6_hi, kLsRounds, kAvgVar, out_stride, n);
}
return;
}
#endif
encode_bc1_scalar(pBlocks, pSrc_pixels, num_blocks, block_stride); // scalar fallback (no SSE4.1)
}
// ================================ BC4 (RGTC1, single channel) ================================
// Scalar reference (oracle) for one BC4 block -- clone of basist::encode_bc4 (raw bbox min/max, exact
// nearest-of-8 integer-threshold selector), but the solid case is forced to 8-value mode (red0 > red1) so
// every block is 8-color. v = 16 contiguous channel bytes (texel i = y*4+x). out = 8 bytes.
// Compute the nearest-of-8 selectors + the resulting decoded SSE for an 8-value endpoint pair (r0 > r1).
// Optionally accumulates the 1-D least-squares moments (rank = interpolation weight, 0..7).
static int bc4_eval(const uint8_t* v, int r0, int r1, uint64_t& sel_out, int* pSw, int* pSww, int* pSv, int* pSwv, uint32_t src_stride)
{
const int delta = r0 - r1;
const int t0 = delta * 13, t1 = delta * 11, t2 = delta * 9, t3 = delta * 7, t4 = delta * 5, t5 = delta * 3, t6 = delta;
const int bias = 4 - r1 * 14;
uint64_t sel = 0; int sse = 0, Sw = 0, Sww = 0, Sv = 0, Swv = 0;
for (int i = 0; i < 16; i++)
{
const int val = v[i * src_stride];
const int sv = val * 14 + bias;
const int rank = (sv >= t0) + (sv >= t1) + (sv >= t2) + (sv >= t3) + (sv >= t4) + (sv >= t5) + (sv >= t6);
const int idx = (rank == 7) ? 0 : (rank == 0) ? 1 : (8 - rank);
sel |= (uint64_t)idx << (i * 3);
const int recon = (rank * r0 + (7 - rank) * r1) / 7; // == decoded value for this selector (matches unpack_bc4)
const int d = val - recon; sse += d * d;
Sw += rank; Sww += rank * rank; Sv += val; Swv += rank * val;
}
sel_out = sel;
if (pSw) { *pSw = Sw; *pSww = Sww; *pSv = Sv; *pSwv = Swv; }
return sse;
}
static void encode_bc4_block(const uint8_t* v, uint8_t out[8], bool do_ls, uint32_t src_stride)
{
int mn = v[0], mx = v[0];
for (int i = 1; i < 16; i++) { const int x = v[i * src_stride]; mn = basisu::minimum(mn, x); mx = basisu::maximum(mx, x); }
// Solid -> force an 8-value block (red0 > red1) that still reconstructs the exact value.
if (mn == mx)
{
int r0, r1, idx;
if (mx > 0) { r0 = mx; r1 = mx - 1; idx = 0; } // all selectors index 0 = red0 = value
else { r0 = 1; r1 = 0; idx = 1; } // value 0: all selectors index 1 = red1 = 0
out[0] = (uint8_t)r0; out[1] = (uint8_t)r1;
uint64_t sel = 0; for (int i = 0; i < 16; i++) sel |= (uint64_t)idx << (i * 3);
for (int i = 0; i < 6; i++) out[2 + i] = (uint8_t)(sel >> (i * 8));
return;
}
// Candidate A: raw bbox endpoints (== basist::encode_bc4) + (for HQ) its LS moments.
int Sw, Sww, Sv, Swv; uint64_t sel;
int r0 = mx, r1 = mn;
int sse = bc4_eval(v, r0, r1, sel, &Sw, &Sww, &Sv, &Swv, src_stride);
// Candidate B (HQ only): one 1-D least-squares endpoint refit (value ~= a + (rank/7)*b), adopted only if
// it lowers SSE -- monotonic, can never regress below bbox/encode_bc4. Always 8-value (require nr0 > nr1).
if (do_ls)
{
const int detI = 16 * Sww - Sw * Sw;
if (detI != 0)
{
const float b = 7.0f * (float)(16 * Swv - Sw * Sv) / (float)detI; // slope = red0 - red1
const float bsw7 = b * (float)Sw * (1.0f / 7.0f);
const float a = ((float)Sv - bsw7) / 16.0f; // intercept = red1
const int nr1 = basisu::clamp<int>((int)rintf(a), 0, 255);
const int nr0 = basisu::clamp<int>((int)rintf(a + b), 0, 255);
if (nr0 > nr1)
{
uint64_t selB;
const int sseB = bc4_eval(v, nr0, nr1, selB, nullptr, nullptr, nullptr, nullptr, src_stride);
if (sseB < sse) { r0 = nr0; r1 = nr1; sel = selB; sse = sseB; }
}
}
}
out[0] = (uint8_t)r0; out[1] = (uint8_t)r1;
for (int i = 0; i < 6; i++) out[2 + i] = (uint8_t)(sel >> (i * 8));
}
void encode_bc4_scalar(void* pBlocks, const uint8_t* pSrc_pixels, uint32_t num_blocks, uint32_t block_stride, bool high_quality, uint32_t src_stride)
{
uint8_t* pOut = (uint8_t*)pBlocks;
const uint32_t out_stride = 8 * block_stride;
const uint32_t in_block = 16 * src_stride; // bytes between consecutive blocks in the source
for (uint32_t b = 0; b < num_blocks; b++)
encode_bc4_block(pSrc_pixels + (size_t)b * in_block, pOut + b * out_stride, high_quality, src_stride);
}
void encode_bc4_spmd(void* pBlocks, const uint8_t* pSrc_pixels, uint32_t num_blocks, uint32_t block_stride, bool high_quality, uint32_t src_stride)
{
uint8_t* pOut = (uint8_t*)pBlocks;
(void)pOut;
const uint32_t out_stride = 8 * block_stride;
(void)out_stride;
const uint32_t in_block = 16 * src_stride;
(void)in_block;
#if BASISU_SUPPORT_SSE
if (g_cpu_supports_sse41)
{
const uint32_t do_ls = high_quality ? 1 : 0;
// Full groups read straight from the (possibly strided) source -- no copy.
uint32_t base = 0;
for (; base + 4 <= num_blocks; base += 4)
encode_bc4_blocks_4_sse41(pSrc_pixels + (size_t)base * in_block, pOut + base * out_stride, out_stride, 4, do_ls, src_stride);
// Tail (<4): gather the channel into a tightly-packed 64-byte stack buffer (pad with last block), then
// encode with src_stride=1. Only the tail copies; the bulk above is zero-copy.
const uint32_t n = num_blocks - base; // 0..3
if (n)
{
uint8_t grp[64];
for (uint32_t gi = 0; gi < 4; gi++)
{
const uint8_t* sp = pSrc_pixels + (size_t)(base + ((gi < n) ? gi : (n - 1))) * in_block;
for (uint32_t t = 0; t < 16; t++) grp[gi * 16 + t] = sp[t * src_stride];
}
encode_bc4_blocks_4_sse41(grp, pOut + base * out_stride, out_stride, n, do_ls, 1);
}
return;
}
#endif
encode_bc4_scalar(pBlocks, pSrc_pixels, num_blocks, block_stride, high_quality, src_stride); // scalar fallback
}
// ================================ High-level RGBA format helpers ================================
// NO allocations, NO channel-extraction copies: the BC4 path reads the requested channel straight out of the
// RGBA pixels via src_stride=4 (the channel pointer is &pPixels->r/g/b/a). encode_bc4_* handles the <4 tail
// without overrunning the (possibly strided) output.
void encode_bc1(void* pBlocks, const color_rgba* pPixels, uint32_t num_blocks, bool use_spmd)
{
if (use_spmd) encode_bc1_spmd(pBlocks, pPixels, num_blocks, 1);
else encode_bc1_scalar(pBlocks, pPixels, num_blocks, 1);
}
void encode_bc4(void* pBlocks, const color_rgba* pPixels, uint32_t num_blocks, bool use_spmd, bool high_quality)
{
const uint8_t* pR = &pPixels[0].r; // R channel, stride 4, contiguous output
if (use_spmd) encode_bc4_spmd(pBlocks, pR, num_blocks, 1, high_quality, 4);
else encode_bc4_scalar(pBlocks, pR, num_blocks, 1, high_quality, 4);
}
void encode_bc5(void* pBlocks, const color_rgba* pPixels, uint32_t num_blocks, bool use_spmd, bool high_quality)
{
uint8_t* pOut = (uint8_t*)pBlocks; // [0..7] = BC4 of R, [8..15] = BC4 of G (each block_stride 2)
const uint8_t* pR = &pPixels[0].r, * pG = &pPixels[0].g;
if (use_spmd) { encode_bc4_spmd(pOut, pR, num_blocks, 2, high_quality, 4); encode_bc4_spmd(pOut + 8, pG, num_blocks, 2, high_quality, 4); }
else { encode_bc4_scalar(pOut, pR, num_blocks, 2, high_quality, 4); encode_bc4_scalar(pOut + 8, pG, num_blocks, 2, high_quality, 4); }
}
void encode_bc3(void* pBlocks, const color_rgba* pPixels, uint32_t num_blocks, bool use_spmd, bool high_quality)
{
uint8_t* pOut = (uint8_t*)pBlocks; // [0..7] = BC4 alpha block, [8..15] = BC1 color block
const uint8_t* pA = &pPixels[0].a; // A channel, stride 4
if (use_spmd) { encode_bc4_spmd(pOut, pA, num_blocks, 2, high_quality, 4); encode_bc1_spmd(pOut + 8, pPixels, num_blocks, 2); }
else { encode_bc4_scalar(pOut, pA, num_blocks, 2, high_quality, 4); encode_bc1_scalar(pOut + 8, pPixels, num_blocks, 2); }
}
void encode_bc2(void* pBlocks, const color_rgba* pPixels, uint32_t num_blocks, bool use_spmd)
{
uint8_t* pOut = (uint8_t*)pBlocks; // [0..7] = explicit 4-bit alpha (scalar), [8..15] = BC1 color block
for (uint32_t b = 0; b < num_blocks; b++)
{
uint8_t* o = pOut + b * 16;
const color_rgba* p = pPixels + b * 16;
for (uint32_t y = 0; y < 4; y++)
{
uint32_t row = 0; // one little-endian 16-bit word per row; texel x in nibble x
for (uint32_t x = 0; x < 4; x++) { const int a8 = p[y * 4 + x].a; const int a4 = (a8 * 2 + 17) / 34; row |= (uint32_t)a4 << (x * 4); } // round(a8/17)
o[y * 2] = (uint8_t)row; o[y * 2 + 1] = (uint8_t)(row >> 8);
}
}
if (use_spmd) encode_bc1_spmd(pOut + 8, pPixels, num_blocks, 2);
else encode_bc1_scalar(pOut + 8, pPixels, num_blocks, 2);
}
} // namespace bc_spmd
} // namespace basisu