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:
@@ -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)).
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user