Adapted renderer for new texture indexing

master
Mark 2024-01-04 17:18:31 -08:00
parent c382431747
commit 7cde4f2346
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
15 changed files with 421 additions and 248 deletions

View File

@ -1,3 +1,5 @@
// INCLUDE: global uniform header
struct InstanceInput { struct InstanceInput {
@location(2) transform_matrix_0: vec4<f32>, @location(2) transform_matrix_0: vec4<f32>,
@location(3) transform_matrix_1: vec4<f32>, @location(3) transform_matrix_1: vec4<f32>,
@ -9,27 +11,12 @@ struct InstanceInput {
struct VertexInput { struct VertexInput {
@location(0) position: vec3<f32>, @location(0) position: vec3<f32>,
@location(1) texture_coords: vec2<f32>, @location(1) texture_coords: vec2<f32>,
} };
struct VertexOutput { struct VertexOutput {
@builtin(position) position: vec4<f32>, @builtin(position) position: vec4<f32>,
@location(0) texture_coords: vec2<f32>, @location(0) texture_coords: vec2<f32>,
@location(1) texture_index: u32, @location(1) texture_index: u32,
}
@group(1) @binding(0)
var<uniform> global: GlobalUniform;
struct GlobalUniform {
camera_position: vec2<f32>,
camera_zoom: vec2<f32>,
camera_zoom_limits: vec2<f32>,
window_size: vec2<f32>,
window_aspect: vec2<f32>,
starfield_texture: vec2<u32>,
starfield_tile_size: vec2<f32>,
starfield_size_limits: vec2<f32>,
current_time: vec2<f32>,
}; };
@ -56,8 +43,17 @@ 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);
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; return out;
} }

View File

