From bb4e0aa66907fd41787f7807bf1f17b63d9e6d5e Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sat, 20 Jun 2026 09:20:38 -0400 Subject: [PATCH 1/8] Add a coefficients trait for converting the affine coefficient notation `flam3` uses to how `glam` represents it --- enkou-shaders/src/lib.rs | 64 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/enkou-shaders/src/lib.rs b/enkou-shaders/src/lib.rs index d2b343a..cfadb05 100644 --- a/enkou-shaders/src/lib.rs +++ b/enkou-shaders/src/lib.rs @@ -2,11 +2,73 @@ use bytemuck::{Pod, Zeroable}; use core::f32::consts::PI; -use glam::{Vec3, Vec4, vec2, vec3}; +use glam::{Affine2, Vec3, Vec4, vec2, vec3}; #[cfg(target_arch = "spirv")] use spirv_std::num_traits::Float; use spirv_std::spirv; +/// Utility trait for [`Affine2`] to convert between `flam3` notation and [`glam`]. +pub trait Coefficients2 { + /// Convert affine transformation coefficients to the [`Affine2`] representation. + /// Parameters use the following form: + /// + /// ```text + /// (a * x + b * y + c, d * x + e * y + f) + /// ``` + /// + /// ``` + /// # use glam::{Affine2, vec2}; + /// # use crate::enkou_shaders::Coefficients2; + /// let coefs = Affine2::from_coefficients(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); + /// let (x, y) = (7.0, 8.0); + /// assert_eq!( + /// coefs.transform_point2(vec2(x, y)), + /// vec2( + /// coefs.a() * x + coefs.b() * y + coefs.c(), + /// coefs.d() * x + coefs.e() * y + coefs.f() + /// ) + /// ); + /// ``` + fn from_coefficients(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Affine2; + + fn a(&self) -> f32; + fn b(&self) -> f32; + fn c(&self) -> f32; + fn d(&self) -> f32; + fn e(&self) -> f32; + fn f(&self) -> f32; +} + +impl Coefficients2 for Affine2 { + fn from_coefficients(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Affine2 { + Affine2::from_cols_array(&[a, d, b, e, c, f]) + } + + fn a(&self) -> f32 { + self.matrix2.x_axis.x + } + + fn b(&self) -> f32 { + self.matrix2.y_axis.x + } + + fn c(&self) -> f32 { + self.translation.x + } + + fn d(&self) -> f32 { + self.matrix2.x_axis.y + } + + fn e(&self) -> f32 { + self.matrix2.y_axis.y + } + + fn f(&self) -> f32 { + self.translation.y + } +} + #[derive(Copy, Clone, Pod, Zeroable)] #[repr(C)] pub struct ShaderConstants { From 1709336062b2e941a054dd27bec8872459a70171 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sat, 20 Jun 2026 10:05:04 -0400 Subject: [PATCH 2/8] Add an initial implementation of the chaos game --- Cargo.lock | 17 +++++++++++++++ Cargo.toml | 3 ++- enkou-shaders/Cargo.toml | 5 +++-- enkou-shaders/src/lib.rs | 46 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb8c6b3..4684777 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,6 +155,7 @@ version = "0.1.0" dependencies = [ "bytemuck", "glam", + "rand", "spirv-std", ] @@ -210,6 +211,7 @@ version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "898f5a568a84989b6c0f8caa50a93074b97dbdc58fc6d9543157bb4562758933" dependencies = [ + "bytemuck", "libm", ] @@ -435,6 +437,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "raw-string" version = "0.3.5" diff --git a/Cargo.toml b/Cargo.toml index fa17411..cdedf41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu.git", rev = "67f1ff2" anyhow = "1.0.102" bytemuck = { version = "1.25.0", features = ["derive"] } -glam = { version = "0.33.1", default-features = false, features = ["libm"] } +glam = { version = "0.33.1", default-features = false, features = ["bytemuck", "scalar-math"] } +rand = { version = "0.10.1", default-features = false } rspirv = "0.13.0" diff --git a/enkou-shaders/Cargo.toml b/enkou-shaders/Cargo.toml index 59ffd5a..ecbf4a0 100644 --- a/enkou-shaders/Cargo.toml +++ b/enkou-shaders/Cargo.toml @@ -10,6 +10,7 @@ repository.workspace = true workspace = true [dependencies] -spirv-std.workspace = true -glam.workspace = true bytemuck.workspace = true +glam.workspace = true +rand.workspace = true +spirv-std.workspace = true diff --git a/enkou-shaders/src/lib.rs b/enkou-shaders/src/lib.rs index cfadb05..ce89c67 100644 --- a/enkou-shaders/src/lib.rs +++ b/enkou-shaders/src/lib.rs @@ -1,8 +1,12 @@ +//! # Enkou #![no_std] +#![warn(missing_docs)] use bytemuck::{Pod, Zeroable}; use core::f32::consts::PI; -use glam::{Affine2, Vec3, Vec4, vec2, vec3}; +use glam::{Affine2, Vec3, Vec4, vec2, vec3, Vec2}; +use rand::{Rng, RngExt}; +use rand::distr::StandardUniform; #[cfg(target_arch = "spirv")] use spirv_std::num_traits::Float; use spirv_std::spirv; @@ -69,6 +73,46 @@ impl Coefficients2 for Affine2 { } } +#[derive(Copy, Clone, Pod, Zeroable)] +#[repr(C)] +pub struct Transform { + pub coefficients: Affine2, +} + +impl Transform { + pub fn new(coefficients: Affine2) -> Self { + Transform { coefficients } + } + + pub fn transform_point(&self, point: Vec2) -> Vec2 { + self.coefficients.transform_point2(point) + } +} + +/// Iterate one step in the chaos game; choose the next transform, apply it, +/// and return the resulting point. Also returns the transform index so that +/// path-dependent weights (the "Xaos" table in Apophysis) can be chosen +/// for the next iteration step. +/// +/// # Arguments +/// +/// * `weights` - Weights are assumed to be normalized; adding all elements together should return the value 1 +pub fn step_chaos_game(rng: &mut R, point: Vec2, weights: &[f32], transforms: &[Transform]) -> (Vec2, u32) { + let mut choice_weight = rng.sample::(StandardUniform); + let mut transform_index: u32 = 0; + + for weight in weights { + choice_weight -= weight; + if choice_weight <= 0.0 { + break; + } + + transform_index += 1; + } + + (transforms[transform_index as usize].transform_point(point), transform_index) +} + #[derive(Copy, Clone, Pod, Zeroable)] #[repr(C)] pub struct ShaderConstants { From 90f886f97114a5dff2c814e29be2d82ce98436dd Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sat, 20 Jun 2026 15:10:25 -0400 Subject: [PATCH 3/8] Implement the IFS camera --- Cargo.lock | 1 + Cargo.toml | 1 + enkou-shaders/Cargo.toml | 1 + enkou-shaders/src/camera.rs | 156 ++++++++++++++++++++++++++++++++++++ enkou-shaders/src/lib.rs | 18 ++++- 5 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 enkou-shaders/src/camera.rs diff --git a/Cargo.lock b/Cargo.lock index 4684777..3fb615e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,6 +155,7 @@ version = "0.1.0" dependencies = [ "bytemuck", "glam", + "libm", "rand", "spirv-std", ] diff --git a/Cargo.toml b/Cargo.toml index cdedf41..75f972f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu.git", rev = "67f1ff2" anyhow = "1.0.102" bytemuck = { version = "1.25.0", features = ["derive"] } glam = { version = "0.33.1", default-features = false, features = ["bytemuck", "scalar-math"] } +libm = "0.2.16" rand = { version = "0.10.1", default-features = false } rspirv = "0.13.0" diff --git a/enkou-shaders/Cargo.toml b/enkou-shaders/Cargo.toml index ecbf4a0..4e89b76 100644 --- a/enkou-shaders/Cargo.toml +++ b/enkou-shaders/Cargo.toml @@ -12,5 +12,6 @@ workspace = true [dependencies] bytemuck.workspace = true glam.workspace = true +libm.workspace = true rand.workspace = true spirv-std.workspace = true diff --git a/enkou-shaders/src/camera.rs b/enkou-shaders/src/camera.rs new file mode 100644 index 0000000..532378b --- /dev/null +++ b/enkou-shaders/src/camera.rs @@ -0,0 +1,156 @@ +use bytemuck::{Pod, Zeroable}; +use glam::{Affine2, IVec2, UVec2, Vec2, vec2}; +use libm::powf; + +#[derive(Copy, Clone, Pod, Zeroable)] +#[repr(C)] +pub struct Camera { + transform: Affine2, +} + +impl Camera { + /// Construct a new camera that maps IFS coordinates to pixel coordinates. + /// + /// The camera object is itself an affine transformation, but it's helpful to express + /// the parameters in individual steps, and compose them internally. + /// + /// # Arguments + /// + /// * `dimensions` - Width and height of the output image (in pixels). + /// * `center` - Location of the origin in IFS coordinates. Positive `x` shifts the image + /// left, and positive `y` position shifts the image up. + /// * `rotate` - Rotation angle (in radians) of IFS coordinates. Rotation is applied after the + /// `center` translation, so it is about the new origin. + /// * `zoom` - Zoom factor applied to IFS coordinates. IFS coordinates are scaled by + /// `pow(2, zoom)`, so a zoom factor of 0 is the identity. + /// * `scale` - Pixels per unit of IFS coordinates. By default, this parameter is chosen such + /// that the largest dimension will cover the range `[-2, 2]`, but values higher or lower + /// can be used as a secondary zoom. + pub fn new(dimensions: UVec2, center: Vec2, rotate: f32, zoom: Vec2, scale: Vec2) -> Camera { + let ifs_center_transform = Affine2::from_translation(-center); + let zoom_transform = Affine2::from_scale(vec2(powf(2.0, zoom.x), powf(2.0, zoom.y))); + let scale_transform = Affine2::from_scale(scale); + let rotate_transform = Affine2::from_angle(rotate); + let image_center_transform = Affine2::from_translation((dimensions / 2).as_vec2()); + + let transform = image_center_transform + * rotate_transform + * scale_transform + * zoom_transform + * ifs_center_transform; + + Camera { transform } + } + + /// Map a point from IFS coordinates to pixel coordinates. + /// + /// ``` + /// # use glam::{vec2, ivec2, uvec2, Vec2}; + /// # use crate::enkou_shaders::camera::Camera; + /// // Output image is 600x600 pixels, centered at the origin, no rotation, no zoom, + /// // and scaled such that it covers the range [-2, 2]. + /// // Use the origin as the IFS coordinate, so the pixel coordinate is the center of the image + /// let camera = Camera::new( + /// uvec2(600, 600), + /// Vec2::ZERO, + /// 0.0, + /// Vec2::ZERO, + /// vec2(150.0, 150.0) + /// ); + /// assert_eq!(camera.transform_point(vec2(0.0, 0.0)), ivec2(300, 300)); + /// ``` + pub fn transform_point(&self, point: Vec2) -> IVec2 { + self.transform.transform_point2(point).as_ivec2() + } +} + +#[cfg(test)] +mod test { + use crate::camera::Camera; + use glam::{Affine2, Vec2, ivec2, uvec2, vec2}; + use libm::powf; + + #[test] + pub fn manual_camera() { + let starting_point = vec2(1.0, 1.0); + + // Move the origin; points move right and up by one unit, giving us (2.0, 2.0) + let center = vec2(-1.0, -1.0); + let point = starting_point - center; + + // Rotate about the new origin; points move counter-clockwise, giving us (-2.0, 2.0) + let rotate = 90.0f32.to_radians(); + let point = Affine2::from_angle(rotate).transform_point2(point); + + // Zoom in by a factor of 1; points will be twice as far from the origin, + // giving us (-4.0, 4.0) + let zoom = vec2(1.0, 1.0); + let point = point * vec2(powf(2.0, zoom.x), powf(2.0, zoom.y)); + + // Apply scaling; scale 100 in a 1000 x 1000 image is an effective range + // of [-5, 5] in IFS coordinates. + // After scaling, the point is (-400.0, 400.0) + let scale = vec2(100.0, 100.0); + let point = point * scale; + + // Move the origin from (0, 0) to image center, + // giving us (100.0, 900.0) + let dimensions = uvec2(1000, 1000); + let point = point.as_ivec2() + dimensions.as_ivec2() / 2; + + // Check that the camera implementation ends up at the same point + let camera = Camera::new(dimensions, center, rotate, zoom, scale); + + // The camera is implemented by composing affine transforms, + // which ends up with a slightly different result because of rounding. + let error = camera.transform_point(starting_point) - point; + assert!(error.x.abs() <= 1); + assert!(error.y.abs() <= 1); + } + + #[test] + pub fn point_outside_camera() { + // Scale 250 for an image 1000 x 1000 gives an effective range of [-2, 2] + let camera = Camera::new( + uvec2(1000, 1000), + Vec2::ZERO, + 0.0, + Vec2::ZERO, + vec2(250.0, 250.0), + ); + + // Converting a point outside the effective range is legal, but outside the image bounds + assert_eq!(camera.transform_point(vec2(3.0, 3.0)), ivec2(1250, 1250)); + } + + #[test] + pub fn point_outside_camera_negative() { + // Scale 250 for an image 1000 x 1000 gives an effective range of [-2, 2] + let camera = Camera::new( + uvec2(1000, 1000), + Vec2::ZERO, + 0.0, + Vec2::ZERO, + vec2(250.0, 250.0), + ); + + // Converting a point outside the effective range is legal, but outside the image bounds + assert_eq!(camera.transform_point(vec2(-3.0, -3.0)), ivec2(-250, -250)); + } + + #[test] + pub fn aspect_ratio() { + // Scale 100 for an image 1600 x 900 gives an effective X range of [-8, 8], + // and effective Y range of [-4.5, 4.5] + let camera = Camera::new( + uvec2(1600, 900), + Vec2::ZERO, + 0.0, + Vec2::ZERO, + vec2(100.0, 100.0), + ); + + // This point is inside the image width, but outside its height + assert_eq!(camera.transform_point(vec2(6.0, 6.0)), ivec2(1400, 1050)); + } +} diff --git a/enkou-shaders/src/lib.rs b/enkou-shaders/src/lib.rs index ce89c67..170d1b7 100644 --- a/enkou-shaders/src/lib.rs +++ b/enkou-shaders/src/lib.rs @@ -2,11 +2,13 @@ #![no_std] #![warn(missing_docs)] +pub mod camera; + use bytemuck::{Pod, Zeroable}; use core::f32::consts::PI; -use glam::{Affine2, Vec3, Vec4, vec2, vec3, Vec2}; -use rand::{Rng, RngExt}; +use glam::{Affine2, Vec2, Vec3, Vec4, vec2, vec3}; use rand::distr::StandardUniform; +use rand::{Rng, RngExt}; #[cfg(target_arch = "spirv")] use spirv_std::num_traits::Float; use spirv_std::spirv; @@ -97,7 +99,12 @@ impl Transform { /// # Arguments /// /// * `weights` - Weights are assumed to be normalized; adding all elements together should return the value 1 -pub fn step_chaos_game(rng: &mut R, point: Vec2, weights: &[f32], transforms: &[Transform]) -> (Vec2, u32) { +pub fn step_chaos_game( + rng: &mut R, + point: Vec2, + weights: &[f32], + transforms: &[Transform], +) -> (Vec2, u32) { let mut choice_weight = rng.sample::(StandardUniform); let mut transform_index: u32 = 0; @@ -110,7 +117,10 @@ pub fn step_chaos_game(rng: &mut R, point: Vec2, weights: &[f32], transf transform_index += 1; } - (transforms[transform_index as usize].transform_point(point), transform_index) + ( + transforms[transform_index as usize].transform_point(point), + transform_index, + ) } #[derive(Copy, Clone, Pod, Zeroable)] From beb1c8526ff1c05df9ff815165b062412d26643e Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Mon, 22 Jun 2026 20:46:47 -0400 Subject: [PATCH 4/8] Implement a basic Sierpinski Gasket IFS --- Cargo.lock | 913 ++++++++++++++++++++++++++++++- Cargo.toml | 4 +- enkou-shaders/Cargo.toml | 6 + enkou-shaders/examples/gasket.rs | 75 +++ enkou-shaders/src/camera.rs | 21 +- enkou-shaders/src/chaos_game.rs | 68 +++ enkou-shaders/src/lib.rs | 54 +- enkou-shaders/src/transform.rs | 18 + 8 files changed, 1103 insertions(+), 56 deletions(-) create mode 100644 enkou-shaders/examples/gasket.rs create mode 100644 enkou-shaders/src/chaos_game.rs create mode 100644 enkou-shaders/src/transform.rs diff --git a/Cargo.lock b/Cargo.lock index 3fb615e..48923e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,123 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "autocfg" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -29,6 +134,27 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + +[[package]] +name = "built" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "bytemuck" version = "1.25.0" @@ -49,6 +175,12 @@ dependencies = [ "syn", ] +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "camino" version = "1.2.3" @@ -111,12 +243,70 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "directories" version = "6.0.0" @@ -149,15 +339,25 @@ dependencies = [ "syn", ] +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + [[package]] name = "enkou-shaders" version = "0.1.0" dependencies = [ + "anyhow", "bytemuck", "glam", + "image", "libm", - "rand", + "rand 0.10.1", + "rand_xoshiro", "spirv-std", + "tempfile", ] [[package]] @@ -169,6 +369,26 @@ dependencies = [ "rspirv 0.13.0+sdk-1.4.341.0", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -186,6 +406,67 @@ dependencies = [ "typeid", ] +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -206,6 +487,28 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "glam" version = "0.33.1" @@ -216,6 +519,17 @@ dependencies = [ "libm", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.17.1" @@ -325,6 +639,45 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7" + [[package]] name = "indexmap" version = "2.14.0" @@ -335,18 +688,64 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libm" version = "0.2.16" @@ -362,6 +761,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.2" @@ -374,12 +779,121 @@ version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", +] + [[package]] name = "memchr" version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -390,6 +904,12 @@ dependencies = [ "libm", ] +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + [[package]] name = "option-ext" version = "0.2.0" @@ -405,12 +925,37 @@ dependencies = [ "num-traits", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -420,6 +965,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -429,6 +983,46 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.45" @@ -438,13 +1032,48 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + [[package]] name = "rand" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "rand_core", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", ] [[package]] @@ -453,23 +1082,107 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_xoshiro" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662effc7698e08ea324d3acccf8d9d7f7bf79b9785e270a174ea36e56900c91d" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.4", + "rand_chacha", + "simd_helpers", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rgb", +] + [[package]] name = "raw-string" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0501e134c6905fee1f10fed25b0a7e1261bf676cffac9543a7d0730dec01af2" +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_users" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom", + "getrandom 0.2.17", "libredox", "thiserror", ] +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + [[package]] name = "rspirv" version = "0.12.0+sdk-1.3.268.0" @@ -509,6 +1222,25 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "semver" version = "1.0.28" @@ -593,6 +1325,27 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "smallvec" version = "1.15.2" @@ -691,6 +1444,19 @@ dependencies = [ "syn", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror" version = "2.0.18" @@ -711,6 +1477,20 @@ dependencies = [ "syn", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "tinystr" version = "0.8.3" @@ -798,12 +1578,83 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "windows-link" version = "0.2.1" @@ -828,12 +1679,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "writeable" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + [[package]] name = "yoke" version = "0.8.3" @@ -857,6 +1720,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.8" @@ -916,3 +1799,27 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 75f972f..54064db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,9 @@ spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu.git", rev = "67f1ff2" anyhow = "1.0.102" bytemuck = { version = "1.25.0", features = ["derive"] } glam = { version = "0.33.1", default-features = false, features = ["bytemuck", "scalar-math"] } +image = { version = "0.25.10", default-features = false, features = ["default-formats"]} libm = "0.2.16" rand = { version = "0.10.1", default-features = false } rspirv = "0.13.0" - +rand_xoshiro = "0.8.1" +tempfile = "3.27.0" diff --git a/enkou-shaders/Cargo.toml b/enkou-shaders/Cargo.toml index 4e89b76..6bd320c 100644 --- a/enkou-shaders/Cargo.toml +++ b/enkou-shaders/Cargo.toml @@ -15,3 +15,9 @@ glam.workspace = true libm.workspace = true rand.workspace = true spirv-std.workspace = true + +[dev-dependencies] +anyhow.workspace = true +image.workspace = true +rand_xoshiro.workspace = true +tempfile.workspace = true diff --git a/enkou-shaders/examples/gasket.rs b/enkou-shaders/examples/gasket.rs new file mode 100644 index 0000000..ae0b8ac --- /dev/null +++ b/enkou-shaders/examples/gasket.rs @@ -0,0 +1,75 @@ +use anyhow::{Context, Result}; +use enkou_shaders::Coefficients2; +use enkou_shaders::camera::Camera; +use enkou_shaders::chaos_game::ChaosGame; +use enkou_shaders::transform::Transform; +use glam::{Affine2, Vec2, uvec2, UVec2}; +use image::{GrayImage, Luma}; +use rand::SeedableRng; +use rand_xoshiro::Xoshiro256StarStar; +use std::mem; +use std::process::Command; +use tempfile::{NamedTempFile}; + +const ITERATIONS: u32 = 50_000; +const ITERATIONS_DISCARD: u32 = 20; +const IMAGE_DIMENSION: UVec2 = uvec2(600, 600); + +pub fn main() -> Result<()> { + let seed: u64 = 4; // chosen by fair dice roll + let mut rng = Xoshiro256StarStar::seed_from_u64(seed); + + let transforms = [ + // F_0: (x / 2, y / 2) + Transform::new(Affine2::from_coefficients(0.5, 0.0, 0.0, 0.0, 0.5, 0.0)), + // F_1: ((x + 1) / 2, y / 2) + Transform::new(Affine2::from_coefficients(0.5, 0.0, 0.5, 0.0, 0.5, 0.0)), + // F_2: (x / 2, (y + 1) / 2) + Transform::new(Affine2::from_coefficients(0.5, 0.0, 0.0, 0.0, 0.5, 0.5)), + ]; + + let weights = [1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0]; + + let mut image = GrayImage::new(IMAGE_DIMENSION.x, IMAGE_DIMENSION.y); + + // The gasket is defined on the range [0, 1] for both X and Y + let camera = Camera::new( + IMAGE_DIMENSION, + Vec2::ONE * 0.5, + 0.0, + Vec2::ZERO, + IMAGE_DIMENSION.as_vec2(), + ); + + let mut chaos_game = ChaosGame::new(&mut rng, &transforms, &weights); + for i in 0..ITERATIONS { + let next_point = chaos_game.next().unwrap(); + + if i < ITERATIONS_DISCARD { + continue; + } + + if let Some(next_point) = camera.transform_point_to_image(next_point) { + image.put_pixel(next_point.x, next_point.y, Luma([255u8])) + } + } + + let temp = NamedTempFile::with_suffix(".png").context("Unable to create file for image")?; + image.save(temp.path()).context("Unable to save image")?; + + let open_program = cfg_select! { + unix => "xdg-open", + _ => panic!("Unknown system"), + }; + + Command::new(open_program) + .arg(temp.path()) + .spawn()? + .wait()?; + + // In case the image viewer forks and gives control back prior to reading the file, + // drop it and don't run the constructor + mem::forget(temp); + + Ok(()) +} diff --git a/enkou-shaders/src/camera.rs b/enkou-shaders/src/camera.rs index 532378b..a05125b 100644 --- a/enkou-shaders/src/camera.rs +++ b/enkou-shaders/src/camera.rs @@ -5,6 +5,7 @@ use libm::powf; #[derive(Copy, Clone, Pod, Zeroable)] #[repr(C)] pub struct Camera { + dimensions: UVec2, transform: Affine2, } @@ -39,7 +40,10 @@ impl Camera { * zoom_transform * ifs_center_transform; - Camera { transform } + Camera { + dimensions, + transform, + } } /// Map a point from IFS coordinates to pixel coordinates. @@ -62,6 +66,21 @@ impl Camera { pub fn transform_point(&self, point: Vec2) -> IVec2 { self.transform.transform_point2(point).as_ivec2() } + + /// Map a point from IFS coordinates to pixel coordinates (like [`transform_point`]), + /// and check that the result is within the provided image dimensions. + pub fn transform_point_to_image(&self, point: Vec2) -> Option { + let pixel_coordinates = self.transform_point(point); + if pixel_coordinates.x < 0 + || pixel_coordinates.y < 0 + || (pixel_coordinates.x as u32) >= self.dimensions.x + || (pixel_coordinates.y as u32) >= self.dimensions.y + { + None + } else { + Some(pixel_coordinates.as_uvec2()) + } + } } #[cfg(test)] diff --git a/enkou-shaders/src/chaos_game.rs b/enkou-shaders/src/chaos_game.rs new file mode 100644 index 0000000..718ab56 --- /dev/null +++ b/enkou-shaders/src/chaos_game.rs @@ -0,0 +1,68 @@ +use glam::{vec2, Vec2}; +use rand::distr::{Distribution, StandardUniform}; +use rand::{Rng, RngExt}; +use crate::transform::Transform; + +struct BiUnit; +impl Distribution for BiUnit { + fn sample(&self, rng: &mut R) -> f32 { + rng.sample::(StandardUniform) * 2.0 - 1.0 + } +} + +/// Iterate one step in the chaos game; choose the next transform, apply it, +/// and return the resulting point. Also returns the transform index so that +/// path-dependent weights (the "Xaos" table in Apophysis) can be chosen +/// for the next iteration step. +/// +/// # Arguments +/// +/// * `weights` - Weights are assumed to be normalized; adding all elements together should return the value 1 +pub fn step_chaos_game( + point: Vec2, + rng: &mut R, + transforms: &[Transform], + weights: &[f32], +) -> (Vec2, u32) { + let mut choice_weight = rng.sample::(StandardUniform); + let mut transform_index: u32 = 0; + + for weight in weights { + choice_weight -= weight; + if choice_weight <= 0.0 { + break; + } + + transform_index += 1; + } + + ( + transforms[transform_index as usize].transform_point(point), + transform_index, + ) +} + +pub struct ChaosGame<'a, R: Rng> { + current_point: Vec2, + rng: &'a mut R, + transforms: &'a [Transform], + weights: &'a [f32], +} + +impl<'a, R: Rng> ChaosGame<'a, R> { + pub fn new(rng: &'a mut R, transforms: &'a [Transform], weights: &'a [f32]) -> Self { + let current_point = vec2(rng.sample(BiUnit), rng.sample(BiUnit)); + ChaosGame { current_point, rng, transforms, weights } + } +} + +impl<'a, R: Rng> Iterator for ChaosGame<'a, R> { + type Item = Vec2; + + fn next(&mut self) -> Option { + let (next_point, _) = step_chaos_game(self.current_point, self.rng, self.transforms, self.weights); + self.current_point = next_point; + + Some(next_point) + } +} \ No newline at end of file diff --git a/enkou-shaders/src/lib.rs b/enkou-shaders/src/lib.rs index 170d1b7..b46424a 100644 --- a/enkou-shaders/src/lib.rs +++ b/enkou-shaders/src/lib.rs @@ -3,12 +3,12 @@ #![warn(missing_docs)] pub mod camera; +pub mod chaos_game; +pub mod transform; use bytemuck::{Pod, Zeroable}; use core::f32::consts::PI; -use glam::{Affine2, Vec2, Vec3, Vec4, vec2, vec3}; -use rand::distr::StandardUniform; -use rand::{Rng, RngExt}; +use glam::{Affine2, Vec3, Vec4, vec2, vec3}; #[cfg(target_arch = "spirv")] use spirv_std::num_traits::Float; use spirv_std::spirv; @@ -75,54 +75,6 @@ impl Coefficients2 for Affine2 { } } -#[derive(Copy, Clone, Pod, Zeroable)] -#[repr(C)] -pub struct Transform { - pub coefficients: Affine2, -} - -impl Transform { - pub fn new(coefficients: Affine2) -> Self { - Transform { coefficients } - } - - pub fn transform_point(&self, point: Vec2) -> Vec2 { - self.coefficients.transform_point2(point) - } -} - -/// Iterate one step in the chaos game; choose the next transform, apply it, -/// and return the resulting point. Also returns the transform index so that -/// path-dependent weights (the "Xaos" table in Apophysis) can be chosen -/// for the next iteration step. -/// -/// # Arguments -/// -/// * `weights` - Weights are assumed to be normalized; adding all elements together should return the value 1 -pub fn step_chaos_game( - rng: &mut R, - point: Vec2, - weights: &[f32], - transforms: &[Transform], -) -> (Vec2, u32) { - let mut choice_weight = rng.sample::(StandardUniform); - let mut transform_index: u32 = 0; - - for weight in weights { - choice_weight -= weight; - if choice_weight <= 0.0 { - break; - } - - transform_index += 1; - } - - ( - transforms[transform_index as usize].transform_point(point), - transform_index, - ) -} - #[derive(Copy, Clone, Pod, Zeroable)] #[repr(C)] pub struct ShaderConstants { diff --git a/enkou-shaders/src/transform.rs b/enkou-shaders/src/transform.rs new file mode 100644 index 0000000..c692672 --- /dev/null +++ b/enkou-shaders/src/transform.rs @@ -0,0 +1,18 @@ +use bytemuck::{Pod, Zeroable}; +use glam::{Affine2, Vec2}; + +#[derive(Copy, Clone, Pod, Zeroable)] +#[repr(C)] +pub struct Transform { + pub coefficients: Affine2, +} + +impl Transform { + pub fn new(coefficients: Affine2) -> Self { + Transform { coefficients } + } + + pub fn transform_point(&self, point: Vec2) -> Vec2 { + self.coefficients.transform_point2(point) + } +} \ No newline at end of file From 67b94522d021fcba6ebab147d0b17fce2bf74079 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sat, 27 Jun 2026 10:11:01 -0400 Subject: [PATCH 5/8] Run `cargo fmt` --- enkou-shaders/examples/gasket.rs | 4 ++-- enkou-shaders/src/chaos_game.rs | 16 +++++++++++----- enkou-shaders/src/transform.rs | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/enkou-shaders/examples/gasket.rs b/enkou-shaders/examples/gasket.rs index ae0b8ac..0b7e1da 100644 --- a/enkou-shaders/examples/gasket.rs +++ b/enkou-shaders/examples/gasket.rs @@ -3,13 +3,13 @@ use enkou_shaders::Coefficients2; use enkou_shaders::camera::Camera; use enkou_shaders::chaos_game::ChaosGame; use enkou_shaders::transform::Transform; -use glam::{Affine2, Vec2, uvec2, UVec2}; +use glam::{Affine2, UVec2, Vec2, uvec2}; use image::{GrayImage, Luma}; use rand::SeedableRng; use rand_xoshiro::Xoshiro256StarStar; use std::mem; use std::process::Command; -use tempfile::{NamedTempFile}; +use tempfile::NamedTempFile; const ITERATIONS: u32 = 50_000; const ITERATIONS_DISCARD: u32 = 20; diff --git a/enkou-shaders/src/chaos_game.rs b/enkou-shaders/src/chaos_game.rs index 718ab56..56190f0 100644 --- a/enkou-shaders/src/chaos_game.rs +++ b/enkou-shaders/src/chaos_game.rs @@ -1,7 +1,7 @@ -use glam::{vec2, Vec2}; +use crate::transform::Transform; +use glam::{Vec2, vec2}; use rand::distr::{Distribution, StandardUniform}; use rand::{Rng, RngExt}; -use crate::transform::Transform; struct BiUnit; impl Distribution for BiUnit { @@ -52,7 +52,12 @@ pub struct ChaosGame<'a, R: Rng> { impl<'a, R: Rng> ChaosGame<'a, R> { pub fn new(rng: &'a mut R, transforms: &'a [Transform], weights: &'a [f32]) -> Self { let current_point = vec2(rng.sample(BiUnit), rng.sample(BiUnit)); - ChaosGame { current_point, rng, transforms, weights } + ChaosGame { + current_point, + rng, + transforms, + weights, + } } } @@ -60,9 +65,10 @@ impl<'a, R: Rng> Iterator for ChaosGame<'a, R> { type Item = Vec2; fn next(&mut self) -> Option { - let (next_point, _) = step_chaos_game(self.current_point, self.rng, self.transforms, self.weights); + let (next_point, _) = + step_chaos_game(self.current_point, self.rng, self.transforms, self.weights); self.current_point = next_point; Some(next_point) } -} \ No newline at end of file +} diff --git a/enkou-shaders/src/transform.rs b/enkou-shaders/src/transform.rs index c692672..e343827 100644 --- a/enkou-shaders/src/transform.rs +++ b/enkou-shaders/src/transform.rs @@ -15,4 +15,4 @@ impl Transform { pub fn transform_point(&self, point: Vec2) -> Vec2 { self.coefficients.transform_point2(point) } -} \ No newline at end of file +} From a9da463041d785f1bebadd7f3de6e3a92619536d Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sat, 27 Jun 2026 10:11:26 -0400 Subject: [PATCH 6/8] Fix the documentation --- enkou-shaders/examples/gasket.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enkou-shaders/examples/gasket.rs b/enkou-shaders/examples/gasket.rs index 0b7e1da..51dfb6e 100644 --- a/enkou-shaders/examples/gasket.rs +++ b/enkou-shaders/examples/gasket.rs @@ -68,7 +68,7 @@ pub fn main() -> Result<()> { .wait()?; // In case the image viewer forks and gives control back prior to reading the file, - // drop it and don't run the constructor + // drop it and don't run the destructor mem::forget(temp); Ok(()) From 344ecc3450c9cba618962431b82a526d0367d620 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sat, 27 Jun 2026 11:02:19 -0400 Subject: [PATCH 7/8] Add missing documentation --- enkou-shaders/src/camera.rs | 17 ++++++++++++----- enkou-shaders/src/chaos_game.rs | 19 +++++++++++++++++++ enkou-shaders/src/lib.rs | 8 ++++++-- enkou-shaders/src/transform.rs | 10 +++++++++- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/enkou-shaders/src/camera.rs b/enkou-shaders/src/camera.rs index a05125b..7844233 100644 --- a/enkou-shaders/src/camera.rs +++ b/enkou-shaders/src/camera.rs @@ -1,7 +1,14 @@ +//! # Camera +//! +//! Map points from the IFS coordinate system to pixel coordinates. This is a lossy transformation. use bytemuck::{Pod, Zeroable}; use glam::{Affine2, IVec2, UVec2, Vec2, vec2}; use libm::powf; +/// Settings used to map IFS coordinates to pixel coordinates. +/// +/// The camera is itself an affine transformation, capable of zoom, rotation, and translation +/// of the IFS coordinates before rendering to the final image. #[derive(Copy, Clone, Pod, Zeroable)] #[repr(C)] pub struct Camera { @@ -10,10 +17,10 @@ pub struct Camera { } impl Camera { - /// Construct a new camera that maps IFS coordinates to pixel coordinates. + /// Construct a new camera for translating IFS coordinates to pixel coordinates. /// - /// The camera object is itself an affine transformation, but it's helpful to express - /// the parameters in individual steps, and compose them internally. + /// While the camera is implemented as a single affine transformation, it's helpful + /// to express the transform steps individually. /// /// # Arguments /// @@ -24,7 +31,7 @@ impl Camera { /// `center` translation, so it is about the new origin. /// * `zoom` - Zoom factor applied to IFS coordinates. IFS coordinates are scaled by /// `pow(2, zoom)`, so a zoom factor of 0 is the identity. - /// * `scale` - Pixels per unit of IFS coordinates. By default, this parameter is chosen such + /// * `scale` - Pixels per unit of IFS coordinates. This parameter is usually chosen such /// that the largest dimension will cover the range `[-2, 2]`, but values higher or lower /// can be used as a secondary zoom. pub fn new(dimensions: UVec2, center: Vec2, rotate: f32, zoom: Vec2, scale: Vec2) -> Camera { @@ -67,7 +74,7 @@ impl Camera { self.transform.transform_point2(point).as_ivec2() } - /// Map a point from IFS coordinates to pixel coordinates (like [`transform_point`]), + /// Map a point from IFS coordinates to pixel coordinates (like [`transform_point`](Camera::transform_point)), /// and check that the result is within the provided image dimensions. pub fn transform_point_to_image(&self, point: Vec2) -> Option { let pixel_coordinates = self.transform_point(point); diff --git a/enkou-shaders/src/chaos_game.rs b/enkou-shaders/src/chaos_game.rs index 56190f0..4d59e52 100644 --- a/enkou-shaders/src/chaos_game.rs +++ b/enkou-shaders/src/chaos_game.rs @@ -1,3 +1,17 @@ +//! # Chaos Game +//! +//! Fractal flames are a class of +//! [iterated function systems](https://en.wikipedia.org/wiki/Iterated_function_system) +//! that generate images following a simple algorithm: +//! +//! - Pick a starting point `(x, y)` +//! - Iterate: +//! - Pick a [`Transform`] from the set of available transforms +//! - Apply the current point to the chosen transform, generating a new point `(x, y)` +//! - Plot the new point `(x, y)` +//! +//! This algorithm is also known as the ["chaos game"](https://en.wikipedia.org/wiki/Chaos_game), +//! and it forms the basic system for producing images. use crate::transform::Transform; use glam::{Vec2, vec2}; use rand::distr::{Distribution, StandardUniform}; @@ -42,6 +56,10 @@ pub fn step_chaos_game( ) } +/// Iterator for chaos game state. Holds the current point and references to all other data +/// necessary to generate fractal flame images. +/// +/// New points in the chaos game are produced by iterating on the chaos game. pub struct ChaosGame<'a, R: Rng> { current_point: Vec2, rng: &'a mut R, @@ -50,6 +68,7 @@ pub struct ChaosGame<'a, R: Rng> { } impl<'a, R: Rng> ChaosGame<'a, R> { + /// Create a new chaos game iterator pub fn new(rng: &'a mut R, transforms: &'a [Transform], weights: &'a [f32]) -> Self { let current_point = vec2(rng.sample(BiUnit), rng.sample(BiUnit)); ChaosGame { diff --git a/enkou-shaders/src/lib.rs b/enkou-shaders/src/lib.rs index b46424a..d3d2a4a 100644 --- a/enkou-shaders/src/lib.rs +++ b/enkou-shaders/src/lib.rs @@ -13,9 +13,10 @@ use glam::{Affine2, Vec3, Vec4, vec2, vec3}; use spirv_std::num_traits::Float; use spirv_std::spirv; -/// Utility trait for [`Affine2`] to convert between `flam3` notation and [`glam`]. +/// Utility trait to convert between `flam3` notation and [`glam`]. +#[allow(missing_docs)] pub trait Coefficients2 { - /// Convert affine transformation coefficients to the [`Affine2`] representation. + /// Convert affine transformation coefficients to the [`glam`] representation. /// Parameters use the following form: /// /// ```text @@ -77,6 +78,7 @@ impl Coefficients2 for Affine2 { #[derive(Copy, Clone, Pod, Zeroable)] #[repr(C)] +#[allow(missing_docs)] pub struct ShaderConstants { pub width: u32, pub height: u32, @@ -84,11 +86,13 @@ pub struct ShaderConstants { } #[spirv(fragment)] +#[allow(missing_docs)] pub fn main_fs(vtx_color: Vec3, output: &mut Vec4) { *output = Vec4::from((vtx_color, 1.)); } #[spirv(vertex)] +#[allow(missing_docs)] pub fn main_vs( #[spirv(vertex_index)] vert_id: i32, #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] constants: &ShaderConstants, diff --git a/enkou-shaders/src/transform.rs b/enkou-shaders/src/transform.rs index e343827..fddcb56 100644 --- a/enkou-shaders/src/transform.rs +++ b/enkou-shaders/src/transform.rs @@ -1,17 +1,25 @@ +//! # Transform +//! +//! Transforms are the "functions" in an iterated function system. They take in a point, +//! and generate a new point. For fractal flames, transforms are always affine, +//! but produce more interesting images once we add variations. use bytemuck::{Pod, Zeroable}; use glam::{Affine2, Vec2}; +/// Affine transform for use in the [`chaos_game`](crate::chaos_game). #[derive(Copy, Clone, Pod, Zeroable)] #[repr(C)] pub struct Transform { - pub coefficients: Affine2, + coefficients: Affine2, } impl Transform { + /// Create a new transform from an affine transformation matrix pub fn new(coefficients: Affine2) -> Self { Transform { coefficients } } + /// Apply this transform to a point in IFS coordinates, producing a new point pub fn transform_point(&self, point: Vec2) -> Vec2 { self.coefficients.transform_point2(point) } From 55cece063ff8d0bc8ef1e5172cda0511ec2d8259 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sat, 27 Jun 2026 11:07:23 -0400 Subject: [PATCH 8/8] Fix documentation whitespace --- enkou-shaders/src/camera.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enkou-shaders/src/camera.rs b/enkou-shaders/src/camera.rs index 7844233..21c6867 100644 --- a/enkou-shaders/src/camera.rs +++ b/enkou-shaders/src/camera.rs @@ -1,12 +1,12 @@ //! # Camera -//! +//! //! Map points from the IFS coordinate system to pixel coordinates. This is a lossy transformation. use bytemuck::{Pod, Zeroable}; use glam::{Affine2, IVec2, UVec2, Vec2, vec2}; use libm::powf; /// Settings used to map IFS coordinates to pixel coordinates. -/// +/// /// The camera is itself an affine transformation, capable of zoom, rotation, and translation /// of the IFS coordinates before rendering to the final image. #[derive(Copy, Clone, Pod, Zeroable)]