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}