From a65372f866b712dee0089d254d3d95c3d8bc41fb Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 6 Jan 2024 14:02:50 -0800 Subject: [PATCH] Moved positioning logic to shaders --- crates/render/shaders/include/animate.wgsl | 44 +++--- crates/render/shaders/object.wgsl | 130 +++++++++++++----- crates/render/shaders/particle.wgsl | 88 ++++++++---- crates/render/shaders/starfield.wgsl | 32 ++--- crates/render/shaders/ui.wgsl | 4 +- crates/render/src/globaluniform/atlas.rs | 6 +- crates/render/src/globaluniform/data.rs | 4 +- .../render/src/globaluniform/globaluniform.rs | 26 ++-- crates/render/src/globaluniform/mod.rs | 6 +- crates/render/src/globaluniform/object.rs | 16 ++- crates/render/src/gpustate.rs | 91 +++++------- crates/render/src/sprite.rs | 9 +- crates/render/src/texturearray.rs | 4 +- crates/render/src/vertexbuffer/types.rs | 26 ++-- 14 files changed, 286 insertions(+), 200 deletions(-) diff --git a/crates/render/shaders/include/animate.wgsl b/crates/render/shaders/include/animate.wgsl index ad74216..c7cdcd3 100644 --- a/crates/render/shaders/include/animate.wgsl +++ b/crates/render/shaders/include/animate.wgsl @@ -1,43 +1,41 @@ -// Pick frame of animation from an instance. -// -// This function assumes that the uniform header has been loaded, -// and that `InstanceInput` contains a field `texture_index` -fn animate(instance: InstanceInput, age: f32) -> u32 { - - let idx = instance.texture_index; - let len = sprites[idx].frame_count; - let rep = sprites[idx].repeatmode; - let fps = sprites[idx].fps; - var frame: u32 = u32(0); +// Pick a frame of a sprite animation from an instance. +fn animate(sprite_index: u32, age: f32, offset: f32) -> f32 { + let len = global_sprites[sprite_index].frame_count; + let rep = global_sprites[sprite_index].repeatmode; + let fps = global_sprites[sprite_index].fps; + var frame: f32 = 0.0; // Once if rep == u32(1) { - frame = u32(min( - age / fps, + frame = min( + age / fps + offset, f32(len) - 1.0 - )); + ); // Reverse } else if rep == u32(2) { - let x = age / fps; + let x = age / fps + offset; let m = f32(len) * 2.0 - 1.0; // x fmod m - frame = u32(x - floor(x / m) * m); + frame = x - floor(x / m) * m; - if frame >= len { - frame = len + len - frame - u32(1); + if frame >= f32(len) { + frame = ( + f32(len) + f32(len) - 1.0 + // Split integer and fractional part so tweening works properly + - floor(frame) + + fract(frame) + ); } // Repeat (default) } else { - let x = age / fps; + let x = age / fps + offset; let m = f32(len); - // x fmod m - frame = u32(x - floor(x / m) * m); - + frame = x - floor(x / m) * m; } - return frame + sprites[idx].first_frame; + return frame + f32(global_sprites[sprite_index].first_frame); } \ No newline at end of file diff --git a/crates/render/shaders/object.wgsl b/crates/render/shaders/object.wgsl index 2b71e90..b489d77 100644 --- a/crates/render/shaders/object.wgsl +++ b/crates/render/shaders/object.wgsl @@ -1,7 +1,7 @@ // INCLUDE: global uniform header struct InstanceInput { - @location(2) texture_index: u32, + @location(2) sprite_index: u32, @location(3) object_index: u32, }; @@ -12,8 +12,11 @@ struct VertexInput { struct VertexOutput { @builtin(position) position: vec4, - @location(0) texture_coords: vec2, - @location(1) texture_index: u32, + @location(0) tween: f32, + @location(1) texture_index_a: u32, + @location(2) texture_coords_a: vec2, + @location(3) texture_index_b: u32, + @location(4) texture_coords_b: vec2, }; @@ -25,42 +28,76 @@ var sampler_array: binding_array; // INCLUDE: animate.wgsl -fn transform_vertex(obj: ObjectLocation, vertex: VertexInput, instance: InstanceInput) -> vec4 { +fn transform_vertex(obj: ObjectData, vertex_position: vec2, sprite_index: u32) -> vec4 { // Object scale - let scale = obj.size / (global.camera_zoom.x * obj.zpos); + let scale = obj.size / (global_data.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 + vertex_position.x * scale * global_sprites[sprite_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 - ); + // (It's thus done later if this is a child) + if obj.is_child == u32(0) { + pos = vec2( + pos.x / global_data.window_aspect.x, + pos.y + ); + } - // Distance-adjusted world position - let trans = (vec2(obj.xpos, obj.ypos) - global.camera_position) / obj.zpos; - - // Finally, translate + // 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) - ); + if obj.is_child == u32(0) { + let trans = (vec2(obj.xpos, obj.ypos) - global_data.camera_position) / obj.zpos; + pos = pos + vec2( + trans.x / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x, + trans.y / (global_data.camera_zoom.x / 2.0) + ); + } + + if obj.is_child == u32(1) { + // Apply translation relative to parent + // Note that obj.zpos is ignored + pos = pos + vec2( + obj.xpos / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x, + obj.ypos / (global_data.camera_zoom.x / 2.0) + ); + + let parent = objects[obj.parent]; + + // Apply parent's rotation + pos = mat2x2( + vec2(cos(parent.angle), sin(parent.angle)), + vec2(-sin(parent.angle), cos(parent.angle)) + ) * pos; + + // Correct for screen aspect, preserving height + pos = vec2( + pos.x / global_data.window_aspect.x, + pos.y + ); + + // Apply parent's translation + let ptrans = (vec2(parent.xpos, parent.ypos) - global_data.camera_position) / parent.zpos; + pos = pos + vec2( + ptrans.x / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x, + ptrans.y / (global_data.camera_zoom.x / 2.0) + ); + } return vec4(pos, 0.0, 1.0);; } @@ -71,16 +108,36 @@ fn vertex_main( instance: InstanceInput, ) -> VertexOutput { var out: VertexOutput; - out.position = transform_vertex(objects[instance.object_index], vertex, instance); - let t = atlas[animate(instance, global.current_time.x)]; - out.texture_index = t.atlas_texture; - out.texture_coords = vec2(t.xpos, t.ypos); + + + out.position = transform_vertex(objects[instance.object_index], vertex.position.xy, instance.sprite_index); + + + // Compute texture coordinates + + let age = global_data.current_time.x; + let frame = animate(instance.sprite_index, age, 0.0); + out.tween = fract(frame); + + let t = global_atlas[u32(floor(frame))]; + out.texture_index_a = u32(t.atlas_texture); + out.texture_coords_a = 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); + out.texture_coords_a = out.texture_coords_a + vec2(t.width, 0.0); } if vertex.texture_coords.y == 1.0 { - out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height); + out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height); + } + + let b = global_atlas[u32(floor(animate(instance.sprite_index, age, 1.0)))]; + out.texture_index_b = u32(b.atlas_texture); + out.texture_coords_b = vec2(b.xpos, b.ypos); + if vertex.texture_coords.x == 1.0 { + out.texture_coords_b = out.texture_coords_b + vec2(b.width, 0.0); + } + if vertex.texture_coords.y == 1.0 { + out.texture_coords_b = out.texture_coords_b + vec2(0.0, b.height); } return out; @@ -89,10 +146,19 @@ fn vertex_main( @fragment fn fragment_main(in: VertexOutput) -> @location(0) vec4 { - return textureSampleLevel( - texture_array[in.texture_index], - sampler_array[0], - in.texture_coords, - 0.0 - ).rgba; + return mix( + textureSampleLevel( + texture_array[in.texture_index_a], + sampler_array[0], + in.texture_coords_a, + 0.0 + ).rgba, + textureSampleLevel( + texture_array[in.texture_index_b], + sampler_array[0], + in.texture_coords_b, + 0.0 + ).rgba, + in.tween + ); } \ No newline at end of file diff --git a/crates/render/shaders/particle.wgsl b/crates/render/shaders/particle.wgsl index a700353..b99e1c4 100644 --- a/crates/render/shaders/particle.wgsl +++ b/crates/render/shaders/particle.wgsl @@ -3,12 +3,12 @@ struct InstanceInput { @location(2) position: vec2, @location(3) velocity: vec2, - @location(4) rotation_0: vec2, - @location(5) rotation_1: vec2, + @location(4) angle: f32, + @location(5) angvel: f32, @location(6) size: f32, @location(7) created: f32, @location(8) expires: f32, - @location(9) texture_index: u32, + @location(9) sprite_index: u32, }; struct VertexInput { @@ -18,8 +18,11 @@ struct VertexInput { struct VertexOutput { @builtin(position) position: vec4, - @location(0) texture_coords: vec2, - @location(1) texture_index: u32, + @location(0) tween: f32, + @location(1) texture_index_a: u32, + @location(2) texture_coords_a: vec2, + @location(3) texture_index_b: u32, + @location(4) texture_coords_b: vec2, } @@ -38,24 +41,31 @@ fn vertex_main( ) -> VertexOutput { var out: VertexOutput; - out.texture_coords = vertex.texture_coords; // Skip expired particles - if instance.expires < global.current_time.x { - out.texture_index = u32(0); - // Draw off screen + if instance.expires < global_data.current_time.x { + out.tween = 0.0; + out.texture_index_a = u32(0); + out.texture_index_b = u32(0); + out.texture_coords_a = vec2(0.0, 0.0); + out.texture_coords_b = vec2(0.0, 0.0); out.position = vec4(2.0, 2.0, 0.0, 1.0); return out; } - let age = global.current_time.x - instance.created; + let age = global_data.current_time.x - instance.created; + // Apply transformations - let rotation = mat2x2(instance.rotation_0, instance.rotation_1); - var scale: f32 = instance.size / global.camera_zoom.x; + let angle = instance.angle + age * instance.angvel; + let rotation = mat2x2( + vec2(cos(angle), sin(angle)), + vec2(-sin(angle), cos(angle)) + ); + var scale: f32 = instance.size / global_data.camera_zoom.x; var pos: vec2 = vec2(vertex.position.x, vertex.position.y); pos = pos * vec2( - sprites[instance.texture_index].aspect * scale / global.window_aspect.x, + global_sprites[instance.sprite_index].aspect * scale / global_data.window_aspect.x, scale ); @@ -64,38 +74,62 @@ fn vertex_main( var ipos: vec2 = ( vec2(instance.position.x, instance.position.y) + (instance.velocity * age) - - global.camera_position + - global_data.camera_position ); pos = pos + vec2( - ipos.x / (global.camera_zoom.x/2.0) / global.window_aspect.x, - ipos.y / (global.camera_zoom.x/2.0) + ipos.x / (global_data.camera_zoom.x/2.0) / global_data.window_aspect.x, + ipos.y / (global_data.camera_zoom.x/2.0) ); out.position = vec4(pos, 0.0, 1.0); // Compute texture coordinates - let t = atlas[animate(instance, age)]; - out.texture_index = u32(t.atlas_texture); - out.texture_coords = vec2(t.xpos, t.ypos); + let frame = animate(instance.sprite_index, age, 0.0); + out.tween = fract(frame); + + let t = global_atlas[u32(floor(frame))]; + out.texture_index_a = u32(t.atlas_texture); + out.texture_coords_a = 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); + out.texture_coords_a = out.texture_coords_a + vec2(t.width, 0.0); } if vertex.texture_coords.y == 1.0 { - out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height); + out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height); } + let b = global_atlas[u32(floor(animate(instance.sprite_index, age, 1.0)))]; + out.texture_index_b = u32(b.atlas_texture); + out.texture_coords_b = vec2(b.xpos, b.ypos); + if vertex.texture_coords.x == 1.0 { + out.texture_coords_b = out.texture_coords_b + vec2(b.width, 0.0); + } + if vertex.texture_coords.y == 1.0 { + out.texture_coords_b = out.texture_coords_b + vec2(0.0, b.height); + } + + return out; } @fragment fn fragment_main(in: VertexOutput) -> @location(0) vec4 { - return textureSampleLevel( - texture_array[in.texture_index], - sampler_array[0], - in.texture_coords, - 0.0 - ).rgba; + + return mix( + textureSampleLevel( + texture_array[in.texture_index_a], + sampler_array[0], + in.texture_coords_a, + 0.0 + ).rgba, + textureSampleLevel( + texture_array[in.texture_index_b], + sampler_array[0], + in.texture_coords_b, + 0.0 + ).rgba, + in.tween + ); } \ No newline at end of file diff --git a/crates/render/shaders/starfield.wgsl b/crates/render/shaders/starfield.wgsl index 3426d14..6875389 100644 --- a/crates/render/shaders/starfield.wgsl +++ b/crates/render/shaders/starfield.wgsl @@ -41,18 +41,18 @@ fn vertex_main( // Center of the tile the camera is currently in, in game coordinates. // x div y = x - (x mod y) let tile_center = ( - global.camera_position.xy + global_data.camera_position.xy - ( fmod( - global.camera_position.xy + global.starfield_tile_size.x / 2.0, - global.starfield_tile_size.x - ) - global.starfield_tile_size.x / 2.0 + global_data.camera_position.xy + global_data.starfield_tile_size.x / 2.0, + global_data.starfield_tile_size.x + ) - global_data.starfield_tile_size.x / 2.0 ) ); let zoom_min_times = ( - global.camera_zoom.x / global.camera_zoom_limits.x + global_data.camera_zoom.x / global_data.camera_zoom_limits.x ); // Hide n% of the smallest stars @@ -64,8 +64,8 @@ fn vertex_main( if ( instance.size < ( - hide_fraction * (global.starfield_size_limits.y - global.starfield_size_limits.x) - + (global.starfield_size_limits.x) + hide_fraction * (global_data.starfield_size_limits.y - global_data.starfield_size_limits.x) + + (global_data.starfield_size_limits.x) ) ) { out.position = vec4(2.0, 2.0, 0.0, 1.0); @@ -76,41 +76,41 @@ fn vertex_main( // Apply sprite aspect ratio & scale factor // also applies screen aspect ratio // Note that we do NOT scale for distance here---this is intentional. - var scale: f32 = instance.size / global.camera_zoom.x; + var scale: f32 = instance.size / global_data.camera_zoom.x; // Minimum scale to prevent flicker at large zoom levels - var real_size = scale * global.window_size.xy; + var real_size = scale * global_data.window_size.xy; if (real_size.x < 2.0 || real_size.y < 2.0) { // Otherwise, clamp to a minimum scale - scale = 2.0 / max(global.window_size.x, global.window_size.y); + scale = 2.0 / max(global_data.window_size.x, global_data.window_size.y); } // Divide by two, because viewport height is 2 in screen units // (coordinates go from -1 to 1) var pos: vec2 = vec2( - vertex.position.x * (scale/2.0) / global.window_aspect.x, + vertex.position.x * (scale/2.0) / global_data.window_aspect.x, vertex.position.y * (scale/2.0) ); // World position relative to camera // (Note that instance position is in a different // coordinate system than usual) - let camera_pos = (instance.position.xy + tile_center) - global.camera_position.xy; + let camera_pos = (instance.position.xy + tile_center) - global_data.camera_position.xy; // Translate pos = pos + ( // Don't forget to correct distance for screen aspect ratio too! - (camera_pos / (global.camera_zoom.x * instance.position.z)) - / vec2(global.window_aspect.x, 1.0) + (camera_pos / (global_data.camera_zoom.x * instance.position.z)) + / vec2(global_data.window_aspect.x, 1.0) ); out.position = vec4(pos, 0.0, 1.0) * instance.position.z; // Starfield sprites may not be animated - let i = sprites[global.starfield_sprite.x].first_frame; - let t = atlas[i]; + let i = global_sprites[global_data.starfield_sprite.x].first_frame; + let t = global_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 8a5b870..c36512e 100644 --- a/crates/render/shaders/ui.wgsl +++ b/crates/render/shaders/ui.wgsl @@ -6,7 +6,7 @@ struct InstanceInput { @location(4) transform_matrix_2: vec4, @location(5) transform_matrix_3: vec4, @location(6) color_transform: vec4, - @location(7) texture_index: u32, + @location(7) sprite_index: u32, }; struct VertexInput { @@ -48,7 +48,7 @@ fn vertex_main( out.color_transform = instance.color_transform; // Pick texture frame - let t = atlas[animate(instance, global.current_time.x)]; + let t = global_atlas[u32(animate(instance.sprite_index, global_data.current_time.x, 0.0))]; 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/atlas.rs b/crates/render/src/globaluniform/atlas.rs index d973bd2..2f8edbf 100644 --- a/crates/render/src/globaluniform/atlas.rs +++ b/crates/render/src/globaluniform/atlas.rs @@ -5,7 +5,7 @@ use wgpu; #[repr(C)] #[derive(Debug, Copy, Clone, Pod, Zeroable, Default)] -pub struct ImageLocation { +pub struct AtlasImageLocation { // Image box, in texture coordinates pub xpos: f32, pub ypos: f32, @@ -20,14 +20,14 @@ pub struct ImageLocation { #[derive(Debug, Copy, Clone)] pub struct AtlasArray { - pub data: [ImageLocation; IMAGE_LIMIT as usize], + pub data: [AtlasImageLocation; IMAGE_LIMIT as usize], } unsafe impl Pod for AtlasArray {} unsafe impl Zeroable for AtlasArray { fn zeroed() -> Self { Self { - data: [ImageLocation::zeroed(); IMAGE_LIMIT as usize], + data: [AtlasImageLocation::zeroed(); IMAGE_LIMIT as usize], } } } diff --git a/crates/render/src/globaluniform/data.rs b/crates/render/src/globaluniform/data.rs index f95d1d5..b8d0494 100644 --- a/crates/render/src/globaluniform/data.rs +++ b/crates/render/src/globaluniform/data.rs @@ -9,7 +9,7 @@ use wgpu; // all smaller values must be padded. // also, [f32; 3] are aligned as [f32; 4] // (since alignments must be powers of two) -pub struct DataContent { +pub struct GlobalDataContent { /// Camera position, in game units pub camera_position: [f32; 2], @@ -43,6 +43,6 @@ pub struct DataContent { pub current_time: [f32; 2], } -impl DataContent { +impl GlobalDataContent { pub const SIZE: u64 = mem::size_of::() as wgpu::BufferAddress; } diff --git a/crates/render/src/globaluniform/globaluniform.rs b/crates/render/src/globaluniform/globaluniform.rs index 9e2aad2..f3023af 100644 --- a/crates/render/src/globaluniform/globaluniform.rs +++ b/crates/render/src/globaluniform/globaluniform.rs @@ -1,7 +1,7 @@ use galactica_constants::{IMAGE_LIMIT, OBJECT_SPRITE_INSTANCE_LIMIT, SPRITE_LIMIT}; use wgpu; -use super::{object::ObjectLocationArray, AtlasArray, DataContent, SpriteDataArray}; +use super::{object::ObjectLocationArray, AtlasArray, GlobalDataContent, SpriteDataArray}; pub struct GlobalUniform { pub data_buffer: wgpu::Buffer, @@ -10,7 +10,7 @@ pub struct GlobalUniform { pub object_buffer: wgpu::Buffer, pub bind_group: wgpu::BindGroup, pub bind_group_layout: wgpu::BindGroupLayout, - pub content: DataContent, + pub content: GlobalDataContent, } impl GlobalUniform { @@ -21,8 +21,8 @@ impl GlobalUniform { out.push_str(&format!("@group({group}) @binding(0)\n")); out.push_str( r#" - var global: GlobalUniform; - struct GlobalUniform { + var global_data: GlobalData; + struct GlobalData { camera_position: vec2, camera_zoom: vec2, camera_zoom_limits: vec2, @@ -41,13 +41,13 @@ impl GlobalUniform { out.push_str(&format!( r#" @group({group}) @binding(1) - var atlas: array; + var global_atlas: array; "# )); out.push_str("\n"); out.push_str( r#" - struct ImageLocation { + struct AtlasImageLocation { xpos: f32, ypos: f32, width: f32, @@ -73,7 +73,7 @@ impl GlobalUniform { out.push_str(&format!( r#" @group({group}) @binding(2) - var sprites: array; + var global_sprites: array; "# )); out.push_str("\n"); @@ -98,22 +98,22 @@ impl GlobalUniform { out.push_str(&format!( r#" @group({group}) @binding(3) - var objects: array; + var objects: array; "# )); out.push_str("\n"); out.push_str( r#" - struct ObjectLocation { + struct ObjectData { xpos: f32, ypos: f32, zpos: f32, angle: f32, size: f32, + parent: u32, + is_child: u32, padding_a: f32, - padding_b: f32, - padding_c: f32, }; "#, ); @@ -125,7 +125,7 @@ impl GlobalUniform { pub fn new(device: &wgpu::Device) -> Self { let data_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("global uniform data buffer"), - size: DataContent::SIZE, + size: GlobalDataContent::SIZE, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); @@ -227,7 +227,7 @@ impl GlobalUniform { object_buffer, bind_group, bind_group_layout, - content: DataContent::default(), + content: GlobalDataContent::default(), }; } } diff --git a/crates/render/src/globaluniform/mod.rs b/crates/render/src/globaluniform/mod.rs index 468fc7c..1235b6f 100644 --- a/crates/render/src/globaluniform/mod.rs +++ b/crates/render/src/globaluniform/mod.rs @@ -4,8 +4,8 @@ mod globaluniform; mod object; mod sprite; -pub use atlas::{AtlasArray, ImageLocation}; -pub use data::DataContent; +pub use atlas::{AtlasArray, AtlasImageLocation}; +pub use data::GlobalDataContent; pub use globaluniform::GlobalUniform; -pub use object::ObjectLocation; +pub use object::ObjectData; pub use sprite::{SpriteData, SpriteDataArray}; diff --git a/crates/render/src/globaluniform/object.rs b/crates/render/src/globaluniform/object.rs index 70f291c..c99a23a 100644 --- a/crates/render/src/globaluniform/object.rs +++ b/crates/render/src/globaluniform/object.rs @@ -5,30 +5,36 @@ use wgpu; #[repr(C)] #[derive(Debug, Copy, Clone, Pod, Zeroable, Default)] -pub struct ObjectLocation { +pub struct ObjectData { pub xpos: f32, pub ypos: f32, pub zpos: f32, pub angle: f32, pub size: f32, - pub _padding: [f32; 3], + /// Index of parent object + pub parent: u32, + + /// 1 if has parent, 0 if not + pub is_child: u32, + + pub _padding: [f32; 1], } -impl ObjectLocation { +impl ObjectData { 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], + pub data: [ObjectData; 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], + data: [ObjectData::zeroed(); OBJECT_SPRITE_INSTANCE_LIMIT as usize], } } } diff --git a/crates/render/src/gpustate.rs b/crates/render/src/gpustate.rs index 9da3ea3..f169600 100644 --- a/crates/render/src/gpustate.rs +++ b/crates/render/src/gpustate.rs @@ -1,6 +1,6 @@ use anyhow::Result; use bytemuck; -use cgmath::{EuclideanSpace, Matrix2, Matrix4, Point2, Rad, Vector3}; +use cgmath::{EuclideanSpace, Matrix4, Point2, Rad, Vector3}; use galactica_constants; use rand::seq::SliceRandom; use std::{iter, rc::Rc}; @@ -9,9 +9,8 @@ use winit::{self, dpi::LogicalSize, window::Window}; use crate::{ content, - globaluniform::{DataContent, GlobalUniform, ObjectLocation}, + globaluniform::{GlobalDataContent, GlobalUniform, ObjectData}, pipeline::PipelineBuilder, - sprite::ObjectSubSprite, starfield::Starfield, texturearray::TextureArray, vertexbuffer::{ @@ -302,7 +301,6 @@ impl GPUState { } /// Create a ObjectInstance for an object and add it to `instances`. - /// Also handles child sprites. fn push_object_sprite( &self, state: &RenderState, @@ -328,8 +326,7 @@ impl GPUState { // 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 + // Don't draw sprites that are off the screen if pos.x < clip_ne.x - m || pos.y > clip_ne.y + m || pos.x > clip_sw.x + m @@ -338,16 +335,19 @@ impl GPUState { return; } + let idx = instances.len(); // 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 { + ObjectData::SIZE * idx as u64, + bytemuck::cast_slice(&[ObjectData { xpos: s.pos.x, ypos: s.pos.y, zpos: s.pos.z, angle: Rad::from(s.angle).0, size: s.size, + parent: 0, + is_child: 0, _padding: Default::default(), }]), ); @@ -355,62 +355,36 @@ impl GPUState { // Push this object's instance instances.push(ObjectInstance { sprite_index: s.sprite.get_index(), - object_index: instances.len() as u32, + object_index: idx as u32, }); - } - /* + // Add children if let Some(children) = &s.children { for c in children { - self.push_object_subsprite(&state, instances, c, pos, s.angle); + self.queue.write_buffer( + &self.global_uniform.object_buffer, + ObjectData::SIZE * instances.len() as u64, + bytemuck::cast_slice(&[ObjectData { + xpos: c.pos.x, + ypos: c.pos.y, + zpos: c.pos.z, + angle: Rad::from(c.angle).0, + size: c.size, + parent: idx as u32, + is_child: 1, + _padding: Default::default(), + }]), + ); + + instances.push(ObjectInstance { + sprite_index: c.sprite.get_index(), + object_index: instances.len() as u32, + }); } } } - /// Add an object sprite's subsprite to `instances`. - /// Only called by `self.push_object_sprite`. - fn push_object_subsprite( - &self, - state: &RenderState, - instances: &mut Vec, - s: &ObjectSubSprite, - parent_pos: Point2, - parent_angle: Deg, - ) { - let scale = s.size / (s.pos.z * state.camera_zoom); - let sprite_aspect_and_scale = - 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 / (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 / (state.camera_zoom / 2.0) / self.window_aspect, - y: s.pos.y / (state.camera_zoom / 2.0), - z: 0.0, - }); - - // Order matters! - // The rightmost matrix is applied first. - let t = OPENGL_TO_WGPU_MATRIX - * ptranslate * screen_aspect - * protate * translate - * rotate * sprite_aspect_and_scale; - - instances.push(ObjectInstance { - transform: t.into(), - sprite_index: s.sprite.get_index(), - }); - } - */ - - /// Create a ObjectInstance for a ui sprite and add it to `instances` + /// Create a UiInstance for a ui sprite and add it to `instances` fn push_ui_sprite(&self, instances: &mut Vec, s: &UiSprite) { let logical_size: LogicalSize = self.window_size.to_logical(self.window.scale_factor()); @@ -578,7 +552,7 @@ impl GPUState { self.queue.write_buffer( &self.global_uniform.data_buffer, 0, - bytemuck::cast_slice(&[DataContent { + bytemuck::cast_slice(&[GlobalDataContent { camera_position: state.camera_pos.into(), camera_zoom: [state.camera_zoom, 0.0], camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX], @@ -606,7 +580,8 @@ impl GPUState { bytemuck::cast_slice(&[ParticleInstance { position: [i.pos.x, i.pos.y], velocity: i.velocity.into(), - rotation: Matrix2::from_angle(i.angle).into(), + angle: i.angle.0, + angvel: i.angvel.0, size: i.size, sprite_index: i.sprite.get_index(), created: state.current_time, diff --git a/crates/render/src/sprite.rs b/crates/render/src/sprite.rs index f42f28e..4465323 100644 --- a/crates/render/src/sprite.rs +++ b/crates/render/src/sprite.rs @@ -1,5 +1,5 @@ use crate::content; -use cgmath::{Deg, Point2, Point3, Vector2}; +use cgmath::{Deg, Point2, Point3, Rad, Vector2}; /// Instructions to create a new particle pub struct ParticleBuilder { @@ -12,8 +12,11 @@ pub struct ParticleBuilder { /// This particle's velocity, in world coordinates pub velocity: Vector2, - /// This particle's angle, in world coordinates - pub angle: Deg, + /// This particle's angle + pub angle: Rad, + + /// This particle's angular velocity (rad/sec) + pub angvel: Rad, /// This particle's lifetime, in seconds pub lifetime: f32, diff --git a/crates/render/src/texturearray.rs b/crates/render/src/texturearray.rs index 0610245..3e5c91c 100644 --- a/crates/render/src/texturearray.rs +++ b/crates/render/src/texturearray.rs @@ -1,6 +1,6 @@ use crate::{ content, - globaluniform::{AtlasArray, ImageLocation, SpriteData, SpriteDataArray}, + globaluniform::{AtlasArray, AtlasImageLocation, SpriteData, SpriteDataArray}, }; use anyhow::Result; use bytemuck::Zeroable; @@ -129,7 +129,7 @@ impl TextureArray { // Insert texture location data for path in &t.frames { let image = ct.get_image(&path); - image_locations.data[image_counter as usize] = ImageLocation { + image_locations.data[image_counter as usize] = AtlasImageLocation { xpos: image.x, ypos: image.y, width: image.w, diff --git a/crates/render/src/vertexbuffer/types.rs b/crates/render/src/vertexbuffer/types.rs index 17924ba..41dc1e2 100644 --- a/crates/render/src/vertexbuffer/types.rs +++ b/crates/render/src/vertexbuffer/types.rs @@ -186,11 +186,14 @@ pub struct ParticleInstance { /// World position of this particle pub position: [f32; 2], - /// Velocity of this sprite, in world coordinates + /// Velocity of this particle, in world coordinates pub velocity: [f32; 2], - /// Rotation matrix for this sprite, in world coordinates - pub rotation: [[f32; 2]; 2], + /// Angle of this particle, in radians + pub angle: f32, + + /// Angular velocity of this particle, rad/sec + pub angvel: f32, /// The height of this particle, in world units pub size: f32, @@ -225,38 +228,39 @@ impl BufferObject for ParticleInstance { shader_location: 3, format: wgpu::VertexFormat::Float32x2, }, - // Rotation + // Angle wgpu::VertexAttribute { offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, shader_location: 4, - format: wgpu::VertexFormat::Float32x2, + format: wgpu::VertexFormat::Float32, }, + // Angvel wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress, + offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, shader_location: 5, - format: wgpu::VertexFormat::Float32x2, + format: wgpu::VertexFormat::Float32, }, // Size wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, + offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress, shader_location: 6, format: wgpu::VertexFormat::Float32, }, // Created wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress, + offset: mem::size_of::<[f32; 7]>() as wgpu::BufferAddress, shader_location: 7, format: wgpu::VertexFormat::Float32, }, // Expires wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 10]>() as wgpu::BufferAddress, + offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, shader_location: 8, format: wgpu::VertexFormat::Float32, }, // Sprite wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress, + offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress, shader_location: 9, format: wgpu::VertexFormat::Uint32, },