Skip to main content

currents/
currents.rs

1// Photon tracing: currents
2// Complex example demonstrating multi-buffer ping-pong computation
3use cuneus::compute::{ComputeShader, PassDescription, COMPUTE_TEXTURE_FORMAT_RGBA16};
4use cuneus::{Core, RenderKit, ShaderApp, ShaderControls, ShaderManager};
5use cuneus::{ExportManager, UniformProvider};
6use cuneus::WindowEvent;
7
8#[repr(C)]
9#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
10struct CurrentsParams {
11    sphere_radius: f32,
12    sphere_pos_x: f32,
13    sphere_pos_y: f32,
14    critic2_interval: f32,
15    critic2_pause: f32,
16    critic3_interval: f32,
17    metallic_reflection: f32,
18    line_intensity: f32,
19    pattern_scale: f32,
20    noise_strength: f32,
21    gradient_r: f32,
22    gradient_g: f32,
23    gradient_b: f32,
24    gradient_w: f32,
25    line_color_r: f32,
26    line_color_g: f32,
27    line_color_b: f32,
28    line_color_w: f32,
29    gradient_intensity: f32,
30    line_intensity_final: f32,
31    c2_min: f32,
32    c2_max: f32,
33    c3_min: f32,
34    c3_max: f32,
35    fbm_scale: f32,
36    fbm_offset: f32,
37    gamma: f32,
38}
39
40impl Default for CurrentsParams {
41    fn default() -> Self {
42        Self {
43            sphere_radius: 0.2,
44            sphere_pos_x: 0.0,
45            sphere_pos_y: -0.2,
46            critic2_interval: 10.0,
47            critic2_pause: 5.0,
48            critic3_interval: 10.0,
49            metallic_reflection: 1.8,
50            line_intensity: 0.8,
51            pattern_scale: 150.0,
52            noise_strength: 1.0,
53            gradient_r: 1.0,
54            gradient_g: 2.0,
55            gradient_b: 3.0,
56            gradient_w: 4.0,
57            line_color_r: 1.0,
58            line_color_g: 2.0,
59            line_color_b: 3.0,
60            line_color_w: 4.0,
61            gradient_intensity: 1.5,
62            line_intensity_final: 1.5,
63            c2_min: 333.0,
64            c2_max: 1.0,
65            c3_min: 1.0,
66            c3_max: 3.0,
67            fbm_scale: 4.0,
68            fbm_offset: 1.0,
69            gamma: 2.1,
70        }
71    }
72}
73
74impl UniformProvider for CurrentsParams {
75    fn as_bytes(&self) -> &[u8] {
76        bytemuck::bytes_of(self)
77    }
78}
79
80struct CurrentsShader {
81    base: RenderKit,
82    compute_shader: ComputeShader,
83    current_params: CurrentsParams,
84}
85
86impl CurrentsShader {
87    fn clear_buffers(&mut self, core: &Core) {
88        self.compute_shader.clear_all_buffers(core);
89    }
90}
91
92impl ShaderManager for CurrentsShader {
93    fn init(core: &Core) -> Self {
94        let base = RenderKit::new(core);
95
96        // Define the 5 passes
97        let passes = vec![
98            PassDescription::new("pattern", &["pattern"]), // self-feedback
99            PassDescription::new("trace_fine", &["trace_fine", "pattern"]), // self + pattern
100            PassDescription::new("trace_coarse", &["trace_coarse", "pattern"]), // self + pattern
101            PassDescription::new("accumulate", &["accumulate", "trace_coarse", "trace_fine"]), // self + both traces
102            PassDescription::new("main_image", &["accumulate"]), // final output
103        ];
104
105        let config = ComputeShader::builder()
106            .with_entry_point("pattern")
107            .with_multi_pass(&passes)
108            .with_custom_uniforms::<CurrentsParams>()
109            .with_workgroup_size([16, 16, 1])
110            .with_texture_format(COMPUTE_TEXTURE_FORMAT_RGBA16)
111            .with_label("Currents Multi-Pass")
112            .build();
113
114        let compute_shader = cuneus::compute_shader!(core, "shaders/currents.wgsl", config);
115
116        let initial_params = CurrentsParams::default();
117        let shader = Self {
118            base,
119            compute_shader,
120            current_params: initial_params,
121        };
122
123        // Initialize custom uniform with default parameters
124        shader
125            .compute_shader
126            .set_custom_params(initial_params, &core.queue);
127
128        shader
129    }
130
131    fn update(&mut self, core: &Core) {
132        // Update time
133        let current_time = self.base.controls.get_time(&self.base.start_time);
134        let delta = 1.0 / 60.0;
135        self.compute_shader
136            .set_time(current_time, delta, &core.queue);
137        // Handle export
138        self.compute_shader.handle_export(core, &mut self.base);
139    }
140
141    fn resize(&mut self, core: &Core) {
142        self.base.default_resize(core, &mut self.compute_shader);
143    }
144
145    fn render(&mut self, core: &Core) -> Result<(), cuneus::SurfaceError> {
146        let mut frame = self.base.begin_frame(core)?;
147
148        let mut controls_request = self
149            .base
150            .controls
151            .get_ui_request(&self.base.start_time, &core.size, self.base.fps_tracker.fps());
152
153        // Handle UI and controls
154        let mut params = self.current_params;
155        let mut changed = false;
156        let mut should_start_export = false;
157        let mut export_request = self.base.export_manager.get_ui_request();
158
159        let full_output = if self.base.key_handler.show_ui {
160            self.base.render_ui(core, |ctx| {
161                RenderKit::apply_default_style(ctx);
162
163                egui::Window::new("Multi-Buffer Ping-Pong Example")
164                    .collapsible(true)
165                    .resizable(true)
166                    .default_width(280.0)
167                    .show(ctx, |ui| {
168                        // CURRENTS MODE UI
169                        egui::CollapsingHeader::new("Sphere Settings")
170                            .default_open(false)
171                            .show(ui, |ui| {
172                                changed |= ui
173                                    .add(
174                                        egui::Slider::new(&mut params.sphere_radius, 0.05..=0.5)
175                                            .text("Sphere Radius"),
176                                    )
177                                    .changed();
178                                changed |= ui
179                                    .add(
180                                        egui::Slider::new(&mut params.sphere_pos_x, -1.0..=1.0)
181                                            .text("Sphere X"),
182                                    )
183                                    .changed();
184                                changed |= ui
185                                    .add(
186                                        egui::Slider::new(&mut params.sphere_pos_y, -1.0..=1.0)
187                                            .text("Sphere Y"),
188                                    )
189                                    .changed();
190                                changed |= ui
191                                    .add(
192                                        egui::Slider::new(
193                                            &mut params.metallic_reflection,
194                                            0.5..=3.0,
195                                        )
196                                        .text("Metallic Reflection"),
197                                    )
198                                    .changed();
199                            });
200
201                        egui::CollapsingHeader::new("Pattern Control")
202                            .default_open(false)
203                            .show(ui, |ui| {
204                                changed |= ui
205                                    .add(
206                                        egui::Slider::new(&mut params.pattern_scale, 50.0..=300.0)
207                                            .text("Pattern Scale"),
208                                    )
209                                    .changed();
210                                changed |= ui
211                                    .add(
212                                        egui::Slider::new(&mut params.critic2_interval, 5.0..=20.0)
213                                            .text("Flow Interval"),
214                                    )
215                                    .changed();
216                                changed |= ui
217                                    .add(
218                                        egui::Slider::new(&mut params.critic2_pause, 1.0..=10.0)
219                                            .text("Flow Pause"),
220                                    )
221                                    .changed();
222                                changed |= ui
223                                    .add(
224                                        egui::Slider::new(&mut params.critic3_interval, 5.0..=20.0)
225                                            .text("Scale Interval"),
226                                    )
227                                    .changed();
228                                changed |= ui
229                                    .add(
230                                        egui::Slider::new(&mut params.noise_strength, 0.5..=5.0)
231                                            .text("Noise Strength"),
232                                    )
233                                    .changed();
234                            });
235
236                        egui::CollapsingHeader::new("Noise")
237                            .default_open(false)
238                            .show(ui, |ui| {
239                                ui.label("Oscillator 2 (c2):");
240                                changed |= ui
241                                    .add(
242                                        egui::Slider::new(&mut params.c2_min, 1.0..=500.0)
243                                            .text("C2 Min"),
244                                    )
245                                    .changed();
246                                changed |= ui
247                                    .add(
248                                        egui::Slider::new(&mut params.c2_max, 0.1..=10.0)
249                                            .text("C2 Max"),
250                                    )
251                                    .changed();
252
253                                ui.separator();
254                                ui.label("Oscillator 3 (c3):");
255                                changed |= ui
256                                    .add(
257                                        egui::Slider::new(&mut params.c3_min, 0.1..=10.0)
258                                            .text("C3 Min"),
259                                    )
260                                    .changed();
261                                changed |= ui
262                                    .add(
263                                        egui::Slider::new(&mut params.c3_max, 0.5..=10.0)
264                                            .text("C3 Max"),
265                                    )
266                                    .changed();
267
268                                ui.separator();
269                                ui.label("FBM Noise:");
270                                changed |= ui
271                                    .add(
272                                        egui::Slider::new(&mut params.fbm_scale, 1.0..=10.0)
273                                            .text("FBM Scale"),
274                                    )
275                                    .changed();
276                                changed |= ui
277                                    .add(
278                                        egui::Slider::new(&mut params.fbm_offset, 0.1..=5.0)
279                                            .text("FBM Offset"),
280                                    )
281                                    .changed();
282                            });
283
284                        egui::CollapsingHeader::new("Colors & Post-Processing")
285                            .default_open(false)
286                            .show(ui, |ui| {
287                                ui.horizontal(|ui| {
288                                    ui.label("Gradient:");
289                                    let mut color =
290                                        [params.gradient_r, params.gradient_g, params.gradient_b];
291                                    if ui.color_edit_button_rgb(&mut color).changed() {
292                                        params.gradient_r = color[0];
293                                        params.gradient_g = color[1];
294                                        params.gradient_b = color[2];
295                                        changed = true;
296                                    }
297                                });
298
299                                ui.horizontal(|ui| {
300                                    ui.label("Lines:");
301                                    let mut color = [
302                                        params.line_color_r,
303                                        params.line_color_g,
304                                        params.line_color_b,
305                                    ];
306                                    if ui.color_edit_button_rgb(&mut color).changed() {
307                                        params.line_color_r = color[0];
308                                        params.line_color_g = color[1];
309                                        params.line_color_b = color[2];
310                                        changed = true;
311                                    }
312                                });
313
314                                ui.separator();
315                                changed |= ui
316                                    .add(
317                                        egui::Slider::new(
318                                            &mut params.gradient_intensity,
319                                            0.1..=2.0,
320                                        )
321                                        .text("Gradient Intensity"),
322                                    )
323                                    .changed();
324                                changed |= ui
325                                    .add(
326                                        egui::Slider::new(
327                                            &mut params.line_intensity_final,
328                                            0.1..=2.0,
329                                        )
330                                        .text("Line Final Intensity"),
331                                    )
332                                    .changed();
333
334                                ui.separator();
335                                changed |= ui
336                                    .add(
337                                        egui::Slider::new(&mut params.line_intensity, 0.1..=3.0)
338                                            .text("Line Intensity"),
339                                    )
340                                    .changed();
341                                changed |= ui
342                                    .add(
343                                        egui::Slider::new(&mut params.gamma, 0.1..=4.0)
344                                            .text("Gamma Correction"),
345                                    )
346                                    .changed();
347                            });
348
349                        ui.separator();
350
351                        ShaderControls::render_controls_widget(ui, &mut controls_request);
352
353                        ui.separator();
354
355                        should_start_export =
356                            ExportManager::render_export_ui_widget(ui, &mut export_request);
357
358                        ui.separator();
359                        ui.label(format!("Frame: {}", self.compute_shader.current_frame));
360                    });
361            })
362        } else {
363            self.base.render_ui(core, |_ctx| {})
364        };
365
366        self.base.export_manager.apply_ui_request(export_request);
367        if controls_request.should_clear_buffers {
368            self.clear_buffers(core);
369        }
370        self.base.apply_control_request(controls_request);
371
372        if changed {
373            self.current_params = params;
374            self.compute_shader.set_custom_params(params, &core.queue);
375            // Reset frame counter for proper photon accumulation restart
376            self.compute_shader.current_frame = 0;
377        }
378
379        if should_start_export {
380            self.base.export_manager.start_export();
381        }
382
383        // Create command encoder
384
385        self.compute_shader.dispatch(&mut frame.encoder, core);
386
387        self.base.renderer.render_to_view(&mut frame.encoder, &frame.view, &self.compute_shader.get_output_texture().bind_group);
388
389        self.base.end_frame(core, frame, full_output);
390
391        Ok(())
392    }
393
394    fn handle_input(&mut self, core: &Core, event: &WindowEvent) -> bool {
395        self.base.default_handle_input(core, event)
396    }
397}
398
399fn main() -> Result<(), Box<dyn std::error::Error>> {
400    env_logger::init();
401    let (app, event_loop) = ShaderApp::new("Photon Tracing", 800, 600);
402
403    app.run(event_loop, CurrentsShader::init)
404}