perf(pipeline): per-frame hatch+wipeout frustum cull at draw time (Phase 2.3)
The Phase 2.1 step-4 attempt at culling hatches inside `synced_hatch_models` was wrong: GPU hatch/wipeout buffers are uploaded on geometry_epoch only (see render.rs — they're "static buffers"), so any CPU-side cull at build time would freeze the visible set at the geometry epoch boundary and never re-evaluate on pan. Reverted that approach; the build-time function now returns the full visible set again and the cache key is back to geometry_epoch. The actual per-frame frustum cull moves into the existing draw-time skip-flag machinery: - `compute_hatch_lod` now also folds in `aabb_offscreen` (ORed with the existing Phase 3.3 sub-pixel skip), so out-of-view hatches are skipped at the draw call. - New `compute_wipeout_lod` + `wipeout_skip_flags` mirror the hatch path. The wipeout draw loop honors the flag. - `Primitive::prepare` calls the new compute step alongside the rest. Per-frame projection + skip is a few hundred microseconds on dense docs; far cheaper than re-uploading GPU buffers per pan tick. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
49fddea1ef
commit
b70c9f9262
3 changed files with 52 additions and 63 deletions
|
|
@ -65,6 +65,10 @@ pub struct Pipeline {
|
|||
hatch_skip_flags: Vec<bool>,
|
||||
/// Wipeout fills — rendered after wires in a separate pass.
|
||||
gpu_wipeouts: Vec<HatchGpu>,
|
||||
/// Per-wipeout draw-time skip flag (Phase 2.3 frustum cull). `true`
|
||||
/// when the wipeout's projected AABB sits entirely outside the
|
||||
/// viewport rect. Recomputed by `compute_wipeout_lod`.
|
||||
wipeout_skip_flags: Vec<bool>,
|
||||
/// Pixel scissor rects [x, y, w, h] for viewport-clipped wipeouts. Recomputed each frame.
|
||||
wipeout_pixel_scissors: Vec<Option<[u32; 4]>>,
|
||||
gpu_images: Vec<ImageGpu>,
|
||||
|
|
@ -622,6 +626,7 @@ impl Pipeline {
|
|||
hatch_pixel_scissors: vec![],
|
||||
hatch_skip_flags: vec![],
|
||||
gpu_wipeouts: vec![],
|
||||
wipeout_skip_flags: vec![],
|
||||
wipeout_pixel_scissors: vec![],
|
||||
gpu_images: vec![],
|
||||
image_pixel_scissors: vec![],
|
||||
|
|
@ -676,7 +681,11 @@ impl Pipeline {
|
|||
self.hatch_skip_flags = self
|
||||
.gpu_hatches
|
||||
.iter()
|
||||
.map(|h| aabb_below_pixel(h.world_aabb, view_proj, clip_w, clip_h, 2.0))
|
||||
.map(|h| {
|
||||
// Phase 3.3 sub-pixel LOD skip OR Phase 2.3 frustum skip.
|
||||
aabb_below_pixel(h.world_aabb, view_proj, clip_w, clip_h, 2.0)
|
||||
|| aabb_offscreen(h.world_aabb, view_proj, clip_w, clip_h)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
|
|
@ -689,6 +698,18 @@ impl Pipeline {
|
|||
.collect();
|
||||
}
|
||||
|
||||
/// Per-frame wipeout frustum-skip flag (Phase 2.3). Mirrors
|
||||
/// `compute_hatch_lod`'s frustum branch. No sub-pixel skip:
|
||||
/// wipeouts mask, so dropping a sub-pixel one wouldn't be wrong
|
||||
/// but also wouldn't pay off — they're usually few.
|
||||
pub fn compute_wipeout_lod(&mut self, view_proj: glam::Mat4, clip_w: u32, clip_h: u32) {
|
||||
self.wipeout_skip_flags = self
|
||||
.gpu_wipeouts
|
||||
.iter()
|
||||
.map(|h| aabb_offscreen(h.world_aabb, view_proj, clip_w, clip_h))
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// Recompute pixel scissor rects for viewport-clipped raster images.
|
||||
pub fn compute_image_scissors(&mut self, view_proj: glam::Mat4, clip_w: u32, clip_h: u32) {
|
||||
self.image_pixel_scissors = self
|
||||
|
|
@ -1098,6 +1119,9 @@ impl Pipeline {
|
|||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
let mut scissor_active = false;
|
||||
for (i, wipeout) in self.gpu_wipeouts.iter().enumerate() {
|
||||
if self.wipeout_skip_flags.get(i).copied().unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
match self.wipeout_pixel_scissors.get(i) {
|
||||
Some(Some([x, y, w, h])) => {
|
||||
pass.set_scissor_rect(*x, *y, *w, *h);
|
||||
|
|
|
|||
Loading…
Reference in a new issue