use crate::sys;
use super::Context;
use super::binding::{CTX_MUTEX, with_bound_context};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FrameLifecycleState {
Idle,
InFrame,
Rendered,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct FrameLifecycleStamp {
context: super::ContextId,
native_frame: i32,
state: FrameLifecycleState,
}
impl FrameLifecycleStamp {
#[must_use]
pub const fn context_id(self) -> super::ContextId {
self.context
}
#[must_use]
pub const fn state(self) -> FrameLifecycleState {
self.state
}
}
#[derive(Copy, Clone, Debug)]
pub struct FramePrepareOptions {
pub display_size: [f32; 2],
pub delta_time: f32,
pub framebuffer_scale: Option<[f32; 2]>,
pub backend_flags: crate::BackendFlags,
}
impl FramePrepareOptions {
pub fn new(display_size: [f32; 2], delta_time: f32) -> Self {
Self {
display_size,
delta_time,
framebuffer_scale: None,
backend_flags: crate::BackendFlags::empty(),
}
}
#[must_use]
pub fn framebuffer_scale(mut self, scale: [f32; 2]) -> Self {
self.framebuffer_scale = Some(scale);
self
}
#[must_use]
pub fn backend_flags(mut self, flags: crate::BackendFlags) -> Self {
self.backend_flags |= flags;
self
}
#[must_use]
pub fn renderer_has_textures(self) -> Self {
self.backend_flags(crate::BackendFlags::RENDERER_HAS_TEXTURES)
}
}
#[must_use = "dropping FrameToken ends the frame without rendering; call render() or render_snapshot() to produce draw data"]
#[doc(alias = "EndFrame")]
pub struct FrameToken<'ctx> {
ctx: &'ctx mut Context,
}
#[must_use = "use the closure result and reconcile the pending frame before drawing"]
pub struct FrameResult<'ctx, T> {
pub value: T,
pub pending_frame: crate::render::PendingFrame<'ctx>,
}
impl<'ctx, T> FrameResult<'ctx, T> {
pub fn into_parts(self) -> (T, crate::render::PendingFrame<'ctx>) {
(self.value, self.pending_frame)
}
}
impl Context {
pub fn prepare_frame(&mut self, options: FramePrepareOptions) {
let io = self.io_mut();
io.set_display_size(options.display_size);
io.set_delta_time(options.delta_time);
if let Some(scale) = options.framebuffer_scale {
io.set_display_framebuffer_scale(scale);
}
if !options.backend_flags.is_empty() {
io.set_backend_flags(io.backend_flags() | options.backend_flags);
}
}
pub fn frame_lifecycle_state(&self) -> FrameLifecycleState {
self.frame_lifecycle_stamp().state()
}
pub fn frame_lifecycle_stamp(&self) -> FrameLifecycleStamp {
let _guard = CTX_MUTEX.lock();
self.assert_current_context("Context::frame_lifecycle_stamp()");
self.frame_lifecycle_stamp_unlocked()
}
#[doc(alias = "EndFrame")]
pub fn end_frame(&mut self) -> bool {
let _guard = CTX_MUTEX.lock();
self.assert_current_context("Context::end_frame()");
self.end_frame_for_teardown_unlocked()
}
pub(super) fn end_frame_for_teardown_unlocked(&mut self) -> bool {
if self.raw.is_null() || !unsafe { (*self.raw).WithinFrameScope } {
return false;
}
unsafe {
with_bound_context(self.raw, || {
let _ = crate::list_clipper::forget_context_clippers(self.raw);
sys::igEndFrame();
});
}
self.texture_registry
.borrow_mut()
.observe_native_texture_list_refresh();
true
}
pub fn begin_frame(&mut self) -> FrameToken<'_> {
self.try_begin_frame()
.unwrap_or_else(|error| panic!("Context::begin_frame() rejected completion: {error}"))
}
pub fn try_begin_frame(
&mut self,
) -> Result<FrameToken<'_>, crate::render::RendererConsumerError> {
let _ = self.try_frame()?;
Ok(FrameToken { ctx: self })
}
#[doc(alias = "NewFrame")]
pub fn frame(&mut self) -> &mut crate::ui::Ui {
self.try_frame()
.unwrap_or_else(|error| panic!("Context::frame() rejected completion: {error}"))
}
pub fn try_frame(
&mut self,
) -> Result<&mut crate::ui::Ui, crate::render::RendererConsumerError> {
let _guard = CTX_MUTEX.lock();
self.assert_current_context("Context::try_frame()");
self.assert_can_begin_frame_unlocked("Context::try_frame()");
self.poll_snapshot_completions_unlocked()?;
self.collect_retired_textures();
unsafe {
let io = sys::igGetIO_Nil();
if !io.is_null() && ((*io).DisplaySize.x < 0.0 || (*io).DisplaySize.y < 0.0) {
panic!(
"Context::try_frame() called with invalid io.DisplaySize ({}, {}). \
Set io.DisplaySize (and typically io.DeltaTime) before starting a frame. \
If you are using a windowing/event-loop library, prefer a platform backend such as \
dear-imgui-winit::WinitPlatform::prepare_frame().",
(*io).DisplaySize.x,
(*io).DisplaySize.y
);
}
self.assert_docking_config_stable_unlocked("Context::try_frame()");
#[cfg(feature = "multi-viewport")]
self.prepare_multi_viewport_new_frame_contract_unlocked("Context::try_frame()");
crate::fonts::assert_no_font_atlas_texture_borrows((*io).Fonts, "Context::try_frame()");
let renderer_has_textures =
((*io).BackendFlags & sys::ImGuiBackendFlags_RendererHasTextures as i32) != 0;
crate::fonts::assert_font_atlas_renderer_mode(
(*io).Fonts,
renderer_has_textures,
"Context::try_frame()",
);
if let Some(shared_font_atlas) = &self.shared_font_atlas {
shared_font_atlas.prepare_frame(renderer_has_textures);
}
sys::igNewFrame();
}
Ok(&mut self.ui)
}
fn assert_docking_config_stable_unlocked(&self, caller: &str) {
let frame_count = unsafe { (*self.raw).FrameCount };
if frame_count == 0 {
return;
}
let io = self.io_ptr(caller);
let requested = unsafe { (*io).ConfigFlags } & sys::ImGuiConfigFlags_DockingEnable as i32;
let active = unsafe { (*self.raw).ConfigFlagsCurrFrame }
& sys::ImGuiConfigFlags_DockingEnable as i32;
assert_eq!(
requested, active,
"{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"
);
}
pub fn frame_with_result<F, R>(
&mut self,
consumer: &crate::render::SynchronousRendererConsumer,
f: F,
) -> FrameResult<'_, R>
where
F: FnOnce(&crate::ui::Ui) -> R,
{
let frame = self.begin_frame();
let value = f(frame.ui());
let pending_frame = frame.render(consumer);
FrameResult {
value,
pending_frame,
}
}
#[doc(alias = "Render", alias = "GetDrawData")]
pub fn render(
&mut self,
consumer: &crate::render::SynchronousRendererConsumer,
) -> crate::render::PendingFrame<'_> {
self.try_render(consumer)
.unwrap_or_else(|error| panic!("Context::render() failed: {error}"))
}
pub fn try_render(
&mut self,
consumer: &crate::render::SynchronousRendererConsumer,
) -> Result<crate::render::PendingFrame<'_>, crate::render::SnapshotError> {
self.require_managed_renderer("Context::try_render()")?;
let draw_data = self.render_raw();
let (epoch, requests) =
self.begin_synchronous_render(consumer, draw_data.as_ptr().cast_const())?;
Ok(crate::render::PendingFrame::new(
self, draw_data, epoch, requests,
))
}
pub fn render_legacy(&mut self) -> crate::render::ReconciledFrame<'_> {
let renderer_has_textures = self
.io()
.backend_flags()
.contains(crate::BackendFlags::RENDERER_HAS_TEXTURES);
assert!(
!renderer_has_textures,
"Context::render_legacy() cannot bypass a managed renderer consumer"
);
let draw_data = self.render_raw();
crate::render::ReconciledFrame::new_legacy(self, draw_data)
}
fn require_managed_renderer(
&self,
caller: &'static str,
) -> Result<(), crate::render::RendererConsumerError> {
if self
.io()
.backend_flags()
.contains(crate::BackendFlags::RENDERER_HAS_TEXTURES)
{
Ok(())
} else {
Err(crate::render::RendererConsumerError::RendererTexturesUnavailable { caller })
}
}
fn render_raw(&mut self) -> std::ptr::NonNull<crate::render::DrawData> {
let _guard = CTX_MUTEX.lock();
self.assert_current_context("Context::render()");
self.assert_can_render_unlocked("Context::render()");
let draw_data = unsafe {
let abandoned_clippers = crate::list_clipper::forget_context_clippers(self.raw);
if abandoned_clippers != 0 {
self.end_frame_for_teardown_unlocked();
panic!(
"Context::render() rejected a frame with {abandoned_clippers} list clipper token(s) still active or forgotten"
);
}
sys::igRender();
let dd = sys::igGetDrawData();
if dd.is_null() {
panic!("Context::render() returned null draw data");
}
std::ptr::NonNull::new_unchecked(dd as *mut crate::render::DrawData)
};
self.texture_registry
.borrow_mut()
.observe_native_texture_list_refresh();
draw_data
}
pub fn render_snapshot(
&mut self,
consumer: &crate::render::snapshot::DetachedRendererConsumer,
) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
{
let draw_data = self.render_raw();
self.capture_main_snapshot(consumer, draw_data.as_ptr())
}
#[cfg(feature = "multi-viewport")]
pub fn render_platform_viewport_snapshot(
&mut self,
consumer: &crate::render::snapshot::DetachedRendererConsumer,
) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
{
let _ = self.render_raw();
self.platform_viewport_snapshot(consumer)
}
#[cfg(feature = "multi-viewport")]
pub fn platform_viewport_snapshot(
&mut self,
consumer: &crate::render::snapshot::DetachedRendererConsumer,
) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
{
let _guard = CTX_MUTEX.lock();
self.assert_current_context("Context::platform_viewport_snapshot()");
if self.frame_lifecycle_state_unlocked() != FrameLifecycleState::Rendered {
panic!(
"Context::platform_viewport_snapshot() called before rendering the current frame"
);
}
self.capture_platform_snapshot(consumer)
}
pub(super) fn frame_lifecycle_state_unlocked(&self) -> FrameLifecycleState {
self.frame_lifecycle_stamp_unlocked().state()
}
fn frame_lifecycle_stamp_unlocked(&self) -> FrameLifecycleStamp {
unsafe {
let raw = &*self.raw;
let state = if raw.WithinFrameScope {
FrameLifecycleState::InFrame
} else if raw.FrameCountRendered == raw.FrameCount && raw.FrameCount > 0 {
FrameLifecycleState::Rendered
} else {
FrameLifecycleState::Idle
};
FrameLifecycleStamp {
context: self.id(),
native_frame: raw.FrameCount,
state,
}
}
}
fn assert_can_begin_frame_unlocked(&self, caller: &str) {
if self.frame_lifecycle_state_unlocked() == FrameLifecycleState::InFrame {
panic!("{caller} called while another Dear ImGui frame is already open");
}
}
fn assert_can_render_unlocked(&self, caller: &str) {
if self.frame_lifecycle_state_unlocked() != FrameLifecycleState::InFrame {
panic!("{caller} called without an open Dear ImGui frame");
}
}
}
impl<'ctx> FrameToken<'ctx> {
pub fn ui(&self) -> &crate::ui::Ui {
&self.ctx.ui
}
pub fn lifecycle_state(&self) -> FrameLifecycleState {
self.ctx.frame_lifecycle_state()
}
pub fn render(
self,
consumer: &crate::render::SynchronousRendererConsumer,
) -> crate::render::PendingFrame<'ctx> {
self.try_render(consumer)
.unwrap_or_else(|error| panic!("FrameToken::render() failed: {error}"))
}
pub fn try_render(
self,
consumer: &crate::render::SynchronousRendererConsumer,
) -> Result<crate::render::PendingFrame<'ctx>, crate::render::SnapshotError> {
let ctx = self.ctx as *mut Context;
match unsafe { (&mut *ctx).try_render(consumer) } {
Ok(frame) => {
std::mem::forget(self);
Ok(frame)
}
Err(error) => Err(error),
}
}
pub fn render_legacy(self) -> crate::render::ReconciledFrame<'ctx> {
let ctx = self.ctx as *mut Context;
let frame = unsafe { (&mut *ctx).render_legacy() };
std::mem::forget(self);
frame
}
pub fn render_snapshot(
self,
consumer: &crate::render::snapshot::DetachedRendererConsumer,
) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
{
let ctx = self.ctx as *mut Context;
let draw_data = unsafe { (&mut *ctx).render_raw().as_ptr() };
std::mem::forget(self);
unsafe { (&mut *ctx).capture_main_snapshot(consumer, draw_data) }
}
#[cfg(feature = "multi-viewport")]
pub fn render_platform_viewport_snapshot(
self,
consumer: &crate::render::snapshot::DetachedRendererConsumer,
) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
{
let ctx = self.ctx as *mut Context;
let snapshot = unsafe { (&mut *ctx).render_platform_viewport_snapshot(consumer) };
std::mem::forget(self);
snapshot
}
}
impl Drop for FrameToken<'_> {
fn drop(&mut self) {
let _guard = CTX_MUTEX.lock();
self.ctx.end_frame_for_teardown_unlocked();
}
}