From 7ca267e7842b54603fbaa588dec5673edb6a7cad Mon Sep 17 00:00:00 2001
From: Finn Bear <finnbearlabs@gmail.com>
Date: Sun, 8 May 2022 00:02:52 -0700
Subject: [PATCH 1/5] It begins.

---
 Cargo.lock                    | 384 +++++++++++++++++++++++++++++++++-
 Cargo.toml                    |   5 +-
 src/main.rs                   |   2 +
 src/slide/s6_fractals.rs      |  18 +-
 src/slide/s6_fractals/wgpu.rs |  62 ++++++
 5 files changed, 466 insertions(+), 5 deletions(-)
 create mode 100644 src/slide/s6_fractals/wgpu.rs

diff --git a/Cargo.lock b/Cargo.lock
index 4602ed3..0251c52 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -80,6 +80,21 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "arrayvec"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
+
+[[package]]
+name = "ash"
+version = "0.34.0+1.2.203"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0f780da53d0063880d45554306489f09dd8d1bda47688b4a57bc579119356df"
+dependencies = [
+ "libloading",
+]
+
 [[package]]
 name = "async-broadcast"
 version = "0.3.4"
@@ -195,6 +210,21 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
+[[package]]
+name = "bit-set"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -285,6 +315,12 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
+[[package]]
+name = "cfg_aliases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+
 [[package]]
 name = "cgl"
 version = "0.3.2"
@@ -334,6 +370,15 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "cmake"
+version = "0.1.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a"
+dependencies = [
+ "cc",
+]
+
 [[package]]
 name = "cocoa"
 version = "0.24.0"
@@ -365,6 +410,16 @@ dependencies = [
  "objc",
 ]
 
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
 [[package]]
 name = "color_quant"
 version = "1.1.0"
@@ -399,6 +454,12 @@ dependencies = [
  "custom_derive",
 ]
 
+[[package]]
+name = "copyless"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536"
+
 [[package]]
 name = "copypasta"
 version = "0.7.1"
@@ -561,6 +622,17 @@ version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
 
+[[package]]
+name = "d3d12"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c"
+dependencies = [
+ "bitflags",
+ "libloading",
+ "winapi",
+]
+
 [[package]]
 name = "dark-light"
 version = "0.2.2"
@@ -970,6 +1042,15 @@ dependencies = [
  "slab",
 ]
 
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
 [[package]]
 name = "generative_art_cartoon"
 version = "0.1.0"
@@ -978,9 +1059,12 @@ dependencies = [
  "egui_demo_lib",
  "image",
  "imageproc",
+ "pollster",
  "rand 0.8.5",
  "rand_chacha 0.3.1",
+ "shaderc",
  "structopt",
+ "wgpu",
 ]
 
 [[package]]
@@ -1100,6 +1184,45 @@ dependencies = [
  "gl_generator",
 ]
 
