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

Texture loading in egui (#1110)

* Move texture allocation into epaint/egui proper
* Add TextureHandle
* egui_glow: cast using bytemuck instead of unsafe code
* Optimize glium painter
* Optimize WebGL
* Add example of loading an image from file
This commit is contained in:
Emil Ernerfeldt
2022-01-15 13:59:52 +01:00
committed by GitHub
parent 6c616a1b69
commit 66d80e2519
59 changed files with 1297 additions and 860 deletions

View File

@@ -3,9 +3,11 @@ All notable changes to the `egui_glow` integration will be noted in this file.
## Unreleased
* `EguiGlow::run` no longer returns the shapes to paint, but stores them internally until you call `EguiGlow::paint` ([#1110](https://github.com/emilk/egui/pull/1110)).
* Added `set_texture_filter` method to `Painter` ((#1041)[https://github.com/emilk/egui/pull/1041]).
* Fix failure to run in Chrome ((#1092)[https://github.com/emilk/egui/pull/1092]).
## 0.16.0 - 2021-12-29
* Made winit/glutin an optional dependency ([#868](https://github.com/emilk/egui/pull/868)).
* Simplified `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)).

View File

@@ -23,10 +23,13 @@ include = [
all-features = true
[dependencies]
egui = { version = "0.16.0", path = "../egui", default-features = false, features = ["single_threaded", "convert_bytemuck"] }
egui = { version = "0.16.0", path = "../egui", default-features = false, features = [
"convert_bytemuck",
"single_threaded",
] }
epi = { version = "0.16.0", path = "../epi", optional = true }
bytemuck = "1.7"
epi = { version = "0.16.0", path = "../epi", optional = true }
glow = "0.11"
memoffset = "0.6"

View File

@@ -50,7 +50,7 @@ fn main() {
let mut redraw = || {
let mut quit = false;
let (needs_repaint, shapes) = egui_glow.run(gl_window.window(), |egui_ctx| {
let needs_repaint = egui_glow.run(gl_window.window(), |egui_ctx| {
egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| {
ui.heading("Hello World!");
if ui.button("Quit").clicked() {
@@ -78,7 +78,7 @@ fn main() {
// draw things behind egui here
egui_glow.paint(&gl_window, &gl, shapes);
egui_glow.paint(&gl_window, &gl);
// draw things on top of egui here

View File

@@ -82,11 +82,11 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
std::thread::sleep(std::time::Duration::from_millis(10));
}
let (needs_repaint, mut tex_allocation_data, shapes) =
let (needs_repaint, mut textures_delta, shapes) =
integration.update(gl_window.window());
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
for (id, image) in tex_allocation_data.creations {
for (id, image) in textures_delta.set {
painter.set_texture(&gl, id, &image);
}
@@ -99,7 +99,6 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
gl.clear_color(color[0], color[1], color[2], color[3]);
gl.clear(glow::COLOR_BUFFER_BIT);
}
painter.upload_egui_texture(&gl, &integration.egui_ctx.font_image());
painter.paint_meshes(
&gl,
gl_window.window().inner_size().into(),
@@ -110,8 +109,8 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
gl_window.swap_buffers().unwrap();
}
for id in tex_allocation_data.destructions.drain(..) {
painter.free_texture(id);
for id in textures_delta.free.drain(..) {
painter.free_texture(&gl, id);
}
{

View File

@@ -112,6 +112,9 @@ pub struct EguiGlow {
pub egui_ctx: egui::Context,
pub egui_winit: egui_winit::State,
pub painter: crate::Painter,
shapes: Vec<egui::epaint::ClippedShape>,
textures_delta: egui::TexturesDelta,
}
#[cfg(feature = "winit")]
@@ -128,6 +131,8 @@ impl EguiGlow {
eprintln!("some error occurred in initializing painter\n{}", error);
})
.unwrap(),
shapes: Default::default(),
textures_delta: Default::default(),
}
}
@@ -141,36 +146,51 @@ impl EguiGlow {
self.egui_winit.on_event(&self.egui_ctx, event)
}
/// Returns `needs_repaint` and shapes to draw.
/// Returns `true` if egui requests a repaint.
///
/// Call [`Self::paint`] later to paint.
pub fn run(
&mut self,
window: &glutin::window::Window,
run_ui: impl FnMut(&egui::Context),
) -> (bool, Vec<egui::epaint::ClippedShape>) {
) -> bool {
let raw_input = self.egui_winit.take_egui_input(window);
let (egui_output, shapes) = self.egui_ctx.run(raw_input, run_ui);
let needs_repaint = egui_output.needs_repaint;
self.egui_winit
let textures_delta = self
.egui_winit
.handle_output(window, &self.egui_ctx, egui_output);
(needs_repaint, shapes)
self.shapes = shapes;
self.textures_delta.append(textures_delta);
needs_repaint
}
/// Paint the results of the last call to [`Self::run`].
pub fn paint(
&mut self,
gl_window: &glutin::WindowedContext<glutin::PossiblyCurrent>,
gl: &glow::Context,
shapes: Vec<egui::epaint::ClippedShape>,
) {
let shapes = std::mem::take(&mut self.shapes);
let mut textures_delta = std::mem::take(&mut self.textures_delta);
for (id, image) in textures_delta.set {
self.painter.set_texture(gl, id, &image);
}
let clipped_meshes = self.egui_ctx.tessellate(shapes);
let dimensions: [u32; 2] = gl_window.window().inner_size().into();
self.painter
.upload_egui_texture(gl, &self.egui_ctx.font_image());
self.painter.paint_meshes(
gl,
dimensions,
self.egui_ctx.pixels_per_point(),
clipped_meshes,
);
for id in textures_delta.free.drain(..) {
self.painter.free_texture(gl, id);
}
}
/// Call to release the allocated graphics resources.

View File

@@ -86,10 +86,6 @@ pub fn check_for_gl_error(gl: &glow::Context, context: &str) {
}
}
pub(crate) unsafe fn as_u8_slice<T>(s: &[T]) -> &[u8] {
std::slice::from_raw_parts(s.as_ptr().cast::<u8>(), s.len() * std::mem::size_of::<T>())
}
pub(crate) fn glow_print(s: impl std::fmt::Display) {
#[cfg(target_arch = "wasm32")]
web_sys::console::log_1(&format!("egui_glow: {}", s).into());

View File

@@ -2,7 +2,6 @@
use std::collections::HashMap;
use bytemuck::cast_slice;
use egui::{
emath::Rect,
epaint::{Color32, Mesh, Vertex},
@@ -11,7 +10,7 @@ use glow::HasContext;
use memoffset::offset_of;
use crate::misc_util::{
as_u8_slice, check_for_gl_error, compile_shader, glow_print, link_program, srgb_texture2d,
check_for_gl_error, compile_shader, glow_print, link_program, srgb_texture2d,
};
use crate::post_process::PostProcess;
use crate::shader_version::ShaderVersion;
@@ -30,8 +29,6 @@ pub struct Painter {
program: glow::Program,
u_screen_size: glow::UniformLocation,
u_sampler: glow::UniformLocation,
egui_texture: Option<glow::Texture>,
egui_texture_version: Option<u64>,
is_webgl_1: bool,
is_embedded: bool,
vertex_array: crate::misc_util::VAO,
@@ -42,8 +39,7 @@ pub struct Painter {
vertex_buffer: glow::Buffer,
element_array_buffer: glow::Buffer,
/// Index is the same as in [`egui::TextureId::User`].
user_textures: HashMap<u64, glow::Texture>,
textures: HashMap<egui::TextureId, glow::Texture>,
#[cfg(feature = "epi")]
next_native_tex_id: u64, // TODO: 128-bit texture space?
@@ -212,8 +208,6 @@ impl Painter {
program,
u_screen_size,
u_sampler,
egui_texture: None,
egui_texture_version: None,
is_webgl_1,
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
vertex_array,
@@ -222,7 +216,7 @@ impl Painter {
post_process,
vertex_buffer,
element_array_buffer,
user_textures: Default::default(),
textures: Default::default(),
#[cfg(feature = "epi")]
next_native_tex_id: 1 << 32,
textures_to_destroy: Vec::new(),
@@ -231,41 +225,6 @@ impl Painter {
}
}
pub fn upload_egui_texture(&mut self, gl: &glow::Context, font_image: &egui::FontImage) {
self.assert_not_destroyed();
if self.egui_texture_version == Some(font_image.version) {
return; // No change
}
let gamma = if self.is_embedded && self.post_process.is_none() {
1.0 / 2.2
} else {
1.0
};
let pixels: Vec<u8> = font_image
.srgba_pixels(gamma)
.flat_map(|a| Vec::from(a.to_array()))
.collect();
if let Some(old_tex) = std::mem::replace(
&mut self.egui_texture,
Some(srgb_texture2d(
gl,
self.is_webgl_1,
self.srgb_support,
self.texture_filter,
&pixels,
font_image.width,
font_image.height,
)),
) {
unsafe {
gl.delete_texture(old_tex);
}
}
self.egui_texture_version = Some(font_image.version);
}
unsafe fn prepare_painting(
&mut self,
[width_in_pixels, height_in_pixels]: [u32; 2],
@@ -370,14 +329,14 @@ impl Painter {
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
gl.buffer_data_u8_slice(
glow::ARRAY_BUFFER,
as_u8_slice(mesh.vertices.as_slice()),
bytemuck::cast_slice(&mesh.vertices),
glow::STREAM_DRAW,
);
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
gl.buffer_data_u8_slice(
glow::ELEMENT_ARRAY_BUFFER,
as_u8_slice(mesh.indices.as_slice()),
bytemuck::cast_slice(&mesh.indices),
glow::STREAM_DRAW,
);
@@ -425,52 +384,75 @@ impl Painter {
// ------------------------------------------------------------------------
#[cfg(feature = "epi")]
pub fn set_texture(&mut self, gl: &glow::Context, tex_id: u64, image: &epi::Image) {
pub fn set_texture(
&mut self,
gl: &glow::Context,
tex_id: egui::TextureId,
image: &egui::ImageData,
) {
self.assert_not_destroyed();
assert_eq!(
image.size[0] * image.size[1],
image.pixels.len(),
"Mismatch between texture size and texel count"
);
let gl_texture = match image {
egui::ImageData::Color(image) => {
assert_eq!(
image.width() * image.height(),
image.pixels.len(),
"Mismatch between texture size and texel count"
);
let data: &[u8] = cast_slice(image.pixels.as_ref());
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
let gl_texture = srgb_texture2d(
gl,
self.is_webgl_1,
self.srgb_support,
self.texture_filter,
data,
image.size[0],
image.size[1],
);
srgb_texture2d(
gl,
self.is_webgl_1,
self.srgb_support,
self.texture_filter,
data,
image.size[0],
image.size[1],
)
}
egui::ImageData::Alpha(image) => {
let gamma = if self.is_embedded && self.post_process.is_none() {
1.0 / 2.2
} else {
1.0
};
let data: Vec<u8> = image
.srgba_pixels(gamma)
.flat_map(|a| a.to_array())
.collect();
if let Some(old_tex) = self.user_textures.insert(tex_id, gl_texture) {
self.textures_to_destroy.push(old_tex);
srgb_texture2d(
gl,
self.is_webgl_1,
self.srgb_support,
self.texture_filter,
&data,
image.size[0],
image.size[1],
)
}
};
if let Some(old_tex) = self.textures.insert(tex_id, gl_texture) {
unsafe { gl.delete_texture(old_tex) };
}
}
pub fn free_texture(&mut self, tex_id: u64) {
self.user_textures.remove(&tex_id);
pub fn free_texture(&mut self, gl: &glow::Context, tex_id: egui::TextureId) {
if let Some(old_tex) = self.textures.remove(&tex_id) {
unsafe { gl.delete_texture(old_tex) };
}
}
fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
self.assert_not_destroyed();
match texture_id {
egui::TextureId::Egui => self.egui_texture,
egui::TextureId::User(id) => self.user_textures.get(&id).copied(),
}
self.textures.get(&texture_id).copied()
}
unsafe fn destroy_gl(&self, gl: &glow::Context) {
gl.delete_program(self.program);
if let Some(tex) = self.egui_texture {
gl.delete_texture(tex);
}
for tex in self.user_textures.values() {
for tex in self.textures.values() {
gl.delete_texture(*tex);
}
gl.delete_buffer(self.vertex_buffer);
@@ -533,20 +515,15 @@ impl epi::NativeTexture for Painter {
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
self.assert_not_destroyed();
let id = self.next_native_tex_id;
let id = egui::TextureId::User(self.next_native_tex_id);
self.next_native_tex_id += 1;
self.user_textures.insert(id, native);
egui::TextureId::User(id as u64)
self.textures.insert(id, native);
id
}
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
if let egui::TextureId::User(id) = id {
if let Some(old_tex) = self.user_textures.insert(id, replacing) {
self.textures_to_destroy.push(old_tex);
}
if let Some(old_tex) = self.textures.insert(id, replacing) {
self.textures_to_destroy.push(old_tex);
}
}
}

View File

@@ -116,7 +116,7 @@ impl PostProcess {
gl.bind_buffer(glow::ARRAY_BUFFER, Some(pos_buffer));
gl.buffer_data_u8_slice(
glow::ARRAY_BUFFER,
crate::misc_util::as_u8_slice(&positions),
bytemuck::cast_slice(&positions),
glow::STATIC_DRAW,
);