diff --git a/horizon-patches/0001-Mod-API-AirDef-fork-world-models-client-particles-so.patch b/horizon-patches/0001-Mod-API-AirDef-fork-world-models-client-particles-so.patch index 607d483..1707bcb 100644 --- a/horizon-patches/0001-Mod-API-AirDef-fork-world-models-client-particles-so.patch +++ b/horizon-patches/0001-Mod-API-AirDef-fork-world-models-client-particles-so.patch @@ -1,7 +1,7 @@ From 3de7a6875140a107e28563cc44489b55f5794517 Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 12:37:45 +0300 -Subject: [PATCH 01/12] Mod API (AirDef fork): world models, client particles, +Subject: [PATCH 01/13] Mod API (AirDef fork): world models, client particles, sounds, ground heights render.set-models draws block models with an interpolated pose per id; diff --git a/horizon-patches/0002-cargo-horizon-mod-report-particles-and-sounds.patch b/horizon-patches/0002-cargo-horizon-mod-report-particles-and-sounds.patch index 86c16fa..fcd8946 100644 --- a/horizon-patches/0002-cargo-horizon-mod-report-particles-and-sounds.patch +++ b/horizon-patches/0002-cargo-horizon-mod-report-particles-and-sounds.patch @@ -1,7 +1,7 @@ From a6d64e6415bf618539af464e521091e72b72d72f Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 12:39:45 +0300 -Subject: [PATCH 02/12] cargo-horizon-mod: report particles and sounds +Subject: [PATCH 02/13] cargo-horizon-mod: report particles and sounds Co-Authored-By: Claude Opus 5.5 --- diff --git a/horizon-patches/0003-Mod-API-AirDef-fork-full-screen-post-pass-for-mods.patch b/horizon-patches/0003-Mod-API-AirDef-fork-full-screen-post-pass-for-mods.patch index 1f4127e..f733795 100644 --- a/horizon-patches/0003-Mod-API-AirDef-fork-full-screen-post-pass-for-mods.patch +++ b/horizon-patches/0003-Mod-API-AirDef-fork-full-screen-post-pass-for-mods.patch @@ -1,7 +1,7 @@ From ee79b3562feebe51959a01f1585a9e85bd53ccb5 Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 13:33:10 +0300 -Subject: [PATCH 03/12] Mod API (AirDef fork): full-screen post pass for mods +Subject: [PATCH 03/13] Mod API (AirDef fork): full-screen post pass for mods render.set-post compiles a mod's WGSL after a prelude (camera matrices, time, 256 vec4 of params, the world's colour and depth) and runs it after diff --git a/horizon-patches/0004-Mod-models-draw-far-models-as-scaled-impostors-past-.patch b/horizon-patches/0004-Mod-models-draw-far-models-as-scaled-impostors-past-.patch index 215b215..d47e4b9 100644 --- a/horizon-patches/0004-Mod-models-draw-far-models-as-scaled-impostors-past-.patch +++ b/horizon-patches/0004-Mod-models-draw-far-models-as-scaled-impostors-past-.patch @@ -1,7 +1,7 @@ From 2b137dbbedd540daeebc7f5e387847aeb56a1355 Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 22:09:30 +0300 -Subject: [PATCH 04/12] Mod models: draw far models as scaled impostors past +Subject: [PATCH 04/13] Mod models: draw far models as scaled impostors past the far plane, cull at 4096 --- diff --git a/horizon-patches/0005-Mod-cameras-render.set-views-draws-the-world-from-ot.patch b/horizon-patches/0005-Mod-cameras-render.set-views-draws-the-world-from-ot.patch index 9d2faa4..0efca7d 100644 --- a/horizon-patches/0005-Mod-cameras-render.set-views-draws-the-world-from-ot.patch +++ b/horizon-patches/0005-Mod-cameras-render.set-views-draws-the-world-from-ot.patch @@ -1,7 +1,7 @@ From c8ebca6e0af68630e65211844b34d25c5b985c03 Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 22:31:00 +0300 -Subject: [PATCH 05/12] Mod cameras: render.set-views draws the world from +Subject: [PATCH 05/13] Mod cameras: render.set-views draws the world from other points into pictures that HUD view shapes show (API level 9) Co-Authored-By: Claude Opus 5.5 diff --git a/horizon-patches/0006-Mods-more-fuel-per-tick-and-per-event-AirDef-plans-r.patch b/horizon-patches/0006-Mods-more-fuel-per-tick-and-per-event-AirDef-plans-r.patch index 10452ad..5bba268 100644 --- a/horizon-patches/0006-Mods-more-fuel-per-tick-and-per-event-AirDef-plans-r.patch +++ b/horizon-patches/0006-Mods-more-fuel-per-tick-and-per-event-AirDef-plans-r.patch @@ -1,7 +1,7 @@ From 8d2261fc4f8072c32e454139cfea7a8c0cd9d664 Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 23:08:21 +0300 -Subject: [PATCH 06/12] Mods: more fuel per tick and per event (AirDef plans +Subject: [PATCH 06/13] Mods: more fuel per tick and per event (AirDef plans raids) Co-Authored-By: Claude Opus 5.5 diff --git a/horizon-patches/0007-Mod-cameras-up-to-1920x1080-full-screen-replays.patch b/horizon-patches/0007-Mod-cameras-up-to-1920x1080-full-screen-replays.patch index 97e33b5..ef3796e 100644 --- a/horizon-patches/0007-Mod-cameras-up-to-1920x1080-full-screen-replays.patch +++ b/horizon-patches/0007-Mod-cameras-up-to-1920x1080-full-screen-replays.patch @@ -1,7 +1,7 @@ From 6c548de34def8e4a624d734243418090798bd16a Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 23:25:29 +0300 -Subject: [PATCH 07/12] Mod cameras up to 1920x1080 (full-screen replays) +Subject: [PATCH 07/13] Mod cameras up to 1920x1080 (full-screen replays) Co-Authored-By: Claude Opus 5.5 --- diff --git a/horizon-patches/0008-Mod-cameras-distant-terrain-tiles-fog-measured-from-.patch b/horizon-patches/0008-Mod-cameras-distant-terrain-tiles-fog-measured-from-.patch index be369c2..330b8da 100644 --- a/horizon-patches/0008-Mod-cameras-distant-terrain-tiles-fog-measured-from-.patch +++ b/horizon-patches/0008-Mod-cameras-distant-terrain-tiles-fog-measured-from-.patch @@ -1,7 +1,7 @@ From 9b9e93269d67401cc620567a884d5ce97f68d76e Mon Sep 17 00:00:00 2001 From: chexit Date: Thu, 29 Oct 2026 11:15:11 +0300 -Subject: [PATCH 08/12] Mod cameras: distant terrain tiles, fog measured from +Subject: [PATCH 08/13] Mod cameras: distant terrain tiles, fog measured from the camera, no impostors while one is shown Co-Authored-By: Claude Opus 5.5 diff --git a/horizon-patches/0009-Mods-game.world-id-so-mods-keep-state-per-world-API-.patch b/horizon-patches/0009-Mods-game.world-id-so-mods-keep-state-per-world-API-.patch index f8cceba..913a1f1 100644 --- a/horizon-patches/0009-Mods-game.world-id-so-mods-keep-state-per-world-API-.patch +++ b/horizon-patches/0009-Mods-game.world-id-so-mods-keep-state-per-world-API-.patch @@ -1,7 +1,7 @@ From 065d9fa02966238a5bd9ff460392a1a809db0b11 Mon Sep 17 00:00:00 2001 From: chexit Date: Thu, 29 Oct 2026 11:25:45 +0300 -Subject: [PATCH 09/12] Mods: game.world-id, so mods keep state per world (API +Subject: [PATCH 09/13] Mods: game.world-id, so mods keep state per world (API level 9) Co-Authored-By: Claude Opus 5.5 diff --git a/horizon-patches/0010-cargo-horizon-mod-trial-runs-behind-feature-full-so-.patch b/horizon-patches/0010-cargo-horizon-mod-trial-runs-behind-feature-full-so-.patch index eaf7ae0..8ae20b7 100644 --- a/horizon-patches/0010-cargo-horizon-mod-trial-runs-behind-feature-full-so-.patch +++ b/horizon-patches/0010-cargo-horizon-mod-trial-runs-behind-feature-full-so-.patch @@ -1,7 +1,7 @@ From 1bf28135fd9a31f837323dc3a8d81ed73343d816 Mon Sep 17 00:00:00 2001 From: chexit Date: Thu, 29 Oct 2026 12:01:08 +0300 -Subject: [PATCH 10/12] cargo-horizon-mod: trial runs behind feature full, so a +Subject: [PATCH 10/13] cargo-horizon-mod: trial runs behind feature full, so a packing-only tool skips wasmtime Co-Authored-By: Claude Opus 5.5 diff --git a/horizon-patches/0011-HD-lighting-G-buffer-pass-per-pixel-mod-point-lights.patch b/horizon-patches/0011-HD-lighting-G-buffer-pass-per-pixel-mod-point-lights.patch index 1deb29a..7d7f366 100644 --- a/horizon-patches/0011-HD-lighting-G-buffer-pass-per-pixel-mod-point-lights.patch +++ b/horizon-patches/0011-HD-lighting-G-buffer-pass-per-pixel-mod-point-lights.patch @@ -1,7 +1,7 @@ From 0a56f5616df4699588cbcc529d4720661baa810f Mon Sep 17 00:00:00 2001 From: chexit Date: Thu, 29 Oct 2026 13:18:43 +0300 -Subject: [PATCH 11/12] HD lighting: G-buffer pass, per-pixel mod point lights +Subject: [PATCH 11/13] HD lighting: G-buffer pass, per-pixel mod point lights in HDR, bloom and tone curve; render.set-lights and render.set-lighting (API level 10) diff --git a/horizon-patches/0012-HD-lighting-stage-2-sun-and-moon-shadow-cascades-the.patch b/horizon-patches/0012-HD-lighting-stage-2-sun-and-moon-shadow-cascades-the.patch index a6fe62e..bdd693b 100644 --- a/horizon-patches/0012-HD-lighting-stage-2-sun-and-moon-shadow-cascades-the.patch +++ b/horizon-patches/0012-HD-lighting-stage-2-sun-and-moon-shadow-cascades-the.patch @@ -1,7 +1,7 @@ From 9ebf0031fe9ba6b7f02afb26e4fd3a1ba4ddb0d0 Mon Sep 17 00:00:00 2001 From: chexit Date: Thu, 29 Oct 2026 13:59:43 +0300 -Subject: [PATCH 12/12] HD lighting stage 2: sun and moon shadow cascades, the +Subject: [PATCH 12/13] HD lighting stage 2: sun and moon shadow cascades, the world relit (sun by face angle and shadow, sky and block light through the lightmap), light shafts and halos, fog over it again, a light colour grade diff --git a/horizon-patches/0013-HD-bind-groups-cached-until-a-resize-or-a-new-source.patch b/horizon-patches/0013-HD-bind-groups-cached-until-a-resize-or-a-new-source.patch new file mode 100644 index 0000000..6eee260 --- /dev/null +++ b/horizon-patches/0013-HD-bind-groups-cached-until-a-resize-or-a-new-source.patch @@ -0,0 +1,439 @@ +From 7a5a38f68923aec62f10bfa0df50b436474ed612 Mon Sep 17 00:00:00 2001 +From: chexit +Date: Thu, 29 Oct 2026 16:19:53 +0300 +Subject: [PATCH 13/13] 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 +--- + 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, ++ /// 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, ++ up_groups: Vec, + 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) +