diff --git a/src/render/bufferdata.rs b/src/render/bufferdata.rs index 31748fc..4fb80b9 100644 --- a/src/render/bufferdata.rs +++ b/src/render/bufferdata.rs @@ -3,15 +3,15 @@ use std::mem; // Represents a textured vertex in WGSL #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -pub struct Vertex { +pub struct TexturedVertex { pub position: [f32; 3], pub texture_coords: [f32; 2], } -impl Vertex { +impl TexturedVertex { pub fn desc() -> wgpu::VertexBufferLayout<'static> { wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as wgpu::BufferAddress, + array_stride: mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ wgpu::VertexAttribute { @@ -29,6 +29,75 @@ impl Vertex { } } +// Represents a plain vertex in WGSL +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct PlainVertex { + pub position: [f32; 3], +} + +impl PlainVertex { + pub fn desc() -> wgpu::VertexBufferLayout<'static> { + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }], + } + } +} + +// Represents a star instance in WGSL +#[repr(C)] +#[derive(Debug, 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], +} + +impl StarInstance { + // Number of bytes used to store this data. + // Should match desc() below. + pub const SIZE: u64 = mem::size_of::() as wgpu::BufferAddress; + + pub fn desc() -> 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 { + 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, + }, + ], + } + } +} + // Represents a sprite instance in WGSL #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] @@ -44,43 +113,39 @@ pub struct SpriteInstance { impl SpriteInstance { // Number of bytes used to store this data. // Should match desc() below. - pub const SIZE: u64 = 20; + pub const SIZE: u64 = mem::size_of::() as wgpu::BufferAddress; pub fn desc() -> wgpu::VertexBufferLayout<'static> { wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as wgpu::BufferAddress, - // We need to switch from using a step mode of Vertex to Instance + 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 vertex 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 { offset: 0, - // While our vertex shader only uses locations 0, and 1 now, in later tutorials, we'll - // be using 2, 3, and 4, for Vertex. We'll start at slot 5, not conflict with them later - shader_location: 5, + shader_location: 2, format: wgpu::VertexFormat::Float32x4, }, wgpu::VertexAttribute { offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, - shader_location: 6, + shader_location: 3, format: wgpu::VertexFormat::Float32x4, }, wgpu::VertexAttribute { offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, - shader_location: 7, + shader_location: 4, format: wgpu::VertexFormat::Float32x4, }, wgpu::VertexAttribute { offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress, - shader_location: 8, + shader_location: 5, format: wgpu::VertexFormat::Float32x4, }, wgpu::VertexAttribute { offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress, - shader_location: 9, + shader_location: 6, format: wgpu::VertexFormat::Uint32, }, ], diff --git a/src/render/gpustate.rs b/src/render/gpustate.rs index e94da15..905f546 100644 --- a/src/render/gpustate.rs +++ b/src/render/gpustate.rs @@ -8,10 +8,11 @@ use winit::{self, window::Window}; use crate::{Camera, Sprite}; use super::{ - bufferdata::{SpriteInstance, Vertex}, + bufferdata::{PlainVertex, SpriteInstance, StarInstance, TexturedVertex}, + pipeline::PipelineBuilder, texturearray::TextureArray, util::Transform, - SPRITE_MESH_INDICES, SPRITE_MESH_VERTICES, + SPRITE_INDICES, SPRITE_VERTICES, STARFIELD_VERTICES, }; pub struct GPUState { @@ -23,18 +24,23 @@ pub struct GPUState { pub window: Window, pub size: winit::dpi::PhysicalSize, - render_pipeline: wgpu::RenderPipeline, + sprite_pipeline: wgpu::RenderPipeline, + starfield_pipeline: wgpu::RenderPipeline, - vertex_buffer: wgpu::Buffer, - index_buffer: wgpu::Buffer, + star_vertex_buffer: wgpu::Buffer, + sprite_vertex_buffer: wgpu::Buffer, + sprite_index_buffer: wgpu::Buffer, texture_array: TextureArray, - instance_buffer: wgpu::Buffer, + + sprite_instance_buffer: wgpu::Buffer, + star_instance_buffer: wgpu::Buffer, } 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 async fn new(window: Window) -> Result { let size = window.inner_size(); @@ -101,87 +107,62 @@ impl GPUState { // Load textures let texture_array = TextureArray::new(&device, &queue)?; - // Render pipeline - let render_pipeline; - let render_pipeline_layout; + // Render pipelines + let sprite_pipeline = PipelineBuilder::new("sprite", &device) + .set_shader(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/render/shaders/", + "sprite.wgsl" + ))) + .set_format(config.format) + .set_triangle(true) + .set_vertex_buffers(&[TexturedVertex::desc(), SpriteInstance::desc()]) + .set_bind_group_layouts(&[&texture_array.bind_group_layout]) + .build(); - { - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("sprite shader"), - source: wgpu::ShaderSource::Wgsl( - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/src/render/shaders/", - "shader.wgsl" - )) - .into(), - ), - }); + let starfield_pipeline = PipelineBuilder::new("starfield", &device) + .set_shader(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/render/shaders/", + "starfield.wgsl" + ))) + .set_format(config.format) + .set_triangle(false) + .set_vertex_buffers(&[PlainVertex::desc(), StarInstance::desc()]) + .build(); - render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("render pipeline layout"), - bind_group_layouts: &[&texture_array.bind_group_layout], - push_constant_ranges: &[], - }); - - render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("render pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vertex_shader_main", - buffers: &[Vertex::desc(), SpriteInstance::desc()], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fragment_shader_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }); - } - - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + let sprite_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("vertex buffer"), - contents: bytemuck::cast_slice(SPRITE_MESH_VERTICES), + contents: bytemuck::cast_slice(SPRITE_VERTICES), usage: wgpu::BufferUsages::VERTEX, }); - let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + let sprite_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("vertex index buffer"), - contents: bytemuck::cast_slice(SPRITE_MESH_INDICES), + contents: bytemuck::cast_slice(SPRITE_INDICES), usage: wgpu::BufferUsages::INDEX, }); - let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("instance buffer"), + let star_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("vertex buffer"), + contents: bytemuck::cast_slice(STARFIELD_VERTICES), + usage: wgpu::BufferUsages::VERTEX, + }); + + let sprite_instance_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("sprite instance buffer"), usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, size: SpriteInstance::SIZE * Self::SPRITE_LIMIT, mapped_at_creation: false, }); + let star_instance_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("star instance buffer"), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + size: StarInstance::SIZE * Self::STAR_LIMIT, + mapped_at_creation: false, + }); + return Ok(Self { surface, device, @@ -189,10 +170,13 @@ impl GPUState { config, size, window, - render_pipeline, - vertex_buffer, - index_buffer, - instance_buffer, + sprite_pipeline, + starfield_pipeline, + sprite_vertex_buffer, + sprite_index_buffer, + star_vertex_buffer, + sprite_instance_buffer, + star_instance_buffer, texture_array, }); } @@ -259,6 +243,7 @@ impl GPUState { let clip_sw = Point2::from((1.0, -1.0)) * camera.zoom; let mut instances: Vec = Vec::new(); + for s in sprites { let pos = s.post_parallax_position(camera) - camera.pos.to_vec(); let texture = self.texture_array.get_texture(&s.name[..]); @@ -306,26 +291,53 @@ impl GPUState { } // Enforce sprite limit - if sprites.len() as u64 >= Self::SPRITE_LIMIT { + if sprites.len() as u64 > Self::SPRITE_LIMIT { // TODO: no panic, handle this better. panic!("Sprite limit exceeded!") } // Write new sprite data to buffer - self.queue - .write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&instances)); - - render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]); - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - render_pass.set_vertex_buffer(1, self.instance_buffer.slice(..)); - render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); - render_pass.draw_indexed( - 0..SPRITE_MESH_INDICES.len() as u32, + self.queue.write_buffer( + &self.sprite_instance_buffer, 0, - 0..instances.len() as _, + bytemuck::cast_slice(&instances), ); + render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]); + + let nstances: Vec = (-10..10) + .map(|x| StarInstance { + transform: cgmath::Matrix4::from_translation(cgmath::Vector3 { + x: x as f32 / 10.0, + y: 0.0, + z: 0.0, + }) + .into(), + }) + .collect(); + + self.queue.write_buffer( + &self.star_instance_buffer, + 0, + bytemuck::cast_slice(&nstances), + ); + + // Starfield pipeline + render_pass.set_vertex_buffer(0, self.star_vertex_buffer.slice(..)); + render_pass.set_vertex_buffer(1, self.star_instance_buffer.slice(..)); + render_pass.set_pipeline(&self.starfield_pipeline); + render_pass.draw(0..STARFIELD_VERTICES.len() as _, 0..nstances.len() as _); + + // Sprite pipeline + render_pass.set_vertex_buffer(0, self.sprite_vertex_buffer.slice(..)); + render_pass.set_vertex_buffer(1, self.sprite_instance_buffer.slice(..)); + render_pass.set_index_buffer( + self.sprite_index_buffer.slice(..), + wgpu::IndexFormat::Uint16, + ); + render_pass.set_pipeline(&self.sprite_pipeline); + render_pass.draw_indexed(0..SPRITE_INDICES.len() as u32, 0, 0..instances.len() as _); + // begin_render_pass borrows encoder mutably, so we can't call finish() // without dropping this variable. drop(render_pass);