- Sync with latest upstream dev branch - Include all map improvements: 2D/3D toggle, shadows, labels, overlays - Include platform updates: location API, audio echo cancellation - Preserve fork-specific re-exports (gltf, csg, test)
9.1 KiB
Numbered Overlays And Multi-Level Gauss Blur
Goal
Replace the current single overlay channel with numbered overlay levels, then add two Gauss capture levels:
- Gauss level 0: captures the base window scene before any overlay.
- Gauss level 1: captures the base scene plus overlay level 0.
This lets glass on overlay level 0 blur/lens the app background, while glass on overlay level 1 can blur/lens the already-composited level-0 UI. It also keeps the rule clear: a glass surface samples only completed lower levels, never itself or peers in the same level.
Current State
The current overlay path is singular:
draw/src/cx_2d.rsstores oneoverlay_id, oneoverlay_pass_id, andoverlay_draw_depth.draw/src/overlay.rsowns one overlay rootDrawList.draw/src/draw_list_2d.rs::begin_overlay_reuse()appends a widget draw list to that one overlay root.widgets/src/window.rsowns oneoverlay: Overlay.widgets/src/gauss_view.rs::request_window_gauss()only succeeds whilecx.is_drawing_overlay()is true.Window::begin()chooses either the normal pass or thegauss_scenecapture pass, then points the one overlay root at the final window pass.
That means all overlay widgets are in one ordering bucket. A glass popup can sample the base app scene, but another glass layer above it cannot sample the popup as part of its blurred backing. Overlapping glass in the same overlay level also cannot produce nested blur because all glass is sampling the same lower snapshot.
Proposed Model
Introduce ordered overlay levels:
pub type OverlayLevel = u8;
pub const OVERLAY_LEVEL_BASE: OverlayLevel = 0;
pub const OVERLAY_LEVEL_FLOATING: OverlayLevel = 1;
pub const OVERLAY_LEVEL_TOP: OverlayLevel = 2;
Level order is strict:
- Main scene
- Overlay level 0
- Overlay level 1
- Overlay level 2 and higher
Within a level, existing append order and begin_overlay_last behavior still apply. Across levels, numeric level decides ordering.
Public API Shape
Keep existing calls as level-0 compatibility:
draw_list.begin_overlay_reuse(cx); // means level 0
draw_list.begin_overlay_last(cx); // means level 0, last within level 0
Add explicit level APIs:
draw_list.begin_overlay_level_reuse(cx, level);
draw_list.begin_overlay_level_last(cx, level);
Widgets that own overlay draw lists should gain a live property:
#[live(0)]
overlay_level: u8,
Then Tooltip, PopupNotification, PopupMenu, Modal, dock ghost overlays, and the new glass layer can choose where they belong without custom render code.
Render Context Changes
Replace the single overlay target fields in Cx2d with a small overlay target table plus current overlay draw state:
pub struct OverlayTarget {
pub draw_list_id: DrawListId,
pub pass_id: Option<DrawPassId>,
}
pub struct Cx2d<'a, 'b> {
overlay_targets: SmallVec<[Option<OverlayTarget>; 4]>,
current_overlay_level: Option<OverlayLevel>,
overlay_draw_depth: usize,
...
}
Required helpers:
pub fn is_drawing_overlay(&self) -> bool;
pub fn current_overlay_level(&self) -> Option<OverlayLevel>;
pub fn overlay_target(&self, level: OverlayLevel) -> Option<&OverlayTarget>;
DrawList2d::begin_overlay_level_* should:
- Look up the target for the requested level.
- Set this draw list's pass to the target pass, falling back to the current pass if no explicit pass exists.
- Store this draw list under that level's overlay root.
- Set
current_overlay_levelwhile drawing. - Increment/decrement
overlay_draw_depthas today.
Overlay Stack
Replace or wrap Overlay with an OverlayStack owned by Window:
pub struct OverlayStack {
levels: Vec<Overlay>,
}
Responsibilities:
- Allocate one root overlay draw list per level.
- During window begin, publish all active overlay targets into
Cx2d. - During window end, append each overlay root in ascending level order.
- Flush stale sublists per level, using the same redraw-id cleanup as today's single
Overlay::end().
Backward compatibility can be kept by making Overlay a thin level-0 wrapper or by preserving Overlay::begin() as OverlayStack::begin_level(0).
Two Gauss Capture Levels
Extend the per-window Gauss state from one request/snapshot to two indexed capture states:
pub const WINDOW_GAUSS_LEVELS: usize = 2;
struct GaussCaptureState {
requested_last_frame: bool,
requested_this_frame: bool,
capture_active: bool,
snapshot: Option<GaussBlurSnapshot>,
}
struct GaussWindowEntry {
generation: u64,
levels: [GaussCaptureState; WINDOW_GAUSS_LEVELS],
}
Add APIs:
pub fn request_window_gauss_level(cx: &mut Cx2d, level: usize) -> Option<GaussBlurSnapshot>;
pub fn request_window_gauss(cx: &mut Cx2d) -> Option<GaussBlurSnapshot>;
Default behavior:
- If drawing overlay level 0,
request_window_gauss()requests Gauss level 0. - If drawing overlay level 1 or higher,
request_window_gauss()requests Gauss level 1. - Explicit
request_window_gauss_level()exists for advanced widgets and tests.
This mapping keeps existing GaussRoundedView source-compatible while allowing higher overlays to sample a later scene.
Window Render Order
When no Gauss is requested, rendering can remain almost identical:
- Draw main scene to the window pass.
- Draw overlay roots in numeric order.
When only Gauss level 0 is requested:
- Draw main scene into
gauss_stack[0].scene. - Build blur chain 0.
- Draw scene 0 texture to the window pass.
- Draw overlay level 0+ to the window pass. Level-0 glass samples snapshot 0.
When Gauss level 1 is requested:
- Draw main scene into
gauss_stack[0].scene. - Build blur chain 0.
- Draw/copy scene 0 into
gauss_stack[1].scene. - Draw overlay level 0 into
gauss_stack[1].scene. Level-0 glass samples snapshot 0. - Build blur chain 1 from
gauss_stack[1].scene. - Draw scene 1 texture to the window pass.
- Draw overlay level 1+ to the window pass. Level-1 glass samples snapshot 1.
This avoids feedback loops:
- Level 0 never samples itself.
- Level 1 samples base plus completed level 0.
- Same-level glass overlap is still not nested blur, by design.
Gauss Stack Ownership
Window currently owns one GaussStack. Change that to two stacks:
gauss_stacks: [GaussStack; WINDOW_GAUSS_LEVELS],
To reduce duplicated code:
- Keep
GaussStackas the render-texture/mip-chain owner. - Add a helper that renders one stack from an input draw phase.
- Reuse existing
DrawGaussDownsample,DrawGaussUpsample, andDrawGaussScene.
The second stack needs a way to start with the previous level's scene texture. A simple first implementation can draw gauss_stack[0].scene_texture into gauss_stack[1].scene with DrawGaussScene, then draw overlay level 0 over it.
Widget Migration
Add overlay_level to:
PopupNotificationTooltipPopupMenuModal- Dock drag ghost overlay
mod.widgets.glass.Layer
Defaults:
- Existing behavior stays level 0.
- Menus/tooltips that must float over glass app chrome can opt into level 1.
- Debug/drag/cursor-like overlays can use level 2 or
begin_overlay_level_last().
Compatibility Rules
- Existing apps keep working because
begin_overlay_reuse()maps to level 0. - Existing
GaussRoundedViewkeeps working becauserequest_window_gauss()maps from current overlay level. - Non-overlay Gauss requests should still return
None. - Existing single-level popup demos should look unchanged.
Tests And Validation
Add focused tests or demos:
- Base scene only: no overlay, no Gauss request, same as current render.
- Level-0 glass over detailed background: samples Gauss level 0.
- Level-1 glass popup over level-0 glass nav: samples Gauss level 1 and visibly blurs/lenses the nav.
- Same-level overlapping glass: does not recursively blur peers.
begin_overlay_last()remains last only within its level.- Stale overlay draw lists are removed independently per level.
Runtime validation should use Studio release runs and screenshots, because this is a UI/render-stack change.
Implementation Phases
- Add numbered overlay plumbing to
Cx2d,DrawList2d, andOverlayStack, keeping old APIs as level-0 wrappers. - Migrate
Windowfrom oneOverlaytoOverlayStackwith one level enabled first. - Add
overlay_levelfields to overlay widgets and verify old demos are unchanged. - Refactor Gauss window global state to indexed capture states.
- Add two
GaussStacks toWindowand implement level-1 capture/composite. - Update
request_window_gauss()to choose a snapshot from the current overlay level. - Build a small demo with level-0 app chrome and level-1 popup glass to prove nested blur.
Open Decisions
- Maximum overlay levels: fixed small array versus dynamic vector. A fixed small array is simpler and likely enough initially.
- Whether level 2 should get a third Gauss capture later. For now it should render above level 1 while sampling level-1's snapshot if it uses glass.
- Whether pass compositing should draw full scene textures at each capture level or draw only overlay deltas. Full scene textures are simpler and safer first; delta compositing can be optimized later.