+[[package]]
+name = "gpu-alloc"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d"
+dependencies = [
+ "bitflags",
+ "gpu-alloc-types",
+]
+
+[[package]]
+name = "gpu-alloc-types"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "gpu-descriptor"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda"
+dependencies = [
+ "bitflags",
+ "gpu-descriptor-types",
+ "hashbrown 0.11.2",
+]
+
+[[package]]
+name = "gpu-descriptor-types"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126"
+dependencies = [
+ "bitflags",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.9.1"
@@ -1109,6 +1232,15 @@ dependencies = [
  "ahash 0.4.7",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+dependencies = [
+ "ahash 0.7.6",
+]
+
 [[package]]
 name = "heck"
 version = "0.3.3"
@@ -1133,6 +1265,12 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
+[[package]]
+name = "hexf-parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
+
 [[package]]
 name = "ident_case"
 version = "1.0.1"
@@ -1182,6 +1320,22 @@ dependencies = [
  "rusttype",
 ]
 
+[[package]]
+name = "indexmap"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.11.2",
+]
+
+[[package]]
+name = "inplace_it"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca"
+
 [[package]]
 name = "instant"
 version = "0.1.12"
@@ -1232,6 +1386,16 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "khronos-egl"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3"
+dependencies = [
+ "libc",
+ "libloading",
+]
+
 [[package]]
 name = "khronos_api"
 version = "3.1.0"
@@ -1333,6 +1497,20 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "metal"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0514f491f4cc03632ab399ee01e2c1c1b12d3e1cf2d667c1ff5f87d6dcd2084"
+dependencies = [
+ "bitflags",
+ "block",
+ "core-graphics-types",
+ "foreign-types",
+ "log",
+ "objc",
+]
+
 [[package]]
 name = "minimal-lexical"
 version = "0.2.1"
@@ -1371,6 +1549,24 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "naga"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3012f2dbcc79e8e0b5825a4836a7106a75dd9b2fe42c528163be0f572538c705"
+dependencies = [
+ "bit-set",
+ "bitflags",
+ "codespan-reporting",
+ "hexf-parse",
+ "indexmap",
+ "log",
+ "num-traits",
+ "rustc-hash",
+ "spirv",
+ "thiserror",
+]
+
 [[package]]
 name = "nalgebra"
 version = "0.30.1"
@@ -1641,6 +1837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
 dependencies = [
  "malloc_buf",
+ "objc_exception",
 ]
 
 [[package]]
@@ -1654,6 +1851,15 @@ dependencies = [
  "objc_id",
 ]
 
+[[package]]
+name = "objc_exception"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
+dependencies = [
+ "cc",
+]
+
 [[package]]
 name = "objc_id"
 version = "0.1.1"
@@ -1676,7 +1882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485"
 dependencies = [
  "dlv-list",
- "hashbrown",
+ "hashbrown 0.9.1",
 ]
 
 [[package]]
@@ -1802,6 +2008,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "pollster"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7"
+
 [[package]]
 name = "ppv-lite86"
 version = "0.2.16"
@@ -1851,6 +2063,12 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "profiling"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9145ac0af1d93c638c98c40cf7d25665f427b2a44ad0a99b1dccf3e2f25bb987"
+
 [[package]]
 name = "quick-xml"
 version = "0.22.0"
@@ -1949,6 +2167,12 @@ dependencies = [
  "rand_core 0.5.1",
 ]
 
+[[package]]
+name = "range-alloc"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6"
+
 [[package]]
 name = "raw-window-handle"
 version = "0.4.3"
@@ -2025,6 +2249,21 @@ version = "0.6.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
 
+[[package]]
+name = "renderdoc-sys"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
+
+[[package]]
+name = "roxmltree"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b"
+dependencies = [
+ "xmlparser",
+]
+
 [[package]]
 name = "rust-ini"
 version = "0.17.0"
@@ -2035,6 +2274,12 @@ dependencies = [
  "ordered-multimap",
 ]
 
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
 [[package]]
 name = "rusttype"
 version = "0.9.2"
@@ -2121,6 +2366,27 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
 
+[[package]]
+name = "shaderc"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80e6fe602a861622769530a23bc40bfba31adbf186d0c8412e83f5519c5d6bee"
+dependencies = [
+ "libc",
+ "shaderc-sys",
+]
+
+[[package]]
+name = "shaderc-sys"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3794498651f8173d0afbc0bb8aca45ced111098227e755dde4c0ef2888c8d0bf"
+dependencies = [
+ "cmake",
+ "libc",
+ "roxmltree",
+]
+
 [[package]]
 name = "shared_library"
 version = "0.1.9"
@@ -2204,6 +2470,16 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "spirv"
+version = "0.2.0+1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830"
+dependencies = [
+ "bitflags",
+ "num-traits",
+]
+
 [[package]]
 name = "static_assertions"
 version = "1.1.0"
@@ -2257,6 +2533,15 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
 [[package]]
 name = "textwrap"
 version = "0.11.0"
@@ -2650,6 +2935,97 @@ dependencies = [
  "cc",
 ]
 
+[[package]]
+name = "wgpu"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97cd781ff044d6d697b632a2e212032c2e957d1afaa21dbf58069cbb8f78567"
+dependencies = [
+ "arrayvec",
+ "js-sys",
+ "log",
+ "naga",
+ "parking_lot",
+ "raw-window-handle",
+ "smallvec",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "wgpu-core",
+ "wgpu-hal",
+ "wgpu-types",
+]
+
+[[package]]
+name = "wgpu-core"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4688c000eb841ca55f7b35db659b78d6e1cd77d7caf8fb929f4e181f754047d"
+dependencies = [
+ "arrayvec",
+ "bitflags",
+ "cfg_aliases",
+ "codespan-reporting",
+ "copyless",
+ "fxhash",
+ "log",
+ "naga",
+ "parking_lot",
+ "profiling",
+ "raw-window-handle",
+ "smallvec",
+ "thiserror",
+ "wgpu-hal",
+ "wgpu-types",
+]
+
+[[package]]
+name = "wgpu-hal"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d684ea6a34974a2fc19f1dfd183d11a62e22d75c4f187a574bb1224df8e056c2"
+dependencies = [
+ "arrayvec",
+ "ash",
+ "bit-set",
+ "bitflags",
+ "block",
+ "core-graphics-types",
+ "d3d12",
+ "foreign-types",
+ "fxhash",
+ "glow",
+ "gpu-alloc",
+ "gpu-descriptor",
+ "inplace_it",
+ "js-sys",
+ "khronos-egl",
+ "libloading",
+ "log",
+ "metal",
+ "naga",
+ "objc",
+ "parking_lot",
+ "profiling",
+ "range-alloc",
+ "raw-window-handle",
+ "renderdoc-sys",
+ "thiserror",
+ "wasm-bindgen",
+ "web-sys",
+ "wgpu-types",
+ "winapi",
+]
+
+[[package]]
+name = "wgpu-types"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "549533d9e1cdd4b4cda7718d33ff500fc4c34b5467b71d76b547ae0324f3b2a2"
+dependencies = [
+ "bitflags",
+]
+
 [[package]]
 name = "wide"
 version = "0.7.4"
@@ -2785,6 +3161,12 @@ version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
 
+[[package]]
+name = "xmlparser"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8"
+
 [[package]]
 name = "zbus"
 version = "2.1.1"
diff --git a/Cargo.toml b/Cargo.toml
index 6770c87..34448f7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,4 +10,7 @@ image = {version = "0.24", default-features = false, features=["png"]}
 imageproc = {git = "https://github.com/finnbear/imageproc", branch = "draw_filled_polygon"}
 rand = "0.8"
 rand_chacha = "0.3"
-structopt = "0.3"
\ No newline at end of file
+structopt = "0.3"
+wgpu = "0.12"
+pollster = "0.2"
+shaderc = "0.8"
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index d3ff066..f9e5dc0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,6 +2,8 @@
 #![feature(mixed_integer_ops)]
 #![feature(total_cmp)]
 #![feature(int_abs_diff)]
+// For shaderc
+#![feature(format_args_capture)]
 
 pub mod cartoon;
 pub mod color;
diff --git a/src/slide/s6_fractals.rs b/src/slide/s6_fractals.rs
index 1657fd2..d13e468 100644
--- a/src/slide/s6_fractals.rs
+++ b/src/slide/s6_fractals.rs
@@ -4,12 +4,13 @@ mod julia_gradient;
 mod mandelbrot;
 mod mandelbrot_gradient;
 mod rectangle;
+mod wgpu;
 
 use crate::color::set_alpha;
 use crate::component::arrow::Arrow;
 use crate::component::code::{pseudocode, Code};
 use crate::component::grid::Grid;
-use crate::egui::{Color32, Context, Frame, Pos2, Shape, Stroke, Ui};
+use crate::egui::{Color32, Context, Frame, Pos2, Rect, Shape, Stroke, Ui};
 use crate::fade_in::{fade_in, fade_in_manual, fade_out_manual};
 use crate::governor::Governor;
 use crate::slide::s4_automata::randomize_grid;
@@ -22,10 +23,12 @@ use crate::slide::s6_fractals::rectangle::rectangle;
 use crate::slide::Slide;
 use crate::window::WindowPosition;
 use eframe::egui::style::Margin;
-use eframe::egui::{Align2, Vec2};
+use eframe::egui::{Align2, ColorImage, Vec2};
 use eframe::epaint::CircleShape;
 use std::iter;
 use std::ops::{Bound, RangeBounds};
+use crate::component::image::Image;
+use crate::egui;
 
 /// Mandelbrot, etc.
 pub struct Fractals {
@@ -84,6 +87,10 @@ enum FractalsState {
         /// How many pixels of the grid we've filled in.
         pixels: usize,
     },
+    MandelbrotZoom {
+        /// Fractal zoom background image.
+        image: ColorImage,
+    }
 }
 
 impl Default for Fractals {
@@ -163,7 +170,8 @@ impl Slide for Fractals {
             FractalsState::MandelbrotGradient { .. } => {
                 self.state = FractalsState::JuliaGradient { pixels: 0 }
             }
-            FractalsState::JuliaGradient { .. } => return true,
+            FractalsState::JuliaGradient { .. } => self.state = FractalsState::MandelbrotZoom {image: ColorImage::new([1280, 720], Color32::RED)},
+            FractalsState::MandelbrotZoom {..} => return true,
         }
         false
     }
@@ -280,6 +288,10 @@ impl Slide for Fractals {
                 algo = Box::new(wrap_f32_algo(julia_gradient));
                 limit_pixels = Some(pixels);
             }
+            FractalsState::MandelbrotZoom {image} => {
+                let screen = ui.ctx().input().screen_rect();
+                egui::Image::new(&ui.ctx().load_texture("zoom", image.clone()), Vec2::ZERO).paint_at(ui, screen);
+            }
         }
 
         // Which pixels the algorithm should render.
