pub struct ShaderEffects { /* private fields */ }Expand description
Owns every GPU resource the shader-showcase feature needs: lazily-compiled
per-program pipelines and per-(program, quantized size) offscreen
targets.
Implementations§
Source§impl ShaderEffects
impl ShaderEffects
Sourcepub fn new(pipeline_cache: Option<PipelineCache>) -> Self
pub fn new(pipeline_cache: Option<PipelineCache>) -> Self
Create an empty engine seeded with an optional clone of the surface’s
wgpu::PipelineCache (used as RenderPipelineDescriptor.cache so a
persisted cache speeds first compilation). None on adapters without
PIPELINE_CACHE support (Metal/desktop), which is a plain cold start.
Sourcepub fn needs_compile(&self, id: u64) -> bool
pub fn needs_compile(&self, id: u64) -> bool
Whether program id still needs its pipeline compiled — false once it is
either compiled or known-failed. Pure predicate (the fast-path skip
ensure_pipeline consults first), unit-testable
without a device.
Sourcepub fn ensure_pipeline(&mut self, device: &Device, id: u64, wgsl: &str)
pub fn ensure_pipeline(&mut self, device: &Device, id: u64, wgsl: &str)
Lazily compile the render pipeline for program id from wgsl (the
fragment source; see the module-level shader contract). A no-op if the
program is already compiled or already known-failed.
Compilation is wrapped in a Validation error scope: a shader that fails
to validate records id as failed (skipped forever after) with a
rate-limited log::warn!, and never panics — the FFI no-panic
invariant. The validation error also surfaces through the device’s
latched uncaptured-error handler.
Sourcepub fn mark_seen(
&mut self,
live_ids: &HashSet<u64>,
live_target_keys: &HashSet<(u64, u32, u32)>,
) -> Vec<u64>
pub fn mark_seen( &mut self, live_ids: &HashSet<u64>, live_target_keys: &HashSet<(u64, u32, u32)>, ) -> Vec<u64>
Bump the frame counter and record every id in live_ids (this frame’s
distinct program ids, taken before compile/target work — so an id whose
pipeline just failed to compile is still marked seen) as last seen at
the new frame, and every key in live_target_keys (this frame’s
distinct (id, quantized w, quantized h) target keys — see
quantized_target_key) as last seen the same way. Ages out and
drops any target key that has now gone unseen for at least
MAX_UNSEEN_TARGET_FRAMES frames (its own, shorter clock —
[reapable_target_ages]), then returns every program id that has now
gone unseen for at least MAX_UNSEEN_FRAMES frames (via
[reapable_ids]), ready to hand to Self::reap.
Called once per frame regardless of whether either set is empty — a scene that stops drawing shader quads entirely must still age out and eventually reap every previously-seen id and target, not just ones still present.
Sourcepub fn reap(&mut self, stale_ids: &[u64])
pub fn reap(&mut self, stale_ids: &[u64])
Reap every resource for each id in stale_ids (a Self::mark_seen
output): every targets entry whose key’s id matches (and its
target_last_seen row), plus the compiled pipelines entry, any
failed record, and the last_seen row itself. Dropping failed
alongside the rest means a program id that reappears after being
reaped is treated as brand new: it recompiles cleanly rather than
hitting a stale skip from a compile failure that happened frames ago
(or never happened at all). The clamp-warning latch is dropped on the
same trigger: a reaped id that returns warns afresh on its first
oversized request, exactly like a brand-new program — and this reap is
also what bounds the latch set’s growth, the same way it bounds every
other map here.
Sourcepub fn ensure_target(&mut self, device: &Device, id: u64, w: u32, h: u32)
pub fn ensure_target(&mut self, device: &Device, id: u64, w: u32, h: u32)
Ensure a target exists for (id, w, h), creating it (at
quantized_target_key’s quantized, device-clamped extent) if
absent. Age-based reclaim of an unseen target — same-quantized-key
requests aside — is handled separately by Self::mark_seen, so two
distinct (quantized) sizes of the same program can coexist across
many frames rather than evicting each other every one.
A no-op if program id has no compiled pipeline (never compiled, or
compile-failed): a target is useless without the pipeline that owns its
bind-group layout, so the caller compiles first. w/h are quantized
and clamped to the device’s own max_texture_dimension_2d ceiling
(floored at 1) — a device-validity floor only, not the caller’s size
policy: a caller may still apply a tighter policy cap of its own
(e.g. frust_engine::effects::shader_quad’s 8192) before calling, but
an oversized request that reaches here creates a texture at the
device ceiling instead of tripping wgpu validation, and warns once
per distinct oversized (id, requested_w, requested_h) pair — the
warning latches the first time this exact request is clamped by the
ceiling (never merely because quantization rounded it up), then
repeats only if the request changes. A freshly created entry’s
target_last_seen row is stamped at the current frame so it starts
with a valid age even if the caller’s own Self::mark_seen call for
this frame has not run yet, and its generation is stamped from
Self::next_generation — see the module header’s “Target identity”
section.
Before minting a genuinely new key, enforces MAX_TARGETS_PER_ID:
if id already holds the cap’s worth of resident keys, its own
least-recently-seen one ([oldest_target_key_for_id]) is evicted
immediately, ahead of and independent of Self::mark_seen’s
age-based reap — see MAX_TARGETS_PER_ID’s own doc for why.
Sourcepub fn encode_pass(
&self,
encoder: &mut CommandEncoder,
queue: &Queue,
device: &Device,
id: u64,
requested: (u32, u32),
time: f32,
)
pub fn encode_pass( &self, encoder: &mut CommandEncoder, queue: &Queue, device: &Device, id: u64, requested: (u32, u32), time: f32, )
Encode one fullscreen-triangle pass for program id, requested at
requested (the caller’s exact device-space extent — never the
quantized target’s own, larger size), into its target, writing the
uniform buffer (resolution, time) first. A no-op if the program’s
pipeline or its (quantized) target is missing. Adds no queue.submit
— the caller owns encoder creation and submission ordering relative to
the frame’s own passes.
Renders into the sub-rect (0, 0)..requested of the target via
wgpu’s viewport, never the whole (possibly larger, quantized)
attachment: the resolution uniform a shader reads is requested
itself (clamped to what the target can actually hold, in the rare
case the device ceiling shrank it below the request), so a quad
samples back content sized exactly to itself even when its target is
shared with, or larger than, other nearby-sized requests.
Sourcepub fn target_texture(
&self,
device: &Device,
id: u64,
w: u32,
h: u32,
) -> Option<&Texture>
pub fn target_texture( &self, device: &Device, id: u64, w: u32, h: u32, ) -> Option<&Texture>
The offscreen texture program id was rendered into for a request at
(w, h) (looked up by quantized_target_key, device’s own
ceiling), or None if that (quantized) target does not exist (never
ensure_targeted, or the pipeline compile
failed).
The read-back seam. A consumer that draws the result instead wants
Self::target_view, since a scene-texture registration takes a view;
nothing is registered with a foreign renderer and nothing is handed
back on eviction — the pool owns the texture and the borrow lives as
long as the caller holds it. The returned texture is the whole
(possibly larger, quantized) attachment — a read-back consumer wanting
only the rendered sub-rect wants Self::target_extent alongside it.
Sourcepub fn target_view(
&self,
device: &Device,
id: u64,
w: u32,
h: u32,
) -> Option<&TextureView>
pub fn target_view( &self, device: &Device, id: u64, w: u32, h: u32, ) -> Option<&TextureView>
The full (quantized) extent view of program id’s (w, h)-requested
target — the handle a consumer registers to sample the rendered result
— or None on the same terms as Self::target_texture.
The same view the pass wrote through, rather than a fresh one per
frame: a view is a handle onto the texture, so creating one per
registration would churn a resource that never changes while its
target lives. This is a whole-texture view even where only the
(0, 0)..target_extent sub-rect was actually rendered this frame —
see Self::target_extent for the sub-rect a registration should
state instead.
Sourcepub fn target_extent(
&self,
device: &Device,
id: u64,
w: u32,
h: u32,
) -> Option<(u32, u32)>
pub fn target_extent( &self, device: &Device, id: u64, w: u32, h: u32, ) -> Option<(u32, u32)>
The extent a (w, h) request at program id was actually rendered
at this call — the sub-rect Self::encode_pass draws into, not the
(quantized, possibly larger) texture Self::target_view hands back
— or None on the same terms as Self::target_texture (no
(quantized) target exists for this id/extent at all).
Not (w, h) echoed back unconditionally: in the rare case the
device’s real ceiling constrains the target below what was asked
(w/h past device’s max_texture_dimension_2d), the answer is
clamped the same way Self::encode_pass itself clamps what it
renders, so a registration built from this value and the target’s
view always agree on what the attachment actually holds at the
caller’s requested corner.
Sourcepub fn target_generation(
&self,
device: &Device,
id: u64,
w: u32,
h: u32,
) -> Option<u64>
pub fn target_generation( &self, device: &Device, id: u64, w: u32, h: u32, ) -> Option<u64>
The generation program id’s (w, h)-requested target was created at
— bumped by Self::ensure_target every time it actually creates a
new texture at the resolved key, never on a cache hit — or None on
the same terms as Self::target_texture (no target exists for this
id/extent at all).
The identity half of a registration alongside Self::target_extent:
two targets can share an identical requested extent yet be genuinely
different GPU resources (one reaped and recreated behind a caller that
only compared extents), and this is what lets a caller (e.g.
frust_engine::effects::shader_quad::ShaderQuadPass::register) tell
them apart — see the module header’s “Target identity” section.