mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
ifcviewer-web: handle device loss so GPU pressure can't freeze the tab
Reported: open a model in the web viewer, then launch the desktop BonsaiViewer and open another model — the browser tab freezes, and a fresh web tab then fails with "RequestDevice failed: Not enough memory left". Root cause is GPU-memory contention: two heavy GPU clients on one GPU, and the desktop app's allocations starve the browser's WebGPU process, which reclaims our device. We can't conjure GPU memory, but we were amplifying the symptom: with no device-lost handler, render() kept driving a dead device — wgpuSurfaceGetCurrentTexture returns Lost every frame and the reconfigure + requestFrame retry becomes a tight per-RAF loop that hangs the tab. Now the web device descriptor wires a device-lost callback that latches device_lost_ (ignoring the intentional Destroyed reason from our own shutdown); render() bails while set, so the loop goes idle instead of spinning, and the console logs guidance to reload. The fresh-tab RequestDevice OOM is genuine GPU exhaustion — surfaced as before, now with a clearer message. Verified the lost callback doesn't disturb Dawn-web's RequestDevice (all 5 web smoke tests still init + pass); desktop unaffected (device_lost_ stays false). The real contention path can't be reproduced in the headless harness, so the loss handler itself is covered by review, not a test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1625,6 +1625,24 @@ void ViewportCore::initWgpuAsyncWeb(std::function<void(bool)> on_complete) {
|
||||
// RequestDevice silently never resolves the promise when passed
|
||||
// nullptr.
|
||||
WGPUDeviceDescriptor dd = {};
|
||||
// Device-lost handler. If another GPU client (e.g. the desktop app
|
||||
// launched alongside this tab) exhausts GPU memory, the browser can
|
||||
// reclaim our device. Without this we'd keep driving render() on a
|
||||
// dead device — wgpuSurfaceGetCurrentTexture returns Lost forever and
|
||||
// the reconfigure+requestFrame retry becomes a tight loop that hangs
|
||||
// the tab. Latch a flag so render() bails and the loop goes idle.
|
||||
// Skip the intentional Destroyed reason (fired by our own shutdown).
|
||||
dd.deviceLostCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous;
|
||||
dd.deviceLostCallbackInfo.callback =
|
||||
[](const WGPUDevice* /*dev*/, WGPUDeviceLostReason reason,
|
||||
WGPUStringView msg, void* ud1, void* /*ud2*/) {
|
||||
if (reason == WGPUDeviceLostReason_Destroyed) return;
|
||||
auto* core = static_cast<ViewportCore*>(ud1);
|
||||
core->device_lost_ = true;
|
||||
Log::warn() << "[wgpu] device lost (GPU memory reclaimed?): "
|
||||
<< svToStr(msg) << " — reload the page";
|
||||
};
|
||||
dd.deviceLostCallbackInfo.userdata1 = c->core;
|
||||
wgpuAdapterRequestDevice(adapter, &dd, dcb);
|
||||
};
|
||||
acb.userdata1 = ctx;
|
||||
@@ -5400,6 +5418,10 @@ inline float srgbToLinear(float c) {
|
||||
|
||||
void ViewportCore::render() {
|
||||
if (!device_ || !queue_ || !surface_) return;
|
||||
// Device lost (e.g. GPU memory reclaimed by another client). Stop here so
|
||||
// we don't busy-loop reconfiguring a dead surface — that retry storm is
|
||||
// what otherwise freezes the tab. The page logs guidance to reload.
|
||||
if (device_lost_) return;
|
||||
|
||||
Stopwatch frame_timer;
|
||||
frame_timer.start();
|
||||
|
||||
@@ -688,6 +688,14 @@ private:
|
||||
WGPUTextureFormat surface_view_format_ = WGPUTextureFormat_Undefined;
|
||||
bool surface_configured_ = false;
|
||||
|
||||
public:
|
||||
// Latched by the device-lost callback (web) when the GPU reclaims our
|
||||
// device — typically GPU-memory pressure from another client. render()
|
||||
// bails while set so the loop doesn't hammer a dead surface (which freezes
|
||||
// the tab). Public so the spontaneous C callback can set it.
|
||||
bool device_lost_ = false;
|
||||
private:
|
||||
|
||||
// ---- Pipelines + bind-group layouts (built once after init) -------------
|
||||
//
|
||||
// Main render pipeline group: one shader module + two bind group
|
||||
|
||||
Reference in New Issue
Block a user