ctru/services/
gfx.rs

1//! Graphics service.
2//!
3//! The GFX service controls (in a somewhat high-level way) the console's LCD screens.
4//! The screens are subordinate to the GFX service handle and can be used by only one borrower at a time.
5#![doc(alias = "graphics")]
6
7use std::cell::{Ref, RefCell, RefMut};
8use std::marker::PhantomData;
9use std::sync::Mutex;
10
11use crate::error::Result;
12use crate::sealed::Sealed;
13use crate::services::ServiceReference;
14use crate::services::gspgpu::{self, FramebufferFormat};
15
16/// Trait to handle common functionality for all screens.
17///
18/// This trait is implemented by the screen structs for working with frame buffers and
19/// drawing to the screens. Graphics-related code can be made generic over this
20/// trait to work with any of the given screens.
21#[doc(alias = "gfxScreen_t")]
22pub trait Screen: Sealed {
23    /// Returns the `libctru` value for the Screen kind.
24    fn as_raw(&self) -> ctru_sys::gfxScreen_t;
25
26    /// Returns the Screen side (left or right).
27    fn side(&self) -> Side;
28
29    /// Returns a [`RawFrameBuffer`] for the screen (if the framebuffer was allocated on the HEAP).
30    ///
31    /// # Notes
32    ///
33    /// The pointer of the framebuffer returned by this function can change after each call
34    /// to this function if double buffering is enabled, so it's suggested to NOT save it for later use.
35    ///
36    /// # Panics
37    ///
38    /// If the [`Gfx`] service was initialised via [`Gfx::with_formats_vram()`] this function will crash the program with an ARM exception.
39    #[doc(alias = "gfxGetFramebuffer")]
40    fn raw_framebuffer(&mut self) -> RawFrameBuffer<'_> {
41        let mut width: u16 = 0;
42        let mut height: u16 = 0;
43        let ptr = unsafe {
44            ctru_sys::gfxGetFramebuffer(self.as_raw(), self.side().into(), &mut width, &mut height)
45        };
46        RawFrameBuffer {
47            ptr,
48            width: width.into(),
49            height: height.into(),
50            screen: PhantomData,
51        }
52    }
53
54    /// Gets the framebuffer format.
55    #[doc(alias = "gfxGetScreenFormat")]
56    fn framebuffer_format(&self) -> FramebufferFormat {
57        unsafe { ctru_sys::gfxGetScreenFormat(self.as_raw()) }.into()
58    }
59
60    /// Change the framebuffer format.
61    ///
62    /// [`Swap::swap_buffers`] must be called after this method for the configuration
63    /// change to take effect.
64    #[doc(alias = "gfxSetScreenFormat")]
65    fn set_framebuffer_format(&mut self, fmt: FramebufferFormat) {
66        unsafe { ctru_sys::gfxSetScreenFormat(self.as_raw(), fmt.into()) }
67    }
68}
69
70/// The top LCD screen.
71///
72/// Mutable access to this struct is required to write to the top screen's frame buffer.
73///
74/// To enable 3D mode, it can be converted into a [`TopScreen3D`].
75pub struct TopScreen {
76    left: TopScreenLeft,
77    right: TopScreenRight,
78}
79
80/// The top LCD screen set in stereoscopic 3D mode.
81///
82/// A helper container for both sides of the top screen. Once the [`TopScreen`] is
83/// converted into this, 3D mode will be enabled until this struct is dropped.
84pub struct TopScreen3D<'screen> {
85    screen: &'screen RefCell<TopScreen>,
86}
87
88/// Trait for screens that can have its frame buffers swapped, when double buffering is enabled.
89///
90/// This trait applies to all [`Screen`]s that have swappable frame buffers.
91pub trait Swap: Sealed {
92    /// Swaps the video buffers.
93    ///
94    /// Even if double buffering is disabled, "swapping" the buffers has the side effect
95    /// of committing any configuration changes to the buffers (e.g. [`TopScreen::set_wide_mode()`],
96    /// [`Screen::set_framebuffer_format()`], [`Swap::set_double_buffering()`]), so it should still be used.
97    ///
98    /// This should be called once per frame at most.
99    #[doc(alias = "gfxScreenSwapBuffers")]
100    fn swap_buffers(&mut self);
101
102    /// Set whether to use double buffering.
103    ///
104    /// # Notes
105    ///
106    /// Double buffering is enabled by default.
107    /// [`Swap::swap_buffers`] must be called after this function for the configuration
108    /// change to take effect.
109    #[doc(alias = "gfxSetDoubleBuffering")]
110    fn set_double_buffering(&mut self, enabled: bool);
111}
112
113impl Swap for TopScreen3D<'_> {
114    fn swap_buffers(&mut self) {
115        unsafe {
116            ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_TOP, true);
117        }
118    }
119
120    fn set_double_buffering(&mut self, enabled: bool) {
121        unsafe { ctru_sys::gfxSetDoubleBuffering(ctru_sys::GFX_TOP, enabled) }
122    }
123}
124
125impl Swap for TopScreen {
126    fn swap_buffers(&mut self) {
127        unsafe {
128            ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_TOP, false);
129        }
130    }
131
132    fn set_double_buffering(&mut self, enabled: bool) {
133        unsafe { ctru_sys::gfxSetDoubleBuffering(ctru_sys::GFX_TOP, enabled) }
134    }
135}
136
137impl Swap for BottomScreen {
138    fn swap_buffers(&mut self) {
139        unsafe {
140            ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_BOTTOM, false);
141        }
142    }
143
144    fn set_double_buffering(&mut self, enabled: bool) {
145        unsafe { ctru_sys::gfxSetDoubleBuffering(ctru_sys::GFX_BOTTOM, enabled) }
146    }
147}
148
149/// A screen with buffers that can be flushed.
150///
151/// This trait applies to any [`Screen`] that has data written to its frame buffer.
152pub trait Flush: Sealed {
153    /// Flushes the video buffer(s) for this screen.
154    ///
155    /// Note that you must still call [`Swap::swap_buffers`] after this method for the buffer contents to be displayed.
156    #[doc(alias = "gfxFlushBuffers")]
157    fn flush_buffers(&mut self);
158}
159
160impl<S: Screen> Flush for S {
161    fn flush_buffers(&mut self) {
162        let framebuffer = self.raw_framebuffer();
163
164        // Flush the data array. `self.raw_framebuffer` should get the correct parameters for all kinds of screens
165        let _ = unsafe {
166            ctru_sys::GSPGPU_FlushDataCache(
167                framebuffer.ptr.cast(),
168                (framebuffer.height * framebuffer.width) as u32,
169            )
170        };
171    }
172}
173
174impl Flush for TopScreen3D<'_> {
175    /// Unlike most other implementations of [`Flush`], this flushes the buffers for both
176    /// the left and right sides of the top screen.
177    fn flush_buffers(&mut self) {
178        let (mut left, mut right) = self.split_mut();
179        left.flush_buffers();
180        right.flush_buffers();
181    }
182}
183
184/// The left side of the top screen, when using 3D mode.
185#[derive(Debug)]
186#[non_exhaustive]
187pub struct TopScreenLeft;
188
189/// The right side of the top screen, when using 3D mode.
190#[derive(Debug)]
191#[non_exhaustive]
192pub struct TopScreenRight;
193
194/// The bottom LCD screen.
195///
196/// Mutable access to this struct is required to write to the bottom screen's frame buffer.
197#[derive(Debug)]
198#[non_exhaustive]
199pub struct BottomScreen;
200
201/// Representation of a framebuffer for one [`Side`] of the top screen, or the entire bottom screen.
202///
203/// The inner pointer is only valid for one frame if double
204/// buffering is enabled. Data written to `ptr` will be rendered to the screen.
205#[derive(Debug)]
206pub struct RawFrameBuffer<'screen> {
207    /// Pointer to graphics data to be rendered.
208    pub ptr: *mut u8,
209    /// The width of the framebuffer in pixels.
210    pub width: usize,
211    /// The height of the framebuffer in pixels.
212    pub height: usize,
213    /// Keep a mutable reference to the Screen for which this framebuffer is tied.
214    screen: PhantomData<&'screen mut dyn Screen>,
215}
216
217/// Side of the [`TopScreen`]'s framebuffer.
218///
219/// The top screen of the 3DS can have two separate sets of framebuffers to support its 3D functionality
220#[doc(alias = "gfx3dSide_t")]
221#[derive(Copy, Clone, Debug, PartialEq, Eq)]
222#[repr(u8)]
223pub enum Side {
224    /// The left framebuffer. This framebuffer is also the one used when 3D is disabled
225    Left = ctru_sys::GFX_LEFT,
226    /// The right framebuffer
227    Right = ctru_sys::GFX_RIGHT,
228}
229
230/// Handle to the GFX service.
231///
232/// This service is a wrapper around the lower-level [GSPGPU](crate::services::gspgpu) service that
233/// provides helper functions and utilities for software rendering.
234pub struct Gfx {
235    /// Top screen representation.
236    pub top_screen: RefCell<TopScreen>,
237    /// Bottom screen representation.
238    pub bottom_screen: RefCell<BottomScreen>,
239    _service_handler: ServiceReference,
240}
241
242pub(crate) static GFX_ACTIVE: Mutex<()> = Mutex::new(());
243
244impl Gfx {
245    /// Initialize a new default service handle.
246    ///
247    /// # Notes
248    ///
249    /// The new `Gfx` instance will allocate the needed framebuffers in the CPU-GPU shared memory region (to ensure compatibiltiy with all possible uses of the `Gfx` service).
250    /// As such, it's the same as calling:
251    ///
252    /// ```
253    /// # let _runner = test_runner::GdbRunner::default();
254    /// # use std::error::Error;
255    /// # fn main() -> Result<(), Box<dyn Error>> {
256    /// #
257    /// # use ctru::services::gfx::Gfx;
258    /// # use ctru::services::gspgpu::FramebufferFormat;
259    /// #
260    /// Gfx::with_formats_shared(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8)?;
261    /// #
262    /// # Ok(())
263    /// # }
264    /// ```
265    ///
266    /// Have a look at [`Gfx::with_formats_vram()`] if you aren't interested in manipulating the framebuffers using the CPU.
267    ///
268    /// # Example
269    ///
270    /// ```
271    /// # let _runner = test_runner::GdbRunner::default();
272    /// # use std::error::Error;
273    /// # fn main() -> Result<(), Box<dyn Error>> {
274    /// #
275    /// use ctru::services::gfx::Gfx;
276    ///
277    /// let gfx = Gfx::new()?;
278    /// #
279    /// # Ok(())
280    /// # }
281    /// ```
282    #[doc(alias = "gfxInit")]
283    pub fn new() -> Result<Self> {
284        Gfx::with_formats_shared(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8)
285    }
286
287    /// Initialize a new service handle with the chosen framebuffer formats on the HEAP for the top and bottom screens.
288    ///
289    /// Use [`Gfx::new()`] instead of this function to initialize the module with default parameters
290    ///
291    /// # Example
292    ///
293    /// ```
294    /// # let _runner = test_runner::GdbRunner::default();
295    /// # use std::error::Error;
296    /// # fn main() -> Result<(), Box<dyn Error>> {
297    /// #
298    /// use ctru::services::gfx::Gfx;
299    /// use ctru::services::gspgpu::FramebufferFormat;
300    ///
301    /// // Top screen uses RGBA8, bottom screen uses RGB565.
302    /// // The screen buffers are allocated in the standard HEAP memory, and not in VRAM.
303    /// let gfx = Gfx::with_formats_shared(FramebufferFormat::Rgba8, FramebufferFormat::Rgb565)?;
304    /// #
305    /// # Ok(())
306    /// # }
307    /// ```
308    #[doc(alias = "gfxInit")]
309    pub fn with_formats_shared(
310        top_fb_fmt: FramebufferFormat,
311        bottom_fb_fmt: FramebufferFormat,
312    ) -> Result<Self> {
313        Self::with_configuration(top_fb_fmt, bottom_fb_fmt, false)
314    }
315
316    /// Initialize a new service handle with the chosen framebuffer formats on the VRAM for the top and bottom screens.
317    ///
318    /// # Notes
319    ///
320    /// Though unsafe to do so, it's suggested to use VRAM buffers when working exclusively with the GPU,
321    /// since they result in faster performance and less memory waste.
322    ///
323    /// # Safety
324    ///
325    /// By initializing the [`Gfx`] service as such, all functionality that relies on CPU manipulation of the framebuffers will
326    /// be completely unavailable (usually resulting in an ARM panic if wrongly used).
327    ///
328    /// Usage of functionality such as [`Console`](crate::console::Console) and [`Screen::raw_framebuffer()`] will result in ARM exceptions.
329    ///
330    /// # Example
331    ///
332    /// ```
333    /// # use std::error::Error;
334    /// # fn main() -> Result<(), Box<dyn Error>> {
335    /// #
336    /// use ctru::services::{gfx::Gfx, gspgpu::FramebufferFormat};
337    ///
338    /// // Top screen uses RGBA8, bottom screen uses RGB565.
339    /// // The screen buffers are allocated in the in VRAM, so they will NOT be accessible from the CPU.
340    /// let gfx = unsafe { Gfx::with_formats_vram(FramebufferFormat::Rgba8, FramebufferFormat::Rgb565)? };
341    /// #
342    /// # Ok(())
343    /// # }
344    /// ```
345    #[doc(alias = "gfxInit")]
346    pub unsafe fn with_formats_vram(
347        top_fb_fmt: FramebufferFormat,
348        bottom_fb_fmt: FramebufferFormat,
349    ) -> Result<Self> {
350        Self::with_configuration(top_fb_fmt, bottom_fb_fmt, true)
351    }
352
353    // Internal function to handle the initialization of `Gfx`.
354    fn with_configuration(
355        top_fb_fmt: FramebufferFormat,
356        bottom_fb_fmt: FramebufferFormat,
357        vram_buffer: bool,
358    ) -> Result<Self> {
359        let handler = ServiceReference::new(
360            &GFX_ACTIVE,
361            || unsafe {
362                ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), vram_buffer);
363
364                Ok(())
365            },
366            || unsafe { ctru_sys::gfxExit() },
367        )?;
368
369        Ok(Self {
370            top_screen: RefCell::new(TopScreen::new()),
371            bottom_screen: RefCell::new(BottomScreen),
372            _service_handler: handler,
373        })
374    }
375
376    /// Waits for the vertical blank event.
377    ///
378    /// Use this to synchronize your application with the refresh rate of the LCD screens
379    ///
380    /// # Example
381    ///
382    /// ```
383    /// # let _runner = test_runner::GdbRunner::default();
384    /// # use std::error::Error;
385    /// # fn main() -> Result<(), Box<dyn Error>> {
386    /// #
387    /// use ctru::services::apt::Apt;
388    /// use ctru::services::gfx::Gfx;
389    /// let apt = Apt::new()?;
390    /// let gfx = Gfx::new()?;
391    ///
392    /// // Simple main loop.
393    /// while apt.main_loop() {
394    ///     // Main program logic
395    ///
396    ///     // Wait for the screens to refresh.
397    ///     // This blocks the current thread to make it run at 60Hz.
398    ///     gfx.wait_for_vblank();
399    /// }
400    /// #
401    /// # Ok(())
402    /// # }
403    /// ```
404    pub fn wait_for_vblank(&self) {
405        gspgpu::wait_for_event(gspgpu::Event::VBlank0, true);
406    }
407}
408
409impl TopScreen3D<'_> {
410    /// Immutably borrow the two sides of the screen as `(left, right)`.
411    pub fn split(&self) -> (Ref<'_, TopScreenLeft>, Ref<'_, TopScreenRight>) {
412        Ref::map_split(self.screen.borrow(), |screen| (&screen.left, &screen.right))
413    }
414
415    /// Mutably borrow the two sides of the screen as `(left, right)`.
416    pub fn split_mut(&self) -> (RefMut<'_, TopScreenLeft>, RefMut<'_, TopScreenRight>) {
417        RefMut::map_split(self.screen.borrow_mut(), |screen| {
418            (&mut screen.left, &mut screen.right)
419        })
420    }
421}
422
423/// Convert the [`TopScreen`] into a [`TopScreen3D`] and activate stereoscopic 3D.
424///
425/// # Example
426///
427/// ```
428/// # let _runner = test_runner::GdbRunner::default();
429/// # use std::error::Error;
430/// # fn main() -> Result<(), Box<dyn Error>> {
431/// #
432/// use ctru::services::gfx::{Gfx, TopScreen, TopScreen3D};
433/// let gfx = Gfx::new()?;
434///
435/// let mut top_screen = TopScreen3D::from(&gfx.top_screen);
436///
437/// let (left, right) = top_screen.split_mut();
438///
439/// // Rendering must be done twice for each side
440/// // (with a slight variation in perspective to simulate the eye-to-eye distance).
441/// render(left);
442/// render(right);
443/// #
444/// # Ok(())
445/// # }
446/// #
447/// # use ctru::services::gfx::Screen;
448/// # use std::cell::RefMut;
449/// # fn render(screen: RefMut<'_, dyn Screen>) {}
450/// ```
451impl<'screen> From<&'screen RefCell<TopScreen>> for TopScreen3D<'screen> {
452    #[doc(alias = "gfxSet3D")]
453    fn from(top_screen: &'screen RefCell<TopScreen>) -> Self {
454        unsafe {
455            ctru_sys::gfxSet3D(true);
456        }
457
458        TopScreen3D { screen: top_screen }
459    }
460}
461
462impl Drop for TopScreen3D<'_> {
463    fn drop(&mut self) {
464        unsafe {
465            ctru_sys::gfxSet3D(false);
466        }
467    }
468}
469
470impl TopScreen {
471    fn new() -> Self {
472        Self {
473            left: TopScreenLeft,
474            right: TopScreenRight,
475        }
476    }
477
478    /// Enable or disable wide mode on the top screen.
479    ///
480    /// # Notes
481    ///
482    /// [`Swap::swap_buffers`] must be called after this method for the configuration
483    /// to take effect.
484    ///
485    /// Wide mode does NOT work on Old 2DS models (but still does on New 2DS XL models).
486    #[doc(alias = "gfxSetWide")]
487    pub fn set_wide_mode(&mut self, enable: bool) {
488        unsafe {
489            ctru_sys::gfxSetWide(enable);
490        }
491    }
492
493    /// Returns whether or not wide mode is enabled on the top screen.
494    #[doc(alias = "gfxIsWide")]
495    pub fn is_wide(&self) -> bool {
496        unsafe { ctru_sys::gfxIsWide() }
497    }
498}
499
500// When 3D mode is disabled, only the left side is used, so this Screen impl
501// just forwards everything to the TopScreenLeft.
502impl Screen for TopScreen {
503    fn as_raw(&self) -> ctru_sys::gfxScreen_t {
504        self.left.as_raw()
505    }
506
507    fn side(&self) -> Side {
508        self.left.side()
509    }
510}
511
512impl Screen for TopScreenLeft {
513    fn as_raw(&self) -> ctru_sys::gfxScreen_t {
514        ctru_sys::GFX_TOP
515    }
516
517    fn side(&self) -> Side {
518        Side::Left
519    }
520}
521
522impl Screen for TopScreenRight {
523    fn as_raw(&self) -> ctru_sys::gfxScreen_t {
524        ctru_sys::GFX_TOP
525    }
526
527    fn side(&self) -> Side {
528        Side::Right
529    }
530}
531
532impl Screen for BottomScreen {
533    fn as_raw(&self) -> ctru_sys::gfxScreen_t {
534        ctru_sys::GFX_BOTTOM
535    }
536
537    fn side(&self) -> Side {
538        Side::Left
539    }
540}
541
542from_impl!(Side, ctru_sys::gfx3dSide_t);
543
544#[cfg(test)]
545mod tests {
546    use super::*;
547    use crate::Error;
548
549    #[test]
550    fn gfx_duplicate() {
551        // NOTE: this is expected to fail if using the console test runner, since
552        // that necessarily creates a Gfx as part of its test setup:
553        let _gfx = Gfx::new().unwrap();
554
555        assert!(matches!(Gfx::new(), Err(Error::ServiceAlreadyActive)));
556    }
557}