Skip to main content

system/
system.rs

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