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:
parent
647e020824
commit
b7441eeee7
28 changed files with 548 additions and 824 deletions
|
@ -5,6 +5,9 @@ NOTE: [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.m
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## 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
|
## 0.15.0 - 2021-10-24
|
||||||
|
|
|
@ -11,7 +11,7 @@ impl epi::App for MyApp {
|
||||||
"Native file dialogs and drag-and-drop files"
|
"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| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.label("Drag-and-drop files onto the window!");
|
ui.label("Drag-and-drop files onto the window!");
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ impl epi::App for MyApp {
|
||||||
"My egui App"
|
"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;
|
let Self { name, age } = self;
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
|
|
@ -10,26 +10,20 @@ impl epi::App for MyApp {
|
||||||
"Show an image with eframe/egui"
|
"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() {
|
if self.texture.is_none() {
|
||||||
// Load the image:
|
// Load the image:
|
||||||
let image_data = include_bytes!("rust-logo-256x256.png");
|
let image_data = include_bytes!("rust-logo-256x256.png");
|
||||||
use image::GenericImageView;
|
use image::GenericImageView;
|
||||||
let image = image::load_from_memory(image_data).expect("Failed to load image");
|
let image = image::load_from_memory(image_data).expect("Failed to load image");
|
||||||
let image_buffer = image.to_rgba8();
|
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();
|
let pixels = image_buffer.into_vec();
|
||||||
assert_eq!(size.0 * size.1 * 4, pixels.len());
|
let image = epi::Image::from_rgba_unmultiplied(size, &pixels);
|
||||||
let pixels: Vec<_> = pixels
|
|
||||||
.chunks_exact(4)
|
|
||||||
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Allocate a texture:
|
// Allocate a texture:
|
||||||
let texture = frame
|
let texture = frame.alloc_texture(image);
|
||||||
.tex_allocator()
|
let size = egui::Vec2::new(size[0] as f32, size[1] as f32);
|
||||||
.alloc_srgba_premultiplied(size, &pixels);
|
|
||||||
let size = egui::Vec2::new(size.0 as f32, size.1 as f32);
|
|
||||||
self.texture = Some((size, texture));
|
self.texture = Some((size, texture));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
//! "My egui App"
|
//! "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| {
|
//! egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
//! ui.heading("Hello World!");
|
//! 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"
|
/// "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| {
|
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
/// ui.heading("Hello World!");
|
/// ui.heading("Hello World!");
|
||||||
/// });
|
/// });
|
||||||
|
|
|
@ -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)).
|
* 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)).
|
* Updated `winit` to 0.26 ([#930](https://github.com/emilk/egui/pull/930)).
|
||||||
|
|
||||||
|
|
||||||
## 0.15.0 - 2021-10-24
|
## 0.15.0 - 2021-10-24
|
||||||
First stand-alone release. Previously part of `egui_glium`.
|
First stand-alone release. Previously part of `egui_glium`.
|
||||||
|
|
|
@ -55,9 +55,10 @@ pub fn handle_app_output(
|
||||||
window: &winit::window::Window,
|
window: &winit::window::Window,
|
||||||
current_pixels_per_point: f32,
|
current_pixels_per_point: f32,
|
||||||
app_output: epi::backend::AppOutput,
|
app_output: epi::backend::AppOutput,
|
||||||
) {
|
) -> epi::backend::TexAllocationData {
|
||||||
let epi::backend::AppOutput {
|
let epi::backend::AppOutput {
|
||||||
quit: _,
|
quit: _,
|
||||||
|
tex_allocation_data,
|
||||||
window_size,
|
window_size,
|
||||||
window_title,
|
window_title,
|
||||||
decorated,
|
decorated,
|
||||||
|
@ -85,6 +86,8 @@ pub fn handle_app_output(
|
||||||
if drag_window {
|
if drag_window {
|
||||||
let _ = window.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`].
|
/// Everything needed to make a winit-based integration for [`epi`].
|
||||||
pub struct EpiIntegration {
|
pub struct EpiIntegration {
|
||||||
integration_name: &'static str,
|
frame: epi::Frame,
|
||||||
persistence: crate::epi::Persistence,
|
persistence: crate::epi::Persistence,
|
||||||
repaint_signal: std::sync::Arc<dyn epi::RepaintSignal>,
|
|
||||||
pub egui_ctx: egui::CtxRef,
|
pub egui_ctx: egui::CtxRef,
|
||||||
egui_winit: crate::State,
|
egui_winit: crate::State,
|
||||||
pub app: Box<dyn epi::App>,
|
pub app: Box<dyn epi::App>,
|
||||||
latest_frame_time: Option<f32>,
|
|
||||||
/// When set, it is time to quit
|
/// When set, it is time to quit
|
||||||
quit: bool,
|
quit: bool,
|
||||||
}
|
}
|
||||||
|
@ -201,8 +202,7 @@ impl EpiIntegration {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
integration_name: &'static str,
|
integration_name: &'static str,
|
||||||
window: &winit::window::Window,
|
window: &winit::window::Window,
|
||||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
repaint_signal: std::sync::Arc<dyn epi::backend::RepaintSignal>,
|
||||||
repaint_signal: std::sync::Arc<dyn epi::RepaintSignal>,
|
|
||||||
persistence: crate::epi::Persistence,
|
persistence: crate::epi::Persistence,
|
||||||
app: Box<dyn epi::App>,
|
app: Box<dyn epi::App>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -210,54 +210,50 @@ impl EpiIntegration {
|
||||||
|
|
||||||
*egui_ctx.memory() = persistence.load_memory().unwrap_or_default();
|
*egui_ctx.memory() = persistence.load_memory().unwrap_or_default();
|
||||||
|
|
||||||
let mut slf = Self {
|
let frame = epi::Frame::new(epi::backend::FrameData {
|
||||||
integration_name,
|
info: epi::IntegrationInfo {
|
||||||
persistence,
|
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,
|
repaint_signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut slf = Self {
|
||||||
|
frame,
|
||||||
|
persistence,
|
||||||
egui_ctx,
|
egui_ctx,
|
||||||
egui_winit: crate::State::new(window),
|
egui_winit: crate::State::new(window),
|
||||||
app,
|
app,
|
||||||
latest_frame_time: None,
|
|
||||||
quit: false,
|
quit: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
slf.setup(window, tex_allocator);
|
slf.setup(window);
|
||||||
if slf.app.warm_up_enabled() {
|
if slf.app.warm_up_enabled() {
|
||||||
slf.warm_up(window, tex_allocator);
|
slf.warm_up(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
slf
|
slf
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(
|
fn setup(&mut self, window: &winit::window::Window) {
|
||||||
&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();
|
|
||||||
self.app
|
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;
|
self.quit |= app_output.quit;
|
||||||
|
let tex_alloc_data =
|
||||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
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(
|
fn warm_up(&mut self, window: &winit::window::Window) {
|
||||||
&mut self,
|
|
||||||
window: &winit::window::Window,
|
|
||||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
|
||||||
) {
|
|
||||||
let saved_memory = self.egui_ctx.memory().clone();
|
let saved_memory = self.egui_ctx.memory().clone();
|
||||||
self.egui_ctx.memory().set_everything_is_visible(true);
|
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.memory() = saved_memory; // We don't want to remember that windows were huge.
|
||||||
self.egui_ctx.clear_animations();
|
self.egui_ctx.clear_animations();
|
||||||
}
|
}
|
||||||
|
@ -277,37 +273,31 @@ impl EpiIntegration {
|
||||||
pub fn update(
|
pub fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
window: &winit::window::Window,
|
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 frame_start = std::time::Instant::now();
|
||||||
|
|
||||||
let raw_input = self.egui_winit.take_egui_input(window);
|
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| {
|
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;
|
let needs_repaint = egui_output.needs_repaint;
|
||||||
self.egui_winit
|
self.egui_winit
|
||||||
.handle_output(window, &self.egui_ctx, egui_output);
|
.handle_output(window, &self.egui_ctx, egui_output);
|
||||||
|
|
||||||
|
let app_output = self.frame.take_app_output();
|
||||||
self.quit |= app_output.quit;
|
self.quit |= app_output.quit;
|
||||||
|
let tex_allocation_data =
|
||||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
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;
|
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) {
|
pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
|
||||||
|
@ -321,17 +311,3 @@ impl EpiIntegration {
|
||||||
.save(&mut *self.app, &self.egui_ctx, window);
|
.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)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ impl epi::App for ColorTest {
|
||||||
"🎨 Color test"
|
"🎨 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| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
if frame.is_web() {
|
if frame.is_web() {
|
||||||
ui.label(
|
ui.label(
|
||||||
|
@ -43,18 +43,14 @@ impl epi::App for ColorTest {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
}
|
}
|
||||||
ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| {
|
ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| {
|
||||||
self.ui(ui, &mut Some(frame.tex_allocator()));
|
self.ui(ui, Some(frame));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ColorTest {
|
impl ColorTest {
|
||||||
pub fn ui(
|
pub fn ui(&mut self, ui: &mut Ui, tex_allocator: Option<&dyn epi::TextureAllocator>) {
|
||||||
&mut self,
|
|
||||||
ui: &mut Ui,
|
|
||||||
mut tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
|
|
||||||
) {
|
|
||||||
ui.set_max_width(680.0);
|
ui.set_max_width(680.0);
|
||||||
|
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
|
@ -105,10 +101,10 @@ impl ColorTest {
|
||||||
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
|
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
|
||||||
self.tex_gradient(ui, tex_allocator, "Ground truth (texture)", 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| {
|
ui.horizontal(|ui| {
|
||||||
let g = Gradient::one_color(Color32::from(tex_color));
|
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 texel_offset = 0.5 / (g.0.len() as f32);
|
||||||
let uv =
|
let uv =
|
||||||
Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
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(
|
fn show_gradients(
|
||||||
&mut self,
|
&mut self,
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
|
tex_allocator: Option<&dyn epi::TextureAllocator>,
|
||||||
bg_fill: Color32,
|
bg_fill: Color32,
|
||||||
(left, right): (Color32, Color32),
|
(left, right): (Color32, Color32),
|
||||||
) {
|
) {
|
||||||
|
@ -261,7 +257,7 @@ impl ColorTest {
|
||||||
fn tex_gradient(
|
fn tex_gradient(
|
||||||
&mut self,
|
&mut self,
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
|
tex_allocator: Option<&dyn epi::TextureAllocator>,
|
||||||
label: &str,
|
label: &str,
|
||||||
bg_fill: Color32,
|
bg_fill: Color32,
|
||||||
gradient: &Gradient,
|
gradient: &Gradient,
|
||||||
|
@ -271,7 +267,7 @@ impl ColorTest {
|
||||||
}
|
}
|
||||||
if let Some(tex_allocator) = tex_allocator {
|
if let Some(tex_allocator) = tex_allocator {
|
||||||
ui.horizontal(|ui| {
|
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 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));
|
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))
|
ui.add(Image::new(tex, GRADIENT_SIZE).bg_fill(bg_fill).uv(uv))
|
||||||
|
@ -391,16 +387,15 @@ impl Gradient {
|
||||||
struct TextureManager(HashMap<Gradient, TextureId>);
|
struct TextureManager(HashMap<Gradient, TextureId>);
|
||||||
|
|
||||||
impl TextureManager {
|
impl TextureManager {
|
||||||
fn get(
|
fn get(&mut self, tex_allocator: &dyn epi::TextureAllocator, gradient: &Gradient) -> TextureId {
|
||||||
&mut self,
|
|
||||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
|
||||||
gradient: &Gradient,
|
|
||||||
) -> TextureId {
|
|
||||||
*self.0.entry(gradient.clone()).or_insert_with(|| {
|
*self.0.entry(gradient.clone()).or_insert_with(|| {
|
||||||
let pixels = gradient.to_pixel_row();
|
let pixels = gradient.to_pixel_row();
|
||||||
let width = pixels.len();
|
let width = pixels.len();
|
||||||
let height = 1;
|
let height = 1;
|
||||||
tex_allocator.alloc_srgba_premultiplied((width, height), &pixels)
|
tex_allocator.alloc(epi::Image {
|
||||||
|
size: [width, height],
|
||||||
|
pixels,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ impl epi::App for DemoApp {
|
||||||
fn setup(
|
fn setup(
|
||||||
&mut self,
|
&mut self,
|
||||||
_ctx: &egui::CtxRef,
|
_ctx: &egui::CtxRef,
|
||||||
_frame: &mut epi::Frame<'_>,
|
_frame: &epi::Frame,
|
||||||
_storage: Option<&dyn epi::Storage>,
|
_storage: Option<&dyn epi::Storage>,
|
||||||
) {
|
) {
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
|
@ -31,7 +31,7 @@ impl epi::App for DemoApp {
|
||||||
epi::set_value(storage, epi::APP_KEY, self);
|
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);
|
self.demo_windows.ui(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ impl epi::App for FractalClock {
|
||||||
"🕑 Fractal Clock"
|
"🕑 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()
|
egui::CentralPanel::default()
|
||||||
.frame(Frame::dark_canvas(&ctx.style()))
|
.frame(Frame::dark_canvas(&ctx.style()))
|
||||||
.show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight()));
|
.show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight()));
|
||||||
|
|
|
@ -7,7 +7,7 @@ struct Resource {
|
||||||
text: Option<String>,
|
text: Option<String>,
|
||||||
|
|
||||||
/// If set, the response was an image.
|
/// 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").
|
/// If set, the response was text with some supported syntax highlighting (e.g. ".rs" or ".md").
|
||||||
colored_text: Option<ColoredText>,
|
colored_text: Option<ColoredText>,
|
||||||
|
@ -17,7 +17,7 @@ impl Resource {
|
||||||
fn from_response(ctx: &egui::Context, response: ehttp::Response) -> Self {
|
fn from_response(ctx: &egui::Context, response: ehttp::Response) -> Self {
|
||||||
let content_type = response.content_type().unwrap_or_default();
|
let content_type = response.content_type().unwrap_or_default();
|
||||||
let image = if content_type.starts_with("image/") {
|
let image = if content_type.starts_with("image/") {
|
||||||
Image::decode(&response.bytes)
|
decode_image(&response.bytes)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -67,7 +67,7 @@ impl epi::App for HttpApp {
|
||||||
"⬇ HTTP"
|
"⬇ 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 {
|
if let Some(receiver) = &mut self.in_progress {
|
||||||
// Are we there yet?
|
// Are we there yet?
|
||||||
if let Ok(result) = receiver.try_recv() {
|
if let Ok(result) = receiver.try_recv() {
|
||||||
|
@ -95,13 +95,13 @@ impl epi::App for HttpApp {
|
||||||
|
|
||||||
if trigger_fetch {
|
if trigger_fetch {
|
||||||
let request = ehttp::Request::get(&self.url);
|
let request = ehttp::Request::get(&self.url);
|
||||||
let repaint_signal = frame.repaint_signal();
|
let frame = frame.clone();
|
||||||
let (sender, receiver) = std::sync::mpsc::channel();
|
let (sender, receiver) = std::sync::mpsc::channel();
|
||||||
self.in_progress = Some(receiver);
|
self.in_progress = Some(receiver);
|
||||||
|
|
||||||
ehttp::fetch(request, move |response| {
|
ehttp::fetch(request, move |response| {
|
||||||
sender.send(response).ok();
|
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;
|
let mut trigger_fetch = false;
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
@ -160,12 +160,7 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> bo
|
||||||
trigger_fetch
|
trigger_fetch
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui_resource(
|
fn ui_resource(ui: &mut egui::Ui, frame: &epi::Frame, tex_mngr: &mut TexMngr, resource: &Resource) {
|
||||||
ui: &mut egui::Ui,
|
|
||||||
frame: &mut epi::Frame<'_>,
|
|
||||||
tex_mngr: &mut TexMngr,
|
|
||||||
resource: &Resource,
|
|
||||||
) {
|
|
||||||
let Resource {
|
let Resource {
|
||||||
response,
|
response,
|
||||||
text,
|
text,
|
||||||
|
@ -218,7 +213,7 @@ fn ui_resource(
|
||||||
|
|
||||||
if let Some(image) = image {
|
if let Some(image) = image {
|
||||||
if let Some(texture_id) = tex_mngr.texture(frame, &response.url, 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);
|
size *= (ui.available_width() / size.x).min(1.0);
|
||||||
ui.image(texture_id, size);
|
ui.image(texture_id, size);
|
||||||
}
|
}
|
||||||
|
@ -304,44 +299,27 @@ struct TexMngr {
|
||||||
impl TexMngr {
|
impl TexMngr {
|
||||||
fn texture(
|
fn texture(
|
||||||
&mut self,
|
&mut self,
|
||||||
frame: &mut epi::Frame<'_>,
|
frame: &epi::Frame,
|
||||||
url: &str,
|
url: &str,
|
||||||
image: &Image,
|
image: &epi::Image,
|
||||||
) -> Option<egui::TextureId> {
|
) -> Option<egui::TextureId> {
|
||||||
if self.loaded_url != url {
|
if self.loaded_url != url {
|
||||||
if let Some(texture_id) = self.texture_id.take() {
|
if let Some(texture_id) = self.texture_id.take() {
|
||||||
frame.tex_allocator().free(texture_id);
|
frame.free_texture(texture_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.texture_id = Some(
|
self.texture_id = Some(frame.alloc_texture(image.clone()));
|
||||||
frame
|
|
||||||
.tex_allocator()
|
|
||||||
.alloc_srgba_premultiplied(image.size, &image.pixels),
|
|
||||||
);
|
|
||||||
self.loaded_url = url.to_owned();
|
self.loaded_url = url.to_owned();
|
||||||
}
|
}
|
||||||
self.texture_id
|
self.texture_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Image {
|
fn decode_image(bytes: &[u8]) -> Option<epi::Image> {
|
||||||
size: (usize, usize),
|
use image::GenericImageView;
|
||||||
pixels: Vec<egui::Color32>,
|
let image = image::load_from_memory(bytes).ok()?;
|
||||||
}
|
let image_buffer = image.to_rgba8();
|
||||||
|
let size = [image.width() as usize, image.height() as usize];
|
||||||
impl Image {
|
let pixels = image_buffer.into_vec();
|
||||||
fn decode(bytes: &[u8]) -> Option<Image> {
|
Some(epi::Image::from_rgba_unmultiplied(size, &pixels))
|
||||||
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 })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ impl Default for BackendPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
self.frame_history
|
||||||
.on_new_frame(ctx.input().time, frame.info().cpu_usage);
|
.on_new_frame(ctx.input().time, frame.info().cpu_usage);
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ impl BackendPanel {
|
||||||
self.egui_windows.windows(ctx);
|
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);
|
egui::trace!(ui);
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.heading("💻 Backend");
|
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() {
|
if frame.is_web() {
|
||||||
ui.label("egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
|
ui.label("egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
|
||||||
ui.label(
|
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
|
// For instance: `egui_web` sets `pixels_per_point` every frame to force
|
||||||
// egui to use the same scale as the web zoom factor.
|
// 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();
|
let integration_controls_pixels_per_point = ui.input().raw.pixels_per_point.is_some();
|
||||||
if !integration_controls_pixels_per_point {
|
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);
|
ui.ctx().set_pixels_per_point(new_pixels_per_point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ impl epi::App for EasyMarkEditor {
|
||||||
"🖹 EasyMark editor"
|
"🖹 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| {
|
egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {
|
||||||
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
||||||
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {
|
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl epi::App for WrapApp {
|
||||||
fn setup(
|
fn setup(
|
||||||
&mut self,
|
&mut self,
|
||||||
_ctx: &egui::CtxRef,
|
_ctx: &egui::CtxRef,
|
||||||
_frame: &mut epi::Frame<'_>,
|
_frame: &epi::Frame,
|
||||||
_storage: Option<&dyn epi::Storage>,
|
_storage: Option<&dyn epi::Storage>,
|
||||||
) {
|
) {
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
|
@ -72,7 +72,7 @@ impl epi::App for WrapApp {
|
||||||
cfg!(not(debug_assertions))
|
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(web_info) = frame.info().web_info.as_ref() {
|
||||||
if let Some(anchor) = web_info.web_location_hash.strip_prefix('#') {
|
if let Some(anchor) = web_info.web_location_hash.strip_prefix('#') {
|
||||||
self.selected_anchor = anchor.to_owned();
|
self.selected_anchor = anchor.to_owned();
|
||||||
|
@ -126,7 +126,7 @@ impl epi::App for WrapApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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.
|
// A menu-bar is a horizontal layout with some special styles applied.
|
||||||
// egui::menu::bar(ui, |ui| {
|
// egui::menu::bar(ui, |ui| {
|
||||||
ui.horizontal_wrapped(|ui| {
|
ui.horizontal_wrapped(|ui| {
|
||||||
|
|
|
@ -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)).
|
* Simplify `EguiGlium` interface ([#871](https://github.com/emilk/egui/pull/871)).
|
||||||
* Remove `EguiGlium::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)).
|
* 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)).
|
* 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
|
## 0.15.0 - 2021-10-24
|
||||||
|
|
|
@ -1,22 +1,6 @@
|
||||||
use crate::*;
|
|
||||||
use egui::Color32;
|
|
||||||
use glium::glutin;
|
use glium::glutin;
|
||||||
|
|
||||||
impl epi::TextureAllocator for Painter {
|
use crate::*;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RequestRepaintEvent;
|
struct RequestRepaintEvent;
|
||||||
|
|
||||||
|
@ -24,7 +8,7 @@ struct GliumRepaintSignal(
|
||||||
std::sync::Mutex<glutin::event_loop::EventLoopProxy<RequestRepaintEvent>>,
|
std::sync::Mutex<glutin::event_loop::EventLoopProxy<RequestRepaintEvent>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl epi::RepaintSignal for GliumRepaintSignal {
|
impl epi::backend::RepaintSignal for GliumRepaintSignal {
|
||||||
fn request_repaint(&self) {
|
fn request_repaint(&self) {
|
||||||
self.0.lock().unwrap().send_event(RequestRepaintEvent).ok();
|
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(
|
let mut integration = egui_winit::epi::EpiIntegration::new(
|
||||||
"egui_glium",
|
"egui_glium",
|
||||||
display.gl_window().window(),
|
display.gl_window().window(),
|
||||||
&mut painter,
|
|
||||||
repaint_signal,
|
repaint_signal,
|
||||||
persistence,
|
persistence,
|
||||||
app,
|
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));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (needs_repaint, shapes) =
|
let (needs_repaint, mut tex_allocation_data, shapes) =
|
||||||
integration.update(display.gl_window().window(), &mut painter);
|
integration.update(display.gl_window().window());
|
||||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
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 _;
|
use glium::Surface as _;
|
||||||
let mut target = display.draw();
|
let mut target = display.draw();
|
||||||
|
@ -104,6 +92,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
||||||
target.finish().unwrap();
|
target.finish().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for id in tex_allocation_data.destructions.drain(..) {
|
||||||
|
painter.free_texture(id);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
*control_flow = if integration.should_quit() {
|
*control_flow = if integration.should_quit() {
|
||||||
glutin::event_loop::ControlFlow::Exit
|
glutin::event_loop::ControlFlow::Exit
|
||||||
|
|
|
@ -14,7 +14,7 @@ use {
|
||||||
uniform,
|
uniform,
|
||||||
uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
|
uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::{collections::HashMap, rc::Rc},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Painter {
|
pub struct Painter {
|
||||||
|
@ -22,19 +22,11 @@ pub struct Painter {
|
||||||
egui_texture: Option<SrgbTexture2d>,
|
egui_texture: Option<SrgbTexture2d>,
|
||||||
egui_texture_version: Option<u64>,
|
egui_texture_version: Option<u64>,
|
||||||
|
|
||||||
/// `None` means unallocated (freed) slot.
|
/// Index is the same as in [`egui::TextureId::User`].
|
||||||
user_textures: Vec<Option<UserTexture>>,
|
user_textures: HashMap<u64, Rc<SrgbTexture2d>>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
// TODO: 128-bit texture space?
|
||||||
struct UserTexture {
|
next_native_tex_id: u64,
|
||||||
/// 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>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
|
@ -65,6 +57,7 @@ impl Painter {
|
||||||
egui_texture: None,
|
egui_texture: None,
|
||||||
egui_texture_version: None,
|
egui_texture_version: None,
|
||||||
user_textures: Default::default(),
|
user_textures: Default::default(),
|
||||||
|
next_native_tex_id: 1 << 32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +99,6 @@ impl Painter {
|
||||||
egui_texture: &egui::Texture,
|
egui_texture: &egui::Texture,
|
||||||
) {
|
) {
|
||||||
self.upload_egui_texture(display, egui_texture);
|
self.upload_egui_texture(display, egui_texture);
|
||||||
self.upload_pending_user_textures(display);
|
|
||||||
|
|
||||||
for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes {
|
for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes {
|
||||||
self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh);
|
self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh);
|
||||||
|
@ -114,7 +106,7 @@ impl Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(never)] // Easier profiling
|
#[inline(never)] // Easier profiling
|
||||||
pub fn paint_mesh<T: glium::Surface>(
|
fn paint_mesh<T: glium::Surface>(
|
||||||
&mut self,
|
&mut self,
|
||||||
target: &mut T,
|
target: &mut T,
|
||||||
display: &glium::Display,
|
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 {
|
pub fn set_texture(
|
||||||
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(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
id: egui::TextureId,
|
facade: &dyn glium::backend::Facade,
|
||||||
size: (usize, usize),
|
tex_id: u64,
|
||||||
pixels: &[Color32],
|
image: &epi::Image,
|
||||||
) {
|
) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
size.0 * size.1,
|
image.size[0] * image.size[1],
|
||||||
pixels.len(),
|
image.pixels.len(),
|
||||||
"Mismatch between texture size and texel count"
|
"Mismatch between texture size and texel count"
|
||||||
);
|
);
|
||||||
|
|
||||||
if let egui::TextureId::User(id) = id {
|
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = image
|
||||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
.pixels
|
||||||
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = pixels
|
.chunks(image.size[0] as usize)
|
||||||
.chunks(size.0 as usize)
|
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
|
||||||
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
|
.collect();
|
||||||
.collect();
|
|
||||||
|
|
||||||
*user_texture = UserTexture {
|
let format = texture::SrgbFormat::U8U8U8U8;
|
||||||
pixels,
|
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||||
gl_texture: None,
|
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) {
|
pub fn free_texture(&mut self, tex_id: u64) {
|
||||||
if let egui::TextureId::User(id) = id {
|
self.user_textures.remove(&tex_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<&SrgbTexture2d> {
|
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
|
||||||
match texture_id {
|
match texture_id {
|
||||||
egui::TextureId::Egui => self.egui_texture.as_ref(),
|
egui::TextureId::Egui => self.egui_texture.as_ref(),
|
||||||
egui::TextureId::User(id) => self
|
egui::TextureId::User(id) => self.user_textures.get(&id).map(|rc| rc.as_ref()),
|
||||||
.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(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,26 +264,15 @@ impl epi::NativeTexture for Painter {
|
||||||
type Texture = Rc<SrgbTexture2d>;
|
type Texture = Rc<SrgbTexture2d>;
|
||||||
|
|
||||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||||
let id = self.alloc_user_texture();
|
let id = self.next_native_tex_id;
|
||||||
if let egui::TextureId::User(id) = id {
|
self.next_native_tex_id += 1;
|
||||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
self.user_textures.insert(id, native);
|
||||||
*user_texture = UserTexture {
|
egui::TextureId::User(id as u64)
|
||||||
pixels: vec![],
|
|
||||||
gl_texture: Some(native),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||||
if let egui::TextureId::User(id) = id {
|
if let egui::TextureId::User(id) = id {
|
||||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
self.user_textures.insert(id, replacing);
|
||||||
*user_texture = UserTexture {
|
|
||||||
pixels: vec![],
|
|
||||||
gl_texture: Some(replacing),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)).
|
* Simplify `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)).
|
||||||
* Remove `EguiGlow::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)).
|
* 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)).
|
* 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
|
## 0.15.0 - 2021-10-24
|
||||||
|
|
|
@ -4,7 +4,7 @@ struct RequestRepaintEvent;
|
||||||
|
|
||||||
struct GlowRepaintSignal(std::sync::Mutex<glutin::event_loop::EventLoopProxy<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) {
|
fn request_repaint(&self) {
|
||||||
self.0.lock().unwrap().send_event(RequestRepaintEvent).ok();
|
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(
|
let mut integration = egui_winit::epi::EpiIntegration::new(
|
||||||
"egui_glow",
|
"egui_glow",
|
||||||
gl_window.window(),
|
gl_window.window(),
|
||||||
&mut painter,
|
|
||||||
repaint_signal,
|
repaint_signal,
|
||||||
persistence,
|
persistence,
|
||||||
app,
|
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));
|
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);
|
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();
|
let color = integration.app.clear_color();
|
||||||
unsafe {
|
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.upload_egui_texture(&gl, &integration.egui_ctx.texture());
|
||||||
painter.paint_meshes(
|
painter.paint_meshes(
|
||||||
gl_window.window().inner_size().into(),
|
|
||||||
&gl,
|
&gl,
|
||||||
|
gl_window.window().inner_size().into(),
|
||||||
integration.egui_ctx.pixels_per_point(),
|
integration.egui_ctx.pixels_per_point(),
|
||||||
clipped_meshes,
|
clipped_meshes,
|
||||||
);
|
);
|
||||||
|
@ -105,6 +110,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
||||||
gl_window.swap_buffers().unwrap();
|
gl_window.swap_buffers().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for id in tex_allocation_data.destructions.drain(..) {
|
||||||
|
painter.free_texture(id);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
*control_flow = if integration.should_quit() {
|
*control_flow = if integration.should_quit() {
|
||||||
glutin::event_loop::ControlFlow::Exit
|
glutin::event_loop::ControlFlow::Exit
|
||||||
|
|
|
@ -166,8 +166,8 @@ impl EguiGlow {
|
||||||
self.painter
|
self.painter
|
||||||
.upload_egui_texture(gl, &self.egui_ctx.texture());
|
.upload_egui_texture(gl, &self.egui_ctx.texture());
|
||||||
self.painter.paint_meshes(
|
self.painter.paint_meshes(
|
||||||
dimensions,
|
|
||||||
gl,
|
gl,
|
||||||
|
dimensions,
|
||||||
self.egui_ctx.pixels_per_point(),
|
self.egui_ctx.pixels_per_point(),
|
||||||
clipped_meshes,
|
clipped_meshes,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
#![allow(unsafe_code)]
|
#![allow(unsafe_code)]
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
emath::Rect,
|
emath::Rect,
|
||||||
epaint::{Color32, Mesh, Vertex},
|
epaint::{Color32, Mesh, Vertex},
|
||||||
};
|
};
|
||||||
pub use glow::Context;
|
|
||||||
|
|
||||||
use memoffset::offset_of;
|
|
||||||
|
|
||||||
use glow::HasContext;
|
use glow::HasContext;
|
||||||
|
use memoffset::offset_of;
|
||||||
|
|
||||||
use crate::misc_util::{
|
use crate::misc_util::{
|
||||||
as_u8_slice, compile_shader, glow_debug_print, link_program, srgbtexture2d,
|
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::shader_version::ShaderVersion;
|
||||||
use crate::vao_emulate;
|
use crate::vao_emulate;
|
||||||
|
|
||||||
|
pub use glow::Context;
|
||||||
|
|
||||||
const VERT_SRC: &str = include_str!("shader/vertex.glsl");
|
const VERT_SRC: &str = include_str!("shader/vertex.glsl");
|
||||||
const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
|
const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
|
||||||
|
|
||||||
|
@ -34,29 +35,21 @@ pub struct Painter {
|
||||||
is_embedded: bool,
|
is_embedded: bool,
|
||||||
vertex_array: crate::misc_util::VAO,
|
vertex_array: crate::misc_util::VAO,
|
||||||
srgb_support: bool,
|
srgb_support: bool,
|
||||||
/// `None` means unallocated (freed) slot.
|
|
||||||
pub(crate) user_textures: Vec<Option<UserTexture>>,
|
|
||||||
post_process: Option<PostProcess>,
|
post_process: Option<PostProcess>,
|
||||||
vertex_buffer: glow::Buffer,
|
vertex_buffer: glow::Buffer,
|
||||||
element_array_buffer: glow::Buffer,
|
element_array_buffer: glow::Buffer,
|
||||||
|
|
||||||
// Stores outdated OpenGL textures that are yet to be deleted
|
/// Index is the same as in [`egui::TextureId::User`].
|
||||||
old_textures: Vec<glow::Texture>,
|
user_textures: HashMap<u64, glow::Texture>,
|
||||||
// Only used in debug builds, to make sure we are destroyed correctly.
|
// 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,
|
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 {
|
impl Painter {
|
||||||
/// Create painter.
|
/// Create painter.
|
||||||
///
|
///
|
||||||
|
@ -195,11 +188,12 @@ impl Painter {
|
||||||
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
|
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
|
||||||
vertex_array,
|
vertex_array,
|
||||||
srgb_support,
|
srgb_support,
|
||||||
user_textures: Default::default(),
|
|
||||||
post_process,
|
post_process,
|
||||||
vertex_buffer,
|
vertex_buffer,
|
||||||
element_array_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,
|
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.
|
/// of the effects your program might have on this code. Look at the source if in doubt.
|
||||||
pub fn paint_meshes(
|
pub fn paint_meshes(
|
||||||
&mut self,
|
&mut self,
|
||||||
inner_size: [u32; 2],
|
|
||||||
gl: &glow::Context,
|
gl: &glow::Context,
|
||||||
|
inner_size: [u32; 2],
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
clipped_meshes: Vec<egui::ClippedMesh>,
|
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||||
) {
|
) {
|
||||||
//chimera of egui_glow and egui_web
|
|
||||||
self.assert_not_destroyed();
|
self.assert_not_destroyed();
|
||||||
|
|
||||||
self.upload_pending_user_textures(gl);
|
|
||||||
if let Some(ref mut post_process) = self.post_process {
|
if let Some(ref mut post_process) = self.post_process {
|
||||||
unsafe {
|
unsafe {
|
||||||
post_process.begin(gl, inner_size[0] as i32, inner_size[1] as i32);
|
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();
|
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!(
|
assert_eq!(
|
||||||
size.0 * size.1,
|
image.size[0] * image.size[1],
|
||||||
pixels.len(),
|
image.pixels.len(),
|
||||||
"Mismatch between size and texel count"
|
"Mismatch between texture size and texel count"
|
||||||
);
|
);
|
||||||
|
|
||||||
if let egui::TextureId::User(id) = id {
|
// TODO: optimize
|
||||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
let pixels: Vec<u8> = image
|
||||||
let data: Vec<u8> = pixels
|
.pixels
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|srgba| Vec::from(srgba.to_array()))
|
.flat_map(|srgba| Vec::from(srgba.to_array()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if let UserTexture {
|
let gl_texture = srgbtexture2d(
|
||||||
gl_texture: Some(old_tex),
|
gl,
|
||||||
..
|
self.is_webgl_1,
|
||||||
} = std::mem::replace(
|
self.srgb_support,
|
||||||
user_texture,
|
&pixels,
|
||||||
UserTexture {
|
image.size[0],
|
||||||
data,
|
image.size[1],
|
||||||
size,
|
);
|
||||||
gl_texture: None,
|
|
||||||
},
|
if let Some(old_tex) = self.user_textures.insert(tex_id, gl_texture) {
|
||||||
) {
|
self.textures_to_destroy.push(old_tex);
|
||||||
self.old_textures.push(old_tex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free_user_texture(&mut self, id: egui::TextureId) {
|
pub fn free_texture(&mut self, tex_id: u64) {
|
||||||
self.assert_not_destroyed();
|
self.user_textures.remove(&tex_id);
|
||||||
|
|
||||||
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<glow::Texture> {
|
fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
|
||||||
self.assert_not_destroyed();
|
self.assert_not_destroyed();
|
||||||
|
|
||||||
match texture_id {
|
match texture_id {
|
||||||
egui::TextureId::Egui => self.egui_texture,
|
egui::TextureId::Egui => self.egui_texture,
|
||||||
egui::TextureId::User(id) => self.user_textures.get(id as usize)?.as_ref()?.gl_texture,
|
egui::TextureId::User(id) => self.user_textures.get(&id).copied(),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,14 +434,12 @@ impl Painter {
|
||||||
if let Some(tex) = self.egui_texture {
|
if let Some(tex) = self.egui_texture {
|
||||||
gl.delete_texture(tex);
|
gl.delete_texture(tex);
|
||||||
}
|
}
|
||||||
for tex in self.user_textures.iter().flatten() {
|
for tex in self.user_textures.values() {
|
||||||
if let Some(t) = tex.gl_texture {
|
gl.delete_texture(*tex);
|
||||||
gl.delete_texture(t);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
gl.delete_buffer(self.vertex_buffer);
|
gl.delete_buffer(self.vertex_buffer);
|
||||||
gl.delete_buffer(self.element_array_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);
|
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")]
|
#[cfg(feature = "epi")]
|
||||||
impl epi::NativeTexture for Painter {
|
impl epi::NativeTexture for Painter {
|
||||||
type Texture = glow::Texture;
|
type Texture = glow::Texture;
|
||||||
|
|
||||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
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) {
|
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||||
if let egui::TextureId::User(id) = id {
|
if let egui::TextureId::User(id) = id {
|
||||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
if let Some(old_tex) = self.user_textures.insert(id, replacing) {
|
||||||
*user_texture = UserTexture {
|
self.textures_to_destroy.push(old_tex);
|
||||||
data: vec![],
|
|
||||||
gl_texture: Some(replacing),
|
|
||||||
size: (0, 0),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/)).
|
* 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)).
|
* 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
|
## 0.15.0 - 2021-10-24
|
||||||
### Added
|
### Added
|
||||||
* Remove "http" feature (use https://github.com/emilk/ehttp instead!).
|
* Remove "http" feature (use https://github.com/emilk/ehttp instead!).
|
||||||
|
|
|
@ -67,7 +67,7 @@ impl NeedRepaint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::RepaintSignal for NeedRepaint {
|
impl epi::backend::RepaintSignal for NeedRepaint {
|
||||||
fn request_repaint(&self) {
|
fn request_repaint(&self) {
|
||||||
self.0.store(true, SeqCst);
|
self.0.store(true, SeqCst);
|
||||||
}
|
}
|
||||||
|
@ -76,28 +76,44 @@ impl epi::RepaintSignal for NeedRepaint {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
pub struct AppRunner {
|
pub struct AppRunner {
|
||||||
|
frame: epi::Frame,
|
||||||
egui_ctx: egui::CtxRef,
|
egui_ctx: egui::CtxRef,
|
||||||
painter: Box<dyn Painter>,
|
painter: Box<dyn Painter>,
|
||||||
previous_frame_time: Option<f32>,
|
|
||||||
pub(crate) input: WebInput,
|
pub(crate) input: WebInput,
|
||||||
app: Box<dyn epi::App>,
|
app: Box<dyn epi::App>,
|
||||||
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
||||||
storage: LocalStorage,
|
storage: LocalStorage,
|
||||||
prefer_dark_mode: Option<bool>,
|
|
||||||
last_save_time: f64,
|
last_save_time: f64,
|
||||||
screen_reader: crate::screen_reader::ScreenReader,
|
screen_reader: crate::screen_reader::ScreenReader,
|
||||||
pub(crate) text_cursor_pos: Option<egui::Pos2>,
|
pub(crate) text_cursor_pos: Option<egui::Pos2>,
|
||||||
pub(crate) mutable_text_under_cursor: bool,
|
pub(crate) mutable_text_under_cursor: bool,
|
||||||
|
pending_texture_destructions: Vec<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppRunner {
|
impl AppRunner {
|
||||||
pub fn new(canvas_id: &str, app: Box<dyn epi::App>) -> Result<Self, JsValue> {
|
pub fn new(canvas_id: &str, app: Box<dyn epi::App>) -> Result<Self, JsValue> {
|
||||||
let egui_ctx = egui::CtxRef::default();
|
let painter = create_painter(canvas_id)?;
|
||||||
|
|
||||||
load_memory(&egui_ctx);
|
|
||||||
|
|
||||||
let prefer_dark_mode = crate::prefer_dark_mode();
|
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) {
|
if prefer_dark_mode == Some(true) {
|
||||||
egui_ctx.set_visuals(egui::Visuals::dark());
|
egui_ctx.set_visuals(egui::Visuals::dark());
|
||||||
} else {
|
} else {
|
||||||
|
@ -107,32 +123,24 @@ impl AppRunner {
|
||||||
let storage = LocalStorage::default();
|
let storage = LocalStorage::default();
|
||||||
|
|
||||||
let mut runner = Self {
|
let mut runner = Self {
|
||||||
|
frame,
|
||||||
egui_ctx,
|
egui_ctx,
|
||||||
painter: create_painter(canvas_id)?,
|
painter,
|
||||||
previous_frame_time: None,
|
|
||||||
input: Default::default(),
|
input: Default::default(),
|
||||||
app,
|
app,
|
||||||
needs_repaint: Default::default(),
|
needs_repaint,
|
||||||
storage,
|
storage,
|
||||||
prefer_dark_mode,
|
|
||||||
last_save_time: now_sec(),
|
last_save_time: now_sec(),
|
||||||
screen_reader: Default::default(),
|
screen_reader: Default::default(),
|
||||||
text_cursor_pos: None,
|
text_cursor_pos: None,
|
||||||
mutable_text_under_cursor: false,
|
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
|
runner
|
||||||
.app
|
.app
|
||||||
.setup(&runner.egui_ctx, &mut frame, Some(&runner.storage));
|
.setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(runner)
|
Ok(runner)
|
||||||
|
@ -170,18 +178,6 @@ impl AppRunner {
|
||||||
Ok(())
|
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> {
|
pub fn logic(&mut self) -> Result<(egui::Output, Vec<egui::ClippedMesh>), JsValue> {
|
||||||
let frame_start = now_sec();
|
let frame_start = now_sec();
|
||||||
|
|
||||||
|
@ -189,33 +185,31 @@ impl AppRunner {
|
||||||
let canvas_size = canvas_size_in_points(self.canvas_id());
|
let canvas_size = canvas_size_in_points(self.canvas_id());
|
||||||
let raw_input = self.input.new_frame(canvas_size);
|
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| {
|
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);
|
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||||
|
|
||||||
self.handle_egui_output(&egui_output);
|
self.handle_egui_output(&egui_output);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
let app_output = self.frame.take_app_output();
|
||||||
let epi::backend::AppOutput {
|
let epi::backend::AppOutput {
|
||||||
quit: _, // Can't quit a web page
|
quit: _, // Can't quit a web page
|
||||||
window_size: _, // Can't resize a web page
|
window_size: _, // Can't resize a web page
|
||||||
window_title: _, // TODO: change title of window
|
window_title: _, // TODO: change title of window
|
||||||
decorated: _, // Can't toggle decorations
|
decorated: _, // Can't toggle decorations
|
||||||
drag_window: _, // Can't be dragged
|
drag_window: _, // Can't be dragged
|
||||||
|
tex_allocation_data,
|
||||||
} = app_output;
|
} = 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))
|
Ok((egui_output, clipped_meshes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +217,11 @@ impl AppRunner {
|
||||||
self.painter.upload_egui_texture(&self.egui_ctx.texture());
|
self.painter.upload_egui_texture(&self.egui_ctx.texture());
|
||||||
self.painter.clear(self.app.clear_color());
|
self.painter.clear(self.app.clear_color());
|
||||||
self.painter
|
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) {
|
fn handle_egui_output(&mut self, output: &egui::Output) {
|
||||||
|
|
|
@ -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 crate::{canvas_element_or_die, console_error};
|
||||||
use egui::{ClippedMesh, Rgba, Texture};
|
use egui::{ClippedMesh, Rgba, Texture};
|
||||||
use egui_glow::glow;
|
use egui_glow::glow;
|
||||||
use epi::TextureAllocator;
|
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
use web_sys::HtmlCanvasElement;
|
use web_sys::HtmlCanvasElement;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use web_sys::WebGl2RenderingContext;
|
||||||
|
use web_sys::WebGlRenderingContext;
|
||||||
|
|
||||||
pub(crate) struct WrappedGlowPainter {
|
pub(crate) struct WrappedGlowPainter {
|
||||||
pub(crate) gl_ctx: glow::Context,
|
pub(crate) gl_ctx: glow::Context,
|
||||||
|
@ -67,8 +66,12 @@ fn requires_brightening(canvas: &web_sys::HtmlCanvasElement) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Painter for WrappedGlowPainter {
|
impl crate::Painter for WrappedGlowPainter {
|
||||||
fn as_tex_allocator(&mut self) -> &mut dyn TextureAllocator {
|
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
|
||||||
&mut self.painter
|
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 {
|
fn debug_info(&self) -> String {
|
||||||
|
@ -99,8 +102,8 @@ impl crate::Painter for WrappedGlowPainter {
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
||||||
self.painter.paint_meshes(
|
self.painter.paint_meshes(
|
||||||
canvas_dimension,
|
|
||||||
&self.gl_ctx,
|
&self.gl_ctx,
|
||||||
|
canvas_dimension,
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
clipped_meshes,
|
clipped_meshes,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use wasm_bindgen::prelude::JsValue;
|
use wasm_bindgen::prelude::JsValue;
|
||||||
|
|
||||||
pub trait Painter {
|
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;
|
fn debug_info(&self) -> String;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
js_sys::WebAssembly,
|
js_sys::WebAssembly,
|
||||||
wasm_bindgen::{prelude::*, JsCast},
|
wasm_bindgen::{prelude::*, JsCast},
|
||||||
|
@ -29,19 +31,11 @@ pub struct WebGlPainter {
|
||||||
egui_texture: WebGlTexture,
|
egui_texture: WebGlTexture,
|
||||||
egui_texture_version: Option<u64>,
|
egui_texture_version: Option<u64>,
|
||||||
|
|
||||||
/// `None` means unallocated (freed) slot.
|
/// Index is the same as in [`egui::TextureId::User`].
|
||||||
user_textures: Vec<Option<UserTexture>>,
|
user_textures: HashMap<u64, WebGlTexture>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
// TODO: 128-bit texture space?
|
||||||
struct UserTexture {
|
next_native_tex_id: u64,
|
||||||
size: (usize, usize),
|
|
||||||
|
|
||||||
/// Pending upload (will be emptied later).
|
|
||||||
pixels: Vec<u8>,
|
|
||||||
|
|
||||||
/// Lazily uploaded
|
|
||||||
gl_texture: Option<WebGlTexture>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebGlPainter {
|
impl WebGlPainter {
|
||||||
|
@ -111,109 +105,14 @@ impl WebGlPainter {
|
||||||
egui_texture,
|
egui_texture,
|
||||||
egui_texture_version: None,
|
egui_texture_version: None,
|
||||||
user_textures: Default::default(),
|
user_textures: Default::default(),
|
||||||
|
next_native_tex_id: 1 << 32,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_user_texture_index(&mut self) -> usize {
|
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||||
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> {
|
|
||||||
match texture_id {
|
match texture_id {
|
||||||
egui::TextureId::Egui => Some(&self.egui_texture),
|
egui::TextureId::Egui => Some(&self.egui_texture),
|
||||||
egui::TextureId::User(id) => self
|
egui::TextureId::User(id) => self.user_textures.get(&id),
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
impl epi::NativeTexture for WebGlPainter {
|
||||||
type Texture = WebGlTexture;
|
type Texture = WebGlTexture;
|
||||||
|
|
||||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||||
let id = self.alloc_user_texture_index();
|
let id = self.next_native_tex_id;
|
||||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id) {
|
self.next_native_tex_id += 1;
|
||||||
*user_texture = UserTexture {
|
self.user_textures.insert(id, native);
|
||||||
size: (0, 0),
|
|
||||||
pixels: vec![],
|
|
||||||
gl_texture: Some(native),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
egui::TextureId::User(id as u64)
|
egui::TextureId::User(id as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||||
if let egui::TextureId::User(id) = id {
|
if let egui::TextureId::User(id) = id {
|
||||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
if let Some(user_texture) = self.user_textures.get_mut(&id) {
|
||||||
*user_texture = UserTexture {
|
*user_texture = replacing;
|
||||||
size: (0, 0),
|
|
||||||
pixels: vec![],
|
|
||||||
gl_texture: Some(replacing),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Painter for WebGlPainter {
|
impl crate::Painter for WebGlPainter {
|
||||||
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator {
|
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
|
||||||
self
|
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 {
|
fn debug_info(&self) -> String {
|
||||||
|
@ -467,8 +390,6 @@ impl crate::Painter for WebGlPainter {
|
||||||
clipped_meshes: Vec<egui::ClippedMesh>,
|
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
self.upload_user_textures();
|
|
||||||
|
|
||||||
let gl = &self.gl;
|
let gl = &self.gl;
|
||||||
|
|
||||||
if let Some(ref mut post_process) = self.post_process {
|
if let Some(ref mut post_process) = self.post_process {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//! Mostly a carbon-copy of `webgl1.rs`.
|
//! Mostly a carbon-copy of `webgl1.rs`.
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
js_sys::WebAssembly,
|
js_sys::WebAssembly,
|
||||||
|
@ -30,19 +31,11 @@ pub struct WebGl2Painter {
|
||||||
egui_texture: WebGlTexture,
|
egui_texture: WebGlTexture,
|
||||||
egui_texture_version: Option<u64>,
|
egui_texture_version: Option<u64>,
|
||||||
|
|
||||||
/// `None` means unallocated (freed) slot.
|
/// Index is the same as in [`egui::TextureId::User`].
|
||||||
user_textures: Vec<Option<UserTexture>>,
|
user_textures: HashMap<u64, WebGlTexture>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
// TODO: 128-bit texture space?
|
||||||
struct UserTexture {
|
next_native_tex_id: u64,
|
||||||
size: (usize, usize),
|
|
||||||
|
|
||||||
/// Pending upload (will be emptied later).
|
|
||||||
pixels: Vec<u8>,
|
|
||||||
|
|
||||||
/// Lazily uploaded
|
|
||||||
gl_texture: Option<WebGlTexture>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebGl2Painter {
|
impl WebGl2Painter {
|
||||||
|
@ -96,109 +89,14 @@ impl WebGl2Painter {
|
||||||
egui_texture,
|
egui_texture,
|
||||||
egui_texture_version: None,
|
egui_texture_version: None,
|
||||||
user_textures: Default::default(),
|
user_textures: Default::default(),
|
||||||
|
next_native_tex_id: 1 << 32,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_user_texture_index(&mut self) -> usize {
|
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||||
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> {
|
|
||||||
match texture_id {
|
match texture_id {
|
||||||
egui::TextureId::Egui => Some(&self.egui_texture),
|
egui::TextureId::Egui => Some(&self.egui_texture),
|
||||||
egui::TextureId::User(id) => self
|
egui::TextureId::User(id) => self.user_textures.get(&id),
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
impl epi::NativeTexture for WebGl2Painter {
|
||||||
type Texture = WebGlTexture;
|
type Texture = WebGlTexture;
|
||||||
|
|
||||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||||
let id = self.alloc_user_texture_index();
|
let id = self.next_native_tex_id;
|
||||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id) {
|
self.next_native_tex_id += 1;
|
||||||
*user_texture = UserTexture {
|
self.user_textures.insert(id, native);
|
||||||
size: (0, 0),
|
|
||||||
pixels: vec![],
|
|
||||||
gl_texture: Some(native),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
egui::TextureId::User(id as u64)
|
egui::TextureId::User(id as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||||
if let egui::TextureId::User(id) = id {
|
if let egui::TextureId::User(id) = id {
|
||||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
if let Some(user_texture) = self.user_textures.get_mut(&id) {
|
||||||
*user_texture = UserTexture {
|
*user_texture = replacing;
|
||||||
size: (0, 0),
|
|
||||||
pixels: vec![],
|
|
||||||
gl_texture: Some(replacing),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Painter for WebGl2Painter {
|
impl crate::Painter for WebGl2Painter {
|
||||||
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator {
|
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
|
||||||
self
|
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 {
|
fn debug_info(&self) -> String {
|
||||||
|
@ -448,8 +370,6 @@ impl crate::Painter for WebGl2Painter {
|
||||||
clipped_meshes: Vec<egui::ClippedMesh>,
|
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
self.upload_user_textures();
|
|
||||||
|
|
||||||
let gl = &self.gl;
|
let gl = &self.gl;
|
||||||
|
|
||||||
self.post_process
|
self.post_process
|
||||||
|
|
211
epi/src/lib.rs
211
epi/src/lib.rs
|
@ -95,6 +95,8 @@ pub mod file_storage;
|
||||||
|
|
||||||
pub use egui; // Re-export for user convenience
|
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,
|
/// 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`].
|
/// 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`].
|
/// The given [`egui::CtxRef`] is only valid for the duration of this call.
|
||||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut Frame<'_>);
|
/// 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.
|
/// Called once before the first frame.
|
||||||
///
|
///
|
||||||
|
@ -113,13 +118,7 @@ pub trait App {
|
||||||
/// [`egui::Context::set_visuals`] etc.
|
/// [`egui::Context::set_visuals`] etc.
|
||||||
///
|
///
|
||||||
/// Also allows you to restore state, if there is a storage (required the "persistence" feature).
|
/// Also allows you to restore state, if there is a storage (required the "persistence" feature).
|
||||||
fn setup(
|
fn setup(&mut self, _ctx: &egui::CtxRef, _frame: &Frame, _storage: Option<&dyn Storage>) {}
|
||||||
&mut self,
|
|
||||||
_ctx: &egui::CtxRef,
|
|
||||||
_frame: &mut Frame<'_>,
|
|
||||||
_storage: Option<&dyn Storage>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `true` a warm-up call to [`Self::update`] will be issued where
|
/// If `true` a warm-up call to [`Self::update`] will be issued where
|
||||||
/// `ctx.memory().everything_is_visible()` will be set to `true`.
|
/// `ctx.memory().everything_is_visible()` will be set to `true`.
|
||||||
|
@ -240,7 +239,7 @@ impl Default for NativeOptions {
|
||||||
/// Image data for the icon.
|
/// Image data for the icon.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct IconData {
|
pub struct IconData {
|
||||||
/// RGBA pixels.
|
/// RGBA pixels, unmultiplied.
|
||||||
pub rgba: Vec<u8>,
|
pub rgba: Vec<u8>,
|
||||||
|
|
||||||
/// Image width. This should be a multiple of 4.
|
/// 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?),
|
/// It provides methods to inspect the surroundings (are we on the web?),
|
||||||
/// allocate textures, and change settings (e.g. window size).
|
/// 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.
|
/// True if you are in a web environment.
|
||||||
pub fn is_web(&self) -> bool {
|
pub fn is_web(&self) -> bool {
|
||||||
self.info().web_info.is_some()
|
self.lock().info.web_info.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information about the integration.
|
/// Information about the integration.
|
||||||
pub fn info(&self) -> &IntegrationInfo {
|
pub fn info(&self) -> IntegrationInfo {
|
||||||
&self.0.info
|
self.lock().info.clone()
|
||||||
}
|
|
||||||
|
|
||||||
/// A way to allocate textures.
|
|
||||||
pub fn tex_allocator(&mut self) -> &mut dyn TextureAllocator {
|
|
||||||
self.0.tex_allocator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signal the app to stop/exit/quit the app (only works for native apps, not web apps).
|
/// 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.
|
/// The framework will not quit immediately, but at the end of the this frame.
|
||||||
pub fn quit(&mut self) {
|
pub fn quit(&self) {
|
||||||
self.0.output.quit = true;
|
self.lock().output.quit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the desired inner size of the window (in egui points).
|
/// Set the desired inner size of the window (in egui points).
|
||||||
pub fn set_window_size(&mut self, size: egui::Vec2) {
|
pub fn set_window_size(&self, size: egui::Vec2) {
|
||||||
self.0.output.window_size = Some(size);
|
self.lock().output.window_size = Some(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the desired title of the window.
|
/// Set the desired title of the window.
|
||||||
pub fn set_window_title(&mut self, title: &str) {
|
pub fn set_window_title(&self, title: &str) {
|
||||||
self.0.output.window_title = Some(title.to_owned());
|
self.lock().output.window_title = Some(title.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set whether to show window decorations (i.e. a frame around you app).
|
/// 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.
|
/// If false it will be difficult to move and resize the app.
|
||||||
pub fn set_decorations(&mut self, decorated: bool) {
|
pub fn set_decorations(&self, decorated: bool) {
|
||||||
self.0.output.decorated = Some(decorated);
|
self.lock().output.decorated = Some(decorated);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When called, the native window will follow the
|
/// When called, the native window will follow the
|
||||||
/// movement of the cursor while the primary mouse button is down.
|
/// movement of the cursor while the primary mouse button is down.
|
||||||
///
|
///
|
||||||
/// Does not work on the web, and works badly on Mac.
|
/// Does not work on the web.
|
||||||
pub fn drag_window(&mut self) {
|
pub fn drag_window(&self) {
|
||||||
self.0.output.drag_window = true;
|
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.
|
/// This signals the [`egui`] integration that a repaint is required.
|
||||||
pub fn repaint_signal(&self) -> std::sync::Arc<dyn RepaintSignal> {
|
///
|
||||||
self.0.repaint_signal.clone()
|
/// 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.
|
/// There is no way to change a texture.
|
||||||
/// Instead allocate a new texture and free the previous one with [`Self::free`].
|
/// Instead allocate a new texture and free the previous one with [`Self::free`].
|
||||||
fn alloc_srgba_premultiplied(
|
fn alloc(&self, image: Image) -> egui::TextureId;
|
||||||
&mut self,
|
|
||||||
size: (usize, usize),
|
|
||||||
srgba_pixels: &[egui::Color32],
|
|
||||||
) -> egui::TextureId;
|
|
||||||
|
|
||||||
/// Free the given texture.
|
/// 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
|
/// 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);
|
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.
|
/// 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`.
|
/// You only need to look here if you are writing a backend for `epi`.
|
||||||
pub mod backend {
|
pub mod backend {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::*;
|
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.
|
/// The data required by [`Frame`] each frame.
|
||||||
pub struct FrameBuilder<'a> {
|
pub struct FrameData {
|
||||||
/// Information about the integration.
|
/// Information about the integration.
|
||||||
pub info: IntegrationInfo,
|
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.
|
/// 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.
|
/// 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>,
|
pub repaint_signal: std::sync::Arc<dyn RepaintSignal>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FrameBuilder<'a> {
|
/// The data needed in order to allocate and free textures/images.
|
||||||
/// Wrap us in a [`Frame`] to send to [`App::update`].
|
#[derive(Default)]
|
||||||
pub fn build(self) -> Frame<'a> {
|
#[must_use]
|
||||||
Frame(self)
|
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.
|
/// Action that can be taken by the user app.
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Default)]
|
||||||
|
#[must_use]
|
||||||
pub struct AppOutput {
|
pub struct AppOutput {
|
||||||
/// Set to `true` to stop the app.
|
/// Set to `true` to stop the app.
|
||||||
/// This does nothing for web apps.
|
/// 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.
|
/// Set to some string to rename the outer window (e.g. glium window) to this title.
|
||||||
pub window_title: Option<String>,
|
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>,
|
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,
|
pub drag_window: bool,
|
||||||
|
|
||||||
|
/// A way to allocate textures (on integrations that support it).
|
||||||
|
pub tex_allocation_data: TexAllocationData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue