//! The visualiser contract: one trait the bin drives, one render context. //! //! The bin holds a `Box` 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`. 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>> { 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>> { 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>> { 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() } }