Added object state uniform, moved transform logic

master
Mark 2024-01-05 19:56:26 -08:00
parent 7565f09e3b
commit 0e692feb66
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
14 changed files with 216 additions and 151 deletions

View File

@ -5,9 +5,9 @@
fn animate(instance: InstanceInput, age: f32) -> u32 { fn animate(instance: InstanceInput, age: f32) -> u32 {
let idx = instance.texture_index; let idx = instance.texture_index;
let len = sprites.data[idx].frame_count; let len = sprites[idx].frame_count;
let rep = sprites.data[idx].repeatmode; let rep = sprites[idx].repeatmode;
let fps = sprites.data[idx].fps; let fps = sprites[idx].fps;
var frame: u32 = u32(0); 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;
} }

View File

@ -1,11 +1,8 @@
// INCLUDE: global uniform header // INCLUDE: global uniform header
struct InstanceInput { struct InstanceInput {
@location(2) transform_matrix_0: vec4<f32>, @location(2) texture_index: u32,
@location(3) transform_matrix_1: vec4<f32>, @location(3) object_index: u32,
@location(4) transform_matrix_2: vec4<f32>,
@location(5) transform_matrix_3: vec4<f32>,
@location(6) texture_index: u32,
}; };
struct VertexInput { struct VertexInput {
@ -28,23 +25,55 @@ var sampler_array: binding_array<sampler>;
// INCLUDE: animate.wgsl // INCLUDE: animate.wgsl
fn transform_vertex(obj: ObjectLocation, vertex: VertexInput, instance: InstanceInput) -> vec4<f32> {
// 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<f32> = 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<f32>(pos, 0.0, 1.0);;
}
@vertex @vertex
fn vertex_main( fn vertex_main(
vertex: VertexInput, vertex: VertexInput,
instance: InstanceInput, instance: InstanceInput,
) -> VertexOutput { ) -> VertexOutput {
let transform = mat4x4<f32>(
instance.transform_matrix_0,
instance.transform_matrix_1,
instance.transform_matrix_2,
instance.transform_matrix_3,
);
var out: VertexOutput; var out: VertexOutput;
out.position = transform * vec4<f32>(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_index = t.atlas_texture;
out.texture_coords = vec2(t.xpos, t.ypos); out.texture_coords = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 { if vertex.texture_coords.x == 1.0 {

View File

@ -55,7 +55,7 @@ fn vertex_main(
var scale: f32 = instance.size / global.camera_zoom.x; var scale: f32 = instance.size / global.camera_zoom.x;
var pos: vec2<f32> = vec2(vertex.position.x, vertex.position.y); var pos: vec2<f32> = vec2(vertex.position.x, vertex.position.y);
pos = pos * vec2<f32>( pos = pos * vec2<f32>(
sprites.data[instance.texture_index].aspect * scale / global.window_aspect.x, sprites[instance.texture_index].aspect * scale / global.window_aspect.x,
scale scale
); );
@ -76,7 +76,7 @@ fn vertex_main(
// Compute texture coordinates // 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_index = u32(t.atlas_texture);
out.texture_coords = vec2(t.xpos, t.ypos); out.texture_coords = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 { if vertex.texture_coords.x == 1.0 {

View File

@ -109,8 +109,8 @@ fn vertex_main(
// Starfield sprites may not be animated // Starfield sprites may not be animated
let i = sprites.data[global.starfield_sprite.x].first_frame; let i = sprites[global.starfield_sprite.x].first_frame;
let t = atlas.data[i]; let t = atlas[i];
out.texture_index = u32(t.atlas_texture); out.texture_index = u32(t.atlas_texture);
out.texture_coords = vec2(t.xpos, t.ypos); out.texture_coords = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 { if vertex.texture_coords.x == 1.0 {

View File

@ -48,7 +48,7 @@ fn vertex_main(
out.color_transform = instance.color_transform; out.color_transform = instance.color_transform;
// Pick texture frame // 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_index = u32(t.atlas_texture);
out.texture_coords = vec2(t.xpos, t.ypos); out.texture_coords = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 { if vertex.texture_coords.x == 1.0 {

View File

@ -19,12 +19,12 @@ pub struct ImageLocation {
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct ImageLocationArray { pub struct AtlasArray {
pub data: [ImageLocation; IMAGE_LIMIT as usize], pub data: [ImageLocation; IMAGE_LIMIT as usize],
} }
unsafe impl Pod for ImageLocationArray {} unsafe impl Pod for AtlasArray {}
unsafe impl Zeroable for ImageLocationArray { unsafe impl Zeroable for AtlasArray {
fn zeroed() -> Self { fn zeroed() -> Self {
Self { Self {
data: [ImageLocation::zeroed(); IMAGE_LIMIT as usize], 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 { fn default() -> Self {
Self::zeroed() Self::zeroed()
} }
} }
#[repr(C)] impl AtlasArray {
#[derive(Debug, Copy, Clone, Default, Pod, Zeroable)]
pub struct AtlasContent {
pub data: ImageLocationArray,
}
impl AtlasContent {
pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress; pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
} }

View File

@ -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 wgpu;
use super::{AtlasContent, DataContent, SpriteContent}; use super::{object::ObjectLocationArray, AtlasArray, DataContent, SpriteDataArray};
pub struct GlobalUniform { pub struct GlobalUniform {
pub data_buffer: wgpu::Buffer, pub data_buffer: wgpu::Buffer,
pub atlas_buffer: wgpu::Buffer, pub atlas_buffer: wgpu::Buffer,
pub sprite_buffer: wgpu::Buffer, pub sprite_buffer: wgpu::Buffer,
pub object_buffer: wgpu::Buffer,
pub bind_group: wgpu::BindGroup, pub bind_group: wgpu::BindGroup,
pub bind_group_layout: wgpu::BindGroupLayout, pub bind_group_layout: wgpu::BindGroupLayout,
pub content: DataContent, pub content: DataContent,
@ -16,6 +17,7 @@ impl GlobalUniform {
pub fn shader_header(&self, group: u32) -> String { pub fn shader_header(&self, group: u32) -> String {
let mut out = String::new(); let mut out = String::new();
// Global game data
out.push_str(&format!("@group({group}) @binding(0)\n")); out.push_str(&format!("@group({group}) @binding(0)\n"));
out.push_str( out.push_str(
r#" r#"
@ -35,10 +37,16 @@ impl GlobalUniform {
); );
out.push_str("\n"); 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<uniform> atlas: array<ImageLocation, {IMAGE_LIMIT}>;
"#
));
out.push_str("\n");
out.push_str( out.push_str(
r#" r#"
var<uniform> atlas: AtlasUniform;
struct ImageLocation { struct ImageLocation {
xpos: f32, xpos: f32,
ypos: f32, ypos: f32,
@ -53,14 +61,6 @@ impl GlobalUniform {
}; };
"#, "#,
); );
out.push_str(&format!(
r#"
struct AtlasUniform {{
data: array<ImageLocation, {}>,
}};
"#,
IMAGE_LIMIT,
));
out.push_str("\n"); out.push_str("\n");
// TODO: document // 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` // `Buffer is bound with size 3456 where the shader expects 5184 in group[1] compact index 2`
// More notes are in datacontent // 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<uniform> sprites: array<SpriteData, {SPRITE_LIMIT}>;
"#
));
out.push_str("\n");
out.push_str( out.push_str(
r#" r#"
var<uniform> sprites: SpriteUniform;
struct SpriteData { struct SpriteData {
frame_count: u32, frame_count: u32,
repeatmode: u32, repeatmode: u32,
@ -86,15 +92,32 @@ impl GlobalUniform {
}; };
"#, "#,
); );
out.push_str("\n");
// Object location data
out.push_str(&format!( out.push_str(&format!(
r#" r#"
struct SpriteUniform {{ @group({group}) @binding(3)
data: array<SpriteData, {}>, var<uniform> objects: array<ObjectLocation, {OBJECT_SPRITE_INSTANCE_LIMIT}>;
}}; "#
"#,
SPRITE_LIMIT,
)); ));
out.push_str("\n"); 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; return out;
} }
@ -109,14 +132,21 @@ impl GlobalUniform {
let atlas_buffer = device.create_buffer(&wgpu::BufferDescriptor { let atlas_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("global uniform atlas buffer"), label: Some("global uniform atlas buffer"),
size: AtlasContent::SIZE, size: AtlasArray::SIZE,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false, mapped_at_creation: false,
}); });
let sprite_buffer = device.create_buffer(&wgpu::BufferDescriptor { let sprite_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("global uniform sprite buffer"), 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, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false, mapped_at_creation: false,
}); });
@ -153,6 +183,16 @@ impl GlobalUniform {
}, },
count: None, 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"), label: Some("global uniform bind group layout"),
}); });
@ -172,6 +212,10 @@ impl GlobalUniform {
binding: 2, binding: 2,
resource: sprite_buffer.as_entire_binding(), resource: sprite_buffer.as_entire_binding(),
}, },
wgpu::BindGroupEntry {
binding: 3,
resource: object_buffer.as_entire_binding(),
},
], ],
label: Some("global uniform bind group"), label: Some("global uniform bind group"),
}); });
@ -180,6 +224,7 @@ impl GlobalUniform {
data_buffer, data_buffer,
atlas_buffer, atlas_buffer,
sprite_buffer, sprite_buffer,
object_buffer,
bind_group, bind_group,
bind_group_layout, bind_group_layout,
content: DataContent::default(), content: DataContent::default(),

View File

@ -1,9 +1,11 @@
mod atlascontent; mod atlas;
mod datacontent; mod data;
mod globaluniform; mod globaluniform;
mod spritecontent; mod object;
mod sprite;
pub use atlascontent::{AtlasContent, ImageLocation, ImageLocationArray}; pub use atlas::{AtlasArray, ImageLocation};
pub use datacontent::DataContent; pub use data::DataContent;
pub use globaluniform::GlobalUniform; pub use globaluniform::GlobalUniform;
pub use spritecontent::{SpriteContent, SpriteData, SpriteDataArray}; pub use object::ObjectLocation;
pub use sprite::{SpriteData, SpriteDataArray};

View File

@ -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::<Self>() 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::<Self>() as wgpu::BufferAddress;
}

View File

@ -38,12 +38,6 @@ impl Default for SpriteDataArray {
} }
} }
#[repr(C)] impl SpriteDataArray {
#[derive(Debug, Copy, Clone, Default, Pod, Zeroable)]
pub struct SpriteContent {
pub data: SpriteDataArray,
}
impl SpriteContent {
pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress; pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
} }

View File

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use bytemuck; use bytemuck;
use cgmath::{Deg, EuclideanSpace, Matrix2, Matrix4, Point2, Vector3}; use cgmath::{EuclideanSpace, Matrix2, Matrix4, Point2, Rad, Vector3};
use galactica_constants; use galactica_constants;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use std::{iter, rc::Rc}; use std::{iter, rc::Rc};
@ -9,7 +9,7 @@ use winit::{self, dpi::LogicalSize, window::Window};
use crate::{ use crate::{
content, content,
globaluniform::{AtlasContent, DataContent, GlobalUniform, SpriteContent}, globaluniform::{DataContent, GlobalUniform, ObjectLocation},
pipeline::PipelineBuilder, pipeline::PipelineBuilder,
sprite::ObjectSubSprite, sprite::ObjectSubSprite,
starfield::Starfield, starfield::Starfield,
@ -322,12 +322,11 @@ impl GPUState {
}; };
// Game dimensions of this sprite post-scale. // Game dimensions of this sprite post-scale.
// Don't divide by 2, we use this later. // Post-scale width or height, whichever is larger.
let height = s.size / s.pos.z; // This is in game units.
//
// Width or height, whichever is larger. // We take the maximum to account for rotated sprites.
// Accounts for sprite rotation. let m = (s.size / s.pos.z) * s.sprite.aspect.max(1.0);
let m = height * s.sprite.aspect.max(1.0);
// Don't draw (or compute matrices for) // Don't draw (or compute matrices for)
// sprites that are off the screen // sprites that are off the screen
@ -339,51 +338,27 @@ impl GPUState {
return; return;
} }
// TODO: clean up // Write this object's location data
let scale = height / state.camera_zoom; self.queue.write_buffer(
&self.global_uniform.object_buffer,
// Note that our mesh starts centered at (0, 0). ObjectLocation::SIZE * instances.len() as u64,
// This is essential---we do not want scale and rotation bytemuck::cast_slice(&[ObjectLocation {
// changing our sprite's position! xpos: s.pos.x,
ypos: s.pos.y,
// Apply sprite aspect ratio, preserving height. zpos: s.pos.z,
// This must be done *before* rotation. angle: Rad::from(s.angle).0,
// size: s.size,
// We apply the provided scale here as well as a minor optimization _padding: Default::default(),
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;
// Push this object's instance
instances.push(ObjectInstance { instances.push(ObjectInstance {
transform: t.into(),
sprite_index: s.sprite.get_index(), sprite_index: s.sprite.get_index(),
object_index: instances.len() as u32,
}); });
}
/*
// Add children // Add children
if let Some(children) = &s.children { if let Some(children) = &s.children {
for c in children { for c in children {
@ -433,6 +408,7 @@ impl GPUState {
sprite_index: s.sprite.get_index(), sprite_index: s.sprite.get_index(),
}); });
} }
*/
/// Create a ObjectInstance for a ui sprite and add it to `instances` /// Create a ObjectInstance for a ui sprite and add it to `instances`
fn push_ui_sprite(&self, instances: &mut Vec<UiInstance>, s: &UiSprite) { fn push_ui_sprite(&self, instances: &mut Vec<UiInstance>, s: &UiSprite) {
@ -551,16 +527,12 @@ impl GPUState {
self.queue.write_buffer( self.queue.write_buffer(
&self.global_uniform.atlas_buffer, &self.global_uniform.atlas_buffer,
0, 0,
bytemuck::cast_slice(&[AtlasContent { bytemuck::cast_slice(&[self.texture_array.image_locations]),
data: self.texture_array.image_locations,
}]),
); );
self.queue.write_buffer( self.queue.write_buffer(
&self.global_uniform.sprite_buffer, &self.global_uniform.sprite_buffer,
0, 0,
bytemuck::cast_slice(&[SpriteContent { bytemuck::cast_slice(&[self.texture_array.sprite_data]),
data: self.texture_array.sprite_data,
}]),
); );
self.update_starfield_buffer(); self.update_starfield_buffer();
@ -601,6 +573,7 @@ impl GPUState {
}); });
let s = state.content.get_starfield_handle(); let s = state.content.get_starfield_handle();
// Update global values // Update global values
self.queue.write_buffer( self.queue.write_buffer(
&self.global_uniform.data_buffer, &self.global_uniform.data_buffer,

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
content, content,
globaluniform::{ImageLocation, ImageLocationArray, SpriteData, SpriteDataArray}, globaluniform::{AtlasArray, ImageLocation, SpriteData, SpriteDataArray},
}; };
use anyhow::Result; use anyhow::Result;
use bytemuck::Zeroable; use bytemuck::Zeroable;
@ -86,7 +86,7 @@ pub struct Texture {
pub struct TextureArray { pub struct TextureArray {
pub bind_group: wgpu::BindGroup, pub bind_group: wgpu::BindGroup,
pub bind_group_layout: BindGroupLayout, pub bind_group_layout: BindGroupLayout,
pub image_locations: ImageLocationArray, pub image_locations: AtlasArray,
pub sprite_data: SpriteDataArray, 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(); let mut sprite_data = SpriteDataArray::zeroed();
println!("sending to gpu"); println!("sending to gpu");

View File

@ -84,12 +84,11 @@ impl BufferObject for StarfieldInstance {
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ObjectInstance { pub struct ObjectInstance {
/// Extra transformations this sprite /// What texture to use for this instance
/// (rotation, etc)
pub transform: [[f32; 4]; 4],
/// What texture to use for this sprite
pub sprite_index: u32, pub sprite_index: u32,
/// Which object this instance is for
pub object_index: u32,
} }
impl BufferObject for ObjectInstance { impl BufferObject for ObjectInstance {
@ -101,31 +100,16 @@ impl BufferObject for ObjectInstance {
// instance when the shader starts processing a new instance // instance when the shader starts processing a new instance
step_mode: wgpu::VertexStepMode::Instance, step_mode: wgpu::VertexStepMode::Instance,
attributes: &[ attributes: &[
// 4 arrays = 1 4x4 matrix // Sprite
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: 0, offset: 0,
shader_location: 2, shader_location: 2,
format: wgpu::VertexFormat::Float32x4, format: wgpu::VertexFormat::Uint32,
}, },
// Object
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 1]>() as wgpu::BufferAddress,
shader_location: 3, 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, format: wgpu::VertexFormat::Uint32,
}, },
], ],