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
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 548 additions and 824 deletions

View file

@ -5,6 +5,9 @@ NOTE: [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.m
## Unreleased
* `Frame` can now be cloned, saved, and passed to background threads ([#999](https://github.com/emilk/egui/pull/999)).
* Added `Frame::request_repaint` to replace `repaint_signal` ([#999](https://github.com/emilk/egui/pull/999)).
* Added `Frame::alloc_texture/free_texture` to replace `tex_allocator` ([#999](https://github.com/emilk/egui/pull/999)).
## 0.15.0 - 2021-10-24

View file

@ -11,7 +11,7 @@ impl epi::App for MyApp {
"Native file dialogs and drag-and-drop files"
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.label("Drag-and-drop files onto the window!");

View file

@ -19,7 +19,7 @@ impl epi::App for MyApp {
"My egui App"
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
let Self { name, age } = self;
egui::CentralPanel::default().show(ctx, |ui| {

View file

@ -10,26 +10,20 @@ impl epi::App for MyApp {
"Show an image with eframe/egui"
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
if self.texture.is_none() {
// Load the image:
let image_data = include_bytes!("rust-logo-256x256.png");
use image::GenericImageView;
let image = image::load_from_memory(image_data).expect("Failed to load image");
let image_buffer = image.to_rgba8();
let size = (image.width() as usize, image.height() as usize);
let size = [image.width() as usize, image.height() as usize];
let pixels = image_buffer.into_vec();
assert_eq!(size.0 * size.1 * 4, pixels.len());
let pixels: Vec<_> = pixels
.chunks_exact(4)
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
.collect();
let image = epi::Image::from_rgba_unmultiplied(size, &pixels);
// Allocate a texture:
let texture = frame
.tex_allocator()
.alloc_srgba_premultiplied(size, &pixels);
let size = egui::Vec2::new(size.0 as f32, size.1 as f32);
let texture = frame.alloc_texture(image);
let size = egui::Vec2::new(size[0] as f32, size[1] as f32);
self.texture = Some((size, texture));
}

View file

@ -24,7 +24,7 @@
//! "My egui App"
//! }
//!
//! fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
//! fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
//! egui::CentralPanel::default().show(ctx, |ui| {
//! ui.heading("Hello World!");
//! });
@ -127,7 +127,7 @@ pub fn start_web(canvas_id: &str, app: Box<dyn epi::App>) -> Result<(), wasm_bin
/// "My egui App"
/// }
///
/// fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
/// fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
/// egui::CentralPanel::default().show(ctx, |ui| {
/// ui.heading("Hello World!");
/// });

View file

@ -9,5 +9,6 @@ All notable changes to the `egui-winit` integration will be noted in this file.
* Remove `State::is_quit_event` and `State::is_quit_shortcut` ([#881](https://github.com/emilk/egui/pull/881)).
* Updated `winit` to 0.26 ([#930](https://github.com/emilk/egui/pull/930)).
## 0.15.0 - 2021-10-24
First stand-alone release. Previously part of `egui_glium`.

View file

@ -55,9 +55,10 @@ pub fn handle_app_output(
window: &winit::window::Window,
current_pixels_per_point: f32,
app_output: epi::backend::AppOutput,
) {
) -> epi::backend::TexAllocationData {
let epi::backend::AppOutput {
quit: _,
tex_allocation_data,
window_size,
window_title,
decorated,
@ -85,6 +86,8 @@ pub fn handle_app_output(
if drag_window {
let _ = window.drag_window();
}
tex_allocation_data
}
// ----------------------------------------------------------------------------
@ -186,13 +189,11 @@ impl Persistence {
/// Everything needed to make a winit-based integration for [`epi`].
pub struct EpiIntegration {
integration_name: &'static str,
frame: epi::Frame,
persistence: crate::epi::Persistence,
repaint_signal: std::sync::Arc<dyn epi::RepaintSignal>,
pub egui_ctx: egui::CtxRef,
egui_winit: crate::State,
pub app: Box<dyn epi::App>,
latest_frame_time: Option<f32>,
/// When set, it is time to quit
quit: bool,
}
@ -201,8 +202,7 @@ impl EpiIntegration {
pub fn new(
integration_name: &'static str,
window: &winit::window::Window,
tex_allocator: &mut dyn epi::TextureAllocator,
repaint_signal: std::sync::Arc<dyn epi::RepaintSignal>,
repaint_signal: std::sync::Arc<dyn epi::backend::RepaintSignal>,
persistence: crate::epi::Persistence,
app: Box<dyn epi::App>,
) -> Self {
@ -210,54 +210,50 @@ impl EpiIntegration {
*egui_ctx.memory() = persistence.load_memory().unwrap_or_default();
let mut slf = Self {
integration_name,
persistence,
let frame = epi::Frame::new(epi::backend::FrameData {
info: epi::IntegrationInfo {
name: integration_name,
web_info: None,
prefer_dark_mode: None, // TODO: figure out system default
cpu_usage: None,
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
},
output: Default::default(),
repaint_signal,
});
let mut slf = Self {
frame,
persistence,
egui_ctx,
egui_winit: crate::State::new(window),
app,
latest_frame_time: None,
quit: false,
};
slf.setup(window, tex_allocator);
slf.setup(window);
if slf.app.warm_up_enabled() {
slf.warm_up(window, tex_allocator);
slf.warm_up(window);
}
slf
}
fn setup(
&mut self,
window: &winit::window::Window,
tex_allocator: &mut dyn epi::TextureAllocator,
) {
let mut app_output = epi::backend::AppOutput::default();
let mut frame = epi::backend::FrameBuilder {
info: integration_info(self.integration_name, window, None),
tex_allocator,
output: &mut app_output,
repaint_signal: self.repaint_signal.clone(),
}
.build();
fn setup(&mut self, window: &winit::window::Window) {
self.app
.setup(&self.egui_ctx, &mut frame, self.persistence.storage());
.setup(&self.egui_ctx, &self.frame, self.persistence.storage());
let app_output = self.frame.take_app_output();
self.quit |= app_output.quit;
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
let tex_alloc_data =
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
self.frame.lock().output.tex_allocation_data = tex_alloc_data; // Do it later
}
fn warm_up(
&mut self,
window: &winit::window::Window,
tex_allocator: &mut dyn epi::TextureAllocator,
) {
fn warm_up(&mut self, window: &winit::window::Window) {
let saved_memory = self.egui_ctx.memory().clone();
self.egui_ctx.memory().set_everything_is_visible(true);
self.update(window, tex_allocator);
let (_, tex_alloc_data, _) = self.update(window);
self.frame.lock().output.tex_allocation_data = tex_alloc_data; // handle it next frame
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
self.egui_ctx.clear_animations();
}
@ -277,37 +273,31 @@ impl EpiIntegration {
pub fn update(
&mut self,
window: &winit::window::Window,
tex_allocator: &mut dyn epi::TextureAllocator,
) -> (bool, Vec<egui::epaint::ClippedShape>) {
) -> (
bool,
epi::backend::TexAllocationData,
Vec<egui::epaint::ClippedShape>,
) {
let frame_start = std::time::Instant::now();
let raw_input = self.egui_winit.take_egui_input(window);
let mut app_output = epi::backend::AppOutput::default();
let mut frame = epi::backend::FrameBuilder {
info: integration_info(self.integration_name, window, self.latest_frame_time),
tex_allocator,
output: &mut app_output,
repaint_signal: self.repaint_signal.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 needs_repaint = egui_output.needs_repaint;
self.egui_winit
.handle_output(window, &self.egui_ctx, egui_output);
let app_output = self.frame.take_app_output();
self.quit |= app_output.quit;
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
let tex_allocation_data =
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
let frame_time = (std::time::Instant::now() - frame_start).as_secs_f64() as f32;
self.latest_frame_time = Some(frame_time);
self.frame.lock().info.cpu_usage = Some(frame_time);
(needs_repaint, shapes)
(needs_repaint, tex_allocation_data, shapes)
}
pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
@ -321,17 +311,3 @@ impl EpiIntegration {
.save(&mut *self.app, &self.egui_ctx, window);
}
}
fn integration_info(
integration_name: &'static str,
window: &winit::window::Window,
previous_frame_time: Option<f32>,
) -> epi::IntegrationInfo {
epi::IntegrationInfo {
name: integration_name,
web_info: None,
prefer_dark_mode: None, // TODO: figure out system default
cpu_usage: previous_frame_time,
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
}
}

View file

@ -34,7 +34,7 @@ impl epi::App for ColorTest {
"🎨 Color test"
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
if frame.is_web() {
ui.label(
@ -43,18 +43,14 @@ impl epi::App for ColorTest {
ui.separator();
}
ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| {
self.ui(ui, &mut Some(frame.tex_allocator()));
self.ui(ui, Some(frame));
});
});
}
}
impl ColorTest {
pub fn ui(
&mut self,
ui: &mut Ui,
mut tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
) {
pub fn ui(&mut self, ui: &mut Ui, tex_allocator: Option<&dyn epi::TextureAllocator>) {
ui.set_max_width(680.0);
ui.vertical_centered(|ui| {
@ -105,10 +101,10 @@ impl ColorTest {
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
self.tex_gradient(ui, tex_allocator, "Ground truth (texture)", WHITE, &g);
}
if let Some(tex_allocator) = &mut tex_allocator {
if let Some(tex_allocator) = tex_allocator {
ui.horizontal(|ui| {
let g = Gradient::one_color(Color32::from(tex_color));
let tex = self.tex_mngr.get(*tex_allocator, &g);
let tex = self.tex_mngr.get(tex_allocator, &g);
let texel_offset = 0.5 / (g.0.len() as f32);
let uv =
Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
@ -167,7 +163,7 @@ impl ColorTest {
fn show_gradients(
&mut self,
ui: &mut Ui,
tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
tex_allocator: Option<&dyn epi::TextureAllocator>,
bg_fill: Color32,
(left, right): (Color32, Color32),
) {
@ -261,7 +257,7 @@ impl ColorTest {
fn tex_gradient(
&mut self,
ui: &mut Ui,
tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
tex_allocator: Option<&dyn epi::TextureAllocator>,
label: &str,
bg_fill: Color32,
gradient: &Gradient,
@ -271,7 +267,7 @@ impl ColorTest {
}
if let Some(tex_allocator) = tex_allocator {
ui.horizontal(|ui| {
let tex = self.tex_mngr.get(*tex_allocator, gradient);
let tex = self.tex_mngr.get(tex_allocator, gradient);
let texel_offset = 0.5 / (gradient.0.len() as f32);
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
ui.add(Image::new(tex, GRADIENT_SIZE).bg_fill(bg_fill).uv(uv))
@ -391,16 +387,15 @@ impl Gradient {
struct TextureManager(HashMap<Gradient, TextureId>);
impl TextureManager {
fn get(
&mut self,
tex_allocator: &mut dyn epi::TextureAllocator,
gradient: &Gradient,
) -> TextureId {
fn get(&mut self, tex_allocator: &dyn epi::TextureAllocator, gradient: &Gradient) -> TextureId {
*self.0.entry(gradient.clone()).or_insert_with(|| {
let pixels = gradient.to_pixel_row();
let width = pixels.len();
let height = 1;
tex_allocator.alloc_srgba_premultiplied((width, height), &pixels)
tex_allocator.alloc(epi::Image {
size: [width, height],
pixels,
})
})
}
}

View file

@ -17,7 +17,7 @@ impl epi::App for DemoApp {
fn setup(
&mut self,
_ctx: &egui::CtxRef,
_frame: &mut epi::Frame<'_>,
_frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>,
) {
#[cfg(feature = "persistence")]
@ -31,7 +31,7 @@ impl epi::App for DemoApp {
epi::set_value(storage, epi::APP_KEY, self);
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
self.demo_windows.ui(ctx);
}
}

View file

@ -37,7 +37,7 @@ impl epi::App for FractalClock {
"🕑 Fractal Clock"
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
egui::CentralPanel::default()
.frame(Frame::dark_canvas(&ctx.style()))
.show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight()));

View file

@ -7,7 +7,7 @@ struct Resource {
text: Option<String>,
/// If set, the response was an image.
image: Option<Image>,
image: Option<epi::Image>,
/// If set, the response was text with some supported syntax highlighting (e.g. ".rs" or ".md").
colored_text: Option<ColoredText>,
@ -17,7 +17,7 @@ impl Resource {
fn from_response(ctx: &egui::Context, response: ehttp::Response) -> Self {
let content_type = response.content_type().unwrap_or_default();
let image = if content_type.starts_with("image/") {
Image::decode(&response.bytes)
decode_image(&response.bytes)
} else {
None
};
@ -67,7 +67,7 @@ impl epi::App for HttpApp {
"⬇ HTTP"
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
if let Some(receiver) = &mut self.in_progress {
// Are we there yet?
if let Ok(result) = receiver.try_recv() {
@ -95,13 +95,13 @@ impl epi::App for HttpApp {
if trigger_fetch {
let request = ehttp::Request::get(&self.url);
let repaint_signal = frame.repaint_signal();
let frame = frame.clone();
let (sender, receiver) = std::sync::mpsc::channel();
self.in_progress = Some(receiver);
ehttp::fetch(request, move |response| {
sender.send(response).ok();
repaint_signal.request_repaint();
frame.request_repaint();
});
}
@ -127,7 +127,7 @@ impl epi::App for HttpApp {
}
}
fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> bool {
fn ui_url(ui: &mut egui::Ui, frame: &epi::Frame, url: &mut String) -> bool {
let mut trigger_fetch = false;
ui.horizontal(|ui| {
@ -160,12 +160,7 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> bo
trigger_fetch
}
fn ui_resource(
ui: &mut egui::Ui,
frame: &mut epi::Frame<'_>,
tex_mngr: &mut TexMngr,
resource: &Resource,
) {
fn ui_resource(ui: &mut egui::Ui, frame: &epi::Frame, tex_mngr: &mut TexMngr, resource: &Resource) {
let Resource {
response,
text,
@ -218,7 +213,7 @@ fn ui_resource(
if let Some(image) = image {
if let Some(texture_id) = tex_mngr.texture(frame, &response.url, image) {
let mut size = egui::Vec2::new(image.size.0 as f32, image.size.1 as f32);
let mut size = egui::Vec2::new(image.size[0] as f32, image.size[1] as f32);
size *= (ui.available_width() / size.x).min(1.0);
ui.image(texture_id, size);
}
@ -304,44 +299,27 @@ struct TexMngr {
impl TexMngr {
fn texture(
&mut self,
frame: &mut epi::Frame<'_>,
frame: &epi::Frame,
url: &str,
image: &Image,
image: &epi::Image,
) -> Option<egui::TextureId> {
if self.loaded_url != url {
if let Some(texture_id) = self.texture_id.take() {
frame.tex_allocator().free(texture_id);
frame.free_texture(texture_id);
}
self.texture_id = Some(
frame
.tex_allocator()
.alloc_srgba_premultiplied(image.size, &image.pixels),
);
self.texture_id = Some(frame.alloc_texture(image.clone()));
self.loaded_url = url.to_owned();
}
self.texture_id
}
}
struct Image {
size: (usize, usize),
pixels: Vec<egui::Color32>,
}
impl Image {
fn decode(bytes: &[u8]) -> Option<Image> {
use image::GenericImageView;
let image = image::load_from_memory(bytes).ok()?;
let image_buffer = image.to_rgba8();
let size = (image.width() as usize, image.height() as usize);
let pixels = image_buffer.into_vec();
assert_eq!(size.0 * size.1 * 4, pixels.len());
let pixels = pixels
.chunks(4)
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
.collect();
Some(Image { size, pixels })
}
fn decode_image(bytes: &[u8]) -> Option<epi::Image> {
use image::GenericImageView;
let image = image::load_from_memory(bytes).ok()?;
let image_buffer = image.to_rgba8();
let size = [image.width() as usize, image.height() as usize];
let pixels = image_buffer.into_vec();
Some(epi::Image::from_rgba_unmultiplied(size, &pixels))
}

View file

@ -78,7 +78,7 @@ impl Default for BackendPanel {
}
impl BackendPanel {
pub fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
pub fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
self.frame_history
.on_new_frame(ctx.input().time, frame.info().cpu_usage);
@ -92,7 +92,7 @@ impl BackendPanel {
self.egui_windows.windows(ctx);
}
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) {
egui::trace!(ui);
ui.vertical_centered(|ui| {
ui.heading("💻 Backend");
@ -147,7 +147,7 @@ impl BackendPanel {
}
}
fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) {
if frame.is_web() {
ui.label("egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
ui.label(
@ -170,13 +170,13 @@ impl BackendPanel {
}
}
show_integration_name(ui, frame.info());
show_integration_name(ui, &frame.info());
// For instance: `egui_web` sets `pixels_per_point` every frame to force
// egui to use the same scale as the web zoom factor.
let integration_controls_pixels_per_point = ui.input().raw.pixels_per_point.is_some();
if !integration_controls_pixels_per_point {
if let Some(new_pixels_per_point) = self.pixels_per_point_ui(ui, frame.info()) {
if let Some(new_pixels_per_point) = self.pixels_per_point_ui(ui, &frame.info()) {
ui.ctx().set_pixels_per_point(new_pixels_per_point);
}
}

View file

@ -34,7 +34,7 @@ impl epi::App for EasyMarkEditor {
"🖹 EasyMark editor"
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {

View file

@ -45,7 +45,7 @@ impl epi::App for WrapApp {
fn setup(
&mut self,
_ctx: &egui::CtxRef,
_frame: &mut epi::Frame<'_>,
_frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>,
) {
#[cfg(feature = "persistence")]
@ -72,7 +72,7 @@ impl epi::App for WrapApp {
cfg!(not(debug_assertions))
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
if let Some(web_info) = frame.info().web_info.as_ref() {
if let Some(anchor) = web_info.web_location_hash.strip_prefix('#') {
self.selected_anchor = anchor.to_owned();
@ -126,7 +126,7 @@ impl epi::App for WrapApp {
}
impl WrapApp {
fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) {
// A menu-bar is a horizontal layout with some special styles applied.
// egui::menu::bar(ui, |ui| {
ui.horizontal_wrapped(|ui| {

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);
}
}
}

View file

@ -7,6 +7,7 @@ All notable changes to the `egui_glow` integration will be noted in this file.
* Simplify `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)).
* Remove `EguiGlow::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)).
* Updated `glutin` to 0.28 ([#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

@ -4,7 +4,7 @@ struct RequestRepaintEvent;
struct GlowRepaintSignal(std::sync::Mutex<glutin::event_loop::EventLoopProxy<RequestRepaintEvent>>);
impl epi::RepaintSignal for GlowRepaintSignal {
impl epi::backend::RepaintSignal for GlowRepaintSignal {
fn request_repaint(&self) {
self.0.lock().unwrap().send_event(RequestRepaintEvent).ok();
}
@ -64,7 +64,6 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
let mut integration = egui_winit::epi::EpiIntegration::new(
"egui_glow",
gl_window.window(),
&mut painter,
repaint_signal,
persistence,
app,
@ -83,9 +82,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(gl_window.window(), &mut painter);
let (needs_repaint, mut tex_allocation_data, shapes) =
integration.update(gl_window.window());
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
for (id, image) in tex_allocation_data.creations {
painter.set_texture(&gl, id, &image);
}
// paint:
{
let color = integration.app.clear_color();
unsafe {
@ -96,8 +101,8 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
}
painter.upload_egui_texture(&gl, &integration.egui_ctx.texture());
painter.paint_meshes(
gl_window.window().inner_size().into(),
&gl,
gl_window.window().inner_size().into(),
integration.egui_ctx.pixels_per_point(),
clipped_meshes,
);
@ -105,6 +110,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
gl_window.swap_buffers().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

@ -166,8 +166,8 @@ impl EguiGlow {
self.painter
.upload_egui_texture(gl, &self.egui_ctx.texture());
self.painter.paint_meshes(
dimensions,
gl,
dimensions,
self.egui_ctx.pixels_per_point(),
clipped_meshes,
);

View file

@ -1,14 +1,13 @@
#![allow(unsafe_code)]
use std::collections::HashMap;
use egui::{
emath::Rect,
epaint::{Color32, Mesh, Vertex},
};
pub use glow::Context;
use memoffset::offset_of;
use glow::HasContext;
use memoffset::offset_of;
use crate::misc_util::{
as_u8_slice, compile_shader, glow_debug_print, link_program, srgbtexture2d,
@ -17,6 +16,8 @@ use crate::post_process::PostProcess;
use crate::shader_version::ShaderVersion;
use crate::vao_emulate;
pub use glow::Context;
const VERT_SRC: &str = include_str!("shader/vertex.glsl");
const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
@ -34,29 +35,21 @@ pub struct Painter {
is_embedded: bool,
vertex_array: crate::misc_util::VAO,
srgb_support: bool,
/// `None` means unallocated (freed) slot.
pub(crate) user_textures: Vec<Option<UserTexture>>,
post_process: Option<PostProcess>,
vertex_buffer: glow::Buffer,
element_array_buffer: glow::Buffer,
// Stores outdated OpenGL textures that are yet to be deleted
old_textures: Vec<glow::Texture>,
// Only used in debug builds, to make sure we are destroyed correctly.
/// Index is the same as in [`egui::TextureId::User`].
user_textures: HashMap<u64, glow::Texture>,
// TODO: 128-bit texture space?
next_native_tex_id: u64,
/// Stores outdated OpenGL textures that are yet to be deleted
textures_to_destroy: Vec<glow::Texture>,
/// Only used in debug builds, to make sure we are destroyed correctly.
destroyed: bool,
}
#[derive(Default)]
pub(crate) struct UserTexture {
/// Pending upload (will be emptied later).
/// This is the format glow likes.
pub(crate) data: Vec<u8>,
pub(crate) size: (usize, usize),
/// Lazily uploaded
pub(crate) gl_texture: Option<glow::Texture>,
}
impl Painter {
/// Create painter.
///
@ -195,11 +188,12 @@ impl Painter {
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
vertex_array,
srgb_support,
user_textures: Default::default(),
post_process,
vertex_buffer,
element_array_buffer,
old_textures: Vec::new(),
user_textures: Default::default(),
next_native_tex_id: 1 << 32,
textures_to_destroy: Vec::new(),
destroyed: false,
})
}
@ -298,15 +292,13 @@ impl Painter {
/// of the effects your program might have on this code. Look at the source if in doubt.
pub fn paint_meshes(
&mut self,
inner_size: [u32; 2],
gl: &glow::Context,
inner_size: [u32; 2],
pixels_per_point: f32,
clipped_meshes: Vec<egui::ClippedMesh>,
) {
//chimera of egui_glow and egui_web
self.assert_not_destroyed();
self.upload_pending_user_textures(gl);
if let Some(ref mut post_process) = self.post_process {
unsafe {
post_process.begin(gl, inner_size[0] as i32, inner_size[1] as i32);
@ -393,128 +385,47 @@ 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 {
pub fn set_texture(&mut self, gl: &glow::Context, tex_id: u64, image: &epi::Image) {
self.assert_not_destroyed();
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
}
/// register glow texture as egui texture
/// Usable for render to image rectangle
#[allow(clippy::needless_pass_by_value)]
pub fn register_glow_texture(&mut self, texture: glow::Texture) -> egui::TextureId {
self.assert_not_destroyed();
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) {
if let UserTexture {
gl_texture: Some(old_tex),
..
} = std::mem::replace(
user_texture,
UserTexture {
data: vec![],
size: (0, 0),
gl_texture: Some(texture),
},
) {
self.old_textures.push(old_tex);
}
}
}
id
}
pub fn set_user_texture(
&mut self,
id: egui::TextureId,
size: (usize, usize),
pixels: &[Color32],
) {
self.assert_not_destroyed();
assert_eq!(
size.0 * size.1,
pixels.len(),
"Mismatch between size and texel count"
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 data: Vec<u8> = pixels
.iter()
.flat_map(|srgba| Vec::from(srgba.to_array()))
.collect();
// TODO: optimize
let pixels: Vec<u8> = image
.pixels
.iter()
.flat_map(|srgba| Vec::from(srgba.to_array()))
.collect();
if let UserTexture {
gl_texture: Some(old_tex),
..
} = std::mem::replace(
user_texture,
UserTexture {
data,
size,
gl_texture: None,
},
) {
self.old_textures.push(old_tex);
}
}
let gl_texture = srgbtexture2d(
gl,
self.is_webgl_1,
self.srgb_support,
&pixels,
image.size[0],
image.size[1],
);
if let Some(old_tex) = self.user_textures.insert(tex_id, gl_texture) {
self.textures_to_destroy.push(old_tex);
}
}
pub fn free_user_texture(&mut self, id: egui::TextureId) {
self.assert_not_destroyed();
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<glow::Texture> {
fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
self.assert_not_destroyed();
match texture_id {
egui::TextureId::Egui => self.egui_texture,
egui::TextureId::User(id) => self.user_textures.get(id as usize)?.as_ref()?.gl_texture,
}
}
pub fn upload_pending_user_textures(&mut self, gl: &glow::Context) {
self.assert_not_destroyed();
for user_texture in self.user_textures.iter_mut().flatten() {
if user_texture.gl_texture.is_none() {
let data = std::mem::take(&mut user_texture.data);
user_texture.gl_texture = Some(srgbtexture2d(
gl,
self.is_webgl_1,
self.srgb_support,
&data,
user_texture.size.0,
user_texture.size.1,
));
user_texture.size = (0, 0);
}
}
for t in self.old_textures.drain(..) {
unsafe {
gl.delete_texture(t);
}
egui::TextureId::User(id) => self.user_textures.get(&id).copied(),
}
}
@ -523,14 +434,12 @@ impl Painter {
if let Some(tex) = self.egui_texture {
gl.delete_texture(tex);
}
for tex in self.user_textures.iter().flatten() {
if let Some(t) = tex.gl_texture {
gl.delete_texture(t);
}
for tex in self.user_textures.values() {
gl.delete_texture(*tex);
}
gl.delete_buffer(self.vertex_buffer);
gl.delete_buffer(self.element_array_buffer);
for t in &self.old_textures {
for t in &self.textures_to_destroy {
gl.delete_texture(*t);
}
}
@ -580,39 +489,25 @@ impl Drop for Painter {
}
}
#[cfg(feature = "epi")]
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);
}
}
#[cfg(feature = "epi")]
impl epi::NativeTexture for Painter {
type Texture = glow::Texture;
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
self.register_glow_texture(native)
self.assert_not_destroyed();
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 {
data: vec![],
gl_texture: Some(replacing),
size: (0, 0),
};
if let Some(old_tex) = self.user_textures.insert(id, replacing) {
self.textures_to_destroy.push(old_tex);
}
}
}

View file

@ -7,6 +7,7 @@ All notable changes to the `egui_web` integration will be noted in this file.
* Fix [dark rendering in WebKitGTK](https://github.com/emilk/egui/issues/794) ([#888](https://github.com/emilk/egui/pull/888/)).
* Add feature `glow` to switch to a [`glow`](https://github.com/grovesNL/glow) based painter ([#868](https://github.com/emilk/egui/pull/868)).
## 0.15.0 - 2021-10-24
### Added
* Remove "http" feature (use https://github.com/emilk/ehttp instead!).

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

View file

@ -95,6 +95,8 @@ pub mod file_storage;
pub use egui; // Re-export for user convenience
use std::sync::{Arc, Mutex};
// ----------------------------------------------------------------------------
/// Implement this trait to write apps that can be compiled both natively using the [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) crate,
@ -104,8 +106,11 @@ pub trait App {
///
/// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
///
/// To force a repaint, call either [`egui::Context::request_repaint`] or use [`Frame::repaint_signal`].
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut Frame<'_>);
/// The given [`egui::CtxRef`] is only valid for the duration of this call.
/// The [`Frame`] however can be cloned and saved.
///
/// To force a repaint, call either [`egui::Context::request_repaint`] or [`Frame::request_repaint`].
fn update(&mut self, ctx: &egui::CtxRef, frame: &Frame);
/// Called once before the first frame.
///
@ -113,13 +118,7 @@ pub trait App {
/// [`egui::Context::set_visuals`] etc.
///
/// Also allows you to restore state, if there is a storage (required the "persistence" feature).
fn setup(
&mut self,
_ctx: &egui::CtxRef,
_frame: &mut Frame<'_>,
_storage: Option<&dyn Storage>,
) {
}
fn setup(&mut self, _ctx: &egui::CtxRef, _frame: &Frame, _storage: Option<&dyn Storage>) {}
/// If `true` a warm-up call to [`Self::update`] will be issued where
/// `ctx.memory().everything_is_visible()` will be set to `true`.
@ -240,7 +239,7 @@ impl Default for NativeOptions {
/// Image data for the icon.
#[derive(Clone)]
pub struct IconData {
/// RGBA pixels.
/// RGBA pixels, unmultiplied.
pub rgba: Vec<u8>,
/// Image width. This should be a multiple of 4.
@ -254,57 +253,99 @@ pub struct IconData {
///
/// It provides methods to inspect the surroundings (are we on the web?),
/// allocate textures, and change settings (e.g. window size).
pub struct Frame<'a>(backend::FrameBuilder<'a>);
///
/// [`Frame`] is cheap to clone and is safe to pass to other threads.
#[derive(Clone)]
pub struct Frame(pub Arc<Mutex<backend::FrameData>>);
impl Frame {
/// Create a `Frame` - called by the integration.
#[doc(hidden)]
pub fn new(frame_data: backend::FrameData) -> Self {
Self(Arc::new(Mutex::new(frame_data)))
}
/// Convenience to access the underlying `backend::FrameData`.
#[doc(hidden)]
#[inline]
pub fn lock(&self) -> std::sync::MutexGuard<'_, backend::FrameData> {
self.0.lock().unwrap()
}
impl<'a> Frame<'a> {
/// True if you are in a web environment.
pub fn is_web(&self) -> bool {
self.info().web_info.is_some()
self.lock().info.web_info.is_some()
}
/// Information about the integration.
pub fn info(&self) -> &IntegrationInfo {
&self.0.info
}
/// A way to allocate textures.
pub fn tex_allocator(&mut self) -> &mut dyn TextureAllocator {
self.0.tex_allocator
pub fn info(&self) -> IntegrationInfo {
self.lock().info.clone()
}
/// Signal the app to stop/exit/quit the app (only works for native apps, not web apps).
/// The framework will not quit immediately, but at the end of the this frame.
pub fn quit(&mut self) {
self.0.output.quit = true;
pub fn quit(&self) {
self.lock().output.quit = true;
}
/// Set the desired inner size of the window (in egui points).
pub fn set_window_size(&mut self, size: egui::Vec2) {
self.0.output.window_size = Some(size);
pub fn set_window_size(&self, size: egui::Vec2) {
self.lock().output.window_size = Some(size);
}
/// Set the desired title of the window.
pub fn set_window_title(&mut self, title: &str) {
self.0.output.window_title = Some(title.to_owned());
pub fn set_window_title(&self, title: &str) {
self.lock().output.window_title = Some(title.to_owned());
}
/// Set whether to show window decorations (i.e. a frame around you app).
/// If false it will be difficult to move and resize the app.
pub fn set_decorations(&mut self, decorated: bool) {
self.0.output.decorated = Some(decorated);
pub fn set_decorations(&self, decorated: bool) {
self.lock().output.decorated = Some(decorated);
}
/// When called, the native window will follow the
/// movement of the cursor while the primary mouse button is down.
///
/// Does not work on the web, and works badly on Mac.
pub fn drag_window(&mut self) {
self.0.output.drag_window = true;
/// Does not work on the web.
pub fn drag_window(&self) {
self.lock().output.drag_window = true;
}
/// If you need to request a repaint from another thread, clone this and send it to that other thread.
pub fn repaint_signal(&self) -> std::sync::Arc<dyn RepaintSignal> {
self.0.repaint_signal.clone()
/// This signals the [`egui`] integration that a repaint is required.
///
/// Call this e.g. when a background process finishes in an async context and/or background thread.
pub fn request_repaint(&self) {
self.lock().repaint_signal.request_repaint();
}
/// for integrations only: call once per frame
pub fn take_app_output(&self) -> crate::backend::AppOutput {
let mut lock = self.lock();
let next_id = lock.output.tex_allocation_data.next_id;
let app_output = std::mem::take(&mut lock.output);
lock.output.tex_allocation_data.next_id = next_id;
app_output
}
/// Allocate a texture. Free it again with [`Self::free_texture`].
pub fn alloc_texture(&self, image: Image) -> egui::TextureId {
self.lock().output.tex_allocation_data.alloc(image)
}
/// Free a texture that has been previously allocated with [`Self::alloc_texture`]. Idempotent.
pub fn free_texture(&self, id: egui::TextureId) {
self.lock().output.tex_allocation_data.free(id);
}
}
impl TextureAllocator for Frame {
fn alloc(&self, image: Image) -> egui::TextureId {
self.lock().output.tex_allocation_data.alloc(image)
}
fn free(&self, id: egui::TextureId) {
self.lock().output.tex_allocation_data.free(id);
}
}
@ -344,14 +385,33 @@ pub trait TextureAllocator {
///
/// There is no way to change a texture.
/// Instead allocate a new texture and free the previous one with [`Self::free`].
fn alloc_srgba_premultiplied(
&mut self,
size: (usize, usize),
srgba_pixels: &[egui::Color32],
) -> egui::TextureId;
fn alloc(&self, image: Image) -> egui::TextureId;
/// Free the given texture.
fn free(&mut self, id: egui::TextureId);
fn free(&self, id: egui::TextureId);
}
/// A 2D color image in RAM.
#[derive(Clone, Default)]
pub struct Image {
/// width, height
pub size: [usize; 2],
/// The pixels, row by row, from top to bottom.
pub pixels: Vec<egui::Color32>,
}
impl Image {
/// Create an `Image` from flat RGBA data.
/// Panics unless `size[0] * size[1] * 4 == rgba.len()`.
/// This is usually what you want to use after having loaded an image.
pub fn from_rgba_unmultiplied(size: [usize; 2], rgba: &[u8]) -> Self {
assert_eq!(size[0] * size[1] * 4, rgba.len());
let pixels = rgba
.chunks_exact(4)
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
.collect();
Self { size, pixels }
}
}
/// Abstraction for platform dependent texture reference
@ -367,13 +427,6 @@ pub trait NativeTexture {
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture);
}
/// How to signal the [`egui`] integration that a repaint is required.
pub trait RepaintSignal: Send + Sync {
/// This signals the [`egui`] integration that a repaint is required.
/// This is meant to be called when a background process finishes in an async context and/or background thread.
fn request_repaint(&self);
}
// ----------------------------------------------------------------------------
/// A place where you can store custom data in a way that persists when you restart the app.
@ -423,29 +476,68 @@ pub const APP_KEY: &str = "app";
/// You only need to look here if you are writing a backend for `epi`.
pub mod backend {
use std::collections::HashMap;
use super::*;
/// How to signal the [`egui`] integration that a repaint is required.
pub trait RepaintSignal: Send + Sync {
/// This signals the [`egui`] integration that a repaint is required.
///
/// Call this e.g. when a background process finishes in an async context and/or background thread.
fn request_repaint(&self);
}
/// The data required by [`Frame`] each frame.
pub struct FrameBuilder<'a> {
pub struct FrameData {
/// Information about the integration.
pub info: IntegrationInfo,
/// A way to allocate textures (on integrations that support it).
pub tex_allocator: &'a mut dyn TextureAllocator,
/// Where the app can issue commands back to the integration.
pub output: &'a mut AppOutput,
pub output: AppOutput,
/// If you need to request a repaint from another thread, clone this and send it to that other thread.
pub repaint_signal: std::sync::Arc<dyn RepaintSignal>,
}
impl<'a> FrameBuilder<'a> {
/// Wrap us in a [`Frame`] to send to [`App::update`].
pub fn build(self) -> Frame<'a> {
Frame(self)
/// The data needed in order to allocate and free textures/images.
#[derive(Default)]
#[must_use]
pub struct TexAllocationData {
/// We allocate texture id linearly.
pub(crate) next_id: u64,
/// New creations this frame
pub creations: HashMap<u64, Image>,
/// destructions this frame.
pub destructions: Vec<u64>,
}
impl TexAllocationData {
/// Should only be used by integrations
pub fn take(&mut self) -> Self {
let next_id = self.next_id;
let ret = std::mem::take(self);
self.next_id = next_id;
ret
}
/// Allocate a new texture.
pub fn alloc(&mut self, image: Image) -> egui::TextureId {
let id = self.next_id;
self.next_id += 1;
self.creations.insert(id, image);
egui::TextureId::User(id)
}
/// Free an existing texture.
pub fn free(&mut self, id: egui::TextureId) {
if let egui::TextureId::User(id) = id {
self.destructions.push(id);
}
}
}
/// Action that can be taken by the user app.
#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Default)]
#[must_use]
pub struct AppOutput {
/// Set to `true` to stop the app.
/// This does nothing for web apps.
@ -457,10 +549,13 @@ pub mod backend {
/// Set to some string to rename the outer window (e.g. glium window) to this title.
pub window_title: Option<String>,
/// Set to some bool to change window decorations
/// Set to some bool to change window decorations.
pub decorated: Option<bool>,
/// Set to true to drap window
/// Set to true to drag window while primary mouse button is down.
pub drag_window: bool,
/// A way to allocate textures (on integrations that support it).
pub tex_allocation_data: TexAllocationData,
}
}