From 7cde4f2346f6b9fc8840a7d274e9b3849c6ecf05 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 4 Jan 2024 17:18:31 -0800 Subject: [PATCH] Adapted renderer for new texture indexing --- crates/render/shaders/object.wgsl | 32 ++--- crates/render/shaders/particle.wgsl | 31 ++--- crates/render/shaders/starfield.wgsl | 34 +++-- crates/render/shaders/ui.wgsl | 31 ++--- crates/render/src/globaldata.rs | 96 -------------- .../render/src/globaluniform/atlascontent.rs | 44 +++++++ .../render/src/globaluniform/datacontent.rs | 48 +++++++ .../render/src/globaluniform/globaluniform.rs | 120 ++++++++++++++++++ crates/render/src/globaluniform/mod.rs | 7 + crates/render/src/gpustate.rs | 117 +++++++++++------ crates/render/src/lib.rs | 6 +- .../src/{framestate.rs => renderstate.rs} | 2 +- crates/render/src/sprite.rs | 18 +-- crates/render/src/texturearray.rs | 75 +++++++---- crates/render/src/vertexbuffer/types.rs | 8 +- 15 files changed, 421 insertions(+), 248 deletions(-) delete mode 100644 crates/render/src/globaldata.rs create mode 100644 crates/render/src/globaluniform/atlascontent.rs create mode 100644 crates/render/src/globaluniform/datacontent.rs create mode 100644 crates/render/src/globaluniform/globaluniform.rs create mode 100644 crates/render/src/globaluniform/mod.rs rename crates/render/src/{framestate.rs => renderstate.rs} (95%) diff --git a/crates/render/shaders/object.wgsl b/crates/render/shaders/object.wgsl index 8aee61c..86113b5 100644 --- a/crates/render/shaders/object.wgsl +++ b/crates/render/shaders/object.wgsl @@ -1,3 +1,5 @@ +// INCLUDE: global uniform header + struct InstanceInput { @location(2) transform_matrix_0: vec4, @location(3) transform_matrix_1: vec4, @@ -9,27 +11,12 @@ struct InstanceInput { struct VertexInput { @location(0) position: vec3, @location(1) texture_coords: vec2, -} +}; struct VertexOutput { @builtin(position) position: vec4, @location(0) texture_coords: vec2, @location(1) texture_index: u32, -} - - -@group(1) @binding(0) -var global: GlobalUniform; -struct GlobalUniform { - camera_position: vec2, - camera_zoom: vec2, - camera_zoom_limits: vec2, - window_size: vec2, - window_aspect: vec2, - starfield_texture: vec2, - starfield_tile_size: vec2, - starfield_size_limits: vec2, - current_time: vec2, }; @@ -56,8 +43,17 @@ fn vertex_main( var out: VertexOutput; out.position = transform * vec4(vertex.position, 1.0); - out.texture_coords = vertex.texture_coords; - out.texture_index = instance.texture_index; + + 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); + } + if vertex.texture_coords.y == 1.0 { + out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height); + } + return out; } diff --git a/crates/render/shaders/particle.wgsl b/crates/render/shaders/particle.wgsl index 2632b75..2d989a6 100644 --- a/crates/render/shaders/particle.wgsl +++ b/crates/render/shaders/particle.wgsl @@ -1,3 +1,5 @@ +// INCLUDE: global uniform header + struct InstanceInput { @location(2) position: vec2, @location(3) velocity: vec2, @@ -22,21 +24,6 @@ struct VertexOutput { } -@group(1) @binding(0) -var global: GlobalUniform; -struct GlobalUniform { - camera_position: vec2, - camera_zoom: vec2, - camera_zoom_limits: vec2, - window_size: vec2, - window_aspect: vec2, - starfield_texture: vec2, - starfield_tile_size: vec2, - starfield_size_limits: vec2, - current_time: vec2, -}; - - @group(0) @binding(0) var texture_array: binding_array>; @group(0) @binding(1) @@ -57,7 +44,7 @@ fn vertex_main( out.texture_coords = vertex.texture_coords; if instance.expires < global.current_time.x { - out.texture_index = instance.texture_index_len_rep.x; + out.texture_index = u32(0); out.position = vec4(2.0, 2.0, 0.0, 1.0); return out; } @@ -80,7 +67,17 @@ fn vertex_main( } - out.texture_index = instance.texture_index_len_rep.x + frame; + // Pick image + 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); + } + if vertex.texture_coords.y == 1.0 { + out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height); + } + let rotation = mat2x2(instance.rotation_0, instance.rotation_1); var scale: f32 = instance.size / global.camera_zoom.x; diff --git a/crates/render/shaders/starfield.wgsl b/crates/render/shaders/starfield.wgsl index 0d287f2..9bc09b0 100644 --- a/crates/render/shaders/starfield.wgsl +++ b/crates/render/shaders/starfield.wgsl @@ -1,3 +1,5 @@ +// INCLUDE: global uniform header + struct InstanceInput { @location(2) position: vec3, @location(3) size: f32, @@ -12,24 +14,10 @@ struct VertexInput { struct VertexOutput { @builtin(position) position: vec4, @location(0) texture_coords: vec2, - @location(1) tint: vec2, + @location(1) texture_index: u32, + @location(2) tint: vec2, } -@group(1) @binding(0) -var global: GlobalUniform; -struct GlobalUniform { - camera_position: vec2, - camera_zoom: vec2, - camera_zoom_limits: vec2, - window_size: vec2, - window_aspect: vec2, - starfield_texture: vec2, - starfield_tile_size: vec2, - starfield_size_limits: vec2, - current_time: vec2, -}; - - @group(0) @binding(0) var texture_array: binding_array>; @group(0) @binding(1) @@ -49,7 +37,6 @@ fn vertex_main( ) -> VertexOutput { var out: VertexOutput; - out.texture_coords = vertex.texture_coords; out.tint = instance.tint; // Center of the tile the camera is currently in, in game coordinates. @@ -120,6 +107,17 @@ fn vertex_main( ); out.position = vec4(pos, 0.0, 1.0) * instance.position.z; + + 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); + } + if vertex.texture_coords.y == 1.0 { + out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height); + } + return out; } @@ -136,7 +134,7 @@ fn fragment_main(in: VertexOutput) -> @location(0) vec4 { let c_del = c_bot - c_top; return textureSampleLevel( - texture_array[global.starfield_texture.x], + texture_array[in.texture_index], sampler_array[0], in.texture_coords, 0.0 diff --git a/crates/render/shaders/ui.wgsl b/crates/render/shaders/ui.wgsl index 30e7f81..e8467d5 100644 --- a/crates/render/shaders/ui.wgsl +++ b/crates/render/shaders/ui.wgsl @@ -1,3 +1,5 @@ +// INCLUDE: global uniform header + struct InstanceInput { @location(2) transform_matrix_0: vec4, @location(3) transform_matrix_1: vec4, @@ -19,22 +21,6 @@ struct VertexOutput { @location(2) color_transform: vec4, } - -@group(1) @binding(0) -var global: GlobalUniform; -struct GlobalUniform { - camera_position: vec2, - camera_zoom: vec2, - camera_zoom_limits: vec2, - window_size: vec2, - window_aspect: vec2, - starfield_texture: vec2, - starfield_tile_size: vec2, - starfield_size_limits: vec2, - current_time: vec2, -}; - - @group(0) @binding(0) var texture_array: binding_array>; @group(0) @binding(1) @@ -58,9 +44,18 @@ fn vertex_main( var out: VertexOutput; out.position = transform * vec4(vertex.position, 1.0); - out.texture_coords = vertex.texture_coords; - out.texture_index = instance.texture_index; out.color_transform = instance.color_transform; + + 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); + } + if vertex.texture_coords.y == 1.0 { + out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height); + } + return out; } diff --git a/crates/render/src/globaldata.rs b/crates/render/src/globaldata.rs deleted file mode 100644 index 8cb0251..0000000 --- a/crates/render/src/globaldata.rs +++ /dev/null @@ -1,96 +0,0 @@ -use bytemuck; -use std::mem; -use wgpu; - -pub struct GlobalData { - pub buffer: wgpu::Buffer, - pub bind_group: wgpu::BindGroup, - pub bind_group_layout: wgpu::BindGroupLayout, - pub content: GlobalDataContent, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)] -// Uniforms require uniform alignment. -// Since the largest value in this array is a [f32; 2], -// all smaller values must be padded. -// also, [f32; 3] are aligned as [f32; 4] -// (since alignments must be powers of two) -pub struct GlobalDataContent { - /// Camera position, in game units - pub camera_position: [f32; 2], - - /// Camera zoom value, in game units. - /// Second component is ignored. - pub camera_zoom: [f32; 2], - - /// Camera zoom min and max. - pub camera_zoom_limits: [f32; 2], - - /// Size ratio of window, in physical pixels - pub window_size: [f32; 2], - - /// Aspect ratio of window - /// Second component is ignored. - pub window_aspect: [f32; 2], - - /// Texture index of starfield sprites - /// Second component is ignored. - pub starfield_texture: [u32; 2], - - // Size of (square) starfield tiles, in game units - /// Second component is ignored. - pub starfield_tile_size: [f32; 2], - - /// Min and max starfield star size, in game units - pub starfield_size_limits: [f32; 2], - - /// Current game time, in seconds. - /// Second component is ignored. - pub current_time: [f32; 2], -} - -impl GlobalDataContent { - const SIZE: u64 = mem::size_of::() as wgpu::BufferAddress; -} - -impl GlobalData { - pub fn new(device: &wgpu::Device) -> Self { - let buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: GlobalDataContent::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 { - binding: 0, - 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("globaldata bind group layout"), - }); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }], - label: Some("globaldata bind group"), - }); - - return Self { - buffer, - bind_group, - bind_group_layout, - content: GlobalDataContent::default(), - }; - } -} diff --git a/crates/render/src/globaluniform/atlascontent.rs b/crates/render/src/globaluniform/atlascontent.rs new file mode 100644 index 0000000..afccd34 --- /dev/null +++ b/crates/render/src/globaluniform/atlascontent.rs @@ -0,0 +1,44 @@ +use bytemuck::{Pod, Zeroable}; +use std::mem; +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, +} + +#[derive(Debug, Copy, Clone)] +pub struct ImageLocations { + /// Format: [x, y, w, h], in texture coordinates + pub data: [ImageLocation; 108], +} + +unsafe impl Pod for ImageLocations {} +unsafe impl Zeroable for ImageLocations { + fn zeroed() -> Self { + Self { + data: [ImageLocation::zeroed(); 108], + } + } +} + +impl Default for ImageLocations { + fn default() -> Self { + Self::zeroed() + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, Default, Pod, Zeroable)] +pub struct AtlasContent { + pub locations: ImageLocations, +} + +impl AtlasContent { + pub const SIZE: u64 = mem::size_of::() as wgpu::BufferAddress; +} diff --git a/crates/render/src/globaluniform/datacontent.rs b/crates/render/src/globaluniform/datacontent.rs new file mode 100644 index 0000000..f95d1d5 --- /dev/null +++ b/crates/render/src/globaluniform/datacontent.rs @@ -0,0 +1,48 @@ +use bytemuck; +use std::mem; +use wgpu; + +#[repr(C)] +#[derive(Debug, Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)] +// Uniforms require uniform alignment. +// Since the largest value in this array is a [f32; 2], +// all smaller values must be padded. +// also, [f32; 3] are aligned as [f32; 4] +// (since alignments must be powers of two) +pub struct DataContent { + /// Camera position, in game units + pub camera_position: [f32; 2], + + /// Camera zoom value, in game units. + /// Second component is ignored. + pub camera_zoom: [f32; 2], + + /// Camera zoom min and max. + pub camera_zoom_limits: [f32; 2], + + /// Size ratio of window, in physical pixels + pub window_size: [f32; 2], + + /// Aspect ratio of window + /// Second component is ignored. + pub window_aspect: [f32; 2], + + /// Index of starfield sprite + /// Second component is ignored. + pub starfield_sprite: [u32; 2], + + // Size of (square) starfield tiles, in game units + /// Second component is ignored. + pub starfield_tile_size: [f32; 2], + + /// Min and max starfield star size, in game units + pub starfield_size_limits: [f32; 2], + + /// Current game time, in seconds. + /// Second component is ignored. + pub current_time: [f32; 2], +} + +impl DataContent { + 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 new file mode 100644 index 0000000..57ac422 --- /dev/null +++ b/crates/render/src/globaluniform/globaluniform.rs @@ -0,0 +1,120 @@ +use wgpu; + +use super::{AtlasContent, DataContent}; + +pub struct GlobalUniform { + pub data_buffer: wgpu::Buffer, + pub atlas_buffer: wgpu::Buffer, + pub bind_group: wgpu::BindGroup, + pub bind_group_layout: wgpu::BindGroupLayout, + pub content: DataContent, +} + +impl GlobalUniform { + pub fn shader_header(&self, group: u32) -> String { + let mut out = String::new(); + + out.push_str(&format!("@group({group}) @binding(0)\n")); + out.push_str( + r#" + var global: GlobalUniform; + struct GlobalUniform { + camera_position: vec2, + camera_zoom: vec2, + camera_zoom_limits: vec2, + window_size: vec2, + window_aspect: vec2, + starfield_texture: vec2, + starfield_tile_size: vec2, + starfield_size_limits: vec2, + current_time: vec2, + }; + "#, + ); + out.push_str("\n"); + + out.push_str(&format!("@group({group}) @binding(1)\n")); + out.push_str( + r#" + var atlas: AtlasUniform; + struct TextureLocation { + xpos: f32, + ypos: f32, + width: f32, + height: f32, + }; + struct AtlasUniform { + texture_locations: array, + }; + "#, + ); + out.push_str("\n"); + + return out; + } + + pub fn new(device: &wgpu::Device) -> Self { + let data_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("global uniform data buffer"), + size: DataContent::SIZE, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let atlas_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("global uniform atlas buffer"), + size: AtlasContent::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 { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + 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"), + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: data_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: atlas_buffer.as_entire_binding(), + }, + ], + label: Some("global uniform bind group"), + }); + + return Self { + data_buffer, + atlas_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 new file mode 100644 index 0000000..efb5be4 --- /dev/null +++ b/crates/render/src/globaluniform/mod.rs @@ -0,0 +1,7 @@ +mod atlascontent; +mod datacontent; +mod globaluniform; + +pub use atlascontent::{AtlasContent, ImageLocation, ImageLocations}; +pub use datacontent::DataContent; +pub use globaluniform::GlobalUniform; diff --git a/crates/render/src/gpustate.rs b/crates/render/src/gpustate.rs index 37ba8f5..7ce0f44 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, - globaldata::{GlobalData, GlobalDataContent}, + globaluniform::{AtlasContent, DataContent, GlobalUniform}, pipeline::PipelineBuilder, sprite::ObjectSubSprite, starfield::Starfield, @@ -18,7 +18,7 @@ use crate::{ types::{ObjectInstance, ParticleInstance, StarfieldInstance, TexturedVertex, UiInstance}, BufferObject, VertexBuffer, }, - FrameState, ObjectSprite, UiSprite, OPENGL_TO_WGPU_MATRIX, + ObjectSprite, RenderState, UiSprite, OPENGL_TO_WGPU_MATRIX, }; /// A high-level GPU wrapper. Consumes game state, @@ -44,7 +44,7 @@ pub struct GPUState { starfield: Starfield, texture_array: TextureArray, - global_data: GlobalData, + global_uniform: GlobalUniform, vertex_buffers: VertexBuffers, } @@ -60,6 +60,19 @@ struct VertexBuffers { particle: Rc, } +/// Preprocess shader files +fn preprocess_shader( + shader: &str, + global_uniform: &GlobalUniform, + global_uniform_group: u32, +) -> String { + // Insert common headers + shader.replace( + "// INCLUDE: global uniform header", + &global_uniform.shader_header(global_uniform_group), + ) +} + impl GPUState { /// Make a new GPUState that draws on `window` pub async fn new(window: Window, ct: &content::Content) -> Result { @@ -161,22 +174,26 @@ impl GPUState { }; // Load uniforms - let global_data = GlobalData::new(&device); + let global_uniform = GlobalUniform::new(&device); let texture_array = TextureArray::new(&device, &queue, ct)?; // Make sure these match the indices in each shader let bind_group_layouts = &[ &texture_array.bind_group_layout, - &global_data.bind_group_layout, + &global_uniform.bind_group_layout, ]; // Create render pipelines let object_pipeline = PipelineBuilder::new("object", &device) - .set_shader(include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/shaders/", - "object.wgsl" - ))) + .set_shader(&preprocess_shader( + &include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/shaders/", + "object.wgsl" + )), + &global_uniform, + 1, + )) .set_format(config.format) .set_triangle(true) .set_vertex_buffer(&vertex_buffers.object) @@ -184,11 +201,15 @@ impl GPUState { .build(); let starfield_pipeline = PipelineBuilder::new("starfield", &device) - .set_shader(include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/shaders/", - "starfield.wgsl" - ))) + .set_shader(&preprocess_shader( + &include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/shaders/", + "starfield.wgsl" + )), + &global_uniform, + 1, + )) .set_format(config.format) .set_triangle(true) .set_vertex_buffer(&vertex_buffers.starfield) @@ -196,11 +217,11 @@ impl GPUState { .build(); let ui_pipeline = PipelineBuilder::new("ui", &device) - .set_shader(include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/shaders/", - "ui.wgsl" - ))) + .set_shader(&preprocess_shader( + &include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/", "ui.wgsl")), + &global_uniform, + 1, + )) .set_format(config.format) .set_triangle(true) .set_vertex_buffer(&vertex_buffers.ui) @@ -208,11 +229,15 @@ impl GPUState { .build(); let particle_pipeline = PipelineBuilder::new("particle", &device) - .set_shader(include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/shaders/", - "particle.wgsl" - ))) + .set_shader(&preprocess_shader( + &include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/shaders/", + "particle.wgsl" + )), + &global_uniform, + 1, + )) .set_format(config.format) .set_triangle(true) .set_vertex_buffer(&vertex_buffers.particle) @@ -239,7 +264,7 @@ impl GPUState { starfield, texture_array, - global_data, + global_uniform, vertex_buffers, }); } @@ -283,7 +308,7 @@ impl GPUState { } - camera_pos.to_vec()) / s.pos.z }; - let texture = self.texture_array.get_texture(s.texture); + 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. @@ -345,7 +370,7 @@ impl GPUState { instances.push(ObjectInstance { transform: t.into(), - texture_index: texture.index, + sprite_index: texture.index, }); // Add children @@ -366,7 +391,7 @@ impl GPUState { parent_pos: Point2, parent_angle: Deg, ) { - let texture = self.texture_array.get_texture(s.texture); + let texture = self.texture_array.get_texture(s.sprite); let scale = s.size / (s.pos.z * camera_zoom); let sprite_aspect_and_scale = Matrix4::from_nonuniform_scale(texture.aspect * scale, scale, 1.0); @@ -395,7 +420,7 @@ impl GPUState { instances.push(ObjectInstance { transform: t.into(), - texture_index: texture.index, + sprite_index: texture.index, }); } @@ -404,7 +429,7 @@ impl GPUState { let logical_size: LogicalSize = self.window_size.to_logical(self.window.scale_factor()); - let texture = self.texture_array.get_texture(s.texture); + let texture = self.texture_array.get_texture(s.sprite); let width = s.dimensions.x; let height = s.dimensions.y; @@ -448,7 +473,7 @@ impl GPUState { instances.push(UiInstance { transform: (OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * scale).into(), - texture_index: texture.index, + sprite_index: texture.index, color: s.color.unwrap_or([1.0, 1.0, 1.0, 1.0]), }); } @@ -456,7 +481,7 @@ 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: FrameState) -> (usize, usize) { + fn update_sprite_instances(&self, framestate: RenderState) -> (usize, usize) { let mut object_instances: Vec = Vec::new(); // Game coordinates (relative to camera) of ne and sw corners of screen. @@ -518,8 +543,22 @@ impl GPUState { ); } + /// Initialize the rendering engine + pub fn init(&mut self) { + // Update global values + self.queue.write_buffer( + &self.global_uniform.atlas_buffer, + 0, + bytemuck::cast_slice(&[AtlasContent { + locations: self.texture_array.data, + }]), + ); + + self.update_starfield_buffer(); + } + /// Main render function. Draws sprites on a window. - pub fn render(&mut self, framestate: FrameState) -> Result<(), wgpu::SurfaceError> { + pub fn render(&mut self, framestate: RenderState) -> Result<(), wgpu::SurfaceError> { let output = self.surface.get_current_texture()?; let view = output .texture @@ -554,9 +593,9 @@ impl GPUState { // Update global values self.queue.write_buffer( - &self.global_data.buffer, + &self.global_uniform.data_buffer, 0, - bytemuck::cast_slice(&[GlobalDataContent { + bytemuck::cast_slice(&[DataContent { camera_position: framestate.camera_pos.into(), camera_zoom: [framestate.camera_zoom, 0.0], camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX], @@ -565,7 +604,7 @@ impl GPUState { self.window_size.height as f32, ], window_aspect: [self.window_aspect, 0.0], - starfield_texture: [self.texture_array.get_starfield_texture().index, 0], + starfield_sprite: [self.texture_array.get_starfield_texture().index, 0], starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0], starfield_size_limits: [ galactica_constants::STARFIELD_SIZE_MIN, @@ -577,7 +616,7 @@ impl GPUState { // Write all new particles to GPU buffer for i in framestate.new_particles.iter() { - let texture = self.texture_array.get_texture(i.texture); + let texture = self.texture_array.get_texture(i.sprite); self.queue.write_buffer( &self.vertex_buffers.particle.instances, ParticleInstance::SIZE * self.vertex_buffers.particle_array_head, @@ -607,7 +646,7 @@ impl GPUState { // These should match the indices in each shader, // and should each have a corresponding bind group layout. render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]); - render_pass.set_bind_group(1, &self.global_data.bind_group, &[]); + render_pass.set_bind_group(1, &self.global_uniform.bind_group, &[]); // Starfield pipeline self.vertex_buffers.starfield.set_in_pass(&mut render_pass); diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index acb669a..a341f5a 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -7,18 +7,18 @@ //! and the only one external code should interact with. //! (Excluding data structs, like [`ObjectSprite`]) -mod framestate; -mod globaldata; +mod globaluniform; mod gpustate; mod pipeline; +mod renderstate; mod sprite; mod starfield; mod texturearray; mod vertexbuffer; -pub use framestate::FrameState; use galactica_content as content; pub use gpustate::GPUState; +pub use renderstate::RenderState; pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, ParticleBuilder, UiSprite}; use cgmath::Matrix4; diff --git a/crates/render/src/framestate.rs b/crates/render/src/renderstate.rs similarity index 95% rename from crates/render/src/framestate.rs rename to crates/render/src/renderstate.rs index 4c3d369..bad503d 100644 --- a/crates/render/src/framestate.rs +++ b/crates/render/src/renderstate.rs @@ -3,7 +3,7 @@ use cgmath::Point2; use crate::{ObjectSprite, ParticleBuilder, UiSprite}; /// Bundles parameters passed to a single call to GPUState::render -pub struct FrameState<'a> { +pub struct RenderState<'a> { /// Camera position, in world units pub camera_pos: Point2, diff --git a/crates/render/src/sprite.rs b/crates/render/src/sprite.rs index d0e97e1..f42f28e 100644 --- a/crates/render/src/sprite.rs +++ b/crates/render/src/sprite.rs @@ -3,8 +3,8 @@ use cgmath::{Deg, Point2, Point3, Vector2}; /// Instructions to create a new particle pub struct ParticleBuilder { - /// The texture to use for this particle - pub texture: content::TextureHandle, + /// The sprite to use for this particle + pub sprite: content::SpriteHandle, /// This object's center, in world coordinates. pub pos: Point2, @@ -18,7 +18,7 @@ pub struct ParticleBuilder { /// This particle's lifetime, in seconds pub lifetime: f32, - /// The size of this sprite, + /// The size of this particle, /// given as height in world units. pub size: f32, } @@ -54,8 +54,8 @@ pub enum AnchoredUiPosition { /// A sprite that represents a ui element #[derive(Debug, Clone)] pub struct UiSprite { - /// The texture to use for this sprite - pub texture: content::TextureHandle, + /// The sprite to draw + pub sprite: content::SpriteHandle, /// This object's position, in logical (dpi-adjusted) pixels pub pos: AnchoredUiPosition, @@ -75,8 +75,8 @@ pub struct UiSprite { /// Ships, planets, debris, etc #[derive(Debug, Clone)] pub struct ObjectSprite { - /// The texture to use for this sprite - pub texture: content::TextureHandle, + /// The sprite to draw + pub sprite: content::SpriteHandle, /// This object's center, in world coordinates. pub pos: Point3, @@ -97,8 +97,8 @@ pub struct ObjectSprite { /// A sprite that is drawn relative to an ObjectSprite. #[derive(Debug, Clone)] pub struct ObjectSubSprite { - /// The sprite texture to draw - pub texture: content::TextureHandle, + /// The sprite to draw + pub sprite: content::SpriteHandle, /// This object's position, in world coordinates. /// This is relative to this sprite's parent. diff --git a/crates/render/src/texturearray.rs b/crates/render/src/texturearray.rs index 66249ad..8d8d6a5 100644 --- a/crates/render/src/texturearray.rs +++ b/crates/render/src/texturearray.rs @@ -1,5 +1,10 @@ -use crate::content; +use crate::{ + content, + globaluniform::{ImageLocation, ImageLocations}, +}; use anyhow::Result; +use bytemuck::Zeroable; +use galactica_packer::SpriteAtlasImage; use image::GenericImageView; use std::{collections::HashMap, fs::File, io::Read, num::NonZeroU32}; use wgpu::BindGroupLayout; @@ -67,30 +72,32 @@ impl RawTexture { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct Texture { pub index: u32, // Index in texture array pub len: u32, // Number of frames pub fps: f32, // Frames per second pub aspect: f32, // width / height pub repeat: u32, // How to re-play this texture + pub location: Vec, } pub struct TextureArray { pub bind_group: wgpu::BindGroup, pub bind_group_layout: BindGroupLayout, - starfield_handle: content::TextureHandle, - textures: HashMap, + starfield_handle: content::SpriteHandle, + sprites: HashMap, + pub data: ImageLocations, } impl TextureArray { - pub fn get_starfield_texture(&self) -> Texture { - *self.textures.get(&self.starfield_handle).unwrap() + pub fn get_starfield_texture(&self) -> &Texture { + self.sprites.get(&self.starfield_handle).unwrap() } - pub fn get_texture(&self, handle: content::TextureHandle) -> Texture { - match self.textures.get(&handle) { - Some(x) => *x, + 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"), } } @@ -98,26 +105,44 @@ impl TextureArray { pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, ct: &content::Content) -> Result { // Load all textures let mut texture_data = Vec::new(); - let mut textures = HashMap::new(); + let mut sprites = HashMap::new(); - for t in &ct.textures { - let index = texture_data.len() as u32; - for f in &t.frames { - let mut f = File::open(&f)?; - let mut bytes = Vec::new(); - f.read_to_end(&mut bytes)?; - texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, &t.name)?); - } - textures.insert( + println!("opening image"); + let mut f = File::open("atlas-0.bmp")?; + let mut bytes = Vec::new(); + f.read_to_end(&mut bytes)?; + texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, "Atlas")?); + + let mut tx = ImageLocations::zeroed(); + + println!("sending to gpu"); + let mut i = 0; + for t in &ct.sprites { + let loc = ct.get_image(&t.frames[0]); + + sprites.insert( t.handle, Texture { - index, - aspect: t.handle.aspect, + index: i, + aspect: t.aspect, fps: t.fps, len: t.frames.len() as u32, repeat: t.repeat.as_int(), + location: vec![loc.clone()], }, ); + + // 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, + }; + i += 1; + } } let sampler = device.create_sampler(&wgpu::SamplerDescriptor { @@ -136,7 +161,7 @@ impl TextureArray { // Texture data wgpu::BindGroupLayoutEntry { binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Texture { multisampled: false, view_dimension: wgpu::TextureViewDimension::D2, @@ -147,7 +172,7 @@ impl TextureArray { // Texture sampler wgpu::BindGroupLayoutEntry { binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: NonZeroU32::new(1), }, @@ -161,7 +186,6 @@ impl TextureArray { entries: &[ wgpu::BindGroupEntry { binding: 0, - // Array of all views resource: wgpu::BindingResource::TextureViewArray(&views), }, wgpu::BindGroupEntry { @@ -174,8 +198,9 @@ impl TextureArray { return Ok(Self { bind_group, bind_group_layout, - textures: textures, + sprites, starfield_handle: ct.get_starfield_handle(), + data: tx, }); } } diff --git a/crates/render/src/vertexbuffer/types.rs b/crates/render/src/vertexbuffer/types.rs index 4b2f18c..27b4c11 100644 --- a/crates/render/src/vertexbuffer/types.rs +++ b/crates/render/src/vertexbuffer/types.rs @@ -89,7 +89,7 @@ pub struct ObjectInstance { pub transform: [[f32; 4]; 4], /// What texture to use for this sprite - pub texture_index: u32, + pub sprite_index: u32, } impl BufferObject for ObjectInstance { @@ -122,7 +122,7 @@ impl BufferObject for ObjectInstance { shader_location: 5, format: wgpu::VertexFormat::Float32x4, }, - // Texture + // Sprite wgpu::VertexAttribute { offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress, shader_location: 6, @@ -146,7 +146,7 @@ pub struct UiInstance { pub color: [f32; 4], /// What texture to use for this sprite - pub texture_index: u32, + pub sprite_index: u32, } impl BufferObject for UiInstance { @@ -185,7 +185,7 @@ impl BufferObject for UiInstance { shader_location: 6, format: wgpu::VertexFormat::Float32x4, }, - // Texture + // Sprite wgpu::VertexAttribute { offset: mem::size_of::<[f32; 20]>() as wgpu::BufferAddress, shader_location: 7,