191 lines
6.0 KiB
Rust
191 lines
6.0 KiB
Rust
//! The visualiser contract: one trait the bin drives, one render context.
|
|
//!
|
|
//! The bin holds a `Box<dyn Visualizer>` and talks to this trait instead of
|
|
//! hard-coding a mode. `breakcore` is the only mode today; the trait is kept
|
|
//! deliberately general so a future GPU visualiser is a new `impl Visualizer`
|
|
//! over the shared aspect-aware [`crate::viz::shader::ShaderPipeline`] base —
|
|
//! nothing in the bin's hot path changes.
|
|
//!
|
|
//! Every mode is GPU-driven now (`is_gpu` → true): it owns a wgpu pipeline,
|
|
//! renders into its own non-square target and presents/captures it directly.
|
|
//! The trait still carries the `Draw` no-op default so a future CPU mode can
|
|
//! be added without reshaping the contract.
|
|
//!
|
|
//! Determinism is unaffected: dispatch is pure indirection; the visualiser's
|
|
//! `update` still advances its `Rng`/integration once per frame, so `--render`
|
|
//! stays bit-reproducible.
|
|
|
|
use crate::audio::Bands;
|
|
use crate::viz::breakcore::Breakcore;
|
|
use crate::viz::fingerprint::Fingerprint;
|
|
use crate::viz::monolith::Monolith;
|
|
use crate::viz::palette::Palette;
|
|
use nannou::prelude::Draw;
|
|
use nannou::wgpu;
|
|
|
|
/// Per-frame shared tunables handed to a visualiser. Cheap to copy (it only
|
|
/// borrows the palette).
|
|
#[derive(Clone, Copy)]
|
|
pub struct RenderContext<'a> {
|
|
pub pal: &'a Palette,
|
|
pub scale: f32,
|
|
pub warp: f32,
|
|
pub feedback: bool,
|
|
pub fade: f32,
|
|
pub ca_px: f32,
|
|
pub drive: f32,
|
|
/// Hard march-step ceiling for this run (preset/cfg gated, ≤ the shader's
|
|
/// own absolute cap). The visualiser must not request more.
|
|
pub march_cap: u32,
|
|
}
|
|
|
|
/// One visualiser mode. GPU modes set [`is_gpu`](Self::is_gpu) and implement
|
|
/// [`render_gpu`](Self::render_gpu)/[`current_tex`](Self::current_tex)/
|
|
/// [`capture_raw`](Self::capture_raw); a future `Draw` mode would implement
|
|
/// [`draw`](Self::draw) instead. Object-safe: held as `Box<dyn Visualizer>`.
|
|
pub trait Visualizer {
|
|
fn name(&self) -> &'static str;
|
|
fn seed(&self) -> u64;
|
|
fn reseed(&mut self, seed: u64);
|
|
fn update(&mut self, b: &Bands, dt: f32);
|
|
/// Element count for the HUD (capsule control points).
|
|
fn element_count(&self) -> usize;
|
|
|
|
/// `true` ⇒ this mode owns a wgpu pipeline and uses the GPU methods
|
|
/// below; `false` ⇒ it draws through `Draw`.
|
|
fn is_gpu(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
/// CPU/`Draw` path. No-op for GPU-driven modes.
|
|
fn draw(&self, _d: &Draw, _ctx: &RenderContext) {}
|
|
|
|
/// GPU path: render this frame's own pipeline target and return it.
|
|
/// `None` for `Draw`-based modes.
|
|
fn render_gpu(
|
|
&mut self,
|
|
_device: &wgpu::Device,
|
|
_queue: &wgpu::Queue,
|
|
_ctx: &RenderContext,
|
|
) -> Option<&wgpu::Texture> {
|
|
None
|
|
}
|
|
|
|
/// The texture to present this frame (GPU modes only).
|
|
fn current_tex(&self) -> Option<&wgpu::Texture> {
|
|
None
|
|
}
|
|
|
|
/// Synchronous RGBA readback of the current target (GPU modes only).
|
|
fn capture_raw(
|
|
&self,
|
|
_device: &wgpu::Device,
|
|
_queue: &wgpu::Queue,
|
|
) -> Option<anyhow::Result<Vec<u8>>> {
|
|
None
|
|
}
|
|
|
|
/// Commit a song-fingerprint to this visualiser. Modes that pick their
|
|
/// archetype from per-track stats use it; modes that don't ignore it.
|
|
/// The bin calls this once in `--render` (pre-computed from the
|
|
/// `Timeline`) and once in live as soon as the live accumulator is
|
|
/// ready. Default = no-op so existing modes need no edits.
|
|
fn install_fingerprint(&mut self, _fp: Fingerprint) {}
|
|
|
|
/// `true` once the visualiser has committed a fingerprint. Stays `true`
|
|
/// for modes that don't use one (their archetype is always "ready").
|
|
fn fingerprint_ready(&self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
impl Visualizer for Breakcore {
|
|
fn name(&self) -> &'static str {
|
|
"breakcore"
|
|
}
|
|
fn seed(&self) -> u64 {
|
|
self.seed
|
|
}
|
|
fn reseed(&mut self, seed: u64) {
|
|
Breakcore::reseed(self, seed)
|
|
}
|
|
fn update(&mut self, b: &Bands, dt: f32) {
|
|
Breakcore::update(self, b, dt)
|
|
}
|
|
fn element_count(&self) -> usize {
|
|
self.point_count()
|
|
}
|
|
fn is_gpu(&self) -> bool {
|
|
true
|
|
}
|
|
fn render_gpu(
|
|
&mut self,
|
|
device: &wgpu::Device,
|
|
queue: &wgpu::Queue,
|
|
c: &RenderContext,
|
|
) -> Option<&wgpu::Texture> {
|
|
Some(Breakcore::render(
|
|
self, device, queue, c.pal, c.scale, c.warp, c.feedback, c.fade, c.ca_px, c.drive,
|
|
c.march_cap,
|
|
))
|
|
}
|
|
fn current_tex(&self) -> Option<&wgpu::Texture> {
|
|
Some(self.current())
|
|
}
|
|
fn capture_raw(
|
|
&self,
|
|
device: &wgpu::Device,
|
|
queue: &wgpu::Queue,
|
|
) -> Option<anyhow::Result<Vec<u8>>> {
|
|
Some(Breakcore::capture_raw(self, device, queue))
|
|
}
|
|
}
|
|
|
|
impl Visualizer for Monolith {
|
|
fn name(&self) -> &'static str {
|
|
"monolith"
|
|
}
|
|
fn seed(&self) -> u64 {
|
|
self.seed
|
|
}
|
|
fn reseed(&mut self, seed: u64) {
|
|
Monolith::reseed(self, seed)
|
|
}
|
|
fn update(&mut self, b: &Bands, dt: f32) {
|
|
Monolith::update(self, b, dt)
|
|
}
|
|
fn element_count(&self) -> usize {
|
|
Monolith::element_count(self)
|
|
}
|
|
fn is_gpu(&self) -> bool {
|
|
true
|
|
}
|
|
fn render_gpu(
|
|
&mut self,
|
|
device: &wgpu::Device,
|
|
queue: &wgpu::Queue,
|
|
c: &RenderContext,
|
|
) -> Option<&wgpu::Texture> {
|
|
Some(Monolith::render(
|
|
self, device, queue, c.pal, c.scale, c.warp, c.feedback, c.fade, c.ca_px, c.drive,
|
|
c.march_cap,
|
|
))
|
|
}
|
|
fn current_tex(&self) -> Option<&wgpu::Texture> {
|
|
Some(self.current())
|
|
}
|
|
fn capture_raw(
|
|
&self,
|
|
device: &wgpu::Device,
|
|
queue: &wgpu::Queue,
|
|
) -> Option<anyhow::Result<Vec<u8>>> {
|
|
Some(Monolith::capture_raw(self, device, queue))
|
|
}
|
|
fn install_fingerprint(&mut self, fp: Fingerprint) {
|
|
Monolith::install_fingerprint(self, fp)
|
|
}
|
|
fn fingerprint_ready(&self) -> bool {
|
|
self.fingerprint_committed()
|
|
}
|
|
}
|