#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CastDat {
pub col: i32,
pub dist: i32,
}
#[derive(Debug, Clone)]
pub struct ScanScratch {
pub radar: Vec<CastDat>,
pub angstart: Vec<isize>,
pub gscanptr: usize,
pub sky_cur_lng: i32,
pub sky_cur_dir: i32,
pub sky_off: i32,
pub lastx: Vec<i32>,
pub uurend: Vec<i32>,
pub uurend_half_stride: usize,
pub cf: Vec<crate::grouscan::CfType>,
pub gpz: [i32; 2],
pub gdz: [i32; 2],
pub gixy: [i32; 2],
pub gxmax: i32,
pub gi0: i32,
pub gi1: i32,
pub skycast: CastDat,
pub fog_col: i32,
pub foglut: Vec<i32>,
pub gcsub: [i64; 9],
pub sideshademode: bool,
}
impl ScanScratch {
#[must_use]
pub fn new_for_size(xres: u32, yres: u32, vsid: u32) -> Self {
let per_col_budget = std::cmp::max(6 * 256, (yres as usize) * 2);
let radar_cap = (xres as usize) * per_col_budget;
let angstart_cap = (xres as usize) * 4;
let half_stride = xres as usize;
let lastx_cap = std::cmp::max(yres, vsid) as usize;
Self {
radar: vec![CastDat::default(); radar_cap],
angstart: vec![0isize; angstart_cap],
gscanptr: 0,
sky_cur_lng: -1,
sky_cur_dir: 0,
sky_off: 0,
lastx: vec![0i32; lastx_cap],
uurend: vec![0i32; half_stride * 2],
uurend_half_stride: half_stride,
cf: vec![crate::grouscan::CfType::default(); crate::grouscan::CF_LEN],
gpz: [0; 2],
gdz: [0; 2],
gixy: [0; 2],
gxmax: 0,
gi0: 0,
gi1: 0,
skycast: CastDat::default(),
fog_col: 0,
foglut: Vec::new(),
gcsub: [0x00ff_00ff_00ff_00ff; 9],
sideshademode: false,
}
}
pub fn set_side_shades(&mut self, top: i8, bot: i8, left: i8, right: i8, up: i8, down: i8) {
let pack = |intensity: i8| -> i64 {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
let high = u64::from(intensity as u8) << 56;
#[allow(clippy::cast_possible_wrap)]
{
(high | 0x00ff_00ff_00ff_00ff_u64) as i64
}
};
self.gcsub[0] = 0x00ff_00ff_00ff_00ff;
self.gcsub[1] = 0x00ff_00ff_00ff_00ff;
self.gcsub[2] = pack(top);
self.gcsub[3] = pack(bot);
self.gcsub[4] = pack(left);
self.gcsub[5] = pack(right);
self.gcsub[6] = pack(up);
self.gcsub[7] = pack(down);
self.gcsub[8] = 0x00ff_00ff_00ff_00ff;
self.sideshademode =
top != 0 || bot != 0 || left != 0 || right != 0 || up != 0 || down != 0;
}
pub fn set_skycast(&mut self, col: i32, dist: i32) {
self.skycast = CastDat { col, dist };
}
pub fn set_fog(&mut self, col: i32, max_scan_dist: i32) {
if max_scan_dist <= 0 {
self.fog_col = 0;
self.foglut.clear();
return;
}
self.fog_col = col;
self.foglut = vec![32767; 2048];
let step = i32::MAX / max_scan_dist;
let mut acc: i32 = 0;
for entry in self.foglut.iter_mut().take(2048) {
let Some(next) = acc.checked_add(step) else {
break;
};
#[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
let hi16 = ((acc as u32) >> 16) as i32;
*entry = hi16;
acc = next;
}
}
pub fn reset_for_quadrant(&mut self, sky_cur_dir: i32) {
self.gscanptr = 0;
self.sky_cur_lng = -1;
self.sky_cur_dir = sky_cur_dir;
self.sky_off = 0;
}
}
#[derive(Debug)]
pub struct ScratchPool {
scratches: Vec<ScanScratch>,
}
impl ScratchPool {
#[must_use]
pub fn new(xres: u32, yres: u32, vsid: u32) -> Self {
Self::new_parallel(xres, yres, vsid, 1)
}
#[must_use]
pub fn new_parallel(xres: u32, yres: u32, vsid: u32, n_threads: usize) -> Self {
let n = n_threads.max(1);
Self {
scratches: (0..n)
.map(|_| ScanScratch::new_for_size(xres, yres, vsid))
.collect(),
}
}
#[must_use]
pub fn n_threads(&self) -> usize {
self.scratches.len()
}
#[must_use]
pub fn slot(&self, idx: usize) -> &ScanScratch {
&self.scratches[idx]
}
#[must_use]
pub fn slot_mut(&mut self, idx: usize) -> &mut ScanScratch {
&mut self.scratches[idx]
}
pub(crate) fn slots_mut(&mut self) -> std::slice::IterMut<'_, ScanScratch> {
self.scratches.iter_mut()
}
pub(crate) fn slots_mut_slice(&mut self) -> &mut [ScanScratch] {
&mut self.scratches
}
pub fn set_skycast(&mut self, col: i32, dist: i32) {
for s in self.slots_mut() {
s.set_skycast(col, dist);
}
}
pub fn set_fog(&mut self, col: i32, max_scan_dist: i32) {
for s in self.slots_mut() {
s.set_fog(col, max_scan_dist);
}
}
pub fn set_side_shades(&mut self, top: i8, bot: i8, left: i8, right: i8, up: i8, down: i8) {
for s in self.slots_mut() {
s.set_side_shades(top, bot, left, right, up, down);
}
}
}
#[allow(clippy::too_many_arguments)]
pub trait Rasterizer {
fn frame_setup(&mut self, _ctx: &crate::scan_loops::ScanContext<'_>) {}
fn gline(&mut self, scratch: &mut ScanScratch, length: u32, x0: f32, y0: f32, x1: f32, y1: f32);
fn hrend(
&mut self,
scratch: &mut ScanScratch,
sx: i32,
sy: i32,
p1: i32,
plc: i32,
incr: i32,
j: i32,
);
fn vrend(&mut self, scratch: &mut ScanScratch, sx: i32, sy: i32, p1: i32, iplc: i32, iinc: i32);
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Default)]
struct RecordingRasterizer {
events: Vec<&'static str>,
}
impl Rasterizer for RecordingRasterizer {
fn gline(&mut self, _: &mut ScanScratch, _: u32, _: f32, _: f32, _: f32, _: f32) {
self.events.push("gline");
}
fn hrend(&mut self, _: &mut ScanScratch, _: i32, _: i32, _: i32, _: i32, _: i32, _: i32) {
self.events.push("hrend");
}
fn vrend(&mut self, _: &mut ScanScratch, _: i32, _: i32, _: i32, _: i32, _: i32) {
self.events.push("vrend");
}
}
#[test]
fn scratch_initial_state() {
let s = ScanScratch::new_for_size(640, 480, 2048);
assert_eq!(s.gscanptr, 0);
assert_eq!(s.sky_cur_lng, -1);
assert_eq!(s.sky_cur_dir, 0);
assert!(!s.radar.is_empty());
assert!(!s.angstart.is_empty());
}
#[test]
fn scratch_reset_for_quadrant_keeps_buffers() {
let mut s = ScanScratch::new_for_size(640, 480, 2048);
let radar_cap = s.radar.len();
let angstart_cap = s.angstart.len();
s.gscanptr = 12345;
s.sky_cur_lng = 7;
s.reset_for_quadrant(-1);
assert_eq!(s.gscanptr, 0);
assert_eq!(s.sky_cur_lng, -1);
assert_eq!(s.sky_cur_dir, -1);
assert_eq!(s.radar.len(), radar_cap);
assert_eq!(s.angstart.len(), angstart_cap);
}
#[test]
fn set_side_shades_zero_keeps_mode_off() {
let mut s = ScanScratch::new_for_size(64, 64, 64);
s.set_side_shades(0, 0, 0, 0, 0, 0);
assert!(!s.sideshademode);
assert_eq!(s.gcsub[0], 0x00ff_00ff_00ff_00ff);
assert_eq!(s.gcsub[1], 0x00ff_00ff_00ff_00ff);
}
#[test]
fn set_side_shades_nonzero_flips_mode_on() {
let mut s = ScanScratch::new_for_size(64, 64, 64);
s.set_side_shades(15, 15, 15, 15, 15, 15);
assert!(s.sideshademode);
assert_eq!((s.gcsub[4] >> 56) & 0xff, 15);
assert_eq!((s.gcsub[7] >> 56) & 0xff, 15);
assert_eq!(s.gcsub[0], 0x00ff_00ff_00ff_00ff);
assert_eq!(s.gcsub[1], 0x00ff_00ff_00ff_00ff);
}
#[test]
fn set_side_shades_one_arg_nonzero_flips_mode_on() {
let mut s = ScanScratch::new_for_size(64, 64, 64);
s.set_side_shades(0, 0, 0, 0, 0, 1);
assert!(s.sideshademode);
}
#[test]
fn rasterizer_trait_object_dispatch() {
let mut rec = RecordingRasterizer::default();
let mut scratch = ScanScratch::new_for_size(64, 64, 64);
let r: &mut dyn Rasterizer = &mut rec;
r.gline(&mut scratch, 4, 0.0, 0.0, 1.0, 1.0);
r.hrend(&mut scratch, 0, 0, 10, 0, 1, 0);
r.vrend(&mut scratch, 0, 0, 10, 0, 1);
assert_eq!(rec.events, ["gline", "hrend", "vrend"]);
}
}