1#![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
10pub const RDRAM_MAX_SIZE: usize = alp::RDRAM_MAX_SIZE as usize;
12
13#[repr(u32)]
15#[non_exhaustive]
16#[derive(Copy, Clone, Debug)]
17pub enum DpCompatProfile {
18 Low = alp::dp_compat_profile_DP_COMPAT_LOW,
20 Medium = alp::dp_compat_profile_DP_COMPAT_MEDIUM,
22 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#[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 pub const NUM_REG: usize = alp::dp_register_DP_NUM_REG as usize;
51}
52
53#[repr(u32)]
55#[non_exhaustive]
56#[derive(Copy, Clone, Debug)]
57pub enum ViInterp {
58 Nearest = alp::vi_interp_VI_INTERP_NEAREST,
60 Linear = alp::vi_interp_VI_INTERP_LINEAR,
62 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#[repr(u32)]
75#[non_exhaustive]
76#[derive(Copy, Clone, Debug)]
77pub enum ViMode {
78 Normal = alp::vi_mode_VI_MODE_NORMAL,
80 Color = alp::vi_mode_VI_MODE_COLOR,
82 Depth = alp::vi_mode_VI_MODE_DEPTH,
84 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#[repr(u32)]
97#[allow(missing_docs)]
98#[derive(Copy, Clone, Debug)]
99pub enum ViRegister {
100 Status = alp::vi_register_VI_STATUS,
102 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 Leap = alp::vi_register_VI_LEAP,
112 HStart = alp::vi_register_VI_H_START,
114 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 pub const NUM_REG: usize = alp::vi_register_VI_NUM_REG as usize;
124}
125
126#[repr(C)]
128#[derive(Copy, Clone, Debug)]
129pub struct Pixel {
130 pub r: u8,
132 pub g: u8,
134 pub b: u8,
136 pub a: u8,
138}
139
140#[derive(Copy, Clone, Debug)]
142pub struct FrameBuffer {
143 pub pixels: &'static [Pixel],
147 pub width: u32,
149 pub height: u32,
151 pub height_out: u32,
153 pub pitch: u32,
157}
158
159impl FrameBuffer {
160 #[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 #[inline]
168 pub const fn as_nested_slice(&self) -> &'static [[u8; 4]] {
169 unsafe { transmute(self.pixels) }
170 }
171}
172
173#[repr(C)]
175#[derive(Copy, Clone, Debug)]
176pub struct ViConfig {
177 pub mode: ViMode,
179 pub interp: ViInterp,
181 pub widescreen: bool,
183 pub hide_overscan: bool,
185 pub vsync: bool,
187 pub exclusive: bool,
189 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#[derive(Debug)]
210pub struct GfxConfig<'a> {
211 pub rdram: &'a mut [u8],
213 pub dmem: &'a mut [u8; 1000],
215 pub vi_reg: [&'a mut u32; ViRegister::NUM_REG],
217 pub dp_reg: [&'a mut u32; DpRegister::NUM_REG],
219 pub mi_intr_reg: &'a mut u32,
221 pub mi_intr_cb: fn(),
223}
224
225#[repr(C)]
227#[derive(Copy, Clone, Debug, Default)]
228pub struct DpConfig {
229 pub compat: DpCompatProfile,
231}
232
233#[derive(Debug)]
235pub struct Config<'a> {
236 pub gfx: GfxConfig<'a>,
238 pub vi: ViConfig,
240 pub dp: DpConfig,
242 pub parallel: bool,
244 pub busyloop: bool,
246 pub num_workers: u32,
248}
249
250impl<'a> Config<'a> {
251 #[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
266static mut MI_INTR_CB: Option<fn()> = None;
268
269#[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 #[inline]
286 pub fn can_init() -> bool {
287 unsafe { MI_INTR_CB.is_none() }
288 }
289
290 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 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 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}