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