// 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 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(mnr, r); mng = basisu::minimum(mng, g); mnb = basisu::minimum(mnb, b); mxr = basisu::maximum(mxr, r); mxg = basisu::maximum(mxg, g); mxb = basisu::maximum(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)rintf(a), 0, 255); const int nr0 = basisu::clamp((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