Added initial support for egui.

This commit is contained in:
Filipe Rodrigues 2022-01-18 14:44:25 +00:00
parent d497a28eb0
commit 1f17478d07
4 changed files with 320 additions and 60 deletions

150
Cargo.lock generated
View File

@ -2,6 +2,22 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "ab_glyph"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61caed9aec6daeee1ea38ccf5fb225e4f96c1eeead1b4a5c267324a63cf02326"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser",
]
[[package]]
name = "ab_glyph_rasterizer"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e"
[[package]]
name = "adler"
version = "1.0.2"
@ -58,6 +74,12 @@ dependencies = [
"libloading",
]
[[package]]
name = "atomic_refcell"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d"
[[package]]
name = "atty"
version = "0.2.14"
@ -194,10 +216,12 @@ version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"js-sys",
"libc",
"num-integer",
"num-traits",
"time",
"wasm-bindgen",
"winapi 0.3.9",
]
@ -522,12 +546,106 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "egui"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c733356eb5f1139fdeedc370c00e9ea689c5d9120502c43925285bc7249a333"
dependencies = [
"ahash",
"epaint",
"nohash-hasher",
]
[[package]]
name = "egui_demo_lib"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad907b870f83da0fbf9532967b0d22d19edf78d1b7d5aa83d0e95a79a632600b"
dependencies = [
"chrono",
"egui",
"enum-map",
"epi",
"unicode_names2",
]
[[package]]
name = "egui_wgpu_backend"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e59185e6e0ba5691235434f3eef768dcbcbdf3322055819435a1697ebd8facff"
dependencies = [
"bytemuck",
"egui",
"wgpu",
]
[[package]]
name = "egui_winit_platform"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f145532d38e25621e3d24814fa76ce8b3ed714f2acf081060097fa571a357d"
dependencies = [
"egui",
"winit",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "emath"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55673de2eb96660dde25ba7b2d36a7054beead1a2bec74dcfd5eb05a1e1ba76d"
[[package]]
name = "enum-map"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e893a7ba6116821058dec84a6fb14fb2a97cd8ce5fd0f85d5a4e760ecd7329d9"
dependencies = [
"enum-map-derive",
"serde",
]
[[package]]
name = "enum-map-derive"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84278eae0af6e34ff6c1db44c11634a694aafac559ff3080e4db4e4ac35907aa"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "epaint"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adfd9296f7f92902e41c0e8e5deca6d2fb29f289c86d03a01ea01bd7498316c2"
dependencies = [
"ab_glyph",
"ahash",
"atomic_refcell",
"emath",
"nohash-hasher",
]
[[package]]
name = "epi"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ae4ce3271febeacc5b4afbd77e500316c6ba316561067acbdddf0c14268a7c"
dependencies = [
"egui",
]
[[package]]
name = "filetime"
version = "0.2.15"
@ -1112,6 +1230,12 @@ dependencies = [
"memoffset",
]
[[package]]
name = "nohash-hasher"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "nom"
version = "7.0.0"
@ -1257,6 +1381,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "owned_ttf_parser"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ef05f2882a8b3e7acc10c153ade2631f7bfc8ce00d2bf3fb8f4e9d2ae6ea5c3"
dependencies = [
"ttf-parser",
]
[[package]]
name = "parking_lot"
version = "0.11.2"
@ -1667,6 +1800,12 @@ dependencies = [
"serde",
]
[[package]]
name = "ttf-parser"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ccbe8381883510b6a2d8f1e32905bddd178c11caef8083086d0c0c9ab0ac281"
[[package]]
name = "twox-hash"
version = "1.6.1"
@ -1690,6 +1829,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "unicode_names2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87d6678d7916394abad0d4b19df4d3802e1fd84abd7d701f39b75ee71b9e8cf1"
[[package]]
name = "version_check"
version = "0.9.3"
@ -2094,6 +2239,11 @@ dependencies = [
"cgmath",
"clap",
"crossbeam",
"egui",
"egui_demo_lib",
"egui_wgpu_backend",
"egui_winit_platform",
"epi",
"image",
"log",
"notify",

View File

@ -12,6 +12,13 @@ winit = "0.26.1"
# Rendering
wgpu = {version = "0.12.0", features = ["serde", "replay"]}
# Gui
egui = "0.16.1"
egui_demo_lib = "0.16" # TODO: Remove
egui_wgpu_backend = "0.16.0"
egui_winit_platform = "0.13.0"
epi = "0.16.0"
# Async
pollster = "0.2.4"

6
run.sh
View File

@ -3,10 +3,10 @@
set -e
cargo run -- \
~/.wallpaper/active \
~/.wallpaper/local \
--image-duration 5.0 \
--window-geometry "3280x1080+0+0" \
--panel-geometry "1920x1080+1360+0" \
--window-geometry "1920x1080+1360+0" \
--panel-geometry "1920x1080+0+0" \
--fade-point 0.8 \
--image-backlog 4 \

View File

@ -8,8 +8,11 @@ use anyhow::Context;
use crossbeam::thread;
use parking_lot::Mutex;
use std::{
sync::atomic::{self, AtomicBool},
time::Duration,
sync::{
atomic::{self, AtomicBool},
Arc,
},
time::{Duration, Instant},
};
use winit::{
dpi::{PhysicalPosition, PhysicalSize},
@ -23,13 +26,11 @@ use winit::{
};
use x11::xlib;
/// Application state
///
/// Stores all of the application state
pub struct App {
/// Event loop
event_loop: EventLoop<!>,
/// Inner state
// Note: This is required because the event loop can't be
// shared in between threads, but everything else we need
// to share.
struct Inner {
/// Window
///
/// [`Wgpu`] required a static reference to it, which is
@ -40,7 +41,9 @@ pub struct App {
wgpu: Wgpu,
/// Path loader
_path_loader: PathLoader,
// Note: Although we have it behind a mutex, we don't need to access it,
// it's only there so we can share `self` between threads
_path_loader: Mutex<PathLoader>,
/// Image loader
image_loader: ImageLoader,
@ -50,6 +53,29 @@ pub struct App {
/// Panels renderer
panels_renderer: PanelsRenderer,
/// Egui platform
egui_platform: Mutex<egui_winit_platform::Platform>,
/// Egui render pass
egui_render_pass: Mutex<egui_wgpu_backend::RenderPass>,
/// Egui repaint signal
egui_repaint_signal: Arc<EguiRepaintSignal>,
/// Egui frame time
egui_frame_time: Mutex<Option<Duration>>,
}
/// Application state
///
/// Stores all of the application state
pub struct App {
/// Event loop
event_loop: EventLoop<!>,
/// Inner
inner: Inner,
}
impl App {
@ -89,49 +115,62 @@ impl App {
.await
.context("Unable to create panels renderer")?;
// Create the egui platform
let window_size = window.inner_size();
let egui_platform = egui_winit_platform::Platform::new(egui_winit_platform::PlatformDescriptor {
physical_width: window_size.width,
physical_height: window_size.height,
scale_factor: window.scale_factor(),
font_definitions: egui::FontDefinitions::default(),
style: egui::Style::default(),
});
// Create the egui render pass
let egui_render_pass = egui_wgpu_backend::RenderPass::new(wgpu.device(), wgpu.texture_format(), 1);
// Create the egui repaint signal
let egui_repaint_signal = Arc::new(EguiRepaintSignal);
Ok(Self {
event_loop,
window,
wgpu,
_path_loader: path_loader,
image_loader,
panels,
panels_renderer,
inner: Inner {
window,
wgpu,
_path_loader: Mutex::new(path_loader),
image_loader,
panels,
panels_renderer,
egui_platform: Mutex::new(egui_platform),
egui_render_pass: Mutex::new(egui_render_pass),
egui_repaint_signal,
egui_frame_time: Mutex::new(None),
},
})
}
/// Runs the app 'till completion
pub fn run(mut self) -> Result<(), anyhow::Error> {
// Start the renderer thread
// Start all threads and then wait in the main thread for events
let inner = self.inner;
let should_quit = AtomicBool::new(false);
thread::scope(|s| {
// Spawn the updater thread
s.builder()
.name("Updater thread".to_owned())
.spawn(Self::updater_thread(
&should_quit,
&self.wgpu,
&self.panels,
&self.panels_renderer,
&self.image_loader,
))
.spawn(Self::updater_thread(&inner, &should_quit))
.context("Unable to start renderer thread")?;
// Spawn the renderer thread
s.builder()
.name("Renderer thread".to_owned())
.spawn(Self::renderer_thread(
&should_quit,
&self.wgpu,
&self.panels,
self.window,
&self.panels_renderer,
))
.spawn(Self::renderer_thread(&inner, &should_quit))
.context("Unable to start renderer thread")?;
// Run event loop in this thread until we quit
self.event_loop.run_return(|event, _, control_flow| {
// Update egui
inner.egui_platform.lock().handle_event(&event);
// Set control for to wait for next event, since we're not doing
// anything else on the main thread
*control_flow = EventLoopControlFlow::Wait;
@ -139,7 +178,7 @@ impl App {
// Then handle the event
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::Resized(size) => self.wgpu.resize(size),
WindowEvent::Resized(size) => inner.wgpu.resize(size),
WindowEvent::CloseRequested | WindowEvent::Destroyed => {
log::warn!("Received close request, closing window");
*control_flow = EventLoopControlFlow::Exit;
@ -159,18 +198,14 @@ impl App {
}
/// Returns the function to run in the updater thread
fn updater_thread<'a>(
should_quit: &'a AtomicBool, wgpu: &'a Wgpu, panels: &'a Mutex<Vec<Panel>>,
panels_renderer: &'a PanelsRenderer, image_loader: &'a ImageLoader,
) -> impl FnOnce(&thread::Scope) + 'a {
fn updater_thread<'a>(inner: &'a Inner, should_quit: &'a AtomicBool) -> impl FnOnce(&thread::Scope) + 'a {
move |_| {
// Duration we're sleep
let sleep_duration = Duration::from_secs_f32(1.0 / 60.0);
while !should_quit.load(atomic::Ordering::Relaxed) {
// Render
let (res, frame_duration) =
crate::util::measure(|| Self::update(wgpu, &mut *panels.lock(), panels_renderer, image_loader));
let (res, frame_duration) = crate::util::measure(|| Self::update(inner));
match res {
Ok(()) => log::debug!("Took {frame_duration:?} to render"),
Err(err) => log::warn!("Unable to render: {err:?}"),
@ -185,16 +220,15 @@ impl App {
}
/// Updates
fn update(
wgpu: &Wgpu, panels: &mut [Panel], panels_renderer: &PanelsRenderer, image_loader: &ImageLoader,
) -> Result<(), anyhow::Error> {
for panel in panels {
fn update(inner: &Inner) -> Result<(), anyhow::Error> {
let mut panels = inner.panels.lock();
for panel in &mut *panels {
if let Err(err) = panel.update(
wgpu.device(),
wgpu.queue(),
panels_renderer.uniforms_bind_group_layout(),
panels_renderer.texture_bind_group_layout(),
image_loader,
inner.wgpu.device(),
inner.wgpu.queue(),
inner.panels_renderer.uniforms_bind_group_layout(),
inner.panels_renderer.texture_bind_group_layout(),
&inner.image_loader,
) {
log::warn!("Unable to update panel: {err:?}");
}
@ -204,18 +238,17 @@ impl App {
}
/// Returns the function to run in the renderer thread
fn renderer_thread<'a>(
should_quit: &'a AtomicBool, wgpu: &'a Wgpu, panels: &'a Mutex<Vec<Panel>>, window: &'a Window,
panels_renderer: &'a PanelsRenderer,
) -> impl FnOnce(&thread::Scope) + 'a {
fn renderer_thread<'a>(inner: &'a Inner, should_quit: &'a AtomicBool) -> impl FnOnce(&thread::Scope) + 'a {
move |_| {
// Duration we're sleep
let sleep_duration = Duration::from_secs_f32(1.0 / 60.0);
// Display the demo application that ships with egui.
let mut demo_app = egui_demo_lib::WrapApp::default();
while !should_quit.load(atomic::Ordering::Relaxed) {
// Render
let (res, frame_duration) =
crate::util::measure(|| Self::render(wgpu, &mut **panels.lock(), window, panels_renderer));
let (res, frame_duration) = crate::util::measure(|| Self::render(inner, &mut demo_app));
match res {
Ok(()) => log::debug!("Took {frame_duration:?} to render"),
Err(err) => log::warn!("Unable to render: {err:?}"),
@ -230,10 +263,71 @@ impl App {
}
/// Renders
fn render(
wgpu: &Wgpu, panels: &mut [Panel], window: &Window, panels_renderer: &PanelsRenderer,
) -> Result<(), anyhow::Error> {
wgpu.render(|encoder, view| panels_renderer.render(panels, encoder, view, wgpu.queue(), window.inner_size()))
fn render(inner: &Inner, demo_app: &mut egui_demo_lib::WrapApp) -> Result<(), anyhow::Error> {
// Start the egui frame
// Note: We need to make sure we keep the platform locked
// while the frame is active, as the main thread expected
// the frame to be over when processing events.
let mut egui_platform = inner.egui_platform.lock();
let egui_frame_start = Instant::now();
egui_platform.begin_frame();
let app_output = epi::backend::AppOutput::default();
#[allow(clippy::cast_possible_truncation)] // Unfortunately `egui` takes an `f32`
let egui_frame = epi::Frame::new(epi::backend::FrameData {
info: epi::IntegrationInfo {
name: "egui",
web_info: None,
cpu_usage: inner.egui_frame_time.lock().as_ref().map(Duration::as_secs_f32),
native_pixels_per_point: Some(inner.window.scale_factor() as f32),
prefer_dark_mode: None,
},
output: app_output,
repaint_signal: inner.egui_repaint_signal.clone(),
});
// TODO: Draw egui here
epi::App::update(demo_app, &egui_platform.context(), &egui_frame);
// Finish the frame
let (_output, paint_commands) = egui_platform.end_frame(Some(inner.window));
let paint_jobs = egui_platform.context().tessellate(paint_commands);
*inner.egui_frame_time.lock() = Some(egui_frame_start.elapsed());
inner.wgpu.render(|encoder, surface_view| {
// Render the panels
let mut panels = inner.panels.lock();
inner
.panels_renderer
.render(
&mut *panels,
encoder,
surface_view,
inner.wgpu.queue(),
inner.window.inner_size(),
)
.context("Unable to render panels")?;
// Render egui
let window_size = inner.window.inner_size();
#[allow(clippy::cast_possible_truncation)] // Unfortunately `egui` takes an `f32`
let screen_descriptor = egui_wgpu_backend::ScreenDescriptor {
physical_width: window_size.width,
physical_height: window_size.height,
scale_factor: inner.window.scale_factor() as f32,
};
let device = inner.wgpu.device();
let queue = inner.wgpu.queue();
let mut egui_render_pass = inner.egui_render_pass.lock();
egui_render_pass.update_texture(device, queue, &egui_platform.context().font_image());
egui_render_pass.update_user_textures(device, queue);
egui_render_pass.update_buffers(device, queue, &paint_jobs, &screen_descriptor);
// Record all render passes.
egui_render_pass
.execute(encoder, surface_view, &paint_jobs, &screen_descriptor, None)
.context("Unable to render egui")
})
}
}
@ -307,3 +401,12 @@ unsafe fn set_display_always_below(window: &Window) {
unsafe { xlib::XMapRaised(display, window) };
unsafe { xlib::XFlush(display) };
}
/// Egui repaint signal
// Note: We paint egui every frame, so this isn't required currently, but
// we should take it into consideration eventually.
struct EguiRepaintSignal;
impl epi::backend::RepaintSignal for EguiRepaintSignal {
fn request_repaint(&self) {}
}