summaryrefslogtreecommitdiff
path: root/alligator_resources
diff options
context:
space:
mode:
authorMicha White <botahamec@outlook.com>2022-10-25 20:49:31 -0400
committerMicha White <botahamec@outlook.com>2022-10-25 20:49:31 -0400
commitf04763063848df9d8e0d0f1177bc7e2cef50e19e (patch)
tree8d707918d0658882acb25303c11b17e2ae918b36 /alligator_resources
parent323c6ca03bc1d560b02114445764dab5e753049e (diff)
Added the texture packer
Diffstat (limited to 'alligator_resources')
-rw-r--r--alligator_resources/Cargo.toml3
-rw-r--r--alligator_resources/src/lib.rs1
-rw-r--r--alligator_resources/src/texture.rs130
3 files changed, 122 insertions, 12 deletions
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<image::Rgba<u16>, 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<DecodingError>;
-pub struct TextureManager {
- textures: HashMap<TextureId, Rgba16Texture>,
-}
-
#[allow(clippy::missing_const_for_fn)]
fn convert_image_decoding(e: image::ImageError) -> Expect<DecodingError> {
if let image::ImageError::Decoding(de) = e {
@@ -55,14 +53,67 @@ fn convert_image_decoding(e: image::ImageError) -> Expect<DecodingError> {
}
}
+type Rgba16Texture = image::ImageBuffer<image::Rgba<u16>, Box<[u16]>>;
+
+pub struct TextureManager {
+ textures: HashMap<TextureId, Rgba16Texture>,
+ 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<u16>]> = 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<TextureId, LoadError> {
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<TextureId>> {
+ 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<f32> {
+ 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<f32> {
+ 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
+ }
}