1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00

Make epi::Frame cloneable so you can allocate textures in other threads (#999)

Closes https://github.com/emilk/egui/issues/673

Also adds `epi::Image`
This commit is contained in:
Emil Ernerfeldt
2021-12-26 21:21:28 +01:00
committed by GitHub
parent 647e020824
commit b7441eeee7
28 changed files with 548 additions and 824 deletions

View File

@@ -6,6 +6,7 @@ All notable changes to the `egui_glium` integration will be noted in this file.
* Simplify `EguiGlium` interface ([#871](https://github.com/emilk/egui/pull/871)).
* Remove `EguiGlium::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)).
* Updated `glium` to 0.31 ([#930](https://github.com/emilk/egui/pull/930)).
* Changed the `Painter` inteface slightly ([#999](https://github.com/emilk/egui/pull/999)).
## 0.15.0 - 2021-10-24

View File

@@ -1,22 +1,6 @@
use crate::*;
use egui::Color32;
use glium::glutin;
impl epi::TextureAllocator for Painter {
fn alloc_srgba_premultiplied(
&mut self,
size: (usize, usize),
srgba_pixels: &[Color32],
) -> egui::TextureId {
let id = self.alloc_user_texture();
self.set_user_texture(id, size, srgba_pixels);
id
}
fn free(&mut self, id: egui::TextureId) {
self.free_user_texture(id);
}
}
use crate::*;
struct RequestRepaintEvent;
@@ -24,7 +8,7 @@ struct GliumRepaintSignal(
std::sync::Mutex<glutin::event_loop::EventLoopProxy<RequestRepaintEvent>>,
);
impl epi::RepaintSignal for GliumRepaintSignal {
impl epi::backend::RepaintSignal for GliumRepaintSignal {
fn request_repaint(&self) {
self.0.lock().unwrap().send_event(RequestRepaintEvent).ok();
}
@@ -64,7 +48,6 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
let mut integration = egui_winit::epi::EpiIntegration::new(
"egui_glium",
display.gl_window().window(),
&mut painter,
repaint_signal,
persistence,
app,
@@ -83,10 +66,15 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
std::thread::sleep(std::time::Duration::from_millis(10));
}
let (needs_repaint, shapes) =
integration.update(display.gl_window().window(), &mut painter);
let (needs_repaint, mut tex_allocation_data, shapes) =
integration.update(display.gl_window().window());
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
for (id, image) in tex_allocation_data.creations {
painter.set_texture(&display, id, &image);
}
// paint:
{
use glium::Surface as _;
let mut target = display.draw();
@@ -104,6 +92,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
target.finish().unwrap();
}
for id in tex_allocation_data.destructions.drain(..) {
painter.free_texture(id);
}
{
*control_flow = if integration.should_quit() {
glutin::event_loop::ControlFlow::Exit

View File

@@ -14,7 +14,7 @@ use {
uniform,
uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
},
std::rc::Rc,
std::{collections::HashMap, rc::Rc},
};
pub struct Painter {
@@ -22,19 +22,11 @@ pub struct Painter {
egui_texture: Option<SrgbTexture2d>,
egui_texture_version: Option<u64>,
/// `None` means unallocated (freed) slot.
user_textures: Vec<Option<UserTexture>>,
}
/// Index is the same as in [`egui::TextureId::User`].
user_textures: HashMap<u64, Rc<SrgbTexture2d>>,
#[derive(Default)]
struct UserTexture {
/// Pending upload (will be emptied later).
/// This is the format glium likes.
pixels: Vec<Vec<(u8, u8, u8, u8)>>,
/// Lazily uploaded from [`Self::pixels`],
/// or owned by the user via `register_native_texture`.
gl_texture: Option<Rc<SrgbTexture2d>>,
// TODO: 128-bit texture space?
next_native_tex_id: u64,
}
impl Painter {
@@ -65,6 +57,7 @@ impl Painter {
egui_texture: None,
egui_texture_version: None,
user_textures: Default::default(),
next_native_tex_id: 1 << 32,
}
}
@@ -106,7 +99,6 @@ impl Painter {
egui_texture: &egui::Texture,
) {
self.upload_egui_texture(display, egui_texture);
self.upload_pending_user_textures(display);
for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes {
self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh);
@@ -114,7 +106,7 @@ impl Painter {
}
#[inline(never)] // Easier profiling
pub fn paint_mesh<T: glium::Surface>(
fn paint_mesh<T: glium::Surface>(
&mut self,
target: &mut T,
display: &glium::Display,
@@ -229,82 +221,40 @@ impl Painter {
}
// ------------------------------------------------------------------------
// user textures: this is an experimental feature.
// No need to implement this in your egui integration!
pub fn alloc_user_texture(&mut self) -> egui::TextureId {
for (i, tex) in self.user_textures.iter_mut().enumerate() {
if tex.is_none() {
*tex = Some(Default::default());
return egui::TextureId::User(i as u64);
}
}
let id = egui::TextureId::User(self.user_textures.len() as u64);
self.user_textures.push(Some(Default::default()));
id
}
pub fn set_user_texture(
pub fn set_texture(
&mut self,
id: egui::TextureId,
size: (usize, usize),
pixels: &[Color32],
facade: &dyn glium::backend::Facade,
tex_id: u64,
image: &epi::Image,
) {
assert_eq!(
size.0 * size.1,
pixels.len(),
image.size[0] * image.size[1],
image.pixels.len(),
"Mismatch between texture size and texel count"
);
if let egui::TextureId::User(id) = id {
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = pixels
.chunks(size.0 as usize)
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
.collect();
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = image
.pixels
.chunks(image.size[0] as usize)
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
.collect();
*user_texture = UserTexture {
pixels,
gl_texture: None,
};
}
}
let format = texture::SrgbFormat::U8U8U8U8;
let mipmaps = texture::MipmapsOption::NoMipmap;
let gl_texture = SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap();
self.user_textures.insert(tex_id, gl_texture.into());
}
pub fn free_user_texture(&mut self, id: egui::TextureId) {
if let egui::TextureId::User(id) = id {
let index = id as usize;
if index < self.user_textures.len() {
self.user_textures[index] = None;
}
}
pub fn free_texture(&mut self, tex_id: u64) {
self.user_textures.remove(&tex_id);
}
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
match texture_id {
egui::TextureId::Egui => self.egui_texture.as_ref(),
egui::TextureId::User(id) => self
.user_textures
.get(id as usize)?
.as_ref()?
.gl_texture
.as_ref()
.map(|rc| rc.as_ref()),
}
}
pub fn upload_pending_user_textures(&mut self, facade: &dyn glium::backend::Facade) {
for user_texture in self.user_textures.iter_mut().flatten() {
if user_texture.gl_texture.is_none() {
let pixels = std::mem::take(&mut user_texture.pixels);
let format = texture::SrgbFormat::U8U8U8U8;
let mipmaps = texture::MipmapsOption::NoMipmap;
user_texture.gl_texture = Some(
SrgbTexture2d::with_format(facade, pixels, format, mipmaps)
.unwrap()
.into(),
);
}
egui::TextureId::User(id) => self.user_textures.get(&id).map(|rc| rc.as_ref()),
}
}
}
@@ -314,26 +264,15 @@ impl epi::NativeTexture for Painter {
type Texture = Rc<SrgbTexture2d>;
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
let id = self.alloc_user_texture();
if let egui::TextureId::User(id) = id {
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
*user_texture = UserTexture {
pixels: vec![],
gl_texture: Some(native),
}
}
}
id
let id = self.next_native_tex_id;
self.next_native_tex_id += 1;
self.user_textures.insert(id, native);
egui::TextureId::User(id as u64)
}
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
if let egui::TextureId::User(id) = id {
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
*user_texture = UserTexture {
pixels: vec![],
gl_texture: Some(replacing),
};
}
self.user_textures.insert(id, replacing);
}
}
}