@@ -87,11 +87,17 @@ struct Frame;
8787pub 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
9399impl < ' 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