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}