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