Skip to main content

Renderer

Struct Renderer 

Source
pub struct Renderer { /* private fields */ }
Expand description

Owns the GPU context, the built-in systems, and the loaded presets; renders one frame per call by evaluating the active preset into the active system.

Every entry point that accepts a caller’s frame delta — render, render_tapped, capture_stream — replaces a degenerate one through sanitize_frame_dt before anything below sees it, and an entry added later owes the same call.

Implementations§

Source§

impl Renderer

Source

pub fn capture_frame( &mut self, frame: &AnalysisFrame, ) -> Result<CaptureImage, RenderError>

Advance the scene clock one step and capture that single frame into an offscreen texture, returning tight RGBA (Plan 0013). Off the hot path — blocks on GPU readback; never call it from a live loop.

Source

pub fn capture_preset( &mut self, name: &str, frame: &AnalysisFrame, frames: u32, ) -> Result<CaptureImage, RenderError>

Capture preset name after advancing it frames steps from a fixed initial state, driven by a single constant frame (Plan 0013). A pure function of (name, frame, frames): the scenes are rebuilt so any stateful system (e.g. the seeded swarm particles) starts from its deterministic seed, and the scene clock resets to 0.0, so the result is independent of any earlier capture. Errors if name is not in the roster. frames is treated as at least 1.

Source

pub fn capture_preset_over( &mut self, name: &str, stimulus: &[AnalysisFrame], ) -> Result<Vec<CaptureImage>, RenderError>

Capture preset name across a time-varying stimulus (Plan 0037): one rendered frame per entry of stimulus, read back in order, so the returned images are the response while it changes rather than after it settles.

This is the primitive capture_preset cannot be: holding one frame for every step converges every smoother before the pixels are read, which makes the result identical for any [smoothing] constant (ADR-0039). capture_preset is left exactly as it was — four suites and --report consume it — and this is its sibling, sharing the same reset_for_capture seed so both are pure functions of their arguments.

The clock advances one FALLBACK_DT per entry, so index i is second i * dt of the response. An empty stimulus yields no images. Errors if name is not in the roster.

Off the hot path, and more so than its sibling — it blocks on a GPU readback per frame, not once per call. The target and readback buffer are allocated up front rather than per frame, because building GPU resources mid-sequence perturbs what the feedback stages resolve to on the DX12 software adapter.

Source

pub fn capture_preset_at( &mut self, name: &str, frame: &AnalysisFrame, at_frames: &[u32], ) -> Result<Vec<CaptureImage>, RenderError>

Advance preset name under a single constant frame and read back only the frames named in at_frames (Plan 0085 Phase 1) — the long-run primitive, and the one a horizon needs.

Its two siblings cannot serve a run of tens of thousands of frames: capture_preset reseeds from scratch on every call, so sampling k points costs O(k·N) renders, and capture_preset_over reads back every frame, so a ten-minute run at 720p would materialize ~36,000 images. This renders N frames once and holds at_frames.len() of them.

Frame numbering matches capture_audio: frame 0 is the first advanced frame, so at_frames = [n - 1] returns exactly what capture_preset(name, frame, n) returns — asserted in core/tests/suite/capture_advance.rs rather than argued, because it is the property that lets a horizon’s rows be compared with every other capture this repo takes.

Deterministic on the same terms as its siblings: scenes are rebuilt to their seed, the clock resets to 0.0, and the step is a fixed FALLBACK_DT — so a row at index k does not depend on how far the run was asked to go. Images come back in at_frames order; a repeated index yields the same frame twice rather than rendering it twice. An empty at_frames renders nothing.

Off the hot path — it blocks on a GPU readback per requested frame. The readback buffer is built once, at the first requested frame, and reused for every later one. Both halves of that matter on the DX12 software adapter, where building GPU resources mid-sequence perturbs what the feedback stages resolve to (the hazard capture_preset_over documents, and a horizon is precisely a long feedback sequence): reusing it means the perturbation happens once rather than per sample, and doing it at the first sample rather than up front is what puts the allocation at the same point in the sequence capture_preset puts it — which is what makes the two agree pixel-for-pixel on WARP as well as on hardware. It also stays independent of the horizon requested, since the first sample sits at the same frame index however long the run is.

Source

pub fn capture_stream( &mut self, name: &str, frames: u32, dt: f32, analysis: &mut dyn FnMut(u32) -> AnalysisFrame, sink: &mut dyn FnMut(u32, &CaptureImage) -> Result<(), String>, ) -> Result<(), RenderError>

Render preset name for frames frames at an injected dt, handing each frame to sink the moment it is read back (Plan 0101 / ADR-0114) — the streaming primitive, and the one an offline video render needs.

