1use std::{
2 mem::size_of,
3 num::NonZeroU64,
4};
5
6use web_time::Instant;
7use wgpu::{
8 self,
9 include_wgsl,
10 AddressMode,
11 BindGroup,
12 BindGroupDescriptor,
13 BindGroupEntry,
14 BindGroupLayout,
15 BindGroupLayoutDescriptor,
16 BindGroupLayoutEntry,
17 BindingResource,
18 BindingType,
19 Buffer,
20 BufferBindingType,
21 BufferDescriptor,
22 BufferUsages,
23 Color,
24 ColorTargetState,
25 ColorWrites,
26 CommandEncoder,
27 Device,
28 Extent3d,
29 FilterMode,
30 FragmentState,
31 LoadOp,
32 MultisampleState,
33 Operations,
34 PipelineCompilationOptions,
35 PipelineLayoutDescriptor,
36 PrimitiveState,
37 PrimitiveTopology,
38 Queue,
39 RenderBundle,
40 RenderBundleDescriptor,
41 RenderBundleEncoderDescriptor,
42 RenderPassColorAttachment,
43 RenderPassDescriptor,
44 RenderPipeline,
45 RenderPipelineDescriptor,
46 Sampler,
47 SamplerBindingType,
48 SamplerDescriptor,
49 ShaderStages,
50 StoreOp,
51 SurfaceConfiguration,
52 Texture,
53 TextureDescriptor,
54 TextureDimension,
55 TextureFormat,
56 TextureSampleType,
57 TextureUsages,
58 TextureView,
59 TextureViewDescriptor,
60 TextureViewDimension,
61 VertexState,
62};
63
64use crate::backend::PostProcessor;
65
66#[repr(C)]
67#[derive(bytemuck::Pod, bytemuck::Zeroable, Debug, Clone, Copy)]
68struct Uniforms {
69 screen_size: [f32; 2],
70 preserve_aspect: u32,
71 use_srgb: u32,
72}
73
74pub struct DefaultPostProcessor<const PRESERVE_ASPECT: bool = false> {
80 uniforms: Buffer,
81 bindings: BindGroupLayout,
82 sampler: Sampler,
83 pipeline: RenderPipeline,
84
85 blitter: RenderBundle,
86}
87
88pub type AspectPreservingDefaultPostProcessor = DefaultPostProcessor<true>;
91
92impl<const PRESERVE_ASPECT: bool> PostProcessor for DefaultPostProcessor<PRESERVE_ASPECT> {
93 type UserData = ();
94
95 fn compile(
96 device: &Device,
97 text_view: &TextureView,
98 surface_config: &SurfaceConfiguration,
99 _user_data: Self::UserData,
100 ) -> Self {
101 let uniforms = device.create_buffer(&BufferDescriptor {
102 label: Some("Text Blit Uniforms"),
103 size: size_of::<Uniforms>() as u64,
104 usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
105 mapped_at_creation: false,
106 });
107
108 let sampler = device.create_sampler(&SamplerDescriptor {
109 address_mode_u: AddressMode::ClampToEdge,
110 address_mode_v: AddressMode::ClampToEdge,
111 address_mode_w: AddressMode::ClampToEdge,
112 mag_filter: FilterMode::Nearest,
113 min_filter: FilterMode::Nearest,
114 mipmap_filter: FilterMode::Nearest,
115 ..Default::default()
116 });
117
118 let layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
119 label: Some("Text Blit Bindings Layout"),
120 entries: &[
121 BindGroupLayoutEntry {
122 binding: 0,
123 visibility: ShaderStages::FRAGMENT,
124 ty: BindingType::Texture {
125 sample_type: TextureSampleType::Float { filterable: true },
126 view_dimension: TextureViewDimension::D2,
127 multisampled: false,
128 },
129 count: None,
130 },
131 BindGroupLayoutEntry {
132 binding: 1,
133 visibility: ShaderStages::FRAGMENT,
134 ty: BindingType::Sampler(SamplerBindingType::Filtering),
135 count: None,
136 },
137 BindGroupLayoutEntry {
138 binding: 2,
139 visibility: ShaderStages::FRAGMENT,
140 ty: BindingType::Buffer {
141 ty: BufferBindingType::Uniform,
142 has_dynamic_offset: false,
143 min_binding_size: NonZeroU64::new(size_of::<Uniforms>() as u64),
144 },
145 count: None,
146 },
147 ],
148 });
149
150 let shader = device.create_shader_module(include_wgsl!("shaders/blit.wgsl"));
151
152 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
153 label: Some("Text Blit Layout"),
154 bind_group_layouts: &[&layout],
155 push_constant_ranges: &[],
156 });
157
158 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
159 label: Some("Text Blitter Pipeline"),
160 layout: Some(&pipeline_layout),
161 vertex: VertexState {
162 module: &shader,
163 entry_point: Some("vs_main"),
164 compilation_options: PipelineCompilationOptions::default(),
165 buffers: &[],
166 },
167 primitive: PrimitiveState {
168 topology: PrimitiveTopology::TriangleStrip,
169 ..Default::default()
170 },
171 depth_stencil: None,
172 multisample: MultisampleState::default(),
173 fragment: Some(FragmentState {
174 module: &shader,
175 entry_point: Some("fs_main"),
176 compilation_options: PipelineCompilationOptions::default(),
177 targets: &[Some(ColorTargetState {
178 format: surface_config.format,
179 blend: None,
180 write_mask: ColorWrites::ALL,
181 })],
182 }),
183 multiview: None,
184 cache: None,
185 });
186
187 let blitter = build_blitter(
188 device,
189 &layout,
190 text_view,
191 &sampler,
192 &uniforms,
193 surface_config,
194 &pipeline,
195 );
196
197 Self {
198 uniforms,
199 bindings: layout,
200 sampler,
201 pipeline,
202 blitter,
203 }
204 }
205
206 fn resize(
207 &mut self,
208 device: &Device,
209 text_view: &TextureView,
210 surface_config: &SurfaceConfiguration,
211 ) {
212 self.blitter = build_blitter(
213 device,
214 &self.bindings,
215 text_view,
216 &self.sampler,
217 &self.uniforms,
218 surface_config,
219 &self.pipeline,
220 );
221 }
222
223 fn process(
224 &mut self,
225 encoder: &mut CommandEncoder,
226 queue: &Queue,
227 _text_view: &TextureView,
228 surface_config: &SurfaceConfiguration,
229 surface_view: &TextureView,
230 ) {
231 {
232 let mut uniforms = queue
233 .write_buffer_with(
234 &self.uniforms,
235 0,
236 NonZeroU64::new(size_of::<Uniforms>() as u64).unwrap(),
237 )
238 .unwrap();
239 uniforms.copy_from_slice(bytemuck::bytes_of(&Uniforms {
240 screen_size: [surface_config.width as f32, surface_config.height as f32],
241 preserve_aspect: u32::from(PRESERVE_ASPECT),
242 use_srgb: u32::from(surface_config.format.is_srgb()),
243 }));
244 }
245
246 let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
247 label: Some("Text Blit Pass"),
248 color_attachments: &[Some(RenderPassColorAttachment {
249 view: surface_view,
250 resolve_target: None,
251 ops: Operations {
252 load: LoadOp::Clear(Color::TRANSPARENT),
253 store: StoreOp::Store,
254 },
255 depth_slice: None,
256 })],
257 ..Default::default()
258 });
259
260 pass.execute_bundles(Some(&self.blitter));
261 }
262}
263
264fn build_blitter(
265 device: &Device,
266 layout: &BindGroupLayout,
267 text_view: &TextureView,
268 sampler: &Sampler,
269 uniforms: &Buffer,
270 surface_config: &SurfaceConfiguration,
271 pipeline: &RenderPipeline,
272) -> RenderBundle {
273 let bindings = device.create_bind_group(&BindGroupDescriptor {
274 label: Some("Text Blit Bindings"),
275 layout,
276 entries: &[
277 BindGroupEntry {
278 binding: 0,
279 resource: BindingResource::TextureView(text_view),
280 },
281 BindGroupEntry {
282 binding: 1,
283 resource: BindingResource::Sampler(sampler),
284 },
285 BindGroupEntry {
286 binding: 2,
287 resource: uniforms.as_entire_binding(),
288 },
289 ],
290 });
291
292 let mut encoder = device.create_render_bundle_encoder(&RenderBundleEncoderDescriptor {
293 label: Some("Text Blit Pass Encoder"),
294 color_formats: &[Some(surface_config.format)],
295 depth_stencil: None,
296 sample_count: 1,
297 multiview: None,
298 });
299
300 encoder.set_pipeline(pipeline);
301
302 encoder.set_bind_group(0, &bindings, &[]);
303 encoder.draw(0..3, 0..1);
304
305 encoder.finish(&RenderBundleDescriptor {
306 label: Some("Text Blit Pass Bundle"),
307 })
308}
309
310#[repr(C)]
311#[derive(bytemuck::Pod, bytemuck::Zeroable, Debug, Clone, Copy)]
312struct CrtUniforms {
313 modulate_crt: [f32; 3],
314 _pad0: f32,
316 resolution: [f32; 2],
317 brightness: f32,
318 modulate_accumulate: f32,
319 modulate_blend: f32,
320 slow_fade: i32,
321 curve_factor: f32,
322 ghost_factor: f32,
323 scanline_factor: f32,
324 corner_radius: f32,
325 mask_type: f32,
326 mask_strength: f32,
327 use_srgb: i32,
328 milliseconds: u32,
329 _pad1: [f32; 2],
330}
331
332#[derive(Debug, Clone)]
336pub struct CrtSettings {
337 pub modulate_r: f32,
341 pub modulate_g: f32,
345 pub modulate_b: f32,
349 pub brightness: f32,
353 pub curve_factor: f32,
357 pub ghost_factor: f32,
361 pub scanline_factor: f32,
365 pub corner_radius_factor: f32,
369 pub mask_type: f32,
377 pub mask_strength: f32,
381 pub slow_fade: f32,
386}
387
388impl Default for CrtSettings {
389 fn default() -> Self {
390 Self {
391 modulate_r: 1.0,
392 modulate_g: 1.0,
393 modulate_b: 1.0,
394 brightness: 0.09,
395 curve_factor: 1.0,
396 ghost_factor: 0.15,
397 scanline_factor: 0.4,
398 corner_radius_factor: 210.0,
399 mask_type: 3.0,
400 mask_strength: 0.2,
401 slow_fade: 0.0,
402 }
403 }
404}
405
406pub struct CrtPostProcessor {
408 _sampler: Sampler,
409
410 _crt_uniforms_buffer: Buffer,
411 crt_pass: RenderBundle,
412
413 blur_x_uniforms: Buffer,
414 blur_y_uniforms: Buffer,
415
416 blur_x_dest: TextureView,
417 blur_x_pass: RenderBundle,
418
419 blur_y_dest: TextureView,
420 blur_y_pass: RenderBundle,
421
422 accumulate_texture_in: Texture,
423 accumulate_texture_out: Texture,
424 accumulate_view_out: TextureView,
425
426 width: u32,
427 height: u32,
428 timer: Instant,
429
430 settings: CrtSettings,
431}
432
433impl PostProcessor for CrtPostProcessor {
434 type UserData = CrtSettings;
435
436 fn compile(
437 device: &Device,
438 text_view: &TextureView,
439 surface_config: &SurfaceConfiguration,
440 user_data: Self::UserData,
441 ) -> Self {
442 let drawable_width = surface_config.width;
443 #[cfg(not(target_arch = "wasm32"))]
444 let drawable_height = surface_config.height - 1;
445 #[cfg(target_arch = "wasm32")]
446 let drawable_height = surface_config.height;
447
448 let sampler = device.create_sampler(&SamplerDescriptor {
449 address_mode_u: AddressMode::ClampToEdge,
450 address_mode_v: AddressMode::ClampToEdge,
451 address_mode_w: AddressMode::ClampToEdge,
452 mag_filter: FilterMode::Nearest,
453 min_filter: FilterMode::Nearest,
454 mipmap_filter: FilterMode::Nearest,
455 ..Default::default()
456 });
457
458 let texture_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
459 label: Some("Texture Sourced Layout"),
460 entries: &[
461 BindGroupLayoutEntry {
462 binding: 0,
463 visibility: ShaderStages::FRAGMENT,
464 ty: BindingType::Texture {
465 sample_type: TextureSampleType::Float { filterable: true },
466 view_dimension: TextureViewDimension::D2,
467 multisampled: false,
468 },
469 count: None,
470 },
471 BindGroupLayoutEntry {
472 binding: 1,
473 visibility: ShaderStages::FRAGMENT,
474 ty: BindingType::Sampler(SamplerBindingType::Filtering),
475 count: None,
476 },
477 ],
478 });
479
480 let text_out_as_in_binding = device.create_bind_group(&BindGroupDescriptor {
481 label: Some("Text Compositor Output"),
482 layout: &texture_layout,
483 entries: &[
484 BindGroupEntry {
485 binding: 0,
486 resource: BindingResource::TextureView(text_view),
487 },
488 BindGroupEntry {
489 binding: 1,
490 resource: BindingResource::Sampler(&sampler),
491 },
492 ],
493 });
494
495 let accumulate_texture_in = device.create_texture(&TextureDescriptor {
496 label: Some("Accumulate Out A"),
497 size: Extent3d {
498 width: drawable_width,
499 height: drawable_height,
500 depth_or_array_layers: 1,
501 },
502 mip_level_count: 1,
503 sample_count: 1,
504 dimension: TextureDimension::D2,
505 format: TextureFormat::Rgba8Unorm,
506 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
507 view_formats: &[],
508 });
509
510 let accumulate_view_in =
511 accumulate_texture_in.create_view(&TextureViewDescriptor::default());
512
513 let accumulate_in_binding = device.create_bind_group(&BindGroupDescriptor {
514 label: Some("Accumulate Input"),
515 layout: &texture_layout,
516 entries: &[
517 BindGroupEntry {
518 binding: 0,
519 resource: BindingResource::TextureView(&accumulate_view_in),
520 },
521 BindGroupEntry {
522 binding: 1,
523 resource: BindingResource::Sampler(&sampler),
524 },
525 ],
526 });
527
528 let accumulate_texture_out = device.create_texture(&TextureDescriptor {
529 label: Some("Accumulate Out B"),
530 size: Extent3d {
531 width: drawable_width,
532 height: surface_config.height,
533 depth_or_array_layers: 1,
534 },
535 mip_level_count: 1,
536 sample_count: 1,
537 dimension: TextureDimension::D2,
538 format: TextureFormat::Rgba8Unorm,
539 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC,
540 view_formats: &[],
541 });
542
543 let accumulate_view_out =
544 accumulate_texture_out.create_view(&TextureViewDescriptor::default());
545
546 let crt_uniforms_buffer = device.create_buffer(&BufferDescriptor {
547 label: Some("CRT Uniforms buffer"),
548 size: size_of::<CrtUniforms>() as u64,
549 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
550 mapped_at_creation: false,
551 });
552
553 let (crt_pipeline, crt_fs_uniforms) = build_crt(
554 device,
555 surface_config,
556 &texture_layout,
557 &crt_uniforms_buffer,
558 );
559
560 let blur_x_dest = device.create_texture(&TextureDescriptor {
561 label: Some("Blur x pass"),
562 size: Extent3d {
563 width: drawable_width,
564 height: drawable_height,
565 depth_or_array_layers: 1,
566 },
567 mip_level_count: 1,
568 sample_count: 1,
569 dimension: TextureDimension::D2,
570 format: TextureFormat::Rgba8Unorm,
571 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT,
572 view_formats: &[],
573 });
574
575 let blur_x_dest = blur_x_dest.create_view(&TextureViewDescriptor::default());
576
577 let blur_y_dest = device.create_texture(&TextureDescriptor {
578 label: Some("Blur y pass"),
579 size: Extent3d {
580 width: drawable_width,
581 height: drawable_height,
582 depth_or_array_layers: 1,
583 },
584 mip_level_count: 1,
585 sample_count: 1,
586 dimension: TextureDimension::D2,
587 format: TextureFormat::Rgba8Unorm,
588 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT,
589 view_formats: &[],
590 });
591
592 let blur_y_dest = blur_y_dest.create_view(&TextureViewDescriptor::default());
593
594 let blur_out_as_in_binding = device.create_bind_group(&BindGroupDescriptor {
595 label: Some("Blur output binding"),
596 layout: &texture_layout,
597 entries: &[
598 BindGroupEntry {
599 binding: 0,
600 resource: BindingResource::TextureView(&blur_y_dest),
601 },
602 BindGroupEntry {
603 binding: 1,
604 resource: BindingResource::Sampler(&sampler),
605 },
606 ],
607 });
608
609 let blur_x_uniforms = device.create_buffer(&BufferDescriptor {
610 label: Some("Blur buffer"),
611 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
612 size: size_of::<[f32; 4]>() as u64,
613 mapped_at_creation: false,
614 });
615
616 let blur_y_uniforms = device.create_buffer(&BufferDescriptor {
617 label: Some("Blur buffer"),
618 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
619 size: size_of::<[f32; 4]>() as u64,
620 mapped_at_creation: false,
621 });
622
623 let (blur_pipeline, blur_layout) = build_blur(device);
624
625 let blur_x_pass = {
626 let mut encoder = device.create_render_bundle_encoder(&RenderBundleEncoderDescriptor {
627 label: Some("Blur x text pass"),
628 color_formats: &[Some(TextureFormat::Rgba8Unorm)],
629 depth_stencil: None,
630 sample_count: 1,
631 multiview: None,
632 });
633
634 encoder.set_pipeline(&blur_pipeline);
635
636 let blur_bindings = device.create_bind_group(&BindGroupDescriptor {
637 label: Some("Blur x text bindings"),
638 layout: &blur_layout,
639 entries: &[
640 BindGroupEntry {
641 binding: 0,
642 resource: BindingResource::TextureView(text_view),
643 },
644 BindGroupEntry {
645 binding: 1,
646 resource: BindingResource::Sampler(&sampler),
647 },
648 BindGroupEntry {
649 binding: 2,
650 resource: blur_x_uniforms.as_entire_binding(),
651 },
652 ],
653 });
654 encoder.set_bind_group(0, &blur_bindings, &[]);
655 encoder.draw(0..3, 0..1);
656
657 encoder.finish(&RenderBundleDescriptor {
658 label: Some("Blur x text render bundle"),
659 })
660 };
661
662 let blur_y_pass = {
663 let mut encoder = device.create_render_bundle_encoder(&RenderBundleEncoderDescriptor {
664 label: Some("Blur y pass"),
665 color_formats: &[Some(TextureFormat::Rgba8Unorm)],
666 depth_stencil: None,
667 sample_count: 1,
668 multiview: None,
669 });
670
671 encoder.set_pipeline(&blur_pipeline);
672
673 let blur_bindings = device.create_bind_group(&BindGroupDescriptor {
674 label: Some("Blur y bindings"),
675 layout: &blur_layout,
676 entries: &[
677 BindGroupEntry {
678 binding: 0,
679 resource: BindingResource::TextureView(&blur_x_dest),
680 },
681 BindGroupEntry {
682 binding: 1,
683 resource: BindingResource::Sampler(&sampler),
684 },
685 BindGroupEntry {
686 binding: 2,
687 resource: blur_y_uniforms.as_entire_binding(),
688 },
689 ],
690 });
691 encoder.set_bind_group(0, &blur_bindings, &[]);
692 encoder.draw(0..3, 0..1);
693
694 encoder.finish(&RenderBundleDescriptor {
695 label: Some("Blur y render bundle"),
696 })
697 };
698
699 let crt_pass = {
700 let mut encoder = device.create_render_bundle_encoder(&RenderBundleEncoderDescriptor {
701 label: Some("CRT Render Bundle Encoder"),
702 color_formats: &[Some(surface_config.format), Some(TextureFormat::Rgba8Unorm)],
703 depth_stencil: None,
704 sample_count: 1,
705 multiview: None,
706 });
707
708 encoder.set_pipeline(&crt_pipeline);
709
710 encoder.set_bind_group(0, &text_out_as_in_binding, &[]);
711 encoder.set_bind_group(1, &accumulate_in_binding, &[]);
712 encoder.set_bind_group(2, &blur_out_as_in_binding, &[]);
713 encoder.set_bind_group(3, &crt_fs_uniforms, &[]);
714 encoder.draw(0..3, 0..1);
715
716 encoder.finish(&RenderBundleDescriptor {
717 label: Some("CRT Render Bundle"),
718 })
719 };
720
721 Self {
722 _sampler: sampler,
723 _crt_uniforms_buffer: crt_uniforms_buffer,
724 crt_pass,
725 blur_x_uniforms,
726 blur_y_uniforms,
727 blur_x_dest,
728 blur_x_pass,
729 blur_y_dest,
730 blur_y_pass,
731 accumulate_texture_in,
732 accumulate_texture_out,
733 accumulate_view_out,
734 width: drawable_width,
735 height: drawable_height,
736 timer: Instant::now(),
737 settings: user_data,
738 }
739 }
740
741 fn resize(
742 &mut self,
743 device: &Device,
744 text_view: &TextureView,
745 surface_config: &SurfaceConfiguration,
746 ) {
747 let settings = self.settings.clone();
748 let timer = self.timer;
749
750 *self = Self::compile(device, text_view, surface_config, settings);
751
752 self.timer = timer;
753 }
754
755 fn process(
756 &mut self,
757 encoder: &mut CommandEncoder,
758 queue: &Queue,
759 _text_view: &TextureView,
760 surface_config: &SurfaceConfiguration,
761 surface_view: &TextureView,
762 ) {
763 {
764 let mut uniforms = queue
765 .write_buffer_with(
766 &self.blur_x_uniforms,
767 0,
768 NonZeroU64::new(size_of::<[f32; 2]>() as u64).unwrap(),
769 )
770 .unwrap();
771 uniforms.copy_from_slice(bytemuck::cast_slice(&[1f32 / self.width as f32, 0.0]));
772 }
773
774 {
775 let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
776 label: Some("Blur x pass"),
777 color_attachments: &[Some(RenderPassColorAttachment {
778 view: &self.blur_x_dest,
779 resolve_target: None,
780 ops: Operations {
781 load: LoadOp::Clear(Color::TRANSPARENT),
782 store: StoreOp::Store,
783 },
784 depth_slice: None,
785 })],
786 ..Default::default()
787 });
788
789 render_pass.execute_bundles(Some(&self.blur_x_pass));
790 }
791
792 {
793 let mut uniforms = queue
794 .write_buffer_with(
795 &self.blur_y_uniforms,
796 0,
797 NonZeroU64::new(size_of::<[f32; 2]>() as u64).unwrap(),
798 )
799 .unwrap();
800 uniforms.copy_from_slice(bytemuck::cast_slice(&[0.0, 1f32 / self.height as f32]));
801 }
802
803 {
804 let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
805 label: Some("Blur y pass"),
806 color_attachments: &[Some(RenderPassColorAttachment {
807 view: &self.blur_y_dest,
808 resolve_target: None,
809 ops: Operations {
810 load: LoadOp::Clear(Color::TRANSPARENT),
811 store: StoreOp::Store,
812 },
813 depth_slice: None,
814 })],
815 ..Default::default()
816 });
817
818 render_pass.execute_bundles(Some(&self.blur_y_pass));
819 }
820
821 {
822 let mut uniforms = queue
823 .write_buffer_with(
824 &self._crt_uniforms_buffer,
825 0,
826 NonZeroU64::new(size_of::<CrtUniforms>() as u64).unwrap(),
827 )
828 .unwrap();
829
830 uniforms.copy_from_slice(bytemuck::bytes_of(&CrtUniforms {
831 modulate_crt: [
832 self.settings.modulate_r,
833 self.settings.modulate_g,
834 self.settings.modulate_b,
835 ],
836 _pad0: 0.,
837 brightness: self.settings.brightness,
838 modulate_accumulate: 1.,
839 modulate_blend: 1.,
840 slow_fade: i32::from(self.settings.slow_fade == 1.0),
841 resolution: [self.width as f32, self.height as f32],
842 curve_factor: self.settings.curve_factor,
843 ghost_factor: self.settings.ghost_factor,
844 scanline_factor: self.settings.scanline_factor,
845 corner_radius: self.settings.corner_radius_factor,
846 mask_type: self.settings.mask_type,
847 mask_strength: self.settings.mask_strength,
848 use_srgb: i32::from(surface_config.format.is_srgb()),
849 milliseconds: self.timer.elapsed().as_millis() as u32,
850 _pad1: [0.0; 2],
851 }));
852 }
853 self.timer = Instant::now();
854
855 {
856 let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
857 label: Some("CRT Pass"),
858 color_attachments: &[
859 Some(RenderPassColorAttachment {
860 view: surface_view,
861 resolve_target: None,
862 ops: Operations {
863 load: LoadOp::Clear(Color::TRANSPARENT),
864 store: StoreOp::Store,
865 },
866 depth_slice: None,
867 }),
868 Some(RenderPassColorAttachment {
869 view: &self.accumulate_view_out,
870 resolve_target: None,
871 ops: Operations {
872 load: LoadOp::Load,
873 store: StoreOp::Store,
874 },
875 depth_slice: None,
876 }),
877 ],
878 ..Default::default()
879 });
880
881 render_pass.execute_bundles(Some(&self.crt_pass));
882 }
883
884 encoder.copy_texture_to_texture(
885 self.accumulate_texture_out.as_image_copy(),
886 self.accumulate_texture_in.as_image_copy(),
887 Extent3d {
888 width: self.width,
889 height: self.height,
890 depth_or_array_layers: 1,
891 },
892 );
893 }
894
895 fn needs_update(&self) -> bool {
896 self.settings.slow_fade == 1.0
897 }
898}
899
900fn build_blur(device: &Device) -> (RenderPipeline, BindGroupLayout) {
901 let shader = device.create_shader_module(include_wgsl!("shaders/blur.wgsl"));
902
903 let fragment_shader_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
904 label: Some("Blur Fragment Binding Layout"),
905 entries: &[
906 BindGroupLayoutEntry {
907 binding: 0,
908 visibility: ShaderStages::FRAGMENT,
909 ty: BindingType::Texture {
910 sample_type: TextureSampleType::Float { filterable: true },
911 view_dimension: TextureViewDimension::D2,
912 multisampled: false,
913 },
914 count: None,
915 },
916 BindGroupLayoutEntry {
917 binding: 1,
918 visibility: ShaderStages::FRAGMENT,
919 ty: BindingType::Sampler(SamplerBindingType::Filtering),
920 count: None,
921 },
922 BindGroupLayoutEntry {
923 binding: 2,
924 visibility: ShaderStages::FRAGMENT,
925 ty: BindingType::Buffer {
926 ty: BufferBindingType::Uniform,
927 has_dynamic_offset: false,
928 min_binding_size: Some(NonZeroU64::new(size_of::<[f32; 4]>() as u64).unwrap()),
929 },
930 count: None,
931 },
932 ],
933 });
934
935 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
936 label: Some("Blur Layout"),
937 bind_group_layouts: &[&fragment_shader_layout],
938 push_constant_ranges: &[],
939 });
940
941 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
942 label: Some("Blur Pipeline"),
943 layout: Some(&pipeline_layout),
944 vertex: VertexState {
945 module: &shader,
946 entry_point: Some("vs_main"),
947 compilation_options: PipelineCompilationOptions::default(),
948 buffers: &[],
949 },
950 primitive: PrimitiveState {
951 topology: PrimitiveTopology::TriangleStrip,
952 ..Default::default()
953 },
954 depth_stencil: None,
955 multisample: MultisampleState::default(),
956 fragment: Some(FragmentState {
957 module: &shader,
958 entry_point: Some("fs_main"),
959 compilation_options: PipelineCompilationOptions::default(),
960 targets: &[Some(ColorTargetState {
961 format: TextureFormat::Rgba8Unorm,
962 blend: None,
963 write_mask: ColorWrites::ALL,
964 })],
965 }),
966 multiview: None,
967 cache: None,
968 });
969
970 (pipeline, fragment_shader_layout)
971}
972
973fn build_crt(
974 device: &Device,
975 config: &SurfaceConfiguration,
976 texture_layout: &BindGroupLayout,
977 crt_uniforms_buffer: &Buffer,
978) -> (RenderPipeline, BindGroup) {
979 let shader = device.create_shader_module(include_wgsl!("shaders/crt.wgsl"));
980
981 let uniforms_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
982 label: Some("CRT Fragment Uniforms Binding Layout"),
983 entries: &[BindGroupLayoutEntry {
984 binding: 0,
985 visibility: ShaderStages::FRAGMENT,
986 ty: BindingType::Buffer {
987 ty: BufferBindingType::Uniform,
988 has_dynamic_offset: false,
989 min_binding_size: Some(NonZeroU64::new(size_of::<CrtUniforms>() as u64).unwrap()),
990 },
991 count: None,
992 }],
993 });
994
995 let fs_uniforms = device.create_bind_group(&BindGroupDescriptor {
996 label: Some("CRT Fragment Uniforms Binding"),
997 layout: &uniforms_layout,
998 entries: &[BindGroupEntry {
999 binding: 0,
1000 resource: crt_uniforms_buffer.as_entire_binding(),
1001 }],
1002 });
1003
1004 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
1005 label: Some("CRT Layout"),
1006 bind_group_layouts: &[
1007 texture_layout,
1008 texture_layout,
1009 texture_layout,
1010 &uniforms_layout,
1011 ],
1012 push_constant_ranges: &[],
1013 });
1014
1015 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
1016 label: Some("CRT Pipeline"),
1017 layout: Some(&pipeline_layout),
1018 vertex: VertexState {
1019 module: &shader,
1020 entry_point: Some("vs_main"),
1021 compilation_options: PipelineCompilationOptions::default(),
1022 buffers: &[],
1023 },
1024 primitive: PrimitiveState {
1025 topology: PrimitiveTopology::TriangleStrip,
1026 ..Default::default()
1027 },
1028 depth_stencil: None,
1029 multisample: MultisampleState::default(),
1030 fragment: Some(FragmentState {
1031 module: &shader,
1032 entry_point: Some("fs_main"),
1033 compilation_options: PipelineCompilationOptions::default(),
1034 targets: &[
1035 Some(ColorTargetState {
1036 format: config.format,
1037 blend: None,
1038 write_mask: ColorWrites::ALL,
1039 }),
1040 Some(ColorTargetState {
1041 format: TextureFormat::Rgba8Unorm,
1042 blend: None,
1043 write_mask: ColorWrites::ALL,
1044 }),
1045 ],
1046 }),
1047 multiview: None,
1048 cache: None,
1049 });
1050
1051 (pipeline, fs_uniforms)
1052}