diff --git a/src/slide/s6_fractals/wgpu.rs b/src/slide/s6_fractals/wgpu.rs
new file mode 100644
index 0000000..729b0e0
--- /dev/null
+++ b/src/slide/s6_fractals/wgpu.rs
@@ -0,0 +1,62 @@
+use wgpu::{Backends, ComputePipeline, ComputePipelineDescriptor, Device, Instance, Queue};
+
+struct Accelerator {
+    instance: Instance,
+    device: Device,
+    queue: Queue,
+    pipeline: ComputePipeline,
+}
+
+impl Accelerator {
+    pub fn new() -> Self {
+        let instance = Instance::new(Backends::all());
+        let adapter = pollster::block_on(instance
+            .request_adapter(&wgpu::RequestAdapterOptions {
+                power_preference: wgpu::PowerPreference::HighPerformance,
+                force_fallback_adapter: false,
+                compatible_surface: None,
+            }))
+            .unwrap();
+        let (device, queue) = pollster::block_on(adapter
+            .request_device(&Default::default(), None))
+            .unwrap();
+        let compute_module = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
+            label: Some("accelerator shader"),
+            source: vs_data,
+        });
+        let compute_src = include_str!("compute.glsl");
+        let mut compiler = shaderc::Compiler::new().unwrap();
+        let compute_spirv = compiler
+            .compile_into_spirv(
+                vs_src,
+                shaderc::ShaderKind::,
+                "compute.glsl",
+                "main",
+                None,
+            )
+            .unwrap();
+        let fs_spirv = compiler
+            .compile_into_spirv(
+                fs_src,
+                shaderc::ShaderKind::Fragment,
+                "shader.frag",
+                "main",
+                None,
+            )
+            .unwrap();
+        let vs_data = wgpu::util::make_spirv(vs_spirv.as_binary_u8());
+        let fs_data = wgpu::util::make_spirv(fs_spirv.as_binary_u8());
+        let pipeline = device.create_compute_pipeline(&ComputePipelineDescriptor{
+            label: Some("accelerator pipeline"),
+            layout: None,
+            module: &compute_module,
+            entry_point: "main"
+        });
+        Self{
+            instance,
+            device,
+            queue,
+            pipeline,
+        }
+    }
+}
\ No newline at end of file
-- 
GitLab


From 08d72460be06d8c441ce9a2ec94d251cc90318c3 Mon Sep 17 00:00:00 2001
From: Finn Bear <finnbearlabs@gmail.com>
Date: Sun, 8 May 2022 11:28:08 -0700
Subject: [PATCH 2/5] Compute shader progress.

---
 Cargo.lock                         |  47 +----
 Cargo.toml                         |   2 +-
 src/main.rs                        |   5 -
 src/slide/s6_fractals.rs           |  45 ++++-
 src/slide/s6_fractals/compute.wgsl |  51 +++++
 src/slide/s6_fractals/wgpu.rs      | 289 ++++++++++++++++++++++++-----
 6 files changed, 334 insertions(+), 105 deletions(-)
 create mode 100644 src/slide/s6_fractals/compute.wgsl

diff --git a/Cargo.lock b/Cargo.lock
index 0251c52..e017503 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -370,15 +370,6 @@ dependencies = [
  "winapi",
 ]
 
-[[package]]
-name = "cmake"
-version = "0.1.48"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a"
-dependencies = [
- "cc",
-]
-
 [[package]]
 name = "cocoa"
 version = "0.24.0"
