dear_imgui_wgpu/renderer/
render.rs1#[cfg(feature = "mv-log")]
2use std::sync::{Mutex, OnceLock};
3
4use super::{
5 RendererRenderStateGuard, WgpuRenderer, draw::FramebufferExtent,
6 map_renderer_render_state_error,
7};
8use crate::wgpu;
9use crate::{GammaMode, RendererError, RendererResult, Uniforms};
10use dear_imgui_rs::{
11 ContextBinding,
12 render::{DrawData, PendingFrame, ReconciledFrame},
13 sys,
14};
15use wgpu::RenderPass;
16
17#[allow(unused_macros)]
18macro_rules! mvlog {
19 ($($arg:tt)*) => {
20 if cfg!(feature = "mv-log") { eprintln!($($arg)*); }
21 }
22}
23
24fn with_bound_context<R>(
25 binding: &ContextBinding,
26 callback: impl FnOnce() -> RendererResult<R>,
27) -> RendererResult<R> {
28 binding
29 .try_with_bound_context(callback)
30 .map_err(|_| RendererError::ContextDropped)?
31}
32
33fn platform_io_for_current_context() -> RendererResult<*mut sys::ImGuiPlatformIO> {
34 let context = unsafe { sys::igGetCurrentContext() };
35 let platform_io = unsafe { sys::igGetPlatformIO_ContextPtr(context) };
36 if platform_io.is_null() {
37 Err(RendererError::InvalidRenderState(
38 "bound Dear ImGui context has no PlatformIO".to_owned(),
39 ))
40 } else {
41 Ok(platform_io)
42 }
43}
44
45impl WgpuRenderer {
46 fn ensure_frame_prepared(&self) -> RendererResult<()> {
47 let native_frame_count = unsafe { sys::igGetFrameCount() };
48 match self.backend_data.as_ref() {
49 Some(backend_data)
50 if backend_data
51 .frame_cursor
52 .is_native_frame(native_frame_count) =>
53 {
54 Ok(())
55 }
56 Some(_) => Err(RendererError::FrameNotPrepared),
57 None => Err(RendererError::InvalidRenderState(
58 "Renderer not initialized".to_owned(),
59 )),
60 }
61 }
62
63 fn prepare_frame_bound(&mut self, frame: &PendingFrame<'_>) -> RendererResult<()> {
64 let epoch = frame.epoch();
65 self.prepare_frame_epoch(epoch.sequence(), unsafe { sys::igGetFrameCount() })
66 }
67
68 pub fn render(
74 &mut self,
75 frame: PendingFrame<'_>,
76 render_pass: &mut RenderPass<'_>,
77 framebuffer_extent: FramebufferExtent,
78 ) -> RendererResult<()> {
79 let frame = self.reconcile_frame(frame)?;
80 self.render_reconciled(frame, render_pass, framebuffer_extent)
81 }
82
83 pub fn render_reconciled(
88 &mut self,
89 frame: ReconciledFrame<'_>,
90 render_pass: &mut RenderPass<'_>,
91 framebuffer_extent: FramebufferExtent,
92 ) -> RendererResult<()> {
93 self.ensure_renderer_contract()?;
94 self.ensure_reconciled_frame_matches(&frame)?;
95 let binding = self.bound_context()?;
96 with_bound_context(&binding, || {
97 let platform_io = platform_io_for_current_context()?;
98 self.render_read_only_draw_data_with_fb_size(
99 frame.draw_data(),
100 render_pass,
101 framebuffer_extent.width(),
102 framebuffer_extent.height(),
103 true,
104 platform_io,
105 )
106 })
107 }
108
109 pub fn reconcile_frame<'frame>(
114 &mut self,
115 frame: PendingFrame<'frame>,
116 ) -> RendererResult<ReconciledFrame<'frame>> {
117 self.ensure_renderer_contract()?;
118 self.ensure_pending_frame_matches(&frame)?;
119 let binding = self.bound_context()?;
120 with_bound_context(&binding, || {
121 Self::preflight_draw_callback_support(frame.draw_requirements())?;
122 self.prepare_frame_bound(&frame)?;
123 self.reconcile_frame_bound(frame)
124 })
125 }
126
127 fn reconcile_frame_bound<'frame>(
128 &mut self,
129 frame: PendingFrame<'frame>,
130 ) -> RendererResult<ReconciledFrame<'frame>> {
131 let request_epoch = frame.epoch().sequence();
132 let backend_data = self.backend_data.as_mut().ok_or_else(|| {
133 RendererError::InvalidRenderState("Renderer not initialized".to_owned())
134 })?;
135 let feedback = self.texture_manager.handle_texture_requests(
136 frame.texture_requests(),
137 request_epoch,
138 &backend_data.device,
139 &backend_data.queue,
140 &mut backend_data.render_resources,
141 )?;
142 let frame = frame.reconcile_texture_feedback(feedback)?;
143 self.texture_manager
144 .prune_destroyed_managed_textures(frame.completion_progress().watermark());
145 Ok(frame)
146 }
147
148 pub(super) fn render_read_only_draw_data_with_fb_size(
150 &mut self,
151 draw_data: &DrawData,
152 render_pass: &mut RenderPass<'_>,
153 fb_width: u32,
154 fb_height: u32,
155 main_viewport: bool,
156 platform_io: *mut sys::ImGuiPlatformIO,
157 ) -> RendererResult<()> {
158 self.ensure_frame_prepared()?;
159 self.log_framebuffer_mismatch(draw_data, fb_width, fb_height, main_viewport);
160 let extent = FramebufferExtent::new(fb_width, fb_height);
161 if extent.is_empty() {
162 return Ok(());
163 }
164 self.render_draw_data_at_extent(draw_data, render_pass, extent, platform_io)
165 }
166
167 fn render_draw_data_at_extent(
168 &mut self,
169 draw_data: &DrawData,
170 render_pass: &mut RenderPass<'_>,
171 extent: FramebufferExtent,
172 platform_io: *mut sys::ImGuiPlatformIO,
173 ) -> RendererResult<()> {
174 self.ensure_renderer_contract()?;
175 if !draw_data.valid() {
176 return Ok(());
177 }
178 Self::preflight_draw_callback_support(draw_data.requirements())?;
179 unsafe {
180 RendererRenderStateGuard::<crate::WgpuRenderStateStorage>::preflight(platform_io)
181 }
182 .map_err(map_renderer_render_state_error)?;
183
184 let backend_data = self.backend_data.as_mut().ok_or_else(|| {
185 RendererError::InvalidRenderState("Renderer not initialized".to_owned())
186 })?;
187 let prepared = Self::prepare_draw_data(
188 &self.texture_manager,
189 &self.default_texture,
190 draw_data,
191 extent,
192 backend_data,
193 )?;
194 if prepared.is_empty() {
195 return Ok(());
196 }
197
198 let gamma = match self.gamma_mode {
199 GammaMode::Auto => Uniforms::gamma_for_format(backend_data.render_target_format),
200 GammaMode::Linear => 1.0,
201 GammaMode::Gamma22 => 2.2,
202 };
203 let state = Self::prepare_render_state_static(
204 draw_data,
205 backend_data,
206 gamma,
207 prepared.has_elements(),
208 )?;
209 let device = backend_data.device.clone();
210 Self::execute_prepared_draw_data(
211 prepared,
212 &state,
213 extent,
214 render_pass,
215 platform_io,
216 &device,
217 )
218 }
219
220 #[cfg(feature = "mv-log")]
221 fn log_framebuffer_mismatch(
222 &self,
223 draw_data: &DrawData,
224 fb_width: u32,
225 fb_height: u32,
226 main_viewport: bool,
227 ) {
228 static LAST_MISMATCH: OnceLock<Mutex<Option<(u32, u32, u32, u32, bool)>>> = OnceLock::new();
229 let expected_width = (draw_data.display_size()[0] * draw_data.framebuffer_scale()[0])
230 .round()
231 .max(0.0) as u32;
232 let expected_height = (draw_data.display_size()[1] * draw_data.framebuffer_scale()[1])
233 .round()
234 .max(0.0) as u32;
235 if expected_width == fb_width && expected_height == fb_height {
236 return;
237 }
238 let key = (
239 expected_width,
240 expected_height,
241 fb_width,
242 fb_height,
243 main_viewport,
244 );
245 let mut previous = LAST_MISMATCH
246 .get_or_init(|| Mutex::new(None))
247 .lock()
248 .unwrap();
249 if *previous != Some(key) {
250 mvlog!(
251 "[wgpu-mv] fb mismatch expected=({}, {}) override=({}, {}) disp=({:.1},{:.1}) fb_scale=({:.2},{:.2}) main={}",
252 expected_width,
253 expected_height,
254 fb_width,
255 fb_height,
256 draw_data.display_size()[0],
257 draw_data.display_size()[1],
258 draw_data.framebuffer_scale()[0],
259 draw_data.framebuffer_scale()[1],
260 main_viewport
261 );
262 *previous = Some(key);
263 }
264 }
265
266 #[cfg(not(feature = "mv-log"))]
267 fn log_framebuffer_mismatch(
268 &self,
269 _draw_data: &DrawData,
270 _fb_width: u32,
271 _fb_height: u32,
272 _main_viewport: bool,
273 ) {
274 }
275}