Skip to main content

dear_imgui_wgpu/renderer/
render.rs

1#[cfg(feature = "mv-log")]
2use std::sync::{Mutex, OnceLock};
3
4use super::{ActiveSampler, RendererRenderStateGuard, WgpuRenderer};
5use crate::wgpu;
6use crate::{GammaMode, RendererError, RendererResult, Uniforms};
7use dear_imgui_rs::{Context, TextureId, render::DrawData, sys};
8use wgpu::RenderPass;
9
10#[allow(unused_macros)]
11macro_rules! mvlog {
12    ($($arg:tt)*) => {
13        if cfg!(feature = "mv-log") { eprintln!($($arg)*); }
14    }
15}
16
17impl WgpuRenderer {
18    /// Render Dear ImGui draw data
19    ///
20    /// This corresponds to ImGui_ImplWGPU_RenderDrawData in the C++ implementation
21    pub fn render_draw_data(
22        &mut self,
23        draw_data: &mut DrawData,
24        render_pass: &mut RenderPass,
25    ) -> RendererResult<()> {
26        let platform_io = unsafe { sys::igGetPlatformIO_Nil() };
27        self.render_draw_data_ex(draw_data, render_pass, platform_io)
28    }
29
30    /// Finalize and render the frame for an explicit ImGui context.
31    ///
32    /// This is the preferred entry point for multi-context applications because the temporary
33    /// `PlatformIO.Renderer_RenderState` pointer used by draw callbacks is written to the
34    /// provided context instead of whichever Dear ImGui context is current.
35    pub fn render_context(
36        &mut self,
37        ctx: &mut Context,
38        render_pass: &mut RenderPass,
39    ) -> RendererResult<()> {
40        let platform_io = ctx.platform_io_mut().as_raw_mut();
41        let draw_data = ctx.render();
42        self.render_draw_data_ex(draw_data, render_pass, platform_io)
43    }
44
45    pub(super) fn render_draw_data_ex(
46        &mut self,
47        draw_data: &mut DrawData,
48        render_pass: &mut RenderPass,
49        platform_io: *mut sys::ImGuiPlatformIO,
50    ) -> RendererResult<()> {
51        // Early out if nothing to draw (avoid binding/drawing without buffers)
52        let mut total_vtx_count = 0usize;
53        let mut total_idx_count = 0usize;
54        for dl in draw_data.draw_lists() {
55            total_vtx_count += dl.vtx_buffer().len();
56            total_idx_count += dl.idx_buffer().len();
57        }
58        if total_vtx_count == 0 || total_idx_count == 0 {
59            return Ok(());
60        }
61
62        let backend_data = self.backend_data.as_mut().ok_or_else(|| {
63            RendererError::InvalidRenderState("Renderer not initialized".to_string())
64        })?;
65
66        // Avoid rendering when minimized
67        let fb_width = (draw_data.display_size[0] * draw_data.framebuffer_scale[0]) as i32;
68        let fb_height = (draw_data.display_size[1] * draw_data.framebuffer_scale[1]) as i32;
69        if fb_width <= 0 || fb_height <= 0 || !draw_data.valid() {
70            return Ok(());
71        }
72
73        self.texture_manager.handle_texture_updates(
74            draw_data,
75            &backend_data.device,
76            &backend_data.queue,
77            &mut backend_data.render_resources,
78        );
79
80        // Advance to next frame
81        backend_data.next_frame();
82
83        // Prepare frame resources
84        Self::prepare_frame_resources_static(draw_data, backend_data)?;
85
86        // Compute gamma based on renderer mode
87        let gamma = match self.gamma_mode {
88            GammaMode::Auto => Uniforms::gamma_for_format(backend_data.render_target_format),
89            GammaMode::Linear => 1.0,
90            GammaMode::Gamma22 => 2.2,
91        };
92
93        // Setup render state
94        Self::setup_render_state_static(draw_data, render_pass, backend_data, gamma)?;
95        // Override viewport to the provided framebuffer size to avoid partial viewport issues
96        render_pass.set_viewport(0.0, 0.0, fb_width as f32, fb_height as f32, 0.0, 1.0);
97
98        // Setup render state structure (for callbacks and custom texture bindings)
99        // Note: We need to be careful with lifetimes here, so we'll set it just before rendering
100        // and clear it immediately after
101        unsafe {
102            // Create a temporary render state structure
103            let mut render_state = crate::WgpuRenderState::new(&backend_data.device, render_pass);
104            let _render_state_guard = RendererRenderStateGuard::set(
105                platform_io,
106                &mut render_state as *mut _ as *mut std::ffi::c_void,
107            )?;
108
109            // Render draw lists with the render state exposed
110            let result = Self::render_draw_lists_static(
111                &mut self.texture_manager,
112                &self.default_texture,
113                draw_data,
114                render_pass,
115                backend_data,
116                gamma,
117            );
118
119            if let Err(e) = result {
120                eprintln!("[wgpu-mv] render_draw_lists_static error: {:?}", e);
121                return Err(e);
122            }
123        }
124
125        Ok(())
126    }
127
128    pub fn render_draw_data_with_fb_size(
129        &mut self,
130        draw_data: &mut DrawData,
131        render_pass: &mut RenderPass,
132        fb_width: u32,
133        fb_height: u32,
134    ) -> RendererResult<()> {
135        let platform_io = unsafe { sys::igGetPlatformIO_Nil() };
136        // Public helper used by the main window: advance frame resources as usual.
137        self.render_draw_data_with_fb_size_ex(
138            draw_data,
139            render_pass,
140            fb_width,
141            fb_height,
142            true,
143            platform_io,
144        )
145    }
146
147    /// Finalize and render the frame for an explicit ImGui context and framebuffer size.
148    ///
149    /// Use this variant in multi-context applications when overriding framebuffer dimensions.
150    /// Draw callbacks read the render state through the matching context's `PlatformIO`.
151    pub fn render_context_with_fb_size(
152        &mut self,
153        ctx: &mut Context,
154        render_pass: &mut RenderPass,
155        fb_width: u32,
156        fb_height: u32,
157    ) -> RendererResult<()> {
158        let platform_io = ctx.platform_io_mut().as_raw_mut();
159        let draw_data = ctx.render();
160        self.render_draw_data_with_fb_size_ex(
161            draw_data,
162            render_pass,
163            fb_width,
164            fb_height,
165            true,
166            platform_io,
167        )
168    }
169
170    /// Internal variant that optionally skips advancing the frame index.
171    ///
172    /// When `advance_frame` is `false`, we reuse the current frame resources.
173    pub(super) fn render_draw_data_with_fb_size_ex(
174        &mut self,
175        draw_data: &mut DrawData,
176        render_pass: &mut RenderPass,
177        fb_width: u32,
178        fb_height: u32,
179        advance_frame: bool,
180        platform_io: *mut sys::ImGuiPlatformIO,
181    ) -> RendererResult<()> {
182        // Log only when the override framebuffer size doesn't match the draw data scale.
183        // This helps diagnose HiDPI/viewport scaling issues without spamming per-frame traces.
184        #[cfg(feature = "mv-log")]
185        {
186            static LAST_MISMATCH: OnceLock<Mutex<Option<(u32, u32, u32, u32, bool)>>> =
187                OnceLock::new();
188            let last = LAST_MISMATCH.get_or_init(|| Mutex::new(None));
189            let expected_w = (draw_data.display_size()[0] * draw_data.framebuffer_scale()[0])
190                .round()
191                .max(0.0) as u32;
192            let expected_h = (draw_data.display_size()[1] * draw_data.framebuffer_scale()[1])
193                .round()
194                .max(0.0) as u32;
195            if expected_w != fb_width || expected_h != fb_height {
196                let key = (expected_w, expected_h, fb_width, fb_height, advance_frame);
197                let mut guard = last.lock().unwrap();
198                if *guard != Some(key) {
199                    mvlog!(
200                        "[wgpu-mv] fb mismatch expected=({}, {}) override=({}, {}) disp=({:.1},{:.1}) fb_scale=({:.2},{:.2}) main={}",
201                        expected_w,
202                        expected_h,
203                        fb_width,
204                        fb_height,
205                        draw_data.display_size()[0],
206                        draw_data.display_size()[1],
207                        draw_data.framebuffer_scale()[0],
208                        draw_data.framebuffer_scale()[1],
209                        advance_frame
210                    );
211                    *guard = Some(key);
212                }
213            }
214        }
215        let total_vtx_count: usize = draw_data.draw_lists().map(|dl| dl.vtx_buffer().len()).sum();
216        let total_idx_count: usize = draw_data.draw_lists().map(|dl| dl.idx_buffer().len()).sum();
217        if total_vtx_count == 0 || total_idx_count == 0 {
218            return Ok(());
219        }
220        let backend_data = self.backend_data.as_mut().ok_or_else(|| {
221            RendererError::InvalidRenderState("Renderer not initialized".to_string())
222        })?;
223
224        // Skip if invalid/minimized
225        if fb_width == 0 || fb_height == 0 || !draw_data.valid() {
226            return Ok(());
227        }
228
229        self.texture_manager.handle_texture_updates(
230            draw_data,
231            &backend_data.device,
232            &backend_data.queue,
233            &mut backend_data.render_resources,
234        );
235
236        if advance_frame {
237            backend_data.next_frame();
238        }
239        Self::prepare_frame_resources_static(draw_data, backend_data)?;
240
241        let gamma = match self.gamma_mode {
242            GammaMode::Auto => Uniforms::gamma_for_format(backend_data.render_target_format),
243            GammaMode::Linear => 1.0,
244            GammaMode::Gamma22 => 2.2,
245        };
246
247        Self::setup_render_state_static(draw_data, render_pass, backend_data, gamma)?;
248
249        unsafe {
250            let mut render_state = crate::WgpuRenderState::new(&backend_data.device, render_pass);
251            let _render_state_guard = RendererRenderStateGuard::set(
252                platform_io,
253                &mut render_state as *mut _ as *mut std::ffi::c_void,
254            )?;
255
256            // Reuse core routine but clamp scissor by overriding framebuffer bounds.
257            // Extract common bind group handles up front to avoid borrowing conflicts with render_resources.
258            let device = backend_data.device.clone();
259            let (common_layout, uniform_buffer, default_common_bg, nearest_common_bg) = {
260                let ub = backend_data
261                    .render_resources
262                    .uniform_buffer()
263                    .ok_or_else(|| {
264                        RendererError::InvalidRenderState(
265                            "Uniform buffer not initialized".to_string(),
266                        )
267                    })?;
268                let nearest_bg = backend_data
269                    .render_resources
270                    .nearest_common_bind_group()
271                    .ok_or_else(|| {
272                        RendererError::InvalidRenderState(
273                            "Nearest sampler bind group not initialized".to_string(),
274                        )
275                    })?;
276                (
277                    ub.bind_group_layout().clone(),
278                    ub.buffer().clone(),
279                    ub.bind_group().clone(),
280                    nearest_bg.clone(),
281                )
282            };
283            let mut standard_sampler = ActiveSampler::Linear;
284            let mut current_sampler = ActiveSampler::Linear;
285
286            let mut global_idx_offset: u32 = 0;
287            let mut global_vtx_offset: i32 = 0;
288            let clip_off = draw_data.display_pos();
289            let clip_scale = draw_data.framebuffer_scale();
290            let fbw = fb_width as f32;
291            let fbh = fb_height as f32;
292
293            for draw_list in draw_data.draw_lists() {
294                let vtx_buffer = draw_list.vtx_buffer();
295                let idx_buffer = draw_list.idx_buffer();
296                for cmd in draw_list.commands() {
297                    match cmd {
298                        dear_imgui_rs::render::DrawCmd::Elements {
299                            count,
300                            cmd_params,
301                            raw_cmd,
302                        } => {
303                            // Texture bind group resolution mirrors render_draw_lists_static
304                            // Resolve effective ImTextureID using raw_cmd (modern texture path)
305                            let mut cmd_copy = *raw_cmd;
306                            let tex_id = TextureId::from(dear_imgui_rs::sys::ImDrawCmd_GetTexID(
307                                &mut cmd_copy,
308                            ));
309
310                            // Switch common bind group (sampler) if this texture uses a custom sampler
311                            // or a standard sampler callback changed the default.
312                            let desired_sampler = if tex_id.is_null() {
313                                standard_sampler
314                            } else {
315                                self.texture_manager
316                                    .custom_sampler_id_for_texture(tex_id)
317                                    .map(ActiveSampler::Custom)
318                                    .unwrap_or(standard_sampler)
319                            };
320                            if desired_sampler != current_sampler {
321                                match desired_sampler {
322                                    ActiveSampler::Linear => {
323                                        render_pass.set_bind_group(0, &default_common_bg, &[]);
324                                    }
325                                    ActiveSampler::Nearest => {
326                                        render_pass.set_bind_group(0, &nearest_common_bg, &[]);
327                                    }
328                                    ActiveSampler::Custom(sampler_id) => {
329                                        if let Some(bg0) = self
330                                            .texture_manager
331                                            .get_or_create_common_bind_group_for_sampler(
332                                                &device,
333                                                &common_layout,
334                                                &uniform_buffer,
335                                                sampler_id,
336                                            )
337                                        {
338                                            render_pass.set_bind_group(0, &bg0, &[]);
339                                        } else {
340                                            render_pass.set_bind_group(0, &default_common_bg, &[]);
341                                        }
342                                    }
343                                }
344                                current_sampler = desired_sampler;
345                            }
346
347                            let texture_bind_group = if tex_id.is_null() {
348                                if let Some(default_tex) = &self.default_texture {
349                                    backend_data
350                                        .render_resources
351                                        .get_or_create_image_bind_group(
352                                            &backend_data.device,
353                                            TextureId::null(),
354                                            default_tex,
355                                        )?
356                                        .clone()
357                                } else {
358                                    return Err(RendererError::InvalidRenderState(
359                                        "Default texture not available".to_string(),
360                                    ));
361                                }
362                            } else if let Some(wgpu_texture) =
363                                self.texture_manager.get_texture(tex_id)
364                            {
365                                backend_data
366                                    .render_resources
367                                    .get_or_create_image_bind_group(
368                                        &backend_data.device,
369                                        tex_id,
370                                        &wgpu_texture.texture_view,
371                                    )?
372                                    .clone()
373                            } else if let Some(default_tex) = &self.default_texture {
374                                backend_data
375                                    .render_resources
376                                    .get_or_create_image_bind_group(
377                                        &backend_data.device,
378                                        TextureId::null(),
379                                        default_tex,
380                                    )?
381                                    .clone()
382                            } else {
383                                return Err(RendererError::InvalidRenderState(
384                                    "Texture not found and no default texture".to_string(),
385                                ));
386                            };
387                            render_pass.set_bind_group(1, &texture_bind_group, &[]);
388
389                            // Compute clip rect in framebuffer space
390                            let mut clip_min_x =
391                                (cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0];
392                            let mut clip_min_y =
393                                (cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1];
394                            let mut clip_max_x =
395                                (cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0];
396                            let mut clip_max_y =
397                                (cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1];
398                            // Clamp to override framebuffer bounds
399                            clip_min_x = clip_min_x.max(0.0);
400                            clip_min_y = clip_min_y.max(0.0);
401                            clip_max_x = clip_max_x.min(fbw);
402                            clip_max_y = clip_max_y.min(fbh);
403                            if clip_max_x <= clip_min_x || clip_max_y <= clip_min_y {
404                                continue;
405                            }
406                            render_pass.set_scissor_rect(
407                                clip_min_x as u32,
408                                clip_min_y as u32,
409                                (clip_max_x - clip_min_x) as u32,
410                                (clip_max_y - clip_min_y) as u32,
411                            );
412                            let Ok(count_u32) = u32::try_from(count) else {
413                                continue;
414                            };
415                            let Ok(idx_offset_u32) = u32::try_from(cmd_params.idx_offset) else {
416                                continue;
417                            };
418                            let Some(start_index) = idx_offset_u32.checked_add(global_idx_offset)
419                            else {
420                                continue;
421                            };
422                            let Some(end_index) = start_index.checked_add(count_u32) else {
423                                continue;
424                            };
425                            let Ok(vtx_offset_i32) = i32::try_from(cmd_params.vtx_offset) else {
426                                continue;
427                            };
428                            let Some(vertex_offset) = vtx_offset_i32.checked_add(global_vtx_offset)
429                            else {
430                                continue;
431                            };
432                            render_pass.draw_indexed(start_index..end_index, vertex_offset, 0..1);
433                        }
434                        dear_imgui_rs::render::DrawCmd::ResetRenderState => {
435                            Self::setup_render_state_static(
436                                draw_data,
437                                render_pass,
438                                backend_data,
439                                gamma,
440                            )?;
441                            standard_sampler = ActiveSampler::Linear;
442                            current_sampler = ActiveSampler::Linear;
443                        }
444                        dear_imgui_rs::render::DrawCmd::SetSamplerLinear => {
445                            standard_sampler = ActiveSampler::Linear;
446                            if current_sampler != ActiveSampler::Linear {
447                                render_pass.set_bind_group(0, &default_common_bg, &[]);
448                                current_sampler = ActiveSampler::Linear;
449                            }
450                        }
451                        dear_imgui_rs::render::DrawCmd::SetSamplerNearest => {
452                            standard_sampler = ActiveSampler::Nearest;
453                            if current_sampler != ActiveSampler::Nearest {
454                                render_pass.set_bind_group(0, &nearest_common_bg, &[]);
455                                current_sampler = ActiveSampler::Nearest;
456                            }
457                        }
458                        dear_imgui_rs::render::DrawCmd::RawCallback { .. } => {
459                            // Unsupported raw callbacks; skip.
460                        }
461                    }
462                }
463
464                let idx_len_u32 = u32::try_from(idx_buffer.len())
465                    .map_err(|_| RendererError::DrawBufferTooLarge { buffer: "index" })?;
466                global_idx_offset = global_idx_offset
467                    .checked_add(idx_len_u32)
468                    .ok_or_else(|| RendererError::DrawBufferOffsetOverflow { buffer: "index" })?;
469
470                let vtx_len_i32 = i32::try_from(vtx_buffer.len())
471                    .map_err(|_| RendererError::DrawBufferTooLarge { buffer: "vertex" })?;
472                global_vtx_offset = global_vtx_offset
473                    .checked_add(vtx_len_i32)
474                    .ok_or_else(|| RendererError::DrawBufferOffsetOverflow { buffer: "vertex" })?;
475            }
476        }
477
478        Ok(())
479    }
480}