1use cuneus::prelude::*;
2use cuneus::{Core, RenderKit, ShaderApp, ShaderManager, UniformProvider};
3
4#[repr(C)]
5#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
6struct ExperimentParams {
7
8 col_bg: [f32; 4],
9 col_line: [f32; 4],
10 col_core: [f32; 4],
11 col_amber: [f32; 4],
12
13 ball_offset_x: f32,
14 ball_offset_y: f32,
15 ball_sink: f32,
16
17 distortion_amt: f32,
18 noise_amt: f32,
19 stream_width: f32,
20
21 speed: f32,
22 scale: f32,
23 angle: f32,
24 line_freq: f32,
25
26 cam_height: f32,
27 cam_distance: f32,
28 cam_fov: f32,
29
30 rim_intensity: f32,
31 spotlight_intensity: f32,
32 spotlight_height: f32,
33
34 gamma: f32,
35 saturation: f32,
36 contrast: f32,
37
38 orbital_enabled: u32,
39 orbital_speed: f32,
40 orbital_radius: f32,
41 _pad: [f32; 2],
42}
43
44impl Default for ExperimentParams {
45 fn default() -> Self {
46 Self {
47 col_bg: [0.05, 0.02, 0.10, 1.0],
48 col_line: [0.55, 0.40, 0.85, 1.0],
49 col_core: [1.0, 0.1, 0.2, 1.0],
50 col_amber: [1.0, 0.6, 0.1, 1.0],
51
52 ball_offset_x: 0.0,
53 ball_offset_y: 0.0,
54 ball_sink: 1.0,
55
56 distortion_amt: 50.0,
57 noise_amt: 300.0,
58 stream_width: 0.08,
59
60 speed: 1.0,
61 scale: 1.35,
62 angle: -1.785398,
63 line_freq: 100.0,
64
65 cam_height: 3.58,
66 cam_distance: 6.0,
67 cam_fov: 1.7,
68
69 rim_intensity: 1.2,
70 spotlight_intensity: 1.5,
71 spotlight_height: 1.0,
72
73 gamma: 0.66,
74 saturation: 0.71,
75 contrast: 1.1,
76
77 orbital_enabled: 1,
78 orbital_speed: 0.3,
79 orbital_radius: 2.12,
80 _pad: [0.0; 2],
81 }
82 }
83}
84
85impl UniformProvider for ExperimentParams {
86 fn as_bytes(&self) -> &[u8] {
87 bytemuck::bytes_of(self)
88 }
89}
90
91struct ExperimentShader {
92 base: RenderKit,
93 compute_shader: ComputeShader,
94 current_params: ExperimentParams,
95 orbital_enabled: bool,
96}
97
98impl ShaderManager for ExperimentShader {
99 fn init(core: &Core) -> Self {
100 let base = RenderKit::new(core);
101 let initial_params = ExperimentParams::default();
102
103 let config = ComputeShader::builder()
104 .with_entry_point("main")
105 .with_custom_uniforms::<ExperimentParams>()
106 .with_workgroup_size([16, 16, 1])
107 .with_texture_format(wgpu::TextureFormat::Rgba16Float)
108 .with_label("Currents Port")
109 .build();
110
111 let compute_shader = cuneus::compute_shader!(core, "shaders/tameimp.wgsl", config);
112
113 compute_shader.set_custom_params(initial_params, &core.queue);
114
115 Self {
116 base,
117 compute_shader,
118 current_params: initial_params,
119 orbital_enabled: true,
120 }
121 }
122
123 fn update(&mut self, core: &Core) {
124 self.compute_shader.handle_export(core, &mut self.base);
125 }
126
127 fn render(&mut self, core: &Core) -> Result<(), cuneus::SurfaceError> {
128 let mut frame = self.base.begin_frame(core)?;
129
130 let mut params = self.current_params;
131 let mut changed = false;
132 let mut should_start_export = false;
133 let mut export_request = self.base.export_manager.get_ui_request();
134 let mut controls_request = self
135 .base
136 .controls
137 .get_ui_request(&self.base.start_time, &core.size, self.base.fps_tracker.fps());
138
139
140 let mut orbital_enabled = self.orbital_enabled;
141
142 let full_output = if self.base.key_handler.show_ui {
143 self.base.render_ui(core, |ctx| {
144 ctx.global_style_mut(|style| {
145 style.visuals.window_fill = egui::Color32::from_black_alpha(220);
146 style.visuals.window_stroke = egui::Stroke::new(1.0, egui::Color32::from_gray(60));
147 style.text_styles.get_mut(&egui::TextStyle::Body).unwrap().size = 11.0;
148 style.text_styles.get_mut(&egui::TextStyle::Button).unwrap().size = 10.0;
149 style.text_styles.get_mut(&egui::TextStyle::Small).unwrap().size = 9.0;
150 style.text_styles.get_mut(&egui::TextStyle::Heading).unwrap().size = 12.0;
151 style.spacing.slider_width = 140.0;
152 style.spacing.item_spacing = egui::vec2(4.0, 3.0);
153 });
154
155 egui::Window::new("Currents")
156 .default_width(220.0)
157 .show(ctx, |ui| {
158
159 egui::CollapsingHeader::new("🔮 ball").default_open(true).show(ui, |ui| {
160 ui.horizontal(|ui| {
161 ui.label("X");
162 changed |= ui.add(egui::Slider::new(&mut params.ball_offset_x, -1.0..=1.0).show_value(true)).changed();
163 });
164 ui.horizontal(|ui| {
165 ui.label("Y");
166 changed |= ui.add(egui::Slider::new(&mut params.ball_offset_y, -1.0..=1.0).show_value(true)).changed();
167 });
168 ui.horizontal(|ui| {
169 ui.label("Sink");
170 changed |= ui.add(egui::Slider::new(&mut params.ball_sink, -3.2..=3.2).show_value(true)).changed();
171 });
172 ui.horizontal(|ui| {
173 ui.label("Angle");
174 changed |= ui.add(egui::Slider::new(&mut params.angle, -3.14..=3.14).show_value(true)).changed();
175 });
176 });
177
178 egui::CollapsingHeader::new("🌊 noise").default_open(false).show(ui, |ui| {
179 ui.horizontal(|ui| {
180 ui.label("Distortion");
181 changed |= ui.add(egui::Slider::new(&mut params.distortion_amt, 0.0..=75.0).show_value(true)).changed();
182 });
183 ui.horizontal(|ui| {
184 ui.label("Stream W");
185 changed |= ui.add(egui::Slider::new(&mut params.stream_width, 0.01..=0.3).show_value(true)).changed();
186 });
187 ui.horizontal(|ui| {
188 ui.label("Line Freq");
189 changed |= ui.add(egui::Slider::new(&mut params.line_freq, 20.0..=200.0).show_value(true)).changed();
190 });
191 });
192 egui::CollapsingHeader::new("📷 cam").default_open(false).show(ui, |ui| {
193 ui.horizontal(|ui| {
194 if ui.checkbox(&mut orbital_enabled, "🔄 Orbital").changed() {
195 changed = true;
196 }
197 });
198 if orbital_enabled {
199 ui.horizontal(|ui| {
200 ui.label("Orb Speed");
201 changed |= ui.add(egui::Slider::new(&mut params.orbital_speed, 0.01..=2.0).show_value(true)).changed();
202 });
203 ui.horizontal(|ui| {
204 ui.label("Orb Radius");
205 changed |= ui.add(egui::Slider::new(&mut params.orbital_radius, 0.1..=3.0).show_value(true)).changed();
206 });
207 }
208
209 ui.separator();
210
211 ui.horizontal(|ui| {
212 ui.label("Height");
213 changed |= ui.add(egui::Slider::new(&mut params.cam_height, 0.5..=5.0).show_value(true)).changed();
214 });
215 ui.horizontal(|ui| {
216 ui.label("FOV");
217 changed |= ui.add(egui::Slider::new(&mut params.cam_fov, 0.5..=2.5).show_value(true)).changed();
218 });
219 ui.horizontal(|ui| {
220 ui.label("Scale");
221 changed |= ui.add(egui::Slider::new(&mut params.scale, 0.3..=3.0).show_value(true)).changed();
222 });
223 });
224
225 egui::CollapsingHeader::new("💡 light").default_open(false).show(ui, |ui| {
226 ui.horizontal(|ui| {
227 ui.label("Spot Int");
228 changed |= ui.add(egui::Slider::new(&mut params.spotlight_intensity, 0.0..=5.0).show_value(true)).changed();
229 });
230 ui.horizontal(|ui| {
231 ui.label("Spot H");
232 changed |= ui.add(egui::Slider::new(&mut params.spotlight_height, 0.2..=3.0).show_value(true)).changed();
233 });
234 ui.horizontal(|ui| {
235 ui.label("fog");
236 changed |= ui.add(egui::Slider::new(&mut params.cam_distance, 3.0..=8.0).show_value(true)).changed();
237 });
238 });
239
240 egui::CollapsingHeader::new("✨ post").default_open(false).show(ui, |ui| {
241 ui.horizontal(|ui| {
242 ui.label("Gamma");
243 changed |= ui.add(egui::Slider::new(&mut params.gamma, 0.2..=2.0).show_value(true)).changed();
244 });
245 ui.horizontal(|ui| {
246 ui.label("Saturation");
247 changed |= ui.add(egui::Slider::new(&mut params.saturation, 0.0..=2.5).show_value(true)).changed();
248 });
249 ui.horizontal(|ui| {
250 ui.label("Contrast");
251 changed |= ui.add(egui::Slider::new(&mut params.contrast, 0.5..=2.0).show_value(true)).changed();
252 });
253 });
254 egui::CollapsingHeader::new("🎨 cols").default_open(false).show(ui, |ui| {
255 egui::Grid::new("colors_grid")
256 .num_columns(2)
257 .spacing([10.0, 4.0])
258 .striped(true)
259 .show(ui, |ui| {
260 ui.label("Background");
261 let mut rgb = [params.col_bg[0], params.col_bg[1], params.col_bg[2]];
262 if ui.color_edit_button_rgb(&mut rgb).changed() {
263 params.col_bg[0] = rgb[0];
264 params.col_bg[1] = rgb[1];
265 params.col_bg[2] = rgb[2];
266 changed = true;
267 }
268 ui.end_row();
269 ui.label("Line");
270 let mut rgb = [params.col_line[0], params.col_line[1], params.col_line[2]];
271 if ui.color_edit_button_rgb(&mut rgb).changed() {
272 params.col_line[0] = rgb[0];
273 params.col_line[1] = rgb[1];
274 params.col_line[2] = rgb[2];
275 changed = true;
276 }
277 ui.end_row();
278 ui.label("Stream Core");
279 let mut rgb = [params.col_core[0], params.col_core[1], params.col_core[2]];
280 if ui.color_edit_button_rgb(&mut rgb).changed() {
281 params.col_core[0] = rgb[0];
282 params.col_core[1] = rgb[1];
283 params.col_core[2] = rgb[2];
284 changed = true;
285 }
286 ui.end_row();
287 ui.label("Stream Amber");
288 let mut rgb = [params.col_amber[0], params.col_amber[1], params.col_amber[2]];
289 if ui.color_edit_button_rgb(&mut rgb).changed() {
290 params.col_amber[0] = rgb[0];
291 params.col_amber[1] = rgb[1];
292 params.col_amber[2] = rgb[2];
293 changed = true;
294 }
295 ui.end_row();
296 });
297 });
298 egui::CollapsingHeader::new("⏱").default_open(false).show(ui, |ui| {
299 ui.horizontal(|ui| {
300 ui.label("Speed");
301 changed |= ui.add(egui::Slider::new(&mut params.speed, 0.0..=2.0).show_value(true)).changed();
302 });
303 });
304
305 ui.separator();
306
307 egui::CollapsingHeader::new("▶").default_open(false).show(ui, |ui| {
308 cuneus::ShaderControls::render_controls_widget(ui, &mut controls_request);
309 });
310
311 egui::CollapsingHeader::new("💾").default_open(false).show(ui, |ui| {
312 should_start_export = cuneus::ExportManager::render_export_ui_widget(ui, &mut export_request);
313 });
314
315 ui.separator();
316 ui.horizontal(|ui| {
317 ui.separator();
318 ui.small("H: toggle UI");
319 });
320 });
321 })
322 } else {
323 self.base.render_ui(core, |_ctx| {})
324 };
325
326 self.orbital_enabled = orbital_enabled;
328 params.orbital_enabled = if orbital_enabled { 1 } else { 0 };
329
330 self.base.export_manager.apply_ui_request(export_request);
331 self.base.apply_control_request(controls_request);
332
333 let current_time = self.base.controls.get_time(&self.base.start_time);
334 self.compute_shader
335 .set_time(current_time, 1.0 / 60.0, &core.queue);
336
337 if changed {
338 self.current_params = params;
339 self.compute_shader.set_custom_params(params, &core.queue);
340 }
341
342 if should_start_export {
343 self.base.export_manager.start_export();
344 }
345
346 self.compute_shader.dispatch(&mut frame.encoder, core);
347
348 self.base.renderer.render_to_view(&mut frame.encoder, &frame.view, &self.compute_shader.get_output_texture().bind_group);
349
350 self.base.end_frame(core, frame, full_output);
351 Ok(())
352 }
353
354 fn resize(&mut self, core: &Core) {
355 self.base.default_resize(core, &mut self.compute_shader);
356 }
357
358 fn handle_input(&mut self, core: &Core, event: &WindowEvent) -> bool {
359 self.base.default_handle_input(core, event)
360 }
361}
362
363fn main() -> Result<(), Box<dyn std::error::Error>> {
364 env_logger::init();
365 let (app, event_loop) = ShaderApp::new("Currents", 600, 800);
366 app.run(event_loop, ExperimentShader::init)
367}