1use 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 #[doc(alias = "C3D_ClearBits")]
30 pub struct ClearFlags: u8 {
31 const COLOR = citro3d_sys::C3D_CLEAR_COLOR;
33 const DEPTH = citro3d_sys::C3D_CLEAR_DEPTH;
35 const ALL = citro3d_sys::C3D_CLEAR_ALL;
37 }
38}
39
40#[repr(u8)]
42#[derive(Clone, Copy, Debug)]
43#[doc(alias = "GPU_COLORBUF")]
44pub enum ColorFormat {
45 RGBA8 = ctru_sys::GPU_RB_RGBA8,
47 RGB8 = ctru_sys::GPU_RB_RGB8,
49 RGBA5551 = ctru_sys::GPU_RB_RGBA5551,
51 RGB565 = ctru_sys::GPU_RB_RGB565,
53 RGBA4 = ctru_sys::GPU_RB_RGBA4,
55}
56
57#[repr(u8)]
59#[derive(Clone, Copy, Debug)]
60#[doc(alias = "GPU_DEPTHBUF")]
61#[doc(alias = "C3D_DEPTHTYPE")]
62pub enum DepthFormat {
63 Depth16 = ctru_sys::GPU_RB_DEPTH16,
65 Depth24 = ctru_sys::GPU_RB_DEPTH24,
67 Depth24Stencil8 = ctru_sys::GPU_RB_DEPTH24_STENCIL8,
69}
70
71#[doc(alias = "C3D_RenderTarget")]
74pub trait Target {
75 fn as_raw(&self) -> *mut citro3d_sys::C3D_RenderTarget;
77
78 #[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#[doc(alias = "C3D_RenderTarget")]
152pub struct ScreenTarget<'screen> {
153 raw: *mut citro3d_sys::C3D_RenderTarget,
154 _screen: RefMut<'screen, dyn Screen>,
157 _queue: Rc<RenderQueue>,
158}
159
160impl<'screen> ScreenTarget<'screen> {
161 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 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 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 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 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 #[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 #[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 #[doc(alias = "C3D_SetBufInfo")]
301 pub fn set_buffer_info(&mut self, buffer_info: &buffer::Info) {
302 let raw: *const _ = &buffer_info.0;
303 unsafe { citro3d_sys::C3D_SetBufInfo(raw.cast_mut()) };
305 }
306
307 #[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 #[doc(alias = "C3D_SetAttrInfo")]
320 pub fn set_attr_info(&mut self, attr_info: &attrib::Info) {
321 let raw: *const _ = &attr_info.0;
322 unsafe { citro3d_sys::C3D_SetAttrInfo(raw.cast_mut()) };
324 }
325
326 #[doc(alias = "C3D_DrawArrays")]
332 pub fn draw_arrays(
333 &mut self,
334 primitive: buffer::Primitive,
335 vbo_data: buffer::Slice<'instance>,
336 ) {
337 if !self.is_program_bound {
339 panic!("Tried to draw arrays when no shader program is bound");
340 }
341
342 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 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 #[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 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 I::TYPE,
416 elements,
417 );
418 }
419 }
420
421 pub fn bind_program(&mut self, program: &'instance shader::Program) {
423 unsafe {
426 citro3d_sys::C3D_BindProgram(program.as_raw().cast_mut());
427 }
428
429 self.is_program_bound = true;
430 }
431
432 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 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 uniform.into().bind(self, shader::Type::Vertex, index);
464 }
465
466 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 uniform.into().bind(self, shader::Type::Geometry, index);
491 }
492
493 #[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 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 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 citro3d_sys::C3D_ProcTexBind(0, std::ptr::null_mut());
567
568 citro3d_sys::C3D_TexShadowParams(true, 0.0);
572
573 citro3d_sys::C3D_TexEnvBufColor(0xFFFFFFFF);
577 citro3d_sys::C3D_FogColor(0);
579 citro3d_sys::C3D_FogLutBind(std::ptr::null_mut());
581
582 self.bind_light_env(None);
586
587 for i in 0..texenv::TEXENV_COUNT {
598 texenv::TexEnv::init_reset(texenv::TexEnv::get_texenv(i));
599 }
600
601 let empty_info = attrib::Info::default();
606 self.set_attr_info(&empty_info);
607
608 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}