forked from 1b-agent/opencadstudio-upstream
fix: give back the memory of viewports nobody is drawing
`MultiPipeline::inners` only ever grew. A slot that no viewport is using still owned its wire arena, its render targets, its text atlas and every category buffer — tens of megabytes each — and nothing ever asked for them back. Zoom a paper layout out until five viewports are on screen, and the memory the hidden ones are holding is exactly what the visible one cannot get. `Pipeline::release_heavy_resources` hands it back: arenas, wire and block batches, the handle index, selection and preview buffers, wipeouts, images, mesh batches, silhouettes, the text atlas, and the render targets, which go through `ensure_depth_texture` at its smallest size rather than being reached for directly so the blit bind group stays consistent with the views it samples — about 0.6 MiB against the ~70 MiB a full-canvas slot holds. Everything released is derived state whose rebuild is guarded by a cache key, and every one of those keys is reset. That reset was already written once, inline, for slot reuse; both now call `forget_cached_keys` so they cannot disagree about what "this slot holds nothing you can trust" means. It gained two entries in the move — `cached_highlight_key` and `silhouette_source_key` — which slot reuse had not been clearing, so a recycled slot could keep a stale highlight or silhouette. The policy is gradual while the device is healthy: a slot is released once it has been idle for 64 prepare calls, so an ordinary session pays nothing and a layout the user keeps returning to stays warm. It stops being patient the moment the device refuses an allocation — at that point holding an invisible viewport is what turns a degraded frame into a session that cannot recover, and the operator needs the drawing to stay usable long enough to close a viewport or two. The cost of releasing a slot that is wanted again is one slow frame. `slots-released n= urgent= slots=` reports when it happens, since the alternative is inferring it from a memory number. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d81a858040
commit
0118b28d10
2 files changed
+147
-20
No files matched your search
+18
-20
@@ -367,6 +367,9 @@ impl shader::Primitive for Primitive {
|
||||
let scale = viewport.scale_factor() as f32;
|
||||
let instance_ids: Vec<u64> = self.viewports.iter().map(|vp| vp.instance_id).collect();
|
||||
let slots = pipeline.resolve_slots(device, queue, &instance_ids);
|
||||
// Compared after the frame's uploads: a move means the device refused
|
||||
// something, which changes how patient the slot release below can be.
|
||||
let errors_at_entry = crate::scene::pipeline::gpu_errors_seen();
|
||||
|
||||
for (i, vp) in self.viewports.iter().enumerate() {
|
||||
let inner = &mut pipeline.inners[slots[i]];
|
||||
@@ -381,26 +384,7 @@ impl shader::Primitive for Primitive {
|
||||
// the surviving viewport's text/geometry vanish.
|
||||
if inner.slot_id != vp.instance_id {
|
||||
inner.slot_id = vp.instance_id;
|
||||
inner.cached_epoch = (u64::MAX, u64::MAX, u64::MAX);
|
||||
inner.cached_wire_id = u64::MAX;
|
||||
inner.cached_selection = (u64::MAX, u64::MAX);
|
||||
inner.cached_mesh_content_id = u64::MAX;
|
||||
inner.cached_face3d_key = (u64::MAX, false, false, u64::MAX);
|
||||
inner.cached_solid_visibility =
|
||||
(u64::MAX, [u32::MAX; 3], u64::MAX);
|
||||
inner.cached_hatch_source = None;
|
||||
inner.cached_preview_hatch_source = None;
|
||||
inner.cached_wipeout_source = None;
|
||||
inner.cached_image_source = None;
|
||||
inner.cached_text_source = None;
|
||||
inner.cached_mesh_source = None;
|
||||
inner.cached_face3d_source = None;
|
||||
inner.cached_face3d_depth_source = None;
|
||||
inner.wire_cull_key = (u64::MAX, u64::MAX, 0, 0);
|
||||
inner.hatch_lod_key = (usize::MAX, u64::MAX, 0, 0, false);
|
||||
inner.wipeout_lod_key = (usize::MAX, u64::MAX, 0, 0, false);
|
||||
inner.silhouette_key = (usize::MAX, u64::MAX, [u32::MAX; 3], false);
|
||||
inner.render_sig = u64::MAX;
|
||||
inner.forget_cached_keys();
|
||||
}
|
||||
// The MSAA / depth / resolve textures are always sized to the
|
||||
// FULL viewport rectangle (not the on-canvas-visible portion)
|
||||
@@ -1217,6 +1201,20 @@ impl shader::Primitive for Primitive {
|
||||
self.viewports.len(),
|
||||
);
|
||||
}
|
||||
// Give back what nobody is drawing. Gradual while the device is
|
||||
// healthy; immediate once it has refused an allocation, because the
|
||||
// memory a hidden viewport is holding is exactly what the visible one
|
||||
// could not get — and freeing it is what lets the operator close a
|
||||
// viewport or two and carry on instead of restarting.
|
||||
let errors_now = crate::scene::pipeline::gpu_errors_seen();
|
||||
let urgent = errors_now != errors_at_entry;
|
||||
let released = pipeline.release_idle_slots(device, &slots, urgent);
|
||||
if released > 0 && crate::perf::enabled() {
|
||||
crate::perf_record!(
|
||||
"[perf] slots-released n={released} urgent={urgent} slots={}",
|
||||
pipeline.inners.len(),
|
||||
);
|
||||
}
|
||||
report_gpu_live(pipeline);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user