Compare commits

...

2 Commits

5 changed files with 66 additions and 11 deletions

View File

@ -56,9 +56,13 @@ use {
cgmath::Point2,
chrono::TimeDelta,
clap::Parser,
core::time::Duration,
directories::ProjectDirs,
std::{collections::HashMap, fs, sync::Arc, time::Instant},
tokio::sync::{Mutex, mpsc},
std::{collections::HashMap, fs, sync::Arc},
tokio::{
sync::{Mutex, mpsc},
time::Instant,
},
winit::{
application::ApplicationHandler,
dpi::{PhysicalPosition, PhysicalSize},
@ -266,6 +270,7 @@ impl WinitApp {
window,
monitor_name: app_window.monitor_name,
monitor_geometry: Mutex::new(app_window.monitor_geometry),
monitor_refresh_rate_mhz: app_window.monitor_refresh_rate_mhz,
});
let (renderer_event_tx, renderer_event_rx) = mpsc::unbounded_channel();
@ -318,6 +323,7 @@ enum RendererEvent {
}
/// Renderer task
#[expect(clippy::too_many_lines, reason = "TODO: Split it up")]
async fn renderer(
shared: &Shared,
shared_window: &SharedWindow,
@ -328,7 +334,40 @@ async fn renderer(
egui_painter: EguiPainter,
mut menu: Menu,
) -> Result<(), AppError> {
let frame_duration = Duration::from_secs_f64(1000.0) / shared_window.monitor_refresh_rate_mhz;
tracing::info!(
"Window {:?} refresh rate: {:.2} Hz",
shared_window.monitor_name,
f64::from(shared_window.monitor_refresh_rate_mhz) / 1000.0,
);
tracing::info!(
"Window {:?} frame duration: {frame_duration:.2?}",
shared_window.monitor_name
);
let mut next_frame = Instant::now();
loop {
let prev_frame_end = Instant::now();
let cur_frame_start = next_frame;
next_frame += frame_duration;
// Wait until the start of the next frame.
// Note: We manually sleep instead of letting wgpu block for us
// when retrieving the texture to ensure that `tokio` isn't
// blocked, since those are non-async, while this sleep is.
#[time(wait_next_frame)]
tokio::time::sleep_until(cur_frame_start).await;
// If we were too late, we need to skip some frames
if let Some(late) = prev_frame_end.checked_duration_since(cur_frame_start) &&
late > frame_duration
{
#[expect(clippy::cast_sign_loss, reason = "Durations are always positive")]
let frames = late.div_duration_f64(frame_duration).floor() as u32;
tracing::trace!("Frame rendered late {late:.2?}, skipping {frames} frames");
next_frame += frame_duration * frames;
}
let window_geometry = *shared_window.monitor_geometry.lock().await;
// Paint egui
@ -418,10 +457,11 @@ async fn renderer(
.render_frame_times(shared_window.window.id())
.await
.add(metrics::RenderFrameTime {
paint_egui: frame_paint_egui,
render_start: frame_render_start,
wait_next_frame,
paint_egui: frame_paint_egui,
render_start: frame_render_start,
render_panels: frame_render_panels,
render_egui: frame_render_egui,
render_egui: frame_render_egui,
render_finish: frame_render_finish,
handle_events: frame_handle_events,
});

View File

@ -17,6 +17,7 @@ pub fn draw(ui: &mut egui::Ui, render_frame_times: &mut FrameTimes<RenderFrameTi
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(strum::EnumIter)]
enum DurationIdx {
WaitNextFrame,
PaintEgui,
RenderStart,
RenderPanels,
@ -28,6 +29,7 @@ enum DurationIdx {
impl super::DurationIdx<RenderFrameTime> for DurationIdx {
fn name(&self) -> String {
match self {
Self::WaitNextFrame => "Wait next frame".to_owned(),
Self::PaintEgui => "Paint egui".to_owned(),
Self::RenderStart => "Render start".to_owned(),
Self::RenderPanels => "Render panels".to_owned(),
@ -39,6 +41,7 @@ impl super::DurationIdx<RenderFrameTime> for DurationIdx {
fn duration_of(&self, frame_time: &RenderFrameTime) -> Option<Duration> {
let duration = match self {
Self::WaitNextFrame => frame_time.wait_next_frame,
Self::PaintEgui => frame_time.paint_egui,
Self::RenderStart => frame_time.render_start,
Self::RenderPanels => frame_time.render_panels,

View File

@ -133,12 +133,13 @@ impl<T> Default for FrameTimes<T> {
/// Render frame time.
#[derive(Clone, Copy, Debug)]
pub struct RenderFrameTime {
pub paint_egui: Duration,
pub render_start: Duration,
pub render_panels: Duration,
pub render_egui: Duration,
pub render_finish: Duration,
pub handle_events: Duration,
pub wait_next_frame: Duration,
pub paint_egui: Duration,
pub render_start: Duration,
pub render_panels: Duration,
pub render_egui: Duration,
pub render_finish: Duration,
pub handle_events: Duration,
}
/// Render panels frame time.

View File

@ -50,4 +50,7 @@ pub struct SharedWindow {
/// Monitor geometry
pub monitor_geometry: Mutex<Rect<i32, u32>>,
/// Monitor refresh rate (in mHz)
pub monitor_refresh_rate_mhz: u32,
}

View File

@ -21,6 +21,9 @@ pub struct AppWindow {
/// Monitor geometry
pub monitor_geometry: Rect<i32, u32>,
/// Monitor refresh rate (in mHz)
pub monitor_refresh_rate_mhz: u32,
/// Window
pub window: Window,
}
@ -35,6 +38,10 @@ pub fn create(event_loop: &ActiveEventLoop, transparent_windows: bool) -> Result
.name()
.unwrap_or_else(|| format!("Monitor #{}", monitor_idx + 1));
let monitor_refresh_rate_mhz = monitor
.refresh_rate_millihertz()
.context("Unable to get monitor refresh rate")?;
let monitor_geometry = self::monitor_geometry(&monitor);
tracing::debug!("Found monitor {monitor_name:?} geometry: {monitor_geometry}");
@ -58,6 +65,7 @@ pub fn create(event_loop: &ActiveEventLoop, transparent_windows: bool) -> Result
Ok(AppWindow {
monitor_name,
monitor_geometry,
monitor_refresh_rate_mhz,
window,
})
})