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