mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-31 08:56:34 +00:00
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>
This commit is contained in:
+119
-56
@@ -109,6 +109,39 @@ void releaseWgpuModelGpuData(ModelGpuData& m, BufferPool& pool) {
|
||||
|
||||
// ---- Scene mutators -------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
|
||||
// The GPU-side records the shaders read, built from the CPU mirrors.
|
||||
std::vector<MeshGpu> meshGpuRecords(const std::vector<MeshInfo>& meshes) {
|
||||
std::vector<MeshGpu> gpu;
|
||||
gpu.reserve(meshes.size());
|
||||
for (const auto& mesh_info : meshes) {
|
||||
MeshGpu rec = {};
|
||||
for (int a = 0; a < 3; ++a) {
|
||||
rec.aabb_min[a] = mesh_info.local_aabb_min[a];
|
||||
rec.aabb_max[a] = mesh_info.local_aabb_max[a];
|
||||
}
|
||||
gpu.push_back(rec);
|
||||
}
|
||||
return gpu;
|
||||
}
|
||||
|
||||
std::vector<InstanceGpu> instanceGpuRecords(const std::vector<InstanceInfo>& instances) {
|
||||
std::vector<InstanceGpu> gpu(instances.size());
|
||||
for (size_t i = 0; i < instances.size(); ++i) {
|
||||
const InstanceInfo& inst = instances[i];
|
||||
InstanceGpu& dst = gpu[i];
|
||||
std::memcpy(dst.transform, inst.transform, sizeof(dst.transform));
|
||||
dst.object_id = inst.object_id;
|
||||
dst.color_override_rgba8 = inst.color_override_rgba8;
|
||||
dst.mesh_id = inst.mesh_id;
|
||||
dst._pad1 = 0;
|
||||
}
|
||||
return gpu;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ViewportCore::removeModel(uint32_t session_model_id) {
|
||||
auto it = models_gpu_.find(session_model_id);
|
||||
if (it == models_gpu_.end()) return;
|
||||
@@ -142,6 +175,46 @@ void ViewportCore::showModel(uint32_t session_model_id) {
|
||||
host_->requestFrame();
|
||||
}
|
||||
|
||||
void ViewportCore::unloadModel(uint32_t session_model_id) {
|
||||
auto it = models_gpu_.find(session_model_id);
|
||||
if (it == models_gpu_.end() || it->second.unloaded) return;
|
||||
ModelGpuData& m = it->second;
|
||||
for (std::size_t ci = 0; ci < m.chunks.size(); ++ci) unloadChunk(m, ci);
|
||||
releaseModelBuffers(m);
|
||||
m.unloaded = true;
|
||||
host_->requestFrame();
|
||||
}
|
||||
|
||||
bool ViewportCore::loadModel(uint32_t session_model_id) {
|
||||
auto it = models_gpu_.find(session_model_id);
|
||||
if (it == models_gpu_.end()) return false;
|
||||
ModelGpuData& m = it->second;
|
||||
if (!m.unloaded) return true;
|
||||
// Chunks stream back in on demand once the model is drawable again;
|
||||
// only the model's own buffers have to be recreated here.
|
||||
if (!createModelBuffers(session_model_id, m,
|
||||
meshGpuRecords(m.meshes), instanceGpuRecords(m.instances))) {
|
||||
Log::warn() << "[wgpu] model " << session_model_id
|
||||
<< " not reloaded: the device cannot fit its metadata buffers";
|
||||
return false;
|
||||
}
|
||||
m.unloaded = false;
|
||||
host_->requestFrame();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ViewportCore::isModelUnloaded(uint32_t session_model_id) const {
|
||||
auto it = models_gpu_.find(session_model_id);
|
||||
return it != models_gpu_.end() && it->second.unloaded;
|
||||
}
|
||||
|
||||
std::uint64_t ViewportCore::modelVramBytes(uint32_t session_model_id) const {
|
||||
auto it = models_gpu_.find(session_model_id);
|
||||
if (it == models_gpu_.end()) return 0;
|
||||
const ModelGpuData& m = it->second;
|
||||
return m.vram_bytes_vbo + m.vram_bytes_ebo + m.vram_bytes_ssbo;
|
||||
}
|
||||
|
||||
void ViewportCore::setFederatedFalseOrigin(const Eigen::Matrix4d& matrix_meters) {
|
||||
if (federated_false_origin_meters_ == matrix_meters) return;
|
||||
federated_false_origin_meters_ = matrix_meters;
|
||||
@@ -264,17 +337,7 @@ float ViewportCore::chunkScreenAreaPx(const ModelGpuData::Chunk& c,
|
||||
|
||||
void ViewportCore::uploadInstanceRecords(ModelGpuData& m) {
|
||||
if (!wgpu_initialized_ || m.instances.empty() || m.instance_storage == nullptr) return;
|
||||
|
||||
std::vector<InstanceGpu> gpu(m.instances.size());
|
||||
for (size_t i = 0; i < m.instances.size(); ++i) {
|
||||
const InstanceInfo& inst = m.instances[i];
|
||||
InstanceGpu& dst = gpu[i];
|
||||
std::memcpy(dst.transform, inst.transform, sizeof(dst.transform));
|
||||
dst.object_id = inst.object_id;
|
||||
dst.color_override_rgba8 = inst.color_override_rgba8;
|
||||
dst.mesh_id = inst.mesh_id;
|
||||
dst._pad1 = 0;
|
||||
}
|
||||
const std::vector<InstanceGpu> gpu = instanceGpuRecords(m.instances);
|
||||
wgpuQueueWriteBuffer(queue_, m.instance_storage, 0,
|
||||
gpu.data(), gpu.size() * sizeof(InstanceGpu));
|
||||
}
|
||||
@@ -284,8 +347,11 @@ void ViewportCore::recomposeAndUploadModel(uint32_t session_model_id) {
|
||||
auto it = models_gpu_.find(session_model_id);
|
||||
if (it == models_gpu_.end()) return;
|
||||
ModelGpuData& m = it->second;
|
||||
if (m.instances.empty() || m.instance_storage == nullptr) return;
|
||||
if (m.instances.empty()) return;
|
||||
|
||||
// The CPU mirrors are recomposed even while the model is unloaded (no
|
||||
// instance_storage): cull and loadModel read them, and the upload
|
||||
// below is skipped on its own.
|
||||
for (auto& inst : m.instances) composeInstanceFromPlacement(inst, m);
|
||||
uploadInstanceRecords(m);
|
||||
|
||||
@@ -2451,7 +2517,7 @@ void ViewportCore::driveStreamingLoads() {
|
||||
// last ~30 frames.
|
||||
constexpr float HISTORY_ALPHA = 1.0f / 30.0f;
|
||||
for (auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
for (auto& c : m.chunks) {
|
||||
if (c.is_resident && c.frustum_visible_count > 0) {
|
||||
c.last_visible_frame_idx = streaming_frame_idx_;
|
||||
@@ -2656,7 +2722,7 @@ void ViewportCore::driveStreamingLoads() {
|
||||
std::vector<Candidate> candidates;
|
||||
candidates.reserve(64);
|
||||
for (auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.streaming_file_path.empty() || m.hidden) continue;
|
||||
if (m.streaming_file_path.empty() || !m.drawable()) continue;
|
||||
for (std::size_t ci = 0; ci < m.chunks.size(); ++ci) {
|
||||
auto& c = m.chunks[ci];
|
||||
if (c.is_resident) continue;
|
||||
@@ -2872,7 +2938,7 @@ void ViewportCore::driveStreamingLoads() {
|
||||
const bool growth_may_land = pool_.growth_pending() || pool_.can_grow();
|
||||
bool visible_pending = false;
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.streaming_file_path.empty() || m.hidden) continue;
|
||||
if (m.streaming_file_path.empty() || !m.drawable()) continue;
|
||||
for (const auto& c : m.chunks) {
|
||||
if (c.is_resident) continue;
|
||||
if (c.is_loading) { visible_pending = true; break; }
|
||||
@@ -3557,38 +3623,18 @@ void ViewportCore::applyCachedModel(std::uint32_t session_model_id,
|
||||
// Index section is NOT loaded upfront. Each chunk's index slice is
|
||||
// range-read alongside its vertex bytes in loadChunkBytesAndUploadGpu.
|
||||
|
||||
// MeshGpu storage (per-mesh quant basis).
|
||||
std::vector<MeshGpu> mesh_gpu;
|
||||
mesh_gpu.reserve(metadata.meta.meshes.size());
|
||||
for (const auto& mesh_info : metadata.meta.meshes) {
|
||||
MeshGpu mesh_gpu_record = {};
|
||||
mesh_gpu_record.aabb_min[0] = mesh_info.local_aabb_min[0];
|
||||
mesh_gpu_record.aabb_min[1] = mesh_info.local_aabb_min[1];
|
||||
mesh_gpu_record.aabb_min[2] = mesh_info.local_aabb_min[2];
|
||||
mesh_gpu_record.aabb_max[0] = mesh_info.local_aabb_max[0];
|
||||
mesh_gpu_record.aabb_max[1] = mesh_info.local_aabb_max[1];
|
||||
mesh_gpu_record.aabb_max[2] = mesh_info.local_aabb_max[2];
|
||||
mesh_gpu.push_back(mesh_gpu_record);
|
||||
}
|
||||
|
||||
// InstanceGpu storage. Rebase object_ids globally.
|
||||
// Rebase object_ids globally (element metadata records rebase to match).
|
||||
const std::uint32_t object_id_base = next_object_id_;
|
||||
std::uint32_t max_local_id = 0;
|
||||
std::vector<InstanceGpu> inst_gpu;
|
||||
inst_gpu.reserve(metadata.meta.instances.size());
|
||||
for (auto& instance_cpu : metadata.meta.instances) {
|
||||
if (instance_cpu.object_id > max_local_id) max_local_id = instance_cpu.object_id;
|
||||
instance_cpu.object_id = object_id_base + instance_cpu.object_id;
|
||||
InstanceGpu instance_gpu = {};
|
||||
std::memcpy(instance_gpu.transform, instance_cpu.transform, sizeof(instance_gpu.transform));
|
||||
instance_gpu.object_id = instance_cpu.object_id;
|
||||
instance_gpu.color_override_rgba8 = instance_cpu.color_override_rgba8;
|
||||
instance_gpu.mesh_id = instance_cpu.mesh_id;
|
||||
inst_gpu.push_back(instance_gpu);
|
||||
}
|
||||
next_object_id_ = object_id_base + max_local_id + 1;
|
||||
model_gpu_data.object_id_base = object_id_base; // element metadata records rebase to match
|
||||
model_gpu_data.object_id_base = object_id_base;
|
||||
|
||||
const std::vector<MeshGpu> mesh_gpu = meshGpuRecords(metadata.meta.meshes);
|
||||
const std::vector<InstanceGpu> inst_gpu = instanceGpuRecords(metadata.meta.instances);
|
||||
if (!createModelBuffers(session_model_id, model_gpu_data, mesh_gpu, inst_gpu)) {
|
||||
Log::warn() << "[wgpu] model " << session_model_id
|
||||
<< " not loaded: the device cannot fit its metadata buffers";
|
||||
@@ -3980,7 +4026,7 @@ void ViewportCore::pumpWebChunkLoads() {
|
||||
auto it = models_gpu_.find(next.session_model_id);
|
||||
if (it == models_gpu_.end()) continue;
|
||||
ModelGpuData& m = it->second;
|
||||
if (m.hidden || next.ci >= m.chunks.size()) continue;
|
||||
if (!m.drawable() || next.ci >= m.chunks.size()) continue;
|
||||
auto& c = m.chunks[next.ci];
|
||||
if (c.is_resident || c.is_loading) continue;
|
||||
if (c.contribution_visible_count == 0) continue; // no longer worth drawing
|
||||
@@ -4328,6 +4374,7 @@ void ViewportCore::streamingProgress(int& resident_chunks, int& total_chunks) co
|
||||
resident_chunks = 0;
|
||||
total_chunks = 0;
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.unloaded) continue;
|
||||
for (const auto& c : m.chunks) {
|
||||
++total_chunks;
|
||||
if (c.is_resident) ++resident_chunks;
|
||||
@@ -4359,7 +4406,7 @@ void ViewportCore::streamingModelProgress(int idx, int& resident_chunks,
|
||||
total_chunks = 0;
|
||||
if (idx < 0 || idx >= int(models_gpu_.size())) return;
|
||||
auto it = models_gpu_.find(modelIdsInLoadOrder()[std::size_t(idx)]);
|
||||
if (it == models_gpu_.end()) return;
|
||||
if (it == models_gpu_.end() || it->second.unloaded) return;
|
||||
for (const auto& c : it->second.chunks) {
|
||||
++total_chunks;
|
||||
if (c.is_resident) ++resident_chunks;
|
||||
@@ -4428,7 +4475,7 @@ void ViewportCore::streamingByteProgress(std::uint64_t& total_bytes,
|
||||
// whole model this view even requires.
|
||||
total_bytes = needed_bytes = loaded_bytes = 0;
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
for (const auto& c : m.chunks) {
|
||||
// Report COMPRESSED bytes — what actually crosses the network. Fall
|
||||
// back to raw for direct (in-memory) loads that have no blobs.
|
||||
@@ -5682,7 +5729,7 @@ void ViewportCore::encodeSelectionMaskPass(WGPUCommandEncoder enc) {
|
||||
// Same draw stream as the main pass, opaque and transparent together —
|
||||
// a selected element that happens to be translucent still gets a halo.
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
for (const auto& c : m.chunks) {
|
||||
if (!c.bind_group || c.total_visible_vertices == 0) continue;
|
||||
wgpuRenderPassEncoderSetBindGroup(pass, 1, c.bind_group, 0, nullptr);
|
||||
@@ -6144,7 +6191,7 @@ void ViewportCore::encodePickReadbackToStaging(int x_pixels, int y_pixels,
|
||||
wgpuRenderPassEncoderSetPipeline(pass, pick_pipeline_);
|
||||
wgpuRenderPassEncoderSetBindGroup(pass, 0, frame_bind_group_, 0, nullptr);
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
for (const auto& c : m.chunks) {
|
||||
if (!c.bind_group || c.total_visible_vertices == 0) continue;
|
||||
wgpuRenderPassEncoderSetBindGroup(pass, 1, c.bind_group, 0, nullptr);
|
||||
@@ -6343,7 +6390,7 @@ void ViewportCore::isolateSelected() {
|
||||
// object_id 0 (unpickable) is skipped.
|
||||
const auto& sel_ids = selection_.selectionIds();
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
for (const InstanceInfo& inst : m.instances) {
|
||||
if (inst.object_id == 0) continue;
|
||||
if (sel_ids.find(inst.object_id) == sel_ids.end())
|
||||
@@ -6366,7 +6413,7 @@ void ViewportCore::hideAll() {
|
||||
// showAll, and isolateSelected with an empty selection. Model-hidden
|
||||
// models are already gone from the cull, so they contribute nothing.
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
for (const InstanceInfo& inst : m.instances) visibility_.hide(inst.object_id);
|
||||
}
|
||||
Log::info().noquote().nospace() << "[wgpu] hid all (" << visibility_.hiddenCount() << ")";
|
||||
@@ -6546,7 +6593,7 @@ bool ViewportCore::encodeBoxPickToStaging(int& x, int& y, int& w, int& h,
|
||||
wgpuRenderPassEncoderSetPipeline(pass, pick_pipeline_);
|
||||
wgpuRenderPassEncoderSetBindGroup(pass, 0, frame_bind_group_, 0, nullptr);
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
for (const auto& c : m.chunks) {
|
||||
if (!c.bind_group || c.total_visible_vertices == 0) continue;
|
||||
wgpuRenderPassEncoderSetBindGroup(pass, 1, c.bind_group, 0, nullptr);
|
||||
@@ -6666,7 +6713,7 @@ bool ViewportCore::encodeXrayBoxPickToStaging(int& x, int& y, int& w, int& h,
|
||||
wgpuRenderPassEncoderSetPipeline(pass, box_pick_pipeline_);
|
||||
wgpuRenderPassEncoderSetBindGroup(pass, 0, frame_bind_group_, 0, nullptr);
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
for (const auto& c : m.chunks) {
|
||||
if (!c.bind_group || c.total_visible_vertices == 0) continue;
|
||||
wgpuRenderPassEncoderSetBindGroup(pass, 1, c.bind_group, 0, nullptr);
|
||||
@@ -6856,7 +6903,7 @@ bool ViewportCore::raycastSurfaceForObject(std::uint32_t object_id, int x_pixels
|
||||
float best_radius = 0.0f;
|
||||
bool found = false;
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
for (const auto& inst : m.instances) {
|
||||
if (inst.object_id != object_id) continue;
|
||||
float t = 0.0f;
|
||||
@@ -7171,7 +7218,7 @@ bool ViewportCore::raycast(const float origin[3], const float dir[3],
|
||||
float best_normal[3] = {0, 0, 0};
|
||||
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
for (std::uint32_t inst_idx = 0; inst_idx < std::uint32_t(m.instances.size()); ++inst_idx) {
|
||||
const InstanceInfo& inst = m.instances[inst_idx];
|
||||
if (!rayAabbSlab(origin, inv_d, inst.world_aabb_min, inst.world_aabb_max)) {
|
||||
@@ -7732,7 +7779,7 @@ void ViewportCore::render() {
|
||||
std::vector<std::pair<std::uint32_t, std::future<std::uint32_t>>> futures;
|
||||
futures.reserve(models_gpu_.size());
|
||||
for (auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
auto& m_ref = m;
|
||||
futures.emplace_back(session_model_id, std::async(std::launch::async,
|
||||
[this, &m_ref, &planes, &eye_a, &fwd_a, &right_a, &up_a,
|
||||
@@ -7749,7 +7796,7 @@ void ViewportCore::render() {
|
||||
}
|
||||
} else {
|
||||
for (auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
hiz_reject_count_ += cullModelCpuCompute(
|
||||
m, planes, eye_a, fwd_a, right_a, up_a, focal_px,
|
||||
effective_min_px, lod1_pixel_threshold_,
|
||||
@@ -7761,7 +7808,7 @@ void ViewportCore::render() {
|
||||
Stopwatch upload_timer;
|
||||
upload_timer.start();
|
||||
for (auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
cullModelCpuUpload(m);
|
||||
for (const auto& c : m.chunks) {
|
||||
last_visible_objects_ += c.total_visible_draws;
|
||||
@@ -7842,7 +7889,7 @@ void ViewportCore::render() {
|
||||
wgpuRenderPassEncoderSetBindGroup(pass, 0, frame_bind_group_, 0, nullptr);
|
||||
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
for (const auto& c : m.chunks) {
|
||||
if (!c.bind_group || c.opaque_visible_vertices == 0) continue;
|
||||
wgpuRenderPassEncoderSetBindGroup(pass, 1, c.bind_group, 0, nullptr);
|
||||
@@ -7853,7 +7900,7 @@ void ViewportCore::render() {
|
||||
|
||||
wgpuRenderPassEncoderSetPipeline(pass, main_pipeline_transparent_);
|
||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
if (!m.drawable()) continue;
|
||||
for (const auto& c : m.chunks) {
|
||||
if (!c.bind_group) continue;
|
||||
const std::uint32_t transparent_verts =
|
||||
@@ -7975,13 +8022,29 @@ void ViewportCore::render() {
|
||||
stats.unique_meshes = total_meshes;
|
||||
std::uint32_t draw_calls = 0;
|
||||
for (const auto& [session_model_id, mm] : models_gpu_) {
|
||||
if (mm.hidden) continue;
|
||||
if (!mm.drawable()) continue;
|
||||
for (const auto& c : mm.chunks) {
|
||||
if (c.is_resident && c.total_visible_draws > 0) ++draw_calls;
|
||||
}
|
||||
}
|
||||
stats.gl_draw_calls = draw_calls;
|
||||
stats.indirect_sub_draws = last_sub_draws_;
|
||||
std::uint32_t wanted = 0, wanted_missing = 0;
|
||||
std::uint64_t wanted_missing_bytes = 0;
|
||||
for (const auto& [session_model_id, mm] : models_gpu_) {
|
||||
if (!mm.drawable()) continue;
|
||||
for (const auto& c : mm.chunks) {
|
||||
if (c.contribution_visible_count == 0) continue;
|
||||
++wanted;
|
||||
if (c.is_resident) continue;
|
||||
++wanted_missing;
|
||||
wanted_missing_bytes += c.vertex_byte_size
|
||||
+ c.index_count * sizeof(std::uint32_t);
|
||||
}
|
||||
}
|
||||
stats.chunks_wanted = wanted;
|
||||
stats.chunks_wanted_missing = wanted_missing;
|
||||
stats.wanted_missing_bytes = wanted_missing_bytes;
|
||||
stats.vram_used_bytes = pool_.total_used_bytes();
|
||||
stats.vram_capacity_bytes = pool_.total_capacity_bytes();
|
||||
stats.vram_budget_bytes = budget_.bounded() ? budget_.cache_budget_bytes() : 0;
|
||||
|
||||
Reference in New Issue
Block a user