From 0e692feb6614b05243d8a4f64d66af1e3f259a0e Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 5 Jan 2024 19:56:26 -0800 Subject: [PATCH] Added object state uniform, moved transform logic --- crates/render/shaders/include/animate.wgsl | 8 +- crates/render/shaders/object.wgsl | 59 +++++++++---- crates/render/shaders/particle.wgsl | 4 +- crates/render/shaders/starfield.wgsl | 4 +- crates/render/shaders/ui.wgsl | 2 +- .../{atlascontent.rs => atlas.rs} | 16 ++-- .../globaluniform/{datacontent.rs => data.rs} | 0 .../render/src/globaluniform/globaluniform.rs | 87 ++++++++++++++----- crates/render/src/globaluniform/mod.rs | 14 +-- crates/render/src/globaluniform/object.rs | 44 ++++++++++ .../{spritecontent.rs => sprite.rs} | 8 +- crates/render/src/gpustate.rs | 83 ++++++------------ crates/render/src/texturearray.rs | 6 +- crates/render/src/vertexbuffer/types.rs | 32 ++----- 14 files changed, 216 insertions(+), 151 deletions(-) rename crates/render/src/globaluniform/{atlascontent.rs => atlas.rs} (69%) rename crates/render/src/globaluniform/{datacontent.rs => data.rs} (100%) create mode 100644 crates/render/src/globaluniform/object.rs rename crates/render/src/globaluniform/{spritecontent.rs => sprite.rs} (85%) diff --git a/crates/render/shaders/include/animate.wgsl b/crates/render/shaders/include/animate.wgsl index c2eaab6..ad74216 100644 --- a/crates/render/shaders/include/animate.wgsl +++ b/crates/render/shaders/include/animate.wgsl @@ -5,9 +5,9 @@ fn animate(instance: InstanceInput, age: f32) -> u32 { let idx = instance.texture_index; - let len = sprites.data[idx].frame_count; - let rep = sprites.data[idx].repeatmode; - let fps = sprites.data[idx].fps; + let len = sprites[idx].frame_count; + let rep = sprites[idx].repeatmode; + let fps = sprites[idx].fps; var frame: u32 = u32(0); @@ -39,5 +39,5 @@ fn animate(instance: InstanceInput, age: f32) -> u32 { } - return frame + sprites.data[idx].first_frame; + return frame + sprites[idx].first_frame; } \ No newline at end of file diff --git a/crates/render/shaders/object.wgsl b/crates/render/shaders/object.wgsl index 769de50..2b71e90 100644 --- a/crates/render/shaders/object.wgsl +++ b/crates/render/shaders/object.wgsl @@ -1,11 +1,8 @@ // INCLUDE: global uniform header struct InstanceInput { - @location(2) transform_matrix_0: vec4, - @location(3) transform_matrix_1: vec4, - @location(4) transform_matrix_2: vec4, - @location(5) transform_matrix_3: vec4, - @location(6) texture_index: u32, + @location(2) texture_index: u32, + @location(3) object_index: u32, }; struct VertexInput { @@ -28,23 +25,55 @@ var sampler_array: binding_array; // INCLUDE: animate.wgsl +fn transform_vertex(obj: ObjectLocation, vertex: VertexInput, instance: InstanceInput) -> vec4 { + // Object scale + let scale = obj.size / (global.camera_zoom.x * obj.zpos); + + // Apply scale and sprite aspect + // Note that our mesh starts centered at (0, 0). This is important! + var pos: vec2 = vec2( + vertex.position.x * scale * sprites[instance.texture_index].aspect, + vertex.position.y * scale + ); + + // Apply rotation + pos = mat2x2( + vec2(cos(obj.angle), sin(obj.angle)), + vec2(-sin(obj.angle), cos(obj.angle)) + ) * pos; + + // Correct for screen aspect, preserving height + // This must be done AFTER rotation. + pos = vec2( + pos.x / global.window_aspect.x, + pos.y + ); + + // Distance-adjusted world position + let trans = (vec2(obj.xpos, obj.ypos) - global.camera_position) / obj.zpos; + + // Finally, translate + // + // Note that we divide camera zoom by two. + // The height of the viewport is `zoom` in game units, + // but it's 2 in screen units (since coordinates range from -1 to 1) + pos = pos + vec2( + trans.x / (global.camera_zoom.x / 2.0) / global.window_aspect.x, + trans.y / (global.camera_zoom.x / 2.0) + ); + + return vec4(pos, 0.0, 1.0);; +} + @vertex fn vertex_main( vertex: VertexInput, instance: InstanceInput, ) -> VertexOutput { - - let transform = mat4x4( - instance.transform_matrix_0, - instance.transform_matrix_1, - instance.transform_matrix_2, - instance.transform_matrix_3, - ); - var out: VertexOutput; - out.position = transform * vec4(vertex.position, 1.0); + out.position = transform_vertex(objects[instance.object_index], vertex, instance); - let t = atlas.data[animate(instance, global.current_time.x)]; + let t = atlas[animate(instance, global.current_time.x)]; out.texture_index = t.atlas_texture; out.texture_coords = vec2(t.xpos, t.ypos); if vertex.texture_coords.x == 1.0 { diff --git a/crates/render/shaders/particle.wgsl b/crates/render/shaders/particle.wgsl index 5d1b43d..a700353 100644 --- a/crates/render/shaders/particle.wgsl +++ b/crates/render/shaders/particle.wgsl @@ -55,7 +55,7 @@ fn vertex_main( var scale: f32 = instance.size / global.camera_zoom.x; var pos: vec2 = vec2(vertex.position.x, vertex.position.y); pos = pos * vec2( - sprites.data[instance.texture_index].aspect * scale / global.window_aspect.x, + sprites[instance.texture_index].aspect * scale / global.window_aspect.x, scale ); @@ -76,7 +76,7 @@ fn vertex_main( // Compute texture coordinates - let t = atlas.data[animate(instance, age)]; + let t = atlas[animate(instance, age)]; out.texture_index = u32(t.atlas_texture); out.texture_coords = vec2(t.xpos, t.ypos); if vertex.texture_coords.x == 1.0 { diff --git a/crates/render/shaders/starfield.wgsl b/crates/render/shaders/starfield.wgsl index fea16f0..3426d14 100644 --- a/crates/render/shaders/starfield.wgsl +++ b/crates/render/shaders/starfield.wgsl @@ -109,8 +109,8 @@ fn vertex_main( // Starfield sprites may not be animated - let i = sprites.data[global.starfield_sprite.x].first_frame; - let t = atlas.data[i]; + let i = sprites[global.starfield_sprite.x].first_frame; + let t = atlas[i]; out.texture_index = u32(t.atlas_texture); out.texture_coords = vec2(t.xpos, t.ypos); if vertex.texture_coords.x == 1.0 { diff --git a/crates/render/shaders/ui.wgsl b/crates/render/shaders/ui.wgsl index 1f80f89..8a5b870 100644 --- a/crates/render/shaders/ui.wgsl +++ b/crates/render/shaders/ui.wgsl @@ -48,7 +48,7 @@ fn vertex_main( out.color_transform = instance.color_transform; // Pick texture frame - let t = atlas.data[animate(instance, global.current_time.x)]; + let t = atlas[animate(instance, global.current_time.x)]; out.texture_index = u32(t.atlas_texture); out.texture_coords = vec2(t.xpos, t.ypos); if vertex.texture_coords.x == 1.0 { diff --git a/crates/render/src/globaluniform/atlascontent.rs b/crates/render/src/globaluniform/atlas.rs similarity index 69% rename from crates/render/src/globaluniform/atlascontent.rs rename to crates/render/src/globaluniform/atlas.rs index 4d7e8e9..d973bd2 100644 --- a/crates/render/src/globaluniform/atlascontent.rs +++ b/crates/render/src/globaluniform/atlas.rs @@ -19,12 +19,12 @@ pub struct ImageLocation { } #[derive(Debug, Copy, Clone)] -pub struct ImageLocationArray { +pub struct AtlasArray { pub data: [ImageLocation; IMAGE_LIMIT as usize], } -unsafe impl Pod for ImageLocationArray {} -unsafe impl Zeroable for ImageLocationArray { +unsafe impl Pod for AtlasArray {} +unsafe impl Zeroable for AtlasArray { fn zeroed() -> Self { Self { data: [ImageLocation::zeroed(); IMAGE_LIMIT as usize], @@ -32,18 +32,12 @@ unsafe impl Zeroable for ImageLocationArray { } } -impl Default for ImageLocationArray { +impl Default for AtlasArray { fn default() -> Self { Self::zeroed() } } -#[repr(C)] -#[derive(Debug, Copy, Clone, Default, Pod, Zeroable)] -pub struct AtlasContent { - pub data: ImageLocationArray, -} - -impl AtlasContent { +impl AtlasArray { pub const SIZE: u64 = mem::size_of::() as wgpu::BufferAddress; } diff --git a/crates/render/src/globaluniform/datacontent.rs b/crates/render/src/globaluniform/data.rs similarity index 100% rename from crates/render/src/globaluniform/datacontent.rs rename to crates/render/src/globaluniform/data.rs diff --git a/crates/render/src/globaluniform/globaluniform.rs b/crates/render/src/globaluniform/globaluniform.rs index ce24071..9e2aad2 100644 --- a/crates/render/src/globaluniform/globaluniform.rs +++ b/crates/render/src/globaluniform/globaluniform.rs @@ -1,12 +1,13 @@ -use galactica_constants::{IMAGE_LIMIT, SPRITE_LIMIT}; +use galactica_constants::{IMAGE_LIMIT, OBJECT_SPRITE_INSTANCE_LIMIT, SPRITE_LIMIT}; use wgpu; -use super::{AtlasContent, DataContent, SpriteContent}; +use super::{object::ObjectLocationArray, AtlasArray, DataContent, SpriteDataArray}; pub struct GlobalUniform { pub data_buffer: wgpu::Buffer, pub atlas_buffer: wgpu::Buffer, pub sprite_buffer: wgpu::Buffer, + pub object_buffer: wgpu::Buffer, pub bind_group: wgpu::BindGroup, pub bind_group_layout: wgpu::BindGroupLayout, pub content: DataContent, @@ -16,6 +17,7 @@ impl GlobalUniform { pub fn shader_header(&self, group: u32) -> String { let mut out = String::new(); + // Global game data out.push_str(&format!("@group({group}) @binding(0)\n")); out.push_str( r#" @@ -35,10 +37,16 @@ impl GlobalUniform { ); out.push_str("\n"); - out.push_str(&format!("@group({group}) @binding(1)\n")); + // Atlas image locations + out.push_str(&format!( + r#" + @group({group}) @binding(1) + var atlas: array; + "# + )); + out.push_str("\n"); out.push_str( r#" - var atlas: AtlasUniform; struct ImageLocation { xpos: f32, ypos: f32, @@ -53,14 +61,6 @@ impl GlobalUniform { }; "#, ); - out.push_str(&format!( - r#" - struct AtlasUniform {{ - data: array, - }}; - "#, - IMAGE_LIMIT, - )); out.push_str("\n"); // TODO: document @@ -69,10 +69,16 @@ impl GlobalUniform { // `Buffer is bound with size 3456 where the shader expects 5184 in group[1] compact index 2` // More notes are in datacontent - out.push_str(&format!("@group({group}) @binding(2)\n")); + // Sprite data + out.push_str(&format!( + r#" + @group({group}) @binding(2) + var sprites: array; + "# + )); + out.push_str("\n"); out.push_str( r#" - var sprites: SpriteUniform; struct SpriteData { frame_count: u32, repeatmode: u32, @@ -86,15 +92,32 @@ impl GlobalUniform { }; "#, ); + out.push_str("\n"); + + // Object location data out.push_str(&format!( r#" - struct SpriteUniform {{ - data: array, - }}; - "#, - SPRITE_LIMIT, + @group({group}) @binding(3) + var objects: array; + "# )); out.push_str("\n"); + out.push_str( + r#" + struct ObjectLocation { + xpos: f32, + ypos: f32, + zpos: f32, + angle: f32, + size: f32, + + padding_a: f32, + padding_b: f32, + padding_c: f32, + }; + "#, + ); + out.push_str("\n"); return out; } @@ -109,14 +132,21 @@ impl GlobalUniform { let atlas_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("global uniform atlas buffer"), - size: AtlasContent::SIZE, + size: AtlasArray::SIZE, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let sprite_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("global uniform sprite buffer"), - size: SpriteContent::SIZE, + size: SpriteDataArray::SIZE, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let object_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("global uniform object buffer"), + size: ObjectLocationArray::SIZE, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); @@ -153,6 +183,16 @@ impl GlobalUniform { }, count: None, }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, ], label: Some("global uniform bind group layout"), }); @@ -172,6 +212,10 @@ impl GlobalUniform { binding: 2, resource: sprite_buffer.as_entire_binding(), }, + wgpu::BindGroupEntry { + binding: 3, + resource: object_buffer.as_entire_binding(), + }, ], label: Some("global uniform bind group"), }); @@ -180,6 +224,7 @@ impl GlobalUniform { data_buffer, atlas_buffer, sprite_buffer, + object_buffer, bind_group, bind_group_layout, content: DataContent::default(), diff --git a/crates/render/src/globaluniform/mod.rs b/crates/render/src/globaluniform/mod.rs index 6454b45..468fc7c 100644 --- a/crates/render/src/globaluniform/mod.rs +++ b/crates/render/src/globaluniform/mod.rs @@ -1,9 +1,11 @@ -mod atlascontent; -mod datacontent; +mod atlas; +mod data; mod globaluniform; -mod spritecontent; +mod object; +mod sprite; -pub use atlascontent::{AtlasContent, ImageLocation, ImageLocationArray}; -pub use datacontent::DataContent; +pub use atlas::{AtlasArray, ImageLocation}; +pub use data::DataContent; pub use globaluniform::GlobalUniform; -pub use spritecontent::{SpriteContent, SpriteData, SpriteDataArray}; +pub use object::ObjectLocation; +pub use sprite::{SpriteData, SpriteDataArray}; diff --git a/crates/render/src/globaluniform/object.rs b/crates/render/src/globaluniform/object.rs new file mode 100644 index 0000000..70f291c --- /dev/null +++ b/crates/render/src/globaluniform/object.rs @@ -0,0 +1,44 @@ +use bytemuck::{Pod, Zeroable}; +use galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT; +use std::mem; +use wgpu; + +#[repr(C)] +#[derive(Debug, Copy, Clone, Pod, Zeroable, Default)] +pub struct ObjectLocation { + pub xpos: f32, + pub ypos: f32, + pub zpos: f32, + pub angle: f32, + pub size: f32, + + pub _padding: [f32; 3], +} + +impl ObjectLocation { + pub const SIZE: u64 = mem::size_of::() as wgpu::BufferAddress; +} + +#[derive(Debug, Copy, Clone)] +pub struct ObjectLocationArray { + pub data: [ObjectLocation; OBJECT_SPRITE_INSTANCE_LIMIT as usize], +} + +unsafe impl Pod for ObjectLocationArray {} +unsafe impl Zeroable for ObjectLocationArray { + fn zeroed() -> Self { + Self { + data: [ObjectLocation::zeroed(); OBJECT_SPRITE_INSTANCE_LIMIT as usize], + } + } +} + +impl Default for ObjectLocationArray { + fn default() -> Self { + Self::zeroed() + } +} + +impl ObjectLocationArray { + pub const SIZE: u64 = mem::size_of::() as wgpu::BufferAddress; +} diff --git a/crates/render/src/globaluniform/spritecontent.rs b/crates/render/src/globaluniform/sprite.rs similarity index 85% rename from crates/render/src/globaluniform/spritecontent.rs rename to crates/render/src/globaluniform/sprite.rs index 59e034d..8317962 100644 --- a/crates/render/src/globaluniform/spritecontent.rs +++ b/crates/render/src/globaluniform/sprite.rs @@ -38,12 +38,6 @@ impl Default for SpriteDataArray { } } -#[repr(C)] -#[derive(Debug, Copy, Clone, Default, Pod, Zeroable)] -pub struct SpriteContent { - pub data: SpriteDataArray, -} - -impl SpriteContent { +impl SpriteDataArray { pub const SIZE: u64 = mem::size_of::() as wgpu::BufferAddress; } diff --git a/crates/render/src/gpustate.rs b/crates/render/src/gpustate.rs index c232399..9da3ea3 100644 --- a/crates/render/src/gpustate.rs +++ b/crates/render/src/gpustate.rs @@ -1,6 +1,6 @@ use anyhow::Result; use bytemuck; -use cgmath::{Deg, EuclideanSpace, Matrix2, Matrix4, Point2, Vector3}; +use cgmath::{EuclideanSpace, Matrix2, Matrix4, Point2, Rad, Vector3}; use galactica_constants; use rand::seq::SliceRandom; use std::{iter, rc::Rc}; @@ -9,7 +9,7 @@ use winit::{self, dpi::LogicalSize, window::Window}; use crate::{ content, - globaluniform::{AtlasContent, DataContent, GlobalUniform, SpriteContent}, + globaluniform::{DataContent, GlobalUniform, ObjectLocation}, pipeline::PipelineBuilder, sprite::ObjectSubSprite, starfield::Starfield, @@ -322,12 +322,11 @@ impl GPUState { }; // Game dimensions of this sprite post-scale. - // Don't divide by 2, we use this later. - let height = s.size / s.pos.z; - - // Width or height, whichever is larger. - // Accounts for sprite rotation. - let m = height * s.sprite.aspect.max(1.0); + // Post-scale width or height, whichever is larger. + // This is in game units. + // + // We take the maximum to account for rotated sprites. + let m = (s.size / s.pos.z) * s.sprite.aspect.max(1.0); // Don't draw (or compute matrices for) // sprites that are off the screen @@ -339,51 +338,27 @@ impl GPUState { return; } - // TODO: clean up - let scale = height / state.camera_zoom; - - // Note that our mesh starts centered at (0, 0). - // This is essential---we do not want scale and rotation - // changing our sprite's position! - - // Apply sprite aspect ratio, preserving height. - // This must be done *before* rotation. - // - // We apply the provided scale here as well as a minor optimization - let sprite_aspect_and_scale = - Matrix4::from_nonuniform_scale(s.sprite.aspect * scale, scale, 1.0); - - // Apply rotation - let rotate = Matrix4::from_angle_z(s.angle); - - // Apply screen aspect ratio, again preserving height. - // This must be done AFTER rotation... think about it! - let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0); - - // After finishing all ops, translate. - // This must be done last, all other operations - // require us to be at (0, 0). - // - // Note that we divide camera zoom by two. - // THIS IS IMPORTANT! - // The height of the viewport is `zoom` in game units, - // but it's 2 in screen units! (since coordinates range from -1 to 1) - let translate = Matrix4::from_translation(Vector3 { - x: pos.x / (state.camera_zoom / 2.0) / self.window_aspect, - y: pos.y / (state.camera_zoom / 2.0), - z: 0.0, - }); - - // Order matters! - // The rightmost matrix is applied first. - let t = - OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * sprite_aspect_and_scale; + // Write this object's location data + self.queue.write_buffer( + &self.global_uniform.object_buffer, + ObjectLocation::SIZE * instances.len() as u64, + bytemuck::cast_slice(&[ObjectLocation { + xpos: s.pos.x, + ypos: s.pos.y, + zpos: s.pos.z, + angle: Rad::from(s.angle).0, + size: s.size, + _padding: Default::default(), + }]), + ); + // Push this object's instance instances.push(ObjectInstance { - transform: t.into(), sprite_index: s.sprite.get_index(), + object_index: instances.len() as u32, }); - + } + /* // Add children if let Some(children) = &s.children { for c in children { @@ -433,6 +408,7 @@ impl GPUState { sprite_index: s.sprite.get_index(), }); } + */ /// Create a ObjectInstance for a ui sprite and add it to `instances` fn push_ui_sprite(&self, instances: &mut Vec, s: &UiSprite) { @@ -551,16 +527,12 @@ impl GPUState { self.queue.write_buffer( &self.global_uniform.atlas_buffer, 0, - bytemuck::cast_slice(&[AtlasContent { - data: self.texture_array.image_locations, - }]), + bytemuck::cast_slice(&[self.texture_array.image_locations]), ); self.queue.write_buffer( &self.global_uniform.sprite_buffer, 0, - bytemuck::cast_slice(&[SpriteContent { - data: self.texture_array.sprite_data, - }]), + bytemuck::cast_slice(&[self.texture_array.sprite_data]), ); self.update_starfield_buffer(); @@ -601,6 +573,7 @@ impl GPUState { }); let s = state.content.get_starfield_handle(); + // Update global values self.queue.write_buffer( &self.global_uniform.data_buffer, diff --git a/crates/render/src/texturearray.rs b/crates/render/src/texturearray.rs index 1da041a..0610245 100644 --- a/crates/render/src/texturearray.rs +++ b/crates/render/src/texturearray.rs @@ -1,6 +1,6 @@ use crate::{ content, - globaluniform::{ImageLocation, ImageLocationArray, SpriteData, SpriteDataArray}, + globaluniform::{AtlasArray, ImageLocation, SpriteData, SpriteDataArray}, }; use anyhow::Result; use bytemuck::Zeroable; @@ -86,7 +86,7 @@ pub struct Texture { pub struct TextureArray { pub bind_group: wgpu::BindGroup, pub bind_group_layout: BindGroupLayout, - pub image_locations: ImageLocationArray, + pub image_locations: AtlasArray, pub sprite_data: SpriteDataArray, } @@ -109,7 +109,7 @@ impl TextureArray { )?); } - let mut image_locations = ImageLocationArray::zeroed(); + let mut image_locations = AtlasArray::zeroed(); let mut sprite_data = SpriteDataArray::zeroed(); println!("sending to gpu"); diff --git a/crates/render/src/vertexbuffer/types.rs b/crates/render/src/vertexbuffer/types.rs index 18ffa8a..17924ba 100644 --- a/crates/render/src/vertexbuffer/types.rs +++ b/crates/render/src/vertexbuffer/types.rs @@ -84,12 +84,11 @@ impl BufferObject for StarfieldInstance { #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct ObjectInstance { - /// Extra transformations this sprite - /// (rotation, etc) - pub transform: [[f32; 4]; 4], - - /// What texture to use for this sprite + /// What texture to use for this instance pub sprite_index: u32, + + /// Which object this instance is for + pub object_index: u32, } impl BufferObject for ObjectInstance { @@ -101,31 +100,16 @@ impl BufferObject for ObjectInstance { // instance when the shader starts processing a new instance step_mode: wgpu::VertexStepMode::Instance, attributes: &[ - // 4 arrays = 1 4x4 matrix + // Sprite wgpu::VertexAttribute { offset: 0, shader_location: 2, - format: wgpu::VertexFormat::Float32x4, + format: wgpu::VertexFormat::Uint32, }, + // Object wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, + offset: mem::size_of::<[f32; 1]>() as wgpu::BufferAddress, shader_location: 3, - format: wgpu::VertexFormat::Float32x4, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, - shader_location: 4, - format: wgpu::VertexFormat::Float32x4, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress, - shader_location: 5, - format: wgpu::VertexFormat::Float32x4, - }, - // Sprite - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress, - shader_location: 6, format: wgpu::VertexFormat::Uint32, }, ],