ccc3e20902
Smoke: one rule by engine, no heat on trails, no trail on P1-SUN. Searchlights: two lamps a target, off with an empty sky. Halos fade before their reach and their sum is limited. Launch sites load chunks through the server thread, go off-map as a last resort, and reach 4000 blocks. Air defence: ranges x1.5, shots grow with urgency, claims released, /airdef lastchance, long shots on a big radar's tracks. Build: rust-toolchain.toml, airdef.py check, generator manifest and --check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
440 lines
17 KiB
Diff
440 lines
17 KiB
Diff
From 7a5a38f68923aec62f10bfa0df50b436474ed612 Mon Sep 17 00:00:00 2001
|
|
From: chexit <chexit@duck.com>
|
|
Date: Thu, 29 Oct 2026 16:19:53 +0300
|
|
Subject: [PATCH 13/40] 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>
|
|
---
|
|
crates/horizon-client/src/gfx.rs | 39 ++++-
|
|
crates/horizon-client/src/gfx/hd.rs | 247 +++++++++++++++++-----------
|
|
2 files changed, 188 insertions(+), 98 deletions(-)
|
|
|
|
diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs
|
|
index e351763fd..f6bbe038c 100644
|
|
--- a/crates/horizon-client/src/gfx.rs
|
|
+++ b/crates/horizon-client/src/gfx.rs
|
|
@@ -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
|
|
diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs
|
|
index c36c1795d..fe2634f2c 100644
|
|
--- a/crates/horizon-client/src/gfx/hd.rs
|
|
+++ b/crates/horizon-client/src/gfx/hd.rs
|
|
@@ -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);
|
|
}
|
|
}
|
|
|
|
--
|
|
2.54.0 (Apple Git-157)
|
|
|