Its three siblings all return a Vec<CaptureImage>, which is exactly what a video render cannot afford: a 1080p frame is 8.29 MB, so a four-minute track at 60 fps is 119 GB of retained images. Nothing is retained here — the frame is handed to sink and dropped, so the resident set of a 14,400-frame render is the same as a 100-frame one.

It is also the only capture entry point whose step is not the fixed FALLBACK_DT. A render at --fps 30 advances the scene by 1/30 s a frame, or the visuals would run at half speed against their own soundtrack; that dt is the caller’s, exactly as it is for the live frontend (ADR-0013). At 60 fps dt is FALLBACK_DT, which is what makes a rendered frame comparable with every other capture this repo takes.

analysis supplies the AnalysisFrame for each frame index. The audio hop clock and the frame clock are different clocks and only the caller knows the mapping between them, so this deliberately does not walk PCM — unlike capture_audio, which welds one rendered frame to one analysis hop.

Deterministic on the same terms as its siblings: scenes rebuilt to their seed, the clock reset to 0.0, the salt pinned. Given a deterministic analysis the whole run is a pure function of (name, frames, dt).

Off the hot path — it blocks on a GPU readback every frame, which is also what bounds its memory: read_back polls, so each frame’s submission is retired before the next is encoded (the retention Plan 0099 measured). The target and the readback buffer are built once and reused, so a long run allocates no GPU resources mid-sequence.

A sink error stops the run and comes back as RenderError::Sink carrying the consumer’s own message.

A dt that is not finite and positive is replaced by FALLBACK_DT for the whole run (sanitize_frame_dt, ADR-0191).

Source

pub fn capture_audio( &mut self, name: &str, pcm: &[f32], format: AudioFormat, at_frames: &[u32], ) -> Result<Vec<CaptureImage>, RenderError>

Drive preset name with real audio through the real analyzer and capture the frames at at_frames (Plan 0013). The PCM is fed hop-by-hop into a fresh Analyzer (format validated at the intake boundary — the source-agnostic rule); each produced AnalysisFrame drives one rendered frame, so at_frames indexes the hop sequence (frame 0 is the first hop). Deterministic: scenes are rebuilt to their seed and the clock resets to 0, exactly like capture_preset.

This is in-memory PCM only — no file, decoder, or OS audio-source code, just like a frontend pushing samples. Returned images are in at_frames order; an index past the audio length is an error.

Source

pub fn capture_audio_after_warmup( &mut self, name: &str, pcm: &[f32], format: AudioFormat, at_frames: &[u32], warmup_hops: usize, ) -> Result<AudioCapture, RenderError>

capture_audio, with the first warmup_hops hops advanced but not rasterized (Plan 0084 Phase 3).

A warm-up hop still pushes its samples, still publishes its AnalysisFrame, and still advances the scene clock by one FALLBACK_DT — the hop happened, it just did not draw. What it skips is the render pass, which is why a caller that only needs the analyzer warm (core/tests/reactivity.rs drives WARMUP_HOPS of them per capture, at silence, and reads none of them back) stops paying a full rasterization per hop to reach a DSP state that needs no pixels.

This does not warm GPU-side scene state. Analysis is a pure function of its window and the render pass never touches the analyzer, so the published frames are bit-for-bit what they would have been — but a scene that integrates on the GPU (particles, trails, reaction-diffusion) has that many fewer steps behind it at the first rendered hop. Time-driven scenes are unaffected, since the clock advances either way.

An at_frames entry inside the warm-up span was never rendered and is an error, the same one an index past the audio length gives.

Source§

impl Renderer

Source

pub fn open_tap(&self) -> FrameTap

Open a FrameTap sized to this renderer’s configured target — the one GPU allocation a tapped run makes, so the per-frame path makes none.

The tap is fixed at the size it is built with. A resize underneath a live tap leaves the two disagreeing, and the tap wins: it is what render_tapped draws and copies against. Reopen after a resize to follow it.

Infallible: RenderContext floors both dimensions at 1 where the size enters, so there is nothing left to reject here.

Source

pub fn render_tapped( &mut self, tap: &mut FrameTap, frame: &AnalysisFrame, dt: f32, ) -> Result<Option<CaptureImage>, RenderError>

Advance the scene clock by dt real seconds, draw the active preset for frame through the same draw_frame the window presents through, and hand back the previous call’s frame out of tap.

dt is per call, which is the difference between this and capture_stream’s one fixed step: a caller that falls behind the wall clock yields fewer, correctly-timed frames rather than a picture running slow against the music.