@@ -1055,6 +1046,7 @@ dependencies = [
 name = "generative_art_cartoon"
 version = "0.1.0"
 dependencies = [
+ "bytemuck",
  "eframe",
  "egui_demo_lib",
  "image",
@@ -1062,7 +1054,6 @@ dependencies = [
  "pollster",
  "rand 0.8.5",
  "rand_chacha 0.3.1",
- "shaderc",
  "structopt",
  "wgpu",
 ]
@@ -2255,15 +2246,6 @@ version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
 
-[[package]]
-name = "roxmltree"
-version = "0.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b"
-dependencies = [
- "xmlparser",
-]
-
 [[package]]
 name = "rust-ini"
 version = "0.17.0"
@@ -2366,27 +2348,6 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
 
-[[package]]
-name = "shaderc"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80e6fe602a861622769530a23bc40bfba31adbf186d0c8412e83f5519c5d6bee"
-dependencies = [
- "libc",
- "shaderc-sys",
-]
-
-[[package]]
-name = "shaderc-sys"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3794498651f8173d0afbc0bb8aca45ced111098227e755dde4c0ef2888c8d0bf"
-dependencies = [
- "cmake",
- "libc",
- "roxmltree",
-]
-
 [[package]]
 name = "shared_library"
 version = "0.1.9"
@@ -3161,12 +3122,6 @@ version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
 
-[[package]]
-name = "xmlparser"
-version = "0.13.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8"
-
 [[package]]
 name = "zbus"
 version = "2.1.1"
diff --git a/Cargo.toml b/Cargo.toml
index 34448f7..54fe911 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,4 +13,4 @@ rand_chacha = "0.3"
 structopt = "0.3"
 wgpu = "0.12"
 pollster = "0.2"
-shaderc = "0.8"
\ No newline at end of file
+bytemuck = "1.9"
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index f9e5dc0..f90c788 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,9 +1,4 @@
-#![feature(derive_default_enum)]
 #![feature(mixed_integer_ops)]
-#![feature(total_cmp)]
-#![feature(int_abs_diff)]
-// For shaderc
-#![feature(format_args_capture)]
 
 pub mod cartoon;
 pub mod color;
diff --git a/src/slide/s6_fractals.rs b/src/slide/s6_fractals.rs
index d13e468..cdb425e 100644
--- a/src/slide/s6_fractals.rs
+++ b/src/slide/s6_fractals.rs
@@ -10,7 +10,8 @@ use crate::color::set_alpha;
 use crate::component::arrow::Arrow;
 use crate::component::code::{pseudocode, Code};
 use crate::component::grid::Grid;
-use crate::egui::{Color32, Context, Frame, Pos2, Rect, Shape, Stroke, Ui};
+use crate::egui;
+use crate::egui::{Color32, Context, Frame, Pos2, Shape, Stroke, Ui};
 use crate::fade_in::{fade_in, fade_in_manual, fade_out_manual};
 use crate::governor::Governor;
 use crate::slide::s4_automata::randomize_grid;
@@ -20,6 +21,7 @@ use crate::slide::s6_fractals::julia_gradient::julia as julia_gradient;
 use crate::slide::s6_fractals::mandelbrot::mandelbrot;
 use crate::slide::s6_fractals::mandelbrot_gradient::mandelbrot as mandelbrot_gradient;
 use crate::slide::s6_fractals::rectangle::rectangle;
+use crate::slide::s6_fractals::wgpu::Accelerator;
 use crate::slide::Slide;
 use crate::window::WindowPosition;
 use eframe::egui::style::Margin;
@@ -27,8 +29,6 @@ use eframe::egui::{Align2, ColorImage, Vec2};
 use eframe::epaint::CircleShape;
 use std::iter;
 use std::ops::{Bound, RangeBounds};
-use crate::component::image::Image;
-use crate::egui;
 
 /// Mandelbrot, etc.
 pub struct Fractals {
@@ -90,7 +90,10 @@ enum FractalsState {
     MandelbrotZoom {
         /// Fractal zoom background image.
         image: ColorImage,
-    }
+        /// GPU accelerator.
+        accelerator: Accelerator,
+        zoom: f32,
+    },
 }
 
 impl Default for Fractals {
@@ -116,7 +119,11 @@ impl Default for Fractals {
         grid.stroke_width = 0.0;
 
         Self {
-            state: FractalsState::default(),
+            state: FractalsState::MandelbrotZoom {
+                image: ColorImage::new([1280, 720], Color32::RED),
+                accelerator: Accelerator::new(1280, 720),
+                zoom: 1.0,
+            }, //FractalsState::default(),
             governor: Governor::default(),
             code,
             grid,
@@ -170,8 +177,14 @@ impl Slide for Fractals {
             FractalsState::MandelbrotGradient { .. } => {
                 self.state = FractalsState::JuliaGradient { pixels: 0 }
             }
-            FractalsState::JuliaGradient { .. } => self.state = FractalsState::MandelbrotZoom {image: ColorImage::new([1280, 720], Color32::RED)},
-            FractalsState::MandelbrotZoom {..} => return true,
+            FractalsState::JuliaGradient { .. } => {
+                self.state = FractalsState::MandelbrotZoom {
+                    image: ColorImage::new([1280, 720], Color32::RED),
+                    accelerator: Accelerator::new(1280, 720),
+                    zoom: 1.0,
+                }
+            }
+            FractalsState::MandelbrotZoom { .. } => return true,
         }
         false
     }
@@ -288,9 +301,23 @@ impl Slide for Fractals {
                 algo = Box::new(wrap_f32_algo(julia_gradient));
                 limit_pixels = Some(pixels);
             }
-            FractalsState::MandelbrotZoom {image} => {
+            FractalsState::MandelbrotZoom {
+                image,
+                accelerator,
+                zoom,
+            } => {
                 let screen = ui.ctx().input().screen_rect();
-                egui::Image::new(&ui.ctx().load_texture("zoom", image.clone()), Vec2::ZERO).paint_at(ui, screen);
+                *zoom *= 0.9;
+                accelerator.run(
+                    Pos2::new(
+                        0.3602404434376143632361252444495,
+                        -0.6413130610648031748603750151,
+                    ),
+                    *zoom,
+                    image,
+                );
+                egui::Image::new(&ui.ctx().load_texture("zoom", image.clone()), Vec2::ZERO)
+                    .paint_at(ui, screen);
             }
         }
 
diff --git a/src/slide/s6_fractals/compute.wgsl b/src/slide/s6_fractals/compute.wgsl
new file mode 100644
index 0000000..008bb5a
--- /dev/null
+++ b/src/slide/s6_fractals/compute.wgsl
@@ -0,0 +1,51 @@
+struct ComputeParams {
+    width: u32;
+    height: u32;
+    center_x: f32;
+    center_y: f32;
+    zoom: f32;
+};
+
+[[group(0), binding(0)]] var<uniform> params: ComputeParams;
+[[group(0), binding(1)]] var texture: [[stride(16)]] texture_storage_2d<rgba8unorm, write>;
+
+fn mandelbrot(x: f32, y: f32) -> f32 {
+    let max: f32 = 80.0;
+    var x1: f32 = 0.0;
+    var y1: f32 = 0.0;
+    var i: f32 = 0.0;
+
+    loop {
+        if (x1 * x1 + y1 * y1 > 4.0 || i > max) {
+            break;
+        }
+
+        let x_tmp: f32 = x1 * x1 - y1 * y1 + x;
+        y1 = 2.0 * x1 * y1 + y;
+        x1 = x_tmp;
+        i = i + 1.0;
+    }
+
+    return i / max;
+}
+
+[[stage(compute), workgroup_size(8, 8)]]
+fn main([[builtin(global_invocation_id)]] global_id: vec3<u32>) {
+    let X: u32 = global_id.x;
+    let Y: u32 = global_id.y;
+    let W: u32 = params.width;
+    let H: u32 = params.height;
+
+    if (X >= W || Y >= H) {
+        return;
+    }
+
+    let aspect = f32(W) / f32(H);
+    let nx = (f32(X) / f32(W) - 0.5) * 2.0;
+    let ny = (f32(Y) / f32(H) - 0.5) * 2.0;
+    let fx = params.center_x + nx * params.zoom * aspect;
+    let fy = params.center_y + ny * params.zoom;
+    let iter = mandelbrot(fx, fy);
+
+    textureStore(texture, vec2<i32>(i32(X), i32(Y)), vec4<f32>(iter, iter, iter, 1.0));
+}
\ No newline at end of file
diff --git a/src/slide/s6_fractals/wgpu.rs b/src/slide/s6_fractals/wgpu.rs
index 729b0e0..45ffa58 100644
--- a/src/slide/s6_fractals/wgpu.rs
+++ b/src/slide/s6_fractals/wgpu.rs
@@ -1,62 +1,263 @@
-use wgpu::{Backends, ComputePipeline, ComputePipelineDescriptor, Device, Instance, Queue};
+use bytemuck::{Pod, Zeroable};
+use eframe::egui::{ColorImage, Pos2};
+use std::borrow::Cow;
+use std::mem;
+use std::num::NonZeroU32;
+use wgpu::{
+    Backends, BindGroup, Buffer, BufferDescriptor, ComputePipeline, ComputePipelineDescriptor,
+    Device, Extent3d, Instance, Queue, StorageTextureAccess, Texture, TextureDimension,
+    TextureFormat, TextureUsages,
+};
 
-struct Accelerator {
-    instance: Instance,
+/// Accelerates mandelbrot rendering.
+pub struct Accelerator {
+    width: u32,
+    height: u32,
     device: Device,
+    params: ComputeParams,
     queue: Queue,
+    texture: Texture,
+    output_buffer: Buffer,
     pipeline: ComputePipeline,
+    bind_group: BindGroup,
 }
 
 impl Accelerator {
-    pub fn new() -> Self {
-        let instance = Instance::new(Backends::all());
-        let adapter = pollster::block_on(instance
-            .request_adapter(&wgpu::RequestAdapterOptions {
-                power_preference: wgpu::PowerPreference::HighPerformance,
-                force_fallback_adapter: false,
-                compatible_surface: None,
-            }))
-            .unwrap();
-        let (device, queue) = pollster::block_on(adapter
-            .request_device(&Default::default(), None))
-            .unwrap();
+    pub fn new(width: u32, height: u32) -> Self {
+        let instance = Instance::new(Backends::VULKAN);
+        let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
+            power_preference: wgpu::PowerPreference::HighPerformance,
+            force_fallback_adapter: false,
+            compatible_surface: None,
+        }))
+        .unwrap();
+        let (device, queue) =
+            pollster::block_on(adapter.request_device(&Default::default(), None)).unwrap();
         let compute_module = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
             label: Some("accelerator shader"),
-            source: vs_data,
+            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("compute.wgsl"))),
         });
-        let compute_src = include_str!("compute.glsl");
-        let mut compiler = shaderc::Compiler::new().unwrap();
-        let compute_spirv = compiler
-            .compile_into_spirv(
-                vs_src,
-                shaderc::ShaderKind::,
-                "compute.glsl",
-                "main",
-                None,
-            )
-            .unwrap();
-        let fs_spirv = compiler
-            .compile_into_spirv(
-                fs_src,
-                shaderc::ShaderKind::Fragment,
-                "shader.frag",
-                "main",
-                None,
-            )
-            .unwrap();
-        let vs_data = wgpu::util::make_spirv(vs_spirv.as_binary_u8());
-        let fs_data = wgpu::util::make_spirv(fs_spirv.as_binary_u8());
-        let pipeline = device.create_compute_pipeline(&ComputePipelineDescriptor{
+
+        let format = TextureFormat::Rgba8Unorm;
+        let texture = device.create_texture(&wgpu::TextureDescriptor {
+            label: None,
+            size: wgpu::Extent3d {
+                width,
+                height,
+                depth_or_array_layers: 1,
+            },
+            mip_level_count: 1,
+            sample_count: 1,
+            dimension: TextureDimension::D2,
+            format,
+            usage: TextureUsages::COPY_SRC | TextureUsages::STORAGE_BINDING,
+        });
+        let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+        let params = ComputeParams::new(&device);
+
+        // Create the bind group layout and compute pipeline for the life algorithm.
+        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+            entries: &[
+                wgpu::BindGroupLayoutEntry {
+                    binding: 0,
+                    visibility: wgpu::ShaderStages::COMPUTE,
+                    ty: params.binding_type(),
+                    count: None,
+                },
+                wgpu::BindGroupLayoutEntry {
+                    binding: 1,
+                    visibility: wgpu::ShaderStages::COMPUTE,
+                    ty: wgpu::BindingType::StorageTexture {
+                        access: StorageTextureAccess::WriteOnly,
+                        format,
+                        view_dimension: wgpu::TextureViewDimension::D2,
+                    },
+                    count: None,
+                },
+            ],
+            label: None,
+        });
+
+        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+            label: Some("accelerator pipeline layout"),
+            bind_group_layouts: &[&bind_group_layout],
+            push_constant_ranges: &[],
+        });
+
+        let pipeline = device.create_compute_pipeline(&ComputePipelineDescriptor {
             label: Some("accelerator pipeline"),
-            layout: None,
+            layout: Some(&pipeline_layout),
             module: &compute_module,
-            entry_point: "main"
+            entry_point: "main",
+        });
+
+        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+            layout: &bind_group_layout,
+            entries: &[
+                wgpu::BindGroupEntry {
+                    binding: 0,
+                    resource: params.binding_resource(),
+                },
+                wgpu::BindGroupEntry {
+                    binding: 1,
+                    resource: wgpu::BindingResource::TextureView(&texture_view),
+                },
+            ],
+            label: None,
         });