@ -1,3 +1,5 @@
// INCLUDE: global uniform header
struct InstanceInput { struct InstanceInput {
@location(2) position: vec2<f32>, @location(2) position: vec2<f32>,
@location(3) velocity: vec2<f32>, @location(3) velocity: vec2<f32>,
@ -22,21 +24,6 @@ struct VertexOutput {
} }
@group(1) @binding(0)
var<uniform> global: GlobalUniform;
struct GlobalUniform {
camera_position: vec2<f32>,
camera_zoom: vec2<f32>,
camera_zoom_limits: vec2<f32>,
window_size: vec2<f32>,
window_aspect: vec2<f32>,
starfield_texture: vec2<u32>,
starfield_tile_size: vec2<f32>,
starfield_size_limits: vec2<f32>,
current_time: vec2<f32>,
};
@group(0) @binding(0) @group(0) @binding(0)
var texture_array: binding_array<texture_2d<f32>>; var texture_array: binding_array<texture_2d<f32>>;
@group(0) @binding(1) @group(0) @binding(1)
@ -57,7 +44,7 @@ fn vertex_main(
out.texture_coords = vertex.texture_coords; out.texture_coords = vertex.texture_coords;
if instance.expires < global.current_time.x { if instance.expires < global.current_time.x {
out.texture_index = instance.texture_index_len_rep.x; out.texture_index = u32(0);
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0); out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
return out; 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); let rotation = mat2x2(instance.rotation_0, instance.rotation_1);
var scale: f32 = instance.size / global.camera_zoom.x; var scale: f32 = instance.size / global.camera_zoom.x;

View File

@ -1,3 +1,5 @@
// INCLUDE: global uniform header
struct InstanceInput { struct InstanceInput {
@location(2) position: vec3<f32>, @location(2) position: vec3<f32>,
@location(3) size: f32, @location(3) size: f32,
@ -12,24 +14,10 @@ struct VertexInput {
struct VertexOutput { struct VertexOutput {
@builtin(position) position: vec4<f32>, @builtin(position) position: vec4<f32>,
@location(0) texture_coords: vec2<f32>, @location(0) texture_coords: vec2<f32>,
@location(1) tint: vec2<f32>, @location(1) texture_index: u32,
@location(2) tint: vec2<f32>,
} }
@group(1) @binding(0)
var<uniform> global: GlobalUniform;
struct GlobalUniform {
camera_position: vec2<f32>,
camera_zoom: vec2<f32>,
camera_zoom_limits: vec2<f32>,
window_size: vec2<f32>,
window_aspect: vec2<f32>,
starfield_texture: vec2<u32>,
starfield_tile_size: vec2<f32>,
starfield_size_limits: vec2<f32>,
current_time: vec2<f32>,
};
@group(0) @binding(0) @group(0) @binding(0)
var texture_array: binding_array<texture_2d<f32>>; var texture_array: binding_array<texture_2d<f32>>;
@group(0) @binding(1) @group(0) @binding(1)
@ -49,7 +37,6 @@ fn vertex_main(
) -> VertexOutput { ) -> VertexOutput {
var out: VertexOutput; var out: VertexOutput;
out.texture_coords = vertex.texture_coords;
out.tint = instance.tint; out.tint = instance.tint;
// Center of the tile the camera is currently in, in game coordinates. // Center of the tile the camera is currently in, in game coordinates.
@ -120,6 +107,17 @@ 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;
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; return out;
} }
@ -136,7 +134,7 @@ fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
let c_del = c_bot - c_top; let c_del = c_bot - c_top;
return textureSampleLevel( return textureSampleLevel(
texture_array[global.starfield_texture.x], texture_array[in.texture_index],
sampler_array[0], sampler_array[0],
in.texture_coords, in.texture_coords,
0.0 0.0

View File

@ -1,3 +1,5 @@
// INCLUDE: global uniform header
struct InstanceInput { struct InstanceInput {
@location(2) transform_matrix_0: vec4<f32>, @location(2) transform_matrix_0: vec4<f32>,
@location(3) transform_matrix_1: vec4<f32>, @location(3) transform_matrix_1: vec4<f32>,
@ -19,22 +21,6 @@ struct VertexOutput {
@location(2) color_transform: vec4<f32>, @location(2) color_transform: vec4<f32>,
} }
@group(1) @binding(0)
var<uniform> global: GlobalUniform;
struct GlobalUniform {
camera_position: vec2<f32>,
camera_zoom: vec2<f32>,
camera_zoom_limits: vec2<f32>,
window_size: vec2<f32>,
window_aspect: vec2<f32>,
starfield_texture: vec2<u32>,
starfield_tile_size: vec2<f32>,
starfield_size_limits: vec2<f32>,
current_time: vec2<f32>,
};
@group(0) @binding(0) @group(0) @binding(0)
var texture_array: binding_array<texture_2d<f32>>; var texture_array: binding_array<texture_2d<f32>>;
@group(0) @binding(1) @group(0) @binding(1)
@ -58,9 +44,18 @@ 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);
out.texture_coords = vertex.texture_coords;
out.texture_index = instance.texture_index;
out.color_transform = instance.color_transform; 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; return out;
} }

View File

@ -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::<Self>() 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(),
};
}
}

View File

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

View File

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

View File

@ -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<uniform> global: GlobalUniform;
struct GlobalUniform {
camera_position: vec2<f32>,
camera_zoom: vec2<f32>,
camera_zoom_limits: vec2<f32>,
window_size: vec2<f32>,
window_aspect: vec2<f32>,
starfield_texture: vec2<u32>,
starfield_tile_size: vec2<f32>,
starfield_size_limits: vec2<f32>,
current_time: vec2<f32>,
};
"#,
);
out.push_str("\n");
out.push_str(&format!("@group({group}) @binding(1)\n"));
out.push_str(
r#"
var<uniform> atlas: AtlasUniform;
struct TextureLocation {
xpos: f32,
ypos: f32,
width: f32,
height: f32,
};
struct AtlasUniform {
texture_locations: array<TextureLocation, 108>,
};
"#,
);
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(),
};
}
}

View File

@ -0,0 +1,7 @@
mod atlascontent;
mod datacontent;
mod globaluniform;
pub use atlascontent::{AtlasContent, ImageLocation, ImageLocations};
pub use datacontent::DataContent;
pub use globaluniform::GlobalUniform;

View File

