1use 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 #[doc(alias = "C3D_ClearBits")]
31 pub struct ClearFlags: u8 {
32 const COLOR = citro3d_sys::C3D_CLEAR_COLOR;
34 const DEPTH = citro3d_sys::C3D_CLEAR_DEPTH;
36 const ALL = citro3d_sys::C3D_CLEAR_ALL;
38 }
39}
40
41#[repr(u8)]
43#[derive(Clone, Copy, Debug)]
44#[doc(alias = "GPU_COLORBUF")]
45pub enum ColorFormat {
46 RGBA8 = ctru_sys::GPU_RB_RGBA8,
48 RGB8 = ctru_sys::GPU_RB_RGB8,
50 RGBA5551 = ctru_sys::GPU_RB_RGBA5551,
52 RGB565 = ctru_sys::GPU_RB_RGB565,
54 RGBA4 = ctru_sys::GPU_RB_RGBA4,
56}
57
58#[repr(u8)]
60#[derive(Clone, Copy, Debug)]
61#[doc(alias = "GPU_DEPTHBUF")]
62#[doc(alias = "C3D_DEPTHTYPE")]
63pub enum DepthFormat {
64 Depth16 = ctru_sys::GPU_RB_DEPTH16,
66 Depth24 = ctru_sys::GPU_RB_DEPTH24,
68 Depth24Stencil8 = ctru_sys::GPU_RB_DEPTH24_STENCIL8,
70}
71
72#[doc(alias = "C3D_RenderTarget")]
75pub trait Target {
76 fn as_raw(&self) -> *mut citro3d_sys::C3D_RenderTarget;
78
79 #[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#[doc(alias = "C3D_RenderTarget")]
153pub struct ScreenTarget<'screen> {
154 raw: *mut citro3d_sys::C3D_RenderTarget,
155 _screen: RefMut<'screen, dyn Screen>,
158 _queue: Rc<RenderQueue>,
159}
160
161impl<'screen> ScreenTarget<'screen> {
162 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 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 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 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 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 #[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 #[doc(alias = "C3D_SetBufInfo")]
291 pub fn set_buffer_info(&mut self, buffer_info: &'instance buffer::Info) {
292 unsafe { citro3d_sys::C3D_SetBufInfo(buffer_info.as_raw()) };
294 }
295
296 #[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 #[doc(alias = "C3D_SetAttrInfo")]
309 pub fn set_attr_info(&mut self, attr_info: &attrib::Info) {
310 let raw: *const _ = &attr_info.0;
311 unsafe { citro3d_sys::C3D_SetAttrInfo(raw.cast_mut()) };
313 }
314
315 #[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 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 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 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 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 #[doc(alias = "C3D_DrawElements")]
392 #[allow(clippy::ptr_arg)] 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 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 I::TYPE,
430 elements,
431 );
432 }
433 }
434
435 pub fn bind_program(&mut self, program: &'instance shader::Program) {
437 unsafe {
440 citro3d_sys::C3D_BindProgram(program.as_raw().cast_mut());
441 }
442
443 self.is_program_bound = true;
444 }
445
446 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 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 uniform.into().bind(self, shader::Type::Vertex, index);
482 }
483
484 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 uniform.into().bind(self, shader::Type::Geometry, index);
513 }
514
515 #[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 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 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 citro3d_sys::C3D_ProcTexBind(0, std::ptr::null_mut());
589
590 citro3d_sys::C3D_TexShadowParams(true, 0.0);
594
595 citro3d_sys::C3D_TexEnvBufColor(0xFFFFFFFF);
599 citro3d_sys::C3D_FogColor(0);
601 citro3d_sys::C3D_FogLutBind(std::ptr::null_mut());
603
604 self.bind_light_env(None);
608
609 for i in 0..texenv::TEXENV_COUNT {
620 texenv::TexEnv::init_reset(texenv::TexEnv::get_texenv(i));
621 }
622
623 let empty_info = attrib::Info::default();
628 self.set_attr_info(&empty_info);
629
630 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}