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
46/// Representation of `citro3d`'s internal render queue. This is something that
47/// lives in the global context, but it keeps references to resources that are
48/// used for rendering, so it's useful for us to have something to represent its
49/// lifetime.
50struct RenderQueue;
51
52/// The single instance for using `citro3d`. This is the base type that an application
53/// should instantiate to use this library.
54#[non_exhaustive]
55#[must_use]
56pub struct Instance {
57    queue: Rc<RenderQueue>,
58}
59
60impl fmt::Debug for Instance {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        f.debug_struct("Instance").finish_non_exhaustive()
63    }
64}
65
66impl Instance {
67    /// Initialize the default `citro3d` instance.
68    ///
69    /// # Errors
70    ///
71    /// Fails if `citro3d` cannot be initialized.
72    pub fn new() -> Result<Self> {
73        Self::with_cmdbuf_size(citro3d_sys::C3D_DEFAULT_CMDBUF_SIZE.try_into().unwrap())
74    }
75
76    /// Initialize the instance with a specified command buffer size.
77    ///
78    /// # Errors
79    ///
80    /// Fails if `citro3d` cannot be initialized.
81    #[doc(alias = "C3D_Init")]
82    pub fn with_cmdbuf_size(size: usize) -> Result<Self> {
83        if unsafe { citro3d_sys::C3D_Init(size) } {
84            Ok(Self {
85                queue: Rc::new(RenderQueue),
86            })
87        } else {
88            Err(Error::FailedToInitialize)
89        }
90    }
91
92    /// Create a new render target with the specified size, color format,
93    /// and depth format.
94    ///
95    /// # Errors
96    ///
97    /// Fails if the target could not be created with the given parameters.
98    #[doc(alias = "C3D_RenderTargetCreate")]
99    #[doc(alias = "C3D_RenderTargetSetOutput")]
100    pub fn render_target<'screen>(
101        &self,
102        width: usize,
103        height: usize,
104        screen: RefMut<'screen, dyn Screen>,
105        depth_format: Option<render::DepthFormat>,
106    ) -> Result<render::ScreenTarget<'screen>> {
107        render::ScreenTarget::new(width, height, screen, depth_format, Rc::clone(&self.queue))
108    }
109
110    /// Create a new render target that renders to a texture with the specified size, color format,
111    /// and depth format.
112    ///
113    /// # Errors
114    ///
115    /// Fails if the target could not be created with the given parameters.
116    pub fn render_target_texture(
117        &self,
118        texture: texture::Texture,
119        face: texture::Face,
120        depth_format: Option<render::DepthFormat>,
121    ) -> Result<render::TextureTarget> {
122        render::TextureTarget::new(texture, face, depth_format, Rc::clone(&self.queue))
123    }
124
125    /// Render a frame.
126    ///
127    /// The passed in function/closure can access a [`Frame`] to emit draw calls.
128    #[doc(alias = "C3D_FrameBegin")]
129    #[doc(alias = "C3D_FrameEnd")]
130    pub fn render_frame_with<'istance: 'frame, 'frame>(
131        &'istance mut self,
132        f: impl FnOnce(Frame<'frame>) -> Frame<'frame>,
133    ) {
134        let frame = f(Frame::new(self));
135
136        // Explicit drop for FrameEnd (when the GPU command buffer is flushed).
137        drop(frame);
138    }
139}
140
141// This only exists to be an alias, which admittedly is kinda silly. The default
142// impl should be equivalent though, since RenderQueue has a drop impl too.
143impl Drop for Instance {
144    #[doc(alias = "C3D_Fini")]
145    fn drop(&mut self) {}
146}
147
148impl Drop for RenderQueue {
149    fn drop(&mut self) {
150        unsafe {
151            citro3d_sys::C3D_Fini();
152        }
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use ctru::services::gfx::Gfx;
159
160    use super::*;
161
162    #[test]
163    fn select_render_target() {
164        let gfx = Gfx::new().unwrap();
165        let screen = gfx.top_screen.borrow_mut();
166
167        let mut instance = Instance::new().unwrap();
168        let target = instance.render_target(10, 10, screen, None).unwrap();
169
170        instance.render_frame_with(|mut frame| {
171            frame.select_render_target(&target).unwrap();
172
173            frame
174        });
175
176        // Check that we don't get a double-free or use-after-free by dropping
177        // the global instance before dropping the target.
178        drop(instance);
179        drop(target);
180    }
181}