diff options
| author | Micha White <botahamec@outlook.com> | 2022-10-02 13:49:47 -0400 |
|---|---|---|
| committer | Micha White <botahamec@outlook.com> | 2022-10-02 13:49:47 -0400 |
| commit | 511d3873f5f567c97eecd69d186bb4f93f927d58 (patch) | |
| tree | b99779ade2b150d51d800b7275a0c310a7591439 /src | |
| parent | 39e36dd10cd7a335897e66e0f613d0191e7f9eba (diff) | |
Hacked in textures
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/renderer.rs | 201 | ||||
| -rw-r--r-- | src/texture.rs | 59 |
3 files changed, 226 insertions, 36 deletions
@@ -16,4 +16,6 @@ pub(crate) use camera::Camera; pub use config::RenderWindowConfig; pub use instance::Instance; pub use renderer::Renderer; +pub use texture::ImageFormat; +pub(crate) use texture::TextureAtlases; pub(crate) use vertex::Vertex; diff --git a/src/renderer.rs b/src/renderer.rs index d45233b..14a7b6a 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,9 +1,13 @@ use std::{convert::TryInto, mem::size_of, num::NonZeroU32}; use crate::{ - camera::CameraUniform, instance::InstanceId, vertex::SQUARE, Camera, Instance, - RenderWindowConfig, Vertex, + camera::CameraUniform, + instance::InstanceId, + texture::{TextureError, TextureId}, + vertex::SQUARE, + Camera, ImageFormat, Instance, RenderWindowConfig, TextureAtlases, Vertex, }; +use image::{EncodableLayout, GenericImage}; use pollster::FutureExt; use thiserror::Error; use wgpu::{include_wgsl, util::DeviceExt}; @@ -39,7 +43,7 @@ pub enum NewRendererError { WindowInitError(#[from] OsError), } -#[derive(Debug)] +// TODO make this Debug pub struct Renderer { // TODO move some of this data elsewhere surface: wgpu::Surface, @@ -56,6 +60,10 @@ 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, window: Window, } @@ -222,17 +230,6 @@ impl Renderer { }], }); - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Sprite Render Pipeline Layout"), - bind_group_layouts: &[&camera_bind_group_layout], - push_constant_ranges: &[], - }); - - // set up a pipeline for sprite rendering - let render_pipeline = - Self::sprite_render_pipeline(&device, surface_config.format, &render_pipeline_layout); - // the vertex buffer used for rendering squares let square_vertices = SQUARE .len() @@ -249,6 +246,71 @@ impl Renderer { let (instance_buffer, instance_buffer_size) = 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), + }, + ], + }); + + 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], + push_constant_ranges: &[], + }); + + // set up a pipeline for sprite rendering + let render_pipeline = + Self::sprite_render_pipeline(&device, surface_config.format, &render_pipeline_layout); + Ok(Self { surface, surface_config, @@ -264,6 +326,10 @@ impl Renderer { camera, camera_buffer, camera_bind_group, + textures, + texture_size, + diffuse_texture, + diffuse_bind_group, window, }) } @@ -304,23 +370,6 @@ impl Renderer { self.window.set_title(title); } - fn expand_instance_buffer(&mut self) { - (self.instance_buffer, self.instance_buffer_size) = - Self::new_instance_buffer(&self.device, &self.instances); - } - - fn fill_instance_buffer(&mut self) { - if self.instances.len() > self.instance_buffer_size { - self.expand_instance_buffer(); - } - - self.queue.write_buffer( - &self.instance_buffer, - 0 as wgpu::BufferAddress, - bytemuck::cast_slice(&self.instances), - ); - } - /// Add an instance to the renderer, and returns an `InstanceId` to the /// instance. This id becomes invalid if the instances are cleared. pub fn push_instance(&mut self, instance: Instance) -> InstanceId { @@ -354,6 +403,61 @@ impl Renderer { &mut self.camera } + /// 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_mem( + &mut self, + texture: &[u8], + format: ImageFormat, + ) -> Result<TextureId, TextureError> { + self.textures.load_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) + } + + 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) + } + + 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) + } + + 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) + } + + fn expand_instance_buffer(&mut self) { + (self.instance_buffer, self.instance_buffer_size) = + Self::new_instance_buffer(&self.device, &self.instances); + } + + fn fill_instance_buffer(&mut self) { + if self.instances.len() > self.instance_buffer_size { + self.expand_instance_buffer(); + } + + self.queue.write_buffer( + &self.instance_buffer, + 0 as wgpu::BufferAddress, + bytemuck::cast_slice(&self.instances), + ); + } + fn refresh_camera_buffer(&mut self) { self.queue.write_buffer( &self.camera_buffer, @@ -362,6 +466,39 @@ impl Renderer { ); } + // TODO optimize this + fn fill_textures(&mut self) { + // create a base image + let mut image = image::RgbaImage::from_vec( + self.texture_size.width, + self.texture_size.height, + vec![0; 4 * self.texture_size.width as usize * self.texture_size.height as usize], + ) + .unwrap(); + + // put the packed texture into the base image + let Ok(atlases) = self.textures.atlases() else { return }; + let Some(atlas) = atlases.first() else { return }; + 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, + }, + 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 @@ -392,6 +529,7 @@ impl Renderer { .expect("expected less than 3 billion instances"); self.refresh_camera_buffer(); + self.fill_textures(); { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { @@ -409,6 +547,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_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 8a36334..9184304 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -12,9 +12,10 @@ use thiserror::Error; static NEXT_TEXTURE_ID: AtomicUsize = AtomicUsize::new(0); #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -struct TextureId(usize); +pub struct TextureId(usize); impl TextureId { + #[allow(clippy::new_without_default)] pub fn new() -> Self { Self(NEXT_TEXTURE_ID.fetch_add(1, Ordering::Relaxed)) } @@ -58,15 +59,16 @@ impl From<ImageError> for TextureError { } } -struct TextureAtlases<'a> { +// TODO make this Debug +pub struct TextureAtlases<'a> { packer: MultiTexturePacker<'a, image::RgbaImage, TextureId>, + width: u32, + height: u32, } impl<'a> Default for TextureAtlases<'a> { fn default() -> Self { - Self { - packer: MultiTexturePacker::new_skyline(TexturePackerConfig::default()), - } + Self::new(1024, 1024) } } @@ -78,8 +80,11 @@ impl<'a> TextureAtlases<'a> { packer: MultiTexturePacker::new_skyline(TexturePackerConfig { max_width: width, max_height: height, + //trim: false, ..Default::default() }), + width, + height, } } @@ -98,6 +103,50 @@ impl<'a> TextureAtlases<'a> { Ok(id) } + fn texture_frame(&self, id: TextureId) -> Option<&texture_packer::Frame<TextureId>> { + self.packer + .get_pages() + .iter() + .map(|a| a.get_frame(&id)) + .next()? + } + + pub fn texture_width(&self, id: TextureId) -> Option<u32> { + let frame = self.texture_frame(id)?; + Some(frame.frame.w) + } + + pub fn texture_height(&self, id: TextureId) -> Option<u32> { + let frame = self.texture_frame(id)?; + Some(frame.frame.h) + } + + pub fn texture_x(&self, id: TextureId) -> Option<u32> { + let frame = self.texture_frame(id)?; + Some(frame.frame.x) + } + + pub fn texture_y(&self, id: TextureId) -> Option<u32> { + let frame = self.texture_frame(id)?; + 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 { + wgpu::Extent3d { + width: self.width, + height: self.height, + depth_or_array_layers: 1, + } + } + pub(crate) fn atlases(&self) -> ExportResult<Box<[DynamicImage]>> { self.packer .get_pages() |
