forked from 1b-agent/opencadstudio-upstream
Fix layout cache correctness and GPU resource recovery
This commit is contained in:
1 parent
1a4b389b0e
commit
ac8a545669
11 files changed
+352
-326
No files matched your search
@@ -653,6 +653,36 @@ impl OpenCADStudio {
|
||||
mod tests {
|
||||
use crate::app::OpenCADStudio;
|
||||
|
||||
#[test]
|
||||
fn layout_notice_skips_grid_camera_and_scene_builds() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
assert_eq!(app.automation_op(r#"{"op":"new"}"#)["ok"], true);
|
||||
assert_eq!(app.automation_op(r#"{"op":"run","cmd":"LINE 0,0 10,10"}"#)["ok"], true);
|
||||
let i = app.active_tab;
|
||||
let scene = &mut app.tabs[i].scene;
|
||||
scene.document.add_layout("Review").unwrap();
|
||||
scene.set_current_layout("Review".to_string());
|
||||
let mut viewport = acadrust::entities::Viewport::new();
|
||||
viewport.id = 2;
|
||||
viewport.width = 100.0;
|
||||
viewport.height = 50.0;
|
||||
viewport.status.is_on = true;
|
||||
scene.add_entity(acadrust::EntityType::Viewport(viewport));
|
||||
for entity in scene.document.entities_mut() {
|
||||
if let acadrust::EntityType::Viewport(viewport) = entity {
|
||||
viewport.status.grid_on = true;
|
||||
}
|
||||
}
|
||||
let before = scene.last_tess_wires.get();
|
||||
app.layout_settling = true;
|
||||
drop(app.view_main());
|
||||
assert_eq!(app.tabs[i].scene.last_tess_wires.get(), before);
|
||||
let _ = app.update(crate::app::Message::LayoutSettled);
|
||||
assert!(!app.layout_settling);
|
||||
drop(app.view_main());
|
||||
assert!(app.tabs[i].scene.last_tess_wires.get() > before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn automation_ops_round_trip() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
|
||||
+2
-10
@@ -1055,14 +1055,7 @@ pub(super) struct OpenCADStudio {
|
||||
/// `Some` while a CAD file is loading — drives the modal overlay.
|
||||
/// Cleared when the load finishes, errors, or the user cancels.
|
||||
pub(super) opening: Option<OpenProgress>,
|
||||
/// Set the moment a layout switch changes `current_layout`, cleared on the
|
||||
/// next presented frame.
|
||||
///
|
||||
/// The switch itself costs about 10 ms; the frame after it rebuilds the
|
||||
/// whole wire set and can block the main thread for seconds. While this is
|
||||
/// set the viewport is replaced by a notice, so that frame is cheap — which
|
||||
/// gives the compositor an answer, and the user something other than a
|
||||
/// stale window, immediately before the expensive one.
|
||||
/// Show a lightweight notice until the next layout redraw.
|
||||
pub(super) layout_settling: bool,
|
||||
open_job_serial: u64,
|
||||
/// Last repair or failed-open report shown in the recovery modal.
|
||||
@@ -2292,8 +2285,7 @@ pub enum Message {
|
||||
/// Timer pulse while the cursor is dwelling on a grip; drives the
|
||||
/// dwell-to-popup transition without requiring further mouse motion.
|
||||
GripDwellTick,
|
||||
/// One frame has been presented since a layout switch; the next may do the
|
||||
/// expensive rebuild.
|
||||
/// The notice redraw allows the next frame to build the scene.
|
||||
LayoutSettled,
|
||||
/// Timer pulse while a rollover hit-test is queued; fires when the
|
||||
/// cursor has been still long enough to safely run the pick.
|
||||
|
||||
@@ -3046,8 +3046,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
Message::LayoutSettled => {
|
||||
// The notice frame has been presented, so the compositor has
|
||||
// had its answer. Let the next frame build the real thing.
|
||||
// Restore the scene after the notice redraw.
|
||||
self.layout_settling = false;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
@@ -5535,9 +5535,7 @@ impl OpenCADStudio {
|
||||
.layers
|
||||
.sync_with_viewports(&doc_layers, vp_info);
|
||||
let layers_ms = perf_phase.elapsed().as_secs_f64() * 1000.0;
|
||||
// The switch is cheap; the frame that follows it is not. Draw a notice
|
||||
// for one frame first, so the compositor gets an answer before the main
|
||||
// thread disappears into rebuilding the wire set.
|
||||
// Give the layout rebuild a visible notice first.
|
||||
self.layout_settling = true;
|
||||
if perf {
|
||||
crate::perf_record!(
|
||||
|
||||
+12
-18
@@ -36,11 +36,7 @@ pub(in crate::app) const VIEWPORT_CAPTURE_BOUNDS_ID: &str = "viewport-capture-bo
|
||||
|
||||
const VIEWCUBE_HIT_SIZE: f32 = VIEWCUBE_REGION_PX;
|
||||
|
||||
/// Base surface directly under the crosshair, selection marquee, and grid overlay.
|
||||
/// In paper space, drafting surface context is unconditionally the white sheet (`tab.scene.paper_bg_color`),
|
||||
/// matching AutoCAD's model (where crosshairs and overlays adapt per context — model vs. sheet/layout —
|
||||
/// rather than per-pixel cursor sampling). This avoids flicker across sheet edges and provides a clean
|
||||
/// dark crosshair that maintains ~5.85:1 contrast against the default medium-gray desk margin (#8A8A8A).
|
||||
/// Background used by drafting overlays in model or paper space.
|
||||
fn crosshair_background(tab: &DocumentTab, is_paper: bool) -> [f32; 4] {
|
||||
if !is_paper {
|
||||
return tab.scene.bg_color;
|
||||
@@ -273,10 +269,7 @@ impl OpenCADStudio {
|
||||
// borders) and the floating content viewports blit on top.
|
||||
mark("viewport_3d");
|
||||
let viewport_3d: Element<'_, Message> = if self.layout_settling && !tab.is_start {
|
||||
// One cheap frame between the switch and the rebuild. No shader
|
||||
// widget means no scene primitive, so `prepare` has nothing to do
|
||||
// and the event loop turns — which is what stops the compositor
|
||||
// deciding the application has stopped responding.
|
||||
// Omit the shader and drafting overlays for the notice frame.
|
||||
let bg = crosshair_background(tab, is_paper);
|
||||
let lum = 0.299 * bg[0] + 0.587 * bg[1] + 0.114 * bg[2];
|
||||
let ink = if lum > 0.5 {
|
||||
@@ -376,7 +369,7 @@ impl OpenCADStudio {
|
||||
// above the crosshair overlay so it actually receives mouse events, and
|
||||
// it owns the divider resize. Only built for the Model layout.
|
||||
mark("model_input");
|
||||
let model_input_layer: Option<Element<'_, Message>> = if is_paper || tab.is_start {
|
||||
let model_input_layer: Option<Element<'_, Message>> = if is_paper || tab.is_start || self.layout_settling {
|
||||
None
|
||||
} else {
|
||||
let scene = &tab.scene;
|
||||
@@ -394,7 +387,9 @@ impl OpenCADStudio {
|
||||
};
|
||||
|
||||
mark("grid");
|
||||
let grid_overlay = {
|
||||
let grid_overlay = if self.layout_settling || tab.is_start {
|
||||
Space::new().into()
|
||||
} else {
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let model_basis = {
|
||||
let (o, ux, uy, uz) = tab.ucs_xform().axes();
|
||||
@@ -479,7 +474,9 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
};
|
||||
|
||||
mark("selection_overlay");
|
||||
let selection_overlay = {
|
||||
let selection_overlay = if self.layout_settling || tab.is_start {
|
||||
Space::new().into()
|
||||
} else {
|
||||
// Hold a single `Ref<'_, SelectionState>` for the whole overlay
|
||||
// block. The `vp_size` and `last_move_pos` reads below become
|
||||
// field accesses on `sel_ref` (no redundant `borrow()`s), and
|
||||
@@ -996,7 +993,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
};
|
||||
|
||||
mark("viewport_stack");
|
||||
let mut viewport_stack = if tab.is_start {
|
||||
let mut viewport_stack = if tab.is_start || self.layout_settling {
|
||||
// Start tab: only the welcome widget over a flat background.
|
||||
// Skip every drawing-only overlay (selection markers, snap info,
|
||||
// mouse-area capturing draw clicks, viewcube, nav toolbar, …).
|
||||
@@ -1057,7 +1054,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
.height(Fill)
|
||||
};
|
||||
|
||||
if !thumbnail_capture_clean {
|
||||
if !thumbnail_capture_clean && !self.layout_settling {
|
||||
// Per-pane input pane_grid goes ABOVE the crosshair overlay so it
|
||||
// receives mouse events (the overlay's `Hidden` cursor would otherwise
|
||||
// starve any layer beneath it). The controls bar is pushed on top of it.
|
||||
@@ -2279,10 +2276,7 @@ impl OpenCADStudio {
|
||||
// the mouse perfectly still — `ViewportMove` alone would never
|
||||
// fire again. Auto-stops once the hover clears or the popup is
|
||||
// already open.
|
||||
// One frame after a layout switch, then stop. `window::frames` fires
|
||||
// once a frame has actually been presented, which is the guarantee this
|
||||
// needs: the notice is on screen and the compositor has been answered
|
||||
// before the expensive frame starts.
|
||||
// Resume scene construction after the notice redraw.
|
||||
let layout_settle = if self.layout_settling {
|
||||
window::frames().map(|_| Message::LayoutSettled)
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user