dear_imgui_wgpu/renderer/
init.rs1use 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};
14use wgpu::*;
15
16impl WgpuRenderer {
17 pub fn new(init_info: WgpuInitInfo, imgui_ctx: &mut Context) -> RendererResult<Self> {
40 let mut renderer = Self::empty();
41 renderer.initialize_for_context(init_info, imgui_ctx)?;
42 Ok(renderer)
43 }
44
45 pub(super) fn empty() -> Self {
46 Self {
47 context_state: None,
48 backend_data: None,
49 shader_manager: ShaderManager::new(),
50 texture_manager: WgpuTextureManager::new(),
51 default_texture: None,
52 gamma_mode: GammaMode::Auto,
53 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
54 viewport_clear_color: Color::BLACK,
55 renderer_consumer: None,
56 drop_deferral: None,
57 }
58 }
59
60 fn initialize_device(&mut self, init_info: WgpuInitInfo) -> RendererResult<()> {
65 self.ensure_uninitialized()?;
66
67 let mut backend_data = WgpuBackendData::new(init_info);
69
70 let fmt = backend_data.render_target_format;
74 if let Some(adapter) = backend_data.init_info.adapter.as_ref() {
75 let fmt_features = adapter.get_texture_format_features(fmt);
76 if !fmt_features
77 .allowed_usages
78 .contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
79 || !fmt_features
80 .flags
81 .contains(wgpu::TextureFormatFeatureFlags::BLENDABLE)
82 {
83 return Err(RendererError::InvalidRenderState(format!(
84 "Render target format {:?} is not suitable for ImGui WGPU renderer (requires RENDER_ATTACHMENT + BLENDABLE). allowed_usages={:?} flags={:?}",
85 fmt, fmt_features.allowed_usages, fmt_features.flags
86 )));
87 }
88 }
89
90 if let Err(error) = self.create_device_objects(&mut backend_data) {
91 self.shader_manager = ShaderManager::new();
92 self.default_texture = None;
93 return Err(error);
94 }
95
96 self.backend_data = Some(backend_data);
97 Ok(())
98 }
99
100 fn ensure_uninitialized(&self) -> RendererResult<()> {
101 if self.backend_data.is_some()
102 || self.context_state.is_some()
103 || self.renderer_consumer.is_some()
104 {
105 return Err(RendererError::InvalidRenderState(
106 "renderer is already initialized; call shutdown() with its ImGui context before reinitializing"
107 .to_owned(),
108 ));
109 }
110 Ok(())
111 }
112
113 fn initialize_for_context(
114 &mut self,
115 init_info: WgpuInitInfo,
116 imgui_ctx: &mut Context,
117 ) -> RendererResult<()> {
118 self.ensure_uninitialized()?;
119 Self::ensure_context_available(imgui_ctx)?;
120 self.initialize_device(init_info)?;
121
122 if let Err(error) = self.attach_context(imgui_ctx) {
123 self.invalidate_device_objects_only();
124 self.backend_data = None;
125 return Err(error);
126 }
127
128 Ok(())
129 }
130
131 fn attach_context(&mut self, imgui_ctx: &mut Context) -> RendererResult<()> {
132 let (renderer_flags_added, renderer_name_ptr) = Self::configure_imgui_context(imgui_ctx)?;
133 if let Err(error) = self.bind_context(imgui_ctx, renderer_flags_added) {
134 Self::clear_unbound_imgui_context(imgui_ctx, renderer_flags_added, renderer_name_ptr);
135 return Err(error);
136 }
137 let consumer = match imgui_ctx.create_synchronous_renderer_consumer() {
138 Ok(consumer) => consumer,
139 Err(error) => {
140 self.clear_bound_imgui_context(imgui_ctx);
141 return Err(error.into());
142 }
143 };
144 self.renderer_consumer = Some(consumer);
145
146 let consumer = self
151 .renderer_consumer
152 .take()
153 .ok_or(RendererError::ContextNotBound)?;
154 let reset = match imgui_ctx.prepare_renderer_texture_reset(&consumer) {
155 Ok(reset) => reset,
156 Err(error) => {
157 drop(consumer);
158 self.clear_bound_imgui_context(imgui_ctx);
159 return Err(error.into());
160 }
161 };
162 reset.commit();
163 self.texture_manager.clear_destroyed_managed_textures();
164 self.renderer_consumer = Some(consumer);
165
166 Ok(())
167 }
168
169 fn ensure_context_available(imgui_context: &Context) -> RendererResult<()> {
170 if !imgui_context.io().backend_renderer_user_data().is_null() {
171 return Err(RendererError::ContextAlreadyHasRenderer);
172 }
173 if imgui_context.io().backend_renderer_name().is_some() {
174 return Err(RendererError::ContextAlreadyHasRenderer);
175 }
176 let reserved_flags = BackendFlags::RENDERER_HAS_VTX_OFFSET
177 | BackendFlags::RENDERER_HAS_TEXTURES
178 | BackendFlags::from_bits_retain(
179 dear_imgui_rs::sys::ImGuiBackendFlags_RendererHasViewports,
180 );
181 if !(imgui_context.io().backend_flags() & reserved_flags).is_empty() {
182 return Err(RendererError::ContextAlreadyHasRenderer);
183 }
184
185 let platform_io = imgui_context.platform_io();
186 let raw = unsafe { &*platform_io.as_raw() };
188 if unsafe { !platform_io.renderer_render_state().is_null() }
189 || raw.Renderer_TextureMaxWidth != 0
190 || raw.Renderer_TextureMaxHeight != 0
191 || raw.Renderer_CreateWindow.is_some()
192 || raw.Renderer_DestroyWindow.is_some()
193 || raw.Renderer_SetWindowSize.is_some()
194 || raw.Renderer_RenderWindow.is_some()
195 || raw.Renderer_SwapBuffers.is_some()
196 {
197 return Err(RendererError::ContextAlreadyHasRenderer);
198 }
199 let draw_callbacks_occupied = platform_io.draw_callback_reset_render_state_raw().is_some()
200 || platform_io.draw_callback_set_sampler_linear_raw().is_some()
201 || platform_io
202 .draw_callback_set_sampler_nearest_raw()
203 .is_some();
204
205 if imgui_context.io().backend_renderer_name().is_some() || draw_callbacks_occupied {
206 Err(RendererError::ContextAlreadyHasRenderer)
207 } else {
208 Ok(())
209 }
210 }
211
212 pub fn set_gamma_mode(&mut self, mode: GammaMode) {
214 self.gamma_mode = mode;
215 }
216
217 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
223 pub fn set_viewport_clear_color(&mut self, color: Color) {
224 self.viewport_clear_color = color;
225 }
226
227 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
229 pub fn viewport_clear_color(&self) -> Color {
230 self.viewport_clear_color
231 }
232
233 pub(super) fn configure_imgui_context(
234 imgui_context: &mut Context,
235 ) -> RendererResult<(BackendFlags, *const std::ffi::c_char)> {
236 Self::ensure_context_available(imgui_context)?;
239 imgui_context
240 .set_renderer_name(Some(format!(
241 "dear-imgui-wgpu {}",
242 env!("CARGO_PKG_VERSION")
243 )))
244 .map_err(|error| {
245 RendererError::InvalidRenderState(format!(
246 "failed to configure Dear ImGui renderer name: {error}"
247 ))
248 })?;
249 let renderer_name_ptr = imgui_context
250 .io()
251 .backend_renderer_name()
252 .expect("WGPU just published BackendRendererName")
253 .as_ptr();
254
255 let io = imgui_context.io_mut();
256 let previous_flags = io.backend_flags();
257 let renderer_flags =
258 BackendFlags::RENDERER_HAS_VTX_OFFSET | BackendFlags::RENDERER_HAS_TEXTURES;
259
260 io.set_backend_flags(previous_flags | renderer_flags);
264
265 let platform_io = imgui_context.platform_io_mut();
266 unsafe {
269 platform_io
270 .set_draw_callback_reset_render_state_raw(Some(draw_callback_reset_render_state));
271 platform_io
272 .set_draw_callback_set_sampler_linear_raw(Some(draw_callback_set_sampler_linear));
273 platform_io
274 .set_draw_callback_set_sampler_nearest_raw(Some(draw_callback_set_sampler_nearest));
275 }
276
277 Ok((renderer_flags & !previous_flags, renderer_name_ptr))
278 }
279
280 fn clear_unbound_imgui_context(
281 imgui_context: &mut Context,
282 renderer_flags_added: BackendFlags,
283 renderer_name_ptr: *const std::ffi::c_char,
284 ) {
285 let owned_name = imgui_context
286 .io()
287 .backend_renderer_name()
288 .is_some_and(|name| name.as_ptr() == renderer_name_ptr);
289 if owned_name {
290 imgui_context
291 .set_renderer_name::<String>(None)
292 .expect("clearing WGPU BackendRendererName must not fail");
293 }
294 let io = imgui_context.io_mut();
295 io.set_backend_flags(io.backend_flags() & !renderer_flags_added);
296 Self::clear_owned_draw_callbacks(imgui_context.platform_io_mut());
297 }
298
299 pub(super) fn clear_bound_imgui_context(&mut self, imgui_context: &mut Context) {
300 if let Some(state) = self.context_state.as_ref() {
301 state.clear_with_context(imgui_context);
302 }
303 self.clear_context_state();
304 }
305
306 pub(super) fn create_device_objects(
308 &mut self,
309 backend_data: &mut WgpuBackendData,
310 ) -> RendererResult<()> {
311 backend_data
312 .render_resources
313 .initialize(&backend_data.device)?;
314 self.shader_manager.initialize(&backend_data.device)?;
315 self.default_texture =
316 Some(self.create_default_texture(&backend_data.device, &backend_data.queue)?);
317 self.create_render_pipeline(backend_data)
318 }
319
320 fn create_default_texture(
322 &self,
323 device: &Device,
324 queue: &Queue,
325 ) -> RendererResult<TextureView> {
326 let texture = device.create_texture(&TextureDescriptor {
327 label: Some("Dear ImGui Default Texture"),
328 size: Extent3d {
329 width: 1,
330 height: 1,
331 depth_or_array_layers: 1,
332 },
333 mip_level_count: 1,
334 sample_count: 1,
335 dimension: TextureDimension::D2,
336 format: TextureFormat::Rgba8Unorm,
337 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
338 view_formats: &[],
339 });
340
341 queue.write_texture(
343 wgpu::TexelCopyTextureInfo {
344 texture: &texture,
345 mip_level: 0,
346 origin: wgpu::Origin3d::ZERO,
347 aspect: wgpu::TextureAspect::All,
348 },
349 &[255u8, 255u8, 255u8, 255u8], wgpu::TexelCopyBufferLayout {
351 offset: 0,
352 bytes_per_row: Some(4),
353 rows_per_image: Some(1),
354 },
355 Extent3d {
356 width: 1,
357 height: 1,
358 depth_or_array_layers: 1,
359 },
360 );
361
362 Ok(texture.create_view(&TextureViewDescriptor::default()))
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn context_preflight_rejects_an_existing_renderer_name() {
372 let mut context = Context::create();
373 context
374 .set_renderer_name(Some("foreign-renderer"))
375 .expect("test renderer name should be valid");
376
377 assert!(matches!(
378 WgpuRenderer::ensure_context_available(&context),
379 Err(RendererError::ContextAlreadyHasRenderer)
380 ));
381 }
382
383 #[test]
384 fn context_preflight_rejects_reserved_renderer_flags() {
385 let mut context = Context::create();
386 context
387 .io_mut()
388 .set_backend_flags(BackendFlags::RENDERER_HAS_TEXTURES);
389
390 assert!(matches!(
391 WgpuRenderer::ensure_context_available(&context),
392 Err(RendererError::ContextAlreadyHasRenderer)
393 ));
394 context.io_mut().set_backend_flags(BackendFlags::empty());
395 }
396
397 #[test]
398 fn context_preflight_always_rejects_the_viewport_renderer_capability() {
399 let context = Context::create();
400 let viewport_flag = BackendFlags::from_bits_retain(
401 dear_imgui_rs::sys::ImGuiBackendFlags_RendererHasViewports,
402 );
403 let raw_io = unsafe { dear_imgui_rs::sys::igGetIO_ContextPtr(context.as_raw()) };
404 unsafe { (*raw_io).BackendFlags = viewport_flag.bits() };
405 let flags_before = context.io().backend_flags();
406
407 assert!(matches!(
408 WgpuRenderer::ensure_context_available(&context),
409 Err(RendererError::ContextAlreadyHasRenderer)
410 ));
411 assert_eq!(context.io().backend_flags(), flags_before);
412 assert!(context.io().backend_renderer_user_data().is_null());
413 assert!(context.io().backend_renderer_name().is_none());
414
415 unsafe { (*raw_io).BackendFlags = 0 };
416 }
417
418 #[test]
419 fn unconfigure_removes_the_exclusive_wgpu_claim() {
420 let mut context = Context::create();
421 let (added, _) = WgpuRenderer::configure_imgui_context(&mut context)
422 .expect("fresh context should accept WGPU renderer state");
423 assert_eq!(
424 added,
425 BackendFlags::RENDERER_HAS_VTX_OFFSET | BackendFlags::RENDERER_HAS_TEXTURES
426 );
427 let mut renderer = WgpuRenderer::empty();
428 renderer
429 .bind_context(&mut context, added)
430 .expect("configured Context should bind once");
431
432 renderer.clear_bound_imgui_context(&mut context);
433 assert_eq!(context.io().backend_flags(), BackendFlags::empty());
434 assert!(context.io().backend_renderer_user_data().is_null());
435 assert!(context.io().backend_renderer_name().is_none());
436 assert!(
437 context
438 .platform_io()
439 .draw_callback_reset_render_state_raw()
440 .is_none()
441 );
442 }
443}