First real GPU audio node: FFT-based (overlap-add) convolution of the input with a fixed impulse response, computed on the GPU via render::GpuCompute on the transport's non-RT worker. More...
#include <gpu_convolver.hpp>
Public Types | |
| enum class | ProviderPolicy : std::uint8_t { Auto , StagedOnly , SharedRequired , StagedBlocking } |
| Host-thread selection for the provider used during the next prepare(). More... | |
Public Member Functions | |
| GpuConvolver (uint32_t channels, uint32_t block_size, uint32_t sample_rate, std::vector< float > impulse_response) | |
| GpuConvolver (uint32_t channels, uint32_t block_size, uint32_t sample_rate, std::vector< float > impulse_response, uint32_t latency_blocks) | |
| ~GpuConvolver () override | |
| bool | set_provider_policy (ProviderPolicy policy) noexcept |
| Select the provider policy for the next prepare(). | |
| ProviderPolicy | provider_policy () const noexcept |
| bool | configure_trace (const GpuConvolverTraceConfig &config) noexcept |
| Enable bounded shared-I/O tracing for the next prepare(). | |
| bool | configure_staged_breaker (const GpuConvolverStagedBreaker &config) noexcept |
| Set the staged-path circuit breaker for the next prepare(). | |
| GpuConvolverStagedBreaker | staged_breaker () const noexcept |
| GpuAudioNodeDescriptor | descriptor () const override |
| bool | prepare () override |
| Non-RT. Allocate/compile/upload static data here. | |
| bool | cpu_fallback_prepared () const noexcept override |
| Host-thread witness that the realtime CPU fallback is already built before prepare() returns. | |
| void | process_block (const audio::BufferView< const float > &input, audio::BufferView< float > &output, uint32_t n) override |
| Non-RT worker context. Process exactly one block (n == block_size). | |
| void | prime_fallback (const audio::BufferView< const float > &input, uint32_t n) noexcept override |
| RT-safe. | |
| void | process_cpu_fallback (const audio::BufferView< const float > &, audio::BufferView< float > &output, uint32_t n) noexcept override |
| RT-safe miss substitute: emit the latency-aligned block that prime_fallback() already computed for this timeline slot. | |
| bool | gpu_available () const |
| True if the GPU compute device initialized (process_block uses the GPU). | |
| std::string | backend () const |
| The live native compute backend ("Metal"/"D3D12"/"Vulkan"), or "" if CPU-only. | |
| uint32_t | fft_size () const |
| GpuAudioStatus | status_snapshot () const noexcept override |
| Allocation-free provider-neutral lifecycle projection, safe to call from a host or UI thread while a transport runs this node (not concurrently with prepare()). | |
| bool | bind_realtime_path (detail::RealtimeGpuNodeBinding &binding) noexcept override |
| Private transport hook, not for node authors: a node that the GPU audio transport drives through a stamped realtime path describes that path here. | |
| GpuAudioRecoveryReason | recovery_reason () const noexcept |
| First latched recovery cause, or None. Same snapshot as status_snapshot(). | |
| bool | fenced () const noexcept |
| True before preparation, or once GPU delivery was fenced or its provider lost. | |
| std::uint64_t | sanitized_input_samples () const noexcept |
| Non-finite input samples (NaN, +/-Inf) replaced with zero before they reached the GPU, on the shared realtime path or the staged path, since prepare(). | |
| virtual bool | release_bounded (std::uint64_t) noexcept |
| Host thread, audio stopped. | |
Static Public Attributes | |
| static constexpr uint32_t | kLatencyBlocks = 2 |
| Default worker/round-trip latency, in host blocks. | |
| static constexpr uint32_t | kMaxLatencyBlocks = 64 |
| static constexpr uint32_t | kSharedIoSlots = 16 |
Protected Member Functions | |
| void | init_fallback () |
| Allocate + load the CPU fallback machinery (both the RT miss fallback and the no-GPU worker fallback) and the latency-alignment delay ring. | |
| void | render_worker_fallback (const audio::BufferView< const float > &input, audio::BufferView< float > &output, uint32_t n) noexcept |
| No-GPU worker path: run the (continuously-fed) worker fallback for every prepared channel into output, zeroing anything else. | |
| GpuAudioRecoveryReason | staged_recovery_reason () const noexcept |
| bool | has_realtime_shared_io () const noexcept |
| detail::SharedIoProviderIdentity | provider_identity_for_diagnostics () const noexcept |
| bool | wait_staged_readback (StagedReadbackState &staged, float *dest) |
| void | submit_staged_readback (StagedReadbackState &staged, std::size_t slot_index) |
| void | configure_staged_pipeline () |
| std::uint32_t | staged_pipeline_depth () const noexcept |
| void | latch_staged_recovery (GpuAudioRecoveryReason reason) noexcept |
| void | update_staged_breaker (StagedReadbackState &staged, bool on_time) noexcept |
| std::uint64_t | staged_cpu_blocks () const noexcept |
| std::uint64_t | staged_gpu_blocks () const noexcept |
| std::uint64_t | staged_breaker_trips () const noexcept |
| GpuAudioRecoveryReason | staged_latched_reason () const noexcept |
Static Protected Member Functions | |
| static std::uint8_t | process_realtime_shared_io (void *self, const audio::BufferView< const float > &input, audio::BufferView< float > &output, std::uint32_t n, std::uint64_t sequence, bool input_valid, std::uint64_t callback_start_ns) noexcept |
| static std::uint32_t | service_realtime_shared_io (void *self, std::uint64_t now_ns) noexcept |
| static bool | fence_realtime_shared_io (void *self) noexcept |
| static void | complete_realtime_shared_io (void *, std::uint64_t, std::uint8_t, std::uint64_t callback_end_ns, std::uint64_t result_visible_ns) noexcept |
| static std::uint64_t | next_realtime_shared_io_sequence (void *) noexcept |
| static bool | realtime_shared_io_health (const void *self, detail::RealtimeGpuHealth &) noexcept |
| static bool | set_realtime_shared_io_notifier (void *self, detail::SharedIoCompletionNotifier) noexcept |
| static bool | set_staged_pipeline_hosting (void *self, bool hosted) noexcept |
Protected Attributes | |
| uint32_t | channels_ |
| uint32_t | block_ |
| uint32_t | sample_rate_ |
| uint32_t | latency_blocks_ = kLatencyBlocks |
| ProviderPolicy | provider_policy_ = ProviderPolicy::Auto |
| std::vector< float > | ir_ |
| uint32_t | fft_size_ = 0 |
| std::uint8_t | trial_requested_path_ = 0 |
| std::uint64_t | trial_generation_ = 1 |
| bool | trial_configured_ = false |
| bool | trial_enable_trace_ = false |
| bool | trial_capture_admissions_ = false |
| bool | trial_capture_callback_timing_ = false |
| bool | trial_staged_sync_reference_ = false |
| std::uint32_t | trial_success_stride_ = 1 |
| std::uint8_t | trial_completion_policy_ = 0 |
| std::uint64_t | trial_completion_wait_ns_ = 0 |
| std::unique_ptr< render::GpuCompute > | gpu_ |
| std::unique_ptr< SharedIoState > | shared_io_ |
| std::unique_ptr< detail::StagedAsyncTrialState > | staged_trial_ |
| std::vector< detail::SharedIoTraceRecord > | staged_sync_records_ |
| std::uint64_t | staged_sequence_ = 0 |
| std::vector< float > | ir_spec_ |
| std::vector< std::vector< float > > | carry_ |
| std::vector< float > | in_pad_ |
| std::vector< float > | time_ |
| std::vector< signal::FixedIrConvolver > | fallback_ |
| std::vector< signal::FixedIrConvolver > | worker_fallback_ |
| uint32_t | fallback_delay_blocks_ = 0 |
| uint32_t | fb_delay_idx_ = 0 |
| std::vector< float > | fb_delay_ |
| std::vector< float > | fb_out_ |
| bool | prepared_ = false |
| std::unique_ptr< StagedReadbackState > | staged_readback_ |
Friends | |
| struct | detail::GpuConvolverTestAccess |
| bool | detail::configure_gpu_convolver_trial (GpuConvolver &, const detail::GpuConvolverTrialConfig &) noexcept |
| bool | detail::drain_gpu_convolver_trial_records (GpuConvolver &, std::vector< detail::SharedIoTraceRecord > &) noexcept |
| bool | detail::drain_gpu_convolver_trial_admissions (GpuConvolver &, std::vector< detail::SharedIoTraceAdmission > &) noexcept |
| std::uint64_t | detail::gpu_convolver_trial_engine_id (const GpuConvolver &) noexcept |
First real GPU audio node: FFT-based (overlap-add) convolution of the input with a fixed impulse response, computed on the GPU via render::GpuCompute on the transport's non-RT worker.
Each block issues ONE batched GPU convolution — forward FFT, complex multiply by the resident IR spectrum, and inverse FFT for every channel, fused into a single submit with a single readback. The per-call GPU round-trip (~0.5 ms of map latency) is therefore paid once per block rather than once per channel.
Staged readback (Auto / StagedOnly). The worker never waits on the GPU: it submits block N with GpuCompute::convolve_batch_async and returns, and the result counts only if it has landed by the time block N is due, latency_blocks - 1 calls later (descriptor().worker_pipeline_blocks; 1 at the default lead of 2). That pipeline sits inside the reported latency, so total latency and PDC are unchanged. A late result is discarded. Every block's input lands in exactly one place: the GPU overlap-add carry when its result is on time, otherwise the worker's zero-latency CPU convolution. The output is the carry plus whatever the CPU convolution still owes, so a late, failed, non-finite or device-lost block is the exact continuation, never silence. The CPU convolution runs only for a missed block and the IR-length tail after it; a GPU hit costs none. A run of misses trips the breaker (GpuConvolverStagedBreaker): the worker stops submitting, runs on the CPU, and probes the GPU periodically until it is on time again. A node prepared with a one-block lead cannot pipeline and waits, bounded, inside the block instead.
Lifetime: while a GpuAudioTransport hosts a pipelining GpuConvolver (descriptor().worker_pipeline_blocks > 0), the transport's release() opts the node back out of pipelining. A node destroyed first detaches itself from the transport, so release() then touches nothing; a non-pipelining node is never touched by release(). Destroying it first is safe only once neither pump() nor process() runs on its transport: the audio callback's process() calls the node's CPU fallback, and a transport running its own worker (Config::run_worker_thread) pumps the node until release() joins it. Otherwise release that transport before destroying the node.
CPU fallback (MissPolicy::CpuFallback). The fallback is a zero-latency signal::FixedIrConvolver per channel that the transport keeps CONTINUOUSLY fed via prime_fallback() — one direct-FIR or partitioned block each audio block, not only on misses. A fallback fed only on misses would have gaps in its history, so a substituted block would not continue the convolution (its IR tail energy from the preceding audio would be missing): an audible discontinuity, not merely lower precision. Because the priming path is zero-latency but the transport reports latency_blocks of PDC for the GPU pipeline, the fallback's output is pushed through a matching latency_blocks-deep delay so a miss substitute equals the wet block the worker ring would have returned (input(t-L) convolved) — the seam is sample-aligned, never a dry glitch and never a block early/late.
process_block still waits (bounded) on the GPU readback, which is why it runs on the transport's non-RT worker and never on the audio thread. Live use at small block sizes should still prefer the CPU fallback: a prepared CPU convolution costs microseconds, and no amount of batching beats a map round-trip for a single stereo pair.
|
strong |
Host-thread selection for the provider used during the next prepare().
Auto preserves staged/CPU execution even when shared support is compiled, using the deadline-bounded staged readback described above. StagedOnly is the same staged path, named explicitly. SharedRequired explicitly opts into the authenticated shared Dawn path. StagedBlocking keeps the earlier unbounded GpuCompute::convolve_batch readback for callers that depend on its timing; it still fills failed or device-lost blocks from the CPU continuation. StagedBlocking is a transition escape hatch and is planned for removal in the next minor release. Selection is immutable while prepared and never changes from the audio callback.
| Enumerator | |
|---|---|
| Auto | |
| StagedOnly | |
| SharedRequired | |
| StagedBlocking | |
| pulp::gpu_audio::GpuConvolver::GpuConvolver | ( | uint32_t | channels, |
| uint32_t | block_size, | ||
| uint32_t | sample_rate, | ||
| std::vector< float > | impulse_response ) |
| pulp::gpu_audio::GpuConvolver::GpuConvolver | ( | uint32_t | channels, |
| uint32_t | block_size, | ||
| uint32_t | sample_rate, | ||
| std::vector< float > | impulse_response, | ||
| uint32_t | latency_blocks ) |
|
override |
| std::string pulp::gpu_audio::GpuConvolver::backend | ( | ) | const |
The live native compute backend ("Metal"/"D3D12"/"Vulkan"), or "" if CPU-only.
On the shared path this names Dawn's underlying Metal backend; use the transport capability report when the provider identity itself matters.
|
overridevirtualnoexcept |
Private transport hook, not for node authors: a node that the GPU audio transport drives through a stamped realtime path describes that path here.
Returns false for a node the transport drives through its staged worker path, which is the default.
Reimplemented from pulp::gpu_audio::GpuAudioNode.
|
staticprotectednoexcept |
|
noexcept |
Set the staged-path circuit breaker for the next prepare().
Returns false while prepared or for a zero field.
|
protected |
|
noexcept |
Enable bounded shared-I/O tracing for the next prepare().
This is a host-thread operation and fails after preparation. The transport worker remains the sole live queue consumer; final close drains any remainder.
|
inlineoverridevirtualnoexcept |
Host-thread witness that the realtime CPU fallback is already built before prepare() returns.
A descriptor's supports_cpu_fallback bit is only a declaration; the transport admits MissPolicy::CpuFallback after both the declaration and this prepared witness are true. Nodes with no verified fallback remain fail-closed and must use silence on a miss.
Reimplemented from pulp::gpu_audio::GpuAudioNode.
|
overridevirtual |
Implements pulp::gpu_audio::GpuAudioNode.
|
staticprotectednoexcept |
|
noexcept |
True before preparation, or once GPU delivery was fenced or its provider lost.
A diagnostic snapshot, not a promise about the next block.
|
inline |
| bool pulp::gpu_audio::GpuConvolver::gpu_available | ( | ) | const |
True if the GPU compute device initialized (process_block uses the GPU).
|
protectednoexcept |
|
inlineprotected |
Allocate + load the CPU fallback machinery (both the RT miss fallback and the no-GPU worker fallback) and the latency-alignment delay ring.
Shared by the render and no-render prepare() paths so they can never drift.
|
protectednoexcept |
|
staticprotectednoexcept |
|
overridevirtual |
Non-RT. Allocate/compile/upload static data here.
Implements pulp::gpu_audio::GpuAudioNode.
|
overridevirtualnoexcept |
RT-safe.
Feed the continuously-fed CPU fallback one block so its history stays current, and stage the latency-aligned substitute for a possible miss this block. Called by the transport on every block (hit or miss). Non-finite samples enter as zero, as on the GPU paths, so the substitute keeps the IR tail and matches the GPU output it stands in for.
Reimplemented from pulp::gpu_audio::GpuAudioNode.
|
overridevirtual |
Non-RT worker context. Process exactly one block (n == block_size).
Implements pulp::gpu_audio::GpuAudioNode.
|
inlineoverridevirtualnoexcept |
RT-safe miss substitute: emit the latency-aligned block that prime_fallback() already computed for this timeline slot.
Reimplemented from pulp::gpu_audio::GpuAudioNode.
|
staticprotectednoexcept |
|
protectednoexcept |
|
inlinenoexcept |
|
staticprotectednoexcept |
|
noexcept |
First latched recovery cause, or None. Same snapshot as status_snapshot().
|
inlinevirtualnoexceptinherited |
Host thread, audio stopped.
Release provider resources within budget_ns, returning false if teardown had to be abandoned to stay in budget. The default holds nothing to release.
Reimplemented in pulp::gpu_audio::GpuWaveNetRealtimeNode.
|
inlineprotectednoexcept |
No-GPU worker path: run the (continuously-fed) worker fallback for every prepared channel into output, zeroing anything else.
Both selected engines are zero-latency, so the fallback matches the GPU path's algorithmic timing.
This is the SOLE audio path when the device fails to init (the !gpu_ branch of process_block) and in the no-render build, so it carries its own non-finite-input guard: a single NaN/Inf would otherwise poison worker_fallback_'s history for the whole IR tail (the IR length, plus one block on the FFT engine). On a poisoned block we reset the convolver and emit silence for that channel; it self-heals on the next finite block instead of dying for the tail. The staged GPU path hands it input already sanitized, so the guard never fires there.
|
noexcept |
Non-finite input samples (NaN, +/-Inf) replaced with zero before they reached the GPU, on the shared realtime path or the staged path, since prepare().
One such sample would otherwise ring through the whole IR tail or fence the GPU on a non-finite readback. Counted, not latched. Always 0 without a GPU device.
|
staticprotectednoexcept |
|
noexcept |
Select the provider policy for the next prepare().
Returns false when called after preparation; callers must quiesce and release the node before selecting a new policy. SharedRequired fails preparation unless the exact authenticated shared provider is available.
|
staticprotectednoexcept |
|
staticprotectednoexcept |
|
noexcept |
|
protectednoexcept |
|
protectednoexcept |
|
protectednoexcept |
|
protectednoexcept |
|
protectednoexcept |
|
protectednoexcept |
|
overridevirtualnoexcept |
Allocation-free provider-neutral lifecycle projection, safe to call from a host or UI thread while a transport runs this node (not concurrently with prepare()).
The authenticated shared path reports the health its transport last sampled after a service step: a fenced session is Degraded, a lost provider is Unavailable, each with the first latched recovery reason. The staged path reports what it can prove: provider and engine are Unknown while the staged GpuCompute device is live, the engine is Cpu when no device initialized, and a lost device is Unavailable with ProviderLost.
Reimplemented from pulp::gpu_audio::GpuAudioNode.
|
protected |
|
protectednoexcept |
|
protected |
|
friend |
|
friend |
|
friend |
|
friend |
|
friend |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
staticconstexpr |
Default worker/round-trip latency, in host blocks.
A constructed node may select another positive lead for a controlled campaign; the selected value is reported as PDC and shared by the bridge and CPU fallback.
|
staticconstexpr |
|
staticconstexpr |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |