diff --git a/profile.json b/profile.json index bfaecf7..671d51e 100644 --- a/profile.json +++ b/profile.json @@ -4,12 +4,12 @@ { "geometry": { "pos": { - "x": 0, - "y": 312 + "x": 1360, + "y": 0 }, "size": { - "x": 1360, - "y": 768 + "x": 1920, + "y": 1080 } }, "duration": 1800, diff --git a/zsw-egui/src/lib.rs b/zsw-egui/src/lib.rs index e31d67d..46cf066 100644 --- a/zsw-egui/src/lib.rs +++ b/zsw-egui/src/lib.rs @@ -7,13 +7,12 @@ use { anyhow::Context, crossbeam::atomic::AtomicCell, + futures::{channel::mpsc, SinkExt}, std::{ sync::Arc, - task::Waker, time::{Duration, Instant}, }, winit::window::Window, - zsw_util::FetchUpdate, zsw_wgpu::Wgpu, }; @@ -25,9 +24,6 @@ pub struct Egui { /// Last frame time frame_time: AtomicCell>, - - /// Paint jobs waker - paint_jobs_waker: AtomicCell>, } impl std::fmt::Debug for Egui { @@ -35,7 +31,6 @@ impl std::fmt::Debug for Egui { f.debug_struct("Egui") .field("repaint_signal", &self.repaint_signal) .field("frame_time", &self.frame_time) - .field("paint_jobs_waker", &"..") .finish() } } @@ -46,15 +41,7 @@ impl Egui { pub fn new( window: &Window, wgpu: &Wgpu, - ) -> Result< - ( - Self, - EguiPlatformResource, - EguiRenderPassResource, - EguiPaintJobsResource, - ), - anyhow::Error, - > { + ) -> Result<(Self, EguiPlatformResource, EguiRenderPassResource, EguiPainterResource), anyhow::Error> { // Create the egui platform // TODO: Check if it's fine to use the window size here instead of the // wgpu surface size @@ -77,18 +64,20 @@ impl Egui { let service = Self { repaint_signal, frame_time: AtomicCell::new(None), - paint_jobs_waker: AtomicCell::new(None), }; // Create the resources + // Note: By using a 0-size channel we achieve the least latency + let (paint_jobs_tx, paint_jobs_rx) = mpsc::channel(0); let platform_resource = EguiPlatformResource { platform }; - let render_pass_resource = EguiRenderPassResource { render_pass }; - let paint_jobs_resource = EguiPaintJobsResource { - paint_jobs: FetchUpdate::new(vec![]), + let render_pass_resource = EguiRenderPassResource { + render_pass, + paint_jobs: vec![], + paint_jobs_rx, }; + let painter_resource = EguiPainterResource { paint_jobs_tx }; - - Ok((service, platform_resource, render_pass_resource, paint_jobs_resource)) + Ok((service, platform_resource, render_pass_resource, painter_resource)) } /// Draws egui @@ -138,25 +127,23 @@ impl Egui { platform_resource.platform.context().font_image() } - /// Returns the current paint jobs - pub fn paint_jobs<'a>(&self, paint_jobs_resource: &'a mut EguiPaintJobsResource) -> &'a [egui::ClippedMesh] { - // Get the paint jobs - let paint_jobs = paint_jobs_resource.paint_jobs.fetch(); - - // If we have a waker, wake them - if let Some(waker) = self.paint_jobs_waker.take() { - waker.wake(); - } - - paint_jobs - } - - /// Returns the render pass - pub fn render_pass<'a>( + /// Returns the render pass and paint jobs + pub fn render_pass_with_paint_jobs<'a>( &self, render_pass_resource: &'a mut EguiRenderPassResource, - ) -> &'a mut egui_wgpu_backend::RenderPass { - &mut render_pass_resource.render_pass + ) -> (&'a mut egui_wgpu_backend::RenderPass, &'a [egui::ClippedMesh]) { + // If we have any new paint jobs, update them + // TODO: Not panic here when the painter quit + if let Ok(paint_jobs) = render_pass_resource + .paint_jobs_rx + .try_next() + .transpose() + .expect("Egui painter quit") + { + render_pass_resource.paint_jobs = paint_jobs; + } + + (&mut render_pass_resource.render_pass, &render_pass_resource.paint_jobs) } /// Updates the paint jobs @@ -164,21 +151,15 @@ impl Egui { /// Returns `Err` if they haven't been fetched yet pub async fn update_paint_jobs( &self, - paint_jobs_resource: &mut EguiPaintJobsResource, + painter_resource: &mut EguiPainterResource, paint_jobs: Vec, - ) -> Result<(), Vec> { - paint_jobs_resource.paint_jobs.update(paint_jobs) - } - - /// Registers a waker to be woken up by the paint jobs being fetched - pub fn set_paint_jobs_waker(&self, paint_jobs_resource: &EguiPaintJobsResource, waker: Waker) { - // Set the waker - self.paint_jobs_waker.store(Some(waker)); - - // If the paint jobs were fetched in the meantime without waking, wake up - if paint_jobs_resource.paint_jobs.is_seen() && let Some(waker) = self.paint_jobs_waker.take() { - waker.wake(); - } + ) { + // TDO: Not panic + painter_resource + .paint_jobs_tx + .send(paint_jobs) + .await + .expect("Egui renderer quit"); } } @@ -196,7 +177,14 @@ impl std::fmt::Debug for EguiPlatformResource { /// Render pass resource pub struct EguiRenderPassResource { + /// Render pass render_pass: egui_wgpu_backend::RenderPass, + + /// Current paint jobs + paint_jobs: Vec, + + /// Paint jobs receiver + paint_jobs_rx: mpsc::Receiver>, } impl std::fmt::Debug for EguiRenderPassResource { @@ -207,10 +195,11 @@ impl std::fmt::Debug for EguiRenderPassResource { } } -/// Paint jobs resource +/// Painter resource #[derive(Debug)] -pub struct EguiPaintJobsResource { - paint_jobs: FetchUpdate>, +pub struct EguiPainterResource { + /// Paint jobs sender + paint_jobs_tx: mpsc::Sender>, } /// Repaint signal diff --git a/zsw-renderer/src/lib.rs b/zsw-renderer/src/lib.rs index c5ab365..4ab3932 100644 --- a/zsw-renderer/src/lib.rs +++ b/zsw-renderer/src/lib.rs @@ -9,7 +9,7 @@ use { std::{mem, time::Duration}, tokio::time::Instant, winit::window::Window, - zsw_egui::{Egui, EguiPaintJobsResource, EguiPlatformResource, EguiRenderPassResource}, + zsw_egui::{Egui, EguiPainterResource, EguiPlatformResource, EguiRenderPassResource}, zsw_img::ImageLoader, zsw_input::Input, zsw_panels::{Panels, PanelsResource}, @@ -50,7 +50,7 @@ impl Renderer { + Resources + Resources + Resources - + Resources, + + Resources, { // Duration we're sleeping let sleep_duration = Duration::from_secs_f32(1.0 / 60.0); @@ -104,9 +104,8 @@ impl Renderer { /// Lock tree: /// [`zsw_wgpu::SurfaceLock`] on `wgpu` /// - [`zsw_panels::PanelsLock`] on `panels` - /// - [`zsw_egui::PaintJobsLock`] on `egui` - /// - [`zsw_egui::RenderPassLock`] on `egui` - /// - [`zsw_egui::PlatformLock`] on `egui` + /// - [`zsw_egui::RenderPassLock`] on `egui` + /// - [`zsw_egui::PlatformLock`] on `egui` async fn render(services: &S, resources: &R) -> Result<(), anyhow::Error> where S: Services + Services + Services + Services + Services, @@ -114,7 +113,7 @@ impl Renderer { + Resources + Resources + Resources - + Resources, + + Resources, { let wgpu = services.service::(); let egui = services.service::(); @@ -158,15 +157,11 @@ impl Renderer { // Get the egui render results // DEADLOCK: Caller ensures we can lock it. - let mut egui_paint_jobs_resource = resources.resource::().await; - let egui_paint_jobs = egui.paint_jobs(&mut egui_paint_jobs_resource); + let mut render_pass_resource = resources.resource::().await; + let (egui_render_pass, egui_paint_jobs) = egui.render_pass_with_paint_jobs(&mut render_pass_resource); // If we have any paint jobs, draw egui if !egui_paint_jobs.is_empty() { - // DEADLOCK: Caller ensures we can lock it after the wgpu surface lock - let mut render_pass_resource = resources.resource::().await; - let egui_render_pass = egui.render_pass(&mut render_pass_resource); - let font_image = { // DEADLOCK: Caller ensures we can lock it after the egui render pass lock let platform_resource = resources.resource::().await; @@ -189,7 +184,7 @@ impl Renderer { .context("Unable to render egui")?; } - mem::drop(egui_paint_jobs_resource); + mem::drop(render_pass_resource); wgpu.finish_render(frame); Ok(()) diff --git a/zsw-settings-window/src/lib.rs b/zsw-settings-window/src/lib.rs index c7b6e1d..f28d44e 100644 --- a/zsw-settings-window/src/lib.rs +++ b/zsw-settings-window/src/lib.rs @@ -15,16 +15,15 @@ use { egui::{plot, Widget}, futures::lock::Mutex, pollster::FutureExt, - std::mem, winit::{ dpi::{PhysicalPosition, PhysicalSize}, window::Window, }, - zsw_egui::{Egui, EguiPaintJobsResource, EguiPlatformResource}, + zsw_egui::{Egui, EguiPainterResource, EguiPlatformResource}, zsw_panels::{Panel, PanelState, PanelStateImage, PanelStateImages, Panels, PanelsResource}, zsw_playlist::{Playlist, PlaylistImage, PlaylistResource}, zsw_profiles::{Profile, Profiles, ProfilesResource}, - zsw_util::{CondvarFuture, Rect, Resources, Services}, + zsw_util::{Rect, Resources, Services}, zsw_wgpu::{Wgpu, WgpuSurfaceResource}, }; @@ -82,7 +81,7 @@ impl SettingsWindow { /// - [`zsw_profiles::ProfilesLock`] on `profiles` /// - [`zsw_playlist::PlaylistLock`] on `playlist` /// - [`zsw_panels::PanelsLock`] on `panels` - /// Blocks until [`Self::paint_jobs`] on `egui` is called. + /// Blocks until [`Self::update_paint_jobs`] on `egui` is called. pub async fn run(&self, services: &S, resources: &R) -> ! where S: Services @@ -96,7 +95,7 @@ impl SettingsWindow { + Resources + Resources + Resources - + Resources, + + Resources, { let wgpu = services.service::(); let egui = services.service::(); @@ -148,26 +147,10 @@ impl SettingsWindow { }) }; + // Try to update the paint jobs + let mut egui_painter_resource = resources.resource::().await; match res { - // If we got the paint jobs, try to update - Ok(mut paint_jobs) => loop { - let mut paint_jobs_resource = resources.resource::().await; - match egui.update_paint_jobs(&mut paint_jobs_resource, paint_jobs).await { - Ok(()) => break, - // If we didn't get it, register a waker and wait - Err(old_paint_jobs) => { - // Drop the resource and wait until woken - CondvarFuture::new(|waker| { - egui.set_paint_jobs_waker(&paint_jobs_resource, waker.clone()); - mem::drop(paint_jobs_resource); - }) - .await; - - // Then try again - paint_jobs = old_paint_jobs; - }, - } - }, + Ok(paint_jobs) => egui.update_paint_jobs(&mut egui_painter_resource, paint_jobs).await, Err(err) => tracing::warn!(?err, "Unable to draw egui"), } } diff --git a/zsw/src/app.rs b/zsw/src/app.rs index d60e51f..dca25c0 100644 --- a/zsw/src/app.rs +++ b/zsw/src/app.rs @@ -90,7 +90,7 @@ pub async fn create_services_resources(window: Arc) -> Result<(Services, Panels::new(wgpu.device(), wgpu.surface_texture_format()).context("Unable to create panels")?; // Create egui - let (egui, egui_platform_resource, egui_render_pass_resource, egui_paint_jobs_resource) = + let (egui, egui_platform_resource, egui_render_pass_resource, egui_painter_resource) = Egui::new(&window, &wgpu).context("Unable to create egui state")?; // Create the profiles @@ -127,7 +127,7 @@ pub async fn create_services_resources(window: Arc) -> Result<(Services, wgpu_surface: Mutex::new(wgpu_surface_resource), egui_platform: Mutex::new(egui_platform_resource), egui_render_pass: Mutex::new(egui_render_pass_resource), - egui_paint_jobs: Mutex::new(egui_paint_jobs_resource), + egui_painter: Mutex::new(egui_painter_resource), }; Ok((services, resources)) diff --git a/zsw/src/app/resources.rs b/zsw/src/app/resources.rs index 0fc656a..3cf3539 100644 --- a/zsw/src/app/resources.rs +++ b/zsw/src/app/resources.rs @@ -1,11 +1,9 @@ //! Resources -use zsw_egui::EguiPaintJobsResource; - // Imports use { futures::lock::{Mutex, MutexLockFuture}, - zsw_egui::{EguiPlatformResource, EguiRenderPassResource}, + zsw_egui::{EguiPainterResource, EguiPlatformResource, EguiRenderPassResource}, zsw_panels::PanelsResource, zsw_playlist::PlaylistResource, zsw_profiles::ProfilesResource, @@ -34,8 +32,8 @@ pub struct Resources { /// Egui render pass pub egui_render_pass: Mutex, - /// Egui paint jobs - pub egui_paint_jobs: Mutex, + /// Egui painter + pub egui_painter: Mutex, } impl ResourcesBundle for Resources {} @@ -48,7 +46,7 @@ impl ResourcesBundle for Resources {} [ WgpuSurfaceResource ] [ wgpu_surface ]; [ EguiPlatformResource ] [ egui_platform ]; [ EguiRenderPassResource ] [ egui_render_pass ]; - [ EguiPaintJobsResource ] [ egui_paint_jobs ]; + [ EguiPainterResource ] [ egui_painter ]; )] impl zsw_util::Resources for Resources { fn lock(&self) -> MutexLockFuture {