Skip to main content

Module overlay

Module overlay 

Source
Expand description

Overlay surfaces — a second, translucent wgpu surface positioned above a hosted native view, drawing a subrange of the frame’s commands (render-model Phase 4, decision D10). The segmented frame (OverlayCut, SurfaceRenderer::encode_segmented, SurfaceRenderer::submit_segmented) is the general path: one scene cut into the main surface’s segment and one range per overlay, recorded into one encoder and submitted once. The spike (OVERLAY_SPIKE_ENV_VAR, FRUST_OVERLAY_SPIKE=1) is its degenerate single fixed cut, kept as it was. A renderer that is never handed a cut (and never has the spike’s split armed) draws exactly what it drew before this module existed.

§What an overlay is

A frame normally lands on one swapchain. A native view a host places above that swapchain (Mode A: macOS always, Android’s default opaque surface) covers whatever frust paints under it — a tooltip, a popover or a drag shadow that should paint over the native control cannot. An overlay is a second swapchain the host stacks above the native view, cleared to transparent every frame, onto which the commands that paint after the native view (in display-list order) are drawn instead of onto the main surface. The main surface draws the commands before the cut; the overlay draws the commands from the cut on.

§The contract a host attaches to

A host (an Android SurfaceView, an NSView/UIView hosting a CAMetalLayer) supplies the platform object; this module does the rest.

  1. Create the platform object above the hosted view, transparent and input-transparent: it must never take a touch, a click or focus (the native view under it and the main surface keep today’s input rules). On Apple, a CAMetalLayer with opaque = NO in a view that returns nil from hitTest: and sits above the hosted NSView/UIView; on Android, a translucent SurfaceView that is neither focusable nor clickable.

  2. On the render thread, with the RenderContext the main surface was created from (its device must already exist — create the overlay after the main surface), call OverlaySurface::new with an OverlayTarget naming that object and its size in physical pixels:

    • Apple: OverlayTarget::CoreAnimationLayer with the CAMetalLayer* (cfg(target_vendor = "apple")).
    • Android and any raw-window-handle host: OverlayTarget::RawHandle with the display and window handles (Android: OverlayTarget::android_ndk_window over the ANativeWindow* the SurfaceView’s Surface yields).

    Pass the main SurfaceRenderer too when there is one: the overlay then builds its pipelines through the same persisted driver cache.

  3. Every frame — the segmented frame: hand the main surface the frame’s scene with the overlays’ ranges as cuts (SurfaceRenderer::encode_segmented; the ranges are the differ’s ViewCommand::Overlay.command_ranges, ascending and disjoint), acquire (SurfaceRenderer::acquire), then submit with one OverlayCut per range, in the same order (SurfaceRenderer::submit_segmented): the main segment and every overlay’s range are recorded into one encoder and submitted once, then presented main first, overlays after, in cut order. Where every present must land in one platform transaction (iOS presentsWithTransaction, a macOS CATransaction carrying the native views’ geometry), SurfaceRenderer::submit_segmented_deferred hands them back as SegmentedPresents for SegmentedPresents::present_all inside it.

    The spike’s single cut is the older shape and still works: arm the main surface with SurfaceRenderer::set_overlay_split so it stops drawing the overlay’s commands, call OverlaySurface::render with spike_cut(scene)..scene.commands().len() (see spike_cut) — its own encoder and its own submit — and present it with OverlaySurface::present right after the main surface’s present (or OverlaySurface::take_deferred_present beside the main frame’s DeferredPresent).

  4. Resize with OverlaySurface::resize (physical pixels) whenever the platform object’s size changes.

  5. Tear down by dropping the OverlaySurface before the platform object it was built on — the same ordering the main surface keeps (drop the surface, then release the window/layer); an un-presented frame is dropped with it, never presented against a dead window.

An overlay is full-size — the same extent and origin as the main surface, so a range’s device coordinates need no offset (the differ’s v1 overlay rect is the whole window).

§Segments

The main surface draws the complement of the cuts (main_segment_ranges, projected by project_main_segment); each overlay draws its cut. Both go through frust_engine::renderer::project_command_ranges’s bracket rule, so every segment is a well-nested display list: a clip a cut falls inside clips both sides, and every drawing command of the frame lands in exactly one segment.

Only a servable cut is taken (cut_is_servable): one whose range the overlay’s engine draws exactly as the main surface would. A range holding a DrawRetained, a PushRetained bracket, a SceneTexture or a BackdropBlur, or one a retained bracket is open across (opened before the range and still open at its start, or opened inside it and still open past its end), is kept on the main surface: the main segment draws it exactly as an uncut frame would, under the native view, and that overlay is handed an empty range (CutServiceability::served_range) and clears to transparent — the same degrade a host whose overlay was refused already has. OverlayCutOutcome::serviceability says which cuts were kept and why. Without the rule such content would draw on no surface at all (the main segment excludes it, the overlay cannot draw it), or a retained page would be repainted on the overlay while the main store kept advertising the old one. A differ cut opens one command past a leading Mode B hole-punch (the ClearRect recorded at the slot’s own index), so the punch stays in the main segment, where it keeps erasing what lies under the native view; the overlay’s range holds only the content painted above the slot.

