Added sprite data uniform

master
Mark 2024-01-04 18:15:30 -08:00
parent 613245b92e
commit a4ca62e1dc
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
16 changed files with 219 additions and 134 deletions

View File

@ -11,7 +11,11 @@ use std::{cmp::Eq, hash::Hash};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct SpriteHandle { pub struct SpriteHandle {
/// The index of this sprite in content.sprites /// 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) /// The aspect ratio of this sprite (width / height)
pub aspect: f32, pub aspect: f32,

View File

@ -250,7 +250,7 @@ impl Content {
pub fn get_sprite(&self, h: SpriteHandle) -> &Sprite { 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. // 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. // 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 /// Get a sprite from a path

View File

@ -105,7 +105,7 @@ impl crate::Build for Sprite {
})?; })?;
let h = SpriteHandle { let h = SpriteHandle {
index: ct.sprites.len(), index: ct.sprites.len() as u32,
aspect: dim.0 as f32 / dim.1 as f32, aspect: dim.0 as f32 / dim.1 as f32,
}; };
@ -162,7 +162,7 @@ impl crate::Build for Sprite {
let dim = dim.unwrap(); let dim = dim.unwrap();
let h = SpriteHandle { let h = SpriteHandle {
index: ct.sprites.len(), index: ct.sprites.len() as u32,
aspect: dim.0 as f32 / dim.1 as f32, aspect: dim.0 as f32 / dim.1 as f32,
}; };

View File

@ -182,6 +182,7 @@ impl Game {
ui_sprites: self.get_ui_sprites(), ui_sprites: self.get_ui_sprites(),
new_particles: &mut self.new_particles, new_particles: &mut self.new_particles,
current_time: self.start_instant.elapsed().as_secs_f32(), current_time: self.start_instant.elapsed().as_secs_f32(),
content: &self.content,
} }
} }

View File

@ -44,8 +44,9 @@ fn vertex_main(
var out: VertexOutput; var out: VertexOutput;
out.position = transform * vec4<f32>(vertex.position, 1.0); out.position = transform * vec4<f32>(vertex.position, 1.0);
let i = sprites.data[instance.texture_index].first_frame;
let t = atlas.data[i];
out.texture_index = u32(0); out.texture_index = u32(0);
let t = atlas.texture_locations[instance.texture_index];
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 {
out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y); out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y);

View File

@ -8,8 +8,7 @@ struct InstanceInput {
@location(6) size: f32, @location(6) size: f32,
@location(7) created: f32, @location(7) created: f32,
@location(8) expires: f32, @location(8) expires: f32,
@location(9) texture_index_len_rep: vec3<u32>, @location(9) texture_index: u32,
@location(10) texture_aspect_fps: vec2<f32>,
}; };
struct VertexInput { struct VertexInput {
@ -51,25 +50,29 @@ fn vertex_main(
let age = global.current_time.x - instance.created; 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); var frame: u32 = u32(0);
if instance.texture_index_len_rep.z == u32(1) { if rep == u32(1) {
// Repeat // Repeat
frame = u32(fmod( frame = u32(fmod(
(age / instance.texture_aspect_fps.y), (age / fps),
f32(instance.texture_index_len_rep.y) f32(len)
)); ));
} else { } else {
// Once // Once
frame = u32(min( frame = u32(min(
(age / instance.texture_aspect_fps.y), (age / fps),
f32(instance.texture_index_len_rep.y) - 1.0 f32(len) - 1.0
)); ));
} }
// Pick image // Pick image
frame = frame + sprites.data[instance.texture_index].first_frame;
let t = atlas.data[frame];
out.texture_index = u32(0); 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); out.texture_coords = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 { if vertex.texture_coords.x == 1.0 {
out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y); out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y);
@ -84,7 +87,7 @@ fn vertex_main(
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>(
instance.texture_aspect_fps.x * scale / global.window_aspect.x, sprites.data[instance.texture_index].aspect * scale / global.window_aspect.x,
scale scale
); );
pos = rotation * pos; pos = rotation * pos;

View File

@ -108,8 +108,9 @@ fn vertex_main(
out.position = vec4<f32>(pos, 0.0, 1.0) * instance.position.z; out.position = vec4<f32>(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); out.texture_index = u32(0);
let t = atlas.texture_locations[global.starfield_texture.x];
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 {
out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y); out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y);

View File

@ -46,8 +46,9 @@ fn vertex_main(
out.position = transform * vec4<f32>(vertex.position, 1.0); out.position = transform * vec4<f32>(vertex.position, 1.0);
out.color_transform = instance.color_transform; 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); out.texture_index = u32(0);
let t = atlas.texture_locations[instance.texture_index];
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 {
out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y); out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y);

