gled 2.29.0

gled is an application for creating animations and effects on artnet or dmx installations
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>,
    /// In 2^n of bpm
    pub speed_exponent: i32,
    pub group_index: usize,
    pub animation: Option<AssetId<Animation>>,
    pub animation_config: AnimationConfig,

    /// Overwrite the animation with this one, must be only used in the code editor/preview
    #[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)
    }

    /// Build the thumbnail pipeline on the UI device unless it is current.
    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);
    }

    /// Build the LED output pipeline on the output device unless it is current.
    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);
    }

    /// Mark the pipeline as outdated, e.g. after its animation's code changed.
    /// The next `ensure_*_pipeline` call rebuilds it.
    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);
    }
}