From f04763063848df9d8e0d0f1177bc7e2cef50e19e Mon Sep 17 00:00:00 2001 From: Micha White Date: Tue, 25 Oct 2022 20:49:31 -0400 Subject: Added the texture packer --- alligator_resources/Cargo.toml | 3 + alligator_resources/src/lib.rs | 1 + alligator_resources/src/texture.rs | 130 +++++++++++++++++++++++++++++++++---- 3 files changed, 122 insertions(+), 12 deletions(-) (limited to 'alligator_resources') diff --git a/alligator_resources/Cargo.toml b/alligator_resources/Cargo.toml index 7d09857..74a696b 100644 --- a/alligator_resources/Cargo.toml +++ b/alligator_resources/Cargo.toml @@ -9,3 +9,6 @@ edition = "2021" image = "0.24" thiserror = "1" exun = "0.1" +texture_packer = { git = "https://github.com/botahamec/piston_texture_packer", branch = "u16" } +profiling = "1" +bytemuck = { version = "1", features = ["extern_crate_alloc"] } diff --git a/alligator_resources/src/lib.rs b/alligator_resources/src/lib.rs index 5ea8ad1..b85eab1 100644 --- a/alligator_resources/src/lib.rs +++ b/alligator_resources/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(new_uninit)] #![warn(clippy::nursery)] #![warn(clippy::pedantic)] #![allow(clippy::module_name_repetitions)] diff --git a/alligator_resources/src/texture.rs b/alligator_resources/src/texture.rs index ba0ff47..79e0e94 100644 --- a/alligator_resources/src/texture.rs +++ b/alligator_resources/src/texture.rs @@ -1,13 +1,15 @@ use std::collections::HashMap; +use std::mem::MaybeUninit; use std::sync::atomic::{AtomicUsize, Ordering}; use exun::{Expect, Expected, Unexpected}; +use image::{GenericImage, ImageBuffer}; +use texture_packer::exporter::ImageExporter; +use texture_packer::{Frame, TexturePacker, TexturePackerConfig}; use thiserror::Error; static NEXT_TEXTURE_ID: AtomicUsize = AtomicUsize::new(0); -type Rgba16Texture = image::ImageBuffer, Box<[u16]>>; - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct TextureId(usize); @@ -42,10 +44,6 @@ pub struct DecodingError(#[from] image::error::DecodingError); type LoadError = Expect; -pub struct TextureManager { - textures: HashMap, -} - #[allow(clippy::missing_const_for_fn)] fn convert_image_decoding(e: image::ImageError) -> Expect { if let image::ImageError::Decoding(de) = e { @@ -55,14 +53,67 @@ fn convert_image_decoding(e: image::ImageError) -> Expect { } } +type Rgba16Texture = image::ImageBuffer, Box<[u16]>>; + +pub struct TextureManager { + textures: HashMap, + packer: TexturePacker<'static, Rgba16Texture, TextureId>, + atlas: Rgba16Texture, // cached texture atlas + width: u32, + height: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TextureConfig { + pub capacity: usize, + pub atlas_width: u32, + pub atlas_height: u32, +} + +fn packer(width: u32, height: u32) -> TexturePacker<'static, Rgba16Texture, TextureId> { + TexturePacker::new_skyline(TexturePackerConfig { + max_width: width, + max_height: height, + allow_rotation: false, + trim: false, + texture_padding: 0, + ..Default::default() + }) +} + impl TextureManager { + /// Create a new `TextureManager` with the given config options. #[must_use] - pub fn new() -> Self { + pub fn new(config: TextureConfig) -> Self { + let width = config.atlas_width; + let height = config.atlas_height; + let textures = HashMap::with_capacity(config.capacity); + let packer = packer(width, height); + + let atlas: Box<[MaybeUninit]> = Box::new_zeroed_slice((4 * width * height) as _); + let atlas = unsafe { atlas.assume_init() }; + let atlas = + Rgba16Texture::from_raw(width, height, atlas).expect("atlas cache is too small"); + Self { - textures: HashMap::new(), + textures, + packer, + atlas, + width, + height, } } + /// Resize the texture atlas + pub fn resize_atlas(&mut self, width: u32, height: u32) { + self.packer = packer(width, height); + } + + /// Clear the texture atlas + pub fn clear_atlas(&mut self) { + self.packer = packer(self.width, self.height); + } + /// Loads a texture from memory in the given format. /// /// # Errors @@ -76,17 +127,72 @@ impl TextureManager { format: ImageFormat, ) -> Result { let id = TextureId::new(); - let texture = image::load_from_memory_with_format(buf, format.into()) - .map_err(convert_image_decoding)?; - + let texture = image::load_from_memory_with_format(buf, format.into()); + let texture = texture.map_err(convert_image_decoding)?; let texture = texture.into_rgb16(); let width = texture.width(); let height = texture.height(); let buf = texture.into_raw().into_boxed_slice(); - let texture = image::ImageBuffer::from_raw(width, height, buf).unwrap(); + let texture = ImageBuffer::from_raw(width, height, buf).expect("image buffer is too small"); self.textures.insert(id, texture); Ok(id) } + + pub fn atlas(&mut self) -> &Rgba16Texture { + let atlas = { + profiling::scope!("export atlas"); + // TODO unexpect_msg? + ImageExporter::export(&self.packer).expect("ImageExporter error?") + }; + + profiling::scope!("copy image"); + self.atlas + .copy_from(&atlas.into_rgba16(), 0, 0) + .expect("image cache was too small"); + + &self.atlas + } + + fn texture(&self, id: TextureId) -> Option<&Rgba16Texture> { + self.textures.get(&id) + } + + /// Get the subtexture + fn subtexture(&self, id: TextureId) -> Option<&Frame> { + self.packer.get_frame(&id) + } + + /// Get the x-position of a texture, if it is in the texture atlas + #[must_use] + #[allow(clippy::cast_precision_loss)] // TODO remove this + pub fn subtexture_x(&self, id: TextureId) -> Option { + let x = self.subtexture(id)?.frame.x; + Some(x as f32 / self.width as f32) + } + + /// Get the y-position of a texture, if it is in the texture atlas + #[must_use] + #[allow(clippy::cast_precision_loss)] + pub fn subtexture_y(&self, id: TextureId) -> Option { + let y = self.subtexture(id)?.frame.y; + Some(y as f32 / self.height as f32) + } + + /// Get the width of a texture + #[must_use] + #[allow(clippy::cast_precision_loss)] + pub fn texture_width(&self, id: TextureId) -> f32 { + let width = self.texture(id).expect("invalid TextureId").width(); + width as f32 / self.width as f32 + } + + /// Get the height of a texture + #[must_use] + #[allow(clippy::cast_precision_loss)] + pub fn texture_height(&self, id: TextureId) -> f32 { + let height = self.texture(id).expect("invalid TextureId").height(); + height as f32 / self.height as f32 + } } -- cgit v1.2.3