diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/renderer.rs | 128 | ||||
| -rw-r--r-- | src/texture.rs | 208 |
3 files changed, 211 insertions, 127 deletions
@@ -17,5 +17,5 @@ pub use config::RenderWindowConfig; pub use instance::Instance; pub use renderer::Renderer; pub use texture::ImageFormat; -pub(crate) use texture::TextureAtlases; +pub(crate) use texture::WgpuTextures; pub(crate) use vertex::Vertex; diff --git a/src/renderer.rs b/src/renderer.rs index 516b1b1..2c26836 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -5,9 +5,8 @@ use crate::{ instance::InstanceId, texture::{TextureError, TextureId}, vertex::SQUARE, - Camera, ImageFormat, Instance, RenderWindowConfig, TextureAtlases, Vertex, + Camera, ImageFormat, Instance, RenderWindowConfig, Vertex, WgpuTextures, }; -use image::{EncodableLayout, GenericImage}; use pollster::FutureExt; use thiserror::Error; use wgpu::{include_wgsl, util::DeviceExt}; @@ -60,11 +59,7 @@ pub struct Renderer { camera: Camera, camera_buffer: wgpu::Buffer, camera_bind_group: wgpu::BindGroup, - textures: TextureAtlases<'static>, - texture_size: wgpu::Extent3d, - diffuse_texture: wgpu::Texture, - diffuse_bind_group: wgpu::BindGroup, - image: image::RgbaImage, + textures: WgpuTextures, window: Window, } @@ -248,70 +243,16 @@ impl Renderer { Self::new_instance_buffer(&device, &instances); // TODO make this configurable - let textures = TextureAtlases::new(window.inner_size().width, window.inner_size().height); - let texture_size = textures.extent_3d(); // textures.extent_3d(); - let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("diffuse texture"), - size: texture_size, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - }); - // TODO I don't think this refreshes anything - let diffuse_texture_view = - diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default()); - // TODO make this configurable - let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); - let texture_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Texture Bind Group Layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - }); - let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("Diffuse Bind Group"), - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture_view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_sampler), - }, - ], - }); - // TODO make this all resizable - let image = image::RgbaImage::from_vec( - texture_size.width, - texture_size.height, - vec![0; 4 * texture_size.width as usize * texture_size.height as usize], - ) - .unwrap(); + let (textures, texture_layout) = WgpuTextures::new( + &device, + window.inner_size().width, + window.inner_size().height, + ); let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Sprite Render Pipeline Layout"), - bind_group_layouts: &[&camera_bind_group_layout, &texture_bind_group_layout], + bind_group_layouts: &[&camera_bind_group_layout, &texture_layout], push_constant_ranges: &[], }); @@ -335,10 +276,6 @@ impl Renderer { camera_buffer, camera_bind_group, textures, - texture_size, - diffuse_texture, - diffuse_bind_group, - image, window, }) } @@ -418,36 +355,28 @@ impl Renderer { /// /// This returns an error if the texture is not in the given format, or if /// the texture is so large that it cannot fit in the texture atlas. - pub fn texture_from_mem( + pub fn texture_from_memory( &mut self, texture: &[u8], format: ImageFormat, ) -> Result<TextureId, TextureError> { - self.textures.load_from_memory(texture, format) + self.textures.texture_from_memory(texture, format) } pub fn texture_width(&self, id: TextureId) -> Option<f32> { - self.textures - .texture_width(id) - .map(|u| u as f32 / self.texture_size.width as f32) + self.textures.texture_width(id) } pub fn texture_height(&self, id: TextureId) -> Option<f32> { - self.textures - .texture_height(id) - .map(|u| u as f32 / self.texture_size.height as f32) + self.textures.texture_height(id) } pub fn texture_x(&self, id: TextureId) -> Option<f32> { - self.textures - .texture_x(id) - .map(|u| u as f32 / self.texture_size.width as f32) + self.textures.texture_x(id) } pub fn texture_y(&self, id: TextureId) -> Option<f32> { - self.textures - .texture_y(id) - .map(|u| u as f32 / self.texture_size.height as f32) + self.textures.texture_y(id) } fn expand_instance_buffer(&mut self) { @@ -475,31 +404,6 @@ impl Renderer { ); } - // TODO optimize this - fn fill_textures(&mut self) { - // put the packed texture into the base image - let Ok(atlases) = self.textures.atlases() else { return }; - let Some(atlas) = atlases.first() else { return }; - self.image.copy_from(atlas, 0, 0).unwrap(); - - // copy that to the gpu - self.queue.write_texture( - wgpu::ImageCopyTexture { - texture: &self.diffuse_texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - self.image.as_bytes(), - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: NonZeroU32::new(self.texture_size.width * 4), - rows_per_image: NonZeroU32::new(self.texture_size.height), - }, - self.texture_size, - ); - } - /// Renders a new frame to the window /// /// # Errors @@ -530,7 +434,7 @@ impl Renderer { .expect("expected less than 3 billion instances"); self.refresh_camera_buffer(); - self.fill_textures(); + self.textures.fill_textures(&self.queue); { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { @@ -548,7 +452,7 @@ impl Renderer { render_pass.set_pipeline(&self.render_pipeline); render_pass.set_bind_group(0, &self.camera_bind_group, &[]); - render_pass.set_bind_group(1, &self.diffuse_bind_group, &[]); + render_pass.set_bind_group(1, self.textures.bind_group(), &[]); render_pass.set_vertex_buffer(0, self.square_vertex_buffer.slice(..)); render_pass.set_vertex_buffer(1, self.instance_buffer.slice(..)); render_pass.draw(0..self.square_vertices, 0..num_instances); diff --git a/src/texture.rs b/src/texture.rs index f5d1c34..4ba460d 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -1,8 +1,9 @@ use std::error::Error; +use std::num::NonZeroU32; use std::sync::atomic::{AtomicUsize, Ordering}; use image::error::DecodingError; -use image::{DynamicImage, ImageError}; +use image::{DynamicImage, EncodableLayout, GenericImage, ImageError, RgbaImage}; use texture_packer::{ exporter::{ExportResult, ImageExporter}, MultiTexturePacker, TexturePackerConfig, @@ -60,11 +61,12 @@ impl From<ImageError> for TextureError { } // TODO make this Debug +// TODO make these resizable pub struct TextureAtlases<'a> { packer: MultiTexturePacker<'a, image::RgbaImage, TextureId>, + images: Vec<RgbaImage>, width: u32, height: u32, - changed: bool, } impl<'a> Default for TextureAtlases<'a> { @@ -86,7 +88,7 @@ impl<'a> TextureAtlases<'a> { }), width, height, - changed: false, + images: Vec::with_capacity(1), } } @@ -101,7 +103,6 @@ impl<'a> TextureAtlases<'a> { self.packer .pack_own(id, img) .map_err(TextureError::TextureTooLarge)?; - self.changed = true; Ok(id) } @@ -134,15 +135,7 @@ impl<'a> TextureAtlases<'a> { Some(frame.frame.y) } - pub(crate) const fn bytes_per_row(&self) -> u32 { - self.width * 4 - } - - pub(crate) const fn height(&self) -> u32 { - self.height - } - - pub(crate) const fn extent_3d(&self) -> wgpu::Extent3d { + const fn extent_3d(&self) -> wgpu::Extent3d { wgpu::Extent3d { width: self.width, height: self.height, @@ -150,7 +143,7 @@ impl<'a> TextureAtlases<'a> { } } - pub(crate) fn atlases(&self) -> ExportResult<Box<[DynamicImage]>> { + fn atlases(&self) -> ExportResult<Box<[DynamicImage]>> { self.packer .get_pages() .iter() @@ -158,4 +151,191 @@ impl<'a> TextureAtlases<'a> { .collect::<ExportResult<Vec<DynamicImage>>>() .map(Vec::into_boxed_slice) } + + fn push_image(&mut self) -> &mut RgbaImage { + self.images.push( + RgbaImage::from_raw( + self.width, + self.height, + vec![0; 4 * self.width as usize * self.height as usize], + ) + .expect("the image was the wrong size"), + ); + + self.images + .last_mut() + .expect("we just added an image to the list") + } + + fn fill_images(&mut self) -> ExportResult<()> { + for (i, atlas) in self.atlases()?.iter().enumerate() { + #[allow(clippy::option_if_let_else)] + if let Some(image) = self.images.get_mut(i) { + image + .copy_from(atlas, 0, 0) + .expect("atlases shouldn't be too large"); + } else { + let image = self.push_image(); + image + .copy_from(atlas, 0, 0) + .expect("atlases shouldn't be too large"); + } + } + + Ok(()) + } + + fn images(&mut self) -> ExportResult<&[RgbaImage]> { + self.fill_images()?; + Ok(&self.images) + } +} + +pub struct WgpuTextures { + atlases: TextureAtlases<'static>, + diffuse_texture: wgpu::Texture, + diffuse_bind_group: wgpu::BindGroup, +} + +impl WgpuTextures { + // TODO this is still too large + pub fn new(device: &wgpu::Device, width: u32, height: u32) -> (Self, wgpu::BindGroupLayout) { + let atlases = TextureAtlases::new(width, height); + let atlas_size = atlases.extent_3d(); + + let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("Diffuse Texture"), + size: atlas_size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + }); + + // TODO I don't think this refreshes anything + let diffuse_texture_view = + diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); + + let texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Texture Bind Group Layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("Diffuse Bind Group"), + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_sampler), + }, + ], + }); + + ( + Self { + atlases, + diffuse_texture, + diffuse_bind_group, + }, + texture_bind_group_layout, + ) + } + + /// Loads a texture from memory, in the given file format + /// + /// # Errors + /// + /// This returns an error if the texture is not in the given format, or if + /// the texture is so large that it cannot fit in the texture atlas. + pub fn texture_from_memory( + &mut self, + texture: &[u8], + format: ImageFormat, + ) -> Result<TextureId, TextureError> { + self.atlases.load_from_memory(texture, format) + } + + // TODO try to remove this + #[allow(clippy::cast_precision_loss)] + pub fn texture_width(&self, id: TextureId) -> Option<f32> { + self.atlases + .texture_width(id) + .map(|u| u as f32 / self.atlases.extent_3d().width as f32) + } + + #[allow(clippy::cast_precision_loss)] + pub fn texture_height(&self, id: TextureId) -> Option<f32> { + self.atlases + .texture_height(id) + .map(|u| u as f32 / self.atlases.extent_3d().height as f32) + } + + #[allow(clippy::cast_precision_loss)] + pub fn texture_x(&self, id: TextureId) -> Option<f32> { + self.atlases + .texture_x(id) + .map(|u| u as f32 / self.atlases.extent_3d().width as f32) + } + + #[allow(clippy::cast_precision_loss)] + pub fn texture_y(&self, id: TextureId) -> Option<f32> { + self.atlases + .texture_y(id) + .map(|u| u as f32 / self.atlases.extent_3d().height as f32) + } + + pub const fn bind_group(&self) -> &wgpu::BindGroup { + &self.diffuse_bind_group + } + + pub fn fill_textures(&mut self, queue: &wgpu::Queue) { + let atlas_size = self.atlases.extent_3d(); + + // put the packed texture into the base image + let Ok(atlases) = self.atlases.images() else { return }; + let Some(atlas) = atlases.first() else { return }; + + // copy that to the gpu + queue.write_texture( + wgpu::ImageCopyTexture { + texture: &self.diffuse_texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + atlas.as_bytes(), + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new(atlas_size.width * 4), + rows_per_image: NonZeroU32::new(atlas_size.height), + }, + atlas_size, + ); + } } |
