1use 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, Eq, PartialEq)]
35pub struct FrameLifecycleStamp {
36 context: super::ContextId,
37 native_frame: i32,
38 state: FrameLifecycleState,
39}
40
41impl FrameLifecycleStamp {
42 #[must_use]
44 pub const fn context_id(self) -> super::ContextId {
45 self.context
46 }
47
48 #[must_use]
50 pub const fn state(self) -> FrameLifecycleState {
51 self.state
52 }
53}
54
55#[derive(Copy, Clone, Debug)]
57pub struct FramePrepareOptions {
58 pub display_size: [f32; 2],
60 pub delta_time: f32,
62 pub framebuffer_scale: Option<[f32; 2]>,
64 pub backend_flags: crate::BackendFlags,
66}
67
68impl FramePrepareOptions {
69 pub fn new(display_size: [f32; 2], delta_time: f32) -> Self {
71 Self {
72 display_size,
73 delta_time,
74 framebuffer_scale: None,
75 backend_flags: crate::BackendFlags::empty(),
76 }
77 }
78
79 #[must_use]
81 pub fn framebuffer_scale(mut self, scale: [f32; 2]) -> Self {
82 self.framebuffer_scale = Some(scale);
83 self
84 }
85
86 #[must_use]
88 pub fn backend_flags(mut self, flags: crate::BackendFlags) -> Self {
89 self.backend_flags |= flags;
90 self
91 }
92
93 #[must_use]
95 pub fn renderer_has_textures(self) -> Self {
96 self.backend_flags(crate::BackendFlags::RENDERER_HAS_TEXTURES)
97 }
98}
99
100#[must_use = "dropping FrameToken ends the frame without rendering; call render() or render_snapshot() to produce draw data"]
107#[doc(alias = "EndFrame")]
108pub struct FrameToken<'ctx> {
109 ctx: &'ctx mut Context,
110}
111
112#[must_use = "use the closure result and reconcile the pending frame before drawing"]
114pub struct FrameResult<'ctx, T> {
115 pub value: T,
117 pub pending_frame: crate::render::PendingFrame<'ctx>,
119}
120
121impl<'ctx, T> FrameResult<'ctx, T> {
122 pub fn into_parts(self) -> (T, crate::render::PendingFrame<'ctx>) {
124 (self.value, self.pending_frame)
125 }
126}
127
128impl Context {
129 pub fn prepare_frame(&mut self, options: FramePrepareOptions) {
135 let io = self.io_mut();
136 io.set_display_size(options.display_size);
137 io.set_delta_time(options.delta_time);
138 if let Some(scale) = options.framebuffer_scale {
139 io.set_display_framebuffer_scale(scale);
140 }
141 if !options.backend_flags.is_empty() {
142 io.set_backend_flags(io.backend_flags() | options.backend_flags);
143 }
144 }
145
146 pub fn frame_lifecycle_state(&self) -> FrameLifecycleState {
148 self.frame_lifecycle_stamp().state()
149 }
150
151 pub fn frame_lifecycle_stamp(&self) -> FrameLifecycleStamp {
156 let _guard = CTX_MUTEX.lock();
157 self.assert_current_context("Context::frame_lifecycle_stamp()");
158 self.frame_lifecycle_stamp_unlocked()
159 }
160
161 #[doc(alias = "EndFrame")]
167 pub fn end_frame(&mut self) -> bool {
168 let _guard = CTX_MUTEX.lock();
169 self.assert_current_context("Context::end_frame()");
170 self.end_frame_for_teardown_unlocked()
171 }
172
173 pub(super) fn end_frame_for_teardown_unlocked(&mut self) -> bool {
174 if self.raw.is_null() || !unsafe { (*self.raw).WithinFrameScope } {
175 return false;
176 }
177 self.clear_ime_preedit();
178 unsafe {
179 with_bound_context(self.raw, || {
180 let _ = crate::list_clipper::forget_context_clippers(self.raw);
181 sys::igEndFrame();
182 });
183 }
184 self.texture_registry
185 .borrow_mut()
186 .observe_native_texture_list_refresh();
187 true
188 }
189
190 pub fn begin_frame(&mut self) -> FrameToken<'_> {
196 self.try_begin_frame()
197 .unwrap_or_else(|error| panic!("Context::begin_frame() rejected completion: {error}"))
198 }
199
200 pub fn try_begin_frame(
202 &mut self,
203 ) -> Result<FrameToken<'_>, crate::render::RendererConsumerError> {
204 let _ = self.try_frame()?;
205 Ok(FrameToken { ctx: self })
206 }
207
208 #[doc(alias = "NewFrame")]
213 pub fn frame(&mut self) -> &mut crate::ui::Ui {
214 self.try_frame()
215 .unwrap_or_else(|error| panic!("Context::frame() rejected completion: {error}"))
216 }
217
218 pub fn try_frame(
224 &mut self,
225 ) -> Result<&mut crate::ui::Ui, crate::render::RendererConsumerError> {
226 let _guard = CTX_MUTEX.lock();
227 self.assert_current_context("Context::try_frame()");
228 self.assert_can_begin_frame_unlocked("Context::try_frame()");
229 self.poll_snapshot_completions_unlocked()?;
230 self.collect_retired_textures();
231
232 unsafe {
233 let io = sys::igGetIO_Nil();
238 if !io.is_null() && ((*io).DisplaySize.x < 0.0 || (*io).DisplaySize.y < 0.0) {
239 panic!(
240 "Context::try_frame() called with invalid io.DisplaySize ({}, {}). \
241Set io.DisplaySize (and typically io.DeltaTime) before starting a frame. \
242If you are using a windowing/event-loop library, prefer a platform backend such as \
243dear-imgui-winit::WinitPlatform::prepare_frame().",
244 (*io).DisplaySize.x,
245 (*io).DisplaySize.y
246 );
247 }
248 self.assert_docking_config_stable_unlocked("Context::try_frame()");
249 #[cfg(feature = "multi-viewport")]
250 self.prepare_multi_viewport_new_frame_contract_unlocked("Context::try_frame()");
251 crate::fonts::assert_no_font_atlas_texture_borrows((*io).Fonts, "Context::try_frame()");
252 let renderer_has_textures =
253 ((*io).BackendFlags & sys::ImGuiBackendFlags_RendererHasTextures as i32) != 0;
254 crate::fonts::assert_font_atlas_renderer_mode(
255 (*io).Fonts,
256 renderer_has_textures,
257 "Context::try_frame()",
258 );
259 if let Some(shared_font_atlas) = &self.shared_font_atlas {
260 shared_font_atlas.prepare_frame(renderer_has_textures);
261 }
262 sys::igNewFrame();
263 }
264 Ok(&mut self.ui)
265 }
266
267 fn assert_docking_config_stable_unlocked(&self, caller: &str) {
268 let frame_count = unsafe { (*self.raw).FrameCount };
269 if frame_count == 0 {
270 return;
271 }
272
273 let io = self.io_ptr(caller);
274 let requested = unsafe { (*io).ConfigFlags } & sys::ImGuiConfigFlags_DockingEnable as i32;
275 let active = unsafe { (*self.raw).ConfigFlagsCurrFrame }
276 & sys::ImGuiConfigFlags_DockingEnable as i32;
277 assert_eq!(
278 requested, active,
279 "{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"
280 );
281 }
282
283 pub fn frame_with_result<F, R>(
289 &mut self,
290 consumer: &crate::render::SynchronousRendererConsumer,
291 f: F,
292 ) -> FrameResult<'_, R>
293 where
294 F: FnOnce(&crate::ui::Ui) -> R,
295 {
296 let frame = self.begin_frame();
297 let value = f(frame.ui());
298 let pending_frame = frame.render(consumer);
299 FrameResult {
300 value,
301 pending_frame,
302 }
303 }
304
305 #[doc(alias = "Render", alias = "GetDrawData")]
307 pub fn render(
308 &mut self,
309 consumer: &crate::render::SynchronousRendererConsumer,
310 ) -> crate::render::PendingFrame<'_> {
311 self.try_render(consumer)
312 .unwrap_or_else(|error| panic!("Context::render() failed: {error}"))
313 }
314
315 pub fn try_render(
317 &mut self,
318 consumer: &crate::render::SynchronousRendererConsumer,
319 ) -> Result<crate::render::PendingFrame<'_>, crate::render::SnapshotError> {
320 self.require_managed_renderer("Context::try_render()")?;
321 let draw_data = self.render_raw();
322 let (epoch, requests) =
323 self.begin_synchronous_render(consumer, draw_data.as_ptr().cast_const())?;
324 Ok(crate::render::PendingFrame::new(
325 self, draw_data, epoch, requests,
326 ))
327 }
328
329 pub fn render_legacy(&mut self) -> crate::render::ReconciledFrame<'_> {
336 let renderer_has_textures = self
337 .io()
338 .backend_flags()
339 .contains(crate::BackendFlags::RENDERER_HAS_TEXTURES);
340 assert!(
341 !renderer_has_textures,
342 "Context::render_legacy() cannot bypass a managed renderer consumer"
343 );
344 let draw_data = self.render_raw();
345 crate::render::ReconciledFrame::new_legacy(self, draw_data)
346 }
347
348 fn require_managed_renderer(
349 &self,
350 caller: &'static str,
351 ) -> Result<(), crate::render::RendererConsumerError> {
352 if self
353 .io()
354 .backend_flags()
355 .contains(crate::BackendFlags::RENDERER_HAS_TEXTURES)
356 {
357 Ok(())
358 } else {
359 Err(crate::render::RendererConsumerError::RendererTexturesUnavailable { caller })
360 }
361 }
362
363 fn render_raw(&mut self) -> std::ptr::NonNull<crate::render::DrawData> {
364 let _guard = CTX_MUTEX.lock();
365 self.assert_current_context("Context::render()");
366 self.assert_can_render_unlocked("Context::render()");
367
368 let draw_data = unsafe {
369 let abandoned_clippers = crate::list_clipper::forget_context_clippers(self.raw);
370 if abandoned_clippers != 0 {
371 self.end_frame_for_teardown_unlocked();
372 panic!(
373 "Context::render() rejected a frame with {abandoned_clippers} list clipper token(s) still active or forgotten"
374 );
375 }
376 if (*self.raw).WithinFrameScope {
377 self.render_ime_preedit();
378 }
379 sys::igRender();
380 let dd = sys::igGetDrawData();
381 if dd.is_null() {
382 panic!("Context::render() returned null draw data");
383 }
384 std::ptr::NonNull::new_unchecked(dd as *mut crate::render::DrawData)
385 };
386 self.texture_registry
387 .borrow_mut()
388 .observe_native_texture_list_refresh();
389 draw_data
390 }
391
392 pub fn render_snapshot(
398 &mut self,
399 consumer: &crate::render::snapshot::DetachedRendererConsumer,
400 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
401 {
402 let draw_data = self.render_raw();
403 self.capture_main_snapshot(consumer, draw_data.as_ptr())
404 }
405
406 #[cfg(feature = "multi-viewport")]
416 pub fn render_platform_viewport_snapshot(
417 &mut self,
418 consumer: &crate::render::snapshot::DetachedRendererConsumer,
419 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
420 {
421 let _ = self.render_raw();
422 self.platform_viewport_snapshot(consumer)
423 }
424
425 #[cfg(feature = "multi-viewport")]
432 pub fn platform_viewport_snapshot(
433 &mut self,
434 consumer: &crate::render::snapshot::DetachedRendererConsumer,
435 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
436 {
437 let _guard = CTX_MUTEX.lock();
438 self.assert_current_context("Context::platform_viewport_snapshot()");
439 if self.frame_lifecycle_state_unlocked() != FrameLifecycleState::Rendered {
440 panic!(
441 "Context::platform_viewport_snapshot() called before rendering the current frame"
442 );
443 }
444 self.capture_platform_snapshot(consumer)
445 }
446
447 pub(super) fn frame_lifecycle_state_unlocked(&self) -> FrameLifecycleState {
448 self.frame_lifecycle_stamp_unlocked().state()
449 }
450
451 fn frame_lifecycle_stamp_unlocked(&self) -> FrameLifecycleStamp {
452 unsafe {
453 let raw = &*self.raw;
454 let state = if raw.WithinFrameScope {
455 FrameLifecycleState::InFrame
456 } else if raw.FrameCountRendered == raw.FrameCount && raw.FrameCount > 0 {
457 FrameLifecycleState::Rendered
458 } else {
459 FrameLifecycleState::Idle
460 };
461 FrameLifecycleStamp {
462 context: self.id(),
463 native_frame: raw.FrameCount,
464 state,
465 }
466 }
467 }
468
469 fn assert_can_begin_frame_unlocked(&self, caller: &str) {
470 if self.frame_lifecycle_state_unlocked() == FrameLifecycleState::InFrame {
471 panic!("{caller} called while another Dear ImGui frame is already open");
472 }
473 }
474
475 fn assert_can_render_unlocked(&self, caller: &str) {
476 if self.frame_lifecycle_state_unlocked() != FrameLifecycleState::InFrame {
477 panic!("{caller} called without an open Dear ImGui frame");
478 }
479 }
480}
481
482impl<'ctx> FrameToken<'ctx> {
483 pub fn ui(&self) -> &crate::ui::Ui {
488 &self.ctx.ui
489 }
490
491 pub fn lifecycle_state(&self) -> FrameLifecycleState {
493 self.ctx.frame_lifecycle_state()
494 }
495
496 pub fn render(
498 self,
499 consumer: &crate::render::SynchronousRendererConsumer,
500 ) -> crate::render::PendingFrame<'ctx> {
501 self.try_render(consumer)
502 .unwrap_or_else(|error| panic!("FrameToken::render() failed: {error}"))
503 }
504
505 pub fn try_render(
507 self,
508 consumer: &crate::render::SynchronousRendererConsumer,
509 ) -> Result<crate::render::PendingFrame<'ctx>, crate::render::SnapshotError> {
510 let ctx = self.ctx as *mut Context;
511 match unsafe { (&mut *ctx).try_render(consumer) } {
512 Ok(frame) => {
513 std::mem::forget(self);
514 Ok(frame)
515 }
516 Err(error) => Err(error),
517 }
518 }
519
520 pub fn render_legacy(self) -> crate::render::ReconciledFrame<'ctx> {
522 let ctx = self.ctx as *mut Context;
523 let frame = unsafe { (&mut *ctx).render_legacy() };
524 std::mem::forget(self);
525 frame
526 }
527
528 pub fn render_snapshot(
533 self,
534 consumer: &crate::render::snapshot::DetachedRendererConsumer,
535 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
536 {
537 let ctx = self.ctx as *mut Context;
538 let draw_data = unsafe { (&mut *ctx).render_raw().as_ptr() };
539 std::mem::forget(self);
540 unsafe { (&mut *ctx).capture_main_snapshot(consumer, draw_data) }
541 }
542
543 #[cfg(feature = "multi-viewport")]
545 pub fn render_platform_viewport_snapshot(
546 self,
547 consumer: &crate::render::snapshot::DetachedRendererConsumer,
548 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
549 {
550 let ctx = self.ctx as *mut Context;
551 let snapshot = unsafe { (&mut *ctx).render_platform_viewport_snapshot(consumer) };
552 std::mem::forget(self);
553 snapshot
554 }
555}
556
557impl Drop for FrameToken<'_> {
558 fn drop(&mut self) {
559 let _guard = CTX_MUTEX.lock();
560 self.ctx.end_frame_for_teardown_unlocked();
561 }
562}