mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-30 08:33:10 +00:00
wgpu backend: HiZ occlusion culling
Stage 7 of the wgpu port. Per-frame after the main render pass:
1. encodeHizResolve runs a depth-only render pass that samples the
MSAA depth texture (sample 0) and max-reduces it into a small
single-sample Depth32Float target (256 × ~h-aspect). Implemented
as a fullscreen-triangle WGSL pipeline; one nested loop per
output texel over its source rect. WebGPU has no built-in depth
resolve, so this combined resolve+downsample fragment shader is
the way.
2. copyTextureToBuffer writes the small resolved depth into a
CPU-mappable staging buffer (≈ 160 KB at 256×160).
3. readbackAndBuildHizPyramid maps the staging buffer (sync via
wgpuInstanceProcessEvents — small enough that the stall is
well under a millisecond), strips per-row padding, and CPU
max-reduces a full mip pyramid (level 0 → 1×1). Stores the VP
used so the next frame can project AABBs into the same space.
Next frame, cullModelCpu calls aabbOccludedByHiz after the frustum
test: projects all 8 AABB corners through hiz_vp_, computes the
screen-space AABB and the nearest projected z, picks the mip level
where the AABB covers ≤ 2 texels per axis, samples that level's 2×2
window, and culls iff min_z > max_pyramid_depth in [0,1] z.
Plumbing changes:
- depth_texture_ gains TextureBinding usage so the resolve shader
can read it.
- hiz_enabled_ master switch defaults true; mirrors IFC_NO_HIZ in
the GL backend. Disabling skips encode + readback entirely.
- Bench output's "hiz_rej N" field now reflects actual rejections.
Verified: basic.ifc (3 instances, no occluders) renders pixel-
identical to pre-HiZ — proves the test rejects nothing it shouldn't.
Real rejection counts need a dense scene; this should drop visible-
objects count noticeably on real BIM benchmarks where back-of-room
walls hide each other.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <QWindow>
|
||||
#include <QColor>
|
||||
#include <QMatrix4x4>
|
||||
#include <QPoint>
|
||||
#include <QString>
|
||||
|
||||
@@ -118,6 +119,23 @@ private:
|
||||
void releaseDepthTexture();
|
||||
void ensureMsaaColorTexture(int w, int h);
|
||||
void releaseMsaaColorTexture();
|
||||
|
||||
bool buildHizPipeline();
|
||||
void ensureHizTextures(int viewport_w, int viewport_h);
|
||||
void releaseHizResources();
|
||||
// Resolves the just-rendered MSAA depth into the small single-sample
|
||||
// HiZ texture and copies it to the staging buffer. Encoded onto `enc`
|
||||
// so it ships in the same command buffer as the main draw.
|
||||
void encodeHizResolve(WGPUCommandEncoder enc);
|
||||
// Maps the staging buffer (waits via processEvents), max-reduces a CPU
|
||||
// mip pyramid, stores the VP used. Run after submitting the encoder so
|
||||
// the GPU has begun the copy. Updates hiz_valid_ to true on success.
|
||||
void readbackAndBuildHizPyramid(const QMatrix4x4& vp_used);
|
||||
// Project AABB through hiz_vp_ and test against the pyramid. False
|
||||
// (keep) if HiZ isn't valid yet, AABB straddles the near plane, or
|
||||
// any projection is unreliable. True (cull) when AABB is provably
|
||||
// behind every relevant pyramid cell.
|
||||
bool aabbOccludedByHiz(const float mn[3], const float mx[3]) const;
|
||||
void updateFrameUniforms();
|
||||
void flushPendingSidecarQueue();
|
||||
bool computeSceneAabb(float mn[3], float mx[3]) const;
|
||||
@@ -143,7 +161,8 @@ private:
|
||||
const float eye[3], const float forward[3],
|
||||
float focal_px,
|
||||
float min_radius_px,
|
||||
float lod1_threshold_px);
|
||||
float lod1_threshold_px,
|
||||
bool hiz_enabled);
|
||||
|
||||
bool wgpu_initialized_ = false;
|
||||
bool surface_configured_ = false;
|
||||
@@ -182,6 +201,40 @@ private:
|
||||
int msaa_h_ = 0;
|
||||
static constexpr uint32_t SAMPLE_COUNT = 4;
|
||||
|
||||
// HiZ occlusion culling. After each frame's main render pass we
|
||||
// downsample MSAA depth into a small single-sample Depth32Float texture
|
||||
// (hiz_resolve_texture_), copy it into a CPU-mappable staging buffer,
|
||||
// wait for the map via processEvents, and max-reduce a mip pyramid on
|
||||
// CPU. The cull pass in the *next* frame projects each instance's AABB
|
||||
// through hiz_vp_ (the VP used to fill the pyramid) and rejects when
|
||||
// the AABB's nearest projected z is behind the pyramid's coverage.
|
||||
//
|
||||
// GL's HiZ default is 256 wide; we match. Height tracks viewport aspect.
|
||||
static constexpr uint32_t HIZ_BASE_W = 256;
|
||||
|
||||
WGPUShaderModule hiz_shader_module_ = nullptr;
|
||||
WGPUBindGroupLayout hiz_bgl_ = nullptr;
|
||||
WGPUPipelineLayout hiz_pipeline_layout_ = nullptr;
|
||||
WGPURenderPipeline hiz_pipeline_ = nullptr;
|
||||
WGPUBuffer hiz_uniform_buffer_ = nullptr;
|
||||
WGPUBindGroup hiz_bind_group_ = nullptr;
|
||||
|
||||
WGPUTexture hiz_resolve_texture_ = nullptr;
|
||||
WGPUTextureView hiz_resolve_view_ = nullptr;
|
||||
WGPUBuffer hiz_staging_buffer_ = nullptr;
|
||||
uint32_t hiz_resolve_w_ = 0;
|
||||
uint32_t hiz_resolve_h_ = 0;
|
||||
uint32_t hiz_padded_bpr_ = 0; // bytes per row in the staging buffer
|
||||
|
||||
// CPU mip pyramid (max-reduce). hiz_pyramid_[hiz_mip_offset_[L] + y*W + x].
|
||||
std::vector<float> hiz_pyramid_;
|
||||
std::vector<uint32_t> hiz_mip_offset_;
|
||||
std::vector<uint32_t> hiz_mip_w_;
|
||||
std::vector<uint32_t> hiz_mip_h_;
|
||||
QMatrix4x4 hiz_vp_;
|
||||
bool hiz_valid_ = false;
|
||||
uint32_t hiz_reject_count_ = 0; // per-frame stat
|
||||
|
||||
QColor background_color_ = QColor("#202329");
|
||||
|
||||
// Camera (orbit, right-handed Y-up world → wait, BIM is +Z up).
|
||||
@@ -200,6 +253,10 @@ private:
|
||||
// with mouse-driven motion-state tracking later).
|
||||
float min_pixel_radius_ = 2.0f;
|
||||
|
||||
// Master switch for HiZ occlusion. Set false to skip the depth resolve
|
||||
// + readback + cull test entirely (matches IFC_NO_HIZ in the GL backend).
|
||||
bool hiz_enabled_ = true;
|
||||
|
||||
// Switch to LOD1 when an instance's projected bounding-sphere radius
|
||||
// drops below this many pixels. 0 disables (always LOD0). Defaults
|
||||
// mirror AppSettings::lod1PixelThreshold() in the GL backend.
|
||||
|
||||
Reference in New Issue
Block a user