mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 06:40:06 -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:
@@ -2,10 +2,8 @@
|
||||
#![allow(semicolon_in_expressions_from_macros)] // glium::program! macro
|
||||
|
||||
use {
|
||||
egui::{
|
||||
emath::Rect,
|
||||
epaint::{Color32, Mesh},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
egui::{emath::Rect, epaint::Mesh},
|
||||
glium::{
|
||||
implement_vertex,
|
||||
index::PrimitiveType,
|
||||
@@ -14,19 +12,17 @@ use {
|
||||
uniform,
|
||||
uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
|
||||
},
|
||||
std::{collections::HashMap, rc::Rc},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub struct Painter {
|
||||
program: glium::Program,
|
||||
egui_texture: Option<SrgbTexture2d>,
|
||||
egui_texture_version: Option<u64>,
|
||||
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, Rc<SrgbTexture2d>>,
|
||||
textures: AHashMap<egui::TextureId, Rc<SrgbTexture2d>>,
|
||||
|
||||
#[cfg(feature = "epi")]
|
||||
next_native_tex_id: u64, // TODO: 128-bit texture space?
|
||||
/// [`egui::TextureId::User`] index
|
||||
next_native_tex_id: u64,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
@@ -54,40 +50,12 @@ impl Painter {
|
||||
|
||||
Painter {
|
||||
program,
|
||||
egui_texture: None,
|
||||
egui_texture_version: None,
|
||||
user_textures: Default::default(),
|
||||
textures: Default::default(),
|
||||
#[cfg(feature = "epi")]
|
||||
next_native_tex_id: 1 << 32,
|
||||
next_native_tex_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upload_egui_texture(
|
||||
&mut self,
|
||||
facade: &dyn glium::backend::Facade,
|
||||
font_image: &egui::FontImage,
|
||||
) {
|
||||
if self.egui_texture_version == Some(font_image.version) {
|
||||
return; // No change
|
||||
}
|
||||
|
||||
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = font_image
|
||||
.pixels
|
||||
.chunks(font_image.width as usize)
|
||||
.map(|row| {
|
||||
row.iter()
|
||||
.map(|&a| Color32::from_white_alpha(a).to_tuple())
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let format = texture::SrgbFormat::U8U8U8U8;
|
||||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||
self.egui_texture =
|
||||
Some(SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap());
|
||||
self.egui_texture_version = Some(font_image.version);
|
||||
}
|
||||
|
||||
/// Main entry-point for painting a frame.
|
||||
/// You should call `target.clear_color(..)` before
|
||||
/// and `target.finish()` after this.
|
||||
@@ -97,10 +65,7 @@ impl Painter {
|
||||
target: &mut T,
|
||||
pixels_per_point: f32,
|
||||
cipped_meshes: Vec<egui::ClippedMesh>,
|
||||
font_image: &egui::FontImage,
|
||||
) {
|
||||
self.upload_egui_texture(display, font_image);
|
||||
|
||||
for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes {
|
||||
self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh);
|
||||
}
|
||||
@@ -118,7 +83,8 @@ impl Painter {
|
||||
debug_assert!(mesh.is_valid());
|
||||
|
||||
let vertex_buffer = {
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Vertex {
|
||||
a_pos: [f32; 2],
|
||||
a_tc: [f32; 2],
|
||||
@@ -126,18 +92,10 @@ impl Painter {
|
||||
}
|
||||
implement_vertex!(Vertex, a_pos, a_tc, a_srgba);
|
||||
|
||||
let vertices: Vec<Vertex> = mesh
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|v| Vertex {
|
||||
a_pos: [v.pos.x, v.pos.y],
|
||||
a_tc: [v.uv.x, v.uv.y],
|
||||
a_srgba: v.color.to_array(),
|
||||
})
|
||||
.collect();
|
||||
let vertices: &[Vertex] = bytemuck::cast_slice(&mesh.vertices);
|
||||
|
||||
// TODO: we should probably reuse the `VertexBuffer` instead of allocating a new one each frame.
|
||||
glium::VertexBuffer::new(display, &vertices).unwrap()
|
||||
glium::VertexBuffer::new(display, vertices).unwrap()
|
||||
};
|
||||
|
||||
// TODO: we should probably reuse the `IndexBuffer` instead of allocating a new one each frame.
|
||||
@@ -223,41 +181,48 @@ impl Painter {
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
#[cfg(feature = "epi")]
|
||||
pub fn set_texture(
|
||||
&mut self,
|
||||
facade: &dyn glium::backend::Facade,
|
||||
tex_id: u64,
|
||||
image: &epi::Image,
|
||||
tex_id: egui::TextureId,
|
||||
image: &egui::ImageData,
|
||||
) {
|
||||
assert_eq!(
|
||||
image.size[0] * image.size[1],
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = image
|
||||
.pixels
|
||||
.chunks(image.size[0] as usize)
|
||||
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
|
||||
.collect();
|
||||
|
||||
let pixels: Vec<(u8, u8, u8, u8)> = match image {
|
||||
egui::ImageData::Color(image) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
image.pixels.iter().map(|color| color.to_tuple()).collect()
|
||||
}
|
||||
egui::ImageData::Alpha(image) => {
|
||||
let gamma = 1.0;
|
||||
image
|
||||
.srgba_pixels(gamma)
|
||||
.map(|color| color.to_tuple())
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
let glium_image = glium::texture::RawImage2d {
|
||||
data: std::borrow::Cow::Owned(pixels),
|
||||
width: image.width() as _,
|
||||
height: image.height() as _,
|
||||
format: glium::texture::ClientFormat::U8U8U8U8,
|
||||
};
|
||||
let format = texture::SrgbFormat::U8U8U8U8;
|
||||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||
let gl_texture = SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap();
|
||||
let gl_texture = SrgbTexture2d::with_format(facade, glium_image, format, mipmaps).unwrap();
|
||||
|
||||
self.user_textures.insert(tex_id, gl_texture.into());
|
||||
self.textures.insert(tex_id, gl_texture.into());
|
||||
}
|
||||
|
||||
pub fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
pub fn free_texture(&mut self, tex_id: egui::TextureId) {
|
||||
self.textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
|
||||
match texture_id {
|
||||
egui::TextureId::Egui => self.egui_texture.as_ref(),
|
||||
egui::TextureId::User(id) => self.user_textures.get(&id).map(|rc| rc.as_ref()),
|
||||
}
|
||||
self.textures.get(&texture_id).map(|rc| rc.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,15 +231,13 @@ impl epi::NativeTexture for Painter {
|
||||
type Texture = Rc<SrgbTexture2d>;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
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 {
|
||||
self.user_textures.insert(id, replacing);
|
||||
}
|
||||
self.textures.insert(id, replacing);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user