@ -8,7 +8,7 @@ use winit::{self, dpi::LogicalSize, window::Window};
use crate::{ use crate::{
content, content,
globaldata::{GlobalData, GlobalDataContent}, globaluniform::{AtlasContent, DataContent, GlobalUniform},
pipeline::PipelineBuilder, pipeline::PipelineBuilder,
sprite::ObjectSubSprite, sprite::ObjectSubSprite,
starfield::Starfield, starfield::Starfield,
@ -18,7 +18,7 @@ use crate::{
types::{ObjectInstance, ParticleInstance, StarfieldInstance, TexturedVertex, UiInstance}, types::{ObjectInstance, ParticleInstance, StarfieldInstance, TexturedVertex, UiInstance},
BufferObject, VertexBuffer, BufferObject, VertexBuffer,
}, },
FrameState, ObjectSprite, UiSprite, OPENGL_TO_WGPU_MATRIX, ObjectSprite, RenderState, UiSprite, OPENGL_TO_WGPU_MATRIX,
}; };
/// A high-level GPU wrapper. Consumes game state, /// A high-level GPU wrapper. Consumes game state,
@ -44,7 +44,7 @@ pub struct GPUState {
starfield: Starfield, starfield: Starfield,
texture_array: TextureArray, texture_array: TextureArray,
global_data: GlobalData, global_uniform: GlobalUniform,
vertex_buffers: VertexBuffers, vertex_buffers: VertexBuffers,
} }
@ -60,6 +60,19 @@ struct VertexBuffers {
particle: Rc<VertexBuffer>, particle: Rc<VertexBuffer>,
} }
/// 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 { impl GPUState {
/// Make a new GPUState that draws on `window` /// Make a new GPUState that draws on `window`
pub async fn new(window: Window, ct: &content::Content) -> Result<Self> { pub async fn new(window: Window, ct: &content::Content) -> Result<Self> {
@ -161,22 +174,26 @@ impl GPUState {
}; };
// Load uniforms // Load uniforms
let global_data = GlobalData::new(&device); let global_uniform = GlobalUniform::new(&device);
let texture_array = TextureArray::new(&device, &queue, ct)?; let texture_array = TextureArray::new(&device, &queue, ct)?;
// Make sure these match the indices in each shader // Make sure these match the indices in each shader
let bind_group_layouts = &[ let bind_group_layouts = &[
&texture_array.bind_group_layout, &texture_array.bind_group_layout,
&global_data.bind_group_layout, &global_uniform.bind_group_layout,
]; ];
// Create render pipelines // Create render pipelines
let object_pipeline = PipelineBuilder::new("object", &device) let object_pipeline = PipelineBuilder::new("object", &device)
.set_shader(include_str!(concat!( .set_shader(&preprocess_shader(
env!("CARGO_MANIFEST_DIR"), &include_str!(concat!(
"/shaders/", env!("CARGO_MANIFEST_DIR"),
"object.wgsl" "/shaders/",
))) "object.wgsl"
)),
&global_uniform,
1,
))
.set_format(config.format) .set_format(config.format)
.set_triangle(true) .set_triangle(true)
.set_vertex_buffer(&vertex_buffers.object) .set_vertex_buffer(&vertex_buffers.object)
@ -184,11 +201,15 @@ impl GPUState {
.build(); .build();
let starfield_pipeline = PipelineBuilder::new("starfield", &device) let starfield_pipeline = PipelineBuilder::new("starfield", &device)
.set_shader(include_str!(concat!( .set_shader(&preprocess_shader(
env!("CARGO_MANIFEST_DIR"), &include_str!(concat!(
"/shaders/", env!("CARGO_MANIFEST_DIR"),
"starfield.wgsl" "/shaders/",
))) "starfield.wgsl"
)),
&global_uniform,
1,
))
.set_format(config.format) .set_format(config.format)
.set_triangle(true) .set_triangle(true)
.set_vertex_buffer(&vertex_buffers.starfield) .set_vertex_buffer(&vertex_buffers.starfield)
@ -196,11 +217,11 @@ impl GPUState {
.build(); .build();
let ui_pipeline = PipelineBuilder::new("ui", &device) let ui_pipeline = PipelineBuilder::new("ui", &device)
.set_shader(include_str!(concat!( .set_shader(&preprocess_shader(
env!("CARGO_MANIFEST_DIR"), &include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/", "ui.wgsl")),
"/shaders/", &global_uniform,
"ui.wgsl" 1,
))) ))
.set_format(config.format) .set_format(config.format)
.set_triangle(true) .set_triangle(true)
.set_vertex_buffer(&vertex_buffers.ui) .set_vertex_buffer(&vertex_buffers.ui)
@ -208,11 +229,15 @@ impl GPUState {
.build(); .build();
let particle_pipeline = PipelineBuilder::new("particle", &device) let particle_pipeline = PipelineBuilder::new("particle", &device)
.set_shader(include_str!(concat!( .set_shader(&preprocess_shader(
env!("CARGO_MANIFEST_DIR"), &include_str!(concat!(
"/shaders/", env!("CARGO_MANIFEST_DIR"),
"particle.wgsl" "/shaders/",
))) "particle.wgsl"
)),
&global_uniform,
1,
))
.set_format(config.format) .set_format(config.format)
.set_triangle(true) .set_triangle(true)
.set_vertex_buffer(&vertex_buffers.particle) .set_vertex_buffer(&vertex_buffers.particle)
@ -239,7 +264,7 @@ impl GPUState {
starfield, starfield,
texture_array, texture_array,
global_data, global_uniform,
vertex_buffers, vertex_buffers,
}); });
} }
@ -283,7 +308,7 @@ impl GPUState {
} - camera_pos.to_vec()) } - camera_pos.to_vec())
/ s.pos.z / 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. // 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.
@ -345,7 +370,7 @@ impl GPUState {
instances.push(ObjectInstance { instances.push(ObjectInstance {
transform: t.into(), transform: t.into(),
texture_index: texture.index, sprite_index: texture.index,
}); });
// Add children // Add children
@ -366,7 +391,7 @@ impl GPUState {
parent_pos: Point2<f32>, parent_pos: Point2<f32>,
parent_angle: Deg<f32>, parent_angle: Deg<f32>,
) { ) {
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 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(texture.aspect * scale, scale, 1.0);
@ -395,7 +420,7 @@ impl GPUState {
instances.push(ObjectInstance { instances.push(ObjectInstance {
transform: t.into(), transform: t.into(),
texture_index: texture.index, sprite_index: texture.index,
}); });
} }
@ -404,7 +429,7 @@ 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.texture); 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;
@ -448,7 +473,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(),
texture_index: texture.index, sprite_index: texture.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]),
}); });
} }
@ -456,7 +481,7 @@ 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: FrameState) -> (usize, usize) { fn update_sprite_instances(&self, framestate: 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.
@ -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. /// 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 output = self.surface.get_current_texture()?;
let view = output let view = output
.texture .texture
@ -554,9 +593,9 @@ impl GPUState {
// Update global values // Update global values
self.queue.write_buffer( self.queue.write_buffer(
&self.global_data.buffer, &self.global_uniform.data_buffer,
0, 0,
bytemuck::cast_slice(&[GlobalDataContent { bytemuck::cast_slice(&[DataContent {
camera_position: framestate.camera_pos.into(), camera_position: framestate.camera_pos.into(),
camera_zoom: [framestate.camera_zoom, 0.0], camera_zoom: [framestate.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],
@ -565,7 +604,7 @@ impl GPUState {
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_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_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,
@ -577,7 +616,7 @@ impl GPUState {
// Write all new particles to GPU buffer // Write all new particles to GPU buffer
for i in framestate.new_particles.iter() { 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.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,
@ -607,7 +646,7 @@ impl GPUState {
// 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.
render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]); 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 // Starfield pipeline
self.vertex_buffers.starfield.set_in_pass(&mut render_pass); self.vertex_buffers.starfield.set_in_pass(&mut render_pass);

View File

@ -7,18 +7,18 @@
//! and the only one external code should interact with. //! and the only one external code should interact with.
//! (Excluding data structs, like [`ObjectSprite`]) //! (Excluding data structs, like [`ObjectSprite`])
mod framestate; mod globaluniform;
mod globaldata;
mod gpustate; mod gpustate;
mod pipeline; mod pipeline;
mod renderstate;
mod sprite; mod sprite;
mod starfield; mod starfield;
mod texturearray; mod texturearray;
mod vertexbuffer; mod vertexbuffer;
pub use framestate::FrameState;
use galactica_content as content; use galactica_content as content;
pub use gpustate::GPUState; pub use gpustate::GPUState;
pub use renderstate::RenderState;
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, ParticleBuilder, UiSprite}; pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, ParticleBuilder, UiSprite};
use cgmath::Matrix4; use cgmath::Matrix4;

View File

@ -3,7 +3,7 @@ use cgmath::Point2;
use crate::{ObjectSprite, ParticleBuilder, UiSprite}; use crate::{ObjectSprite, ParticleBuilder, UiSprite};
/// Bundles parameters passed to a single call to GPUState::render /// Bundles parameters passed to a single call to GPUState::render
pub struct FrameState<'a> { pub struct RenderState<'a> {
/// Camera position, in world units /// Camera position, in world units
pub camera_pos: Point2<f32>, pub camera_pos: Point2<f32>,

View File

@ -3,8 +3,8 @@ use cgmath::{Deg, Point2, Point3, Vector2};
/// Instructions to create a new particle /// Instructions to create a new particle
pub struct ParticleBuilder { pub struct ParticleBuilder {
/// The texture to use for this particle /// The sprite to use for this particle
pub texture: content::TextureHandle, pub sprite: content::SpriteHandle,
/// This object's center, in world coordinates. /// This object's center, in world coordinates.
pub pos: Point2<f32>, pub pos: Point2<f32>,
@ -18,7 +18,7 @@ pub struct ParticleBuilder {
/// This particle's lifetime, in seconds /// This particle's lifetime, in seconds
pub lifetime: f32, pub lifetime: f32,
/// The size of this sprite, /// The size of this particle,
/// given as height in world units. /// given as height in world units.
pub size: f32, pub size: f32,
} }
@ -54,8 +54,8 @@ pub enum AnchoredUiPosition {
/// A sprite that represents a ui element /// A sprite that represents a ui element
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct UiSprite { pub struct UiSprite {
/// The texture to use for this sprite /// The sprite to draw
pub texture: content::TextureHandle, pub sprite: content::SpriteHandle,
/// This object's position, in logical (dpi-adjusted) pixels /// This object's position, in logical (dpi-adjusted) pixels
pub pos: AnchoredUiPosition, pub pos: AnchoredUiPosition,
@ -75,8 +75,8 @@ pub struct UiSprite {
/// Ships, planets, debris, etc /// Ships, planets, debris, etc
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjectSprite { pub struct ObjectSprite {
/// The texture to use for this sprite /// The sprite to draw
pub texture: content::TextureHandle, pub sprite: content::SpriteHandle,
/// This object's center, in world coordinates. /// This object's center, in world coordinates.
pub pos: Point3<f32>, pub pos: Point3<f32>,
@ -97,8 +97,8 @@ pub struct ObjectSprite {
/// A sprite that is drawn relative to an ObjectSprite. /// A sprite that is drawn relative to an ObjectSprite.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjectSubSprite { pub struct ObjectSubSprite {
/// The sprite texture to draw /// The sprite to draw
pub texture: content::TextureHandle, pub sprite: content::SpriteHandle,
/// This object's position, in world coordinates. /// This object's position, in world coordinates.
/// This is relative to this sprite's parent. /// This is relative to this sprite's parent.

View File

@ -1,5 +1,10 @@
use crate::content; use crate::{
content,
globaluniform::{ImageLocation, ImageLocations},
};
use anyhow::Result; use anyhow::Result;
use bytemuck::Zeroable;
use galactica_packer::SpriteAtlasImage;
use image::GenericImageView; use image::GenericImageView;
use std::{collections::HashMap, fs::File, io::Read, num::NonZeroU32}; use std::{collections::HashMap, fs::File, io::Read, num::NonZeroU32};
use wgpu::BindGroupLayout; use wgpu::BindGroupLayout;
@ -67,30 +72,32 @@ impl RawTexture {
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub struct Texture { pub struct Texture {
pub index: u32, // Index in texture array pub index: u32, // Index in texture array
pub len: u32, // Number of frames pub len: u32, // Number of frames
pub fps: f32, // Frames per second pub fps: f32, // Frames per second
pub aspect: f32, // width / height pub aspect: f32, // width / height
pub repeat: u32, // How to re-play this texture pub repeat: u32, // How to re-play this texture
pub location: Vec<SpriteAtlasImage>,
} }
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::TextureHandle, starfield_handle: content::SpriteHandle,
textures: HashMap<content::TextureHandle, Texture>, sprites: HashMap<content::SpriteHandle, Texture>,
pub data: ImageLocations,
} }
impl TextureArray { impl TextureArray {
pub fn get_starfield_texture(&self) -> Texture { pub fn get_starfield_texture(&self) -> &Texture {
*self.textures.get(&self.starfield_handle).unwrap() self.sprites.get(&self.starfield_handle).unwrap()
} }
pub fn get_texture(&self, handle: content::TextureHandle) -> Texture { pub fn get_texture(&self, handle: content::SpriteHandle) -> &Texture {
match self.textures.get(&handle) { match self.sprites.get(&handle) {
Some(x) => *x, Some(x) => x,
None => unreachable!("Tried to get a texture that doesn't exist"), 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<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 textures = HashMap::new(); let mut sprites = HashMap::new();
for t in &ct.textures { println!("opening image");
let index = texture_data.len() as u32; let mut f = File::open("atlas-0.bmp")?;
for f in &t.frames { let mut bytes = Vec::new();
let mut f = File::open(&f)?; f.read_to_end(&mut bytes)?;
let mut bytes = Vec::new(); texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, "Atlas")?);
f.read_to_end(&mut bytes)?;
texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, &t.name)?); let mut tx = ImageLocations::zeroed();
}
textures.insert( 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, t.handle,
Texture { Texture {
index, index: i,
aspect: t.handle.aspect, aspect: t.aspect,
fps: t.fps, fps: t.fps,
len: t.frames.len() as u32, len: t.frames.len() as u32,
repeat: t.repeat.as_int(), 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 { let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
@ -136,7 +161,7 @@ impl TextureArray {
// Texture data // Texture data
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
binding: 0, binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Texture { ty: wgpu::BindingType::Texture {
multisampled: false, multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2, view_dimension: wgpu::TextureViewDimension::D2,
@ -147,7 +172,7 @@ impl TextureArray {
// Texture sampler // Texture sampler
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
binding: 1, binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: NonZeroU32::new(1), count: NonZeroU32::new(1),
}, },
@ -161,7 +186,6 @@ impl TextureArray {
entries: &[ entries: &[
wgpu::BindGroupEntry { wgpu::BindGroupEntry {
binding: 0, binding: 0,
// Array of all views
resource: wgpu::BindingResource::TextureViewArray(&views), resource: wgpu::BindingResource::TextureViewArray(&views),
}, },
wgpu::BindGroupEntry { wgpu::BindGroupEntry {
@ -174,8 +198,9 @@ impl TextureArray {
return Ok(Self { return Ok(Self {
bind_group, bind_group,
bind_group_layout, bind_group_layout,
textures: textures, sprites,
starfield_handle: ct.get_starfield_handle(), starfield_handle: ct.get_starfield_handle(),
data: tx,
}); });
} }
} }

View File

@ -89,7 +89,7 @@ pub struct ObjectInstance {
pub transform: [[f32; 4]; 4], pub transform: [[f32; 4]; 4],
/// What texture to use for this sprite /// What texture to use for this sprite
pub texture_index: u32, pub sprite_index: u32,
} }
impl BufferObject for ObjectInstance { impl BufferObject for ObjectInstance {
@ -122,7 +122,7 @@ impl BufferObject for ObjectInstance {
shader_location: 5, shader_location: 5,
format: wgpu::VertexFormat::Float32x4, format: wgpu::VertexFormat::Float32x4,
}, },
// Texture // Sprite
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress,
shader_location: 6, shader_location: 6,
@ -146,7 +146,7 @@ pub struct UiInstance {
pub color: [f32; 4], pub color: [f32; 4],
/// What texture to use for this sprite /// What texture to use for this sprite
pub texture_index: u32, pub sprite_index: u32,
} }
impl BufferObject for UiInstance { impl BufferObject for UiInstance {
@ -185,7 +185,7 @@ impl BufferObject for UiInstance {
shader_location: 6, shader_location: 6,
format: wgpu::VertexFormat::Float32x4, format: wgpu::VertexFormat::Float32x4,
}, },
// Texture // Sprite
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 20]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 20]>() as wgpu::BufferAddress,
shader_location: 7, shader_location: 7,