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 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 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 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 self.compute_shader.dispatch_stage(&mut frame.encoder, core, 0);
306
307 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}