Skip to main content

ratatui_wgpu/
shaders.rs

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
73/// The default post-processor. Used when you don't want to perform any custom
74/// shading on the output. This just blits the composited text to the surface.
75/// This will stretch characters if the render area size falls between multiples
76/// of the character size. Use `AspectPreservingDefaultPostProcessor` if you
77/// don't want this behavior.
78pub 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
87/// A default post-processor which preserves the aspect ratio of characters when
88/// the render area size falls in between multiples of the character size.
89pub 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    // All vec3s are padded like vec 4s
314    _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/// Settings for the CRT post-processor.
332///
333/// See struct members for more information on each setting.
334#[derive(Clone, Debug)]
335pub struct CrtSettings {
336    /// How much to increase/reduce the red channel of the CRT effect.
337    /// A good range of values is 0.1 to 2.0.
338    /// Defaults to 1.0.
339    pub modulate_r: f32,
340    /// How much to increase/reduce the green channel of the CRT effect.
341    /// A good range of values is 0.1 to 2.0.
342    /// Defaults to 1.0.
343    pub modulate_g: f32,
344    /// How much to increase/reduce the blue channel of the CRT effect.
345    /// A good range of values is 0.1 to 2.0.
346    /// Defaults to 1.0.
347    pub modulate_b: f32,
348    /// The brightness of the CRT effect.
349    /// A good range of values is 0.0 to 0.2.
350    /// Defaults to 0.09.
351    pub brightness: f32,
352    /// How much to curve the screen for the CRT effect.
353    /// A good range of values is 0.0 to 2.0.
354    /// Defaults to 1.0.
355    pub curve_factor: f32,
356    /// How much "ghosting" to apply to the CRT effect.
357    /// A good range of values is 0.0 to 1.0.
358    /// Defaults to 0.15.
359    pub ghost_factor: f32,
360    /// How strongly to apply the scanline effect.
361    /// A good range of values is 0.0 to 2.0.
362    /// Defaults to 0.4.
363    pub scanline_factor: f32,
364    /// The radius of the corner clipping.
365    /// A good range of values is 0.0 to 500.0.
366    /// Defaults to 210.0.
367    pub corner_radius_factor: f32,
368    /// The type of mask to apply to the CRT effect.
369    /// 1.0 - TV style mask
370    /// 2.0 - Apeture-grille style mask
371    /// 3.0 - VGA (stretched)
372    /// 4.0 - VGA (no stretch)
373    /// Other values will disable the mask.
374    /// Defaults to 3.0.
375    pub mask_type: f32,
376    /// How strongly to apply the mask to the CRT effect.
377    /// A good range of values is 0.0 to 1.0.
378    /// Defaults to 0.2.
379    pub mask_strength: f32,
380    /// How much to fade between frames for the CRT effect. A value of 1.0 will
381    /// result in ghosting from previous frames for animations/screen
382    /// transitions.
383    /// Defaults to 0.0.
384    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
405/// A post-processor which applies a CRT effect to the output.
406pub 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}