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};
22use wgpu::*;
23
24#[allow(unused_macros)]
27macro_rules! mvlog {
28 ($($arg:tt)*) => {
29 if cfg!(feature = "mv-log") { eprintln!($($arg)*); }
30 }
31}
32
33pub struct WgpuRenderer {
37 backend_data: Option<WgpuBackendData>,
39 shader_manager: ShaderManager,
41 texture_manager: WgpuTextureManager,
43 default_texture: Option<TextureView>,
45 font_texture_id: Option<u64>,
47 gamma_mode: GammaMode,
49}
50
51impl WgpuRenderer {
52 pub fn new(init_info: WgpuInitInfo, imgui_ctx: &mut Context) -> RendererResult<Self> {
69 let mut renderer = Self::empty();
70 renderer.init_with_context(init_info, imgui_ctx)?;
71 Ok(renderer)
72 }
73
74 pub fn empty() -> Self {
88 Self {
89 backend_data: None,
90 shader_manager: ShaderManager::new(),
91 texture_manager: WgpuTextureManager::new(),
92 default_texture: None,
93 font_texture_id: None,
94 gamma_mode: GammaMode::Auto,
95 }
96 }
97
98 pub fn init(&mut self, init_info: WgpuInitInfo) -> RendererResult<()> {
102 let mut backend_data = WgpuBackendData::new(init_info);
104
105 backend_data
107 .render_resources
108 .initialize(&backend_data.device)?;
109
110 self.shader_manager.initialize(&backend_data.device)?;
112
113 let default_texture =
115 self.create_default_texture(&backend_data.device, &backend_data.queue)?;
116 self.default_texture = Some(default_texture);
117
118 self.create_device_objects(&mut backend_data)?;
120
121 self.backend_data = Some(backend_data);
122 Ok(())
123 }
124
125 pub fn new_without_font_atlas(
130 init_info: WgpuInitInfo,
131 imgui_ctx: &mut Context,
132 ) -> RendererResult<Self> {
133 let mut renderer = Self::empty();
134
135 renderer.init(init_info)?;
137
138 renderer.configure_imgui_context(imgui_ctx);
140
141 Ok(renderer)
145 }
146
147 pub fn init_with_context(
152 &mut self,
153 init_info: WgpuInitInfo,
154 imgui_ctx: &mut Context,
155 ) -> RendererResult<()> {
156 self.init(init_info)?;
158
159 self.configure_imgui_context(imgui_ctx);
162
163 self.prepare_font_atlas(imgui_ctx)?;
165
166 Ok(())
167 }
168
169 pub fn set_gamma_mode(&mut self, mode: GammaMode) {
171 self.gamma_mode = mode;
172 }
173
174 pub fn configure_imgui_context(&self, imgui_context: &mut Context) {
176 let io = imgui_context.io_mut();
177 let mut flags = io.backend_flags();
178
179 flags.insert(BackendFlags::RENDERER_HAS_VTX_OFFSET);
182 flags.insert(BackendFlags::RENDERER_HAS_TEXTURES);
184
185 #[cfg(feature = "multi-viewport")]
186 {
187 flags.insert(BackendFlags::RENDERER_HAS_VIEWPORTS);
189 }
190
191 io.set_backend_flags(flags);
192 }
194
195 pub fn prepare_font_atlas(&mut self, imgui_ctx: &mut Context) -> RendererResult<()> {
197 if let Some(backend_data) = &self.backend_data {
198 let device = backend_data.device.clone();
199 let queue = backend_data.queue.clone();
200 self.reload_font_texture(imgui_ctx, &device, &queue)?;
201 if self.font_texture_id.is_none() {
204 if let Some(tex_id) =
205 self.try_upload_font_atlas_legacy(imgui_ctx, &device, &queue)?
206 {
207 if cfg!(debug_assertions) {
208 tracing::debug!(
209 target: "dear-imgui-wgpu",
210 "[dear-imgui-wgpu][debug] Font atlas uploaded via fallback (legacy-only) path; user textures use modern ImTextureData. tex_id={}",
211 tex_id
212 );
213 }
214 self.font_texture_id = Some(tex_id);
215 }
216 } else if cfg!(debug_assertions) {
217 tracing::debug!(
218 target: "dear-imgui-wgpu",
219 "[dear-imgui-wgpu][debug] Font atlas tex_id already set: {:?}",
220 self.font_texture_id
221 );
222 }
223 }
224 Ok(())
225 }
226
227 fn create_device_objects(&mut self, backend_data: &mut WgpuBackendData) -> RendererResult<()> {
231 let device = &backend_data.device;
232
233 let (common_layout, image_layout) = crate::shaders::create_bind_group_layouts(device);
235
236 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
238 label: Some("Dear ImGui Pipeline Layout"),
239 bind_group_layouts: &[&common_layout, &image_layout],
240 push_constant_ranges: &[],
241 });
242
243 let shader_module = self.shader_manager.get_shader_module()?;
245
246 let vertex_buffer_layout = crate::shaders::create_vertex_buffer_layout();
248 let vertex_buffer_layouts = [vertex_buffer_layout];
249
250 let vertex_state =
252 crate::shaders::create_vertex_state(shader_module, &vertex_buffer_layouts);
253
254 let color_target = ColorTargetState {
256 format: backend_data.render_target_format,
257 blend: Some(BlendState {
258 color: BlendComponent {
259 src_factor: BlendFactor::SrcAlpha,
260 dst_factor: BlendFactor::OneMinusSrcAlpha,
261 operation: BlendOperation::Add,
262 },
263 alpha: BlendComponent {
264 src_factor: BlendFactor::One,
265 dst_factor: BlendFactor::OneMinusSrcAlpha,
266 operation: BlendOperation::Add,
267 },
268 }),
269 write_mask: ColorWrites::ALL,
270 };
271
272 let use_gamma_correction = matches!(
274 backend_data.render_target_format,
275 TextureFormat::Rgba8UnormSrgb
276 | TextureFormat::Bgra8UnormSrgb
277 | TextureFormat::Bc1RgbaUnormSrgb
278 | TextureFormat::Bc2RgbaUnormSrgb
279 | TextureFormat::Bc3RgbaUnormSrgb
280 | TextureFormat::Bc7RgbaUnormSrgb
281 );
282
283 let color_targets = [Some(color_target)];
285 let fragment_state = crate::shaders::create_fragment_state(
286 shader_module,
287 &color_targets,
288 use_gamma_correction,
289 );
290
291 let depth_stencil = backend_data
293 .depth_stencil_format
294 .map(|format| DepthStencilState {
295 format,
296 depth_write_enabled: false, depth_compare: CompareFunction::Always, stencil: StencilState {
299 front: StencilFaceState {
300 compare: CompareFunction::Always, fail_op: StencilOperation::Keep, depth_fail_op: StencilOperation::Keep, pass_op: StencilOperation::Keep, },
305 back: StencilFaceState {
306 compare: CompareFunction::Always, fail_op: StencilOperation::Keep, depth_fail_op: StencilOperation::Keep, pass_op: StencilOperation::Keep, },
311 read_mask: 0xff, write_mask: 0xff, },
314 bias: DepthBiasState::default(),
315 });
316
317 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
319 label: Some("Dear ImGui Render Pipeline"),
320 layout: Some(&pipeline_layout),
321 vertex: vertex_state,
322 primitive: PrimitiveState {
323 topology: PrimitiveTopology::TriangleList,
324 strip_index_format: None,
325 front_face: FrontFace::Cw,
326 cull_mode: None,
327 polygon_mode: PolygonMode::Fill,
328 unclipped_depth: false,
329 conservative: false,
330 },
331 depth_stencil,
332 multisample: backend_data.init_info.pipeline_multisample_state,
333 fragment: Some(fragment_state),
334 multiview: None,
335 cache: None,
336 });
337
338 backend_data.pipeline_state = Some(pipeline);
339 Ok(())
340 }
341
342 fn create_default_texture(
344 &self,
345 device: &Device,
346 queue: &Queue,
347 ) -> RendererResult<TextureView> {
348 let texture = device.create_texture(&TextureDescriptor {
349 label: Some("Dear ImGui Default Texture"),
350 size: Extent3d {
351 width: 1,
352 height: 1,
353 depth_or_array_layers: 1,
354 },
355 mip_level_count: 1,
356 sample_count: 1,
357 dimension: TextureDimension::D2,
358 format: TextureFormat::Rgba8Unorm,
359 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
360 view_formats: &[],
361 });
362
363 queue.write_texture(
365 wgpu::TexelCopyTextureInfo {
366 texture: &texture,
367 mip_level: 0,
368 origin: wgpu::Origin3d::ZERO,
369 aspect: wgpu::TextureAspect::All,
370 },
371 &[255u8, 255u8, 255u8, 255u8], wgpu::TexelCopyBufferLayout {
373 offset: 0,
374 bytes_per_row: Some(4),
375 rows_per_image: Some(1),
376 },
377 Extent3d {
378 width: 1,
379 height: 1,
380 depth_or_array_layers: 1,
381 },
382 );
383
384 Ok(texture.create_view(&TextureViewDescriptor::default()))
385 }
386
387 fn reload_font_texture(
393 &mut self,
394 imgui_ctx: &mut Context,
395 _device: &Device,
396 _queue: &Queue,
397 ) -> RendererResult<()> {
398 let mut fonts = imgui_ctx.font_atlas_mut();
399 if !fonts.is_built() {
402 fonts.build();
403 }
404
405 Ok(())
410 }
411
412 fn try_upload_font_atlas_legacy(
415 &mut self,
416 imgui_ctx: &mut Context,
417 device: &Device,
418 queue: &Queue,
419 ) -> RendererResult<Option<u64>> {
420 let fonts = imgui_ctx.font_atlas();
422 let raw_tex = fonts.get_tex_data();
424 if raw_tex.is_null() {
425 if cfg!(debug_assertions) {
426 tracing::debug!(
427 target: "dear-imgui-wgpu",
428 "[dear-imgui-wgpu][debug] Font atlas TexData is null; skip legacy upload"
429 );
430 }
431 return Ok(None);
432 }
433 let (width, height, bpp, pixels_slice): (u32, u32, i32, Option<&[u8]>) = unsafe {
435 let w = (*raw_tex).Width as u32;
436 let h = (*raw_tex).Height as u32;
437 let bpp = (*raw_tex).BytesPerPixel;
438 let px_ptr = (*raw_tex).Pixels as *const u8;
439 if px_ptr.is_null() || w == 0 || h == 0 {
440 (w, h, bpp, None)
441 } else {
442 let size = (w as usize) * (h as usize) * (bpp as usize).max(1);
443 (w, h, bpp, Some(std::slice::from_raw_parts(px_ptr, size)))
444 }
445 };
446
447 if let Some(src) = pixels_slice {
448 if cfg!(debug_assertions) {
449 tracing::debug!(
450 target: "dear-imgui-wgpu",
451 "[dear-imgui-wgpu][debug] Font atlas texdata: {}x{} bpp={} (fallback upload for font atlas)",
452 width, height, bpp
453 );
454 }
455 let (format, converted): (wgpu::TextureFormat, Vec<u8>) = if bpp == 4 {
457 (wgpu::TextureFormat::Rgba8Unorm, src.to_vec())
458 } else if bpp == 1 {
459 let mut out = Vec::with_capacity((width as usize) * (height as usize) * 4);
461 for &a in src.iter() {
462 out.extend_from_slice(&[255, 255, 255, a]);
463 }
464 (wgpu::TextureFormat::Rgba8Unorm, out)
465 } else {
466 if cfg!(debug_assertions) {
468 tracing::debug!(
469 target: "dear-imgui-wgpu",
470 "[dear-imgui-wgpu][debug] Unexpected font atlas bpp={} -> skip",
471 bpp
472 );
473 }
474 return Ok(None);
475 };
476
477 let texture = device.create_texture(&wgpu::TextureDescriptor {
479 label: Some("Dear ImGui Font Atlas"),
480 size: wgpu::Extent3d {
481 width,
482 height,
483 depth_or_array_layers: 1,
484 },
485 mip_level_count: 1,
486 sample_count: 1,
487 dimension: wgpu::TextureDimension::D2,
488 format,
489 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
490 view_formats: &[],
491 });
492 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
493 let bpp = 4u32;
495 let unpadded = width * bpp;
496 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
497 let padded = unpadded.div_ceil(align) * align;
498 if padded == unpadded {
499 queue.write_texture(
500 wgpu::TexelCopyTextureInfo {
501 texture: &texture,
502 mip_level: 0,
503 origin: wgpu::Origin3d::ZERO,
504 aspect: wgpu::TextureAspect::All,
505 },
506 &converted,
507 wgpu::TexelCopyBufferLayout {
508 offset: 0,
509 bytes_per_row: Some(unpadded),
510 rows_per_image: Some(height),
511 },
512 wgpu::Extent3d {
513 width,
514 height,
515 depth_or_array_layers: 1,
516 },
517 );
518 } else {
519 let mut padded_buf = vec![0u8; (padded * height) as usize];
520 for row in 0..height as usize {
521 let src = row * (unpadded as usize);
522 let dst = row * (padded as usize);
523 padded_buf[dst..dst + (unpadded as usize)]
524 .copy_from_slice(&converted[src..src + (unpadded as usize)]);
525 }
526 queue.write_texture(
527 wgpu::TexelCopyTextureInfo {
528 texture: &texture,
529 mip_level: 0,
530 origin: wgpu::Origin3d::ZERO,
531 aspect: wgpu::TextureAspect::All,
532 },
533 &padded_buf,
534 wgpu::TexelCopyBufferLayout {
535 offset: 0,
536 bytes_per_row: Some(padded),
537 rows_per_image: Some(height),
538 },
539 wgpu::Extent3d {
540 width,
541 height,
542 depth_or_array_layers: 1,
543 },
544 );
545 if cfg!(debug_assertions) {
546 tracing::debug!(
547 target: "dear-imgui-wgpu",
548 "[dear-imgui-wgpu][debug] Upload font atlas with padded row pitch: unpadded={} padded={}",
549 unpadded, padded
550 );
551 }
552 }
553
554 let tex_id = self
556 .texture_manager
557 .register_texture(crate::WgpuTexture::new(texture, view));
558
559 {
561 let mut fonts_mut = imgui_ctx.font_atlas_mut();
562 fonts_mut.set_texture_id(dear_imgui_rs::TextureId::from(tex_id));
563 }
564 if cfg!(debug_assertions) {
565 tracing::debug!(
566 target: "dear-imgui-wgpu",
567 "[dear-imgui-wgpu][debug] Font atlas fallback upload complete: tex_id={}",
568 tex_id
569 );
570 }
571
572 return Ok(Some(tex_id));
573 }
574 if cfg!(debug_assertions) {
575 tracing::debug!(
576 target: "dear-imgui-wgpu",
577 "[dear-imgui-wgpu][debug] Font atlas has no CPU pixel buffer; skipping fallback upload (renderer will use modern texture updates)"
578 );
579 }
580 Ok(None)
581 }
582
583 pub fn texture_manager(&self) -> &WgpuTextureManager {
585 &self.texture_manager
586 }
587
588 pub fn texture_manager_mut(&mut self) -> &mut WgpuTextureManager {
590 &mut self.texture_manager
591 }
592
593 pub fn is_initialized(&self) -> bool {
595 self.backend_data.is_some()
596 }
597
598 pub fn update_texture(
622 &mut self,
623 texture_data: &dear_imgui_rs::TextureData,
624 ) -> RendererResult<crate::TextureUpdateResult> {
625 if let Some(backend_data) = &self.backend_data {
626 self.texture_manager
627 .update_single_texture(texture_data, &backend_data.device, &backend_data.queue)
628 .map_err(RendererError::TextureCreationFailed)
629 } else {
630 Err(RendererError::InvalidRenderState(
631 "Renderer not initialized".to_string(),
632 ))
633 }
634 }
635
636 pub fn new_frame(&mut self) -> RendererResult<()> {
640 let needs_recreation = if let Some(backend_data) = &self.backend_data {
641 backend_data.pipeline_state.is_none()
642 } else {
643 false
644 };
645
646 if needs_recreation {
647 let mut backend_data = self.backend_data.take().unwrap();
649 self.create_device_objects(&mut backend_data)?;
650 self.backend_data = Some(backend_data);
651 }
652 Ok(())
653 }
654
655 pub fn render_draw_data(
659 &mut self,
660 draw_data: &DrawData,
661 render_pass: &mut RenderPass,
662 ) -> RendererResult<()> {
663 mvlog!(
664 "[wgpu-mv] render_draw_data: valid={} lists={} fb_scale=({:.2},{:.2}) disp=({:.1},{:.1})",
665 draw_data.valid(),
666 draw_data.draw_lists_count(),
667 draw_data.framebuffer_scale()[0],
668 draw_data.framebuffer_scale()[1],
669 draw_data.display_size()[0],
670 draw_data.display_size()[1]
671 );
672 let mut total_vtx_count = 0usize;
674 let mut total_idx_count = 0usize;
675 for dl in draw_data.draw_lists() {
676 total_vtx_count += dl.vtx_buffer().len();
677 total_idx_count += dl.idx_buffer().len();
678 }
679 if total_vtx_count == 0 || total_idx_count == 0 {
680 mvlog!("[wgpu-mv] no vertices/indices; skipping render");
681 return Ok(());
682 }
683
684 let backend_data = self.backend_data.as_mut().ok_or_else(|| {
685 RendererError::InvalidRenderState("Renderer not initialized".to_string())
686 })?;
687
688 let fb_width = (draw_data.display_size[0] * draw_data.framebuffer_scale[0]) as i32;
690 let fb_height = (draw_data.display_size[1] * draw_data.framebuffer_scale[1]) as i32;
691 if fb_width <= 0 || fb_height <= 0 || !draw_data.valid() {
692 return Ok(());
693 }
694
695 mvlog!("[wgpu-mv] handle_texture_updates");
696 self.texture_manager.handle_texture_updates(
697 draw_data,
698 &backend_data.device,
699 &backend_data.queue,
700 );
701
702 mvlog!("[wgpu-mv] next_frame before: {}", backend_data.frame_index);
704 backend_data.next_frame();
705 mvlog!("[wgpu-mv] next_frame after: {}", backend_data.frame_index);
706
707 mvlog!("[wgpu-mv] prepare_frame_resources");
709 Self::prepare_frame_resources_static(draw_data, backend_data)?;
710
711 let gamma = match self.gamma_mode {
713 GammaMode::Auto => Uniforms::gamma_for_format(backend_data.render_target_format),
714 GammaMode::Linear => 1.0,
715 GammaMode::Gamma22 => 2.2,
716 };
717
718 mvlog!("[wgpu-mv] setup_render_state");
720 Self::setup_render_state_static(draw_data, render_pass, backend_data, gamma)?;
721 render_pass.set_viewport(0.0, 0.0, fb_width as f32, fb_height as f32, 0.0, 1.0);
723
724 unsafe {
728 let platform_io = dear_imgui_rs::sys::igGetPlatformIO_Nil();
730
731 mvlog!("[wgpu-mv] create render_state");
733 let mut render_state = crate::WgpuRenderState::new(&backend_data.device, render_pass);
734
735 (*platform_io).Renderer_RenderState =
737 &mut render_state as *mut _ as *mut std::ffi::c_void;
738
739 let result = Self::render_draw_lists_static(
741 &mut self.texture_manager,
742 &self.default_texture,
743 draw_data,
744 render_pass,
745 backend_data,
746 gamma,
747 );
748
749 (*platform_io).Renderer_RenderState = std::ptr::null_mut();
751
752 if let Err(e) = result {
753 eprintln!("[wgpu-mv] render_draw_lists_static error: {:?}", e);
754 return Err(e);
755 }
756 }
757
758 Ok(())
759 }
760
761 pub fn render_draw_data_with_fb_size(
766 &mut self,
767 draw_data: &DrawData,
768 render_pass: &mut RenderPass,
769 fb_width: u32,
770 fb_height: u32,
771 ) -> RendererResult<()> {
772 mvlog!(
773 "[wgpu-mv] render_draw_data(with_fb) lists={} override_fb=({}, {}) disp=({:.1},{:.1})",
774 draw_data.draw_lists_count(),
775 fb_width,
776 fb_height,
777 draw_data.display_size()[0],
778 draw_data.display_size()[1]
779 );
780 let total_vtx_count: usize = draw_data.draw_lists().map(|dl| dl.vtx_buffer().len()).sum();
781 let total_idx_count: usize = draw_data.draw_lists().map(|dl| dl.idx_buffer().len()).sum();
782 if total_vtx_count == 0 || total_idx_count == 0 {
783 return Ok(());
784 }
785 let backend_data = self.backend_data.as_mut().ok_or_else(|| {
786 RendererError::InvalidRenderState("Renderer not initialized".to_string())
787 })?;
788
789 if fb_width == 0 || fb_height == 0 || !draw_data.valid() {
791 return Ok(());
792 }
793
794 self.texture_manager.handle_texture_updates(
795 draw_data,
796 &backend_data.device,
797 &backend_data.queue,
798 );
799
800 backend_data.next_frame();
801 Self::prepare_frame_resources_static(draw_data, backend_data)?;
802
803 let gamma = match self.gamma_mode {
804 GammaMode::Auto => Uniforms::gamma_for_format(backend_data.render_target_format),
805 GammaMode::Linear => 1.0,
806 GammaMode::Gamma22 => 2.2,
807 };
808
809 Self::setup_render_state_static(draw_data, render_pass, backend_data, gamma)?;
810
811 unsafe {
812 let platform_io = dear_imgui_rs::sys::igGetPlatformIO_Nil();
813 let mut render_state = crate::WgpuRenderState::new(&backend_data.device, render_pass);
814 (*platform_io).Renderer_RenderState =
815 &mut render_state as *mut _ as *mut std::ffi::c_void;
816
817 let mut global_idx_offset: u32 = 0;
819 let mut global_vtx_offset: i32 = 0;
820 let clip_off = draw_data.display_pos();
821 let clip_scale = draw_data.framebuffer_scale();
822 let fbw = fb_width as f32;
823 let fbh = fb_height as f32;
824
825 for draw_list in draw_data.draw_lists() {
826 let vtx_buffer = draw_list.vtx_buffer();
827 let idx_buffer = draw_list.idx_buffer();
828 let mut cmd_i = 0;
829 for cmd in draw_list.commands() {
830 match cmd {
831 dear_imgui_rs::render::DrawCmd::Elements {
832 count,
833 cmd_params,
834 raw_cmd,
835 } => {
836 let texture_bind_group = {
838 let tex_id = unsafe {
840 dear_imgui_rs::sys::ImDrawCmd_GetTexID(
841 raw_cmd as *mut dear_imgui_rs::sys::ImDrawCmd,
842 )
843 } as u64;
844 if tex_id == 0 {
845 if let Some(default_tex) = &self.default_texture {
846 backend_data
847 .render_resources
848 .get_or_create_image_bind_group(
849 &backend_data.device,
850 0,
851 default_tex,
852 )?
853 .clone()
854 } else {
855 return Err(RendererError::InvalidRenderState(
856 "Default texture not available".to_string(),
857 ));
858 }
859 } else if let Some(wgpu_texture) =
860 self.texture_manager.get_texture(tex_id)
861 {
862 backend_data
863 .render_resources
864 .get_or_create_image_bind_group(
865 &backend_data.device,
866 tex_id,
867 &wgpu_texture.texture_view,
868 )?
869 .clone()
870 } else if let Some(default_tex) = &self.default_texture {
871 backend_data
872 .render_resources
873 .get_or_create_image_bind_group(
874 &backend_data.device,
875 0,
876 default_tex,
877 )?
878 .clone()
879 } else {
880 return Err(RendererError::InvalidRenderState(
881 "Texture not found and no default texture".to_string(),
882 ));
883 }
884 };
885 render_pass.set_bind_group(1, &texture_bind_group, &[]);
886
887 let mut clip_min_x =
889 (cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0];
890 let mut clip_min_y =
891 (cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1];
892 let mut clip_max_x =
893 (cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0];
894 let mut clip_max_y =
895 (cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1];
896 clip_min_x = clip_min_x.max(0.0);
898 clip_min_y = clip_min_y.max(0.0);
899 clip_max_x = clip_max_x.min(fbw);
900 clip_max_y = clip_max_y.min(fbh);
901 if clip_max_x <= clip_min_x || clip_max_y <= clip_min_y {
902 cmd_i += 1;
903 continue;
904 }
905 render_pass.set_scissor_rect(
906 clip_min_x as u32,
907 clip_min_y as u32,
908 (clip_max_x - clip_min_x) as u32,
909 (clip_max_y - clip_min_y) as u32,
910 );
911 let start_index = cmd_params.idx_offset as u32 + global_idx_offset;
912 let end_index = start_index + count as u32;
913 let vertex_offset = (cmd_params.vtx_offset as i32) + global_vtx_offset;
914 render_pass.draw_indexed(start_index..end_index, vertex_offset, 0..1);
915 }
916 dear_imgui_rs::render::DrawCmd::ResetRenderState => {
917 Self::setup_render_state_static(
918 draw_data,
919 render_pass,
920 backend_data,
921 gamma,
922 )?;
923 }
924 dear_imgui_rs::render::DrawCmd::RawCallback { .. } => {
925 }
927 }
928 cmd_i += 1;
929 }
930
931 global_idx_offset += idx_buffer.len() as u32;
932 global_vtx_offset += vtx_buffer.len() as i32;
933 }
934
935 (*platform_io).Renderer_RenderState = std::ptr::null_mut();
936 }
937
938 Ok(())
939 }
940
941 fn prepare_frame_resources_static(
943 draw_data: &DrawData,
944 backend_data: &mut WgpuBackendData,
945 ) -> RendererResult<()> {
946 mvlog!("[wgpu-mv] totals start");
947 let mut total_vtx_count = 0;
949 let mut total_idx_count = 0;
950 for draw_list in draw_data.draw_lists() {
951 total_vtx_count += draw_list.vtx_buffer().len();
952 total_idx_count += draw_list.idx_buffer().len();
953 }
954 mvlog!(
955 "[wgpu-mv] totals vtx={} idx={}",
956 total_vtx_count,
957 total_idx_count
958 );
959
960 if total_vtx_count == 0 || total_idx_count == 0 {
961 return Ok(());
962 }
963
964 let mut vertices = Vec::with_capacity(total_vtx_count);
966 let mut indices = Vec::with_capacity(total_idx_count);
967
968 for draw_list in draw_data.draw_lists() {
969 vertices.extend_from_slice(draw_list.vtx_buffer());
970 indices.extend_from_slice(draw_list.idx_buffer());
971 }
972
973 let frame_index = backend_data.frame_index % backend_data.num_frames_in_flight;
975 let frame_resources = &mut backend_data.frame_resources[frame_index as usize];
976
977 frame_resources
979 .ensure_vertex_buffer_capacity(&backend_data.device, total_vtx_count)
980 .map_err(RendererError::BufferCreationFailed)?;
981 frame_resources
982 .ensure_index_buffer_capacity(&backend_data.device, total_idx_count)
983 .map_err(RendererError::BufferCreationFailed)?;
984
985 frame_resources
986 .upload_vertex_data(&backend_data.queue, &vertices)
987 .map_err(RendererError::BufferCreationFailed)?;
988 frame_resources
989 .upload_index_data(&backend_data.queue, &indices)
990 .map_err(RendererError::BufferCreationFailed)?;
991
992 Ok(())
993 }
994
995 fn setup_render_state_static(
999 draw_data: &DrawData,
1000 render_pass: &mut RenderPass,
1001 backend_data: &WgpuBackendData,
1002 gamma: f32,
1003 ) -> RendererResult<()> {
1004 let pipeline = backend_data
1005 .pipeline_state
1006 .as_ref()
1007 .ok_or_else(|| RendererError::InvalidRenderState("Pipeline not created".to_string()))?;
1008
1009 let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
1011 let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
1012 render_pass.set_viewport(0.0, 0.0, fb_width, fb_height, 0.0, 1.0);
1013
1014 render_pass.set_pipeline(pipeline);
1016
1017 let mvp =
1019 Uniforms::create_orthographic_matrix(draw_data.display_pos, draw_data.display_size);
1020 let mut uniforms = Uniforms::new();
1021 uniforms.update(mvp, gamma);
1022
1023 if let Some(uniform_buffer) = backend_data.render_resources.uniform_buffer() {
1025 uniform_buffer.update(&backend_data.queue, &uniforms);
1026 render_pass.set_bind_group(0, uniform_buffer.bind_group(), &[]);
1027 }
1028
1029 let frame_resources = &backend_data.frame_resources
1031 [(backend_data.frame_index % backend_data.num_frames_in_flight) as usize];
1032 if let (Some(vertex_buffer), Some(index_buffer)) = (
1033 frame_resources.vertex_buffer(),
1034 frame_resources.index_buffer(),
1035 ) {
1036 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
1037 render_pass.set_index_buffer(index_buffer.slice(..), IndexFormat::Uint16);
1038 }
1039
1040 Ok(())
1041 }
1042
1043 fn render_draw_lists_static(
1045 texture_manager: &mut WgpuTextureManager,
1046 default_texture: &Option<TextureView>,
1047 draw_data: &DrawData,
1048 render_pass: &mut RenderPass,
1049 backend_data: &mut WgpuBackendData,
1050 gamma: f32,
1051 ) -> RendererResult<()> {
1052 let mut global_vtx_offset = 0i32;
1053 let mut global_idx_offset = 0u32;
1054 let clip_scale = draw_data.framebuffer_scale;
1055 let clip_off = draw_data.display_pos;
1056 let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
1057 let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
1058
1059 let mut list_i = 0usize;
1060 for draw_list in draw_data.draw_lists() {
1061 mvlog!(
1062 "[wgpu-mv] list[{}]: vtx={} idx={} cmds~?",
1063 list_i,
1064 draw_list.vtx_buffer().len(),
1065 draw_list.idx_buffer().len()
1066 );
1067 let mut cmd_i = 0usize;
1068 for cmd in draw_list.commands() {
1069 match cmd {
1070 dear_imgui_rs::render::DrawCmd::Elements {
1071 count,
1072 cmd_params,
1073 raw_cmd,
1074 } => {
1075 mvlog!(
1076 "[wgpu-mv] list[{}] cmd[{}]: count={} tex=?",
1077 list_i,
1078 cmd_i,
1079 count
1080 );
1081 let texture_bind_group = {
1089 let tex_id = unsafe {
1091 dear_imgui_rs::sys::ImDrawCmd_GetTexID(
1092 raw_cmd as *mut dear_imgui_rs::sys::ImDrawCmd,
1093 )
1094 } as u64;
1095 if tex_id == 0 {
1096 if let Some(default_tex) = default_texture {
1098 backend_data
1099 .render_resources
1100 .get_or_create_image_bind_group(
1101 &backend_data.device,
1102 0,
1103 default_tex,
1104 )?
1105 .clone()
1106 } else {
1107 return Err(RendererError::InvalidRenderState(
1108 "Default texture not available".to_string(),
1109 ));
1110 }
1111 } else if let Some(wgpu_texture) = texture_manager.get_texture(tex_id) {
1112 backend_data
1113 .render_resources
1114 .get_or_create_image_bind_group(
1115 &backend_data.device,
1116 tex_id,
1117 wgpu_texture.view(),
1118 )?
1119 .clone()
1120 } else {
1121 if let Some(default_tex) = default_texture {
1123 backend_data
1124 .render_resources
1125 .get_or_create_image_bind_group(
1126 &backend_data.device,
1127 0,
1128 default_tex,
1129 )?
1130 .clone()
1131 } else {
1132 return Err(RendererError::InvalidRenderState(
1133 "Texture not found and no default texture".to_string(),
1134 ));
1135 }
1136 }
1137 };
1138
1139 render_pass.set_bind_group(1, &texture_bind_group, &[]);
1141
1142 let clip_min_x = (cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0];
1144 let clip_min_y = (cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1];
1145 let clip_max_x = (cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0];
1146 let clip_max_y = (cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1];
1147
1148 let clip_min_x = clip_min_x.max(0.0);
1150 let clip_min_y = clip_min_y.max(0.0);
1151 let clip_max_x = clip_max_x.min(fb_width);
1152 let clip_max_y = clip_max_y.min(fb_height);
1153
1154 if clip_max_x <= clip_min_x || clip_max_y <= clip_min_y {
1155 continue;
1156 }
1157
1158 render_pass.set_scissor_rect(
1160 clip_min_x as u32,
1161 clip_min_y as u32,
1162 (clip_max_x - clip_min_x) as u32,
1163 (clip_max_y - clip_min_y) as u32,
1164 );
1165
1166 let start_index = cmd_params.idx_offset as u32 + global_idx_offset;
1168 let end_index = start_index + count as u32;
1169 let vertex_offset = (cmd_params.vtx_offset as i32) + global_vtx_offset;
1170 render_pass.draw_indexed(start_index..end_index, vertex_offset, 0..1);
1171 }
1172 dear_imgui_rs::render::DrawCmd::ResetRenderState => {
1173 Self::setup_render_state_static(
1175 draw_data,
1176 render_pass,
1177 backend_data,
1178 gamma,
1179 )?;
1180 }
1181 dear_imgui_rs::render::DrawCmd::RawCallback { .. } => {
1182 tracing::warn!(
1185 target: "dear-imgui-wgpu",
1186 "Warning: Raw callbacks are not supported in WGPU renderer"
1187 );
1188 }
1189 }
1190 cmd_i += 1;
1191 }
1192
1193 global_idx_offset += draw_list.idx_buffer().len() as u32;
1194 global_vtx_offset += draw_list.vtx_buffer().len() as i32;
1195 list_i += 1;
1196 }
1197
1198 Ok(())
1199 }
1200
1201 pub fn invalidate_device_objects(&mut self) -> RendererResult<()> {
1205 if let Some(ref mut backend_data) = self.backend_data {
1206 backend_data.pipeline_state = None;
1207 backend_data.render_resources = RenderResources::new();
1208
1209 for frame_resources in &mut backend_data.frame_resources {
1211 *frame_resources = FrameResources::new();
1212 }
1213 }
1214
1215 self.texture_manager.clear();
1217 self.default_texture = None;
1218 self.font_texture_id = None;
1219
1220 Ok(())
1221 }
1222
1223 pub fn shutdown(&mut self) {
1227 self.invalidate_device_objects().ok();
1228 self.backend_data = None;
1229 }
1230}
1231
1232impl Default for WgpuRenderer {
1233 fn default() -> Self {
1234 Self::empty()
1235 }
1236}
1237
1238#[cfg(feature = "multi-viewport")]
1240pub mod multi_viewport {
1241 use super::*;
1242 use dear_imgui_rs::platform_io::Viewport;
1243 use std::ffi::c_void;
1244 use std::sync::OnceLock;
1245 use std::sync::atomic::{AtomicUsize, Ordering};
1246 #[cfg(not(target_arch = "wasm32"))]
1247 use winit::window::Window;
1248
1249 pub struct ViewportWgpuData {
1251 pub surface: wgpu::Surface<'static>,
1252 pub config: wgpu::SurfaceConfiguration,
1253 pub pending_frame: Option<wgpu::SurfaceTexture>,
1254 }
1255
1256 static RENDERER_PTR: AtomicUsize = AtomicUsize::new(0);
1257 static GLOBAL: OnceLock<GlobalHandles> = OnceLock::new();
1258
1259 struct GlobalHandles {
1260 instance: Option<wgpu::Instance>,
1261 adapter: Option<wgpu::Adapter>,
1262 device: wgpu::Device,
1263 queue: wgpu::Queue,
1264 render_target_format: wgpu::TextureFormat,
1265 }
1266
1267 pub fn enable(renderer: &mut WgpuRenderer, imgui_context: &mut Context) {
1269 unsafe {
1271 let platform_io = imgui_context.platform_io_mut();
1272 platform_io.set_renderer_create_window(Some(
1273 renderer_create_window as unsafe extern "C" fn(*mut Viewport),
1274 ));
1275 platform_io.set_renderer_destroy_window(Some(
1276 renderer_destroy_window as unsafe extern "C" fn(*mut Viewport),
1277 ));
1278 platform_io.set_renderer_set_window_size(Some(
1279 renderer_set_window_size
1280 as unsafe extern "C" fn(*mut Viewport, dear_imgui_rs::sys::ImVec2),
1281 ));
1282 platform_io.set_platform_render_window_raw(Some(platform_render_window_sys));
1284 platform_io.set_platform_swap_buffers_raw(Some(platform_swap_buffers_sys));
1285 }
1286
1287 RENDERER_PTR.store(renderer as *mut _ as usize, Ordering::SeqCst);
1289
1290 if let Some(backend) = renderer.backend_data.as_ref() {
1292 let _ = GLOBAL.set(GlobalHandles {
1293 instance: backend.instance.clone(),
1294 adapter: backend.adapter.clone(),
1295 device: backend.device.clone(),
1296 queue: backend.queue.clone(),
1297 render_target_format: backend.render_target_format,
1298 });
1299 }
1300 }
1301
1302 unsafe fn get_renderer<'a>() -> &'a mut WgpuRenderer {
1303 let ptr = RENDERER_PTR.load(Ordering::SeqCst) as *mut WgpuRenderer;
1304 &mut *ptr
1305 }
1306
1307 unsafe fn viewport_user_data_mut<'a>(vp: *mut Viewport) -> Option<&'a mut ViewportWgpuData> {
1309 let vpm = &mut *vp;
1310 let data = vpm.renderer_user_data();
1311 if data.is_null() {
1312 None
1313 } else {
1314 Some(&mut *(data as *mut ViewportWgpuData))
1315 }
1316 }
1317
1318 pub unsafe extern "C" fn renderer_create_window(vp: *mut Viewport) {
1320 if vp.is_null() {
1321 return;
1322 }
1323 mvlog!("[wgpu-mv] Renderer_CreateWindow");
1324
1325 let global = match GLOBAL.get() {
1326 Some(g) => g,
1327 None => return,
1328 };
1329
1330 let vpm = &mut *vp;
1332 let window_ptr = vpm.platform_handle();
1333 if window_ptr.is_null() {
1334 return;
1335 }
1336
1337 #[cfg(not(target_arch = "wasm32"))]
1338 let window: &Window = &*(window_ptr as *const Window);
1339
1340 #[cfg(not(target_arch = "wasm32"))]
1341 let instance = match &global.instance {
1342 Some(i) => i.clone(),
1343 None => return, };
1345
1346 #[cfg(not(target_arch = "wasm32"))]
1347 let surface = match instance.create_surface(window) {
1348 Ok(s) => s,
1349 Err(e) => {
1350 eprintln!("[wgpu-mv] create_surface error: {:?}", e);
1351 return;
1352 }
1353 };
1354 let surface: wgpu::Surface<'static> = std::mem::transmute(surface);
1356
1357 #[cfg(not(target_arch = "wasm32"))]
1358 let size = window.inner_size();
1359 #[cfg(not(target_arch = "wasm32"))]
1360 let width = size.width.max(1);
1361 #[cfg(not(target_arch = "wasm32"))]
1362 let height = size.height.max(1);
1363
1364 #[cfg(not(target_arch = "wasm32"))]
1365 let mut config = {
1366 if let Some(adapter) = &global.adapter {
1368 let caps = surface.get_capabilities(adapter);
1369 let format = if caps.formats.contains(&global.render_target_format) {
1370 global.render_target_format
1371 } else {
1372 eprintln!(
1374 "[wgpu-mv] Surface doesn't support pipeline format {:?}; supported: {:?}. Skipping configure.",
1375 global.render_target_format, caps.formats
1376 );
1377 return;
1378 };
1379 let present_mode = if caps.present_modes.contains(&wgpu::PresentMode::Fifo) {
1380 wgpu::PresentMode::Fifo
1381 } else {
1382 caps.present_modes
1384 .get(0)
1385 .cloned()
1386 .unwrap_or(wgpu::PresentMode::Fifo)
1387 };
1388 let alpha_mode = if caps.alpha_modes.contains(&wgpu::CompositeAlphaMode::Opaque) {
1389 wgpu::CompositeAlphaMode::Opaque
1390 } else if caps.alpha_modes.contains(&wgpu::CompositeAlphaMode::Auto) {
1391 wgpu::CompositeAlphaMode::Auto
1392 } else {
1393 caps.alpha_modes
1394 .get(0)
1395 .cloned()
1396 .unwrap_or(wgpu::CompositeAlphaMode::Opaque)
1397 };
1398 wgpu::SurfaceConfiguration {
1399 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1400 format,
1401 width,
1402 height,
1403 present_mode,
1404 alpha_mode,
1405 view_formats: vec![format],
1406 desired_maximum_frame_latency: 1,
1407 }
1408 } else {
1409 wgpu::SurfaceConfiguration {
1411 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1412 format: global.render_target_format,
1413 width,
1414 height,
1415 present_mode: wgpu::PresentMode::Fifo,
1416 alpha_mode: wgpu::CompositeAlphaMode::Opaque,
1417 view_formats: vec![global.render_target_format],
1418 desired_maximum_frame_latency: 1,
1419 }
1420 }
1421 };
1422
1423 #[cfg(not(target_arch = "wasm32"))]
1424 {
1425 surface.configure(&global.device, &config);
1427 }
1428
1429 #[cfg(not(target_arch = "wasm32"))]
1430 {
1431 let data = ViewportWgpuData {
1432 surface,
1433 config,
1434 pending_frame: None,
1435 };
1436 vpm.set_renderer_user_data(Box::into_raw(Box::new(data)) as *mut c_void);
1437 }
1438 }
1439
1440 pub unsafe extern "C" fn renderer_destroy_window(vp: *mut Viewport) {
1442 if vp.is_null() {
1443 return;
1444 }
1445 mvlog!("[wgpu-mv] Renderer_DestroyWindow");
1446 if let Some(data) = viewport_user_data_mut(vp) {
1447 data.pending_frame.take();
1449 let _ = Box::from_raw(data as *mut ViewportWgpuData);
1451 let vpm = &mut *vp;
1452 vpm.set_renderer_user_data(std::ptr::null_mut());
1453 }
1454 }
1455
1456 pub unsafe extern "C" fn renderer_set_window_size(
1458 vp: *mut Viewport,
1459 size: dear_imgui_rs::sys::ImVec2,
1460 ) {
1461 if vp.is_null() {
1462 return;
1463 }
1464 mvlog!(
1465 "[wgpu-mv] Renderer_SetWindowSize to ({}, {})",
1466 size.x,
1467 size.y
1468 );
1469 let global = match GLOBAL.get() {
1470 Some(g) => g,
1471 None => return,
1472 };
1473 if let Some(data) = viewport_user_data_mut(vp) {
1474 let vpm_ref = &*vp;
1476 let scale = vpm_ref.framebuffer_scale();
1477 let new_w = (size.x * scale[0]).max(1.0) as u32;
1478 let new_h = (size.y * scale[1]).max(1.0) as u32;
1479 if data.config.width != new_w || data.config.height != new_h {
1480 data.config.width = new_w;
1481 data.config.height = new_h;
1482 data.surface.configure(&global.device, &data.config);
1483 }
1484 }
1485 }
1486
1487 pub unsafe extern "C" fn renderer_render_window(vp: *mut Viewport, _render_arg: *mut c_void) {
1489 if vp.is_null() {
1490 return;
1491 }
1492 mvlog!("[wgpu-mv] Renderer_RenderWindow");
1493 let renderer = match (get_renderer() as *mut WgpuRenderer).as_mut() {
1494 Some(r) => r,
1495 None => return,
1496 };
1497 let (device, queue) = match renderer.backend_data.as_ref() {
1499 Some(b) => (b.device.clone(), b.queue.clone()),
1500 None => return,
1501 };
1502 let vpm = &mut *vp;
1503 let raw_dd = vpm.draw_data();
1504 if raw_dd.is_null() {
1505 return;
1506 }
1507 let draw_data: &dear_imgui_rs::render::DrawData = std::mem::transmute(&*raw_dd);
1509 mvlog!(
1510 "[wgpu-mv] draw_data: valid={} lists={} fb_scale=({:.2},{:.2}) disp=({:.1},{:.1})",
1511 draw_data.valid(),
1512 draw_data.draw_lists_count(),
1513 draw_data.framebuffer_scale()[0],
1514 draw_data.framebuffer_scale()[1],
1515 draw_data.display_size()[0],
1516 draw_data.display_size()[1]
1517 );
1518
1519 mvlog!("[wgpu-mv] retrieving viewport user data");
1520 if let Some(data) = unsafe { viewport_user_data_mut(vp) } {
1521 mvlog!("[wgpu-mv] have viewport user data; acquiring surface frame");
1522 let frame = match data.surface.get_current_texture() {
1524 Ok(f) => f,
1525 Err(wgpu::SurfaceError::Outdated) | Err(wgpu::SurfaceError::Lost) => {
1526 #[cfg(not(target_arch = "wasm32"))]
1528 {
1529 let vpm_ref = &*vp;
1530 let window_ptr = vpm_ref.platform_handle();
1531 if !window_ptr.is_null() {
1532 let window: &winit::window::Window = &*(window_ptr as *const _);
1533 let size = window.inner_size();
1534 if size.width > 0 && size.height > 0 {
1535 data.config.width = size.width;
1536 data.config.height = size.height;
1537 data.surface.configure(&device, &data.config);
1538 }
1539 }
1540 }
1541 return;
1542 }
1543 Err(wgpu::SurfaceError::Timeout) => {
1544 return;
1546 }
1547 Err(e) => {
1548 eprintln!("[wgpu-mv] get_current_texture error: {:?}", e);
1549 return;
1550 }
1551 };
1552 mvlog!("[wgpu-mv] acquired frame; creating view");
1553 let view = frame
1554 .texture
1555 .create_view(&wgpu::TextureViewDescriptor::default());
1556 mvlog!("[wgpu-mv] creating command encoder");
1558 let render_block = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1559 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1560 label: Some("dear-imgui-wgpu::viewport-encoder"),
1561 });
1562 mvlog!("[wgpu-mv] begin_render_pass start");
1563 {
1564 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1565 label: Some("dear-imgui-wgpu::viewport-pass"),
1566 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1567 view: &view,
1568 resolve_target: None,
1569 ops: wgpu::Operations {
1570 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1571 store: wgpu::StoreOp::Store,
1572 },
1573 depth_slice: None,
1574 })],
1575 depth_stencil_attachment: None,
1576 occlusion_query_set: None,
1577 timestamp_writes: None,
1578 });
1579 mvlog!("[wgpu-mv] begin_render_pass ok");
1580 mvlog!("[wgpu-mv] about to render_draw_data for viewport");
1581 let saved_index_opt = renderer.backend_data.as_ref().map(|b| b.frame_index);
1583 if let Some(vd) = viewport_user_data_mut(vp) {
1584 let fb_w = vd.config.width;
1585 let fb_h = vd.config.height;
1586 if let Err(e) = renderer.render_draw_data_with_fb_size(
1587 &draw_data,
1588 &mut render_pass,
1589 fb_w,
1590 fb_h,
1591 ) {
1592 eprintln!("[wgpu-mv] render_draw_data(with_fb) error: {:?}", e);
1593 }
1594 } else if let Err(e) = renderer.render_draw_data(&draw_data, &mut render_pass) {
1595 eprintln!("[wgpu-mv] render_draw_data error: {:?}", e);
1596 }
1597 if let Some(saved_index) = saved_index_opt {
1598 if let Some(backend) = renderer.backend_data.as_mut() {
1599 if saved_index != u32::MAX {
1601 backend.frame_index = saved_index;
1602 }
1603 }
1604 }
1605 mvlog!("[wgpu-mv] finished render_draw_data");
1606 }
1607 mvlog!("[wgpu-mv] submitting queue");
1608 queue.submit(std::iter::once(encoder.finish()));
1609 mvlog!("[wgpu-mv] submit ok");
1610 }));
1611 if render_block.is_err() {
1612 eprintln!(
1613 "[wgpu-mv] panic during viewport render block; skipping present for this viewport"
1614 );
1615 return;
1616 }
1617 data.pending_frame = Some(frame);
1618 mvlog!("[wgpu-mv] submitted and stored pending frame");
1619 }
1620 }
1621
1622 pub unsafe extern "C" fn renderer_swap_buffers(vp: *mut Viewport, _render_arg: *mut c_void) {
1624 if vp.is_null() {
1625 return;
1626 }
1627 mvlog!("[wgpu-mv] Renderer_SwapBuffers");
1628 if let Some(data) = viewport_user_data_mut(vp) {
1629 if let Some(frame) = data.pending_frame.take() {
1630 frame.present();
1631 }
1632 }
1633 }
1634
1635 pub unsafe extern "C" fn platform_render_window_sys(
1637 vp: *mut dear_imgui_rs::sys::ImGuiViewport,
1638 arg: *mut c_void,
1639 ) {
1640 if vp.is_null() {
1641 return;
1642 }
1643 renderer_render_window(vp as *mut Viewport, arg);
1644 }
1645
1646 pub unsafe extern "C" fn platform_swap_buffers_sys(
1647 vp: *mut dear_imgui_rs::sys::ImGuiViewport,
1648 arg: *mut c_void,
1649 ) {
1650 if vp.is_null() {
1651 return;
1652 }
1653 renderer_swap_buffers(vp as *mut Viewport, arg);
1654 }
1655}