Each target compiles its own segment: the engine’s compiled frame is specific to its target’s size and root, so a segmented frame is one compile per target — the main segment’s plus one per overlay — never one shared compile. Every engine records and frust-render submits once per frame (the engine’s own atlas-growth and glyph-replay submits aside): the overlays’ passes ride the main frame’s command buffer.

§Alpha

The overlay asks for a translucent composite mode (frust_gpu::surface::resolve_alpha_mode’s preference order, Inherit then PostMultiplied then PreMultiplied) and refuses with OverlayError::NoTranslucentMode when the platform grants none: an opaque overlay would black out everything under it. It draws the engine’s premultiplied output straight into the swapchain, which is right for every mode the platform composites premultiplied — Android’s Inherit and PreMultiplied, and Metal’s PostMultiplied (Metal reads that mode premultiplied despite its name; see crate::context::choose_engine_render_path). A compositor that genuinely reads straight alpha is refused with OverlayError::StraightAlpha rather than served half-right.

§What the overlay renderer is

Its own frust_engine::EngineRenderer on the main surface’s device and queue (OverlayRenderer), warmed for the overlay’s format and built with retention off: a retained page belongs to the main surface’s store, so a DrawRetained handed to the overlay is dropped (counted in RangeCut::dropped_draw_retained) and a PushRetained body is painted inline. The range is cut by frust_engine::renderer::project_command_range — read its bracket rule for what happens to a clip, layer or retained bracket a cut falls inside. A backdrop blur handed to the overlay draws nothing (its swapchain cannot be read back), and an external-pass texture (SceneTexture) is bound in the main renderer’s registry only, so it draws nothing on the overlay either — which is why the segmented frame never hands an overlay any of them (the servability rule above) and a served cut’s RangeCut::dropped_draw_retained is always zero. The spike’s single fixed cut (OverlaySurface::render) applies no such rule. Shader quads are prepared over the whole scene by every overlay (the pre-pass is per scene, on the overlay’s own engine), so the overlay serves a quad in its range, and one outside it is rendered and never sampled there. Each overlay engine schedules its own budget.

§The sanctioned unsafe zone

OverlaySurface::new is the one unsafe function here: it turns a caller-owned raw window handle or CAMetalLayer* into a wgpu::Surface (wgpu::Instance::create_surface_unsafe), the same crossing frust_gpu::lifecycle::create_android_surface/create_metal_surface make for the main surface. Its # Safety section is the whole contract.

Structs§

OverlayCut
One overlay of a segmented frame: the OverlaySurface that draws the command range range of the frame’s scene, named by the differ’s overlay_id (frust_shell_common’s ViewCommand::Overlay, whose command_range is this range).
OverlayCutOutcome
What one OverlayCut of a segmented frame did.
OverlayRenderer
The overlay’s renderer, free of any surface: a retention-off frust_engine::EngineRenderer that records a command range into a caller-supplied target cleared to transparent.
OverlaySurface
A translucent swapchain above a hosted native view, drawing one command range of the frame — see the module docs for the host contract.
SegmentedOutcome
What SurfaceRenderer::submit_segmented did with one frame.
SegmentedPresents
The un-presented frames of one segmented frame — SurfaceRenderer::submit_segmented_deferred’s answer for a host that pairs the presents itself (iOS/macOS inside one CATransaction).

Enums§

CutServiceability
Whether an overlay can draw a cut’s range exactly as the main surface would — cut_is_servable’s answer.
OverlayError
Why an overlay could not be created or could not draw a frame.
OverlayFrame
What one OverlaySurface::record (or OverlaySurface::render) did.
OverlayTarget
The platform object an OverlaySurface presents into.
UnservableReason
Why a cut’s range cannot be drawn on an overlay and stays on the main surface — CutServiceability::Unservable’s reason.

Constants§

OVERLAY_SPIKE_ENV_VAR
The spike’s switch: FRUST_OVERLAY_SPIKE, read at compile time (option_env!, so a frust build --define bakes it into an Android or iOS app with no runtime environment) or at runtime, non-zero meaning on.

Functions§

cut_is_servable
Classifies the cut range of commands: whether an overlay can draw it exactly as the main surface would (see the module docs’ Segments).
main_segment_ranges
The ranges of a len-command scene the main surface draws when the overlays draw cuts: the complement of the cuts, ascending, empty gaps left out — 0..first.start, the gap between each cut and the next, and whatever follows the last cut.
overlay_spike_enabled
Whether the overlay spike is switched on (OVERLAY_SPIKE_ENV_VAR).
overlay_spike_value
The spike switch’s raw value when it is on, else None — a shell that offers more than one host arrangement under the spike (Android’s z-order A/B, see frust-shell-android) reads its variant from here.
project_main_segment
Projects the main surface’s segment of scene — every command outside the overlay cuts (main_segment_ranges) — into out, through frust_engine::renderer::project_command_ranges: each gap between cuts is projected on its own, so its brackets are rebalanced (a clip open across a cut closes before it and reopens after it) and the concatenation is a well-nested display list. What SurfaceRenderer::encode_segmented keeps for the main surface.
spike_cut
The spike’s fixed cut: the command index just past the scene’s first ClearRect, or None when it records none.