Draws under SaltMode::Live, because a tap is a live render path and not a capture: a preset declaring seed = "random" (ADR-0051) must vary per launch here exactly as it does in the window. Both salts are equal for every preset that declares anything else, which is why a tapped frame and a capture_frame of the same preset at the same clock are still byte-identical (core/tests/suite/frame_tap.rs).

§One frame in flight, and Ok(None) is the ordinary first answer

This frame’s submission carries the copy out of the tap’s texture, the previous frame’s map is taken before it is drawn, and the caller sees frame N while frame N+1 is on the GPU. So the first call after open_tap yields Ok(None), and every later call yields a frame.

It waits only when the GPU is behind. A previous map that has landed is taken without blocking; one that has not is waited for before this frame is submitted, so at most one frame is ever queued and a caller that outruns the GPU is held to the GPU’s rate. FrameTap carries the cycle and says why the wait is the loop’s only backpressure. The same bound keeps a long run’s memory per-frame, the retention Plan 0099 measured.

A dt that is not finite and positive is replaced by one nominal step before the clock sees it (sanitize_frame_dt, ADR-0191).

Feeds the diagnostics frame clock, as render does, because a tap is the whole of a windowless run’s live output and metrics is what that run reports. Counted per frame drawn rather than per frame handed back, so the rate is the tap’s own throughput and does not read as halved on the pipeline’s first call. No capture entry point feeds it: their frames are drawn offline and would make a rate of nothing.

Times every pass it encodes, where the tap has a timer — the query set is armed around the draw, resolved into the same submission, and read back on the same poll that takes the pixels, so a tapped run costs one extra buffer copy and no extra wait (ADR-0245).

Source

pub fn drain_tap(&mut self, tap: &mut FrameTap) -> Option<CaptureImage>

Wait for the frame tap still holds and hand it back, drawing nothing.

render_tapped keeps one frame in flight, so a run that stops asking for frames leaves one behind. This is how a bounded run collects it, and how a test takes exactly one frame per call: render_tapped then this, and the pipeline is empty again.

None when nothing is in flight — an unused tap, or one already drained.

A live loop has no use for it: render_tapped already waits when the GPU is behind, and calling this per frame would wait even when it is not.

Source§

impl Renderer

The renderer-facing half: opening and closing the readback, and taking what it produced.

Every one of these delegates to the PreviewService in render::preview the renderer holds — the readback and the intermediate it copies out of are one concern with one owner, and a Renderer accessor that reached past that owner could open half of it.

Source

pub fn open_preview_readback( &mut self, width: u32, height: u32, ) -> Result<(), RenderError>

Open a non-blocking readback of the preview, yielding widthxheight frames.

Requires an open preview — the blit that fills the readback’s tap samples that intermediate and has nothing to read without one. The size is the caller’s and is answered for the life of the readback: a renderer resize rebuilds the intermediate under the blit and moves nothing here (ADR-0187).

Calling it again replaces the readback, which is how a caller changes the size it asked for.

Refused when the frames would come out at a format no consumer can be told the order of, so the announcement that follows can always be true (ADR-0187).

Source

pub fn close_preview_readback(&mut self)

Close the readback and free its staging buffer. A closed readback yields nothing and costs the frame one Option test.

Source

pub fn preview_readback_size(&self) -> Option<(u32, u32)>

The size of the frames the readback yields, or None when it is closed.

The size the caller asked for, and not the output’s. The frames are a scaled, letterboxed copy of the intermediate rather than an exact one, so this is a fixed property of the open readback: it answers the same pair across every resize, and a consumer told it once never has to be told again.

Source

pub fn take_preview_frame(&mut self) -> Option<CaptureImage>

Take the frame the readback produced, if one has landed.

A frame is available at most every other call in the steady state, and None means “not yet” rather than “never”: the caller sends what it gets and does not wait.

Source§

impl Renderer

The roster-facing half of Renderer: replacing the preset set, moving the active index, and applying the incoming preset’s structural config to its scene. An impl Renderer continuation for the same reason tier_governor is one – these read and write Roster and nothing else about the device.

Source

pub fn set_presets(&mut self, presets: Vec<Preset>)

Replace the preset roster (the standalone’s hot-reload path). An empty set is ignored so a preset directory that briefly reads empty — or whose files are all malformed — leaves the last good roster rendering (NFR 10).

Source

pub fn set_param_override( &mut self, name: &str, value: f32, ) -> Result<(), ParamError>

Hold value on name until it is cleared, shadowing whatever the active preset’s binding for it evaluates to (ADR-0176).

