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)]
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}
85
86#[must_use = "use the closure result and reconcile the pending frame before drawing"]
88pub struct FrameResult<'ctx, T> {
89 pub value: T,
91 pub pending_frame: crate::render::PendingFrame<'ctx>,
93}
94
95impl<'ctx, T> FrameResult<'ctx, T> {
96 pub fn into_parts(self) -> (T, crate::render::PendingFrame<'ctx>) {
98 (self.value, self.pending_frame)
99 }
100}
101
102impl Context {
103 pub fn prepare_frame(&mut self, options: FramePrepareOptions) {
109 let io = self.io_mut();
110 io.set_display_size(options.display_size);
111 io.set_delta_time(options.delta_time);
112 if let Some(scale) = options.framebuffer_scale {
113 io.set_display_framebuffer_scale(scale);
114 }
115 if !options.backend_flags.is_empty() {
116 io.set_backend_flags(io.backend_flags() | options.backend_flags);
117 }
118 }
119
120 pub fn frame_lifecycle_state(&self) -> FrameLifecycleState {
122 let _guard = CTX_MUTEX.lock();
123 self.assert_current_context("Context::frame_lifecycle_state()");
124 self.frame_lifecycle_state_unlocked()
125 }
126
127 #[doc(alias = "EndFrame")]
133 pub fn end_frame(&mut self) -> bool {
134 let _guard = CTX_MUTEX.lock();
135 self.assert_current_context("Context::end_frame()");
136 self.end_frame_for_teardown_unlocked()
137 }
138
139 pub(super) fn end_frame_for_teardown_unlocked(&mut self) -> bool {
140 if self.raw.is_null() || !unsafe { (*self.raw).WithinFrameScope } {
141 return false;
142 }
143 unsafe {
144 with_bound_context(self.raw, || {
145 let _ = crate::list_clipper::forget_context_clippers(self.raw);
146 sys::igEndFrame();
147 });
148 }
149 self.texture_registry
150 .borrow_mut()
151 .observe_native_texture_list_refresh();
152 true
153 }
154
155 pub fn begin_frame(&mut self) -> FrameToken<'_> {
161 self.try_begin_frame()
162 .unwrap_or_else(|error| panic!("Context::begin_frame() rejected completion: {error}"))
163 }
164
165 pub fn try_begin_frame(
167 &mut self,
168 ) -> Result<FrameToken<'_>, crate::render::RendererConsumerError> {
169 let _ = self.try_frame()?;
170 Ok(FrameToken { ctx: self })
171 }
172
173 #[doc(alias = "NewFrame")]
178 pub fn frame(&mut self) -> &mut crate::ui::Ui {
179 self.try_frame()
180 .unwrap_or_else(|error| panic!("Context::frame() rejected completion: {error}"))
181 }
182
183 pub fn try_frame(
189 &mut self,
190 ) -> Result<&mut crate::ui::Ui, crate::render::RendererConsumerError> {
191 let _guard = CTX_MUTEX.lock();
192 self.assert_current_context("Context::try_frame()");
193 self.assert_can_begin_frame_unlocked("Context::try_frame()");
194 self.poll_snapshot_completions_unlocked()?;
195 self.collect_retired_textures();
196
197 unsafe {
198 let io = sys::igGetIO_Nil();
203 if !io.is_null() && ((*io).DisplaySize.x < 0.0 || (*io).DisplaySize.y < 0.0) {
204 panic!(
205 "Context::try_frame() called with invalid io.DisplaySize ({}, {}). \
206Set io.DisplaySize (and typically io.DeltaTime) before starting a frame. \
207If you are using a windowing/event-loop library, prefer a platform backend such as \
208dear-imgui-winit::WinitPlatform::prepare_frame().",
209 (*io).DisplaySize.x,
210 (*io).DisplaySize.y
211 );
212 }
213 self.assert_docking_config_stable_unlocked("Context::try_frame()");
214 #[cfg(feature = "multi-viewport")]
215 self.prepare_multi_viewport_new_frame_contract_unlocked("Context::try_frame()");
216 crate::fonts::assert_no_font_atlas_texture_borrows((*io).Fonts, "Context::try_frame()");
217 let renderer_has_textures =
218 ((*io).BackendFlags & sys::ImGuiBackendFlags_RendererHasTextures as i32) != 0;
219 crate::fonts::assert_font_atlas_renderer_mode(
220 (*io).Fonts,
221 renderer_has_textures,
222 "Context::try_frame()",
223 );
224 if let Some(shared_font_atlas) = &self.shared_font_atlas {
225 shared_font_atlas.prepare_frame(renderer_has_textures);
226 }
227 sys::igNewFrame();
228 }
229 Ok(&mut self.ui)
230 }
231
232 fn assert_docking_config_stable_unlocked(&self, caller: &str) {
233 let frame_count = unsafe { (*self.raw).FrameCount };
234 if frame_count == 0 {
235 return;
236 }
237
238 let io = self.io_ptr(caller);
239 let requested = unsafe { (*io).ConfigFlags } & sys::ImGuiConfigFlags_DockingEnable as i32;
240 let active = unsafe { (*self.raw).ConfigFlagsCurrFrame }
241 & sys::ImGuiConfigFlags_DockingEnable as i32;
242 assert_eq!(
243 requested, active,
244 "{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"
245 );
246 }
247
248 pub fn frame_with_result<F, R>(
254 &mut self,
255 consumer: &crate::render::SynchronousRendererConsumer,
256 f: F,
257 ) -> FrameResult<'_, R>
258 where
259 F: FnOnce(&crate::ui::Ui) -> R,
260 {
261 let frame = self.begin_frame();
262 let value = f(frame.ui());
263 let pending_frame = frame.render(consumer);
264 FrameResult {
265 value,
266 pending_frame,
267 }
268 }
269
270 #[doc(alias = "Render", alias = "GetDrawData")]
272 pub fn render(
273 &mut self,
274 consumer: &crate::render::SynchronousRendererConsumer,
275 ) -> crate::render::PendingFrame<'_> {
276 self.try_render(consumer)
277 .unwrap_or_else(|error| panic!("Context::render() failed: {error}"))
278 }
279
280 pub fn try_render(
282 &mut self,
283 consumer: &crate::render::SynchronousRendererConsumer,
284 ) -> Result<crate::render::PendingFrame<'_>, crate::render::SnapshotError> {
285 self.require_managed_renderer("Context::try_render()")?;
286 let draw_data = self.render_raw();
287 let (epoch, requests) =
288 self.begin_synchronous_render(consumer, draw_data.as_ptr().cast_const())?;
289 Ok(crate::render::PendingFrame::new(
290 self, draw_data, epoch, requests,
291 ))
292 }
293
294 pub fn render_legacy(&mut self) -> crate::render::ReconciledFrame<'_> {
301 let renderer_has_textures = self
302 .io()
303 .backend_flags()
304 .contains(crate::BackendFlags::RENDERER_HAS_TEXTURES);
305 assert!(
306 !renderer_has_textures,
307 "Context::render_legacy() cannot bypass a managed renderer consumer"
308 );
309 let draw_data = self.render_raw();
310 crate::render::ReconciledFrame::new_legacy(self, draw_data)
311 }
312
313 fn require_managed_renderer(
314 &self,
315 caller: &'static str,
316 ) -> Result<(), crate::render::RendererConsumerError> {
317 if self
318 .io()
319 .backend_flags()
320 .contains(crate::BackendFlags::RENDERER_HAS_TEXTURES)
321 {
322 Ok(())
323 } else {
324 Err(crate::render::RendererConsumerError::RendererTexturesUnavailable { caller })
325 }
326 }
327
328 fn render_raw(&mut self) -> std::ptr::NonNull<crate::render::DrawData> {
329 let _guard = CTX_MUTEX.lock();
330 self.assert_current_context("Context::render()");
331 self.assert_can_render_unlocked("Context::render()");
332
333 let draw_data = unsafe {
334 let abandoned_clippers = crate::list_clipper::forget_context_clippers(self.raw);
335 if abandoned_clippers != 0 {
336 self.end_frame_for_teardown_unlocked();
337 panic!(
338 "Context::render() rejected a frame with {abandoned_clippers} list clipper token(s) still active or forgotten"
339 );
340 }
341 sys::igRender();
342 let dd = sys::igGetDrawData();
343 if dd.is_null() {
344 panic!("Context::render() returned null draw data");
345 }
346 std::ptr::NonNull::new_unchecked(dd as *mut crate::render::DrawData)
347 };
348 self.texture_registry
349 .borrow_mut()
350 .observe_native_texture_list_refresh();
351 draw_data
352 }
353
354 pub fn render_snapshot(
360 &mut self,
361 consumer: &crate::render::snapshot::DetachedRendererConsumer,
362 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
363 {
364 let draw_data = self.render_raw();
365 self.capture_main_snapshot(consumer, draw_data.as_ptr())
366 }
367
368 #[cfg(feature = "multi-viewport")]
377 pub fn render_platform_viewport_snapshot(
378 &mut self,
379 consumer: &crate::render::snapshot::DetachedRendererConsumer,
380 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
381 {
382 let _ = self.render_raw();
383 self.platform_viewport_snapshot(consumer)
384 }
385
386 #[cfg(feature = "multi-viewport")]
393 pub fn platform_viewport_snapshot(
394 &mut self,
395 consumer: &crate::render::snapshot::DetachedRendererConsumer,
396 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
397 {
398 let _guard = CTX_MUTEX.lock();
399 self.assert_current_context("Context::platform_viewport_snapshot()");
400 if self.frame_lifecycle_state_unlocked() != FrameLifecycleState::Rendered {
401 panic!(
402 "Context::platform_viewport_snapshot() called before rendering the current frame"
403 );
404 }
405 self.capture_platform_snapshot(consumer)
406 }
407
408 pub(super) fn frame_lifecycle_state_unlocked(&self) -> FrameLifecycleState {
409 unsafe {
410 let raw = &*self.raw;
411 if raw.WithinFrameScope {
412 FrameLifecycleState::InFrame
413 } else if raw.FrameCountRendered == raw.FrameCount && raw.FrameCount > 0 {
414 FrameLifecycleState::Rendered
415 } else {
416 FrameLifecycleState::Idle
417 }
418 }
419 }
420
421 fn assert_can_begin_frame_unlocked(&self, caller: &str) {
422 if self.frame_lifecycle_state_unlocked() == FrameLifecycleState::InFrame {
423 panic!("{caller} called while another Dear ImGui frame is already open");
424 }
425 }
426
427 fn assert_can_render_unlocked(&self, caller: &str) {
428 if self.frame_lifecycle_state_unlocked() != FrameLifecycleState::InFrame {
429 panic!("{caller} called without an open Dear ImGui frame");
430 }
431 }
432}
433
434impl<'ctx> FrameToken<'ctx> {
435 pub fn ui(&self) -> &crate::ui::Ui {
440 &self.ctx.ui
441 }
442
443 pub fn lifecycle_state(&self) -> FrameLifecycleState {
445 self.ctx.frame_lifecycle_state()
446 }
447
448 pub fn render(
450 self,
451 consumer: &crate::render::SynchronousRendererConsumer,
452 ) -> crate::render::PendingFrame<'ctx> {
453 self.try_render(consumer)
454 .unwrap_or_else(|error| panic!("FrameToken::render() failed: {error}"))
455 }
456
457 pub fn try_render(
459 self,
460 consumer: &crate::render::SynchronousRendererConsumer,
461 ) -> Result<crate::render::PendingFrame<'ctx>, crate::render::SnapshotError> {
462 let ctx = self.ctx as *mut Context;
463 match unsafe { (&mut *ctx).try_render(consumer) } {
464 Ok(frame) => {
465 std::mem::forget(self);
466 Ok(frame)
467 }
468 Err(error) => Err(error),
469 }
470 }
471
472 pub fn render_legacy(self) -> crate::render::ReconciledFrame<'ctx> {
474 let ctx = self.ctx as *mut Context;
475 let frame = unsafe { (&mut *ctx).render_legacy() };
476 std::mem::forget(self);
477 frame
478 }
479
480 pub fn render_snapshot(
485 self,
486 consumer: &crate::render::snapshot::DetachedRendererConsumer,
487 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
488 {
489 let ctx = self.ctx as *mut Context;
490 let draw_data = unsafe { (&mut *ctx).render_raw().as_ptr() };
491 std::mem::forget(self);
492 unsafe { (&mut *ctx).capture_main_snapshot(consumer, draw_data) }
493 }
494
495 #[cfg(feature = "multi-viewport")]
497 pub fn render_platform_viewport_snapshot(
498 self,
499 consumer: &crate::render::snapshot::DetachedRendererConsumer,
500 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
501 {
502 let ctx = self.ctx as *mut Context;
503 let snapshot = unsafe { (&mut *ctx).render_platform_viewport_snapshot(consumer) };
504 std::mem::forget(self);
505 snapshot
506 }
507}
508
509impl Drop for FrameToken<'_> {
510 fn drop(&mut self) {
511 let _guard = CTX_MUTEX.lock();
512 self.ctx.end_frame_for_teardown_unlocked();
513 }
514}