#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_lossless,
clippy::similar_names,
clippy::too_many_arguments,
clippy::too_many_lines,
clippy::doc_markdown,
clippy::many_single_char_names
)]
use crate::sprite::DrawTarget;
#[inline]
fn mulshr16(a: i32, d: i32) -> i32 {
((i64::from(a) * i64::from(d)) >> 16) as i32
}
#[inline]
fn shldiv16(a: i32, b: i32) -> i32 {
((i64::from(a) << 16) / i64::from(b)) as i32
}
pub fn drawtile(
target: &mut DrawTarget<'_>,
tile_pixels: &[i32],
tile_pitch_bytes: i32,
tx: i32,
ty: i32,
tcx: i32,
tcy: i32,
sx: i32,
sy: i32,
xz: i32,
yz: i32,
black: i32,
white: i32,
) {
if tile_pixels.is_empty() || xz == 0 || yz == 0 {
return;
}
let sx0 = sx.wrapping_sub(mulshr16(tcx, xz));
let sx1 = sx0.wrapping_add(xz.wrapping_mul(tx));
let sy0 = sy.wrapping_sub(mulshr16(tcy, yz));
let sy1 = sy0.wrapping_add(yz.wrapping_mul(ty));
let xres = target.width as i32;
let yres = target.height as i32;
let x0 = ((sx0 + 65535) >> 16).max(0);
let x1 = ((sx1 + 65535) >> 16).min(xres);
let y0 = ((sy0 + 65535) >> 16).max(0);
let y1 = ((sy1 + 65535) >> 16).min(yres);
if x0 >= x1 || y0 >= y1 {
return;
}
let ui = shldiv16(65536, xz);
let u = mulshr16(-sx0, ui);
let vi = shldiv16(65536, yz);
let v = mulshr16(-sy0, vi);
let pitch_pixels = target.pitch_pixels;
let tile_pitch_pixels = (tile_pitch_bytes >> 2) as usize;
if (black ^ white) & 0x00ff_0000_u32.wrapping_shl(8) as i32 == 0
&& (black ^ white) & (0xff_u32 << 24) as i32 == 0
{
if xz == 32768 && yz == 32768 {
for y in y0..y1 {
let vv = y.wrapping_mul(vi).wrapping_add(v);
let row_pixel = (y as usize) * pitch_pixels;
let plc_pixel = (((x0.wrapping_mul(ui).wrapping_add(u)) >> 16) as usize)
+ ((vv >> 16) as usize) * tile_pitch_pixels;
for x in x0..x1 {
let k = (x - x0) as usize;
let ta = tile_pixels[plc_pixel + k * 2] as u32;
let tb = tile_pixels[plc_pixel + k * 2 + 1] as u32;
let ba = tile_pixels[plc_pixel + k * 2 + tile_pitch_pixels] as u32;
let bb = tile_pixels[plc_pixel + k * 2 + tile_pitch_pixels + 1] as u32;
let mut out: u32 = 0;
for b in 0..4u32 {
let va = (ta >> (b * 8)) & 0xff;
let vb = (tb >> (b * 8)) & 0xff;
let va_avg = (va + vb + 1) >> 1;
let v2a = (ba >> (b * 8)) & 0xff;
let v2b = (bb >> (b * 8)) & 0xff;
let vb_avg = (v2a + v2b + 1) >> 1;
let avg2 = (va_avg + vb_avg + 1) >> 1;
out |= avg2 << (b * 8);
}
unsafe { target.fb_write(row_pixel + x as usize, out) };
}
}
} else {
let plc = x0.wrapping_mul(ui).wrapping_add(u);
for y in y0..y1 {
let vv = y.wrapping_mul(vi).wrapping_add(v);
let row_pixel = (y as usize) * pitch_pixels;
let j_pixel = ((vv >> 16) as usize) * tile_pitch_pixels;
let mut uu = plc;
for x in x0..x1 {
let src = tile_pixels[j_pixel + ((uu >> 16) as usize)];
unsafe { target.fb_write(row_pixel + x as usize, src as u32) };
uu = uu.wrapping_add(ui);
}
}
}
} else {
let mut bw_scale = [0i16; 4];
let mut bk = [0i32; 4];
for b in 0..4usize {
let bl = (black >> (b * 8)) & 0xff;
let wh = (white >> (b * 8)) & 0xff;
let mut diff = wh - bl;
if diff == 255 {
diff = 256;
} else if diff == -255 {
diff = -256;
}
bw_scale[b] = (diff << 4) as i16;
bk[b] = bl;
}
for y in y0..y1 {
let vv = y.wrapping_mul(vi).wrapping_add(v);
let row_pixel = (y as usize) * pitch_pixels;
let j_pixel = ((vv >> 16) as usize) * tile_pitch_pixels;
let mut uu = x0.wrapping_mul(ui).wrapping_add(u);
for x in x0..x1 {
let src = tile_pixels[j_pixel + ((uu >> 16) as usize)];
uu = uu.wrapping_add(ui);
let mut mod_word = [0i16; 4];
let mut isat: u32 = 0;
for (b, mw) in mod_word.iter_mut().enumerate() {
let byte = (src >> (b * 8)) & 0xff;
let prod = ((byte << 4) * i32::from(bw_scale[b])) >> 16;
let m = (prod + bk[b]) as i16;
*mw = m;
let r = i32::from(m).clamp(0, 255);
isat |= ((r as u32) & 0xff) << (b * 8);
}
let i = isat as i32;
if (i.wrapping_add(0x0100_0000) as u32) < 0x0200_0000 {
if i < 0 {
unsafe { target.fb_write(row_pixel + x as usize, i as u32) };
}
continue;
}
let dst = unsafe { target.fb_read(row_pixel + x as usize) } & 0x00ff_ffff;
let alpha_shifted = i32::from(mod_word[3]) << 4;
let mut blended: u32 = 0;
for (b, &mw) in mod_word.iter().enumerate() {
let screen_byte = ((dst >> (b * 8)) & 0xff) as i32;
let delta = i32::from(mw) - screen_byte;
let scaled = ((delta << 4) * alpha_shifted) >> 16;
let r = (scaled + screen_byte).clamp(0, 255);
blended |= (r as u32) << (b * 8);
}
unsafe { target.fb_write(row_pixel + x as usize, blended) };
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn alloc_fb(w: u32, h: u32, fill_col: u32) -> (Vec<u32>, Vec<f32>) {
let n = (w * h) as usize;
(vec![fill_col; n], vec![f32::INFINITY; n])
}
fn make_target<'a>(fb: &'a mut [u32], zb: &'a mut [f32], w: u32, h: u32) -> DrawTarget<'a> {
DrawTarget::new(fb, zb, w as usize, w, h)
}
#[test]
fn one_x_no_alpha_copies_tile_pixels_unchanged() {
let tile: Vec<i32> = (0..16).map(|i| 0x80_000000_u32 as i32 + i).collect();
let (mut fb, mut zb) = alloc_fb(16, 16, 0);
let mut target = make_target(&mut fb, &mut zb, 16, 16);
drawtile(
&mut target,
&tile,
4 * 4,
4,
4,
2 << 16,
2 << 16,
8 << 16,
8 << 16,
1 << 16,
1 << 16,
0,
0,
);
assert_eq!(fb[7 * 16 + 7], 0x80_000005);
assert_eq!(fb[9 * 16 + 9], 0x80_00000f);
}
#[test]
fn half_zoom_averages_2x2_blocks() {
let tile: Vec<i32> = vec![0x80_ffffff_u32 as i32; 16];
let (mut fb, mut zb) = alloc_fb(16, 16, 0);
let mut target = make_target(&mut fb, &mut zb, 16, 16);
drawtile(
&mut target,
&tile,
4 * 4,
4,
4,
2 << 16,
2 << 16,
8 << 16,
8 << 16,
32768,
32768,
0,
0,
);
let touched: Vec<u32> = fb.iter().copied().filter(|&p| p != 0).collect();
assert!(!touched.is_empty(), "blit produced no pixels");
for p in touched {
assert_eq!(p, 0x80_ffffff, "averaged white tile must stay white");
}
}
#[test]
fn fully_offscreen_is_noop() {
let tile: Vec<i32> = vec![0x80_aabbcc_u32 as i32; 16];
let (mut fb, mut zb) = alloc_fb(16, 16, 0xdead_beef);
let mut target = make_target(&mut fb, &mut zb, 16, 16);
drawtile(
&mut target,
&tile,
16,
4,
4,
2 << 16,
2 << 16,
10000 << 16,
10000 << 16,
1 << 16,
1 << 16,
0,
0,
);
assert!(fb.iter().all(|&p| p == 0xdead_beef));
}
}