dear_imgui_wgpu/renderer/init.rs
1use super::{
2 WgpuRenderer,
3 callbacks::{
4 draw_callback_reset_render_state, draw_callback_set_sampler_linear,
5 draw_callback_set_sampler_nearest,
6 },
7};
8use crate::wgpu;
9use crate::{
10 GammaMode, RendererError, RendererResult, ShaderManager, WgpuBackendData, WgpuInitInfo,
11 WgpuTextureManager,
12};
13use dear_imgui_rs::{BackendFlags, Context, TextureId, sys};
14use wgpu::*;
15
16impl WgpuRenderer {
17 /// Create a new WGPU renderer with full initialization (recommended)
18 ///
19 /// This is the preferred way to create a WGPU renderer as it ensures proper
20 /// initialization order and is consistent with other backends.
21 ///
22 /// # Arguments
23 /// * `init_info` - WGPU initialization information (device, queue, format)
24 /// * `imgui_ctx` - Dear ImGui context to configure
25 ///
26 /// # Example
27 /// ```rust,no_run
28 /// use dear_imgui_rs::Context;
29 /// use dear_imgui_wgpu::{WgpuRenderer, WgpuInitInfo};
30 ///
31 /// # fn main() -> Result<(), dear_imgui_wgpu::RendererError> {
32 /// # let (device, queue) = todo!("initialize a WGPU Device/Queue");
33 /// # let surface_format = wgpu::TextureFormat::Bgra8UnormSrgb;
34 /// # let mut imgui_context = Context::create();
35 /// let init_info = WgpuInitInfo::new(device, queue, surface_format);
36 /// let mut renderer = WgpuRenderer::new(init_info, &mut imgui_context)?;
37 /// # Ok(()) }
38 /// ```
39 pub fn new(init_info: WgpuInitInfo, imgui_ctx: &mut Context) -> RendererResult<Self> {
40 // Native and wasm experimental path: fully configure context, including font atlas.
41 #[cfg(any(
42 not(target_arch = "wasm32"),
43 all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental")
44 ))]
45 {
46 let mut renderer = Self::empty();
47 renderer.init_with_context(init_info, imgui_ctx)?;
48 Ok(renderer)
49 }
50
51 // Default wasm path: skip font atlas manipulation for safety.
52 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
53 {
54 Self::new_without_font_atlas(init_info, imgui_ctx)
55 }
56 }
57
58 /// Create an empty WGPU renderer for advanced usage
59 ///
60 /// This creates an uninitialized renderer that must be initialized later
61 /// using `init_with_context()`. Most users should use `new()` instead.
62 ///
63 /// # Example
64 /// ```rust,no_run
65 /// use dear_imgui_rs::Context;
66 /// use dear_imgui_wgpu::{WgpuRenderer, WgpuInitInfo};
67 ///
68 /// # fn main() -> Result<(), dear_imgui_wgpu::RendererError> {
69 /// # let (device, queue) = todo!("initialize a WGPU Device/Queue");
70 /// # let surface_format = wgpu::TextureFormat::Bgra8UnormSrgb;
71 /// # let mut imgui_context = Context::create();
72 /// let mut renderer = WgpuRenderer::empty();
73 /// let init_info = WgpuInitInfo::new(device, queue, surface_format);
74 /// renderer.init_with_context(init_info, &mut imgui_context)?;
75 /// # Ok(()) }
76 /// ```
77 pub fn empty() -> Self {
78 Self {
79 backend_data: None,
80 shader_manager: ShaderManager::new(),
81 texture_manager: WgpuTextureManager::new(),
82 default_texture: None,
83 gamma_mode: GammaMode::Auto,
84 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
85 viewport_clear_color: Color::BLACK,
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 // Preflight: ensure the render target format is render-attachable and blendable.
97 // The ImGui pipeline always uses alpha blending; non-blendable formats will
98 // fail validation later with less actionable errors.
99 let fmt = backend_data.render_target_format;
100 if let Some(adapter) = backend_data.adapter.as_ref() {
101 let fmt_features = adapter.get_texture_format_features(fmt);
102 if !fmt_features
103 .allowed_usages
104 .contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
105 || !fmt_features
106 .flags
107 .contains(wgpu::TextureFormatFeatureFlags::BLENDABLE)
108 {
109 return Err(RendererError::InvalidRenderState(format!(
110 "Render target format {:?} is not suitable for ImGui WGPU renderer (requires RENDER_ATTACHMENT + BLENDABLE). allowed_usages={:?} flags={:?}",
111 fmt, fmt_features.allowed_usages, fmt_features.flags
112 )));
113 }
114 }
115
116 // Initialize render resources
117 backend_data
118 .render_resources
119 .initialize(&backend_data.device)?;
120
121 // Initialize shaders
122 self.shader_manager.initialize(&backend_data.device)?;
123
124 // Create default texture (1x1 white pixel)
125 let default_texture =
126 self.create_default_texture(&backend_data.device, &backend_data.queue)?;
127 self.default_texture = Some(default_texture);
128
129 // Create device objects (pipeline, etc.)
130 self.create_device_objects(&mut backend_data)?;
131
132 self.backend_data = Some(backend_data);
133 Ok(())
134 }
135
136 /// Initialize the renderer with ImGui context configuration (without font atlas for WASM)
137 ///
138 /// This is a variant of init_with_context that skips font atlas preparation,
139 /// useful for WASM builds where font atlas memory sharing is problematic.
140 pub fn new_without_font_atlas(
141 init_info: WgpuInitInfo,
142 imgui_ctx: &mut Context,
143 ) -> RendererResult<Self> {
144 let mut renderer = Self::empty();
145
146 // First initialize the renderer
147 renderer.init(init_info)?;
148
149 // Then configure the ImGui context with backend capabilities
150 renderer.configure_imgui_context(imgui_ctx);
151
152 // Skip font atlas preparation for WASM
153 // The default font will be used automatically by Dear ImGui
154
155 Ok(renderer)
156 }
157
158 /// Initialize the renderer with ImGui context configuration
159 ///
160 /// This is a convenience method that combines init() and configure_imgui_context()
161 /// to ensure proper initialization order, similar to the glow backend approach.
162 pub fn init_with_context(
163 &mut self,
164 init_info: WgpuInitInfo,
165 imgui_ctx: &mut Context,
166 ) -> RendererResult<()> {
167 // First initialize the renderer
168 self.init(init_info)?;
169
170 // Then configure the ImGui context with backend capabilities
171 // This must be done BEFORE preparing the font atlas
172 self.configure_imgui_context(imgui_ctx);
173
174 // Finally prepare the font atlas
175 self.prepare_font_atlas(imgui_ctx)?;
176
177 Ok(())
178 }
179
180 /// Set gamma mode
181 pub fn set_gamma_mode(&mut self, mode: GammaMode) {
182 self.gamma_mode = mode;
183 }
184
185 /// Set clear color for secondary viewports (multi-viewport mode).
186 ///
187 /// This color is used as the load/clear color when rendering ImGui-created
188 /// platform windows via `RenderPlatformWindowsDefault`. It is independent
189 /// from whatever clear color your main swapchain uses.
190 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
191 pub fn set_viewport_clear_color(&mut self, color: Color) {
192 self.viewport_clear_color = color;
193 }
194
195 /// Get current clear color for secondary viewports.
196 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
197 pub fn viewport_clear_color(&self) -> Color {
198 self.viewport_clear_color
199 }
200
201 /// Configure Dear ImGui context with WGPU backend capabilities
202 pub fn configure_imgui_context(&self, imgui_context: &mut Context) {
203 let should_set_name = imgui_context.io().backend_renderer_name().is_none();
204 if should_set_name {
205 let _ = imgui_context.set_renderer_name(Some(format!(
206 "dear-imgui-wgpu {}",
207 env!("CARGO_PKG_VERSION")
208 )));
209 }
210
211 let io = imgui_context.io_mut();
212 let mut flags = io.backend_flags();
213
214 // Set WGPU renderer capabilities
215 // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
216 flags.insert(BackendFlags::RENDERER_HAS_VTX_OFFSET);
217 // We can honor ImGuiPlatformIO::Textures[] requests during render.
218 flags.insert(BackendFlags::RENDERER_HAS_TEXTURES);
219
220 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
221 {
222 // We can render additional platform windows
223 flags.insert(BackendFlags::RENDERER_HAS_VIEWPORTS);
224 }
225
226 io.set_backend_flags(flags);
227
228 let platform_io = imgui_context.platform_io_mut();
229 platform_io
230 .set_draw_callback_reset_render_state_raw(Some(draw_callback_reset_render_state));
231 platform_io
232 .set_draw_callback_set_sampler_linear_raw(Some(draw_callback_set_sampler_linear));
233 platform_io
234 .set_draw_callback_set_sampler_nearest_raw(Some(draw_callback_set_sampler_nearest));
235 }
236
237 /// Prepare font atlas for rendering
238 pub fn prepare_font_atlas(&mut self, imgui_ctx: &mut Context) -> RendererResult<()> {
239 if let Some(backend_data) = &self.backend_data {
240 let device = backend_data.device.clone();
241 let queue = backend_data.queue.clone();
242 self.reload_font_texture(imgui_ctx, &device, &queue)?;
243 if imgui_ctx
244 .io()
245 .backend_flags()
246 .contains(BackendFlags::RENDERER_HAS_TEXTURES)
247 {
248 // New backend texture system: font textures are produced via DrawData::textures()
249 // requests; do not assign a legacy TexID.
250 return Ok(());
251 }
252
253 // Legacy fallback: only upload when the atlas does not already resolve to a live
254 // WGPU texture. This keeps the backend idempotent without carrying a separate
255 // renderer-side font texture cache now that the managed ImTextureData path is the
256 // primary mode.
257 let mut tex_ref = imgui_ctx.font_atlas().get_tex_ref();
258 let existing_tex_id = unsafe { sys::ImTextureRef_GetTexID(&mut tex_ref) };
259 let existing_tex_id = TextureId::from(existing_tex_id);
260 let has_live_font_texture = !existing_tex_id.is_null()
261 && self.texture_manager.contains_texture(existing_tex_id);
262
263 if !has_live_font_texture
264 && let Some(tex_id) =
265 self.try_upload_font_atlas_legacy(imgui_ctx, &device, &queue)?
266 && cfg!(debug_assertions)
267 {
268 tracing::debug!(
269 target: "dear-imgui-wgpu",
270 "[dear-imgui-wgpu][debug] Font atlas uploaded via legacy fallback path. tex_id={}",
271 tex_id.id()
272 );
273 }
274 }
275 Ok(())
276 }
277
278 // create_device_objects moved to renderer/pipeline.rs
279
280 /// Create a default 1x1 white texture
281 fn create_default_texture(
282 &self,
283 device: &Device,
284 queue: &Queue,
285 ) -> RendererResult<TextureView> {
286 let texture = device.create_texture(&TextureDescriptor {
287 label: Some("Dear ImGui Default Texture"),
288 size: Extent3d {
289 width: 1,
290 height: 1,
291 depth_or_array_layers: 1,
292 },
293 mip_level_count: 1,
294 sample_count: 1,
295 dimension: TextureDimension::D2,
296 format: TextureFormat::Rgba8Unorm,
297 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
298 view_formats: &[],
299 });
300
301 // Upload white pixel
302 queue.write_texture(
303 wgpu::TexelCopyTextureInfo {
304 texture: &texture,
305 mip_level: 0,
306 origin: wgpu::Origin3d::ZERO,
307 aspect: wgpu::TextureAspect::All,
308 },
309 &[255u8, 255u8, 255u8, 255u8], // RGBA white
310 wgpu::TexelCopyBufferLayout {
311 offset: 0,
312 bytes_per_row: Some(4),
313 rows_per_image: Some(1),
314 },
315 Extent3d {
316 width: 1,
317 height: 1,
318 depth_or_array_layers: 1,
319 },
320 );
321
322 Ok(texture.create_view(&TextureViewDescriptor::default()))
323 }
324}
325
326impl Default for WgpuRenderer {
327 fn default() -> Self {
328 Self::empty()
329 }
330}