use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use super::{
WgpuRenderer,
callbacks::{
draw_callback_matches, draw_callback_reset_render_state, draw_callback_set_sampler_linear,
draw_callback_set_sampler_nearest,
},
core::{RendererContextState, RendererPublication},
};
use crate::{FrameResourceArena, RenderResources, RendererError, RendererResult, ShaderManager};
use dear_imgui_rs::{
Context, ContextAttachment, ContextAttachmentError, ContextAttachmentLease,
ContextAttachmentRole, ContextAttachmentTeardownError, ContextBindingError, ContextDestroyed,
ContextTeardown, render::SynchronousRendererConsumer,
};
struct WgpuRendererDropAttachmentMarker;
struct DeferredRendererResources {
context_state: Option<RendererContextState>,
backend_data: Option<crate::WgpuBackendData>,
shader_manager: Option<ShaderManager>,
texture_manager: Option<crate::WgpuTextureManager>,
default_texture: Option<wgpu::TextureView>,
renderer_consumer: Option<SynchronousRendererConsumer>,
}
impl DeferredRendererResources {
fn release_renderer_resources(
&mut self,
context: &ContextTeardown<'_>,
) -> Result<(), ContextAttachmentTeardownError> {
let consumer = self.renderer_consumer.take().ok_or_else(|| {
ContextAttachmentTeardownError::new(
"deferred WGPU renderer resources lost their renderer consumer",
)
})?;
let result = context.with_renderer_texture_reset(&consumer, || {
self.backend_data.take();
self.shader_manager.take();
self.texture_manager.take();
self.default_texture.take();
Ok(())
});
if result.is_err() {
self.renderer_consumer = Some(consumer);
}
result
}
fn release_after_context_destroyed(mut self) {
drop(self.context_state.take());
}
}
struct RendererDropControl {
deferred: RefCell<Option<DeferredRendererResources>>,
publication: RefCell<Option<RendererPublication>>,
context_destroyed: Cell<bool>,
#[cfg(test)]
renderer_release_count: Cell<u32>,
}
struct RendererDropAttachment {
control: Rc<RendererDropControl>,
}
impl ContextAttachment for RendererDropAttachment {
fn quiesce(
&self,
context: &ContextTeardown<'_>,
) -> Result<(), dear_imgui_rs::ContextAttachmentTeardownError> {
if let Some(publication) = self.control.publication.borrow().as_ref() {
context.with_bound_context(|| unsafe {
publication.clear_owned_raw_state_bound();
});
}
Ok(())
}
fn release_renderer_resources(
&self,
context: &ContextTeardown<'_>,
) -> Result<(), ContextAttachmentTeardownError> {
if let Some(resources) = self.control.deferred.borrow_mut().as_mut() {
resources.release_renderer_resources(context)?;
#[cfg(test)]
self.control
.renderer_release_count
.set(self.control.renderer_release_count.get() + 1);
}
Ok(())
}
fn context_destroyed(&self, _context: ContextDestroyed) {
self.control.context_destroyed.set(true);
self.control.publication.borrow_mut().take();
if let Some(resources) = self.control.deferred.borrow_mut().take() {
resources.release_after_context_destroyed();
}
}
}
pub(super) struct RendererDropDeferral {
control: Rc<RendererDropControl>,
attachment: ContextAttachmentLease,
}
impl RendererDropDeferral {
pub(super) fn register(context: &mut Context) -> Result<Self, ContextAttachmentError> {
let control = Rc::new(RendererDropControl {
deferred: RefCell::new(None),
publication: RefCell::new(None),
context_destroyed: Cell::new(false),
#[cfg(test)]
renderer_release_count: Cell::new(0),
});
let attachment = context.register_attachment::<WgpuRendererDropAttachmentMarker>(
ContextAttachmentRole::Extension,
Rc::new(RendererDropAttachment {
control: Rc::clone(&control),
}),
)?;
Ok(Self {
control,
attachment,
})
}
pub(super) fn set_publication(&self, publication: RendererPublication) {
debug_assert!(self.control.publication.borrow().is_none());
self.control.publication.borrow_mut().replace(publication);
}
fn defer_to_context(self, resources: DeferredRendererResources) {
debug_assert!(self.control.deferred.borrow().is_none());
self.control.deferred.borrow_mut().replace(resources);
self.attachment.defer_to_context();
}
#[cfg(test)]
fn control_for_test(&self) -> Rc<RendererDropControl> {
Rc::clone(&self.control)
}
}
#[cfg(test)]
impl RendererDropControl {
fn has_deferred_resources(&self) -> bool {
self.deferred.borrow().is_some()
}
fn renderer_release_count(&self) -> u32 {
self.renderer_release_count.get()
}
fn context_was_destroyed(&self) -> bool {
self.context_destroyed.get()
}
}
impl WgpuRenderer {
pub(super) fn prepare_frame_epoch(
&mut self,
epoch: u64,
native_frame_count: i32,
) -> RendererResult<()> {
let needs_recreation = match &self.backend_data {
Some(backend_data) => {
self.ensure_context_alive()?;
self.ensure_renderer_contract()?;
!backend_data.is_initialized()
}
None => {
return Err(RendererError::InvalidRenderState(
"renderer is not initialized".to_owned(),
));
}
};
if needs_recreation {
let mut backend_data = self
.backend_data
.take()
.expect("prepare_frame_epoch() already verified backend data");
let result = self.create_device_objects(&mut backend_data);
self.backend_data = Some(backend_data);
result?;
}
self.backend_data
.as_mut()
.expect("prepare_frame_epoch() already verified backend data")
.begin_frame(epoch, native_frame_count)
}
pub fn invalidate_device_objects(&mut self, imgui_context: &mut Context) -> RendererResult<()> {
self.ensure_context_matches(imgui_context)?;
self.ensure_renderer_contract()?;
let consumer = self
.renderer_consumer
.take()
.ok_or(RendererError::ContextNotBound)?;
let reset = match imgui_context.prepare_renderer_texture_reset(&consumer) {
Ok(reset) => reset,
Err(error) => {
self.renderer_consumer = Some(consumer);
return Err(error.into());
}
};
self.invalidate_device_objects_only();
reset.commit();
self.texture_manager.clear_destroyed_managed_textures();
self.renderer_consumer = Some(consumer);
Ok(())
}
pub(super) fn invalidate_device_objects_only(&mut self) {
if let Some(ref mut backend_data) = self.backend_data {
backend_data.pipeline_state = None;
backend_data.render_resources = RenderResources::new();
backend_data.frame_resources = FrameResourceArena::new();
backend_data.frame_cursor = Default::default();
}
self.texture_manager.clear_managed_textures();
self.default_texture = None;
self.shader_manager = ShaderManager::new();
}
fn release_all_device_objects_only(&mut self) {
self.invalidate_device_objects_only();
self.texture_manager.clear_external_views();
}
pub fn shutdown(&mut self, imgui_context: &mut Context) -> RendererResult<()> {
self.ensure_context_matches(imgui_context)?;
let consumer = self
.renderer_consumer
.take()
.ok_or(RendererError::ContextNotBound)?;
let reset = match imgui_context.prepare_renderer_texture_reset(&consumer) {
Ok(reset) => reset,
Err(error) => {
self.renderer_consumer = Some(consumer);
return Err(error.into());
}
};
self.release_all_device_objects_only();
reset.commit();
self.texture_manager.clear_destroyed_managed_textures();
self.backend_data = None;
drop(consumer);
self.clear_bound_imgui_context(imgui_context);
Ok(())
}
#[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
pub(super) fn preflight_shutdown(&self, imgui_context: &mut Context) -> RendererResult<()> {
self.ensure_context_matches(imgui_context)?;
let consumer = self
.renderer_consumer
.as_ref()
.ok_or(RendererError::ContextNotBound)?;
let _reset = imgui_context.prepare_renderer_texture_reset(consumer)?;
Ok(())
}
pub(super) fn owned_draw_callbacks_match(
platform_io: &dear_imgui_rs::platform_io::PlatformIo,
) -> bool {
draw_callback_matches(
platform_io.draw_callback_reset_render_state_raw(),
draw_callback_reset_render_state,
) && draw_callback_matches(
platform_io.draw_callback_set_sampler_linear_raw(),
draw_callback_set_sampler_linear,
) && draw_callback_matches(
platform_io.draw_callback_set_sampler_nearest_raw(),
draw_callback_set_sampler_nearest,
)
}
pub(super) fn owns_any_standard_draw_callback(
platform_io: &dear_imgui_rs::platform_io::PlatformIo,
) -> bool {
draw_callback_matches(
platform_io.draw_callback_reset_render_state_raw(),
draw_callback_reset_render_state,
) || draw_callback_matches(
platform_io.draw_callback_set_sampler_linear_raw(),
draw_callback_set_sampler_linear,
) || draw_callback_matches(
platform_io.draw_callback_set_sampler_nearest_raw(),
draw_callback_set_sampler_nearest,
)
}
pub(super) fn clear_owned_draw_callbacks(
platform_io: &mut dear_imgui_rs::platform_io::PlatformIo,
) {
if draw_callback_matches(
platform_io.draw_callback_reset_render_state_raw(),
draw_callback_reset_render_state,
) {
unsafe { platform_io.set_draw_callback_reset_render_state_raw(None) };
}
if draw_callback_matches(
platform_io.draw_callback_set_sampler_linear_raw(),
draw_callback_set_sampler_linear,
) {
unsafe { platform_io.set_draw_callback_set_sampler_linear_raw(None) };
}
if draw_callback_matches(
platform_io.draw_callback_set_sampler_nearest_raw(),
draw_callback_set_sampler_nearest,
) {
unsafe { platform_io.set_draw_callback_set_sampler_nearest_raw(None) };
}
}
#[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
pub(super) fn shutdown_during_context_teardown(
&mut self,
context: &ContextTeardown<'_>,
release_viewports: impl FnOnce() -> Result<(), ContextAttachmentTeardownError>,
) -> Result<(), ContextAttachmentTeardownError> {
let consumer = self.renderer_consumer.take().ok_or_else(|| {
ContextAttachmentTeardownError::new(
"WGPU viewport renderer lost its renderer consumer before Context teardown",
)
})?;
let result = context.with_renderer_texture_reset(&consumer, || {
release_viewports()?;
self.release_all_device_objects_only();
self.backend_data = None;
Ok(())
});
match result {
Ok(()) => {
self.texture_manager.clear_destroyed_managed_textures();
drop(consumer);
self.clear_context_state();
Ok(())
}
Err(error) => {
self.renderer_consumer = Some(consumer);
Err(error)
}
}
}
#[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
pub(super) fn shutdown_after_context_destroyed(&mut self) {
self.release_all_device_objects_only();
self.backend_data = None;
self.renderer_consumer = None;
self.clear_context_state();
}
fn defer_resources_to_context(&mut self, context_state: Option<RendererContextState>) {
let deferral = self.drop_deferral.take().unwrap_or_else(|| {
debug_assert!(
false,
"a bound WGPU renderer must retain its Context drop attachment"
);
std::process::abort();
});
let resources = DeferredRendererResources {
context_state,
backend_data: self.backend_data.take(),
shader_manager: Some(std::mem::take(&mut self.shader_manager)),
texture_manager: Some(std::mem::take(&mut self.texture_manager)),
default_texture: self.default_texture.take(),
renderer_consumer: self.renderer_consumer.take(),
};
deferral.defer_to_context(resources);
}
}
impl Drop for WgpuRenderer {
fn drop(&mut self) {
let clear_result = self.context_state.as_ref().map(|state| {
let binding = state.context();
binding.try_with_bound_context(|| unsafe { state.clear_owned_raw_state_bound() })
});
match clear_result {
Some(Ok(_)) => {
drop(self.context_state.take());
self.defer_resources_to_context(None);
}
Some(Err(error)) => {
if matches!(error, ContextBindingError::NativeDestroyed) {
self.drop_deferral = None;
drop(self.context_state.take());
} else {
let context_state = self.context_state.take();
self.defer_resources_to_context(context_state);
}
}
None => {
self.drop_deferral = None;
}
}
}
}
#[cfg(test)]
mod tests {
use dear_imgui_rs::{
BackendFlags, Context, sys,
texture::{OwnedTextureData, TextureFormat, TextureStatus},
};
use super::WgpuRenderer;
use crate::{
RendererError,
renderer::callbacks::{
draw_callback_reset_render_state, draw_callback_set_sampler_linear,
draw_callback_set_sampler_nearest,
},
};
fn configured_test_renderer(context: &mut Context) -> WgpuRenderer {
let (flags, _) = WgpuRenderer::configure_imgui_context(context)
.expect("fresh Context should accept WGPU renderer state");
let mut renderer = WgpuRenderer::empty();
renderer
.bind_context(context, flags)
.expect("configured Context should bind once");
renderer.renderer_consumer = Some(
context
.create_synchronous_renderer_consumer()
.expect("test Context should create a renderer consumer"),
);
renderer
}
unsafe extern "C" fn foreign_draw_callback(
_parent_list: *const sys::ImDrawList,
_cmd: *const sys::ImDrawCmd,
) {
}
fn renderer_flags() -> BackendFlags {
BackendFlags::RENDERER_HAS_TEXTURES | BackendFlags::RENDERER_HAS_VTX_OFFSET
}
fn install_complete_foreign_renderer_takeover(context: &mut Context) {
context
.set_renderer_name(Some("foreign-renderer".to_owned()))
.unwrap();
unsafe {
context
.io_mut()
.set_backend_renderer_user_data(std::ptr::dangling_mut::<u8>().cast());
let platform_io = context.platform_io_mut();
platform_io.set_draw_callback_reset_render_state_raw(Some(foreign_draw_callback));
platform_io.set_draw_callback_set_sampler_linear_raw(Some(foreign_draw_callback));
platform_io.set_draw_callback_set_sampler_nearest_raw(Some(foreign_draw_callback));
}
}
fn assert_complete_foreign_takeover_is_preserved(context: &Context) {
assert_eq!(
context.io().backend_renderer_name().unwrap().to_bytes(),
b"foreign-renderer"
);
assert!(!context.io().backend_renderer_user_data().is_null());
assert!(context.io().backend_flags().contains(renderer_flags()));
let platform_io = context.platform_io();
for callback in [
platform_io.draw_callback_reset_render_state_raw(),
platform_io.draw_callback_set_sampler_linear_raw(),
platform_io.draw_callback_set_sampler_nearest_raw(),
] {
assert!(callback.is_some_and(|callback| {
std::ptr::fn_addr_eq(
callback,
foreign_draw_callback
as unsafe extern "C" fn(*const sys::ImDrawList, *const sys::ImDrawCmd),
)
}));
}
}
fn clear_complete_foreign_renderer_takeover(context: &mut Context) {
context.set_renderer_name::<String>(None).unwrap();
unsafe {
context
.io_mut()
.set_backend_renderer_user_data(std::ptr::null_mut());
let mut flags = context.io().backend_flags();
flags.remove(renderer_flags());
context.io_mut().set_backend_flags(flags);
let platform_io = context.platform_io_mut();
platform_io.set_draw_callback_reset_render_state_raw(None);
platform_io.set_draw_callback_set_sampler_linear_raw(None);
platform_io.set_draw_callback_set_sampler_nearest_raw(None);
}
}
#[test]
fn unbound_device_invalidation_preserves_managed_texture_bindings() {
let mut context = Context::create();
let texture = OwnedTextureData::from_pixels(TextureFormat::RGBA32, 1, 1, &[0; 4]).unwrap();
let texture = context.register_texture(texture);
let mut renderer = WgpuRenderer::empty();
let result = renderer.invalidate_device_objects(&mut context);
assert!(matches!(result, Err(RendererError::ContextNotBound)));
context
.with_texture(texture, |texture| {
assert_eq!(texture.status(), TextureStatus::WantCreate);
assert!(texture.texture_id().is_null());
})
.expect("registered texture should remain active");
}
#[test]
fn renderer_owns_the_context_synchronous_consumer() {
let mut context = Context::create();
let mut renderer = configured_test_renderer(&mut context);
let consumer = renderer
.renderer_consumer()
.expect("configured renderer should own a consumer");
assert_eq!(consumer.context_id(), context.id());
assert!(matches!(
context.create_detached_renderer_consumer(),
Err(dear_imgui_rs::render::RendererConsumerError::ConsumerAlreadyActive)
));
renderer.shutdown(&mut context).unwrap();
}
#[test]
fn dropping_renderer_defers_resources_to_context_teardown() {
let mut context = Context::create();
let renderer = configured_test_renderer(&mut context);
let control = renderer
.drop_deferral
.as_ref()
.expect("bound renderer must install a deferred-drop attachment")
.control_for_test();
drop(renderer);
assert!(control.has_deferred_resources());
assert_eq!(control.renderer_release_count(), 0);
assert!(context.io().backend_renderer_user_data().is_null());
assert!(context.io().backend_renderer_name().is_none());
assert!(!context.io().backend_flags().intersects(renderer_flags()));
assert!(matches!(
WgpuRenderer::empty().bind_context(&mut context, BackendFlags::empty()),
Err(RendererError::ContextAttachment(
dear_imgui_rs::ContextAttachmentError::DuplicateAttachment
))
));
assert!(control.has_deferred_resources());
assert_eq!(control.renderer_release_count(), 0);
drop(context);
assert_eq!(control.renderer_release_count(), 1);
assert!(!control.has_deferred_resources());
assert!(control.context_was_destroyed());
}
#[test]
fn context_first_renderer_drop_uses_the_native_destroyed_path() {
let mut context = Context::create();
let renderer = configured_test_renderer(&mut context);
let control = renderer
.drop_deferral
.as_ref()
.expect("bound renderer must install a deferred-drop attachment")
.control_for_test();
drop(context);
assert!(control.context_was_destroyed());
assert!(!control.has_deferred_resources());
assert_eq!(control.renderer_release_count(), 0);
drop(renderer);
}
#[test]
fn foreign_context_lifecycle_calls_are_transactional() {
let mut owner = Context::create();
let mut renderer = configured_test_renderer(&mut owner);
let suspended_owner = owner.suspend_or_panic();
let mut foreign = Context::create();
let foreign_flags = foreign.io().backend_flags();
assert!(matches!(
renderer.invalidate_device_objects(&mut foreign),
Err(RendererError::ContextMismatch)
));
assert!(matches!(
renderer.shutdown(&mut foreign),
Err(RendererError::ContextMismatch)
));
assert_eq!(foreign.io().backend_flags(), foreign_flags);
assert!(renderer.context_state.is_some());
let suspended_foreign = foreign.suspend_or_panic();
let mut owner = suspended_owner
.activate()
.expect("owner context should reactivate");
renderer
.shutdown(&mut owner)
.expect("matching context should shut down the test renderer");
assert!(renderer.context_state.is_none());
drop(suspended_foreign);
}
#[test]
fn moving_renderer_preserves_published_token_and_name_identity() {
let mut context = Context::create();
let renderer = configured_test_renderer(&mut context);
let token = context.io().backend_renderer_user_data();
let name = context
.io()
.backend_renderer_name()
.expect("renderer name should be published")
.as_ptr();
let mut moved = renderer;
assert_eq!(context.io().backend_renderer_user_data(), token);
assert_eq!(context.io().backend_renderer_name().unwrap().as_ptr(), name);
moved
.ensure_renderer_contract()
.expect("moving WgpuRenderer must preserve its raw identities");
moved.shutdown(&mut context).unwrap();
}
#[test]
fn same_bytes_at_a_foreign_name_pointer_are_drift_and_survive_teardown() {
let mut context = Context::create();
let mut renderer = configured_test_renderer(&mut context);
let owned_name = context.io().backend_renderer_name().unwrap().as_ptr();
context
.set_renderer_name(Some(format!(
"dear-imgui-wgpu {}",
env!("CARGO_PKG_VERSION")
)))
.unwrap();
let foreign_name = context.io().backend_renderer_name().unwrap().as_ptr();
assert_ne!(foreign_name, owned_name);
assert!(matches!(
renderer.ensure_renderer_contract(),
Err(RendererError::RendererStateDrift {
field: "BackendRendererName"
})
));
assert_eq!(
context.io().backend_renderer_name().unwrap().as_ptr(),
foreign_name
);
renderer.shutdown(&mut context).unwrap();
assert_eq!(
context.io().backend_renderer_name().unwrap().as_ptr(),
foreign_name
);
context.set_renderer_name::<String>(None).unwrap();
}
#[test]
fn foreign_user_data_is_drift_and_survives_teardown() {
let mut context = Context::create();
let mut renderer = configured_test_renderer(&mut context);
let foreign = std::ptr::dangling_mut::<u8>().cast();
unsafe { context.io_mut().set_backend_renderer_user_data(foreign) };
assert!(matches!(
renderer.ensure_renderer_contract(),
Err(RendererError::RendererStateDrift {
field: "BackendRendererUserData"
})
));
assert_eq!(context.io().backend_renderer_user_data(), foreign);
renderer.shutdown(&mut context).unwrap();
assert_eq!(context.io().backend_renderer_user_data(), foreign);
unsafe {
context
.io_mut()
.set_backend_renderer_user_data(std::ptr::null_mut())
};
}
#[test]
fn shutdown_preserves_flags_after_complete_foreign_renderer_takeover() {
let mut context = Context::create();
let mut renderer = configured_test_renderer(&mut context);
install_complete_foreign_renderer_takeover(&mut context);
assert!(matches!(
renderer.ensure_renderer_contract(),
Err(RendererError::RendererStateDrift {
field: "BackendRendererUserData"
})
));
assert_complete_foreign_takeover_is_preserved(&context);
renderer.shutdown(&mut context).unwrap();
assert_complete_foreign_takeover_is_preserved(&context);
clear_complete_foreign_renderer_takeover(&mut context);
}
#[test]
fn drop_preserves_flags_after_complete_foreign_renderer_takeover() {
let mut context = Context::create();
let renderer = configured_test_renderer(&mut context);
install_complete_foreign_renderer_takeover(&mut context);
assert!(matches!(
renderer.ensure_renderer_contract(),
Err(RendererError::RendererStateDrift {
field: "BackendRendererUserData"
})
));
drop(renderer);
assert_complete_foreign_takeover_is_preserved(&context);
clear_complete_foreign_renderer_takeover(&mut context);
}
#[test]
fn renderer_contract_fault_is_sticky_across_raw_aba_restoration() {
let mut context = Context::create();
let mut renderer = configured_test_renderer(&mut context);
let token = context.io().backend_renderer_user_data();
let name = context.io().backend_renderer_name().unwrap().as_ptr();
let mut flags = context.io().backend_flags();
flags.remove(BackendFlags::RENDERER_HAS_TEXTURES);
context.io_mut().set_backend_flags(flags);
assert!(matches!(
renderer.ensure_renderer_contract(),
Err(RendererError::RendererStateDrift {
field: "RENDERER_HAS_TEXTURES"
})
));
let io = unsafe { sys::igGetIO_ContextPtr(context.as_raw()) };
unsafe {
(*io).BackendRendererUserData = token;
(*io).BackendRendererName = name;
(*io).BackendFlags |= (BackendFlags::RENDERER_HAS_VTX_OFFSET
| BackendFlags::RENDERER_HAS_TEXTURES)
.bits();
let platform_io = context.platform_io_mut();
platform_io
.set_draw_callback_reset_render_state_raw(Some(draw_callback_reset_render_state));
platform_io
.set_draw_callback_set_sampler_linear_raw(Some(draw_callback_set_sampler_linear));
platform_io
.set_draw_callback_set_sampler_nearest_raw(Some(draw_callback_set_sampler_nearest));
}
assert!(matches!(
renderer.ensure_renderer_contract(),
Err(RendererError::RendererStateDrift {
field: "RENDERER_HAS_TEXTURES"
})
));
assert!(context.io().backend_renderer_user_data().is_null());
assert!(context.io().backend_renderer_name().is_none());
assert!(!context.io().backend_flags().intersects(
BackendFlags::RENDERER_HAS_VTX_OFFSET | BackendFlags::RENDERER_HAS_TEXTURES
));
renderer.shutdown(&mut context).unwrap();
}
#[test]
fn explicit_shutdown_revokes_context_state_and_allows_replacement() {
let mut context = Context::create();
let mut renderer = configured_test_renderer(&mut context);
renderer.shutdown(&mut context).unwrap();
assert!(context.io().backend_renderer_user_data().is_null());
assert!(context.io().backend_renderer_name().is_none());
assert!(!context.io().backend_flags().intersects(
BackendFlags::RENDERER_HAS_VTX_OFFSET | BackendFlags::RENDERER_HAS_TEXTURES
));
assert!(
context
.platform_io()
.draw_callback_reset_render_state_raw()
.is_none()
);
assert!(
context
.platform_io()
.draw_callback_set_sampler_linear_raw()
.is_none()
);
assert!(
context
.platform_io()
.draw_callback_set_sampler_nearest_raw()
.is_none()
);
let mut replacement = configured_test_renderer(&mut context);
replacement.shutdown(&mut context).unwrap();
}
}