citro3d/
lib.rs

1#![feature(custom_test_frameworks)]
2#![test_runner(test_runner::run_gdb)]
3#![feature(allocator_api)]
4#![feature(doc_cfg)]
5#![feature(doc_auto_cfg)]
6#![doc(html_root_url = "https://rust3ds.github.io/citro3d-rs/crates")]
7#![doc(
8    html_favicon_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png"
9)]
10#![doc(
11    html_logo_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png"
12)]
13
14//! Safe Rust bindings to `citro3d`. This crate wraps `citro3d-sys` to provide
15//! safer APIs for graphics programs targeting the 3DS.
16//!
17//! ## Feature flags
18#![doc = document_features::document_features!()]
19
20pub mod attrib;
21pub mod buffer;
22pub mod color;
23pub mod error;
24pub mod fog;
25pub mod light;
26pub mod math;
27pub mod render;
28pub mod shader;
29pub mod texenv;
30pub mod texture;
31pub mod uniform;
32
33use std::cell::RefMut;
34use std::fmt;
35use std::rc::Rc;
36
37use ctru::services::gfx::Screen;
38pub use error::{Error, Result};
39
40use crate::render::RenderPass;
41
42pub mod macros {
43    //! Helper macros for working with shaders.
44    pub use citro3d_macros::*;
45}
46
47mod private {
48    pub trait Sealed {}
49    impl Sealed for u8 {}
50    impl Sealed for u16 {}
51}
52
53/// Representation of `citro3d`'s internal render queue. This is something that
54/// lives in the global context, but it keeps references to resources that are
55/// used for rendering, so it's useful for us to have something to represent its
56/// lifetime.
57struct RenderQueue;
58
59/// The single instance for using `citro3d`. This is the base type that an application
60/// should instantiate to use this library.
61#[non_exhaustive]
62#[must_use]
63pub struct Instance {
64    queue: Rc<RenderQueue>,
65}
66
67impl fmt::Debug for Instance {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        f.debug_struct("Instance").finish_non_exhaustive()
70    }
71}
72
73impl Instance {
74    /// Initialize the default `citro3d` instance.
75    ///
76    /// # Errors
77    ///
78    /// Fails if `citro3d` cannot be initialized.
79    pub fn new() -> Result<Self> {
80        Self::with_cmdbuf_size(citro3d_sys::C3D_DEFAULT_CMDBUF_SIZE.try_into().unwrap())
81    }
82
83    /// Initialize the instance with a specified command buffer size.
84    ///
85    /// # Errors
86    ///
87    /// Fails if `citro3d` cannot be initialized.
88    #[doc(alias = "C3D_Init")]
89    pub fn with_cmdbuf_size(size: usize) -> Result<Self> {
90        if unsafe { citro3d_sys::C3D_Init(size) } {
91            Ok(Self {
92                queue: Rc::new(RenderQueue),
93            })
94        } else {
95            Err(Error::FailedToInitialize)
96        }
97    }
98
99    /// Create a new render target with the specified size, color format,
100    /// and depth format.
101    ///
102    /// # Errors
103    ///
104    /// Fails if the target could not be created with the given parameters.
105    #[doc(alias = "C3D_RenderTargetCreate")]
106    #[doc(alias = "C3D_RenderTargetSetOutput")]
107    pub fn render_target<'screen>(
108        &self,
109        width: usize,
110        height: usize,
111        screen: RefMut<'screen, dyn Screen>,
112        depth_format: Option<render::DepthFormat>,
113    ) -> Result<render::Target<'screen>> {
114        render::Target::new(width, height, screen, depth_format, Rc::clone(&self.queue))
115    }
116
117    /// Render a frame.
118    ///
119    /// The passed in function/closure can access a [`RenderPass`] to emit draw calls.
120    #[doc(alias = "C3D_FrameBegin")]
121    #[doc(alias = "C3D_FrameEnd")]
122    pub fn render_frame_with<'istance: 'frame, 'frame>(
123        &'istance mut self,
124        f: impl FnOnce(RenderPass<'frame>) -> RenderPass<'frame>,
125    ) {
126        let pass = f(RenderPass::new(self));
127
128        // Explicit drop for FrameEnd (when the GPU command buffer is flushed).
129        drop(pass);
130    }
131}
132
133// This only exists to be an alias, which admittedly is kinda silly. The default
134// impl should be equivalent though, since RenderQueue has a drop impl too.
135impl Drop for Instance {
136    #[doc(alias = "C3D_Fini")]
137    fn drop(&mut self) {}
138}
139
140impl Drop for RenderQueue {
141    fn drop(&mut self) {
142        unsafe {
143            citro3d_sys::C3D_Fini();
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use ctru::services::gfx::Gfx;
151
152    use super::*;
153
154    #[test]
155    fn select_render_target() {
156        let gfx = Gfx::new().unwrap();
157        let screen = gfx.top_screen.borrow_mut();
158
159        let mut instance = Instance::new().unwrap();
160        let target = instance.render_target(10, 10, screen, None).unwrap();
161
162        instance.render_frame_with(|mut pass| {
163            pass.select_render_target(&target).unwrap();
164
165            pass
166        });
167
168        // Check that we don't get a double-free or use-after-free by dropping
169        // the global instance before dropping the target.
170        drop(instance);
171        drop(target);
172    }
173}