mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-22 16:41:07 +00:00
ab99024307
Loading enough models drove the chunk pool to the driver's refusal point, after which the first click aborted: the pick attachments are allocated lazily, wgpu-native reported their OOM as a validation error nobody observed, and the invalid views reached wgpuQueueSubmit, which panics across the FFI boundary. Two policy defects compounding: the cache was allowed to take the last byte, and nothing but the pool's own growth was treated as fallible. GPU memory is now two tiers. Required allocations (per-pixel attachments, a model's metadata buffers, readback staging) are eager, deterministic and fallible; the chunk pool is an elastic cache that grows only to a budget and yields whenever a required allocation fails. - GpuBudget (pure, unit-tested): desktop derives the budget from the driver's free-memory report minus a reserve for the attachments at 4K; web keeps the wasm-heap cap; either lowers it on pressure. The budget's source differs per platform, the mechanism does not. - GpuAllocScope: the OOM/Validation error-scope dance in one place, synchronous on wgpu-native, provisional on Dawn-web. BufferPool's inline copy now uses it. - BufferPool::shrinkToCapacity releases whole sub-buffers newest-first after the owner empties them; growth clamps to the budget instead of overshooting. - ViewportCore::allocateRequired runs any required creation under a scope and, on failure, lowers the budget, evicts and releases cache sub-buffers, waits for the device to reclaim them, and retries until it fits or the cache is at its floor. Pick attachments are created with the other attachments in configureSurface; render() skips a frame rather than submit invalid views; a model whose buffers cannot fit is not loaded instead of aborting. Verified on a 4 GB GeForce: the pool clamps itself at the derived budget (256+256+67 MB for a 579 MB budget) and, in a standalone check against the real device, a pool grown to the driver's refusal point observes a failed required allocation, releases 320 MB and succeeds on retry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
93 lines
3.8 KiB
C++
93 lines
3.8 KiB
C++
/********************************************************************************
|
|
* *
|
|
* This file is part of IfcOpenShell. *
|
|
* *
|
|
* IfcOpenShell is free software: you can redistribute it and/or modify *
|
|
* it under the terms of the Lesser GNU General Public License as published by *
|
|
* the Free Software Foundation, either version 3.0 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* IfcOpenShell is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* Lesser GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the Lesser GNU General Public License *
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
|
* *
|
|
********************************************************************************/
|
|
|
|
#include "GpuAllocScope.h"
|
|
|
|
#include <cassert>
|
|
|
|
namespace {
|
|
|
|
// Shared between the two pop callbacks; freed by whichever fires last.
|
|
struct PendingPop {
|
|
GpuAllocScope::Callback on_result;
|
|
int remaining = 2;
|
|
bool error = false;
|
|
// Desktop: points at a flag on end()'s stack frame so the spin-wait
|
|
// can observe completion without touching this (freed) object.
|
|
bool* done = nullptr;
|
|
};
|
|
|
|
void onPopped(WGPUPopErrorScopeStatus, WGPUErrorType type, WGPUStringView,
|
|
void* userdata1, void* /*userdata2*/) {
|
|
auto* pending = static_cast<PendingPop*>(userdata1);
|
|
if (type != WGPUErrorType_NoError) pending->error = true;
|
|
if (--pending->remaining > 0) return;
|
|
const bool ok = !pending->error;
|
|
bool* done = pending->done;
|
|
GpuAllocScope::Callback on_result = std::move(pending->on_result);
|
|
delete pending;
|
|
on_result(ok);
|
|
if (done) *done = true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
GpuAllocScope::GpuAllocScope(WGPUInstance instance, WGPUDevice device)
|
|
: instance_(instance), device_(device) {
|
|
// Validation outer, OutOfMemory inner: each pop sees the errors of
|
|
// its own filter, and an OOM reported under either classification
|
|
// reaches one of the two.
|
|
wgpuDevicePushErrorScope(device_, WGPUErrorFilter_Validation);
|
|
wgpuDevicePushErrorScope(device_, WGPUErrorFilter_OutOfMemory);
|
|
}
|
|
|
|
GpuAllocScope::~GpuAllocScope() {
|
|
assert(ended_ && "GpuAllocScope::end() must be called exactly once");
|
|
}
|
|
|
|
void GpuAllocScope::end(Callback on_result) {
|
|
assert(!ended_);
|
|
ended_ = true;
|
|
|
|
bool done = false;
|
|
auto* pending = new PendingPop{std::move(on_result)};
|
|
|
|
WGPUPopErrorScopeCallbackInfo cb = {};
|
|
#if defined(__EMSCRIPTEN__)
|
|
// Dawn-web resolves pops from the JS event loop; the caller proceeds
|
|
// provisionally and hears back in on_result.
|
|
cb.mode = WGPUCallbackMode_AllowSpontaneous;
|
|
#else
|
|
// wgpu-native fires these from wgpuInstanceProcessEvents, which we
|
|
// spin below so on_result has run by the time end() returns.
|
|
cb.mode = WGPUCallbackMode_AllowProcessEvents;
|
|
pending->done = &done;
|
|
#endif
|
|
cb.callback = onPopped;
|
|
cb.userdata1 = pending;
|
|
wgpuDevicePopErrorScope(device_, cb); // OutOfMemory (inner)
|
|
wgpuDevicePopErrorScope(device_, cb); // Validation (outer)
|
|
|
|
#if !defined(__EMSCRIPTEN__)
|
|
while (!done) wgpuInstanceProcessEvents(instance_);
|
|
#else
|
|
(void)done;
|
|
#endif
|
|
}
|