Skip to content

Commit

Permalink
Replace missing textures with a placeholder (#106)
Browse files Browse the repository at this point in the history
* refactor: load maps even if there are missing textures

There will still be toasts for missing textures, but now maps with
missing textures will load with placeholder textures instead of refusing
to open.

* fix: show toasts in reverse order

This changes the toasts to show the most recent one at the top so that
long strings of errors will show properly.

* style: use better error messages for texture loading errors

* fix: filter out useless backtrace frames on Windows

* feat: replace missing textures in atlas with a placeholder texture

* refactor: move placeholder.png into graphics crate

* perf: only initialize blank autotile texture when needed

* feat: replace missing character graphics with a placeholder texture

* feat: replace missing panorama and fog with a placeholder texture
  • Loading branch information
white-axe authored Feb 17, 2024
1 parent 4ab5c86 commit fb3c0ff
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 59 deletions.
13 changes: 12 additions & 1 deletion crates/core/src/toasts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,25 @@ use color_eyre::Section;
use itertools::Itertools;

/// A toasts management struct.
#[derive(Default)]
pub struct Toasts {
inner: egui_notify::Toasts,
}

impl Default for Toasts {
fn default() -> Self {
Self {
inner: egui_notify::Toasts::default().reverse(true),
}
}
}

// We wrap the toasts structs in a RefCell to maintain interior mutability.
#[allow(dead_code)]
impl Toasts {
fn new() -> Self {
Default::default()
}

/// Add a custom toast.
pub fn add(&mut self, toast: egui_notify::Toast) {
self.inner.add(toast);
Expand Down
Binary file added crates/graphics/data/placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions crates/graphics/src/atlas_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl Loader {
Ok(self
.atlases
.entry(tileset.id)
.or_try_insert_with(|| Atlas::new(graphics_state, filesystem, tileset))?
.or_insert_with(|| Atlas::new(graphics_state, filesystem, tileset))
.clone())
}

Expand All @@ -44,7 +44,7 @@ impl Loader {
Ok(self
.atlases
.entry(tileset.id)
.insert(Atlas::new(graphics_state, filesystem, tileset)?)
.insert(Atlas::new(graphics_state, filesystem, tileset))
.clone())
}

Expand Down
64 changes: 59 additions & 5 deletions crates/graphics/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
// You should have received a copy of the GNU General Public License
// along with Luminol. If not, see <http://www.gnu.org/licenses/>.

use color_eyre::eyre::Context;
use image::EncodableLayout;
use itertools::Itertools;
use wgpu::util::DeviceExt;

use std::sync::Arc;

use fragile::Fragile;
Expand Down Expand Up @@ -60,11 +65,60 @@ impl Event {
};

let texture = if let Some(ref filename) = page.graphic.character_name {
graphics_state.texture_loader.load_now_dir(
filesystem,
"Graphics/Characters",
filename,
)?
let texture = graphics_state
.texture_loader
.load_now_dir(filesystem, "Graphics/Characters", filename)
.wrap_err_with(|| format!("Error loading event character graphic {filename:?}"));
match texture {
Ok(t) => t,
Err(e) => {
graphics_state.send_texture_error(e);

let placeholder_char_texture = graphics_state
.texture_loader
.get("placeholder_char_texture")
.unwrap_or_else(|| {
let placeholder_img = graphics_state.placeholder_img();

graphics_state.texture_loader.register_texture(
"placeholder_char_texture",
graphics_state.render_state.device.create_texture_with_data(
&graphics_state.render_state.queue,
&wgpu::TextureDescriptor {
label: Some("placeholder_char_texture"),
size: wgpu::Extent3d {
width: 128,
height: 128,
depth_or_array_layers: 1,
},
dimension: wgpu::TextureDimension::D2,
mip_level_count: 1,
sample_count: 1,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
},
wgpu::util::TextureDataOrder::LayerMajor,
&itertools::iproduct!(0..128, 0..128, 0..4)
.map(|(y, x, c)| {
// Tile the placeholder image
placeholder_img.as_bytes()[(c
+ (x % placeholder_img.width()) * 4
+ (y % placeholder_img.height())
* 4
* placeholder_img.width())
as usize]
})
.collect_vec(),
),
)
});

placeholder_char_texture
}
}
} else if page.graphic.tile_id.is_some() {
atlas.atlas_texture.clone()
} else {
Expand Down
24 changes: 24 additions & 0 deletions crates/graphics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ pub struct GraphicsState {

pipelines: Pipelines,
bind_group_layouts: BindGroupLayouts,

texture_error_tx: crossbeam::channel::Sender<color_eyre::Report>,
texture_error_rx: crossbeam::channel::Receiver<color_eyre::Report>,
}

pub struct BindGroupLayouts {
Expand Down Expand Up @@ -99,6 +102,8 @@ impl GraphicsState {
..Default::default()
});

let (texture_error_tx, texture_error_rx) = crossbeam::channel::unbounded();

Self {
texture_loader,
atlas_loader: atlas_cache,
Expand All @@ -108,12 +113,31 @@ impl GraphicsState {

pipelines,
bind_group_layouts,

texture_error_tx,
texture_error_rx,
}
}

pub fn push_constants_supported(&self) -> bool {
push_constants_supported(&self.render_state)
}

pub fn send_texture_error(&self, error: color_eyre::Report) {
self.texture_error_tx
.try_send(error)
.expect("failed to send texture error");
}

pub fn texture_errors(&self) -> impl Iterator<Item = color_eyre::Report> + '_ {
self.texture_error_rx.try_iter()
}

pub fn placeholder_img(&self) -> image::RgbaImage {
image::load_from_memory(include_bytes!("../data/placeholder.png"))
.expect("assets/placeholder.png is not a valid image")
.to_rgba8()
}
}

pub fn push_constants_supported(render_state: &luminol_egui_wgpu::RenderState) -> bool {
Expand Down
117 changes: 107 additions & 10 deletions crates/graphics/src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
// You should have received a copy of the GNU General Public License
// along with Luminol. If not, see <http://www.gnu.org/licenses/>.

use color_eyre::eyre::Context;
use image::EncodableLayout;
use itertools::Itertools;
use wgpu::util::DeviceExt;

use std::sync::Arc;

use std::time::Duration;
Expand Down Expand Up @@ -143,14 +148,60 @@ impl Map {
let collision = Collision::new(graphics_state, viewport.clone(), passages);

let panorama = if let Some(ref panorama_name) = tileset.panorama_name {
let texture = graphics_state
.texture_loader
.load_now_dir(filesystem, "Graphics/Panoramas", panorama_name)
.wrap_err_with(|| format!("Error loading map panorama {panorama_name:?}"))
.unwrap_or_else(|e| {
graphics_state.send_texture_error(e);

graphics_state
.texture_loader
.get("placeholder_tile_texture")
.unwrap_or_else(|| {
let placeholder_img = graphics_state.placeholder_img();

graphics_state.texture_loader.register_texture(
"placeholder_tile_texture",
graphics_state.render_state.device.create_texture_with_data(
&graphics_state.render_state.queue,
&wgpu::TextureDescriptor {
label: Some("placeholder_tile_texture"),
size: wgpu::Extent3d {
width: 32,
height: 32,
depth_or_array_layers: 1,
},
dimension: wgpu::TextureDimension::D2,
mip_level_count: 1,
sample_count: 1,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
},
wgpu::util::TextureDataOrder::LayerMajor,
&itertools::iproduct!(0..32, 0..32, 0..4)
.map(|(y, x, c)| {
// Tile the placeholder image
placeholder_img.as_bytes()[(c
+ (x % placeholder_img.width()) * 4
+ (y % placeholder_img.height())
* 4
* placeholder_img.width())
as usize]
})
.collect_vec(),
),
)
})
});

Some(Plane::new(
graphics_state,
viewport.clone(),
graphics_state.texture_loader.load_now_dir(
filesystem,
"Graphics/Panoramas",
panorama_name,
)?,
texture,
tileset.panorama_hue,
100,
luminol_data::BlendMode::Normal,
Expand All @@ -162,14 +213,60 @@ impl Map {
None
};
let fog = if let Some(ref fog_name) = tileset.fog_name {
let texture = graphics_state
.texture_loader
.load_now_dir(filesystem, "Graphics/Fogs", fog_name)
.wrap_err_with(|| format!("Error loading map fog {fog_name:?}"))
.unwrap_or_else(|e| {
graphics_state.send_texture_error(e);

graphics_state
.texture_loader
.get("placeholder_tile_texture")
.unwrap_or_else(|| {
let placeholder_img = graphics_state.placeholder_img();

graphics_state.texture_loader.register_texture(
"placeholder_tile_texture",
graphics_state.render_state.device.create_texture_with_data(
&graphics_state.render_state.queue,
&wgpu::TextureDescriptor {
label: Some("placeholder_tile_texture"),
size: wgpu::Extent3d {
width: 32,
height: 32,
depth_or_array_layers: 1,
},
dimension: wgpu::TextureDimension::D2,
mip_level_count: 1,
sample_count: 1,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
},
wgpu::util::TextureDataOrder::LayerMajor,
&itertools::iproduct!(0..32, 0..32, 0..4)
.map(|(y, x, c)| {
// Tile the placeholder image
placeholder_img.as_bytes()[(c
+ (x % placeholder_img.width()) * 4
+ (y % placeholder_img.height())
* 4
* placeholder_img.width())
as usize]
})
.collect_vec(),
),
)
})
});

Some(Plane::new(
graphics_state,
viewport.clone(),
graphics_state.texture_loader.load_now_dir(
filesystem,
"Graphics/Fogs",
fog_name,
)?,
texture,
tileset.fog_hue,
tileset.fog_zoom,
tileset.fog_blend_type,
Expand Down
Loading

0 comments on commit fb3c0ff

Please sign in to comment.