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