1use std::{
2 cell::{Cell, RefCell},
3 rc::Rc,
4};
5
6use super::{
7 WgpuRenderer,
8 callbacks::{
9 draw_callback_matches, draw_callback_reset_render_state, draw_callback_set_sampler_linear,
10 draw_callback_set_sampler_nearest,
11 },
12 core::{RendererContextState, RendererPublication},
13};
14use crate::{FrameResourceArena, RenderResources, RendererError, RendererResult, ShaderManager};
15use dear_imgui_rs::{
16 Context, ContextAttachment, ContextAttachmentError, ContextAttachmentLease,
17 ContextAttachmentRole, ContextAttachmentTeardownError, ContextBindingError, ContextDestroyed,
18 ContextTeardown, render::SynchronousRendererConsumer,
19};
20
21struct WgpuRendererDropAttachmentMarker;
22
23struct DeferredRendererResources {
28 context_state: Option<RendererContextState>,
29 backend_data: Option<crate::WgpuBackendData>,
30 shader_manager: Option<ShaderManager>,
31 texture_manager: Option<crate::WgpuTextureManager>,
32 default_texture: Option<wgpu::TextureView>,
33 renderer_consumer: Option<SynchronousRendererConsumer>,
34}
35
36impl DeferredRendererResources {
37 fn release_renderer_resources(
38 &mut self,
39 context: &ContextTeardown<'_>,
40 ) -> Result<(), ContextAttachmentTeardownError> {
41 let consumer = self.renderer_consumer.take().ok_or_else(|| {
42 ContextAttachmentTeardownError::new(
43 "deferred WGPU renderer resources lost their renderer consumer",
44 )
45 })?;
46
47 let result = context.with_renderer_texture_reset(&consumer, || {
52 self.backend_data.take();
53 self.shader_manager.take();
54 self.texture_manager.take();
55 self.default_texture.take();
56 Ok(())
57 });
58 if result.is_err() {
59 self.renderer_consumer = Some(consumer);
60 }
61 result
62 }
63
64 fn release_after_context_destroyed(mut self) {
65 drop(self.context_state.take());
68 }
69}
70
71struct RendererDropControl {
72 deferred: RefCell<Option<DeferredRendererResources>>,
73 publication: RefCell<Option<RendererPublication>>,
74 context_destroyed: Cell<bool>,
75 #[cfg(test)]
76 renderer_release_count: Cell<u32>,
77}
78
79struct RendererDropAttachment {
80 control: Rc<RendererDropControl>,
81}
82
83impl ContextAttachment for RendererDropAttachment {
84 fn quiesce(
85 &self,
86 context: &ContextTeardown<'_>,
87 ) -> Result<(), dear_imgui_rs::ContextAttachmentTeardownError> {
88 if let Some(publication) = self.control.publication.borrow().as_ref() {
89 context.with_bound_context(|| unsafe {
90 publication.clear_owned_raw_state_bound();
91 });
92 }
93 Ok(())
94 }
95
96 fn release_renderer_resources(
97 &self,
98 context: &ContextTeardown<'_>,
99 ) -> Result<(), ContextAttachmentTeardownError> {
100 if let Some(resources) = self.control.deferred.borrow_mut().as_mut() {
101 resources.release_renderer_resources(context)?;
102 #[cfg(test)]
103 self.control
104 .renderer_release_count
105 .set(self.control.renderer_release_count.get() + 1);
106 }
107 Ok(())
108 }
109
110 fn context_destroyed(&self, _context: ContextDestroyed) {
111 self.control.context_destroyed.set(true);
115 self.control.publication.borrow_mut().take();
116 if let Some(resources) = self.control.deferred.borrow_mut().take() {
117 resources.release_after_context_destroyed();
118 }
119 }
120}
121
122pub(super) struct RendererDropDeferral {
124 control: Rc<RendererDropControl>,
125 attachment: ContextAttachmentLease,
126}
127
128impl RendererDropDeferral {
129 pub(super) fn register(context: &mut Context) -> Result<Self, ContextAttachmentError> {
130 let control = Rc::new(RendererDropControl {
131 deferred: RefCell::new(None),
132 publication: RefCell::new(None),
133 context_destroyed: Cell::new(false),
134 #[cfg(test)]
135 renderer_release_count: Cell::new(0),
136 });
137 let attachment = context.register_attachment::<WgpuRendererDropAttachmentMarker>(
138 ContextAttachmentRole::Extension,
139 Rc::new(RendererDropAttachment {
140 control: Rc::clone(&control),
141 }),
142 )?;
143 Ok(Self {
144 control,
145 attachment,
146 })
147 }
148
149 pub(super) fn set_publication(&self, publication: RendererPublication) {
150 debug_assert!(self.control.publication.borrow().is_none());
151 self.control.publication.borrow_mut().replace(publication);
152 }
153
154 fn defer_to_context(self, resources: DeferredRendererResources) {
155 debug_assert!(self.control.deferred.borrow().is_none());
156 self.control.deferred.borrow_mut().replace(resources);
157 self.attachment.defer_to_context();
158 }
159
160 #[cfg(test)]
161 fn control_for_test(&self) -> Rc<RendererDropControl> {
162 Rc::clone(&self.control)
163 }
164}
165
166#[cfg(test)]
167impl RendererDropControl {
168 fn has_deferred_resources(&self) -> bool {
169 self.deferred.borrow().is_some()
170 }
171
172 fn renderer_release_count(&self) -> u32 {
173 self.renderer_release_count.get()
174 }
175
176 fn context_was_destroyed(&self) -> bool {
177 self.context_destroyed.get()
178 }
179}
180
181impl WgpuRenderer {
182 pub(super) fn prepare_frame_epoch(
183 &mut self,
184 epoch: u64,
185 native_frame_count: i32,
186 ) -> RendererResult<()> {
187 let needs_recreation = match &self.backend_data {
188 Some(backend_data) => {
189 self.ensure_context_alive()?;
190 self.ensure_renderer_contract()?;
191 !backend_data.is_initialized()
192 }
193 None => {
194 return Err(RendererError::InvalidRenderState(
195 "renderer is not initialized".to_owned(),
196 ));
197 }
198 };
199
200 if needs_recreation {
201 let mut backend_data = self
202 .backend_data
203 .take()
204 .expect("prepare_frame_epoch() already verified backend data");
205 let result = self.create_device_objects(&mut backend_data);
206 self.backend_data = Some(backend_data);
207 result?;
208 }
209 self.backend_data
210 .as_mut()
211 .expect("prepare_frame_epoch() already verified backend data")
212 .begin_frame(epoch, native_frame_count)
213 }
214
215 pub fn invalidate_device_objects(&mut self, imgui_context: &mut Context) -> RendererResult<()> {
222 self.ensure_context_matches(imgui_context)?;
223 self.ensure_renderer_contract()?;
224 let consumer = self
225 .renderer_consumer
226 .take()
227 .ok_or(RendererError::ContextNotBound)?;
228 let reset = match imgui_context.prepare_renderer_texture_reset(&consumer) {
229 Ok(reset) => reset,
230 Err(error) => {
231 self.renderer_consumer = Some(consumer);
232 return Err(error.into());
233 }
234 };
235
236 self.invalidate_device_objects_only();
237 reset.commit();
238 self.texture_manager.clear_destroyed_managed_textures();
239 self.renderer_consumer = Some(consumer);
240
241 Ok(())
242 }
243
244 pub(super) fn invalidate_device_objects_only(&mut self) {
245 if let Some(ref mut backend_data) = self.backend_data {
246 backend_data.pipeline_state = None;
247 backend_data.render_resources = RenderResources::new();
248
249 backend_data.frame_resources = FrameResourceArena::new();
251 backend_data.frame_cursor = Default::default();
252 }
253
254 self.texture_manager.clear_managed_textures();
255 self.default_texture = None;
256 self.shader_manager = ShaderManager::new();
257 }
258
259 fn release_all_device_objects_only(&mut self) {
260 self.invalidate_device_objects_only();
261 self.texture_manager.clear_external_views();
262 }
263
264 pub fn shutdown(&mut self, imgui_context: &mut Context) -> RendererResult<()> {
273 self.ensure_context_matches(imgui_context)?;
274 let consumer = self
277 .renderer_consumer
278 .take()
279 .ok_or(RendererError::ContextNotBound)?;
280 let reset = match imgui_context.prepare_renderer_texture_reset(&consumer) {
281 Ok(reset) => reset,
282 Err(error) => {
283 self.renderer_consumer = Some(consumer);
284 return Err(error.into());
285 }
286 };
287
288 self.release_all_device_objects_only();
289 reset.commit();
290 self.texture_manager.clear_destroyed_managed_textures();
291 self.backend_data = None;
292 drop(consumer);
293 self.clear_bound_imgui_context(imgui_context);
294 Ok(())
295 }
296
297 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
301 pub(super) fn preflight_shutdown(&self, imgui_context: &mut Context) -> RendererResult<()> {
302 self.ensure_context_matches(imgui_context)?;
303 let consumer = self
304 .renderer_consumer
305 .as_ref()
306 .ok_or(RendererError::ContextNotBound)?;
307 let _reset = imgui_context.prepare_renderer_texture_reset(consumer)?;
310 Ok(())
311 }
312
313 pub(super) fn owned_draw_callbacks_match(
314 platform_io: &dear_imgui_rs::platform_io::PlatformIo,
315 ) -> bool {
316 draw_callback_matches(
317 platform_io.draw_callback_reset_render_state_raw(),
318 draw_callback_reset_render_state,
319 ) && draw_callback_matches(
320 platform_io.draw_callback_set_sampler_linear_raw(),
321 draw_callback_set_sampler_linear,
322 ) && draw_callback_matches(
323 platform_io.draw_callback_set_sampler_nearest_raw(),
324 draw_callback_set_sampler_nearest,
325 )
326 }
327
328 pub(super) fn owns_any_standard_draw_callback(
329 platform_io: &dear_imgui_rs::platform_io::PlatformIo,
330 ) -> bool {
331 draw_callback_matches(
332 platform_io.draw_callback_reset_render_state_raw(),
333 draw_callback_reset_render_state,
334 ) || draw_callback_matches(
335 platform_io.draw_callback_set_sampler_linear_raw(),
336 draw_callback_set_sampler_linear,
337 ) || draw_callback_matches(
338 platform_io.draw_callback_set_sampler_nearest_raw(),
339 draw_callback_set_sampler_nearest,
340 )
341 }
342
343 pub(super) fn clear_owned_draw_callbacks(
344 platform_io: &mut dear_imgui_rs::platform_io::PlatformIo,
345 ) {
346 if draw_callback_matches(
347 platform_io.draw_callback_reset_render_state_raw(),
348 draw_callback_reset_render_state,
349 ) {
350 unsafe { platform_io.set_draw_callback_reset_render_state_raw(None) };
351 }
352 if draw_callback_matches(
353 platform_io.draw_callback_set_sampler_linear_raw(),
354 draw_callback_set_sampler_linear,
355 ) {
356 unsafe { platform_io.set_draw_callback_set_sampler_linear_raw(None) };
357 }
358 if draw_callback_matches(
359 platform_io.draw_callback_set_sampler_nearest_raw(),
360 draw_callback_set_sampler_nearest,
361 ) {
362 unsafe { platform_io.set_draw_callback_set_sampler_nearest_raw(None) };
363 }
364 }
367
368 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
369 pub(super) fn shutdown_during_context_teardown(
370 &mut self,
371 context: &ContextTeardown<'_>,
372 release_viewports: impl FnOnce() -> Result<(), ContextAttachmentTeardownError>,
373 ) -> Result<(), ContextAttachmentTeardownError> {
374 let consumer = self.renderer_consumer.take().ok_or_else(|| {
375 ContextAttachmentTeardownError::new(
376 "WGPU viewport renderer lost its renderer consumer before Context teardown",
377 )
378 })?;
379
380 let result = context.with_renderer_texture_reset(&consumer, || {
385 release_viewports()?;
386 self.release_all_device_objects_only();
387 self.backend_data = None;
388 Ok(())
389 });
390 match result {
391 Ok(()) => {
392 self.texture_manager.clear_destroyed_managed_textures();
393 drop(consumer);
394 self.clear_context_state();
395 Ok(())
396 }
397 Err(error) => {
398 self.renderer_consumer = Some(consumer);
399 Err(error)
400 }
401 }
402 }
403
404 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
405 pub(super) fn shutdown_after_context_destroyed(&mut self) {
406 self.release_all_device_objects_only();
407 self.backend_data = None;
408 self.renderer_consumer = None;
409 self.clear_context_state();
410 }
411
412 fn defer_resources_to_context(&mut self, context_state: Option<RendererContextState>) {
413 let deferral = self.drop_deferral.take().unwrap_or_else(|| {
417 debug_assert!(
418 false,
419 "a bound WGPU renderer must retain its Context drop attachment"
420 );
421 std::process::abort();
422 });
423 let resources = DeferredRendererResources {
424 context_state,
425 backend_data: self.backend_data.take(),
426 shader_manager: Some(std::mem::take(&mut self.shader_manager)),
427 texture_manager: Some(std::mem::take(&mut self.texture_manager)),
428 default_texture: self.default_texture.take(),
429 renderer_consumer: self.renderer_consumer.take(),
430 };
431 deferral.defer_to_context(resources);
432 }
433}
434
435impl Drop for WgpuRenderer {
436 fn drop(&mut self) {
437 let clear_result = self.context_state.as_ref().map(|state| {
438 let binding = state.context();
439 binding.try_with_bound_context(|| unsafe { state.clear_owned_raw_state_bound() })
440 });
441 match clear_result {
442 Some(Ok(_)) => {
443 drop(self.context_state.take());
446 self.defer_resources_to_context(None);
447 }
448 Some(Err(error)) => {
449 if matches!(error, ContextBindingError::NativeDestroyed) {
450 self.drop_deferral = None;
454 drop(self.context_state.take());
455 } else {
456 let context_state = self.context_state.take();
459 self.defer_resources_to_context(context_state);
460 }
461 }
462 None => {
463 self.drop_deferral = None;
464 }
465 }
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use dear_imgui_rs::{
472 BackendFlags, Context, sys,
473 texture::{OwnedTextureData, TextureFormat, TextureStatus},
474 };
475
476 use super::WgpuRenderer;
477 use crate::{
478 RendererError,
479 renderer::callbacks::{
480 draw_callback_reset_render_state, draw_callback_set_sampler_linear,
481 draw_callback_set_sampler_nearest,
482 },
483 };
484
485 fn configured_test_renderer(context: &mut Context) -> WgpuRenderer {
486 let (flags, _) = WgpuRenderer::configure_imgui_context(context)
487 .expect("fresh Context should accept WGPU renderer state");
488 let mut renderer = WgpuRenderer::empty();
489 renderer
490 .bind_context(context, flags)
491 .expect("configured Context should bind once");
492 renderer.renderer_consumer = Some(
493 context
494 .create_synchronous_renderer_consumer()
495 .expect("test Context should create a renderer consumer"),
496 );
497 renderer
498 }
499
500 unsafe extern "C" fn foreign_draw_callback(
501 _parent_list: *const sys::ImDrawList,
502 _cmd: *const sys::ImDrawCmd,
503 ) {
504 }
505
506 fn renderer_flags() -> BackendFlags {
507 BackendFlags::RENDERER_HAS_TEXTURES | BackendFlags::RENDERER_HAS_VTX_OFFSET
508 }
509
510 fn install_complete_foreign_renderer_takeover(context: &mut Context) {
511 context
512 .set_renderer_name(Some("foreign-renderer".to_owned()))
513 .unwrap();
514 unsafe {
515 context
516 .io_mut()
517 .set_backend_renderer_user_data(std::ptr::dangling_mut::<u8>().cast());
518 let platform_io = context.platform_io_mut();
519 platform_io.set_draw_callback_reset_render_state_raw(Some(foreign_draw_callback));
520 platform_io.set_draw_callback_set_sampler_linear_raw(Some(foreign_draw_callback));
521 platform_io.set_draw_callback_set_sampler_nearest_raw(Some(foreign_draw_callback));
522 }
523 }
524
525 fn assert_complete_foreign_takeover_is_preserved(context: &Context) {
526 assert_eq!(
527 context.io().backend_renderer_name().unwrap().to_bytes(),
528 b"foreign-renderer"
529 );
530 assert!(!context.io().backend_renderer_user_data().is_null());
531 assert!(context.io().backend_flags().contains(renderer_flags()));
532 let platform_io = context.platform_io();
533 for callback in [
534 platform_io.draw_callback_reset_render_state_raw(),
535 platform_io.draw_callback_set_sampler_linear_raw(),
536 platform_io.draw_callback_set_sampler_nearest_raw(),
537 ] {
538 assert!(callback.is_some_and(|callback| {
539 std::ptr::fn_addr_eq(
540 callback,
541 foreign_draw_callback
542 as unsafe extern "C" fn(*const sys::ImDrawList, *const sys::ImDrawCmd),
543 )
544 }));
545 }
546 }
547
548 fn clear_complete_foreign_renderer_takeover(context: &mut Context) {
549 context.set_renderer_name::<String>(None).unwrap();
550 unsafe {
551 context
552 .io_mut()
553 .set_backend_renderer_user_data(std::ptr::null_mut());
554 let mut flags = context.io().backend_flags();
555 flags.remove(renderer_flags());
556 context.io_mut().set_backend_flags(flags);
557 let platform_io = context.platform_io_mut();
558 platform_io.set_draw_callback_reset_render_state_raw(None);
559 platform_io.set_draw_callback_set_sampler_linear_raw(None);
560 platform_io.set_draw_callback_set_sampler_nearest_raw(None);
561 }
562 }
563
564 #[test]
565 fn unbound_device_invalidation_preserves_managed_texture_bindings() {
566 let mut context = Context::create();
567 let texture = OwnedTextureData::from_pixels(TextureFormat::RGBA32, 1, 1, &[0; 4]).unwrap();
568 let texture = context.register_texture(texture);
569
570 let mut renderer = WgpuRenderer::empty();
571 let result = renderer.invalidate_device_objects(&mut context);
572
573 assert!(matches!(result, Err(RendererError::ContextNotBound)));
574 context
575 .with_texture(texture, |texture| {
576 assert_eq!(texture.status(), TextureStatus::WantCreate);
577 assert!(texture.texture_id().is_null());
578 })
579 .expect("registered texture should remain active");
580 }
581
582 #[test]
583 fn renderer_owns_the_context_synchronous_consumer() {
584 let mut context = Context::create();
585 let mut renderer = configured_test_renderer(&mut context);
586
587 let consumer = renderer
588 .renderer_consumer()
589 .expect("configured renderer should own a consumer");
590 assert_eq!(consumer.context_id(), context.id());
591 assert!(matches!(
592 context.create_detached_renderer_consumer(),
593 Err(dear_imgui_rs::render::RendererConsumerError::ConsumerAlreadyActive)
594 ));
595
596 renderer.shutdown(&mut context).unwrap();
597 }
598
599 #[test]
600 fn dropping_renderer_defers_resources_to_context_teardown() {
601 let mut context = Context::create();
602 let renderer = configured_test_renderer(&mut context);
603 let control = renderer
604 .drop_deferral
605 .as_ref()
606 .expect("bound renderer must install a deferred-drop attachment")
607 .control_for_test();
608 drop(renderer);
609
610 assert!(control.has_deferred_resources());
611 assert_eq!(control.renderer_release_count(), 0);
612 assert!(context.io().backend_renderer_user_data().is_null());
613 assert!(context.io().backend_renderer_name().is_none());
614 assert!(!context.io().backend_flags().intersects(renderer_flags()));
615 assert!(matches!(
616 WgpuRenderer::empty().bind_context(&mut context, BackendFlags::empty()),
617 Err(RendererError::ContextAttachment(
618 dear_imgui_rs::ContextAttachmentError::DuplicateAttachment
619 ))
620 ));
621
622 assert!(control.has_deferred_resources());
623 assert_eq!(control.renderer_release_count(), 0);
624
625 drop(context);
626
627 assert_eq!(control.renderer_release_count(), 1);
628 assert!(!control.has_deferred_resources());
629 assert!(control.context_was_destroyed());
630 }
631
632 #[test]
633 fn context_first_renderer_drop_uses_the_native_destroyed_path() {
634 let mut context = Context::create();
635 let renderer = configured_test_renderer(&mut context);
636 let control = renderer
637 .drop_deferral
638 .as_ref()
639 .expect("bound renderer must install a deferred-drop attachment")
640 .control_for_test();
641
642 drop(context);
643
644 assert!(control.context_was_destroyed());
645 assert!(!control.has_deferred_resources());
646 assert_eq!(control.renderer_release_count(), 0);
647 drop(renderer);
648 }
649
650 #[test]
651 fn foreign_context_lifecycle_calls_are_transactional() {
652 let mut owner = Context::create();
653 let mut renderer = configured_test_renderer(&mut owner);
654
655 let suspended_owner = owner.suspend_or_panic();
656 let mut foreign = Context::create();
657 let foreign_flags = foreign.io().backend_flags();
658
659 assert!(matches!(
660 renderer.invalidate_device_objects(&mut foreign),
661 Err(RendererError::ContextMismatch)
662 ));
663 assert!(matches!(
664 renderer.shutdown(&mut foreign),
665 Err(RendererError::ContextMismatch)
666 ));
667 assert_eq!(foreign.io().backend_flags(), foreign_flags);
668 assert!(renderer.context_state.is_some());
669
670 let suspended_foreign = foreign.suspend_or_panic();
671 let mut owner = suspended_owner
672 .activate()
673 .expect("owner context should reactivate");
674 renderer
675 .shutdown(&mut owner)
676 .expect("matching context should shut down the test renderer");
677 assert!(renderer.context_state.is_none());
678 drop(suspended_foreign);
679 }
680
681 #[test]
682 fn moving_renderer_preserves_published_token_and_name_identity() {
683 let mut context = Context::create();
684 let renderer = configured_test_renderer(&mut context);
685 let token = context.io().backend_renderer_user_data();
686 let name = context
687 .io()
688 .backend_renderer_name()
689 .expect("renderer name should be published")
690 .as_ptr();
691
692 let mut moved = renderer;
693 assert_eq!(context.io().backend_renderer_user_data(), token);
694 assert_eq!(context.io().backend_renderer_name().unwrap().as_ptr(), name);
695 moved
696 .ensure_renderer_contract()
697 .expect("moving WgpuRenderer must preserve its raw identities");
698 moved.shutdown(&mut context).unwrap();
699 }
700
701 #[test]
702 fn same_bytes_at_a_foreign_name_pointer_are_drift_and_survive_teardown() {
703 let mut context = Context::create();
704 let mut renderer = configured_test_renderer(&mut context);
705 let owned_name = context.io().backend_renderer_name().unwrap().as_ptr();
706 context
707 .set_renderer_name(Some(format!(
708 "dear-imgui-wgpu {}",
709 env!("CARGO_PKG_VERSION")
710 )))
711 .unwrap();
712 let foreign_name = context.io().backend_renderer_name().unwrap().as_ptr();
713 assert_ne!(foreign_name, owned_name);
714
715 assert!(matches!(
716 renderer.ensure_renderer_contract(),
717 Err(RendererError::RendererStateDrift {
718 field: "BackendRendererName"
719 })
720 ));
721 assert_eq!(
722 context.io().backend_renderer_name().unwrap().as_ptr(),
723 foreign_name
724 );
725
726 renderer.shutdown(&mut context).unwrap();
727 assert_eq!(
728 context.io().backend_renderer_name().unwrap().as_ptr(),
729 foreign_name
730 );
731 context.set_renderer_name::<String>(None).unwrap();
732 }
733
734 #[test]
735 fn foreign_user_data_is_drift_and_survives_teardown() {
736 let mut context = Context::create();
737 let mut renderer = configured_test_renderer(&mut context);
738 let foreign = std::ptr::dangling_mut::<u8>().cast();
739 unsafe { context.io_mut().set_backend_renderer_user_data(foreign) };
740
741 assert!(matches!(
742 renderer.ensure_renderer_contract(),
743 Err(RendererError::RendererStateDrift {
744 field: "BackendRendererUserData"
745 })
746 ));
747 assert_eq!(context.io().backend_renderer_user_data(), foreign);
748
749 renderer.shutdown(&mut context).unwrap();
750 assert_eq!(context.io().backend_renderer_user_data(), foreign);
751 unsafe {
752 context
753 .io_mut()
754 .set_backend_renderer_user_data(std::ptr::null_mut())
755 };
756 }
757
758 #[test]
759 fn shutdown_preserves_flags_after_complete_foreign_renderer_takeover() {
760 let mut context = Context::create();
761 let mut renderer = configured_test_renderer(&mut context);
762 install_complete_foreign_renderer_takeover(&mut context);
763
764 assert!(matches!(
765 renderer.ensure_renderer_contract(),
766 Err(RendererError::RendererStateDrift {
767 field: "BackendRendererUserData"
768 })
769 ));
770 assert_complete_foreign_takeover_is_preserved(&context);
771
772 renderer.shutdown(&mut context).unwrap();
773 assert_complete_foreign_takeover_is_preserved(&context);
774 clear_complete_foreign_renderer_takeover(&mut context);
775 }
776
777 #[test]
778 fn drop_preserves_flags_after_complete_foreign_renderer_takeover() {
779 let mut context = Context::create();
780 let renderer = configured_test_renderer(&mut context);
781 install_complete_foreign_renderer_takeover(&mut context);
782
783 assert!(matches!(
784 renderer.ensure_renderer_contract(),
785 Err(RendererError::RendererStateDrift {
786 field: "BackendRendererUserData"
787 })
788 ));
789 drop(renderer);
790
791 assert_complete_foreign_takeover_is_preserved(&context);
792 clear_complete_foreign_renderer_takeover(&mut context);
793 }
794
795 #[test]
796 fn renderer_contract_fault_is_sticky_across_raw_aba_restoration() {
797 let mut context = Context::create();
798 let mut renderer = configured_test_renderer(&mut context);
799 let token = context.io().backend_renderer_user_data();
800 let name = context.io().backend_renderer_name().unwrap().as_ptr();
801 let mut flags = context.io().backend_flags();
802 flags.remove(BackendFlags::RENDERER_HAS_TEXTURES);
803 context.io_mut().set_backend_flags(flags);
804
805 assert!(matches!(
806 renderer.ensure_renderer_contract(),
807 Err(RendererError::RendererStateDrift {
808 field: "RENDERER_HAS_TEXTURES"
809 })
810 ));
811
812 let io = unsafe { sys::igGetIO_ContextPtr(context.as_raw()) };
813 unsafe {
814 (*io).BackendRendererUserData = token;
815 (*io).BackendRendererName = name;
816 (*io).BackendFlags |= (BackendFlags::RENDERER_HAS_VTX_OFFSET
817 | BackendFlags::RENDERER_HAS_TEXTURES)
818 .bits();
819 let platform_io = context.platform_io_mut();
820 platform_io
821 .set_draw_callback_reset_render_state_raw(Some(draw_callback_reset_render_state));
822 platform_io
823 .set_draw_callback_set_sampler_linear_raw(Some(draw_callback_set_sampler_linear));
824 platform_io
825 .set_draw_callback_set_sampler_nearest_raw(Some(draw_callback_set_sampler_nearest));
826 }
827
828 assert!(matches!(
829 renderer.ensure_renderer_contract(),
830 Err(RendererError::RendererStateDrift {
831 field: "RENDERER_HAS_TEXTURES"
832 })
833 ));
834 assert!(context.io().backend_renderer_user_data().is_null());
835 assert!(context.io().backend_renderer_name().is_none());
836 assert!(!context.io().backend_flags().intersects(
837 BackendFlags::RENDERER_HAS_VTX_OFFSET | BackendFlags::RENDERER_HAS_TEXTURES
838 ));
839
840 renderer.shutdown(&mut context).unwrap();
841 }
842
843 #[test]
844 fn explicit_shutdown_revokes_context_state_and_allows_replacement() {
845 let mut context = Context::create();
846 let mut renderer = configured_test_renderer(&mut context);
847 renderer.shutdown(&mut context).unwrap();
848
849 assert!(context.io().backend_renderer_user_data().is_null());
850 assert!(context.io().backend_renderer_name().is_none());
851 assert!(!context.io().backend_flags().intersects(
852 BackendFlags::RENDERER_HAS_VTX_OFFSET | BackendFlags::RENDERER_HAS_TEXTURES
853 ));
854 assert!(
855 context
856 .platform_io()
857 .draw_callback_reset_render_state_raw()
858 .is_none()
859 );
860 assert!(
861 context
862 .platform_io()
863 .draw_callback_set_sampler_linear_raw()
864 .is_none()
865 );
866 assert!(
867 context
868 .platform_io()
869 .draw_callback_set_sampler_nearest_raw()
870 .is_none()
871 );
872
873 let mut replacement = configured_test_renderer(&mut context);
874 replacement.shutdown(&mut context).unwrap();
875 }
876}