Egui paint jobs now go over a channel.

This commit is contained in:
Filipe Rodrigues 2022-07-27 20:45:38 +01:00
parent 461ce33a17
commit 8b586614eb
6 changed files with 69 additions and 104 deletions

View File

@ -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,

View File

@ -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<Option<Duration>>,
/// Paint jobs waker
paint_jobs_waker: AtomicCell<Option<Waker>>,
}
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<egui::ClippedMesh>,
) -> Result<(), Vec<egui::ClippedMesh>> {
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<egui::ClippedMesh>,
/// Paint jobs receiver
paint_jobs_rx: mpsc::Receiver<Vec<egui::ClippedMesh>>,
}
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<Vec<egui::ClippedMesh>>,
pub struct EguiPainterResource {
/// Paint jobs sender
paint_jobs_tx: mpsc::Sender<Vec<egui::ClippedMesh>>,
}
/// Repaint signal

View File

@ -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<WgpuSurfaceResource>
+ Resources<EguiPlatformResource>
+ Resources<EguiRenderPassResource>
+ Resources<EguiPaintJobsResource>,
+ Resources<EguiPainterResource>,
{
// 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<S, R>(services: &S, resources: &R) -> Result<(), anyhow::Error>
where
S: Services<Wgpu> + Services<Egui> + Services<Window> + Services<Panels> + Services<Input>,
@ -114,7 +113,7 @@ impl Renderer {
+ Resources<WgpuSurfaceResource>
+ Resources<EguiPlatformResource>
+ Resources<EguiRenderPassResource>
+ Resources<EguiPaintJobsResource>,
+ Resources<EguiPainterResource>,
{
let wgpu = services.service::<Wgpu>();
let egui = services.service::<Egui>();
@ -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::<EguiPaintJobsResource>().await;
let egui_paint_jobs = egui.paint_jobs(&mut egui_paint_jobs_resource);
let mut render_pass_resource = resources.resource::<EguiRenderPassResource>().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::<EguiRenderPassResource>().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::<EguiPlatformResource>().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(())

View File

@ -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<S, R>(&self, services: &S, resources: &R) -> !
where
S: Services<Wgpu>
@ -96,7 +95,7 @@ impl SettingsWindow {
+ Resources<ProfilesResource>
+ Resources<WgpuSurfaceResource>
+ Resources<EguiPlatformResource>
+ Resources<EguiPaintJobsResource>,
+ Resources<EguiPainterResource>,
{
let wgpu = services.service::<Wgpu>();
let egui = services.service::<Egui>();
@ -148,26 +147,10 @@ impl SettingsWindow {
})
};
// Try to update the paint jobs
let mut egui_painter_resource = resources.resource::<EguiPainterResource>().await;
match res {
// If we got the paint jobs, try to update
Ok(mut paint_jobs) => loop {
let mut paint_jobs_resource = resources.resource::<EguiPaintJobsResource>().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"),
}
}

View File

@ -90,7 +90,7 @@ pub async fn create_services_resources(window: Arc<Window>) -> 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<Window>) -> 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))

View File

@ -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<EguiRenderPassResource>,
/// Egui paint jobs
pub egui_paint_jobs: Mutex<EguiPaintJobsResource>,
/// Egui painter
pub egui_painter: Mutex<EguiPainterResource>,
}
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<ty> for Resources {
fn lock(&self) -> MutexLockFuture<ty> {