refactor: use typed slices for float predictor output
Change predict_f32/f16/f64 to take &mut [[u8; N]] output instead of &mut [u8], making sample alignment a compile-time guarantee rather than a silent runtime truncation. The caller uses as_chunks_mut at the boundary, with a debug_assert that the remainder is empty. This follows 197g's suggestion in #376 to move alignment validation to the caller via typed slices, keeping the predictor functions clean for future SIMD dispatch.
This commit is contained in:
1 parent
6a3282e9f2
commit
149492ca6e
2 files changed
+55
-39
No files matched your search
+12
-3
@@ -1227,9 +1227,18 @@ impl Image {
|
|||||||
|
|
||||||
let row = &mut row[..data_row_bytes];
|
let row = &mut row[..data_row_bytes];
|
||||||
match color_type.bit_depth() {
|
match color_type.bit_depth() {
|
||||||
16 => predict_f16(&mut encoded, row, samples),
|
16 => {
|
||||||
32 => predict_f32(&mut encoded, row, samples),
|
let (out, _) = row.as_chunks_mut::<2>();
|
||||||
64 => predict_f64(&mut encoded, row, samples),
|
predict_f16(&mut encoded, out, samples);
|
||||||
|
}
|
||||||
|
32 => {
|
||||||
|
let (out, _) = row.as_chunks_mut::<4>();
|
||||||
|
predict_f32(&mut encoded, out, samples);
|
||||||
|
}
|
||||||
|
64 => {
|
||||||
|
let (out, _) = row.as_chunks_mut::<8>();
|
||||||
|
predict_f64(&mut encoded, out, samples);
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
|
|||||||
+43
-36
@@ -397,17 +397,17 @@ fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: u16) {
|
|||||||
/// The TIFF float predictor splits each N-byte sample into N separate byte planes within the row,
|
/// The TIFF float predictor splits each N-byte sample into N separate byte planes within the row,
|
||||||
/// then delta-encodes each plane. Decoding reverses this: prefix-sum to undo the deltas, then
|
/// then delta-encodes each plane. Decoding reverses this: prefix-sum to undo the deltas, then
|
||||||
/// transpose the N planes back into packed samples.
|
/// transpose the N planes back into packed samples.
|
||||||
fn predict_f32(input: &mut [u8], output: &mut [u8], samples: u16) {
|
fn predict_f32(input: &mut [u8], output: &mut [[u8; 4]], samples: u16) {
|
||||||
one_byte_predict_generic(input, usize::from(samples));
|
one_byte_predict_generic(input, usize::from(samples));
|
||||||
deinterleave_f32_planes(input, output);
|
deinterleave_f32_planes(input, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn predict_f16(input: &mut [u8], output: &mut [u8], samples: u16) {
|
fn predict_f16(input: &mut [u8], output: &mut [[u8; 2]], samples: u16) {
|
||||||
one_byte_predict_generic(input, usize::from(samples));
|
one_byte_predict_generic(input, usize::from(samples));
|
||||||
deinterleave_f16_planes(input, output);
|
deinterleave_f16_planes(input, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn predict_f64(input: &mut [u8], output: &mut [u8], samples: u16) {
|
fn predict_f64(input: &mut [u8], output: &mut [[u8; 8]], samples: u16) {
|
||||||
one_byte_predict_generic(input, usize::from(samples));
|
one_byte_predict_generic(input, usize::from(samples));
|
||||||
deinterleave_f64_planes(input, output);
|
deinterleave_f64_planes(input, output);
|
||||||
}
|
}
|
||||||
@@ -439,8 +439,8 @@ fn one_byte_predict_generic(buf: &mut [u8], stride: usize) {
|
|||||||
/// Each function splits the input into equal-sized plane slices, then gathers one byte per plane
|
/// Each function splits the input into equal-sized plane slices, then gathers one byte per plane
|
||||||
/// per sample. Using `from_be_bytes` for endian conversion lets LLVM recognise the byte-swap idiom
|
/// per sample. Using `from_be_bytes` for endian conversion lets LLVM recognise the byte-swap idiom
|
||||||
/// and emit efficient code (bswap or movbe).
|
/// and emit efficient code (bswap or movbe).
|
||||||
fn deinterleave_f32_planes(input: &[u8], output: &mut [u8]) {
|
fn deinterleave_f32_planes(input: &[u8], output: &mut [[u8; 4]]) {
|
||||||
let n = output.len() / 4;
|
let n = output.len();
|
||||||
if n == 0 || input.len() < n * 4 {
|
if n == 0 || input.len() < n * 4 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -448,13 +448,12 @@ fn deinterleave_f32_planes(input: &[u8], output: &mut [u8]) {
|
|||||||
let (p1, rest) = rest.split_at(n);
|
let (p1, rest) = rest.split_at(n);
|
||||||
let (p2, p3) = rest.split_at(n);
|
let (p2, p3) = rest.split_at(n);
|
||||||
|
|
||||||
// Batch 4 samples (16 output bytes) per iteration. Fixed-size array slicing eliminates
|
// Batch 4 samples per iteration. Fixed-size array slicing eliminates bounds checks.
|
||||||
// bounds checks in the inner loop.
|
let (out_chunks, out_rest) = output.as_chunks_mut::<4>();
|
||||||
let (out_chunks, out_rest) = output.as_chunks_mut::<16>();
|
let (p0_chunks, _) = p0.as_chunks::<4>();
|
||||||
let (p0_chunks, p0_rest) = p0.as_chunks::<4>();
|
let (p1_chunks, _) = p1.as_chunks::<4>();
|
||||||
let (p1_chunks, p1_rest) = p1.as_chunks::<4>();
|
let (p2_chunks, _) = p2.as_chunks::<4>();
|
||||||
let (p2_chunks, p2_rest) = p2.as_chunks::<4>();
|
let (p3_chunks, _) = p3.as_chunks::<4>();
|
||||||
let (p3_chunks, p3_rest) = p3.as_chunks::<4>();
|
|
||||||
|
|
||||||
for ((((out, b0), b1), b2), b3) in out_chunks
|
for ((((out, b0), b1), b2), b3) in out_chunks
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
@@ -467,34 +466,34 @@ fn deinterleave_f32_planes(input: &[u8], output: &mut [u8]) {
|
|||||||
let s1 = u32::from_be_bytes([b0[1], b1[1], b2[1], b3[1]]).to_ne_bytes();
|
let s1 = u32::from_be_bytes([b0[1], b1[1], b2[1], b3[1]]).to_ne_bytes();
|
||||||
let s2 = u32::from_be_bytes([b0[2], b1[2], b2[2], b3[2]]).to_ne_bytes();
|
let s2 = u32::from_be_bytes([b0[2], b1[2], b2[2], b3[2]]).to_ne_bytes();
|
||||||
let s3 = u32::from_be_bytes([b0[3], b1[3], b2[3], b3[3]]).to_ne_bytes();
|
let s3 = u32::from_be_bytes([b0[3], b1[3], b2[3], b3[3]]).to_ne_bytes();
|
||||||
*out = [
|
out[0] = s0;
|
||||||
s0[0], s0[1], s0[2], s0[3], s1[0], s1[1], s1[2], s1[3], s2[0], s2[1], s2[2], s2[3],
|
out[1] = s1;
|
||||||
s3[0], s3[1], s3[2], s3[3],
|
out[2] = s2;
|
||||||
];
|
out[3] = s3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remaining 0–3 samples.
|
// Remaining 0–3 samples.
|
||||||
for (j, out) in out_rest.chunks_exact_mut(4).enumerate() {
|
let base = out_chunks.len() * 4;
|
||||||
out.copy_from_slice(
|
for (j, out) in out_rest.iter_mut().enumerate() {
|
||||||
&u32::from_be_bytes([p0_rest[j], p1_rest[j], p2_rest[j], p3_rest[j]]).to_ne_bytes(),
|
*out = u32::from_be_bytes([p0[base + j], p1[base + j], p2[base + j], p3[base + j]])
|
||||||
);
|
.to_ne_bytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinterleave_f16_planes(input: &[u8], output: &mut [u8]) {
|
fn deinterleave_f16_planes(input: &[u8], output: &mut [[u8; 2]]) {
|
||||||
let n = output.len() / 2;
|
let n = output.len();
|
||||||
if n == 0 || input.len() < n * 2 {
|
if n == 0 || input.len() < n * 2 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let (p0, p1) = input.split_at(n);
|
let (p0, p1) = input.split_at(n);
|
||||||
|
|
||||||
for (out, (b0, b1)) in output.chunks_exact_mut(2).zip(p0.iter().zip(p1)) {
|
for (out, (b0, b1)) in output.iter_mut().zip(p0.iter().zip(p1)) {
|
||||||
out.copy_from_slice(&u16::from_be_bytes([*b0, *b1]).to_ne_bytes());
|
*out = u16::from_be_bytes([*b0, *b1]).to_ne_bytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinterleave_f64_planes(input: &[u8], output: &mut [u8]) {
|
fn deinterleave_f64_planes(input: &[u8], output: &mut [[u8; 8]]) {
|
||||||
let n = output.len() / 8;
|
let n = output.len();
|
||||||
if n == 0 || input.len() < n * 8 {
|
if n == 0 || input.len() < n * 8 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -506,8 +505,7 @@ fn deinterleave_f64_planes(input: &[u8], output: &mut [u8]) {
|
|||||||
let (p5, rest) = rest.split_at(n);
|
let (p5, rest) = rest.split_at(n);
|
||||||
let (p6, p7) = rest.split_at(n);
|
let (p6, p7) = rest.split_at(n);
|
||||||
|
|
||||||
// Zip all 8 planes with the output to avoid indexed access and bounds checks.
|
for (out, (((((((b0, b1), b2), b3), b4), b5), b6), b7)) in output.iter_mut().zip(
|
||||||
let iter = output.chunks_exact_mut(8).zip(
|
|
||||||
p0.iter()
|
p0.iter()
|
||||||
.zip(p1)
|
.zip(p1)
|
||||||
.zip(p2)
|
.zip(p2)
|
||||||
@@ -516,11 +514,8 @@ fn deinterleave_f64_planes(input: &[u8], output: &mut [u8]) {
|
|||||||
.zip(p5)
|
.zip(p5)
|
||||||
.zip(p6)
|
.zip(p6)
|
||||||
.zip(p7),
|
.zip(p7),
|
||||||
);
|
) {
|
||||||
for (out, (((((((b0, b1), b2), b3), b4), b5), b6), b7)) in iter {
|
*out = u64::from_be_bytes([*b0, *b1, *b2, *b3, *b4, *b5, *b6, *b7]).to_ne_bytes();
|
||||||
out.copy_from_slice(
|
|
||||||
&u64::from_be_bytes([*b0, *b1, *b2, *b3, *b4, *b5, *b6, *b7]).to_ne_bytes(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,9 +545,21 @@ fn fix_endianness_and_predict(
|
|||||||
scratch.resize(buf.len(), 0);
|
scratch.resize(buf.len(), 0);
|
||||||
scratch.copy_from_slice(buf);
|
scratch.copy_from_slice(buf);
|
||||||
match bit_depth {
|
match bit_depth {
|
||||||
16 => predict_f16(scratch, buf, samples),
|
16 => {
|
||||||
32 => predict_f32(scratch, buf, samples),
|
let (output, rest) = buf.as_chunks_mut::<2>();
|
||||||
64 => predict_f64(scratch, buf, samples),
|
debug_assert!(rest.is_empty(), "f16 row buffer not a multiple of 2 bytes");
|
||||||
|
predict_f16(scratch, output, samples);
|
||||||
|
}
|
||||||
|
32 => {
|
||||||
|
let (output, rest) = buf.as_chunks_mut::<4>();
|
||||||
|
debug_assert!(rest.is_empty(), "f32 row buffer not a multiple of 4 bytes");
|
||||||
|
predict_f32(scratch, output, samples);
|
||||||
|
}
|
||||||
|
64 => {
|
||||||
|
let (output, rest) = buf.as_chunks_mut::<8>();
|
||||||
|
debug_assert!(rest.is_empty(), "f64 row buffer not a multiple of 8 bytes");
|
||||||
|
predict_f64(scratch, output, samples);
|
||||||
|
}
|
||||||
_ => unreachable!("Caller should have validated arguments. Please file a bug."),
|
_ => unreachable!("Caller should have validated arguments. Please file a bug."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user