From a4ca62e1dc4d493e32fd251438da4ac179fc8b5e Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 4 Jan 2024 18:15:30 -0800 Subject: [PATCH] Added sprite data uniform --- crates/content/src/handle.rs | 6 +- crates/content/src/lib.rs | 2 +- crates/content/src/part/sprite.rs | 4 +- crates/galactica/src/game.rs | 1 + crates/render/shaders/object.wgsl | 3 +- crates/render/shaders/particle.wgsl | 21 ++-- crates/render/shaders/starfield.wgsl | 3 +- crates/render/shaders/ui.wgsl | 3 +- .../render/src/globaluniform/atlascontent.rs | 21 ++-- .../render/src/globaluniform/globaluniform.rs | 59 +++++++++++- crates/render/src/globaluniform/mod.rs | 4 +- .../render/src/globaluniform/spritecontent.rs | 48 ++++++++++ crates/render/src/gpustate.rs | 95 +++++++++---------- crates/render/src/renderstate.rs | 4 + crates/render/src/texturearray.rs | 64 +++++-------- crates/render/src/vertexbuffer/types.rs | 15 +-- 16 files changed, 219 insertions(+), 134 deletions(-) create mode 100644 crates/render/src/globaluniform/spritecontent.rs diff --git a/crates/content/src/handle.rs b/crates/content/src/handle.rs index 715636d..be0fdf3 100644 --- a/crates/content/src/handle.rs +++ b/crates/content/src/handle.rs @@ -11,7 +11,11 @@ use std::{cmp::Eq, hash::Hash}; #[derive(Debug, Clone, Copy)] pub struct SpriteHandle { /// The index of this sprite in content.sprites - pub(crate) index: usize, + /// This must be public, since render uses this to + /// select sprites. + /// + /// This is a u32 for that same reason, too. + pub index: u32, /// The aspect ratio of this sprite (width / height) pub aspect: f32, diff --git a/crates/content/src/lib.rs b/crates/content/src/lib.rs index af7f2fd..0d6eec6 100644 --- a/crates/content/src/lib.rs +++ b/crates/content/src/lib.rs @@ -250,7 +250,7 @@ impl Content { pub fn get_sprite(&self, h: SpriteHandle) -> &Sprite { // In theory, this could fail if h has a bad index, but that shouldn't ever happen. // The only handles that exist should be created by this crate. - return &self.sprites[h.index]; + return &self.sprites[h.index as usize]; } /// Get a sprite from a path diff --git a/crates/content/src/part/sprite.rs b/crates/content/src/part/sprite.rs index f7b85a2..060cf09 100644 --- a/crates/content/src/part/sprite.rs +++ b/crates/content/src/part/sprite.rs @@ -105,7 +105,7 @@ impl crate::Build for Sprite { })?; let h = SpriteHandle { - index: ct.sprites.len(), + index: ct.sprites.len() as u32, aspect: dim.0 as f32 / dim.1 as f32, }; @@ -162,7 +162,7 @@ impl crate::Build for Sprite { let dim = dim.unwrap(); let h = SpriteHandle { - index: ct.sprites.len(), + index: ct.sprites.len() as u32, aspect: dim.0 as f32 / dim.1 as f32, }; diff --git a/crates/galactica/src/game.rs b/crates/galactica/src/game.rs index a2e148d..cc95916 100644 --- a/crates/galactica/src/game.rs +++ b/crates/galactica/src/game.rs @@ -182,6 +182,7 @@ impl Game { ui_sprites: self.get_ui_sprites(), new_particles: &mut self.new_particles, current_time: self.start_instant.elapsed().as_secs_f32(), + content: &self.content, } } diff --git a/crates/render/shaders/object.wgsl b/crates/render/shaders/object.wgsl index 86113b5..6530bff 100644 --- a/crates/render/shaders/object.wgsl +++ b/crates/render/shaders/object.wgsl @@ -44,8 +44,9 @@ fn vertex_main( var out: VertexOutput; out.position = transform * vec4(vertex.position, 1.0); + let i = sprites.data[instance.texture_index].first_frame; + let t = atlas.data[i]; out.texture_index = u32(0); - let t = atlas.texture_locations[instance.texture_index]; out.texture_coords = vec2(t.xpos, t.ypos); if vertex.texture_coords.x == 1.0 { out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y); diff --git a/crates/render/shaders/particle.wgsl b/crates/render/shaders/particle.wgsl index 2d989a6..2a6f40a 100644 --- a/crates/render/shaders/particle.wgsl +++ b/crates/render/shaders/particle.wgsl @@ -8,8 +8,7 @@ struct InstanceInput { @location(6) size: f32, @location(7) created: f32, @location(8) expires: f32, - @location(9) texture_index_len_rep: vec3, - @location(10) texture_aspect_fps: vec2, + @location(9) texture_index: u32, }; struct VertexInput { @@ -51,25 +50,29 @@ fn vertex_main( let age = global.current_time.x - instance.created; + let len = sprites.data[instance.texture_index].frame_count; + let rep = sprites.data[instance.texture_index].repeatmode; + let fps = sprites.data[instance.texture_index].fps; var frame: u32 = u32(0); - if instance.texture_index_len_rep.z == u32(1) { + if rep == u32(1) { // Repeat frame = u32(fmod( - (age / instance.texture_aspect_fps.y), - f32(instance.texture_index_len_rep.y) + (age / fps), + f32(len) )); } else { // Once frame = u32(min( - (age / instance.texture_aspect_fps.y), - f32(instance.texture_index_len_rep.y) - 1.0 + (age / fps), + f32(len) - 1.0 )); } // Pick image + frame = frame + sprites.data[instance.texture_index].first_frame; + let t = atlas.data[frame]; out.texture_index = u32(0); - let t = atlas.texture_locations[instance.texture_index_len_rep.x + frame]; out.texture_coords = vec2(t.xpos, t.ypos); if vertex.texture_coords.x == 1.0 { out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y); @@ -84,7 +87,7 @@ fn vertex_main( var pos: vec2 = vec2(vertex.position.x, vertex.position.y); pos = pos * vec2( - instance.texture_aspect_fps.x * scale / global.window_aspect.x, + sprites.data[instance.texture_index].aspect * scale / global.window_aspect.x, scale ); pos = rotation * pos; diff --git a/crates/render/shaders/starfield.wgsl b/crates/render/shaders/starfield.wgsl index 9bc09b0..d872a50 100644 --- a/crates/render/shaders/starfield.wgsl +++ b/crates/render/shaders/starfield.wgsl @@ -108,8 +108,9 @@ fn vertex_main( out.position = vec4(pos, 0.0, 1.0) * instance.position.z; + let i = sprites.data[global.starfield_sprite.x].first_frame; + let t = atlas.data[i]; out.texture_index = u32(0); - let t = atlas.texture_locations[global.starfield_texture.x]; out.texture_coords = vec2(t.xpos, t.ypos); if vertex.texture_coords.x == 1.0 { out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y); diff --git a/crates/render/shaders/ui.wgsl b/crates/render/shaders/ui.wgsl index e8467d5..765369a 100644 --- a/crates/render/shaders/ui.wgsl +++ b/crates/render/shaders/ui.wgsl @@ -46,8 +46,9 @@ fn vertex_main( out.position = transform * vec4(vertex.position, 1.0); out.color_transform = instance.color_transform; + let i = sprites.data[instance.texture_index].first_frame; + let t = atlas.data[i]; out.texture_index = u32(0); - let t = atlas.texture_locations[instance.texture_index]; out.texture_coords = vec2(t.xpos, t.ypos); if vertex.texture_coords.x == 1.0 { out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y); diff --git a/crates/render/src/globaluniform/atlascontent.rs b/crates/render/src/globaluniform/atlascontent.rs index afccd34..d5a3c86 100644 --- a/crates/render/src/globaluniform/atlascontent.rs +++ b/crates/render/src/globaluniform/atlascontent.rs @@ -5,21 +5,20 @@ use wgpu; #[repr(C)] #[derive(Debug, Copy, Clone, Pod, Zeroable, Default)] pub struct ImageLocation { - /// Format: [x, y, w, h], in texture coordinates - pub x: f32, - pub y: f32, - pub w: f32, - pub h: f32, + // Image box, in texture coordinates + pub xpos: f32, + pub ypos: f32, + pub width: f32, + pub height: f32, } #[derive(Debug, Copy, Clone)] -pub struct ImageLocations { - /// Format: [x, y, w, h], in texture coordinates +pub struct ImageLocationArray { pub data: [ImageLocation; 108], } -unsafe impl Pod for ImageLocations {} -unsafe impl Zeroable for ImageLocations { +unsafe impl Pod for ImageLocationArray {} +unsafe impl Zeroable for ImageLocationArray { fn zeroed() -> Self { Self { data: [ImageLocation::zeroed(); 108], @@ -27,7 +26,7 @@ unsafe impl Zeroable for ImageLocations { } } -impl Default for ImageLocations { +impl Default for ImageLocationArray { fn default() -> Self { Self::zeroed() } @@ -36,7 +35,7 @@ impl Default for ImageLocations { #[repr(C)] #[derive(Debug, Copy, Clone, Default, Pod, Zeroable)] pub struct AtlasContent { - pub locations: ImageLocations, + pub data: ImageLocationArray, } impl AtlasContent { diff --git a/crates/render/src/globaluniform/globaluniform.rs b/crates/render/src/globaluniform/globaluniform.rs index 57ac422..3477ec3 100644 --- a/crates/render/src/globaluniform/globaluniform.rs +++ b/crates/render/src/globaluniform/globaluniform.rs @@ -1,10 +1,11 @@ use wgpu; -use super::{AtlasContent, DataContent}; +use super::{AtlasContent, DataContent, SpriteContent}; pub struct GlobalUniform { pub data_buffer: wgpu::Buffer, pub atlas_buffer: wgpu::Buffer, + pub sprite_buffer: wgpu::Buffer, pub bind_group: wgpu::BindGroup, pub bind_group_layout: wgpu::BindGroupLayout, pub content: DataContent, @@ -24,7 +25,7 @@ impl GlobalUniform { camera_zoom_limits: vec2, window_size: vec2, window_aspect: vec2, - starfield_texture: vec2, + starfield_sprite: vec2, starfield_tile_size: vec2, starfield_size_limits: vec2, current_time: vec2, @@ -37,14 +38,42 @@ impl GlobalUniform { out.push_str( r#" var atlas: AtlasUniform; - struct TextureLocation { + struct ImageLocation { xpos: f32, ypos: f32, width: f32, height: f32, }; struct AtlasUniform { - texture_locations: array, + data: array, + }; + "#, + ); + out.push_str("\n"); + + // TODO: document + // wgpu uniforms require constant item sizes. + // if you get an error like the following,check! + // `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")); + out.push_str( + r#" + var sprites: SpriteUniform; + struct SpriteData { + frame_count: u32, + repeatmode: u32, + aspect: f32, + fps: f32, + first_frame: u32, + + padding_a: f32, + padding_b: f32, + padding_c: f32, + }; + struct SpriteUniform { + data: array, }; "#, ); @@ -68,6 +97,13 @@ impl GlobalUniform { mapped_at_creation: false, }); + let sprite_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("global uniform sprite buffer"), + size: SpriteContent::SIZE, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ wgpu::BindGroupLayoutEntry { @@ -90,6 +126,16 @@ impl GlobalUniform { }, count: None, }, + wgpu::BindGroupLayoutEntry { + binding: 2, + 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"), }); @@ -105,6 +151,10 @@ impl GlobalUniform { binding: 1, resource: atlas_buffer.as_entire_binding(), }, + wgpu::BindGroupEntry { + binding: 2, + resource: sprite_buffer.as_entire_binding(), + }, ], label: Some("global uniform bind group"), }); @@ -112,6 +162,7 @@ impl GlobalUniform { return Self { data_buffer, atlas_buffer, + sprite_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 efb5be4..6454b45 100644 --- a/crates/render/src/globaluniform/mod.rs +++ b/crates/render/src/globaluniform/mod.rs @@ -1,7 +1,9 @@ mod atlascontent; mod datacontent; mod globaluniform; +mod spritecontent; -pub use atlascontent::{AtlasContent, ImageLocation, ImageLocations}; +pub use atlascontent::{AtlasContent, ImageLocation, ImageLocationArray}; pub use datacontent::DataContent; pub use globaluniform::GlobalUniform; +pub use spritecontent::{SpriteContent, SpriteData, SpriteDataArray}; diff --git a/crates/render/src/globaluniform/spritecontent.rs b/crates/render/src/globaluniform/spritecontent.rs new file mode 100644 index 0000000..f724c89 --- /dev/null +++ b/crates/render/src/globaluniform/spritecontent.rs @@ -0,0 +1,48 @@ +use bytemuck::{Pod, Zeroable}; +use std::mem; +use wgpu; + +#[repr(C)] +#[derive(Debug, Copy, Clone, Pod, Zeroable, Default)] +pub struct SpriteData { + // Animation parameters + pub frame_count: u32, + pub repeatmode: u32, + pub aspect: f32, + pub fps: f32, + + // Index of first frame in ImageLocationArray + pub first_frame: u32, + // stride must be a multiple of 16 + pub _padding: [f32; 3], +} + +#[derive(Debug, Copy, Clone)] +pub struct SpriteDataArray { + pub data: [SpriteData; 108], +} + +unsafe impl Pod for SpriteDataArray {} +unsafe impl Zeroable for SpriteDataArray { + fn zeroed() -> Self { + Self { + data: [SpriteData::zeroed(); 108], + } + } +} + +impl Default for SpriteDataArray { + fn default() -> Self { + Self::zeroed() + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, Default, Pod, Zeroable)] +pub struct SpriteContent { + pub data: SpriteDataArray, +} + +impl SpriteContent { + 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 7ce0f44..810f593 100644 --- a/crates/render/src/gpustate.rs +++ b/crates/render/src/gpustate.rs @@ -8,7 +8,7 @@ use winit::{self, dpi::LogicalSize, window::Window}; use crate::{ content, - globaluniform::{AtlasContent, DataContent, GlobalUniform}, + globaluniform::{AtlasContent, DataContent, GlobalUniform, SpriteContent}, pipeline::PipelineBuilder, sprite::ObjectSubSprite, starfield::Starfield, @@ -292,8 +292,7 @@ impl GPUState { /// Also handles child sprites. fn push_object_sprite( &self, - camera_zoom: f32, - camera_pos: Point2, + state: &RenderState, instances: &mut Vec, clip_ne: Point2, clip_sw: Point2, @@ -305,10 +304,9 @@ impl GPUState { (Point2 { x: s.pos.x, y: s.pos.y, - } - camera_pos.to_vec()) + } - state.camera_pos.to_vec()) / s.pos.z }; - let texture = self.texture_array.get_texture(s.sprite); // Game dimensions of this sprite post-scale. // Don't divide by 2, we use this later. @@ -316,7 +314,7 @@ impl GPUState { // Width or height, whichever is larger. // Accounts for sprite rotation. - let m = height * texture.aspect.max(1.0); + let m = height * s.sprite.aspect.max(1.0); // Don't draw (or compute matrices for) // sprites that are off the screen @@ -329,7 +327,7 @@ impl GPUState { } // TODO: clean up - let scale = height / camera_zoom; + 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 @@ -340,7 +338,7 @@ impl GPUState { // // We apply the provided scale here as well as a minor optimization let sprite_aspect_and_scale = - Matrix4::from_nonuniform_scale(texture.aspect * scale, scale, 1.0); + Matrix4::from_nonuniform_scale(s.sprite.aspect * scale, scale, 1.0); // Apply rotation let rotate = Matrix4::from_angle_z(s.angle); @@ -358,8 +356,8 @@ impl GPUState { // 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 / (camera_zoom / 2.0) / self.window_aspect, - y: pos.y / (camera_zoom / 2.0), + x: pos.x / (state.camera_zoom / 2.0) / self.window_aspect, + y: pos.y / (state.camera_zoom / 2.0), z: 0.0, }); @@ -370,13 +368,13 @@ impl GPUState { instances.push(ObjectInstance { transform: t.into(), - sprite_index: texture.index, + sprite_index: s.sprite.index, }); // Add children if let Some(children) = &s.children { for c in children { - self.push_object_subsprite(camera_zoom, instances, c, pos, s.angle); + self.push_object_subsprite(&state, instances, c, pos, s.angle); } } } @@ -385,29 +383,28 @@ impl GPUState { /// Only called by `self.push_object_sprite`. fn push_object_subsprite( &self, - camera_zoom: f32, + state: &RenderState, instances: &mut Vec, s: &ObjectSubSprite, parent_pos: Point2, parent_angle: Deg, ) { - let texture = self.texture_array.get_texture(s.sprite); - let scale = s.size / (s.pos.z * camera_zoom); + let scale = s.size / (s.pos.z * state.camera_zoom); let sprite_aspect_and_scale = - Matrix4::from_nonuniform_scale(texture.aspect * scale, scale, 1.0); + Matrix4::from_nonuniform_scale(s.sprite.aspect * scale, scale, 1.0); let rotate = Matrix4::from_angle_z(s.angle); let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0); let ptranslate = Matrix4::from_translation(Vector3 { - x: parent_pos.x / (camera_zoom / 2.0) / self.window_aspect, - y: parent_pos.y / (camera_zoom / 2.0), + x: parent_pos.x / (state.camera_zoom / 2.0) / self.window_aspect, + y: parent_pos.y / (state.camera_zoom / 2.0), z: 0.0, }); let protate = Matrix4::from_angle_z(parent_angle); let translate = Matrix4::from_translation(Vector3 { - x: s.pos.x / (camera_zoom / 2.0) / self.window_aspect, - y: s.pos.y / (camera_zoom / 2.0), + x: s.pos.x / (state.camera_zoom / 2.0) / self.window_aspect, + y: s.pos.y / (state.camera_zoom / 2.0), z: 0.0, }); @@ -420,7 +417,7 @@ impl GPUState { instances.push(ObjectInstance { transform: t.into(), - sprite_index: texture.index, + sprite_index: s.sprite.index, }); } @@ -429,7 +426,6 @@ impl GPUState { let logical_size: LogicalSize = self.window_size.to_logical(self.window.scale_factor()); - let texture = self.texture_array.get_texture(s.sprite); let width = s.dimensions.x; let height = s.dimensions.y; @@ -473,7 +469,7 @@ impl GPUState { instances.push(UiInstance { transform: (OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * scale).into(), - sprite_index: texture.index, + sprite_index: s.sprite.index, color: s.color.unwrap_or([1.0, 1.0, 1.0, 1.0]), }); } @@ -481,23 +477,16 @@ impl GPUState { /// Make an instance for all the game's sprites /// (Objects and UI) /// This will Will panic if any X_SPRITE_INSTANCE_LIMIT is exceeded. - fn update_sprite_instances(&self, framestate: RenderState) -> (usize, usize) { + fn update_sprite_instances(&self, state: &RenderState) -> (usize, usize) { let mut object_instances: Vec = Vec::new(); // Game coordinates (relative to camera) of ne and sw corners of screen. // Used to skip off-screen sprites. - let clip_ne = Point2::from((-self.window_aspect, 1.0)) * framestate.camera_zoom; - let clip_sw = Point2::from((self.window_aspect, -1.0)) * framestate.camera_zoom; + let clip_ne = Point2::from((-self.window_aspect, 1.0)) * state.camera_zoom; + let clip_sw = Point2::from((self.window_aspect, -1.0)) * state.camera_zoom; - for s in framestate.object_sprites { - self.push_object_sprite( - framestate.camera_zoom, - framestate.camera_pos, - &mut object_instances, - clip_ne, - clip_sw, - &s, - ); + for s in &state.object_sprites { + self.push_object_sprite(state, &mut object_instances, clip_ne, clip_sw, &s); } // Enforce sprite limit @@ -514,7 +503,7 @@ impl GPUState { let mut ui_instances: Vec = Vec::new(); - for s in framestate.ui_sprites { + for s in &state.ui_sprites { self.push_ui_sprite(&mut ui_instances, &s); } @@ -550,7 +539,14 @@ impl GPUState { &self.global_uniform.atlas_buffer, 0, bytemuck::cast_slice(&[AtlasContent { - locations: self.texture_array.data, + data: 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, }]), ); @@ -558,7 +554,7 @@ impl GPUState { } /// Main render function. Draws sprites on a window. - pub fn render(&mut self, framestate: RenderState) -> Result<(), wgpu::SurfaceError> { + pub fn render(&mut self, state: RenderState) -> Result<(), wgpu::SurfaceError> { let output = self.surface.get_current_texture()?; let view = output .texture @@ -591,32 +587,32 @@ impl GPUState { timestamp_writes: None, }); + let s = state.content.get_starfield_handle(); // Update global values self.queue.write_buffer( &self.global_uniform.data_buffer, 0, bytemuck::cast_slice(&[DataContent { - camera_position: framestate.camera_pos.into(), - camera_zoom: [framestate.camera_zoom, 0.0], + camera_position: state.camera_pos.into(), + camera_zoom: [state.camera_zoom, 0.0], camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX], window_size: [ self.window_size.width as f32, self.window_size.height as f32, ], window_aspect: [self.window_aspect, 0.0], - starfield_sprite: [self.texture_array.get_starfield_texture().index, 0], + starfield_sprite: [s.index, 0], starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0], starfield_size_limits: [ galactica_constants::STARFIELD_SIZE_MIN, galactica_constants::STARFIELD_SIZE_MAX, ], - current_time: [framestate.current_time, 0.0], + current_time: [state.current_time, 0.0], }]), ); // Write all new particles to GPU buffer - for i in framestate.new_particles.iter() { - let texture = self.texture_array.get_texture(i.sprite); + for i in state.new_particles.iter() { self.queue.write_buffer( &self.vertex_buffers.particle.instances, ParticleInstance::SIZE * self.vertex_buffers.particle_array_head, @@ -625,10 +621,9 @@ impl GPUState { velocity: i.velocity.into(), rotation: Matrix2::from_angle(i.angle).into(), size: i.size, - texture_index_len_rep: [texture.index, texture.len, texture.repeat], - texture_aspect_fps: [texture.aspect, texture.fps], - created: framestate.current_time, - expires: framestate.current_time + i.lifetime, + sprite_index: i.sprite.index, + created: state.current_time, + expires: state.current_time + i.lifetime, }]), ); self.vertex_buffers.particle_array_head += 1; @@ -638,10 +633,10 @@ impl GPUState { self.vertex_buffers.particle_array_head = 0; } } - framestate.new_particles.clear(); + state.new_particles.clear(); // Create sprite instances - let (n_object, n_ui) = self.update_sprite_instances(framestate); + let (n_object, n_ui) = self.update_sprite_instances(&state); // These should match the indices in each shader, // and should each have a corresponding bind group layout. diff --git a/crates/render/src/renderstate.rs b/crates/render/src/renderstate.rs index bad503d..3daf995 100644 --- a/crates/render/src/renderstate.rs +++ b/crates/render/src/renderstate.rs @@ -1,4 +1,5 @@ use cgmath::Point2; +use galactica_content::Content; use crate::{ObjectSprite, ParticleBuilder, UiSprite}; @@ -23,4 +24,7 @@ pub struct RenderState<'a> { // TODO: handle overflow /// The current time, in seconds pub current_time: f32, + + /// Game content + pub content: &'a Content, } diff --git a/crates/render/src/texturearray.rs b/crates/render/src/texturearray.rs index 8d8d6a5..b73a9f5 100644 --- a/crates/render/src/texturearray.rs +++ b/crates/render/src/texturearray.rs @@ -1,12 +1,12 @@ use crate::{ content, - globaluniform::{ImageLocation, ImageLocations}, + globaluniform::{ImageLocation, ImageLocationArray, SpriteData, SpriteDataArray}, }; use anyhow::Result; use bytemuck::Zeroable; use galactica_packer::SpriteAtlasImage; use image::GenericImageView; -use std::{collections::HashMap, fs::File, io::Read, num::NonZeroU32}; +use std::{fs::File, io::Read, num::NonZeroU32}; use wgpu::BindGroupLayout; pub(crate) struct RawTexture { @@ -85,27 +85,14 @@ pub struct Texture { pub struct TextureArray { pub bind_group: wgpu::BindGroup, pub bind_group_layout: BindGroupLayout, - starfield_handle: content::SpriteHandle, - sprites: HashMap, - pub data: ImageLocations, + pub image_locations: ImageLocationArray, + pub sprite_data: SpriteDataArray, } impl TextureArray { - pub fn get_starfield_texture(&self) -> &Texture { - self.sprites.get(&self.starfield_handle).unwrap() - } - - pub fn get_texture(&self, handle: content::SpriteHandle) -> &Texture { - match self.sprites.get(&handle) { - Some(x) => x, - None => unreachable!("Tried to get a texture that doesn't exist"), - } - } - pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, ct: &content::Content) -> Result { // Load all textures let mut texture_data = Vec::new(); - let mut sprites = HashMap::new(); println!("opening image"); let mut f = File::open("atlas-0.bmp")?; @@ -113,35 +100,31 @@ impl TextureArray { f.read_to_end(&mut bytes)?; texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, "Atlas")?); - let mut tx = ImageLocations::zeroed(); + let mut image_locations = ImageLocationArray::zeroed(); + let mut sprite_data = SpriteDataArray::zeroed(); println!("sending to gpu"); - let mut i = 0; + let mut image_counter = 0; for t in &ct.sprites { - let loc = ct.get_image(&t.frames[0]); - - sprites.insert( - t.handle, - Texture { - index: i, - aspect: t.aspect, - fps: t.fps, - len: t.frames.len() as u32, - repeat: t.repeat.as_int(), - location: vec![loc.clone()], - }, - ); + sprite_data.data[image_counter as usize] = SpriteData { + frame_count: t.frames.len() as u32, + repeatmode: t.repeat.as_int(), + aspect: t.aspect, + fps: t.fps, + first_frame: image_counter, + _padding: Default::default(), + }; // Insert texture location data for path in &t.frames { let image = ct.get_image(&path); - tx.data[i as usize] = ImageLocation { - x: image.x, - y: image.y, - w: image.w, - h: image.h, + image_locations.data[image_counter as usize] = ImageLocation { + xpos: image.x, + ypos: image.y, + width: image.w, + height: image.h, }; - i += 1; + image_counter += 1; } } @@ -198,9 +181,8 @@ impl TextureArray { return Ok(Self { bind_group, bind_group_layout, - sprites, - starfield_handle: ct.get_starfield_handle(), - data: tx, + image_locations, + sprite_data, }); } } diff --git a/crates/render/src/vertexbuffer/types.rs b/crates/render/src/vertexbuffer/types.rs index 27b4c11..18ffa8a 100644 --- a/crates/render/src/vertexbuffer/types.rs +++ b/crates/render/src/vertexbuffer/types.rs @@ -219,9 +219,8 @@ pub struct ParticleInstance { /// Time is kept by a variable in the global uniform. pub expires: f32, - /// What texture to use for this particle - pub texture_index_len_rep: [u32; 3], - pub texture_aspect_fps: [f32; 2], + /// What sprite to use for this particle + pub sprite_index: u32, } impl BufferObject for ParticleInstance { @@ -271,17 +270,11 @@ impl BufferObject for ParticleInstance { shader_location: 8, format: wgpu::VertexFormat::Float32, }, - // Texture index / len / repeat + // Sprite wgpu::VertexAttribute { offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress, shader_location: 9, - format: wgpu::VertexFormat::Uint32x3, - }, - // Texture aspect / fps - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 14]>() as wgpu::BufferAddress, - shader_location: 10, - format: wgpu::VertexFormat::Float32x2, + format: wgpu::VertexFormat::Uint32, }, ], }