From 0fcf15f89b574f9887d9468ba6f175b77aba6f9a Mon Sep 17 00:00:00 2001 From: QuantumEntangledAndy Date: Mon, 18 Apr 2022 15:18:06 +0700 Subject: [PATCH 1/9] Clippy lint fixes --- dotrix_core/src/assets/mesh.rs | 4 ++-- dotrix_core/src/world.rs | 6 +++--- examples/compute/main.rs | 5 +---- src/lib.rs | 4 +++- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/dotrix_core/src/assets/mesh.rs b/dotrix_core/src/assets/mesh.rs index c12c8d1c..035e7234 100644 --- a/dotrix_core/src/assets/mesh.rs +++ b/dotrix_core/src/assets/mesh.rs @@ -354,7 +354,7 @@ mod tests { [-width, width, width], ]; - width = width / 2.0; + width /= 2.0; let verticies_test_original_2: Vec<[f32; 3]> = vec![ [-width, -width, -width], @@ -367,7 +367,7 @@ mod tests { [-width, width, width], ]; - width = width / 2.0; + width /= 2.0; let verticies_test_original_3: Vec<[u32; 3]> = vec![ [-width as u32, -width as u32, -width as u32], diff --git a/dotrix_core/src/world.rs b/dotrix_core/src/world.rs index 96cdc3e6..abe8e492 100644 --- a/dotrix_core/src/world.rs +++ b/dotrix_core/src/world.rs @@ -604,7 +604,7 @@ mod tests { let world = spawn(); let entity = Entity::from(0); let query = world.get::<(&Armor, &Health)>(entity); - assert_eq!(query.is_some(), true); + assert!(query.is_some()); if let Some((&armor, &health)) = query { assert_eq!(armor, Armor(100)); assert_eq!(health, Health(100)); @@ -630,8 +630,8 @@ mod tests { assert_eq!(spawned.len(), 3); - for i in 0..3 { - assert_eq!(spawned[i], Entity::from(i as u64 + 1)); + for (i, ent) in spawned.iter().enumerate() { + assert_eq!(ent, &Entity::from(i as u64 + 1)); } } } diff --git a/examples/compute/main.rs b/examples/compute/main.rs index 663dbf8d..e3ebba79 100644 --- a/examples/compute/main.rs +++ b/examples/compute/main.rs @@ -184,10 +184,7 @@ fn compute( Binding::Storage("Particles", Stage::Compute, &spawner.particles), ], )], - options: ComputeOptions { - cs_main: "main", - ..Default::default() - }, + options: ComputeOptions { cs_main: "main" }, }, ); } diff --git a/src/lib.rs b/src/lib.rs index 180947b7..3f5677d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,6 +128,7 @@ impl Dotrix { } } + #[must_use] /// Adds system, service or extension to the application pub fn with(mut self, engine_unit: T) -> Self where @@ -137,14 +138,15 @@ impl Dotrix { self } + #[must_use] /// Adds a system to the application pub fn with_system(mut self, system: System) -> Self { self.app.as_mut().unwrap().add_system(system); self } + #[must_use] /// Adds a service to the application - pub fn with_service(mut self, service: T) -> Self { self.app.as_mut().unwrap().add_service(service); self From bfdea0bc4cebba1306c717b5d3f42cb4bddbe708 Mon Sep 17 00:00:00 2001 From: QuantumEntangledAndy Date: Mon, 18 Apr 2022 15:18:52 +0700 Subject: [PATCH 2/9] Set the name of the shader module on load --- dotrix_core/src/assets/shader.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/dotrix_core/src/assets/shader.rs b/dotrix_core/src/assets/shader.rs index c4e31f32..bbe61c2d 100644 --- a/dotrix_core/src/assets/shader.rs +++ b/dotrix_core/src/assets/shader.rs @@ -16,6 +16,7 @@ impl Shader { /// Loads the shader to GPU pub fn load(&mut self, renderer: &Renderer) { if !self.module.loaded() { + self.module.label = self.name.clone(); renderer.load_shader(&mut self.module, &self.code); } } From 5c2470118830f6ced5a09382e1048d8e11ce3c96 Mon Sep 17 00:00:00 2001 From: QuantumEntangledAndy Date: Mon, 18 Apr 2022 15:31:05 +0700 Subject: [PATCH 3/9] Add empty buffer creation Add buffer map_read/write Add copy texture to buffer Add method to copy texture to cpu --- dotrix_core/src/assets/texture.rs | 13 ++++ dotrix_core/src/renderer.rs | 30 +++++++++ dotrix_core/src/renderer/bindings.rs | 8 +-- dotrix_core/src/renderer/buffer.rs | 36 +++++++++++ dotrix_core/src/renderer/context.rs | 42 +++++++++++++ dotrix_core/src/renderer/texture.rs | 93 +++++++++++++++++++++++++++- 6 files changed, 215 insertions(+), 7 deletions(-) diff --git a/dotrix_core/src/assets/texture.rs b/dotrix_core/src/assets/texture.rs index cee7ab0d..3315271e 100644 --- a/dotrix_core/src/assets/texture.rs +++ b/dotrix_core/src/assets/texture.rs @@ -45,4 +45,17 @@ impl Texture { pub fn unload(&mut self) { self.buffer.unload(); } + + /// Fetch data from the gpu + /// + /// This is useful textures that are altered on the gpu + /// + /// This operation is slow and should mostly be + /// used for debugging + pub fn fetch_from_gpu( + &mut self, + renderer: &mut Renderer, + ) -> impl std::future::Future, wgpu::BufferAsyncError>> { + renderer.fetch_texture(&self.buffer, [self.width, self.height, self.depth]) + } } diff --git a/dotrix_core/src/renderer.rs b/dotrix_core/src/renderer.rs index 43f20d19..6d8a5729 100644 --- a/dotrix_core/src/renderer.rs +++ b/dotrix_core/src/renderer.rs @@ -100,6 +100,11 @@ impl Renderer { buffer.load(self.context(), data); } + /// Create a buffer on GPU without data + pub fn create_buffer(&self, buffer: &mut Buffer, size: u32, mapped: bool) { + buffer.create(self.context(), size, mapped); + } + /// Loads the sampler to GPU pub fn load_sampler(&self, sampler: &mut Sampler) { sampler.load(self.context()); @@ -110,6 +115,27 @@ impl Renderer { shader_module.load(self.context(), code); } + /// Copy a texture to a buffer + pub fn copy_texture_to_buffer( + &mut self, + texture: &Texture, + buffer: &Buffer, + extent: [u32; 3], + bytes_per_pixel: u32, + ) { + self.context_mut() + .run_copy_texture_to_buffer(texture, buffer, extent, bytes_per_pixel); + } + + /// Fetch texture from GPU + pub fn fetch_texture( + &mut self, + texture: &Texture, + dimensions: [u32; 3], + ) -> impl std::future::Future, wgpu::BufferAsyncError>> { + texture.fetch_from_gpu(dimensions, self.context_mut()) + } + /// Forces engine to reload shaders pub fn reload(&mut self) { self.dirty = true; @@ -263,6 +289,10 @@ pub fn release(mut renderer: Mut) { if renderer.cycle == 0 { renderer.cycle = 1; } + // Check for resource cleanups and mapping callbacks + if let Some(context) = renderer.context.as_ref() { + context.device.poll(wgpu::Maintain::Poll); + } } /// Resize handling system diff --git a/dotrix_core/src/renderer/bindings.rs b/dotrix_core/src/renderer/bindings.rs index 5e554636..c38a5315 100644 --- a/dotrix_core/src/renderer/bindings.rs +++ b/dotrix_core/src/renderer/bindings.rs @@ -76,9 +76,7 @@ impl<'a> BindGroup<'a> { visibility: stage.into(), ty: wgpu::BindingType::Texture { multisampled: false, - sample_type: wgpu::TextureSampleType::Float { - filterable: texture.is_filterable(), - }, + sample_type: texture.sample_type(), view_dimension: wgpu::TextureViewDimension::D2, }, count: None, @@ -88,9 +86,7 @@ impl<'a> BindGroup<'a> { visibility: stage.into(), ty: wgpu::BindingType::Texture { multisampled: false, - sample_type: wgpu::TextureSampleType::Float { - filterable: texture.is_filterable(), - }, + sample_type: texture.sample_type(), view_dimension: wgpu::TextureViewDimension::Cube, }, count: None, diff --git a/dotrix_core/src/renderer/buffer.rs b/dotrix_core/src/renderer/buffer.rs index c7ecf578..1325b722 100644 --- a/dotrix_core/src/renderer/buffer.rs +++ b/dotrix_core/src/renderer/buffer.rs @@ -47,6 +47,16 @@ impl Buffer { Self::new(label).use_as_indirect() } + /// Construct new Map Read buffer + pub fn map_read(label: &str) -> Self { + Self::new(label).use_as_map_read() + } + + /// Construct new Map Write buffer + pub fn map_write(label: &str) -> Self { + Self::new(label).use_as_map_write() + } + /// Allow to use as Vertex Buffer #[must_use] pub fn use_as_vertex(mut self) -> Self { @@ -82,6 +92,20 @@ impl Buffer { self } + /// Allow to use as Map Read Buffer + #[must_use] + pub fn use_as_map_read(mut self) -> Self { + self.usage |= wgpu::BufferUsages::MAP_READ; + self + } + + /// Allow to use as Map Write Buffer + #[must_use] + pub fn use_as_map_write(mut self) -> Self { + self.usage |= wgpu::BufferUsages::MAP_WRITE; + self + } + /// Allow reading from buffer #[must_use] pub fn allow_read(mut self) -> Self { @@ -116,6 +140,18 @@ impl Buffer { } } + /// Create buffer of size without data + /// + /// Typically used for staging buffers + pub fn create(&mut self, ctx: &Context, size: u32, mapped: bool) { + self.wgpu_buffer = Some(ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: Some(self.label.as_str()), + size: size as wgpu::BufferAddress, + usage: self.usage, + mapped_at_creation: mapped, + })); + } + /// Check if buffer isloaded pub fn loaded(&self) -> bool { self.wgpu_buffer.is_some() diff --git a/dotrix_core/src/renderer/context.rs b/dotrix_core/src/renderer/context.rs index 2617576b..a23d639b 100644 --- a/dotrix_core/src/renderer/context.rs +++ b/dotrix_core/src/renderer/context.rs @@ -254,6 +254,48 @@ impl Context { cpass.dispatch(args.work_groups.x, args.work_groups.y, args.work_groups.z); } } + + pub(crate) fn run_copy_texture_to_buffer( + &mut self, + texture: &super::Texture, + buffer: &super::Buffer, + extent: [u32; 3], + bytes_per_pixel: u32, + ) { + let encoder = self.encoder.as_mut().expect("WGPU encoder must be set"); + let unpadded_bytes_per_row: u32 = + std::num::NonZeroU32::new(bytes_per_pixel as u32 * extent[0]) + .unwrap() + .into(); + let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as u32; + let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align; + let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding; + + encoder.copy_texture_to_buffer( + wgpu::ImageCopyTexture { + texture: texture + .wgpu_texture + .as_ref() + .expect("Texture must be loaded"), + mip_level: 0, + origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyBuffer { + buffer: buffer.wgpu_buffer.as_ref().expect("Buffer must be ready"), + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(std::num::NonZeroU32::new(padded_bytes_per_row).unwrap()), + rows_per_image: Some(std::num::NonZeroU32::new(extent[1]).unwrap()), + }, + }, + wgpu::Extent3d { + width: extent[0], + height: extent[1], + depth_or_array_layers: extent[2], + }, + ); + } } pub(crate) async fn init(window: &winit::window::Window, sample_count: u32) -> Context { diff --git a/dotrix_core/src/renderer/texture.rs b/dotrix_core/src/renderer/texture.rs index 003ca48b..83912378 100644 --- a/dotrix_core/src/renderer/texture.rs +++ b/dotrix_core/src/renderer/texture.rs @@ -1,4 +1,4 @@ -use super::Context; +use super::{Buffer, Context}; use wgpu; /// GPU Texture Implementation @@ -7,6 +7,8 @@ pub struct Texture { pub label: String, /// WGPU Texture view pub wgpu_texture_view: Option, + /// WGPU Texture + pub wgpu_texture: Option, /// Texture usage pub usage: wgpu::TextureUsages, /// Texture format @@ -18,6 +20,7 @@ impl Default for Texture { Self { label: String::from("Noname Texture"), wgpu_texture_view: None, + wgpu_texture: None, usage: wgpu::TextureUsages::empty(), format: wgpu::TextureFormat::Rgba8UnormSrgb, } @@ -160,6 +163,8 @@ impl Texture { layer_size, ); } + + self.wgpu_texture = Some(texture); } /// Checks if texture is loaded @@ -183,4 +188,90 @@ impl Texture { pub fn is_filterable(&self) -> bool { self.format.describe().guaranteed_format_features.filterable } + + /// Get the texture bytes per pixels + pub fn pixel_bytes(&self) -> u8 { + self.format.describe().block_size + } + + /// Get the number of channels + pub fn num_channels(&self) -> u8 { + self.format.describe().components + } + + /// Get the texture sample type (float/uint etc) + pub fn sample_type(&self) -> wgpu::TextureSampleType { + self.format.describe().sample_type + } + + /// Fetch data from the gpu + /// + /// This is useful textures that are altered on the gpu + /// + /// This operation is slow and should mostly be + /// used for debugging + pub fn fetch_from_gpu( + &self, + dimensions: [u32; 3], + ctx: &mut Context, + ) -> impl std::future::Future, wgpu::BufferAsyncError>> { + let bytes_per_pixel: u32 = self.pixel_bytes() as u32; + let mut staging_buffer = Buffer::map_read("Texture Fetch Staging buffer"); + let unpadded_bytes_per_row: u32 = + std::num::NonZeroU32::new(bytes_per_pixel as u32 * dimensions[0]) + .unwrap() + .into(); + let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as u32; + let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align; + let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding; + + staging_buffer.create( + ctx, + padded_bytes_per_row * dimensions[0] * dimensions[1], + false, + ); + ctx.run_copy_texture_to_buffer(self, &staging_buffer, dimensions, bytes_per_pixel); + + async move { + // TODO: Urgently work out a better way to await the next frame. + std::thread::sleep(std::time::Duration::from_secs(1)); + + let wgpu_buffer = staging_buffer.wgpu_buffer.expect("Buffer must be loaded"); + let buffer_slice = wgpu_buffer.slice(..); + // Gets the future representing when `staging_buffer` can be read from + let buffer_future = buffer_slice.map_async(wgpu::MapMode::Read); + + match buffer_future.await { + Ok(()) => { + // Gets contents of buffer + let data = buffer_slice.get_mapped_range(); + // This strips the padding on each row + let result: Vec = data + .chunks_exact((padded_bytes_per_row * dimensions[1]) as usize) + .flat_map(|img| { + let rows: Vec> = img + .chunks_exact(padded_bytes_per_row as usize) + .map(|row| row[0..(unpadded_bytes_per_row as usize)].to_vec()) + .collect(); + rows + }) + .flatten() + .collect(); + + // With the current interface, we have to make sure all mapped views are + // dropped before we unmap the buffer. + drop(data); + wgpu_buffer.unmap(); // Unmaps buffer from memory + // If you are familiar with C++ these 2 lines can be thought of similarly to: + // delete myPointer; + // myPointer = NULL; + // It effectively frees the memory + // + Ok(result) + } + + Err(e) => Err(e), + } + } + } } From e5fa542f0da0a12a4544df10d9d75f770d8aa9ed Mon Sep 17 00:00:00 2001 From: QuantumEntangledAndy Date: Mon, 18 Apr 2022 15:39:00 +0700 Subject: [PATCH 4/9] Change 3D texture to Cube texture --- dotrix_core/src/cubemap.rs | 2 +- dotrix_core/src/renderer/bindings.rs | 25 ++++++++++++++---- dotrix_core/src/renderer/texture.rs | 39 +++++++++++++++++++++++----- dotrix_sky/src/skybox.rs | 2 +- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/dotrix_core/src/cubemap.rs b/dotrix_core/src/cubemap.rs index 39d0b075..80d0348a 100644 --- a/dotrix_core/src/cubemap.rs +++ b/dotrix_core/src/cubemap.rs @@ -32,7 +32,7 @@ impl Default for CubeMap { bottom: Id::default(), back: Id::default(), front: Id::default(), - buffer: TextureBuffer::new("CubeMap Texture Buffer"), + buffer: TextureBuffer::new_cube("CubeMap Texture Buffer"), } } } diff --git a/dotrix_core/src/renderer/bindings.rs b/dotrix_core/src/renderer/bindings.rs index c38a5315..e19c78ae 100644 --- a/dotrix_core/src/renderer/bindings.rs +++ b/dotrix_core/src/renderer/bindings.rs @@ -30,10 +30,12 @@ pub enum Binding<'a> { Uniform(&'a str, Stage, &'a Buffer), /// Texture binding Texture(&'a str, Stage, &'a Texture), - /// 3D Texture binding - Texture3D(&'a str, Stage, &'a Texture), + /// Cube Texture binding + TextureCube(&'a str, Stage, &'a Texture), /// Storage texture binding StorageTexture(&'a str, Stage, &'a Texture, Access), + /// Storage texture cube binding + StorageTextureCube(&'a str, Stage, &'a Texture, Access), /// Texture sampler binding Sampler(&'a str, Stage, &'a Sampler), /// Storage binding @@ -81,7 +83,7 @@ impl<'a> BindGroup<'a> { }, count: None, }, - Binding::Texture3D(_, stage, texture) => wgpu::BindGroupLayoutEntry { + Binding::TextureCube(_, stage, texture) => wgpu::BindGroupLayoutEntry { binding: index as u32, visibility: stage.into(), ty: wgpu::BindingType::Texture { @@ -101,6 +103,18 @@ impl<'a> BindGroup<'a> { }, count: None, }, + Binding::StorageTextureCube(_, stage, texture, access) => { + wgpu::BindGroupLayoutEntry { + binding: index as u32, + visibility: stage.into(), + ty: wgpu::BindingType::StorageTexture { + access: access.into(), + format: texture.format, + view_dimension: wgpu::TextureViewDimension::Cube, + }, + count: None, + } + } Binding::Sampler(_, stage, _) => wgpu::BindGroupLayoutEntry { binding: index as u32, visibility: stage.into(), @@ -165,8 +179,9 @@ impl Bindings { uniform.get().as_entire_binding() } Binding::Texture(_, _, texture) - | Binding::Texture3D(_, _, texture) - | Binding::StorageTexture(_, _, texture, _) => { + | Binding::TextureCube(_, _, texture) + | Binding::StorageTexture(_, _, texture, _) + | Binding::StorageTextureCube(_, _, texture, _) => { wgpu::BindingResource::TextureView(texture.get()) } Binding::Sampler(_, _, sampler) => { diff --git a/dotrix_core/src/renderer/texture.rs b/dotrix_core/src/renderer/texture.rs index 83912378..10974d28 100644 --- a/dotrix_core/src/renderer/texture.rs +++ b/dotrix_core/src/renderer/texture.rs @@ -1,6 +1,11 @@ use super::{Buffer, Context}; use wgpu; +pub enum TextureKind { + D2, + Cube, +} + /// GPU Texture Implementation pub struct Texture { /// Texture label @@ -11,6 +16,8 @@ pub struct Texture { pub wgpu_texture: Option, /// Texture usage pub usage: wgpu::TextureUsages, + /// Texture kind + pub kind: TextureKind, /// Texture format pub format: wgpu::TextureFormat, } @@ -23,6 +30,7 @@ impl Default for Texture { wgpu_texture: None, usage: wgpu::TextureUsages::empty(), format: wgpu::TextureFormat::Rgba8UnormSrgb, + kind: TextureKind::D2, } } } @@ -37,6 +45,16 @@ impl Texture { } } + /// Constructs a CubeMap GPU Texture + pub fn new_cube(label: &str) -> Self { + Self { + label: String::from(label), + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + kind: TextureKind::Cube, + ..Default::default() + } + } + /// Constructs GPU Storage Texture pub fn storage(label: &str) -> Self { Self { @@ -106,6 +124,14 @@ impl Texture { /// Loads data into the texture buffer pub(crate) fn load<'a>(&mut self, ctx: &Context, width: u32, height: u32, layers: &[&'a [u8]]) { + let dimension = match self.kind { + TextureKind::D2 => wgpu::TextureViewDimension::D2, + TextureKind::Cube => { + assert!(layers.len() == 6); + wgpu::TextureViewDimension::Cube + } + }; + let format = self.format; let usage = self.usage; let depth_or_array_layers = layers.len() as u32; @@ -120,12 +146,17 @@ impl Texture { }; let max_mips = 1; + let tex_dimension: wgpu::TextureDimension = match self.kind { + TextureKind::D2 => wgpu::TextureDimension::D2, + TextureKind::Cube => wgpu::TextureDimension::D2, + }; + let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { label: Some(&self.label), size, mip_level_count: max_mips as u32, sample_count: 1, - dimension: wgpu::TextureDimension::D2, + dimension: tex_dimension, format, usage, }); @@ -133,11 +164,7 @@ impl Texture { self.wgpu_texture_view = Some(texture.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(format), - dimension: Some(if depth_or_array_layers == 6 { - wgpu::TextureViewDimension::Cube - } else { - wgpu::TextureViewDimension::D2 - }), + dimension: Some(dimension), ..wgpu::TextureViewDescriptor::default() })); diff --git a/dotrix_sky/src/skybox.rs b/dotrix_sky/src/skybox.rs index 41c10013..02d99591 100644 --- a/dotrix_sky/src/skybox.rs +++ b/dotrix_sky/src/skybox.rs @@ -160,7 +160,7 @@ pub fn render( ), BindGroup::new( "Locals", - vec![Binding::Texture3D( + vec![Binding::TextureCube( "CubeMap", Stage::Fragment, &cubemap.buffer, From 3741156afc7497a8294ab8f447785011262547a3 Mon Sep 17 00:00:00 2001 From: QuantumEntangledAndy Date: Mon, 18 Apr 2022 15:42:41 +0700 Subject: [PATCH 5/9] Add 3d textures --- dotrix_core/src/renderer/bindings.rs | 30 +++++++++++++++++++++++++++- dotrix_core/src/renderer/texture.rs | 13 ++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/dotrix_core/src/renderer/bindings.rs b/dotrix_core/src/renderer/bindings.rs index e19c78ae..37cc92af 100644 --- a/dotrix_core/src/renderer/bindings.rs +++ b/dotrix_core/src/renderer/bindings.rs @@ -32,10 +32,14 @@ pub enum Binding<'a> { Texture(&'a str, Stage, &'a Texture), /// Cube Texture binding TextureCube(&'a str, Stage, &'a Texture), + /// 3D Texture binding + Texture3D(&'a str, Stage, &'a Texture), /// Storage texture binding StorageTexture(&'a str, Stage, &'a Texture, Access), /// Storage texture cube binding StorageTextureCube(&'a str, Stage, &'a Texture, Access), + /// Storage texture binding 3D + StorageTexture3D(&'a str, Stage, &'a Texture, Access), /// Texture sampler binding Sampler(&'a str, Stage, &'a Sampler), /// Storage binding @@ -93,6 +97,16 @@ impl<'a> BindGroup<'a> { }, count: None, }, + Binding::Texture3D(_, stage, texture) => wgpu::BindGroupLayoutEntry { + binding: index as u32, + visibility: stage.into(), + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: texture.sample_type(), + view_dimension: wgpu::TextureViewDimension::D3, + }, + count: None, + }, Binding::StorageTexture(_, stage, texture, access) => wgpu::BindGroupLayoutEntry { binding: index as u32, visibility: stage.into(), @@ -115,6 +129,18 @@ impl<'a> BindGroup<'a> { count: None, } } + Binding::StorageTexture3D(_, stage, texture, access) => { + wgpu::BindGroupLayoutEntry { + binding: index as u32, + visibility: stage.into(), + ty: wgpu::BindingType::StorageTexture { + access: access.into(), + format: texture.format, + view_dimension: wgpu::TextureViewDimension::D3, + }, + count: None, + } + } Binding::Sampler(_, stage, _) => wgpu::BindGroupLayoutEntry { binding: index as u32, visibility: stage.into(), @@ -180,8 +206,10 @@ impl Bindings { } Binding::Texture(_, _, texture) | Binding::TextureCube(_, _, texture) + | Binding::Texture3D(_, _, texture) | Binding::StorageTexture(_, _, texture, _) - | Binding::StorageTextureCube(_, _, texture, _) => { + | Binding::StorageTextureCube(_, _, texture, _) + | Binding::StorageTexture3D(_, _, texture, _) => { wgpu::BindingResource::TextureView(texture.get()) } Binding::Sampler(_, _, sampler) => { diff --git a/dotrix_core/src/renderer/texture.rs b/dotrix_core/src/renderer/texture.rs index 10974d28..a4fd76ce 100644 --- a/dotrix_core/src/renderer/texture.rs +++ b/dotrix_core/src/renderer/texture.rs @@ -4,6 +4,7 @@ use wgpu; pub enum TextureKind { D2, Cube, + D3, } /// GPU Texture Implementation @@ -55,6 +56,16 @@ impl Texture { } } + /// Constructs a 3D GPU Texture + pub fn new_3d(label: &str) -> Self { + Self { + label: String::from(label), + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + kind: TextureKind::D3, + ..Default::default() + } + } + /// Constructs GPU Storage Texture pub fn storage(label: &str) -> Self { Self { @@ -130,6 +141,7 @@ impl Texture { assert!(layers.len() == 6); wgpu::TextureViewDimension::Cube } + TextureKind::D3 => wgpu::TextureViewDimension::D3, }; let format = self.format; @@ -149,6 +161,7 @@ impl Texture { let tex_dimension: wgpu::TextureDimension = match self.kind { TextureKind::D2 => wgpu::TextureDimension::D2, TextureKind::Cube => wgpu::TextureDimension::D2, + TextureKind::D3 => wgpu::TextureDimension::D3, }; let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { From 0cdfb79a09d70e81a830b168e2c642707dbe8c8a Mon Sep 17 00:00:00 2001 From: QuantumEntangledAndy Date: Mon, 18 Apr 2022 15:48:18 +0700 Subject: [PATCH 6/9] Add 2d texture arrays --- dotrix_core/src/renderer/bindings.rs | 28 ++++++++++++++++++++++++++++ dotrix_core/src/renderer/texture.rs | 13 +++++++++++++ 2 files changed, 41 insertions(+) diff --git a/dotrix_core/src/renderer/bindings.rs b/dotrix_core/src/renderer/bindings.rs index 37cc92af..be28b2ff 100644 --- a/dotrix_core/src/renderer/bindings.rs +++ b/dotrix_core/src/renderer/bindings.rs @@ -32,12 +32,16 @@ pub enum Binding<'a> { Texture(&'a str, Stage, &'a Texture), /// Cube Texture binding TextureCube(&'a str, Stage, &'a Texture), + /// 2D Texture Array binding + TextureArray(&'a str, Stage, &'a Texture), /// 3D Texture binding Texture3D(&'a str, Stage, &'a Texture), /// Storage texture binding StorageTexture(&'a str, Stage, &'a Texture, Access), /// Storage texture cube binding StorageTextureCube(&'a str, Stage, &'a Texture, Access), + /// Storage 2D texture array binding + StorageTextureArray(&'a str, Stage, &'a Texture, Access), /// Storage texture binding 3D StorageTexture3D(&'a str, Stage, &'a Texture, Access), /// Texture sampler binding @@ -97,6 +101,16 @@ impl<'a> BindGroup<'a> { }, count: None, }, + Binding::TextureArray(_, stage, texture) => wgpu::BindGroupLayoutEntry { + binding: index as u32, + visibility: stage.into(), + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: texture.sample_type(), + view_dimension: wgpu::TextureViewDimension::D2Array, + }, + count: None, + }, Binding::Texture3D(_, stage, texture) => wgpu::BindGroupLayoutEntry { binding: index as u32, visibility: stage.into(), @@ -129,6 +143,18 @@ impl<'a> BindGroup<'a> { count: None, } } + Binding::StorageTextureArray(_, stage, texture, access) => { + wgpu::BindGroupLayoutEntry { + binding: index as u32, + visibility: stage.into(), + ty: wgpu::BindingType::StorageTexture { + access: access.into(), + format: texture.format, + view_dimension: wgpu::TextureViewDimension::D2Array, + }, + count: None, + } + } Binding::StorageTexture3D(_, stage, texture, access) => { wgpu::BindGroupLayoutEntry { binding: index as u32, @@ -206,9 +232,11 @@ impl Bindings { } Binding::Texture(_, _, texture) | Binding::TextureCube(_, _, texture) + | Binding::TextureArray(_, _, texture) | Binding::Texture3D(_, _, texture) | Binding::StorageTexture(_, _, texture, _) | Binding::StorageTextureCube(_, _, texture, _) + | Binding::StorageTextureArray(_, _, texture, _) | Binding::StorageTexture3D(_, _, texture, _) => { wgpu::BindingResource::TextureView(texture.get()) } diff --git a/dotrix_core/src/renderer/texture.rs b/dotrix_core/src/renderer/texture.rs index a4fd76ce..9c02aee4 100644 --- a/dotrix_core/src/renderer/texture.rs +++ b/dotrix_core/src/renderer/texture.rs @@ -4,6 +4,7 @@ use wgpu; pub enum TextureKind { D2, Cube, + D2Array, D3, } @@ -56,6 +57,16 @@ impl Texture { } } + /// Constructs a 2D Array GPU Texture + pub fn new_array(label: &str) -> Self { + Self { + label: String::from(label), + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + kind: TextureKind::D2Array, + ..Default::default() + } + } + /// Constructs a 3D GPU Texture pub fn new_3d(label: &str) -> Self { Self { @@ -141,6 +152,7 @@ impl Texture { assert!(layers.len() == 6); wgpu::TextureViewDimension::Cube } + TextureKind::D2Array => wgpu::TextureViewDimension::D2Array, TextureKind::D3 => wgpu::TextureViewDimension::D3, }; @@ -161,6 +173,7 @@ impl Texture { let tex_dimension: wgpu::TextureDimension = match self.kind { TextureKind::D2 => wgpu::TextureDimension::D2, TextureKind::Cube => wgpu::TextureDimension::D2, + TextureKind::D2Array => wgpu::TextureDimension::D2, TextureKind::D3 => wgpu::TextureDimension::D3, }; From d7352547f6c30fb69e414e9c88d081e2e1aff793 Mon Sep 17 00:00:00 2001 From: QuantumEntangledAndy Date: Mon, 18 Apr 2022 16:01:21 +0700 Subject: [PATCH 7/9] Add methods to update the values of a texture without recreating it --- dotrix_core/src/renderer.rs | 30 ++++++++- dotrix_core/src/renderer/texture.rs | 101 +++++++++++++++++++++------- 2 files changed, 105 insertions(+), 26 deletions(-) diff --git a/dotrix_core/src/renderer.rs b/dotrix_core/src/renderer.rs index 6d8a5729..1bc23a10 100644 --- a/dotrix_core/src/renderer.rs +++ b/dotrix_core/src/renderer.rs @@ -84,7 +84,9 @@ impl Renderer { buffer.load(self.context(), attributes, indices, count as u32); }*/ - /// Loads the texture buffer to GPU + /// Loads the texture buffer to GPU. + /// This will recreate the texture, as a result it must be rebound on any pipelines for changes + /// to take effect pub fn load_texture<'a>( &self, texture: &mut Texture, @@ -95,6 +97,32 @@ impl Renderer { texture.load(self.context(), width, height, layers); } + /// Load data from cpu to a texture buffer on GPU + /// This is a noop if texture has not been loaded with `load_texture` + /// Unexpected results/errors occur if the dimensions differs from it dimensions at load time + pub fn update_texture<'a>( + &self, + texture: &mut Texture, + width: u32, + height: u32, + layers: &'a [&'a [u8]], + ) { + texture.update(self.context(), width, height, layers); + } + + /// This will `[update_texture]` if texture has been loaded or `[load_texture]` if not + /// the same cavets of `[update_texture]` apply in that care must be taken not to change + /// the dimensions between `load` and `update` + pub fn update_or_load_texture<'a>( + &self, + texture: &mut Texture, + width: u32, + height: u32, + layers: &'a [&'a [u8]], + ) { + texture.update_or_load(self.context(), width, height, layers); + } + /// Loads the buffer to GPU pub fn load_buffer<'a>(&self, buffer: &mut Buffer, data: &'a [u8]) { buffer.load(self.context(), data); diff --git a/dotrix_core/src/renderer/texture.rs b/dotrix_core/src/renderer/texture.rs index 9c02aee4..2d189f19 100644 --- a/dotrix_core/src/renderer/texture.rs +++ b/dotrix_core/src/renderer/texture.rs @@ -145,6 +145,12 @@ impl Texture { } /// Loads data into the texture buffer + /// + /// This will recreate the texture backend on the gpu. Which means it must be rebound + /// in the pipelines for changes to take effect. + /// + /// If you want to update the values without recreating and therefore rebinding the texture + /// see `[update]` pub(crate) fn load<'a>(&mut self, ctx: &Context, width: u32, height: u32, layers: &[&'a [u8]]) { let dimension = match self.kind { TextureKind::D2 => wgpu::TextureViewDimension::D2, @@ -164,10 +170,7 @@ impl Texture { height, depth_or_array_layers, }; - let layer_size = wgpu::Extent3d { - depth_or_array_layers: 1, - ..size - }; + let max_mips = 1; let tex_dimension: wgpu::TextureDimension = match self.kind { @@ -194,30 +197,78 @@ impl Texture { ..wgpu::TextureViewDescriptor::default() })); - for (i, data) in layers.iter().enumerate() { - let bytes_per_row = std::num::NonZeroU32::new(data.len() as u32 / height).unwrap(); - ctx.queue.write_texture( - wgpu::ImageCopyTexture { - texture: &texture, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0, - y: 0, - z: i as u32, + self.wgpu_texture = Some(texture); + + self.update(ctx, width, height, layers) + } + + /// This will write to a texture but not create it + /// This can be used to update a texture's value with out recreating and therefore without the + /// need to rebind it + /// however if the size of the texture is changed it will behave oddly or even panic + /// + /// This is a no op if the texture has not been loaded + pub(crate) fn update<'a>( + &mut self, + ctx: &Context, + width: u32, + height: u32, + layers: &[&'a [u8]], + ) { + if let Some(texture) = self.wgpu_texture.as_ref() { + let depth_or_array_layers = layers.len() as u32; + let size = wgpu::Extent3d { + width, + height, + depth_or_array_layers, + }; + let layer_size = wgpu::Extent3d { + depth_or_array_layers: 1, + ..size + }; + + for (i, data) in layers.iter().enumerate() { + let bytes_per_row = std::num::NonZeroU32::new(data.len() as u32 / height).unwrap(); + ctx.queue.write_texture( + wgpu::ImageCopyTexture { + texture, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0, + y: 0, + z: i as u32, + }, + aspect: wgpu::TextureAspect::All, + }, + data, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(bytes_per_row), + rows_per_image: Some(std::num::NonZeroU32::new(height).unwrap()), }, - aspect: wgpu::TextureAspect::All, - }, - data, - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(bytes_per_row), - rows_per_image: Some(std::num::NonZeroU32::new(height).unwrap()), - }, - layer_size, - ); + layer_size, + ); + } } + } - self.wgpu_texture = Some(texture); + /// This method will update a gpu texture if it exists with new data or + /// load a new texture onto the gpu if it does not. + /// + /// The same cavets of [`update`] apply in that care must be taken to not + /// change the size of the texture between [`load`] and [`update`] + pub(crate) fn update_or_load<'a>( + &mut self, + ctx: &Context, + width: u32, + height: u32, + layers: &[&'a [u8]], + ) { + if self.wgpu_texture.is_none() { + self.load(ctx, width, height, layers); + } else { + self.update(ctx, width, height, layers); + } } /// Checks if texture is loaded From 9ff7a4ef253f07829dd488cebca1e901206a737a Mon Sep 17 00:00:00 2001 From: QuantumEntangledAndy Date: Tue, 19 Apr 2022 10:36:19 +0700 Subject: [PATCH 8/9] Unload `texture` and `texture_view` --- dotrix_core/src/renderer/texture.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/dotrix_core/src/renderer/texture.rs b/dotrix_core/src/renderer/texture.rs index 2d189f19..95ad574c 100644 --- a/dotrix_core/src/renderer/texture.rs +++ b/dotrix_core/src/renderer/texture.rs @@ -278,6 +278,7 @@ impl Texture { /// Release all resources used by the texture pub fn unload(&mut self) { + self.wgpu_texture.take(); self.wgpu_texture_view.take(); } From 7922b2d335db1010bb83d566ed1def7a0ca7acd6 Mon Sep 17 00:00:00 2001 From: QuantumEntangledAndy Date: Wed, 20 Apr 2022 15:18:23 +0700 Subject: [PATCH 9/9] Using wgpu::TextureViewDimension for texture backend kind --- dotrix_core/src/renderer/texture.rs | 37 ++++++++++------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/dotrix_core/src/renderer/texture.rs b/dotrix_core/src/renderer/texture.rs index 95ad574c..0679fae0 100644 --- a/dotrix_core/src/renderer/texture.rs +++ b/dotrix_core/src/renderer/texture.rs @@ -1,13 +1,6 @@ use super::{Buffer, Context}; use wgpu; -pub enum TextureKind { - D2, - Cube, - D2Array, - D3, -} - /// GPU Texture Implementation pub struct Texture { /// Texture label @@ -19,7 +12,7 @@ pub struct Texture { /// Texture usage pub usage: wgpu::TextureUsages, /// Texture kind - pub kind: TextureKind, + pub kind: wgpu::TextureViewDimension, /// Texture format pub format: wgpu::TextureFormat, } @@ -32,7 +25,7 @@ impl Default for Texture { wgpu_texture: None, usage: wgpu::TextureUsages::empty(), format: wgpu::TextureFormat::Rgba8UnormSrgb, - kind: TextureKind::D2, + kind: wgpu::TextureViewDimension::D2, } } } @@ -52,7 +45,7 @@ impl Texture { Self { label: String::from(label), usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - kind: TextureKind::Cube, + kind: wgpu::TextureViewDimension::Cube, ..Default::default() } } @@ -62,7 +55,7 @@ impl Texture { Self { label: String::from(label), usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - kind: TextureKind::D2Array, + kind: wgpu::TextureViewDimension::D2Array, ..Default::default() } } @@ -72,7 +65,7 @@ impl Texture { Self { label: String::from(label), usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - kind: TextureKind::D3, + kind: wgpu::TextureViewDimension::D3, ..Default::default() } } @@ -152,14 +145,9 @@ impl Texture { /// If you want to update the values without recreating and therefore rebinding the texture /// see `[update]` pub(crate) fn load<'a>(&mut self, ctx: &Context, width: u32, height: u32, layers: &[&'a [u8]]) { - let dimension = match self.kind { - TextureKind::D2 => wgpu::TextureViewDimension::D2, - TextureKind::Cube => { - assert!(layers.len() == 6); - wgpu::TextureViewDimension::Cube - } - TextureKind::D2Array => wgpu::TextureViewDimension::D2Array, - TextureKind::D3 => wgpu::TextureViewDimension::D3, + let dimension = self.kind; + if let wgpu::TextureViewDimension::Cube = dimension { + assert_eq!(layers.len(), 6); }; let format = self.format; @@ -174,10 +162,11 @@ impl Texture { let max_mips = 1; let tex_dimension: wgpu::TextureDimension = match self.kind { - TextureKind::D2 => wgpu::TextureDimension::D2, - TextureKind::Cube => wgpu::TextureDimension::D2, - TextureKind::D2Array => wgpu::TextureDimension::D2, - TextureKind::D3 => wgpu::TextureDimension::D3, + wgpu::TextureViewDimension::D2 => wgpu::TextureDimension::D2, + wgpu::TextureViewDimension::Cube => wgpu::TextureDimension::D2, + wgpu::TextureViewDimension::D2Array => wgpu::TextureDimension::D2, + wgpu::TextureViewDimension::D3 => wgpu::TextureDimension::D3, + _ => unimplemented!(), }; let texture = ctx.device.create_texture(&wgpu::TextureDescriptor {