dear_imgui_rs/context/
frame.rs1use crate::sys;
14
15use super::Context;
16use super::binding::{CTX_MUTEX, with_bound_context};
17
18#[derive(Copy, Clone, Debug, Eq, PartialEq)]
20pub enum FrameLifecycleState {
21 Idle,
23 InFrame,
25 Rendered,
27}
28
29#[derive(Copy, Clone, Debug)]
31pub struct FramePrepareOptions {
32 pub display_size: [f32; 2],
34 pub delta_time: f32,
36 pub framebuffer_scale: Option<[f32; 2]>,
38 pub backend_flags: crate::BackendFlags,
40}
41
42impl FramePrepareOptions {
43 pub fn new(display_size: [f32; 2], delta_time: f32) -> Self {
45 Self {
46 display_size,
47 delta_time,
48 framebuffer_scale: None,
49 backend_flags: crate::BackendFlags::empty(),
50 }
51 }
52
53 #[must_use]
55 pub fn framebuffer_scale(mut self, scale: [f32; 2]) -> Self {
56 self.framebuffer_scale = Some(scale);
57 self
58 }
59
60 #[must_use]
62 pub fn backend_flags(mut self, flags: crate::BackendFlags) -> Self {
63 self.backend_flags |= flags;
64 self
65 }
66
67 #[must_use]
69 pub fn renderer_has_textures(self) -> Self {
70 self.backend_flags(crate::BackendFlags::RENDERER_HAS_TEXTURES)
71 }
72}
73
74#[must_use = "dropping FrameToken ends the frame without rendering; call render() or render_snapshot() to produce draw data"]
81#[doc(alias = "EndFrame")]
82pub struct FrameToken<'ctx> {
83 ctx: &'ctx mut Context,
84 closed: bool,
85}
86
87pub struct FrameResult<'ctx, T> {
89 pub value: T,
91 pub rendered_frame: crate::render::RenderedFrame<'ctx>,
93}
94
95impl Context {
96 pub fn prepare_frame(&mut self, options: FramePrepareOptions) {
102 let io = self.io_mut();
103 io.set_display_size(options.display_size);
104 io.set_delta_time(options.delta_time);
105 if let Some(scale) = options.framebuffer_scale {
106 io.set_display_framebuffer_scale(scale);
107 }
108 if !options.backend_flags.is_empty() {
109 io.set_backend_flags(io.backend_flags() | options.backend_flags);
110 }
111 }
112
113 pub fn frame_lifecycle_state(&self) -> FrameLifecycleState {
115 let _guard = CTX_MUTEX.lock();
116 self.assert_current_context("Context::frame_lifecycle_state()");
117 self.frame_lifecycle_state_unlocked()
118 }
119
120 #[doc(alias = "EndFrame")]
126 pub fn end_frame(&mut self) -> bool {
127 let _guard = CTX_MUTEX.lock();
128 self.assert_current_context("Context::end_frame()");
129 self.end_frame_for_teardown_unlocked()
130 }
131
132 pub(super) fn end_frame_for_teardown_unlocked(&mut self) -> bool {
133 if self.raw.is_null() || !unsafe { (*self.raw).WithinFrameScope } {
134 return false;
135 }
136 unsafe {
137 with_bound_context(self.raw, || {
138 let _ = crate::list_clipper::forget_context_clippers(self.raw);
139 sys::igEndFrame();
140 });
141 }
142 self.texture_registry
143 .borrow_mut()
144 .observe_native_texture_list_refresh();
145 true
146 }
147
148 pub fn begin_frame(&mut self) -> FrameToken<'_> {
154 let _ = self.frame();
155 FrameToken {
156 ctx: self,
157 closed: false,
158 }
159 }
160
161 #[doc(alias = "NewFrame")]
166 pub fn frame(&mut self) -> &mut crate::ui::Ui {
167 let _guard = CTX_MUTEX.lock();
168 self.assert_current_context("Context::frame()");
169 self.assert_can_begin_frame_unlocked("Context::frame()");
170 self.poll_snapshot_completions_or_panic("Context::frame()");
171 self.collect_retired_textures();
172
173 unsafe {
174 let io = sys::igGetIO_Nil();
179 if !io.is_null() && ((*io).DisplaySize.x < 0.0 || (*io).DisplaySize.y < 0.0) {
180 panic!(
181 "Context::frame() called with invalid io.DisplaySize ({}, {}). \
182Set io.DisplaySize (and typically io.DeltaTime) before starting a frame. \
183If you are using a windowing/event-loop library, prefer a platform backend such as \
184dear-imgui-winit::WinitPlatform::prepare_frame().",
185 (*io).DisplaySize.x,
186 (*io).DisplaySize.y
187 );
188 }
189 self.assert_docking_config_stable_unlocked("Context::frame()");
190 #[cfg(feature = "multi-viewport")]
191 self.prepare_multi_viewport_new_frame_contract_unlocked("Context::frame()");
192 crate::fonts::assert_no_font_atlas_texture_borrows((*io).Fonts, "Context::frame()");
193 let renderer_has_textures =
194 ((*io).BackendFlags & sys::ImGuiBackendFlags_RendererHasTextures as i32) != 0;
195 crate::fonts::assert_font_atlas_renderer_mode(
196 (*io).Fonts,
197 renderer_has_textures,
198 "Context::frame()",
199 );
200 if let Some(shared_font_atlas) = &self.shared_font_atlas {
201 shared_font_atlas.prepare_frame(renderer_has_textures);
202 }
203 sys::igNewFrame();
204 }
205 &mut self.ui
206 }
207
208 fn assert_docking_config_stable_unlocked(&self, caller: &str) {
209 let frame_count = unsafe { (*self.raw).FrameCount };
210 if frame_count == 0 {
211 return;
212 }
213
214 let io = self.io_ptr(caller);
215 let requested = unsafe { (*io).ConfigFlags } & sys::ImGuiConfigFlags_DockingEnable as i32;
216 let active = unsafe { (*self.raw).ConfigFlagsCurrFrame }
217 & sys::ImGuiConfigFlags_DockingEnable as i32;
218 assert_eq!(
219 requested, active,
220 "{caller} cannot change ConfigFlags::DOCKING_ENABLE after the first frame; Dear ImGui clears live dock nodes when docking is disabled and cannot restore them safely when it is re-enabled"
221 );
222 }
223
224 pub fn frame_with_result<F, R>(&mut self, f: F) -> FrameResult<'_, R>
230 where
231 F: FnOnce(&crate::ui::Ui) -> R,
232 {
233 let frame = self.begin_frame();
234 let value = f(frame.ui());
235 let rendered_frame = frame.render();
236 FrameResult {
237 value,
238 rendered_frame,
239 }
240 }
241
242 #[doc(alias = "Render", alias = "GetDrawData")]
248 pub fn render(&mut self) -> crate::render::RenderedFrame<'_> {
249 let draw_data = self.render_raw();
250 let renderer_has_textures = unsafe {
251 let io = self.io_ptr("Context::render()");
252 ((*io).BackendFlags & sys::ImGuiBackendFlags_RendererHasTextures as i32) != 0
253 };
254 let (epoch, requests) = if renderer_has_textures {
255 let (epoch, requests) = self
256 .begin_synchronous_render(draw_data.as_ptr().cast_const())
257 .unwrap_or_else(|error| {
258 panic!("Context::render() requires an active synchronous renderer consumer: {error}")
259 });
260 (Some(epoch), requests)
261 } else {
262 (None, Vec::new())
263 };
264 crate::render::RenderedFrame::new(self, draw_data, epoch, requests)
265 }
266
267 fn render_raw(&mut self) -> std::ptr::NonNull<crate::render::DrawData> {
268 let _guard = CTX_MUTEX.lock();
269 self.assert_current_context("Context::render()");
270 self.assert_can_render_unlocked("Context::render()");
271
272 let draw_data = unsafe {
273 let abandoned_clippers = crate::list_clipper::forget_context_clippers(self.raw);
274 if abandoned_clippers != 0 {
275 self.end_frame_for_teardown_unlocked();
276 panic!(
277 "Context::render() rejected a frame with {abandoned_clippers} list clipper token(s) still active or forgotten"
278 );
279 }
280 sys::igRender();
281 let dd = sys::igGetDrawData();
282 if dd.is_null() {
283 panic!("Context::render() returned null draw data");
284 }
285 std::ptr::NonNull::new_unchecked(dd as *mut crate::render::DrawData)
286 };
287 self.texture_registry
288 .borrow_mut()
289 .observe_native_texture_list_refresh();
290 draw_data
291 }
292
293 pub fn render_snapshot(
299 &mut self,
300 consumer: &crate::render::snapshot::RendererConsumer,
301 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
302 {
303 let draw_data = self.render_raw();
304 self.capture_main_snapshot(consumer, draw_data.as_ptr())
305 }
306
307 #[cfg(feature = "multi-viewport")]
316 pub fn render_platform_viewport_snapshot(
317 &mut self,
318 consumer: &crate::render::snapshot::RendererConsumer,
319 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
320 {
321 let _ = self.render_raw();
322 self.platform_viewport_snapshot(consumer)
323 }
324
325 #[cfg(feature = "multi-viewport")]
332 pub fn platform_viewport_snapshot(
333 &mut self,
334 consumer: &crate::render::snapshot::RendererConsumer,
335 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
336 {
337 let _guard = CTX_MUTEX.lock();
338 self.assert_current_context("Context::platform_viewport_snapshot()");
339 if self.frame_lifecycle_state_unlocked() != FrameLifecycleState::Rendered {
340 panic!(
341 "Context::platform_viewport_snapshot() called before rendering the current frame"
342 );
343 }
344 self.capture_platform_snapshot(consumer)
345 }
346
347 pub(super) fn frame_lifecycle_state_unlocked(&self) -> FrameLifecycleState {
348 unsafe {
349 let raw = &*self.raw;
350 if raw.WithinFrameScope {
351 FrameLifecycleState::InFrame
352 } else if raw.FrameCountRendered == raw.FrameCount && raw.FrameCount > 0 {
353 FrameLifecycleState::Rendered
354 } else {
355 FrameLifecycleState::Idle
356 }
357 }
358 }
359
360 fn assert_can_begin_frame_unlocked(&self, caller: &str) {
361 if self.frame_lifecycle_state_unlocked() == FrameLifecycleState::InFrame {
362 panic!("{caller} called while another Dear ImGui frame is already open");
363 }
364 }
365
366 fn assert_can_render_unlocked(&self, caller: &str) {
367 if self.frame_lifecycle_state_unlocked() != FrameLifecycleState::InFrame {
368 panic!("{caller} called without an open Dear ImGui frame");
369 }
370 }
371}
372
373impl<'ctx> FrameToken<'ctx> {
374 pub fn ui(&self) -> &crate::ui::Ui {
379 &self.ctx.ui
380 }
381
382 pub fn lifecycle_state(&self) -> FrameLifecycleState {
384 self.ctx.frame_lifecycle_state()
385 }
386
387 pub fn render(mut self) -> crate::render::RenderedFrame<'ctx> {
389 let ctx = self.ctx as *mut Context;
390 let draw_data = unsafe { (&mut *ctx).render() };
391 self.closed = true;
392 std::mem::forget(self);
393 draw_data
394 }
395
396 pub fn render_snapshot(
401 mut self,
402 consumer: &crate::render::snapshot::RendererConsumer,
403 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
404 {
405 let ctx = self.ctx as *mut Context;
406 let draw_data = unsafe { (&mut *ctx).render_raw().as_ptr() };
407 self.closed = true;
408 std::mem::forget(self);
409 unsafe { (&mut *ctx).capture_main_snapshot(consumer, draw_data) }
410 }
411
412 #[cfg(feature = "multi-viewport")]
414 pub fn render_platform_viewport_snapshot(
415 mut self,
416 consumer: &crate::render::snapshot::RendererConsumer,
417 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
418 {
419 let ctx = self.ctx as *mut Context;
420 let snapshot = unsafe { (&mut *ctx).render_platform_viewport_snapshot(consumer) };
421 self.closed = true;
422 std::mem::forget(self);
423 snapshot
424 }
425}
426
427impl Drop for FrameToken<'_> {
428 fn drop(&mut self) {
429 if self.closed {
430 return;
431 }
432
433 let _guard = CTX_MUTEX.lock();
434 self.ctx.end_frame_for_teardown_unlocked();
435 self.closed = true;
436 }
437}