1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 13:20:05 -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

@@ -67,7 +67,7 @@ impl NeedRepaint {
}
}
impl epi::RepaintSignal for NeedRepaint {
impl epi::backend::RepaintSignal for NeedRepaint {
fn request_repaint(&self) {
self.0.store(true, SeqCst);
}
@@ -76,28 +76,44 @@ impl epi::RepaintSignal for NeedRepaint {
// ----------------------------------------------------------------------------
pub struct AppRunner {
frame: epi::Frame,
egui_ctx: egui::CtxRef,
painter: Box<dyn Painter>,
previous_frame_time: Option<f32>,
pub(crate) input: WebInput,
app: Box<dyn epi::App>,
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
storage: LocalStorage,
prefer_dark_mode: Option<bool>,
last_save_time: f64,
screen_reader: crate::screen_reader::ScreenReader,
pub(crate) text_cursor_pos: Option<egui::Pos2>,
pub(crate) mutable_text_under_cursor: bool,
pending_texture_destructions: Vec<u64>,
}
impl AppRunner {
pub fn new(canvas_id: &str, app: Box<dyn epi::App>) -> Result<Self, JsValue> {
let egui_ctx = egui::CtxRef::default();
load_memory(&egui_ctx);
let painter = create_painter(canvas_id)?;
let prefer_dark_mode = crate::prefer_dark_mode();
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
let frame = epi::Frame::new(epi::backend::FrameData {
info: epi::IntegrationInfo {
name: painter.name(),
web_info: Some(epi::WebInfo {
web_location_hash: location_hash().unwrap_or_default(),
}),
prefer_dark_mode,
cpu_usage: None,
native_pixels_per_point: Some(native_pixels_per_point()),
},
output: Default::default(),
repaint_signal: needs_repaint.clone(),
});
let egui_ctx = egui::CtxRef::default();
load_memory(&egui_ctx);
if prefer_dark_mode == Some(true) {
egui_ctx.set_visuals(egui::Visuals::dark());
} else {
@@ -107,32 +123,24 @@ impl AppRunner {
let storage = LocalStorage::default();
let mut runner = Self {
frame,
egui_ctx,
painter: create_painter(canvas_id)?,
previous_frame_time: None,
painter,
input: Default::default(),
app,
needs_repaint: Default::default(),
needs_repaint,
storage,
prefer_dark_mode,
last_save_time: now_sec(),
screen_reader: Default::default(),
text_cursor_pos: None,
mutable_text_under_cursor: false,
pending_texture_destructions: Default::default(),
};
{
let mut app_output = epi::backend::AppOutput::default();
let mut frame = epi::backend::FrameBuilder {
info: runner.integration_info(),
tex_allocator: runner.painter.as_tex_allocator(),
output: &mut app_output,
repaint_signal: runner.needs_repaint.clone(),
}
.build();
runner
.app
.setup(&runner.egui_ctx, &mut frame, Some(&runner.storage));
.setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage));
}
Ok(runner)
@@ -170,18 +178,6 @@ impl AppRunner {
Ok(())
}
fn integration_info(&self) -> epi::IntegrationInfo {
epi::IntegrationInfo {
name: self.painter.name(),
web_info: Some(epi::WebInfo {
web_location_hash: location_hash().unwrap_or_default(),
}),
prefer_dark_mode: self.prefer_dark_mode,
cpu_usage: self.previous_frame_time,
native_pixels_per_point: Some(native_pixels_per_point()),
}
}
pub fn logic(&mut self) -> Result<(egui::Output, Vec<egui::ClippedMesh>), JsValue> {
let frame_start = now_sec();
@@ -189,33 +185,31 @@ impl AppRunner {
let canvas_size = canvas_size_in_points(self.canvas_id());
let raw_input = self.input.new_frame(canvas_size);
let mut app_output = epi::backend::AppOutput::default();
let mut frame = epi::backend::FrameBuilder {
info: self.integration_info(),
tex_allocator: self.painter.as_tex_allocator(),
output: &mut app_output,
repaint_signal: self.needs_repaint.clone(),
}
.build();
let (egui_output, shapes) = self.egui_ctx.run(raw_input, |egui_ctx| {
self.app.update(egui_ctx, &mut frame);
self.app.update(egui_ctx, &self.frame);
});
let clipped_meshes = self.egui_ctx.tessellate(shapes);
self.handle_egui_output(&egui_output);
{
let app_output = self.frame.take_app_output();
let epi::backend::AppOutput {
quit: _, // Can't quit a web page
window_size: _, // Can't resize a web page
window_title: _, // TODO: change title of window
decorated: _, // Can't toggle decorations
drag_window: _, // Can't be dragged
tex_allocation_data,
} = app_output;
for (id, image) in tex_allocation_data.creations {
self.painter.set_texture(id, image);
}
self.pending_texture_destructions = tex_allocation_data.destructions;
}
self.previous_frame_time = Some((now_sec() - frame_start) as f32);
self.frame.lock().info.cpu_usage = Some((now_sec() - frame_start) as f32);
Ok((egui_output, clipped_meshes))
}
@@ -223,7 +217,11 @@ impl AppRunner {
self.painter.upload_egui_texture(&self.egui_ctx.texture());
self.painter.clear(self.app.clear_color());
self.painter
.paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point())
.paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point())?;
for id in self.pending_texture_destructions.drain(..) {
self.painter.free_texture(id);
}
Ok(())
}
fn handle_egui_output(&mut self, output: &egui::Output) {

View File

@@ -1,13 +1,12 @@
#[cfg(not(target_arch = "wasm32"))]
use crate::web_sys::WebGl2RenderingContext;
use crate::web_sys::WebGlRenderingContext;
use crate::{canvas_element_or_die, console_error};
use egui::{ClippedMesh, Rgba, Texture};
use egui_glow::glow;
use epi::TextureAllocator;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web_sys::HtmlCanvasElement;
#[cfg(not(target_arch = "wasm32"))]
use web_sys::WebGl2RenderingContext;
use web_sys::WebGlRenderingContext;
pub(crate) struct WrappedGlowPainter {
pub(crate) gl_ctx: glow::Context,
@@ -67,8 +66,12 @@ fn requires_brightening(canvas: &web_sys::HtmlCanvasElement) -> bool {
}
impl crate::Painter for WrappedGlowPainter {
fn as_tex_allocator(&mut self) -> &mut dyn TextureAllocator {
&mut self.painter
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
self.painter.set_texture(&self.gl_ctx, tex_id, &image);
}
fn free_texture(&mut self, tex_id: u64) {
self.painter.free_texture(tex_id);
}
fn debug_info(&self) -> String {
@@ -99,8 +102,8 @@ impl crate::Painter for WrappedGlowPainter {
) -> Result<(), JsValue> {
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
self.painter.paint_meshes(
canvas_dimension,
&self.gl_ctx,
canvas_dimension,
pixels_per_point,
clipped_meshes,
);

View File

@@ -1,7 +1,9 @@
use wasm_bindgen::prelude::JsValue;
pub trait Painter {
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator;
fn set_texture(&mut self, tex_id: u64, image: epi::Image);
fn free_texture(&mut self, tex_id: u64);
fn debug_info(&self) -> String;

View File

@@ -1,3 +1,5 @@
use std::collections::HashMap;
use {
js_sys::WebAssembly,
wasm_bindgen::{prelude::*, JsCast},
@@ -29,19 +31,11 @@ pub struct WebGlPainter {
egui_texture: WebGlTexture,
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, WebGlTexture>,
#[derive(Default)]
struct UserTexture {
size: (usize, usize),
/// Pending upload (will be emptied later).
pixels: Vec<u8>,
/// Lazily uploaded
gl_texture: Option<WebGlTexture>,
// TODO: 128-bit texture space?
next_native_tex_id: u64,
}
impl WebGlPainter {
@@ -111,109 +105,14 @@ impl WebGlPainter {
egui_texture,
egui_texture_version: None,
user_textures: Default::default(),
next_native_tex_id: 1 << 32,
})
}
fn alloc_user_texture_index(&mut self) -> usize {
for (index, tex) in self.user_textures.iter_mut().enumerate() {
if tex.is_none() {
*tex = Some(Default::default());
return index;
}
}
let index = self.user_textures.len();
self.user_textures.push(Some(Default::default()));
index
}
fn alloc_user_texture(
&mut self,
size: (usize, usize),
srgba_pixels: &[Color32],
) -> egui::TextureId {
let index = self.alloc_user_texture_index();
assert_eq!(
size.0 * size.1,
srgba_pixels.len(),
"Mismatch between texture size and texel count"
);
if let Some(Some(user_texture)) = self.user_textures.get_mut(index) {
let mut pixels: Vec<u8> = Vec::with_capacity(srgba_pixels.len() * 4);
for srgba in srgba_pixels {
pixels.push(srgba.r());
pixels.push(srgba.g());
pixels.push(srgba.b());
pixels.push(srgba.a());
}
*user_texture = UserTexture {
size,
pixels,
gl_texture: None,
};
}
egui::TextureId::User(index as u64)
}
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 get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
match texture_id {
egui::TextureId::Egui => Some(&self.egui_texture),
egui::TextureId::User(id) => self
.user_textures
.get(id as usize)?
.as_ref()?
.gl_texture
.as_ref(),
}
}
fn upload_user_textures(&mut self) {
let gl = &self.gl;
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 gl_texture = gl.create_texture().unwrap();
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
let level = 0;
let internal_format = self.texture_format;
let border = 0;
let src_format = self.texture_format;
let src_type = Gl::UNSIGNED_BYTE;
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
Gl::TEXTURE_2D,
level,
internal_format as i32,
user_texture.size.0 as i32,
user_texture.size.1 as i32,
border,
src_format,
src_type,
Some(&pixels),
)
.unwrap();
user_texture.gl_texture = Some(gl_texture);
}
egui::TextureId::User(id) => self.user_textures.get(&id),
}
}
@@ -338,51 +237,75 @@ impl WebGlPainter {
}
}
impl epi::TextureAllocator for WebGlPainter {
fn alloc_srgba_premultiplied(
&mut self,
size: (usize, usize),
srgba_pixels: &[egui::Color32],
) -> egui::TextureId {
self.alloc_user_texture(size, srgba_pixels)
}
fn free(&mut self, id: egui::TextureId) {
self.free_user_texture(id)
}
}
impl epi::NativeTexture for WebGlPainter {
type Texture = WebGlTexture;
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
let id = self.alloc_user_texture_index();
if let Some(Some(user_texture)) = self.user_textures.get_mut(id) {
*user_texture = UserTexture {
size: (0, 0),
pixels: vec![],
gl_texture: Some(native),
}
}
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 {
size: (0, 0),
pixels: vec![],
gl_texture: Some(replacing),
}
if let Some(user_texture) = self.user_textures.get_mut(&id) {
*user_texture = replacing;
}
}
}
}
impl crate::Painter for WebGlPainter {
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator {
self
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
assert_eq!(
image.size[0] * image.size[1],
image.pixels.len(),
"Mismatch between texture size and texel count"
);
// TODO: optimize
let mut pixels: Vec<u8> = Vec::with_capacity(image.pixels.len() * 4);
for srgba in image.pixels {
pixels.push(srgba.r());
pixels.push(srgba.g());
pixels.push(srgba.b());
pixels.push(srgba.a());
}
let gl = &self.gl;
let gl_texture = gl.create_texture().unwrap();
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as _);
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _);
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _);
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _);
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
let level = 0;
let internal_format = self.texture_format;
let border = 0;
let src_format = self.texture_format;
let src_type = Gl::UNSIGNED_BYTE;
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
Gl::TEXTURE_2D,
level,
internal_format as _,
image.size[0] as _,
image.size[1] as _,
border,
src_format,
src_type,
Some(&pixels),
)
.unwrap();
self.user_textures.insert(tex_id, gl_texture);
}
fn free_texture(&mut self, tex_id: u64) {
self.user_textures.remove(&tex_id);
}
fn debug_info(&self) -> String {
@@ -467,8 +390,6 @@ impl crate::Painter for WebGlPainter {
clipped_meshes: Vec<egui::ClippedMesh>,
pixels_per_point: f32,
) -> Result<(), JsValue> {
self.upload_user_textures();
let gl = &self.gl;
if let Some(ref mut post_process) = self.post_process {

View File

@@ -1,4 +1,5 @@
//! Mostly a carbon-copy of `webgl1.rs`.
use std::collections::HashMap;
use {
js_sys::WebAssembly,
@@ -30,19 +31,11 @@ pub struct WebGl2Painter {
egui_texture: WebGlTexture,
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, WebGlTexture>,
#[derive(Default)]
struct UserTexture {
size: (usize, usize),
/// Pending upload (will be emptied later).
pixels: Vec<u8>,
/// Lazily uploaded
gl_texture: Option<WebGlTexture>,
// TODO: 128-bit texture space?
next_native_tex_id: u64,
}
impl WebGl2Painter {
@@ -96,109 +89,14 @@ impl WebGl2Painter {
egui_texture,
egui_texture_version: None,
user_textures: Default::default(),
next_native_tex_id: 1 << 32,
})
}
fn alloc_user_texture_index(&mut self) -> usize {
for (index, tex) in self.user_textures.iter_mut().enumerate() {
if tex.is_none() {
*tex = Some(Default::default());
return index;
}
}
let index = self.user_textures.len();
self.user_textures.push(Some(Default::default()));
index
}
fn alloc_user_texture(
&mut self,
size: (usize, usize),
srgba_pixels: &[Color32],
) -> egui::TextureId {
let index = self.alloc_user_texture_index();
assert_eq!(
size.0 * size.1,
srgba_pixels.len(),
"Mismatch between texture size and texel count"
);
if let Some(Some(user_texture)) = self.user_textures.get_mut(index) {
let mut pixels: Vec<u8> = Vec::with_capacity(srgba_pixels.len() * 4);
for srgba in srgba_pixels {
pixels.push(srgba.r());
pixels.push(srgba.g());
pixels.push(srgba.b());
pixels.push(srgba.a());
}
*user_texture = UserTexture {
size,
pixels,
gl_texture: None,
};
}
egui::TextureId::User(index as u64)
}
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 get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
match texture_id {
egui::TextureId::Egui => Some(&self.egui_texture),
egui::TextureId::User(id) => self
.user_textures
.get(id as usize)?
.as_ref()?
.gl_texture
.as_ref(),
}
}
fn upload_user_textures(&mut self) {
let gl = &self.gl;
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 gl_texture = gl.create_texture().unwrap();
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
let level = 0;
let internal_format = Gl::SRGB8_ALPHA8;
let border = 0;
let src_format = Gl::RGBA;
let src_type = Gl::UNSIGNED_BYTE;
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
Gl::TEXTURE_2D,
level,
internal_format as i32,
user_texture.size.0 as i32,
user_texture.size.1 as i32,
border,
src_format,
src_type,
Some(&pixels),
)
.unwrap();
user_texture.gl_texture = Some(gl_texture);
}
egui::TextureId::User(id) => self.user_textures.get(&id),
}
}
@@ -323,51 +221,75 @@ impl WebGl2Painter {
}
}
impl epi::TextureAllocator for WebGl2Painter {
fn alloc_srgba_premultiplied(
&mut self,
size: (usize, usize),
srgba_pixels: &[egui::Color32],
) -> egui::TextureId {
self.alloc_user_texture(size, srgba_pixels)
}
fn free(&mut self, id: egui::TextureId) {
self.free_user_texture(id)
}
}
impl epi::NativeTexture for WebGl2Painter {
type Texture = WebGlTexture;
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
let id = self.alloc_user_texture_index();
if let Some(Some(user_texture)) = self.user_textures.get_mut(id) {
*user_texture = UserTexture {
size: (0, 0),
pixels: vec![],
gl_texture: Some(native),
}
}
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 {
size: (0, 0),
pixels: vec![],
gl_texture: Some(replacing),
}
if let Some(user_texture) = self.user_textures.get_mut(&id) {
*user_texture = replacing;
}
}
}
}
impl crate::Painter for WebGl2Painter {
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator {
self
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
assert_eq!(
image.size[0] * image.size[1],
image.pixels.len(),
"Mismatch between texture size and texel count"
);
// TODO: optimize
let mut pixels: Vec<u8> = Vec::with_capacity(image.pixels.len() * 4);
for srgba in image.pixels {
pixels.push(srgba.r());
pixels.push(srgba.g());
pixels.push(srgba.b());
pixels.push(srgba.a());
}
let gl = &self.gl;
let gl_texture = gl.create_texture().unwrap();
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as _);
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _);
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _);
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _);
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
let level = 0;
let internal_format = Gl::SRGB8_ALPHA8;
let border = 0;
let src_format = Gl::RGBA;
let src_type = Gl::UNSIGNED_BYTE;
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
Gl::TEXTURE_2D,
level,
internal_format as _,
image.size[0] as _,
image.size[1] as _,
border,
src_format,
src_type,
Some(&pixels),
)
.unwrap();
self.user_textures.insert(tex_id, gl_texture);
}
fn free_texture(&mut self, tex_id: u64) {
self.user_textures.remove(&tex_id);
}
fn debug_info(&self) -> String {
@@ -448,8 +370,6 @@ impl crate::Painter for WebGl2Painter {
clipped_meshes: Vec<egui::ClippedMesh>,
pixels_per_point: f32,
) -> Result<(), JsValue> {
self.upload_user_textures();
let gl = &self.gl;
self.post_process