Skip to main content

nebula/
nebula.rs

1use cuneus::compute::*;
2use cuneus::prelude::*;
3
4cuneus::uniform_params! {
5    struct NebulaParams {
6    iterations: i32,
7    formuparam: f32,
8    volsteps: i32,
9    stepsize: f32,
10    zoom: f32,
11    tile: f32,
12    speed: f32,
13    brightness: f32,
14    dust_intensity: f32,
15    distfading: f32,
16    color_variation: f32,
17    n_boxes: f32,
18    rotation: i32,
19    depth: f32,
20    color_mode: i32,
21    _padding1: f32,
22
23    rotation_x: f32,
24    rotation_y: f32,
25    click_state: i32,
26    scale: f32,
27
28    exposure: f32,
29    gamma: f32,
30
31    _padding4: f32,
32    _padding5: f32,
33    _padding6: f32,
34    _padding7: f32,
35    _padding8: f32,
36    _padding9: f32,
37    _padding10: f32,
38
39    time_scale: f32,
40    visual_mode: i32,
41    _padding2: f32,
42    _padding3: f32,
43    _pad_m1: f32,
44    _pad_m2: f32,
45    _pad_m3: f32,
46    }
47}
48
49struct NebulaShader {
50    base: RenderKit,
51    compute_shader: ComputeShader,
52    current_params: NebulaParams,
53    frame_count: u32}
54
55impl NebulaShader {
56    fn clear_buffers(&mut self, core: &Core) {
57        self.compute_shader.clear_all_buffers(core);
58        self.frame_count = 0;
59    }
60}
61
62impl ShaderManager for NebulaShader {
63    fn init(core: &Core) -> Self {
64        let base = RenderKit::new(core);
65
66        let initial_params = NebulaParams {
67            iterations: 17,
68            formuparam: 0.52,
69            volsteps: 6,
70            stepsize: 0.31,
71            zoom: 5.0,
72            tile: 0.35,
73            speed: 0.020,
74            brightness: 0.00062,
75            dust_intensity: 1.0,
76            distfading: 0.95,
77            color_variation: 0.51,
78            n_boxes: 10.0,
79            rotation: 1,
80            depth: 5.0,
81            color_mode: 1,
82            _padding1: 0.0,
83
84            rotation_x: 0.0,
85            rotation_y: 0.0,
86            click_state: 0,
87            scale: 1.0,
88
89            exposure: 1.6,
90            gamma: 0.400,
91
92            _padding4: 0.0,
93            _padding5: 0.0,
94            _padding6: 0.0,
95            _padding7: 0.0,
96            _padding8: 0.0,
97            _padding9: 0.0,
98            _padding10: 0.0,
99
100            time_scale: 1.0,
101            visual_mode: 0,
102            _padding2: 0.0,
103            _padding3: 0.0,
104            _pad_m1: 0.0,
105            _pad_m2: 0.0,
106            _pad_m3: 0.0,
107        };
108
109        let mut config = ComputeShader::builder()
110            .with_entry_point("volumetric_render")
111            .with_custom_uniforms::<NebulaParams>()
112            .with_atomic_buffer(3)
113            .with_workgroup_size([16, 16, 1])
114            .with_texture_format(COMPUTE_TEXTURE_FORMAT_RGBA16)
115            .with_label("Nebula Unified")
116            .build();
117
118        // Add second entry point manually
119        config.entry_points.push("main_image".to_string());
120
121        let compute_shader = cuneus::compute_shader!(core, "shaders/nebula.wgsl", config);
122
123        compute_shader.set_custom_params(initial_params, &core.queue);
124
125        Self {
126            base,
127            compute_shader,
128            current_params: initial_params,
129            frame_count: 0}
130    }
131
132    fn update(&mut self, core: &Core) {
133        // Handle export
134        self.compute_shader.handle_export(core, &mut self.base);
135    }
136
137    fn resize(&mut self, core: &Core) {
138        self.base.default_resize(core, &mut self.compute_shader);
139    }
140
141    fn render(&mut self, core: &Core) -> Result<(), cuneus::SurfaceError> {
142        let mut frame = self.base.begin_frame(core)?;
143
144        let mut params = self.current_params;
145        let mut changed = false;
146        let mut should_start_export = false;
147        let mut export_request = self.base.export_manager.get_ui_request();
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        // Mouse interaction
154        if self.base.mouse_tracker.uniform.buttons[0] & 1 != 0 {
155            params.rotation_x = self.base.mouse_tracker.uniform.position[0];
156            params.rotation_y = self.base.mouse_tracker.uniform.position[1];
157            params.click_state = 1;
158            changed = true;
159        } else {
160            params.click_state = 0;
161        }
162
163        let full_output = if self.base.key_handler.show_ui {
164            self.base.render_ui(core, |ctx| {
165                RenderKit::apply_default_style(ctx);
166
167                egui::Window::new("universe")
168                    .collapsible(true)
169                    .resizable(true)
170                    .default_width(320.0)
171                    .show(ctx, |ui| {
172                        egui::CollapsingHeader::new("Volumetric Parameters")
173                            .default_open(false)
174                            .show(ui, |ui| {
175                                changed |= ui
176                                    .add(
177                                        egui::Slider::new(&mut params.iterations, 5..=30)
178                                            .text("Iterations"),
179                                    )
180                                    .changed();
181                                changed |= ui
182                                    .add(
183                                        egui::Slider::new(&mut params.formuparam, 0.1..=1.0)
184                                            .text("Form Parameter"),
185                                    )
186                                    .changed();
187                                changed |= ui
188                                    .add(
189                                        egui::Slider::new(&mut params.volsteps, 1..=20)
190                                            .text("Volume Steps"),
191                                    )
192                                    .changed();
193                                changed |= ui
194                                    .add(
195                                        egui::Slider::new(&mut params.stepsize, 0.05..=0.5)
196                                            .text("Step Size"),
197                                    )
198                                    .changed();
199                                changed |= ui
200                                    .add(
201                                        egui::Slider::new(&mut params.zoom, 0.1..=112.0)
202                                            .text("Zoom"),
203                                    )
204                                    .changed();
205                                changed |= ui
206                                    .add(
207                                        egui::Slider::new(&mut params.tile, 0.1..=3.0).text("Tile"),
208                                    )
209                                    .changed();
210                            });
211
212                        egui::CollapsingHeader::new("Appearance")
213                            .default_open(false)
214                            .show(ui, |ui| {
215                                changed |= ui
216                                    .add(
217                                        egui::Slider::new(&mut params.brightness, 0.0001..=0.015)
218                                            .logarithmic(true)
219                                            .text("Brightness"),
220                                    )
221                                    .changed();
222                                changed |= ui
223                                    .add(
224                                        egui::Slider::new(&mut params.dust_intensity, 0.0..=2.0)
225                                            .text("Dust Intensity"),
226                                    )
227                                    .changed();
228                                changed |= ui
229                                    .add(
230                                        egui::Slider::new(&mut params.distfading, 0.1..=3.0)
231                                            .text("Distance Fading"),
232                                    )
233                                    .changed();
234                                changed |= ui
235                                    .add(
236                                        egui::Slider::new(&mut params.color_variation, 0.2..=5.0)
237                                            .text("Color Variation"),
238                                    )
239                                    .changed();
240                                changed |= ui
241                                    .add(
242                                        egui::Slider::new(&mut params.exposure, 0.2..=3.0)
243                                            .text("Exposure"),
244                                    )
245                                    .changed();
246                                changed |= ui
247                                    .add(
248                                        egui::Slider::new(&mut params.gamma, 0.1..=1.2)
249                                            .text("Gamma"),
250                                    )
251                                    .changed();
252                            });
253
254                        egui::CollapsingHeader::new("Animation")
255                            .default_open(false)
256                            .show(ui, |ui| {
257                                changed |= ui
258                                    .add(
259                                        egui::Slider::new(&mut params.speed, -0.1..=0.1)
260                                            .text("Galaxy Speed"),
261                                    )
262                                    .changed();
263                                changed |= ui
264                                    .add(
265                                        egui::Slider::new(&mut params.time_scale, 0.1..=2.0)
266                                            .text("Animation Speed"),
267                                    )
268                                    .changed();
269                            });
270
271                        ui.separator();
272                        ShaderControls::render_controls_widget(ui, &mut controls_request);
273                        ui.separator();
274                        should_start_export =
275                            ExportManager::render_export_ui_widget(ui, &mut export_request);
276                    });
277            })
278        } else {
279            self.base.render_ui(core, |_ctx| {})
280        };
281
282        self.base.export_manager.apply_ui_request(export_request);
283        if controls_request.should_clear_buffers {
284            self.clear_buffers(core);
285        }
286        self.base.apply_control_request(controls_request);
287
288        if changed {
289            self.current_params = params;
290            self.compute_shader.set_custom_params(params, &core.queue);
291        }
292
293        if should_start_export {
294            self.base.export_manager.start_export();
295        }
296
297        let current_time = self.base.controls.get_time(&self.base.start_time);
298        let delta = 1.0 / 60.0;
299        self.compute_shader
300            .set_time(current_time, delta, &core.queue);
301        self.compute_shader.time_uniform.data.frame = self.frame_count;
302        self.compute_shader.time_uniform.update(&core.queue);
303
304        // Stage 0: Volumetric render (not doing anything in this case, just placeholder)
305        self.compute_shader.dispatch_stage(&mut frame.encoder, core, 0);
306
307        // Stage 1: Main image render
308        self.compute_shader.dispatch_stage(&mut frame.encoder, core, 1);
309
310        self.base.renderer.render_to_view(&mut frame.encoder, &frame.view, &self.compute_shader.get_output_texture().bind_group);
311
312        self.frame_count = self.frame_count.wrapping_add(1);
313
314        self.base.end_frame(core, frame, full_output);
315
316        Ok(())
317    }
318
319    fn handle_input(&mut self, core: &Core, event: &WindowEvent) -> bool {
320        if self.base.default_handle_input(core, event) {
321            return true;
322        }
323        self.base.handle_mouse_input(core, event, false)
324    }
325}
326
327fn main() -> Result<(), Box<dyn std::error::Error>> {
328    env_logger::init();
329    let (app, event_loop) = ShaderApp::new("universe", 800, 600);
330    app.run(event_loop, NebulaShader::init)
331}