-        Self{
-            instance,
+
+        let output_buffer_size =
+            (mem::size_of::<u32>() as u32 * width * height) as wgpu::BufferAddress;
+        let output_buffer_desc = wgpu::BufferDescriptor {
+            size: output_buffer_size,
+            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
+            label: None,
+            mapped_at_creation: false,
+        };
+        let output_buffer = device.create_buffer(&output_buffer_desc);
+
+        Self {
+            width,
+            height,
             device,
             queue,
+            params,
+            texture,
+            output_buffer,
             pipeline,
+            bind_group,
+        }
+    }
+
+    pub fn run(&mut self, center: Pos2, zoom: f32, output: &mut ColorImage) {
+        self.params.set(
+            &self.device,
+            Params {
+                width: self.width,
+                height: self.height,
+                center_x: center.x,
+                center_y: center.y,
+                zoom,
+            },
+        );
+
+        // Must match compute.wgsl.
+        const WORKGROUP_SIZE: (u32, u32) = (8, 8);
+
+        let mut command_encoder =
+            self.device
+                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
+                    label: Some("accelerator command encoder"),
+                });
+
+        {
+            let mut compute = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
+                label: Some("accelerator run"),
+            });
+
+            let x_dim = self.width + WORKGROUP_SIZE.0 - 1;
+            let x_groups = x_dim / WORKGROUP_SIZE.0;
+            let y_dim = self.height + WORKGROUP_SIZE.1 - 1;
+            let y_groups = y_dim / WORKGROUP_SIZE.1;
+
+            compute.set_pipeline(&self.pipeline);
+            compute.set_bind_group(0, &self.bind_group, &[]);
+            compute.dispatch(x_groups, y_groups, 1);
+        }
+
+        command_encoder.copy_texture_to_buffer(
+            wgpu::ImageCopyTexture {
+                aspect: wgpu::TextureAspect::All,
+                texture: &self.texture,
+                mip_level: 0,
+                origin: wgpu::Origin3d::ZERO,
+            },
+            wgpu::ImageCopyBuffer {
+                buffer: &self.output_buffer,
+                layout: wgpu::ImageDataLayout {
+                    offset: 0,
+                    bytes_per_row: NonZeroU32::new(mem::size_of::<u32>() as u32 * self.width),
+                    rows_per_image: NonZeroU32::new(self.height),
+                },
+            },
+            Extent3d {
+                width: self.width,
+                height: self.height,
+                depth_or_array_layers: 1,
+            },
+        );
+
+        self.queue.submit(Some(command_encoder.finish()));
+
+        {
+            let buffer_slice = self.output_buffer.slice(..);
+
+            let mapping = buffer_slice.map_async(wgpu::MapMode::Read);
+            self.device.poll(wgpu::Maintain::Wait);
+            pollster::block_on(mapping).unwrap();
+
+            let data = buffer_slice.get_mapped_range();
+
+            *output = ColorImage::from_rgba_unmultiplied(
+                [self.width as usize, self.height as usize],
+                data.as_ref(),
+            );
         }
+        self.output_buffer.unmap();
     }
