1use crate::GammaMode;
17use crate::{
18 FrameResources, RenderResources, RendererError, RendererResult, ShaderManager, Uniforms,
19 WgpuBackendData, WgpuInitInfo, WgpuTextureManager,
20};
21use dear_imgui_rs::{BackendFlags, Context, render::DrawData, sys};
22#[cfg(feature = "mv-log")]
23use std::sync::{Mutex, OnceLock};
24use wgpu::*;
25
26#[allow(unused_macros)]
29macro_rules! mvlog {
30 ($($arg:tt)*) => {
31 if cfg!(feature = "mv-log") { eprintln!($($arg)*); }
32 }
33}
34
35struct RendererRenderStateGuard {
36 platform_io: *mut sys::ImGuiPlatformIO,
37}
38
39#[derive(Copy, Clone, Eq, PartialEq)]
40enum ActiveSampler {
41 Linear,
42 Nearest,
43 Custom(u64),
44}
45
46unsafe extern "C" fn draw_callback_reset_render_state(
47 _parent_list: *const sys::ImDrawList,
48 _cmd: *const sys::ImDrawCmd,
49) {
50}
51
52unsafe extern "C" fn draw_callback_set_sampler_linear(
53 _parent_list: *const sys::ImDrawList,
54 _cmd: *const sys::ImDrawCmd,
55) {
56}
57
58unsafe extern "C" fn draw_callback_set_sampler_nearest(
59 _parent_list: *const sys::ImDrawList,
60 _cmd: *const sys::ImDrawCmd,
61) {
62}
63
64impl RendererRenderStateGuard {
65 unsafe fn set(
66 platform_io: *mut sys::ImGuiPlatformIO,
67 render_state: *mut std::ffi::c_void,
68 ) -> RendererResult<Self> {
69 if platform_io.is_null() {
70 return Err(RendererError::InvalidRenderState(
71 "PlatformIO not available for renderer render state".to_string(),
72 ));
73 }
74
75 unsafe {
76 (*platform_io).Renderer_RenderState = render_state;
77 }
78 Ok(Self { platform_io })
79 }
80}
81
82impl Drop for RendererRenderStateGuard {
83 fn drop(&mut self) {
84 unsafe {
85 if !self.platform_io.is_null() {
86 (*self.platform_io).Renderer_RenderState = std::ptr::null_mut();
87 }
88 }
89 }
90}
91pub struct WgpuRenderer {
96 backend_data: Option<WgpuBackendData>,
98 shader_manager: ShaderManager,
100 texture_manager: WgpuTextureManager,
102 default_texture: Option<TextureView>,
104 gamma_mode: GammaMode,
106 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
108 viewport_clear_color: Color,
109}
110
111impl WgpuRenderer {
112 pub fn new(init_info: WgpuInitInfo, imgui_ctx: &mut Context) -> RendererResult<Self> {
135 #[cfg(any(
137 not(target_arch = "wasm32"),
138 all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental")
139 ))]
140 {
141 let mut renderer = Self::empty();
142 renderer.init_with_context(init_info, imgui_ctx)?;
143 Ok(renderer)
144 }
145
146 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
148 {
149 Self::new_without_font_atlas(init_info, imgui_ctx)
150 }
151 }
152
153 pub fn empty() -> Self {
173 Self {
174 backend_data: None,
175 shader_manager: ShaderManager::new(),
176 texture_manager: WgpuTextureManager::new(),
177 default_texture: None,
178 gamma_mode: GammaMode::Auto,
179 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
180 viewport_clear_color: Color::BLACK,
181 }
182 }
183
184 pub fn init(&mut self, init_info: WgpuInitInfo) -> RendererResult<()> {
188 let mut backend_data = WgpuBackendData::new(init_info);
190
191 let fmt = backend_data.render_target_format;
195 if let Some(adapter) = backend_data.adapter.as_ref() {
196 let fmt_features = adapter.get_texture_format_features(fmt);
197 if !fmt_features
198 .allowed_usages
199 .contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
200 || !fmt_features
201 .flags
202 .contains(wgpu::TextureFormatFeatureFlags::BLENDABLE)
203 {
204 return Err(RendererError::InvalidRenderState(format!(
205 "Render target format {:?} is not suitable for ImGui WGPU renderer (requires RENDER_ATTACHMENT + BLENDABLE). allowed_usages={:?} flags={:?}",
206 fmt, fmt_features.allowed_usages, fmt_features.flags
207 )));
208 }
209 }
210
211 backend_data
213 .render_resources
214 .initialize(&backend_data.device)?;
215
216 self.shader_manager.initialize(&backend_data.device)?;
218
219 let default_texture =
221 self.create_default_texture(&backend_data.device, &backend_data.queue)?;
222 self.default_texture = Some(default_texture);
223
224 self.create_device_objects(&mut backend_data)?;
226
227 self.backend_data = Some(backend_data);
228 Ok(())
229 }
230
231 pub fn new_without_font_atlas(
236 init_info: WgpuInitInfo,
237 imgui_ctx: &mut Context,
238 ) -> RendererResult<Self> {
239 let mut renderer = Self::empty();
240
241 renderer.init(init_info)?;
243
244 renderer.configure_imgui_context(imgui_ctx);
246
247 Ok(renderer)
251 }
252
253 pub fn init_with_context(
258 &mut self,
259 init_info: WgpuInitInfo,
260 imgui_ctx: &mut Context,
261 ) -> RendererResult<()> {
262 self.init(init_info)?;
264
265 self.configure_imgui_context(imgui_ctx);
268
269 self.prepare_font_atlas(imgui_ctx)?;
271
272 Ok(())
273 }
274
275 pub fn set_gamma_mode(&mut self, mode: GammaMode) {
277 self.gamma_mode = mode;
278 }
279
280 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
286 pub fn set_viewport_clear_color(&mut self, color: Color) {
287 self.viewport_clear_color = color;
288 }
289
290 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
292 pub fn viewport_clear_color(&self) -> Color {
293 self.viewport_clear_color
294 }
295
296 pub fn configure_imgui_context(&self, imgui_context: &mut Context) {
298 let should_set_name = imgui_context.io().backend_renderer_name().is_none();
299 if should_set_name {
300 let _ = imgui_context.set_renderer_name(Some(format!(
301 "dear-imgui-wgpu {}",
302 env!("CARGO_PKG_VERSION")
303 )));
304 }
305
306 let io = imgui_context.io_mut();
307 let mut flags = io.backend_flags();
308
309 flags.insert(BackendFlags::RENDERER_HAS_VTX_OFFSET);
312 flags.insert(BackendFlags::RENDERER_HAS_TEXTURES);
314
315 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
316 {
317 flags.insert(BackendFlags::RENDERER_HAS_VIEWPORTS);
319 }
320
321 io.set_backend_flags(flags);
322
323 let platform_io = imgui_context.platform_io_mut();
324 platform_io
325 .set_draw_callback_reset_render_state_raw(Some(draw_callback_reset_render_state));
326 platform_io
327 .set_draw_callback_set_sampler_linear_raw(Some(draw_callback_set_sampler_linear));
328 platform_io
329 .set_draw_callback_set_sampler_nearest_raw(Some(draw_callback_set_sampler_nearest));
330 }
331
332 pub fn prepare_font_atlas(&mut self, imgui_ctx: &mut Context) -> RendererResult<()> {
334 if let Some(backend_data) = &self.backend_data {
335 let device = backend_data.device.clone();
336 let queue = backend_data.queue.clone();
337 self.reload_font_texture(imgui_ctx, &device, &queue)?;
338 if imgui_ctx
339 .io()
340 .backend_flags()
341 .contains(BackendFlags::RENDERER_HAS_TEXTURES)
342 {
343 return Ok(());
346 }
347
348 let mut tex_ref = imgui_ctx.font_atlas().get_tex_ref();
353 let existing_tex_id = unsafe { sys::ImTextureRef_GetTexID(&mut tex_ref) };
354 let has_live_font_texture =
355 existing_tex_id != 0 && self.texture_manager.contains_texture(existing_tex_id);
356
357 if !has_live_font_texture
358 && let Some(tex_id) =
359 self.try_upload_font_atlas_legacy(imgui_ctx, &device, &queue)?
360 && cfg!(debug_assertions)
361 {
362 tracing::debug!(
363 target: "dear-imgui-wgpu",
364 "[dear-imgui-wgpu][debug] Font atlas uploaded via legacy fallback path. tex_id={}",
365 tex_id
366 );
367 }
368 }
369 Ok(())
370 }
371
372 fn create_default_texture(
376 &self,
377 device: &Device,
378 queue: &Queue,
379 ) -> RendererResult<TextureView> {
380 let texture = device.create_texture(&TextureDescriptor {
381 label: Some("Dear ImGui Default Texture"),
382 size: Extent3d {
383 width: 1,
384 height: 1,
385 depth_or_array_layers: 1,
386 },
387 mip_level_count: 1,
388 sample_count: 1,
389 dimension: TextureDimension::D2,
390 format: TextureFormat::Rgba8Unorm,
391 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
392 view_formats: &[],
393 });
394
395 queue.write_texture(
397 wgpu::TexelCopyTextureInfo {
398 texture: &texture,
399 mip_level: 0,
400 origin: wgpu::Origin3d::ZERO,
401 aspect: wgpu::TextureAspect::All,
402 },
403 &[255u8, 255u8, 255u8, 255u8], wgpu::TexelCopyBufferLayout {
405 offset: 0,
406 bytes_per_row: Some(4),
407 rows_per_image: Some(1),
408 },
409 Extent3d {
410 width: 1,
411 height: 1,
412 depth_or_array_layers: 1,
413 },
414 );
415
416 Ok(texture.create_view(&TextureViewDescriptor::default()))
417 }
418
419 pub fn texture_manager(&self) -> &WgpuTextureManager {
432 &self.texture_manager
433 }
434
435 pub fn texture_manager_mut(&mut self) -> &mut WgpuTextureManager {
437 &mut self.texture_manager
438 }
439
440 pub fn is_initialized(&self) -> bool {
442 self.backend_data.is_some()
443 }
444
445 pub fn update_texture(
470 &mut self,
471 texture_data: &dear_imgui_rs::TextureData,
472 ) -> RendererResult<crate::TextureUpdateResult> {
473 if let Some(backend_data) = &mut self.backend_data {
474 let result = self.texture_manager.update_single_texture(
475 texture_data,
476 &backend_data.device,
477 &backend_data.queue,
478 )?;
479
480 match result {
483 crate::TextureUpdateResult::Created { texture_id } => {
484 backend_data
485 .render_resources
486 .remove_image_bind_group(texture_id.id());
487 }
488 crate::TextureUpdateResult::Updated | crate::TextureUpdateResult::Destroyed => {
489 let id = texture_data.tex_id().id();
490 if id != 0 {
491 backend_data.render_resources.remove_image_bind_group(id);
492 }
493 }
494 crate::TextureUpdateResult::Failed | crate::TextureUpdateResult::NoAction => {}
495 }
496
497 Ok(result)
498 } else {
499 Err(RendererError::InvalidRenderState(
500 "Renderer not initialized".to_string(),
501 ))
502 }
503 }
504
505 pub fn new_frame(&mut self) -> RendererResult<()> {
509 let needs_recreation = if let Some(backend_data) = &self.backend_data {
510 backend_data.pipeline_state.is_none()
511 } else {
512 false
513 };
514
515 if needs_recreation {
516 let mut backend_data = self.backend_data.take().unwrap();
518 self.create_device_objects(&mut backend_data)?;
519 self.backend_data = Some(backend_data);
520 }
521 Ok(())
522 }
523
524 pub fn render_draw_data(
528 &mut self,
529 draw_data: &DrawData,
530 render_pass: &mut RenderPass,
531 ) -> RendererResult<()> {
532 let platform_io = unsafe { sys::igGetPlatformIO_Nil() };
533 self.render_draw_data_ex(draw_data, render_pass, platform_io)
534 }
535
536 pub fn render_context(
542 &mut self,
543 ctx: &mut Context,
544 render_pass: &mut RenderPass,
545 ) -> RendererResult<()> {
546 let platform_io = ctx.platform_io_mut().as_raw_mut();
547 let draw_data = ctx.render();
548 self.render_draw_data_ex(draw_data, render_pass, platform_io)
549 }
550
551 fn render_draw_data_ex(
552 &mut self,
553 draw_data: &DrawData,
554 render_pass: &mut RenderPass,
555 platform_io: *mut sys::ImGuiPlatformIO,
556 ) -> RendererResult<()> {
557 let mut total_vtx_count = 0usize;
559 let mut total_idx_count = 0usize;
560 for dl in draw_data.draw_lists() {
561 total_vtx_count += dl.vtx_buffer().len();
562 total_idx_count += dl.idx_buffer().len();
563 }
564 if total_vtx_count == 0 || total_idx_count == 0 {
565 return Ok(());
566 }
567
568 let backend_data = self.backend_data.as_mut().ok_or_else(|| {
569 RendererError::InvalidRenderState("Renderer not initialized".to_string())
570 })?;
571
572 let fb_width = (draw_data.display_size[0] * draw_data.framebuffer_scale[0]) as i32;
574 let fb_height = (draw_data.display_size[1] * draw_data.framebuffer_scale[1]) as i32;
575 if fb_width <= 0 || fb_height <= 0 || !draw_data.valid() {
576 return Ok(());
577 }
578
579 self.texture_manager.handle_texture_updates(
580 draw_data,
581 &backend_data.device,
582 &backend_data.queue,
583 &mut backend_data.render_resources,
584 );
585
586 backend_data.next_frame();
588
589 Self::prepare_frame_resources_static(draw_data, backend_data)?;
591
592 let gamma = match self.gamma_mode {
594 GammaMode::Auto => Uniforms::gamma_for_format(backend_data.render_target_format),
595 GammaMode::Linear => 1.0,
596 GammaMode::Gamma22 => 2.2,
597 };
598
599 Self::setup_render_state_static(draw_data, render_pass, backend_data, gamma)?;
601 render_pass.set_viewport(0.0, 0.0, fb_width as f32, fb_height as f32, 0.0, 1.0);
603
604 unsafe {
608 let mut render_state = crate::WgpuRenderState::new(&backend_data.device, render_pass);
610 let _render_state_guard = RendererRenderStateGuard::set(
611 platform_io,
612 &mut render_state as *mut _ as *mut std::ffi::c_void,
613 )?;
614
615 let result = Self::render_draw_lists_static(
617 &mut self.texture_manager,
618 &self.default_texture,
619 draw_data,
620 render_pass,
621 backend_data,
622 gamma,
623 );
624
625 if let Err(e) = result {
626 eprintln!("[wgpu-mv] render_draw_lists_static error: {:?}", e);
627 return Err(e);
628 }
629 }
630
631 Ok(())
632 }
633
634 pub fn render_draw_data_with_fb_size(
635 &mut self,
636 draw_data: &DrawData,
637 render_pass: &mut RenderPass,
638 fb_width: u32,
639 fb_height: u32,
640 ) -> RendererResult<()> {
641 let platform_io = unsafe { sys::igGetPlatformIO_Nil() };
642 self.render_draw_data_with_fb_size_ex(
644 draw_data,
645 render_pass,
646 fb_width,
647 fb_height,
648 true,
649 platform_io,
650 )
651 }
652
653 pub fn render_context_with_fb_size(
658 &mut self,
659 ctx: &mut Context,
660 render_pass: &mut RenderPass,
661 fb_width: u32,
662 fb_height: u32,
663 ) -> RendererResult<()> {
664 let platform_io = ctx.platform_io_mut().as_raw_mut();
665 let draw_data = ctx.render();
666 self.render_draw_data_with_fb_size_ex(
667 draw_data,
668 render_pass,
669 fb_width,
670 fb_height,
671 true,
672 platform_io,
673 )
674 }
675
676 fn render_draw_data_with_fb_size_ex(
680 &mut self,
681 draw_data: &DrawData,
682 render_pass: &mut RenderPass,
683 fb_width: u32,
684 fb_height: u32,
685 advance_frame: bool,
686 platform_io: *mut sys::ImGuiPlatformIO,
687 ) -> RendererResult<()> {
688 #[cfg(feature = "mv-log")]
691 {
692 static LAST_MISMATCH: OnceLock<Mutex<Option<(u32, u32, u32, u32, bool)>>> =
693 OnceLock::new();
694 let last = LAST_MISMATCH.get_or_init(|| Mutex::new(None));
695 let expected_w = (draw_data.display_size()[0] * draw_data.framebuffer_scale()[0])
696 .round()
697 .max(0.0) as u32;
698 let expected_h = (draw_data.display_size()[1] * draw_data.framebuffer_scale()[1])
699 .round()
700 .max(0.0) as u32;
701 if expected_w != fb_width || expected_h != fb_height {
702 let key = (expected_w, expected_h, fb_width, fb_height, advance_frame);
703 let mut guard = last.lock().unwrap();
704 if *guard != Some(key) {
705 mvlog!(
706 "[wgpu-mv] fb mismatch expected=({}, {}) override=({}, {}) disp=({:.1},{:.1}) fb_scale=({:.2},{:.2}) main={}",
707 expected_w,
708 expected_h,
709 fb_width,
710 fb_height,
711 draw_data.display_size()[0],
712 draw_data.display_size()[1],
713 draw_data.framebuffer_scale()[0],
714 draw_data.framebuffer_scale()[1],
715 advance_frame
716 );
717 *guard = Some(key);
718 }
719 }
720 }
721 let total_vtx_count: usize = draw_data.draw_lists().map(|dl| dl.vtx_buffer().len()).sum();
722 let total_idx_count: usize = draw_data.draw_lists().map(|dl| dl.idx_buffer().len()).sum();
723 if total_vtx_count == 0 || total_idx_count == 0 {
724 return Ok(());
725 }
726 let backend_data = self.backend_data.as_mut().ok_or_else(|| {
727 RendererError::InvalidRenderState("Renderer not initialized".to_string())
728 })?;
729
730 if fb_width == 0 || fb_height == 0 || !draw_data.valid() {
732 return Ok(());
733 }
734
735 self.texture_manager.handle_texture_updates(
736 draw_data,
737 &backend_data.device,
738 &backend_data.queue,
739 &mut backend_data.render_resources,
740 );
741
742 if advance_frame {
743 backend_data.next_frame();
744 }
745 Self::prepare_frame_resources_static(draw_data, backend_data)?;
746
747 let gamma = match self.gamma_mode {
748 GammaMode::Auto => Uniforms::gamma_for_format(backend_data.render_target_format),
749 GammaMode::Linear => 1.0,
750 GammaMode::Gamma22 => 2.2,
751 };
752
753 Self::setup_render_state_static(draw_data, render_pass, backend_data, gamma)?;
754
755 unsafe {
756 let mut render_state = crate::WgpuRenderState::new(&backend_data.device, render_pass);
757 let _render_state_guard = RendererRenderStateGuard::set(
758 platform_io,
759 &mut render_state as *mut _ as *mut std::ffi::c_void,
760 )?;
761
762 let device = backend_data.device.clone();
765 let (common_layout, uniform_buffer, default_common_bg, nearest_common_bg) = {
766 let ub = backend_data
767 .render_resources
768 .uniform_buffer()
769 .ok_or_else(|| {
770 RendererError::InvalidRenderState(
771 "Uniform buffer not initialized".to_string(),
772 )
773 })?;
774 let nearest_bg = backend_data
775 .render_resources
776 .nearest_common_bind_group()
777 .ok_or_else(|| {
778 RendererError::InvalidRenderState(
779 "Nearest sampler bind group not initialized".to_string(),
780 )
781 })?;
782 (
783 ub.bind_group_layout().clone(),
784 ub.buffer().clone(),
785 ub.bind_group().clone(),
786 nearest_bg.clone(),
787 )
788 };
789 let mut standard_sampler = ActiveSampler::Linear;
790 let mut current_sampler = ActiveSampler::Linear;
791
792 let mut global_idx_offset: u32 = 0;
793 let mut global_vtx_offset: i32 = 0;
794 let clip_off = draw_data.display_pos();
795 let clip_scale = draw_data.framebuffer_scale();
796 let fbw = fb_width as f32;
797 let fbh = fb_height as f32;
798
799 for draw_list in draw_data.draw_lists() {
800 let vtx_buffer = draw_list.vtx_buffer();
801 let idx_buffer = draw_list.idx_buffer();
802 for cmd in draw_list.commands() {
803 match cmd {
804 dear_imgui_rs::render::DrawCmd::Elements {
805 count,
806 cmd_params,
807 raw_cmd,
808 } => {
809 let mut cmd_copy = *raw_cmd;
812 let tex_id =
813 dear_imgui_rs::sys::ImDrawCmd_GetTexID(&mut cmd_copy) as u64;
814
815 let desired_sampler = if tex_id == 0 {
818 standard_sampler
819 } else {
820 self.texture_manager
821 .custom_sampler_id_for_texture(tex_id)
822 .map(ActiveSampler::Custom)
823 .unwrap_or(standard_sampler)
824 };
825 if desired_sampler != current_sampler {
826 match desired_sampler {
827 ActiveSampler::Linear => {
828 render_pass.set_bind_group(0, &default_common_bg, &[]);
829 }
830 ActiveSampler::Nearest => {
831 render_pass.set_bind_group(0, &nearest_common_bg, &[]);
832 }
833 ActiveSampler::Custom(sampler_id) => {
834 if let Some(bg0) = self
835 .texture_manager
836 .get_or_create_common_bind_group_for_sampler(
837 &device,
838 &common_layout,
839 &uniform_buffer,
840 sampler_id,
841 )
842 {
843 render_pass.set_bind_group(0, &bg0, &[]);
844 } else {
845 render_pass.set_bind_group(0, &default_common_bg, &[]);
846 }
847 }
848 }
849 current_sampler = desired_sampler;
850 }
851
852 let texture_bind_group = if tex_id == 0 {
853 if let Some(default_tex) = &self.default_texture {
854 backend_data
855 .render_resources
856 .get_or_create_image_bind_group(
857 &backend_data.device,
858 0,
859 default_tex,
860 )?
861 .clone()
862 } else {
863 return Err(RendererError::InvalidRenderState(
864 "Default texture not available".to_string(),
865 ));
866 }
867 } else if let Some(wgpu_texture) =
868 self.texture_manager.get_texture(tex_id)
869 {
870 backend_data
871 .render_resources
872 .get_or_create_image_bind_group(
873 &backend_data.device,
874 tex_id,
875 &wgpu_texture.texture_view,
876 )?
877 .clone()
878 } else if let Some(default_tex) = &self.default_texture {
879 backend_data
880 .render_resources
881 .get_or_create_image_bind_group(
882 &backend_data.device,
883 0,
884 default_tex,
885 )?
886 .clone()
887 } else {
888 return Err(RendererError::InvalidRenderState(
889 "Texture not found and no default texture".to_string(),
890 ));
891 };
892 render_pass.set_bind_group(1, &texture_bind_group, &[]);
893
894 let mut clip_min_x =
896 (cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0];
897 let mut clip_min_y =
898 (cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1];
899 let mut clip_max_x =
900 (cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0];
901 let mut clip_max_y =
902 (cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1];
903 clip_min_x = clip_min_x.max(0.0);
905 clip_min_y = clip_min_y.max(0.0);
906 clip_max_x = clip_max_x.min(fbw);
907 clip_max_y = clip_max_y.min(fbh);
908 if clip_max_x <= clip_min_x || clip_max_y <= clip_min_y {
909 continue;
910 }
911 render_pass.set_scissor_rect(
912 clip_min_x as u32,
913 clip_min_y as u32,
914 (clip_max_x - clip_min_x) as u32,
915 (clip_max_y - clip_min_y) as u32,
916 );
917 let Ok(count_u32) = u32::try_from(count) else {
918 continue;
919 };
920 let Ok(idx_offset_u32) = u32::try_from(cmd_params.idx_offset) else {
921 continue;
922 };
923 let Some(start_index) = idx_offset_u32.checked_add(global_idx_offset)
924 else {
925 continue;
926 };
927 let Some(end_index) = start_index.checked_add(count_u32) else {
928 continue;
929 };
930 let Ok(vtx_offset_i32) = i32::try_from(cmd_params.vtx_offset) else {
931 continue;
932 };
933 let Some(vertex_offset) = vtx_offset_i32.checked_add(global_vtx_offset)
934 else {
935 continue;
936 };
937 render_pass.draw_indexed(start_index..end_index, vertex_offset, 0..1);
938 }
939 dear_imgui_rs::render::DrawCmd::ResetRenderState => {
940 Self::setup_render_state_static(
941 draw_data,
942 render_pass,
943 backend_data,
944 gamma,
945 )?;
946 standard_sampler = ActiveSampler::Linear;
947 current_sampler = ActiveSampler::Linear;
948 }
949 dear_imgui_rs::render::DrawCmd::SetSamplerLinear => {
950 standard_sampler = ActiveSampler::Linear;
951 if current_sampler != ActiveSampler::Linear {
952 render_pass.set_bind_group(0, &default_common_bg, &[]);
953 current_sampler = ActiveSampler::Linear;
954 }
955 }
956 dear_imgui_rs::render::DrawCmd::SetSamplerNearest => {
957 standard_sampler = ActiveSampler::Nearest;
958 if current_sampler != ActiveSampler::Nearest {
959 render_pass.set_bind_group(0, &nearest_common_bg, &[]);
960 current_sampler = ActiveSampler::Nearest;
961 }
962 }
963 dear_imgui_rs::render::DrawCmd::RawCallback { .. } => {
964 }
966 }
967 }
968
969 let idx_len_u32 = u32::try_from(idx_buffer.len())
970 .map_err(|_| RendererError::Generic("index buffer too large".to_string()))?;
971 global_idx_offset =
972 global_idx_offset.checked_add(idx_len_u32).ok_or_else(|| {
973 RendererError::Generic("index buffer offset overflow".to_string())
974 })?;
975
976 let vtx_len_i32 = i32::try_from(vtx_buffer.len())
977 .map_err(|_| RendererError::Generic("vertex buffer too large".to_string()))?;
978 global_vtx_offset =
979 global_vtx_offset.checked_add(vtx_len_i32).ok_or_else(|| {
980 RendererError::Generic("vertex buffer offset overflow".to_string())
981 })?;
982 }
983 }
984
985 Ok(())
986 }
987
988 pub fn invalidate_device_objects(&mut self) -> RendererResult<()> {
1003 if let Some(ref mut backend_data) = self.backend_data {
1004 backend_data.pipeline_state = None;
1005 backend_data.render_resources = RenderResources::new();
1006
1007 for frame_resources in &mut backend_data.frame_resources {
1009 *frame_resources = FrameResources::new();
1010 }
1011 }
1012
1013 self.texture_manager.clear();
1015 self.default_texture = None;
1016
1017 Ok(())
1018 }
1019
1020 pub fn shutdown(&mut self) {
1024 self.invalidate_device_objects().ok();
1025 self.backend_data = None;
1026 }
1027}
1028
1029mod draw;
1031mod external_textures;
1032mod font_atlas;
1033#[cfg(feature = "multi-viewport-winit")]
1034pub mod multi_viewport;
1035#[cfg(feature = "multi-viewport-sdl3")]
1036pub mod multi_viewport_sdl3;
1037mod pipeline;
1038#[cfg(feature = "multi-viewport-sdl3")]
1039mod sdl3_raw_window_handle;
1040
1041impl Default for WgpuRenderer {
1042 fn default() -> Self {
1043 Self::empty()
1044 }
1045}
1046
1047#[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
1048impl Drop for WgpuRenderer {
1049 fn drop(&mut self) {
1050 #[cfg(feature = "multi-viewport-winit")]
1053 {
1054 multi_viewport::clear_for_drop(self as *mut WgpuRenderer);
1055 }
1056 #[cfg(feature = "multi-viewport-sdl3")]
1057 {
1058 multi_viewport_sdl3::clear_for_drop(self as *mut WgpuRenderer);
1059 }
1060 }
1061}
1062
1063#[cfg(test)]
1064mod tests {
1065 use super::*;
1066
1067 #[test]
1068 fn renderer_render_state_guard_clears_on_drop() {
1069 unsafe {
1070 let platform_io = sys::ImGuiPlatformIO_ImGuiPlatformIO();
1071 assert!(!platform_io.is_null());
1072
1073 let mut render_state = 7u8;
1074 {
1075 let _guard = RendererRenderStateGuard::set(
1076 platform_io,
1077 (&mut render_state as *mut u8).cast(),
1078 )
1079 .expect("render state guard should set a valid PlatformIO");
1080 assert_eq!(
1081 (*platform_io).Renderer_RenderState,
1082 (&mut render_state as *mut u8).cast()
1083 );
1084 }
1085
1086 assert!((*platform_io).Renderer_RenderState.is_null());
1087 sys::ImGuiPlatformIO_destroy(platform_io);
1088 }
1089 }
1090}