Minor cleanup
parent
5345355909
commit
6e59d95752
|
@ -174,10 +174,10 @@ pub async fn run() -> Result<()> {
|
|||
Event::RedrawRequested(window_id) if window_id == gpu.window().id() => {
|
||||
gpu.update();
|
||||
game.update();
|
||||
match gpu.render(&game.sprites(), game.camera) {
|
||||
match gpu.render(&game) {
|
||||
Ok(_) => {}
|
||||
// Reconfigure the surface if lost
|
||||
Err(wgpu::SurfaceError::Lost) => gpu.resize(gpu.size),
|
||||
Err(wgpu::SurfaceError::Lost) => gpu.resize(gpu.window_size),
|
||||
// The system is out of memory, we should probably quit
|
||||
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
||||
// All other errors (Outdated, Timeout) should be resolved by the next frame
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use anyhow::Result;
|
||||
use bytemuck;
|
||||
use cgmath::{EuclideanSpace, Point2};
|
||||
use cgmath::{EuclideanSpace, Point2, Vector2};
|
||||
use std::{iter, rc::Rc};
|
||||
use wgpu;
|
||||
use winit::{self, window::Window};
|
||||
use winit::{self, dpi::PhysicalSize, window::Window};
|
||||
|
||||
use crate::{Camera, Sprite};
|
||||
use crate::Game;
|
||||
|
||||
use super::{
|
||||
pipeline::PipelineBuilder,
|
||||
|
@ -13,7 +13,7 @@ use super::{
|
|||
util::Transform,
|
||||
vertexbuffer::{
|
||||
data::{SPRITE_INDICES, SPRITE_VERTICES},
|
||||
types::{PlainVertex, SpriteInstance, StarInstance, TexturedVertex},
|
||||
types::{PlainVertex, SpriteInstance, StarfieldInstance, TexturedVertex},
|
||||
VertexBuffer,
|
||||
},
|
||||
};
|
||||
|
@ -25,7 +25,8 @@ pub struct GPUState {
|
|||
queue: wgpu::Queue,
|
||||
|
||||
pub window: Window,
|
||||
pub size: winit::dpi::PhysicalSize<u32>,
|
||||
pub window_size: winit::dpi::PhysicalSize<u32>,
|
||||
window_aspect: f32,
|
||||
|
||||
sprite_pipeline: wgpu::RenderPipeline,
|
||||
starfield_pipeline: wgpu::RenderPipeline,
|
||||
|
@ -42,11 +43,12 @@ struct VertexBuffers {
|
|||
impl GPUState {
|
||||
// We can draw at most this many sprites on the screen.
|
||||
// TODO: compile-time option
|
||||
pub const SPRITE_LIMIT: u64 = 100;
|
||||
pub const STAR_LIMIT: u64 = 100;
|
||||
pub const SPRITE_INSTANCE_LIMIT: u64 = 100;
|
||||
pub const STARFIELD_INSTANCE_LIMIT: u64 = 100;
|
||||
|
||||
pub async fn new(window: Window) -> Result<Self> {
|
||||
let size = window.inner_size();
|
||||
let window_size = window.inner_size();
|
||||
let window_aspect = window_size.width as f32 / window_size.height as f32;
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::all(),
|
||||
|
@ -97,8 +99,8 @@ impl GPUState {
|
|||
config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: surface_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
width: window_size.width,
|
||||
height: window_size.height,
|
||||
present_mode: surface_caps.present_modes[0],
|
||||
alpha_mode: surface_caps.alpha_modes[0],
|
||||
view_formats: vec![],
|
||||
|
@ -113,15 +115,15 @@ impl GPUState {
|
|||
&device,
|
||||
Some(SPRITE_VERTICES),
|
||||
Some(SPRITE_INDICES),
|
||||
Self::SPRITE_LIMIT,
|
||||
Self::SPRITE_INSTANCE_LIMIT,
|
||||
)),
|
||||
|
||||
starfield: Rc::new(VertexBuffer::new::<PlainVertex, StarInstance>(
|
||||
starfield: Rc::new(VertexBuffer::new::<PlainVertex, StarfieldInstance>(
|
||||
"starfield",
|
||||
&device,
|
||||
None,
|
||||
None,
|
||||
Self::STAR_LIMIT,
|
||||
Self::STARFIELD_INSTANCE_LIMIT,
|
||||
)),
|
||||
};
|
||||
|
||||
|
@ -159,7 +161,8 @@ impl GPUState {
|
|||
queue,
|
||||
|
||||
window,
|
||||
size,
|
||||
window_size,
|
||||
window_aspect,
|
||||
|
||||
sprite_pipeline,
|
||||
starfield_pipeline,
|
||||
|
@ -173,9 +176,10 @@ impl GPUState {
|
|||
&self.window
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
pub fn resize(&mut self, new_size: PhysicalSize<u32>) {
|
||||
if new_size.width > 0 && new_size.height > 0 {
|
||||
self.size = new_size;
|
||||
self.window_size = new_size;
|
||||
self.window_aspect = new_size.width as f32 / new_size.height as f32;
|
||||
self.config.width = new_size.width;
|
||||
self.config.height = new_size.height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
|
@ -184,56 +188,20 @@ impl GPUState {
|
|||
|
||||
pub fn update(&mut self) {}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
sprites: &Vec<Sprite>,
|
||||
camera: Camera,
|
||||
) -> Result<(), wgpu::SurfaceError> {
|
||||
let output = self.surface.get_current_texture()?;
|
||||
let view = output
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("sprite render encoder"),
|
||||
});
|
||||
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("sprite render pass"),
|
||||
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
occlusion_query_set: None,
|
||||
timestamp_writes: None,
|
||||
});
|
||||
|
||||
// Correct for screen aspect ratio
|
||||
// (it may not be square!)
|
||||
let screen_aspect = self.size.width as f32 / self.size.height as f32;
|
||||
/// Make a SpriteInstance for each of the game's visible sprites.
|
||||
/// Will panic if SPRITE_INSTANCE_LIMIT is exceeded.
|
||||
///
|
||||
/// This is only called inside self.render()
|
||||
fn make_sprite_instances(&self, game: &Game) -> Vec<SpriteInstance> {
|
||||
let mut instances: Vec<SpriteInstance> = Vec::new();
|
||||
|
||||
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
||||
// Used to skip off-screen sprites.
|
||||
let clip_ne = Point2::from((-1.0, 1.0)) * camera.zoom;
|
||||
let clip_sw = Point2::from((1.0, -1.0)) * camera.zoom;
|
||||
let clip_ne = Point2::from((-1.0, 1.0)) * game.camera.zoom;
|
||||
let clip_sw = Point2::from((1.0, -1.0)) * game.camera.zoom;
|
||||
|
||||
let mut instances: Vec<SpriteInstance> = Vec::new();
|
||||
|
||||
for s in sprites {
|
||||
let pos = s.post_parallax_position(camera) - camera.pos.to_vec();
|
||||
for s in game.sprites() {
|
||||
let pos = s.post_parallax_position(game.camera) - game.camera.pos.to_vec();
|
||||
let texture = self.texture_array.get_texture(&s.name[..]);
|
||||
|
||||
// Game dimensions of this sprite post-scale.
|
||||
|
@ -261,14 +229,14 @@ impl GPUState {
|
|||
//
|
||||
// We can't use screen_pos to exclude off-screen sprites because
|
||||
// it can't account for height and width.
|
||||
let scale = height / camera.zoom;
|
||||
let screen_pos: Point2<f32> = pos / camera.zoom;
|
||||
let scale = height / game.camera.zoom;
|
||||
let screen_pos: Point2<f32> = pos / game.camera.zoom;
|
||||
|
||||
instances.push(SpriteInstance {
|
||||
transform: Transform {
|
||||
pos: screen_pos,
|
||||
aspect: texture.aspect,
|
||||
screen_aspect,
|
||||
sprite_aspect: texture.aspect,
|
||||
window_aspect: self.window_aspect,
|
||||
rotate: s.angle,
|
||||
scale,
|
||||
}
|
||||
|
@ -279,46 +247,101 @@ impl GPUState {
|
|||
}
|
||||
|
||||
// Enforce sprite limit
|
||||
if sprites.len() as u64 > Self::SPRITE_LIMIT {
|
||||
if instances.len() as u64 > Self::SPRITE_INSTANCE_LIMIT {
|
||||
// TODO: no panic, handle this better.
|
||||
panic!("Sprite limit exceeded!")
|
||||
unreachable!("Sprite limit exceeded!")
|
||||
}
|
||||
|
||||
// Write new sprite data to buffer
|
||||
self.queue.write_buffer(
|
||||
&self.vertex_buffers.sprite.instances,
|
||||
0,
|
||||
bytemuck::cast_slice(&instances),
|
||||
);
|
||||
return instances;
|
||||
}
|
||||
|
||||
render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]);
|
||||
|
||||
let nstances: Vec<StarInstance> = (-10..10)
|
||||
.map(|x| StarInstance {
|
||||
transform: cgmath::Matrix4::from_translation(cgmath::Vector3 {
|
||||
/// Make a StarfieldInstance for each star that needs to be drawn.
|
||||
/// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded.
|
||||
///
|
||||
/// This is only called inside self.render()
|
||||
fn make_starfield_instances(&self, _game: &Game) -> Vec<StarfieldInstance> {
|
||||
let instances: Vec<StarfieldInstance> = (-10..10)
|
||||
.map(|x| StarfieldInstance {
|
||||
position: Vector2 {
|
||||
x: x as f32 / 10.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
})
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Enforce starfield limit
|
||||
if instances.len() as u64 > Self::STARFIELD_INSTANCE_LIMIT {
|
||||
// TODO: no panic, handle this better.
|
||||
unreachable!("Starfield limit exceeded!")
|
||||
}
|
||||
return instances;
|
||||
}
|
||||
|
||||
pub fn render(&mut self, game: &Game) -> Result<(), wgpu::SurfaceError> {
|
||||
let output = self.surface.get_current_texture()?;
|
||||
let view = output
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("render encoder"),
|
||||
});
|
||||
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("render pass"),
|
||||
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
occlusion_query_set: None,
|
||||
timestamp_writes: None,
|
||||
});
|
||||
|
||||
// Create sprite instances
|
||||
let sprite_instances = self.make_sprite_instances(game);
|
||||
self.queue.write_buffer(
|
||||
&self.vertex_buffers.sprite.instances,
|
||||
0,
|
||||
bytemuck::cast_slice(&sprite_instances),
|
||||
);
|
||||
|
||||
// Create starfield instances
|
||||
let starfield_instances = self.make_starfield_instances(game);
|
||||
self.queue.write_buffer(
|
||||
&self.vertex_buffers.starfield.instances,
|
||||
0,
|
||||
bytemuck::cast_slice(&nstances),
|
||||
bytemuck::cast_slice(&starfield_instances),
|
||||
);
|
||||
|
||||
render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]);
|
||||
|
||||
// Starfield pipeline
|
||||
self.vertex_buffers.starfield.set_in_pass(&mut render_pass);
|
||||
render_pass.set_pipeline(&self.starfield_pipeline);
|
||||
render_pass.draw(0..1, 0..nstances.len() as _);
|
||||
render_pass.draw(0..1, 0..starfield_instances.len() as _);
|
||||
|
||||
// Sprite pipeline
|
||||
self.vertex_buffers.sprite.set_in_pass(&mut render_pass);
|
||||
render_pass.set_pipeline(&self.sprite_pipeline);
|
||||
render_pass.draw_indexed(0..SPRITE_INDICES.len() as u32, 0, 0..instances.len() as _);
|
||||
render_pass.draw_indexed(
|
||||
0..SPRITE_INDICES.len() as u32,
|
||||
0,
|
||||
0..sprite_instances.len() as _,
|
||||
);
|
||||
|
||||
// begin_render_pass borrows encoder mutably, so we can't call finish()
|
||||
// without dropping this variable.
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
// Vertex shader
|
||||
struct InstanceInput {
|
||||
@location(2) transform_matrix_0: vec4<f32>,
|
||||
@location(3) transform_matrix_1: vec4<f32>,
|
||||
@location(4) transform_matrix_2: vec4<f32>,
|
||||
@location(5) transform_matrix_3: vec4<f32>,
|
||||
@location(2) position: vec2<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
|
@ -14,19 +11,10 @@ struct VertexOutput {
|
|||
fn vertex_shader_main(
|
||||
instance: InstanceInput,
|
||||
) -> VertexOutput {
|
||||
let transform_matrix = mat4x4<f32>(
|
||||
instance.transform_matrix_0,
|
||||
instance.transform_matrix_1,
|
||||
instance.transform_matrix_2,
|
||||
instance.transform_matrix_3,
|
||||
);
|
||||
|
||||
// Stars consist of only one vertex, so we don't need to pass a buffer into this shader.
|
||||
// We need one instance per star, and that's it!
|
||||
let pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||
|
||||
var out: VertexOutput;
|
||||
out.clip_position = transform_matrix * pos;
|
||||
out.clip_position = vec4<f32>(instance.position, 0.0, 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ use super::OPENGL_TO_WGPU_MATRIX;
|
|||
// to put it in the right place, at the right angle, with the right scale.
|
||||
pub struct Transform {
|
||||
pub pos: Point2<f32>, // position on screen
|
||||
pub screen_aspect: f32, // width / height. Screen aspect ratio.
|
||||
pub aspect: f32, // width / height. Sprite aspect ratio.
|
||||
pub window_aspect: f32, // width / height. Screen aspect ratio.
|
||||
pub sprite_aspect: f32, // width / height. Sprite aspect ratio.
|
||||
pub scale: f32, // if scale = 1, this sprite will be as tall as the screen.
|
||||
pub rotate: Deg<f32>, // Around this object's center, in degrees measured ccw from vertical
|
||||
}
|
||||
|
@ -26,14 +26,14 @@ impl Transform {
|
|||
//
|
||||
// We apply the provided scale here as well as a minor optimization
|
||||
let sprite_aspect_and_scale =
|
||||
Matrix4::from_nonuniform_scale(self.aspect * self.scale, self.scale, 1.0);
|
||||
Matrix4::from_nonuniform_scale(self.sprite_aspect * self.scale, self.scale, 1.0);
|
||||
|
||||
// Apply rotation
|
||||
let rotate = Matrix4::from_angle_z(self.rotate);
|
||||
|
||||
// Apply screen aspect ratio, again preserving height.
|
||||
// This must be done AFTER rotation... think about it!
|
||||
let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.screen_aspect, 1.0, 1.0);
|
||||
let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0);
|
||||
|
||||
// After finishing all op, translate.
|
||||
// This must be done last, all other operations
|
||||
|
|
|
@ -53,46 +53,25 @@ impl BufferObject for PlainVertex {
|
|||
}
|
||||
}
|
||||
|
||||
// Represents a star instance in WGSL
|
||||
// Represents a point in the starfield instance in WGSL
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct StarInstance {
|
||||
// All transformations we need to put this star in its place
|
||||
pub transform: [[f32; 4]; 4],
|
||||
pub struct StarfieldInstance {
|
||||
// All transformations we need to put this dot in its place
|
||||
// TODO: we don't need that much data
|
||||
pub position: [f32; 2],
|
||||
}
|
||||
|
||||
impl BufferObject for StarInstance {
|
||||
impl BufferObject for StarfieldInstance {
|
||||
fn layout() -> wgpu::VertexBufferLayout<'static> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: Self::SIZE,
|
||||
// We need to switch from using a step mode of TexturedVertex to Instance
|
||||
// This means that our shaders will only change to use the next
|
||||
// instance when the shader starts processing a new instance
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &[
|
||||
// A mat4 takes up 4 Texturedvertex slots as it is technically 4 vec4s. We need to define a slot
|
||||
// for each vec4. We'll have to reassemble the mat4 in the shader.
|
||||
wgpu::VertexAttribute {
|
||||
attributes: &[wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 2,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
||||
shader_location: 3,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
||||
shader_location: 4,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
|
||||
shader_location: 5,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
],
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +92,7 @@ impl BufferObject for SpriteInstance {
|
|||
fn layout() -> wgpu::VertexBufferLayout<'static> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: Self::SIZE,
|
||||
// We need to switch from using a step mode of TexturedVertex to Instance
|
||||
// Switch to a step mode of Vertex to Instance.
|
||||
// This means that our shaders will only change to use the next
|
||||
// instance when the shader starts processing a new instance
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
|
|
Loading…
Reference in New Issue