forked from 1b-agent/opencadstudio-upstream
perf: account for the device memory the renderer holds
Stage 0 of the roadmap asks for a memory baseline before any more
optimisation, and explicitly not from RSS: the allocator and the driver
both retain high-water memory that RSS cannot tell apart from live
resources.
`gpu-live` counts from the sizes this renderer allocated, in the
categories large enough to decide where the next fix goes:
slots total shadow targets text_atlas wire_arena block_geometry
It is partial by construction and says so, rather than presenting itself
as a total. Hatches, images, meshes and Face3D are not counted yet.
Three of the numbers needed a size that was not reachable before: the
block-geometry chunk records its bytes at build because in storage mode
the buffer belongs to the bind group, the arena exposes its allocation
rather than its occupied prefix — headroom is what a budget has to answer
for — and the text atlas reports its `R8` texel count.
Emitted only when a number moves. `prepare` runs thousands of times in a
session and these change a handful of times, so a per-frame line would
bury the log it is meant to explain.
This answers the question the previous fix left open: whether the arena's
52 MiB per slot, the 16 MiB shadow map every slot allocates whether or not
shadows are on, or the render targets dominate once a paper layout has
four or five viewports on screen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a9a2c82478
commit
d81a858040
5 files changed
+142
No files matched your search
@@ -4727,6 +4727,80 @@ fn aabb_below_pixel(
|
||||
(max_px - min_px).max(max_py - min_py) < threshold_px
|
||||
}
|
||||
|
||||
/// Device bytes a renderer is holding, counted from the sizes it allocated
|
||||
/// rather than from process RSS — the allocator and the driver both keep
|
||||
/// high-water memory that RSS cannot distinguish from live resources.
|
||||
///
|
||||
/// Partial by construction: it covers the categories large enough to decide
|
||||
/// where the next fix goes, and says so rather than pretending to be a total.
|
||||
#[derive(Default, Clone, Copy, PartialEq)]
|
||||
pub(crate) struct GpuLiveBytes {
|
||||
pub slots: usize,
|
||||
pub shadow: u64,
|
||||
pub render_targets: u64,
|
||||
pub text_atlas: u64,
|
||||
pub wire_arena: u64,
|
||||
pub block_geometry: u64,
|
||||
}
|
||||
|
||||
impl GpuLiveBytes {
|
||||
pub(crate) fn total(&self) -> u64 {
|
||||
self.shadow + self.render_targets + self.text_atlas + self.wire_arena + self.block_geometry
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
/// This slot's share. `alloc_size` is the real allocation, which is rounded
|
||||
/// up from the requested size and grows only.
|
||||
fn gpu_live_bytes(&self) -> GpuLiveBytes {
|
||||
// 4x MSAA colour (4 B/sample) + 4x MSAA depth-stencil (4 B/sample) +
|
||||
// one single-sample resolve target.
|
||||
const BYTES_PER_PIXEL: u64 = (MSAA_SAMPLES as u64) * 4 + (MSAA_SAMPLES as u64) * 4 + 4;
|
||||
let pixels = u64::from(self.alloc_size.width) * u64::from(self.alloc_size.height);
|
||||
GpuLiveBytes {
|
||||
slots: 1,
|
||||
// Allocated by every slot whether or not shadows are enabled.
|
||||
shadow: u64::from(SHADOW_MAP_SIZE) * u64::from(SHADOW_MAP_SIZE) * 4,
|
||||
render_targets: pixels * BYTES_PER_PIXEL,
|
||||
text_atlas: self
|
||||
.text_atlas_gpu
|
||||
.as_ref()
|
||||
.map(text_gpu::TextAtlasGpu::gpu_bytes)
|
||||
.unwrap_or(0),
|
||||
wire_arena: self
|
||||
.wire_arena
|
||||
.as_ref()
|
||||
.map(wire_arena::PersistentWireArena::gpu_bytes)
|
||||
.unwrap_or(0)
|
||||
+ self
|
||||
.wire_arena_mesh
|
||||
.as_ref()
|
||||
.map(wire_arena::PersistentWireArena::gpu_bytes)
|
||||
.unwrap_or(0),
|
||||
block_geometry: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MultiPipeline {
|
||||
/// Sum across slots, plus the caches held once for all of them.
|
||||
pub(crate) fn gpu_live_bytes(&self) -> GpuLiveBytes {
|
||||
let mut total = GpuLiveBytes {
|
||||
block_geometry: wire_gpu::block_geometry_bytes(&self.block_geometry),
|
||||
..Default::default()
|
||||
};
|
||||
for inner in &self.inners {
|
||||
let slot = inner.gpu_live_bytes();
|
||||
total.slots += slot.slots;
|
||||
total.shadow += slot.shadow;
|
||||
total.render_targets += slot.render_targets;
|
||||
total.text_atlas += slot.text_atlas;
|
||||
total.wire_arena += slot.wire_arena;
|
||||
}
|
||||
total
|
||||
}
|
||||
}
|
||||
|
||||
/// Bind one block-wire batch and draw it.
|
||||
///
|
||||
/// The two pipeline modes lay the vertex slots out differently — packed puts
|
||||
|
||||
Reference in new issue
Block a user