use super::Effect;
use crate::{
pipeline::{
group::Group, output_mix::OutputMix, renderer_callback::RendererCallback,
texture_to_output::TextureToOutput,
},
storage::animation::{config::AnimationConfig, renderer::AnimationRenderer},
ui::action::UiAction,
wgpu_render_state,
};
use arboard::{Clipboard, ImageData};
use egui::TextureId;
use wgpu::{MapMode, PollType};
#[derive(Debug)]
pub struct EffectState {
pub beat_progression: f32,
pub beats_per_minute: f32,
pub framerate: f32,
pub opacity: f32,
pub color_shift: f32,
pub speed_exponent: i32,
pub animation_config: AnimationConfig,
pub sent_group: Option<Group>,
pub texture_to_output: TextureToOutput,
pub texture_id: OwnedTextureId,
pub output_mix: OutputMix,
pub renderer: AnimationRenderer,
}
impl EffectState {
pub fn new(effect: &Effect) -> Self {
let (renderer, texture_to_output, texture_id) =
setup_pipeline(&effect.shader_code_complete());
let output_mix = OutputMix::new();
Self {
beat_progression: 0.0,
beats_per_minute: 0.0,
framerate: 0.0,
opacity: 0.0,
color_shift: 0.0,
speed_exponent: 0,
animation_config: Default::default(),
sent_group: None,
texture_to_output,
texture_id,
output_mix,
renderer,
}
}
pub fn update(&mut self, effect: &Effect) {
let (renderer, texture_to_output, texture_id) =
setup_pipeline(&effect.shader_code_complete());
self.renderer = renderer;
self.texture_to_output = texture_to_output;
self.texture_id = texture_id;
}
pub fn send_positions(&mut self) {
self.sent_group.take();
}
pub fn texture_id(&self) -> TextureId {
self.texture_id.0
}
pub fn write_data(&self, beat_progression: f32, data: &mut [u8]) {
data[0..4].copy_from_slice(&self.beat_progression.to_le_bytes());
data[4..8].copy_from_slice(&self.beats_per_minute.to_le_bytes());
data[8..12].copy_from_slice(&self.framerate.to_le_bytes());
data[12..16].copy_from_slice(&self.opacity.to_le_bytes());
data[16..20].copy_from_slice(&self.color_shift.to_le_bytes());
data[20..24].copy_from_slice(&2f32.powi(self.speed_exponent).to_le_bytes());
self.animation_config.write_data(
&mut data[24..24 + AnimationConfig::size()],
beat_progression,
);
}
pub const fn size() -> usize {
const SIZE: usize = 24 + AnimationConfig::size();
static_assertions::const_assert_eq!(SIZE % 16, 0);
SIZE
}
pub fn copy_rendered_image_to_clipboard(&self) {
let texture = self.renderer.texture();
let texture_size = texture.size();
let buffer_size = (texture_size.width * texture_size.height * 4) as wgpu::BufferAddress;
let buffer = wgpu_render_state()
.device
.create_buffer(&wgpu::BufferDescriptor {
label: Some("Texture Buffer"),
size: buffer_size,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
let mut encoder =
wgpu_render_state()
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Copy Texture to Buffer"),
});
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: self.renderer.texture(),
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(texture_size.width * 4),
rows_per_image: Some(texture_size.height),
},
},
texture_size,
);
RendererCallback::add(encoder.finish());
let buffer_slice = buffer.slice(..);
let (tx, rx) = std::sync::mpsc::channel();
buffer_slice.map_async(MapMode::Read, move |v| {
tx.send(v).expect("Could not send on oneshot sender")
});
wgpu_render_state()
.device
.poll(PollType::Wait)
.expect("Could not poll device");
rx.recv()
.expect("Could not receive on gpu rx")
.expect("Error receiving answer to output_data map on gpu");
let mut data = buffer_slice.get_mapped_range().to_vec();
buffer.unmap();
std::thread::spawn(move || {
data.chunks_exact_mut(4).for_each(|pixel| {
pixel.swap(0, 2);
pixel[3] = 255;
});
if let Ok(mut clipboard) = Clipboard::new() {
if let Err(err) = clipboard.set_image(ImageData {
bytes: data.into(),
width: texture_size.width as usize,
height: texture_size.height as usize,
}) {
UiAction::Error(format!("Could not copy rendered image to clipboard: {err}"))
.enqueue();
} else {
log::info!("Copied rendered image to clipboard");
}
}
});
}
}
#[derive(Debug)]
pub struct OwnedTextureId(pub TextureId);
impl Drop for OwnedTextureId {
fn drop(&mut self) {
wgpu_render_state().renderer.write().free_texture(&self.0)
}
}
fn setup_pipeline(shader_code: &str) -> (AnimationRenderer, TextureToOutput, OwnedTextureId) {
let renderer = AnimationRenderer::new(shader_code);
let texture_to_output = TextureToOutput::init(renderer.texture(), renderer.uniforms());
let texture_id = OwnedTextureId(
wgpu_render_state()
.renderer
.write()
.register_native_texture(
&wgpu_render_state().device,
renderer.view(),
wgpu::FilterMode::Nearest,
),
);
(renderer, texture_to_output, texture_id)
}