Merged update and render thread.

Updater not doesn't block anymore.
This commit is contained in:
Filipe Rodrigues 2022-01-26 15:49:12 +00:00
parent 9ae9b3c73e
commit 8e97e2a259
3 changed files with 155 additions and 182 deletions

View File

@ -161,8 +161,7 @@ impl App {
let _image_loaders =
util::spawn_scoped_multiple(s, "Image loader", loader_threads, || || self.inner.image_loader.run())?;
// Spawn the updater and renderer thread
let _updater_thread = util::spawn_scoped(s, "Updater", || Self::run_updater(&self.inner))?;
// Spawn the renderer thread
let _renderer_thread = util::spawn_scoped(s, "Renderer", || Self::run_renderer(&self.inner))?;
// Run event loop in this thread until we quit
@ -225,14 +224,24 @@ impl App {
}
}
/// Runs the updater
fn run_updater(inner: &Inner) {
/// Runs the renderer
fn run_renderer(inner: &Inner) {
// Duration we're sleep
let sleep_duration = Duration::from_secs_f32(1.0 / 60.0);
loop {
// Render
// Update
// Note: The update is only useful for displaying, so there's no use
// in running it in another thread.
// Especially given that `update` doesn't block.
let (res, frame_duration) = crate::util::measure(|| Self::update(inner));
match res {
Ok(()) => log::trace!("Took {frame_duration:?} to update"),
Err(err) => log::warn!("Unable to update: {err:?}"),
};
// Render
let (res, frame_duration) = crate::util::measure(|| Self::render(inner));
match res {
Ok(()) => log::trace!("Took {frame_duration:?} to render"),
Err(err) => log::warn!("Unable to render: {err:?}"),
@ -245,7 +254,7 @@ impl App {
}
}
/// Updates
/// Updates all panels
fn update(inner: &Inner) -> Result<(), anyhow::Error> {
let mut panels = inner.panels.lock();
for panel in &mut *panels {
@ -263,26 +272,6 @@ impl App {
Ok(())
}
/// Runs the renderer
fn run_renderer(inner: &Inner) {
// Duration we're sleep
let sleep_duration = Duration::from_secs_f32(1.0 / 60.0);
loop {
// Render
let (res, frame_duration) = crate::util::measure(|| Self::render(inner));
match res {
Ok(()) => log::trace!("Took {frame_duration:?} to render"),
Err(err) => log::warn!("Unable to render: {err:?}"),
};
// Then sleep until next frame
if let Some(duration) = sleep_duration.checked_sub(frame_duration) {
thread::sleep(duration);
}
}
}
/// Renders
fn render(inner: &Inner) -> Result<(), anyhow::Error> {
// Draw egui

View File

@ -15,10 +15,16 @@ pub use self::{
};
// Imports
use crate::{img::ImageLoader, Rect};
use crate::{
img::{Image, ImageLoader},
Rect,
};
use anyhow::Context;
use cgmath::{Matrix4, Vector3};
use std::time::{Duration, Instant};
use std::{
mem,
time::{Duration, Instant},
};
use winit::dpi::PhysicalSize;
/// Panel
@ -46,6 +52,58 @@ pub struct Panel {
/// Fade point
// TODO: Ensure it's between 0.5 and 1.0
pub fade_point: f32,
/// Next image state
pub next_image_state: NextImageState,
}
/// Next image
#[derive(Debug)]
pub enum NextImageState {
/// Ready
Ready(Image),
/// Waiting
Waiting {
/// Instant we've been waiting since
since: Instant,
},
/// Empty
Empty,
}
impl NextImageState {
/// Loads the next image, if waiting or empty
pub fn load_next(&mut self, image_loader: &ImageLoader) -> Result<(), anyhow::Error> {
*self = match mem::replace(self, Self::Empty) {
Self::Ready(image) => Self::Ready(image),
Self::Waiting { since } => match image_loader.try_recv().context("Unable to receive next image")? {
Some(image) => {
log::debug!("Received image after waiting for {:?}", since.elapsed());
Self::Ready(image)
},
None => Self::Waiting { since },
},
Self::Empty => match image_loader.try_recv().context("Unable to receive next image")? {
Some(image) => Self::Ready(image),
None => Self::Waiting { since: Instant::now() },
},
};
Ok(())
}
/// Takes the image, if any
pub fn take_image(&mut self) -> Option<Image> {
let image;
(*self, image) = match mem::replace(self, Self::Empty) {
Self::Ready(image) => (Self::Empty, Some(image)),
Self::Waiting { since } => (Self::Waiting { since }, None),
Self::Empty => (Self::Waiting { since: Instant::now() }, None),
};
image
}
}
impl Panel {
@ -57,97 +115,92 @@ impl Panel {
progress: 0.0,
image_duration,
fade_point,
next_image_state: NextImageState::Empty,
}
}
/// Updates this panel
// TODO: Not block if the image isn't ready.
pub fn update(
&mut self, device: &wgpu::Device, queue: &wgpu::Queue, uniforms_bind_group_layout: &wgpu::BindGroupLayout,
texture_bind_group_layout: &wgpu::BindGroupLayout, image_loader: &ImageLoader,
) -> Result<(), anyhow::Error> {
// If we've been waiting for an image, try to load it
self.next_image_state
.load_next(image_loader)
.context("Unable to load next image")?;
// Next frame's progress
let next_progress = self.progress + (1.0 / 60.0) / self.image_duration.as_secs_f32();
// Progress on image swap
let swapped_progress = self.progress - self.fade_point;
// If past the fade point
let past_fade = self.progress >= self.fade_point;
// If we finished the current image
let finished = self.progress >= 1.0;
// Check the image state
(self.state, self.progress) = match std::mem::replace(&mut self.state, PanelState::Empty) {
// If we're empty, get the next image
PanelState::Empty => {
let image = PanelImage::new(
device,
queue,
uniforms_bind_group_layout,
texture_bind_group_layout,
image_loader,
)
.context("Unable to create image")?;
(PanelState::PrimaryOnly { image }, 0.0)
// Update the image state
// Note: We have to replace the state with `Empty` temporarily due to
// panics that might occur while updating.
(self.state, self.progress) = match mem::replace(&mut self.state, PanelState::Empty) {
// If we're empty, try to load the next image
PanelState::Empty => match self.next_image_state.take_image() {
Some(image) => (
PanelState::PrimaryOnly {
front: PanelImage::new(
device,
queue,
uniforms_bind_group_layout,
texture_bind_group_layout,
&image,
)
.context("Unable to create panel image")?,
},
0.0,
),
None => (PanelState::Empty, 0.0),
},
// If we only have the primary and we're past the fade point, get the next image
// Note: We do this so we can render the first image without waiting
// for both images to load
// TODO: Redo this setup
PanelState::PrimaryOnly { image: front } if past_fade => {
let back = PanelImage::new(
device,
queue,
uniforms_bind_group_layout,
texture_bind_group_layout,
image_loader,
)
.context("Unable to create image")?;
(PanelState::Both { front, back }, next_progress)
// If we only have the primary, try to load the next image
PanelState::PrimaryOnly { front } => match self.next_image_state.take_image() {
Some(image) => (
PanelState::Both {
front,
back: PanelImage::new(
device,
queue,
uniforms_bind_group_layout,
texture_bind_group_layout,
&image,
)
.context("Unable to create panel image")?,
},
next_progress,
),
None => (PanelState::PrimaryOnly { front }, next_progress),
},
// If we have both, update the progress and swap them if finished
PanelState::Both { front, back } if finished => {
// Note: Front and back are swapped here since we implicitly swap
match self::update_swapped(
front,
back,
None,
image_loader,
device,
queue,
texture_bind_group_layout,
past_fade,
)
.context("Unable to update swapped image")?
{
(true, state) => (state, swapped_progress),
(false, state) => (state, next_progress),
}
// If we have both, try to update the progress and swap them if finished
PanelState::Both { mut front, back } if finished => match self.next_image_state.take_image() {
// Note: We update the front and swap them
Some(image) => {
front
.update(device, queue, texture_bind_group_layout, &image)
.context("Unable to update texture")?;
(
PanelState::Both {
front: back,
back: front,
},
swapped_progress,
)
},
// Note: If we're done without a next image, then just stay at 1.0
None => (PanelState::Both { front, back }, 1.0),
},
// If we're swapped, try to update
PanelState::Swapped { front, back, since } => match self::update_swapped(
back,
front,
Some(since),
image_loader,
device,
queue,
texture_bind_group_layout,
past_fade,
)
.context("Unable to update swapped image")?
{
(true, state) => (state, swapped_progress),
(false, state) => (state, next_progress),
},
// Else keep the current state and advance
state => (state, next_progress),
// Else just update the progress
state @ PanelState::Both { .. } => (state, next_progress),
};
Ok(())
@ -182,9 +235,7 @@ impl Panel {
// Get the images to render
let (front, back) = match &mut self.state {
PanelState::Empty => (None, None),
PanelState::PrimaryOnly { image, .. } | PanelState::Swapped { front: image, .. } => {
(Some((image, 1.0, self.progress)), None)
},
PanelState::PrimaryOnly { front, .. } => (Some((front, 1.0, self.progress)), None),
PanelState::Both { front, back } => (
Some((front, 1.0 - back_alpha, self.progress)),
Some((back, back_alpha, back_progress)),
@ -230,7 +281,7 @@ pub enum PanelState {
/// The primary image is loaded. The back image is still not available
PrimaryOnly {
/// Image
image: PanelImage,
front: PanelImage,
},
/// Both
@ -243,56 +294,4 @@ pub enum PanelState {
/// Back image
back: PanelImage,
},
/// Swapped
///
/// Front and back images have been swapped, and the next image needs
/// to be loaded
Swapped {
/// Front image
front: PanelImage,
/// Back image that needs to be swapped
back: PanelImage,
/// Instant we were swapped
since: Instant,
},
}
/// Updates a swapped image state and returns the next state
#[allow(clippy::too_many_arguments)] // TODO:
fn update_swapped(
mut back: PanelImage, front: PanelImage, mut since: Option<Instant>, image_loader: &ImageLoader,
device: &wgpu::Device, queue: &wgpu::Queue, texture_bind_group_layout: &wgpu::BindGroupLayout, force_wait: bool,
) -> Result<(bool, PanelState), anyhow::Error> {
// If we're force waiting and don't have a `since`, create it,
// so we can keep track of how long the request took
if force_wait && since.is_none() {
since = Some(Instant::now());
}
let swapped = back
.try_update(image_loader, device, queue, texture_bind_group_layout, force_wait)
.context("Unable to get next image")?;
let state = match swapped {
// If we updated, switch to `Both`
true => {
// If we didn't just update it, log how long it took
if let Some(since) = since {
let duration = Instant::now().saturating_duration_since(since);
log::trace!("Waited {duration:?} for the next image");
}
PanelState::Both { front, back }
},
// Else stay in `Swapped`
false => PanelState::Swapped {
back,
front,
since: since.unwrap_or_else(Instant::now),
},
};
Ok((swapped, state))
}

View File

@ -2,8 +2,7 @@
// Imports
use super::PanelUniforms;
use crate::img::{Image, ImageLoader, ImageUvs};
use anyhow::Context;
use crate::img::{Image, ImageUvs};
use cgmath::Vector2;
use wgpu::util::DeviceExt;
@ -38,23 +37,15 @@ pub struct PanelImage {
impl PanelImage {
/// Creates a new image
///
/// # Errors
/// Returns error if unable to create the gl texture or the vertex buffer
pub fn new(
device: &wgpu::Device, queue: &wgpu::Queue, uniforms_bind_group_layout: &wgpu::BindGroupLayout,
texture_bind_group_layout: &wgpu::BindGroupLayout, image_loader: &ImageLoader,
texture_bind_group_layout: &wgpu::BindGroupLayout, image: &Image,
) -> Result<Self, anyhow::Error> {
// Get an image receiver and the initial image
let image = image_loader.recv().context("Unable to get image")?;
let image_size = Vector2::new(image.width(), image.height());
// Create the texture and sampler
let (texture, texture_view) = self::create_image_texture(&image, device, queue);
let texture_sampler = create_texture_sampler(device);
let swap_dir = rand::random();
let (texture, texture_view) = self::create_image_texture(image, device, queue);
let texture_sampler = self::create_texture_sampler(device);
// Create the uniforms
let uniforms = PanelUniforms::new();
let uniforms_descriptor = wgpu::util::BufferInitDescriptor {
label: None,
@ -85,29 +76,23 @@ impl PanelImage {
texture_bind_group,
uniforms,
uniforms_bind_group,
image_size,
swap_dir,
image_size: Vector2::new(image.width(), image.height()),
swap_dir: rand::random(),
})
}
/// Tries to update this image and returns if actually updated
/// Updates this image
#[allow(clippy::unnecessary_wraps)] // It might fail in the future
pub fn try_update(
&mut self, image_loader: &ImageLoader, device: &wgpu::Device, queue: &wgpu::Queue,
texture_bind_group_layout: &wgpu::BindGroupLayout, force_wait: bool,
) -> Result<bool, anyhow::Error> {
let image = match image_loader.try_recv().context("Unable to receive image")? {
Some(image) => image,
None => match force_wait {
true => image_loader.recv().context("Unable to receive image")?,
false => return Ok(false),
},
};
pub fn update(
&mut self, device: &wgpu::Device, queue: &wgpu::Queue, texture_bind_group_layout: &wgpu::BindGroupLayout,
image: &Image,
) -> Result<(), anyhow::Error> {
// Update the image
self.image_size = Vector2::new(image.width(), image.height());
self.swap_dir = rand::random();
// Then update our texture
(self.texture, self.texture_view) = self::create_image_texture(&image, device, queue);
(self.texture, self.texture_view) = self::create_image_texture(image, device, queue);
self.texture_bind_group = self::create_texture_bind_group(
texture_bind_group_layout,
&self.texture_view,
@ -115,7 +100,7 @@ impl PanelImage {
device,
);
Ok(true)
Ok(())
}
/// Returns this image's uvs for a panel size