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::{OnceCell, RefMut};
5use std::marker::PhantomData;
6use std::pin::Pin;
7use std::rc::Rc;
8
9use citro3d_sys::{
10    C3D_DEPTHTYPE, C3D_RenderTarget, C3D_RenderTargetCreate, C3D_RenderTargetDelete,
11};
12use ctru::services::gfx::Screen;
13use ctru::services::gspgpu::FramebufferFormat;
14use ctru_sys::{GPU_COLORBUF, GPU_DEPTHBUF};
15
16use crate::{
17    Error, Instance, RenderQueue, Result, attrib,
18    buffer::{self, Index, Indices},
19    light::LightEnv,
20    shader,
21    texenv::{self, TexEnv},
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 displayed on the screen.
74#[doc(alias = "C3D_RenderTarget")]
75pub struct Target<'screen> {
76    raw: *mut citro3d_sys::C3D_RenderTarget,
77    // This is unused after construction, but ensures unique access to the
78    // screen this target writes to during rendering
79    _screen: RefMut<'screen, dyn Screen>,
80    _queue: Rc<RenderQueue>,
81}
82
83struct Frame;
84
85#[non_exhaustive]
86#[must_use]
87pub struct RenderPass<'pass> {
88    texenvs: [OnceCell<TexEnv>; texenv::TEXENV_COUNT],
89    _active_frame: Frame,
90
91    // It is not valid behaviour to bind anything but a correct shader program.
92    // Instead of binding NULL, we simply force the user to have a shader program bound again
93    // before any draw calls.
94    is_program_bound: bool,
95
96    _phantom: PhantomData<&'pass mut Instance>,
97}
98
99impl<'pass> RenderPass<'pass> {
100    pub(crate) fn new(_instance: &'pass mut Instance) -> Self {
101        Self {
102            texenvs: [
103                // thank goodness there's only six of them!
104                OnceCell::new(),
105                OnceCell::new(),
106                OnceCell::new(),
107                OnceCell::new(),
108                OnceCell::new(),
109                OnceCell::new(),
110            ],
111            _active_frame: Frame::new(),
112            is_program_bound: false,
113            _phantom: PhantomData,
114        }
115    }
116
117    /// Select the given render target for the following draw calls.
118    ///
119    /// # Errors
120    ///
121    /// Fails if the given target cannot be used for drawing.
122    #[doc(alias = "C3D_FrameDrawOn")]
123    pub fn select_render_target(&mut self, target: &'pass Target<'_>) -> Result<()> {
124        let _ = self;
125        if unsafe { citro3d_sys::C3D_FrameDrawOn(target.as_raw()) } {
126            Ok(())
127        } else {
128            Err(Error::InvalidRenderTarget)
129        }
130    }
131
132    /// Get the buffer info being used, if it exists.
133    ///
134    /// # Notes
135    ///
136    /// The resulting [`buffer::Info`] is copied (and not taken) from the one currently in use.
137    #[doc(alias = "C3D_GetBufInfo")]
138    pub fn buffer_info(&self) -> Option<buffer::Info> {
139        let raw = unsafe { citro3d_sys::C3D_GetBufInfo() };
140        buffer::Info::copy_from(raw)
141    }
142
143    /// Set the buffer info to use for for the following draw calls.
144    #[doc(alias = "C3D_SetBufInfo")]
145    pub fn set_buffer_info(&mut self, buffer_info: &buffer::Info) {
146        let raw: *const _ = &buffer_info.0;
147        // LIFETIME SAFETY: C3D_SetBufInfo actually copies the pointee instead of mutating it.
148        unsafe { citro3d_sys::C3D_SetBufInfo(raw.cast_mut()) };
149    }
150
151    /// Get the attribute info being used, if it exists.
152    ///
153    /// # Notes
154    ///
155    /// The resulting [`attrib::Info`] is copied (and not taken) from the one currently in use.
156    #[doc(alias = "C3D_GetAttrInfo")]
157    pub fn attr_info(&self) -> Option<attrib::Info> {
158        let raw = unsafe { citro3d_sys::C3D_GetAttrInfo() };
159        attrib::Info::copy_from(raw)
160    }
161
162    /// Set the attribute info to use for any following draw calls.
163    #[doc(alias = "C3D_SetAttrInfo")]
164    pub fn set_attr_info(&mut self, attr_info: &attrib::Info) {
165        let raw: *const _ = &attr_info.0;
166        // LIFETIME SAFETY: C3D_SetAttrInfo actually copies the pointee instead of mutating it.
167        unsafe { citro3d_sys::C3D_SetAttrInfo(raw.cast_mut()) };
168    }
169
170    /// Render primitives from the current vertex array buffer.
171    ///
172    /// # Panics
173    ///
174    /// Panics if no shader program was bound (see [`RenderPass::bind_program`]).
175    #[doc(alias = "C3D_DrawArrays")]
176    pub fn draw_arrays(&mut self, primitive: buffer::Primitive, vbo_data: buffer::Slice<'pass>) {
177        // TODO: Decide whether it's worth returning an `Error` instead of panicking.
178        if !self.is_program_bound {
179            panic!("tried todraw arrays when no shader program is bound");
180        }
181
182        self.set_buffer_info(vbo_data.info());
183
184        // TODO: should we also require the attrib info directly here?
185        unsafe {
186            citro3d_sys::C3D_DrawArrays(
187                primitive as ctru_sys::GPU_Primitive_t,
188                vbo_data.index(),
189                vbo_data.len(),
190            );
191        }
192    }
193
194    /// Draws the vertices in `buf` indexed by `indices`.
195    ///
196    /// # Panics
197    ///
198    /// Panics if no shader program was bound (see [`RenderPass::bind_program`]).
199    #[doc(alias = "C3D_DrawElements")]
200    pub fn draw_elements<I: Index>(
201        &mut self,
202        primitive: buffer::Primitive,
203        vbo_data: buffer::Slice<'pass>,
204        indices: &Indices<'pass, I>,
205    ) {
206        if !self.is_program_bound {
207            panic!("tried to draw elements when no shader program is bound");
208        }
209
210        self.set_buffer_info(vbo_data.info());
211
212        let indices = &indices.buffer;
213        let elements = indices.as_ptr().cast();
214
215        unsafe {
216            citro3d_sys::C3D_DrawElements(
217                primitive as ctru_sys::GPU_Primitive_t,
218                indices.len().try_into().unwrap(),
219                // flag bit for short or byte
220                I::TYPE,
221                elements,
222            );
223        }
224    }
225
226    /// Use the given [`shader::Program`] for the following draw calls.
227    pub fn bind_program(&mut self, program: &'pass shader::Program) {
228        // SAFETY: AFAICT C3D_BindProgram just copies pointers from the given program,
229        // instead of mutating the pointee in any way that would cause UB
230        unsafe {
231            citro3d_sys::C3D_BindProgram(program.as_raw().cast_mut());
232        }
233
234        self.is_program_bound = true;
235    }
236
237    /// Binds a [`LightEnv`] for the following draw calls.
238    pub fn bind_light_env(&mut self, env: Option<Pin<&'pass mut LightEnv>>) {
239        unsafe {
240            citro3d_sys::C3D_LightEnvBind(env.map_or(std::ptr::null_mut(), |env| env.as_raw_mut()));
241        }
242    }
243
244    /// Bind a uniform to the given `index` in the vertex shader for the next draw call.
245    ///
246    /// # Panics
247    ///
248    /// Panics if no shader program was bound (see [`RenderPass::bind_program`]).
249    ///
250    /// # Example
251    ///
252    /// ```
253    /// # let _runner = test_runner::GdbRunner::default();
254    /// # use citro3d::uniform;
255    /// # use citro3d::math::Matrix4;
256    /// #
257    /// # let mut instance = citro3d::Instance::new().unwrap();
258    /// let idx = uniform::Index::from(0);
259    /// let mtx = Matrix4::identity();
260    /// instance.bind_vertex_uniform(idx, &mtx);
261    /// ```
262    pub fn bind_vertex_uniform(&mut self, index: uniform::Index, uniform: impl Into<Uniform>) {
263        if !self.is_program_bound {
264            panic!("tried to bind vertex uniform when no shader program is bound");
265        }
266
267        // LIFETIME SAFETY: Uniform data is copied into global buffers.
268        uniform.into().bind(self, shader::Type::Vertex, index);
269    }
270
271    /// Bind a uniform to the given `index` in the geometry shader for the next draw call.
272    ///
273    /// # Panics
274    ///
275    /// Panics if no shader program was bound (see [`RenderPass::bind_program`]).
276    ///
277    /// # Example
278    ///
279    /// ```
280    /// # let _runner = test_runner::GdbRunner::default();
281    /// # use citro3d::uniform;
282    /// # use citro3d::math::Matrix4;
283    /// #
284    /// # let mut instance = citro3d::Instance::new().unwrap();
285    /// let idx = uniform::Index::from(0);
286    /// let mtx = Matrix4::identity();
287    /// instance.bind_geometry_uniform(idx, &mtx);
288    /// ```
289    pub fn bind_geometry_uniform(&mut self, index: uniform::Index, uniform: impl Into<Uniform>) {
290        if !self.is_program_bound {
291            panic!("tried to bind geometry uniform when no shader program is bound");
292        }
293
294        // LIFETIME SAFETY: Uniform data is copied into global buffers.
295        uniform.into().bind(self, shader::Type::Geometry, index);
296    }
297
298    /// Retrieve the [`TexEnv`] for the given stage, initializing it first if necessary.
299    ///
300    /// # Example
301    ///
302    /// ```
303    /// # use citro3d::texenv;
304    /// # let _runner = test_runner::GdbRunner::default();
305    /// # let mut instance = citro3d::Instance::new().unwrap();
306    /// let stage0 = texenv::Stage::new(0).unwrap();
307    /// let texenv0 = instance.texenv(stage0);
308    /// ```
309    #[doc(alias = "C3D_GetTexEnv")]
310    #[doc(alias = "C3D_TexEnvInit")]
311    pub fn texenv(&mut self, stage: texenv::Stage) -> &mut texenv::TexEnv {
312        let texenv = &mut self.texenvs[stage.0];
313        texenv.get_or_init(|| TexEnv::new(stage));
314        // We have to do this weird unwrap to get a mutable reference,
315        // since there is no `get_mut_or_init` or equivalent
316        texenv.get_mut().unwrap()
317    }
318}
319
320impl<'screen> Target<'screen> {
321    /// Create a new render target with the given parameters. This takes a
322    /// [`RenderQueue`] parameter to make sure this  [`Target`] doesn't outlive
323    /// the render queue.
324    pub(crate) fn new(
325        width: usize,
326        height: usize,
327        screen: RefMut<'screen, dyn Screen>,
328        depth_format: Option<DepthFormat>,
329        queue: Rc<RenderQueue>,
330    ) -> Result<Self> {
331        let color_format: ColorFormat = screen.framebuffer_format().into();
332
333        let raw = unsafe {
334            C3D_RenderTargetCreate(
335                width.try_into()?,
336                height.try_into()?,
337                color_format as GPU_COLORBUF,
338                depth_format.map_or(C3D_DEPTHTYPE { __i: -1 }, DepthFormat::as_raw),
339            )
340        };
341
342        if raw.is_null() {
343            return Err(Error::FailedToInitialize);
344        }
345
346        // Set the render target to actually output to the given screen
347        let flags = transfer::Flags::default()
348            .in_format(color_format.into())
349            .out_format(color_format.into());
350
351        unsafe {
352            citro3d_sys::C3D_RenderTargetSetOutput(
353                raw,
354                screen.as_raw(),
355                screen.side().into(),
356                flags.bits(),
357            );
358        }
359
360        Ok(Self {
361            raw,
362            _screen: screen,
363            _queue: queue,
364        })
365    }
366
367    /// Clear the render target with the given 32-bit RGBA color and depth buffer value.
368    ///
369    /// Use `flags` to specify whether color and/or depth should be overwritten.
370    #[doc(alias = "C3D_RenderTargetClear")]
371    pub fn clear(&mut self, flags: ClearFlags, rgba_color: u32, depth: u32) {
372        unsafe {
373            citro3d_sys::C3D_RenderTargetClear(self.raw, flags.bits(), rgba_color, depth);
374        }
375    }
376
377    /// Return the underlying `citro3d` render target for this target.
378    pub(crate) fn as_raw(&self) -> *mut C3D_RenderTarget {
379        self.raw
380    }
381}
382
383impl Frame {
384    fn new() -> Self {
385        unsafe {
386            citro3d_sys::C3D_FrameBegin(
387                // TODO: begin + end flags should be configurable
388                citro3d_sys::C3D_FRAME_SYNCDRAW,
389            )
390        };
391
392        Self {}
393    }
394}
395
396impl Drop for Frame {
397    fn drop(&mut self) {
398        unsafe {
399            citro3d_sys::C3D_FrameEnd(0);
400        }
401    }
402}
403
404impl Drop for Target<'_> {
405    #[doc(alias = "C3D_RenderTargetDelete")]
406    fn drop(&mut self) {
407        unsafe {
408            C3D_RenderTargetDelete(self.raw);
409        }
410    }
411}
412
413impl From<FramebufferFormat> for ColorFormat {
414    fn from(format: FramebufferFormat) -> Self {
415        match format {
416            FramebufferFormat::Rgba8 => Self::RGBA8,
417            FramebufferFormat::Rgb565 => Self::RGB565,
418            FramebufferFormat::Rgb5A1 => Self::RGBA5551,
419            FramebufferFormat::Rgba4 => Self::RGBA4,
420            // this one seems unusual, but it appears to work fine:
421            FramebufferFormat::Bgr8 => Self::RGB8,
422        }
423    }
424}
425
426impl DepthFormat {
427    fn as_raw(self) -> C3D_DEPTHTYPE {
428        C3D_DEPTHTYPE {
429            __e: self as GPU_DEPTHBUF,
430        }
431    }
432}
433
434impl Drop for RenderPass<'_> {
435    fn drop(&mut self) {
436        unsafe {
437            // TODO: substitute as many as possible with safe wrappers.
438            // These resets are derived from the implementation of `C3D_Init` and by studying the `C3D_Context` struct.
439            citro3d_sys::C3D_DepthMap(true, -1.0, 0.0);
440            citro3d_sys::C3D_CullFace(ctru_sys::GPU_CULL_BACK_CCW);
441            citro3d_sys::C3D_StencilTest(false, ctru_sys::GPU_ALWAYS, 0x00, 0xFF, 0x00);
442            citro3d_sys::C3D_StencilOp(
443                ctru_sys::GPU_STENCIL_KEEP,
444                ctru_sys::GPU_STENCIL_KEEP,
445                ctru_sys::GPU_STENCIL_KEEP,
446            );
447            citro3d_sys::C3D_BlendingColor(0);
448            citro3d_sys::C3D_EarlyDepthTest(false, ctru_sys::GPU_EARLYDEPTH_GREATER, 0);
449            citro3d_sys::C3D_DepthTest(true, ctru_sys::GPU_GREATER, ctru_sys::GPU_WRITE_ALL);
450            citro3d_sys::C3D_AlphaTest(false, ctru_sys::GPU_ALWAYS, 0x00);
451            citro3d_sys::C3D_AlphaBlend(
452                ctru_sys::GPU_BLEND_ADD,
453                ctru_sys::GPU_BLEND_ADD,
454                ctru_sys::GPU_SRC_ALPHA,
455                ctru_sys::GPU_ONE_MINUS_SRC_ALPHA,
456                ctru_sys::GPU_SRC_ALPHA,
457                ctru_sys::GPU_ONE_MINUS_SRC_ALPHA,
458            );
459            citro3d_sys::C3D_FragOpMode(ctru_sys::GPU_FRAGOPMODE_GL);
460            citro3d_sys::C3D_FragOpShadow(0.0, 1.0);
461
462            // The texCoordId has no importance since we are binding NULL
463            citro3d_sys::C3D_ProcTexBind(0, std::ptr::null_mut());
464
465            // ctx->texConfig = BIT(12); I have not found a way to replicate this one yet (maybe not necessary because of texenv's unbinding).
466
467            // ctx->texShadow = BIT(0);
468            citro3d_sys::C3D_TexShadowParams(true, 0.0);
469
470            // ctx->texEnvBuf = 0; I have not found a way to replicate this one yet (maybe not necessary because of texenv's unbinding).
471
472            // ctx->texEnvBufClr = 0xFFFFFFFF;
473            citro3d_sys::C3D_TexEnvBufColor(0xFFFFFFFF);
474            // ctx->fogClr = 0;
475            citro3d_sys::C3D_FogColor(0);
476            //ctx->fogLut = NULL;
477            citro3d_sys::C3D_FogLutBind(std::ptr::null_mut());
478
479            // We don't need to unbind programs (and in citro3D you can't),
480            // since the user is forced to bind them again before drawing next time they render.
481
482            self.bind_light_env(None);
483
484            // TODO: C3D_TexBind doesn't work for NULL
485            // https://github.com/devkitPro/citro3d/blob/9f21cf7b380ce6f9e01a0420f19f0763e5443ca7/source/texture.c#L222
486            /*for i in 0..3 {
487                citro3d_sys::C3D_TexBind(i, std::ptr::null_mut());
488            }*/
489
490            for i in 0..6 {
491                self.texenv(texenv::Stage::new(i).unwrap()).reset();
492            }
493
494            // Unbind attribute information (can't use NULL pointer, so we use an empty attrib::Info instead).
495            //
496            // 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).
497            //       Is it worth keeping? Could hanging be considered better than an ARM exception?
498            let empty_info = attrib::Info::default();
499            self.set_attr_info(&empty_info);
500
501            // ctx->fixedAttribDirty = 0;
502            // ctx->fixedAttribEverDirty = 0;
503            for i in 0..12 {
504                let vec = citro3d_sys::C3D_FixedAttribGetWritePtr(i);
505                (*vec).c = [0.0, 0.0, 0.0, 0.0];
506            }
507        }
508    }
509}