The name does not have to be one the preset binds: anything the active system’s vocabulary claims can be driven, and one it does not is refused here rather than dropped silently at apply time. The override survives every frame until clear_param_override, and is dropped wholesale by a preset switch and by set_presets.

Not eased. A sender moving a slider is already producing a continuous path, and a smoother between the two would make the picture lag the hand.

Source

pub fn clear_param_override(&mut self, name: &str)

Drop name’s override; its binding resumes on the next frame, easing from wherever the smoother left it rather than from the held value. A name that holds no override is a no-op.

Source

pub fn clear_param_overrides(&mut self)

Drop every override at once.

Source

pub fn cycle_preset(&mut self) -> &str

Switch to the next preset; returns its name. Dissolves rather than cuts (Plan 0023): the outgoing preset’s composite is captured on the next frame and blended out over DEFAULT_DURATION_SECS while the incoming one renders live. Every system is built at startup, so no scene is constructed here; the dissolve’s opening frames do allocate its own resources lazily — see begin_transition.

The returned name is the incoming preset’s, immediately — the frontend’s HUD should name where the show is going, not where it has been.

Source

pub fn preset_names(&self) -> impl Iterator<Item = &str>

The loaded preset names in roster order — the browse overlay’s list source (Plan 0008). Selection addresses these by absolute index.

Source

pub fn select_preset(&mut self, index: usize) -> &str

Switch to the preset at index (its absolute position in preset_names); returns the incoming name. Like cycle_preset this dissolves rather than cuts (Plan 0023 Phase 5) — the browse overlay’s select is a switch the operator watches, so it gets the same treatment as Space. An out-of-range index is a no-op (never a panic, never a wrap), so a stale index from a shrunk hot-reloaded roster is harmless.

Use select_preset_now where a blend would be wrong rather than merely unwanted.

Source

pub fn select_preset_now(&mut self, index: usize) -> &str

Jump to the preset at index with no dissolve — the instant-cut escape for paths where a blend is wrong rather than unwanted: a capture, which must stay a pure function of its inputs (NFR §6), or a test placing the roster on a known preset before measuring. Returns the now-active name; an out-of-range index is a no-op.

Source

pub fn select_preset_by_name(&mut self, name: &str) -> bool

Make the preset named name active, returning whether it was found — the by-name form of select_preset, and like it a dissolve. An unknown name leaves the active preset unchanged.

Source§

impl Renderer

Source

