angrylion_rdp_plus/
lib.rs

1//! # Angrylion RDP Plus
2//! Placeholder text
3#![deny(rustdoc::broken_intra_doc_links, missing_docs)]
4
5extern crate angrylion_rdp_plus_sys as alp;
6
7use std::mem::transmute;
8use std::slice::from_raw_parts;
9
10/// Maximum RDRAM size
11pub const RDRAM_MAX_SIZE: usize = alp::RDRAM_MAX_SIZE as usize;
12
13/// Compatibility mode
14#[repr(u32)]
15#[non_exhaustive]
16#[derive(Copy, Clone, Debug)]
17pub enum DpCompatProfile {
18    /// Fast, most glitches
19    Low = alp::dp_compat_profile_DP_COMPAT_LOW,
20    /// Moderate, some glitches
21    Medium = alp::dp_compat_profile_DP_COMPAT_MEDIUM,
22    /// Slow, few glitches
23    High = alp::dp_compat_profile_DP_COMPAT_HIGH,
24}
25
26impl Default for DpCompatProfile {
27    #[inline]
28    fn default() -> Self {
29        Self::Low
30    }
31}
32
33/// Display processor registers
34#[repr(u32)]
35#[allow(missing_docs)]
36#[derive(Copy, Clone, Debug)]
37pub enum DpRegister {
38    Start = alp::dp_register_DP_START,
39    End = alp::dp_register_DP_END,
40    Current = alp::dp_register_DP_CURRENT,
41    Status = alp::dp_register_DP_STATUS,
42    Clock = alp::dp_register_DP_CLOCK,
43    BufBusy = alp::dp_register_DP_BUFBUSY,
44    PipeBusy = alp::dp_register_DP_PIPEBUSY,
45    TMem = alp::dp_register_DP_TMEM,
46}
47
48impl DpRegister {
49    /// Number of DP registers
50    pub const NUM_REG: usize = alp::dp_register_DP_NUM_REG as usize;
51}
52
53/// Interpolation method
54#[repr(u32)]
55#[non_exhaustive]
56#[derive(Copy, Clone, Debug)]
57pub enum ViInterp {
58    /// Blocky (nearest-neightbor)
59    Nearest = alp::vi_interp_VI_INTERP_NEAREST,
60    /// Blurry (bilinear)
61    Linear = alp::vi_interp_VI_INTERP_LINEAR,
62    /// Soft (bilinear + NN)
63    Hybrid = alp::vi_interp_VI_INTERP_HYBRID,
64}
65
66impl Default for ViInterp {
67    #[inline]
68    fn default() -> Self {
69        Self::Hybrid
70    }
71}
72
73/// Output mode
74#[repr(u32)]
75#[non_exhaustive]
76#[derive(Copy, Clone, Debug)]
77pub enum ViMode {
78    /// Color buffer with VI filter
79    Normal = alp::vi_mode_VI_MODE_NORMAL,
80    /// Direct color buffer, unfiltered
81    Color = alp::vi_mode_VI_MODE_COLOR,
82    /// Depth buffer as grayscale
83    Depth = alp::vi_mode_VI_MODE_DEPTH,
84    /// Coverage as grayscale
85    Coverage = alp::vi_mode_VI_MODE_COVERAGE,
86}
87
88impl Default for ViMode {
89    #[inline]
90    fn default() -> Self {
91        Self::Normal
92    }
93}
94
95/// Video interface registers
96#[repr(u32)]
97#[allow(missing_docs)]
98#[derive(Copy, Clone, Debug)]
99pub enum ViRegister {
100    /// aka `VI_CONTROL`
101    Status = alp::vi_register_VI_STATUS,
102    /// aka `VI_DRAM_ADDR`
103    Origin = alp::vi_register_VI_ORIGIN,
104    Width = alp::vi_register_VI_WIDTH,
105    Intr = alp::vi_register_VI_INTR,
106    VCurrentLine = alp::vi_register_VI_V_CURRENT_LINE,
107    Timing = alp::vi_register_VI_TIMING,
108    VSync = alp::vi_register_VI_V_SYNC,
109    HSync = alp::vi_register_VI_H_SYNC,
110    /// aka `VI_H_SYNC_LEAP`
111    Leap = alp::vi_register_VI_LEAP,
112    /// aka `VI_H_VIDEO`
113    HStart = alp::vi_register_VI_H_START,
114    /// aka `VI_V_VIDEO`
115    VStart = alp::vi_register_VI_V_START,
116    VBurst = alp::vi_register_VI_V_BURST,
117    XScale = alp::vi_register_VI_X_SCALE,
118    YScale = alp::vi_register_VI_Y_SCALE,
119}
120
121impl ViRegister {
122    /// Number of VI registers
123    pub const NUM_REG: usize = alp::vi_register_VI_NUM_REG as usize;
124}
125
126/// Pixel type held by [`FrameBuffer`]
127#[repr(C)]
128#[derive(Copy, Clone, Debug)]
129pub struct Pixel {
130    /// Red
131    pub r: u8,
132    /// Green
133    pub g: u8,
134    /// Blue
135    pub b: u8,
136    /// Alpha
137    pub a: u8,
138}
139
140/// Stores the output of [`N64Video::update_screen`]
141#[derive(Copy, Clone, Debug)]
142pub struct FrameBuffer {
143    /// Static reference to ALP's internal framebuffer.
144    /// The contents do not persist between frames.
145    /// If you need to keep it between frames, make an owned copy.
146    pub pixels: &'static [Pixel],
147    /// Width (in pixels) of output
148    pub width: u32,
149    /// Height (in pixels) of output
150    pub height: u32,
151    /// Effective height, can be non-square pixels
152    pub height_out: u32,
153    /// This is greater than `width` when `hide_overscan` is set.
154    /// Since `hide_overscan` skips the unwanted border of the output,
155    /// you must access each row as `pitch` pixels apart, but only draw `width` of those pixels.
156    pub pitch: u32,
157}
158
159impl FrameBuffer {
160    /// Convert pixels to bytes in RGBA ordering
161    #[inline]
162    pub fn as_bytes(&self) -> &'static [u8] {
163        unsafe { from_raw_parts(self.pixels.as_ptr() as *const u8, self.pixels.len() << 2) }
164    }
165
166    /// Convert pixels to slice of `[u8; 4]` in RGBA ordering
167    #[inline]
168    pub const fn as_nested_slice(&self) -> &'static [[u8; 4]] {
169        unsafe { transmute(self.pixels) }
170    }
171}
172
173/// Video Interface (VI) configuration
174#[repr(C)]
175#[derive(Copy, Clone, Debug)]
176pub struct ViConfig {
177    /// Output mode
178    pub mode: ViMode,
179    /// Output interpolation mode
180    pub interp: ViInterp,
181    /// Force 16:9 aspect ratio if true
182    pub widescreen: bool,
183    /// Crop to visible area if true
184    pub hide_overscan: bool,
185    /// Enable vsync if true
186    pub vsync: bool,
187    /// Run in exclusive mode when in fullscreen if true
188    pub exclusive: bool,
189    /// One native pixel is displayed as a multiple of a screen pixel if true
190    pub integer_scaling: bool,
191}
192
193impl Default for ViConfig {
194    #[inline]
195    fn default() -> Self {
196        Self {
197            mode: Default::default(),
198            interp: Default::default(),
199            widescreen: false,
200            hide_overscan: false,
201            vsync: true,
202            exclusive: false,
203            integer_scaling: false,
204        }
205    }
206}
207
208/// Graphics configuration
209#[derive(Debug)]
210pub struct GfxConfig<'a> {
211    /// RDRAM, typically 4 or 8 MiB
212    pub rdram: &'a mut [u8],
213    /// RSP data memory pointer
214    pub dmem: &'a mut [u8; 1000],
215    /// Video interface registers
216    pub vi_reg: [&'a mut u32; ViRegister::NUM_REG],
217    /// Display processor registers
218    pub dp_reg: [&'a mut u32; DpRegister::NUM_REG],
219    /// MIPS interface interrupt register
220    pub mi_intr_reg: &'a mut u32,
221    /// MIPS interface interrupt callback function
222    pub mi_intr_cb: fn(),
223}
224
225/// Display Processor (DP) configuration
226#[repr(C)]
227#[derive(Copy, Clone, Debug, Default)]
228pub struct DpConfig {
229    /// Multithreading compatibility mode
230    pub compat: DpCompatProfile,
231}
232
233/// Configuration passed to [`N64Video::init`]
234#[derive(Debug)]
235pub struct Config<'a> {
236    /// Graphics configuration
237    pub gfx: GfxConfig<'a>,
238    /// Video Interface (VI) configuration
239    pub vi: ViConfig,
240    /// Display Processor (DP) configuration
241    pub dp: DpConfig,
242    /// Use multithreaded renderer if true
243    pub parallel: bool,
244    /// Use a busyloop while waiting for work
245    pub busyloop: bool,
246    /// Number of rendering workers (0 for automatic)
247    pub num_workers: u32,
248}
249
250impl<'a> Config<'a> {
251    /// Creates a [`Config`] struct with default settings.
252    /// You can also initialize the struct manually, as all fields are public.
253    #[inline]
254    pub fn with_gfx_config(gfx: GfxConfig<'a>) -> Self {
255        Self {
256            gfx,
257            vi: Default::default(),
258            dp: Default::default(),
259            parallel: true,
260            busyloop: false,
261            num_workers: 0,
262        }
263    }
264}
265
266// Used to track the existence of an [`N64Video`]
267static mut MI_INTR_CB: Option<fn()> = None;
268
269/// Holds the state of ALP, for integration into an emulator.
270/// Internally, ALP has a static state, so multiple simultaneous instances are not allowed.
271#[derive(Debug)]
272pub struct N64Video<'a>(
273    [&'a mut u32; ViRegister::NUM_REG],
274    [&'a mut u32; DpRegister::NUM_REG],
275);
276
277impl<'a> N64Video<'a> {
278    unsafe extern "C" fn mi_intr_cb() {
279        if let Some(f) = MI_INTR_CB {
280            f()
281        }
282    }
283
284    /// This will return `true` if there are no instances of [`N64Video`], else `false`.
285    #[inline]
286    pub fn can_init() -> bool {
287        unsafe { MI_INTR_CB.is_none() }
288    }
289
290    /// Initializes [`N64Video`] with given [`Config`].
291    /// This will return `None` if there is already an instance. (see [`N64Video::can_init`])
292    pub fn init(config: Config<'a>) -> Option<Self> {
293        unsafe {
294            if MI_INTR_CB.is_none() {
295                None
296            } else {
297                MI_INTR_CB = Some(config.gfx.mi_intr_cb);
298                let mut s = Self(config.gfx.vi_reg, config.gfx.dp_reg);
299                let gfx = alp::n64video_config_gfx {
300                    rdram: config.gfx.rdram.as_mut_ptr(),
301                    rdram_size: config.gfx.rdram.len() as u32,
302                    dmem: config.gfx.dmem.as_mut_ptr(),
303                    vi_reg: transmute(s.0.as_mut_ptr()),
304                    dp_reg: transmute(s.1.as_mut_ptr()),
305                    mi_intr_reg: config.gfx.mi_intr_reg,
306                    mi_intr_cb: Some(Self::mi_intr_cb),
307                };
308
309                let mut cfg = alp::n64video_config {
310                    gfx,
311                    vi: transmute(config.vi),
312                    dp: transmute(config.dp),
313                    parallel: config.parallel,
314                    busyloop: config.busyloop,
315                    num_workers: config.num_workers,
316                };
317
318                alp::n64video_init(&mut cfg);
319                Some(s)
320            }
321        }
322    }
323
324    /// Render to a [`FrameBuffer`]
325    pub fn update_screen(&mut self) -> Option<FrameBuffer> {
326        unsafe {
327            debug_assert!(MI_INTR_CB.is_some());
328            let mut fb = alp::n64video_frame_buffer {
329                pixels: std::ptr::null_mut(),
330                width: 0,
331                height: 0,
332                height_out: 0,
333                pitch: 0,
334                valid: false,
335            };
336            alp::n64video_update_screen(&mut fb);
337            if fb.valid && !fb.pixels.is_null() {
338                Some(FrameBuffer {
339                    pixels: from_raw_parts(
340                        fb.pixels as *const Pixel,
341                        fb.pitch as usize * fb.height as usize,
342                    ),
343                    width: fb.width,
344                    height: fb.height,
345                    height_out: fb.height_out,
346                    pitch: fb.pitch,
347                })
348            } else {
349                None
350            }
351        }
352    }
353
354    /// Process RDP list
355    pub fn process_list(&mut self) {
356        unsafe {
357            debug_assert!(MI_INTR_CB.is_some());
358            alp::n64video_process_list();
359        }
360    }
361}
362
363impl Drop for N64Video<'_> {
364    fn drop(&mut self) {
365        unsafe {
366            debug_assert!(MI_INTR_CB.is_some());
367            MI_INTR_CB = None;
368            alp::n64video_close();
369        }
370    }
371}