citro3d/
render.rs

1//! This module provides render target types and options for controlling transfer
2//! of data to the GPU, including the format of color and depth data to be rendered.
3
4use std::marker::PhantomData;
5use std::pin::Pin;
6use std::rc::Rc;
7use std::{cell::RefMut, ops::Range};
8
9use citro3d_sys::{C3D_DEPTHTYPE, C3D_RenderTargetCreate, C3D_RenderTargetDelete};
10use ctru::linear::LinearAllocator;
11use ctru::services::gfx::Screen;
12use ctru::services::gspgpu::FramebufferFormat;
13use ctru_sys::{GPU_COLORBUF, GPU_DEPTHBUF};
14
15use crate::{
16    Error, Instance, RenderQueue, Result, attrib,
17    buffer::{self, Index},
18    light::LightEnv,
19    render, shader,
20    texenv::{self, TexEnv},
21    texture,
22    uniform::{self, Uniform},
23};
24
25pub mod effect;
26mod transfer;
27
28bitflags::bitflags! {
29    /// Indicate whether color, depth buffer, or both values should be cleared.
30    #[doc(alias = "C3D_ClearBits")]
31    pub struct ClearFlags: u8 {
32        /// Clear the color of the render target.
33        const COLOR = citro3d_sys::C3D_CLEAR_COLOR;
34        /// Clear the depth buffer value of the render target.
35        const DEPTH = citro3d_sys::C3D_CLEAR_DEPTH;
36        /// Clear both color and depth buffer values of the render target.
37        const ALL = citro3d_sys::C3D_CLEAR_ALL;
38    }
39}
40
41/// The color format to use when rendering on the GPU.
42#[repr(u8)]
43#[derive(Clone, Copy, Debug)]
44#[doc(alias = "GPU_COLORBUF")]
45pub enum ColorFormat {
46    /// 8-bit Red + 8-bit Green + 8-bit Blue + 8-bit Alpha.
47    RGBA8 = ctru_sys::GPU_RB_RGBA8,
48    /// 8-bit Red + 8-bit Green + 8-bit Blue.
49    RGB8 = ctru_sys::GPU_RB_RGB8,
50    /// 5-bit Red + 5-bit Green + 5-bit Blue + 1-bit Alpha.
51    RGBA5551 = ctru_sys::GPU_RB_RGBA5551,
52    /// 5-bit Red + 6-bit Green + 5-bit Blue.
53    RGB565 = ctru_sys::GPU_RB_RGB565,
54    /// 4-bit Red + 4-bit Green + 4-bit Blue + 4-bit Alpha.
55    RGBA4 = ctru_sys::GPU_RB_RGBA4,
56}
57
58/// The depth buffer format to use when rendering.
59#[repr(u8)]
60#[derive(Clone, Copy, Debug)]
61#[doc(alias = "GPU_DEPTHBUF")]
62#[doc(alias = "C3D_DEPTHTYPE")]
63pub enum DepthFormat {
64    /// 16-bit depth.
65    Depth16 = ctru_sys::GPU_RB_DEPTH16,
66    /// 24-bit depth.
67    Depth24 = ctru_sys::GPU_RB_DEPTH24,
68    /// 24-bit depth + 8-bit Stencil.
69    Depth24Stencil8 = ctru_sys::GPU_RB_DEPTH24_STENCIL8,
70}
71
72/// A render target for `citro3d`. Frame data will be written to this target
73/// to be rendered on the GPU and used as a source for further rendering.
74#[doc(alias = "C3D_RenderTarget")]
75pub trait Target {
76    /// Return the underlying `citro3d` render target for this target.
77    fn as_raw(&self) -> *mut citro3d_sys::C3D_RenderTarget;
78
79    /// Clear the render target with the given 32-bit RGBA color and depth buffer value.
80    /// Use `flags` to specify whether color and/or depth should be overwritten.
81    #[doc(alias = "C3D_RenderTargetClear")]
82    fn clear(&mut self, flags: ClearFlags, rgba_color: u32, depth: u32) {
83        unsafe {
84            citro3d_sys::C3D_RenderTargetClear(self.as_raw(), flags.bits(), rgba_color, depth);
85        }
86    }
87}
88
89pub struct TextureTarget {
90    raw: *mut citro3d_sys::C3D_RenderTarget,
91    texture: texture::Texture,
92    _queue: Rc<RenderQueue>,
93}
94
95impl TextureTarget {
96    pub(crate) fn new(
97        mut texture: texture::Texture,
98        face: texture::Face,
99        depth_format: Option<DepthFormat>,
100        queue: Rc<RenderQueue>,
101    ) -> Result<Self> {
102        if !texture.in_vram {
103            return Err(Error::InvalidMemoryLocation);
104        }
105
106        let raw = unsafe {
107            citro3d_sys::C3D_RenderTargetCreateFromTex(
108                &mut texture.tex as *mut _,
109                face as _,
110                0,
111                depth_format.map_or(C3D_DEPTHTYPE { __i: -1 }, DepthFormat::as_raw),
112            )
113        };
114
115        if raw.is_null() {
116            return Err(Error::FailedToInitialize);
117        }
118
119        Ok(TextureTarget {
120            raw,
121            texture,
122            _queue: queue,
123        })
124    }
125
126    pub fn texture(&self) -> &texture::Texture {
127        &self.texture
128    }
129
130    pub fn texture_mut(&mut self) -> &mut texture::Texture {
131        &mut self.texture
132    }
133}
134
135impl Target for TextureTarget {
136    fn as_raw(&self) -> *mut citro3d_sys::C3D_RenderTarget {
137        self.raw
138    }
139}
140
141impl Drop for TextureTarget {
142    #[doc(alias = "C3D_RenderTargetDelete")]
143    fn drop(&mut self) {
144        unsafe {
145            C3D_RenderTargetDelete(self.raw);
146        }
147    }
148}
149
150/// A render target for `citro3d`. Frame data will be written to this target
151/// to be rendered on the GPU and displayed on the screen.
152#[doc(alias = "C3D_RenderTarget")]
153pub struct ScreenTarget<'screen> {
154    raw: *mut citro3d_sys::C3D_RenderTarget,
155    // This is unused after construction, but ensures unique access to the
156    // screen this target writes to during rendering
157    _screen: RefMut<'screen, dyn Screen>,
158    _queue: Rc<RenderQueue>,
159}
160
161impl<'screen> ScreenTarget<'screen> {
162    /// Create a new render target with the given parameters. This takes a
163    /// [`RenderQueue`] parameter to make sure this  [`Target`] doesn't outlive
164    /// the render queue.
165    pub(crate) fn new(
166        width: usize,
167        height: usize,
168        screen: RefMut<'screen, dyn Screen>,
169        depth_format: Option<DepthFormat>,
170        queue: Rc<RenderQueue>,
171    ) -> Result<Self> {
172        let color_format: ColorFormat = screen.framebuffer_format().into();
173
174        let raw = unsafe {
175            C3D_RenderTargetCreate(
176                width.try_into()?,
177                height.try_into()?,
178                color_format as GPU_COLORBUF,
179                depth_format.map_or(C3D_DEPTHTYPE { __i: -1 }, DepthFormat::as_raw),
180            )
181        };
182
183        if raw.is_null() {
184            return Err(Error::FailedToInitialize);
185        }
186
187        // Set the render target to actually output to the given screen
188        let flags = transfer::Flags::default()
189            .in_format(color_format.into())
190            .out_format(color_format.into());
191
192        unsafe {
193            citro3d_sys::C3D_RenderTargetSetOutput(
194                raw,
195                screen.as_raw(),
196                screen.side().into(),
197                flags.bits(),
198            );
199        }
200
201        Ok(Self {
202            raw,
203            _screen: screen,
204            _queue: queue,
205        })
206    }
207}
208
209impl<'screen> Target for ScreenTarget<'screen> {
210    fn as_raw(&self) -> *mut citro3d_sys::C3D_RenderTarget {
211        self.raw
212    }
213}
214
215impl Drop for ScreenTarget<'_> {
216    #[doc(alias = "C3D_RenderTargetDelete")]
217    fn drop(&mut self) {
218        unsafe {
219            C3D_RenderTargetDelete(self.raw);
220        }
221    }
222}
223
224impl From<FramebufferFormat> for ColorFormat {
225    fn from(format: FramebufferFormat) -> Self {
226        match format {
227            FramebufferFormat::Rgba8 => Self::RGBA8,
228            FramebufferFormat::Rgb565 => Self::RGB565,
229            FramebufferFormat::Rgb5A1 => Self::RGBA5551,
230            FramebufferFormat::Rgba4 => Self::RGBA4,
231            // this one seems unusual, but it appears to work fine:
232            FramebufferFormat::Bgr8 => Self::RGB8,
233        }
234    }
235}
236
237impl DepthFormat {
238    fn as_raw(self) -> C3D_DEPTHTYPE {
239        C3D_DEPTHTYPE {
240            __e: self as GPU_DEPTHBUF,
241        }
242    }
243}
244
245#[non_exhaustive]
246#[must_use]
247pub struct Frame<'instance> {
248    // It is not valid behaviour to bind anything but a correct shader program.
249    // Instead of binding NULL, we simply force the user to have a shader program bound again
250    // before any draw calls.
251    is_program_bound: bool,
252    texenvs: [Option<TexEnv>; texenv::TEXENV_COUNT],
253    bound_textures: [bool; texture::Index::ALL.len()],
254    _phantom: PhantomData<&'instance mut Instance>,
255}
256
257impl<'instance> Frame<'instance> {
258    pub(crate) fn new(_instance: &'instance mut Instance) -> Self {
259        unsafe {
260            citro3d_sys::C3D_FrameBegin(
261                // TODO: begin + end flags should be configurable
262                citro3d_sys::C3D_FRAME_SYNCDRAW,
263            )
264        };
265
266        Self {
267            is_program_bound: false,
268            texenvs: [None; texenv::TEXENV_COUNT],
269            bound_textures: [false; texture::Index::ALL.len()],
270            _phantom: PhantomData,
271        }
272    }
273
274    /// Select the given render target for the following draw calls.
275    ///
276    /// # Errors
277    ///
278    /// Fails if the given target cannot be used for drawing.
279    #[doc(alias = "C3D_FrameDrawOn")]
280    pub fn select_render_target<T: render::Target>(&mut self, target: &'instance T) -> Result<()> {
281        let _ = self;
282        if unsafe { citro3d_sys::C3D_FrameDrawOn(target.as_raw()) } {
283            Ok(())
284        } else {
285            Err(Error::InvalidRenderTarget)
286        }
287    }
288
289    /// Set the buffer info to use for for the following draw calls.
290    #[doc(alias = "C3D_SetBufInfo")]
291    pub fn set_buffer_info(&mut self, buffer_info: &'instance buffer::Info) {
292        // LIFETIME SAFETY: The internal buffers of `buffer_info` must be kept alive
293        unsafe { citro3d_sys::C3D_SetBufInfo(buffer_info.as_raw()) };
294    }
295
296    /// Get the attribute info being used, if it exists.
297    ///
298    /// # Notes
299    ///
300    /// The resulting [`attrib::Info`] is copied (and not taken) from the one currently in use.
301    #[doc(alias = "C3D_GetAttrInfo")]
302    pub fn attr_info(&self) -> Option<attrib::Info> {
303        let raw = unsafe { citro3d_sys::C3D_GetAttrInfo() };
304        attrib::Info::copy_from(raw)
305    }
306
307    /// Set the attribute info to use for any following draw calls.
308    #[doc(alias = "C3D_SetAttrInfo")]
309    pub fn set_attr_info(&mut self, attr_info: &attrib::Info) {
310        let raw: *const _ = &attr_info.0;
311        // LIFETIME SAFETY: C3D_SetAttrInfo actually copies the pointee instead of mutating it.
312        unsafe { citro3d_sys::C3D_SetAttrInfo(raw.cast_mut()) };
313    }
314
315    /// Render primitives from the current vertex array buffer.
316    /// An optional `range` parameter can be provided to use a contiguous sub-
317    /// array of vertices, if none is provided the entire range will be used.
318    ///
319    /// # Panics
320    ///
321    /// * If no shader program was bound (see [`Frame::bind_program`]).
322    /// * If there are no buffers registered with this [`buffer::Info`].
323    #[doc(alias = "C3D_DrawArrays")]
324    pub fn draw_arrays(
325        &mut self,
326        primitive: buffer::Primitive,
327        buffers: &'instance buffer::Info,
328        range: Option<Range<u16>>,
329    ) -> Result<()> {
330        // TODO: Decide whether it's worth returning an `Error` instead of panicking.
331        if !self.is_program_bound {
332            panic!("Tried to draw arrays when no shader program is bound");
333        }
334
335        let len = buffers.len();
336
337        let start = range.as_ref().map(|r| r.start).unwrap_or(0);
338        if start >= len {
339            return Err(Error::IndexOutOfBounds {
340                idx: start as _,
341                len: len as _,
342            });
343        }
344
345        // len is verified to be >0 from checking the starting index
346        let end = range.as_ref().map(|r| r.end).unwrap_or(len - 1);
347        if end >= len {
348            return Err(Error::IndexOutOfBounds {
349                idx: end as _,
350                len: len as _,
351            });
352        }
353
354        // Ensure that any textures being referenced by the texture environment
355        // have been bound this frame, otherwise it could reference a texture
356        // that was bound in a previous frame outside of its lifetime.
357        for src in self
358            .texenvs
359            .iter()
360            .flat_map(|te| te.as_ref())
361            .flat_map(|te| te.sources.iter())
362        {
363            let Some(index) = src.corresponding_index() else {
364                continue;
365            };
366
367            if !self.bound_textures[index as usize] {
368                panic!("Texenv referenced {src:?} but texture unit {index:?} was not bound.");
369            }
370        }
371
372        self.set_buffer_info(buffers);
373
374        // TODO: should we also require the attrib info directly here?
375        unsafe {
376            citro3d_sys::C3D_DrawArrays(
377                primitive as ctru_sys::GPU_Primitive_t,
378                start as _,
379                (end - start + 1) as _,
380            );
381        }
382
383        Ok(())
384    }
385
386    /// Draws the vertices in `buf` indexed by `indices`.
387    ///
388    /// # Panics
389    ///
390    /// Panics if no shader program was bound (see [`Frame::bind_program`]).
391    #[doc(alias = "C3D_DrawElements")]
392    #[allow(clippy::ptr_arg)] // `&Vec<I, LinearAllocator>` is required to ensure it's allocated in the correct memory space, however clippy insists we use `&[I]`
393    pub fn draw_elements<I: Index>(
394        &mut self,
395        primitive: buffer::Primitive,
396        vbo_data: &'instance buffer::Info,
397        indices: &'instance Vec<I, LinearAllocator>,
398    ) {
399        if !self.is_program_bound {
400            panic!("tried to draw elements when no shader program is bound");
401        }
402
403        // Ensure that any textures being referenced by the texture environment
404        // have been bound this frame, otherwise it could reference a texture
405        // that was bound in a previous frame outside of its lifetime.
406        for env in &self.texenvs {
407            let Some(env) = env.as_ref() else { continue };
408
409            for src in env.sources {
410                let Some(index) = src.corresponding_index() else {
411                    continue;
412                };
413
414                if !self.bound_textures[index as usize] {
415                    panic!("Texenv referenced {src:?} but texture unit {index:?} was not bound.");
416                }
417            }
418        }
419
420        self.set_buffer_info(vbo_data);
421
422        let elements = indices.as_ptr().cast();
423
424        unsafe {
425            citro3d_sys::C3D_DrawElements(
426                primitive as ctru_sys::GPU_Primitive_t,
427                indices.len().try_into().unwrap(),
428                // flag bit for short or byte
429                I::TYPE,
430                elements,
431            );
432        }
433    }
434
435    /// Use the given [`shader::Program`] for the following draw calls.
436    pub fn bind_program(&mut self, program: &'instance shader::Program) {
437        // SAFETY: AFAICT C3D_BindProgram just copies pointers from the given program,
438        // instead of mutating the pointee in any way that would cause UB
439        unsafe {
440            citro3d_sys::C3D_BindProgram(program.as_raw().cast_mut());
441        }
442
443        self.is_program_bound = true;
444    }
445
446    /// Binds a [`LightEnv`] for the following draw calls.
447    pub fn bind_light_env(&mut self, env: Option<Pin<&'instance mut LightEnv>>) {
448        unsafe {
449            citro3d_sys::C3D_LightEnvBind(env.map_or(std::ptr::null_mut(), |env| env.as_raw_mut()));
450        }
451    }
452
453    /// Bind a uniform to the given `index` in the vertex shader for the next draw call.
454    ///
455    /// # Panics
456    ///
457    /// Panics if no shader program was bound (see [`Frame::bind_program`]).
458    ///
459    /// # Example
460    ///
461    /// ```
462    /// # let _runner = test_runner::GdbRunner::default();
463    /// # use citro3d::uniform;
464    /// # use citro3d::math::Matrix4;
465    /// #
466    /// # let mut instance = citro3d::Instance::new().unwrap();
467    /// let idx = uniform::Index::from(0);
468    /// let mtx = Matrix4::identity();
469    /// instance.bind_vertex_uniform(idx, &mtx);
470    /// ```
471    pub fn bind_vertex_uniform<'a>(
472        &mut self,
473        index: uniform::Index,
474        uniform: impl Into<Uniform<'a>>,
475    ) {
476        if !self.is_program_bound {
477            panic!("tried to bind vertex uniform when no shader program is bound");
478        }
479
480        // LIFETIME SAFETY: Uniform data is copied into global buffers.
481        uniform.into().bind(self, shader::Type::Vertex, index);
482    }
483
484    /// Bind a uniform to the given `index` in the geometry shader for the next draw call.
485    ///
486    /// # Panics
487    ///
488    /// Panics if no shader program was bound (see [`Frame::bind_program`]).
489    ///
490    /// # Example
491    ///
492    /// ```
493    /// # let _runner = test_runner::GdbRunner::default();
494    /// # use citro3d::uniform;
495    /// # use citro3d::math::Matrix4;
496    /// #
497    /// # let mut instance = citro3d::Instance::new().unwrap();
498    /// let idx = uniform::Index::from(0);
499    /// let mtx = Matrix4::identity();
500    /// instance.bind_geometry_uniform(idx, &mtx);
501    /// ```
502    pub fn bind_geometry_uniform<'a>(
503        &mut self,
504        index: uniform::Index,
505        uniform: impl Into<Uniform<'a>>,
506    ) {
507        if !self.is_program_bound {
508            panic!("tried to bind geometry uniform when no shader program is bound");
509        }
510
511        // LIFETIME SAFETY: Uniform data is copied into global buffers.
512        uniform.into().bind(self, shader::Type::Geometry, index);
513    }
514
515    /// Set up to 6 stages of [`TexEnv`] to use.
516    /// If more than 6 stages are provided, the 7th onwards
517    /// will be ignored.
518    /// Retrieve the [`TexEnv`] for the given stage, initializing it first if necessary.
519    ///
520    /// # Example
521    ///
522    /// ```
523    /// # use citro3d::texenv;
524    /// let stage0 =
525    ///     texenv::TexEnv::new().src(texenv::Mode::BOTH, texenv::Source::PrimaryColor, None, None);
526    /// let texenv0 = frame.set_texenvs([stage0]);
527    /// ```
528    #[doc(alias = "C3D_SetTexEnv")]
529    pub fn set_texenvs(&mut self, texenvs: &[texenv::TexEnv]) {
530        for i in 0..texenv::TEXENV_COUNT {
531            self.texenvs[i] = texenvs.get(i).cloned();
532            if let Some(texenv) = &self.texenvs[i] {
533                texenv.set_texenv(i).unwrap();
534            } else {
535                unsafe {
536                    let texenv = texenv::TexEnv::get_texenv(i);
537                    texenv::TexEnv::init_reset(texenv);
538                }
539            }
540        }
541    }
542
543    /// Bind the given [`texture::Texture`] to the specified [`texture::Unit`], which should
544    /// correspond to a source or destination texture configured in the [`TexEnv`].
545    pub fn bind_texture(&mut self, index: texture::Index, texture: &'instance texture::Texture) {
546        unsafe { texture.bind(index) };
547        self.bound_textures[index as usize] = true;
548    }
549
550    pub fn set_cull_face(&mut self, cull: render::effect::CullMode) {
551        unsafe {
552            citro3d_sys::C3D_CullFace(cull as u8);
553        }
554    }
555}
556
557impl Drop for Frame<'_> {
558    fn drop(&mut self) {
559        unsafe {
560            citro3d_sys::C3D_FrameEnd(0);
561
562            // TODO: substitute as many as possible with safe wrappers.
563            // These resets are derived from the implementation of `C3D_Init` and by studying the `C3D_Context` struct.
564            citro3d_sys::C3D_DepthMap(true, -1.0, 0.0);
565            citro3d_sys::C3D_CullFace(ctru_sys::GPU_CULL_BACK_CCW);
566            citro3d_sys::C3D_StencilTest(false, ctru_sys::GPU_ALWAYS, 0x00, 0xFF, 0x00);
567            citro3d_sys::C3D_StencilOp(
568                ctru_sys::GPU_STENCIL_KEEP,
569                ctru_sys::GPU_STENCIL_KEEP,
570                ctru_sys::GPU_STENCIL_KEEP,
571            );
572            citro3d_sys::C3D_BlendingColor(0);
573            citro3d_sys::C3D_EarlyDepthTest(false, ctru_sys::GPU_EARLYDEPTH_GREATER, 0);
574            citro3d_sys::C3D_DepthTest(true, ctru_sys::GPU_GREATER, ctru_sys::GPU_WRITE_ALL);
575            citro3d_sys::C3D_AlphaTest(false, ctru_sys::GPU_ALWAYS, 0x00);
576            citro3d_sys::C3D_AlphaBlend(
577                ctru_sys::GPU_BLEND_ADD,
578                ctru_sys::GPU_BLEND_ADD,
579                ctru_sys::GPU_SRC_ALPHA,
580                ctru_sys::GPU_ONE_MINUS_SRC_ALPHA,
581                ctru_sys::GPU_SRC_ALPHA,
582                ctru_sys::GPU_ONE_MINUS_SRC_ALPHA,
583            );
584            citro3d_sys::C3D_FragOpMode(ctru_sys::GPU_FRAGOPMODE_GL);
585            citro3d_sys::C3D_FragOpShadow(0.0, 1.0);
586
587            // The texCoordId has no importance since we are binding NULL
588            citro3d_sys::C3D_ProcTexBind(0, std::ptr::null_mut());
589
590            // ctx->texConfig = BIT(12); I have not found a way to replicate this one yet (maybe not necessary because of texenv's unbinding).
591
592            // ctx->texShadow = BIT(0);
593            citro3d_sys::C3D_TexShadowParams(true, 0.0);
594
595            // ctx->texEnvBuf = 0; I have not found a way to replicate this one yet (maybe not necessary because of texenv's unbinding).
596
597            // ctx->texEnvBufClr = 0xFFFFFFFF;
598            citro3d_sys::C3D_TexEnvBufColor(0xFFFFFFFF);
599            // ctx->fogClr = 0;
600            citro3d_sys::C3D_FogColor(0);
601            //ctx->fogLut = NULL;
602            citro3d_sys::C3D_FogLutBind(std::ptr::null_mut());
603
604            // We don't need to unbind programs (and in citro3D you can't),
605            // since the user is forced to bind them again before drawing next time they render.
606
607            self.bind_light_env(None);
608
609            // TODO: C3D_TexBind doesn't work for NULL
610            // https://github.com/devkitPro/citro3d/blob/9f21cf7b380ce6f9e01a0420f19f0763e5443ca7/source/texture.c#L222
611            // As long as no bound texture environment references a texture outside of its lifetime,
612            // it will be fine to leave it bound?
613            // Therefore, we must ensure that future frames do not reference a texture unless a valid
614            // one has been bound in tis frame.
615            /*for i in 0..3 {
616                citro3d_sys::C3D_TexBind(i, std::ptr::null_mut());
617            }*/
618
619            for i in 0..texenv::TEXENV_COUNT {
620                texenv::TexEnv::init_reset(texenv::TexEnv::get_texenv(i));
621            }
622
623            // Unbind attribute information (can't use NULL pointer, so we use an empty attrib::Info instead).
624            //
625            // TODO: Drawing nothing actually hangs the GPU, so this code is never really helpful (also, not used since the flag makes it a non-issue).
626            //       Is it worth keeping? Could hanging be considered better than an ARM exception?
627            let empty_info = attrib::Info::default();
628            self.set_attr_info(&empty_info);
629
630            // ctx->fixedAttribDirty = 0;
631            // ctx->fixedAttribEverDirty = 0;
632            for i in 0..12 {
633                let vec = citro3d_sys::C3D_FixedAttribGetWritePtr(i);
634                (*vec).c = [0.0, 0.0, 0.0, 0.0];
635            }
636        }
637    }
638}