From 4b789a715cf7b42f5ae282b8218976fd577664be Mon Sep 17 00:00:00 2001 From: Micha White Date: Sun, 5 Feb 2023 10:28:43 -0500 Subject: Move texture atlas out of alligator_resources --- alligator_render/Cargo.toml | 2 +- alligator_render/examples/black.rs | 6 +- alligator_render/examples/bmp.rs | 47 ++++++------ alligator_render/examples/bunnymark.rs | 31 ++++---- alligator_render/src/lib.rs | 2 - alligator_render/src/renderer.rs | 4 +- alligator_render/src/texture.rs | 85 ++++++--------------- alligator_resources/src/texture.rs | 136 ++++----------------------------- 8 files changed, 82 insertions(+), 231 deletions(-) diff --git a/alligator_render/Cargo.toml b/alligator_render/Cargo.toml index a613dcd..8917a2f 100644 --- a/alligator_render/Cargo.toml +++ b/alligator_render/Cargo.toml @@ -19,7 +19,7 @@ cgmath = "0.18" pollster = "0.2" log = "0.4" parking_lot = "0.12" -texture_packer = "0.25" +texture_packer = { git="https://github.com/botahamec/piston_texture_packer", branch="u16" } tracy-client = { version = "0.15", optional = true } dhat = { version = "0.3", optional = true } diff --git a/alligator_render/examples/black.rs b/alligator_render/examples/black.rs index 198eef2..655cbde 100644 --- a/alligator_render/examples/black.rs +++ b/alligator_render/examples/black.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use std::sync::Arc; + use alligator_render::{RenderWindowConfig, Renderer}; use alligator_resources::texture::{TextureManager, TextureManagerConfig}; @@ -19,11 +21,9 @@ fn main() { let texture_config = TextureManagerConfig { initial_capacity: 0, max_size: 0, - atlas_width: 1, - atlas_height: 1, }; - let texture_manager = TextureManager::new(&texture_config); + let texture_manager = Arc::new(TextureManager::new(&texture_config)); let renderer = Renderer::new(&render_config, texture_manager).unwrap(); println!("Startup time: {:?}", start.elapsed()); diff --git a/alligator_render/examples/bmp.rs b/alligator_render/examples/bmp.rs index 0bad037..9d864d0 100644 --- a/alligator_render/examples/bmp.rs +++ b/alligator_render/examples/bmp.rs @@ -1,9 +1,9 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use std::num::NonZeroU32; +use std::{num::NonZeroU32, sync::Arc}; -use alligator_render::{ImageFormat, Instance, RenderWindowConfig, Renderer}; -use alligator_resources::texture::{TextureManager, TextureManagerConfig}; +use alligator_render::{Instance, RenderWindowConfig, Renderer}; +use alligator_resources::texture::{ImageFormat, TextureManager, TextureManagerConfig}; #[profiling::function] fn update(renderer: &mut Renderer) { @@ -26,23 +26,21 @@ fn main() { let texture_config = TextureManagerConfig { initial_capacity: 3, max_size: 3_000_000, - atlas_height: 150, - atlas_width: 150, }; - let texture_manager = TextureManager::new(&texture_config); - let mut renderer = Renderer::new(&render_config, texture_manager).unwrap(); + let texture_manager = Arc::new(TextureManager::new(&texture_config)); + let mut renderer = Renderer::new(&render_config, texture_manager.clone()).unwrap(); // render the alligator let gator = include_bytes!("res/gator.ff"); - let gator_id = renderer - .textures_mut() + let gator_id = texture_manager .load_from_memory(gator, ImageFormat::Farbfeld) .unwrap(); - let gator_width = renderer.textures().texture_width(gator_id).unwrap(); - let gator_height = renderer.textures().texture_height(gator_id).unwrap(); - let gator_x = renderer.textures().texture_x(gator_id).unwrap(); - let gator_y = renderer.textures().texture_y(gator_id).unwrap(); + renderer.textures_mut().load_texture(gator_id).unwrap(); + let gator_width = renderer.textures_mut().texture_width(gator_id).unwrap(); + let gator_height = renderer.textures_mut().texture_height(gator_id).unwrap(); + let gator_x = renderer.textures_mut().texture_x(gator_id).unwrap(); + let gator_y = renderer.textures_mut().texture_y(gator_id).unwrap(); renderer.instances_mut().push_instance(Instance { position: [-0.5, 0.5], @@ -55,14 +53,14 @@ fn main() { // render the ghost let icon = include_bytes!("res/ghost.ico"); - let icon_id = renderer - .textures_mut() + let icon_id = texture_manager .load_from_memory(icon, ImageFormat::Ico) .unwrap(); - let icon_width = renderer.textures().texture_width(icon_id).unwrap(); - let icon_height = renderer.textures().texture_height(icon_id).unwrap(); - let icon_x = renderer.textures().texture_x(icon_id).unwrap(); - let icon_y = renderer.textures().texture_y(icon_id).unwrap(); + renderer.textures_mut().load_texture(icon_id).unwrap(); + let icon_width = renderer.textures_mut().texture_width(icon_id).unwrap(); + let icon_height = renderer.textures_mut().texture_height(icon_id).unwrap(); + let icon_x = renderer.textures_mut().texture_x(icon_id).unwrap(); + let icon_y = renderer.textures_mut().texture_y(icon_id).unwrap(); renderer.instances_mut().push_instance(Instance { position: [0.5, 0.5], @@ -76,14 +74,13 @@ fn main() { // render the bitmap alligator let gator = include_bytes!("res/gator.bmp"); - let gator_id = renderer - .textures_mut() + let gator_id = texture_manager .load_from_memory(gator, ImageFormat::Bmp) .unwrap(); - let gator_width = renderer.textures().texture_width(gator_id).unwrap(); - let gator_height = renderer.textures().texture_height(gator_id).unwrap(); - let gator_x = renderer.textures().texture_x(gator_id).unwrap(); - let gator_y = renderer.textures().texture_y(gator_id).unwrap(); + let gator_width = renderer.textures_mut().texture_width(gator_id).unwrap(); + let gator_height = renderer.textures_mut().texture_height(gator_id).unwrap(); + let gator_x = renderer.textures_mut().texture_x(gator_id).unwrap(); + let gator_y = renderer.textures_mut().texture_y(gator_id).unwrap(); renderer.instances_mut().push_instance(Instance { position: [0.0, -0.5], diff --git a/alligator_render/examples/bunnymark.rs b/alligator_render/examples/bunnymark.rs index ab38b5e..f9aed1a 100644 --- a/alligator_render/examples/bunnymark.rs +++ b/alligator_render/examples/bunnymark.rs @@ -1,9 +1,7 @@ -use std::{num::NonZeroU32, time::Instant}; +use std::{num::NonZeroU32, sync::Arc, time::Instant}; -use alligator_render::{ - ImageFormat, Instance, InstanceId, RenderWindowConfig, Renderer, TextureId, -}; -use alligator_resources::texture::{TextureManager, TextureManagerConfig}; +use alligator_render::{Instance, InstanceId, RenderWindowConfig, Renderer}; +use alligator_resources::texture::{ImageFormat, TextureId, TextureManager, TextureManagerConfig}; fn xorshift_plus(seed: &mut [u64; 2]) -> u64 { let mut t = seed[0]; @@ -88,10 +86,16 @@ impl State { } for _ in 0..=(fps as u64 * 50) { - let texture_x = renderer.textures().texture_x(self.texture_id).unwrap(); - let texture_y = renderer.textures().texture_x(self.texture_id).unwrap(); - let texture_height = renderer.textures().texture_height(self.texture_id).unwrap(); - let texture_width = renderer.textures().texture_width(self.texture_id).unwrap(); + let texture_x = renderer.textures_mut().texture_x(self.texture_id).unwrap(); + let texture_y = renderer.textures_mut().texture_x(self.texture_id).unwrap(); + let texture_height = renderer + .textures_mut() + .texture_height(self.texture_id) + .unwrap(); + let texture_width = renderer + .textures_mut() + .texture_width(self.texture_id) + .unwrap(); let instance_id = renderer.instances_mut().push_instance(Instance { texture_coordinates: [texture_x, texture_y], texture_size: [texture_width, texture_height], @@ -137,15 +141,12 @@ fn main() { let texture_config = TextureManagerConfig { initial_capacity: 1, max_size: 10_000, - atlas_width: 100, - atlas_height: 100, }; let bunny = include_bytes!("res/bunny.ff"); - let texture_manager = TextureManager::new(&texture_config); - let mut renderer = Renderer::new(&render_config, texture_manager).unwrap(); - let texture_id = renderer - .textures_mut() + let texture_manager = Arc::new(TextureManager::new(&texture_config)); + let renderer = Renderer::new(&render_config, texture_manager.clone()).unwrap(); + let texture_id = texture_manager .load_from_memory(bunny, ImageFormat::Farbfeld) .unwrap(); diff --git a/alligator_render/src/lib.rs b/alligator_render/src/lib.rs index 5e0aab6..0d76cc8 100644 --- a/alligator_render/src/lib.rs +++ b/alligator_render/src/lib.rs @@ -17,7 +17,5 @@ pub use instance::Instance; pub(crate) use instance::InstanceBuffer; pub use instance::InstanceId; pub use renderer::Renderer; -pub use texture::ImageFormat; pub(crate) use texture::TextureAtlas; -pub use texture::TextureId; pub(crate) use vertex::Vertex; diff --git a/alligator_render/src/renderer.rs b/alligator_render/src/renderer.rs index f448119..fdd8c34 100644 --- a/alligator_render/src/renderer.rs +++ b/alligator_render/src/renderer.rs @@ -1,5 +1,5 @@ -use std::convert::TryInto; use std::num::NonZeroU32; +use std::{convert::TryInto, sync::Arc}; use crate::{ vertex::SQUARE, Camera, Instance, InstanceBuffer, RenderWindowConfig, TextureAtlas, Vertex, @@ -150,7 +150,7 @@ impl Renderer { // TODO this function needs to be smaller pub fn new( config: &RenderWindowConfig, - textures: TextureManager, + textures: Arc, ) -> Result { // build the window let event_loop = EventLoop::new(); diff --git a/alligator_render/src/texture.rs b/alligator_render/src/texture.rs index 2a86501..0afa0af 100644 --- a/alligator_render/src/texture.rs +++ b/alligator_render/src/texture.rs @@ -1,10 +1,9 @@ use std::error::Error; use std::num::NonZeroU32; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; -use alligator_resources::texture::TextureManager; -use image::error::DecodingError; -use image::{EncodableLayout, GenericImage, ImageError, RgbaImage}; +use alligator_resources::texture::{LoadError, Rgba16Texture, TextureId, TextureManager}; +use image::{EncodableLayout, GenericImage, RgbaImage}; use texture_packer::TexturePacker; use texture_packer::{ exporter::{ExportResult, ImageExporter}, @@ -12,39 +11,6 @@ use texture_packer::{ }; use thiserror::Error; -static NEXT_TEXTURE_ID: AtomicUsize = AtomicUsize::new(0); - -/// The unique ID for a subtexture -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct TextureId(usize); - -impl TextureId { - #[allow(clippy::new_without_default)] - #[must_use] - pub fn new() -> Self { - Self(NEXT_TEXTURE_ID.fetch_add(1, Ordering::Relaxed)) - } -} - -/// These are the formats supported by the renderer. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub enum ImageFormat { - Bmp, - Ico, - Farbfeld, -} - -impl ImageFormat { - const fn format(self) -> image::ImageFormat { - match self { - Self::Bmp => image::ImageFormat::Bmp, - Self::Ico => image::ImageFormat::Ico, - Self::Farbfeld => image::ImageFormat::Farbfeld, - } - } -} - /// The texture did not fit in the texture atlas #[derive(Debug, Error)] #[error("{:?}", .0)] @@ -58,20 +24,11 @@ pub enum TextureError { #[error("{:?}", .0)] TextureTooLarge(#[from] PackError), #[error("{}", .0)] - BadImage(#[from] DecodingError), // TODO don't export this + BadImage(#[from] LoadError), #[error("Unexpected Error (this is a bug in alligator_render): {}", .0)] Unexpected(#[source] Box), } -impl From for TextureError { - fn from(ie: ImageError) -> Self { - match ie { - ImageError::Decoding(de) => de.into(), - _ => Self::Unexpected(Box::new(ie)), - } - } -} - /// Simpler constructor for a wgpu extent3d const fn extent_3d(width: u32, height: u32) -> wgpu::Extent3d { wgpu::Extent3d { @@ -85,8 +42,8 @@ const fn extent_3d(width: u32, height: u32) -> wgpu::Extent3d { // TODO make this Debug // TODO make these resizable pub struct TextureAtlas { - textures: TextureManager, - packer: TexturePacker<'static, image::RgbaImage, TextureId>, + textures: Arc, + packer: TexturePacker<'static, Rgba16Texture, TextureId>, diffuse_texture: wgpu::Texture, diffuse_bind_group: wgpu::BindGroup, image: RgbaImage, @@ -97,11 +54,17 @@ pub struct TextureAtlas { macro_rules! texture_info { ($name: ident, $prop: ident, $divisor: ident) => { - pub fn $name(&self, id: TextureId) -> Option { - let frame = self.texture_frame(id)?; + pub fn $name(&mut self, id: TextureId) -> Result { + let frame = match self.texture_frame(id) { + Some(frame) => frame, + None => { + self.load_texture(id)?; + self.texture_frame(id).unwrap() + } + }; let property = frame.frame.$prop; let value = property as f32 / self.$divisor as f32; - Some(value) + Ok(value) } }; } @@ -112,7 +75,7 @@ impl TextureAtlas { // TODO this is still too large pub fn new( device: &wgpu::Device, - textures: TextureManager, + textures: Arc, width: u32, height: u32, ) -> (Self, wgpu::BindGroupLayout) { @@ -206,13 +169,8 @@ impl TextureAtlas { /// Load a new subtexture from memory // TODO support RGBA16 - pub fn load_from_memory( - &mut self, - buf: &[u8], - format: ImageFormat, - ) -> Result { - let img = image::load_from_memory_with_format(buf, format.format())?.into_rgba8(); - let id = TextureId::new(); + pub fn load_texture(&mut self, id: TextureId) -> Result { + let img = self.textures.load_texture(id)?; self.packer.pack_own(id, img).map_err(PackError)?; Ok(id) } @@ -240,8 +198,11 @@ impl TextureAtlas { Ok(()) } - /// Clear the texture atlas - pub fn clear(&mut self) { + /// Clear the texture atlas, and give it a new size + pub fn clear(&mut self, width: u32, height: u32) { + self.changed = true; + self.width = width; + self.height = height; self.packer = TexturePacker::new_skyline(TexturePackerConfig { max_width: self.width, max_height: self.height, diff --git a/alligator_resources/src/texture.rs b/alligator_resources/src/texture.rs index e5b4147..3a5bf3e 100644 --- a/alligator_resources/src/texture.rs +++ b/alligator_resources/src/texture.rs @@ -1,6 +1,5 @@ use std::cmp::Reverse; -use std::collections::HashMap; -use std::mem::{self}; +use std::mem; use std::path::Path; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; @@ -8,8 +7,6 @@ use std::sync::Arc; use dashmap::DashMap; use image::ImageBuffer; use parking_lot::Mutex; -use texture_packer::exporter::ImageExporter; -use texture_packer::{Frame, TexturePacker, TexturePackerConfig}; use thiserror::Error; use crate::Priority; @@ -76,7 +73,7 @@ fn convert_image_load_error(e: image::ImageError) -> LoadError { } } -type Rgba16Texture = image::ImageBuffer, Box<[u16]>>; +pub type Rgba16Texture = image::ImageBuffer, Box<[u16]>>; fn vec_image_to_box(vec_image: image::ImageBuffer, Vec>) -> Rgba16Texture { let width = vec_image.width(); @@ -238,116 +235,18 @@ impl Texture { } } -pub struct TextureAtlas<'a> { - width: u32, - height: u32, - packer: TexturePacker<'a, Rgba16Texture, TextureId>, -} - -impl<'a> TextureAtlas<'a> { - fn new(width: u32, height: u32, textures: &HashMap) -> Self { - profiling::scope!("new atlas"); - - let mut packer = TexturePacker::new_skyline(TexturePackerConfig { - max_width: width, - max_height: height, - allow_rotation: false, - trim: false, - texture_padding: 0, - ..Default::default() - }); - - for (id, texture) in textures { - if texture.is_loaded() { - let texture = texture - .loaded_texture() - .expect("texture couldn't be loaded"); - - // if the textures don't fit, make a bigger packer - if packer.pack_own(*id, texture.clone()).is_err() { - return Self::new(width * 2, height * 2, textures); - } - } - } - - Self { - width, - height, - packer, - } - } - - fn subtexture(&self, id: TextureId) -> Option<&Frame> { - self.packer.get_frame(&id) - } - - #[must_use] - pub const fn atlas_width(&self) -> u32 { - self.width - } - - #[must_use] - pub const fn atlas_height(&self) -> u32 { - self.height - } - - /// 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)] // TODO remove this - 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, if it is in the texture atlas - #[must_use] - #[allow(clippy::cast_precision_loss)] // TODO remove this - pub fn subtexture_width(&self, id: TextureId) -> Option { - let width = self.subtexture(id)?.frame.w; - Some(width as f32 / self.width as f32) - } - - /// Get the height of a texture, if it is in the texture atlas - #[must_use] - #[allow(clippy::cast_precision_loss)] // TODO remove this - pub fn subtexture_height(&self, id: TextureId) -> Option { - let height = self.subtexture(id)?.frame.h; - Some(height as f32 / self.height as f32) - } - - #[must_use] - pub fn to_texture(&self) -> Rgba16Texture { - profiling::scope!("export atlas"); - vec_image_to_box( - ImageExporter::export(&self.packer) - .expect("ImageExporter error?") - .into_rgba16(), - ) - } -} - pub struct TextureManager { textures: DashMap, max_size: usize, - atlas_width: u32, - atlas_height: u32, needs_atlas_update: AtomicBool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TextureManagerConfig { + /// The initial capacity of the texture manager. This defaults to 500 textures. pub initial_capacity: usize, + /// The maximum amount of heap usage acceptable. Defaults to 10 MiB. pub max_size: usize, - pub atlas_width: u32, - pub atlas_height: u32, } impl Default for TextureManagerConfig { @@ -355,8 +254,6 @@ impl Default for TextureManagerConfig { Self { initial_capacity: 500, max_size: 10 * 1024 * 1024, // 10 MiB - atlas_width: 3980, - atlas_height: 2160, // 4K resolution } } } @@ -370,8 +267,6 @@ impl TextureManager { Self { textures, max_size: config.max_size, - atlas_width: config.atlas_width, - atlas_height: config.atlas_height, needs_atlas_update: AtomicBool::new(false), } } @@ -408,7 +303,7 @@ impl TextureManager { /// This returns `Expected(DecodingError)` if the given buffer was invalid /// for the given format. pub fn load_from_memory( - &mut self, + &self, buf: &[u8], format: ImageFormat, ) -> Result { @@ -432,7 +327,7 @@ impl TextureManager { /// This returns an error if `priority` is set to [`Priority::Urgent`] but /// there was an error in loading the file to a texture. pub fn load_from_file( - &mut self, + &self, path: impl AsRef, priority: Priority, ) -> Result { @@ -463,7 +358,7 @@ impl TextureManager { /// /// This returns an error if `priority` is set to [`Priority::Urgent`] but /// there was an error in loading the file to a texture. - pub fn set_priority(&mut self, id: TextureId, priority: Priority) -> Result<(), LoadError> { + pub fn set_priority(&self, id: TextureId, priority: Priority) -> Result<(), LoadError> { let mut texture = self.textures.get_mut(&id).expect("invalid texture id"); texture.set_priority(priority); @@ -478,10 +373,17 @@ impl TextureManager { /// This returns `true` if a texture has been set to have an urgent /// priority since the last time this function was called. - pub fn needs_atlas_update(&mut self) -> bool { + pub fn needs_atlas_update(&self) -> bool { self.needs_atlas_update.fetch_and(false, Ordering::AcqRel) } + /// Load a texture into memory, if it hasn't been already. Then return a + /// copy of the texture. + /// + /// # Errors + /// + /// This returns an error if an error occurs in loading the texture from + /// disk, such as the file not existing, or not being a valid texture. pub fn load_texture(&self, id: TextureId) -> Result { self.textures .get_mut(&id) @@ -489,12 +391,4 @@ impl TextureManager { .load_texture() .cloned() } - - // Create a texture atlas - /*pub fn atlas(&mut self) -> TextureAtlas<'_> { - let atlas = TextureAtlas::new(self.atlas_width, self.atlas_height, &self.textures); - self.atlas_width = atlas.atlas_width(); - self.atlas_height = atlas.atlas_height(); - atlas - }*/ } -- cgit v1.2.3