From 3f5379843e1294e8e8492cdbc0420fb0fa3d20ed Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sun, 12 Jan 2025 20:27:45 -0500 Subject: [PATCH] feat: Re-write accumulator, add camera settings We can view the IFS-to-image process as a composition of two affine transforms: - One for the in-IFS functions (zoom, rotate, offset) - One for the IFS-to-image functions (scale) And because a composition of affine transforms is itself an affine transform, we can do everything at once. Eventually, the final affine matrix should be provided to the GPU directly, I just wanted to prove this works for now. --- Cargo.lock | 29 +- Cargo.toml | 2 +- crates/flare-lib/build.rs | 1 + crates/flare-lib/examples/gasket.rs | 287 ------------------ crates/flare-lib/src/lib.rs | 222 +------------- crates/flare-shader/src/lib.rs | 401 +++++++++++++++----------- crates/flare-shader/src/points.rs | 55 ---- crates/flare-shader/src/tests.rs | 299 ++++++++++++++----- crates/flare-shader/src/transforms.rs | 79 ----- crates/flare-shader/src/variations.rs | 47 --- 10 files changed, 474 insertions(+), 948 deletions(-) delete mode 100644 crates/flare-lib/examples/gasket.rs delete mode 100644 crates/flare-shader/src/points.rs delete mode 100644 crates/flare-shader/src/transforms.rs delete mode 100644 crates/flare-shader/src/variations.rs diff --git a/Cargo.lock b/Cargo.lock index f28c930..11eb103 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,9 +225,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "cc" -version = "1.2.5" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "jobserver", "libc", @@ -1158,9 +1158,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "crc32fast", "flate2", @@ -1218,9 +1218,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1629,9 +1629,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", "memchr", @@ -2048,12 +2048,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] @@ -2089,18 +2090,18 @@ checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasmparser" -version = "0.218.0" +version = "0.222.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09e46c7fceceaa72b2dd1a8a137ea7fd8f93dfaa69806010a709918e496c5dc" +checksum = "4adf50fde1b1a49c1add6a80d47aea500c88db70551805853aa8b88f3ea27ab5" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 4bd8491..926cb65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ needless_range_loop = "allow" [workspace.lints.rust.unexpected_cfgs] # Rust (properly) doesn't recognize the spirv architecture level = "warn" -check-cfg = ['cfg(target_arch, values("spirv"))', 'cfg(target_feature, values("Int8"))'] +check-cfg = ['cfg(target_arch, values("spirv"))'] # Compile build-dependencies in release mode with # the same settings as regular dependencies. diff --git a/crates/flare-lib/build.rs b/crates/flare-lib/build.rs index 04d73f6..87f09bc 100644 --- a/crates/flare-lib/build.rs +++ b/crates/flare-lib/build.rs @@ -15,6 +15,7 @@ fn main() -> Result<(), Box> { // Compile the shader crate with SpirvBuilder. let result = SpirvBuilder::new(gpu_crate_path, "spirv-unknown-spv1.3") .print_metadata(MetadataPrintout::Full) + .release(false) .build()?; // Copy the SPIR-V to this crate's output directory diff --git a/crates/flare-lib/examples/gasket.rs b/crates/flare-lib/examples/gasket.rs deleted file mode 100644 index 67498fe..0000000 --- a/crates/flare-lib/examples/gasket.rs +++ /dev/null @@ -1,287 +0,0 @@ -use flare_lib::{IterateTransformsEntry, RenderImageEntry, RenderImageHistogramEntry}; -use flare_shader::{ - Coefs, Color, ImageSettings, IterSettings, IterState, Pixel, Point, TransformSpec, - VariationKind, VariationParams, VariationSpec, -}; -use futures::channel::oneshot; -use futures_executor::block_on; -use image::RgbaImage; -use rand::Rng; -use wgpu::util::DeviceExt; - -const ITER_SETTINGS: IterSettings = IterSettings { - transform_count: TRANSFORMS.len() as u32, - fuse_count: 20, - iteration_count: 100_000, -}; - -const TRANSFORMS: [TransformSpec; 3] = [ - TransformSpec { - coefs: Coefs::new(0.5, 0., 0., 0., 0.5, 0.), - post_coefs: Coefs::IDENTITY, - weight: 1., - color: 0.0, - color_speed: 0.0, - variation_offset: 0, - variation_count: 1, - }, - TransformSpec { - coefs: Coefs::new(0.5, 0., 0.5, 0., 0.5, 0.), - post_coefs: Coefs::IDENTITY, - weight: 1., - color: 0.0, - color_speed: 0.0, - variation_offset: 0, - variation_count: 1, - }, - TransformSpec { - coefs: Coefs::new(0.5, 0., 0., 0., 0.5, 0.5), - post_coefs: Coefs::IDENTITY, - weight: 1., - color: 0.0, - color_speed: 0.0, - variation_offset: 0, - variation_count: 1, - }, -]; - -const TRANSFORM_FINAL: TransformSpec = TransformSpec { - coefs: Coefs::IDENTITY, - post_coefs: Coefs::IDENTITY, - weight: 0., - color: 0., - color_speed: 0., - variation_offset: 0, - variation_count: 1, -}; - -const VARIATIONS: [VariationSpec; 1] = [VariationSpec { - weight: 1., - kind: VariationKind::Linear, - params: VariationParams::new(), -}]; - -struct IterateTransformsBuffers { - iter_settings: wgpu::Buffer, - transforms: wgpu::Buffer, - transform_final: wgpu::Buffer, - variations: wgpu::Buffer, - thread_state: wgpu::Buffer, - points: wgpu::Buffer, -} - -impl IterateTransformsBuffers { - fn new(device: &wgpu::Device, thread_state: &[IterState]) -> Self { - Self { - iter_settings: device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("iter_settings"), - usage: wgpu::BufferUsages::UNIFORM, - contents: bytemuck::cast_slice(&[ITER_SETTINGS]), - }), - transforms: device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("transforms"), - usage: wgpu::BufferUsages::STORAGE, - contents: bytemuck::cast_slice(&TRANSFORMS), - }), - transform_final: device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("transform_final"), - usage: wgpu::BufferUsages::STORAGE, - contents: bytemuck::cast_slice(&[TRANSFORM_FINAL]), - }), - variations: device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("variations"), - usage: wgpu::BufferUsages::STORAGE, - contents: bytemuck::cast_slice(&[VARIATIONS]), - }), - thread_state: device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("thread_state"), - usage: wgpu::BufferUsages::STORAGE, - contents: bytemuck::cast_slice(thread_state), - }), - points: device.create_buffer(&wgpu::BufferDescriptor { - label: Some("points"), - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, - size: size_of::() as u64 * ITER_SETTINGS.iteration_count as u64, - mapped_at_creation: false, - }), - } - } -} - -const IMAGE_SETTINGS: ImageSettings = ImageSettings { - size: 600, - point_count: ITER_SETTINGS.iteration_count, - palette_count: PALETTE.len() as u32, -}; - -const PALETTE: [Color; 2] = [Color::WHITE, Color::WHITE]; - -fn run_compute_pass( - encoder: &mut wgpu::CommandEncoder, - pipeline: &wgpu::ComputePipeline, - bind_group: &wgpu::BindGroup, -) { - let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: None, - timestamp_writes: None, - }); - - compute_pass.set_pipeline(pipeline); - compute_pass.set_bind_group(0, bind_group, &[]); - compute_pass.dispatch_workgroups(1, 1, 1); -} - -pub fn main() -> anyhow::Result<()> { - let backends = wgpu::util::backend_bits_from_env().unwrap_or_default(); - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends, - ..Default::default() - }); - - let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - force_fallback_adapter: false, - compatible_surface: None, - }); - let adapter = block_on(adapter).expect("Unable to find adapter"); - - let dq = adapter.request_device( - &wgpu::DeviceDescriptor { - label: Some("gasket"), - required_features: wgpu::Features::SHADER_INT64, - required_limits: wgpu::Limits::default(), - memory_hints: wgpu::MemoryHints::default(), - }, - None, - ); - let (device, queue) = block_on(dq)?; - - let module = wgpu::include_spirv!(concat!(env!("OUT_DIR"), "/shader_binary.spv")); - let shader = device.create_shader_module(module); - - let iterate_transforms = IterateTransformsEntry::new(&device, &shader); - - let mut rng = rand::thread_rng(); - let thread_state = [IterState { - rng_seed: rng.gen(), - current_point: Point::new_random(&mut rng), - }]; - let iterate_transforms_buffers = IterateTransformsBuffers::new(&device, &thread_state); - - let iterate_transforms_bindgroup = iterate_transforms.bind_group( - &device, - &iterate_transforms_buffers.iter_settings, - &iterate_transforms_buffers.transforms, - &iterate_transforms_buffers.transform_final, - &iterate_transforms_buffers.variations, - &iterate_transforms_buffers.thread_state, - &iterate_transforms_buffers.points, - ); - - let render_image_histogram = RenderImageHistogramEntry::new(&device, &shader); - - let image_settings_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("image_settings"), - usage: wgpu::BufferUsages::UNIFORM, - contents: bytemuck::cast_slice(&[IMAGE_SETTINGS]), - }); - let palette_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("palette"), - usage: wgpu::BufferUsages::STORAGE, - contents: bytemuck::cast_slice(&PALETTE), - }); - let pixel_count = (IMAGE_SETTINGS.size * IMAGE_SETTINGS.size) as u64; - let pixel_size = size_of::() as u64 * pixel_count; - let image_histogram_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("image_histogram"), - size: pixel_size, - usage: wgpu::BufferUsages::STORAGE, - mapped_at_creation: false, - }); - - let render_image_histogram_bindgroup = render_image_histogram.bind_group( - &device, - &image_settings_buffer, - &palette_buffer, - &iterate_transforms_buffers.points, - &image_histogram_buffer, - ); - - let render_image = RenderImageEntry::new(&device, &shader); - let image_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("image"), - size: image_histogram_buffer.size(), - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, - mapped_at_creation: false, - }); - - let render_image_bindgroup = render_image.bind_group( - &device, - &image_settings_buffer, - &image_histogram_buffer, - &image_buffer, - ); - - let image_buffer_staging = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("image_staging"), - size: image_buffer.size(), - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - run_compute_pass( - &mut encoder, - &iterate_transforms.compute_pipeline, - &iterate_transforms_bindgroup, - ); - run_compute_pass( - &mut encoder, - &render_image_histogram.compute_pipeline, - &render_image_histogram_bindgroup, - ); - run_compute_pass( - &mut encoder, - &render_image.compute_pipeline, - &render_image_bindgroup, - ); - encoder.copy_buffer_to_buffer( - &image_buffer, - 0, - &image_buffer_staging, - 0, - image_buffer_staging.size(), - ); - queue.submit(Some(encoder.finish())); - - let result: Vec = { - let slice = image_buffer_staging.slice(..); - let (sender, receiver) = oneshot::channel(); - - slice.map_async(wgpu::MapMode::Read, move |result| { - let _ = sender.send(result.unwrap()); - }); - - device.poll(wgpu::Maintain::Wait); - block_on(receiver)?; - - let data = slice.get_mapped_range(); - bytemuck::cast_slice(&data).to_vec() - }; - - let mut pixels_set = 0; - for element in result.iter() { - if element.red > 0.0 || element.green > 0.0 || element.blue > 0.0 || element.alpha > 0.0 { - pixels_set += 1; - } - } - - let result: Vec = result.iter().flat_map(Pixel::to_rgba_u8).collect(); - let mut image = RgbaImage::from_raw(IMAGE_SETTINGS.size, IMAGE_SETTINGS.size, result) - .expect("Unable to create image"); - - image.save("gasket.png")?; - - Ok(()) -} diff --git a/crates/flare-lib/src/lib.rs b/crates/flare-lib/src/lib.rs index b615c91..94ca959 100644 --- a/crates/flare-lib/src/lib.rs +++ b/crates/flare-lib/src/lib.rs @@ -1,219 +1,5 @@ -extern crate core; - -const fn create_bind_group_layout_entry( - binding: u32, - ty: wgpu::BufferBindingType, -) -> wgpu::BindGroupLayoutEntry { - wgpu::BindGroupLayoutEntry { - binding, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - } -} - -fn create_bind_group_entry(binding: u32, resource: &wgpu::Buffer) -> wgpu::BindGroupEntry { - wgpu::BindGroupEntry { - binding, - resource: resource.as_entire_binding(), - } -} - -pub struct IterateTransformsEntry { - pub bind_group_layout: wgpu::BindGroupLayout, - pub pipeline_layout: wgpu::PipelineLayout, - pub compute_pipeline: wgpu::ComputePipeline, -} - -impl IterateTransformsEntry { - #[must_use] - const fn bind_group_layout_entries() -> [wgpu::BindGroupLayoutEntry; 6] { - [ - create_bind_group_layout_entry(0, wgpu::BufferBindingType::Uniform), - create_bind_group_layout_entry(1, wgpu::BufferBindingType::Storage { read_only: true }), - create_bind_group_layout_entry(2, wgpu::BufferBindingType::Storage { read_only: true }), - create_bind_group_layout_entry(3, wgpu::BufferBindingType::Storage { read_only: true }), - create_bind_group_layout_entry( - 4, - wgpu::BufferBindingType::Storage { read_only: false }, - ), - create_bind_group_layout_entry( - 5, - wgpu::BufferBindingType::Storage { read_only: false }, - ), - ] - } - - pub fn new(device: &wgpu::Device, shader: &wgpu::ShaderModule) -> Self { - let layout_entries = Self::bind_group_layout_entries(); - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iterate_transforms"), - entries: &layout_entries, - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("iterate_transforms"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - - let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: Some("iterate_transforms"), - layout: Some(&pipeline_layout), - module: shader, - entry_point: Some("iterate_transforms"), - compilation_options: Default::default(), - cache: Default::default(), - }); - - Self { - bind_group_layout, - pipeline_layout, - compute_pipeline - } - } - - pub fn bind_group( - self: &Self, - device: &wgpu::Device, - iter_settings: &wgpu::Buffer, - transform: &wgpu::Buffer, - transform_final: &wgpu::Buffer, - variation: &wgpu::Buffer, - thread_state: &wgpu::Buffer, - point: &wgpu::Buffer, - ) -> wgpu::BindGroup { - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iterate_transforms"), - layout: &self.bind_group_layout, - entries: &[ - create_bind_group_entry(0, iter_settings), - create_bind_group_entry(1, transform), - create_bind_group_entry(2, transform_final), - create_bind_group_entry(3, variation), - create_bind_group_entry(4, thread_state), - create_bind_group_entry(5, point), - ], - }) - } -} - -pub struct RenderImageHistogramEntry { - pub bind_group_layout: wgpu::BindGroupLayout, - pub pipeline_layout: wgpu::PipelineLayout, - pub compute_pipeline: wgpu::ComputePipeline, -} - -impl RenderImageHistogramEntry { - const fn bind_group_layout_entries() -> [wgpu::BindGroupLayoutEntry; 4] { - [ - create_bind_group_layout_entry(0, wgpu::BufferBindingType::Uniform), - create_bind_group_layout_entry(1, wgpu::BufferBindingType::Storage { read_only: true }), - create_bind_group_layout_entry(2, wgpu::BufferBindingType::Storage { read_only: true }), - create_bind_group_layout_entry(3, wgpu::BufferBindingType::Storage { read_only: false }), - ] - } - - pub fn new(device: &wgpu::Device, shader: &wgpu::ShaderModule) -> Self { - let layout_entries = Self::bind_group_layout_entries(); - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("render_image_histogram"), - entries: &layout_entries, - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("render_image_histogram"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - - let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: Some("render_image_histogram"), - layout: Some(&pipeline_layout), - module: shader, - entry_point: Some("render_image_histogram"), - compilation_options: Default::default(), - cache: Default::default(), - }); - - Self { - bind_group_layout, - pipeline_layout, - compute_pipeline - } - } - - pub fn bind_group(self: &Self, device: &wgpu::Device, image_settings: &wgpu::Buffer, palette: &wgpu::Buffer, point_buffer: &wgpu::Buffer, image_histogram: &wgpu::Buffer) -> wgpu::BindGroup{ - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("render_image_histogram"), - layout: &self.bind_group_layout, - entries: &[ - create_bind_group_entry(0, image_settings), - create_bind_group_entry(1, palette), - create_bind_group_entry(2, point_buffer), - create_bind_group_entry(3, image_histogram), - ] - }) - } -} - -pub struct RenderImageEntry { - pub bind_group_layout: wgpu::BindGroupLayout, - pub pipeline_layout: wgpu::PipelineLayout, - pub compute_pipeline: wgpu::ComputePipeline, -} - -impl RenderImageEntry { - const fn bind_group_layout_entries() -> [wgpu::BindGroupLayoutEntry; 3] { - [ - create_bind_group_layout_entry(0, wgpu::BufferBindingType::Uniform), - create_bind_group_layout_entry(1, wgpu::BufferBindingType::Storage { read_only: true }), - create_bind_group_layout_entry(2, wgpu::BufferBindingType::Storage { read_only: false }), - ] - } - - pub fn new(device: &wgpu::Device, shader: &wgpu::ShaderModule) -> Self { - let layout_entries = Self::bind_group_layout_entries(); - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("render_image"), - entries: &layout_entries, - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("render_image"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - - let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: Some("render_image"), - layout: Some(&pipeline_layout), - module: shader, - entry_point: Some("render_image"), - compilation_options: Default::default(), - cache: Default::default(), - }); - - Self { - bind_group_layout, - pipeline_layout, - compute_pipeline - } - } - - pub fn bind_group(self: &Self, device: &wgpu::Device, image_settings: &wgpu::Buffer, image_histogram: &wgpu::Buffer, image: &wgpu::Buffer) -> wgpu::BindGroup { - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("render_image"), - layout: &self.bind_group_layout, - entries: &[ - create_bind_group_entry(0, image_settings), - create_bind_group_entry(1, image_histogram), - create_bind_group_entry(2, image), - ], - }) - } +#[cfg(test)] +mod tests { + #[test] + fn it_works() {} } \ No newline at end of file diff --git a/crates/flare-shader/src/lib.rs b/crates/flare-shader/src/lib.rs index a55c8af..6f925a6 100644 --- a/crates/flare-shader/src/lib.rs +++ b/crates/flare-shader/src/lib.rs @@ -1,196 +1,259 @@ -#![no_std] -pub mod points; -pub mod transforms; -pub mod variations; +#![cfg_attr(not(test), no_std)] #[cfg(test)] mod tests; +use core::mem::transmute; +use glam::{vec2, Affine2, FloatExt, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use rand::distributions::Standard; -use rand::{Rng, RngCore}; -use rand_xoshiro::Xoshiro256Plus; +use rand::Rng; +use rand_xoshiro::Xoshiro128Plus; use spirv_std::num_traits::Float; -pub use points::*; -pub use transforms::*; -pub use variations::*; - use spirv_std::spirv; -#[derive(Copy, Clone, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)] +#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] #[repr(C)] -pub struct IterSettings { - pub transform_count: u32, - pub fuse_count: u32, - pub iteration_count: u32, +pub struct Coefs { + pub a: f32, + pub b: f32, + pub c: f32, + pub d: f32, + pub e: f32, + pub f: f32, } -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct IterState { - pub rng_seed: [u64; 4], - pub current_point: Point, -} +impl Coefs { + pub const IDENTITY: Coefs = Coefs { + a: 1.0, + b: 0.0, + c: 0.0, + d: 0.0, + e: 1.0, + f: 0.0, + }; -fn next_transform<'a>( - rng: &mut impl RngCore, - transform_weight: f32, - transforms: &'a [TransformSpec], -) -> &'a TransformSpec { - let mut current_weight = rng.sample::(Standard) * transform_weight; - let mut transform_index = 0usize; - loop { - current_weight -= transforms[transform_index].weight; - if current_weight <= 0.0 { - return &transforms[transform_index]; - } + pub const ZERO: Coefs = Coefs { + a: 0.0, + b: 0.0, + c: 0.0, + d: 0.0, + e: 0.0, + f: 0.0, + }; - transform_index += 1; + pub fn transform_point2(&self, point: Vec2) -> Vec2 { + Vec2::new( + self.a * point.x + self.b * point.y + self.c, + self.d * point.x + self.e * point.y + self.f, + ) } } -#[spirv(compute(threads(1)))] -pub fn iterate_transforms( - #[spirv(global_invocation_id)] _global_id: glam::UVec3, - #[spirv(uniform, descriptor_set = 0, binding = 0)] iter_settings: &IterSettings, - #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] transforms: &[TransformSpec], - #[spirv(storage_buffer, descriptor_set = 0, binding = 2)] transform_final: &TransformSpec, - #[spirv(storage_buffer, descriptor_set = 0, binding = 3)] variations: &[VariationSpec], - #[spirv(storage_buffer, descriptor_set = 0, binding = 4)] thread_state: &mut [IterState], - #[spirv(storage_buffer, descriptor_set = 0, binding = 5)] point_buffer: &mut [Point], -) { - let thread_id = 0; - let thread_state_current = &mut thread_state[thread_id]; +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct Transform { + pub coefs: Coefs, + pub post_coefs: Coefs, + pub weight: f32, + pub variation_offset: u32, + pub variation_count: u32, + pub color: f32, + pub color_speed: f32, +} - let mut total_weight = 0.0f32; - for i in 0..iter_settings.transform_count as usize { +impl Transform { + pub fn apply(&self, variations: &[Variation], point: Vec4) -> Vec4 { + // Apply the affine transformation + let point_transform = self.coefs.transform_point2(point.xy()).extend(point.z); + + // Accumulate each variation + let offset = self.variation_offset as usize; + let count = self.variation_count as usize; + let mut point_variation = Vec3::ZERO; + for i in offset..count { + point_variation += variations[i].apply(self.coefs, point_transform); + } + + // Apply the affine post transformation + let point_transform = self + .post_coefs + .transform_point2(point_variation.xy()) + .extend(point_variation.z); + + // Mix color + let color = point.w.lerp(self.color, self.color_speed); + point_transform.extend(color) + } +} + +#[derive(Debug, Copy, Clone)] +#[repr(u32)] +pub enum VariationKind { + Linear = 0, +} + +// UNSAFE: Sound for enums with defined bit patterns and a guaranteed zero discriminant? +unsafe impl bytemuck::Pod for VariationKind {} +unsafe impl bytemuck::Zeroable for VariationKind {} + +pub struct Variation { + pub kind: VariationKind, + pub weight: f32, + pub params: [f32; 8], +} + +impl Variation { + pub const IDENTITY: Variation = Variation { + kind: VariationKind::Linear, + weight: 1.0, + params: [0.0; 8], + }; + + pub fn apply(&self, _coefs: Coefs, point: Vec3) -> Vec3 { + match self.kind { + VariationKind::Linear => point, + } + } +} + +#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct ImageSettings { + pub image_width: i32, + pub image_height: i32, + pub scale: f32, + pub zoom: f32, + pub rotate: f32, + pub offset_x: f32, + pub offset_y: f32, +} + +impl ImageSettings { + pub(crate) fn pixel_index(&self, point: UVec2) -> u32 { + point.x + point.y * self.image_width as u32 + } + + pub fn ifs_pixel_index(&self, point: Vec2) -> Option { + let camera = Affine2::from_scale_angle_translation( + Vec2::splat(2f32.powf(self.zoom)), + self.rotate.to_radians(), + -vec2(self.offset_x, self.offset_y), + ); + let image = Affine2::from_scale_angle_translation( + Vec2::splat(self.scale), + 0.0, + vec2( + self.image_width as f32 / 2.0, + self.image_height as f32 / 2.0, + ), + ); + + // NOTE: order matters here; `camera * image` will give bad results + let ifs_to_image = image * camera; + let image_point = ifs_to_image.transform_point2(point).as_ivec2(); + + if image_point.x < 0 + || image_point.x >= self.image_width + || image_point.y < 0 + || image_point.y >= self.image_height + { + None + } else { + Some(self.pixel_index(image_point.as_uvec2())) + } + } +} + +#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct ThreadState { + pub rng: [u32; 4], + pub point: Vec4, +} + +#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct IterSample { + pub pixel_index: u32, + pub color: f32, +} + +fn next_transform<'a, R: Rng>( + rng: &mut R, + total_weight: f32, + transforms: &'a [Transform], +) -> &'a Transform { + let mut sample = rng.sample::(Standard) * total_weight; + for i in 0..transforms.len() { + sample -= transforms[i].weight; + if sample <= 0.0 { + return &transforms[i]; + } + } + unreachable!() +} + +const FUSE_COUNT: u32 = 20; +const ITER_COUNT: u32 = 100_000; +const ACCUMULATE_SAMPLES_THREADS_PER_WORKGROUP: u32 = 1; +#[spirv(compute(threads(1)))] +pub fn accumulate_samples( + #[spirv(num_workgroups)] num_workgroups: UVec3, + #[spirv(workgroup_id)] workgroup_id: UVec3, + #[spirv(local_invocation_index)] local_invocation_index: u32, + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] image_settings: &ImageSettings, + #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] transforms: &[Transform], + #[spirv(storage_buffer, descriptor_set = 0, binding = 2)] transform_final: &Transform, + #[spirv(storage_buffer, descriptor_set = 0, binding = 3)] variations: &[Variation], + #[spirv(storage_buffer, descriptor_set = 0, binding = 4)] thread_state: &mut [ThreadState], + #[spirv(storage_buffer, descriptor_set = 0, binding = 5)] samples: &mut [IterSample], +) { + // Re-creating GlobalLinearID - https://webgpufundamentals.org/webgpu/lessons/webgpu-compute-shaders.html + let workgroup_index = workgroup_id.x + + workgroup_id.y * num_workgroups.x + + workgroup_id.z * num_workgroups.x + + num_workgroups.y; + let global_invocation_index = + workgroup_index * ACCUMULATE_SAMPLES_THREADS_PER_WORKGROUP + local_invocation_index; + let global_id = global_invocation_index as usize; + + let mut total_weight = 0f32; + for i in 0..transforms.len() { total_weight += transforms[i].weight; } - // Rather than dealing with bytes slices, just convert to the RNG holder directly - let mut rng = - unsafe { core::mem::transmute::<_, Xoshiro256Plus>(thread_state_current.rng_seed) }; + let mut rng: Xoshiro128Plus = + unsafe { transmute::<_, Xoshiro128Plus>(thread_state[global_id].rng) }; + let mut point = thread_state[global_id].point; - let mut current_point = thread_state_current.current_point; - for _i in 0..iter_settings.fuse_count as usize { - let transform = next_transform(&mut rng, total_weight, transforms); - current_point = transform.apply(variations, current_point); + for _i in 0..FUSE_COUNT as usize { + point = next_transform(&mut rng, total_weight, transforms).apply(variations, point); } - for i in 0..iter_settings.iteration_count as usize { - let transform = next_transform(&mut rng, total_weight, transforms); - current_point = transform.apply(variations, current_point); + // let camera = accum_settings.camera; + let sample_offset = global_id * ITER_COUNT as usize; + for i in 0..ITER_COUNT as usize { + point = next_transform(&mut rng, total_weight, transforms).apply(variations, point); - point_buffer[i] = transform_final.apply(variations, current_point); - } - - thread_state_current.rng_seed = unsafe { core::mem::transmute::<_, [u64; 4]>(rng) }; - thread_state_current.current_point = current_point; -} - -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct ImageSettings { - pub size: u32, - pub point_count: u32, - pub palette_count: u32, -} - -#[derive(Copy, Clone, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct Color { - pub red: f32, - pub green: f32, - pub blue: f32, -} - -#[derive(Copy, Clone, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct Pixel { - pub red: f32, - pub green: f32, - pub blue: f32, - pub alpha: f32, -} - -impl Pixel { - pub fn to_rgba_u8(&self) -> [u8; 4] { - [ - (self.red * 255.0) as u8, - (self.green * 255.0) as u8, - (self.blue * 255.0) as u8, - (self.alpha * 255.0) as u8, - ] - } -} - -impl Color { - pub const BLACK: Self = Color { red: 0.0, green: 0.0, blue: 0.0 }; - pub const WHITE: Self = Color { red: 1.0, green: 1.0, blue: 1.0 }; -} - -#[spirv(compute(threads(1)))] -pub fn render_image_histogram( - #[spirv(global_invocation_id)] _global_id: glam::UVec3, - #[spirv(uniform, descriptor_set = 0, binding = 0)] image_settings: &ImageSettings, - #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] palette: &[Color], - #[spirv(storage_buffer, descriptor_set = 0, binding = 2)] point_buffer: &[Point], - #[spirv(storage_buffer, descriptor_set = 0, binding = 3)] image_histogram: &mut [Pixel], -) { - for i in 0..image_settings.point_count as usize { - let point = &point_buffer[i]; - - // Simple camera - square image using the range -2 to 2 - let pixel_x = ((point.x + 2.0) * image_settings.size as f32 / 4f32) as i32; - let pixel_y = ((point.y + 2.0) * image_settings.size as f32 / 4f32) as i32; - - // Pixel outside viewable range - if pixel_x < 0 || pixel_x >= image_settings.size as i32 || pixel_y < 0 || pixel_y >= image_settings.size as i32 { - continue; - } - - let pixel_index = (pixel_y * image_settings.size as i32 + pixel_x) as usize; - - // Get the colors - let palette_index = (point.color * image_settings.palette_count as f32) as u32 * 3; - let color = palette[palette_index as usize]; - - // Update the image - let mut pixel = &mut image_histogram[pixel_index]; - *pixel = Pixel { - red: pixel.red + color.red, - green: pixel.green + color.green, - blue: pixel.blue + color.blue, - alpha: pixel.alpha + 1.0, - }; - } -} - -#[spirv(compute(threads(1)))] -pub fn render_image( - #[spirv(global_invocation_id)] _global_id: glam::UVec3, - #[spirv(uniform, descriptor_set = 0, binding = 0)] image_settings: &ImageSettings, - #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] image_histogram: &[Pixel], - #[spirv(storage_buffer, descriptor_set = 0, binding = 2)] image: &mut [Pixel], -) { - // Normalize pixels based on alpha scaling - let pixel_count = image_settings.size * image_settings.size; - for i in 0..pixel_count as usize { - let pixel_histogram = &image_histogram[i]; - if pixel_histogram.alpha <= 0.0 { - continue; - } - - // `log10` has issues, use a change-of-base instead: - // https://github.com/Rust-GPU/rust-gpu/issues/199 - let w_log10 = pixel_histogram.alpha.ln() / (Float::ln(10.0)); - - let scale = w_log10 / (pixel_histogram.alpha * 1.5); - // Clipping individual channels to 1.0 is bad - image[i] = Pixel { - red: (pixel_histogram.red * scale).min(1.0), - green: (pixel_histogram.green * scale).min(1.0), - blue: (pixel_histogram.blue * scale).min(1.0), - alpha: (pixel_histogram.alpha * scale).min(1.0), + let final_point = transform_final.apply(variations, point); + let final_color = final_point.w; + let final_sample = match image_settings.ifs_pixel_index(final_point.xy()) { + None => IterSample { + pixel_index: u32::MAX, + color: 0.0, + }, + Some(pixel_index) => IterSample { + pixel_index, + color: final_color, + }, }; + + samples[sample_offset + i].pixel_index = final_sample.pixel_index; + samples[sample_offset + i].color = final_sample.color; } + + let current_thread = &mut thread_state[global_id]; + + current_thread.rng = unsafe { transmute::<_, [u32; 4]>(rng) }; + current_thread.point = point; } diff --git a/crates/flare-shader/src/points.rs b/crates/flare-shader/src/points.rs deleted file mode 100644 index 503aca5..0000000 --- a/crates/flare-shader/src/points.rs +++ /dev/null @@ -1,55 +0,0 @@ -use glam::FloatExt; -use rand::distributions::{Distribution, Standard}; -use rand::{Rng, RngCore}; - -#[derive(Copy, Clone, Debug, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct Point { - pub x: f32, - pub y: f32, - pub z: f32, - pub color: f32, -} - -pub struct BiUnit; - -impl Distribution for BiUnit { - fn sample(&self, rng: &mut R) -> f32 { - rng.sample::(Standard) * 2.0 - 1.0 - } -} - -impl Point { - pub fn new_xy(x: f32, y: f32) -> Point { - Point { - x, - y, - ..Default::default() - } - } - - pub fn new_color(color: f32) -> Point { - Point { - color, - ..Default::default() - } - } - - pub fn new_random(rng: &mut impl RngCore) -> Point { - Point { - x: rng.sample(BiUnit), - y: rng.sample(BiUnit), - z: rng.sample(BiUnit), - color: rng.sample(Standard), - } - } - - pub fn lerp_color(self, color: f32, color_speed: f32) -> Self { - Point { - x: self.x, - y: self.y, - z: self.z, - color: self.color.lerp(color, color_speed), - } - } -} \ No newline at end of file diff --git a/crates/flare-shader/src/tests.rs b/crates/flare-shader/src/tests.rs index 296870c..4a6d596 100644 --- a/crates/flare-shader/src/tests.rs +++ b/crates/flare-shader/src/tests.rs @@ -1,85 +1,228 @@ -use crate::{ - iterate_transforms, Coefs, IterSettings, IterState, Point, TransformSpec, VariationKind, - VariationParams, VariationSpec, -}; +use crate::{Coefs, ImageSettings, Transform, Variation}; +use glam::{uvec2, vec2, vec4}; #[test] -fn apply_transform_coefs() { - let transform = TransformSpec { - coefs: Coefs::new(1., 2., 3., 4., 5., 6.), - post_coefs: Coefs::IDENTITY, - variation_offset: 0, - variation_count: 1, - ..Default::default() - }; - let variations = [VariationSpec { - kind: VariationKind::Linear, - weight: 1., - params: VariationParams::default(), - }]; - - let point_out = transform.apply(&variations, Point::new_xy(1., 1.)); - assert_eq!(point_out, Point::new_xy(6., 15.)); +fn apply_coefs_identity() { + let point = vec2(0.5, 0.5); + assert_eq!(point, Coefs::IDENTITY.transform_point2(point)); } #[test] -fn apply_transform_post_coefs() { - let transform = TransformSpec { - coefs: Coefs::IDENTITY, - post_coefs: Coefs::new(1., 2., 3., 4., 5., 6.), - variation_offset: 0, - variation_count: 1, - ..Default::default() - }; - let variations = [VariationSpec { - kind: VariationKind::Linear, - weight: 1., - params: VariationParams::default(), - }]; - - let point = transform.apply(&variations, Point::new_xy(1., 1.)); - assert_eq!(point, Point::new_xy(6., 15.)); -} - -#[test] -fn apply_transform_variation() { - let transform = TransformSpec { - coefs: Coefs::IDENTITY, - post_coefs: Coefs::IDENTITY, - variation_offset: 0, - variation_count: 1, - ..Default::default() - }; - let variations = [VariationSpec { - kind: VariationKind::Linear, - weight: 0.5, - params: VariationParams::default(), - }]; - - let point = transform.apply(&variations, Point::new_xy(1., 1.)); - assert_eq!(point, Point::new_xy(0.5, 0.5)); -} - -#[test] -fn iterate_transform_advances_state() { - let mut thread_states = [IterState { - rng_seed: [1, 2, 3, 4], - current_point: Default::default(), - }]; - - iterate_transforms( - glam::uvec3(0, 0, 0), - &IterSettings { - transform_count: 1, - fuse_count: 1, - iteration_count: 0, - }, - &[TransformSpec::IDENTITY], - &TransformSpec::IDENTITY, - &[], - &mut thread_states, - &mut [] +fn apply_coefs_translate() { + let point = vec2(0.5, 0.5); + assert_eq!( + vec2(1., 1.), + Coefs { + c: 0.5, + f: 0.5, + ..Coefs::IDENTITY + } + .transform_point2(point) ); - - assert_ne!(thread_states[0].rng_seed, [1, 2, 3, 4]); +} + +#[test] +fn apply_transform_identity() { + let point = vec4(0.5, 0.5, 0.0, 0.0); + let transform = Transform { + coefs: Coefs::IDENTITY, + post_coefs: Coefs::IDENTITY, + weight: 1.0, + variation_offset: 0, + variation_count: 1, + color: 0.0, + color_speed: 0.0, + }; + let variations = [Variation::IDENTITY]; + assert_eq!(point, transform.apply(&variations, point)); +} + +#[test] +fn apply_transform_scale() { + let point = vec4(0.5, 0.5, 0.0, 0.0); + let transform = Transform { + coefs: Coefs { + a: 2.0, + e: 2.0, + ..Coefs::ZERO + }, + post_coefs: Coefs::IDENTITY, + weight: 1.0, + variation_offset: 0, + variation_count: 1, + color: 0.0, + color_speed: 0.0, + }; + let variations = [Variation::IDENTITY]; + assert_eq!( + vec4(1.0, 1.0, 0.0, 0.0), + transform.apply(&variations, point) + ); +} + +#[test] +fn apply_transform_color() { + let point = vec4(0.5, 0.5, 0.0, 0.0); + let transform = Transform { + coefs: Coefs { + a: 2.0, + e: 2.0, + ..Coefs::ZERO + }, + post_coefs: Coefs::IDENTITY, + weight: 1.0, + variation_offset: 0, + variation_count: 1, + color: 1.0, + color_speed: 0.2, + }; + let variations = [Variation::IDENTITY]; + assert_eq!( + vec4(1.0, 1.0, 0.0, 0.2), + transform.apply(&variations, point) + ); +} + +#[test] +fn map_pixel_histogram() { + let image = ImageSettings { + image_width: 600, + image_height: 0, + scale: 0.0, + zoom: 0.0, + rotate: 0.0, + offset_x: 0.0, + offset_y: 0.0, + }; + + assert_eq!( + image.pixel_index(uvec2(100, 100)), + (100 + 100 * image.image_width) as u32 + ); +} + +fn assert_image_index(image: &ImageSettings, ifs_x: f32, ifs_y: f32, pixel_x: u32, pixel_y: u32) { + assert_eq!( + image.ifs_pixel_index(vec2(ifs_x, ifs_y)), + Some(image.pixel_index(uvec2(pixel_x, pixel_y))) + ); +} + +#[test] +fn ifs_pixel_index_square() { + // Square image with scale chosen to match range [-2, 2] + let image = ImageSettings { + image_width: 1000, + image_height: 1000, + scale: 250.0, + zoom: 0.0, + rotate: 0.0, + offset_x: 0.0, + offset_y: 0.0, + }; + + assert_eq!(image.ifs_pixel_index(vec2(-2.5, 2.5)), None); + + assert_image_index(&image, 0.0, 0.0, 500, 500); + assert_image_index(&image, -2.0, -2.0, 0, 0); + assert_image_index(&image, 1.999, -2.0, 999, 0); + assert_image_index(&image, -2.0, 1.999, 0, 999); + assert_image_index(&image, 1.999, 1.999, 999, 999); +} + +#[test] +fn ifs_pixel_index_landscape() { + // 16:9 aspect ratio with scale chosen to give a range of [-2, 2] on the X axis. + // On the Y axis, effective range will be [-1.125, 1.125] + let image = ImageSettings { + image_width: 1600, + image_height: 900, + scale: 400.0, + zoom: 0.0, + rotate: 0.0, + offset_x: 0.0, + offset_y: 0.0, + }; + + assert_image_index(&image, 0.0, 0.0, 800, 450); + assert_image_index(&image, -2.0, -1.125, 0, 0); + assert_image_index(&image, 1.999, -1.125, 1599, 0); + assert_image_index(&image, -2.0, 1.12499, 0, 899); + assert_image_index(&image, 1.999, 1.12499, 1599, 899); +} + +#[test] +fn ifs_pixel_index_portrait() { + // 9:16 aspect ratio; swaps effective range of X/Y axes from landscape test + let image = ImageSettings { + image_width: 900, + image_height: 1600, + scale: 400.0, + zoom: 0.0, + rotate: 0.0, + offset_x: 0.0, + offset_y: 0.0, + }; + + assert_image_index(&image, 0.0, 0.0, 450, 800); + assert_image_index(&image, -1.125, -2.0, 0, 0); + assert_image_index(&image, 1.12499, -2.0, 899, 0); + assert_image_index(&image, -1.125, 1.999, 0, 1599); + assert_image_index(&image, 1.12499, 1.999, 899, 1599); +} + +#[test] +fn ifs_pixel_index_rotate() { + let image = ImageSettings { + image_width: 1000, + image_height: 1000, + scale: 250., + zoom: 0., + rotate: 90., + offset_x: 0., + offset_y: 0., + }; + + // Rotation at the origin has no effect + assert_image_index(&image, 0.0, 0.0, 500, 500); + + // 90-degree rotation means this point will become (-0.5, 0.5) + assert_image_index(&image, 0.5, 0.5, 375, 625); +} + +#[test] +fn ifs_pixel_index_zoom() { + // Square image with scale chosen to match [-2, 2], + // but zoom 1 leads to an effective range of [-1, 1] + let image = ImageSettings { + image_width: 1000, + image_height: 1000, + scale: 250., + zoom: 1., + rotate: 0., + offset_x: 0., + offset_y: 0., + }; + + // Zoom has no effect at the origin + assert_image_index(&image, 0.0, 0.0, 500, 500); + + assert_image_index(&image, 0.5, 0.5, 750, 750); +} + +#[test] +fn ifs_pixel_index_offset() { + // Square image with scale chosen to match [-2, 2], + // but offset leads to an effective range of [-1.5, 2.5] + let image = ImageSettings { + image_width: 1000, + image_height: 1000, + scale: 250., + zoom: 0.0, + rotate: 0.0, + offset_x: 0.5, + offset_y: 0.5, + }; + + assert_image_index(&image, 0.0, 0.0, 375, 375); } diff --git a/crates/flare-shader/src/transforms.rs b/crates/flare-shader/src/transforms.rs deleted file mode 100644 index f076cba..0000000 --- a/crates/flare-shader/src/transforms.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::{Point, VariationSpec}; - -#[derive(Copy, Clone, Default, Debug, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct Coefs { - pub a: f32, - pub b: f32, - pub c: f32, - pub d: f32, - pub e: f32, - pub f: f32, -} - -impl Coefs { - pub const IDENTITY: Coefs = Coefs { - a: 1., - b: 0., - c: 0., - d: 0., - e: 1., - f: 0. - }; - - pub const fn new(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self { - Coefs { a, b, c, d, e, f } - } - - pub fn apply(&self, point: &Point) -> Point { - Point { - x: point.x * self.a + point.y * self.b + self.c, - y: point.x * self.d + point.y * self.e + self.f, - z: point.z, - color: point.color - } - } -} - -#[derive(Copy, Clone, Default, Debug, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct TransformSpec { - pub coefs: Coefs, - pub post_coefs: Coefs, - pub weight: f32, - pub color: f32, - pub color_speed: f32, - pub variation_offset: u32, - pub variation_count: u32, -} - -impl TransformSpec { - pub const IDENTITY: TransformSpec = TransformSpec { - coefs: Coefs::IDENTITY, - post_coefs: Coefs::IDENTITY, - weight: 0., - color: 0., - color_speed: 0., - variation_offset: 0, - variation_count: 0, - }; - - pub fn apply(&self, variations: &[VariationSpec], point: Point) -> Point { - let point = self.coefs.apply(&point); - - let variation_offset = self.variation_offset as usize; - let variation_count = self.variation_count as usize; - let mut point_variation = Point::default(); - for i in variation_offset..variation_count { - let point_variation_current = variations[i].apply(&point); - point_variation = Point { - x: point_variation.x + point_variation_current.x, - y: point_variation.y + point_variation_current.y, - z: point_variation.z + point_variation_current.z, - color: point_variation.color, - }; - } - - self.post_coefs.apply(&point_variation).lerp_color(self.color, self.color_speed) - } -} diff --git a/crates/flare-shader/src/variations.rs b/crates/flare-shader/src/variations.rs deleted file mode 100644 index b81aa5d..0000000 --- a/crates/flare-shader/src/variations.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::Point; - -#[derive(Copy, Clone, Debug)] -#[repr(C)] -pub enum VariationKind { - Linear, -} - -// UNSAFE: I think this is fine for repr(C) enums? -unsafe impl bytemuck::Pod for VariationKind {} -unsafe impl bytemuck::Zeroable for VariationKind {} - -#[derive(Copy, Clone, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(transparent)] -pub struct VariationParams([f32; 8]); - -impl VariationParams { - pub const fn new() -> Self { - Self([0f32; 8]) - } -} - -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct VariationSpec { - pub kind: VariationKind, - pub weight: f32, - pub params: VariationParams, -} - -impl VariationSpec { - pub fn apply(&self, point: &Point) -> Point { - let point = match self.kind { - VariationKind::Linear => apply_linear(&self.params, point), - }; - Point { - x: point.x * self.weight, - y: point.y * self.weight, - z: point.z * self.weight, - color: point.color, - } - } -} - -fn apply_linear(_params: &VariationParams, point: &Point) -> Point { - *point -}