1use crate::autointensity::AutoIntensityResources;
2use crate::gradient::{Gradient, RgbColor};
3use crate::intermediate_texture::IntermediateTextureResources;
4use crate::lut_texture::LutTextureResources;
5use crate::pipeline::PipelineResources;
6use crate::transform::AffineTransform;
7use crate::types::UniformData;
8use crate::uniform_buffer::UniformBufferResources;
9use crate::waveform_resources::{WaveformMode, WaveformResources};
10use cgmath::{Vector2, vec2};
11use wgpu::{
12 CommandBuffer, Device, Queue, RenderPass, ShaderModule, ShaderModuleDescriptor, TextureFormat,
13};
14
15#[repr(C)]
16#[derive(Copy, Clone, Default, Debug, PartialOrd, PartialEq, Ord, Eq)]
17pub struct Size<T = u32> {
18 pub width: T,
19 pub height: T,
20}
21
22impl<T> From<(T, T)> for Size<T> {
23 fn from((width, height): (T, T)) -> Size<T> {
24 Size { width, height }
25 }
26}
27
28impl<T> From<[T; 2]> for Size<T> {
29 fn from([width, height]: [T; 2]) -> Size<T> {
30 Size { width, height }
31 }
32}
33
34#[derive(Debug, Copy, Clone)]
35pub struct Viewport {
36 pub size: Size<f32>,
37 pub origin: Vector2<f32>,
38}
39
40#[derive(thiserror::Error, Debug)]
41pub enum Error {
42 #[error("width and height must both greater than zero, got ({0}, {1})")]
43 ZeroSize(u32, u32),
44 #[error("no waveform")]
45 NoWaveform,
46 #[error("`render_intermediate()` must be called before to allocate necessary resource ({0})")]
47 MissingResources(&'static str),
48}
49
50pub type Result<T> = std::result::Result<T, Error>;
51
52#[derive(Debug)]
53struct RenderConfig {
54 #[cfg(feature = "auto-intensity")]
55 calc_auto_intensity: bool,
56 gamma: f32,
57 intensity: f32,
58 beam_radius: f32,
59 decay: Decay,
60 viewport: Viewport,
61}
62
63#[derive(Debug)]
108pub struct Renderer {
109 size: Size,
110 device: Device,
111 texture_format: TextureFormat,
112
113 uniform_resources: UniformBufferResources,
114 intermediate_texture_resources: Option<IntermediateTextureResources>,
115 lut_texture_resources: LutTextureResources,
116 waveform_resources: Option<WaveformResources>,
117 pipeline_resources: Option<PipelineResources>,
118 auto_intensity_resources: Option<AutoIntensityResources>,
119
120 render_shader: ShaderModule,
121 post_fx_shader: ShaderModule,
122 decay_shader: ShaderModule,
123 #[cfg(feature = "auto-intensity")]
124 auto_intensity_shader: ShaderModule,
125
126 render_config: RenderConfig,
127}
128
129impl Renderer {
130 pub fn new(device: &Device, texture_format: &TextureFormat, size: Size) -> Renderer {
149 let uniform_data = UniformBufferResources::new(device);
150
151 let render_shader = device.create_shader_module(ShaderModuleDescriptor {
152 label: Some("Render Shader"),
153 source: wgpu::ShaderSource::Wgsl(include_str!("render.wgsl").into()),
154 });
155 let post_fx_shader = device.create_shader_module(ShaderModuleDescriptor {
156 label: Some("Post-processing Shader"),
157 source: wgpu::ShaderSource::Wgsl(
158 include_str!(concat!(env!("OUT_DIR"), "/postfx.wgsl")).into(),
159 ),
160 });
161 let decay_shader = device.create_shader_module(ShaderModuleDescriptor {
162 label: Some("Decay Shader"),
163 source: wgpu::ShaderSource::Wgsl(include_str!("decay.wgsl").into()),
164 });
165
166 #[cfg(feature = "auto-intensity")]
167 let auto_intensity_shader = device.create_shader_module(ShaderModuleDescriptor {
168 label: Some("Auto Intensity Shader"),
169 source: wgpu::ShaderSource::Wgsl(include_str!("auto_intensity.wgsl").into()),
170 });
171
172 let viewport = Viewport {
173 origin: vec2(0., -1.),
174 size: [1., 2.].into(),
175 };
176
177 Renderer {
178 device: device.clone(),
179 size,
180 intermediate_texture_resources: None,
181 lut_texture_resources: LutTextureResources::new(device),
182 waveform_resources: None,
183 auto_intensity_resources: None,
184 uniform_resources: uniform_data,
185 render_config: RenderConfig {
186 viewport,
187 beam_radius: 6.,
188 intensity: 1.,
189 gamma: 1.0,
190 decay: Decay::instant_redraw(),
191 #[cfg(feature = "auto-intensity")]
192 calc_auto_intensity: false,
193 },
194 texture_format: *texture_format,
195 pipeline_resources: None,
196 render_shader,
197 post_fx_shader,
198 decay_shader,
199 #[cfg(feature = "auto-intensity")]
200 auto_intensity_shader,
201 }
202 }
203}
204
205impl Renderer {
206 pub fn size(&self) -> Size {
208 self.size
209 }
210
211 pub fn resize(&mut self, new_size: Size) -> Result<()> {
238 if new_size.width == 0 || new_size.height == 0 {
239 return Err(Error::ZeroSize(new_size.width, new_size.height));
240 }
241 if self.size != new_size {
242 log::info!("Setting new size: {new_size:?}");
243 self.size = new_size;
244 self.intermediate_texture_resources = None;
245 }
246 Ok(())
247 }
248
249 pub fn set_xlim(&mut self, left: f32, right: f32) {
265 self.render_config.viewport.origin.x = f32::min(left, right);
266 self.render_config.viewport.size.width = (right - left).abs();
267 }
268
269 pub fn set_ylim(&mut self, lower: f32, upper: f32) {
285 self.render_config.viewport.origin.y = f32::min(lower, upper);
286 self.render_config.viewport.size.height = (upper - lower).abs();
287 }
288
289 pub fn set_intensity(&mut self, value: f32) {
295 self.render_config.intensity = value;
296 }
297
298 pub fn set_gamma(&mut self, value: f32) {
300 self.render_config.gamma = value;
301 }
302
303 pub fn set_beam_width(&mut self, width: f32) {
305 self.render_config.beam_radius = width / 2.;
306 }
307
308 pub fn set_waveform_yt(&mut self, waveform: &[f32]) {
317 log::info!("Setting new YT waveform ({} samples)", waveform.len());
318 if self.waveform_resources.as_ref().map(|wf| wf.mode()) != Some(WaveformMode::YT) {
319 self.pipeline_resources = None;
321 }
322 self.waveform_resources = Some(WaveformResources::new(
324 waveform,
325 &self.device,
326 WaveformMode::YT,
327 ))
328 }
329
330 pub fn set_waveform_xy(&mut self, waveform: &[[f32; 2]]) {
340 log::info!("Setting new XY waveform ({} samples)", waveform.len());
341 if self.waveform_resources.as_ref().map(|wf| wf.mode()) != Some(WaveformMode::XY) {
342 self.pipeline_resources = None;
344 }
345 self.waveform_resources = Some(WaveformResources::new(
347 bytemuck::cast_slice(waveform),
348 &self.device,
349 WaveformMode::XY,
350 ))
351 }
352
353 pub fn set_lut(&mut self, lut: impl IntoIterator<Item = (f32, RgbColor<f32>)>) {
354 self.lut_texture_resources.update_lut(Gradient::new(lut));
355 }
356}
357
358#[derive(Debug, Copy, Clone)]
380pub struct Decay {
381 screen_decay: f32,
382 sample_decay: f32,
383}
384
385impl Decay {
386 pub fn new(mut screen_decay: f32, mut sample_decay: f32) -> Decay {
387 screen_decay = screen_decay.max(0.);
388 sample_decay = sample_decay.clamp(0., 1.);
389 Decay {
390 screen_decay,
391 sample_decay,
392 }
393 }
394
395 pub fn with_sample_decay(mut self, sample_decay: f32) -> Decay {
396 self.sample_decay = sample_decay.clamp(0., 1.);
397 self
398 }
399
400 pub fn with_screen_decay(mut self, screen_decay: f32) -> Decay {
401 self.screen_decay = screen_decay.max(0.);
402 self
403 }
404}
405
406impl Decay {
408 pub fn no_decay() -> Decay {
410 Decay {
411 sample_decay: 0.,
412 screen_decay: 0.,
413 }
414 }
415
416 pub fn instant_redraw() -> Decay {
424 Decay {
425 screen_decay: f32::INFINITY,
426 sample_decay: 0.,
427 }
428 }
429
430 pub fn continuous_update(
441 time_since_last_trace: f32,
442 persistence: f32,
443 trace_length: usize,
444 ) -> Decay {
445 Decay::new(
446 time_since_last_trace / persistence,
447 time_since_last_trace / persistence / (trace_length as f32),
448 )
449 }
450}
451
452impl Default for Decay {
453 fn default() -> Self {
454 Decay::instant_redraw()
455 }
456}
457
458impl Renderer {
460 pub fn set_decay(&mut self, decay: Decay) {
461 self.render_config.decay = decay
462 }
463}
464
465impl Renderer {
467 fn prepare(&mut self, queue: &Queue) -> Result<()> {
468 self.lut_texture_resources.prepare(queue);
469 if self.intermediate_texture_resources.is_none() {
470 self.intermediate_texture_resources =
471 Some(IntermediateTextureResources::new(self.size, &self.device));
472 self.pipeline_resources = None;
473 }
474 let intermediate_texture_resources = self.intermediate_texture_resources.as_ref().unwrap();
475
476 if self.auto_intensity_resources.is_none() {
477 let auto_intensity_data = AutoIntensityResources::new(&self.device);
478 self.auto_intensity_resources = Some(auto_intensity_data);
479 #[cfg(feature = "auto-intensity")]
480 self.reset_auto_intensity(queue)?;
481 }
482
483 let Some(waveform_resources) = self.waveform_resources.as_ref() else {
484 return Err(Error::NoWaveform);
485 };
486
487 let auto_intensity_data = self.auto_intensity_resources.as_ref().unwrap();
488 if self.pipeline_resources.is_none() {
489 self.pipeline_resources = Some(PipelineResources::new(
490 &self.render_shader,
491 &self.decay_shader,
492 &self.post_fx_shader,
493 #[cfg(feature = "auto-intensity")]
494 &self.auto_intensity_shader,
495 &self.device,
496 &self.texture_format,
497 intermediate_texture_resources,
498 &self.lut_texture_resources,
499 &self.uniform_resources,
500 waveform_resources,
501 auto_intensity_data,
502 ));
503 }
504
505 self.update_uniform_buffer(queue);
506
507 #[cfg(feature = "auto-intensity")]
508 if self.render_config.calc_auto_intensity {
509 self.set_auto_intensity_value(queue, 1)?;
512 }
513
514 Ok(())
515 }
516
517 fn update_uniform_buffer(&mut self, queue: &Queue) {
518 let transform = AffineTransform::new()
519 .translate(
520 -self.render_config.viewport.origin.x,
521 -self.render_config.viewport.origin.y,
522 )
523 .scale(
524 1. / self.render_config.viewport.size.width,
525 1. / self.render_config.viewport.size.height,
526 )
527 .scale(self.size.width as f32, self.size.height as f32)
528 .wgpu_mat3x3();
529
530 let uniform_data = UniformData {
531 resolution: [self.size.width, self.size.height],
532 transform,
533 beam_radius: self.render_config.beam_radius,
534 intensity: self.render_config.intensity,
535 gamma: self.render_config.gamma,
536 decay_per_sample: self.render_config.decay.sample_decay,
537 num_samples: self
538 .waveform_resources
539 .as_ref()
540 .map(|wf| wf.num_samples())
541 .unwrap_or(0) as u32,
542 screen_decay: self.render_config.decay.screen_decay,
543 };
544 self.uniform_resources.update_data(queue, uniform_data);
545 }
546
547 pub fn render_intermediate(&mut self, queue: &Queue) -> Result<CommandBuffer> {
554 self.prepare(queue)?;
555 let texture_data = self
556 .intermediate_texture_resources
557 .as_ref()
558 .expect("texture_data is missing");
559 let pipelines = self
560 .pipeline_resources
561 .as_ref()
562 .expect("pipeline_data is missing");
563
564 let Some(waveform_resources) = &self.waveform_resources else {
565 return Err(Error::NoWaveform);
566 };
567
568 let mut encoder = self
569 .device
570 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
571 label: Some("Encoder"),
572 });
573 {
574 let load_op = if self.render_config.decay.screen_decay == f32::INFINITY
576 || !texture_data.is_texture_initialized()
577 {
578 texture_data.assume_texture_initialized();
579 wgpu::LoadOp::Clear(wgpu::Color::BLACK)
580 } else {
581 wgpu::LoadOp::Load
582 };
583
584 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
585 label: Some("Render Pass"),
586 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
587 view: texture_data.texture_view(),
588 resolve_target: None,
589 ops: wgpu::Operations {
590 load: load_op,
591 store: wgpu::StoreOp::Store,
592 },
593 })],
594 depth_stencil_attachment: None,
595 occlusion_query_set: None,
596 timestamp_writes: None,
597 });
598
599 if self.render_config.decay.screen_decay > 0.0
601 && self.render_config.decay.screen_decay != f32::INFINITY
602 {
603 render_pass.set_pipeline(pipelines.decay_pipeline());
604 render_pass.set_bind_group(0, self.uniform_resources.bind_group(), &[]);
605 render_pass.draw(0..3, 0..1); }
607
608 render_pass.set_pipeline(pipelines.render_pipeline());
610 render_pass.set_bind_group(0, self.uniform_resources.bind_group(), &[]);
611 if waveform_resources.num_samples() >= 2 {
613 let (start_index, end_index) = match waveform_resources.mode() {
616 WaveformMode::YT => {
617 let start_index: u32 =
619 (self.render_config.viewport.origin.x.floor().max(0.) as i32)
620 .min(waveform_resources.num_samples() as i32 - 1)
621 .try_into()
622 .unwrap();
623 let end_index: u32 = ((self.render_config.viewport.origin.x
624 + self.render_config.viewport.size.width)
625 .ceil()
626 .max(0.) as i32)
627 .min(waveform_resources.num_samples() as i32 - 1)
628 .try_into()
629 .unwrap();
630 (start_index, end_index)
631 }
632 WaveformMode::XY => (0, waveform_resources.num_samples() as u32 - 1),
633 };
634
635 let span = end_index - start_index;
637 render_pass.set_vertex_buffer(0, waveform_resources.buffer().slice(..));
638 render_pass.draw(0..4, (start_index / 2)..(start_index + span).div_ceil(2));
639
640 let stride = waveform_resources.samples_size_bytes() as u64;
642 render_pass.set_vertex_buffer(0, waveform_resources.buffer().slice(stride..));
643 render_pass.draw(4..8, (start_index / 2)..(start_index + span) / 2);
644 }
645 }
646 #[cfg(feature = "auto-intensity")]
647 if self.render_config.calc_auto_intensity {
648 let auto_intensity_data = self
649 .auto_intensity_resources
650 .as_ref()
651 .expect("auto_intensity_data is missing");
652 let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
653 label: Some("Auto Intensity Compute Pass"),
654 timestamp_writes: None,
655 });
656 compute_pass.set_pipeline(pipelines.auto_intensity_pipeline());
657 compute_pass.set_bind_group(0, texture_data.bind_group(), &[]);
658 compute_pass.set_bind_group(1, auto_intensity_data.bind_group_compute(), &[]);
659 compute_pass.dispatch_workgroups(
660 self.size.width.div_ceil(16),
661 self.size.height.div_ceil(16),
662 1,
663 );
664 }
665
666 Ok(encoder.finish())
667 }
668
669 pub fn render(&self, render_pass: &mut RenderPass) -> Result<()> {
674 let Some(ref intermediate_texture_data) = self.intermediate_texture_resources else {
675 return Err(Error::MissingResources("intermediate_texture_data"));
676 };
677 let Some(auto_intensity_data) = &self.auto_intensity_resources else {
678 return Err(Error::MissingResources("auto_intensity_data"));
679 };
680 let Some(ref pipelines) = self.pipeline_resources else {
681 return Err(Error::MissingResources("pipeline_data"));
682 };
683 render_pass.set_pipeline(pipelines.post_fx_pipeline());
684 render_pass.set_bind_group(0, intermediate_texture_data.bind_group(), &[]);
685 render_pass.set_bind_group(1, self.lut_texture_resources.bind_group(), &[]);
686 render_pass.set_bind_group(2, auto_intensity_data.bind_group_fragment(), &[]);
687 render_pass.set_bind_group(3, self.uniform_resources.bind_group(), &[]);
688 render_pass.draw(0..3, 0..1);
689 Ok(())
690 }
691}
692
693#[cfg(feature = "auto-intensity")]
695impl Renderer {
696 pub fn enable_auto_intensity(&mut self, value: bool) {
704 if value != self.render_config.calc_auto_intensity {
705 log::info!("Auto-intensity enabled: {value:?}");
706 self.render_config.calc_auto_intensity = value
707 }
708 }
709
710 pub fn reset_auto_intensity(&self, queue: &Queue) -> Result<()> {
715 log::info!("Resetting auto-intensity value to default");
716 self.set_auto_intensity_value(queue, 1 << 16)
717 }
718
719 fn set_auto_intensity_value(&self, queue: &Queue, value: u32) -> Result<()> {
720 let Some(ref auto_intensity_data) = self.auto_intensity_resources else {
721 return Err(Error::MissingResources("auto_intensity_data"));
722 };
723 log::debug!("Setting auto-intensity value to {value}");
724 queue.write_buffer(
725 auto_intensity_data.max_value_buffer(),
726 0,
727 bytemuck::cast_slice(&[value]),
728 );
729
730 Ok(())
731 }
732}