use super::effect_state::EffectState;
use crate::{
app::svg::Svg,
audio::sound_data::SoundData,
pipeline::{
gpu::{Gpu, OutputContext},
group::Groups,
},
storage::{
Animation, Asset, AssetId, Palette,
animation::config::AnimationConfig,
collections::Collections,
curve::multiplied_curve::{MultipliedCurve, RangeDegrees, RangePercentage},
},
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use wgpu::{CommandEncoder, Queue};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(default)]
pub struct Effect {
pub color_shift: MultipliedCurve<RangeDegrees>,
pub opacity: MultipliedCurve<RangePercentage>,
pub beat_progression: MultipliedCurve<RangePercentage>,
pub beat_progression_offset: MultipliedCurve<RangePercentage>,
pub speed_exponent: i32,
pub group_index: usize,
pub animation: Option<AssetId<Animation>>,
pub animation_config: AnimationConfig,
#[serde(skip)]
pub animation_overwrite: Option<Arc<Asset<Animation>>>,
#[serde(skip)]
pub state: EffectState,
}
impl Eq for Effect {}
impl Clone for Effect {
fn clone(&self) -> Self {
Self {
color_shift: self.color_shift.clone(),
opacity: self.opacity.clone(),
beat_progression: self.beat_progression.clone(),
beat_progression_offset: self.beat_progression_offset.clone(),
speed_exponent: self.speed_exponent,
group_index: self.group_index,
animation: self.animation,
animation_config: self.animation_config.clone(),
animation_overwrite: self.animation_overwrite.clone(),
state: EffectState::default(),
}
}
}
impl Default for Effect {
fn default() -> Self {
Self {
color_shift: MultipliedCurve::new_multiplier(0.0),
opacity: MultipliedCurve::new_multiplier(1.0),
beat_progression: MultipliedCurve::new_curve([
0x2E, 0x07, 0x5C, 0xCB, 0x90, 0xC4, 0x47, 0x4B, 0x8D, 0x16, 0xC6, 0x70, 0x40, 0x54,
0x99, 0xA6,
]),
beat_progression_offset: MultipliedCurve::new_multiplier(0.0),
speed_exponent: Default::default(),
group_index: Default::default(),
animation: Default::default(),
animation_config: Default::default(),
animation_overwrite: Default::default(),
state: Default::default(),
}
}
}
#[derive(Debug)]
pub struct ShaderCodeComplete {
pub fragment_render_matrix: String,
pub compute_render_output: String,
}
impl Default for ShaderCodeComplete {
fn default() -> Self {
Self {
fragment_render_matrix: format!(
"{}\n\n{}\n\n{}",
include_str!("../../../shaders/common.wgsl"),
include_str!("../../../shaders/render_matrix_common.wgsl"),
include_str!("../../../shaders/red.wgsl")
),
compute_render_output: format!(
"{}\n\n{}\n\n{}",
include_str!("../../../shaders/common.wgsl"),
include_str!("../../../shaders/render_output_common.wgsl"),
include_str!("../../../shaders/red.wgsl")
),
}
}
}
impl Effect {
fn pipeline_is_current(&self) -> bool {
self.state.pipeline_animation == Some(self.animation)
}
pub fn ensure_matrix_pipeline(&mut self, gpu: &Gpu, collections: &Collections) {
if self.state.has_matrix_pipeline() && self.pipeline_is_current() {
return;
}
self.state
.set_matrix_shader_code(gpu, &self.shader_code_complete(collections));
self.state.pipeline_animation = Some(self.animation);
}
pub fn ensure_output_pipeline(&mut self, context: &OutputContext, collections: &Collections) {
if self.state.has_output_pipeline() && self.pipeline_is_current() {
return;
}
self.state
.set_output_shader_code(context, &self.shader_code_complete(collections));
self.state.pipeline_animation = Some(self.animation);
}
pub fn invalidate_pipeline(&mut self) {
self.state.pipeline_animation = None;
}
pub fn shader_code_complete(&self, collections: &Collections) -> ShaderCodeComplete {
let Some(animation) = self
.animation_overwrite
.clone()
.or_else(|| self.animation.and_then(|id| Asset::get(id, collections)))
else {
return Default::default();
};
animation.data.shader_code_complete()
}
pub fn prepare(
&mut self,
queue: &Queue,
palette: Option<Palette>,
groups: &Groups,
main_opacity: f32,
collections: &Collections,
sound_data: &SoundData,
) {
let beat_progression = self.state.beat_progression
+ self.beat_progression_offset.value(
self.state.beat_progression,
collections,
sound_data,
);
self.state.beat_progression =
self.beat_progression
.value(beat_progression, collections, sound_data);
self.state.opacity = self
.opacity
.value(beat_progression, collections, sound_data)
* main_opacity;
self.state.color_shift = self
.color_shift
.value(beat_progression, collections, sound_data);
self.state.speed_exponent = self.speed_exponent;
self.state.animation_config = self.animation_config.clone();
let group = groups.get(self.group_index);
if self.state.has_output_pipeline() && self.state.sent_group.as_ref() != group {
let positions = group.map(Svg::positions);
self.state
.set_positions(queue, positions.unwrap_or_default());
self.state.sent_group = group.cloned();
}
self.state
.write_uniforms(queue, beat_progression, palette, collections, sound_data);
}
#[cfg_attr(feature = "profiling", profiling::function)]
pub fn render(&self, encoder: &mut CommandEncoder, render_matrix: bool, send_output: bool) {
self.state.render(encoder, render_matrix, send_output);
}
}