-}
\ No newline at end of file
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Pod, Zeroable)]
+struct Params {
+    width: u32,
+    height: u32,
+    center_x: f32,
+    center_y: f32,
+    zoom: f32,
+}
+
+struct ComputeParams {
+    buffer: wgpu::Buffer,
+}
+
+impl ComputeParams {
+    pub fn new(device: &wgpu::Device) -> Self {
+        let buffer = device.create_buffer(&BufferDescriptor {
+            label: Some("params buffer"),
+            size: mem::size_of::<Params>() as _,
+            usage: wgpu::BufferUsages::UNIFORM
+                | wgpu::BufferUsages::COPY_DST
+                | wgpu::BufferUsages::MAP_WRITE,
+            mapped_at_creation: false,
+        });
+
+        Self { buffer }
+    }
+
+    pub fn set(&mut self, device: &wgpu::Device, params: Params) {
+        let buffer_slice = self.buffer.slice(..);
+
+        let mapping = buffer_slice.map_async(wgpu::MapMode::Write);
+        device.poll(wgpu::Maintain::Wait);
+        pollster::block_on(mapping).unwrap();
+
+        buffer_slice
+            .get_mapped_range_mut()
+            .copy_from_slice(bytemuck::bytes_of(&params));
+        self.buffer.unmap();
+    }
+
+    pub fn binding_resource(&self) -> wgpu::BindingResource {
+        self.buffer.as_entire_binding()
+    }
+
+    pub fn binding_type(&self) -> wgpu::BindingType {
+        wgpu::BindingType::Buffer {
+            ty: wgpu::BufferBindingType::Uniform,
+            has_dynamic_offset: false,
+            min_binding_size: wgpu::BufferSize::new(mem::size_of::<Params>() as _),
+        }
+    }
+}
-- 
GitLab


From 5f52f42e3b595465ea5c3ee7560adedc5cc0fea0 Mon Sep 17 00:00:00 2001
From: Finn Bear <finnbearlabs@gmail.com>
Date: Sun, 8 May 2022 12:17:38 -0700
Subject: [PATCH 3/5] Fractal zoom.

---
 src/fade_in.rs                     | 15 ++++++++
 src/slide/s6_fractals.rs           | 59 +++++++++++++++++++-----------
 src/slide/s6_fractals/compute.wgsl | 20 +++++++---
 3 files changed, 66 insertions(+), 28 deletions(-)

diff --git a/src/fade_in.rs b/src/fade_in.rs
index 9f8b68d..377c861 100644
--- a/src/fade_in.rs
+++ b/src/fade_in.rs
@@ -33,6 +33,21 @@ pub fn fade_in_manual<R>(
     add_contents(ui, alpha)
 }
 
