High-res textures built on first use: no cost in the pixel-art style, the 128x128 atlas only when zoomed in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-08 00:01:43 +01:00
1 parent fb71009110
commit 6ff1cc5f8d
2 files changed
+91 -20

No files matched your search

+83 -17
View File
@@ -101,11 +101,11 @@ pub const T_EGG: u32 = 222; // 2 variants: a painted egg (Easter)
pub struct Gfx { pub struct Gfx {
pub atlas: Texture2D, pub atlas: Texture2D,
pub sprites: Texture2D, pub sprites: Texture2D,
pub hd_sprites: Texture2D, // detailed sprites, 64x64 level (see HD_SPRITES, hd_picture) pub hd_sprites: LazyTex, // detailed sprites, 64x64 level (see HD_SPRITES, hd_picture)
pub hd_sprites32: Texture2D, // the same, 32x32 level pub hd_sprites32: LazyTex, // the same, 32x32 level
pub gear: Texture2D, // their gear parts (see GEAR_HELMET etc.) pub gear: LazyTex, // their gear parts (see GEAR_HELMET etc.)
pub atlas_hd: Texture2D, // the tile atlas of the high-res style, 64x64 tiles (see HD_TILES) pub atlas_hd: LazyTex, // the tile atlas of the high-res style, 64x64 tiles (see HD_TILES)
pub atlas_hd2: Texture2D, // the same with 128x128 tiles (close up) pub atlas_hd2: LazyTex, // the same with 128x128 tiles (close up)
pub materials: [Texture2D; 9], // furniture materials, high-res (see crate::props::Mat) pub materials: [Texture2D; 9], // furniture materials, high-res (see crate::props::Mat)
pub materials_px: [Texture2D; 9], // the same for the pixel-art style (16x16) pub materials_px: [Texture2D; 9], // the same for the pixel-art style (16x16)
pub cursors: Texture2D, // mouse pointer frames, 16x16 each (see CURSOR_*) pub cursors: Texture2D, // mouse pointer frames, 16x16 each (see CURSOR_*)
@@ -347,6 +347,32 @@ pub fn high_res_ui() -> bool {
HIGH_RES_UI.with(|h| h.get()) HIGH_RES_UI.with(|h| h.get())
} }
// a texture with nearest filtering
fn tex(img: &Image) -> Texture2D {
let t = Texture2D::from_image(img);
t.set_filter(FilterMode::Nearest);
t
}
// a texture built on first use (it derefs to the texture)
pub struct LazyTex {
cell: std::cell::OnceCell<Texture2D>,
make: Box<dyn Fn() -> Texture2D>,
}
impl LazyTex {
fn new(make: impl Fn() -> Texture2D + 'static) -> LazyTex {
LazyTex { cell: std::cell::OnceCell::new(), make: Box::new(make) }
}
}
impl std::ops::Deref for LazyTex {
type Target = Texture2D;
fn deref(&self) -> &Texture2D {
self.cell.get_or_init(|| (self.make)())
}
}
// ---- furniture materials (crate::props::Mat order): grey detail the furniture's colours tint // ---- furniture materials (crate::props::Mat order): grey detail the furniture's colours tint
// (the page keeps its colours), 64x64 and 16x16; see art/generated/textures/materials/ // (the page keeps its colours), 64x64 and 16x16; see art/generated/textures/materials/
macro_rules! material { macro_rules! material {
@@ -2939,17 +2965,30 @@ impl Gfx {
cursors.set_filter(FilterMode::Nearest); cursors.set_filter(FilterMode::Nearest);
let badges = Texture2D::from_image(&crate::badges::gen_badges()); let badges = Texture2D::from_image(&crate::badges::gen_badges());
badges.set_filter(FilterMode::Nearest); badges.set_filter(FilterMode::Nearest);
let atlas_hd = Texture2D::from_image(&gen_hd_atlas(HD_TILE)); // the high-res textures are built when first used (the pixel-art style never needs them)
atlas_hd.set_filter(FilterMode::Nearest); let atlas_hd = LazyTex::new(|| tex(&gen_hd_atlas(HD_TILE)));
let atlas_hd2 = Texture2D::from_image(&gen_hd_atlas(HD_TILE2)); let atlas_hd2 = LazyTex::new(|| tex(&gen_hd_atlas(HD_TILE2)));
atlas_hd2.set_filter(FilterMode::Nearest); // both levels of the detailed sprites come from one pass (which also measures the gear anchors)
let (hd64, hd32) = gen_hd_sprites(); let both: std::rc::Rc<std::cell::OnceCell<(Texture2D, Texture2D)>> = Default::default();
let hd_sprites = Texture2D::from_image(&hd64); let both2 = both.clone();
hd_sprites.set_filter(FilterMode::Nearest); let make = move |b: &std::cell::OnceCell<(Texture2D, Texture2D)>| {
let hd_sprites32 = Texture2D::from_image(&hd32); b.get_or_init(|| {
hd_sprites32.set_filter(FilterMode::Nearest); let (a, c) = gen_hd_sprites();
let gear = Texture2D::from_image(&gen_gear()); (tex(&a), tex(&c))
gear.set_filter(FilterMode::Nearest); })
.clone()
};
let hd_sprites = LazyTex::new(move || make(&both).0);
let hd_sprites32 = LazyTex::new(move || {
both2
.get_or_init(|| {
let (a, c) = gen_hd_sprites();
(tex(&a), tex(&c))
})
.1
.clone()
});
let gear = LazyTex::new(|| tex(&gen_gear()));
let mat = |b: &[u8]| { let mat = |b: &[u8]| {
let t = Texture2D::from_file_with_format(b, Some(ImageFormat::Png)); let t = Texture2D::from_file_with_format(b, Some(ImageFormat::Png));
t.set_filter(FilterMode::Nearest); t.set_filter(FilterMode::Nearest);
@@ -3097,7 +3136,7 @@ impl Gfx {
// an atlas cell in the interface (tool and room buttons); the high-res style shows the // an atlas cell in the interface (tool and room buttons); the high-res style shows the
// high-res atlas, so the room buttons show the rooms' new floors // high-res atlas, so the room buttons show the rooms' new floors
pub fn draw_cell(&self, id: u32, x: f32, y: f32, w: f32, h: f32, color: Color) { pub fn draw_cell(&self, id: u32, x: f32, y: f32, w: f32, h: f32, color: Color) {
let (tex, k) = if HIGH_RES_UI.with(|h| h.get()) { (&self.atlas_hd, (HD_TILE / 16) as f32) } else { (&self.atlas, 1.0) }; let (tex, k): (&Texture2D, f32) = if HIGH_RES_UI.with(|h| h.get()) { (&self.atlas_hd, (HD_TILE / 16) as f32) } else { (&self.atlas, 1.0) };
let r = Self::cell(id); let r = Self::cell(id);
draw_texture_ex( draw_texture_ex(
tex, tex,
@@ -3357,6 +3396,33 @@ mod hd_atlas_tests {
} }
} }
#[cfg(test)]
mod startup_timing {
use super::*;
// `cargo test --release --bins startup_timing -- --ignored --nocapture`: how long building
// the textures takes at the start
#[test]
#[ignore]
fn startup_timing() {
let t = std::time::Instant::now();
let _ = gen_atlas();
println!("atlas 16: {:?}", t.elapsed());
let t = std::time::Instant::now();
let _ = gen_sprites();
println!("sprites: {:?}", t.elapsed());
let t = std::time::Instant::now();
let _ = gen_hd_atlas(HD_TILE);
println!("hd atlas 64: {:?}", t.elapsed());
let t = std::time::Instant::now();
let _ = gen_hd_atlas(HD_TILE2);
println!("hd atlas 128: {:?}", t.elapsed());
let t = std::time::Instant::now();
let _ = gen_hd_sprites();
println!("hd sprites: {:?}", t.elapsed());
}
}
#[cfg(test)] #[cfg(test)]
mod atlas_dump { mod atlas_dump {
use super::*; use super::*;
+8 -3
View File
@@ -1406,7 +1406,7 @@ impl R3d {
lava_fresh = true; lava_fresh = true;
} }
let ch = &mut self.chunks[(cz * self.ncx + cx) as usize]; let ch = &mut self.chunks[(cz * self.ncx + cx) as usize];
let atlas = if self.chunks_hd { &gfx.atlas_hd } else { &gfx.atlas }; let atlas: &Texture2D = if self.chunks_hd { &gfx.atlas_hd } else { &gfx.atlas };
let [m, o, l, k] = build_chunk(&g.map, a, &self.lava, cx, cz, atlas, self.wall_sub, self.chunks_hd); let [m, o, l, k] = build_chunk(&g.map, a, &self.lava, cx, cz, atlas, self.wall_sub, self.chunks_hd);
ch.mesh = m; ch.mesh = m;
ch.overlay = o; ch.overlay = o;
@@ -1652,8 +1652,8 @@ impl R3d {
}; };
let atlas = match tile { let atlas = match tile {
16 => &gfx.atlas, 16 => &gfx.atlas,
crate::gfx::HD_TILE => &gfx.atlas_hd, crate::gfx::HD_TILE => &*gfx.atlas_hd,
_ => &gfx.atlas_hd2, _ => &*gfx.atlas_hd2,
}; };
for ch in self.chunks.iter_mut() { for ch in self.chunks.iter_mut() {
for mesh in [&mut ch.mesh, &mut ch.overlay].into_iter().flatten() { for mesh in [&mut ch.mesh, &mut ch.overlay].into_iter().flatten() {
@@ -1808,6 +1808,11 @@ impl R3d {
// high) gives every texel nearly a pixel; fewer pixels would only turn it into noise // high) gives every texel nearly a pixel; fewer pixels would only turn it into noise
let hd = self.pixel_rows >= crate::settings::HIGH_RES_ROWS as u32; let hd = self.pixel_rows >= crate::settings::HIGH_RES_ROWS as u32;
let px_per_tile = world_rows / cam.view_h().max(0.1); let px_per_tile = world_rows / cam.view_h().max(0.1);
if hd {
// the detailed sprites are built on first use; that also measures the gear anchors,
// which the gear below needs
let _: &Texture2D = &gfx.hd_sprites;
}
for e in &g.ents { for e in &g.ents {
if !e.alive || e.held || unit_hidden(g, e) { if !e.alive || e.held || unit_hidden(g, e) {
continue; continue;