use std::collections::HashMap;
use std::sync::Arc;
pub use super::effect::{EffectCache, EffectPipeline};
pub use super::params::{ParamDef, ParamValue};
#[derive(Debug)]
pub enum ExternalImageSource {
#[cfg(target_arch = "wasm32")]
Web(wgpu::CopyExternalImageSourceInfo),
}
impl ExternalImageSource {
#[allow(clippy::needless_return, unreachable_code)]
pub fn pixel_size(&self) -> (u32, u32) {
#[cfg(target_arch = "wasm32")]
match self {
Self::Web(info) => return (info.source.width(), info.source.height()),
}
unreachable!("ExternalImageSource has no variants on this target")
}
}
#[derive(Debug)]
pub struct DirtyFlag(bool);
impl DirtyFlag {
pub fn new_dirty() -> Self {
DirtyFlag(true)
}
pub fn mark(&mut self) {
self.0 = true;
}
pub fn take(&mut self) -> bool {
std::mem::replace(&mut self.0, false)
}
}
impl Default for DirtyFlag {
fn default() -> Self {
Self::new_dirty()
}
}
pub trait Void: std::fmt::Debug {
fn type_id(&self) -> &'static str;
fn clone_boxed(&self) -> Box<dyn Void>;
fn param_values(&self) -> Vec<ParamValue>;
fn take_dirty(&mut self) -> bool;
fn mark_dirty(&mut self);
fn create_cache(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
dst_view: &wgpu::TextureView,
sampler: &wgpu::Sampler,
render_width: u32,
render_height: u32,
) -> EffectCache;
fn needs_animation(&self) -> bool {
false
}
fn update_time(&mut self, _queue: &wgpu::Queue, _cache: &EffectCache, _dt: f32) {}
fn update_params(&mut self, queue: &wgpu::Queue, cache: &EffectCache, params: &[ParamValue]);
fn set_transform(
&mut self,
_queue: &wgpu::Queue,
_cache: &EffectCache,
_transform: &crate::transform::Transform,
) {
}
fn content_extent(&self, canvas_w: u32, canvas_h: u32) -> (f32, f32, f32, f32) {
(0.0, 0.0, canvas_w as f32, canvas_h as f32)
}
fn wants_external_input(&self) -> bool {
false
}
fn upload_external_image(
&mut self,
_device: &wgpu::Device,
_queue: &wgpu::Queue,
_cache: &mut EffectCache,
_source: ExternalImageSource,
) {
}
fn encode(
&self,
encoder: &mut wgpu::CommandEncoder,
cache: &EffectCache,
dst_view: &wgpu::TextureView,
);
fn persistent_frame_size(&self) -> Option<(u32, u32)> {
None
}
fn restore_persistent_pixels(
&mut self,
_device: &wgpu::Device,
_queue: &wgpu::Queue,
_cache: &mut EffectCache,
_width: u32,
_height: u32,
_bytes: &[u8],
) {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub enum CaptureKind {
Camera,
Display,
}
pub struct VoidRegistration {
pub type_id: &'static str,
pub display_name: &'static str,
pub params: &'static [ParamDef],
pub icon: &'static str,
pub supports_preview: bool,
pub supports_live_transform: bool,
pub capture_kind: Option<CaptureKind>,
pub default_transform: fn(u32, u32) -> crate::transform::Transform,
pub create_pipeline: fn(&wgpu::Device, wgpu::TextureFormat) -> EffectPipeline,
pub from_params: fn(&[ParamValue], Arc<EffectPipeline>) -> Box<dyn Void>,
}
pub struct VoidRegistry {
entries: HashMap<&'static str, RegistryEntry>,
}
struct RegistryEntry {
reg: VoidRegistration,
cached_pipeline: Option<Arc<EffectPipeline>>,
}
impl Default for VoidRegistry {
fn default() -> Self {
Self::new()
}
}
impl VoidRegistry {
pub fn new() -> Self {
let mut entries = HashMap::new();
for reg in super::voids::registrations() {
entries.insert(
reg.type_id,
RegistryEntry {
reg,
cached_pipeline: None,
},
);
}
VoidRegistry { entries }
}
pub fn types(&self) -> Vec<&VoidRegistration> {
let mut types: Vec<&VoidRegistration> = self.entries.values().map(|e| &e.reg).collect();
types.sort_by_key(|reg| reg.type_id);
types
}
pub fn param_defs(&self, type_id: &str) -> &'static [ParamDef] {
self.entries
.get(type_id)
.map(|e| e.reg.params)
.unwrap_or(&[])
}
pub fn icon(&self, type_id: &str) -> &'static str {
self.entries.get(type_id).map(|e| e.reg.icon).unwrap_or("")
}
pub fn default_transform(
&self,
type_id: &str,
canvas_w: u32,
canvas_h: u32,
) -> crate::transform::Transform {
self.entries
.get(type_id)
.map(|e| (e.reg.default_transform)(canvas_w, canvas_h))
.unwrap_or_else(crate::transform::Transform::identity)
}
pub fn static_type_id(&self, type_id: &str) -> Option<&'static str> {
self.entries.get_key_value(type_id).map(|(k, _)| *k)
}
pub fn supports_live_transform(&self, type_id: &str) -> bool {
self.entries
.get(type_id)
.map(|e| e.reg.supports_live_transform)
.unwrap_or(false)
}
pub fn has(&self, type_id: &str) -> bool {
self.entries.contains_key(type_id)
}
pub fn display_name(&self, type_id: &str) -> &'static str {
self.entries
.get(type_id)
.map(|e| e.reg.display_name)
.unwrap_or("")
}
pub fn pipeline(
&mut self,
type_id: &str,
device: &wgpu::Device,
format: wgpu::TextureFormat,
) -> Arc<EffectPipeline> {
let entry = self
.entries
.get_mut(type_id)
.unwrap_or_else(|| panic!("Unknown void type: {type_id}"));
entry
.cached_pipeline
.get_or_insert_with(|| Arc::new((entry.reg.create_pipeline)(device, format)))
.clone()
}
pub fn create_void(
&mut self,
type_id: &str,
params: &[ParamValue],
device: &wgpu::Device,
format: wgpu::TextureFormat,
) -> Box<dyn Void> {
let entry = self
.entries
.get_mut(type_id)
.unwrap_or_else(|| panic!("Unknown void type: {type_id}"));
let pipeline = entry
.cached_pipeline
.get_or_insert_with(|| Arc::new((entry.reg.create_pipeline)(device, format)))
.clone();
(entry.reg.from_params)(params, pipeline)
}
}
#[cfg(test)]
mod dirty_flag_tests {
use super::DirtyFlag;
#[test]
fn starts_dirty_so_first_encode_fires() {
let mut flag = DirtyFlag::new_dirty();
assert!(flag.take(), "fresh flag must report dirty on first take");
}
#[test]
fn take_clears() {
let mut flag = DirtyFlag::new_dirty();
assert!(flag.take());
assert!(!flag.take(), "take must clear the flag");
}
#[test]
fn mark_re_arms() {
let mut flag = DirtyFlag::new_dirty();
flag.take();
flag.mark();
assert!(flag.take(), "mark after clear must re-arm");
}
}