| // 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 |