use bytemuck::{Pod, Zeroable}; use bytemuck::__core::mem; use wgpu::util::DeviceExt; use std::{iter, num::NonZeroU32, ops::Range, rc::Rc}; use crate::OPENGL_TO_WGPU_MATRIX; use crate::light::LightRaw; use crate::geometry::{Vertex, import_mesh, create_plane}; #[repr(C)] #[derive(Clone, Copy)] pub struct ForwardUniforms { proj: [[f32; 4]; 4], num_lights: [u32; 4], } unsafe impl Pod for ForwardUniforms {} unsafe impl Zeroable for ForwardUniforms {} #[repr(C)] #[derive(Clone, Copy)] pub struct EntityUniforms { model: [[f32; 4]; 4], color: [f32; 4], } unsafe impl Pod for EntityUniforms {} unsafe impl Zeroable for EntityUniforms {} #[repr(C)] pub struct ShadowUniforms { proj: [[f32; 4]; 4], } pub struct Pass { pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, uniform_buf: wgpu::Buffer, } pub struct Renderer { lights_are_dirty: bool, shadow_pass: Pass, forward_pass: Pass, forward_depth: wgpu::TextureView, light_uniform_buf: wgpu::Buffer, } impl Renderer { const MAX_LIGHTS: usize = 10; const SHADOW_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; const SHADOW_SIZE: wgpu::Extent3d = wgpu::Extent3d { width: 512, height: 512, depth: Self::MAX_LIGHTS as u32, }; const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; fn generate_matrix(aspect_ratio: f32) -> cgmath::Matrix4 { let mx_projection = cgmath::perspective(cgmath::Deg(45f32), aspect_ratio, 1.0, 20.0); let mx_view = cgmath::Matrix4::look_at( cgmath::Point3::new(3.0f32, -10.0, 6.0), cgmath::Point3::new(0f32, 0.0, 0.0), cgmath::Vector3::unit_z(), ); let mx_correction = OPENGL_TO_WGPU_MATRIX; mx_correction * mx_projection * mx_view } } impl Renderer { pub fn create_buffer(&mut self, device: &wgpu::Device) { // Creates the vertex and index buffers for the cube let vertex_size = mem::size_of::(); let (cube_vertex_data, cube_index_data) = import_mesh("/home/mrh/source/3d-min-viable-eng/resources/my_tree.obj"); let cube_vertex_buf = Rc::new(device.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Cubes Vertex Buffer"), contents: bytemuck::cast_slice(&cube_vertex_data), usage: wgpu::BufferUsage::VERTEX, }, )); let cube_index_buf = Rc::new(device.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Cubes Index Buffer"), contents: bytemuck::cast_slice(&cube_index_data), usage: wgpu::BufferUsage::INDEX, }, )); // Creates the vertex and index buffers for the plane let (plane_vertex_data, plane_index_data) = create_plane(7.0); let plane_vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Plane Vertex Buffer"), contents: bytemuck::cast_slice(&plane_vertex_data), usage: wgpu::BufferUsage::VERTEX, }); let plane_index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Plane Index Buffer"), contents: bytemuck::cast_slice(&plane_index_data), usage: wgpu::BufferUsage::INDEX, }); // Creates the uniform for entities, which does the rotation and projection let entity_uniform_size = mem::size_of::() as wgpu::BufferAddress; let plane_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: entity_uniform_size, usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, mapped_at_creation: false, }); } pub fn init(device: &wgpu::Device) -> Renderer { // Pre init the light uniform, with slots enough for MAX_LIGHTS let light_uniform_size = (Self::MAX_LIGHTS * mem::size_of::()) as wgpu::BufferAddress; let light_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: light_uniform_size, usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_SRC | wgpu::BufferUsage::COPY_DST, mapped_at_creation: false, }); // This seems way way way way easier than what I was doing in tracer // Though the attr thing is still a macro. Which would cause issues if // I wanted to get tricky with the 0,1 types let vertex_size = mem::size_of::(); let vertex_attr = wgpu::vertex_attr_array![0 => Float4, 1 => Float4]; let vb_desc = wgpu::VertexBufferDescriptor { stride: vertex_size as wgpu::BufferAddress, step_mode: wgpu::InputStepMode::Vertex, attributes: &vertex_attr, }; // This is also in the runtime which really shouldn't have this let local_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, count: None, ty: wgpu::BindingType::UniformBuffer { dynamic: false, min_binding_size: wgpu::BufferSize::new( mem::size_of::() as _ ), }, }], }); /* There appear to be two passes required for shadows, the shadow pass, and the forward pass Need to open this up in renderdoc and see what it's actually doing */ let shadow_pass = { let uniform_size = mem::size_of::() as wgpu::BufferAddress; // I believe this is just making a_Pos or u_ViewProj available in the vert shader let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[wgpu::BindGroupLayoutEntry { binding: 0, // global visibility: wgpu::ShaderStage::VERTEX, ty: wgpu::BindingType::UniformBuffer { dynamic: false, min_binding_size: wgpu::BufferSize::new(uniform_size), }, count: None, }], }); // Pipeline is similar between passes, but with a different label let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("shadow"), bind_group_layouts: &[&bind_group_layout, &local_bind_group_layout], push_constant_ranges: &[], }); // Holds the shadow uniforms, which is just a 4 vec of quaternians let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: uniform_size, usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, mapped_at_creation: false, }); // Create bind group let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(uniform_buf.slice(..)), }], label: None, }); // Create the render pipeline let vs_module = device.create_shader_module(wgpu::include_spirv!("../resources/bake.vert.spv")); let fs_module = device.create_shader_module(wgpu::include_spirv!("../resources/bake.frag.spv")); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("shadow"), layout: Some(&pipeline_layout), vertex_stage: wgpu::ProgrammableStageDescriptor { module: &vs_module, entry_point: "main", }, fragment_stage: Some(wgpu::ProgrammableStageDescriptor { module: &fs_module, entry_point: "main", }), rasterization_state: Some(wgpu::RasterizationStateDescriptor { front_face: wgpu::FrontFace::Ccw, cull_mode: wgpu::CullMode::Back, depth_bias: 2, // corresponds to bilinear filtering depth_bias_slope_scale: 2.0, depth_bias_clamp: 0.0, clamp_depth: device.features().contains(wgpu::Features::DEPTH_CLAMPING), }), primitive_topology: wgpu::PrimitiveTopology::TriangleList, color_states: &[], depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { format: Self::SHADOW_FORMAT, depth_write_enabled: true, depth_compare: wgpu::CompareFunction::LessEqual, stencil: wgpu::StencilStateDescriptor::default(), }), vertex_state: wgpu::VertexStateDescriptor { index_format: wgpu::IndexFormat::Uint32, vertex_buffers: &[vb_desc.clone()], }, sample_count: 1, sample_mask: !0, alpha_to_coverage_enabled: false, }); Pass { pipeline, bind_group, uniform_buf, } }; let forward_pass = { // Create pipeline layout let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, // global visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::UniformBuffer { dynamic: false, min_binding_size: wgpu::BufferSize::new(mem::size_of::< ForwardUniforms, >() as _), }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, // lights visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::UniformBuffer { dynamic: false, min_binding_size: wgpu::BufferSize::new(light_uniform_size), }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::SampledTexture { multisampled: false, component_type: wgpu::TextureComponentType::Float, dimension: wgpu::TextureViewDimension::D2Array, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 3, visibility: wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::Sampler { comparison: true }, count: None, }, ], label: None, }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("main"), bind_group_layouts: &[&bind_group_layout, &local_bind_group_layout], push_constant_ranges: &[], }); let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32); let forward_uniforms = ForwardUniforms { proj: *mx_total.as_ref(), num_lights: [lights.len() as u32, 0, 0, 0], }; let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Uniform Buffer"), contents: bytemuck::bytes_of(&forward_uniforms), usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, }); // Create bind group let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(uniform_buf.slice(..)), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Buffer(light_uniform_buf.slice(..)), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&shadow_view), }, wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&shadow_sampler), }, ], label: None, }); // Create the render pipeline let vs_module = device.create_shader_module(wgpu::include_spirv!("../resources/forward.vert.spv")); let fs_module = device.create_shader_module(wgpu::include_spirv!("../resources/forward.frag.spv")); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("main"), layout: Some(&pipeline_layout), vertex_stage: wgpu::ProgrammableStageDescriptor { module: &vs_module, entry_point: "main", }, fragment_stage: Some(wgpu::ProgrammableStageDescriptor { module: &fs_module, entry_point: "main", }), rasterization_state: Some(wgpu::RasterizationStateDescriptor { front_face: wgpu::FrontFace::Ccw, cull_mode: wgpu::CullMode::Back, ..Default::default() }), primitive_topology: wgpu::PrimitiveTopology::TriangleList, color_states: &[sc_desc.format.into()], depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { format: Self::DEPTH_FORMAT, depth_write_enabled: true, depth_compare: wgpu::CompareFunction::Less, stencil: wgpu::StencilStateDescriptor::default(), }), vertex_state: wgpu::VertexStateDescriptor { index_format: wgpu::IndexFormat::Uint32, vertex_buffers: &[vb_desc], }, sample_count: 1, sample_mask: !0, alpha_to_coverage_enabled: false, }); Pass { pipeline, bind_group, uniform_buf, } }; let depth_texture = device.create_texture(&wgpu::TextureDescriptor { size: wgpu::Extent3d { width: sc_desc.width, height: sc_desc.height, depth: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: Self::DEPTH_FORMAT, usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, label: None, }); Renderer { lights_are_dirty: false, shadow_pass, forward_pass, forward_depth: depth_texture.create_view(&wgpu::TextureViewDescriptor::default()), light_uniform_buf, } } pub fn render( &mut self, frame: &wgpu::SwapChainTexture, device: &wgpu::Device, queue: &wgpu::Queue, _spawner: &impl futures::task::LocalSpawn, ) { // update uniforms for entity in self.entities.iter_mut() { if entity.rotation_speed != 0.0 { let rotation = cgmath::Matrix4::from_angle_x(cgmath::Deg(entity.rotation_speed)); entity.mx_world = entity.mx_world * rotation; } let data = EntityUniforms { model: entity.mx_world.into(), color: [ entity.color.r as f32, entity.color.g as f32, entity.color.b as f32, entity.color.a as f32, ], }; queue.write_buffer(&entity.uniform_buf, 0, bytemuck::bytes_of(&data)); } if self.lights_are_dirty { self.lights_are_dirty = false; for (i, light) in self.lights.iter().enumerate() { queue.write_buffer( &self.light_uniform_buf, (i * mem::size_of::()) as wgpu::BufferAddress, bytemuck::bytes_of(&light.to_raw()), ); } } let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); encoder.push_debug_group("shadow passes"); for (i, light) in self.lights.iter().enumerate() { encoder.push_debug_group(&format!( "shadow pass {} (light at position {:?})", i, light.pos )); // The light uniform buffer already has the projection, // let's just copy it over to the shadow uniform buffer. encoder.copy_buffer_to_buffer( &self.light_uniform_buf, (i * mem::size_of::()) as wgpu::BufferAddress, &self.shadow_pass.uniform_buf, 0, 64, ); encoder.insert_debug_marker("render entities"); { let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[], depth_stencil_attachment: Some( wgpu::RenderPassDepthStencilAttachmentDescriptor { attachment: &light.target_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), store: true, }), stencil_ops: None, }, ), }); pass.set_pipeline(&self.shadow_pass.pipeline); pass.set_bind_group(0, &self.shadow_pass.bind_group, &[]); for entity in &self.entities { pass.set_bind_group(1, &entity.bind_group, &[]); pass.set_index_buffer(entity.index_buf.slice(..)); pass.set_vertex_buffer(0, entity.vertex_buf.slice(..)); pass.draw_indexed(0..entity.index_count as u32, 0, 0..1); } } encoder.pop_debug_group(); } encoder.pop_debug_group(); // forward pass encoder.push_debug_group("forward rendering pass"); { let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { attachment: &frame.view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.1, g: 0.2, b: 0.3, a: 1.0, }), store: true, }, }], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor { attachment: &self.forward_depth, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), store: false, }), stencil_ops: None, }), }); pass.set_pipeline(&self.forward_pass.pipeline); pass.set_bind_group(0, &self.forward_pass.bind_group, &[]); for entity in &self.entities { pass.set_bind_group(1, &entity.bind_group, &[]); pass.set_index_buffer(entity.index_buf.slice(..)); pass.set_vertex_buffer(0, entity.vertex_buf.slice(..)); pass.draw_indexed(0..entity.index_count as u32, 0, 0..1); } } encoder.pop_debug_group(); queue.submit(iter::once(encoder.finish())); } pub fn optional_features() -> wgpu::Features { wgpu::Features::DEPTH_CLAMPING } pub fn resize( &mut self, sc_desc: &wgpu::SwapChainDescriptor, device: &wgpu::Device, queue: &wgpu::Queue, ) { // update view-projection matrix let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32); let mx_ref: &[f32; 16] = mx_total.as_ref(); queue.write_buffer( &self.forward_pass.uniform_buf, 0, bytemuck::cast_slice(mx_ref), ); let depth_texture = device.create_texture(&wgpu::TextureDescriptor { size: wgpu::Extent3d { width: sc_desc.width, height: sc_desc.height, depth: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: Self::DEPTH_FORMAT, usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, label: None, }); self.forward_depth = depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); } }