1use std::sync::Arc;
8
9use bytemuck::{Pod, Zeroable};
10use scenix_core::{ScenixError, ValidationError};
11use scenix_math::Vec2;
12
13#[derive(Clone, Copy, Debug, PartialEq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct PostContext {
17 pub frame_index: u64,
19 pub resolution: Vec2,
21 pub color_format: wgpu::TextureFormat,
23}
24
25#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct PostStats {
29 pub passes: u32,
31 pub resized_targets: u32,
33}
34
35#[derive(Clone, Copy, Debug, PartialEq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct BloomConfig {
39 pub threshold: f32,
41 pub intensity: f32,
43 pub radius: f32,
45}
46
47impl BloomConfig {
48 pub fn normalized(self) -> Self {
50 Self {
51 threshold: self.threshold.clamp(0.0, 16.0),
52 intensity: self.intensity.clamp(0.0, 16.0),
53 radius: self.radius.clamp(0.0, 64.0),
54 }
55 }
56}
57
58impl Default for BloomConfig {
59 #[inline]
60 fn default() -> Self {
61 Self {
62 threshold: 1.0,
63 intensity: 0.35,
64 radius: 4.0,
65 }
66 }
67}
68
69#[derive(Clone, Copy, Debug, PartialEq)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72pub struct SsaoConfig {
73 pub radius: f32,
75 pub intensity: f32,
77 pub bias: f32,
79}
80
81impl SsaoConfig {
82 pub fn normalized(self) -> Self {
84 Self {
85 radius: self.radius.clamp(0.0, 16.0),
86 intensity: self.intensity.clamp(0.0, 4.0),
87 bias: self.bias.clamp(0.0, 1.0),
88 }
89 }
90}
91
92impl Default for SsaoConfig {
93 #[inline]
94 fn default() -> Self {
95 Self {
96 radius: 0.5,
97 intensity: 1.0,
98 bias: 0.025,
99 }
100 }
101}
102
103#[derive(Clone, Copy, Debug, Default, PartialEq)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub enum ToneMapper {
107 None,
109 Reinhard,
111 #[default]
113 Aces,
114 Exposure(f32),
116}
117
118#[derive(Clone, Copy, Debug, PartialEq)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121pub struct FxaaConfig {
122 pub contrast_threshold: f32,
124 pub relative_threshold: f32,
126}
127
128impl FxaaConfig {
129 pub fn normalized(self) -> Self {
131 Self {
132 contrast_threshold: self.contrast_threshold.clamp(0.0, 1.0),
133 relative_threshold: self.relative_threshold.clamp(0.0, 1.0),
134 }
135 }
136}
137
138impl Default for FxaaConfig {
139 #[inline]
140 fn default() -> Self {
141 Self {
142 contrast_threshold: 0.0312,
143 relative_threshold: 0.125,
144 }
145 }
146}
147
148#[derive(Clone, Copy, Debug, PartialEq)]
150#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
151pub struct TaaConfig {
152 pub feedback: f32,
154 pub jitter: f32,
156}
157
158impl TaaConfig {
159 pub fn normalized(self) -> Self {
161 Self {
162 feedback: self.feedback.clamp(0.0, 1.0),
163 jitter: self.jitter.clamp(0.0, 2.0),
164 }
165 }
166}
167
168impl Default for TaaConfig {
169 #[inline]
170 fn default() -> Self {
171 Self {
172 feedback: 0.9,
173 jitter: 0.5,
174 }
175 }
176}
177
178#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
181pub enum SmaaQuality {
182 Low,
184 #[default]
186 Medium,
187 High,
189 Ultra,
191}
192
193#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
195#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
196pub struct SmaaConfig {
197 pub quality: SmaaQuality,
199}
200
201#[derive(Clone, Copy, Debug, PartialEq)]
203#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
204pub struct DofConfig {
205 pub focus_distance: f32,
207 pub aperture: f32,
209 pub max_blur_radius: f32,
211}
212
213impl DofConfig {
214 pub fn normalized(self) -> Self {
216 Self {
217 focus_distance: self.focus_distance.max(0.001),
218 aperture: self.aperture.clamp(0.0, 32.0),
219 max_blur_radius: self.max_blur_radius.clamp(0.0, 64.0),
220 }
221 }
222}
223
224impl Default for DofConfig {
225 #[inline]
226 fn default() -> Self {
227 Self {
228 focus_distance: 10.0,
229 aperture: 1.4,
230 max_blur_radius: 8.0,
231 }
232 }
233}
234
235#[derive(Clone, Copy, Debug, PartialEq)]
237#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
238pub struct FogPostConfig {
239 pub color: [f32; 3],
241 pub density: f32,
243}
244
245impl FogPostConfig {
246 pub fn normalized(self) -> Self {
248 Self {
249 color: [
250 self.color[0].clamp(0.0, 1.0),
251 self.color[1].clamp(0.0, 1.0),
252 self.color[2].clamp(0.0, 1.0),
253 ],
254 density: self.density.clamp(0.0, 1.0),
255 }
256 }
257}
258
259impl Default for FogPostConfig {
260 #[inline]
261 fn default() -> Self {
262 Self {
263 color: [0.5, 0.6, 0.7],
264 density: 0.05,
265 }
266 }
267}
268
269#[derive(Clone, Copy, Debug, PartialEq)]
271#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
272pub struct OutlineConfig {
273 pub color: [f32; 4],
275 pub threshold: f32,
277 pub thickness: f32,
279}
280
281impl OutlineConfig {
282 pub fn normalized(self) -> Self {
284 Self {
285 color: [
286 self.color[0].clamp(0.0, 1.0),
287 self.color[1].clamp(0.0, 1.0),
288 self.color[2].clamp(0.0, 1.0),
289 self.color[3].clamp(0.0, 1.0),
290 ],
291 threshold: self.threshold.clamp(0.0, 1.0),
292 thickness: self.thickness.clamp(0.0, 16.0),
293 }
294 }
295}
296
297impl Default for OutlineConfig {
298 #[inline]
299 fn default() -> Self {
300 Self {
301 color: [0.0, 0.0, 0.0, 1.0],
302 threshold: 0.1,
303 thickness: 1.0,
304 }
305 }
306}
307
308#[derive(Clone, Copy, Debug, PartialEq)]
310#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
311pub struct MotionBlurConfig {
312 pub strength: f32,
314 pub sample_count: u32,
316}
317
318impl MotionBlurConfig {
319 pub fn normalized(self) -> Self {
321 Self {
322 strength: self.strength.clamp(0.0, 1.0),
323 sample_count: self.sample_count.clamp(1, 32),
324 }
325 }
326}
327
328impl Default for MotionBlurConfig {
329 #[inline]
330 fn default() -> Self {
331 Self {
332 strength: 0.08,
333 sample_count: 8,
334 }
335 }
336}
337
338#[derive(Clone, Debug, PartialEq)]
340#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
341pub enum PostEffect {
342 Bloom(BloomConfig),
344 Ssao(SsaoConfig),
346 Tonemap(ToneMapper),
348 Fxaa(FxaaConfig),
350 Taa(TaaConfig),
352 Smaa(SmaaConfig),
354 Dof(DofConfig),
356 Fog(FogPostConfig),
358 Outline(OutlineConfig),
360 MotionBlur(MotionBlurConfig),
362}
363
364impl PostEffect {
365 #[inline]
367 pub const fn kind_id(&self) -> u32 {
368 match self {
369 Self::Bloom(_) => 1,
370 Self::Ssao(_) => 2,
371 Self::Tonemap(_) => 3,
372 Self::Fxaa(_) => 4,
373 Self::Taa(_) => 5,
374 Self::Smaa(_) => 6,
375 Self::Dof(_) => 7,
376 Self::Fog(_) => 8,
377 Self::Outline(_) => 9,
378 Self::MotionBlur(_) => 10,
379 }
380 }
381
382 fn params(&self) -> [f32; 8] {
383 match *self {
384 Self::Bloom(config) => {
385 let config = config.normalized();
386 [
387 config.threshold,
388 config.intensity,
389 config.radius,
390 0.0,
391 self.kind_id() as f32,
392 0.0,
393 0.0,
394 0.0,
395 ]
396 }
397 Self::Ssao(config) => {
398 let config = config.normalized();
399 [
400 config.radius,
401 config.intensity,
402 config.bias,
403 0.0,
404 self.kind_id() as f32,
405 0.0,
406 0.0,
407 0.0,
408 ]
409 }
410 Self::Tonemap(mapper) => {
411 let (mode, exposure) = match mapper {
412 ToneMapper::None => (0.0, 1.0),
413 ToneMapper::Reinhard => (1.0, 1.0),
414 ToneMapper::Aces => (2.0, 1.0),
415 ToneMapper::Exposure(exposure) => (3.0, exposure.max(0.0)),
416 };
417 [
418 mode,
419 exposure,
420 0.0,
421 0.0,
422 self.kind_id() as f32,
423 0.0,
424 0.0,
425 0.0,
426 ]
427 }
428 Self::Fxaa(config) => {
429 let config = config.normalized();
430 [
431 config.contrast_threshold,
432 config.relative_threshold,
433 0.0,
434 0.0,
435 self.kind_id() as f32,
436 0.0,
437 0.0,
438 0.0,
439 ]
440 }
441 Self::Taa(config) => {
442 let config = config.normalized();
443 [
444 config.feedback,
445 config.jitter,
446 0.0,
447 0.0,
448 self.kind_id() as f32,
449 0.0,
450 0.0,
451 0.0,
452 ]
453 }
454 Self::Smaa(config) => {
455 let quality = match config.quality {
456 SmaaQuality::Low => 0.25,
457 SmaaQuality::Medium => 0.5,
458 SmaaQuality::High => 0.75,
459 SmaaQuality::Ultra => 1.0,
460 };
461 [quality, 0.0, 0.0, 0.0, self.kind_id() as f32, 0.0, 0.0, 0.0]
462 }
463 Self::Dof(config) => {
464 let config = config.normalized();
465 [
466 config.focus_distance,
467 config.aperture,
468 config.max_blur_radius,
469 0.0,
470 self.kind_id() as f32,
471 0.0,
472 0.0,
473 0.0,
474 ]
475 }
476 Self::Fog(config) => {
477 let config = config.normalized();
478 [
479 config.color[0],
480 config.color[1],
481 config.color[2],
482 config.density,
483 self.kind_id() as f32,
484 0.0,
485 0.0,
486 0.0,
487 ]
488 }
489 Self::Outline(config) => {
490 let config = config.normalized();
491 [
492 config.color[0],
493 config.color[1],
494 config.threshold,
495 config.thickness,
496 self.kind_id() as f32,
497 config.color[2],
498 config.color[3],
499 0.0,
500 ]
501 }
502 Self::MotionBlur(config) => {
503 let config = config.normalized();
504 [
505 config.strength,
506 config.sample_count as f32,
507 0.0,
508 0.0,
509 self.kind_id() as f32,
510 0.0,
511 0.0,
512 0.0,
513 ]
514 }
515 }
516 }
517}
518
519pub struct PostTarget {
521 texture: wgpu::Texture,
522 view: wgpu::TextureView,
523 width: u32,
524 height: u32,
525 format: wgpu::TextureFormat,
526}
527
528impl PostTarget {
529 pub fn new(
531 device: &wgpu::Device,
532 label: &str,
533 width: u32,
534 height: u32,
535 format: wgpu::TextureFormat,
536 ) -> Result<Self, ScenixError> {
537 if width == 0 || height == 0 {
538 return Err(ScenixError::Validation(ValidationError::OutOfRange));
539 }
540 let texture = device.create_texture(&wgpu::TextureDescriptor {
541 label: Some(label),
542 size: wgpu::Extent3d {
543 width,
544 height,
545 depth_or_array_layers: 1,
546 },
547 mip_level_count: 1,
548 sample_count: 1,
549 dimension: wgpu::TextureDimension::D2,
550 format,
551 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
552 | wgpu::TextureUsages::TEXTURE_BINDING
553 | wgpu::TextureUsages::COPY_SRC,
554 view_formats: &[],
555 });
556 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
557 Ok(Self {
558 texture,
559 view,
560 width,
561 height,
562 format,
563 })
564 }
565
566 #[inline]
568 pub const fn view(&self) -> &wgpu::TextureView {
569 &self.view
570 }
571
572 #[inline]
574 pub const fn texture(&self) -> &wgpu::Texture {
575 &self.texture
576 }
577
578 #[inline]
580 pub const fn width(&self) -> u32 {
581 self.width
582 }
583
584 #[inline]
586 pub const fn height(&self) -> u32 {
587 self.height
588 }
589
590 #[inline]
592 pub const fn format(&self) -> wgpu::TextureFormat {
593 self.format
594 }
595}
596
597pub struct PostStack {
599 effects: Vec<PostEffect>,
600 bind_group_layout: Option<wgpu::BindGroupLayout>,
601 sampler: Option<wgpu::Sampler>,
602 uniform_buffer: Option<wgpu::Buffer>,
603 pipelines: Vec<(wgpu::TextureFormat, Arc<wgpu::RenderPipeline>)>,
604 scratch: [Option<PostTarget>; 2],
605}
606
607impl PostStack {
608 #[inline]
610 pub fn new() -> Self {
611 Self {
612 effects: Vec::new(),
613 bind_group_layout: None,
614 sampler: None,
615 uniform_buffer: None,
616 pipelines: Vec::new(),
617 scratch: [None, None],
618 }
619 }
620
621 #[inline]
623 pub fn effects(&self) -> &[PostEffect] {
624 &self.effects
625 }
626
627 #[inline]
629 pub fn len(&self) -> usize {
630 self.effects.len()
631 }
632
633 #[inline]
635 pub fn is_empty(&self) -> bool {
636 self.effects.is_empty()
637 }
638
639 pub fn with_bloom(mut self, config: BloomConfig) -> Self {
641 self.effects.push(PostEffect::Bloom(config.normalized()));
642 self
643 }
644
645 pub fn with_ssao(mut self, config: SsaoConfig) -> Self {
647 self.effects.push(PostEffect::Ssao(config.normalized()));
648 self
649 }
650
651 pub fn with_tonemap(mut self, mapper: ToneMapper) -> Self {
653 self.effects.push(PostEffect::Tonemap(mapper));
654 self
655 }
656
657 pub fn with_fxaa(mut self, config: FxaaConfig) -> Self {
659 self.effects.push(PostEffect::Fxaa(config.normalized()));
660 self
661 }
662
663 pub fn with_taa(mut self, config: TaaConfig) -> Self {
665 self.effects.push(PostEffect::Taa(config.normalized()));
666 self
667 }
668
669 pub fn with_smaa(mut self, config: SmaaConfig) -> Self {
671 self.effects.push(PostEffect::Smaa(config));
672 self
673 }
674
675 pub fn with_dof(mut self, config: DofConfig) -> Self {
677 self.effects.push(PostEffect::Dof(config.normalized()));
678 self
679 }
680
681 pub fn with_fog(mut self, config: FogPostConfig) -> Self {
683 self.effects.push(PostEffect::Fog(config.normalized()));
684 self
685 }
686
687 pub fn with_outline(mut self, config: OutlineConfig) -> Self {
689 self.effects.push(PostEffect::Outline(config.normalized()));
690 self
691 }
692
693 pub fn with_motion_blur(mut self, config: MotionBlurConfig) -> Self {
695 self.effects
696 .push(PostEffect::MotionBlur(config.normalized()));
697 self
698 }
699
700 pub fn remove(&mut self, index: usize) -> Option<PostEffect> {
702 if index < self.effects.len() {
703 Some(self.effects.remove(index))
704 } else {
705 None
706 }
707 }
708
709 #[inline]
711 pub fn clear(&mut self) {
712 self.effects.clear();
713 }
714
715 pub fn apply_to_view(
717 &mut self,
718 device: &wgpu::Device,
719 queue: &wgpu::Queue,
720 source: &wgpu::TextureView,
721 output: &wgpu::TextureView,
722 context: PostContext,
723 ) -> Result<PostStats, ScenixError> {
724 if self.effects.is_empty() {
725 return Ok(PostStats::default());
726 }
727 if context.resolution.x <= 0.0 || context.resolution.y <= 0.0 {
728 return Err(ScenixError::Validation(ValidationError::OutOfRange));
729 }
730
731 let width = context.resolution.x as u32;
732 let height = context.resolution.y as u32;
733 let resized_targets =
734 self.ensure_scratch_targets(device, width, height, context.color_format)?;
735 let pipeline = self.pipeline(device, context.color_format);
736 self.ensure_common_resources(device);
737
738 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
739 label: Some("scenix.post.encoder"),
740 });
741 let mut current_scratch: Option<usize> = None;
742
743 for index in 0..self.effects.len() {
744 let last = index + 1 == self.effects.len();
745 let destination_scratch = if last { None } else { Some(index % 2) };
746 let source_view = if let Some(scratch) = current_scratch {
747 self.scratch[scratch].as_ref().unwrap().view()
748 } else {
749 source
750 };
751 let destination_view = if let Some(scratch) = destination_scratch {
752 self.scratch[scratch].as_ref().unwrap().view()
753 } else {
754 output
755 };
756
757 let params = self.effects[index].params();
758 self.render_effect(EffectPass {
759 device,
760 queue,
761 pipeline: &pipeline,
762 encoder: &mut encoder,
763 source: source_view,
764 destination: destination_view,
765 params: ¶ms,
766 });
767 current_scratch = destination_scratch;
768 }
769
770 queue.submit(Some(encoder.finish()));
771 Ok(PostStats {
772 passes: self.effects.len() as u32,
773 resized_targets,
774 })
775 }
776
777 fn ensure_scratch_targets(
778 &mut self,
779 device: &wgpu::Device,
780 width: u32,
781 height: u32,
782 format: wgpu::TextureFormat,
783 ) -> Result<u32, ScenixError> {
784 if self.effects.len() <= 1 {
785 return Ok(0);
786 }
787
788 let mut resized = 0;
789 for index in 0..2 {
790 let replace = self.scratch[index].as_ref().is_none_or(|target| {
791 target.width() != width || target.height() != height || target.format() != format
792 });
793 if replace {
794 self.scratch[index] = Some(PostTarget::new(
795 device,
796 if index == 0 {
797 "scenix.post.scratch.0"
798 } else {
799 "scenix.post.scratch.1"
800 },
801 width,
802 height,
803 format,
804 )?);
805 resized += 1;
806 }
807 }
808 Ok(resized)
809 }
810
811 fn ensure_common_resources(&mut self, device: &wgpu::Device) {
812 if self.bind_group_layout.is_none() {
813 self.bind_group_layout = Some(device.create_bind_group_layout(
814 &wgpu::BindGroupLayoutDescriptor {
815 label: Some("scenix.post.bind_group_layout"),
816 entries: &[
817 wgpu::BindGroupLayoutEntry {
818 binding: 0,
819 visibility: wgpu::ShaderStages::FRAGMENT,
820 ty: wgpu::BindingType::Texture {
821 sample_type: wgpu::TextureSampleType::Float { filterable: true },
822 view_dimension: wgpu::TextureViewDimension::D2,
823 multisampled: false,
824 },
825 count: None,
826 },
827 wgpu::BindGroupLayoutEntry {
828 binding: 1,
829 visibility: wgpu::ShaderStages::FRAGMENT,
830 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
831 count: None,
832 },
833 wgpu::BindGroupLayoutEntry {
834 binding: 2,
835 visibility: wgpu::ShaderStages::FRAGMENT,
836 ty: wgpu::BindingType::Buffer {
837 ty: wgpu::BufferBindingType::Uniform,
838 has_dynamic_offset: false,
839 min_binding_size: None,
840 },
841 count: None,
842 },
843 ],
844 },
845 ));
846 }
847 if self.sampler.is_none() {
848 self.sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor {
849 label: Some("scenix.post.sampler"),
850 mag_filter: wgpu::FilterMode::Linear,
851 min_filter: wgpu::FilterMode::Linear,
852 mipmap_filter: wgpu::MipmapFilterMode::Linear,
853 ..Default::default()
854 }));
855 }
856 if self.uniform_buffer.is_none() {
857 self.uniform_buffer = Some(device.create_buffer(&wgpu::BufferDescriptor {
858 label: Some("scenix.post.uniforms"),
859 size: std::mem::size_of::<PostUniform>() as u64,
860 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
861 mapped_at_creation: false,
862 }));
863 }
864 }
865
866 fn pipeline(
867 &mut self,
868 device: &wgpu::Device,
869 format: wgpu::TextureFormat,
870 ) -> Arc<wgpu::RenderPipeline> {
871 if let Some((_, pipeline)) = self
872 .pipelines
873 .iter()
874 .find(|(pipeline_format, _)| *pipeline_format == format)
875 {
876 return Arc::clone(pipeline);
877 }
878
879 self.ensure_common_resources(device);
880 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
881 label: Some("scenix.post.shader"),
882 source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
883 });
884 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
885 label: Some("scenix.post.pipeline_layout"),
886 bind_group_layouts: &[Some(self.bind_group_layout.as_ref().unwrap())],
887 immediate_size: 0,
888 });
889 let pipeline = Arc::new(
890 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
891 label: Some("scenix.post.pipeline"),
892 layout: Some(&layout),
893 vertex: wgpu::VertexState {
894 module: &shader,
895 entry_point: Some("vs_main"),
896 buffers: &[],
897 compilation_options: wgpu::PipelineCompilationOptions::default(),
898 },
899 primitive: wgpu::PrimitiveState::default(),
900 depth_stencil: None,
901 multisample: wgpu::MultisampleState::default(),
902 fragment: Some(wgpu::FragmentState {
903 module: &shader,
904 entry_point: Some("fs_main"),
905 targets: &[Some(wgpu::ColorTargetState {
906 format,
907 blend: Some(wgpu::BlendState::REPLACE),
908 write_mask: wgpu::ColorWrites::ALL,
909 })],
910 compilation_options: wgpu::PipelineCompilationOptions::default(),
911 }),
912 multiview_mask: None,
913 cache: None,
914 }),
915 );
916 self.pipelines.push((format, Arc::clone(&pipeline)));
917 pipeline
918 }
919
920 fn render_effect(&self, pass: EffectPass<'_>) {
921 let uniform = PostUniform {
922 values: *pass.params,
923 };
924 pass.queue.write_buffer(
925 self.uniform_buffer.as_ref().unwrap(),
926 0,
927 bytemuck::bytes_of(&uniform),
928 );
929 let bind_group = pass.device.create_bind_group(&wgpu::BindGroupDescriptor {
930 label: Some("scenix.post.bind_group"),
931 layout: self.bind_group_layout.as_ref().unwrap(),
932 entries: &[
933 wgpu::BindGroupEntry {
934 binding: 0,
935 resource: wgpu::BindingResource::TextureView(pass.source),
936 },
937 wgpu::BindGroupEntry {
938 binding: 1,
939 resource: wgpu::BindingResource::Sampler(self.sampler.as_ref().unwrap()),
940 },
941 wgpu::BindGroupEntry {
942 binding: 2,
943 resource: self.uniform_buffer.as_ref().unwrap().as_entire_binding(),
944 },
945 ],
946 });
947 let mut render_pass = pass.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
948 label: Some("scenix.post.pass"),
949 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
950 view: pass.destination,
951 depth_slice: None,
952 resolve_target: None,
953 ops: wgpu::Operations {
954 load: wgpu::LoadOp::Load,
955 store: wgpu::StoreOp::Store,
956 },
957 })],
958 depth_stencil_attachment: None,
959 timestamp_writes: None,
960 occlusion_query_set: None,
961 multiview_mask: None,
962 });
963 render_pass.set_pipeline(pass.pipeline);
964 render_pass.set_bind_group(0, &bind_group, &[]);
965 render_pass.draw(0..3, 0..1);
966 }
967}
968
969impl Default for PostStack {
970 #[inline]
971 fn default() -> Self {
972 Self::new()
973 }
974}
975
976#[repr(C)]
977#[derive(Clone, Copy, Pod, Zeroable)]
978struct PostUniform {
979 values: [f32; 8],
980}
981
982struct EffectPass<'a> {
983 device: &'a wgpu::Device,
984 queue: &'a wgpu::Queue,
985 pipeline: &'a wgpu::RenderPipeline,
986 encoder: &'a mut wgpu::CommandEncoder,
987 source: &'a wgpu::TextureView,
988 destination: &'a wgpu::TextureView,
989 params: &'a [f32; 8],
990}