dear_imgui_wgpu/
renderer.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/// Main WGPU renderer for Dear ImGui
25///
26/// This corresponds to the main renderer functionality in imgui_impl_wgpu.cpp
27pub struct WgpuRenderer {
28    /// Backend data
29    backend_data: Option<WgpuBackendData>,
30    /// Shader manager
31    shader_manager: ShaderManager,
32    /// Texture manager
33    texture_manager: WgpuTextureManager,
34    /// Default texture for fallback
35    default_texture: Option<TextureView>,
36    /// Registered font atlas texture id (if created via font-atlas fallback)
37    font_texture_id: Option<u64>,
38    /// Gamma mode: automatic (by format), force linear (1.0), or force 2.2
39    gamma_mode: GammaMode,
40}
41
42impl WgpuRenderer {
43    /// Create a new WGPU renderer with full initialization (recommended)
44    ///
45    /// This is the preferred way to create a WGPU renderer as it ensures proper
46    /// initialization order and is consistent with other backends.
47    ///
48    /// # Arguments
49    /// * `init_info` - WGPU initialization information (device, queue, format)
50    /// * `imgui_ctx` - Dear ImGui context to configure
51    ///
52    /// # Example
53    /// ```rust,no_run
54    /// use dear_imgui_wgpu::{WgpuRenderer, WgpuInitInfo};
55    ///
56    /// let init_info = WgpuInitInfo::new(device, queue, surface_format);
57    /// let mut renderer = WgpuRenderer::new(init_info, &mut imgui_context)?;
58    /// ```
59    pub fn new(init_info: WgpuInitInfo, imgui_ctx: &mut Context) -> RendererResult<Self> {
60        let mut renderer = Self::empty();
61        renderer.init_with_context(init_info, imgui_ctx)?;
62        Ok(renderer)
63    }
64
65    /// Create an empty WGPU renderer for advanced usage
66    ///
67    /// This creates an uninitialized renderer that must be initialized later
68    /// using `init_with_context()`. Most users should use `new()` instead.
69    ///
70    /// # Example
71    /// ```rust,no_run
72    /// use dear_imgui_wgpu::{WgpuRenderer, WgpuInitInfo};
73    ///
74    /// let mut renderer = WgpuRenderer::empty();
75    /// let init_info = WgpuInitInfo::new(device, queue, surface_format);
76    /// renderer.init_with_context(init_info, &mut imgui_context)?;
77    /// ```
78    pub fn empty() -> Self {
79        Self {
80            backend_data: None,
81            shader_manager: ShaderManager::new(),
82            texture_manager: WgpuTextureManager::new(),
83            default_texture: None,
84            font_texture_id: None,
85            gamma_mode: GammaMode::Auto,
86        }
87    }
88
89    /// Initialize the renderer
90    ///
91    /// This corresponds to ImGui_ImplWGPU_Init in the C++ implementation
92    pub fn init(&mut self, init_info: WgpuInitInfo) -> RendererResult<()> {
93        // Create backend data
94        let mut backend_data = WgpuBackendData::new(init_info);
95
96        // Initialize render resources
97        backend_data
98            .render_resources
99            .initialize(&backend_data.device)?;
100
101        // Initialize shaders
102        self.shader_manager.initialize(&backend_data.device)?;
103
104        // Create default texture (1x1 white pixel)
105        let default_texture =
106            self.create_default_texture(&backend_data.device, &backend_data.queue)?;
107        self.default_texture = Some(default_texture);
108
109        // Create device objects (pipeline, etc.)
110        self.create_device_objects(&mut backend_data)?;
111
112        self.backend_data = Some(backend_data);
113        Ok(())
114    }
115
116    /// Initialize the renderer with ImGui context configuration (without font atlas for WASM)
117    ///
118    /// This is a variant of init_with_context that skips font atlas preparation,
119    /// useful for WASM builds where font atlas memory sharing is problematic.
120    pub fn new_without_font_atlas(
121        init_info: WgpuInitInfo,
122        imgui_ctx: &mut Context,
123    ) -> RendererResult<Self> {
124        let mut renderer = Self::empty();
125
126        // First initialize the renderer
127        renderer.init(init_info)?;
128
129        // Then configure the ImGui context with backend capabilities
130        renderer.configure_imgui_context(imgui_ctx);
131
132        // Skip font atlas preparation for WASM
133        // The default font will be used automatically by Dear ImGui
134
135        Ok(renderer)
136    }
137
138    /// Initialize the renderer with ImGui context configuration
139    ///
140    /// This is a convenience method that combines init() and configure_imgui_context()
141    /// to ensure proper initialization order, similar to the glow backend approach.
142    pub fn init_with_context(
143        &mut self,
144        init_info: WgpuInitInfo,
145        imgui_ctx: &mut Context,
146    ) -> RendererResult<()> {
147        // First initialize the renderer
148        self.init(init_info)?;
149
150        // Then configure the ImGui context with backend capabilities
151        // This must be done BEFORE preparing the font atlas
152        self.configure_imgui_context(imgui_ctx);
153
154        // Finally prepare the font atlas
155        self.prepare_font_atlas(imgui_ctx)?;
156
157        Ok(())
158    }
159
160    /// Set gamma mode
161    pub fn set_gamma_mode(&mut self, mode: GammaMode) {
162        self.gamma_mode = mode;
163    }
164
165    /// Configure Dear ImGui context with WGPU backend capabilities
166    pub fn configure_imgui_context(&self, imgui_context: &mut Context) {
167        let io = imgui_context.io_mut();
168        let mut flags = io.backend_flags();
169
170        // Set WGPU renderer capabilities
171        // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
172        flags.insert(BackendFlags::RENDERER_HAS_VTX_OFFSET);
173        // We can honor ImGuiPlatformIO::Textures[] requests during render.
174        flags.insert(BackendFlags::RENDERER_HAS_TEXTURES);
175
176        io.set_backend_flags(flags);
177        // Note: Dear ImGui doesn't expose set_backend_renderer_name in the Rust bindings yet
178    }
179
180    /// Prepare font atlas for rendering
181    pub fn prepare_font_atlas(&mut self, imgui_ctx: &mut Context) -> RendererResult<()> {
182        if let Some(backend_data) = &self.backend_data {
183            let device = backend_data.device.clone();
184            let queue = backend_data.queue.clone();
185            self.reload_font_texture(imgui_ctx, &device, &queue)?;
186            // Fallback: if draw_data-based texture updates are not triggered for the font atlas
187            // on this Dear ImGui version/config, upload the font atlas now and assign a TexID.
188            if self.font_texture_id.is_none() {
189                if let Some(tex_id) =
190                    self.try_upload_font_atlas_legacy(imgui_ctx, &device, &queue)?
191                {
192                    if cfg!(debug_assertions) {
193                        tracing::debug!(
194                            target: "dear-imgui-wgpu",
195                            "[dear-imgui-wgpu][debug] Font atlas uploaded via fallback (legacy-only) path; user textures use modern ImTextureData. tex_id={}",
196                            tex_id
197                        );
198                    }
199                    self.font_texture_id = Some(tex_id);
200                }
201            } else if cfg!(debug_assertions) {
202                tracing::debug!(
203                    target: "dear-imgui-wgpu",
204                    "[dear-imgui-wgpu][debug] Font atlas tex_id already set: {:?}",
205                    self.font_texture_id
206                );
207            }
208        }
209        Ok(())
210    }
211
212    /// Create device objects (pipeline, etc.)
213    ///
214    /// This corresponds to ImGui_ImplWGPU_CreateDeviceObjects in the C++ implementation
215    fn create_device_objects(&mut self, backend_data: &mut WgpuBackendData) -> RendererResult<()> {
216        let device = &backend_data.device;
217
218        // Create bind group layouts
219        let (common_layout, image_layout) = crate::shaders::create_bind_group_layouts(device);
220
221        // Create pipeline layout
222        let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
223            label: Some("Dear ImGui Pipeline Layout"),
224            bind_group_layouts: &[&common_layout, &image_layout],
225            push_constant_ranges: &[],
226        });
227
228        // Get shader module
229        let shader_module = self.shader_manager.get_shader_module()?;
230
231        // Create vertex buffer layout
232        let vertex_buffer_layout = crate::shaders::create_vertex_buffer_layout();
233        let vertex_buffer_layouts = [vertex_buffer_layout];
234
235        // Create vertex state
236        let vertex_state =
237            crate::shaders::create_vertex_state(shader_module, &vertex_buffer_layouts);
238
239        // Create color target state
240        let color_target = ColorTargetState {
241            format: backend_data.render_target_format,
242            blend: Some(BlendState {
243                color: BlendComponent {
244                    src_factor: BlendFactor::SrcAlpha,
245                    dst_factor: BlendFactor::OneMinusSrcAlpha,
246                    operation: BlendOperation::Add,
247                },
248                alpha: BlendComponent {
249                    src_factor: BlendFactor::One,
250                    dst_factor: BlendFactor::OneMinusSrcAlpha,
251                    operation: BlendOperation::Add,
252                },
253            }),
254            write_mask: ColorWrites::ALL,
255        };
256
257        // Determine if we need gamma correction based on format
258        let use_gamma_correction = matches!(
259            backend_data.render_target_format,
260            TextureFormat::Rgba8UnormSrgb
261                | TextureFormat::Bgra8UnormSrgb
262                | TextureFormat::Bc1RgbaUnormSrgb
263                | TextureFormat::Bc2RgbaUnormSrgb
264                | TextureFormat::Bc3RgbaUnormSrgb
265                | TextureFormat::Bc7RgbaUnormSrgb
266        );
267
268        // Create fragment state
269        let color_targets = [Some(color_target)];
270        let fragment_state = crate::shaders::create_fragment_state(
271            shader_module,
272            &color_targets,
273            use_gamma_correction,
274        );
275
276        // Create depth stencil state if needed (matches imgui_impl_wgpu.cpp depth-stencil setup)
277        let depth_stencil = backend_data
278            .depth_stencil_format
279            .map(|format| DepthStencilState {
280                format,
281                depth_write_enabled: false, // matches WGPUOptionalBool_False in C++
282                depth_compare: CompareFunction::Always, // matches WGPUCompareFunction_Always
283                stencil: StencilState {
284                    front: StencilFaceState {
285                        compare: CompareFunction::Always, // matches WGPUCompareFunction_Always
286                        fail_op: StencilOperation::Keep,  // matches WGPUStencilOperation_Keep
287                        depth_fail_op: StencilOperation::Keep, // matches WGPUStencilOperation_Keep
288                        pass_op: StencilOperation::Keep,  // matches WGPUStencilOperation_Keep
289                    },
290                    back: StencilFaceState {
291                        compare: CompareFunction::Always, // matches WGPUCompareFunction_Always
292                        fail_op: StencilOperation::Keep,  // matches WGPUStencilOperation_Keep
293                        depth_fail_op: StencilOperation::Keep, // matches WGPUStencilOperation_Keep
294                        pass_op: StencilOperation::Keep,  // matches WGPUStencilOperation_Keep
295                    },
296                    read_mask: 0xff,  // default value
297                    write_mask: 0xff, // default value
298                },
299                bias: DepthBiasState::default(),
300            });
301
302        // Create render pipeline
303        let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
304            label: Some("Dear ImGui Render Pipeline"),
305            layout: Some(&pipeline_layout),
306            vertex: vertex_state,
307            primitive: PrimitiveState {
308                topology: PrimitiveTopology::TriangleList,
309                strip_index_format: None,
310                front_face: FrontFace::Cw,
311                cull_mode: None,
312                polygon_mode: PolygonMode::Fill,
313                unclipped_depth: false,
314                conservative: false,
315            },
316            depth_stencil,
317            multisample: backend_data.init_info.pipeline_multisample_state,
318            fragment: Some(fragment_state),
319            multiview: None,
320            cache: None,
321        });
322
323        backend_data.pipeline_state = Some(pipeline);
324        Ok(())
325    }
326
327    /// Create a default 1x1 white texture
328    fn create_default_texture(
329        &self,
330        device: &Device,
331        queue: &Queue,
332    ) -> RendererResult<TextureView> {
333        let texture = device.create_texture(&TextureDescriptor {
334            label: Some("Dear ImGui Default Texture"),
335            size: Extent3d {
336                width: 1,
337                height: 1,
338                depth_or_array_layers: 1,
339            },
340            mip_level_count: 1,
341            sample_count: 1,
342            dimension: TextureDimension::D2,
343            format: TextureFormat::Rgba8Unorm,
344            usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
345            view_formats: &[],
346        });
347
348        // Upload white pixel
349        queue.write_texture(
350            wgpu::TexelCopyTextureInfo {
351                texture: &texture,
352                mip_level: 0,
353                origin: wgpu::Origin3d::ZERO,
354                aspect: wgpu::TextureAspect::All,
355            },
356            &[255u8, 255u8, 255u8, 255u8], // RGBA white
357            wgpu::TexelCopyBufferLayout {
358                offset: 0,
359                bytes_per_row: Some(4),
360                rows_per_image: Some(1),
361            },
362            Extent3d {
363                width: 1,
364                height: 1,
365                depth_or_array_layers: 1,
366            },
367        );
368
369        Ok(texture.create_view(&TextureViewDescriptor::default()))
370    }
371
372    /// Load font texture from Dear ImGui context
373    ///
374    /// With the new texture management system in Dear ImGui 1.92+, font textures are
375    /// automatically managed through ImDrawData->Textures[] during rendering.
376    /// However, we need to ensure the font atlas is built and ready before the first render.
377    fn reload_font_texture(
378        &mut self,
379        imgui_ctx: &mut Context,
380        _device: &Device,
381        _queue: &Queue,
382    ) -> RendererResult<()> {
383        let mut fonts = imgui_ctx.font_atlas_mut();
384        // Build the font atlas if not already built
385        // This prepares the font data but doesn't create GPU textures yet
386        if !fonts.is_built() {
387            fonts.build();
388        }
389
390        // Do not manually set TexRef/TexID here. With BackendFlags::RENDERER_HAS_TEXTURES,
391        // Dear ImGui will emit texture requests (WantCreate/WantUpdates) via DrawData::textures(),
392        // and our texture manager will create/upload the font texture on demand during rendering.
393
394        Ok(())
395    }
396
397    /// Legacy/fallback path: upload font atlas texture immediately and assign TexID.
398    /// Returns Some(tex_id) on success, None if texdata is unavailable.
399    fn try_upload_font_atlas_legacy(
400        &mut self,
401        imgui_ctx: &mut Context,
402        device: &Device,
403        queue: &Queue,
404    ) -> RendererResult<Option<u64>> {
405        // SAFETY: Access raw TexData/bytes only to copy pixels. Requires fonts.build() called.
406        let fonts = imgui_ctx.font_atlas();
407        // Try to read raw texture data to determine bytes-per-pixel
408        let raw_tex = fonts.get_tex_data();
409        if raw_tex.is_null() {
410            if cfg!(debug_assertions) {
411                tracing::debug!(
412                    target: "dear-imgui-wgpu",
413                    "[dear-imgui-wgpu][debug] Font atlas TexData is null; skip legacy upload"
414                );
415            }
416            return Ok(None);
417        }
418        // Read metadata
419        let (width, height, bpp, pixels_slice): (u32, u32, i32, Option<&[u8]>) = unsafe {
420            let w = (*raw_tex).Width as u32;
421            let h = (*raw_tex).Height as u32;
422            let bpp = (*raw_tex).BytesPerPixel;
423            let px_ptr = (*raw_tex).Pixels as *const u8;
424            if px_ptr.is_null() || w == 0 || h == 0 {
425                (w, h, bpp, None)
426            } else {
427                let size = (w as usize) * (h as usize) * (bpp as usize).max(1);
428                (w, h, bpp, Some(std::slice::from_raw_parts(px_ptr, size)))
429            }
430        };
431
432        if let Some(src) = pixels_slice {
433            if cfg!(debug_assertions) {
434                tracing::debug!(
435                    target: "dear-imgui-wgpu",
436                    "[dear-imgui-wgpu][debug] Font atlas texdata: {}x{} bpp={} (fallback upload for font atlas)",
437                    width, height, bpp
438                );
439            }
440            // Convert to RGBA8 if needed
441            let (format, converted): (wgpu::TextureFormat, Vec<u8>) = if bpp == 4 {
442                (wgpu::TextureFormat::Rgba8Unorm, src.to_vec())
443            } else if bpp == 1 {
444                // Alpha8 -> RGBA8 (white RGB + alpha)
445                let mut out = Vec::with_capacity((width as usize) * (height as usize) * 4);
446                for &a in src.iter() {
447                    out.extend_from_slice(&[255, 255, 255, a]);
448                }
449                (wgpu::TextureFormat::Rgba8Unorm, out)
450            } else {
451                // Unexpected format; don't proceed
452                if cfg!(debug_assertions) {
453                    tracing::debug!(
454                        target: "dear-imgui-wgpu",
455                        "[dear-imgui-wgpu][debug] Unexpected font atlas bpp={} -> skip",
456                        bpp
457                    );
458                }
459                return Ok(None);
460            };
461
462            // Create WGPU texture
463            let texture = device.create_texture(&wgpu::TextureDescriptor {
464                label: Some("Dear ImGui Font Atlas"),
465                size: wgpu::Extent3d {
466                    width,
467                    height,
468                    depth_or_array_layers: 1,
469                },
470                mip_level_count: 1,
471                sample_count: 1,
472                dimension: wgpu::TextureDimension::D2,
473                format,
474                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
475                view_formats: &[],
476            });
477            let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
478            // Write with 256-byte aligned row pitch
479            let bpp = 4u32;
480            let unpadded = width * bpp;
481            let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
482            let padded = unpadded.div_ceil(align) * align;
483            if padded == unpadded {
484                queue.write_texture(
485                    wgpu::TexelCopyTextureInfo {
486                        texture: &texture,
487                        mip_level: 0,
488                        origin: wgpu::Origin3d::ZERO,
489                        aspect: wgpu::TextureAspect::All,
490                    },
491                    &converted,
492                    wgpu::TexelCopyBufferLayout {
493                        offset: 0,
494                        bytes_per_row: Some(unpadded),
495                        rows_per_image: Some(height),
496                    },
497                    wgpu::Extent3d {
498                        width,
499                        height,
500                        depth_or_array_layers: 1,
501                    },
502                );
503            } else {
504                let mut padded_buf = vec![0u8; (padded * height) as usize];
505                for row in 0..height as usize {
506                    let src = row * (unpadded as usize);
507                    let dst = row * (padded as usize);
508                    padded_buf[dst..dst + (unpadded as usize)]
509                        .copy_from_slice(&converted[src..src + (unpadded as usize)]);
510                }
511                queue.write_texture(
512                    wgpu::TexelCopyTextureInfo {
513                        texture: &texture,
514                        mip_level: 0,
515                        origin: wgpu::Origin3d::ZERO,
516                        aspect: wgpu::TextureAspect::All,
517                    },
518                    &padded_buf,
519                    wgpu::TexelCopyBufferLayout {
520                        offset: 0,
521                        bytes_per_row: Some(padded),
522                        rows_per_image: Some(height),
523                    },
524                    wgpu::Extent3d {
525                        width,
526                        height,
527                        depth_or_array_layers: 1,
528                    },
529                );
530                if cfg!(debug_assertions) {
531                    tracing::debug!(
532                        target: "dear-imgui-wgpu",
533                        "[dear-imgui-wgpu][debug] Upload font atlas with padded row pitch: unpadded={} padded={}",
534                        unpadded, padded
535                    );
536                }
537            }
538
539            // Register texture and set IDs so draw commands can bind it
540            let tex_id = self
541                .texture_manager
542                .register_texture(crate::WgpuTexture::new(texture, view));
543
544            // Set atlas texture id + status OK (updates TexRef and TexData)
545            {
546                let mut fonts_mut = imgui_ctx.font_atlas_mut();
547                fonts_mut.set_texture_id(dear_imgui_rs::TextureId::from(tex_id));
548            }
549            if cfg!(debug_assertions) {
550                tracing::debug!(
551                    target: "dear-imgui-wgpu",
552                    "[dear-imgui-wgpu][debug] Font atlas fallback upload complete: tex_id={}",
553                    tex_id
554                );
555            }
556
557            return Ok(Some(tex_id));
558        }
559        if cfg!(debug_assertions) {
560            tracing::debug!(
561                target: "dear-imgui-wgpu",
562                "[dear-imgui-wgpu][debug] Font atlas has no CPU pixel buffer; skipping fallback upload (renderer will use modern texture updates)"
563            );
564        }
565        Ok(None)
566    }
567
568    /// Get the texture manager
569    pub fn texture_manager(&self) -> &WgpuTextureManager {
570        &self.texture_manager
571    }
572
573    /// Get the texture manager mutably
574    pub fn texture_manager_mut(&mut self) -> &mut WgpuTextureManager {
575        &mut self.texture_manager
576    }
577
578    /// Check if the renderer is initialized
579    pub fn is_initialized(&self) -> bool {
580        self.backend_data.is_some()
581    }
582
583    /// Update a single texture manually
584    ///
585    /// This corresponds to ImGui_ImplWGPU_UpdateTexture in the C++ implementation.
586    /// Use this when you need precise control over texture update timing.
587    ///
588    /// # Returns
589    ///
590    /// Returns a `TextureUpdateResult` that contains any status/ID updates that need
591    /// to be applied to the texture data. This follows Rust's principle of explicit
592    /// state management.
593    ///
594    /// # Example
595    ///
596    /// ```rust,no_run
597    /// # use dear_imgui_wgpu::*;
598    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
599    /// # let mut renderer = WgpuRenderer::new();
600    /// # let mut texture_data = dear_imgui_rs::TextureData::new();
601    /// let result = renderer.update_texture(&texture_data)?;
602    /// result.apply_to(&mut texture_data);
603    /// # Ok(())
604    /// # }
605    /// ```
606    pub fn update_texture(
607        &mut self,
608        texture_data: &dear_imgui_rs::TextureData,
609    ) -> RendererResult<crate::TextureUpdateResult> {
610        if let Some(backend_data) = &self.backend_data {
611            self.texture_manager
612                .update_single_texture(texture_data, &backend_data.device, &backend_data.queue)
613                .map_err(RendererError::TextureCreationFailed)
614        } else {
615            Err(RendererError::InvalidRenderState(
616                "Renderer not initialized".to_string(),
617            ))
618        }
619    }
620
621    /// Called every frame to prepare for rendering
622    ///
623    /// This corresponds to ImGui_ImplWGPU_NewFrame in the C++ implementation
624    pub fn new_frame(&mut self) -> RendererResult<()> {
625        let needs_recreation = if let Some(backend_data) = &self.backend_data {
626            backend_data.pipeline_state.is_none()
627        } else {
628            false
629        };
630
631        if needs_recreation {
632            // Extract the backend data temporarily to avoid borrow checker issues
633            let mut backend_data = self.backend_data.take().unwrap();
634            self.create_device_objects(&mut backend_data)?;
635            self.backend_data = Some(backend_data);
636        }
637        Ok(())
638    }
639
640    /// Render Dear ImGui draw data
641    ///
642    /// This corresponds to ImGui_ImplWGPU_RenderDrawData in the C++ implementation
643    pub fn render_draw_data(
644        &mut self,
645        draw_data: &DrawData,
646        render_pass: &mut RenderPass,
647    ) -> RendererResult<()> {
648        let backend_data = self.backend_data.as_mut().ok_or_else(|| {
649            RendererError::InvalidRenderState("Renderer not initialized".to_string())
650        })?;
651
652        // Avoid rendering when minimized
653        let fb_width = (draw_data.display_size[0] * draw_data.framebuffer_scale[0]) as i32;
654        let fb_height = (draw_data.display_size[1] * draw_data.framebuffer_scale[1]) as i32;
655        if fb_width <= 0 || fb_height <= 0 || !draw_data.valid() {
656            return Ok(());
657        }
658
659        self.texture_manager.handle_texture_updates(
660            draw_data,
661            &backend_data.device,
662            &backend_data.queue,
663        );
664
665        // Advance to next frame
666        backend_data.next_frame();
667
668        // Prepare frame resources
669        Self::prepare_frame_resources_static(draw_data, backend_data)?;
670
671        // Compute gamma based on renderer mode
672        let gamma = match self.gamma_mode {
673            GammaMode::Auto => Uniforms::gamma_for_format(backend_data.render_target_format),
674            GammaMode::Linear => 1.0,
675            GammaMode::Gamma22 => 2.2,
676        };
677
678        // Setup render state
679        Self::setup_render_state_static(draw_data, render_pass, backend_data, gamma)?;
680
681        // Setup render state structure (for callbacks and custom texture bindings)
682        // Note: We need to be careful with lifetimes here, so we'll set it just before rendering
683        // and clear it immediately after
684        unsafe {
685            // Use _Nil variant as our bindings export it
686            let platform_io = dear_imgui_rs::sys::igGetPlatformIO_Nil();
687
688            // Create a temporary render state structure
689            let mut render_state = crate::WgpuRenderState::new(&backend_data.device, render_pass);
690
691            // Set the render state pointer
692            (*platform_io).Renderer_RenderState =
693                &mut render_state as *mut _ as *mut std::ffi::c_void;
694
695            // Render draw lists with the render state exposed
696            let result = Self::render_draw_lists_static(
697                &mut self.texture_manager,
698                &self.default_texture,
699                draw_data,
700                render_pass,
701                backend_data,
702                gamma,
703            );
704
705            // Clear the render state pointer
706            (*platform_io).Renderer_RenderState = std::ptr::null_mut();
707
708            result?;
709        }
710
711        Ok(())
712    }
713
714    /// Prepare frame resources (buffers)
715    fn prepare_frame_resources_static(
716        draw_data: &DrawData,
717        backend_data: &mut WgpuBackendData,
718    ) -> RendererResult<()> {
719        // Calculate total vertex and index counts
720        let mut total_vtx_count = 0;
721        let mut total_idx_count = 0;
722        for draw_list in draw_data.draw_lists() {
723            total_vtx_count += draw_list.vtx_buffer().len();
724            total_idx_count += draw_list.idx_buffer().len();
725        }
726
727        if total_vtx_count == 0 || total_idx_count == 0 {
728            return Ok(());
729        }
730
731        // Collect all vertices and indices first
732        let mut vertices = Vec::with_capacity(total_vtx_count);
733        let mut indices = Vec::with_capacity(total_idx_count);
734
735        for draw_list in draw_data.draw_lists() {
736            vertices.extend_from_slice(draw_list.vtx_buffer());
737            indices.extend_from_slice(draw_list.idx_buffer());
738        }
739
740        // Get current frame resources and update buffers
741        let frame_index = backend_data.frame_index % backend_data.num_frames_in_flight;
742        let frame_resources = &mut backend_data.frame_resources[frame_index as usize];
743
744        // Ensure buffer capacity and upload data
745        frame_resources
746            .ensure_vertex_buffer_capacity(&backend_data.device, total_vtx_count)
747            .map_err(RendererError::BufferCreationFailed)?;
748        frame_resources
749            .ensure_index_buffer_capacity(&backend_data.device, total_idx_count)
750            .map_err(RendererError::BufferCreationFailed)?;
751
752        frame_resources
753            .upload_vertex_data(&backend_data.queue, &vertices)
754            .map_err(RendererError::BufferCreationFailed)?;
755        frame_resources
756            .upload_index_data(&backend_data.queue, &indices)
757            .map_err(RendererError::BufferCreationFailed)?;
758
759        Ok(())
760    }
761
762    /// Setup render state
763    ///
764    /// This corresponds to ImGui_ImplWGPU_SetupRenderState in the C++ implementation
765    fn setup_render_state_static(
766        draw_data: &DrawData,
767        render_pass: &mut RenderPass,
768        backend_data: &WgpuBackendData,
769        gamma: f32,
770    ) -> RendererResult<()> {
771        let pipeline = backend_data
772            .pipeline_state
773            .as_ref()
774            .ok_or_else(|| RendererError::InvalidRenderState("Pipeline not created".to_string()))?;
775
776        // Setup viewport
777        let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
778        let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
779        render_pass.set_viewport(0.0, 0.0, fb_width, fb_height, 0.0, 1.0);
780
781        // Set pipeline
782        render_pass.set_pipeline(pipeline);
783
784        // Update uniforms
785        let mvp =
786            Uniforms::create_orthographic_matrix(draw_data.display_pos, draw_data.display_size);
787        let mut uniforms = Uniforms::new();
788        uniforms.update(mvp, gamma);
789
790        // Update uniform buffer
791        if let Some(uniform_buffer) = backend_data.render_resources.uniform_buffer() {
792            uniform_buffer.update(&backend_data.queue, &uniforms);
793            render_pass.set_bind_group(0, uniform_buffer.bind_group(), &[]);
794        }
795
796        // Set vertex and index buffers
797        let frame_resources = &backend_data.frame_resources
798            [(backend_data.frame_index % backend_data.num_frames_in_flight) as usize];
799        if let (Some(vertex_buffer), Some(index_buffer)) = (
800            frame_resources.vertex_buffer(),
801            frame_resources.index_buffer(),
802        ) {
803            render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
804            render_pass.set_index_buffer(index_buffer.slice(..), IndexFormat::Uint16);
805        }
806
807        Ok(())
808    }
809
810    /// Render all draw lists
811    fn render_draw_lists_static(
812        texture_manager: &mut WgpuTextureManager,
813        default_texture: &Option<TextureView>,
814        draw_data: &DrawData,
815        render_pass: &mut RenderPass,
816        backend_data: &mut WgpuBackendData,
817        gamma: f32,
818    ) -> RendererResult<()> {
819        let mut global_vtx_offset = 0i32;
820        let mut global_idx_offset = 0u32;
821        let clip_scale = draw_data.framebuffer_scale;
822        let clip_off = draw_data.display_pos;
823        let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
824        let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
825
826        for draw_list in draw_data.draw_lists() {
827            for cmd in draw_list.commands() {
828                match cmd {
829                    dear_imgui_rs::render::DrawCmd::Elements {
830                        count,
831                        cmd_params,
832                        raw_cmd,
833                    } => {
834                        // Get texture bind group
835                        //
836                        // Dear ImGui 1.92+ (modern texture system): draw commands may carry
837                        // an ImTextureRef whose `TexID` is 0 in the raw field, and the effective
838                        // id must be resolved via ImDrawCmd_GetTexID using the backend's
839                        // Renderer_RenderState. We call it here, after texture updates have been
840                        // handled for this frame, so it returns a valid non-zero id.
841                        let texture_bind_group = {
842                            // Resolve effective ImTextureID now (after texture updates)
843                            let tex_id = unsafe {
844                                dear_imgui_rs::sys::ImDrawCmd_GetTexID(
845                                    raw_cmd as *mut dear_imgui_rs::sys::ImDrawCmd,
846                                )
847                            } as u64;
848                            if tex_id == 0 {
849                                // Use default texture for null/invalid texture ID
850                                if let Some(default_tex) = default_texture {
851                                    backend_data
852                                        .render_resources
853                                        .get_or_create_image_bind_group(
854                                            &backend_data.device,
855                                            0,
856                                            default_tex,
857                                        )?
858                                        .clone()
859                                } else {
860                                    return Err(RendererError::InvalidRenderState(
861                                        "Default texture not available".to_string(),
862                                    ));
863                                }
864                            } else if let Some(wgpu_texture) = texture_manager.get_texture(tex_id) {
865                                backend_data
866                                    .render_resources
867                                    .get_or_create_image_bind_group(
868                                        &backend_data.device,
869                                        tex_id,
870                                        wgpu_texture.view(),
871                                    )?
872                                    .clone()
873                            } else {
874                                // Fallback to default texture if texture not found
875                                if let Some(default_tex) = default_texture {
876                                    backend_data
877                                        .render_resources
878                                        .get_or_create_image_bind_group(
879                                            &backend_data.device,
880                                            0,
881                                            default_tex,
882                                        )?
883                                        .clone()
884                                } else {
885                                    return Err(RendererError::InvalidRenderState(
886                                        "Texture not found and no default texture".to_string(),
887                                    ));
888                                }
889                            }
890                        };
891
892                        // Set texture bind group
893                        render_pass.set_bind_group(1, &texture_bind_group, &[]);
894
895                        // Project scissor/clipping rectangles into framebuffer space
896                        let clip_min_x = (cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0];
897                        let clip_min_y = (cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1];
898                        let clip_max_x = (cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0];
899                        let clip_max_y = (cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1];
900
901                        // Clamp to viewport
902                        let clip_min_x = clip_min_x.max(0.0);
903                        let clip_min_y = clip_min_y.max(0.0);
904                        let clip_max_x = clip_max_x.min(fb_width);
905                        let clip_max_y = clip_max_y.min(fb_height);
906
907                        if clip_max_x <= clip_min_x || clip_max_y <= clip_min_y {
908                            continue;
909                        }
910
911                        // Apply scissor/clipping rectangle
912                        render_pass.set_scissor_rect(
913                            clip_min_x as u32,
914                            clip_min_y as u32,
915                            (clip_max_x - clip_min_x) as u32,
916                            (clip_max_y - clip_min_y) as u32,
917                        );
918
919                        // Draw
920                        let start_index = cmd_params.idx_offset as u32 + global_idx_offset;
921                        let end_index = start_index + count as u32;
922                        let vertex_offset = (cmd_params.vtx_offset as i32) + global_vtx_offset;
923                        render_pass.draw_indexed(start_index..end_index, vertex_offset, 0..1);
924                    }
925                    dear_imgui_rs::render::DrawCmd::ResetRenderState => {
926                        // Re-apply render state using the same gamma
927                        Self::setup_render_state_static(
928                            draw_data,
929                            render_pass,
930                            backend_data,
931                            gamma,
932                        )?;
933                    }
934                    dear_imgui_rs::render::DrawCmd::RawCallback { .. } => {
935                        // Raw callbacks are not supported in this implementation
936                        // They would require access to the raw Dear ImGui draw list
937                        tracing::warn!(
938                            target: "dear-imgui-wgpu",
939                            "Warning: Raw callbacks are not supported in WGPU renderer"
940                        );
941                    }
942                }
943            }
944
945            global_idx_offset += draw_list.idx_buffer().len() as u32;
946            global_vtx_offset += draw_list.vtx_buffer().len() as i32;
947        }
948
949        Ok(())
950    }
951
952    /// Invalidate device objects
953    ///
954    /// This corresponds to ImGui_ImplWGPU_InvalidateDeviceObjects in the C++ implementation
955    pub fn invalidate_device_objects(&mut self) -> RendererResult<()> {
956        if let Some(ref mut backend_data) = self.backend_data {
957            backend_data.pipeline_state = None;
958            backend_data.render_resources = RenderResources::new();
959
960            // Clear frame resources
961            for frame_resources in &mut backend_data.frame_resources {
962                *frame_resources = FrameResources::new();
963            }
964        }
965
966        // Clear texture manager
967        self.texture_manager.clear();
968        self.default_texture = None;
969        self.font_texture_id = None;
970
971        Ok(())
972    }
973
974    /// Shutdown the renderer
975    ///
976    /// This corresponds to ImGui_ImplWGPU_Shutdown in the C++ implementation
977    pub fn shutdown(&mut self) {
978        self.invalidate_device_objects().ok();
979        self.backend_data = None;
980    }
981}
982
983impl Default for WgpuRenderer {
984    fn default() -> Self {
985        Self::empty()
986    }
987}