dear_imgui_wgpu/renderer/
core.rs1use crate::{
2 GammaMode, RendererError, RendererResult, ShaderManager, WgpuBackendData, WgpuTextureManager,
3};
4use dear_imgui_rs::{
5 BackendFlags, Context, ContextBinding,
6 render::{PendingFrame, ReconciledFrame, SynchronousRendererConsumer},
7 sys,
8};
9use std::{
10 cell::Cell,
11 ffi::{c_char, c_void},
12};
13use wgpu::TextureView;
14
15#[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
16use wgpu::Color;
17
18#[derive(Debug)]
20struct RendererBackendToken {
21 _marker: u8,
22}
23
24#[derive(Clone, Debug)]
29pub(super) struct RendererPublication {
30 context: ContextBinding,
31 token_ptr: *mut c_void,
32 renderer_name_ptr: *const c_char,
33 renderer_flags_added: BackendFlags,
34}
35
36impl RendererPublication {
37 pub(super) fn context(&self) -> ContextBinding {
38 self.context.clone()
39 }
40
41 pub(super) unsafe fn clear_owned_raw_state_bound(&self) -> bool {
45 let io = unsafe { sys::igGetIO_Nil() };
46 if io.is_null() {
47 return false;
48 }
49 let io = unsafe { &mut *io };
51 let owned_name =
52 !self.renderer_name_ptr.is_null() && io.BackendRendererName == self.renderer_name_ptr;
53 let owned_token = io.BackendRendererUserData == self.token_ptr;
54
55 let platform_io = unsafe { sys::igGetPlatformIO_Nil() };
56 let owns_draw_callback = if platform_io.is_null() {
57 false
58 } else {
59 WgpuRenderer::owns_any_standard_draw_callback(unsafe {
61 dear_imgui_rs::platform_io::PlatformIo::from_raw(platform_io)
62 })
63 };
64
65 if owned_name {
66 io.BackendRendererName = std::ptr::null();
67 }
68 if owned_token {
69 io.BackendRendererUserData = std::ptr::null_mut();
70 }
71
72 if owned_name || owned_token || owns_draw_callback {
76 io.BackendFlags &= !self.renderer_flags_added.bits();
77 }
78
79 if !platform_io.is_null() {
80 WgpuRenderer::clear_owned_draw_callbacks(unsafe {
81 dear_imgui_rs::platform_io::PlatformIo::from_raw_mut(platform_io)
82 });
83 }
84 owned_name
85 }
86
87 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
91 pub(super) unsafe fn owns_any_raw_publication_bound(&self) -> bool {
92 let io = unsafe { sys::igGetIO_Nil() };
93 if io.is_null() {
94 return false;
95 }
96 let io = unsafe { &*io };
98 if (!self.renderer_name_ptr.is_null() && io.BackendRendererName == self.renderer_name_ptr)
99 || io.BackendRendererUserData == self.token_ptr
100 {
101 return true;
102 }
103
104 let platform_io = unsafe { sys::igGetPlatformIO_Nil() };
105 !platform_io.is_null()
106 && WgpuRenderer::owns_any_standard_draw_callback(unsafe {
108 dear_imgui_rs::platform_io::PlatformIo::from_raw(platform_io)
109 })
110 }
111}
112
113#[derive(Debug)]
119pub(super) struct RendererContextState {
120 publication: RendererPublication,
121 token: Box<RendererBackendToken>,
122 fault: Cell<Option<&'static str>>,
123}
124
125impl RendererContextState {
126 pub(super) fn publish(
127 context: &mut Context,
128 renderer_flags_added: BackendFlags,
129 ) -> RendererResult<Self> {
130 let renderer_name_ptr = context
131 .io()
132 .backend_renderer_name()
133 .ok_or_else(|| {
134 RendererError::InvalidRenderState(
135 "WGPU renderer name was not published before binding".to_owned(),
136 )
137 })?
138 .as_ptr();
139 if !context.io().backend_renderer_user_data().is_null() {
140 return Err(RendererError::ContextAlreadyHasRenderer);
141 }
142
143 let token = Box::new(RendererBackendToken { _marker: 0 });
144 let publication = RendererPublication {
145 context: context.binding(),
146 token_ptr: std::ptr::from_ref(token.as_ref()).cast_mut().cast(),
147 renderer_name_ptr,
148 renderer_flags_added,
149 };
150 let state = Self {
151 publication,
152 token,
153 fault: Cell::new(None),
154 };
155 unsafe {
158 context
159 .io_mut()
160 .set_backend_renderer_user_data(state.publication.token_ptr);
161 }
162 Ok(state)
163 }
164
165 fn ensure_alive(&self) -> RendererResult<()> {
166 if self.publication.context.is_alive() {
167 Ok(())
168 } else {
169 Err(RendererError::ContextDropped)
170 }
171 }
172
173 pub(super) fn ensure_matches(&self, context: &Context) -> RendererResult<()> {
174 self.ensure_alive()?;
175 if self.publication.context.id() == context.id() {
176 Ok(())
177 } else {
178 Err(RendererError::ContextMismatch)
179 }
180 }
181
182 pub(super) fn ensure_renderer_contract(&self) -> RendererResult<()> {
183 self.ensure_alive()?;
184 if let Some(field) = self.fault.get() {
185 let _ = self.publication.context.try_with_bound_context(|| unsafe {
186 self.clear_owned_raw_state_bound();
187 });
188 return Err(RendererError::RendererStateDrift { field });
189 }
190
191 let fault = self
192 .publication
193 .context
194 .try_with_bound_context(|| {
195 let fault = self.current_renderer_state_fault_bound();
196 if fault.is_some() {
197 unsafe { self.clear_owned_raw_state_bound() };
199 }
200 fault
201 })
202 .map_err(|_| RendererError::ContextDropped)?;
203 if let Some(field) = fault {
204 self.fault.set(Some(field));
205 Err(RendererError::RendererStateDrift { field })
206 } else {
207 Ok(())
208 }
209 }
210
211 pub(super) fn context(&self) -> ContextBinding {
212 self.publication.context()
213 }
214
215 pub(super) fn publication(&self) -> RendererPublication {
216 debug_assert_eq!(self.publication.token_ptr, self.token_ptr());
217 self.publication.clone()
218 }
219
220 pub(super) fn clear_with_context(&self, context: &mut Context) {
221 let owned_name = unsafe { self.publication.clear_owned_raw_state_bound() };
223 if owned_name {
224 context
225 .set_renderer_name::<String>(None)
226 .expect("clearing WGPU BackendRendererName must not fail");
227 }
228 }
229
230 pub(super) unsafe fn clear_owned_raw_state_bound(&self) -> bool {
231 unsafe { self.publication.clear_owned_raw_state_bound() }
232 }
233
234 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
240 pub(super) unsafe fn owns_any_raw_publication_bound(&self) -> bool {
241 unsafe { self.publication.owns_any_raw_publication_bound() }
242 }
243
244 fn current_renderer_state_fault_bound(&self) -> Option<&'static str> {
245 let io = unsafe { sys::igGetIO_Nil() };
246 if io.is_null() {
247 return Some("ImGuiIO");
248 }
249 let io = unsafe { &*io };
251 if io.BackendRendererUserData != self.publication.token_ptr {
252 return Some("BackendRendererUserData");
253 }
254 if io.BackendRendererName != self.publication.renderer_name_ptr {
255 return Some("BackendRendererName");
256 }
257 let flags = BackendFlags::from_bits_retain(io.BackendFlags);
258 for (present, field) in [
259 (
260 flags.contains(BackendFlags::RENDERER_HAS_VTX_OFFSET),
261 "RENDERER_HAS_VTX_OFFSET",
262 ),
263 (
264 flags.contains(BackendFlags::RENDERER_HAS_TEXTURES),
265 "RENDERER_HAS_TEXTURES",
266 ),
267 ] {
268 if !present {
269 return Some(field);
270 }
271 }
272
273 let platform_io = unsafe { sys::igGetPlatformIO_Nil() };
274 if platform_io.is_null() {
275 return Some("PlatformIO");
276 }
277 let platform_io = unsafe { dear_imgui_rs::platform_io::PlatformIo::from_raw(platform_io) };
279 if !unsafe { platform_io.renderer_render_state() }.is_null() {
280 return Some("Renderer_RenderState");
281 }
282 let raw = unsafe { &*platform_io.as_raw() };
284 if raw.Renderer_TextureMaxWidth != 0 {
285 return Some("Renderer_TextureMaxWidth");
286 }
287 if raw.Renderer_TextureMaxHeight != 0 {
288 return Some("Renderer_TextureMaxHeight");
289 }
290 if !WgpuRenderer::owned_draw_callbacks_match(platform_io) {
291 return Some("DrawCallback_*");
292 }
293 None
294 }
295
296 fn token_ptr(&self) -> *mut c_void {
297 std::ptr::from_ref(self.token.as_ref()).cast_mut().cast()
298 }
299}
300
301impl Drop for RendererContextState {
302 fn drop(&mut self) {
303 let _ = self.publication.context.try_with_bound_context(|| unsafe {
305 self.clear_owned_raw_state_bound();
306 });
307 }
308}
309
310pub struct WgpuRenderer {
322 pub(super) context_state: Option<RendererContextState>,
324 pub(super) backend_data: Option<WgpuBackendData>,
326 pub(super) shader_manager: ShaderManager,
328 pub(super) texture_manager: WgpuTextureManager,
330 pub(super) default_texture: Option<TextureView>,
332 pub(super) gamma_mode: GammaMode,
334 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
336 pub(super) viewport_clear_color: Color,
337 pub(super) renderer_consumer: Option<SynchronousRendererConsumer>,
339 pub(super) drop_deferral: Option<super::lifecycle::RendererDropDeferral>,
341}
342
343impl WgpuRenderer {
344 pub(super) fn bind_context(
345 &mut self,
346 context: &mut Context,
347 renderer_flags_added: BackendFlags,
348 ) -> RendererResult<()> {
349 if self.context_state.is_some() {
350 return Err(RendererError::InvalidRenderState(
351 "renderer is already bound to a Dear ImGui context".to_owned(),
352 ));
353 }
354 let drop_deferral = super::lifecycle::RendererDropDeferral::register(context)?;
355 let state = match RendererContextState::publish(context, renderer_flags_added) {
356 Ok(state) => state,
357 Err(error) => {
358 drop(drop_deferral);
359 return Err(error);
360 }
361 };
362 drop_deferral.set_publication(state.publication());
363 self.context_state = Some(state);
364 self.drop_deferral = Some(drop_deferral);
365 Ok(())
366 }
367
368 pub(super) fn ensure_context_alive(&self) -> RendererResult<()> {
369 self.context_state
370 .as_ref()
371 .ok_or(RendererError::ContextNotBound)?
372 .ensure_alive()
373 }
374
375 pub(super) fn ensure_renderer_contract(&self) -> RendererResult<()> {
376 self.context_state
377 .as_ref()
378 .ok_or(RendererError::ContextNotBound)?
379 .ensure_renderer_contract()
380 }
381
382 #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
385 pub(super) fn owns_context_publication_bound(&self) -> bool {
386 self.context_state.as_ref().is_some_and(|state| {
387 unsafe { state.owns_any_raw_publication_bound() }
389 })
390 }
391
392 pub(super) fn ensure_context_matches(&self, context: &Context) -> RendererResult<()> {
393 self.context_state
394 .as_ref()
395 .ok_or(RendererError::ContextNotBound)?
396 .ensure_matches(context)
397 }
398
399 pub(super) fn bound_context(&self) -> RendererResult<ContextBinding> {
400 Ok(self
401 .context_state
402 .as_ref()
403 .ok_or(RendererError::ContextNotBound)?
404 .context())
405 }
406
407 pub(super) fn clear_context_state(&mut self) {
408 self.context_state = None;
409 self.drop_deferral = None;
410 }
411
412 pub fn renderer_consumer(&self) -> RendererResult<&SynchronousRendererConsumer> {
417 self.renderer_consumer
418 .as_ref()
419 .ok_or(RendererError::ContextNotBound)
420 }
421
422 pub(super) fn ensure_pending_frame_matches(
423 &self,
424 frame: &PendingFrame<'_>,
425 ) -> RendererResult<()> {
426 let consumer = self.renderer_consumer()?;
427 if frame.context_id() != consumer.context_id() {
428 return Err(RendererError::ContextMismatch);
429 }
430 let epoch = frame.epoch();
431 if epoch.consumer_generation() != consumer.generation() {
432 return Err(RendererError::InvalidRenderState(format!(
433 "pending frame uses consumer generation {}, WGPU owns generation {}",
434 epoch.consumer_generation(),
435 consumer.generation()
436 )));
437 }
438 Ok(())
439 }
440
441 pub(super) fn ensure_reconciled_frame_matches(
442 &self,
443 frame: &ReconciledFrame<'_>,
444 ) -> RendererResult<()> {
445 let consumer = self.renderer_consumer()?;
446 if frame.context_id() != consumer.context_id() {
447 return Err(RendererError::ContextMismatch);
448 }
449 let epoch = frame.epoch().ok_or_else(|| {
450 RendererError::InvalidRenderState(
451 "WGPU requires a managed-texture renderer epoch".to_owned(),
452 )
453 })?;
454 if epoch.consumer_generation() != consumer.generation() {
455 return Err(RendererError::InvalidRenderState(format!(
456 "reconciled frame uses consumer generation {}, WGPU owns generation {}",
457 epoch.consumer_generation(),
458 consumer.generation()
459 )));
460 }
461 Ok(())
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
470 fn dropped_owner_is_not_confused_with_a_reused_context_address() {
471 let owner = Context::create();
472 let owner_id = owner.id();
473 let binding = owner.binding();
474 drop(owner);
475
476 let replacement = Context::create();
477 assert!(!binding.is_alive());
478 assert_ne!(owner_id, replacement.id());
479 }
480}