dear_imgui_wgpu/renderer/mod.rs
1//! Main WGPU renderer implementation
2//!
3//! This module contains the main WgpuRenderer struct and its implementation,
4//! following the pattern from imgui_impl_wgpu.cpp
5//!
6//! Texture Updates Flow (ImGui 1.92+)
7//! - During `Context::render()`, Dear ImGui emits a list of textures to be processed in
8//! `DrawData::textures()` (see `dear_imgui_rs::render::DrawData::textures`). Each item is an
9//! `ImTextureData*` with a `Status` field:
10//! - `WantCreate`: create a GPU texture, upload all pixels, set `TexID`, then set status `OK`.
11//! - `WantUpdates`: upload `UpdateRect` (and any queued rects) then set `OK`.
12//! - `WantDestroy`: schedule/destroy GPU texture; if unused for some frames, set `Destroyed`.
13//! - This backend honors these transitions in its texture module; users can simply pass
14//! `&mut TextureData` to UI/draw calls and let the backend handle the rest.
15
16use 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// Debug logging helper (off by default). Enable by building this crate with
25// `--features mv-log` to see multi-viewport renderer traces.
26#[allow(unused_macros)]
27macro_rules! mvlog {
28 ($($arg:tt)*) => {
29 if cfg!(feature = "mv-log") { eprintln!($($arg)*); }
30 }
31}
32/// Main WGPU renderer for Dear ImGui
33
34///
35/// This corresponds to the main renderer functionality in imgui_impl_wgpu.cpp
36pub struct WgpuRenderer {
37 /// Backend data
38 backend_data: Option<WgpuBackendData>,
39 /// Shader manager
40 shader_manager: ShaderManager,
41 /// Texture manager
42 texture_manager: WgpuTextureManager,
43 /// Default texture for fallback
44 default_texture: Option<TextureView>,
45 /// Registered font atlas texture id (if created via font-atlas fallback)
46 font_texture_id: Option<u64>,
47 /// Gamma mode: automatic (by format), force linear (1.0), or force 2.2
48 gamma_mode: GammaMode,
49 /// Clear color used for secondary viewports (multi-viewport mode)
50 viewport_clear_color: Color,
51}
52
53impl WgpuRenderer {
54 /// Create a new WGPU renderer with full initialization (recommended)
55 ///
56 /// This is the preferred way to create a WGPU renderer as it ensures proper
57 /// initialization order and is consistent with other backends.
58 ///
59 /// # Arguments
60 /// * `init_info` - WGPU initialization information (device, queue, format)
61 /// * `imgui_ctx` - Dear ImGui context to configure
62 ///
63 /// # Example
64 /// ```rust,no_run
65 /// use dear_imgui_wgpu::{WgpuRenderer, WgpuInitInfo};
66 ///
67 /// let init_info = WgpuInitInfo::new(device, queue, surface_format);
68 /// let mut renderer = WgpuRenderer::new(init_info, &mut imgui_context)?;
69 /// ```
70 pub fn new(init_info: WgpuInitInfo, imgui_ctx: &mut Context) -> RendererResult<Self> {
71 // Native and wasm experimental path: fully configure context, including font atlas.
72 #[cfg(any(
73 not(target_arch = "wasm32"),
74 all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental")
75 ))]
76 {
77 let mut renderer = Self::empty();
78 renderer.init_with_context(init_info, imgui_ctx)?;
79 Ok(renderer)
80 }
81
82 // Default wasm path: skip font atlas manipulation for safety.
83 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
84 {
85 Self::new_without_font_atlas(init_info, imgui_ctx)
86 }
87 }
88
89 /// Create an empty WGPU renderer for advanced usage
90 ///
91 /// This creates an uninitialized renderer that must be initialized later
92 /// using `init_with_context()`. Most users should use `new()` instead.
93 ///
94 /// # Example
95 /// ```rust,no_run
96 /// use dear_imgui_wgpu::{WgpuRenderer, WgpuInitInfo};
97 ///
98 /// let mut renderer = WgpuRenderer::empty();
99 /// let init_info = WgpuInitInfo::new(device, queue, surface_format);
100 /// renderer.init_with_context(init_info, &mut imgui_context)?;
101 /// ```
102 pub fn empty() -> Self {
103 Self {
104 backend_data: None,
105 shader_manager: ShaderManager::new(),
106 texture_manager: WgpuTextureManager::new(),
107 default_texture: None,
108 font_texture_id: None,
109 gamma_mode: GammaMode::Auto,
110 viewport_clear_color: Color::BLACK,
111 }
112 }
113
114 /// Initialize the renderer
115 ///
116 /// This corresponds to ImGui_ImplWGPU_Init in the C++ implementation
117 pub fn init(&mut self, init_info: WgpuInitInfo) -> RendererResult<()> {
118 // Create backend data
119 let mut backend_data = WgpuBackendData::new(init_info);
120
121 // Initialize render resources
122 backend_data
123 .render_resources
124 .initialize(&backend_data.device)?;
125
126 // Initialize shaders
127 self.shader_manager.initialize(&backend_data.device)?;
128
129 // Create default texture (1x1 white pixel)
130 let default_texture =
131 self.create_default_texture(&backend_data.device, &backend_data.queue)?;
132 self.default_texture = Some(default_texture);
133
134 // Create device objects (pipeline, etc.)
135 self.create_device_objects(&mut backend_data)?;
136
137 self.backend_data = Some(backend_data);
138 Ok(())
139 }
140
141 /// Initialize the renderer with ImGui context configuration (without font atlas for WASM)
142 ///
143 /// This is a variant of init_with_context that skips font atlas preparation,
144 /// useful for WASM builds where font atlas memory sharing is problematic.
145 pub fn new_without_font_atlas(
146 init_info: WgpuInitInfo,
147 imgui_ctx: &mut Context,
148 ) -> RendererResult<Self> {
149 let mut renderer = Self::empty();
150
151 // First initialize the renderer
152 renderer.init(init_info)?;
153
154 // Then configure the ImGui context with backend capabilities
155 renderer.configure_imgui_context(imgui_ctx);
156
157 // Skip font atlas preparation for WASM
158 // The default font will be used automatically by Dear ImGui
159
160 Ok(renderer)
161 }
162
163 /// Initialize the renderer with ImGui context configuration
164 ///
165 /// This is a convenience method that combines init() and configure_imgui_context()
166 /// to ensure proper initialization order, similar to the glow backend approach.
167 pub fn init_with_context(
168 &mut self,
169 init_info: WgpuInitInfo,
170 imgui_ctx: &mut Context,
171 ) -> RendererResult<()> {
172 // First initialize the renderer
173 self.init(init_info)?;
174
175 // Then configure the ImGui context with backend capabilities
176 // This must be done BEFORE preparing the font atlas
177 self.configure_imgui_context(imgui_ctx);
178
179 // Finally prepare the font atlas
180 self.prepare_font_atlas(imgui_ctx)?;
181
182 Ok(())
183 }
184
185 /// Set gamma mode
186 pub fn set_gamma_mode(&mut self, mode: GammaMode) {
187 self.gamma_mode = mode;
188 }
189
190 /// Set clear color for secondary viewports (multi-viewport mode).
191 ///
192 /// This color is used as the load/clear color when rendering ImGui-created
193 /// platform windows via `RenderPlatformWindowsDefault`. It is independent
194 /// from whatever clear color your main swapchain uses.
195 #[cfg(feature = "multi-viewport")]
196 pub fn set_viewport_clear_color(&mut self, color: Color) {
197 self.viewport_clear_color = color;
198 }
199
200 /// Get current clear color for secondary viewports.
201 #[cfg(feature = "multi-viewport")]
202 pub fn viewport_clear_color(&self) -> Color {
203 self.viewport_clear_color
204 }
205
206 /// Configure Dear ImGui context with WGPU backend capabilities
207 pub fn configure_imgui_context(&self, imgui_context: &mut Context) {
208 let io = imgui_context.io_mut();
209 let mut flags = io.backend_flags();
210
211 // Set WGPU renderer capabilities
212 // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
213 flags.insert(BackendFlags::RENDERER_HAS_VTX_OFFSET);
214 // We can honor ImGuiPlatformIO::Textures[] requests during render.
215 flags.insert(BackendFlags::RENDERER_HAS_TEXTURES);
216
217 #[cfg(feature = "multi-viewport")]
218 {
219 // We can render additional platform windows
220 flags.insert(BackendFlags::RENDERER_HAS_VIEWPORTS);
221 }
222
223 io.set_backend_flags(flags);
224 // Note: Dear ImGui doesn't expose set_backend_renderer_name in the Rust bindings yet
225 }
226
227 /// Prepare font atlas for rendering
228 pub fn prepare_font_atlas(&mut self, imgui_ctx: &mut Context) -> RendererResult<()> {
229 if let Some(backend_data) = &self.backend_data {
230 let device = backend_data.device.clone();
231 let queue = backend_data.queue.clone();
232 self.reload_font_texture(imgui_ctx, &device, &queue)?;
233 // Fallback: if draw_data-based texture updates are not triggered for the font atlas
234 // on this Dear ImGui version/config, upload the font atlas now and assign a TexID.
235 if self.font_texture_id.is_none() {
236 if let Some(tex_id) =
237 self.try_upload_font_atlas_legacy(imgui_ctx, &device, &queue)?
238 {
239 if cfg!(debug_assertions) {
240 tracing::debug!(
241 target: "dear-imgui-wgpu",
242 "[dear-imgui-wgpu][debug] Font atlas uploaded via fallback (legacy-only) path; user textures use modern ImTextureData. tex_id={}",
243 tex_id
244 );
245 }
246 self.font_texture_id = Some(tex_id);
247 }
248 } else if cfg!(debug_assertions) {
249 tracing::debug!(
250 target: "dear-imgui-wgpu",
251 "[dear-imgui-wgpu][debug] Font atlas tex_id already set: {:?}",
252 self.font_texture_id
253 );
254 }
255 }
256 Ok(())
257 }
258
259 // create_device_objects moved to renderer/pipeline.rs
260
261 /// Create a default 1x1 white texture
262 fn create_default_texture(
263 &self,
264 device: &Device,
265 queue: &Queue,
266 ) -> RendererResult<TextureView> {
267 let texture = device.create_texture(&TextureDescriptor {
268 label: Some("Dear ImGui Default Texture"),
269 size: Extent3d {
270 width: 1,
271 height: 1,
272 depth_or_array_layers: 1,
273 },
274 mip_level_count: 1,
275 sample_count: 1,
276 dimension: TextureDimension::D2,
277 format: TextureFormat::Rgba8Unorm,
278 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
279 view_formats: &[],
280 });
281
282 // Upload white pixel
283 queue.write_texture(
284 wgpu::TexelCopyTextureInfo {
285 texture: &texture,
286 mip_level: 0,
287 origin: wgpu::Origin3d::ZERO,
288 aspect: wgpu::TextureAspect::All,
289 },
290 &[255u8, 255u8, 255u8, 255u8], // RGBA white
291 wgpu::TexelCopyBufferLayout {
292 offset: 0,
293 bytes_per_row: Some(4),
294 rows_per_image: Some(1),
295 },
296 Extent3d {
297 width: 1,
298 height: 1,
299 depth_or_array_layers: 1,
300 },
301 );
302
303 Ok(texture.create_view(&TextureViewDescriptor::default()))
304 }
305
306 /// Load font texture from Dear ImGui context
307 ///
308 /// With the new texture management system in Dear ImGui 1.92+, font textures are
309 /// automatically managed through ImDrawData->Textures[] during rendering.
310 /// However, we need to ensure the font atlas is built and ready before the first render.
311 // reload_font_texture moved to renderer/font_atlas.rs
312
313 /// Legacy/fallback path: upload font atlas texture immediately and assign TexID.
314 /// Returns Some(tex_id) on success, None if texdata is unavailable.
315 // try_upload_font_atlas_legacy moved to renderer/font_atlas.rs
316
317 /// Get the texture manager
318 pub fn texture_manager(&self) -> &WgpuTextureManager {
319 &self.texture_manager
320 }
321
322 /// Get the texture manager mutably
323 pub fn texture_manager_mut(&mut self) -> &mut WgpuTextureManager {
324 &mut self.texture_manager
325 }
326
327 /// Check if the renderer is initialized
328 pub fn is_initialized(&self) -> bool {
329 self.backend_data.is_some()
330 }
331
332 /// Update a single texture manually
333 ///
334 /// This corresponds to ImGui_ImplWGPU_UpdateTexture in the C++ implementation.
335 /// Use this when you need precise control over texture update timing.
336 ///
337 /// # Returns
338 ///
339 /// Returns a `TextureUpdateResult` that contains any status/ID updates that need
340 /// to be applied to the texture data. This follows Rust's principle of explicit
341 /// state management.
342 ///
343 /// # Example
344 ///
345 /// ```rust,no_run
346 /// # use dear_imgui_wgpu::*;
347 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
348 /// # let mut renderer = WgpuRenderer::new();
349 /// # let mut texture_data = dear_imgui_rs::TextureData::new();
350 /// let result = renderer.update_texture(&texture_data)?;
351 /// result.apply_to(&mut texture_data);
352 /// # Ok(())
353 /// # }
354 /// ```
355 pub fn update_texture(
356 &mut self,
357 texture_data: &dear_imgui_rs::TextureData,
358 ) -> RendererResult<crate::TextureUpdateResult> {
359 if let Some(backend_data) = &self.backend_data {
360 self.texture_manager
361 .update_single_texture(texture_data, &backend_data.device, &backend_data.queue)
362 .map_err(RendererError::TextureCreationFailed)
363 } else {
364 Err(RendererError::InvalidRenderState(
365 "Renderer not initialized".to_string(),
366 ))
367 }
368 }
369
370 /// Called every frame to prepare for rendering
371 ///
372 /// This corresponds to ImGui_ImplWGPU_NewFrame in the C++ implementation
373 pub fn new_frame(&mut self) -> RendererResult<()> {
374 let needs_recreation = if let Some(backend_data) = &self.backend_data {
375 backend_data.pipeline_state.is_none()
376 } else {
377 false
378 };
379
380 if needs_recreation {
381 // Extract the backend data temporarily to avoid borrow checker issues
382 let mut backend_data = self.backend_data.take().unwrap();
383 self.create_device_objects(&mut backend_data)?;
384 self.backend_data = Some(backend_data);
385 }
386 Ok(())
387 }
388
389 /// Render Dear ImGui draw data
390 ///
391 /// This corresponds to ImGui_ImplWGPU_RenderDrawData in the C++ implementation
392 pub fn render_draw_data(
393 &mut self,
394 draw_data: &DrawData,
395 render_pass: &mut RenderPass,
396 ) -> RendererResult<()> {
397 mvlog!(
398 "[wgpu-mv] render_draw_data: valid={} lists={} fb_scale=({:.2},{:.2}) disp=({:.1},{:.1})",
399 draw_data.valid(),
400 draw_data.draw_lists_count(),
401 draw_data.framebuffer_scale()[0],
402 draw_data.framebuffer_scale()[1],
403 draw_data.display_size()[0],
404 draw_data.display_size()[1]
405 );
406 // Early out if nothing to draw (avoid binding/drawing without buffers)
407 let mut total_vtx_count = 0usize;
408 let mut total_idx_count = 0usize;
409 for dl in draw_data.draw_lists() {
410 total_vtx_count += dl.vtx_buffer().len();
411 total_idx_count += dl.idx_buffer().len();
412 }
413 if total_vtx_count == 0 || total_idx_count == 0 {
414 mvlog!("[wgpu-mv] no vertices/indices; skipping render");
415 return Ok(());
416 }
417
418 let backend_data = self.backend_data.as_mut().ok_or_else(|| {
419 RendererError::InvalidRenderState("Renderer not initialized".to_string())
420 })?;
421
422 // Avoid rendering when minimized
423 let fb_width = (draw_data.display_size[0] * draw_data.framebuffer_scale[0]) as i32;
424 let fb_height = (draw_data.display_size[1] * draw_data.framebuffer_scale[1]) as i32;
425 if fb_width <= 0 || fb_height <= 0 || !draw_data.valid() {
426 return Ok(());
427 }
428
429 mvlog!("[wgpu-mv] handle_texture_updates");
430 self.texture_manager.handle_texture_updates(
431 draw_data,
432 &backend_data.device,
433 &backend_data.queue,
434 );
435
436 // Advance to next frame
437 mvlog!("[wgpu-mv] next_frame before: {}", backend_data.frame_index);
438 backend_data.next_frame();
439 mvlog!("[wgpu-mv] next_frame after: {}", backend_data.frame_index);
440
441 // Prepare frame resources
442 mvlog!("[wgpu-mv] prepare_frame_resources");
443 Self::prepare_frame_resources_static(draw_data, backend_data)?;
444
445 // Compute gamma based on renderer mode
446 let gamma = match self.gamma_mode {
447 GammaMode::Auto => Uniforms::gamma_for_format(backend_data.render_target_format),
448 GammaMode::Linear => 1.0,
449 GammaMode::Gamma22 => 2.2,
450 };
451
452 // Setup render state
453 mvlog!("[wgpu-mv] setup_render_state");
454 Self::setup_render_state_static(draw_data, render_pass, backend_data, gamma)?;
455 // Override viewport to the provided framebuffer size to avoid partial viewport issues
456 render_pass.set_viewport(0.0, 0.0, fb_width as f32, fb_height as f32, 0.0, 1.0);
457
458 // Setup render state structure (for callbacks and custom texture bindings)
459 // Note: We need to be careful with lifetimes here, so we'll set it just before rendering
460 // and clear it immediately after
461 unsafe {
462 // Use _Nil variant as our bindings export it
463 let platform_io = dear_imgui_rs::sys::igGetPlatformIO_Nil();
464
465 // Create a temporary render state structure
466 mvlog!("[wgpu-mv] create render_state");
467 let mut render_state = crate::WgpuRenderState::new(&backend_data.device, render_pass);
468
469 // Set the render state pointer
470 (*platform_io).Renderer_RenderState =
471 &mut render_state as *mut _ as *mut std::ffi::c_void;
472
473 // Render draw lists with the render state exposed
474 let result = Self::render_draw_lists_static(
475 &mut self.texture_manager,
476 &self.default_texture,
477 draw_data,
478 render_pass,
479 backend_data,
480 gamma,
481 );
482
483 // Clear the render state pointer
484 (*platform_io).Renderer_RenderState = std::ptr::null_mut();
485
486 if let Err(e) = result {
487 eprintln!("[wgpu-mv] render_draw_lists_static error: {:?}", e);
488 return Err(e);
489 }
490 }
491
492 Ok(())
493 }
494
495 pub fn render_draw_data_with_fb_size(
496 &mut self,
497 draw_data: &DrawData,
498 render_pass: &mut RenderPass,
499 fb_width: u32,
500 fb_height: u32,
501 ) -> RendererResult<()> {
502 // Public helper used by the main window: advance frame resources as usual.
503 self.render_draw_data_with_fb_size_ex(draw_data, render_pass, fb_width, fb_height, true)
504 }
505
506 /// Internal variant that optionally skips advancing the frame index.
507 ///
508 /// When `advance_frame` is `false`, we reuse the current frame resources.
509 fn render_draw_data_with_fb_size_ex(
510 &mut self,
511 draw_data: &DrawData,
512 render_pass: &mut RenderPass,
513 fb_width: u32,
514 fb_height: u32,
515 advance_frame: bool,
516 ) -> RendererResult<()> {
517 mvlog!(
518 "[wgpu-mv] render_draw_data(with_fb) lists={} override_fb=({}, {}) disp=({:.1},{:.1})",
519 draw_data.draw_lists_count(),
520 fb_width,
521 fb_height,
522 draw_data.display_size()[0],
523 draw_data.display_size()[1]
524 );
525 let total_vtx_count: usize = draw_data.draw_lists().map(|dl| dl.vtx_buffer().len()).sum();
526 let total_idx_count: usize = draw_data.draw_lists().map(|dl| dl.idx_buffer().len()).sum();
527 if total_vtx_count == 0 || total_idx_count == 0 {
528 return Ok(());
529 }
530 let backend_data = self.backend_data.as_mut().ok_or_else(|| {
531 RendererError::InvalidRenderState("Renderer not initialized".to_string())
532 })?;
533
534 // Skip if invalid/minimized
535 if fb_width == 0 || fb_height == 0 || !draw_data.valid() {
536 return Ok(());
537 }
538
539 self.texture_manager.handle_texture_updates(
540 draw_data,
541 &backend_data.device,
542 &backend_data.queue,
543 );
544
545 if advance_frame {
546 backend_data.next_frame();
547 }
548 Self::prepare_frame_resources_static(draw_data, backend_data)?;
549
550 let gamma = match self.gamma_mode {
551 GammaMode::Auto => Uniforms::gamma_for_format(backend_data.render_target_format),
552 GammaMode::Linear => 1.0,
553 GammaMode::Gamma22 => 2.2,
554 };
555
556 Self::setup_render_state_static(draw_data, render_pass, backend_data, gamma)?;
557
558 unsafe {
559 let platform_io = dear_imgui_rs::sys::igGetPlatformIO_Nil();
560 let mut render_state = crate::WgpuRenderState::new(&backend_data.device, render_pass);
561 (*platform_io).Renderer_RenderState =
562 &mut render_state as *mut _ as *mut std::ffi::c_void;
563
564 // Reuse core routine but clamp scissor by overriding framebuffer bounds.
565 let mut global_idx_offset: u32 = 0;
566 let mut global_vtx_offset: i32 = 0;
567 let clip_off = draw_data.display_pos();
568 let clip_scale = draw_data.framebuffer_scale();
569 let fbw = fb_width as f32;
570 let fbh = fb_height as f32;
571
572 for draw_list in draw_data.draw_lists() {
573 let vtx_buffer = draw_list.vtx_buffer();
574 let idx_buffer = draw_list.idx_buffer();
575 let mut cmd_i = 0;
576 for cmd in draw_list.commands() {
577 match cmd {
578 dear_imgui_rs::render::DrawCmd::Elements {
579 count,
580 cmd_params,
581 raw_cmd,
582 } => {
583 // Texture bind group resolution mirrors render_draw_lists_static
584 let texture_bind_group = {
585 // Resolve effective ImTextureID using raw_cmd (modern texture path)
586 let tex_id = unsafe {
587 dear_imgui_rs::sys::ImDrawCmd_GetTexID(
588 raw_cmd as *mut dear_imgui_rs::sys::ImDrawCmd,
589 )
590 } as u64;
591 if tex_id == 0 {
592 if let Some(default_tex) = &self.default_texture {
593 backend_data
594 .render_resources
595 .get_or_create_image_bind_group(
596 &backend_data.device,
597 0,
598 default_tex,
599 )?
600 .clone()
601 } else {
602 return Err(RendererError::InvalidRenderState(
603 "Default texture not available".to_string(),
604 ));
605 }
606 } else if let Some(wgpu_texture) =
607 self.texture_manager.get_texture(tex_id)
608 {
609 backend_data
610 .render_resources
611 .get_or_create_image_bind_group(
612 &backend_data.device,
613 tex_id,
614 &wgpu_texture.texture_view,
615 )?
616 .clone()
617 } else if let Some(default_tex) = &self.default_texture {
618 backend_data
619 .render_resources
620 .get_or_create_image_bind_group(
621 &backend_data.device,
622 0,
623 default_tex,
624 )?
625 .clone()
626 } else {
627 return Err(RendererError::InvalidRenderState(
628 "Texture not found and no default texture".to_string(),
629 ));
630 }
631 };
632 render_pass.set_bind_group(1, &texture_bind_group, &[]);
633
634 // Compute clip rect in framebuffer space
635 let mut clip_min_x =
636 (cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0];
637 let mut clip_min_y =
638 (cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1];
639 let mut clip_max_x =
640 (cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0];
641 let mut clip_max_y =
642 (cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1];
643 // Clamp to override framebuffer bounds
644 clip_min_x = clip_min_x.max(0.0);
645 clip_min_y = clip_min_y.max(0.0);
646 clip_max_x = clip_max_x.min(fbw);
647 clip_max_y = clip_max_y.min(fbh);
648 if clip_max_x <= clip_min_x || clip_max_y <= clip_min_y {
649 cmd_i += 1;
650 continue;
651 }
652 render_pass.set_scissor_rect(
653 clip_min_x as u32,
654 clip_min_y as u32,
655 (clip_max_x - clip_min_x) as u32,
656 (clip_max_y - clip_min_y) as u32,
657 );
658 let start_index = cmd_params.idx_offset as u32 + global_idx_offset;
659 let end_index = start_index + count as u32;
660 let vertex_offset = (cmd_params.vtx_offset as i32) + global_vtx_offset;
661 render_pass.draw_indexed(start_index..end_index, vertex_offset, 0..1);
662 }
663 dear_imgui_rs::render::DrawCmd::ResetRenderState => {
664 Self::setup_render_state_static(
665 draw_data,
666 render_pass,
667 backend_data,
668 gamma,
669 )?;
670 }
671 dear_imgui_rs::render::DrawCmd::RawCallback { .. } => {
672 // Unsupported raw callbacks; skip.
673 }
674 }
675 cmd_i += 1;
676 }
677
678 global_idx_offset += idx_buffer.len() as u32;
679 global_vtx_offset += vtx_buffer.len() as i32;
680 }
681
682 (*platform_io).Renderer_RenderState = std::ptr::null_mut();
683 }
684
685 Ok(())
686 }
687
688 /// Prepare frame resources (buffers)
689 // prepare_frame_resources_static moved to renderer/draw.rs
690
691 /// Setup render state
692 ///
693 /// This corresponds to ImGui_ImplWGPU_SetupRenderState in the C++ implementation
694 // setup_render_state_static moved to renderer/draw.rs
695
696 /// Render all draw lists
697 // render_draw_lists_static moved to renderer/draw.rs
698
699 /// Invalidate device objects
700 ///
701 /// This corresponds to ImGui_ImplWGPU_InvalidateDeviceObjects in the C++ implementation
702 pub fn invalidate_device_objects(&mut self) -> RendererResult<()> {
703 if let Some(ref mut backend_data) = self.backend_data {
704 backend_data.pipeline_state = None;
705 backend_data.render_resources = RenderResources::new();
706
707 // Clear frame resources
708 for frame_resources in &mut backend_data.frame_resources {
709 *frame_resources = FrameResources::new();
710 }
711 }
712
713 // Clear texture manager
714 self.texture_manager.clear();
715 self.default_texture = None;
716 self.font_texture_id = None;
717
718 Ok(())
719 }
720
721 /// Shutdown the renderer
722 ///
723 /// This corresponds to ImGui_ImplWGPU_Shutdown in the C++ implementation
724 pub fn shutdown(&mut self) {
725 self.invalidate_device_objects().ok();
726 self.backend_data = None;
727 }
728}
729
730// Submodules for renderer features
731mod draw;
732mod external_textures;
733mod font_atlas;
734#[cfg(feature = "multi-viewport")]
735pub mod multi_viewport;
736mod pipeline;
737
738impl Default for WgpuRenderer {
739 fn default() -> Self {
740 Self::empty()
741 }
742}