Skip to main content

roxlap_core/
raster_target.rs

1//! `RasterTarget` — a `Copy` borrowed view of the framebuffer +
2//! zbuffer as raw pointers, the compositing primitive shared by the
3//! DDA terrain renderer ([`crate::dda`]) and the DDA sprite raycaster
4//! ([`crate::dda_sprite`]).
5//!
6//! Holding `&'a mut [u32]` / `&'a mut [f32]` directly would force an
7//! exclusive borrow per instance, blocking the renderer's tile bands
8//! from running on multiple threads even though their pixel writes are
9//! disjoint. `RasterTarget` is constructed safely from exclusive slice
10//! borrows, then re-exposes the memory as raw pointers tied to lifetime
11//! `'a` via `PhantomData`. Once it exists it is the sole path to the
12//! underlying memory for the duration of `'a`.
13//!
14//! # Safety contract for parallel use
15//! Callers that copy a `RasterTarget` and pass copies to multiple
16//! threads MUST guarantee the threads collectively write to
17//! pairwise-disjoint pixel indices. The parallel DDA driver enforces
18//! this via per-band row ranges; single-threaded callers hold one copy
19//! and trivially satisfy the invariant.
20
21use std::marker::PhantomData;
22
23/// A `Copy`-able raw-pointer view over a borrowed `(framebuffer,
24/// zbuffer)` pair. Mint one via [`RasterTarget::new`] — the lifetime
25/// `'a` pins the source slices' exclusive borrows for as long as any
26/// copy lives. Copies handed to multiple threads must write
27/// pairwise-disjoint pixel indices (see the module docs for the full
28/// safety contract).
29#[derive(Clone, Copy, Debug)]
30pub struct RasterTarget<'a> {
31    fb_ptr: *mut u32,
32    fb_len: usize,
33    zb_ptr: *mut f32,
34    zb_len: usize,
35    _marker: PhantomData<&'a mut [u32]>,
36}
37
38// SAFETY: `RasterTarget` is morally a borrowed mutable slice pair —
39// the same shape `&'a mut [u32]` / `&'a mut [f32]` would have, both of
40// which are `Send` when `T: Send`. Multi-thread safety is enforced
41// by the wedge / strip-disjoint write invariant (see struct doc).
42unsafe impl Send for RasterTarget<'_> {}
43
44// SAFETY: sharing `&RasterTarget` across threads exposes only the
45// raw pointers + lengths. Reading a pointer field is itself free of
46// data races; concurrent writes through the pointer are gated by
47// the disjoint-write invariant the caller upholds. Required so
48// `ScalarRasterizer: Sync`, which `rayon::par_iter_mut` needs to
49// share `&rasterizer` across the strip-parallel closures (R12.3.1).
50unsafe impl Sync for RasterTarget<'_> {}
51
52impl<'a> RasterTarget<'a> {
53    /// Build a target from exclusive slice borrows. The slices are
54    /// consumed (their `&'a mut` reborrow is the load-bearing thing —
55    /// this constructor is the only way to mint a `RasterTarget`
56    /// from safe code).
57    #[must_use]
58    pub fn new(framebuffer: &'a mut [u32], zbuffer: &'a mut [f32]) -> Self {
59        Self {
60            fb_ptr: framebuffer.as_mut_ptr(),
61            fb_len: framebuffer.len(),
62            zb_ptr: zbuffer.as_mut_ptr(),
63            zb_len: zbuffer.len(),
64            _marker: PhantomData,
65        }
66    }
67
68    /// Framebuffer length in `u32` elements.
69    #[must_use]
70    pub fn fb_len(self) -> usize {
71        self.fb_len
72    }
73
74    /// Raw mutable framebuffer pointer. Used by SSE blocks that do
75    /// their own arithmetic + bounds reasoning.
76    ///
77    /// # Safety
78    /// Callers must respect `fb_len` and the parallel-use invariant.
79    #[must_use]
80    pub fn fb_ptr(self) -> *mut u32 {
81        self.fb_ptr
82    }
83
84    /// Raw mutable zbuffer pointer. Same contract as
85    /// [`Self::fb_ptr`].
86    #[must_use]
87    pub fn zb_ptr(self) -> *mut f32 {
88        self.zb_ptr
89    }
90
91    /// Write one ARGB pixel.
92    ///
93    /// # Safety
94    /// `idx < self.fb_len()`, plus the parallel-use invariant.
95    pub unsafe fn write_color(self, idx: usize, color: u32) {
96        debug_assert!(idx < self.fb_len, "fb idx {} >= len {}", idx, self.fb_len);
97        // SAFETY: caller asserts in-bounds + disjoint-from-other-threads.
98        unsafe { self.fb_ptr.add(idx).write(color) };
99    }
100
101    /// Write one z-buffer entry.
102    ///
103    /// # Safety
104    /// `idx < self.fb_len()` (zbuffer length matches fb), plus the
105    /// parallel-use invariant.
106    pub unsafe fn write_depth(self, idx: usize, z: f32) {
107        debug_assert!(idx < self.zb_len, "zb idx {} >= len {}", idx, self.zb_len);
108        // SAFETY: caller asserts in-bounds + disjoint-from-other-threads.
109        unsafe { self.zb_ptr.add(idx).write(z) };
110    }
111
112    /// Read one z-buffer entry — the terrain depth a translucent sprite
113    /// march tests against (TV stage).
114    ///
115    /// # Safety
116    /// `idx < self.fb_len()`, plus the parallel-use invariant.
117    #[must_use]
118    pub unsafe fn read_depth(self, idx: usize) -> f32 {
119        debug_assert!(idx < self.zb_len, "zb idx {} >= len {}", idx, self.zb_len);
120        // SAFETY: caller asserts in-bounds + disjoint-from-other-threads.
121        unsafe { *self.zb_ptr.add(idx) }
122    }
123
124    /// Read one framebuffer pixel — the background a translucent sprite
125    /// composites over when its ray exits without an opaque hit (TV stage).
126    ///
127    /// # Safety
128    /// `idx < self.fb_len()`, plus the parallel-use invariant.
129    #[must_use]
130    pub unsafe fn read_color(self, idx: usize) -> u32 {
131        debug_assert!(idx < self.fb_len, "fb idx {} >= len {}", idx, self.fb_len);
132        // SAFETY: caller asserts in-bounds + disjoint-from-other-threads.
133        unsafe { *self.fb_ptr.add(idx) }
134    }
135
136    /// Depth-tested write: store `(color, z)` only if `z` is strictly
137    /// closer (smaller) than the current z-buffer entry. Returns whether
138    /// the pixel was written. The compositing primitive the DDA sprite
139    /// raycaster uses to occlude sprites against terrain.
140    ///
141    /// # Safety
142    /// `idx < self.fb_len()`, plus the parallel-use invariant.
143    #[must_use]
144    pub unsafe fn z_test_write(self, idx: usize, color: u32, z: f32) -> bool {
145        debug_assert!(idx < self.zb_len, "zb idx {} >= len {}", idx, self.zb_len);
146        // SAFETY: caller asserts in-bounds + disjoint-from-other-threads.
147        unsafe {
148            if z < *self.zb_ptr.add(idx) {
149                self.fb_ptr.add(idx).write(color);
150                self.zb_ptr.add(idx).write(z);
151                true
152            } else {
153                false
154            }
155        }
156    }
157}