From 6a3dd4a0ebfe6c0828bcd7a18962550cd19a1a7a Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 29 May 2026 09:40:18 +1000 Subject: [PATCH] =?UTF-8?q?wgpu=20cull:=20contribution-cull=20defaults=202?= =?UTF-8?q?/10=20=E2=86=92=203/15,=20env-var=20overrides?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wgpu computes projected_px from view-Z distance (forward·(centre-eye)), which is the perspective-divide-correct denominator: an instance's on-screen radius really is world_radius * focal / z_view. GL computes the same value but with euclidean distance (sqrt(dx²+dy²+dz²)) — for off-axis instances euclidean > z_view, so GL underestimates screen size and culls more aggressively at the same numeric threshold. Concretely on the federation scene at the test camera, wgpu was drawing ~3× the instances GL drew despite identical 2/10 thresholds: wgpu obj 11067, hiz_rej 16532 vs GL obj 2310, hiz_rej 6823. Same fps (vsync-pinned), but ~30% more cull work for raster output that the user already wasn't seeing because GL had been quietly dropping it. Keeping wgpu's view-Z formula (more physically correct) and bumping the thresholds to 3.0 / 15.0 to match GL's effective drop rate. On the federation scene this lands obj/tri counts within ~10% of GL across the orbit, and shaves cull from 3.44 ms to 2.61 ms avg. basic.ifc parity is unchanged (its 3 instances clear 3 px easily). WGPU_MIN_PX / WGPU_MIN_PX_MOTION env vars added so the thresholds can be swept without rebuilding — needed while we visually confirm the new defaults across more scenes / cameras. Co-Authored-By: Claude Opus 4.7 --- src/ifcviewer-wgpu/WgpuViewportWindow.cpp | 20 ++++++++++++++++++++ src/ifcviewer-wgpu/WgpuViewportWindow.h | 15 ++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/ifcviewer-wgpu/WgpuViewportWindow.cpp b/src/ifcviewer-wgpu/WgpuViewportWindow.cpp index 70b66d3636..9a0e72f9ef 100644 --- a/src/ifcviewer-wgpu/WgpuViewportWindow.cpp +++ b/src/ifcviewer-wgpu/WgpuViewportWindow.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -1302,6 +1303,25 @@ bool WgpuViewportWindow::initWgpu() { wgpuSetLogCallback(onWgpuLog, nullptr); wgpuSetLogLevel(WGPULogLevel_Warn); + // Env-var overrides for contribution-cull thresholds. wgpu uses + // view-Z (perspective-divide-correct) for projected_px, whereas the + // GL backend uses euclidean distance — so for off-axis instances + // wgpu computes a larger projected_px and is less aggressive at the + // same numeric threshold. These knobs exist to let us sweep matching + // values during the perf-parity push without rebuilding. + if (const char* s = std::getenv("WGPU_MIN_PX")) { + const float v = float(std::atof(s)); + if (v >= 0.0f) min_pixel_radius_ = v; + qInfo().noquote().nospace() + << "[wgpu cull] WGPU_MIN_PX=" << min_pixel_radius_; + } + if (const char* s = std::getenv("WGPU_MIN_PX_MOTION")) { + const float v = float(std::atof(s)); + if (v >= 0.0f) motion_min_pixel_radius_ = v; + qInfo().noquote().nospace() + << "[wgpu cull] WGPU_MIN_PX_MOTION=" << motion_min_pixel_radius_; + } + instance_ = wgpuCreateInstance(nullptr); if (!instance_) { qWarning() << "wgpuCreateInstance returned null"; diff --git a/src/ifcviewer-wgpu/WgpuViewportWindow.h b/src/ifcviewer-wgpu/WgpuViewportWindow.h index 85fb7f0b8b..c6d6fad5f3 100644 --- a/src/ifcviewer-wgpu/WgpuViewportWindow.h +++ b/src/ifcviewer-wgpu/WgpuViewportWindow.h @@ -387,9 +387,18 @@ private: // Contribution-cull thresholds. Still-frame uses min_pixel_radius_; // when the camera changed since last frame, the bigger motion threshold // kicks in to drop more sub-pixel detail (and slash per-frame cull cost). - // Matches AppSettings::minPixelRadius / motionMinPixelRadius in GL. - float min_pixel_radius_ = 2.0f; - float motion_min_pixel_radius_ = 10.0f; + // + // GL ships 2.0 / 10.0, but uses euclidean distance for projected_px + // (sqrt(dx² + dy² + dz²)) while wgpu uses view-Z distance (the + // perspective-divide-correct denominator). For off-axis instances + // view_z < euclidean, so wgpu's projected_px is larger than GL's at + // the same numeric threshold — i.e. wgpu is structurally less + // aggressive. Bumping to 3.0 / 15.0 compensates so the effective drop + // rate matches GL's; on the federation scene this lands obj/tri + // counts within ~10% of GL's across an orbit (vs ~3× without the + // bump). Override at runtime via WGPU_MIN_PX / WGPU_MIN_PX_MOTION. + float min_pixel_radius_ = 3.0f; + float motion_min_pixel_radius_ = 15.0f; public: // Master switch for HiZ occlusion. Set false to skip the depth resolve