Skip to main content

spiralchaos/
spiralchaos.rs

1use cuneus::compute::*;
2use cuneus::prelude::*;
3
4cuneus::uniform_params! {
5    struct SpiralParams {
6    a: f32,
7    b: f32,
8    c: f32,
9    dof_amount: f32,
10    dof_focal_dist: f32,
11    rotation_x: f32,
12    rotation_y: f32,
13    click_state: i32,
14    brightness: f32,
15    color1_r: f32,
16    color1_g: f32,
17    color1_b: f32,
18    color2_r: f32,
19    color2_g: f32,
20    color2_b: f32,
21    _padding: u32}
22}
23
24struct SpiralShader {
25    base: RenderKit,
26    compute_shader: ComputeShader,
27    current_params: SpiralParams}
28
29impl SpiralShader {
30    fn clear_buffers(&mut self, core: &Core) {
31        self.compute_shader.clear_all_buffers(core);
32    }
33}
34
35impl ShaderManager for SpiralShader {
36    fn init(core: &Core) -> Self {
37        let initial_params = SpiralParams {
38            a: 1.0,
39            b: 1.0,
40            c: 1.0,
41            dof_amount: 1.0,
42            dof_focal_dist: 1.0,
43            rotation_x: 0.0,
44            rotation_y: 0.0,
45            click_state: 0,
46            brightness: 0.00004,
47            color1_r: 0.0,
48            color1_g: 0.7,
49            color1_b: 1.0,
50            color2_r: 1.0,
51            color2_g: 0.3,
52            color2_b: 0.5,
53            _padding: 0};
54
55        let base = RenderKit::new(core);
56
57        let mut config = ComputeShader::builder()
58            .with_entry_point("Splat")
59            .with_custom_uniforms::<SpiralParams>()
60            .with_atomic_buffer(2)
61            .with_workgroup_size([16, 16, 1])
62            .with_texture_format(COMPUTE_TEXTURE_FORMAT_RGBA16)
63            .with_label("Spiralchaos Unified")
64            .build();
65
66        // Add second entry point manually
67        config.entry_points.push("main_image".to_string());
68
69        let compute_shader = cuneus::compute_shader!(core, "shaders/spiralchaos.wgsl", config);
70
71        compute_shader.set_custom_params(initial_params, &core.queue);
72
73        Self {
74            base,
75            compute_shader,
76            current_params: initial_params}
77    }
78
79    fn update(&mut self, core: &Core) {
80        // Handle export with custom dispatch pattern
81        self.compute_shader.handle_export_dispatch(
82            core,
83            &mut self.base,
84            |shader, encoder, core| {
85                shader.dispatch_stage_with_workgroups(encoder, 0, [4096, 1, 1]);
86                shader.dispatch_stage(encoder, core, 1);
87            },
88        );
89    }
90
91    fn resize(&mut self, core: &Core) {
92        self.base.default_resize(core, &mut self.compute_shader);
93    }
94
95    fn render(&mut self, core: &Core) -> Result<(), cuneus::SurfaceError> {
96        let mut frame = self.base.begin_frame(core)?;
97
98        let mut params = self.current_params;
99        let mut changed = false;
100        let mut should_start_export = false;
101        let mut export_request = self.base.export_manager.get_ui_request();
102        let mut controls_request = self
103            .base
104            .controls
105            .get_ui_request(&self.base.start_time, &core.size, self.base.fps_tracker.fps());
106        let full_output = if self.base.key_handler.show_ui {
107            self.base.render_ui(core, |ctx| {
108                RenderKit::apply_default_style(ctx);
109
110                egui::Window::new("Chaos Spiral")
111                    .collapsible(true)
112                    .resizable(true)
113                    .default_width(250.0)
114                    .show(ctx, |ui| {
115                        egui::CollapsingHeader::new("Spiral Parameters")
116                            .default_open(false)
117                            .show(ui, |ui| {
118                                changed |= ui
119                                    .add(
120                                        egui::Slider::new(&mut params.a, 0.0..=3.0)
121                                            .text("Tightness"),
122                                    )
123                                    .changed();
124                                changed |= ui
125                                    .add(egui::Slider::new(&mut params.b, 0.0..=3.0).text("Speed"))
126                                    .changed();
127                                changed |= ui
128                                    .add(egui::Slider::new(&mut params.c, 0.0..=3.0).text("N Arms"))
129                                    .changed();
130                            });
131
132                        egui::CollapsingHeader::new("DOF")
133                            .default_open(false)
134                            .show(ui, |ui| {
135                                changed |= ui
136                                    .add(
137                                        egui::Slider::new(&mut params.dof_amount, 0.0..=3.0)
138                                            .text("Amount"),
139                                    )
140                                    .changed();
141                                changed |= ui
142                                    .add(
143                                        egui::Slider::new(&mut params.dof_focal_dist, 0.0..=3.0)
144                                            .text("Focal Distance"),
145                                    )
146                                    .changed();
147                            });
148
149                        egui::CollapsingHeader::new("Rotation")
150                            .default_open(false)
151                            .show(ui, |ui| {
152                                changed |= ui
153                                    .add(
154                                        egui::Slider::new(&mut params.rotation_x, -1.0..=1.0)
155                                            .text("X"),
156                                    )
157                                    .changed();
158                                changed |= ui
159                                    .add(
160                                        egui::Slider::new(&mut params.rotation_y, -1.0..=1.0)
161                                            .text("Y"),
162                                    )
163                                    .changed();
164                            });
165
166                        egui::CollapsingHeader::new("Colors")
167                            .default_open(false)
168                            .show(ui, |ui| {
169                                changed |= ui
170                                    .add(
171                                        egui::Slider::new(&mut params.brightness, 0.00001..=0.0001)
172                                            .logarithmic(true)
173                                            .text("Brightness"),
174                                    )
175                                    .changed();
176
177                                ui.horizontal(|ui| {
178                                    ui.label("Color 1:");
179                                    let mut color =
180                                        [params.color1_r, params.color1_g, params.color1_b];
181                                    if ui.color_edit_button_rgb(&mut color).changed() {
182                                        params.color1_r = color[0];
183                                        params.color1_g = color[1];
184                                        params.color1_b = color[2];
185                                        changed = true;
186                                    }
187                                });
188
189                                ui.horizontal(|ui| {
190                                    ui.label("Color 2:");
191                                    let mut color =
192                                        [params.color2_r, params.color2_g, params.color2_b];
193                                    if ui.color_edit_button_rgb(&mut color).changed() {
194                                        params.color2_r = color[0];
195                                        params.color2_g = color[1];
196                                        params.color2_b = color[2];
197                                        changed = true;
198                                    }
199                                });
200                            });
201
202                        ui.separator();
203
204                        ShaderControls::render_controls_widget(ui, &mut controls_request);
205
206                        ui.separator();
207
208                        should_start_export =
209                            ExportManager::render_export_ui_widget(ui, &mut export_request);
210                    });
211            })
212        } else {
213            self.base.render_ui(core, |_ctx| {})
214        };
215
216        self.base.export_manager.apply_ui_request(export_request);
217        if controls_request.should_clear_buffers {
218            self.clear_buffers(core);
219        }
220        self.base.apply_control_request(controls_request);
221
222        let current_time = self.base.controls.get_time(&self.base.start_time);
223
224        let delta = 1.0 / 60.0;
225        self.compute_shader
226            .set_time(current_time, delta, &core.queue);
227
228        // Mouse data integration
229        params.rotation_x = self.base.mouse_tracker.uniform.position[0];
230        params.rotation_y = self.base.mouse_tracker.uniform.position[1];
231        params.click_state = if self.base.mouse_tracker.uniform.buttons[0] & 1 > 0 {
232            1
233        } else {
234            0
235        };
236        changed = true;
237
238        if changed {
239            self.current_params = params;
240            self.compute_shader.set_custom_params(params, &core.queue);
241        }
242
243        if should_start_export {
244            self.base.export_manager.start_export();
245        }
246
247        // Note: in cuneus, individual stage dispatch methods need manual frame management (if you need of course!)
248
249        // Stage 0: Splat particles (workgroup size [256, 1, 1])
250        self.compute_shader
251            .dispatch_stage_with_workgroups(&mut frame.encoder, 0, [4096, 1, 1]);
252
253        // Stage 1: Render to screen (workgroup size [16, 16, 1])
254        self.compute_shader.dispatch_stage(&mut frame.encoder, core, 1);
255
256        self.base.renderer.render_to_view(&mut frame.encoder, &frame.view, &self.compute_shader.get_output_texture().bind_group);
257
258        self.base.end_frame(core, frame, full_output);
259        Ok(())
260    }
261
262    fn handle_input(&mut self, core: &Core, event: &WindowEvent) -> bool {
263        self.base.default_handle_input(core, event)
264    }
265}
266
267fn main() -> Result<(), Box<dyn std::error::Error>> {
268    env_logger::init();
269    let (app, event_loop) = cuneus::ShaderApp::new("Chaos Spiral", 800, 600);
270
271    app.run(event_loop, SpiralShader::init)
272}