diff --git a/crates/voltex_renderer/src/deferred_pipeline.rs b/crates/voltex_renderer/src/deferred_pipeline.rs new file mode 100644 index 0000000..b8a0e72 --- /dev/null +++ b/crates/voltex_renderer/src/deferred_pipeline.rs @@ -0,0 +1,314 @@ +use crate::vertex::MeshVertex; +use crate::fullscreen_quad::FullscreenVertex; +use crate::gpu::DEPTH_FORMAT; +use crate::gbuffer::{ + GBUFFER_POSITION_FORMAT, GBUFFER_NORMAL_FORMAT, GBUFFER_ALBEDO_FORMAT, GBUFFER_MATERIAL_FORMAT, +}; +use crate::light::CameraUniform; + +/// Bind group layout for the G-Buffer pass camera uniform (dynamic offset, group 0). +pub fn gbuffer_camera_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("GBuffer Camera Bind Group Layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: wgpu::BufferSize::new( + std::mem::size_of::() as u64, + ), + }, + count: None, + }, + ], + }) +} + +/// Create the G-Buffer geometry pass render pipeline. +pub fn create_gbuffer_pipeline( + device: &wgpu::Device, + camera_layout: &wgpu::BindGroupLayout, + texture_layout: &wgpu::BindGroupLayout, + material_layout: &wgpu::BindGroupLayout, +) -> wgpu::RenderPipeline { + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("GBuffer Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("deferred_gbuffer.wgsl").into()), + }); + + let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("GBuffer Pipeline Layout"), + bind_group_layouts: &[camera_layout, texture_layout, material_layout], + immediate_size: 0, + }); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("GBuffer Pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[MeshVertex::LAYOUT], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[ + Some(wgpu::ColorTargetState { + format: GBUFFER_POSITION_FORMAT, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + }), + Some(wgpu::ColorTargetState { + format: GBUFFER_NORMAL_FORMAT, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + }), + Some(wgpu::ColorTargetState { + format: GBUFFER_ALBEDO_FORMAT, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + }), + Some(wgpu::ColorTargetState { + format: GBUFFER_MATERIAL_FORMAT, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + }), + ], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + 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: Some(wgpu::DepthStencilState { + format: DEPTH_FORMAT, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview_mask: None, + cache: None, + }) +} + +/// Bind group layout for the lighting pass G-Buffer textures + sampler (group 0). +/// position is Float non-filterable; normal/albedo/material are filterable. +pub fn lighting_gbuffer_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Lighting GBuffer Bind Group Layout"), + entries: &[ + // binding 0: position texture (Rgba32Float → non-filterable) + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + // binding 1: normal texture (filterable) + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + // binding 2: albedo texture (filterable) + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + // binding 3: material texture (filterable) + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + // binding 4: non-filtering sampler (required for non-filterable texture) + wgpu::BindGroupLayoutEntry { + binding: 4, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + count: None, + }, + ], + }) +} + +/// Bind group layout for the lighting pass lights + camera position uniforms (group 1). +pub fn lighting_lights_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Lighting Lights Bind Group Layout"), + entries: &[ + // binding 0: LightsUniform + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // binding 1: CameraPositionUniform + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }) +} + +/// Bind group layout for shadow map + BRDF LUT (group 2). +pub fn lighting_shadow_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Lighting Shadow Bind Group Layout"), + entries: &[ + // binding 0: shadow depth texture + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Depth, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + // binding 1: comparison sampler + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison), + count: None, + }, + // binding 2: ShadowUniform + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // binding 3: BRDF LUT texture + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + // binding 4: filtering sampler for BRDF LUT + wgpu::BindGroupLayoutEntry { + binding: 4, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }) +} + +/// Create the deferred lighting pass render pipeline. +pub fn create_lighting_pipeline( + device: &wgpu::Device, + surface_format: wgpu::TextureFormat, + gbuffer_layout: &wgpu::BindGroupLayout, + lights_layout: &wgpu::BindGroupLayout, + shadow_layout: &wgpu::BindGroupLayout, +) -> wgpu::RenderPipeline { + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Deferred Lighting Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("deferred_lighting.wgsl").into()), + }); + + let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Deferred Lighting Pipeline Layout"), + bind_group_layouts: &[gbuffer_layout, lights_layout, shadow_layout], + immediate_size: 0, + }); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Deferred Lighting Pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[FullscreenVertex::LAYOUT], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: surface_format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + 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_mask: None, + cache: None, + }) +}