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 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 self.compute_shader
297 .dispatch_stage_with_workgroups(&mut frame.encoder, 0, [2048, 1, 1]);
298
299 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}