pub struct EngineRenderer { /* private fields */ }Expand description
One surface’s 2D render engine: a scene in, recorded passes out.
Create one per surface and keep it across frames — the retained scene compiler, gradient cache, pipeline cache, resource textures and intermediate pool are the reason a steady-state frame allocates nothing.
Implementations§
Source§impl EngineRenderer
impl EngineRenderer
Sourcepub fn new(
device: &Device,
caps: &TierCaps,
format: TextureFormat,
pipeline_cache: Option<&PipelineCache>,
) -> Result<Self, EngineError>
pub fn new( device: &Device, caps: &TierCaps, format: TextureFormat, pipeline_cache: Option<&PipelineCache>, ) -> Result<Self, EngineError>
Builds a renderer for format targets on caps’ adapter, warming
every engine pipeline in the background.
pipeline_cache is the host’s persisted driver cache when it has one —
None on every backend but Vulkan.
§Errors
EngineError::AtlasError when the adapter’s resource_texture_dim
is not a power of two of at least [MIN_RESOURCE_TEXTURE_DIM]. The
strip shader reconstructs a resource texture’s width as 1 << bits and
an upload’s row stride must satisfy wgpu’s copy alignment, so neither
the shader’s addressing nor the uploads would be valid otherwise.
Sourcepub fn with_image_sampler(
device: &Device,
caps: &TierCaps,
format: TextureFormat,
pipeline_cache: Option<&PipelineCache>,
image_sampler: bool,
) -> Result<Self, EngineError>
pub fn with_image_sampler( device: &Device, caps: &TierCaps, format: TextureFormat, pipeline_cache: Option<&PipelineCache>, image_sampler: bool, ) -> Result<Self, EngineError>
Self::new with the strip program family stated rather than read
from FRUST_ENGINE_IMAGE_SAMPLER — the programmatic counterpart of
that switch, which only sets the family Self::new picks.
With image_sampler set, every strip program that reads atlas images
filters a bilinear one through a hardware sampler instead of four
textureLoad taps (see crate::gpu::shader_src’s The two program
families), and every strip bind group carries the one linear,
clamp-to-edge sampler this renderer creates for it. A family is a
construction-time choice rather than a per-frame setter because it
decides which programs are compiled and warmed: a caller comparing the
two builds one renderer of each.
§Errors
As Self::new.
Sourcepub fn format(&self) -> TextureFormat
pub fn format(&self) -> TextureFormat
The target format this renderer warmed its pipelines for.
Sourcepub fn image_sampler(&self) -> bool
pub fn image_sampler(&self) -> bool
Whether this renderer’s strip programs filter bilinear atlas images
through the hardware sampler (see Self::with_image_sampler).
Sourcepub fn caps(&self) -> &TierCaps
pub fn caps(&self) -> &TierCaps
The adapter capabilities every sizing decision is made against.
Sourcepub fn targets(&self) -> &IntermediateTargets
pub fn targets(&self) -> &IntermediateTargets
The pool the engine’s off-screen intermediates come from.
Sourcepub fn page_config(&self) -> PageConfig
pub fn page_config(&self) -> PageConfig
The bounds this renderer sizes intermediate layer pages between.
Sourcepub fn atlas_budget(&self) -> AtlasBudget
pub fn atlas_budget(&self) -> AtlasBudget
The atlas geometry image residency allocates within.
Derived from the adapter in Self::new, so this is the tier’s budget
narrowed to what the adapter can create — not a constant.
Sourcepub fn set_atlas_budget(&mut self, budget: AtlasBudget)
pub fn set_atlas_budget(&mut self, budget: AtlasBudget)
Re-budget image residency, dropping every image currently resident and the atlas array holding them.
An atlas rectangle only means anything against the geometry it was allocated in, so a new budget invalidates every one already handed out — which is why the array, the registry of where each image lives and the bind groups naming that array all go in the same step, and each image re-uploads on the next frame that draws it. A start-up or adapter-change operation, never a per-frame one.
Sourcepub fn set_image_residency(&mut self, images: ImageResidency)
pub fn set_image_residency(&mut self, images: ImageResidency)
Replace image residency wholesale, on the same invalidation terms as
Self::set_atlas_budget.
The programmatic counterpart to FRUST_ENGINE_NO_ATLAS: an
[ImageResidency::disabled] residency takes both atlas classes out of
the frame — images are skipped and every glyph is drawn as outline
strips — without a process-global environment variable, which is what a
caller comparing the two paths on one device needs.
Sourcepub fn refused_atlas_regions(&self) -> u64
pub fn refused_atlas_regions(&self) -> u64
How many atlas regions this renderer has declined to write or clear.
Zero on every sound frame: residency allocates inside the budget the array is created at, and the array is grown to the depth the frame reports before its regions are written, so a refusal means those two went out of agreement. The count exists so that disagreement is measurable rather than silent — a refused write is a region the frame believed it had filled.
Sourcepub fn finish_warm_up(&mut self, device: &Device)
pub fn finish_warm_up(&mut self, device: &Device)
Finishes pipeline warm-up on the calling thread, returning only once every engine pipeline exists.
Warm-up is started in the background by Self::new and normally
needs no attention. Two callers want it forced: a host that must not
let the first frame pay for a compile, and anything about to drop the
wgpu::Device shortly after building a renderer — the warm-up worker
holds its own handle on that device, and tearing it down while the
worker is mid-compile is a driver-level hazard rather than a clean
cancellation.
Each variant is requested through the cache, which builds a queued one inline and waits for one the worker has already started, so nothing is compiled twice and nothing is left for the worker to claim afterwards.
“Every” is the warm-up list: the nine Uber states, plus — unless the Uber program is forced — the warmed paint classes. A class variant outside that list is created on the first frame that draws a run of it.
Sourcepub fn set_uber_program(&mut self, uber: bool)
pub fn set_uber_program(&mut self, uber: bool)
Forces every strip run through the whole (Uber) strip program, or
lets passes split into paint-class runs again — the programmatic
counterpart of FRUST_ENGINE_UBER_PROGRAM, which only sets the value a
renderer starts with.
Takes effect from the next frame. With it set a frame draws exactly
what it drew before paint classes existed — one pipeline per pass, the
whole strip.wgsl program of this renderer’s family — which is what a
caller comparing the two needs without a process-global environment
variable.
Sourcepub fn uber_program(&self) -> bool
pub fn uber_program(&self) -> bool
Whether every strip run is drawn with the Uber program.
Sourcepub fn set_glyph_quads(&mut self, enabled: bool)
pub fn set_glyph_quads(&mut self, enabled: bool)
Draws a glyph run resolved entirely out of the glyph atlas as glyph
quads, or the general way — the programmatic counterpart of
FRUST_ENGINE_NO_GLYPH_QUADS, which only sets the value a renderer
starts with (see SceneCompiler::set_glyph_quads).
Takes effect from the next frame. Both arms draw the same pixels, which is what a caller comparing them in one process checks.
Sourcepub fn glyph_quads(&self) -> bool
pub fn glyph_quads(&self) -> bool
Whether resident glyph runs are drawn as glyph quads.
Sourcepub fn set_run_split_min(&mut self, min_run: u32)
pub fn set_run_split_min(&mut self, min_run: u32)
Sets the shortest paint-class run worth a pipeline switch of its own
(see merge_class_runs); shorter runs are merged and drawn with the
Uber program. FRUST_ENGINE_RUN_SPLIT_MIN only sets the value a
renderer starts with.
Sourcepub fn run_split_min(&self) -> u32
pub fn run_split_min(&self) -> u32
The run threshold this renderer splits passes at.
Sourcepub fn set_pass_merge(&mut self, merge: bool)
pub fn set_pass_merge(&mut self, merge: bool)
Records the surface opening as one pass, or as the clear and the
opaque strips apart from the first surface round — the programmatic
counterpart of FRUST_ENGINE_NO_PASS_MERGE, which only sets the value a
renderer starts with.
Takes effect from the next frame. Unmerged, a frame records the clear and the opaque strips as passes of their own ahead of every round (see the module doc’s The frame’s passes) and draws the same pixels, which is what a caller comparing the two arms in one process needs.
Sourcepub fn pass_merge(&self) -> bool
pub fn pass_merge(&self) -> bool
Whether the surface opening is recorded as one pass.
Sourcepub fn last_frame_passes(&self) -> &[FramePass]
pub fn last_frame_passes(&self) -> &[FramePass]
The passes the last Self::encode recorded into the caller’s
encoder, in recording order.
Empty after a refused frame, which records no pass at all. A diagnostic: how many passes a frame costs is the number a tile-based GPU pays a whole-framebuffer load and store for, so it is what a test pins and a capture is read against.
Sourcepub fn compiled_pipelines(&self) -> u64
pub fn compiled_pipelines(&self) -> u64
How many render pipelines this renderer has compiled so far.
Counts distinct pipelines, which is why it is a diagnostic rather
than something to wait on: two entries of EnginePipeline::ALL that
differ only in their colour format describe the same pipeline whenever
the frame’s target format happens to equal
crate::gpu::pipelines::INTERMEDIATE_FORMAT, and the cache compiles
that one variant once. Use Self::finish_warm_up to wait.
Sourcepub fn set_depth_pre_cleared(&mut self, pre_cleared: bool)
pub fn set_depth_pre_cleared(&mut self, pre_cleared: bool)
States whether a caller-supplied depth attachment already holds the
depth this frame should test against — see
DepthAttachment::set_pre_cleared.
Sourcepub fn depth_pre_cleared(&self) -> bool
pub fn depth_pre_cleared(&self) -> bool
Whether a caller-supplied depth attachment is treated as already populated.
The statement is sticky and set once, so a host driving several surfaces (or re-establishing one after a device loss) can read back what this renderer is on rather than tracking it a second time.
Sourcepub fn bind_texture(
&mut self,
id: SceneTextureId,
size: (u32, u32),
view: TextureView,
) -> Option<TextureView>
pub fn bind_texture( &mut self, id: SceneTextureId, size: (u32, u32), view: TextureView, ) -> Option<TextureView>
Registers a caller-owned texture so a
Command::SceneTexture naming
id draws it, returning whatever was registered under id before.
id is the SceneTextureId the texture minted for itself
(frust_gpu::Texture::as_scene_texture), and size is its extent in
texels — the rectangle a display list’s destination is mapped onto.
view must be a non-array 2D view of a float-sampleable texture
carrying wgpu::TextureUsages::TEXTURE_BINDING; wgpu rejects
anything else when the frame’s bind group is built.
Both halves of the registration land here: the view a pass samples and the extent the compiler composes a paint transform against. Registering is idempotent — re-registering the same id replaces the view and drops the bind groups naming the old one.
An extent past u16::MAX on either axis, or a zero one, registers
nothing and answers None: the record the shader reads packs the
source region into u16 halves, so there is no honest rectangle to
name. Scenes drawing that id go on drawing nothing.
Sourcepub fn unbind_texture(&mut self, id: SceneTextureId) -> Option<TextureView>
pub fn unbind_texture(&mut self, id: SceneTextureId) -> Option<TextureView>
Removes the texture registered under id, returning its view.
Sourcepub fn bound_texture(&self, id: SceneTextureId) -> Option<&TextureView>
pub fn bound_texture(&self, id: SceneTextureId) -> Option<&TextureView>
The view registered under id, if any.
Sourcepub fn bound_texture_count(&self) -> usize
pub fn bound_texture_count(&self) -> usize
How many external textures are currently bound.
Sourcepub fn resize(&mut self, device: &Device, width: u32, height: u32)
pub fn resize(&mut self, device: &Device, width: u32, height: u32)
Releases everything sized against the old surface extent and re-establishes what the next frame needs at the new one.
Pipelines, shader modules, the gradient cache and the resource textures are all extent-independent and deliberately survive: a resize must not cost a pipeline rebuild or a ramp re-bake. What goes is the intermediate pool’s parked entries — every one keyed on an extent nothing will ask for again — and the engine-owned depth attachment, which has to match its colour attachment exactly. Reallocating the depth buffer here rather than on the next frame keeps it off the frame path.
Every retained page goes too: a page holds its body as it was laid out for the old extent, and the resident-key ledger published after the next frame reports the empty store, so every body is painted (and re-recorded) at the new one.
Sourcepub fn retained_ledger(&self) -> impl Iterator<Item = RetainedEntry> + '_
pub fn retained_ledger(&self) -> impl Iterator<Item = RetainedEntry> + '_
The retained pages this renderer holds resident after its last frame —
the renderer’s half of the resident-key ledger (see
crate::cache::retained), in no particular order.
Sourcepub fn retained_frame(&self) -> u64
pub fn retained_frame(&self) -> u64
The retained store’s frame clock — how many frames it has planned — for a ledger’s diagnostic frame stamp.
Sourcepub fn retained_generation(&self) -> u64
pub fn retained_generation(&self) -> u64
A counter advanced whenever what Self::retained_ledger reports
changes; a host publishing the ledger only when it moved publishes
every change and nothing else.
Sourcepub fn take_retained_refusal(&mut self) -> bool
pub fn take_retained_refusal(&mut self) -> bool
Whether a frame was refused with EngineError::RetainedKeyMissing
(and the store cleared) since the last call, clearing the flag. A host
that sees true owes the widget tree one more paint against the
now-empty ledger: the refused frame presented nothing.
Sourcepub fn clear_retained(&mut self)
pub fn clear_retained(&mut self)
Drop every retained page — what a surface recreate or a device-scale change calls for. The next ledger read reports the empty store.
Sourcepub fn retained_budget(&self) -> RetainedBudget
pub fn retained_budget(&self) -> RetainedBudget
The byte budget the retained store is held to.
Sourcepub fn set_retained_budget(&mut self, budget: RetainedBudget)
pub fn set_retained_budget(&mut self, budget: RetainedBudget)
Re-budget the retained store, evicting least recently used pages until what it holds fits.
Sourcepub fn set_retained_layers(&mut self, enabled: bool)
pub fn set_retained_layers(&mut self, enabled: bool)
Set whether this renderer records retained brackets at all — the
programmatic counterpart of FRUST_ENGINE_NO_LAYERS (see
SceneCompiler::set_retained_layers). Turning it off clears the
store, since nothing would ever draw from it again.
Sourcepub fn retained_layers(&self) -> bool
pub fn retained_layers(&self) -> bool
Whether this renderer records retained brackets.
Sourcepub fn warm_up_finished(&self) -> u64
pub fn warm_up_finished(&self) -> u64
How many pipelines the warm-up worker has finished building.
Sourcepub fn end_frame(&mut self, _queue: &Queue)
pub fn end_frame(&mut self, _queue: &Queue)
Closes the frame out: ages the intermediate pool by one frame and evicts the gradient cache down to its capacity.
Call once per frame, after the frame’s commands have been submitted and including frames that drew nothing — those are the frames a parked intermediate ages on. Gradient eviction compacts the packed LUT buffer and rewrites the offsets of the survivors, which is why it belongs at the frame boundary rather than mid-frame, where it would invalidate offsets the frame’s own encoded paints already carry.
_queue is part of the signature because the end-of-frame maintenance
this method owns grows queue writes as the engine does (an atlas region
cleared after the frame that consumed it, in the reference renderer);
it has none of them yet.
Sourcepub fn encode(
&mut self,
device: &Device,
queue: &Queue,
encoder: &mut CommandEncoder,
scene: &Scene,
target: EngineTarget<'_>,
base_color: Color,
root: Affine,
) -> Result<(), EngineError>
pub fn encode( &mut self, device: &Device, queue: &Queue, encoder: &mut CommandEncoder, scene: &Scene, target: EngineTarget<'_>, base_color: Color, root: Affine, ) -> Result<(), EngineError>
Compiles scene and records the frame’s passes into encoder.
root is applied ahead of every command’s own transform and
base_color is what the target is cleared to before anything is drawn.
Neither the encoder nor the queue is submitted — see the module header.
§Errors
EngineError::TargetTooLarge for a target outside the u16 device
grid the strip pipeline addresses, EngineError::InvalidTransform for
a non-finite transform, EngineError::InvalidGeometry for non-finite
command geometry (rect extents, radii, path points, stroke or dash
values), EngineError::SchedulerEscalation for a layer shape the
engine’s scheduler does not serve,
EngineError::IntermediateTextureTooLarge for a layer no page can be
sized to, EngineError::AlphaCapacity when a frame’s coverage
outgrows the alpha texture, and EngineError::PaintCapacity when its
encoded paints or colour ramps outgrow theirs. Every one of them is
returned before anything is recorded, uploaded, allocated or submitted,
so a refused frame leaves encoder exactly as it was found and the
renderer’s own resources — the atlas array included — exactly as they
were. That is what lets the caller skip the frame cleanly (nothing is
presented and the previously presented content persists) rather than
present it half-drawn, and what keeps a refused frame’s image uploads
alive for the next frame that is not refused.
Sourcepub fn encode_traced(
&mut self,
device: &Device,
queue: &Queue,
encoder: &mut CommandEncoder,
scene: &Scene,
target: EngineTarget<'_>,
base_color: Color,
root: Affine,
timestamps: FrameTimestamps<'_>,
) -> Result<(), EngineError>
pub fn encode_traced( &mut self, device: &Device, queue: &Queue, encoder: &mut CommandEncoder, scene: &Scene, target: EngineTarget<'_>, base_color: Color, root: Affine, timestamps: FrameTimestamps<'_>, ) -> Result<(), EngineError>
Self::encode, with each pass’s GPU time stamped into timestamps.
The one difference is the sink: every pass this records asks
timestamps for its own timestamp_writes and takes None for an
answer, so a frame encoded with FrameTimestamps::inert — which is
exactly what Self::encode passes — records byte-identical work.
Which pass is charged to which span is EngineSpan’s own
documentation; the host owns the ring behind the sink and reads the
frame’s spans back out of it some frames later (see
frust_gpu::diag::TimestampRing).
§Errors
Exactly Self::encode’s, on exactly its terms — a refused frame has
recorded no pass, so it has taken no timestamp either and the host
abandons the ring’s slot rather than mapping it.
Sourcepub fn record_range(
&mut self,
device: &Device,
queue: &Queue,
encoder: &mut CommandEncoder,
scene: &Scene,
range: Range<usize>,
target: EngineTarget<'_>,
base_color: Color,
root: Affine,
timestamps: FrameTimestamps<'_>,
) -> Result<RangeCut, EngineError>
pub fn record_range( &mut self, device: &Device, queue: &Queue, encoder: &mut CommandEncoder, scene: &Scene, range: Range<usize>, target: EngineTarget<'_>, base_color: Color, root: Affine, timestamps: FrameTimestamps<'_>, ) -> Result<RangeCut, EngineError>
Self::encode_traced over the commands of scene in range alone,
cut out by project_command_range — the overlay spike’s way of
drawing one segment of a frame into a target of its own (render-model
Phase 4, decision D10).
The range is projected into a scene this renderer keeps for the
purpose (its allocation reused from call to call), carrying
DrawRetaineds only when this renderer retains
(Self::retained_layers) — a renderer with retention off refuses a
frame carrying one. The bracket rule is project_command_range’s.
target, base_color (an overlay passes transparent), root and
timestamps mean exactly what they mean to Self::encode_traced.
A range covering the whole scene is still projected (and re-recorded),
so a caller that draws the whole frame calls Self::encode_traced
instead; this method is never on a path that does.
§Errors
Exactly Self::encode_traced’s, over the projected scene.
Sourcepub fn atlas_render_report(&self) -> AtlasRenderReport
pub fn atlas_render_report(&self) -> AtlasRenderReport
What the last frame’s render-to-atlas pass serviced.
Zero across the board on a steady-state frame: text that hit the cache on every glyph frees no rectangle, queues no pixmap and dirties no page.