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.
-
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
CAMetalLayerwithopaque = NOin a view that returnsnilfromhitTest:and sits above the hostedNSView/UIView; on Android, a translucentSurfaceViewthat is neither focusable nor clickable. -
On the render thread, with the
RenderContextthe main surface was created from (its device must already exist — create the overlay after the main surface), callOverlaySurface::newwith anOverlayTargetnaming that object and its size in physical pixels:- Apple:
OverlayTarget::CoreAnimationLayerwith theCAMetalLayer*(cfg(target_vendor = "apple")). - Android and any raw-window-handle host:
OverlayTarget::RawHandlewith the display and window handles (Android:OverlayTarget::android_ndk_windowover theANativeWindow*theSurfaceView’sSurfaceyields).
Pass the main
SurfaceRenderertoo when there is one: the overlay then builds its pipelines through the same persisted driver cache. - Apple:
-
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’sViewCommand::Overlay.command_ranges, ascending and disjoint), acquire (SurfaceRenderer::acquire), then submit with oneOverlayCutper 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 (iOSpresentsWithTransaction, a macOSCATransactioncarrying the native views’ geometry),SurfaceRenderer::submit_segmented_deferredhands them back asSegmentedPresentsforSegmentedPresents::present_allinside it.The spike’s single cut is the older shape and still works: arm the main surface with
SurfaceRenderer::set_overlay_splitso it stops drawing the overlay’s commands, callOverlaySurface::renderwithspike_cut(scene)..scene.commands().len()(seespike_cut) — its own encoder and its own submit — and present it withOverlaySurface::presentright after the main surface’s present (orOverlaySurface::take_deferred_presentbeside the main frame’sDeferredPresent). -
Resize with
OverlaySurface::resize(physical pixels) whenever the platform object’s size changes. -
Tear down by dropping the
OverlaySurfacebefore 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§
- Overlay
Cut - One overlay of a segmented frame: the
OverlaySurfacethat draws the command rangerangeof the frame’s scene, named by the differ’soverlay_id(frust_shell_common’sViewCommand::Overlay, whosecommand_rangeis thisrange). - Overlay
CutOutcome - What one
OverlayCutof a segmented frame did. - Overlay
Renderer - The overlay’s renderer, free of any surface: a retention-off
frust_engine::EngineRendererthat records a command range into a caller-supplied target cleared to transparent. - Overlay
Surface - A translucent swapchain above a hosted native view, drawing one command range of the frame — see the module docs for the host contract.
- Segmented
Outcome - What
SurfaceRenderer::submit_segmenteddid with one frame. - Segmented
Presents - 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 oneCATransaction).
Enums§
- CutServiceability
- Whether an overlay can draw a cut’s range exactly as the main surface
would —
cut_is_servable’s answer. - Overlay
Error - Why an overlay could not be created or could not draw a frame.
- Overlay
Frame - What one
OverlaySurface::record(orOverlaySurface::render) did. - Overlay
Target - The platform object an
OverlaySurfacepresents into. - Unservable
Reason - 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 afrust build --definebakes 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
rangeofcommands: 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 drawcuts: 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, seefrust-shell-android) reads its variant from here. - project_
main_ segment - Projects the main surface’s segment of
scene— every command outside the overlaycuts(main_segment_ranges) — intoout, throughfrust_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. WhatSurfaceRenderer::encode_segmentedkeeps for the main surface. - spike_
cut - The spike’s fixed cut: the command index just past the scene’s first
ClearRect, orNonewhen it records none.