From d037fc4acda287a8980eb135023ad04f36c0f0a2 Mon Sep 17 00:00:00 2001 From: Jonas H Date: Tue, 3 Mar 2026 19:44:52 +0100 Subject: [PATCH] debug rendering --- src/debug/collider_debug.rs | 4 +- src/debug/mod.rs | 3 + src/debug/mode.rs | 46 +++++++ src/main.rs | 15 +++ src/render/debug_overlay.rs | 232 +++++++++++++++++++++++++++++++++ src/render/mod.rs | 183 +++++++++++++++++++++++++- src/render/pipeline.rs | 144 ++++++++++++++++++++ src/render/shadow.rs | 8 +- src/render/types.rs | 6 +- src/shaders/debug_overlay.wgsl | 42 ++++++ src/shaders/main.wesl | 14 +- src/shaders/util.wesl | 3 +- src/utility/input.rs | 4 + 13 files changed, 694 insertions(+), 10 deletions(-) create mode 100644 src/debug/mode.rs create mode 100644 src/render/debug_overlay.rs create mode 100644 src/shaders/debug_overlay.wgsl diff --git a/src/debug/collider_debug.rs b/src/debug/collider_debug.rs index c7132e4..84f4bec 100644 --- a/src/debug/collider_debug.rs +++ b/src/debug/collider_debug.rs @@ -143,7 +143,7 @@ pub fn render_collider_debug() -> Vec index_buffer: wireframe_box.index_buffer.clone(), num_indices: wireframe_box.num_indices, model, - pipeline: Pipeline::Standard, + pipeline: Pipeline::DebugLines, instance_buffer: Some(instance_buffer), num_instances: 1, tile_scale: 4.0, @@ -176,7 +176,7 @@ pub fn render_collider_debug() -> Vec index_buffer: heightfield_mesh.index_buffer.clone(), num_indices: heightfield_mesh.num_indices, model: Mat4::IDENTITY, - pipeline: Pipeline::Standard, + pipeline: Pipeline::DebugLines, instance_buffer: Some(instance_buffer), num_instances: 1, tile_scale: 4.0, diff --git a/src/debug/mod.rs b/src/debug/mod.rs index db69d25..374edf2 100644 --- a/src/debug/mod.rs +++ b/src/debug/mod.rs @@ -1 +1,4 @@ pub mod collider_debug; +pub mod mode; + +pub use mode::DebugMode; diff --git a/src/debug/mode.rs b/src/debug/mode.rs new file mode 100644 index 0000000..1b0b43b --- /dev/null +++ b/src/debug/mode.rs @@ -0,0 +1,46 @@ +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum DebugMode +{ + #[default] + None, + Normals, + Uv, + Depth, + Wireframe, + Colliders, + ShadowMap, + SnowLight, +} + +impl DebugMode +{ + pub fn cycle(self) -> Self + { + match self + { + DebugMode::None => DebugMode::Normals, + DebugMode::Normals => DebugMode::Uv, + DebugMode::Uv => DebugMode::Depth, + DebugMode::Depth => DebugMode::Wireframe, + DebugMode::Wireframe => DebugMode::Colliders, + DebugMode::Colliders => DebugMode::ShadowMap, + DebugMode::ShadowMap => DebugMode::SnowLight, + DebugMode::SnowLight => DebugMode::None, + } + } + + pub fn as_u32(self) -> u32 + { + match self + { + DebugMode::None => 0, + DebugMode::Normals => 1, + DebugMode::Uv => 2, + DebugMode::Depth => 3, + DebugMode::Wireframe => 4, + DebugMode::Colliders => 5, + DebugMode::ShadowMap => 6, + DebugMode::SnowLight => 7, + } + } +} diff --git a/src/main.rs b/src/main.rs index f504f07..ccf8604 100755 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,8 @@ mod texture; mod utility; mod world; +use crate::debug::{collider_debug, DebugMode}; + use std::time::{Duration, Instant}; use glam::Vec3; @@ -94,6 +96,7 @@ fn main() -> Result<(), Box> render::set_snow_depth(&snow_layer.depth_texture_view); let mut noclip_mode = true; + let mut debug_mode = DebugMode::default(); let camera_entity = CameraBundle { position: camera_spawn, @@ -146,6 +149,12 @@ fn main() -> Result<(), Box> noclip_toggle_system(&mut world, &input_state, camera_entity, &mut noclip_mode); + if input_state.debug_cycle_just_pressed + { + debug_mode = debug_mode.cycle(); + println!("Debug mode: {:?}", debug_mode); + } + camera_input_system(&mut world, &input_state); if noclip_mode @@ -183,6 +192,11 @@ fn main() -> Result<(), Box> let mut draw_calls = render_system(&world); draw_calls.extend(snow_layer.get_draw_calls()); + if debug_mode == DebugMode::Colliders + { + draw_calls.extend(collider_debug::render_collider_debug()); + } + if let Some((camera_entity, camera_component)) = world.active_camera() { if let Some(camera_transform) = world.transforms.get(camera_entity) @@ -201,6 +215,7 @@ fn main() -> Result<(), Box> &draw_calls, time, delta, + debug_mode, ); } } diff --git a/src/render/debug_overlay.rs b/src/render/debug_overlay.rs new file mode 100644 index 0000000..2839cc2 --- /dev/null +++ b/src/render/debug_overlay.rs @@ -0,0 +1,232 @@ +use crate::postprocess::ScreenVertex; + +pub struct DebugOverlay +{ + pipeline_shadow: wgpu::RenderPipeline, + pipeline_snow_light: wgpu::RenderPipeline, + shadow_bind_group_layout: wgpu::BindGroupLayout, + snow_light_bind_group_layout: wgpu::BindGroupLayout, +} + +impl DebugOverlay +{ + pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self + { + let shader_source = std::fs::read_to_string("src/shaders/debug_overlay.wgsl") + .expect("Failed to read debug_overlay.wgsl"); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Debug Overlay Shader"), + source: wgpu::ShaderSource::Wgsl(shader_source.into()), + }); + + let shadow_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Shadow Debug Bind Group Layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Depth, + view_dimension: wgpu::TextureViewDimension::D2Array, + multisampled: false, + }, + count: None, + }], + }); + + let snow_light_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Snow Light Debug Bind Group Layout"), + entries: &[ + 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, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let shadow_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Shadow Debug Pipeline Layout"), + bind_group_layouts: &[&shadow_bind_group_layout], + push_constant_ranges: &[], + }); + + let snow_light_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Snow Light Debug Pipeline Layout"), + bind_group_layouts: &[&snow_light_bind_group_layout], + push_constant_ranges: &[], + }); + + let color_target = wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + }; + + let pipeline_shadow = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Shadow Debug Pipeline"), + layout: Some(&shadow_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[ScreenVertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_shadow_map"), + targets: &[Some(color_target.clone())], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + cull_mode: None, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + let pipeline_snow_light = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Snow Light Debug Pipeline"), + layout: Some(&snow_light_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[ScreenVertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_snow_light"), + targets: &[Some(color_target)], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + cull_mode: None, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + Self { + pipeline_shadow, + pipeline_snow_light, + shadow_bind_group_layout, + snow_light_bind_group_layout, + } + } + + pub fn render_shadow_map( + &self, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + shadow_view: &wgpu::TextureView, + quad_vb: &wgpu::Buffer, + quad_ib: &wgpu::Buffer, + quad_num_indices: u32, + device: &wgpu::Device, + ) + { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("Shadow Debug Bind Group"), + layout: &self.shadow_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(shadow_view), + }], + }); + + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Shadow Debug Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + pass.set_pipeline(&self.pipeline_shadow); + pass.set_bind_group(0, &bind_group, &[]); + pass.set_vertex_buffer(0, quad_vb.slice(..)); + pass.set_index_buffer(quad_ib.slice(..), wgpu::IndexFormat::Uint16); + pass.draw_indexed(0..quad_num_indices, 0, 0..1); + } + + pub fn render_snow_light( + &self, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + snow_light_view: &wgpu::TextureView, + snow_light_sampler: &wgpu::Sampler, + quad_vb: &wgpu::Buffer, + quad_ib: &wgpu::Buffer, + quad_num_indices: u32, + device: &wgpu::Device, + ) + { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("Snow Light Debug Bind Group"), + layout: &self.snow_light_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(snow_light_view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(snow_light_sampler), + }, + ], + }); + + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Snow Light Debug Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + pass.set_pipeline(&self.pipeline_snow_light); + pass.set_bind_group(0, &bind_group, &[]); + pass.set_vertex_buffer(0, quad_vb.slice(..)); + pass.set_index_buffer(quad_ib.slice(..), wgpu::IndexFormat::Uint16); + pass.draw_indexed(0..quad_num_indices, 0, 0..1); + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs index a36b7e8..d7e9863 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,16 +1,18 @@ mod bind_group; +mod debug_overlay; mod pipeline; mod shadow; mod types; pub use types::{DrawCall, Pipeline, Spotlight, SpotlightRaw, Uniforms, MAX_SPOTLIGHTS}; +use crate::debug::DebugMode; use crate::postprocess::{create_blit_pipeline, create_fullscreen_quad, LowResFramebuffer}; use crate::texture::{DitherTextures, FlowmapTexture}; +use pipeline::{create_debug_lines_pipeline, create_main_pipeline, create_snow_clipmap_pipeline, + create_wireframe_pipeline}; use std::cell::RefCell; -use pipeline::{create_main_pipeline, create_snow_clipmap_pipeline}; - pub struct Renderer { pub device: wgpu::Device, @@ -22,6 +24,10 @@ pub struct Renderer standard_pipeline: wgpu::RenderPipeline, snow_clipmap_pipeline: wgpu::RenderPipeline, + wireframe_pipeline: Option, + debug_lines_pipeline: Option, + debug_overlay: Option, + wireframe_supported: bool, uniform_buffer: wgpu::Buffer, bind_group_layout: wgpu::BindGroupLayout, @@ -84,8 +90,21 @@ impl Renderer .await .map_err(|_| "Failed to find adapter")?; + let wireframe_supported = + adapter.features().contains(wgpu::Features::POLYGON_MODE_LINE); + let required_features = if wireframe_supported + { + wgpu::Features::POLYGON_MODE_LINE + } + else + { + wgpu::Features::empty() + }; let (device, queue) = adapter - .request_device(&wgpu::DeviceDescriptor::default()) + .request_device(&wgpu::DeviceDescriptor { + required_features, + ..Default::default() + }) .await?; let size = window.size(); @@ -442,6 +461,20 @@ impl Renderer let blit_pipeline = create_blit_pipeline(&device, config.format, &blit_bind_group_layout); + let wireframe_pipeline = if wireframe_supported + { + Some(create_wireframe_pipeline(&device, config.format, &bind_group_layout)) + } + else + { + None + }; + + let debug_lines_pipeline = + Some(create_debug_lines_pipeline(&device, config.format, &bind_group_layout)); + + let debug_overlay = Some(debug_overlay::DebugOverlay::new(&device, config.format)); + let shadow_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Shadow Bind Group Layout"), @@ -465,6 +498,10 @@ impl Renderer framebuffer, standard_pipeline, snow_clipmap_pipeline, + wireframe_pipeline, + debug_lines_pipeline, + debug_overlay, + wireframe_supported, uniform_buffer, bind_group_layout, bind_group, @@ -508,6 +545,7 @@ impl Renderer draw_calls: &[DrawCall], time: f32, delta_time: f32, + debug_mode: DebugMode, ) { let light_view_projections = self.calculate_light_view_projections(); @@ -563,6 +601,7 @@ impl Renderer draw_call.enable_dissolve, draw_call.enable_snow_light, &self.spotlights, + debug_mode.as_u32(), ); self.queue .write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms])); @@ -614,6 +653,10 @@ impl Renderer { Pipeline::Standard => &self.standard_pipeline, Pipeline::SnowClipmap => &self.snow_clipmap_pipeline, + Pipeline::DebugLines => self + .debug_lines_pipeline + .as_ref() + .unwrap_or(&self.standard_pipeline), }; render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, &self.bind_group, &[]); @@ -636,6 +679,138 @@ impl Renderer } } + if debug_mode == DebugMode::Wireframe + { + if let Some(ref wireframe_pipeline) = self.wireframe_pipeline + { + for draw_call in draw_calls.iter() + { + if matches!(draw_call.pipeline, Pipeline::SnowClipmap | Pipeline::DebugLines) + { + continue; + } + + let uniforms = Uniforms::new( + draw_call.model, + *view, + *projection, + &light_view_projections, + camera_position, + player_position, + self.terrain_height_scale, + time, + self.shadow_bias, + draw_call.tile_scale, + draw_call.enable_dissolve, + draw_call.enable_snow_light, + &self.spotlights, + 4, + ); + self.queue.write_buffer( + &self.uniform_buffer, + 0, + bytemuck::cast_slice(&[uniforms]), + ); + + { + let mut wire_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Wireframe Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &self.framebuffer.view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + depth_stencil_attachment: Some( + wgpu::RenderPassDepthStencilAttachment { + view: &self.framebuffer.depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }, + ), + timestamp_writes: None, + occlusion_query_set: None, + }); + + wire_pass.set_pipeline(wireframe_pipeline); + wire_pass.set_bind_group(0, &self.bind_group, &[]); + wire_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..)); + + if let Some(ref instance_buffer) = draw_call.instance_buffer + { + wire_pass.set_vertex_buffer(1, instance_buffer.slice(..)); + } + + wire_pass.set_index_buffer( + draw_call.index_buffer.slice(..), + wgpu::IndexFormat::Uint32, + ); + wire_pass.draw_indexed( + 0..draw_call.num_indices, + 0, + 0..draw_call.num_instances, + ); + } + } + } + } + + if debug_mode == DebugMode::ShadowMap + { + if let Some(ref overlay) = self.debug_overlay + { + let shadow_view = &self.shadow_map_view; + let framebuffer_view = &self.framebuffer.view; + let quad_vb = &self.quad_vb; + let quad_ib = &self.quad_ib; + let quad_num = self.quad_num_indices; + let device = &self.device; + overlay.render_shadow_map( + &mut encoder, + framebuffer_view, + shadow_view, + quad_vb, + quad_ib, + quad_num, + device, + ); + } + } + else if debug_mode == DebugMode::SnowLight + { + if let Some(ref overlay) = self.debug_overlay + { + let snow_light_view = self + .snow_light_accumulation + .as_ref() + .map(|s| s.read_view()) + .unwrap_or(&self.dummy_snow_light_view); + let framebuffer_view = &self.framebuffer.view; + let quad_vb = &self.quad_vb; + let quad_ib = &self.quad_ib; + let quad_num = self.quad_num_indices; + let device = &self.device; + let sampler = &self.dummy_snow_light_sampler; + overlay.render_snow_light( + &mut encoder, + framebuffer_view, + snow_light_view, + sampler, + quad_vb, + quad_ib, + quad_num, + device, + ); + } + } + self.queue.submit(std::iter::once(encoder.finish())); let frame = match self.surface.get_current_texture() @@ -849,6 +1024,7 @@ pub fn render( draw_calls: &[DrawCall], time: f32, delta_time: f32, + debug_mode: DebugMode, ) { GLOBAL_RENDERER.with(|r| { @@ -862,6 +1038,7 @@ pub fn render( draw_calls, time, delta_time, + debug_mode, ); }); } diff --git a/src/render/pipeline.rs b/src/render/pipeline.rs index 901934d..56c7791 100644 --- a/src/render/pipeline.rs +++ b/src/render/pipeline.rs @@ -136,6 +136,150 @@ pub fn create_main_pipeline( }) } +pub fn create_wireframe_pipeline( + device: &wgpu::Device, + format: wgpu::TextureFormat, + bind_group_layout: &wgpu::BindGroupLayout, +) -> wgpu::RenderPipeline +{ + let compiler = Wesl::new("src/shaders"); + let shader_source = compiler + .compile(&"package::main".parse().unwrap()) + .inspect_err(|e| eprintln!("WESL error: {e}")) + .unwrap() + .to_string(); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Wireframe Shader"), + source: wgpu::ShaderSource::Wgsl(shader_source.into()), + }); + + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Wireframe Pipeline Layout"), + bind_group_layouts: &[bind_group_layout], + push_constant_ranges: &[], + }); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Wireframe Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[ + crate::loaders::mesh::Vertex::desc(), + crate::loaders::mesh::InstanceRaw::desc(), + ], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::Line, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::LessEqual, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }) +} + +pub fn create_debug_lines_pipeline( + device: &wgpu::Device, + format: wgpu::TextureFormat, + bind_group_layout: &wgpu::BindGroupLayout, +) -> wgpu::RenderPipeline +{ + let compiler = Wesl::new("src/shaders"); + let shader_source = compiler + .compile(&"package::main".parse().unwrap()) + .inspect_err(|e| eprintln!("WESL error: {e}")) + .unwrap() + .to_string(); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Debug Lines Shader"), + source: wgpu::ShaderSource::Wgsl(shader_source.into()), + }); + + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Debug Lines Pipeline Layout"), + bind_group_layouts: &[bind_group_layout], + push_constant_ranges: &[], + }); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Debug Lines Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[ + crate::loaders::mesh::Vertex::desc(), + crate::loaders::mesh::InstanceRaw::desc(), + ], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::LineList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::Always, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }) +} + pub fn create_snow_clipmap_pipeline( device: &wgpu::Device, format: wgpu::TextureFormat, diff --git a/src/render/shadow.rs b/src/render/shadow.rs index c8ab344..9d7f27f 100644 --- a/src/render/shadow.rs +++ b/src/render/shadow.rs @@ -1,6 +1,6 @@ use glam::Mat4; -use super::types::{DrawCall, Uniforms, MAX_SPOTLIGHTS}; +use super::types::{DrawCall, Pipeline, Uniforms, MAX_SPOTLIGHTS}; use super::Renderer; impl Renderer @@ -31,6 +31,7 @@ impl Renderer false, false, &self.spotlights, + 0, ); self.queue.write_buffer( &self.uniform_buffer, @@ -84,6 +85,11 @@ impl Renderer for draw_call in draw_calls.iter() { + if matches!(draw_call.pipeline, Pipeline::DebugLines) + { + continue; + } + shadow_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..)); if let Some(ref instance_buffer) = draw_call.instance_buffer diff --git a/src/render/types.rs b/src/render/types.rs index 5110bf5..20d5fa5 100644 --- a/src/render/types.rs +++ b/src/render/types.rs @@ -73,7 +73,7 @@ pub struct Uniforms pub enable_dissolve: u32, pub enable_snow_light: u32, pub spotlight_count: u32, - pub _padding1: u32, + pub debug_mode: u32, pub _padding2: u32, pub _padding3: u32, pub spotlights: [SpotlightRaw; MAX_SPOTLIGHTS], @@ -95,6 +95,7 @@ impl Uniforms enable_dissolve: bool, enable_snow_light: bool, spotlights: &[Spotlight], + debug_mode: u32, ) -> Self { let mut spotlight_array = [SpotlightRaw::default(); MAX_SPOTLIGHTS]; @@ -126,7 +127,7 @@ impl Uniforms enable_dissolve: if enable_dissolve { 1 } else { 0 }, enable_snow_light: if enable_snow_light { 1 } else { 0 }, spotlight_count: spotlights.len().min(MAX_SPOTLIGHTS) as u32, - _padding1: 0, + debug_mode, _padding2: 0, _padding3: 0, spotlights: spotlight_array, @@ -139,6 +140,7 @@ pub enum Pipeline { Standard, SnowClipmap, + DebugLines, } pub struct DrawCall diff --git a/src/shaders/debug_overlay.wgsl b/src/shaders/debug_overlay.wgsl new file mode 100644 index 0000000..a625b33 --- /dev/null +++ b/src/shaders/debug_overlay.wgsl @@ -0,0 +1,42 @@ +struct VertexInput { + @location(0) position: vec2, + @location(1) uv: vec2, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) uv: vec2, +} + +@vertex +fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.clip_position = vec4(input.position, 0.0, 1.0); + output.uv = input.uv; + return output; +} + +@group(0) @binding(0) +var t_shadow: texture_depth_2d_array; + +@fragment +fn fs_shadow_map(input: VertexOutput) -> @location(0) vec4 { + let size = vec2(textureDimensions(t_shadow)); + let coord = vec2(input.uv * size); + let depth = textureLoad(t_shadow, coord, 0, 0); + let linearized = 1.0 - depth; + return vec4(linearized, linearized, linearized, 1.0); +} + +@group(0) @binding(1) +var t_snow_light: texture_2d; + +@group(0) @binding(2) +var t_sampler: sampler; + +@fragment +fn fs_snow_light(input: VertexOutput) -> @location(0) vec4 { + let value = textureSample(t_snow_light, t_sampler, input.uv).r; + let remapped = clamp(value * 10.0, 0.0, 1.0); + return vec4(remapped, remapped, remapped, 1.0); +} diff --git a/src/shaders/main.wesl b/src/shaders/main.wesl index f451461..bf3b187 100644 --- a/src/shaders/main.wesl +++ b/src/shaders/main.wesl @@ -72,6 +72,7 @@ fn vs_main(input: VertexInput) -> VertexOutput { } output.dissolve_amount = dissolve_amount; + output.uv = input.uv; return output; } @@ -107,7 +108,17 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { } } - return vec4(brightness, brightness, brightness, 1.0); + switch uniforms.debug_mode { + case 1u { return vec4(in.world_normal * 0.5 + 0.5, 1.0); } + case 2u { return vec4(fract(in.uv), 0.0, 1.0); } + case 3u { + let d = in.clip_position.z / in.clip_position.w; + let lin = (d - 0.95) / 0.05; + return vec4(vec3(lin), 1.0); + } + case 4u { return vec4(1.0); } + default { return vec4(brightness, brightness, brightness, 1.0); } + } } @group(1) @binding(0) @@ -172,6 +183,7 @@ fn vs_snow_main(in: VertexInput) -> VertexOutput { out.world_normal = normal; out.clip_position = uniforms.projection * uniforms.view * vec4(world_position, 1.0); out.dissolve_amount = in.instance_dissolve; + out.uv = uv; return out; } diff --git a/src/shaders/util.wesl b/src/shaders/util.wesl index ce2036b..7af97fd 100644 --- a/src/shaders/util.wesl +++ b/src/shaders/util.wesl @@ -14,6 +14,7 @@ struct VertexOutput { @location(0) world_position: vec3, @location(1) world_normal: vec3, @location(2) dissolve_amount: f32, + @location(3) uv: vec2, } const MAX_SPOTLIGHTS: u32 = 4u; @@ -47,7 +48,7 @@ struct Uniforms { enable_dissolve: u32, enable_snow_light: u32, spotlight_count: u32, - _padding1: u32, + debug_mode: u32, _padding2: u32, _padding3: u32, spotlights: array, diff --git a/src/utility/input.rs b/src/utility/input.rs index b0ecc0e..a44e98d 100755 --- a/src/utility/input.rs +++ b/src/utility/input.rs @@ -12,6 +12,7 @@ pub struct InputState pub space_just_pressed: bool, pub noclip_just_pressed: bool, + pub debug_cycle_just_pressed: bool, pub mouse_delta: (f32, f32), pub mouse_captured: bool, @@ -32,6 +33,7 @@ impl InputState shift: false, space_just_pressed: false, noclip_just_pressed: false, + debug_cycle_just_pressed: false, mouse_delta: (0.0, 0.0), mouse_captured: true, noclip_mode: false, @@ -110,6 +112,7 @@ impl InputState } Keycode::LShift | Keycode::RShift => self.shift = true, Keycode::N => self.noclip_just_pressed = true, + Keycode::F1 => self.debug_cycle_just_pressed = true, _ => {} } @@ -154,6 +157,7 @@ impl InputState { self.space_just_pressed = false; self.noclip_just_pressed = false; + self.debug_cycle_just_pressed = false; self.mouse_delta = (0.0, 0.0); }