Skip to main content

SurfaceRenderer

Struct SurfaceRenderer 

Source
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

Source

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).

Source

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.

Source

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.

Source

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).

Source

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.

Source

pub fn phase(&self) -> SurfacePhase

The current lifecycle phase.

Source

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.

Source

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).

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Trait Implementations§

Source§

impl Default for SurfaceRenderer

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, S> SimdFrom<T, S> for T
where S: Simd,

Source§

fn simd_from(_simd: S, value: T) -> T

Source§

impl<F, T, S> SimdInto<T, S> for F
where T: SimdFrom<F, S>, S: Simd,

Source§

fn simd_into(self, simd: S) -> T

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,