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 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 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 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 self.compute_shader
240 .dispatch_stage_with_workgroups(&mut frame.encoder, 0, [2048, 1, 1]);
241
242 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}