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}