mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
wgpu pool: halve-on-failure in addSubBuffer extracts +35% VRAM
Many Vulkan drivers cap a single VkDeviceMemory allocation at exactly
maxStorageBufferBindingSize (NVIDIA: 2 GB on consumer GeForce) or
refuse big contiguous allocations once heap is fragmented. The old
addSubBuffer gave up at the first refusal, latching growth_disabled_
— so on a 4 GB GeForce we extracted 2 GB and called it done.
The wgpu-mem-probe tool (feab05650) showed the driver actually grants
~3 GB total across multiple sub-buffers — invariant under allocation
pattern (2+1+small, 3×1 GB, 6×512 MB, 12×256 MB all land at 3 GB).
The cap is the hardware/desktop, not the request size.
addSubBuffer now starts at last_growth_size_ (initially
per_sub_buffer_capacity_, decays as the driver refuses larger sizes)
and halves on failure inside a single call. Stops at a 64 MB floor;
below that the per-sub-buffer bookkeeping cost (free list, bind
groups) isn't worth it. growth_disabled_ now latches only when even
64 MB is refused — a true hardware ceiling, not just "the first
attempt didn't fit."
pool_can_fit gains a next_growth_size_bytes() accessor to stay
honest about how big a future sub-buffer can be after the driver
has refused larger sizes.
Measured (big federation, --streaming, close camera):
pool capacity: 2048 MB → 2688 MB (2 GB + 512 MB + 128 MB)
VRAM resident: 2155 MB → 2800 MB (whole scene fits, no eviction)
avg fps: 42 → 53
stream time: 2.5 ms → 0.1 ms (no churn — working set is stable)
On larger GPUs (8 / 16 / 24 GB workstations) the same code extracts
proportionally more (e.g. 4 × 2 GB on a 10 GB+ card).
The GL backend's higher "4+ GB resident" claim is overcommit into
host RAM — explicit Vulkan/wgpu memory management deliberately
doesn't paper over that, and the wgpu-mem-probe data confirms it
isn't recoverable.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3588,11 +3588,11 @@ void WgpuViewportWindow::driveStreamingLoads() {
|
||||
// avoids wasted evict-then-fail loops.
|
||||
auto pool_can_fit = [&](uint64_t bytes) -> bool {
|
||||
if (pool_.largest_free_run_bytes() >= bytes) return true;
|
||||
// Growth might still rescue us — but only if growth hasn't been
|
||||
// refused at this size already. After a refusal, eviction is the
|
||||
// sole path; the eviction loop must run until largest_free_run
|
||||
// catches up.
|
||||
if (pool_.can_grow() && pool_.per_sub_buffer_capacity_bytes() >= bytes) return true;
|
||||
// Growth might still rescue us. Use next_growth_size_bytes()
|
||||
// rather than per_sub_buffer_capacity_bytes() — after a refusal
|
||||
// at e.g. 2 GB, halve-on-failure pushes the next achievable
|
||||
// sub-buffer down to 1 GB; saying "fits if ≤2 GB" would lie.
|
||||
if (pool_.can_grow() && pool_.next_growth_size_bytes() >= bytes) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user