use crate::error::Result;
use crate::processing::dispatch::dispatch_simd;
#[cfg(target_arch = "x86_64")]
use crate::processing::x86::{
store_interleaved_ps, store_interleaved16_mask_ps, store_interleaved16_ps,
};
use crate::surface::Surface;
use super::{Buffer, read_pixels_f32};
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[cfg(target_arch = "aarch64")]
use std::arch::aarch64::*;
#[inline]
pub(crate) fn decode_small_float<const MANT_BITS: u32>(v: u32) -> f32 {
let mant_max = 1u32 << MANT_BITS;
let exp = (v >> MANT_BITS) & 0x1f;
let mant = v & (mant_max - 1);
if exp == 0 {
f32::from_bits(0x3800_0000) * (mant as f32 / mant_max as f32) } else if exp == 0x1f {
if mant == 0 { f32::INFINITY } else { f32::NAN }
} else {
let scale = f32::from_bits((exp + 112) << 23); scale * (1.0 + mant as f32 / mant_max as f32)
}
}
#[inline]
pub(crate) fn decode(word: u32) -> [f32; 3] {
let r = decode_small_float::<6>(word & 0x7ff);
let g = decode_small_float::<6>((word >> 11) & 0x7ff);
let b = decode_small_float::<5>((word >> 22) & 0x3ff);
[r, g, b]
}
pub fn load_b10g11r11_f32(surface: &Surface) -> Result<Buffer<f32>> {
profiling::scope!("load_b10g11r11_f32");
dispatch_simd! {
x86_64: {
avx512: load_b10g11r11_f32_avx512(surface),
avx2_fma: load_b10g11r11_f32_avx2_fma(surface),
sse4_1: load_b10g11r11_f32_sse4_1(surface),
},
aarch64: {
neon: load_b10g11r11_f32_neon(surface),
},
}
load_b10g11r11_f32_serial(surface)
}
#[doc(hidden)]
pub fn load_b10g11r11_f32_serial(surface: &Surface) -> Result<Buffer<f32>> {
profiling::scope!("load_b10g11r11_f32_serial");
read_pixels_f32(surface, 1, 4, |bytes, lanes| {
let word = u32::from_le_bytes(<[u8; 4]>::try_from(bytes).expect("4-byte pixel"));
let [r, g, b] = decode(word);
lanes[0] = r;
lanes[1] = g;
lanes[2] = b;
})
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
const F32_INF_BITS: u32 = 0x7f80_0000;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
const F32_NAN_BITS: u32 = 0x7fc0_0000;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
const F32_2POW_M14_BITS: u32 = 0x3800_0000;
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn decode_codes_sse4_1<const M: i32>(codes: __m128i) -> __m128 {
let mant_max = 1i32 << M;
let mant = _mm_and_si128(codes, _mm_set1_epi32(mant_max - 1));
let exp = _mm_and_si128(_mm_srli_epi32::<M>(codes), _mm_set1_epi32(0x1f));
let mant_f = _mm_cvtepi32_ps(mant);
let frac = _mm_mul_ps(mant_f, _mm_set1_ps(1.0 / mant_max as f32));
let denorm = _mm_mul_ps(_mm_set1_ps(f32::from_bits(F32_2POW_M14_BITS)), frac);
let scale = _mm_castsi128_ps(_mm_slli_epi32::<23>(_mm_add_epi32(
exp,
_mm_set1_epi32(112),
)));
let normal = _mm_mul_ps(scale, _mm_add_ps(_mm_set1_ps(1.0), frac));
let is_denorm = _mm_castsi128_ps(_mm_cmpeq_epi32(exp, _mm_setzero_si128()));
let val = _mm_blendv_ps(normal, denorm, is_denorm);
let is_max = _mm_castsi128_ps(_mm_cmpeq_epi32(exp, _mm_set1_epi32(0x1f)));
let mant_nonzero = _mm_castsi128_ps(_mm_cmpgt_epi32(mant, _mm_setzero_si128()));
let special = _mm_blendv_ps(
_mm_castsi128_ps(_mm_set1_epi32(F32_INF_BITS as i32)),
_mm_castsi128_ps(_mm_set1_epi32(F32_NAN_BITS as i32)),
mant_nonzero,
);
_mm_blendv_ps(val, special, is_max)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn decode_words_sse4_1(words: __m128i) -> (__m128, __m128, __m128) {
let r = _mm_and_si128(words, _mm_set1_epi32(0x7ff));
let g = _mm_and_si128(_mm_srli_epi32::<11>(words), _mm_set1_epi32(0x7ff));
let b = _mm_and_si128(_mm_srli_epi32::<22>(words), _mm_set1_epi32(0x3ff));
unsafe {
(
decode_codes_sse4_1::<6>(r),
decode_codes_sse4_1::<6>(g),
decode_codes_sse4_1::<5>(b),
)
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn store_pixels_sse4_1(r: __m128, g: __m128, b: __m128, dst: *mut f32, count: usize) {
unsafe {
let a = _mm_set1_ps(1.0);
store_interleaved_ps(r, g, b, a, dst, count);
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn load_row_sse4_1(row: &[u8], dst: *mut f32) {
debug_assert_eq!(row.len() % 4, 0);
let mut x = 0;
while x + 16 <= row.len() {
let words = unsafe { _mm_loadu_si128(row.as_ptr().add(x).cast()) };
let (r, g, b) = unsafe { decode_words_sse4_1(words) };
unsafe { store_pixels_sse4_1(r, g, b, dst.add(x), 4) };
x += 16;
}
if x < row.len() {
let count = (row.len() - x) / 4;
let mut padded = [0u8; 16];
padded[..count * 4].copy_from_slice(&row[x..]);
let words = unsafe { _mm_loadu_si128(padded.as_ptr().cast()) };
let (r, g, b) = unsafe { decode_words_sse4_1(words) };
unsafe { store_pixels_sse4_1(r, g, b, dst.add(x), count) };
}
}
#[doc(hidden)]
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse4.1")]
pub unsafe fn load_b10g11r11_f32_sse4_1(surface: &Surface) -> Result<Buffer<f32>> {
profiling::scope!("load_b10g11r11_f32_sse4_1");
unsafe {
super::load_packed_rows(surface, |row, dst| {
load_row_sse4_1(row, dst)
})
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2,fma")]
#[inline]
unsafe fn decode_codes_avx2<const M: i32>(codes: __m256i) -> __m256 {
let mant_max = 1i32 << M;
let mant = _mm256_and_si256(codes, _mm256_set1_epi32(mant_max - 1));
let exp = _mm256_and_si256(_mm256_srli_epi32::<M>(codes), _mm256_set1_epi32(0x1f));
let mant_f = _mm256_cvtepi32_ps(mant);
let frac = _mm256_mul_ps(mant_f, _mm256_set1_ps(1.0 / mant_max as f32));
let denorm = _mm256_mul_ps(_mm256_set1_ps(f32::from_bits(F32_2POW_M14_BITS)), frac);
let scale = _mm256_castsi256_ps(_mm256_slli_epi32::<23>(_mm256_add_epi32(
exp,
_mm256_set1_epi32(112),
)));
let normal = _mm256_fmadd_ps(scale, frac, scale);
let is_denorm = _mm256_castsi256_ps(_mm256_cmpeq_epi32(exp, _mm256_setzero_si256()));
let val = _mm256_blendv_ps(normal, denorm, is_denorm);
let is_max = _mm256_castsi256_ps(_mm256_cmpeq_epi32(exp, _mm256_set1_epi32(0x1f)));
let mant_nonzero = _mm256_castsi256_ps(_mm256_cmpgt_epi32(mant, _mm256_setzero_si256()));
let special = _mm256_blendv_ps(
_mm256_castsi256_ps(_mm256_set1_epi32(F32_INF_BITS as i32)),
_mm256_castsi256_ps(_mm256_set1_epi32(F32_NAN_BITS as i32)),
mant_nonzero,
);
_mm256_blendv_ps(val, special, is_max)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2,fma")]
#[inline]
unsafe fn load_row_avx2_fma(row: &[u8], dst: *mut f32) {
unsafe {
let mut x = 0usize;
while x + 32 <= row.len() {
let words = _mm256_loadu_si256(row.as_ptr().add(x) as *const __m256i);
let r = _mm256_and_si256(words, _mm256_set1_epi32(0x7ff));
let g = _mm256_and_si256(_mm256_srli_epi32::<11>(words), _mm256_set1_epi32(0x7ff));
let b = _mm256_and_si256(_mm256_srli_epi32::<22>(words), _mm256_set1_epi32(0x3ff));
let rf = decode_codes_avx2::<6>(r);
let gf = decode_codes_avx2::<6>(g);
let bf = decode_codes_avx2::<5>(b);
store_pixels_sse4_1(
_mm256_castps256_ps128(rf),
_mm256_castps256_ps128(gf),
_mm256_castps256_ps128(bf),
dst.add(x),
4,
);
store_pixels_sse4_1(
_mm256_extractf128_ps::<1>(rf),
_mm256_extractf128_ps::<1>(gf),
_mm256_extractf128_ps::<1>(bf),
dst.add(x + 16),
4,
);
x += 32;
}
load_row_sse4_1(&row[x..], dst.add(x));
}
}
#[doc(hidden)]
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2,fma")]
pub unsafe fn load_b10g11r11_f32_avx2_fma(surface: &Surface) -> Result<Buffer<f32>> {
profiling::scope!("load_b10g11r11_f32_avx2_fma");
unsafe {
super::load_packed_rows(surface, |row, dst| {
load_row_avx2_fma(row, dst)
})
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx512f,avx512vl,avx512bw")]
#[inline]
unsafe fn decode_codes_avx512<const M: u32>(codes: __m512i) -> __m512 {
let mant_max = 1i32 << M;
let mant = _mm512_and_si512(codes, _mm512_set1_epi32(mant_max - 1));
let exp = _mm512_and_si512(_mm512_srli_epi32::<M>(codes), _mm512_set1_epi32(0x1f));
let mant_f = _mm512_cvtepi32_ps(mant);
let frac = _mm512_mul_ps(mant_f, _mm512_set1_ps(1.0 / mant_max as f32));
let denorm = _mm512_mul_ps(_mm512_set1_ps(f32::from_bits(F32_2POW_M14_BITS)), frac);
let scale = _mm512_castsi512_ps(_mm512_slli_epi32::<23>(_mm512_add_epi32(
exp,
_mm512_set1_epi32(112),
)));
let normal = _mm512_fmadd_ps(scale, frac, scale);
let is_denorm = _mm512_cmpeq_epi32_mask(exp, _mm512_setzero_si512());
let val = _mm512_mask_blend_ps(is_denorm, normal, denorm);
let is_max = _mm512_cmpeq_epi32_mask(exp, _mm512_set1_epi32(0x1f));
let mant_nonzero = _mm512_cmpgt_epi32_mask(mant, _mm512_setzero_si512());
let special = _mm512_mask_blend_ps(
mant_nonzero,
_mm512_castsi512_ps(_mm512_set1_epi32(F32_INF_BITS as i32)),
_mm512_castsi512_ps(_mm512_set1_epi32(F32_NAN_BITS as i32)),
);
_mm512_mask_blend_ps(is_max, val, special)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx512f,avx512vl,avx512bw")]
#[inline]
unsafe fn decode16_words_avx512(words: __m512i) -> (__m512, __m512, __m512) {
let r = _mm512_and_si512(words, _mm512_set1_epi32(0x7ff));
let g = _mm512_and_si512(_mm512_srli_epi32::<11>(words), _mm512_set1_epi32(0x7ff));
let b = _mm512_and_si512(_mm512_srli_epi32::<22>(words), _mm512_set1_epi32(0x3ff));
unsafe {
(
decode_codes_avx512::<6>(r),
decode_codes_avx512::<6>(g),
decode_codes_avx512::<5>(b),
)
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx512f,avx512vl,avx512bw")]
#[inline]
unsafe fn load_row_avx512(row: &[u8], dst: *mut f32) {
unsafe {
let one = _mm512_set1_ps(1.0);
let mut x = 0usize;
while x + 64 <= row.len() {
let words = _mm512_loadu_si512(row.as_ptr().add(x) as *const __m512i);
let (rf, gf, bf) = decode16_words_avx512(words);
store_interleaved16_ps(rf, gf, bf, one, dst.add(x));
x += 64;
}
let rem = (row.len() - x) / 4;
if rem > 0 {
let words =
_mm512_maskz_loadu_epi32((1u16 << rem) - 1, row.as_ptr().add(x) as *const i32);
let (rf, gf, bf) = decode16_words_avx512(words);
store_interleaved16_mask_ps(rf, gf, bf, one, dst.add(x), rem);
}
}
}
#[doc(hidden)]
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx512f,avx512vl,avx512bw")]
pub unsafe fn load_b10g11r11_f32_avx512(surface: &Surface) -> Result<Buffer<f32>> {
profiling::scope!("load_b10g11r11_f32_avx512");
unsafe {
super::load_packed_rows(surface, |row, dst| {
load_row_avx512(row, dst)
})
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[inline]
unsafe fn decode_codes_neon<const M: i32>(codes: uint32x4_t) -> float32x4_t {
let mant_max = 1i32 << M;
let mant = vandq_u32(codes, vdupq_n_u32((mant_max - 1) as u32));
let exp = vandq_u32(vshrq_n_u32::<M>(codes), vdupq_n_u32(0x1f));
let mant_f = vcvtq_f32_u32(mant);
let frac = vmulq_f32(mant_f, vdupq_n_f32(1.0 / mant_max as f32));
let denorm = vmulq_f32(vdupq_n_f32(f32::from_bits(F32_2POW_M14_BITS)), frac);
let scale = vreinterpretq_f32_u32(vshlq_n_u32::<23>(vaddq_u32(exp, vdupq_n_u32(112))));
let normal = vfmaq_f32(scale, scale, frac);
let is_denorm = vceqq_u32(exp, vdupq_n_u32(0));
let val = vbslq_f32(is_denorm, denorm, normal);
let is_max = vceqq_u32(exp, vdupq_n_u32(0x1f));
let mant_nonzero = vtstq_u32(mant, mant);
let inf = vreinterpretq_f32_u32(vdupq_n_u32(F32_INF_BITS));
let nan = vreinterpretq_f32_u32(vdupq_n_u32(F32_NAN_BITS));
let special = vbslq_f32(mant_nonzero, nan, inf);
vbslq_f32(is_max, special, val)
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[inline]
unsafe fn decode_words_neon(words: uint32x4_t) -> float32x4x4_t {
let r = vandq_u32(words, vdupq_n_u32(0x7ff));
let g = vandq_u32(vshrq_n_u32::<11>(words), vdupq_n_u32(0x7ff));
let b = vandq_u32(vshrq_n_u32::<22>(words), vdupq_n_u32(0x3ff));
unsafe {
float32x4x4_t(
decode_codes_neon::<6>(r),
decode_codes_neon::<6>(g),
decode_codes_neon::<5>(b),
vdupq_n_f32(1.0),
)
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[inline]
unsafe fn load_row_neon(row: &[u8], dst: *mut f32) {
unsafe {
let mut x = 0usize;
while x + 16 <= row.len() {
let words = vreinterpretq_u32_u8(vld1q_u8(row.as_ptr().add(x)));
let decoded = decode_words_neon(words);
vst4q_f32(dst.add(x), decoded);
x += 16;
}
if x < row.len() {
let rem = (row.len() - x) / 4;
let mut tmp = [0u8; 16];
tmp[..rem * 4].copy_from_slice(&row[x..x + rem * 4]);
let words = vreinterpretq_u32_u8(vld1q_u8(tmp.as_ptr()));
let decoded = decode_words_neon(words);
let mut scratch = [0.0f32; 16];
vst4q_f32(scratch.as_mut_ptr(), decoded);
std::ptr::copy_nonoverlapping(scratch.as_ptr(), dst.add(x), rem * 4);
}
}
}
#[doc(hidden)]
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
pub unsafe fn load_b10g11r11_f32_neon(surface: &Surface) -> Result<Buffer<f32>> {
profiling::scope!("load_b10g11r11_f32_neon");
unsafe {
super::load_packed_rows(surface, |row, dst| {
load_row_neon(row, dst)
})
}
}
#[cfg(test)]
mod simd_tests {
use super::*;
use crate::alpha::AlphaMode;
#[cfg(target_arch = "x86_64")]
use crate::processing::x86::has_avx512;
use crate::surface::{ColorSpace, Surface};
fn b10_surface(data: Vec<u8>, width: u32, height: u32, stride: u32) -> Surface {
Surface {
data,
width,
height,
depth: 1,
stride,
slice_stride: 0,
format: ktx2::Format::B10G11R11_UFLOAT_PACK32,
color_space: ColorSpace::Linear,
alpha: AlphaMode::Opaque,
}
}
fn assert_load_matches(simd: &[[f32; 4]], surface: &Surface) {
let reference = load_b10g11r11_f32_serial(surface).unwrap().pixels;
assert_eq!(simd.len(), reference.len(), "pixel count");
for (i, (got, want)) in simd.iter().zip(&reference).enumerate() {
for c in 0..4 {
let g = got[c];
let w = want[c];
if w.is_finite() {
assert!(
(g - w).abs() <= 1e-4 * (1.0 + w.abs()),
"pixel {i} lane {c}: got {g} want {w}"
);
} else {
assert!(
g.is_nan() == w.is_nan() && g.is_finite() == w.is_finite(),
"pixel {i} lane {c}: got {g} want {w}"
);
}
}
}
}
fn sweep_surface() -> Surface {
let n = 2048u32; let mut data = Vec::with_capacity((n * 4) as usize);
for i in 0..n {
let r = i & 0x7ff;
let g = i & 0x7ff;
let b = i & 0x3ff;
let word = r | (g << 11) | (b << 22);
data.extend_from_slice(&word.to_le_bytes());
}
b10_surface(data, n, 1, n * 4)
}
fn tail_stride_surface(width: u32) -> Surface {
let h = 3u32;
let row_bytes = width * 4;
let stride = row_bytes + 12; let mut data = vec![0u8; (stride * h) as usize];
for y in 0..h {
for xp in 0..width {
let seed = y.wrapping_mul(9973).wrapping_add(xp.wrapping_mul(7));
let r = seed & 0x7ff;
let g = seed.wrapping_mul(3) & 0x7ff;
let b = seed.wrapping_mul(5) & 0x3ff;
let word = r | (g << 11) | (b << 22);
let off = (y * stride + xp * 4) as usize;
data[off..off + 4].copy_from_slice(&word.to_le_bytes());
}
let pad_off = (y * stride + row_bytes) as usize;
for byte in &mut data[pad_off..pad_off + 12] {
*byte = 0xAB;
}
}
b10_surface(data, width, h, stride)
}
#[test]
#[cfg(target_arch = "x86_64")]
fn sse4_load_sweep_matches_scalar() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let s = sweep_surface();
let simd = unsafe { load_b10g11r11_f32_sse4_1(&s).unwrap() };
assert_load_matches(&simd.pixels, &s);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn sse4_load_tails_match_scalar() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
for width in 1..=17u32 {
let s = tail_stride_surface(width);
let simd = unsafe { load_b10g11r11_f32_sse4_1(&s).unwrap() };
assert_load_matches(&simd.pixels, &s);
}
}
#[test]
#[cfg(target_arch = "x86_64")]
fn avx2_load_sweep_matches_scalar() {
if !(is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma")) {
return;
}
let s = sweep_surface();
let simd = unsafe { load_b10g11r11_f32_avx2_fma(&s).unwrap() };
assert_load_matches(&simd.pixels, &s);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn avx2_load_tails_match_scalar() {
if !(is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma")) {
return;
}
for width in 1..=25u32 {
let s = tail_stride_surface(width);
let simd = unsafe { load_b10g11r11_f32_avx2_fma(&s).unwrap() };
assert_load_matches(&simd.pixels, &s);
}
}
#[test]
#[cfg(target_arch = "x86_64")]
fn avx512_load_sweep_matches_scalar() {
if !has_avx512() {
return;
}
let s = sweep_surface();
let simd = unsafe { load_b10g11r11_f32_avx512(&s).unwrap() };
assert_load_matches(&simd.pixels, &s);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn avx512_load_tails_match_scalar() {
if !has_avx512() {
return;
}
for width in 1..=49u32 {
let s = tail_stride_surface(width);
let simd = unsafe { load_b10g11r11_f32_avx512(&s).unwrap() };
assert_load_matches(&simd.pixels, &s);
}
}
#[test]
#[cfg(target_arch = "aarch64")]
fn neon_load_sweep_matches_scalar() {
if !std::arch::is_aarch64_feature_detected!("neon") {
return;
}
let s = sweep_surface();
let simd = unsafe { load_b10g11r11_f32_neon(&s).unwrap() };
assert_load_matches(&simd.pixels, &s);
}
#[test]
#[cfg(target_arch = "aarch64")]
fn neon_load_tails_match_scalar() {
if !std::arch::is_aarch64_feature_detected!("neon") {
return;
}
for width in 1..=17u32 {
let s = tail_stride_surface(width);
let simd = unsafe { load_b10g11r11_f32_neon(&s).unwrap() };
assert_load_matches(&simd.pixels, &s);
}
}
}