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 {
@location(2) transform_matrix_0: vec4<f32>,
@location(3) transform_matrix_1: vec4<f32>,
@ -9,27 +11,12 @@ struct InstanceInput {
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) texture_coords: vec2<f32>,
}
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) texture_coords: vec2<f32>,
@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;
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;
}

View File

@ -1,3 +1,5 @@
// INCLUDE: global uniform header
struct InstanceInput {
@location(2) position: 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)
var texture_array: binding_array<texture_2d<f32>>;
@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<f32>(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;

View File

@ -1,3 +1,5 @@
// INCLUDE: global uniform header
struct InstanceInput {
@location(2) position: vec3<f32>,
@location(3) size: f32,
@ -12,24 +14,10 @@ struct VertexInput {
struct VertexOutput {
@builtin(position) position: vec4<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)
var texture_array: binding_array<texture_2d<f32>>;
@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<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;
}
@ -136,7 +134,7 @@ fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
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

View File

@ -1,3 +1,5 @@
// INCLUDE: global uniform header
struct InstanceInput {
@location(2) transform_matrix_0: vec4<f32>,
@location(3) transform_matrix_1: vec4<f32>,
@ -19,22 +21,6 @@ struct VertexOutput {
@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)
var texture_array: binding_array<texture_2d<f32>>;
@group(0) @binding(1)
@ -58,9 +44,18 @@ fn vertex_main(
var out: VertexOutput;
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.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;
}

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::{
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<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 {
/// Make a new GPUState that draws on `window`
pub async fn new(window: Window, ct: &content::Content) -> Result<Self> {
@ -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<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 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<f32> =
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<ObjectInstance> = 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);

View File

@ -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;

View File

@ -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<f32>,

View File

@ -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<f32>,
@ -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<f32>,
@ -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.

View File

@ -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<SpriteAtlasImage>,
}
pub struct TextureArray {
pub bind_group: wgpu::BindGroup,
pub bind_group_layout: BindGroupLayout,
starfield_handle: content::TextureHandle,
textures: HashMap<content::TextureHandle, Texture>,
starfield_handle: content::SpriteHandle,
sprites: HashMap<content::SpriteHandle, Texture>,
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<Self> {
// 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,
});
}
}

View File

@ -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,