1use cuneus::compute::*;
2use cuneus::prelude::*;
3use log::error;
4
5cuneus::uniform_params! {
6 struct LegoParams {
7 brick_scale: f32,
8 lightdir_x: f32,
9 lightdir_y: f32,
10 grain: f32,
11 gamma: f32,
12 shadow_str: f32,
13 shadow_dist: f32,
14 ao_str: f32,
15 spec_pow: f32,
16 spec_str: f32,
17 edge_enh: f32,
18 stud_h: f32,
19 base_h: f32,
20 rim_str: f32,
21 res_scale_mult: f32,
22 stud_h_mult: f32,
23 light_r: f32,
24 light_g: f32,
25 light_b: f32,
26 depth_scale: f32,
27 edge_blend: f32,
28 _pad: f32,
29 _pad2: f32,
30 _pad3: f32}
31}
32
33struct LegoShader {
34 base: RenderKit,
35 compute_shader: ComputeShader,
36 current_params: LegoParams}
37
38impl ShaderManager for LegoShader {
39 fn init(core: &Core) -> Self {
40 let initial_params = LegoParams {
41 brick_scale: 0.01,
42 lightdir_x: 0.8,
43 lightdir_y: 0.6,
44 grain: 0.04,
45 gamma: 0.4,
46 shadow_str: 0.5,
47 shadow_dist: 1.25,
48 ao_str: 0.85,
49 spec_pow: 12.0,
50 spec_str: 0.3,
51 edge_enh: 0.15,
52 stud_h: 0.045,
53 base_h: 0.2,
54 rim_str: 0.5,
55 res_scale_mult: 0.2,
56 stud_h_mult: 1.0,
57 light_r: 0.8,
58 light_g: 0.75,
59 light_b: 0.7,
60 depth_scale: 0.85,
61 edge_blend: 0.3,
62 _pad: 0.0,
63 _pad2: 0.0,
64 _pad3: 0.0};
65 let base = RenderKit::new(core);
66
67 let config = ComputeShader::builder()
68 .with_entry_point("main_image")
69 .with_channels(1)
70 .with_custom_uniforms::<LegoParams>()
71 .with_workgroup_size([16, 16, 1])
72 .with_texture_format(COMPUTE_TEXTURE_FORMAT_RGBA16)
73 .with_label("LEGO Effect")
74 .build();
75
76 let compute_shader = cuneus::compute_shader!(core, "shaders/lego.wgsl", config);
77
78 compute_shader.set_custom_params(initial_params, &core.queue);
79
80 Self {
81 base,
82 compute_shader,
83 current_params: initial_params}
84 }
85
86 fn update(&mut self, core: &Core) {
87 self.base.update_current_texture(core, &core.queue);
88
89 if let Some(texture_manager) = self.base.get_current_texture_manager() {
90 self.compute_shader.update_channel_texture(
91 0,
92 &texture_manager.view,
93 &texture_manager.sampler,
94 &core.device,
95 &core.queue,
96 );
97 }
98
99 let current_time = self.base.controls.get_time(&self.base.start_time);
100 let delta = 1.0 / 60.0;
101 self.compute_shader
102 .set_time(current_time, delta, &core.queue);
103 self.compute_shader.handle_export(core, &mut self.base);
104 }
105
106 fn resize(&mut self, core: &Core) {
107 self.base.default_resize(core, &mut self.compute_shader);
108 }
109
110 fn render(&mut self, core: &Core) -> Result<(), cuneus::SurfaceError> {
111 let mut frame = self.base.begin_frame(core)?;
112
113 let mut params = self.current_params;
114 let mut changed = false;
115 let mut should_start_export = false;
116 let mut export_request = self.base.export_manager.get_ui_request();
117 let mut controls_request = self
118 .base
119 .controls
120 .get_ui_request(&self.base.start_time, &core.size, self.base.fps_tracker.fps());
121
122 let using_video_texture = self.base.using_video_texture;
123 let using_hdri_texture = self.base.using_hdri_texture;
124 let using_webcam_texture = self.base.using_webcam_texture;
125 let video_info = self.base.get_video_info();
126 let hdri_info = self.base.get_hdri_info();
127 let webcam_info = self.base.get_webcam_info();
128
129 let current_fps = self.base.fps_tracker.fps();
130
131 let full_output = if self.base.key_handler.show_ui {
132 self.base.render_ui(core, |ctx| {
133 RenderKit::apply_default_style(ctx);
134
135 egui::Window::new("LEGO Effect")
136 .collapsible(true)
137 .resizable(true)
138 .default_width(320.0)
139 .show(ctx, |ui| {
140 ShaderControls::render_media_panel(
141 ui,
142 &mut controls_request,
143 using_video_texture,
144 video_info,
145 using_hdri_texture,
146 hdri_info,
147 using_webcam_texture,
148 webcam_info,
149 );
150
151 ui.separator();
152 egui::CollapsingHeader::new("Brick Geom")
153 .default_open(true)
154 .show(ui, |ui| {
155 changed |= ui
156 .add(
157 egui::Slider::new(&mut params.brick_scale, 0.005..=0.05)
158 .text("Scale"),
159 )
160 .changed();
161 changed |= ui
162 .add(
163 egui::Slider::new(&mut params.stud_h, 0.01..=0.1)
164 .text("Stud H"),
165 )
166 .changed();
167 changed |= ui
168 .add(
169 egui::Slider::new(&mut params.base_h, 0.05..=0.3)
170 .text("Base H"),
171 )
172 .changed();
173 changed |= ui
174 .add(
175 egui::Slider::new(&mut params.stud_h_mult, 1.0..=12.0)
176 .text("Stud Mult"),
177 )
178 .changed();
179 });
180
181 egui::CollapsingHeader::new("Lighting")
182 .default_open(false)
183 .show(ui, |ui| {
184 changed |= ui
185 .add(
186 egui::Slider::new(&mut params.lightdir_x, -1.0..=1.0)
187 .text("Dir X"),
188 )
189 .changed();
190 changed |= ui
191 .add(
192 egui::Slider::new(&mut params.lightdir_y, -1.0..=1.0)
193 .text("Dir Y"),
194 )
195 .changed();
196
197 ui.label("Light Color");
198 let mut color = [params.light_r, params.light_g, params.light_b];
199 if ui.color_edit_button_rgb(&mut color).changed() {
200 params.light_r = color[0];
201 params.light_g = color[1];
202 params.light_b = color[2];
203 changed = true;
204 }
205
206 changed |= ui
207 .add(
208 egui::Slider::new(&mut params.spec_pow, 2.0..=50.0)
209 .text("Spec Pow"),
210 )
211 .changed();
212 changed |= ui
213 .add(
214 egui::Slider::new(&mut params.spec_str, 0.0..=1.0)
215 .text("Spec Str"),
216 )
217 .changed();
218 changed |= ui
219 .add(
220 egui::Slider::new(&mut params.rim_str, 0.0..=1.0)
221 .text("Rim Str"),
222 )
223 .changed();
224 });
225
226 egui::CollapsingHeader::new("Shadows & AO")
227 .default_open(false)
228 .show(ui, |ui| {
229 changed |= ui
230 .add(
231 egui::Slider::new(&mut params.shadow_str, 0.0..=1.0)
232 .text("Shd Str"),
233 )
234 .changed();
235 changed |= ui
236 .add(
237 egui::Slider::new(&mut params.shadow_dist, 0.1..=3.0)
238 .text("Shd Dist"),
239 )
240 .changed();
241 changed |= ui
242 .add(
243 egui::Slider::new(&mut params.ao_str, 0.5..=1.5)
244 .text("AO Cntrst"),
245 )
246 .changed();
247 });
248
249 egui::CollapsingHeader::new("Post-FX")
250 .default_open(false)
251 .show(ui, |ui| {
252 changed |= ui
253 .add(
254 egui::Slider::new(&mut params.edge_enh, 0.0..=0.5)
255 .text("Edge Enh"),
256 )
257 .changed();
258 changed |= ui
259 .add(
260 egui::Slider::new(&mut params.edge_blend, 0.01..=0.3)
261 .text("Edge Blend"),
262 )
263 .changed();
264 changed |= ui
265 .add(
266 egui::Slider::new(&mut params.grain, 0.0..=0.1)
267 .text("Grain"),
268 )
269 .changed();
270 changed |= ui
271 .add(
272 egui::Slider::new(&mut params.gamma, 0.1..=1.4)
273 .text("Gamma"),
274 )
275 .changed();
276 });
277
278 egui::CollapsingHeader::new("Advanced")
279 .default_open(false)
280 .show(ui, |ui| {
281 changed |= ui
282 .add(
283 egui::Slider::new(&mut params.res_scale_mult, 0.01..=2.0)
284 .text("Res Scale"),
285 )
286 .changed();
287 changed |= ui
288 .add(
289 egui::Slider::new(&mut params.depth_scale, 0.5..=1.0)
290 .text("Depth Scl"),
291 )
292 .changed();
293 });
294
295 ui.separator();
296 ShaderControls::render_controls_widget(ui, &mut controls_request);
297 ui.separator();
298 should_start_export =
299 ExportManager::render_export_ui_widget(ui, &mut export_request);
300 ui.separator();
301 ui.label(format!(
302 "Resolution: {}x{}",
303 core.size.width, core.size.height
304 ));
305 ui.label(format!("FPS: {current_fps:.1}"));
306 });
307 })
308 } else {
309 self.base.render_ui(core, |_ctx| {})
310 };
311
312 self.base.apply_media_requests(core, &controls_request);
313
314 self.base.export_manager.apply_ui_request(export_request);
315 if should_start_export {
316 self.base.export_manager.start_export();
317 }
318
319 if changed {
320 self.current_params = params;
321 self.compute_shader.set_custom_params(params, &core.queue);
322 }
323
324 self.compute_shader.dispatch_stage(&mut frame.encoder, core, 0);
325
326 self.base.renderer.render_to_view(&mut frame.encoder, &frame.view, &self.compute_shader.get_output_texture().bind_group);
327
328 self.base.end_frame(core, frame, full_output);
329
330 Ok(())
331 }
332
333 fn handle_input(&mut self, core: &Core, event: &WindowEvent) -> bool {
334 if self.base.default_handle_input(core, event) {
335 return true;
336 }
337 if let WindowEvent::DroppedFile(path) = event {
338 if let Err(e) = self.base.load_media(core, path) {
339 error!("Failed to load dropped file: {e:?}");
340 }
341 return true;
342 }
343 false
344 }
345}
346
347fn main() -> Result<(), Box<dyn std::error::Error>> {
348 cuneus::gst::init()?;
349 env_logger::init();
350 let (app, event_loop) = ShaderApp::new("LEGO Effect", 1280, 720);
351
352 app.run(event_loop, LegoShader::init)
353}