dynamic uniform offsets

This commit is contained in:
Jonas H
2026-03-05 15:03:56 +01:00
parent 28f8c65571
commit 5e5c6a47e7
5 changed files with 187 additions and 43 deletions

View File

@@ -1,5 +1,7 @@
use crate::texture::{DitherTextures, FlowmapTexture};
use std::num::NonZeroU64;
use super::types::Uniforms;
use super::Renderer;
impl Renderer
@@ -24,7 +26,11 @@ impl Renderer
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: uniform_buffer,
offset: 0,
size: NonZeroU64::new(std::mem::size_of::<Uniforms>() as u64),
}),
},
wgpu::BindGroupEntry {
binding: 1,

View File

@@ -102,32 +102,31 @@ impl DebugOverlay
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_mask: 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_mask: None,
cache: None,
});
Self {
pipeline_shadow,

View File

@@ -6,12 +6,19 @@ mod types;
pub use types::{DrawCall, Pipeline, Spotlight, SpotlightRaw, Uniforms, MAX_SPOTLIGHTS};
use crate::entity::EntityHandle;
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 pipeline::{
create_debug_lines_pipeline, create_main_pipeline, create_snow_clipmap_pipeline,
create_wireframe_pipeline,
};
use std::cell::RefCell;
use std::num::NonZeroU64;
const MAX_DRAW_CALLS: usize = 64;
pub struct Renderer
{
@@ -62,6 +69,8 @@ pub struct Renderer
snow_light_accumulation: Option<crate::snow_light::SnowLightAccumulation>,
snow_light_bound: bool,
pub selected_entity: Option<EntityHandle>,
}
impl Renderer
@@ -90,8 +99,9 @@ impl Renderer
.await
.map_err(|_| "Failed to find adapter")?;
let wireframe_supported =
adapter.features().contains(wgpu::Features::POLYGON_MODE_LINE);
let wireframe_supported = adapter
.features()
.contains(wgpu::Features::POLYGON_MODE_LINE);
let required_features = if wireframe_supported
{
wgpu::Features::POLYGON_MODE_LINE
@@ -129,7 +139,7 @@ impl Renderer
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Uniform Buffer"),
size: std::mem::size_of::<Uniforms>() as wgpu::BufferAddress,
size: (std::mem::size_of::<Uniforms>() * MAX_DRAW_CALLS) as wgpu::BufferAddress,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
@@ -306,8 +316,8 @@ impl Renderer
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
has_dynamic_offset: true,
min_binding_size: NonZeroU64::new(std::mem::size_of::<Uniforms>() as u64),
},
count: None,
},
@@ -463,15 +473,22 @@ impl Renderer
let wireframe_pipeline = if wireframe_supported
{
Some(create_wireframe_pipeline(&device, config.format, &bind_group_layout))
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_lines_pipeline = Some(create_debug_lines_pipeline(
&device,
config.format,
&bind_group_layout,
));
let debug_overlay = Some(debug_overlay::DebugOverlay::new(&device, config.format));
@@ -533,6 +550,7 @@ impl Renderer
dummy_snow_light_sampler,
snow_light_accumulation: None,
snow_light_bound: false,
selected_entity: None,
})
}
@@ -585,6 +603,9 @@ impl Renderer
label: Some("Render Encoder"),
});
let uniform_size = std::mem::size_of::<Uniforms>() as u64;
let mut uniform_slot: u64 = 0;
for (i, draw_call) in draw_calls.iter().enumerate()
{
let uniforms = Uniforms::new(
@@ -603,8 +624,15 @@ impl Renderer
&self.spotlights,
debug_mode.as_u32(),
);
self.queue
.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
let offset = uniform_slot * uniform_size;
uniform_slot += 1;
self.queue.write_buffer(
&self.uniform_buffer,
offset,
bytemuck::cast_slice(&[uniforms]),
);
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
@@ -660,7 +688,7 @@ impl Renderer
.unwrap_or(&self.standard_pipeline),
};
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, &self.bind_group, &[]);
render_pass.set_bind_group(0, &self.bind_group, &[offset as u32]);
if let Some(ref displacement_bind_group) = draw_call.displacement_bind_group
{
@@ -686,7 +714,10 @@ impl Renderer
{
for draw_call in draw_calls.iter()
{
if matches!(draw_call.pipeline, Pipeline::SnowClipmap | Pipeline::DebugLines)
if matches!(
draw_call.pipeline,
Pipeline::SnowClipmap | Pipeline::DebugLines
)
{
continue;
}
@@ -707,9 +738,11 @@ impl Renderer
&self.spotlights,
4,
);
let wire_offset = uniform_slot * uniform_size;
uniform_slot += 1;
self.queue.write_buffer(
&self.uniform_buffer,
0,
wire_offset,
bytemuck::cast_slice(&[uniforms]),
);
@@ -742,7 +775,7 @@ impl Renderer
});
wire_pass.set_pipeline(wireframe_pipeline);
wire_pass.set_bind_group(0, &self.bind_group, &[]);
wire_pass.set_bind_group(0, &self.bind_group, &[wire_offset as u32]);
wire_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..));
if let Some(ref instance_buffer) = draw_call.instance_buffer
@@ -764,6 +797,99 @@ impl Renderer
}
}
if let Some(selected) = self.selected_entity
{
if let Some(ref wireframe_pipeline) = self.wireframe_pipeline
{
for draw_call in draw_calls.iter()
{
if draw_call.entity != Some(selected)
{
continue;
}
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,
);
let sel_offset = uniform_slot * uniform_size;
uniform_slot += 1;
self.queue.write_buffer(
&self.uniform_buffer,
sel_offset,
bytemuck::cast_slice(&[uniforms]),
);
{
let mut sel_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Selection 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,
multiview_mask: None,
});
sel_pass.set_pipeline(wireframe_pipeline);
sel_pass.set_bind_group(0, &self.bind_group, &[sel_offset as u32]);
sel_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..));
if let Some(ref instance_buffer) = draw_call.instance_buffer
{
sel_pass.set_vertex_buffer(1, instance_buffer.slice(..));
}
sel_pass.set_index_buffer(
draw_call.index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
sel_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
@@ -1074,3 +1200,12 @@ pub fn update_spotlights(spotlights: Vec<Spotlight>)
renderer.spotlights = spotlights;
});
}
pub fn set_selected_entity(entity: Option<EntityHandle>)
{
GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut();
let renderer = renderer.as_mut().expect("Renderer not set");
renderer.selected_entity = entity;
});
}

View File

@@ -1,6 +1,8 @@
use bytemuck::{Pod, Zeroable};
use glam::Mat4;
use crate::entity::EntityHandle;
pub const MAX_SPOTLIGHTS: usize = 4;
#[repr(C)]
@@ -156,4 +158,5 @@ pub struct DrawCall
pub enable_dissolve: bool,
pub enable_snow_light: bool,
pub displacement_bind_group: Option<wgpu::BindGroup>,
pub entity: Option<EntityHandle>,
}

View File

@@ -60,6 +60,7 @@ pub fn render_system(world: &World) -> Vec<DrawCall>
enable_dissolve: mesh_component.enable_dissolve,
enable_snow_light: mesh_component.enable_snow_light,
displacement_bind_group: None,
entity: Some(entity),
})
})
.collect()