citro3d/
lib.rs

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