debug rendering

This commit is contained in:
Jonas H
2026-03-03 19:44:52 +01:00
parent 53a8276a3c
commit d037fc4acd
13 changed files with 694 additions and 10 deletions

232
src/render/debug_overlay.rs Normal file
View File

@@ -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);
}
}

View File

@@ -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<wgpu::RenderPipeline>,
debug_lines_pipeline: Option<wgpu::RenderPipeline>,
debug_overlay: Option<debug_overlay::DebugOverlay>,
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,
);
});
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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