Compare commits

...

3 commits

Author SHA1 Message Date
Emil Ernerfeldt
6a3e7d17ac Use parking_lot for wrapping TextureManager 2022-03-21 16:37:07 +01:00
Emil Ernerfeldt
079490b30d Make Context::load_texture thread safe 2022-03-21 16:37:07 +01:00
Emil Ernerfeldt
72d264ed6c Make Context::request_repaint safe to call from any thread.
Closes https://github.com/emilk/egui/issues/1379
2022-03-21 16:37:07 +01:00
9 changed files with 92 additions and 65 deletions

View file

@ -9,7 +9,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
### Added ⭐ ### Added ⭐
* Added `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). * Added `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)).
* Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)). * Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)).
* `Context::request_repaint` will wake up UI thread, if integrations has called `Context::set_request_repaint_callback` ([#1366](https://github.com/emilk/egui/pull/1366)). * `Context::request_repaint` will wake up UI thread, if integrations has called `Context::with_repaint_callback` ([#1366](https://github.com/emilk/egui/pull/1366)).
* Added `Ui::push_id` ([#1374](https://github.com/emilk/egui/pull/1374)). * Added `Ui::push_id` ([#1374](https://github.com/emilk/egui/pull/1374)).
### Changed 🔧 ### Changed 🔧

1
Cargo.lock generated
View file

@ -1005,6 +1005,7 @@ dependencies = [
"ahash 0.7.6", "ahash 0.7.6",
"epaint", "epaint",
"nohash-hasher", "nohash-hasher",
"parking_lot 0.12.0",
"ron", "ron",
"serde", "serde",
"tracing", "tracing",

View file

@ -229,12 +229,11 @@ pub struct EpiIntegration {
impl EpiIntegration { impl EpiIntegration {
pub fn new( pub fn new(
integration_name: &'static str, integration_name: &'static str,
egui_ctx: egui::Context,
max_texture_side: usize, max_texture_side: usize,
window: &winit::window::Window, window: &winit::window::Window,
persistence: crate::epi::Persistence, persistence: crate::epi::Persistence,
) -> Self { ) -> Self {
let egui_ctx = egui::Context::default();
*egui_ctx.memory() = persistence.load_memory().unwrap_or_default(); *egui_ctx.memory() = persistence.load_memory().unwrap_or_default();
let prefer_dark_mode = prefer_dark_mode(); let prefer_dark_mode = prefer_dark_mode();

View file

@ -57,6 +57,7 @@ epaint = { version = "0.17.0", path = "../epaint", default-features = false }
ahash = "0.7" ahash = "0.7"
nohash-hasher = "0.2" nohash-hasher = "0.2"
parking_lot = "0.12"
# Optional: # Optional:
ron = { version = "0.7", optional = true } ron = { version = "0.7", optional = true }

View file

@ -1,5 +1,8 @@
// #![warn(missing_docs)] // #![warn(missing_docs)]
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering::SeqCst;
use crate::{ use crate::{
animation_manager::AnimationManager, data::output::PlatformOutput, frame_state::FrameState, animation_manager::AnimationManager, data::output::PlatformOutput, frame_state::FrameState,
input_state::*, layers::GraphicLayers, memory::Options, output::FullOutput, TextureHandle, *, input_state::*, layers::GraphicLayers, memory::Options, output::FullOutput, TextureHandle, *,
@ -8,7 +11,10 @@ use epaint::{mutex::*, stats::*, text::Fonts, TessellationOptions, *};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
struct WrappedTextureManager(Arc<RwLock<epaint::TextureManager>>); // NOTE: we always use `parking_lot` here so we can
// use `WrappedTextureManager` from any thread.
#[derive(Clone)]
struct WrappedTextureManager(Arc<parking_lot::RwLock<epaint::TextureManager>>);
impl Default for WrappedTextureManager { impl Default for WrappedTextureManager {
fn default() -> Self { fn default() -> Self {
@ -21,7 +27,26 @@ impl Default for WrappedTextureManager {
); );
assert_eq!(font_id, TextureId::default()); assert_eq!(font_id, TextureId::default());
Self(Arc::new(RwLock::new(tex_mngr))) Self(Arc::new(parking_lot::RwLock::new(tex_mngr)))
}
}
// ----------------------------------------------------------------------------
struct RepaintInfo {
/// While positive, keep requesting repaints. Decrement at the end of each frame.
repaint_requests: AtomicU32,
request_repaint_callbacks: Option<Box<dyn Fn() + Send + Sync>>,
}
impl Default for RepaintInfo {
fn default() -> Self {
Self {
// Start with painting an extra frame to compensate for some widgets
// that take two frames before they "settle":
repaint_requests: 1.into(),
request_repaint_callbacks: None,
}
} }
} }
@ -33,7 +58,6 @@ struct ContextImpl {
fonts: Option<Fonts>, fonts: Option<Fonts>,
memory: Memory, memory: Memory,
animation_manager: AnimationManager, animation_manager: AnimationManager,
tex_manager: WrappedTextureManager,
input: InputState, input: InputState,
@ -45,10 +69,6 @@ struct ContextImpl {
output: PlatformOutput, output: PlatformOutput,
paint_stats: PaintStats, paint_stats: PaintStats,
/// While positive, keep requesting repaints. Decrement at the end of each frame.
repaint_requests: u32,
request_repaint_callbacks: Option<Box<dyn Fn() + Send + Sync>>,
} }
impl ContextImpl { impl ContextImpl {
@ -142,33 +162,46 @@ impl ContextImpl {
/// paint(full_output.textures_delta, clipped_primitives); /// paint(full_output.textures_delta, clipped_primitives);
/// } /// }
/// ``` /// ```
#[derive(Clone)] #[derive(Clone, Default)]
pub struct Context(Arc<RwLock<ContextImpl>>); pub struct Context {
ctx: Arc<RwLock<ContextImpl>>,
// These are not inside of `ContextImpl` because we want to have thread-safe access to them
// even without running with the multi_threaded feature flag.
// See https://github.com/emilk/egui/issues/1379.
repaint_info: Arc<RepaintInfo>,
tex_manager: WrappedTextureManager,
}
impl std::cmp::PartialEq for Context { impl std::cmp::PartialEq for Context {
fn eq(&self, other: &Context) -> bool { fn eq(&self, other: &Context) -> bool {
Arc::ptr_eq(&self.0, &other.0) Arc::ptr_eq(&self.ctx, &other.ctx)
}
}
impl Default for Context {
fn default() -> Self {
Self(Arc::new(RwLock::new(ContextImpl {
// Start with painting an extra frame to compensate for some widgets
// that take two frames before they "settle":
repaint_requests: 1,
..ContextImpl::default()
})))
} }
} }
impl Context { impl Context {
pub fn new() -> Self {
Self::default()
}
/// Construct a [`Context`] with a callback that is called when the user calls
/// [`Context::request_repaint`].
pub fn with_repaint_callback(callback: impl Fn() + Send + Sync + 'static) -> Self {
Self {
ctx: Default::default(),
repaint_info: Arc::new(RepaintInfo {
request_repaint_callbacks: Some(Box::new(callback)),
..Default::default()
}),
tex_manager: Default::default(),
}
}
fn read(&self) -> RwLockReadGuard<'_, ContextImpl> { fn read(&self) -> RwLockReadGuard<'_, ContextImpl> {
self.0.read() self.ctx.read()
} }
fn write(&self) -> RwLockWriteGuard<'_, ContextImpl> { fn write(&self) -> RwLockWriteGuard<'_, ContextImpl> {
self.0.write() self.ctx.write()
} }
/// Run the ui code for one frame. /// Run the ui code for one frame.
@ -538,26 +571,19 @@ impl Context {
/// If this is called at least once in a frame, then there will be another frame right after this. /// If this is called at least once in a frame, then there will be another frame right after this.
/// Call as many times as you wish, only one repaint will be issued. /// Call as many times as you wish, only one repaint will be issued.
/// ///
/// It is safe to call this from any thread at any time.
///
/// If called from outside the UI thread, the UI thread will wake up and run, /// If called from outside the UI thread, the UI thread will wake up and run,
/// provided the egui integration has set that up via [`Self::set_request_repaint_callback`] /// provided the egui integration has set that up via [`Self::with_repaint_callback`]
/// (this will work on `eframe`). /// (this will work on `eframe`).
pub fn request_repaint(&self) { pub fn request_repaint(&self) {
// request two frames of repaint, just to cover some corner cases (frame delays): // request two frames of repaint, just to cover some corner cases (frame delays):
let mut ctx = self.write(); self.repaint_info.repaint_requests.store(2, SeqCst);
ctx.repaint_requests = 2; if let Some(callback) = &self.repaint_info.request_repaint_callbacks {
if let Some(callback) = &ctx.request_repaint_callbacks {
(callback)(); (callback)();
} }
} }
/// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`].
///
/// This lets you wake up a sleeping UI thread.
pub fn set_request_repaint_callback(&self, callback: impl Fn() + Send + Sync + 'static) {
let callback = Box::new(callback);
self.write().request_repaint_callbacks = Some(callback);
}
/// Tell `egui` which fonts to use. /// Tell `egui` which fonts to use.
/// ///
/// The default `egui` fonts only support latin and cyrillic alphabets, /// The default `egui` fonts only support latin and cyrillic alphabets,
@ -661,6 +687,8 @@ impl Context {
/// ///
/// For how to load an image, see [`ImageData`] and [`ColorImage::from_rgba_unmultiplied`]. /// For how to load an image, see [`ImageData`] and [`ColorImage::from_rgba_unmultiplied`].
/// ///
/// This is safe to call from any thread at any time.
///
/// ``` /// ```
/// struct MyImage { /// struct MyImage {
/// texture: Option<egui::TextureHandle>, /// texture: Option<egui::TextureHandle>,
@ -706,8 +734,10 @@ impl Context {
/// In general it is easier to use [`Self::load_texture`] and [`TextureHandle`]. /// In general it is easier to use [`Self::load_texture`] and [`TextureHandle`].
/// ///
/// You can show stats about the allocated textures using [`Self::texture_ui`]. /// You can show stats about the allocated textures using [`Self::texture_ui`].
pub fn tex_manager(&self) -> Arc<RwLock<epaint::textures::TextureManager>> { ///
self.read().tex_manager.0.clone() /// This is safe to call from any thread at any time.
pub fn tex_manager(&self) -> Arc<parking_lot::RwLock<epaint::textures::TextureManager>> {
self.tex_manager.0.clone()
} }
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -764,20 +794,19 @@ impl Context {
let font_image_delta = ctx_impl.fonts.as_ref().unwrap().font_image_delta(); let font_image_delta = ctx_impl.fonts.as_ref().unwrap().font_image_delta();
if let Some(font_image_delta) = font_image_delta { if let Some(font_image_delta) = font_image_delta {
ctx_impl self.tex_manager
.tex_manager
.0 .0
.write() .write()
.set(TextureId::default(), font_image_delta); .set(TextureId::default(), font_image_delta);
} }
textures_delta = ctx_impl.tex_manager.0.write().take_delta(); textures_delta = self.tex_manager.0.write().take_delta();
}; };
let platform_output: PlatformOutput = std::mem::take(&mut self.output()); let platform_output: PlatformOutput = std::mem::take(&mut self.output());
let needs_repaint = if self.read().repaint_requests > 0 { let needs_repaint = if self.repaint_info.repaint_requests.load(SeqCst) > 0 {
self.write().repaint_requests -= 1; self.repaint_info.repaint_requests.fetch_sub(1, SeqCst);
true true
} else { } else {
false false

View file

@ -46,20 +46,21 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi
let mut painter = crate::Painter::new(gl.clone(), None, "") let mut painter = crate::Painter::new(gl.clone(), None, "")
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
let event_loop_proxy = parking_lot::Mutex::new(event_loop.create_proxy());
let egui_ctx = egui::Context::with_repaint_callback({
move || {
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
}
});
let mut integration = egui_winit::epi::EpiIntegration::new( let mut integration = egui_winit::epi::EpiIntegration::new(
"egui_glow", "egui_glow",
egui_ctx,
painter.max_texture_side(), painter.max_texture_side(),
gl_window.window(), gl_window.window(),
persistence, persistence,
); );
{
let event_loop_proxy = parking_lot::Mutex::new(event_loop.create_proxy());
integration.egui_ctx.set_request_repaint_callback(move || {
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
});
}
let mut app = app_creator(&epi::CreationContext { let mut app = app_creator(&epi::CreationContext {
egui_ctx: integration.egui_ctx.clone(), egui_ctx: integration.egui_ctx.clone(),
integration_info: integration.frame.info(), integration_info: integration.frame.info(),

View file

@ -159,14 +159,12 @@ impl AppRunner {
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default(); let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
let egui_ctx = egui::Context::default(); let egui_ctx = egui::Context::with_repaint_callback({
{
let needs_repaint = needs_repaint.clone(); let needs_repaint = needs_repaint.clone();
egui_ctx.set_request_repaint_callback(move || { move || {
needs_repaint.0.store(true, SeqCst); needs_repaint.0.store(true, SeqCst);
});
} }
});
load_memory(&egui_ctx); load_memory(&egui_ctx);
if prefer_dark_mode == Some(true) { if prefer_dark_mode == Some(true) {

View file

@ -51,7 +51,7 @@ single_threaded = ["atomic_refcell"]
# Only needed if you plan to use the same fonts from multiple threads. # Only needed if you plan to use the same fonts from multiple threads.
# It comes with a minor performance impact. # It comes with a minor performance impact.
multi_threaded = ["parking_lot"] multi_threaded = []
[dependencies] [dependencies]
@ -63,7 +63,7 @@ atomic_refcell = { version = "0.1", optional = true } # Used
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
cint = { version = "^0.2.2", optional = true } cint = { version = "^0.2.2", optional = true }
nohash-hasher = "0.2" nohash-hasher = "0.2"
parking_lot = { version = "0.12", optional = true } # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. parking_lot = "0.12"
serde = { version = "1", optional = true, features = ["derive", "rc"] } serde = { version = "1", optional = true, features = ["derive", "rc"] }
[dev-dependencies] [dev-dependencies]

View file

@ -1,8 +1,6 @@
use crate::{ use crate::{emath::NumExt, mutex::Arc, ImageData, ImageDelta, TextureId, TextureManager};
emath::NumExt,
mutex::{Arc, RwLock}, use parking_lot::RwLock;
ImageData, ImageDelta, TextureId, TextureManager,
};
/// Used to paint images. /// Used to paint images.
/// ///