Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 74 additions & 2 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration};

use containers::area::AreaState;
use emath::GuiRounding as _;
use epaint::{
emath::{self, TSTransform},
Expand All @@ -17,7 +16,7 @@ use epaint::{

use crate::{
animation_manager::AnimationManager,
containers,
containers::{self, area::AreaState},
data::output::PlatformOutput,
epaint, hit_test,
input_state::{InputState, MultiTouchInfo, PointerEvent},
Expand Down Expand Up @@ -3065,6 +3064,12 @@ impl Context {
self.texture_ui(ui);
});

CollapsingHeader::new("🖼 Image loaders")
.default_open(false)
.show(ui, |ui| {
self.loaders_ui(ui);
});

CollapsingHeader::new("🔠 Font texture")
.default_open(false)
.show(ui, |ui| {
Expand Down Expand Up @@ -3147,6 +3152,73 @@ impl Context {
});
}

/// Show stats about different image loaders.
pub fn loaders_ui(&self, ui: &mut crate::Ui) {
struct LoaderInfo {
id: String,
byte_size: usize,
}

let mut byte_loaders = vec![];
let mut image_loaders = vec![];
let mut texture_loaders = vec![];

{
let loaders = self.loaders();
let Loaders {
include: _,
bytes,
image,
texture,
} = loaders.as_ref();

for loader in bytes.lock().iter() {
byte_loaders.push(LoaderInfo {
id: loader.id().to_owned(),
byte_size: loader.byte_size(),
});
}
for loader in image.lock().iter() {
image_loaders.push(LoaderInfo {
id: loader.id().to_owned(),
byte_size: loader.byte_size(),
});
}
for loader in texture.lock().iter() {
texture_loaders.push(LoaderInfo {
id: loader.id().to_owned(),
byte_size: loader.byte_size(),
});
}
}

fn loaders_ui(ui: &mut crate::Ui, title: &str, loaders: &[LoaderInfo]) {
let heading = format!("{} {title} loaders", loaders.len());
crate::CollapsingHeader::new(heading)
.default_open(true)
.show(ui, |ui| {
Grid::new("loaders")
.striped(true)
.num_columns(2)
.show(ui, |ui| {
ui.label("ID");
ui.label("Size");
ui.end_row();

for loader in loaders {
ui.label(&loader.id);
ui.label(format!("{:.3} MB", loader.byte_size as f64 * 1e-6));
ui.end_row();
}
});
});
}

loaders_ui(ui, "byte", &byte_loaders);
loaders_ui(ui, "image", &image_loaders);
loaders_ui(ui, "texture", &texture_loaders);
}

/// Shows the contents of [`Self::memory`].
pub fn memory_ui(&self, ui: &mut crate::Ui) {
if ui
Expand Down
4 changes: 2 additions & 2 deletions crates/egui/src/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ pub type Result<T, E = LoadError> = std::result::Result<T, E>;
/// The size is measured in texels, with the pixels per point already factored in.
///
/// All variants will preserve the original aspect ratio.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SizeHint {
/// Scale original size by some factor.
Scale(OrderedFloat<f32>),
Expand Down Expand Up @@ -402,7 +402,7 @@ pub trait ImageLoader {
fn byte_size(&self) -> usize;

/// Returns `true` if some image is currently being loaded.
///
///
/// NOTE: You probably also want to check [`BytesLoader::has_pending`].
fn has_pending(&self) -> bool {
false
Expand Down
81 changes: 74 additions & 7 deletions crates/egui/src/load/texture_loader.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
use std::borrow::Cow;
use std::sync::atomic::{AtomicU64, Ordering::Relaxed};

use super::{
BytesLoader as _, Context, HashMap, ImagePoll, Mutex, SizeHint, SizedTexture, TextureHandle,
TextureLoadResult, TextureLoader, TextureOptions, TexturePoll,
};

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct PrimaryKey {
uri: String,
texture_options: TextureOptions,
}

/// SVG:s might have several different sizes loaded
type Bucket = HashMap<Option<SizeHint>, Entry>;

struct Entry {
last_used: AtomicU64,
handle: TextureHandle,
}

#[derive(Default)]
pub struct DefaultTextureLoader {
cache: Mutex<HashMap<(Cow<'static, str>, TextureOptions), TextureHandle>>,
pass_index: AtomicU64,
cache: Mutex<HashMap<PrimaryKey, Bucket>>,
}

impl TextureLoader for DefaultTextureLoader {
Expand All @@ -22,17 +37,45 @@ impl TextureLoader for DefaultTextureLoader {
texture_options: TextureOptions,
size_hint: SizeHint,
) -> TextureLoadResult {
let svg_size_hint = if is_svg(uri) {
// For SVGs it's important that we render at the desired size,
// or we might get a blurry image when we scale it up.
// So we make the size hint a part of the cache key.
// This might lead to a lot of extra entries for the same SVG file,
// which is potentially wasteful of RAM, but better that than blurry images.
Some(size_hint)
} else {
// For other images we just use one cache value, no matter what the size we render at.
None
};

let mut cache = self.cache.lock();
if let Some(handle) = cache.get(&(Cow::Borrowed(uri), texture_options)) {
let texture = SizedTexture::from_handle(handle);
let bucket = cache
.entry(PrimaryKey {
uri: uri.to_owned(),
texture_options,
})
.or_default();

if let Some(texture) = bucket.get(&svg_size_hint) {
texture
.last_used
.store(self.pass_index.load(Relaxed), Relaxed);
let texture = SizedTexture::from_handle(&texture.handle);
Ok(TexturePoll::Ready { texture })
} else {
match ctx.try_load_image(uri, size_hint)? {
ImagePoll::Pending { size } => Ok(TexturePoll::Pending { size }),
ImagePoll::Ready { image } => {
let handle = ctx.load_texture(uri, image, texture_options);
let texture = SizedTexture::from_handle(&handle);
cache.insert((Cow::Owned(uri.to_owned()), texture_options), handle);
bucket.insert(
svg_size_hint,
Entry {
last_used: AtomicU64::new(self.pass_index.load(Relaxed)),
handle,
},
);
let reduce_texture_memory = ctx.options(|o| o.reduce_texture_memory);
if reduce_texture_memory {
let loaders = ctx.loaders();
Expand All @@ -54,7 +97,7 @@ impl TextureLoader for DefaultTextureLoader {
#[cfg(feature = "log")]
log::trace!("forget {uri:?}");

self.cache.lock().retain(|(u, _), _| u != uri);
self.cache.lock().retain(|key, _value| key.uri != uri);
}

fn forget_all(&self) {
Expand All @@ -64,11 +107,35 @@ impl TextureLoader for DefaultTextureLoader {
self.cache.lock().clear();
}

fn end_pass(&self, pass_index: u64) {
self.pass_index.store(pass_index, Relaxed);
let mut cache = self.cache.lock();
cache.retain(|_key, bucket| {
if 2 <= bucket.len() {
// There are multiple textures of the same URI (e.g. SVGs of different scales).
// This could be because someone has an SVG in a resizable container,
// and so we get a lot of different sizes of it.
// This could wast VRAM, so we remove the ones that are not used in this frame.
bucket.retain(|_, texture| pass_index <= texture.last_used.load(Relaxed) + 1);
}
!bucket.is_empty()
});
}

fn byte_size(&self) -> usize {
self.cache
.lock()
.values()
.map(|texture| texture.byte_size())
.map(|bucket| {
bucket
.values()
.map(|texture| texture.handle.byte_size())
.sum::<usize>()
})
.sum()
}
}

fn is_svg(uri: &str) -> bool {
uri.ends_with(".svg")
}
11 changes: 10 additions & 1 deletion crates/egui_demo_lib/src/demo/tests/svg_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ impl crate::View for SvgTest {
fn ui(&mut self, ui: &mut egui::Ui) {
let Self { color } = self;
ui.color_edit_button_srgba(color);
ui.add(egui::Image::new(egui::include_image!("../../../data/peace.svg")).tint(*color));
let img_src = egui::include_image!("../../../data/peace.svg");

// First paint a small version…
ui.add_sized(
egui::Vec2 { x: 20.0, y: 20.0 },
egui::Image::new(img_src.clone()).tint(*color),
);

// …then a big one, to make sure they are both crisp
ui.add(egui::Image::new(img_src).tint(*color));
}
}
4 changes: 2 additions & 2 deletions crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading