diff --git a/crates/graphics/src/atlas_cache.rs b/crates/graphics/src/atlas_cache.rs
index 4c174937..fc87a80e 100644
--- a/crates/graphics/src/atlas_cache.rs
+++ b/crates/graphics/src/atlas_cache.rs
@@ -14,7 +14,6 @@
//
// You should have received a copy of the GNU General Public License
// along with Luminol. If not, see .
-
use crate::tiles::Atlas;
#[derive(Default, Debug)]
diff --git a/crates/graphics/src/texture_loader.rs b/crates/graphics/src/texture_loader.rs
index 33bb5d74..d962723a 100644
--- a/crates/graphics/src/texture_loader.rs
+++ b/crates/graphics/src/texture_loader.rs
@@ -31,19 +31,22 @@ use std::sync::Arc;
use wgpu::util::DeviceExt;
pub struct TextureLoader {
- loaded_textures: DashMap>,
- load_errors: DashMap,
- unloaded_textures: DashSet,
+ // todo: add a load state enum for loading textures (waiting on file -> file read -> image loaded -> texture loaded)
+ loaded_textures: DashMap>,
+ load_errors: DashMap,
+ unloaded_textures: DashSet,
render_state: egui_wgpu::RenderState,
}
pub struct Texture {
- wgpu: wgpu::Texture,
- egui: egui::TextureId,
+ pub wgpu: wgpu::Texture,
+ pub egui: egui::TextureId,
}
-pub const LOADER_ID: &str = egui::load::generate_loader_id!(TextureLoader);
+pub const TEXTURE_LOADER_ID: &str = egui::load::generate_loader_id!(TextureLoader);
+
+pub const PROTOCOL: &str = "project://";
// NOTE blindly assumes texture components are 1 byte
fn texture_size_bytes(texture: &wgpu::Texture) -> u32 {
@@ -84,6 +87,30 @@ fn load_wgpu_texture_from_path(
))
}
+fn supported_uri_to_path(uri: &str) -> Option<&camino::Utf8Path> {
+ uri.strip_prefix(PROTOCOL)
+ .map(camino::Utf8Path::new)
+ .filter(|path| path.extension().filter(|&ext| ext != "svg").is_some())
+}
+
+impl Texture {
+ pub fn size(&self) -> wgpu::Extent3d {
+ self.wgpu.size()
+ }
+
+ pub fn size_vec2(&self) -> egui::Vec2 {
+ egui::vec2(self.wgpu.width() as _, self.wgpu.height() as _)
+ }
+
+ pub fn width(&self) -> u32 {
+ self.wgpu.width()
+ }
+
+ pub fn height(&self) -> u32 {
+ self.wgpu.height()
+ }
+}
+
impl TextureLoader {
pub fn new(render_state: egui_wgpu::RenderState) -> Self {
Self {
@@ -95,7 +122,11 @@ impl TextureLoader {
}
}
- pub fn load_unloaded_textures(&self, filesystem: &impl luminol_filesystem::FileSystem) {
+ pub fn load_unloaded_textures(
+ &self,
+ ctx: &egui::Context,
+ filesystem: &impl luminol_filesystem::FileSystem,
+ ) {
// dashmap has no drain method so this is the best we can do
let mut renderer = self.render_state.renderer.write();
for path in self.unloaded_textures.iter() {
@@ -107,7 +138,7 @@ impl TextureLoader {
) {
Ok(t) => t,
Err(error) => {
- self.load_errors.insert(path.to_string(), error);
+ self.load_errors.insert(path.clone(), error);
continue;
}
@@ -116,20 +147,25 @@ impl TextureLoader {
let texture_id = renderer.register_native_texture(
&self.render_state.device,
&wgpu_texture.create_view(&wgpu::TextureViewDescriptor {
- label: Some(&path),
+ label: Some(path.as_str()),
..Default::default()
}),
wgpu::FilterMode::Nearest,
);
self.loaded_textures.insert(
- path.to_string(),
+ path.clone(),
Arc::new(Texture {
wgpu: wgpu_texture,
egui: texture_id,
}),
);
}
+
+ if !self.unloaded_textures.is_empty() {
+ ctx.request_repaint(); // if we've loaded textures
+ }
+
self.unloaded_textures.clear();
}
@@ -150,13 +186,19 @@ impl TextureLoader {
Ok(self.register_texture(path.to_string(), wgpu_texture))
}
- pub fn register_texture(&self, uri: String, wgpu_texture: wgpu::Texture) -> Arc {
+ pub fn register_texture(
+ &self,
+ path: impl Into,
+ wgpu_texture: wgpu::Texture,
+ ) -> Arc {
+ let path = path.into();
+
// todo maybe use custom sampler descriptor?
// would allow for better texture names in debuggers
let texture_id = self.render_state.renderer.write().register_native_texture(
&self.render_state.device,
&wgpu_texture.create_view(&wgpu::TextureViewDescriptor {
- label: Some(&uri),
+ label: Some(path.as_str()),
..Default::default()
}),
wgpu::FilterMode::Nearest,
@@ -166,21 +208,18 @@ impl TextureLoader {
wgpu: wgpu_texture,
egui: texture_id,
});
- self.loaded_textures.insert(uri, texture.clone());
+ self.loaded_textures.insert(path, texture.clone());
texture
}
pub fn get(&self, path: impl AsRef) -> Option> {
- self.loaded_textures
- .get(path.as_ref().as_str())
- .as_deref()
- .cloned()
+ self.loaded_textures.get(path.as_ref()).as_deref().cloned()
}
}
impl egui::load::TextureLoader for TextureLoader {
fn id(&self) -> &str {
- LOADER_ID
+ TEXTURE_LOADER_ID
}
fn load(
@@ -190,31 +229,41 @@ impl egui::load::TextureLoader for TextureLoader {
_: egui::TextureOptions,
_: egui::SizeHint,
) -> TextureLoadResult {
- if let Some(texture) = self.loaded_textures.get(uri).as_deref() {
- let extents = texture.wgpu.size();
+ // check if the uri is supported (starts with project:// and does not end with ".svg")
+ let Some(path) = supported_uri_to_path(uri) else {
+ return Err(LoadError::NotSupported);
+ };
+
+ if let Some(texture) = self.loaded_textures.get(path).as_deref() {
return Ok(TexturePoll::Ready {
- texture: SizedTexture::new(
- texture.egui,
- egui::vec2(extents.width as f32, extents.height as f32),
- ),
+ texture: SizedTexture::new(texture.egui, texture.size_vec2()),
});
}
- if let Some(error) = self.load_errors.get(uri) {
- if error.is::() {
- return Err(LoadError::NotSupported);
- } else {
- return Err(LoadError::Loading(error.to_string()));
+ // if during loading we errored, check if it's because the image crate doesn't support loading this file format
+ if let Some(error) = self.load_errors.get(path) {
+ match error.downcast_ref::() {
+ Some(image::ImageError::Decoding(error))
+ if matches!(error.format_hint(), image::error::ImageFormatHint::Unknown) =>
+ {
+ return Err(LoadError::NotSupported)
+ }
+ Some(image::ImageError::Unsupported(_)) => return Err(LoadError::NotSupported),
+ _ => return Err(LoadError::Loading(error.to_string())),
}
}
- self.unloaded_textures.insert(uri.to_string());
+ self.unloaded_textures.insert(path.to_path_buf());
Ok(TexturePoll::Pending { size: None })
}
fn forget(&self, uri: &str) {
- self.loaded_textures.remove(uri);
+ let Some(path) = supported_uri_to_path(uri) else {
+ return;
+ };
+
+ self.loaded_textures.remove(path);
}
fn forget_all(&self) {