mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 13:20:05 -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:
241
epaint/src/image.rs
Normal file
241
epaint/src/image.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use crate::Color32;
|
||||
|
||||
/// An image stored in RAM.
|
||||
///
|
||||
/// To load an image file, see [`ColorImage::from_rgba_unmultiplied`].
|
||||
///
|
||||
/// In order to paint the image on screen, you first need to convert it to
|
||||
///
|
||||
/// See also: [`ColorImage`], [`AlphaImage`].
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum ImageData {
|
||||
/// RGBA image.
|
||||
Color(ColorImage),
|
||||
/// Used for the font texture.
|
||||
Alpha(AlphaImage),
|
||||
}
|
||||
|
||||
impl ImageData {
|
||||
pub fn size(&self) -> [usize; 2] {
|
||||
match self {
|
||||
Self::Color(image) => image.size,
|
||||
Self::Alpha(image) => image.size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.size()[0]
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.size()[1]
|
||||
}
|
||||
|
||||
pub fn bytes_per_pixel(&self) -> usize {
|
||||
match self {
|
||||
Self::Color(_) => 4,
|
||||
Self::Alpha(_) => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A 2D RGBA color image in RAM.
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct ColorImage {
|
||||
/// width, height.
|
||||
pub size: [usize; 2],
|
||||
/// The pixels, row by row, from top to bottom.
|
||||
pub pixels: Vec<Color32>,
|
||||
}
|
||||
|
||||
impl ColorImage {
|
||||
/// Create an image filled with the given color.
|
||||
pub fn new(size: [usize; 2], color: Color32) -> Self {
|
||||
Self {
|
||||
size,
|
||||
pixels: vec![color; size[0] * size[1]],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an `Image` from flat un-multiplied RGBA data.
|
||||
///
|
||||
/// This is usually what you want to use after having loaded an image file.
|
||||
///
|
||||
/// Panics if `size[0] * size[1] * 4 != rgba.len()`.
|
||||
///
|
||||
/// ## Example using the [`image`](crates.io/crates/image) crate:
|
||||
/// ``` ignore
|
||||
/// fn load_image_from_path(path: &std::path::Path) -> Result<egui::ColorImage, image::ImageError> {
|
||||
/// use image::GenericImageView as _;
|
||||
/// let image = image::io::Reader::open(path)?.decode()?;
|
||||
/// let size = [image.width() as _, image.height() as _];
|
||||
/// let image_buffer = image.to_rgba8();
|
||||
/// let pixels = image_buffer.as_flat_samples();
|
||||
/// Ok(egui::ColorImage::from_rgba_unmultiplied(
|
||||
/// size,
|
||||
/// pixels.as_slice(),
|
||||
/// ))
|
||||
/// }
|
||||
///
|
||||
/// fn load_image_from_memory(image_data: &[u8]) -> Result<ColorImage, image::ImageError> {
|
||||
/// use image::GenericImageView as _;
|
||||
/// let image = image::load_from_memory(image_data)?;
|
||||
/// let size = [image.width() as _, image.height() as _];
|
||||
/// let image_buffer = image.to_rgba8();
|
||||
/// let pixels = image_buffer.as_flat_samples();
|
||||
/// Ok(ColorImage::from_rgba_unmultiplied(
|
||||
/// size,
|
||||
/// pixels.as_slice(),
|
||||
/// ))
|
||||
/// }
|
||||
/// ```
|
||||
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| Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||
.collect();
|
||||
Self { size, pixels }
|
||||
}
|
||||
|
||||
/// An example color image, useful for tests.
|
||||
pub fn example() -> Self {
|
||||
let width = 128;
|
||||
let height = 64;
|
||||
let mut img = Self::new([width, height], Color32::TRANSPARENT);
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let h = x as f32 / width as f32;
|
||||
let s = 1.0;
|
||||
let v = 1.0;
|
||||
let a = y as f32 / height as f32;
|
||||
img[(x, y)] = crate::color::Hsva { h, s, v, a }.into();
|
||||
}
|
||||
}
|
||||
img
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> usize {
|
||||
self.size[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> usize {
|
||||
self.size[1]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<(usize, usize)> for ColorImage {
|
||||
type Output = Color32;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, (x, y): (usize, usize)) -> &Color32 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
&self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<(usize, usize)> for ColorImage {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Color32 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
&mut self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ColorImage> for ImageData {
|
||||
#[inline(always)]
|
||||
fn from(image: ColorImage) -> Self {
|
||||
Self::Color(image)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// An 8-bit image, representing difference levels of transparent white.
|
||||
///
|
||||
/// Used for the font texture
|
||||
#[derive(Clone, Default, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct AlphaImage {
|
||||
/// width, height
|
||||
pub size: [usize; 2],
|
||||
/// The alpha (linear space 0-255) of something white.
|
||||
///
|
||||
/// One byte per pixel. Often you want to use [`Self::srgba_pixels`] instead.
|
||||
pub pixels: Vec<u8>,
|
||||
}
|
||||
|
||||
impl AlphaImage {
|
||||
pub fn new(size: [usize; 2]) -> Self {
|
||||
Self {
|
||||
size,
|
||||
pixels: vec![0; size[0] * size[1]],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> usize {
|
||||
self.size[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> usize {
|
||||
self.size[1]
|
||||
}
|
||||
|
||||
/// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom.
|
||||
///
|
||||
/// `gamma` should normally be set to 1.0.
|
||||
/// If you are having problems with text looking skinny and pixelated, try
|
||||
/// setting a lower gamma, e.g. `0.5`.
|
||||
pub fn srgba_pixels(
|
||||
&'_ self,
|
||||
gamma: f32,
|
||||
) -> impl ExactSizeIterator<Item = super::Color32> + '_ {
|
||||
let srgba_from_alpha_lut: Vec<Color32> = (0..=255)
|
||||
.map(|a| {
|
||||
let a = super::color::linear_f32_from_linear_u8(a).powf(gamma);
|
||||
super::Rgba::from_white_alpha(a).into()
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.pixels
|
||||
.iter()
|
||||
.map(move |&a| srgba_from_alpha_lut[a as usize])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<(usize, usize)> for AlphaImage {
|
||||
type Output = u8;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, (x, y): (usize, usize)) -> &u8 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
&self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<(usize, usize)> for AlphaImage {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut u8 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
&mut self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AlphaImage> for ImageData {
|
||||
#[inline(always)]
|
||||
fn from(image: AlphaImage) -> Self {
|
||||
Self::Alpha(image)
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,7 @@
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
pub mod color;
|
||||
pub mod image;
|
||||
mod mesh;
|
||||
pub mod mutex;
|
||||
mod shadow;
|
||||
@@ -98,10 +99,13 @@ mod stroke;
|
||||
pub mod tessellator;
|
||||
pub mod text;
|
||||
mod texture_atlas;
|
||||
mod texture_handle;
|
||||
pub mod textures;
|
||||
pub mod util;
|
||||
|
||||
pub use {
|
||||
color::{Color32, Rgba},
|
||||
image::{AlphaImage, ColorImage, ImageData},
|
||||
mesh::{Mesh, Mesh16, Vertex},
|
||||
shadow::Shadow,
|
||||
shape::{CircleShape, PathShape, RectShape, Shape, TextShape},
|
||||
@@ -110,6 +114,8 @@ pub use {
|
||||
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
|
||||
text::{Fonts, Galley, TextStyle},
|
||||
texture_atlas::{FontImage, TextureAtlas},
|
||||
texture_handle::TextureHandle,
|
||||
textures::TextureManager,
|
||||
};
|
||||
|
||||
pub use emath::{pos2, vec2, Pos2, Rect, Vec2};
|
||||
@@ -124,21 +130,25 @@ pub use emath;
|
||||
pub const WHITE_UV: emath::Pos2 = emath::pos2(0.0, 0.0);
|
||||
|
||||
/// What texture to use in a [`Mesh`] mesh.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
///
|
||||
/// If you don't want to use a texture, use `TextureId::Epaint(0)` and the [`WHITE_UV`] for uv-coord.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum TextureId {
|
||||
/// The egui font texture.
|
||||
/// If you don't want to use a texture, pick this and the [`WHITE_UV`] for uv-coord.
|
||||
Egui,
|
||||
/// Textures allocated using [`TextureManager`].
|
||||
///
|
||||
/// The first texture (`TextureId::Epaint(0)`) is used for the font data.
|
||||
Managed(u64),
|
||||
|
||||
/// Your own texture, defined in any which way you want.
|
||||
/// egui won't care. The backend renderer will presumably use this to look up what texture to use.
|
||||
/// The backend renderer will presumably use this to look up what texture to use.
|
||||
User(u64),
|
||||
}
|
||||
|
||||
impl Default for TextureId {
|
||||
/// The epaint font texture.
|
||||
fn default() -> Self {
|
||||
Self::Egui
|
||||
Self::Managed(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ impl Mesh {
|
||||
|
||||
#[inline(always)]
|
||||
pub fn colored_vertex(&mut self, pos: Pos2, color: Color32) {
|
||||
crate::epaint_assert!(self.texture_id == TextureId::Egui);
|
||||
crate::epaint_assert!(self.texture_id == TextureId::default());
|
||||
self.vertices.push(Vertex {
|
||||
pos,
|
||||
uv: WHITE_UV,
|
||||
@@ -168,7 +168,7 @@ impl Mesh {
|
||||
/// Uniformly colored rectangle.
|
||||
#[inline(always)]
|
||||
pub fn add_colored_rect(&mut self, rect: Rect, color: Color32) {
|
||||
crate::epaint_assert!(self.texture_id == TextureId::Egui);
|
||||
crate::epaint_assert!(self.texture_id == TextureId::default());
|
||||
self.add_rect_with_uv(rect, [WHITE_UV, WHITE_UV].into(), color);
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ impl Shape {
|
||||
if let Shape::Mesh(mesh) = self {
|
||||
mesh.texture_id
|
||||
} else {
|
||||
super::TextureId::Egui
|
||||
super::TextureId::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -376,12 +376,12 @@ fn allocate_glyph(
|
||||
} else {
|
||||
let glyph_pos = atlas.allocate((glyph_width, glyph_height));
|
||||
|
||||
let texture = atlas.image_mut();
|
||||
let image = atlas.image_mut();
|
||||
glyph.draw(|x, y, v| {
|
||||
if v > 0.0 {
|
||||
let px = glyph_pos.0 + x as usize;
|
||||
let py = glyph_pos.1 + y as usize;
|
||||
texture[(px, py)] = (v * 255.0).round() as u8;
|
||||
image.image[(px, py)] = (v * 255.0).round() as u8;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ pub enum TextStyle {
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
pub fn all() -> impl Iterator<Item = TextStyle> {
|
||||
pub fn all() -> impl ExactSizeIterator<Item = TextStyle> {
|
||||
[
|
||||
TextStyle::Small,
|
||||
TextStyle::Body,
|
||||
@@ -253,13 +253,13 @@ impl Fonts {
|
||||
|
||||
// We want an atlas big enough to be able to include all the Emojis in the `TextStyle::Heading`,
|
||||
// so we can show the Emoji picker demo window.
|
||||
let mut atlas = TextureAtlas::new(2048, 64);
|
||||
let mut atlas = TextureAtlas::new([2048, 64]);
|
||||
|
||||
{
|
||||
// Make the top left pixel fully white:
|
||||
let pos = atlas.allocate((1, 1));
|
||||
assert_eq!(pos, (0, 0));
|
||||
atlas.image_mut()[pos] = 255;
|
||||
atlas.image_mut().image[pos] = 255;
|
||||
}
|
||||
|
||||
let atlas = Arc::new(Mutex::new(atlas));
|
||||
@@ -287,7 +287,7 @@ impl Fonts {
|
||||
let mut atlas = atlas.lock();
|
||||
let texture = atlas.image_mut();
|
||||
// Make sure we seed the texture version with something unique based on the default characters:
|
||||
texture.version = crate::util::hash(&texture.pixels);
|
||||
texture.version = crate::util::hash(&texture.image);
|
||||
}
|
||||
|
||||
Self {
|
||||
@@ -295,7 +295,7 @@ impl Fonts {
|
||||
definitions,
|
||||
fonts,
|
||||
atlas,
|
||||
buffered_font_image: Default::default(), //atlas.lock().texture().clone();
|
||||
buffered_font_image: Default::default(),
|
||||
galley_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,30 @@
|
||||
use crate::image::AlphaImage;
|
||||
|
||||
/// An 8-bit texture containing font data.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct FontImage {
|
||||
/// e.g. a hash of the data. Use this to detect changes!
|
||||
/// If the texture changes, this too will change.
|
||||
pub version: u64,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
/// The alpha (linear space 0-255) of something white.
|
||||
///
|
||||
/// One byte per pixel. Often you want to use [`Self::srgba_pixels`] instead.
|
||||
pub pixels: Vec<u8>,
|
||||
|
||||
/// The actual image data.
|
||||
pub image: AlphaImage,
|
||||
}
|
||||
|
||||
impl FontImage {
|
||||
#[inline]
|
||||
pub fn size(&self) -> [usize; 2] {
|
||||
[self.width, self.height]
|
||||
self.image.size
|
||||
}
|
||||
|
||||
/// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom.
|
||||
///
|
||||
/// `gamma` should normally be set to 1.0.
|
||||
/// If you are having problems with egui text looking skinny and pixelated, try
|
||||
/// setting a lower gamma, e.g. `0.5`.
|
||||
pub fn srgba_pixels(&'_ self, gamma: f32) -> impl Iterator<Item = super::Color32> + '_ {
|
||||
use super::Color32;
|
||||
|
||||
let srgba_from_luminance_lut: Vec<Color32> = (0..=255)
|
||||
.map(|a| {
|
||||
let a = super::color::linear_f32_from_linear_u8(a).powf(gamma);
|
||||
super::Rgba::from_white_alpha(a).into()
|
||||
})
|
||||
.collect();
|
||||
self.pixels
|
||||
.iter()
|
||||
.map(move |&l| srgba_from_luminance_lut[l as usize])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<(usize, usize)> for FontImage {
|
||||
type Output = u8;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, (x, y): (usize, usize)) -> &u8 {
|
||||
assert!(x < self.width);
|
||||
assert!(y < self.height);
|
||||
&self.pixels[y * self.width + x]
|
||||
pub fn width(&self) -> usize {
|
||||
self.image.size[0]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<(usize, usize)> for FontImage {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut u8 {
|
||||
assert!(x < self.width);
|
||||
assert!(y < self.height);
|
||||
&mut self.pixels[y * self.width + x]
|
||||
pub fn height(&self) -> usize {
|
||||
self.image.size[1]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,13 +41,11 @@ pub struct TextureAtlas {
|
||||
}
|
||||
|
||||
impl TextureAtlas {
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
pub fn new(size: [usize; 2]) -> Self {
|
||||
Self {
|
||||
image: FontImage {
|
||||
version: 0,
|
||||
width,
|
||||
height,
|
||||
pixels: vec![0; width * height],
|
||||
image: AlphaImage::new(size),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
@@ -99,12 +68,12 @@ impl TextureAtlas {
|
||||
const PADDING: usize = 1;
|
||||
|
||||
assert!(
|
||||
w <= self.image.width,
|
||||
w <= self.image.width(),
|
||||
"Tried to allocate a {} wide glyph in a {} wide texture atlas",
|
||||
w,
|
||||
self.image.width
|
||||
self.image.width()
|
||||
);
|
||||
if self.cursor.0 + w > self.image.width {
|
||||
if self.cursor.0 + w > self.image.width() {
|
||||
// New row:
|
||||
self.cursor.0 = 0;
|
||||
self.cursor.1 += self.row_height + PADDING;
|
||||
@@ -112,15 +81,7 @@ impl TextureAtlas {
|
||||
}
|
||||
|
||||
self.row_height = self.row_height.max(h);
|
||||
while self.cursor.1 + self.row_height >= self.image.height {
|
||||
self.image.height *= 2;
|
||||
}
|
||||
|
||||
if self.image.width * self.image.height > self.image.pixels.len() {
|
||||
self.image
|
||||
.pixels
|
||||
.resize(self.image.width * self.image.height, 0);
|
||||
}
|
||||
resize_to_min_height(&mut self.image.image, self.cursor.1 + self.row_height);
|
||||
|
||||
let pos = self.cursor;
|
||||
self.cursor.0 += w + PADDING;
|
||||
@@ -128,3 +89,13 @@ impl TextureAtlas {
|
||||
(pos.0 as usize, pos.1 as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn resize_to_min_height(image: &mut AlphaImage, min_height: usize) {
|
||||
while min_height >= image.height() {
|
||||
image.size[1] *= 2; // double the height
|
||||
}
|
||||
|
||||
if image.width() * image.height() > image.pixels.len() {
|
||||
image.pixels.resize(image.width() * image.height(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
107
epaint/src/texture_handle.rs
Normal file
107
epaint/src/texture_handle.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use crate::{
|
||||
emath::NumExt,
|
||||
mutex::{Arc, RwLock},
|
||||
ImageData, TextureId, TextureManager,
|
||||
};
|
||||
|
||||
/// Used to paint images.
|
||||
///
|
||||
/// An _image_ is pixels stored in RAM, and represented using [`ImageData`].
|
||||
/// Before you can paint it however, you need to convert it to a _texture_.
|
||||
///
|
||||
/// If you are using egui, use `egui::Context::load_texture`.
|
||||
///
|
||||
/// The [`TextureHandle`] can be cloned cheaply.
|
||||
/// When the last [`TextureHandle`] for specific texture is dropped, the texture is freed.
|
||||
///
|
||||
/// See also [`TextureManager`].
|
||||
#[must_use]
|
||||
pub struct TextureHandle {
|
||||
tex_mngr: Arc<RwLock<TextureManager>>,
|
||||
id: TextureId,
|
||||
}
|
||||
|
||||
impl Drop for TextureHandle {
|
||||
fn drop(&mut self) {
|
||||
self.tex_mngr.write().free(self.id);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for TextureHandle {
|
||||
fn clone(&self) -> Self {
|
||||
self.tex_mngr.write().retain(self.id);
|
||||
Self {
|
||||
tex_mngr: self.tex_mngr.clone(),
|
||||
id: self.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TextureHandle {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for TextureHandle {}
|
||||
|
||||
impl std::hash::Hash for TextureHandle {
|
||||
#[inline]
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureHandle {
|
||||
/// If you are using egui, use `egui::Context::load_texture` instead.
|
||||
pub fn new(tex_mngr: Arc<RwLock<TextureManager>>, id: TextureId) -> Self {
|
||||
Self { tex_mngr, id }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> TextureId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Assign a new image to an existing texture.
|
||||
pub fn set(&mut self, image: impl Into<ImageData>) {
|
||||
self.tex_mngr.write().set(self.id, image.into());
|
||||
}
|
||||
|
||||
/// width x height
|
||||
pub fn size(&self) -> [usize; 2] {
|
||||
self.tex_mngr.read().meta(self.id).unwrap().size
|
||||
}
|
||||
|
||||
/// width x height
|
||||
pub fn size_vec2(&self) -> crate::Vec2 {
|
||||
let [w, h] = self.size();
|
||||
crate::Vec2::new(w as f32, h as f32)
|
||||
}
|
||||
|
||||
/// width / height
|
||||
pub fn aspect_ratio(&self) -> f32 {
|
||||
let [w, h] = self.size();
|
||||
w as f32 / h.at_least(1) as f32
|
||||
}
|
||||
|
||||
/// Debug-name.
|
||||
pub fn name(&self) -> String {
|
||||
self.tex_mngr.read().meta(self.id).unwrap().name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TextureHandle> for TextureId {
|
||||
#[inline(always)]
|
||||
fn from(handle: &TextureHandle) -> Self {
|
||||
handle.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&mut TextureHandle> for TextureId {
|
||||
#[inline(always)]
|
||||
fn from(handle: &mut TextureHandle) -> Self {
|
||||
handle.id()
|
||||
}
|
||||
}
|
||||
161
epaint/src/textures.rs
Normal file
161
epaint/src/textures.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use crate::{image::ImageData, TextureId};
|
||||
use ahash::AHashMap;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Low-level manager for allocating textures.
|
||||
///
|
||||
/// Communicates with the painting subsystem using [`Self::take_delta`].
|
||||
#[derive(Default)]
|
||||
pub struct TextureManager {
|
||||
/// We allocate texture id:s linearly.
|
||||
next_id: u64,
|
||||
/// Information about currently allocated textures.
|
||||
metas: AHashMap<TextureId, TextureMeta>,
|
||||
delta: TexturesDelta,
|
||||
}
|
||||
|
||||
impl TextureManager {
|
||||
/// Allocate a new texture.
|
||||
///
|
||||
/// The given name can be useful for later debugging.
|
||||
///
|
||||
/// The returned [`TextureId`] will be [`TextureId::Managed`], with an index
|
||||
/// starting from zero and increasing with each call to [`Self::alloc`].
|
||||
///
|
||||
/// The first texture you allocate will be `TextureId::Managed(0) == TexureId::default()` and
|
||||
/// MUST have a white pixel at (0,0) ([`crate::WHITE_UV`]).
|
||||
///
|
||||
/// The texture is given a retain-count of `1`, requiring one call to [`Self::free`] to free it.
|
||||
pub fn alloc(&mut self, name: String, image: ImageData) -> TextureId {
|
||||
let id = TextureId::Managed(self.next_id);
|
||||
self.next_id += 1;
|
||||
|
||||
self.metas.entry(id).or_insert_with(|| TextureMeta {
|
||||
name,
|
||||
size: image.size(),
|
||||
bytes_per_pixel: image.bytes_per_pixel(),
|
||||
retain_count: 1,
|
||||
});
|
||||
|
||||
self.delta.set.insert(id, image);
|
||||
id
|
||||
}
|
||||
|
||||
/// Assign a new image to an existing texture.
|
||||
pub fn set(&mut self, id: TextureId, image: ImageData) {
|
||||
if let Some(meta) = self.metas.get_mut(&id) {
|
||||
meta.size = image.size();
|
||||
meta.bytes_per_pixel = image.bytes_per_pixel();
|
||||
self.delta.set.insert(id, image);
|
||||
} else {
|
||||
crate::epaint_assert!(
|
||||
false,
|
||||
"Tried setting texture {:?} which is not allocated",
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Free an existing texture.
|
||||
pub fn free(&mut self, id: TextureId) {
|
||||
if let std::collections::hash_map::Entry::Occupied(mut entry) = self.metas.entry(id) {
|
||||
let meta = entry.get_mut();
|
||||
meta.retain_count -= 1;
|
||||
if meta.retain_count == 0 {
|
||||
entry.remove();
|
||||
self.delta.free.push(id);
|
||||
}
|
||||
} else {
|
||||
crate::epaint_assert!(
|
||||
false,
|
||||
"Tried freeing texture {:?} which is not allocated",
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Increase the retain-count of the given texture.
|
||||
///
|
||||
/// For each time you call [`Self::retain`] you must call [`Self::free`] on additional time.
|
||||
pub fn retain(&mut self, id: TextureId) {
|
||||
if let Some(meta) = self.metas.get_mut(&id) {
|
||||
meta.retain_count += 1;
|
||||
} else {
|
||||
crate::epaint_assert!(
|
||||
false,
|
||||
"Tried retaining texture {:?} which is not allocated",
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Take and reset changes since last frame.
|
||||
///
|
||||
/// These should be applied to the painting subsystem each frame.
|
||||
pub fn take_delta(&mut self) -> TexturesDelta {
|
||||
std::mem::take(&mut self.delta)
|
||||
}
|
||||
|
||||
/// Get meta-data about a specific texture.
|
||||
pub fn meta(&self, id: TextureId) -> Option<&TextureMeta> {
|
||||
self.metas.get(&id)
|
||||
}
|
||||
|
||||
/// Get meta-data about all allocated textures in some arbitrary order.
|
||||
pub fn allocated(&self) -> impl ExactSizeIterator<Item = (&TextureId, &TextureMeta)> {
|
||||
self.metas.iter()
|
||||
}
|
||||
|
||||
/// Total number of allocated textures.
|
||||
pub fn num_allocated(&self) -> usize {
|
||||
self.metas.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Meta-data about an allocated texture.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TextureMeta {
|
||||
/// A human-readable name useful for debugging.
|
||||
pub name: String,
|
||||
|
||||
/// width x height
|
||||
pub size: [usize; 2],
|
||||
|
||||
/// 4 or 1
|
||||
pub bytes_per_pixel: usize,
|
||||
|
||||
/// Free when this reaches zero.
|
||||
pub retain_count: usize,
|
||||
}
|
||||
|
||||
impl TextureMeta {
|
||||
/// Size in bytes.
|
||||
/// width x height x [`Self::bytes_per_pixel`].
|
||||
pub fn bytes_used(&self) -> usize {
|
||||
self.size[0] * self.size[1] * self.bytes_per_pixel
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// What has been allocated and freed during the last period.
|
||||
///
|
||||
/// These are commands given to the integration painter.
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[must_use = "The painter must take care of this"]
|
||||
pub struct TexturesDelta {
|
||||
/// New or changed textures. Apply before painting.
|
||||
pub set: AHashMap<TextureId, ImageData>,
|
||||
|
||||
/// Texture to free after painting.
|
||||
pub free: Vec<TextureId>,
|
||||
}
|
||||
|
||||
impl TexturesDelta {
|
||||
pub fn append(&mut self, mut newer: TexturesDelta) {
|
||||
self.set.extend(newer.set.into_iter());
|
||||
self.free.append(&mut newer.free);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user