* View-dependent display refinement: bodies seen up close are drawn from finer display plans (#1320) Each frame the viewport works out every shown body's screen-space error (the display quality's chord tolerance in pixels where the body is nearest the eye) and picks a refinement step past the chosen display quality, each halving the chord, with hysteresis. Steps are planned off the UI thread and swapped in when ready, within a triangle budget and a cache size. Faces (GPU plan or CPU mesh), edges, silhouettes and highlights of a refined body all come from the refined plan, so a highlight stays on the drawn triangles. Planned bodies are chunked by content on the GPU, so a refinement re-evaluates only its own chunk. Display only: the reference mesh, the B-rep and exports never change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Display refinement: only collect finished plans while the view stands still, time the choice, keep the web planning one step a frame Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Format Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the Display quality note as it was (the longer one pushed the Colour vision list off the Graphics page) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8b97b9ed62
commit
d43c414b7b
7 files changed
+805
-14
No files matched your search
@@ -295,6 +295,12 @@ fn joined<'a>(blocks: impl Iterator<Item = &'a Option<Batch>>, own: Option<Batch
|
||||
(!parts.is_empty()).then_some(Batch { parts, chunk })
|
||||
}
|
||||
|
||||
/// Planned bodies end a GPU chunk one time in this many (by their plan's address), and a chunk
|
||||
/// holds at most [`MAX_CHUNK_BODIES`]: a scene of a thousand bodies takes a few dozen dispatches
|
||||
/// once, and a changed body re-evaluates a few dozen bodies, not all of them.
|
||||
const CHUNK_SPLIT: u64 = 32;
|
||||
const MAX_CHUNK_BODIES: usize = 128;
|
||||
|
||||
#[derive(Default)]
|
||||
struct Batches {
|
||||
/// The scene blocks on the GPU, by key.
|
||||
@@ -381,18 +387,25 @@ impl Batches {
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
bodies: &[PlannedBody],
|
||||
) -> Vec<PlannedDraw> {
|
||||
// Chunks end where a body's own plan says (or when full), so a body whose plan changes
|
||||
// (an edit, or a refinement as the view zooms in, #1320) re-evaluates only its own
|
||||
// chunk, not the whole scene.
|
||||
let mut chunks: Vec<Vec<&PlannedBody>> = Vec::new();
|
||||
let mut filled = 0usize;
|
||||
let mut open = false;
|
||||
for b in bodies {
|
||||
let n = b.plan.vertex_count();
|
||||
match chunks.last_mut() {
|
||||
Some(c) if filled + n <= solidcraft_gpu::MAX_BATCH => c.push(b),
|
||||
Some(c) if open && filled + n <= solidcraft_gpu::MAX_BATCH => c.push(b),
|
||||
_ => {
|
||||
chunks.push(vec![b]);
|
||||
filled = 0;
|
||||
}
|
||||
}
|
||||
filled += n;
|
||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||
std::hash::Hash::hash(&(Arc::as_ptr(&b.plan) as usize), &mut h);
|
||||
open = !std::hash::Hasher::finish(&h).is_multiple_of(CHUNK_SPLIT) && chunks.last().is_some_and(|c| c.len() < MAX_CHUNK_BODIES);
|
||||
}
|
||||
let key = |c: &[&PlannedBody]| c.iter().map(|b| Arc::as_ptr(&b.plan) as usize).collect::<Vec<usize>>();
|
||||
self.resident.retain(|k, _| chunks.iter().any(|c| key(c) == *k));
|
||||
|
||||
Reference in new issue
Block a user