From 311b75a9557bd2d54cdcad59484802dd93a0acfb Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 24 Aug 2026 09:36:12 +1000 Subject: [PATCH] ifcviewer: carve the margin out of the cache on the first driver growth refusal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On web there is no device-memory query, so the budget sat at the wasm heap cap while the pool grew until Chrome's GPU process refused (observed at 1920 MB on a 66-model session). Nothing acted on that refusal: the cache kept the last byte, and the next attachment reallocation (orbit resize, 76 MB) had to fail first — a few frames of invalid-TextureView errors — before pressure feedback carved out room. The refusal IS the query-less platform's device report. render() now answers the first one by lowering the budget by the required-tier margin and shrinking the pool to it, so attachments and model buffers find headroom without ever failing. Desktop gets the same fallback for drivers GpuMemory cannot answer for. Reproduced under Playwright with a native process squeezing the GPU: Chrome refuses at 512 MB, the margin (256 MB) is released on the next frame, and the session continues with zero uncaptured WebGPU errors — previously the same squeeze produced invalid-view frames before recovery. Co-Authored-By: Claude Fable 5 --- src/ifcviewer/BufferPool.h | 7 +++++++ src/ifcviewer/ViewportCore.cpp | 18 ++++++++++++++++++ src/ifcviewer/ViewportCore.h | 3 +++ 3 files changed, 28 insertions(+) diff --git a/src/ifcviewer/BufferPool.h b/src/ifcviewer/BufferPool.h index 9f38308e72..5e9811f7a9 100644 --- a/src/ifcviewer/BufferPool.h +++ b/src/ifcviewer/BufferPool.h @@ -123,6 +123,13 @@ public: <= max_total_capacity_bytes_); } + // True once the driver (not the budget) has refused growth even at the + // floor size. On platforms with no memory query this is the only device + // report there is: the owner treats the first refusal as a pressure + // event and carves the required-tier margin out of the cache before a + // required allocation has to fail for it (see ViewportCore::render). + bool growth_was_refused() const { return growth_disabled_; } + // Whether a growth is in flight. On web that window is real time — a // provisional sub-buffer validates asynchronously a frame or two later — so // the streaming driver has to know that free space is still on its way and diff --git a/src/ifcviewer/ViewportCore.cpp b/src/ifcviewer/ViewportCore.cpp index 2acc65d501..f953f566db 100644 --- a/src/ifcviewer/ViewportCore.cpp +++ b/src/ifcviewer/ViewportCore.cpp @@ -7732,6 +7732,24 @@ void ViewportCore::render() { Stopwatch frame_timer; frame_timer.start(); + // The driver refusing pool growth is the query-less platform's device + // report (web has no memory query; the pool just grew until the GPU + // process said no). Answer it once, immediately: lower the budget so + // the required-tier margin comes back out of the cache NOW, instead of + // the next attachment resize having to fail — and paint broken frames — + // before pressure feedback carves the same room. + if (pool_.growth_was_refused() && !pool_growth_refusal_handled_) { + pool_growth_refusal_handled_ = true; + const double mb = 1.0 / (1024.0 * 1024.0); + Log::info() << "[wgpu] driver refused geometry-cache growth at " + << double(pool_.total_capacity_bytes()) * mb + << " MB -- reserving the required-tier margin out of the cache"; + if (budget_.onPressure(pool_.total_capacity_bytes(), + GpuBudget::kFixedMarginBytes, 0)) { + applyBudgetToPool(); + } + } + // Drain any HiZ async readbacks completed since last frame. if (hiz_enabled_) drainHizReadbacks(); diff --git a/src/ifcviewer/ViewportCore.h b/src/ifcviewer/ViewportCore.h index 3890cce69c..3f1365f8e8 100644 --- a/src/ifcviewer/ViewportCore.h +++ b/src/ifcviewer/ViewportCore.h @@ -1155,6 +1155,9 @@ private: void releaseRenderAttachments(); GpuBudget budget_; + // Latch: the pool's first driver-refused growth has been answered by + // carving the margin out of the cache (see render()). + bool pool_growth_refusal_handled_ = false; // Adapter ids, read once at init, for matching the driver's memory // report to the card wgpu is actually using. std::uint32_t adapter_vendor_id_ = 0;