Skip to content

Commit f0bd5b0

Browse files
committed
Add non-exhaustive Drop impl for RenderPass
1 parent e5fd46a commit f0bd5b0

File tree

1 file changed

+120
-1
lines changed

1 file changed

+120
-1
lines changed

citro3d/src/render.rs

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,17 @@ struct Frame;
8787
pub struct RenderPass<'pass> {
8888
texenvs: [OnceCell<TexEnv>; texenv::TEXENV_COUNT],
8989
_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+
9096
_phantom: PhantomData<&'pass mut Instance>,
9197
}
9298

9399
impl<'pass> RenderPass<'pass> {
94-
pub(crate) fn new(_istance: &'pass mut Instance) -> Self {
100+
pub(crate) fn new(_instance: &'pass mut Instance) -> Self {
95101
Self {
96102
texenvs: [
97103
// thank goodness there's only six of them!
@@ -103,6 +109,7 @@ impl<'pass> RenderPass<'pass> {
103109
OnceCell::new(),
104110
],
105111
_active_frame: Frame::new(),
112+
is_program_bound: false,
106113
_phantom: PhantomData,
107114
}
108115
}
@@ -161,8 +168,17 @@ impl<'pass> RenderPass<'pass> {
161168
}
162169

163170
/// Render primitives from the current vertex array buffer.
171+
///
172+
/// # Panics
173+
///
174+
/// Panics if no shader program was bound (see [`RenderPass::bind_program`]).
164175
#[doc(alias = "C3D_DrawArrays")]
165176
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+
166182
self.set_buffer_info(vbo_data.info());
167183

168184
// TODO: should we also require the attrib info directly here?
@@ -176,13 +192,21 @@ impl<'pass> RenderPass<'pass> {
176192
}
177193

178194
/// Draws the vertices in `buf` indexed by `indices`.
195+
///
196+
/// # Panics
197+
///
198+
/// Panics if no shader program was bound (see [`RenderPass::bind_program`]).
179199
#[doc(alias = "C3D_DrawElements")]
180200
pub fn draw_elements<I: Index>(
181201
&mut self,
182202
primitive: buffer::Primitive,
183203
vbo_data: buffer::Slice<'pass>,
184204
indices: &Indices<'pass, I>,
185205
) {
206+
if !self.is_program_bound {
207+
panic!("tried to draw elements when no shader program is bound");
208+
}
209+
186210
self.set_buffer_info(vbo_data.info());
187211

188212
let indices = &indices.buffer;
@@ -206,6 +230,8 @@ impl<'pass> RenderPass<'pass> {
206230
unsafe {
207231
citro3d_sys::C3D_BindProgram(program.as_raw().cast_mut());
208232
}
233+
234+
self.is_program_bound = true;
209235
}
210236

211237
/// Binds a [`LightEnv`] for the following draw calls.
@@ -217,6 +243,10 @@ impl<'pass> RenderPass<'pass> {
217243

218244
/// Bind a uniform to the given `index` in the vertex shader for the next draw call.
219245
///
246+
/// # Panics
247+
///
248+
/// Panics if no shader program was bound (see [`RenderPass::bind_program`]).
249+
///
220250
/// # Example
221251
///
222252
/// ```
@@ -230,12 +260,20 @@ impl<'pass> RenderPass<'pass> {
230260
/// instance.bind_vertex_uniform(idx, &mtx);
231261
/// ```
232262
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+
233267
// LIFETIME SAFETY: Uniform data is copied into global buffers.
234268
uniform.into().bind(self, shader::Type::Vertex, index);
235269
}
236270

237271
/// Bind a uniform to the given `index` in the geometry shader for the next draw call.
238272
///
273+
/// # Panics
274+
///
275+
/// Panics if no shader program was bound (see [`RenderPass::bind_program`]).
276+
///
239277
/// # Example
240278
///
241279
/// ```
@@ -249,6 +287,10 @@ impl<'pass> RenderPass<'pass> {
249287
/// instance.bind_geometry_uniform(idx, &mtx);
250288
/// ```
251289
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+
252294
// LIFETIME SAFETY: Uniform data is copied into global buffers.
253295
uniform.into().bind(self, shader::Type::Geometry, index);
254296
}
@@ -388,3 +430,80 @@ impl DepthFormat {
388430
}
389431
}
390432
}
433+
434+
impl 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+
}

0 commit comments

Comments
 (0)