Files
IfcOpenShell/src/ifcviewer/FrameStats.h
T
Dion Moult 8074541057 Surface VRAM shortfall to the user and let them unload models
When the geometry in view needs more GPU memory than the cache can
hold, the viewer keeps the largest on-screen chunks resident and streams
the rest as the camera moves. That is the right degradation, but it was
invisible: nothing told the user the scene did not fit, and the only
lever was removing or hiding models, neither of which is "keep it in the
federation but stop spending GPU memory on it".

Viewer core:
- ModelGpuData::unloaded, with drawable() = !hidden && !unloaded now the
  test every cull / draw / pick / streaming pass uses. unloadModel evicts
  every chunk and releases the model's own buffers; loadModel recreates
  them from the CPU mirrors (no disk read) and lets chunks stream back.
  Recompose keeps the CPU instances current while a model is unloaded so
  a reload sees up-to-date transforms. The MeshGpu/InstanceGpu record
  builders are factored out so load and reload share them.
- FrameStats reports the camera's working set: chunks wanted, how many
  of those are not resident, and their bytes.
- modelVramBytes / isModelUnloaded accessors, forwarded by ViewportWindow.

BonsaiViewer:
- Models tree gains a memory column (name | MB | eye) refreshed once a
  second and on load-state changes; unloaded models read "unloaded" in
  italics. The viewport stays the single authority for the state;
  SessionState only carries the modelLoadStateChanged notification.
- Context menu: "Unload Model" / "Load Model", distinct from hide and
  remove, reporting the MB freed in the status bar.
- Status bar notice, independent of the perf-stats toggle, once the
  shortfall has persisted for 3 s (a moment of missing chunks after any
  camera move is normal): "GPU memory full: N of M visible chunks (X MB)
  not loaded", with a tooltip pointing at Unload. The perf label also
  shows "N/M chunks waiting".

Verified on the GPU: unloading a 497 MB model frees it immediately with
the others still rendering; reloading streams all 180 chunks back.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00

64 lines
3.5 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/>. *
* *
********************************************************************************/
#ifndef IFCVIEWER_FRAMESTATS_H
#define IFCVIEWER_FRAMESTATS_H
#include <cstdint>
// Per-frame statistics emitted from render() so the embedder can
// surface them (bonsai's status bar, the web-side dev console, the
// benchmark accumulator). Pure POD so it travels through ViewportHost
// without dragging Qt along. ViewportWindow::FrameStats re-exports
// this so existing bonsai callers still see the familiar
// ViewportWindow::FrameStats type.
struct FrameStats {
float fps;
float frame_time_ms;
std::uint32_t total_objects;
std::uint32_t visible_objects;
std::uint32_t total_triangles;
std::uint32_t visible_triangles;
std::uint32_t unique_meshes;
std::uint32_t gl_draw_calls; // wgpu draw-call count; name kept for bonsai parity
std::uint32_t indirect_sub_draws; // sub-draws packed into the chunk-indirect lists
// Chunk geometry pool occupancy (see BufferPool): bytes held by
// resident chunks, the pool's current capacity, and the budget the
// pool may grow to (GpuBudget; 0 when still unbounded).
std::uint64_t vram_used_bytes;
std::uint64_t vram_capacity_bytes;
std::uint64_t vram_budget_bytes;
// The camera's working set: chunks the streaming driver wants resident
// (in frustum and large enough on screen) and how many of those are
// not — i.e. geometry the user should be seeing but is not yet, or
// cannot be because it does not fit the cache. Transiently non-zero
// after any camera move; persistently non-zero means the scene does
// not fit in VRAM.
std::uint32_t chunks_wanted;
std::uint32_t chunks_wanted_missing;
std::uint64_t wanted_missing_bytes; // raw vertex + index bytes of the missing chunks
// Whole-device VRAM from the driver (NVML / sysfs, see GpuMemory.h).
// Desktop only; zero on web or when no backend could answer, so
// consumers must treat 0 as "unknown" rather than as empty.
std::uint64_t device_vram_used_bytes;
std::uint64_t device_vram_total_bytes;
};
#endif // IFCVIEWER_FRAMESTATS_H