HD: bind groups cached until a resize or a new source, shadow passes draw only the entities that cast shadows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-29 16:19:53 +03:00
committed by airdef
parent 3cfa79c011
commit 72d9e70187
2 changed files with 188 additions and 98 deletions
+34 -5
View File
@@ -459,6 +459,8 @@ pub struct Renderer {
entity_buffer: wgpu::Buffer,
entity_capacity: u64,
entity_batch: EntityBatch,
/// AirDef fork: the runs of entity quads (first, end) that cast shadows this frame.
shadow_casters: Vec<(u32, u32)>,
group: wgpu::BindGroup,
globals: wgpu::Buffer,
lightmap: wgpu::Texture,
@@ -2744,6 +2746,7 @@ impl Renderer {
entity_buffer,
entity_capacity,
entity_batch: EntityBatch::default(),
shadow_casters: Vec::new(),
group,
globals,
lightmap,
@@ -4980,6 +4983,30 @@ impl Renderer {
}
// The sun's (or moon's) shadow cascades: the world's globals swapped for the light's.
if shadow_strength > 0.0 {
// The quads the shadow shader would not discard (overlays 4, 9, 10 and 40:
// glowing mod geometry), as runs, so those are not vertex-processed twice.
self.shadow_casters.clear();
let mut start = None;
let quads = self.entity_batch.combined.as_chunks::<4>().0;
for (i, quad) in quads.iter().take(entity_quads as usize).enumerate() {
let caster = !matches!(quad[0].layer, 4 | 9 | 10 | 40);
match (caster, start) {
(true, None) => start = Some(i as u32),
(false, Some(first)) => {
self.shadow_casters.push((first, i as u32));
start = None;
}
_ => {}
}
}
if let Some(first) = start {
self.shadow_casters.push((first, entity_quads));
}
// Many tiny runs cost more in draw calls than they save: draw everything.
if self.shadow_casters.len() > 64 {
self.shadow_casters.clear();
self.shadow_casters.push((0, entity_quads));
}
self.queue
.write_buffer(&hd.main_globals, 0, bytemuck::bytes_of(&globals));
for (c, cascade) in hd_cascades.iter().enumerate() {
@@ -5021,11 +5048,13 @@ impl Renderer {
let instance = hd_shadow_first + k as u32;
pass.draw_indexed(0..s.solid * 6, s.start as i32, instance..instance + 1);
}
if entity_quads > 0 {
if !self.shadow_casters.is_empty() {
pass.set_pipeline(&self.shadow_entity);
pass.set_bind_group(0, &self.entity_group, &[]);
pass.set_vertex_buffer(0, self.entity_buffer.slice(..));
pass.draw_indexed(0..entity_quads * 6, 0, 0..1);
for &(first, end) in &self.shadow_casters {
pass.draw_indexed(first * 6..end * 6, 0, 0..1);
}
}
}
encoder.copy_buffer_to_buffer(&hd.main_globals, 0, &self.globals, 0, globals_size);
@@ -5586,7 +5615,7 @@ impl Renderer {
);
} else if let Some((color, depth)) = &scaled
&& hd_on
&& let Some(hd) = &self.hd
&& let Some(hd) = &mut self.hd
{
// AirDef fork: lit by the mods' lights into the HD target, the mod's post pass on
// that, then bloom and the tone curve onto the screen.
@@ -5607,9 +5636,9 @@ impl Renderer {
self.mod_params.1,
);
post.run(&self.device, &mut encoder, (&hd.lit, depth), &hd.fx);
hd.finish(&self.device, &mut encoder, &hd.fx, &view_target);
hd.finish(&self.device, &mut encoder, hd::HdSource::Fx, &view_target);
}
_ => hd.finish(&self.device, &mut encoder, &hd.lit, &view_target),
_ => hd.finish(&self.device, &mut encoder, hd::HdSource::Lit, &view_target),
}
} else if let Some((color, depth)) = &scaled
&& mod_post
+154 -93
View File
@@ -359,6 +359,21 @@ fn target(
Target { view }
}
/// The outside views and sampler the lighting group reads.
type LightKey = (
wgpu::TextureView,
wgpu::TextureView,
wgpu::TextureView,
wgpu::Sampler,
);
/// What bloom and the tone curve read: the lit world, or the post pass's output.
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum HdSource {
Lit,
Fx,
}
pub struct Hd {
size: [u32; 2],
pub albedo: wgpu::TextureView,
@@ -377,6 +392,15 @@ pub struct Hd {
pub lit: wgpu::TextureView,
pub fx: wgpu::TextureView,
bloom: Vec<Target>,
/// Bind groups, rebuilt only when their views change: `resize` drops all of them, the
/// lighting and the source-reading ones are keyed by the views they read.
light_group: Option<(LightKey, wgpu::BindGroup)>,
prefilter_group: Option<(wgpu::TextureView, wgpu::BindGroup)>,
tone_group: Option<(wgpu::TextureView, wgpu::BindGroup)>,
/// Reading bloom level k - 1 (for the down pass into level k, index k - 1) and level k (for
/// the up pass onto level k - 1, index k - 1).
down_groups: Vec<wgpu::BindGroup>,
up_groups: Vec<wgpu::BindGroup>,
frame: wgpu::Buffer,
lights: wgpu::Buffer,
sampler: wgpu::Sampler,
@@ -656,6 +680,11 @@ impl Hd {
lit: target(device, "HD lit", [1, 1], HDR).view,
fx: target(device, "HD post", [1, 1], HDR).view,
bloom: Vec::new(),
light_group: None,
prefilter_group: None,
tone_group: None,
down_groups: Vec::new(),
up_groups: Vec::new(),
frame,
lights,
sampler,
@@ -690,6 +719,16 @@ impl Hd {
target(device, "HD bloom", s, HDR)
})
.collect();
// Every group names a view that was just replaced.
self.light_group = None;
self.prefilter_group = None;
self.tone_group = None;
self.down_groups = (1..self.bloom.len())
.map(|k| self.bloom_group(device, &self.bloom[k - 1].view))
.collect();
self.up_groups = (1..self.bloom.len())
.map(|k| self.bloom_group(device, &self.bloom[k].view))
.collect();
}
/// This frame's camera, sun, fog, lights and settings.
@@ -829,73 +868,85 @@ impl Hd {
/// Lights the world (its colour and depth) into `lit`.
pub fn light(
&self,
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
world: (&wgpu::TextureView, &wgpu::TextureView),
lightmap: (&wgpu::TextureView, &wgpu::Sampler),
) {
let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("HD lighting"),
layout: &self.light_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.frame.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(world.0),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(world.1),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(&self.albedo),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::TextureView(&self.normal),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(&self.depth),
},
wgpu::BindGroupEntry {
binding: 6,
resource: self.lights.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::TextureView(&self.levels),
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::TextureView(&self.shadow_array),
},
wgpu::BindGroupEntry {
binding: 9,
resource: wgpu::BindingResource::Sampler(&self.shadow_sampler),
},
wgpu::BindGroupEntry {
binding: 10,
resource: wgpu::BindingResource::TextureView(lightmap.0),
},
wgpu::BindGroupEntry {
binding: 11,
resource: wgpu::BindingResource::Sampler(lightmap.1),
},
],
});
let key = (
world.0.clone(),
world.1.clone(),
lightmap.0.clone(),
lightmap.1.clone(),
);
if self.light_group.as_ref().is_none_or(|(k, _)| *k != key) {
let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("HD lighting"),
layout: &self.light_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.frame.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(world.0),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(world.1),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(&self.albedo),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::TextureView(&self.normal),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(&self.depth),
},
wgpu::BindGroupEntry {
binding: 6,
resource: self.lights.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::TextureView(&self.levels),
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::TextureView(&self.shadow_array),
},
wgpu::BindGroupEntry {
binding: 9,
resource: wgpu::BindingResource::Sampler(&self.shadow_sampler),
},
wgpu::BindGroupEntry {
binding: 10,
resource: wgpu::BindingResource::TextureView(lightmap.0),
},
wgpu::BindGroupEntry {
binding: 11,
resource: wgpu::BindingResource::Sampler(lightmap.1),
},
],
});
self.light_group = Some((key, group));
}
let Some((_, group)) = &self.light_group else {
return;
};
Self::draw(
encoder,
"HD lighting",
&self.lit,
false,
&self.light_pipeline,
&group,
group,
);
}
@@ -922,12 +973,53 @@ impl Hd {
/// Bloom from `source` (the lit world or the post pass's output), then the tone curve onto `screen`.
pub fn finish(
&self,
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
source: &wgpu::TextureView,
source: HdSource,
screen: &wgpu::TextureView,
) {
let source = match source {
HdSource::Lit => self.lit.clone(),
HdSource::Fx => self.fx.clone(),
};
if self
.prefilter_group
.as_ref()
.is_none_or(|(v, _)| *v != source)
{
let group = self.bloom_group(device, &source);
self.prefilter_group = Some((source.clone(), group));
}
if self.tone_group.as_ref().is_none_or(|(v, _)| *v != source) {
let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("HD tone mapping"),
layout: &self.tone_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.frame.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&source),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&self.bloom[0].view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
],
});
self.tone_group = Some((source, group));
}
let (Some((_, prefilter)), Some((_, tone))) = (&self.prefilter_group, &self.tone_group)
else {
return;
};
// Down: the prefilter into the first level, then each level from the one above.
Self::draw(
encoder,
@@ -935,61 +1027,30 @@ impl Hd {
&self.bloom[0].view,
false,
&self.prefilter,
&self.bloom_group(device, source),
prefilter,
);
for k in 1..self.bloom.len() {
let g = self.bloom_group(device, &self.bloom[k - 1].view);
Self::draw(
encoder,
"HD bloom down",
&self.bloom[k].view,
false,
&self.down,
&g,
&self.down_groups[k - 1],
);
}
// Up: each level added onto the bigger one.
for k in (1..self.bloom.len()).rev() {
let g = self.bloom_group(device, &self.bloom[k].view);
Self::draw(
encoder,
"HD bloom up",
&self.bloom[k - 1].view,
true,
&self.up,
&g,
&self.up_groups[k - 1],
);
}
let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("HD tone mapping"),
layout: &self.tone_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.frame.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(source),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&self.bloom[0].view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
],
});
Self::draw(
encoder,
"HD tone mapping",
screen,
false,
&self.tone,
&group,
);
Self::draw(encoder, "HD tone mapping", screen, false, &self.tone, tone);
}
}