View File

@ -5,21 +5,20 @@ use wgpu;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, Pod, Zeroable, Default)] #[derive(Debug, Copy, Clone, Pod, Zeroable, Default)]
pub struct ImageLocation { pub struct ImageLocation {
/// Format: [x, y, w, h], in texture coordinates // Image box, in texture coordinates
pub x: f32, pub xpos: f32,
pub y: f32, pub ypos: f32,
pub w: f32, pub width: f32,
pub h: f32, pub height: f32,
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct ImageLocations { pub struct ImageLocationArray {
/// Format: [x, y, w, h], in texture coordinates
pub data: [ImageLocation; 108], pub data: [ImageLocation; 108],
} }
unsafe impl Pod for ImageLocations {} unsafe impl Pod for ImageLocationArray {}
unsafe impl Zeroable for ImageLocations { unsafe impl Zeroable for ImageLocationArray {
fn zeroed() -> Self { fn zeroed() -> Self {
Self { Self {
data: [ImageLocation::zeroed(); 108], data: [ImageLocation::zeroed(); 108],
@ -27,7 +26,7 @@ unsafe impl Zeroable for ImageLocations {
} }
} }
impl Default for ImageLocations { impl Default for ImageLocationArray {
fn default() -> Self { fn default() -> Self {
Self::zeroed() Self::zeroed()
} }
@ -36,7 +35,7 @@ impl Default for ImageLocations {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, Default, Pod, Zeroable)] #[derive(Debug, Copy, Clone, Default, Pod, Zeroable)]
pub struct AtlasContent { pub struct AtlasContent {
pub locations: ImageLocations, pub data: ImageLocationArray,
} }
impl AtlasContent { impl AtlasContent {

View File

@ -1,10 +1,11 @@
use wgpu; use wgpu;
use super::{AtlasContent, DataContent}; use super::{AtlasContent, DataContent, SpriteContent};
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 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,
@ -24,7 +25,7 @@ impl GlobalUniform {
camera_zoom_limits: vec2<f32>, camera_zoom_limits: vec2<f32>,
window_size: vec2<f32>, window_size: vec2<f32>,
window_aspect: vec2<f32>, window_aspect: vec2<f32>,
starfield_texture: vec2<u32>, starfield_sprite: vec2<u32>,
starfield_tile_size: vec2<f32>, starfield_tile_size: vec2<f32>,
starfield_size_limits: vec2<f32>, starfield_size_limits: vec2<f32>,
current_time: vec2<f32>, current_time: vec2<f32>,
@ -37,14 +38,42 @@ impl GlobalUniform {
out.push_str( out.push_str(
r#" r#"
var<uniform> atlas: AtlasUniform; var<uniform> atlas: AtlasUniform;
struct TextureLocation { struct ImageLocation {
xpos: f32, xpos: f32,
ypos: f32, ypos: f32,
width: f32, width: f32,
height: f32, height: f32,
}; };
struct AtlasUniform { struct AtlasUniform {
texture_locations: array<TextureLocation, 108>, data: array<ImageLocation, 108>,
};
"#,
);
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<uniform> 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<SpriteData, 108>,
}; };
"#, "#,
); );
@ -68,6 +97,13 @@ impl GlobalUniform {
mapped_at_creation: false, 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 { let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
@ -90,6 +126,16 @@ impl GlobalUniform {
}, },
count: None, 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"), label: Some("global uniform bind group layout"),
}); });
@ -105,6 +151,10 @@ impl GlobalUniform {
binding: 1, binding: 1,
resource: atlas_buffer.as_entire_binding(), resource: atlas_buffer.as_entire_binding(),
}, },
wgpu::BindGroupEntry {
binding: 2,
resource: sprite_buffer.as_entire_binding(),
},
], ],
label: Some("global uniform bind group"), label: Some("global uniform bind group"),
}); });
@ -112,6 +162,7 @@ impl GlobalUniform {
return Self { return Self {
data_buffer, data_buffer,
atlas_buffer, atlas_buffer,
sprite_buffer,
bind_group, bind_group,
bind_group_layout, bind_group_layout,
content: DataContent::default(), content: DataContent::default(),

View File

@ -1,7 +1,9 @@
mod atlascontent; mod atlascontent;
mod datacontent; mod datacontent;
mod globaluniform; mod globaluniform;
mod spritecontent;
pub use atlascontent::{AtlasContent, ImageLocation, ImageLocations}; pub use atlascontent::{AtlasContent, ImageLocation, ImageLocationArray};
pub use datacontent::DataContent; pub use datacontent::DataContent;
pub use globaluniform::GlobalUniform; pub use globaluniform::GlobalUniform;
pub use spritecontent::{SpriteContent, SpriteData, SpriteDataArray};

View File

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

View File

@ -8,7 +8,7 @@ use winit::{self, dpi::LogicalSize, window::Window};
use crate::{ use crate::{
content, content,
globaluniform::{AtlasContent, DataContent, GlobalUniform}, globaluniform::{AtlasContent, DataContent, GlobalUniform, SpriteContent},
pipeline::PipelineBuilder, pipeline::PipelineBuilder,
sprite::ObjectSubSprite, sprite::ObjectSubSprite,
starfield::Starfield, starfield::Starfield,
@ -292,8 +292,7 @@ impl GPUState {
/// Also handles child sprites. /// Also handles child sprites.
fn push_object_sprite( fn push_object_sprite(
&self, &self,
camera_zoom: f32, state: &RenderState,
camera_pos: Point2<f32>,
instances: &mut Vec<ObjectInstance>, instances: &mut Vec<ObjectInstance>,
clip_ne: Point2<f32>, clip_ne: Point2<f32>,
clip_sw: Point2<f32>, clip_sw: Point2<f32>,
@ -305,10 +304,9 @@ impl GPUState {
(Point2 { (Point2 {
x: s.pos.x, x: s.pos.x,
y: s.pos.y, y: s.pos.y,
} - camera_pos.to_vec()) } - state.camera_pos.to_vec())
/ s.pos.z / s.pos.z
}; };
let texture = self.texture_array.get_texture(s.sprite);
// Game dimensions of this sprite post-scale. // Game dimensions of this sprite post-scale.
// Don't divide by 2, we use this later. // Don't divide by 2, we use this later.
@ -316,7 +314,7 @@ impl GPUState {
// Width or height, whichever is larger. // Width or height, whichever is larger.
// Accounts for sprite rotation. // 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) // Don't draw (or compute matrices for)
// sprites that are off the screen // sprites that are off the screen
@ -329,7 +327,7 @@ impl GPUState {
} }
// TODO: clean up // TODO: clean up
let scale = height / camera_zoom; let scale = height / state.camera_zoom;
// Note that our mesh starts centered at (0, 0). // Note that our mesh starts centered at (0, 0).
// This is essential---we do not want scale and rotation // 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 // We apply the provided scale here as well as a minor optimization
let sprite_aspect_and_scale = 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 // Apply rotation
let rotate = Matrix4::from_angle_z(s.angle); let rotate = Matrix4::from_angle_z(s.angle);
@ -358,8 +356,8 @@ impl GPUState {
// The height of the viewport is `zoom` in game units, // The height of the viewport is `zoom` in game units,
// but it's 2 in screen units! (since coordinates range from -1 to 1) // but it's 2 in screen units! (since coordinates range from -1 to 1)
let translate = Matrix4::from_translation(Vector3 { let translate = Matrix4::from_translation(Vector3 {
x: pos.x / (camera_zoom / 2.0) / self.window_aspect, x: pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
y: pos.y / (camera_zoom / 2.0), y: pos.y / (state.camera_zoom / 2.0),
z: 0.0, z: 0.0,
}); });
@ -370,13 +368,13 @@ impl GPUState {
instances.push(ObjectInstance { instances.push(ObjectInstance {
transform: t.into(), transform: t.into(),
sprite_index: texture.index, sprite_index: s.sprite.index,
}); });
// Add children // Add children
if let Some(children) = &s.children { if let Some(children) = &s.children {
for c in 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`. /// Only called by `self.push_object_sprite`.
fn push_object_subsprite( fn push_object_subsprite(
&self, &self,
camera_zoom: f32, state: &RenderState,
instances: &mut Vec<ObjectInstance>, instances: &mut Vec<ObjectInstance>,
s: &ObjectSubSprite, s: &ObjectSubSprite,
parent_pos: Point2<f32>, parent_pos: Point2<f32>,
parent_angle: Deg<f32>, parent_angle: Deg<f32>,
) { ) {
let texture = self.texture_array.get_texture(s.sprite); let scale = s.size / (s.pos.z * state.camera_zoom);
let scale = s.size / (s.pos.z * camera_zoom);
let sprite_aspect_and_scale = 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 rotate = Matrix4::from_angle_z(s.angle);
let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0); let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0);
let ptranslate = Matrix4::from_translation(Vector3 { let ptranslate = Matrix4::from_translation(Vector3 {
x: parent_pos.x / (camera_zoom / 2.0) / self.window_aspect, x: parent_pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
y: parent_pos.y / (camera_zoom / 2.0), y: parent_pos.y / (state.camera_zoom / 2.0),
z: 0.0, z: 0.0,
}); });
let protate = Matrix4::from_angle_z(parent_angle); let protate = Matrix4::from_angle_z(parent_angle);
let translate = Matrix4::from_translation(Vector3 { let translate = Matrix4::from_translation(Vector3 {
x: s.pos.x / (camera_zoom / 2.0) / self.window_aspect, x: s.pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
y: s.pos.y / (camera_zoom / 2.0), y: s.pos.y / (state.camera_zoom / 2.0),
z: 0.0, z: 0.0,
}); });
@ -420,7 +417,7 @@ impl GPUState {
instances.push(ObjectInstance { instances.push(ObjectInstance {
transform: t.into(), transform: t.into(),
sprite_index: texture.index, sprite_index: s.sprite.index,
}); });
} }
@ -429,7 +426,6 @@ impl GPUState {
let logical_size: LogicalSize<f32> = let logical_size: LogicalSize<f32> =
self.window_size.to_logical(self.window.scale_factor()); self.window_size.to_logical(self.window.scale_factor());
let texture = self.texture_array.get_texture(s.sprite);
let width = s.dimensions.x; let width = s.dimensions.x;
let height = s.dimensions.y; let height = s.dimensions.y;
@ -473,7 +469,7 @@ impl GPUState {
instances.push(UiInstance { instances.push(UiInstance {
transform: (OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * scale).into(), 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]), 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 /// Make an instance for all the game's sprites
/// (Objects and UI) /// (Objects and UI)
/// This will Will panic if any X_SPRITE_INSTANCE_LIMIT is exceeded. /// 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<ObjectInstance> = Vec::new(); let mut object_instances: Vec<ObjectInstance> = Vec::new();
// Game coordinates (relative to camera) of ne and sw corners of screen. // Game coordinates (relative to camera) of ne and sw corners of screen.
// Used to skip off-screen sprites. // Used to skip off-screen sprites.
let clip_ne = 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)) * framestate.camera_zoom; let clip_sw = Point2::from((self.window_aspect, -1.0)) * state.camera_zoom;
for s in framestate.object_sprites { for s in &state.object_sprites {
self.push_object_sprite( self.push_object_sprite(state, &mut object_instances, clip_ne, clip_sw, &s);
framestate.camera_zoom,
framestate.camera_pos,
&mut object_instances,
clip_ne,
clip_sw,
&s,
);
} }
// Enforce sprite limit // Enforce sprite limit
@ -514,7 +503,7 @@ impl GPUState {
let mut ui_instances: Vec<UiInstance> = Vec::new(); let mut ui_instances: Vec<UiInstance> = Vec::new();
for s in framestate.ui_sprites { for s in &state.ui_sprites {
self.push_ui_sprite(&mut ui_instances, &s); self.push_ui_sprite(&mut ui_instances, &s);
} }
@ -550,7 +539,14 @@ impl GPUState {
&self.global_uniform.atlas_buffer, &self.global_uniform.atlas_buffer,
0, 0,
bytemuck::cast_slice(&[AtlasContent { 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. /// 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 output = self.surface.get_current_texture()?;
let view = output let view = output
.texture .texture
@ -591,32 +587,32 @@ impl GPUState {
timestamp_writes: None, timestamp_writes: None,
}); });
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,
0, 0,
bytemuck::cast_slice(&[DataContent { bytemuck::cast_slice(&[DataContent {
camera_position: framestate.camera_pos.into(), camera_position: state.camera_pos.into(),
camera_zoom: [framestate.camera_zoom, 0.0], camera_zoom: [state.camera_zoom, 0.0],
camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX], camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX],
window_size: [ window_size: [
self.window_size.width as f32, self.window_size.width as f32,
self.window_size.height as f32, self.window_size.height as f32,
], ],
window_aspect: [self.window_aspect, 0.0], 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_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0],
starfield_size_limits: [ starfield_size_limits: [
galactica_constants::STARFIELD_SIZE_MIN, galactica_constants::STARFIELD_SIZE_MIN,
galactica_constants::STARFIELD_SIZE_MAX, 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 // Write all new particles to GPU buffer
for i in framestate.new_particles.iter() { for i in state.new_particles.iter() {
let texture = self.texture_array.get_texture(i.sprite);
self.queue.write_buffer( self.queue.write_buffer(
&self.vertex_buffers.particle.instances, &self.vertex_buffers.particle.instances,
ParticleInstance::SIZE * self.vertex_buffers.particle_array_head, ParticleInstance::SIZE * self.vertex_buffers.particle_array_head,
@ -625,10 +621,9 @@ impl GPUState {
velocity: i.velocity.into(), velocity: i.velocity.into(),
rotation: Matrix2::from_angle(i.angle).into(), rotation: Matrix2::from_angle(i.angle).into(),
size: i.size, size: i.size,
texture_index_len_rep: [texture.index, texture.len, texture.repeat], sprite_index: i.sprite.index,
texture_aspect_fps: [texture.aspect, texture.fps], created: state.current_time,
created: framestate.current_time, expires: state.current_time + i.lifetime,
expires: framestate.current_time + i.lifetime,
}]), }]),
); );
self.vertex_buffers.particle_array_head += 1; self.vertex_buffers.particle_array_head += 1;
@ -638,10 +633,10 @@ impl GPUState {
self.vertex_buffers.particle_array_head = 0; self.vertex_buffers.particle_array_head = 0;
} }
} }
framestate.new_particles.clear(); state.new_particles.clear();
// Create sprite instances // 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, // These should match the indices in each shader,
// and should each have a corresponding bind group layout. // and should each have a corresponding bind group layout.

View File

@ -1,4 +1,5 @@
use cgmath::Point2; use cgmath::Point2;
use galactica_content::Content;
use crate::{ObjectSprite, ParticleBuilder, UiSprite}; use crate::{ObjectSprite, ParticleBuilder, UiSprite};
@ -23,4 +24,7 @@ pub struct RenderState<'a> {
// TODO: handle overflow // TODO: handle overflow
/// The current time, in seconds /// The current time, in seconds
pub current_time: f32, pub current_time: f32,
/// Game content
pub content: &'a Content,
} }

View File

@ -1,12 +1,12 @@
use crate::{ use crate::{
content, content,
globaluniform::{ImageLocation, ImageLocations}, globaluniform::{ImageLocation, ImageLocationArray, SpriteData, SpriteDataArray},
}; };
use anyhow::Result; use anyhow::Result;
use bytemuck::Zeroable; use bytemuck::Zeroable;
use galactica_packer::SpriteAtlasImage; use galactica_packer::SpriteAtlasImage;
use image::GenericImageView; use image::GenericImageView;
use std::{collections::HashMap, fs::File, io::Read, num::NonZeroU32}; use std::{fs::File, io::Read, num::NonZeroU32};
use wgpu::BindGroupLayout; use wgpu::BindGroupLayout;
pub(crate) struct RawTexture { pub(crate) struct RawTexture {
@ -85,27 +85,14 @@ 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,
starfield_handle: content::SpriteHandle, pub image_locations: ImageLocationArray,
sprites: HashMap<content::SpriteHandle, Texture>, pub sprite_data: SpriteDataArray,
pub data: ImageLocations,
} }
impl TextureArray { 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<Self> { pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, ct: &content::Content) -> Result<Self> {
// Load all textures // Load all textures
let mut texture_data = Vec::new(); let mut texture_data = Vec::new();
let mut sprites = HashMap::new();
println!("opening image"); println!("opening image");
let mut f = File::open("atlas-0.bmp")?; let mut f = File::open("atlas-0.bmp")?;
@ -113,35 +100,31 @@ impl TextureArray {
f.read_to_end(&mut bytes)?; f.read_to_end(&mut bytes)?;
texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, "Atlas")?); 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"); println!("sending to gpu");
let mut i = 0; let mut image_counter = 0;
for t in &ct.sprites { for t in &ct.sprites {
let loc = ct.get_image(&t.frames[0]); sprite_data.data[image_counter as usize] = SpriteData {
frame_count: t.frames.len() as u32,
sprites.insert( repeatmode: t.repeat.as_int(),
t.handle, aspect: t.aspect,
Texture { fps: t.fps,
index: i, first_frame: image_counter,
aspect: t.aspect, _padding: Default::default(),
fps: t.fps, };
len: t.frames.len() as u32,
repeat: t.repeat.as_int(),
location: vec![loc.clone()],
},
);
// Insert texture location data // Insert texture location data
for path in &t.frames { for path in &t.frames {
let image = ct.get_image(&path); let image = ct.get_image(&path);
tx.data[i as usize] = ImageLocation { image_locations.data[image_counter as usize] = ImageLocation {
x: image.x, xpos: image.x,
y: image.y, ypos: image.y,
w: image.w, width: image.w,
h: image.h, height: image.h,
}; };
i += 1; image_counter += 1;
} }
} }
@ -198,9 +181,8 @@ impl TextureArray {
return Ok(Self { return Ok(Self {
bind_group, bind_group,
bind_group_layout, bind_group_layout,
sprites, image_locations,
starfield_handle: ct.get_starfield_handle(), sprite_data,
data: tx,
}); });
} }
} }

View File

@ -219,9 +219,8 @@ pub struct ParticleInstance {
/// Time is kept by a variable in the global uniform. /// Time is kept by a variable in the global uniform.
pub expires: f32, pub expires: f32,
/// What texture to use for this particle /// What sprite to use for this particle
pub texture_index_len_rep: [u32; 3], pub sprite_index: u32,
pub texture_aspect_fps: [f32; 2],
} }
impl BufferObject for ParticleInstance { impl BufferObject for ParticleInstance {
@ -271,17 +270,11 @@ impl BufferObject for ParticleInstance {
shader_location: 8, shader_location: 8,
format: wgpu::VertexFormat::Float32, format: wgpu::VertexFormat::Float32,
}, },
// Texture index / len / repeat // Sprite
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
shader_location: 9, shader_location: 9,
format: wgpu::VertexFormat::Uint32x3, format: wgpu::VertexFormat::Uint32,
},
// Texture aspect / fps
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 14]>() as wgpu::BufferAddress,
shader_location: 10,
format: wgpu::VertexFormat::Float32x2,
}, },
], ],
} }