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}