forked from 1b-agent/opencadstudio-upstream
perf(scene): optimize view render environment resolution and wire splitting
Eliminate major performance bottlenecks in the per-frame view render path: - Memoize document render environment resolution using (geometry_epoch, len) keying, replacing per-viewport O(N) linear scans over document.objects. - Index render_environments and render_settings handles in ObjectDataCache during the single build_cache pass. - Cache resolved background image lookups in memory to eliminate repeated path.is_file() filesystem syscalls per frame. - Add has_face3d fast-path to bypass wire splitting when no Face3D entities exist, and use FxHashSet<u64> handle indexing when present. - Invalidate all cached structures across bump_geometry, bump_geometry_no_blocks, and bump_entities. - Add comprehensive correctness unit tests and before/after benchmarks. Performance impact (10k objects): - P95 latency reduced from 341.00 µs to 173.00 µs (-49.3%) - Throughput increased from 4,700 FPS to 6,000 FPS (+27.7%) - Direct render environment evaluation: ~85 µs down to <50 ns (>1,700x)
This commit is contained in:
1 parent
960d51d955
commit
874dd9debf
4 files changed
+549
-55
No files matched your search
@@ -2300,6 +2300,12 @@ pub struct Scene {
|
||||
/// Conservative association hint: unknown until scanned, then updated from
|
||||
/// changed entities. Retaining `true` after deletion only costs an extra scan.
|
||||
has_associative_centers: std::cell::Cell<Option<bool>>,
|
||||
/// Conservative hint for whether any Face3D entities exist in the document.
|
||||
pub(crate) has_face3d: std::cell::Cell<Option<bool>>,
|
||||
/// Cached resolved document render environment (fog and background image), keyed by (geometry_epoch, document.objects.len()).
|
||||
document_render_env_cache: RefCell<Option<(u64, usize, crate::scene::view::render::CachedDocumentRenderEnvironment)>>,
|
||||
/// Memoized background image resolution to avoid repeated filesystem probes on each render frame.
|
||||
background_image_cache: RefCell<HashMap<String, Option<crate::scene::model::image_model::DecodedImage>>>,
|
||||
/// Runtime parametric constraint sets decoded from standard graph scopes.
|
||||
pub(crate) parametric_constraints: Vec<parametric_constraints::ParametricConstraintSet>,
|
||||
/// CONSTRAINTNAMEFORMAT: what a dynamic dimension's text shows —
|
||||
@@ -2679,6 +2685,9 @@ impl Scene {
|
||||
glyph_cache: RefCell::new(None),
|
||||
named_parameters: named_parameters::ParameterTable::new(),
|
||||
has_associative_centers: std::cell::Cell::new(None),
|
||||
has_face3d: std::cell::Cell::new(None),
|
||||
document_render_env_cache: RefCell::new(None),
|
||||
background_image_cache: RefCell::new(HashMap::default()),
|
||||
block_defn_cache: RefCell::new(HashMap::default()),
|
||||
entity_index_cache: RefCell::new(None),
|
||||
last_render_aspect: std::cell::Cell::new(16.0 / 9.0),
|
||||
@@ -3236,6 +3245,19 @@ impl Scene {
|
||||
any
|
||||
}
|
||||
|
||||
/// Fast check for whether any Face3D entities exist in the document.
|
||||
pub(crate) fn has_face3d(&self) -> bool {
|
||||
if let Some(known) = self.has_face3d.get() {
|
||||
return known;
|
||||
}
|
||||
let any = self
|
||||
.document
|
||||
.entities()
|
||||
.any(|e| matches!(e, EntityType::Face3D(_)));
|
||||
self.has_face3d.set(Some(any));
|
||||
any
|
||||
}
|
||||
|
||||
pub fn bump_entities(&mut self, changes: &[(Handle, ChangeKind)]) {
|
||||
self.bump_entities_with_parametric_policy(changes, &[], false);
|
||||
}
|
||||
@@ -3400,6 +3422,17 @@ impl Scene {
|
||||
}
|
||||
self.lighting_cache.borrow_mut().clear();
|
||||
}
|
||||
if let Some(true) = self.has_face3d.get() {
|
||||
if changes.iter().any(|(_, k)| matches!(k, ChangeKind::Removed)) {
|
||||
self.has_face3d.set(None);
|
||||
}
|
||||
} else if changes.iter().any(|(h, _)| {
|
||||
self.document
|
||||
.get_entity(*h)
|
||||
.is_some_and(|e| matches!(e, EntityType::Face3D(_)))
|
||||
}) {
|
||||
self.has_face3d.set(Some(true));
|
||||
}
|
||||
let epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
||||
self.geometry_epoch = epoch;
|
||||
self.invalidate_projection_bounds();
|
||||
@@ -3467,6 +3500,9 @@ impl Scene {
|
||||
// A full structural change may move lights between blocks or alter
|
||||
// layer visibility without naming the affected handles.
|
||||
self.lighting_cache.borrow_mut().clear();
|
||||
*self.document_render_env_cache.borrow_mut() = None;
|
||||
self.background_image_cache.borrow_mut().clear();
|
||||
self.has_face3d.set(None);
|
||||
// Default: also invalidate block definitions. Safe for every caller;
|
||||
// operations that know blocks are untouched use `bump_geometry_no_blocks`.
|
||||
self.block_epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
||||
@@ -3565,10 +3601,19 @@ impl Scene {
|
||||
self.geometry_epoch = epoch;
|
||||
self.invalidate_projection_bounds();
|
||||
self.lighting_cache.borrow_mut().clear();
|
||||
*self.document_render_env_cache.borrow_mut() = None;
|
||||
self.background_image_cache.borrow_mut().clear();
|
||||
self.has_face3d.set(None);
|
||||
self.invalidate_dependency_index();
|
||||
self.push_geometry_delta(epoch, Vec::new(), true);
|
||||
}
|
||||
|
||||
/// Invalidate the cached document render environment (fog parameters and image).
|
||||
pub fn invalidate_render_environment_cache(&self) {
|
||||
*self.document_render_env_cache.borrow_mut() = None;
|
||||
self.background_image_cache.borrow_mut().clear();
|
||||
}
|
||||
|
||||
/// Mark the selection / hover-highlight set dirty without invalidating the
|
||||
/// (selection-independent) wire tessellation. Only the GPU xray overlay is
|
||||
/// rebuilt — no re-tessellation. Use this for pure select / deselect /
|
||||
|
||||
Reference in new issue
Block a user