Skip to main content

cliffordcompute/
cliffordcompute.rs

1use cuneus::compute::*;
2use cuneus::prelude::*;
3
4cuneus::uniform_params! {
5    struct CliffordParams {
6    a: f32,
7    b: f32,
8    c: f32,
9    d: f32,
10    motion_speed: 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    scale: f32,
22    dof_amount: f32,
23    dof_focal_dist: f32,
24    _pad_m: [f32; 2],
25    }
26}
27
28struct CliffordShader {
29    base: RenderKit,
30    compute_shader: ComputeShader,
31    current_params: CliffordParams}
32
33impl CliffordShader {
34    fn clear_buffers(&mut self, core: &Core) {
35        self.compute_shader.clear_all_buffers(core);
36    }
37}
38
39impl ShaderManager for CliffordShader {
40    fn init(core: &Core) -> Self {
41        let base = RenderKit::new(core);
42
43        let initial_params = CliffordParams {
44            a: 1.7,
45            b: 1.7,
46            c: 0.6,
47            d: 1.2,
48            motion_speed: 1.0,
49            rotation_x: 0.0,
50            rotation_y: 0.0,
51            click_state: 0,
52            brightness: 0.00004,
53            color1_r: 0.0,
54            color1_g: 0.7,
55            color1_b: 1.0,
56            color2_r: 1.0,
57            color2_g: 0.3,
58            color2_b: 0.5,
59            scale: 0.6,
60            dof_amount: 1.0,
61            dof_focal_dist: 0.5,
62            _pad_m: [0.0; 2],
63        };
64
65        let mut config = ComputeShader::builder()
66            .with_entry_point("Splat")
67            .with_custom_uniforms::<CliffordParams>()
68            .with_atomic_buffer(2)
69            .with_workgroup_size([16, 16, 1])
70            .with_texture_format(COMPUTE_TEXTURE_FORMAT_RGBA16)
71            .with_label("Clifford Attractor Unified")
72            .build();
73
74        config.entry_points.push("main_image".to_string());
75
76        let compute_shader = cuneus::compute_shader!(core, "shaders/cliffordcompute.wgsl", config);
77
78
79        compute_shader.set_custom_params(initial_params, &core.queue);
80
81        Self {
82            base,
83            compute_shader,
84            current_params: initial_params}
85    }
86
87    fn update(&mut self, core: &Core) {
88        // Handle export with custom dispatch pattern for cliffordcompute
89        self.compute_shader.handle_export_dispatch(
90            core,
91            &mut self.base,
92            |shader, encoder, core| {
93                shader.dispatch_stage_with_workgroups(encoder, 0, [2048, 1, 1]);
94                shader.dispatch_stage(encoder, core, 1);
95            },
96        );
97    }
98
99    fn resize(&mut self, core: &Core) {
100        self.base.default_resize(core, &mut self.compute_shader);
101    }
102
103    fn render(&mut self, core: &Core) -> Result<(), cuneus::SurfaceError> {
104        let mut frame = self.base.begin_frame(core)?;
105
106        let mut params = self.current_params;
107        let mut changed = false;
108        let mut should_start_export = false;
109        let mut export_request = self.base.export_manager.get_ui_request();
110        let mut controls_request = self
111            .base
112            .controls
113            .get_ui_request(&self.base.start_time, &core.size, self.base.fps_tracker.fps());
114        let full_output = if self.base.key_handler.show_ui {
115            self.base.render_ui(core, |ctx| {
116                RenderKit::apply_default_style(ctx);
117
118                egui::Window::new("Clifford Attractor")
119                    .collapsible(true)
120                    .resizable(true)
121                    .default_width(250.0)
122                    .show(ctx, |ui| {
123                        egui::CollapsingHeader::new("Attractor Parameters")
124                            .default_open(false)
125                            .show(ui, |ui| {
126                                changed |= ui
127                                    .add(
128                                        egui::Slider::new(&mut params.a, -3.0..=3.0)
129                                            .text("Parameter A"),
130                                    )
131                                    .changed();
132                                changed |= ui
133                                    .add(
134                                        egui::Slider::new(&mut params.b, -3.0..=3.0)
135                                            .text("Parameter B"),
136                                    )
137                                    .changed();
138                                changed |= ui
139                                    .add(
140                                        egui::Slider::new(&mut params.c, -3.0..=3.0)
141                                            .text("Parameter C"),
142                                    )
143                                    .changed();
144                                changed |= ui
145                                    .add(
146                                        egui::Slider::new(&mut params.d, -3.0..=3.0)
147                                            .text("Parameter D"),
148                                    )
149                                    .changed();
150                                ui.separator();
151                                ui.label("Interesting presets:");
152                                if ui.button("Classic (1.5, 1.5, 1.4, 1.4)").clicked() {
153                                    params.a = 1.5;
154                                    params.b = 1.5;
155                                    params.c = 1.4;
156                                    params.d = 1.4;
157                                    changed = true;
158                                }
159                                if ui.button("Chaotic (1.7, 1.7, 0.6, 1.2)").clicked() {
160                                    params.a = 1.7;
161                                    params.b = 1.7;
162                                    params.c = 0.6;
163                                    params.d = 1.2;
164                                    changed = true;
165                                }
166                                if ui.button("Symmetric (2.0, -2.0, 1.0, 0.5)").clicked() {
167                                    params.a = 2.0;
168                                    params.b = -2.0;
169                                    params.c = 1.0;
170                                    params.d = 0.5;
171                                    changed = true;
172                                }
173                            });
174
175                        egui::CollapsingHeader::new("Visual Settings")
176                            .default_open(false)
177                            .show(ui, |ui| {
178                                changed |= ui
179                                    .add(
180                                        egui::Slider::new(&mut params.motion_speed, 0.0..=3.0)
181                                            .text("Animation Speed"),
182                                    )
183                                    .changed();
184                                changed |= ui
185                                    .add(
186                                        egui::Slider::new(&mut params.brightness, 0.00001..=0.0001)
187                                            .logarithmic(true)
188                                            .text("Brightness"),
189                                    )
190                                    .changed();
191                                ui.separator();
192                                ui.label("Camera Controls:");
193                                changed |= ui
194                                    .add(
195                                        egui::Slider::new(&mut params.rotation_x, -1.0..=1.0)
196                                            .text("Rotation X"),
197                                    )
198                                    .changed();
199                                changed |= ui
200                                    .add(
201                                        egui::Slider::new(&mut params.rotation_y, -1.0..=1.0)
202                                            .text("Rotation Y"),
203                                    )
204                                    .changed();
205                                ui.separator();
206                                changed |= ui
207                                    .add(
208                                        egui::Slider::new(&mut params.scale, 0.1..=2.0)
209                                            .text("Attractor Scale"),
210                                    )
211                                    .changed();
212                            });
213
214                        egui::CollapsingHeader::new("Depth of Field")
215                            .default_open(false)
216                            .show(ui, |ui| {
217                                changed |= ui
218                                    .add(
219                                        egui::Slider::new(&mut params.dof_amount, 0.0..=3.0)
220                                            .text("DOF Amount"),
221                                    )
222                                    .changed();
223                                changed |= ui
224                                    .add(
225                                        egui::Slider::new(&mut params.dof_focal_dist, 0.0..=1.0)
226                                            .text("Focal Distance"),
227                                    )
228                                    .changed();
229                                params.click_state = 1;
230                            });
231
232                        egui::CollapsingHeader::new("Colors")
233                            .default_open(false)
234                            .show(ui, |ui| {
235                                ui.horizontal(|ui| {
236                                    ui.label("Color 1:");
237                                    let mut color =
238                                        [params.color1_r, params.color1_g, params.color1_b];
239                                    if ui.color_edit_button_rgb(&mut color).changed() {
240                                        params.color1_r = color[0];
241                                        params.color1_g = color[1];
242                                        params.color1_b = color[2];
243                                        changed = true;
244                                    }
245                                });
246
247                                ui.horizontal(|ui| {
248                                    ui.label("Color 2:");
249                                    let mut color =
250                                        [params.color2_r, params.color2_g, params.color2_b];
251                                    if ui.color_edit_button_rgb(&mut color).changed() {
252                                        params.color2_r = color[0];
253                                        params.color2_g = color[1];
254                                        params.color2_b = color[2];
255                                        changed = true;
256                                    }
257                                });
258                            });
259
260                        ui.separator();
261
262                        ShaderControls::render_controls_widget(ui, &mut controls_request);
263
264                        ui.separator();
265
266                        should_start_export =
267                            ExportManager::render_export_ui_widget(ui, &mut export_request);
268                    });
269            })
270        } else {
271            self.base.render_ui(core, |_ctx| {})
272        };
273
274        self.base.export_manager.apply_ui_request(export_request);
275        if controls_request.should_clear_buffers {
276            self.clear_buffers(core);
277        }
278        self.base.apply_control_request(controls_request);
279
280        let current_time = self.base.controls.get_time(&self.base.start_time);
281
282        let delta = 1.0 / 60.0;
283        self.compute_shader
284            .set_time(current_time, delta, &core.queue);
285
286        if changed {
287            self.current_params = params;
288            self.compute_shader.set_custom_params(params, &core.queue);
289        }
290
291        if should_start_export {
292            self.base.export_manager.start_export();
293        }
294
295        // Stage 0: Generate and splat particles (workgroup size [256, 1, 1])
296        self.compute_shader
297            .dispatch_stage_with_workgroups(&mut frame.encoder, 0, [2048, 1, 1]);
298
299        // Stage 1: Render to screen (workgroup size [16, 16, 1])
300        self.compute_shader.dispatch_stage(&mut frame.encoder, core, 1);
301
302        self.base.renderer.render_to_view(&mut frame.encoder, &frame.view, &self.compute_shader.get_output_texture().bind_group);
303
304        self.base.end_frame(core, frame, full_output);
305        Ok(())
306    }
307
308    fn handle_input(&mut self, core: &Core, event: &WindowEvent) -> bool {
309        self.base.default_handle_input(core, event)
310    }
311}
312
313fn main() -> Result<(), Box<dyn std::error::Error>> {
314    env_logger::init();
315    let (app, event_loop) = cuneus::ShaderApp::new("Clifford", 800, 600);
316
317    app.run(event_loop, CliffordShader::init)
318}