pub struct SurfaceRenderer { /* private fields */ }Expand description
Per-surface renderer and lifecycle state machine.
Holds the current surface state plus the cross-call stashes one frame
needs. Constructed empty with SurfaceRenderer::new; the shell brings it
online with on_surface_created.
Implementations§
Source§impl SurfaceRenderer
impl SurfaceRenderer
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates an empty renderer in SurfacePhase::NoSurface.
No GPU work happens here; the surface is created later by the shell via
on_surface_created once a window/surface
exists (deferred window creation on desktop, surfaceCreated on Android).
Sourcepub fn set_initial_pipeline_cache_data(&mut self, data: Option<Vec<u8>>)
pub fn set_initial_pipeline_cache_data(&mut self, data: Option<Vec<u8>>)
Restores the persisted pipeline-cache blob a prior run produced via
pipeline_cache_data, to seed the engine’s
shader-pipeline compilation at the next surface install and cut warm-start
shader/pipeline compilation to near zero on Vulkan.
Builder-style (a setter rather than a new on_surface_created*
parameter) so the three surface-creation entry points — and every shell
call site — keep their signatures; the shell (persistence lands in tasks
13/14) calls this once after SurfaceRenderer::new and before the
first on_surface_created*. Has no effect in practice on adapters
without PIPELINE_CACHE (Metal/DX12): the blob is validated against
the live adapter at install time and discarded on any mismatch. Passing
None clears any restored blob (a cold start).
Caller contract (provenance): data must be bytes this renderer
itself previously produced via
pipeline_cache_data and the shell
persisted verbatim — never bytes from any other source. The framing
check downstream (frust_gpu::pipeline_cache::unframe) proves the
magic tag and the adapter fingerprint, which is provenance by
convention rather than integrity: a deliberately forged blob passing
that frame reaches wgpu’s unsafe pipeline-cache seam, whose
contract makes foreign data undefined behaviour. This method stays
safe because the obligation is a data-handling rule for the shell’s
persistence layer, not something a signature can enforce.
Sourcepub fn pipeline_cache_data(&self) -> Option<Vec<u8>>
pub fn pipeline_cache_data(&self) -> Option<Vec<u8>>
The current pipeline-cache data to persist, framed with the adapter
fingerprint so a later launch can validate it before reuse (see
frust_gpu::pipeline_cache).
None when there is no live cache to read — no surface installed, or an
adapter without PIPELINE_CACHE (Metal/DX12) — or when the driver has
produced nothing to hand back yet. The shell (tasks 13/14) writes the
returned bytes to disk and feeds them back via
set_initial_pipeline_cache_data
on the next launch.
Sourcepub fn surface_resolved_translucent(&self) -> bool
pub fn surface_resolved_translucent(&self) -> bool
Whether the live surface actually resolved to a translucent
(alpha-compositing) mode — the truth a shell’s Mode B paint contract
must key off, replacing the SurfaceAlphaRequest it asked for.
SurfaceAlphaRequest::TranslucentPreferred is a preference: the
configure step resolves it against the platform’s advertised alpha modes
and silently degrades to an opaque swapchain (with a log::warn!) when
none is available. A shell that clears its base color to TRANSPARENT
and lets platform_view slots punch their rects on the strength of the
request alone then presents black rectangles on that opaque swapchain.
Reading this after every successful on_surface_created*/
on_surface_installed — and re-reading it
after every recreate — is what makes a fallback degrade to the Mode A
contract (opaque base, no punch) instead.
false outside SurfacePhase::SurfaceReady: with no live surface
there is nothing translucent to composite against, and false is the
safe (Mode A) default — never punch a hole you can’t prove is a window.
Deliberately not named is_surface_translucent: RenderRoot (which
sits on the other side of this same call chain) already owns a method by
that name for the pushed flag, and a shell threads this value straight
into it. The signature is wgpu-free, like every other value crossing
this crate’s boundary (docs/CODE_STANDARDS.md’s wgpu-leak
anti-pattern).
Sourcepub fn gpu_pass_timings(&self) -> Option<GpuPassTimings>
pub fn gpu_pass_timings(&self) -> Option<GpuPassTimings>
The most recent frame’s real GPU time per pass, or None when this
surface produces no such measurement.
None — the gpu_q=0 case a shell reports — for every reason there is:
no live surface, a device created without
wgpu::Features::TIMESTAMP_QUERY (which a build that did not compile
perf-trace in never asks for), and the first few frames of a surface,
before the ring’s first readback has landed.
The reading lags the calling frame: a frame’s queries are mapped
without ever blocking the frame path, so what comes back is a recent
frame’s GPU cost rather than the one being recorded. In steady state one
fresh reading lands per frame, so the series is complete and offset,
not sparse — see frust_gpu::diag::TimestampRing.
Sourcepub fn phase(&self) -> SurfacePhase
pub fn phase(&self) -> SurfacePhase
The current lifecycle phase.
Sourcepub async fn on_surface_created(
&mut self,
ctx: &mut RenderContext,
window: impl Into<SurfaceTarget<'static>>,
width: u32,
height: u32,
alpha: SurfaceAlphaRequest,
) -> Result<()>
pub async fn on_surface_created( &mut self, ctx: &mut RenderContext, window: impl Into<SurfaceTarget<'static>>, width: u32, height: u32, alpha: SurfaceAlphaRequest, ) -> Result<()>
Brings the surface online (surfaceCreated/resumed): creates the
swapchain and a device-bound renderer, transitioning to
SurfacePhase::SurfaceReady.
Valid from any phase — calling it in SurfaceLost is how the shell
recovers, and calling it in SurfaceReady replaces the surface (the old
one is dropped first). window is any raw window handle the shell owns
(wgpu::SurfaceTarget); no winit dependency is imposed here.
Presentation uses vsync (PresentMode::AutoVsync), matching the
vsync-driven frame pacing the platform shells provide.
Sourcepub async fn on_surface_installed(
&mut self,
ctx: &mut RenderContext,
surface: DetachedSurface,
width: u32,
height: u32,
alpha: SurfaceAlphaRequest,
) -> Result<()>
pub async fn on_surface_installed( &mut self, ctx: &mut RenderContext, surface: DetachedSurface, width: u32, height: u32, alpha: SurfaceAlphaRequest, ) -> Result<()>
Brings the surface online from a DetachedSurface that was created on
the windowing/main thread via
RenderContext::surface_factory,
transitioning to SurfacePhase::SurfaceReady (the
render-thread split).
The desktop counterpart of on_surface_created
for the split: on_surface_created reads the window handle and
configures on one thread, but winit only yields that handle on the main
thread — so the split creates the surface there
(SurfaceFactory::create_detached_surface)
and hands the Send surface here, where the render thread that owns this
renderer/context does the device + swapchain + blitter work. Presentation
uses vsync (PresentMode::AutoVsync), matching on_surface_created.
Valid from any phase (recreation after SurfaceLost/resume replaces the
old surface — dropped first).
Sourcepub async unsafe fn on_surface_created_from_android_window(
&mut self,
ctx: &mut RenderContext,
window_ptr: *mut c_void,
width: u32,
height: u32,
alpha: SurfaceAlphaRequest,
) -> Result<()>
pub async unsafe fn on_surface_created_from_android_window( &mut self, ctx: &mut RenderContext, window_ptr: *mut c_void, width: u32, height: u32, alpha: SurfaceAlphaRequest, ) -> Result<()>
Brings the surface online from a raw ANativeWindow pointer (Android
surfaceCreated), transitioning to SurfacePhase::SurfaceReady.
Compiled unconditionally so a host cargo check --target aarch64-linux-android covers it. This is one of the framework’s
sanctioned unsafe entry points (see also
on_surface_created_from_metal_layer);
the raw-pointer handling is isolated in
frust_gpu::lifecycle::create_android_surface.
§Safety
window_ptr must be a valid, acquired ANativeWindow* that outlives the
surface (and all its SurfaceTextures). See
frust_gpu::lifecycle::create_android_surface for the full contract.
Sourcepub fn on_surface_changed(
&mut self,
ctx: &RenderContext,
width: u32,
height: u32,
)
pub fn on_surface_changed( &mut self, ctx: &RenderContext, width: u32, height: u32, )
Resizes the swapchain (surfaceChanged/Resized).
Only acts in SurfacePhase::SurfaceReady; a resize with no surface is
dropped. The renderer (and its compiled pipelines) is preserved — only
the surface config and its sized attachments are recreated. Zero
dimensions are ignored (a minimized window keeps its last valid size).
Sourcepub fn on_surface_destroyed(&mut self)
pub fn on_surface_destroyed(&mut self)
Tears the surface down (surfaceDestroyed/suspended), transitioning to
SurfacePhase::NoSurface.
Drops the RenderSurface (and its renderer) so no surface or texture
outlives the platform’s underlying window. Idempotent.
Sourcepub fn render(
&mut self,
ctx: &RenderContext,
scene: &Scene,
base_color: Color,
) -> Result<FrameOutcome>
pub fn render( &mut self, ctx: &RenderContext, scene: &Scene, base_color: Color, ) -> Result<FrameOutcome>
Encodes scene and presents it, clearing to base_color; returns the
FrameOutcome so the shell can react.
This is a thin convenience wrapper over the two-phase seam
Self::encode + Self::present: it encodes, and — unless the frame
was skipped for want of a renderable surface — presents. A caller that
wants to attribute GPU encode cost separately from the swapchain-acquire
(vsync) wait — the render-thread-split decision hinges on that split —
calls the two entry points directly and times each with
its own clock (timing stays shell-owned; this crate reads no clock — see
frust-shell-common::perf’s layering note).
In SurfacePhase::NoSurface/SurfacePhase::SurfaceLost the frame is
dropped (FrameOutcome::Skipped) — never panicking, never queueing.
On an Outdated acquire the surface is reconfigured and
FrameOutcome::Redraw asks the shell to try again; on Lost the
surface is dropped, the machine moves to SurfacePhase::SurfaceLost,
and FrameOutcome::SurfaceLost tells the shell to recreate it.
Nothing accumulates across calls: each frame re-encodes from scene.
Sourcepub fn encode(
&mut self,
ctx: &RenderContext,
scene: &Scene,
base_color: Color,
) -> Result<EncodeOutcome>
pub fn encode( &mut self, ctx: &RenderContext, scene: &Scene, base_color: Color, ) -> Result<EncodeOutcome>
Phase 1 of the frame — the encode span: take the frame’s scene. It
does not touch the swapchain, so a caller timing this call in
isolation measures encode cost with no vsync wait folded in. This copies
the display list and stashes base_color; the GPU render moves to
Self::submit (it needs the acquired swapchain texture), so
encode_us is a memcpy and nothing else.
Returns EncodeOutcome::Skipped (no work done, nothing queued) in any
phase but SurfacePhase::SurfaceReady; otherwise
EncodeOutcome::Encoded, after which Self::present finishes the
frame. Nothing accumulates across frames.
Sourcepub fn present(&mut self, ctx: &RenderContext) -> Result<FrameOutcome>
pub fn present(&mut self, ctx: &RenderContext) -> Result<FrameOutcome>
Phase 2 of the frame — the present span: a thin wrapper over the
two-phase Self::acquire + Self::submit seam, kept for callers
(and the Self::render convenience wrapper) that time present as one
span. It acquires the swapchain texture (the blocking vsync/present
wait) and, on success, renders/submits/presents it.
A caller wanting the finer acquire (blocking vsync wait) vs
submit (GPU render + queue-submit + present) attribution — to
separate GPU saturation from render cost — calls
Self::acquire and Self::submit directly, timing each with its own
clock (timing stays shell-owned; this crate reads no clock — see
frust-shell-common::perf’s layering note). present’s combined span
equals acquire + submit by construction.
Assumes Self::encode has already copied this frame’s display list.
In any phase but SurfacePhase::SurfaceReady the call is a
no-op returning FrameOutcome::Skipped. On an Outdated
acquire the surface is reconfigured and FrameOutcome::Redraw asks the
shell to try again; on Lost the surface is dropped, the machine moves
to SurfacePhase::SurfaceLost, and FrameOutcome::SurfaceLost tells
the shell to recreate it.
Sourcepub fn acquire(&mut self, ctx: &RenderContext) -> Result<AcquireOutcome>
pub fn acquire(&mut self, ctx: &RenderContext) -> Result<AcquireOutcome>
Phase 2a of the frame — the acquire sub-span: acquire the swapchain
texture (the blocking vsync/present wait, per the surface’s present mode),
classify the result, and — on a usable acquire — stash the
texture for Self::submit to blit into. Timing this call in isolation
attributes the blocking present/vsync wait separately from Self::submit’s
blit/queue-submit work — the split needed to separate GPU saturation
from blit cost.
Returns AcquireOutcome::Acquired when a texture was stashed (the
caller must follow with Self::submit); otherwise a terminal outcome —
AcquireOutcome::Reconfigured (Outdated acquire, surface reconfigured),
AcquireOutcome::Lost (surface dropped, now SurfaceLost), or
AcquireOutcome::Skipped (no renderable surface or a transient failure).
In any phase but SurfacePhase::SurfaceReady it is a no-op returning
AcquireOutcome::Skipped.
Sourcepub fn submit(&mut self, ctx: &RenderContext) -> Result<FrameOutcome>
pub fn submit(&mut self, ctx: &RenderContext) -> Result<FrameOutcome>
Phase 2b of the frame — the submit sub-span: turn the swapchain
texture Self::acquire stashed into a presented frame, then present it.
Timing this call in isolation attributes the submit work separately from
Self::acquire’s blocking vsync wait.
What the submit span contains: the whole GPU render runs HERE,
targeting the acquired swapchain texture (which does not exist until
Self::acquire) or the surface’s intermediate, then present. So the
frame’s GPU cost lands in submit_us; encode_us is a memcpy.
v3 wire mapping. The acquire_us/submit_us field names and the
wire format are unchanged (perf.rs is untouched). The swapchain
acquire (the blocking vsync wait) happens in Self::acquire and
is recorded in acquire_us, so acquire precedes the GPU render, which
lives entirely in this span.
Must follow an AcquireOutcome::Acquired result from Self::acquire
on the same frame — it consumes the stashed texture. With nothing stashed
(no prior Acquired, or the surface vanished between the two calls) it is
a no-op returning FrameOutcome::Skipped; otherwise
FrameOutcome::Rendered.
A caller that must issue the present itself, on another thread, calls
Self::submit_deferred instead.
Sourcepub fn submit_deferred(
&mut self,
ctx: &RenderContext,
) -> Result<(FrameOutcome, Option<DeferredPresent>)>
pub fn submit_deferred( &mut self, ctx: &RenderContext, ) -> Result<(FrameOutcome, Option<DeferredPresent>)>
Self::submit with the final present step handed back to the caller
instead of issued here: identical GPU work (render + queue-submit), but
the acquired swapchain frame is returned as an opaque, Send
DeferredPresent the caller presents on a thread of its choosing.
The iOS presentsWithTransaction contract is why this exists — see
DeferredPresent’s docs for the full mechanism. Everything else is
unchanged: same outcomes, same stash discipline, and
Some(DeferredPresent) accompanies exactly the
FrameOutcome::Rendered case (every other outcome carries None).
Dropping the returned handle instead of presenting it skips that frame’s
present without error.