Skip to main content

dear_imgui_wgpu/
render_resources.rs

1//! Render resources management for the WGPU renderer
2//!
3//! This module handles shared render resources like samplers, uniforms, and bind groups,
4//! corresponding to the RenderResources struct in imgui_impl_wgpu.cpp
5
6use crate::{RendererError, RendererResult, UniformBuffer};
7use std::collections::HashMap;
8use wgpu::*;
9
10/// Shared render resources
11///
12/// This corresponds to the RenderResources struct in the C++ implementation.
13/// Contains samplers, uniform buffers, and bind group layouts that are shared
14/// across all frames.
15pub struct RenderResources {
16    /// Linear texture sampler
17    pub sampler: Option<Sampler>,
18    /// Nearest/point texture sampler
19    pub sampler_nearest: Option<Sampler>,
20    /// Uniform buffer manager (also owns the common bind group layout)
21    pub uniform_buffer: Option<UniformBuffer>,
22    /// Common bind group using the nearest/point sampler
23    pub nearest_common_bind_group: Option<BindGroup>,
24    /// Image bind groups cache (texture_id -> bind_group)
25    pub image_bind_groups: HashMap<u64, BindGroup>,
26    /// Image bind group layout (cached for efficiency)
27    pub image_bind_group_layout: Option<BindGroupLayout>,
28}
29
30impl RenderResources {
31    /// Create new empty render resources
32    pub fn new() -> Self {
33        Self {
34            sampler: None,
35            sampler_nearest: None,
36            uniform_buffer: None,
37            nearest_common_bind_group: None,
38            image_bind_groups: HashMap::new(),
39            image_bind_group_layout: None,
40        }
41    }
42
43    /// Initialize render resources
44    pub fn initialize(&mut self, device: &Device) -> RendererResult<()> {
45        #[cfg(feature = "wgpu-27")]
46        fn linear_mipmap_filter() -> FilterMode {
47            FilterMode::Linear
48        }
49        #[cfg(feature = "wgpu-27")]
50        fn nearest_mipmap_filter() -> FilterMode {
51            FilterMode::Nearest
52        }
53        #[cfg(any(feature = "wgpu-28", feature = "wgpu-29"))]
54        fn linear_mipmap_filter() -> MipmapFilterMode {
55            MipmapFilterMode::Linear
56        }
57        #[cfg(any(feature = "wgpu-28", feature = "wgpu-29"))]
58        fn nearest_mipmap_filter() -> MipmapFilterMode {
59            MipmapFilterMode::Nearest
60        }
61
62        // Create linear texture sampler (matches imgui_impl_wgpu.cpp sampler setup)
63        // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines'
64        // or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling
65        let sampler = device.create_sampler(&SamplerDescriptor {
66            label: Some("Dear ImGui Texture Sampler"),
67            address_mode_u: AddressMode::ClampToEdge, // matches WGPUAddressMode_ClampToEdge
68            address_mode_v: AddressMode::ClampToEdge, // matches WGPUAddressMode_ClampToEdge
69            address_mode_w: AddressMode::ClampToEdge, // matches WGPUAddressMode_ClampToEdge
70            mag_filter: FilterMode::Linear,           // matches WGPUFilterMode_Linear
71            min_filter: FilterMode::Linear,           // matches WGPUFilterMode_Linear
72            mipmap_filter: linear_mipmap_filter(),    // matches WGPUMipmapFilterMode_Linear
73            anisotropy_clamp: 1,                      // matches maxAnisotropy = 1
74            ..Default::default()
75        });
76
77        let sampler_nearest = device.create_sampler(&SamplerDescriptor {
78            label: Some("Dear ImGui Texture Sampler Nearest"),
79            address_mode_u: AddressMode::ClampToEdge,
80            address_mode_v: AddressMode::ClampToEdge,
81            address_mode_w: AddressMode::ClampToEdge,
82            mag_filter: FilterMode::Nearest,
83            min_filter: FilterMode::Nearest,
84            mipmap_filter: nearest_mipmap_filter(),
85            anisotropy_clamp: 1,
86            ..Default::default()
87        });
88
89        // Create uniform buffer + common bind group layout
90        let uniform_buffer = UniformBuffer::new(device, &sampler);
91
92        let nearest_common_bind_group = device.create_bind_group(&BindGroupDescriptor {
93            label: Some("Dear ImGui Common Bind Group Nearest Sampler"),
94            layout: uniform_buffer.bind_group_layout(),
95            entries: &[
96                BindGroupEntry {
97                    binding: 0,
98                    resource: uniform_buffer.buffer().as_entire_binding(),
99                },
100                BindGroupEntry {
101                    binding: 1,
102                    resource: BindingResource::Sampler(&sampler_nearest),
103                },
104            ],
105        });
106
107        // Create image bind group layout (for texture views)
108        let image_bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
109            label: Some("Dear ImGui Image Bind Group Layout"),
110            entries: &[BindGroupLayoutEntry {
111                binding: 0,
112                visibility: ShaderStages::FRAGMENT,
113                ty: BindingType::Texture {
114                    multisampled: false,
115                    sample_type: TextureSampleType::Float { filterable: true },
116                    view_dimension: TextureViewDimension::D2,
117                },
118                count: None,
119            }],
120        });
121
122        self.sampler = Some(sampler);
123        self.sampler_nearest = Some(sampler_nearest);
124        self.uniform_buffer = Some(uniform_buffer);
125        self.nearest_common_bind_group = Some(nearest_common_bind_group);
126        self.image_bind_group_layout = Some(image_bind_group_layout);
127
128        Ok(())
129    }
130
131    /// Create an image bind group for a texture
132    pub fn create_image_bind_group(
133        &self,
134        device: &Device,
135        texture_view: &TextureView,
136    ) -> RendererResult<BindGroup> {
137        let layout = self.image_bind_group_layout.as_ref().ok_or_else(|| {
138            RendererError::InvalidRenderState("Image bind group layout not initialized".to_string())
139        })?;
140
141        let bind_group = device.create_bind_group(&BindGroupDescriptor {
142            label: Some("Dear ImGui Image Bind Group"),
143            layout,
144            entries: &[BindGroupEntry {
145                binding: 0,
146                resource: BindingResource::TextureView(texture_view),
147            }],
148        });
149
150        Ok(bind_group)
151    }
152
153    /// Get or create an image bind group for a texture
154    pub fn get_or_create_image_bind_group(
155        &mut self,
156        device: &Device,
157        texture_id: u64,
158        texture_view: &TextureView,
159    ) -> RendererResult<&BindGroup> {
160        if !self.image_bind_groups.contains_key(&texture_id) {
161            let bind_group = self.create_image_bind_group(device, texture_view)?;
162            self.image_bind_groups.insert(texture_id, bind_group);
163        }
164
165        self.image_bind_groups.get(&texture_id).ok_or_else(|| {
166            RendererError::InvalidRenderState("Image bind group missing after creation".to_string())
167        })
168    }
169
170    /// Remove an image bind group
171    pub fn remove_image_bind_group(&mut self, texture_id: u64) {
172        self.image_bind_groups.remove(&texture_id);
173    }
174
175    /// Clear all image bind groups
176    pub fn clear_image_bind_groups(&mut self) {
177        self.image_bind_groups.clear();
178    }
179
180    /// Get the texture sampler
181    pub fn sampler(&self) -> Option<&Sampler> {
182        self.sampler.as_ref()
183    }
184
185    /// Get the nearest/point texture sampler
186    pub fn sampler_nearest(&self) -> Option<&Sampler> {
187        self.sampler_nearest.as_ref()
188    }
189
190    /// Get the uniform buffer
191    pub fn uniform_buffer(&self) -> Option<&UniformBuffer> {
192        self.uniform_buffer.as_ref()
193    }
194
195    /// Get the common bind group
196    pub fn common_bind_group(&self) -> Option<&BindGroup> {
197        self.uniform_buffer.as_ref().map(|ub| ub.bind_group())
198    }
199
200    /// Get the common bind group using nearest/point sampling
201    pub fn nearest_common_bind_group(&self) -> Option<&BindGroup> {
202        self.nearest_common_bind_group.as_ref()
203    }
204
205    /// Get the image bind group layout
206    pub fn image_bind_group_layout(&self) -> Option<&BindGroupLayout> {
207        self.image_bind_group_layout.as_ref()
208    }
209
210    /// Check if resources are initialized
211    pub fn is_initialized(&self) -> bool {
212        self.sampler.is_some()
213            && self.sampler_nearest.is_some()
214            && self.uniform_buffer.is_some()
215            && self.nearest_common_bind_group.is_some()
216            && self.image_bind_group_layout.is_some()
217    }
218
219    /// Get statistics for debugging
220    pub fn stats(&self) -> RenderResourcesStats {
221        RenderResourcesStats {
222            image_bind_groups_count: self.image_bind_groups.len(),
223            is_initialized: self.is_initialized(),
224        }
225    }
226}
227
228impl Default for RenderResources {
229    fn default() -> Self {
230        Self::new()
231    }
232}
233
234/// Statistics for render resources
235#[derive(Debug, Clone)]
236pub struct RenderResourcesStats {
237    pub image_bind_groups_count: usize,
238    pub is_initialized: bool,
239}