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#[derive(Clone, Copy, Debug)]
24pub struct RasterTarget<'a> {
25    fb_ptr: *mut u32,
26    fb_len: usize,
27    zb_ptr: *mut f32,
28    zb_len: usize,
29    _marker: PhantomData<&'a mut [u32]>,
30}
31
32// SAFETY: `RasterTarget` is morally a borrowed mutable slice pair —
33// the same shape `&'a mut [u32]` / `&'a mut [f32]` would have, both of
34// which are `Send` when `T: Send`. Multi-thread safety is enforced
35// by the wedge / strip-disjoint write invariant (see struct doc).
36unsafe impl Send for RasterTarget<'_> {}
37
38// SAFETY: sharing `&RasterTarget` across threads exposes only the
39// raw pointers + lengths. Reading a pointer field is itself free of
40// data races; concurrent writes through the pointer are gated by
41// the disjoint-write invariant the caller upholds. Required so
42// `ScalarRasterizer: Sync`, which `rayon::par_iter_mut` needs to
43// share `&rasterizer` across the strip-parallel closures (R12.3.1).
44unsafe impl Sync for RasterTarget<'_> {}
45
46impl<'a> RasterTarget<'a> {
47    /// Build a target from exclusive slice borrows. The slices are
48    /// consumed (their `&'a mut` reborrow is the load-bearing thing —
49    /// this constructor is the only way to mint a `RasterTarget`
50    /// from safe code).
51    #[must_use]
52    pub fn new(framebuffer: &'a mut [u32], zbuffer: &'a mut [f32]) -> Self {
53        Self {
54            fb_ptr: framebuffer.as_mut_ptr(),
55            fb_len: framebuffer.len(),
56            zb_ptr: zbuffer.as_mut_ptr(),
57            zb_len: zbuffer.len(),
58            _marker: PhantomData,
59        }
60    }
61
62    /// Framebuffer length in `u32` elements.
63    #[must_use]
64    pub fn fb_len(self) -> usize {
65        self.fb_len
66    }
67
68    /// Raw mutable framebuffer pointer. Used by SSE blocks that do
69    /// their own arithmetic + bounds reasoning.
70    ///
71    /// # Safety
72    /// Callers must respect `fb_len` and the parallel-use invariant.
73    #[must_use]
74    pub fn fb_ptr(self) -> *mut u32 {
75        self.fb_ptr
76    }
77
78    /// Raw mutable zbuffer pointer. Same contract as
79    /// [`Self::fb_ptr`].
80    #[must_use]
81    pub fn zb_ptr(self) -> *mut f32 {
82        self.zb_ptr
83    }
84
85    /// Write one ARGB pixel.
86    ///
87    /// # Safety
88    /// `idx < self.fb_len()`, plus the parallel-use invariant.
89    pub unsafe fn write_color(self, idx: usize, color: u32) {
90        debug_assert!(idx < self.fb_len, "fb idx {} >= len {}", idx, self.fb_len);
91        // SAFETY: caller asserts in-bounds + disjoint-from-other-threads.
92        unsafe { self.fb_ptr.add(idx).write(color) };
93    }
94
95    /// Write one z-buffer entry.
96    ///
97    /// # Safety
98    /// `idx < self.fb_len()` (zbuffer length matches fb), plus the
99    /// parallel-use invariant.
100    pub unsafe fn write_depth(self, idx: usize, z: f32) {
101        debug_assert!(idx < self.zb_len, "zb idx {} >= len {}", idx, self.zb_len);
102        // SAFETY: caller asserts in-bounds + disjoint-from-other-threads.
103        unsafe { self.zb_ptr.add(idx).write(z) };
104    }
105
106    /// Depth-tested write: store `(color, z)` only if `z` is strictly
107    /// closer (smaller) than the current z-buffer entry. Returns whether
108    /// the pixel was written. The compositing primitive the DDA sprite
109    /// raycaster uses to occlude sprites against terrain.
110    ///
111    /// # Safety
112    /// `idx < self.fb_len()`, plus the parallel-use invariant.
113    #[must_use]
114    pub unsafe fn z_test_write(self, idx: usize, color: u32, z: f32) -> bool {
115        debug_assert!(idx < self.zb_len, "zb idx {} >= len {}", idx, self.zb_len);
116        // SAFETY: caller asserts in-bounds + disjoint-from-other-threads.
117        unsafe {
118            if z < *self.zb_ptr.add(idx) {
119                self.fb_ptr.add(idx).write(color);
120                self.zb_ptr.add(idx).write(z);
121                true
122            } else {
123                false
124            }
125        }
126    }
127}