use super::*;
const WINDOW: usize = 16;
const SAVE_WHOLE: f32 = 7.0;
const SAVE_PART: f32 = 2.5;
#[inline]
pub(super) fn ceil_i32(x: f32) -> i32 {
let t = x as i32;
t.saturating_add((x > t as f32) as i32)
}
#[derive(PartialEq, Clone, Copy)]
enum Class {
Base,
Whole,
Table,
Endpoints,
}
fn rdo_strips(blocks_y: usize) -> Vec<(usize, usize)> {
let workers = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
.clamp(1, blocks_y.max(1));
let workers = workers.min(blocks_y.div_ceil(8).max(1));
let mut v = Vec::with_capacity(workers);
let base = blocks_y / workers;
let extra = blocks_y % workers;
let mut start = 0;
for wi in 0..workers {
let len = base + usize::from(wi < extra);
v.push((start, start + len));
start += len;
}
v
}
pub(crate) fn encode_image_bc1_rdo(
rgba: &[u8],
width: u32,
height: u32,
lambda: f32,
out: &mut [u8],
) -> Result<(), Error> {
let w = width as usize;
let h = height as usize;
if rgba.len() < w * h * 4 {
return Err(Error::TruncatedData);
}
let blocks_x = (w + 3) / 4;
let blocks_y = (h + 3) / 4;
let need = blocks_x
.checked_mul(blocks_y)
.and_then(|n| n.checked_mul(8))
.ok_or(Error::OutOfBounds)?;
if out.len() < need {
return Err(Error::TruncatedData);
}
let (dict, base_blocks) = build_table_dict(rgba, w, h, blocks_x, blocks_y);
let dict_ls: Vec<Option<TableLs>> = dict.iter().map(|&t| table_ls(t)).collect();
let strips = rdo_strips(blocks_y);
let q = super::QUALITY.with(|c| c.get());
let dict = &dict;
let dict_ls = &dict_ls;
let base_blocks = &base_blocks;
std::thread::scope(|scope| {
let mut rest = out;
for &(by0, by1) in &strips {
let band_len = (by1 - by0) * blocks_x * 8;
let (band, tail) = rest.split_at_mut(band_len.min(rest.len()));
rest = tail;
scope.spawn(move || {
super::with_quality(q, || {
let mut prev_row: Vec<[u8; 8]> = vec![[0u8; 8]; blocks_x];
let mut cur_row: Vec<[u8; 8]> = vec![[0u8; 8]; blocks_x];
let mut recent_blocks: [[u8; 8]; WINDOW] = [[0u8; 8]; WINDOW];
let mut recent_tables: [u32; WINDOW] = [0; WINDOW];
let mut recent_ls: [Option<TableLs>; WINDOW] = [None; WINDOW];
let mut recent_eps: [(u16, u16); WINDOW] = [(0, 0); WINDOW];
let mut recent_pal: [[[u8; 3]; 4]; WINDOW] = [[[0u8; 3]; 4]; WINDOW];
let mut recent_pal16: [super::bc1::Pal16; WINDOW] =
[super::bc1::widen_pal(&[[0u8; 3]; 4]); WINDOW];
let mut filled = 0usize;
let mut prev_block = [0u8; 8];
for by in by0..by1 {
for bx in 0..blocks_x {
let pixels = gather_block(rgba, w, h, bx, by);
let base = base_blocks[by * blocks_x + bx];
let base_err = bc1_block_sse(&pixels, &base);
if base_err == 0 {
let oi = ((by - by0) * blocks_x + bx) * 8;
let Some(slot) = band.get_mut(oi..oi + 8) else {
continue;
};
slot.copy_from_slice(&base);
prev_block = base;
if let Some(slot) = cur_row.get_mut(bx) {
*slot = base;
}
let slot = (by * blocks_x + bx) % WINDOW;
recent_blocks[slot] = base;
recent_tables[slot] = u32::from_le_bytes([base[4], base[5], base[6], base[7]]);
recent_ls[slot] = table_ls(recent_tables[slot]);
recent_eps[slot] = (
u16::from_le_bytes([base[0], base[1]]),
u16::from_le_bytes([base[2], base[3]]),
);
recent_pal[slot] = super::bc1::byte_pal_if_needed(
recent_eps[slot].0.max(recent_eps[slot].1),
recent_eps[slot].0.min(recent_eps[slot].1),
);
recent_pal16[slot] = super::bc1::pal16_from_565(
recent_eps[slot].0.max(recent_eps[slot].1),
recent_eps[slot].0.min(recent_eps[slot].1),
);
filled += 1;
continue;
}
let mut best = base;
let n0 = filled.min(WINDOW);
let above: Option<&[u8; 8]> = if by > by0 { prev_row.get(bx) } else { None };
let mut base_score = score_bc1(&base, &recent_blocks[..n0.min(WINDOW)]);
if let Some(ab) = above {
if ab == &base {
base_score = SAVE_WHOLE;
} else if (ab[4..8] == base[4..8] || ab[0..4] == base[0..4])
&& base_score < SAVE_PART
{
base_score = SAVE_PART;
}
}
let lam = lambda * (base_err as f32 / 192.0).min(1.0);
let mut best_j = base_err as f32 - lam * base_score;
let mut best_class = Class::Base;
if filled > 0 {
let lim = ceil_i32(best_j + lam * SAVE_WHOLE);
if lim > 0 {
if let Some(err) = bc1_block_sse_limited(&pixels, &prev_block, lim) {
let j = err as f32 - lambda * SAVE_WHOLE;
if j < best_j {
best_j = j;
best = prev_block;
best_class = Class::Whole;
}
}
}
let pxv = ls_pixels(&pixels);
let psq = super::bc1::psq_rgb(&pixels);
let n = filled.min(WINDOW);
let mut tried: [u32; WINDOW] = [0; WINDOW];
let mut ntried = 0usize;
let mut tried_bits = 0u64;
let mut tried_eps: [(u16, u16); WINDOW] = [(0, 0); WINDOW];
let mut neps = 0usize;
let mut eps_bits = 0u64;
for k in 0..n {
let table = recent_tables[k];
let tb = filter_bit(table);
let dup = (tried_bits & tb) != 0
&& tried[..ntried.min(WINDOW)].contains(&table);
if !dup {
tried[ntried] = table;
ntried += 1;
tried_bits |= tb;
}
let mut lim = ceil_i32(best_j + lam * SAVE_PART);
if lim > 0 && !dup {
if let Some(cand) = recent_ls[k]
.as_ref()
.and_then(|ls| refit_with_ls(&pixels, &pxv, ls, table))
{
if let Some(err) = bc1_block_sse_limited(&pixels, &cand, lim) {
let j = err as f32 - lam * SAVE_PART;
if j < best_j {
best_j = j;
best = cand;
best_class = Class::Table;
lim = ceil_i32(best_j + lam * SAVE_PART);
}
}
}
}
let (c0, c1) = recent_eps[k];
let eb = filter_bit((c0 as u32) | ((c1 as u32) << 16));
let edup = (eps_bits & eb) != 0
&& tried_eps[..neps.min(WINDOW)].contains(&(c0, c1));
if !edup {
tried_eps[neps] = (c0, c1);
neps += 1;
eps_bits |= eb;
}
if c0 > c1 && lim > 0 && !edup {
if let Some((blk, err)) = super::bc1::pack_bc1_scored_pre(
&pixels, c0, c1, &recent_pal[k], &recent_pal16[k], psq,
lim,
) {
let j = err as f32 - lam * SAVE_PART;
if j < best_j {
best_j = j;
best = blk;
best_class = Class::Endpoints;
}
}
}
}
for (di, &table) in dict.iter().enumerate() {
if (tried_bits & filter_bit(table)) != 0
&& tried[..ntried.min(WINDOW)].contains(&table)
{
continue; }
let lim = ceil_i32(best_j + lam * SAVE_PART);
if lim <= 0 {
break;
}
if let Some(cand) = dict_ls[di]
.as_ref()
.and_then(|ls| refit_with_ls(&pixels, &pxv, ls, table))
{
if let Some(err) = bc1_block_sse_limited(&pixels, &cand, lim) {
let j = err as f32 - lam * SAVE_PART;
if j < best_j {
best_j = j;
best = cand;
best_class = Class::Table;
}
}
}
}
}
if best_class == Class::Table {
polish_endpoints_fixed_table(&pixels, &mut best);
}
let _ = best_class;
let oi = ((by - by0) * blocks_x + bx) * 8;
let Some(slot) = band.get_mut(oi..oi + 8) else {
continue;
};
slot.copy_from_slice(&best);
prev_block = best;
if let Some(slot) = cur_row.get_mut(bx) {
*slot = best;
}
let slot = (by * blocks_x + bx) % WINDOW;
recent_blocks[slot] = best;
recent_tables[slot] =
u32::from_le_bytes([best[4], best[5], best[6], best[7]]);
recent_ls[slot] = table_ls(recent_tables[slot]);
recent_eps[slot] = (
u16::from_le_bytes([best[0], best[1]]),
u16::from_le_bytes([best[2], best[3]]),
);
recent_pal[slot] = super::bc1::byte_pal_if_needed(
recent_eps[slot].0.max(recent_eps[slot].1),
recent_eps[slot].0.min(recent_eps[slot].1),
);
recent_pal16[slot] = super::bc1::pal16_from_565(
recent_eps[slot].0.max(recent_eps[slot].1),
recent_eps[slot].0.min(recent_eps[slot].1),
);
filled += 1;
}
std::mem::swap(&mut prev_row, &mut cur_row);
}
});
});
}
debug_assert!(rest.is_empty());
});
Ok(())
}
#[inline]
fn bc1_colors_packed(block: &[u8; 8]) -> [u32; 4] {
let c0 = u16::from_le_bytes([block[0], block[1]]);
let c1 = u16::from_le_bytes([block[2], block[3]]);
let a = from_565_packed(c0);
let b = from_565_packed(c1);
if c0 > c1 {
[a, b, lerp_packed::<2, 1>(a, b), lerp_packed::<1, 2>(a, b)]
} else {
[a, b, lerp_packed::<1, 1>(a, b), 0]
}
}
fn bc1_block_sse_limited(pixels: &[[u8; 4]; 16], block: &[u8; 8], limit: i32) -> Option<i32> {
let table = u32::from_le_bytes([block[4], block[5], block[6], block[7]]);
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
let c0 = u16::from_le_bytes([block[0], block[1]]);
let c1 = u16::from_le_bytes([block[2], block[3]]);
let err = simd::bc1_fixed_sse_565_avx2(pixels, c0, c1, table);
return (err < limit).then_some(err);
}
bc1_block_sse_limited_scalar(pixels, block, table, limit)
}
#[cold]
#[inline(never)]
fn bc1_block_sse_limited_scalar(
pixels: &[[u8; 4]; 16],
block: &[u8; 8],
table: u32,
limit: i32,
) -> Option<i32> {
let packed = bc1_colors_packed(block);
let mut err = 0i32;
for (i, p) in pixels.iter().enumerate() {
let idx = ((table >> (2 * i)) & 3) as usize;
err += sqr_rgb([p[0], p[1], p[2]], unpack_rgb(packed[idx]));
if err >= limit {
return None;
}
}
Some(err)
}
fn bc1_block_sse(pixels: &[[u8; 4]; 16], block: &[u8; 8]) -> i32 {
let table = u32::from_le_bytes([block[4], block[5], block[6], block[7]]);
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
let c0 = u16::from_le_bytes([block[0], block[1]]);
let c1 = u16::from_le_bytes([block[2], block[3]]);
return simd::bc1_fixed_sse_565_avx2(pixels, c0, c1, table);
}
let packed = bc1_colors_packed(block);
let mut err = 0i32;
for (i, p) in pixels.iter().enumerate() {
let idx = ((table >> (2 * i)) & 3) as usize;
err += sqr_rgb([p[0], p[1], p[2]], unpack_rgb(packed[idx]));
}
err
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
type LsPixels = [[f32; 8]; 16];
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
type LsPixels = ();
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
fn ls_pixels(pixels: &[[u8; 4]; 16]) -> LsPixels {
if simd::has_avx2() {
simd::ls_pixels(pixels)
} else {
[[0f32; 8]; 16]
}
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
fn ls_pixels(_pixels: &[[u8; 4]; 16]) -> LsPixels {}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
fn ls_pixels_mode6(pixels: &[[u8; 4]; 16]) -> LsPixels {
if simd::has_avx2() {
simd::ls_pixels_mode6(pixels)
} else {
[[0f32; 8]; 16]
}
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
fn ls_pixels_mode6(_pixels: &[[u8; 4]; 16]) -> LsPixels {}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
fn ls_endpoints_mode6_hot(
pixels: &[[u8; 4]; 16],
pxv: &LsPixels,
indices: &[u8; 16],
) -> Option<([u8; 4], [u8; 4])> {
if simd::has_avx2() {
super::bc7::ls_endpoints_mode6_pxv(pxv, indices)
} else {
super::bc7::ls_endpoints_mode6_scalar(pixels, indices)
}
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
fn ls_endpoints_mode6_hot(
pixels: &[[u8; 4]; 16],
_pxv: &LsPixels,
indices: &[u8; 16],
) -> Option<([u8; 4], [u8; 4])> {
super::bc7::ls_endpoints_mode6_scalar(pixels, indices)
}
#[derive(Clone, Copy)]
struct TableLs {
a00: f32,
a01: f32,
a11: f32,
det: f32,
uw: [[f32; 8]; 16],
}
fn table_ls(table: u32) -> Option<TableLs> {
const W: [f32; 4] = [0.0, 1.0, 1.0 / 3.0, 2.0 / 3.0];
let mut a00 = 0f32;
let mut a01 = 0f32;
let mut a11 = 0f32;
let mut uw = [[0f32; 8]; 16];
for (i, slot) in uw.iter_mut().enumerate() {
let wgt = W[((table >> (2 * i)) & 3) as usize];
let u = 1.0 - wgt;
a00 += u * u;
a01 += u * wgt;
a11 += wgt * wgt;
*slot = [u, u, u, u, wgt, wgt, wgt, wgt];
}
let det = a00 * a11 - a01 * a01;
if det.abs() < 1e-4 {
return None;
}
Some(TableLs { a00, a01, a11, det, uw })
}
#[cold]
#[inline(never)]
fn ls_accum_scalar(pixels: &[[u8; 4]; 16], uw: &[[f32; 8]; 16]) -> ([f32; 3], [f32; 3]) {
let mut b0 = [0f32; 3];
let mut b1 = [0f32; 3];
for (i, p) in pixels.iter().enumerate() {
let (u, wgt) = (uw[i][0], uw[i][4]);
for c in 0..3 {
let x = p[c] as f32;
b0[c] += u * x;
b1[c] += wgt * x;
}
}
(b0, b1)
}
#[inline]
fn filter_bit(v: u32) -> u64 {
1u64 << ((v.wrapping_mul(0x9E37_79B1) >> 26) & 63)
}
fn refit_with_ls(
pixels: &[[u8; 4]; 16],
pxv: &LsPixels,
ls: &TableLs,
table: u32,
) -> Option<[u8; 8]> {
let (a00, a01, a11, det) = (ls.a00, ls.a01, ls.a11, ls.det);
let mut e0 = [0u8; 3];
let mut e1 = [0u8; 3];
#[cfg_attr(not(all(feature = "simd", target_arch = "x86_64")), allow(unused_mut))]
let mut qq: Option<(u16, u16)> = None;
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
let done = if simd::has_avx2() {
qq = Some(simd::ls_accum_solve_565(pxv, &ls.uw, a00, a01, a11, det));
true
} else {
false
};
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
let done = {
let _ = pxv; false
};
if !done {
let (b0, b1) = ls_accum_scalar(pixels, &ls.uw);
for c in 0..3 {
e0[c] = round_clamp_u8((a11 * b0[c] - a01 * b1[c]) / det);
e1[c] = round_clamp_u8((a00 * b1[c] - a01 * b0[c]) / det);
}
}
let (q0, q1) = match qq {
Some(v) => v,
None => (to_565(e0), to_565(e1)),
};
if q0 <= q1 {
return None; }
let mut out = [0u8; 8];
out[0..2].copy_from_slice(&q0.to_le_bytes());
out[2..4].copy_from_slice(&q1.to_le_bytes());
out[4..8].copy_from_slice(&table.to_le_bytes());
Some(out)
}
const DICT_N: usize = 24;
#[derive(Default)]
struct U32Hasher(u64);
impl std::hash::Hasher for U32Hasher {
#[inline]
fn finish(&self) -> u64 {
self.0
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.0 = (self.0 ^ b as u64).wrapping_mul(0x0100_0000_01b3);
}
}
#[inline]
fn write_u32(&mut self, v: u32) {
self.0 = (v as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15);
}
}
type TableCounts = std::collections::HashMap<u32, u32, std::hash::BuildHasherDefault<U32Hasher>>;
fn build_table_dict(
rgba: &[u8],
w: usize,
h: usize,
blocks_x: usize,
blocks_y: usize,
) -> (Vec<u32>, Vec<[u8; 8]>) {
let nblocks = blocks_x * blocks_y;
let workers = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
.clamp(1, blocks_y.max(1));
let mut strips: Vec<(usize, usize)> = Vec::with_capacity(workers);
let base = blocks_y / workers;
let extra = blocks_y % workers;
let mut start = 0;
for wi in 0..workers {
let len = base + usize::from(wi < extra);
strips.push((start, start + len));
start += len;
}
let q = super::QUALITY.with(|c| c.get());
let mut parts: Vec<(Vec<[u8; 8]>, TableCounts)> = Vec::with_capacity(workers);
std::thread::scope(|scope| {
let mut handles = Vec::with_capacity(workers);
for &(by0, by1) in &strips {
handles.push(scope.spawn(move || {
super::with_quality(q, || {
let mut local = Vec::with_capacity((by1 - by0) * blocks_x);
let mut lc = TableCounts::default();
for by in by0..by1 {
for bx in 0..blocks_x {
let pixels = gather_block(rgba, w, h, bx, by);
let blk = encode_bc1_bytes(pixels);
let c0 = u16::from_le_bytes([blk[0], blk[1]]);
let c1 = u16::from_le_bytes([blk[2], blk[3]]);
if c0 > c1 {
let t = u32::from_le_bytes([blk[4], blk[5], blk[6], blk[7]]);
*lc.entry(t).or_insert(0) += 1;
}
local.push(blk);
}
}
(local, lc)
})
}));
}
for hnd in handles {
parts.push(hnd.join().expect("rdo pass-1 worker panicked"));
}
});
let mut counts = TableCounts::default();
let mut blocks = Vec::with_capacity(nblocks);
for (local, lc) in parts {
blocks.extend_from_slice(&local);
for (t, n) in lc {
*counts.entry(t).or_insert(0) += n;
}
}
let mut v: Vec<(u32, u32)> = counts.into_iter().collect();
v.sort_unstable_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
let dict = v
.into_iter()
.take(DICT_N)
.filter(|&(_, n)| n >= 2)
.map(|(t, _)| t)
.collect();
(dict, blocks)
}
#[inline]
fn from_565_chan(c: u16, ch: usize) -> u8 {
match ch {
0 => super::exp5(c >> 11),
1 => super::exp6((c >> 5) & 63),
_ => super::exp5(c & 31),
}
}
#[inline]
fn bc1_chan_sse(planar: &[[u8; 16]; 3], ch: usize, c0: u16, c1: u16, table: u32) -> i32 {
let a = from_565_chan(c0, ch) as u32;
let b = from_565_chan(c1, ch) as u32;
let cols = [
a as u8,
b as u8,
((2 * a + b) / 3) as u8,
((a + 2 * b) / 3) as u8,
];
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return simd::bc1_chan_sse_avx2(&planar[ch], cols, table);
}
bc1_chan_sse_scalar(&planar[ch], cols, table)
}
#[cold]
#[inline(never)]
fn bc1_chan_sse_scalar(plane: &[u8; 16], cols: [u8; 4], table: u32) -> i32 {
let mut e = 0i32;
for (i, &x) in plane.iter().enumerate() {
let idx = ((table >> (2 * i)) & 3) as usize;
let d = cols[idx] as i32 - x as i32;
e += d * d;
}
e
}
fn polish_endpoints_fixed_table(pixels: &[[u8; 4]; 16], block: &mut [u8; 8]) {
let table = u32::from_le_bytes([block[4], block[5], block[6], block[7]]);
let mut planar = [[0u8; 16]; 3];
for (i, px) in pixels.iter().enumerate() {
planar[0][i] = px[0];
planar[1][i] = px[1];
planar[2][i] = px[2];
}
let mut ce = [0i32; 3];
{
let (c0, c1) = (
u16::from_le_bytes([block[0], block[1]]),
u16::from_le_bytes([block[2], block[3]]),
);
for (ch, e) in ce.iter_mut().enumerate() {
*e = bc1_chan_sse(&planar, ch, c0, c1, table);
}
}
let mut err = ce[0] + ce[1] + ce[2];
debug_assert_eq!(err, bc1_block_sse(pixels, block));
for _round in 0..2 {
let c0 = u16::from_le_bytes([block[0], block[1]]);
let c1 = u16::from_le_bytes([block[2], block[3]]);
if c0 <= c1 {
return;
}
let prev = err;
for (base_is_c0, d) in [(true, -1i32), (false, 1i32)] {
for (shift, maxv) in [(11u16, 31u16), (5, 63), (0, 31)] {
let c0n = u16::from_le_bytes([block[0], block[1]]);
let c1n = u16::from_le_bytes([block[2], block[3]]);
let base = if base_is_c0 { c0n } else { c1n };
let cur = (base >> shift) & maxv;
let nv = cur as i32 + d;
if nv < 0 || nv > maxv as i32 {
continue;
}
let cand = (base & !(maxv << shift)) | ((nv as u16) << shift);
let (n0, n1) = if base_is_c0 { (cand, c1n) } else { (c0n, cand) };
if n0 <= n1 {
continue; }
let ch = match shift {
11 => 0usize,
5 => 1,
_ => 2,
};
let cand = bc1_chan_sse(&planar, ch, n0, n1, table);
let total = err - ce[ch] + cand;
if total < err {
err = total;
ce[ch] = cand;
block[0..2].copy_from_slice(&n0.to_le_bytes());
block[2..4].copy_from_slice(&n1.to_le_bytes());
}
}
}
if err >= prev {
break;
}
}
}
const SAVE_WHOLE16: f32 = 14.0;
const SAVE_HALF8: f32 = 6.0;
const BC7_WINDOW: usize = 16;
#[cfg(feature = "decode")]
pub(crate) fn encode_image_bc7_rdo(
rgba: &[u8],
width: u32,
height: u32,
lambda: f32,
out: &mut [u8],
) -> Result<(), Error> {
let w = width as usize;
let h = height as usize;
if rgba.len() < w * h * 4 {
return Err(Error::TruncatedData);
}
let blocks_x = (w + 3) / 4;
let blocks_y = (h + 3) / 4;
let need = blocks_x
.checked_mul(blocks_y)
.and_then(|n| n.checked_mul(16))
.ok_or(Error::OutOfBounds)?;
if out.len() < need {
return Err(Error::TruncatedData);
}
let strips = rdo_strips(blocks_y);
let q = super::QUALITY.with(|c| c.get());
std::thread::scope(|scope| {
let mut rest = out;
for &(by0, by1) in &strips {
let band_len = (by1 - by0) * blocks_x * 16;
let (band, tail) = rest.split_at_mut(band_len.min(rest.len()));
rest = tail;
scope.spawn(move || {
super::with_quality(q, || {
let mut recent: [([u8; 16], bool); BC7_WINDOW] = [([0u8; 16], false); BC7_WINDOW];
type Mode6Parts = ([u8; 4], u8, [u8; 4], u8, [u8; 16]);
let mut recent_m6: [Option<Mode6Parts>; BC7_WINDOW] = [None; BC7_WINDOW];
let mut prev_row: Vec<[u8; 16]> = vec![[0u8; 16]; blocks_x];
let mut cur_row: Vec<[u8; 16]> = vec![[0u8; 16]; blocks_x];
let mut filled = 0usize;
let mut prev_block = [0u8; 16];
for by in by0..by1 {
for bx in 0..blocks_x {
let pixels = gather_block(rgba, w, h, bx, by);
let mut base = [0u8; 16];
let base_err = super::bc7::encode_bc7_mode6_scored(pixels, &mut base);
if base_err == 0 {
let oi = ((by - by0) * blocks_x + bx) * 16;
let Some(slot) = band.get_mut(oi..oi + 16) else {
continue;
};
slot.copy_from_slice(&base);
prev_block = base;
if let Some(slot) = cur_row.get_mut(bx) {
*slot = base;
}
let slot = (by * blocks_x + bx) % BC7_WINDOW;
recent[slot] = (base, base[0] & 0x7F == 0x40);
recent_m6[slot] = parse_mode6(&base);
filled += 1;
continue;
}
let planar = Mode6Planar::new(&pixels);
let pxv = ls_pixels_mode6(&pixels);
let mut best = base;
let n0 = filled.min(BC7_WINDOW);
let above: Option<&[u8; 16]> = if by > by0 { prev_row.get(bx) } else { None };
let mut base_score = score_bc7(&base, &recent[..n0.min(WINDOW)]);
if let Some(ab) = above {
if ab == &base {
base_score = SAVE_WHOLE16;
} else if (ab[8..16] == base[8..16] || ab[0..8] == base[0..8])
&& base_score < SAVE_HALF8
{
base_score = SAVE_HALF8;
}
}
let lam = lambda * (base_err as f32 / 256.0).min(1.0);
let mut best_j = base_err as f32 - lam * base_score;
if filled > 0 {
let mut wholes: [Option<[u8; 16]>; 2] = [Some(prev_block), None];
if let Some(ab) = above {
wholes[1] = Some(*ab);
}
for cand in wholes.into_iter().flatten() {
let err = bc7_block_sse(&pixels, &cand);
let j = err as f32 - lam * SAVE_WHOLE16;
if j < best_j {
best_j = j;
best = cand;
}
}
let n = filled.min(BC7_WINDOW);
for k in 0..n {
let (donor, is_m6) = recent[k];
if !is_m6 {
continue;
}
let _ = &donor;
let Some((dq0, dp0, dq1, dp1, didx)) = recent_m6[k] else {
continue;
};
if let Some((e0, e1)) = ls_endpoints_mode6_hot(&pixels, &pxv, &didx)
{
let q0 = quantize_7p_best(e0);
let q1 = quantize_7p_fixed(e1, dp1);
let (mut q0a, p0a) = q0;
let mut q1a = q1.0;
let dfixed = Mode6Fixed::new(&planar, &didx);
let mut ce =
mode6_chan_errs(&dfixed, q0a, p0a, q1a, dp1);
let mut err: i64 = ce.iter().sum();
polish_mode6_endpoints(
&dfixed,
&mut ce, &mut q0a, p0a, &mut q1a, dp1, &mut err,
);
let j = err as f32 - lam * SAVE_HALF8;
if j < best_j {
best_j = j;
best = pack_bc7_mode6(q0a, p0a, q1a, dp1, didx);
debug_assert_eq!(&best[8..16], &donor[8..16]);
}
}
let du0 = unquantize_7p(dq0, dp0);
let dbase = super::bc7::palette_mode6_base(du0);
for p1 in 0..2u8 {
let (idx, errv) = super::bc7::palette_and_fit_mode6(
&pixels,
dbase,
du0,
unquantize_7p(dq1, p1),
);
if idx[0] > 7 {
continue; }
let j = errv as f32 - lam * SAVE_HALF8;
if j < best_j {
best_j = j;
best = pack_bc7_mode6(dq0, dp0, dq1, p1, idx);
debug_assert_eq!(&best[0..8], &donor[0..8]);
}
}
}
}
let oi = ((by - by0) * blocks_x + bx) * 16;
let Some(slot) = band.get_mut(oi..oi + 16) else {
continue;
};
slot.copy_from_slice(&best);
prev_block = best;
if let Some(slot) = cur_row.get_mut(bx) {
*slot = best;
}
let slot = (by * blocks_x + bx) % BC7_WINDOW;
recent[slot] = (best, best[0] & 0x7F == 0x40);
recent_m6[slot] = parse_mode6(&best);
filled += 1;
}
std::mem::swap(&mut prev_row, &mut cur_row);
}
});
});
}
debug_assert!(rest.is_empty());
});
Ok(())
}
#[cfg(feature = "decode")]
fn bc7_block_sse(pixels: &[[u8; 4]; 16], block: &[u8; 16]) -> i64 {
let mut dec = [0u8; 64];
if !crate::decode::bcn::bc7_fast_block(block, &mut dec, 16) {
bcdec_rs::bc7(block, &mut dec, 16);
}
let mut err = 0i64;
for i in 0..16 {
for c in 0..4 {
let d = dec[i * 4 + c] as i64 - pixels[i][c] as i64;
err += d * d;
}
}
err
}
struct Mode6Fixed<'a> {
planar: &'a Mode6Planar,
w: [i16; 16],
}
pub(super) struct Mode6Planar {
planar: [[u8; 16]; 4],
}
impl Mode6Planar {
#[inline]
fn new(pixels: &[[u8; 4]; 16]) -> Self {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return Self { planar: simd::planar_avx2(pixels) };
}
let mut planar = [[0u8; 16]; 4];
for (i, px) in pixels.iter().enumerate() {
planar[0][i] = px[0];
planar[1][i] = px[1];
planar[2][i] = px[2];
planar[3][i] = px[3];
}
Self { planar }
}
}
impl<'a> Mode6Fixed<'a> {
#[inline]
fn new(planar: &'a Mode6Planar, indices: &[u8; 16]) -> Self {
let mut w = [0i16; 16];
for (i, slot) in w.iter_mut().enumerate() {
debug_assert!(indices[i] < 16);
*slot = W6M[(indices[i] & 15) as usize] as i16;
}
Self { planar, w }
}
}
#[inline]
fn mode6_chan_sse(fixed: &Mode6Fixed, c: usize, v0: u8, v1: u8) -> i64 {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return simd::mode6_chan_sse_avx2(&fixed.planar.planar[c], &fixed.w, v0, v1);
}
mode6_chan_sse_scalar(&fixed.planar.planar[c], &fixed.w, v0, v1)
}
#[cold]
#[inline(never)]
fn mode6_chan_sse_scalar(plane: &[u8; 16], w: &[i16; 16], v0: u8, v1: u8) -> i64 {
let base = v0 as i32 * 64 + 32;
let delta = v1 as i32 - v0 as i32;
let mut err = 0i64;
for (i, &x) in plane.iter().enumerate() {
let v = ((base + w[i] as i32 * delta) >> 6) as u8;
let d = v as i64 - x as i64;
err += d * d;
}
err
}
#[inline]
fn mode6_chan_sse_pair(
fixed: &Mode6Fixed,
c: usize,
a: u8,
b: u8,
dbase: i16,
ddelta: i16,
) -> (i64, i64) {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
return simd::mode6_chan_sse_pair_avx2(
&fixed.planar.planar[c],
&fixed.w,
a,
b,
dbase,
ddelta,
);
}
mode6_chan_sse_pair_scalar(fixed, c, a, b, dbase, ddelta)
}
#[cold]
#[inline(never)]
fn mode6_chan_sse_pair_scalar(
fixed: &Mode6Fixed,
c: usize,
a: u8,
b: u8,
dbase: i16,
ddelta: i16,
) -> (i64, i64) {
let (a1, b1) = if dbase != 0 {
((a as i16 + dbase / 64) as u8, b)
} else {
(a, (b as i16 + ddelta) as u8)
};
(
mode6_chan_sse(fixed, c, a, b),
mode6_chan_sse(fixed, c, a1, b1),
)
}
#[inline]
fn mode6_chan_errs(fixed: &Mode6Fixed, q0: [u8; 4], p0: u8, q1: [u8; 4], p1: u8) -> [i64; 4] {
let c0 = unquantize_7p(q0, p0);
let c1 = unquantize_7p(q1, p1);
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if simd::has_avx2() {
let v = [
(c0[0], c1[0]),
(c0[1], c1[1]),
(c0[2], c1[2]),
(c0[3], c1[3]),
];
return simd::mode6_chan_errs_avx2(&fixed.planar.planar, &fixed.w, &v);
}
let mut e = [0i64; 4];
for (c, ec) in e.iter_mut().enumerate() {
*ec = mode6_chan_sse(fixed, c, c0[c], c1[c]);
}
e
}
const fn build_q7_table() -> [[(u8, u16); 256]; 2] {
let mut t = [[(0u8, 0u16); 256]; 2];
let mut p = 0usize;
while p < 2 {
let mut v = 0usize;
while v < 256 {
let base = (v >> 1) as u8;
let lo = if base == 0 { 0 } else { base - 1 };
let hi = if base + 1 > 127 { 127 } else { base + 1 };
let mut bq = if base < 127 { base } else { 127 };
let mut be = i32::MAX;
let mut cand = lo;
while cand <= hi {
let recon = (((cand as u32) << 1) | p as u32) as u8;
let d = recon as i32 - v as i32;
let e = d * d;
if e < be {
be = e;
bq = cand;
}
cand += 1;
}
t[p][v] = (bq, be as u16);
v += 1;
}
p += 1;
}
t
}
static Q7: [[(u8, u16); 256]; 2] = build_q7_table();
#[inline]
fn quantize_7p_best(c: [u8; 4]) -> ([u8; 4], u8) {
let r0 = &Q7[0];
let r1 = &Q7[1];
let e0 = r0[c[0] as usize].1 as i32
+ r0[c[1] as usize].1 as i32
+ r0[c[2] as usize].1 as i32
+ r0[c[3] as usize].1 as i32;
let e1 = r1[c[0] as usize].1 as i32
+ r1[c[1] as usize].1 as i32
+ r1[c[2] as usize].1 as i32
+ r1[c[3] as usize].1 as i32;
let (row, p) = if e1 < e0 { (r1, 1u8) } else { (r0, 0u8) };
(
[
row[c[0] as usize].0,
row[c[1] as usize].0,
row[c[2] as usize].0,
row[c[3] as usize].0,
],
p,
)
}
fn quantize_7p_fixed(c: [u8; 4], p: u8) -> ([u8; 4], u8) {
let row = &Q7[p as usize];
(
[
row[c[0] as usize].0,
row[c[1] as usize].0,
row[c[2] as usize].0,
row[c[3] as usize].0,
],
p,
)
}
fn parse_mode6(block: &[u8; 16]) -> Option<([u8; 4], u8, [u8; 4], u8, [u8; 16])> {
if block[0] & 0x7F != 0x40 {
return None;
}
let mut lo = [0u8; 8];
let mut hi = [0u8; 8];
lo.copy_from_slice(&block[0..8]);
hi.copy_from_slice(&block[8..16]);
let low = u64::from_le_bytes(lo);
let high = u64::from_le_bytes(hi);
let bit = |i: u32| -> u64 {
if i < 64 {
(low >> i) & 1
} else {
(high >> (i - 64)) & 1
}
};
let bits = |start: u32, n: u32| -> u64 {
let mut v = 0u64;
for k in 0..n {
v |= bit(start + k) << k;
}
v
};
let mut q0 = [0u8; 4];
let mut q1 = [0u8; 4];
let mut pos = 7u32;
for c in 0..4 {
q0[c] = bits(pos, 7) as u8;
pos += 7;
q1[c] = bits(pos, 7) as u8;
pos += 7;
}
let p0 = bit(63) as u8;
let p1 = bit(64) as u8;
let mut indices = [0u8; 16];
indices[0] = bits(65, 3) as u8;
let mut ip = 68u32;
for v in indices.iter_mut().skip(1) {
*v = bits(ip, 4) as u8;
ip += 4;
}
Some((q0, p0, q1, p1, indices))
}
fn polish_mode6_endpoints(
fixed: &Mode6Fixed,
ce: &mut [i64; 4],
q0: &mut [u8; 4],
p0: u8,
q1: &mut [u8; 4],
p1: u8,
err: &mut i64,
) {
debug_assert_eq!(ce.iter().sum::<i64>(), *err);
let mut active = [true; 4];
for _round in 0..2 {
let prev = *err;
let mut moved = [false; 4];
for which in 0..2 {
for c in 0..4 {
if !active[c] {
continue;
}
if ce[c] == 0 {
continue;
}
let other = if which == 0 {
unquantize_7p_chan((*q1)[c], p1)
} else {
unquantize_7p_chan((*q0)[c], p0)
};
let pbit = if which == 0 { p0 } else { p1 };
let start = if which == 0 { (*q0)[c] } else { (*q1)[c] };
let lo_ok = start >= 1;
let hi_ok = start <= 126;
let mk = |nv: u8| {
let mv = unquantize_7p_chan(nv, pbit);
if which == 0 { (mv, other) } else { (other, mv) }
};
let (mut cand_lo, mut cand_hi) = match (lo_ok, hi_ok) {
(true, true) => {
let (a0, b0) = mk(start - 1);
let (dbase, ddelta) =
if which == 0 { (4 * 64, -4) } else { (0, 4) };
let (l, h) = mode6_chan_sse_pair(fixed, c, a0, b0, dbase, ddelta);
(Some(l), Some(h))
}
(true, false) => {
let (a, b) = mk(start - 1);
(Some(mode6_chan_sse(fixed, c, a, b)), None)
}
(false, true) => {
let (a, b) = mk(start + 1);
(None, Some(mode6_chan_sse(fixed, c, a, b)))
}
(false, false) => (None, None),
};
for d in [-1i32, 1] {
let cur = if which == 0 { (*q0)[c] } else { (*q1)[c] };
let nv = cur as i32 + d;
if nv < 0 || nv > 127 {
continue;
}
let cand = match (d, if d < 0 { cand_lo } else { cand_hi }) {
(_, Some(v)) if cur == start => v,
_ => {
let (a, b) = mk(nv as u8);
mode6_chan_sse(fixed, c, a, b)
}
};
if d < 0 {
cand_lo = None;
} else {
cand_hi = None;
}
let total = *err - ce[c] + cand;
if total < *err {
*err = total;
ce[c] = cand;
moved[c] = true;
if which == 0 {
(*q0)[c] = nv as u8;
} else {
(*q1)[c] = nv as u8;
}
}
}
}
}
active = moved;
if *err >= prev {
break;
}
}
}
fn score_bc1(block: &[u8; 8], recent: &[[u8; 8]]) -> f32 {
let key = u64::from_le_bytes(*block);
let (lo, hi) = (key as u32, (key >> 32) as u32);
let mut best = 0f32;
for r in recent {
let rk = u64::from_le_bytes(*r);
if rk == key {
return SAVE_WHOLE;
}
if (rk >> 32) as u32 == hi || rk as u32 == lo {
best = SAVE_PART;
}
}
best
}
fn score_bc7(block: &[u8; 16], recent: &[([u8; 16], bool)]) -> f32 {
let key = u128::from_le_bytes(*block);
let (lo, hi) = (key as u64, (key >> 64) as u64);
let mut best = 0f32;
for (r, _) in recent {
let rk = u128::from_le_bytes(*r);
if rk == key {
return SAVE_WHOLE16;
}
if (rk >> 64) as u64 == hi || rk as u64 == lo {
best = SAVE_HALF8;
}
}
best
}