blob: e6f80d26c8d5354fd519a074d24502b4447d2de1 [file]
// basisu_bc15_spmd_kernels.inl -- Do NOT directly include.
//
// SSE4.1 cppspmd kernel for the standalone BC1 encoder. Included by basisu_bc15_spmd_sse.cpp from file scope after
// cppspmd_sse.h + "using namespace CPPSPMD;". LANES = BLOCKS: 4 independent BC1 blocks per vector, one per lane;
// each lane runs the same scalar-looking program on its block's 16 texels. Mirrors encoder/basisu_kernels_imp.h.
namespace bc_spmd_kern
{
// Opaque 4-color BC1 encode of 4 blocks at once (one block per lane). pBlocks = 4 blocks x 16 texels,
// block-contiguous; pOut = 4*8 bytes. om5*/om6* = solid-color omatch tables (int[256]); ls_rounds, avgvar tune.
struct encode_bc1_4color_blocks : spmd_kernel
{
inline vint div3(const vint& x) { return VUINT_SHIFT_RIGHT(x * vint(43691), 17); } // exact floor(x/3), x small
inline vint expand5(const vint& c) { return VINT_SHIFT_LEFT(c, 3) | VUINT_SHIFT_RIGHT(c, 2); }
inline vint expand6(const vint& c) { return VINT_SHIFT_LEFT(c, 2) | VUINT_SHIFT_RIGHT(c, 4); }
// Round per-lane float RGB (0..255) STRAIGHT into 5/6/5-bit space (single round, like encode_bc1's
// *31/255+0.5) -- avoids the double-rounding (float->8bit->565) that biases endpoints inward.
inline vint f_to_565(const vfloat& r, const vfloat& g, const vfloat& b)
{
const vint r5 = vint(round_nearest(min(max(r, vfloat(0.0f)), vfloat(255.0f)) * vfloat(31.0f / 255.0f)));
const vint g6 = vint(round_nearest(min(max(g, vfloat(0.0f)), vfloat(255.0f)) * vfloat(63.0f / 255.0f)));
const vint b5 = vint(round_nearest(min(max(b, vfloat(0.0f)), vfloat(255.0f)) * vfloat(31.0f / 255.0f)));
return VINT_SHIFT_LEFT(r5, 11) | VINT_SHIFT_LEFT(g6, 5) | b5;
}
// Load texel i of all 4 blocks into per-channel vints (lane j = block j's texel i).
inline void load_texel(const int32_t* p, int i, vint& r, vint& g, vint& b)
{
vint rgba; rgba.m_value = _mm_setr_epi32(p[i], p[16 + i], p[32 + i], p[48 + i]);
r = rgba & vint(0xFF);
g = VINT_SHIFT_RIGHT(rgba, 8) & vint(0xFF);
b = VINT_SHIFT_RIGHT(rgba, 16) & vint(0xFF);
// Opaque 4-color encoder: alpha is ignored entirely.
}
// Integer-threshold selector (encode_bc1's bc1_find_sels scheme): 1 dot + 3 compares/texel. The asymmetric
// (d<=t0) biases extreme texels onto the OUTER selectors so the LS step reaches wider endpoints.
// Palette order is [pal0,pal2,pal3,pal1] along the c0->c1 axis; lut4={1,3,2,0} via the ternary cascade.
struct thresholds { vint t0, t1, t2, ar2, ag2, ab2; };
inline thresholds make_thresholds(
const vint& pr0, const vint& pg0, const vint& pb0, const vint& pr1, const vint& pg1, const vint& pb1,
const vint& pr2, const vint& pg2, const vint& pb2, const vint& pr3, const vint& pg3, const vint& pb3)
{
const vint ar = pr1 - pr0, ag = pg1 - pg0, ab = pb1 - pb0;
const vint dp0 = pr0 * ar + pg0 * ag + pb0 * ab;
const vint dp2 = pr2 * ar + pg2 * ag + pb2 * ab;
const vint dp3 = pr3 * ar + pg3 * ag + pb3 * ab;
const vint dp1 = pr1 * ar + pg1 * ag + pb1 * ab;
thresholds T;
T.t0 = dp0 + dp2; T.t1 = dp2 + dp3; T.t2 = dp3 + dp1;
T.ar2 = ar + ar; T.ag2 = ag + ag; T.ab2 = ab + ab;
return T;
}
inline vint thresh_sel(const thresholds& T, const vint& r, const vint& g, const vint& b)
{
const vint d = r * T.ar2 + g * T.ag2 + b * T.ab2;
return spmd_ternaryi(d <= T.t0, 0, spmd_ternaryi(d < T.t1, 2, spmd_ternaryi(d < T.t2, 3, 1)));
}
// One least-squares round (per lane): assign integer-threshold selectors for the current endpoints, refit
// the endpoint line via the 2x2 normal equations (Cramer), adopt only where well-conditioned (det).
inline void ls_round(const vint* R, const vint* G, const vint* B, vint& c0, vint& c1)
{
const vint pr0 = expand5(VINT_SHIFT_RIGHT(c0, 11) & vint(31)), pg0 = expand6(VINT_SHIFT_RIGHT(c0, 5) & vint(63)), pb0 = expand5(c0 & vint(31));
const vint pr1 = expand5(VINT_SHIFT_RIGHT(c1, 11) & vint(31)), pg1 = expand6(VINT_SHIFT_RIGHT(c1, 5) & vint(63)), pb1 = expand5(c1 & vint(31));
const vint pr2 = div3(vint(2) * pr0 + pr1), pg2 = div3(vint(2) * pg0 + pg1), pb2 = div3(vint(2) * pb0 + pb1);
const vint pr3 = div3(pr0 + vint(2) * pr1), pg3 = div3(pg0 + vint(2) * pg1), pb3 = div3(pb0 + vint(2) * pb1);
const thresholds T = make_thresholds(pr0, pg0, pb0, pr1, pg1, pb1, pr2, pg2, pb2, pr3, pg3, pb3);
vfloat m00(0.0f), m01(0.0f), m11(0.0f);
vfloat a0r(0.0f), a0g(0.0f), a0b(0.0f), a1r(0.0f), a1g(0.0f), a1b(0.0f);
for (int i = 0; i < 16; i++)
{
const vint& r = R[i]; const vint& g = G[i]; const vint& b = B[i];
const vint sel = thresh_sel(T, r, g, b);
// fraction toward c1 (the LS weight b): {0, 1, 1/3, 2/3}[sel]; a = 1 - b.
const vfloat tb = spmd_ternaryf(sel == vint(0), vfloat(0.0f), spmd_ternaryf(sel == vint(1), vfloat(1.0f), spmd_ternaryf(sel == vint(2), vfloat(1.0f / 3.0f), vfloat(2.0f / 3.0f))));
const vfloat ta = vfloat(1.0f) - tb;
m00 = m00 + ta * ta; m01 = m01 + ta * tb; m11 = m11 + tb * tb;
const vfloat cr = (vfloat)r, cg = (vfloat)g, cb = (vfloat)b;
a0r = a0r + ta * cr; a0g = a0g + ta * cg; a0b = a0b + ta * cb;
a1r = a1r + tb * cr; a1g = a1g + tb * cg; a1b = a1b + tb * cb;
}
const vfloat det = m00 * m11 - m01 * m01;
const vfloat inv = vfloat(1.0f) / max(det, vfloat(1e-6f)); // clamp not bias: exact 1/det for real blocks, never /0
const vfloat e0r = (a0r * m11 - a1r * m01) * inv, e0g = (a0g * m11 - a1g * m01) * inv, e0b = (a0b * m11 - a1b * m01) * inv;
const vfloat e1r = (a1r * m00 - a0r * m01) * inv, e1g = (a1g * m00 - a0g * m01) * inv, e1b = (a1b * m00 - a0b * m01) * inv;
vint nc0 = f_to_565(e0r, e0g, e0b), nc1 = f_to_565(e1r, e1g, e1b);
{ const vint hi = max(nc0, nc1), lo = min(nc0, nc1); nc0 = hi; nc1 = lo; }
const vbool ok = det > vfloat(1e-6f); // only adopt a well-conditioned refit; degenerate lane keeps current
c0 = spmd_ternaryi(ok, nc0, c0);
c1 = spmd_ternaryi(ok, nc1, c1);
}
// Block RGB SSE for an endpoint pair (integer-threshold selectors + squared distance). For keep-better.
inline vint eval_error(const vint* R, const vint* G, const vint* B, const vint& c0, const vint& c1)
{
const vint pr0 = expand5(VINT_SHIFT_RIGHT(c0, 11) & vint(31)), pg0 = expand6(VINT_SHIFT_RIGHT(c0, 5) & vint(63)), pb0 = expand5(c0 & vint(31));
const vint pr1 = expand5(VINT_SHIFT_RIGHT(c1, 11) & vint(31)), pg1 = expand6(VINT_SHIFT_RIGHT(c1, 5) & vint(63)), pb1 = expand5(c1 & vint(31));
const vint pr2 = div3(vint(2) * pr0 + pr1), pg2 = div3(vint(2) * pg0 + pg1), pb2 = div3(vint(2) * pb0 + pb1);
const vint pr3 = div3(pr0 + vint(2) * pr1), pg3 = div3(pg0 + vint(2) * pg1), pb3 = div3(pb0 + vint(2) * pb1);
const thresholds T = make_thresholds(pr0, pg0, pb0, pr1, pg1, pb1, pr2, pg2, pb2, pr3, pg3, pb3);
vint err(0);
for (int i = 0; i < 16; i++)
{
const vint& r = R[i]; const vint& g = G[i]; const vint& b = B[i];
const vint sel = thresh_sel(T, r, g, b);
const vint psr = spmd_ternaryi(sel == vint(0), pr0, spmd_ternaryi(sel == vint(1), pr1, spmd_ternaryi(sel == vint(2), pr2, pr3)));
const vint psg = spmd_ternaryi(sel == vint(0), pg0, spmd_ternaryi(sel == vint(1), pg1, spmd_ternaryi(sel == vint(2), pg2, pg3)));
const vint psb = spmd_ternaryi(sel == vint(0), pb0, spmd_ternaryi(sel == vint(1), pb1, spmd_ternaryi(sel == vint(2), pb2, pb3)));
const vint dr = r - psr, dg = g - psg, db = b - psb;
err = err + dr * dr + dg * dg + db * db;
}
return err;
}
void _call(const basisu::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)
{
const int32_t* p = (const int32_t*)pBlocks;
// Stage source pixels to SoA ONCE (lane = block): R[i]/G[i]/B[i] hold texel i of all 4 blocks.
vint R[16], G[16], B[16];
for (int i = 0; i < 16; i++) load_texel(p, i, R[i], G[i], B[i]);
// Pass 1: per-lane bounding box + channel sums + integer moments for covariance.
vint mnr(255), mng(255), mnb(255), mxr(0), mxg(0), mxb(0), sum_r(0), sum_g(0), sum_b(0);
vint sum_rr(0), sum_gg(0), sum_bb(0), sum_rg(0), sum_rb(0), sum_gb(0);
for (int i = 0; i < 16; i++)
{
const vint& r = R[i]; const vint& g = G[i]; const vint& b = B[i];
mnr = min(mnr, r); mng = min(mng, g); mnb = min(mnb, b);
mxr = max(mxr, r); mxg = max(mxg, g); mxb = max(mxb, b);
sum_r = sum_r + r; sum_g = sum_g + g; sum_b = sum_b + b;
sum_rr = sum_rr + r * r; sum_gg = sum_gg + g * g; sum_bb = sum_bb + b * b;
sum_rg = sum_rg + r * g; sum_rb = sum_rb + r * b; sum_gb = sum_gb + g * b;
}
vint color0(0), color1(0), sel_word(0);
// SOLID lanes -> omatch endpoints; all-solid groups skip the non-solid path via SPMD_SELSE's any() check.
const vbool solid = (mnr == mxr) && (mng == mxg) && (mnb == mxb);
SPMD_SIF(solid)
{
// Clone of basist::encode_bc1_solid_block: omatch endpoints from the (full-cross-product) single-color
// tables, then the swap + degenerate handling that GUARANTEES 4-color (color0 > color1), never 3-color.
const vint ar = VUINT_SHIFT_RIGHT(sum_r + vint(8), 4), ag = VUINT_SHIFT_RIGHT(sum_g + vint(8), 4), ab = VUINT_SHIFT_RIGHT(sum_b + vint(8), 4);
vint max16 = VINT_SHIFT_LEFT(load_all(ar[om5lo]), 11) | VINT_SHIFT_LEFT(load_all(ag[om6lo]), 5) | load_all(ab[om5lo]); // 2x-weighted (m_hi)
vint min16 = VINT_SHIFT_LEFT(load_all(ar[om5hi]), 11) | VINT_SHIFT_LEFT(load_all(ag[om6hi]), 5) | load_all(ab[om5hi]); // 1x (m_lo)
vint mask = vint(0xAA);
SPMD_SIF(min16 == max16) // force max16 > min16 (stay 4-color, never 3-color)
{
store(mask, vint(0));
SPMD_SIF(min16 > vint(0)) { store(min16, min16 - vint(1)); }
SPMD_SELSE(min16 > vint(0)) { store(max16, vint(1)); store(min16, vint(0)); store(mask, vint(0x55)); }
SPMD_SENDIF
}
SPMD_SENDIF
SPMD_SIF(max16 < min16) // ensure color0 > color1; selector 2<->3 flips with the swap
{
const vint a = max16, b = min16;
store(max16, b); store(min16, a);
store(mask, mask ^ vint(0x55));
}
SPMD_SENDIF
store(color0, max16); store(color1, min16);
store(sel_word, mask | VINT_SHIFT_LEFT(mask, 8) | VINT_SHIFT_LEFT(mask, 16) | VINT_SHIFT_LEFT(mask, 24));
}
SPMD_SELSE(solid)
{
// PCA seed (METHOD 2, on-axis, sqrt-free): integer-moment covariance -> no-renorm power iteration
// (bbox-diagonal seed, can't overflow at 4 iters) -> scale 1024/max -> endpoints = mean +- extent.
const vfloat cxx = (vfloat)(vint(16) * sum_rr - sum_r * sum_r), cxy = (vfloat)(vint(16) * sum_rg - sum_r * sum_g), cxz = (vfloat)(vint(16) * sum_rb - sum_r * sum_b);
const vfloat cyy = (vfloat)(vint(16) * sum_gg - sum_g * sum_g), cyz = (vfloat)(vint(16) * sum_gb - sum_g * sum_b), czz = (vfloat)(vint(16) * sum_bb - sum_b * sum_b);
vfloat ax = (vfloat)(mxr - mnr), ay = (vfloat)(mxg - mng), az = (vfloat)(mxb - mnb);
// Overflow-safe: covariance entries <= 4.16e6, 4 iters -> |v| <= ~6.2e30 << float max. Do not exceed 5 iters.
for (int it = 0; it < 4; it++)
{
const vfloat nr = ax * cxx + ay * cxy + az * cxz;
const vfloat ng = ax * cxy + ay * cyy + az * cyz;
const vfloat nb = ax * cxz + ay * cyz + az * czz;
ax = nr; ay = ng; az = nb;
}
const vfloat kk = max(max(abs(ax), abs(ay)), abs(az));
const vfloat mm = vfloat(1024.0f) / max(kk, vfloat(1e-3f)); // clamp guards the near-degenerate lane
ax = ax * mm; ay = ay * mm; az = az * mm;
const vfloat inv_len2 = vfloat(1.0f) / (ax * ax + ay * ay + az * az + vfloat(0.0000125f)); // tiny bias: never /0
const vfloat meanx = (vfloat)sum_r * vfloat(1.0f / 16.0f), meany = (vfloat)sum_g * vfloat(1.0f / 16.0f), meanz = (vfloat)sum_b * vfloat(1.0f / 16.0f);
vfloat minp(1e30f), maxp(-1e30f);
for (int i = 0; i < 16; i++)
{
const vfloat pr = ((vfloat)R[i] - meanx) * ax + ((vfloat)G[i] - meany) * ay + ((vfloat)B[i] - meanz) * az;
minp = min(minp, pr); maxp = max(maxp, pr);
}
const vfloat tlo = minp * inv_len2, thi = maxp * inv_len2;
vint c0 = f_to_565(meanx + tlo * ax, meany + tlo * ay, meanz + tlo * az);
vint c1 = f_to_565(meanx + thi * ax, meany + thi * ay, meanz + thi * az);
{ const vint hi = max(c0, c1), lo = min(c0, c1); c0 = hi; c1 = lo; }
SPMD_SIF(c0 == c1) // 4-color needs color0 > color1; nudge the degenerate lanes
{
SPMD_SIF(c1 != vint(0)) { store(c1, c1 - vint(1)); }
SPMD_SELSE(c1 != vint(0)) { store(c0, c0 + vint(1)); }
SPMD_SENDIF
}
SPMD_SENDIF
// Least-squares refinement (the big quality lever).
for (int r = 0; r < ls_rounds; r++) ls_round(R, G, B, c0, c1);
SPMD_SIF(c0 == c1)
{
SPMD_SIF(c1 != vint(0)) { store(c1, c1 - vint(1)); }
SPMD_SELSE(c1 != vint(0)) { store(c0, c0 + vint(1)); }
SPMD_SENDIF
}
SPMD_SENDIF
// Low-variance blocks (ramps) collapse the PCA seed: try an omatch-solid-of-average 2nd seed (which
// survives the collapse), 1 LS round, and keep whichever has lower error. Gated to where it's needed.
const vint var_proxy = (mxr - mnr) + (mxg - mng) + (mxb - mnb);
SPMD_SIF(var_proxy < vint(avgvar))
{
const vint ar = VUINT_SHIFT_RIGHT(sum_r + vint(8), 4), ag = VUINT_SHIFT_RIGHT(sum_g + vint(8), 4), ab = VUINT_SHIFT_RIGHT(sum_b + vint(8), 4);
vint c0b = VINT_SHIFT_LEFT(load_all(ar[om5lo]), 11) | VINT_SHIFT_LEFT(load_all(ag[om6lo]), 5) | load_all(ab[om5lo]);
vint c1b = VINT_SHIFT_LEFT(load_all(ar[om5hi]), 11) | VINT_SHIFT_LEFT(load_all(ag[om6hi]), 5) | load_all(ab[om5hi]);
{ const vint hi = max(c0b, c1b), lo = min(c0b, c1b); c0b = hi; c1b = lo; }
SPMD_SIF(c0b == c1b)
{
SPMD_SIF(c1b != vint(0)) { store(c1b, c1b - vint(1)); }
SPMD_SELSE(c1b != vint(0)) { store(c0b, c0b + vint(1)); }
SPMD_SENDIF
}
SPMD_SENDIF
ls_round(R, G, B, c0b, c1b);
SPMD_SIF(c0b == c1b)
{
SPMD_SIF(c1b != vint(0)) { store(c1b, c1b - vint(1)); }
SPMD_SELSE(c1b != vint(0)) { store(c0b, c0b + vint(1)); }
SPMD_SENDIF
}
SPMD_SENDIF
const vbool use_avg = eval_error(R, G, B, c0b, c1b) < eval_error(R, G, B, c0, c1);
store(c0, spmd_ternaryi(use_avg, c0b, c0));
store(c1, spmd_ternaryi(use_avg, c1b, c1));
}
SPMD_SENDIF
// Final palette + integer-threshold selector pass.
const vint pr0 = expand5(VINT_SHIFT_RIGHT(c0, 11) & vint(31)), pg0 = expand6(VINT_SHIFT_RIGHT(c0, 5) & vint(63)), pb0 = expand5(c0 & vint(31));
const vint pr1 = expand5(VINT_SHIFT_RIGHT(c1, 11) & vint(31)), pg1 = expand6(VINT_SHIFT_RIGHT(c1, 5) & vint(63)), pb1 = expand5(c1 & vint(31));
const vint pr2 = div3(vint(2) * pr0 + pr1), pg2 = div3(vint(2) * pg0 + pg1), pb2 = div3(vint(2) * pb0 + pb1);
const vint pr3 = div3(pr0 + vint(2) * pr1), pg3 = div3(pg0 + vint(2) * pg1), pb3 = div3(pb0 + vint(2) * pb1);
const thresholds T = make_thresholds(pr0, pg0, pb0, pr1, pg1, pb1, pr2, pg2, pb2, pr3, pg3, pb3);
vint sw(0);
for (int i = 0; i < 16; i++)
{
const vint bestsel = thresh_sel(T, R[i], G[i], B[i]);
sw = sw | (bestsel << (i * 2));
}
store(color0, c0); store(color1, c1); store(sel_word, sw);
}
SPMD_SENDIF
// Emit one BC1 block per lane directly to the (possibly strided) output -- no temp buffer. num_write < 4
// for the final partial group so the padded lanes aren't written; out_stride lets BC3/BC5 interleave.
CPPSPMD_DECL(int, c0a[4]); CPPSPMD_DECL(int, c1a[4]); CPPSPMD_DECL(int, swa[4]);
storeu_linear_all(c0a, color0);
storeu_linear_all(c1a, color1);
storeu_linear_all(swa, sel_word);
for (uint32_t j = 0; j < num_write; j++)
{
uint8_t* o = pOut + j * out_stride;
const uint32_t c0 = (uint32_t)c0a[j], c1 = (uint32_t)c1a[j], sw = (uint32_t)swa[j];
o[0] = (uint8_t)c0; o[1] = (uint8_t)(c0 >> 8);
o[2] = (uint8_t)c1; o[3] = (uint8_t)(c1 >> 8);
o[4] = (uint8_t)sw; o[5] = (uint8_t)(sw >> 8); o[6] = (uint8_t)(sw >> 16); o[7] = (uint8_t)(sw >> 24);
}
}
};
} // namespace bc_spmd_kern
namespace bc_spmd_kern
{
// BC4 (RGTC1) -- one single-channel block per lane, 4 blocks/call. Clone of basist::encode_bc4: raw bbox
// min/max, exact nearest-of-8 integer-threshold selector, ALWAYS 8-value mode (solid forced to red0>red1).
// All-integer, GATHER-FREE: texels via manual load; rank->index by arithmetic (no s_tran gather); the 48-bit
// selector packed via uniform shifts into two vints (texels 0-9 in lo, 10-15 in hi), combined with <<30 in scalar.
struct encode_bc4_blocks : spmd_kernel
{
// Nearest-of-8 selectors + decoded SSE for an 8-value endpoint pair (r0 > r1). Packs texels 0-9 into slo,
// 10-15 into shi. Optionally accumulates the 1-D least-squares moments (rank = interpolation weight 0..7).
inline vint bc4_eval(const vint* A, const vint& r0, const vint& r1, vint& slo, vint& shi, vint* pSw, vint* pSww, vint* pSv, vint* pSwv)
{
const vint delta = r0 - r1;
const vint bias = vint(4) - r1 * vint(14);
const vint t0 = delta * vint(13), t1 = delta * vint(11), t2 = delta * vint(9), t3 = delta * vint(7), t4 = delta * vint(5), t5 = delta * vint(3), t6 = delta;
vint sse(0), Sw(0), Sww(0), Sv(0), Swv(0);
slo = vint(0); shi = vint(0);
for (int i = 0; i < 16; i++)
{
const vint val = A[i];
const vint sv = val * vint(14) + bias;
const vint rank = spmd_ternaryi(sv >= t0, 1, 0) + spmd_ternaryi(sv >= t1, 1, 0) + spmd_ternaryi(sv >= t2, 1, 0)
+ spmd_ternaryi(sv >= t3, 1, 0) + spmd_ternaryi(sv >= t4, 1, 0) + spmd_ternaryi(sv >= t5, 1, 0) + spmd_ternaryi(sv >= t6, 1, 0);
const vint idx = spmd_ternaryi(rank == vint(7), vint(0), spmd_ternaryi(rank == vint(0), vint(1), vint(8) - rank));
if (i < 10) slo = slo | (idx << (i * 3));
else shi = shi | (idx << ((i - 10) * 3));
const vint num = rank * r0 + (vint(7) - rank) * r1;
const vint recon = VUINT_SHIFT_RIGHT(num * vint(9363), 16); // == num/7 floor for num<=1785 (matches scalar /7)
const vint d = val - recon; sse = sse + d * d;
Sw = Sw + rank; Sww = Sww + rank * rank; Sv = Sv + val; Swv = Swv + rank * val;
}
if (pSw) { *pSw = Sw; *pSww = Sww; *pSv = Sv; *pSwv = Swv; }
return sse;
}
void _call(const uint8_t* pBlocks, uint8_t* pOut, uint32_t out_stride, uint32_t num_write, uint32_t do_ls, uint32_t src_stride)
{
// Stage 16 channel values to SoA: A[i] = texel i of the 4 lane-blocks. Source values are src_stride bytes
// apart (1 = packed channel, 4 = one channel of RGBA); blocks are 16*src_stride bytes apart.
const uint32_t S = src_stride;
vint A[16];
for (int i = 0; i < 16; i++)
A[i].m_value = _mm_setr_epi32(pBlocks[i * S], pBlocks[(16 + i) * S], pBlocks[(32 + i) * S], pBlocks[(48 + i) * S]);
vint mn = A[0], mx = A[0];
for (int i = 1; i < 16; i++) { mn = min(mn, A[i]); mx = max(mx, A[i]); }
vint color0(0), color1(0), sel_lo(0), sel_hi(0);
const vbool solid = (mx == mn);
SPMD_SIF(solid)
{
// Force 8-value solid: value>0 -> (max, max-1, all idx 0); value==0 -> (1, 0, all idx 1).
const vbool pos = (mx > vint(0));
store(color0, spmd_ternaryi(pos, mx, vint(1)));
store(color1, spmd_ternaryi(pos, mx - vint(1), vint(0)));
const vint idx = spmd_ternaryi(pos, vint(0), vint(1)); // replicated into every 3-bit field
store(sel_lo, idx * vint(0x9249249)); // 10 fields (texels 0-9) all = idx
store(sel_hi, idx * vint(0x9249)); // 6 fields (texels 10-15) all = idx
}
SPMD_SELSE(solid)
{
if (do_ls)
{
// HQ: candidate A = raw bbox (== encode_bc4) + LS moments; candidate B = 1-D LS endpoint refit
// (value ~= a + (rank/7)*b). detf guards /0 for degenerate lanes (refit then rejected). Keep-best.
vint sloA, shiA, Sw, Sww, Sv, Swv;
const vint sseA = bc4_eval(A, mx, mn, sloA, shiA, &Sw, &Sww, &Sv, &Swv);
const vint detI = vint(16) * Sww - Sw * Sw;
const vfloat detf = (vfloat)spmd_ternaryi(detI == vint(0), vint(1), detI);
const vfloat b = vfloat(7.0f) * (vfloat)(vint(16) * Swv - Sw * Sv) / detf; // slope = red0 - red1
const vfloat bsw7 = b * (vfloat)Sw * vfloat(1.0f / 7.0f);
const vfloat a = ((vfloat)Sv - bsw7) / vfloat(16.0f); // intercept = red1
vint nr1 = vint(round_nearest(a)); nr1 = min(max(nr1, vint(0)), vint(255));
vint nr0 = vint(round_nearest(a + b)); nr0 = min(max(nr0, vint(0)), vint(255));
vint sloB, shiB;
const vint sseB = bc4_eval(A, nr0, nr1, sloB, shiB, nullptr, nullptr, nullptr, nullptr);
const vbool adopt = ((detI != vint(0)) && (nr0 > nr1)) && (sseB < sseA);
store(color0, spmd_ternaryi(adopt, nr0, mx));
store(color1, spmd_ternaryi(adopt, nr1, mn));
store(sel_lo, spmd_ternaryi(adopt, sloB, sloA));
store(sel_hi, spmd_ternaryi(adopt, shiB, shiA));
}
else
{
// FAST: raw bbox endpoints + nearest-of-8 selector only (== basist::encode_bc4 quality, ~1.4x).
store(color0, mx); store(color1, mn);
const vint delta = mx - mn;
const vint bias = vint(4) - mn * vint(14);
const vint t0 = delta * vint(13), t1 = delta * vint(11), t2 = delta * vint(9), t3 = delta * vint(7), t4 = delta * vint(5), t5 = delta * vint(3), t6 = delta;
vint slo(0), shi(0);
for (int i = 0; i < 16; i++)
{
const vint sv = A[i] * vint(14) + bias;
const vint rank = spmd_ternaryi(sv >= t0, 1, 0) + spmd_ternaryi(sv >= t1, 1, 0) + spmd_ternaryi(sv >= t2, 1, 0)
+ spmd_ternaryi(sv >= t3, 1, 0) + spmd_ternaryi(sv >= t4, 1, 0) + spmd_ternaryi(sv >= t5, 1, 0) + spmd_ternaryi(sv >= t6, 1, 0);
const vint idx = spmd_ternaryi(rank == vint(7), vint(0), spmd_ternaryi(rank == vint(0), vint(1), vint(8) - rank));
if (i < 10) slo = slo | (idx << (i * 3));
else shi = shi | (idx << ((i - 10) * 3));
}
store(sel_lo, slo); store(sel_hi, shi);
}
}
SPMD_SENDIF
CPPSPMD_DECL(int, c0a[4]); CPPSPMD_DECL(int, c1a[4]); CPPSPMD_DECL(int, sloa[4]); CPPSPMD_DECL(int, shia[4]);
storeu_linear_all(c0a, color0);
storeu_linear_all(c1a, color1);
storeu_linear_all(sloa, sel_lo);
storeu_linear_all(shia, sel_hi);
for (uint32_t j = 0; j < num_write; j++)
{
uint8_t* o = pOut + j * out_stride;
o[0] = (uint8_t)c0a[j]; o[1] = (uint8_t)c1a[j];
const uint64_t W = (uint64_t)(uint32_t)sloa[j] | ((uint64_t)(uint32_t)shia[j] << 30); // 48-bit selector word
o[2] = (uint8_t)W; o[3] = (uint8_t)(W >> 8); o[4] = (uint8_t)(W >> 16);
o[5] = (uint8_t)(W >> 24); o[6] = (uint8_t)(W >> 32); o[7] = (uint8_t)(W >> 40);
}
}
};
} // namespace bc_spmd_kern
namespace basisu
{
namespace bc_spmd
{
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)
{
spmd_call<bc_spmd_kern::encode_bc4_blocks>(pBlocks, pOut, out_stride, num_write, do_ls, src_stride);
}
}
}
// ISA wrapper -- encode 4 blocks at once. Defined here (inside the SSE4.1 TU), declared in basisu_bc15_spmd.cpp.
namespace basisu
{
namespace bc_spmd
{
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)
{
spmd_call<bc_spmd_kern::encode_bc1_4color_blocks>(pBlocks, pOut, om5lo, om5hi, om6lo, om6hi, ls_rounds, avgvar, out_stride, num_write);
}
}
}