pub fn new( target: impl Into<SurfaceTarget<'static>>, width: u32, height: u32, opts: RendererOptions, ) -> Result<Self, RenderError>

Build a renderer drawing into target (a safe window handle — the standalone path). Starts with the embedded default presets.

opts carries the quality-tier pin and the adapter choice; RendererOptions::default() is auto (rich, governed) on whatever adapter wgpu picks for the surface.

Source

pub fn new_headless(opts: HeadlessOptions) -> Result<Self, RenderError>

Build a headless renderer that draws into offscreen textures instead of a window (Plan 0013 capture tooling). Same scenes, presets, and per-frame evaluation as the on-surface path — only the target differs. Starts with the embedded default presets.

Pinned Tier::Floor, and there is no argument to say otherwise (ADR-0045). A capture is a pure function of its inputs (NFR §6), and a baseline that moved because the machine that blessed it was fast is not a baseline — so the floor is the default by construction here rather than by every call site remembering to ask for it. Use new_headless_tiered for a deliberate rich-tier capture.

Source

pub fn new_headless_tiered( opts: HeadlessOptions, tier: Tier, ) -> Result<Self, RenderError>

A headless renderer pinned to tier — the opt-in behind the shot CLI’s --tier, for spot-checking that the rich tier’s raised budgets actually render (Plan 0044 Phase 3). Always pinned, so a capture never demotes mid-run and stays reproducible.

Source

pub fn new_headless_on( opts: HeadlessOptions, tier: Tier, adapter: &AdapterChoice, ) -> Result<Self, RenderError>

A headless renderer pinned to tier, on a named adapter (ADR-0146).

The one real headless constructor; the two above delegate here with the choice their prefer_software flag already implies, so every capture path resolves exactly the adapter it resolved before.

A live video-out needs this because the adapter is not a performance preference there: a Spout receiver can only open a sender that lives on the GPU it renders with, and on a hybrid machine a console process is handed the power-saving one. The sender’s adapter is what that constrains; this one is the renderer’s, and the two are matched by name on each side rather than by a shared index.

Source

pub fn new_headless_scaled( opts: HeadlessOptions, tier: Tier, adapter: &AdapterChoice, grid_scale: Option<GridScale>, ) -> Result<Self, RenderError>

new_headless_on with an explicit grid-scale pin (ADR-0245) — the constructor --stream --grid-scale reaches.

None is what every other headless constructor passes, and it resolves GridScale::FULL on every adapter, so a capture never depends on the class of the machine that took it. A pin is the only way a headless renderer draws its grids at less.

Source

pub fn new_headless_offline( opts: HeadlessOptions, tier: Tier, ) -> Result<Self, RenderError>

A headless renderer pinned to tier and resolving the offline sample ceiling (ADR-0140) — the one constructor shot --render reaches, and the only one in the engine that does.

Separate from new_headless_tiered rather than a flag on it, because the two answer to different bounds and only one of them may ever produce a baseline: a render walks a clip with dt injected and no display to miss, so its ceiling is memory; every other headless path is a capture, and a capture takes the live ceiling so the allocation — and with it the seeded scatter — is the one every committed baseline was blessed against.

Source

pub unsafe fn new_from_surface_target( target: SurfaceTargetUnsafe, width: u32, height: u32, ) -> Result<Self, RenderError>

Renderer targeting a surface the host owns and built the handle for — the C ABI path, and the only constructor that does not create its own surface. Starts with the embedded default presets (no ABI surface for preset selection yet).

The platform lives on the caller’s side of this seam, not here (ADR-0001, ADR-0072): the host knows what kind of window it has and builds the [wgpu::SurfaceTargetUnsafe] for it, so core stays source-agnostic and platform-free. core-cabi is the one that knows about HWND.

Auto tier: the plugin gets rich-with-governor, because the C ABI stays v4 and a plugin-side tier picker is a future ABI question rather than part of ADR-0045.

§Safety

target’s handles must be valid and must outlive this renderer.

Source

pub fn tier(&self) -> Tier

The active quality tier (ADR-0045) — what the diagnostics overlay and the shot report header name.

Source

pub fn set_tier(&mut self, tier: Tier)

Change the quality tier on the running renderer (ADR-0054).

Rebuilds the tier-dependent GPU resources — the scene roster and the composite side — against the new TierConfig, on the existing RenderContext. The device, queue, surface, preset roster, active preset, engine clock, text layer and diagnostics all survive, so the operator stays on the preset they were watching. A dissolve in flight is dropped: its two sides are GPU state built at the outgoing tier.

The visible cost is one re-accumulation of everything that accumulates — trails, reaction-diffusion state, the attractor’s deposit. That is the correct affordance rather than a defect: the operator asked for this and can see that it happened.

A no-op on a surface-less (headless) context, so ADR-0045’s by-construction guarantee that a capture is Tier::Floor survives a public mutator existing on this type at all. The condition is tier::tier_change_permitted, which is a value rather than a comment.

An explicit call pins the tier and clears the governor’s demotion latch. The latch means “the governor took a decision the operator did not ask for, and must be told about it”; once the operator has asked for something, that history is spent. ADR-0045 says the latch is never cleared — ADR-0054 narrows that to “never cleared by the governor”, and is the correction of record.

Source

pub fn grid_scale(&self) -> GridScale

The fraction of the render target the internal grids are drawn at (ADR-0245) — what the diagnostics overlay prints beside the tier.

Source

pub fn internal_grids(&self) -> InternalGrids

The internal grids this renderer resolves for its current target size: the post chain’s and the attractor’s trail field, at the resolved scale.

A report, computed on the call from the same functions the stages and the scene size themselves with, so it cannot describe a grid neither would build. The trail grid is the one the attractor takes when it draws straight into the target; behind an active post stage it draws into that stage’s grid instead, which is post.

Source

pub fn set_grid_scale(&mut self, scale: Option<GridScale>)

Change the grid scale on the running renderer (ADR-0245): Some pins it, None returns it to auto — the table’s row for this tier and adapter.

The same rebuild a tier change runs, and for the same reason: both grids are sized from the resolved tier config, so the stages and the scenes are rebuilt against the new one. The tier itself, its pin and the governor’s latch do not move.

A no-op on a surface-less (headless) context, on the rule set_tier follows: a capture’s scale is fixed at construction (new_headless_scaled), so a public mutator cannot move a baseline.

Source

pub fn set_adapter( &mut self, choice: &AdapterChoice, target: impl Into<SurfaceTarget<'static>>, ) -> Result<(), RenderError>

Move the running renderer onto another graphics adapter (ADR-0246) — the sibling of set_tier one level down.

A new adapter, device and queue come off the retained instance, a fresh surface for target — the same window the renderer was built on — is configured against the new device, and every GPU-owning member is rebuilt on it: the scene roster, the composite side, the blend, the tonemap, ink, the overlay, the text layer, and the program preview and its readback where they are open. The preset roster, the active preset, the engine clock, the now-playing banner and the diagnostics survive, so the operator stays on the preset they were watching. A dissolve in flight is dropped, as set_tier drops one. Accumulated GPU state does not survive: trail fields and simulation domains live in the old device’s memory, so the picture visibly starts afresh.

Transactional. The new device is requested and validated, and every replacement member is built on it, before the old device is released; a choice that cannot produce a device — absent, ambiguous, unable to present, or refusing a device — returns its named error with the running picture untouched. A switch can never leave the application with no renderer. Two devices are briefly alive at the commit, which is the memory cost of that guarantee.

The secondary present target is released, not carried: its swapchain and atlas belong to the old device, so a shell with a console open re-attaches it afterwards with attach_aux.

Asking for the adapter already in use is a no-op returning Ok. Refused with RenderError::Headless on a surface-less context, the guard adapter_change_permitted states as a value.

Source

pub fn active_index(&self) -> usize

The active preset’s index in the roster — what the browse overlay opens on, and what a caller checks a tier rebuild against.

Source

pub fn target_preset_index(&self) -> usize

The index the show is going to: the dissolve’s target while a transition is in flight, and active_index otherwise. This is the “where the show is going, not where it has been” convention cycle_preset already returns a name by, expressed as an index — a host checkmarking a menu wants the user’s most recent choice to be ticked immediately, not a quarter-second later.

Meaningless on an empty roster (0, like active_index); a caller that distinguishes that case reads preset_names first.

Source

pub fn tier_demoted(&self) -> bool

Whether the frame-time governor demoted this session’s tier.

The frontend reports the transition, so a demotion is announced once rather than shouted every frame — the same pattern cap_overflow is surfaced through. A pinned floor answers false; only a governed demotion sets this.

Source

pub fn set_display_hz(&mut self, hz: f32)

Tell the renderer the display’s refresh rate, which sets the frame budget the governor measures against (ADR-0045). Defaults to DEFAULT_DISPLAY_HZ; a rate that is not usable falls back to it rather than producing a degenerate budget.

Off the hot path — call it at startup and on a monitor change. The core does not read this from the platform itself: a refresh rate is a shell concern, and the whole point of the split is that core knows nothing about windows.

Source

pub fn resize(&mut self, width: u32, height: u32)

Reconfigure the surface for a new window size.

Source

pub fn open_preview(&mut self) -> Result<(), RenderError>

Open the program preview: the frame starts being drawn into an intermediate and copied to its destination, so a second consumer can sample the same pixels the show is getting.

Fails when the destination surface does not accept COPY_DST, which is the one thing that makes the copy exact. Reported rather than degraded to a sampling blit: a preview is worth less than a show whose encoded values silently changed.

Idempotent in effect — an already-open preview is rebuilt at the current size, which is also how a caller follows a resize it did not see.

Source

pub fn close_preview(&mut self)

Release the intermediate. Idempotent, and the frame path returns to drawing straight at its destination on the very next frame.

Takes any readback with it: the readback copies out of the intermediate, so one left behind would hold a staging buffer for a texture that no longer exists and never yield another frame.

Source

pub fn preview_state(&self) -> Option<((u32, u32), u64)>

The open preview’s size and identity, or None when closed.

Source

pub fn pixel_order(&self) -> Result<PixelOrder, RenderError>

The channel order every frame this renderer hands a consumer carries.

One answer for every path, because there is one source: the frame tap’s target, the capture target and the preview’s intermediate are all built at the context’s configured format, so a windowed run reports whatever the swapchain negotiated and a headless one reports HEADLESS_FORMAT. A caller announcing a frame pipe reads this instead of naming a constant — a constant is true on one of those two paths and false on the other (ADR-0187).

Err where that format has no name a consumer knows, which is a refusal to publish bytes under a guess.

Source

pub fn attach_aux( &mut self, target: impl Into<SurfaceTarget<'static>>, width: u32, height: u32, frame_latency: u32, ) -> Result<AuxPresentMode, RenderError>

Attach a secondary present target — a second window’s surface, on this renderer’s existing device (ADR-0143).

The core learns nothing about what that window is for. It presents the runs it is handed and nothing else; the shell decides their meaning. Returns the present mode the surface negotiated, so the caller can log which arm ran.

frame_latency is the secondary swapchain’s desired_maximum_frame_latency; see AuxTarget::new for what it paces and for the range it is clamped to.

An already-attached target is replaced. An Err means this adapter cannot drive that surface — the dual-GPU case — and the caller is expected to degrade rather than treat it as fatal: the show is on the primary surface, which is unaffected.

This is also the re-entry after set_adapter, which releases the attached target with the device it belonged to: a shell hands the same window back and a new surface is built on the new device, with the Err above meaning what it means on a first attach.

Source

pub fn aux_frame_latency(&self) -> Option<u32>

The secondary target’s configured frame latency, or None when detached.

Source

pub fn aux_counts(&self) -> Option<AuxCounts>

What the secondary target’s present path has done since it was attached, or None when detached.

The counts live with the target and die with it, so a caller that wants a session’s totals reads them before detach_aux.

Source

pub fn detach_aux(&mut self)

Release the secondary target, its swapchain and its text atlas. Idempotent.

Source

pub fn aux_attached(&self) -> bool

Whether a secondary target is currently attached.

Source

pub fn resize_aux(&mut self, width: u32, height: u32)

Resize the secondary target’s swapchain. No-op with none attached.

Source

pub fn aux_size(&self) -> Option<(u32, u32)>

The secondary target’s size in physical pixels, or None when detached.

Source

pub fn present_aux( &mut self, runs: &[TextRun<'_>], panels: &[Panel], ) -> Result<(), RenderError>

Draw runs on the secondary target and present it. No-op with none attached.

Deliberately not called from render: the two surfaces present independently, so a frame the output drops does not have to cost the console one, and neither surface’s state can reach the other’s.

Independent is not free. The caller decides when this runs, and in the standalone that is the display thread — so a console that stalls stalls the loop that called it, whatever the two surfaces do separately. The cost is a measurement: Plan 0147 Phase 4 put it inside noise across three frame-time regimes on an integrated Radeon, and aux_counts is what makes such a reading distinguishable from a console that never presented at all.

Source

pub fn queue_text(&mut self, runs: &[TextRun<'_>])

Queue text runs to composite over the next rendered frame; the queue is cleared after each render. The standalone fills it each frame with the active preset name and, while the browse overlay is open, its rows. A text-feature (standalone) path — the plugin/default build has no text.

Source

pub fn queue_panels(&mut self, panels: &[Panel])

Queue the panels the next frame’s text sits on; replaced by each call and cleared after each render, like queue_text. All of a frame’s panels are one draw, under the picture and the text.

Source

pub fn measure_text(&mut self, text: &str, size: f32) -> f32

The laid-out width of text at size device pixels, shaped with the same fonts the output’s text is drawn with.

Source

pub fn set_overlay_image( &mut self, image: Option<OverlayImage<'_>>, ) -> Result<(), OverlayImageError>

Set the one picture the shell may draw over the frame, or clear it with None. The only upload: the bytes are copied into a texture here and every later frame reuses it; the texture is reallocated only when the dimensions change. The first call is also what builds the layer’s pipeline — a renderer that never calls this holds none of it.

The bytes are RGBA8 as a capture reads them back, sRGB-encoded, so a still from capture_* draws unchanged. A refused image changes nothing.

Not carried across set_adapter: the texture belongs to the old device, and overlay_image_size answers None afterwards.

Source

pub fn queue_image(&mut self, rect: ImageRect)

Draw the set picture into rect on the next rendered frame, under that frame’s text. The frame consumes it, as it does queued text, so a picture wanted on screen is queued on every frame. A no-op with no picture set. Writes no texture.

Source

pub fn overlay_image_size(&self) -> Option<(u32, u32)>

The set picture’s size, or None when none is set.

Source

pub fn set_now_playing(&mut self, text: &str)

Announce the currently playing track (ADR-0110). The banner fades in, holds, and fades out on its own; the caller only says what, never when to stop.

The string is artist - title, split on the first -. Setting the string that is already set does nothing, so a metadata source may push on every update it receives. An empty string clears the banner.

Source-agnostic by construction (ADR-0001): the argument carries no evidence of whether it came from Windows SMTC or foobar’s titleformat. Callers must not call this from an audio callback — the copy allocates.

Source

pub fn set_reduced_motion(&mut self, reduced: bool)

Make the banner’s fade a step (true) or the theme’s eased envelope (false, the default) — the shell’s reduced-motion choice reaching the one envelope the core owns.

Source

pub fn enable_diagnostics(&mut self, on: bool)

Enable or disable rolling frame-time collection — the gated diagnostics clock read (Plan 0011). The standalone leaves this on so the title always shows live fps/p99; turning it off keeps the core fully clock-free.

Source

pub fn set_overlay(&mut self, on: bool)

Turn the on-screen debug overlay on or off (off by default). Independent of collection, so the plugin can log metrics without painting the overlay.

Source

pub fn overlay_enabled(&self) -> bool

Whether the debug overlay is currently painted.

Source

pub fn metrics(&self) -> Metrics

The current diagnostics snapshot (fps, p99, GPU bytes, …).

Source

pub fn frame_ms_p50(&self) -> f32

Median frame time over the diagnostics window, in milliseconds.

Native-only, beside the analysis snapshot below and for its reason: Metrics mirrors the C ABI’s RlxMetrics, so a field added there widens that surface (ADR-0052). A median beside the p99 is what makes a frame-time reading legible — typical against worst — where the mean the snapshot already carries sits between them saying neither.

Source

pub fn analysis_metrics(&self) -> AnalysisMetrics

The last drawn frame’s analysis snapshot — the levels and the downbeat lock state. Native-only: deliberately absent from the C ABI, so the foobar plugin has no counterpart (ADR-0052).

Source

pub fn preset_name(&self) -> &str

Name of the currently active preset.

Source

pub fn adapter_is_software(&self) -> bool

Whether the active GPU adapter is a CPU/software rasterizer (WARP on DX12). Visual-QA tests read this to skip differential checks the software rasterizer can’t render faithfully — notably the fullscreen-scene + background-pipeline coexistence, which WARP mis-renders while real hardware renders it correctly (Plan 0025 / ADR-0026).

Source

pub fn adapter_description(&self) -> &str

The active adapter’s description — name, backend, device type and driver.

For reports that have to name the machine they were taken on (ADR-0071): a frame time is a fact about a GPU and a driver rather than about the code, so a cost instrument that prints one has to say which. Read by core/tests/collage_cost.rs; nothing on a render path consults it.

Source

pub fn active_system_name(&self) -> &'static str

Name of the built-in system the active preset drives (e.g. the frontend shows it next to the preset name).

This is the scene’s display string — "fragment field", with a space. It is not the key anything looks a system up by; see Renderer::active_system_key, which is a different string for every system whose name is more than one word.

Source

pub fn active_system_key(&self) -> &'static str

The canonical key of the system the active preset drives — "fragment_field", the exact string a preset writes in its system field and the schema export labels that system’s parameter roster with.

Distinct from Renderer::active_system_name and not interchangeable with it: the two coincide on the four systems whose names are one word (swarm, spectrum, emitter, attractor) and differ on every other, so code that resolves a schema roster from the display name works for a quarter of the systems and silently finds nothing for the rest. Anything keyed by system takes this one (ADR-0184).

"" on an empty roster, matching its sibling.

Source

pub fn active_family_key(&self) -> Option<&'static str>

The family the active preset draws, spelled exactly as a preset writes it and as the schema document’s families[].family spells it — "lissajous", "thomas", "chladni", "life_like" (ADR-0194 point 3).

None for a system whose parameters read the same on every family it draws, which is every system family_params answers nothing for. A consumer that gets None has no family to key a range on and falls back to the parameter’s single declared range.

It is the value Scene::configure received, out of the loaded preset’s structural config, rather than a second reading of the scene’s own state. So a parametric_curve preset that declares no [curve] table answers None: nothing configured the scene, and what it draws is whatever the last configured preset left it on — a fact about that hole rather than about this accessor, and reporting a guess would paper over it. Every other family-bearing system builds its config unconditionally.

Source

pub fn active_preset_source(&self) -> Option<&Path>

The file the active preset was read from, or None when it came from the embedded set and has no file on disk.

Absolute, as crate::preset::load_dir recorded it, so a consumer in another process can act on it.

Source

pub fn cap_overflow(&self) -> Option<&CapOverflow>

The segment-cap truncation from the active preset’s last configure, if its geometry hit the fixed cap (ADR-0007: the cap is never a silent cut). Refreshed on every active-preset change (select / cycle / hot-reload); the standalone surfaces it at load. None in the normal case where geometry fit — which is every shipped preset.

Source

pub fn render( &mut self, frame: &AnalysisFrame, dt: f32, ) -> Result<(), RenderError>

Draw the current preset for this analysis frame, advancing all animation by dt real seconds (Plan 0014 Phase 2). The frontend measures and injects elapsed wall-clock time so the visuals run at the same speed on any refresh rate; core never reads a clock. Lost/outdated surfaces self-heal by reconfiguring; timeouts/occlusion skip the frame; only a validation failure (a bug) bubbles up.

A dt that is not finite and positive is replaced by one nominal step before anything reads it (sanitize_frame_dt, ADR-0191).

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<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, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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

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

§

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

§

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