1use std::{cell::Cell, ffi::c_void, marker::PhantomData, ptr::NonNull, rc::Rc};
7
8use crate::{FrameResourceArena, FrameResources, RenderResources, RendererError, RendererResult};
9use dear_imgui_rs::sys;
10use thiserror::Error;
11use wgpu::*;
12
13#[derive(Copy, Clone, Debug, Eq, Error, PartialEq)]
15#[non_exhaustive]
16pub enum WgpuRenderStateAccessError {
17 #[error("no WGPU render state is active on the current Dear ImGui context")]
19 Inactive,
20 #[error("the active WGPU render state is already borrowed")]
22 AlreadyBorrowed,
23}
24
25pub(crate) struct WgpuRenderStateStorage {
26 device: NonNull<Device>,
27 render_pass: NonNull<c_void>,
28 borrowed: Cell<bool>,
29}
30
31impl WgpuRenderStateStorage {
32 pub(crate) fn new(device: &Device, render_pass: &mut RenderPass<'_>) -> Self {
33 Self {
34 device: NonNull::from(device),
35 render_pass: NonNull::from(render_pass).cast(),
36 borrowed: Cell::new(false),
37 }
38 }
39}
40
41struct WgpuRenderStateBorrow<'storage>(&'storage Cell<bool>);
42
43impl Drop for WgpuRenderStateBorrow<'_> {
44 fn drop(&mut self) {
45 self.0.set(false);
46 }
47}
48
49#[derive(Debug)]
67pub struct WgpuRenderState<'callback> {
68 storage: NonNull<WgpuRenderStateStorage>,
69 _callback: PhantomData<&'callback mut WgpuRenderStateStorage>,
70 _ui_thread: PhantomData<Rc<()>>,
71}
72
73impl WgpuRenderState<'_> {
74 pub unsafe fn with_current<R>(
87 callback: impl for<'callback> FnOnce(WgpuRenderState<'callback>) -> R,
88 ) -> Result<R, WgpuRenderStateAccessError> {
89 let platform_io = unsafe { sys::igGetPlatformIO_Nil() };
90 let raw_state = if platform_io.is_null() {
91 None
92 } else {
93 NonNull::new(unsafe { (*platform_io).Renderer_RenderState })
94 }
95 .ok_or(WgpuRenderStateAccessError::Inactive)?;
96 let storage = raw_state.cast::<WgpuRenderStateStorage>();
97 let borrowed = unsafe { &storage.as_ref().borrowed };
98 if borrowed.replace(true) {
99 return Err(WgpuRenderStateAccessError::AlreadyBorrowed);
100 }
101 let _borrow = WgpuRenderStateBorrow(borrowed);
102 Ok(callback(WgpuRenderState {
103 storage,
104 _callback: PhantomData,
105 _ui_thread: PhantomData,
106 }))
107 }
108
109 pub fn device(&self) -> &Device {
111 unsafe { self.storage.as_ref().device.as_ref() }
112 }
113
114 pub fn render_pass(&mut self) -> &mut RenderPass<'_> {
116 unsafe {
117 self.storage
118 .as_ref()
119 .render_pass
120 .cast::<RenderPass<'_>>()
121 .as_mut()
122 }
123 }
124
125 pub fn resources(&mut self) -> (&Device, &mut RenderPass<'_>) {
127 let storage = unsafe { self.storage.as_ref() };
128 let device = unsafe { storage.device.as_ref() };
129 let render_pass = unsafe { storage.render_pass.cast::<RenderPass<'_>>().as_mut() };
130 (device, render_pass)
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub struct WgpuViewportSurfaceConfig {
141 pub present_mode: PresentMode,
143 pub alpha_mode: CompositeAlphaMode,
145 pub desired_maximum_frame_latency: u32,
147}
148
149impl Default for WgpuViewportSurfaceConfig {
150 fn default() -> Self {
151 Self {
152 present_mode: PresentMode::Fifo,
153 alpha_mode: CompositeAlphaMode::Opaque,
154 desired_maximum_frame_latency: 2,
155 }
156 }
157}
158
159impl From<&SurfaceConfiguration> for WgpuViewportSurfaceConfig {
160 fn from(config: &SurfaceConfiguration) -> Self {
161 Self {
162 present_mode: config.present_mode,
163 alpha_mode: config.alpha_mode,
164 desired_maximum_frame_latency: config.desired_maximum_frame_latency,
165 }
166 }
167}
168
169#[derive(Debug, Clone)]
173pub struct WgpuInitInfo {
174 pub instance: Option<Instance>,
176 pub adapter: Option<Adapter>,
178 pub device: Device,
180 pub queue: Queue,
182 pub render_target_format: TextureFormat,
184 pub viewport_surface_config: WgpuViewportSurfaceConfig,
186 pub depth_stencil_format: Option<TextureFormat>,
188 pub pipeline_multisample_state: MultisampleState,
190}
191
192impl WgpuInitInfo {
193 pub fn new(device: Device, queue: Queue, render_target_format: TextureFormat) -> Self {
195 Self {
196 instance: None,
197 adapter: None,
198 device,
199 queue,
200 render_target_format,
201 viewport_surface_config: WgpuViewportSurfaceConfig::default(),
202 depth_stencil_format: None,
203 pipeline_multisample_state: MultisampleState {
204 count: 1,
205 mask: !0,
206 alpha_to_coverage_enabled: false,
207 },
208 }
209 }
210
211 pub fn with_depth_stencil_format(mut self, format: TextureFormat) -> Self {
213 self.depth_stencil_format = Some(format);
214 self
215 }
216
217 pub fn with_multisample_state(mut self, state: MultisampleState) -> Self {
219 self.pipeline_multisample_state = state;
220 self
221 }
222
223 pub fn with_instance(mut self, instance: Instance) -> Self {
225 self.instance = Some(instance);
226 self
227 }
228
229 pub fn with_adapter(mut self, adapter: Adapter) -> Self {
231 self.adapter = Some(adapter);
232 self
233 }
234
235 pub fn with_viewport_surface_config(mut self, config: WgpuViewportSurfaceConfig) -> Self {
237 self.viewport_surface_config = config;
238 self
239 }
240}
241
242pub(crate) struct WgpuBackendData {
246 pub(crate) init_info: WgpuInitInfo,
248 pub(crate) device: Device,
250 pub(crate) queue: Queue,
252 pub(crate) render_target_format: TextureFormat,
254 pub(crate) depth_stencil_format: Option<TextureFormat>,
256 pub(crate) pipeline_state: Option<RenderPipeline>,
258 pub(crate) render_resources: RenderResources,
260 pub(crate) frame_resources: FrameResourceArena,
262 pub(crate) frame_cursor: FrameEpochCursor,
264}
265
266#[derive(Default)]
267pub(crate) struct FrameEpochCursor {
268 epoch: Option<u64>,
269 native_frame_count: Option<i32>,
270}
271
272enum FrameEpochTransition {
273 Reuse,
274 Advance,
275}
276
277impl FrameEpochCursor {
278 fn enter(
279 &mut self,
280 epoch: u64,
281 native_frame_count: i32,
282 ) -> RendererResult<FrameEpochTransition> {
283 if let Some(active_epoch) = self.epoch {
284 if epoch < active_epoch {
285 return Err(RendererError::FrameEpochOutOfOrder {
286 active: active_epoch,
287 received: epoch,
288 });
289 }
290 if epoch == active_epoch {
291 if self.native_frame_count != Some(native_frame_count) {
292 return Err(RendererError::InvalidRenderState(
293 "one WGPU render epoch was observed under multiple Dear ImGui frames"
294 .to_owned(),
295 ));
296 }
297 return Ok(FrameEpochTransition::Reuse);
298 }
299 }
300
301 self.epoch = Some(epoch);
302 self.native_frame_count = Some(native_frame_count);
303 Ok(FrameEpochTransition::Advance)
304 }
305
306 pub(crate) fn is_native_frame(&self, native_frame_count: i32) -> bool {
307 self.native_frame_count == Some(native_frame_count)
308 }
309}
310
311impl WgpuBackendData {
312 pub(crate) fn new(init_info: WgpuInitInfo) -> Self {
314 let queue = init_info.queue.clone();
315 Self {
316 device: init_info.device.clone(),
317 queue,
318 render_target_format: init_info.render_target_format,
319 depth_stencil_format: init_info.depth_stencil_format,
320 pipeline_state: None,
321 render_resources: RenderResources::new(),
322 frame_resources: FrameResourceArena::new(),
323 frame_cursor: FrameEpochCursor::default(),
324 init_info,
325 }
326 }
327
328 pub(crate) fn begin_frame(
330 &mut self,
331 epoch: u64,
332 native_frame_count: i32,
333 ) -> RendererResult<()> {
334 if let FrameEpochTransition::Advance = self.frame_cursor.enter(epoch, native_frame_count)? {
335 self.frame_resources.begin_epoch();
336 }
337 Ok(())
338 }
339
340 pub(crate) fn acquire_frame_resources(&mut self) -> RendererResult<&mut FrameResources> {
341 if self.frame_cursor.epoch.is_none() {
342 return Err(RendererError::FrameNotPrepared);
343 }
344 let frame = self.frame_resources.acquire();
345 frame.ensure_render_bindings(&self.device, &self.render_resources)?;
346 Ok(frame)
347 }
348
349 pub(crate) fn is_initialized(&self) -> bool {
351 self.pipeline_state.is_some()
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn frame_epoch_cursor_is_idempotent_and_rejects_stale_frames() {
361 let mut cursor = FrameEpochCursor::default();
362 assert!(matches!(
363 cursor.enter(4, 12).unwrap(),
364 FrameEpochTransition::Advance
365 ));
366 assert!(matches!(
367 cursor.enter(4, 12).unwrap(),
368 FrameEpochTransition::Reuse
369 ));
370 assert!(matches!(
371 cursor.enter(5, 13).unwrap(),
372 FrameEpochTransition::Advance
373 ));
374 assert!(matches!(
375 cursor.enter(4, 12),
376 Err(RendererError::FrameEpochOutOfOrder {
377 active: 5,
378 received: 4
379 })
380 ));
381 }
382
383 #[test]
384 fn frame_epoch_cursor_rejects_one_epoch_under_two_native_frames() {
385 let mut cursor = FrameEpochCursor::default();
386 cursor.enter(1, 7).unwrap();
387 assert!(matches!(
388 cursor.enter(1, 8),
389 Err(RendererError::InvalidRenderState(_))
390 ));
391 }
392
393 #[test]
394 fn viewport_surface_defaults_are_explicit_and_throughput_safe() {
395 let config = WgpuViewportSurfaceConfig::default();
396 assert_eq!(config.present_mode, PresentMode::Fifo);
397 assert_eq!(config.alpha_mode, CompositeAlphaMode::Opaque);
398 assert_eq!(config.desired_maximum_frame_latency, 2);
399 }
400
401 #[test]
402 fn viewport_surface_config_copies_supported_main_surface_policy() {
403 let surface = SurfaceConfiguration {
404 usage: TextureUsages::RENDER_ATTACHMENT,
405 format: TextureFormat::Bgra8UnormSrgb,
406 #[cfg(feature = "wgpu-30")]
407 color_space: SurfaceColorSpace::DisplayP3,
408 width: 128,
409 height: 96,
410 present_mode: PresentMode::AutoNoVsync,
411 alpha_mode: CompositeAlphaMode::PreMultiplied,
412 view_formats: vec![],
413 desired_maximum_frame_latency: 3,
414 };
415
416 let viewport = WgpuViewportSurfaceConfig::from(&surface);
417 assert_eq!(viewport.present_mode, surface.present_mode);
418 assert_eq!(viewport.alpha_mode, surface.alpha_mode);
419 assert_eq!(
420 viewport.desired_maximum_frame_latency,
421 surface.desired_maximum_frame_latency
422 );
423 }
424}