+/// Fade out some children, assuming the fading out started at [`fade_start`].
+pub fn fade_out<R>(
+    ui: &mut Ui,
+    fade_start: f64,
+    add_contents: impl FnOnce(&mut Ui) -> R,
+) -> InnerResponse<R> {
+    let since_transition = ui.ctx().input().time - fade_start;
+    let alpha = (since_transition * (1.0 / FADE_DURATION)).clamp(0.0, 1.0) as f32;
+    if alpha < 1.0 {
+        // The fade isn't done yet, so schedule another render.
+        ui.ctx().request_repaint();
+    }
+    with_alpha(ui, 1.0 - alpha, |ui| add_contents(ui))
+}
+
 /// Like `fade_in_manual` but provides alpha *decreases* over time.
 pub fn fade_out_manual<R>(
     ui: &mut Ui,
diff --git a/src/slide/s6_fractals.rs b/src/slide/s6_fractals.rs
index cdb425e..77bba7b 100644
--- a/src/slide/s6_fractals.rs
+++ b/src/slide/s6_fractals.rs
@@ -12,7 +12,7 @@ use crate::component::code::{pseudocode, Code};
 use crate::component::grid::Grid;
 use crate::egui;
 use crate::egui::{Color32, Context, Frame, Pos2, Shape, Stroke, Ui};
-use crate::fade_in::{fade_in, fade_in_manual, fade_out_manual};
+use crate::fade_in::{fade_in, fade_in_manual, fade_out, fade_out_manual};
 use crate::governor::Governor;
 use crate::slide::s4_automata::randomize_grid;
 use crate::slide::s6_fractals::circle::circle;
@@ -88,6 +88,8 @@ enum FractalsState {
         pixels: usize,
     },
     MandelbrotZoom {
+        /// When we started fading in.
+        fade_start: f64,
         /// Fractal zoom background image.
         image: ColorImage,
         /// GPU accelerator.
@@ -119,11 +121,7 @@ impl Default for Fractals {
         grid.stroke_width = 0.0;
 
         Self {
-            state: FractalsState::MandelbrotZoom {
-                image: ColorImage::new([1280, 720], Color32::RED),
-                accelerator: Accelerator::new(1280, 720),
-                zoom: 1.0,
-            }, //FractalsState::default(),
+            state: FractalsState::default(),
             governor: Governor::default(),
             code,
             grid,
@@ -179,6 +177,7 @@ impl Slide for Fractals {
             }
             FractalsState::JuliaGradient { .. } => {
                 self.state = FractalsState::MandelbrotZoom {
+                    fade_start: ctx.input().time,
                     image: ColorImage::new([1280, 720], Color32::RED),
                     accelerator: Accelerator::new(1280, 720),
                     zoom: 1.0,
@@ -190,22 +189,29 @@ impl Slide for Fractals {
     }
 
     fn show(&mut self, ui: &mut Ui) {
+        let all_fade_out_start = match &self.state {
+            FractalsState::MandelbrotZoom { fade_start, .. } => *fade_start,
+            _ => f64::INFINITY,
+        };
+
         Frame::none().margin(Margin::same(20.0)).show(ui, |ui| {
-            ui.vertical_centered(|ui| {
-                ui.heading("Fractals");
-                let quote_fade_start = match &self.state {
-                    FractalsState::Before | FractalsState::Grid {..} | FractalsState::Erase {..} | FractalsState::Axes {..} | FractalsState::Rectangle {..} | FractalsState::Circle {..} => None,
-                    FractalsState::Mandelbrot {quote, ..} => *quote,
-                    _ => Some(0.0),
-                };
-                if let Some(quote_fade_start) = quote_fade_start {
-                    fade_in(ui, quote_fade_start, |ui| {
-                        ui.add_space(ui.available_height() - 50.0);
-                        ui.centered_and_justified(|ui| {
-                            ui.label(r#""Beautiful, damn hard, increasingly useful. That's fractals." -Benoit Mandelbrot"#);
+            fade_out(ui, all_fade_out_start, |ui| {
+                ui.vertical_centered(|ui| {
+                    ui.heading("Fractals");
+                    let quote_fade_start = match &self.state {
+                        FractalsState::Before | FractalsState::Grid { .. } | FractalsState::Erase { .. } | FractalsState::Axes { .. } | FractalsState::Rectangle { .. } | FractalsState::Circle { .. } => None,
+                        FractalsState::Mandelbrot { quote, .. } => *quote,
+                        _ => Some(0.0),
+                    };
+                    if let Some(quote_fade_start) = quote_fade_start {
+                        fade_in(ui, quote_fade_start, |ui| {
+                            ui.add_space(ui.available_height() - 50.0);
+                            ui.centered_and_justified(|ui| {
+                                ui.label(r#""Beautiful, damn hard, increasingly useful. That's fractals." -Benoit Mandelbrot"#);
+                            });
                         });
-                    });
-                }
+                    }
+                });
             });
         });
 
@@ -302,20 +308,29 @@ impl Slide for Fractals {
                 limit_pixels = Some(pixels);
             }
             FractalsState::MandelbrotZoom {
+                fade_start,
                 image,
                 accelerator,
                 zoom,
             } => {
+                fade_out_manual(ui, *fade_start, |_, alpha| {
+                    self.grid.alpha = alpha;
+                    self.code.alpha = alpha;
+                });
+
                 let screen = ui.ctx().input().screen_rect();
-                *zoom *= 0.9;
+                if *zoom > 0.00006162543 {
+                    *zoom *= 0.95;
+                }
                 accelerator.run(
                     Pos2::new(
                         0.3602404434376143632361252444495,
-                        -0.6413130610648031748603750151,
+                        -(-0.6413130610648031748603750151 - 0.7 * 0.00006162543),
                     ),
                     *zoom,
                     image,
                 );
+                //println!("zoom: {}", *zoom);
                 egui::Image::new(&ui.ctx().load_texture("zoom", image.clone()), Vec2::ZERO)
                     .paint_at(ui, screen);
             }
diff --git a/src/slide/s6_fractals/compute.wgsl b/src/slide/s6_fractals/compute.wgsl
index 008bb5a..b888e3c 100644
--- a/src/slide/s6_fractals/compute.wgsl
+++ b/src/slide/s6_fractals/compute.wgsl
@@ -9,8 +9,7 @@ struct ComputeParams {
 [[group(0), binding(0)]] var<uniform> params: ComputeParams;
 [[group(0), binding(1)]] var texture: [[stride(16)]] texture_storage_2d<rgba8unorm, write>;
 
-fn mandelbrot(x: f32, y: f32) -> f32 {
-    let max: f32 = 80.0;
+fn mandelbrot(x: f32, y: f32, max: f32) -> f32 {
     var x1: f32 = 0.0;
     var y1: f32 = 0.0;
     var i: f32 = 0.0;
@@ -41,11 +40,20 @@ fn main([[builtin(global_invocation_id)]] global_id: vec3<u32>) {
     }
 
     let aspect = f32(W) / f32(H);
-    let nx = (f32(X) / f32(W) - 0.5) * 2.0;
-    let ny = (f32(Y) / f32(H) - 0.5) * 2.0;
+    let nx = f32(X) / f32(W) - 0.5;
+    let ny = f32(Y) / f32(H) - 0.5;
     let fx = params.center_x + nx * params.zoom * aspect;
     let fy = params.center_y + ny * params.zoom;
-    let iter = mandelbrot(fx, fy);
 
-    textureStore(texture, vec2<i32>(i32(X), i32(Y)), vec4<f32>(iter, iter, iter, 1.0));
+    let iter = mandelbrot(fx, fy, 50.0 + 0.1 / params.zoom);
+
+    var stops = array<vec3<f32>, 3>(vec3<f32>(0.0, 0.0, 1.0), vec3<f32>(0.25, 1.0, 1.0), vec3<f32>(1.0, 1.0, 1.0));
+
+    var color = vec3<f32>(0.0, 0.0, 0.0);
+
+    for (var i: i32 = 0; i < 3; i = i + 1) {
+        color = mix(color, stops[i], smoothStep(f32(i) / 4.0, f32(i + 1) / 4.0, iter));
+    }
+
+    textureStore(texture, vec2<i32>(i32(X), i32(Y)), vec4<f32>(color.x, color.y, color.z, 1.0));
 }
\ No newline at end of file
-- 
GitLab


From e9c9e9aeba5f4071b98d3e8c31a5c01805c23848 Mon Sep 17 00:00:00 2001
From: Finn Bear <finnbearlabs@gmail.com>
Date: Sun, 8 May 2022 12:39:39 -0700
Subject: [PATCH 4/5] Improvements and optimizations.

---
 src/slide/s6_fractals.rs           | 16 +++++++++-------
 src/slide/s6_fractals/compute.wgsl | 12 +++++++++---
 2 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/src/slide/s6_fractals.rs b/src/slide/s6_fractals.rs
index 77bba7b..a1985bd 100644
--- a/src/slide/s6_fractals.rs
+++ b/src/slide/s6_fractals.rs
@@ -318,15 +318,17 @@ impl Slide for Fractals {
                     self.code.alpha = alpha;
                 });
 
+                let ultimate = Pos2::new(
+                    0.3602404434376143632361252444495 + 0.5 * 0.00006162543,
+                    -(-0.6413130610648031748603750151 - 0.7 * 0.00006162543),
+                );
+
+                let time = (ui.ctx().input().time - *fade_start) as f32;
                 let screen = ui.ctx().input().screen_rect();
-                if *zoom > 0.00006162543 {
-                    *zoom *= 0.95;
-                }
+                *zoom = 3.0 * 0.95f32.powf(time * 10.0).max(0.00008);
+                let lerp = (time * 0.2).sqrt().min(1.0);
                 accelerator.run(
-                    Pos2::new(
-                        0.3602404434376143632361252444495,
-                        -(-0.6413130610648031748603750151 - 0.7 * 0.00006162543),
-                    ),
+                    Pos2::new(ultimate.x * lerp, ultimate.y * lerp),
                     *zoom,
                     image,
                 );
diff --git a/src/slide/s6_fractals/compute.wgsl b/src/slide/s6_fractals/compute.wgsl
index b888e3c..aed29fa 100644
--- a/src/slide/s6_fractals/compute.wgsl
+++ b/src/slide/s6_fractals/compute.wgsl
@@ -45,14 +45,20 @@ fn main([[builtin(global_invocation_id)]] global_id: vec3<u32>) {
     let fx = params.center_x + nx * params.zoom * aspect;
     let fy = params.center_y + ny * params.zoom;
 
-    let iter = mandelbrot(fx, fy, 50.0 + 0.1 / params.zoom);
+    let iter = mandelbrot(fx, fy, 90.0 + 0.04 / params.zoom);
 
-    var stops = array<vec3<f32>, 3>(vec3<f32>(0.0, 0.0, 1.0), vec3<f32>(0.25, 1.0, 1.0), vec3<f32>(1.0, 1.0, 1.0));
+    let count: i32 = 3;
+    var stops = array<vec3<f32>, 3>(
+        vec3<f32>(0.1, 0.05, 0.9),
+        vec3<f32>(0.25, 1.0, 1.0),
+        vec3<f32>(1.0, 1.0, 1.0)
+    );
 
     var color = vec3<f32>(0.0, 0.0, 0.0);
 
+    let scale: f32 = f32(count + 1);
     for (var i: i32 = 0; i < 3; i = i + 1) {
-        color = mix(color, stops[i], smoothStep(f32(i) / 4.0, f32(i + 1) / 4.0, iter));
+        color = mix(color, stops[i], smoothStep(f32(i) / scale, f32(i + 1) / 4.0, iter));
     }
 
     textureStore(texture, vec2<i32>(i32(X), i32(Y)), vec4<f32>(color.x, color.y, color.z, 1.0));
-- 
GitLab


From 603e82e0dd914d99a4bc8991ec96aa04eebf26ae Mon Sep 17 00:00:00 2001
From: Finn Bear <finnbearlabs@gmail.com>
Date: Sun, 8 May 2022 15:25:25 -0700
Subject: [PATCH 5/5] Better fractal zoom.

---
 src/slide/s6_fractals.rs           |  8 ++++--
 src/slide/s6_fractals/compute.wgsl | 44 ++++++++++++++++++++----------
 2 files changed, 35 insertions(+), 17 deletions(-)

diff --git a/src/slide/s6_fractals.rs b/src/slide/s6_fractals.rs
index a1985bd..f7e5ae9 100644
--- a/src/slide/s6_fractals.rs
+++ b/src/slide/s6_fractals.rs
@@ -318,15 +318,19 @@ impl Slide for Fractals {
                     self.code.alpha = alpha;
                 });
 
+                /*
                 let ultimate = Pos2::new(
                     0.3602404434376143632361252444495 + 0.5 * 0.00006162543,
                     -(-0.6413130610648031748603750151 - 0.7 * 0.00006162543),
                 );
+                 */
+
+                let ultimate = Pos2::new(-0.613287065, -0.44526086);
 
                 let time = (ui.ctx().input().time - *fade_start) as f32;
                 let screen = ui.ctx().input().screen_rect();
-                *zoom = 3.0 * 0.95f32.powf(time * 10.0).max(0.00008);
-                let lerp = (time * 0.2).sqrt().min(1.0);
+                *zoom = 3.0 * 0.95f32.powf(time * 10.0).max(0.0001);
+                let lerp = (time * 1.0).sqrt().min(1.0);
                 accelerator.run(
                     Pos2::new(ultimate.x * lerp, ultimate.y * lerp),
                     *zoom,
diff --git a/src/slide/s6_fractals/compute.wgsl b/src/slide/s6_fractals/compute.wgsl
index aed29fa..eb85893 100644
--- a/src/slide/s6_fractals/compute.wgsl
+++ b/src/slide/s6_fractals/compute.wgsl
@@ -9,13 +9,13 @@ struct ComputeParams {
 [[group(0), binding(0)]] var<uniform> params: ComputeParams;
 [[group(0), binding(1)]] var texture: [[stride(16)]] texture_storage_2d<rgba8unorm, write>;
 
-fn mandelbrot(x: f32, y: f32, max: f32) -> f32 {
+fn mandelbrot(x: f32, y: f32, cutoff: f32) -> f32 {
     var x1: f32 = 0.0;
     var y1: f32 = 0.0;
     var i: f32 = 0.0;
 
     loop {
-        if (x1 * x1 + y1 * y1 > 4.0 || i > max) {
+        if (x1 * x1 + y1 * y1 > 16.0 || i >= cutoff) {
             break;
         }
 
@@ -25,7 +25,8 @@ fn mandelbrot(x: f32, y: f32, max: f32) -> f32 {
         i = i + 1.0;
     }
 
-    return i / max;
+    var fract = 1.0 - log(log(sqrt(max(1.001, x1 * x1 + y1 * y1)))) / log(2.0);
+    return (i + fract) / cutoff;
 }
 
 [[stage(compute), workgroup_size(8, 8)]]
@@ -45,21 +46,34 @@ fn main([[builtin(global_invocation_id)]] global_id: vec3<u32>) {
     let fx = params.center_x + nx * params.zoom * aspect;
     let fy = params.center_y + ny * params.zoom;
 
-    let iter = mandelbrot(fx, fy, 90.0 + 0.04 / params.zoom);
+    let max_iter = min(270.0, 32.0 + sqrt(16.0 / params.zoom));
+    let o = params.zoom * 0.2 / f32(W);
+    let iter = pow((mandelbrot(fx + o, fy + o, max_iter) + mandelbrot(fx + o, fy - o, max_iter) + mandelbrot(fx - o, fy + o, max_iter) + mandelbrot(fx - o, fy - o, max_iter)) * 0.25, 1.0);
 
-    let count: i32 = 3;
-    var stops = array<vec3<f32>, 3>(
-        vec3<f32>(0.1, 0.05, 0.9),
-        vec3<f32>(0.25, 1.0, 1.0),
-        vec3<f32>(1.0, 1.0, 1.0)
-    );
+    // https://flatuicolors.com/palette/defo
+    var amethyst = vec3<f32>(155.0/255.0, 89.0/255.0, 182.0/255.0);
+    var wet_asphalt = vec3<f32>(52.0/255.0, 73.0/255.0, 94.0/255.0);
+    var emerald = vec3<f32>(46.0/255.0, 204.0/255.0, 113.0/255.0);
+    var sunflower = vec3<f32>(241.0/255.0, 196.0/255.0, 15.0/255.0);
+    var alizarin = vec3<f32>(231.0/255.0, 76.0/255.0, 60.0/255.0);
+    var pomegranate = vec3<f32>(192.0/255.0, 57.0/255.0, 43.0/255.0);
+    var peter_river = vec3<f32>(52.0/255.0, 152.0/255.0, 219.0/255.0);
+    var midnight_blue = vec3<f32>(44.0/255.0, 62.0/255.0, 80.0/255.0);
 
-    var color = vec3<f32>(0.0, 0.0, 0.0);
+    let count: i32 = 8;
+    var stops = array<vec3<f32>, 8>(
+        midnight_blue,
+        alizarin,
+        wet_asphalt,
+        amethyst,
+        peter_river,
+        pomegranate,
+        sunflower,
+        vec3<f32>(0.1, 0.1, 0.1)
+    );
 
-    let scale: f32 = f32(count + 1);
-    for (var i: i32 = 0; i < 3; i = i + 1) {
-        color = mix(color, stops[i], smoothStep(f32(i) / scale, f32(i + 1) / 4.0, iter));
-    }
+    let i = i32(iter * f32(count - 1));
+    let color = mix(stops[i], stops[i + 1], smoothStep(f32(i) / f32(count - 1), f32(i + 1) / f32(count - 1), iter));
 
     textureStore(texture, vec2<i32>(i32(X), i32(Y)), vec4<f32>(color.x, color.y, color.z, 1.0));
 }
\ No newline at end of file
-- 
GitLab