Skip to main content

wisp/render/
blit.rs

1//! Fullscreen-sampler "blit" pipeline — copy a `RenderTexture` onto an
2//! arbitrary target view.
3//!
4//! Used by the auto-dispatch advanced-blend renderer at the end of each
5//! frame to flush the internal `dest_rt` (where compositing happens)
6//! onto the caller-supplied view (typically a window surface or another
7//! `RenderTexture`).
8
9use crate::application::Application;
10use crate::texture::render_texture::RenderTexture;
11
12pub(crate) struct BlitPipeline {
13    /// `BlendState::REPLACE` — copies source pixels exactly. Used for
14    /// the final `dest_rt` → view flush at end of slow-path render.
15    pipeline: wgpu::RenderPipeline,
16    /// `BlendState::ALPHA_BLENDING` — source-over composite. Used by
17    /// the clip dispatch path to layer a masked RT on top of the
18    /// in-progress destination.
19    pipeline_over: wgpu::RenderPipeline,
20    bind_group_layout: wgpu::BindGroupLayout,
21    sampler: wgpu::Sampler,
22}
23
24impl BlitPipeline {
25    pub(crate) fn new(app: &Application, output_format: wgpu::TextureFormat) -> Self {
26        let device = app.device();
27
28        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
29            label: Some("wisp::blit bg layout"),
30            entries: &[
31                wgpu::BindGroupLayoutEntry {
32                    binding: 0,
33                    visibility: wgpu::ShaderStages::FRAGMENT,
34                    ty: wgpu::BindingType::Texture {
35                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
36                        view_dimension: wgpu::TextureViewDimension::D2,
37                        multisampled: false,
38                    },
39                    count: None,
40                },
41                wgpu::BindGroupLayoutEntry {
42                    binding: 1,
43                    visibility: wgpu::ShaderStages::FRAGMENT,
44                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
45                    count: None,
46                },
47            ],
48        });
49
50        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
51            label: Some("wisp::blit pipeline layout"),
52            bind_group_layouts: &[&bind_group_layout],
53            push_constant_ranges: &[],
54        });
55
56        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
57            label: Some("wisp::blit shader"),
58            source: wgpu::ShaderSource::Wgsl(include_str!("../../shaders/blit.wgsl").into()),
59        });
60
61        let make_pipeline = |label: &str, blend: wgpu::BlendState| {
62            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
63                label: Some(label),
64                layout: Some(&pipeline_layout),
65                vertex: wgpu::VertexState {
66                    module: &shader,
67                    entry_point: Some("main_vs"),
68                    buffers: &[],
69                    compilation_options: wgpu::PipelineCompilationOptions::default(),
70                },
71                fragment: Some(wgpu::FragmentState {
72                    module: &shader,
73                    entry_point: Some("main_fs"),
74                    targets: &[Some(wgpu::ColorTargetState {
75                        format: output_format,
76                        blend: Some(blend),
77                        write_mask: wgpu::ColorWrites::ALL,
78                    })],
79                    compilation_options: wgpu::PipelineCompilationOptions::default(),
80                }),
81                primitive: wgpu::PrimitiveState::default(),
82                depth_stencil: None,
83                multisample: wgpu::MultisampleState::default(),
84                multiview: None,
85                cache: None,
86            })
87        };
88        let pipeline = make_pipeline("wisp::blit pipeline", wgpu::BlendState::REPLACE);
89        let pipeline_over = make_pipeline(
90            "wisp::blit pipeline (over)",
91            wgpu::BlendState::ALPHA_BLENDING,
92        );
93
94        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
95            label: Some("wisp::blit sampler"),
96            address_mode_u: wgpu::AddressMode::ClampToEdge,
97            address_mode_v: wgpu::AddressMode::ClampToEdge,
98            address_mode_w: wgpu::AddressMode::ClampToEdge,
99            mag_filter: wgpu::FilterMode::Linear,
100            min_filter: wgpu::FilterMode::Linear,
101            mipmap_filter: wgpu::FilterMode::Nearest,
102            ..Default::default()
103        });
104
105        Self {
106            pipeline,
107            pipeline_over,
108            bind_group_layout,
109            sampler,
110        }
111    }
112
113    /// Sample `src` and write it onto `target_view`, replacing the
114    /// destination pixels. Used for the final `dest_rt` → view flush.
115    pub(crate) fn blit(
116        &self,
117        app: &Application,
118        src: &RenderTexture,
119        target_view: &wgpu::TextureView,
120    ) {
121        self.run(app, src, target_view, &self.pipeline, /*clear=*/ true);
122    }
123
124    /// Sample `src` and source-over composite onto `target_rt`,
125    /// preserving the destination's existing content under transparent
126    /// regions of `src`. Used by the clip dispatcher to layer a masked
127    /// RT on top of the in-progress destination.
128    pub(crate) fn compose_over(
129        &self,
130        app: &Application,
131        src: &RenderTexture,
132        target_rt: &RenderTexture,
133    ) {
134        self.run(
135            app,
136            src,
137            target_rt.view(),
138            &self.pipeline_over,
139            /*clear=*/ false,
140        );
141    }
142
143    fn run(
144        &self,
145        app: &Application,
146        src: &RenderTexture,
147        target_view: &wgpu::TextureView,
148        pipeline: &wgpu::RenderPipeline,
149        clear: bool,
150    ) {
151        let bg = app.device().create_bind_group(&wgpu::BindGroupDescriptor {
152            label: Some("wisp::blit bg"),
153            layout: &self.bind_group_layout,
154            entries: &[
155                wgpu::BindGroupEntry {
156                    binding: 0,
157                    resource: wgpu::BindingResource::TextureView(src.view()),
158                },
159                wgpu::BindGroupEntry {
160                    binding: 1,
161                    resource: wgpu::BindingResource::Sampler(&self.sampler),
162                },
163            ],
164        });
165
166        let mut encoder = app
167            .device()
168            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
169                label: Some("wisp::blit encoder"),
170            });
171        {
172            let load = if clear {
173                wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT)
174            } else {
175                wgpu::LoadOp::Load
176            };
177            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
178                label: Some("wisp::blit pass"),
179                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
180                    view: target_view,
181                    resolve_target: None,
182                    ops: wgpu::Operations {
183                        load,
184                        store: wgpu::StoreOp::Store,
185                    },
186                })],
187                depth_stencil_attachment: None,
188                timestamp_writes: None,
189                occlusion_query_set: None,
190            });
191            pass.set_pipeline(pipeline);
192            pass.set_bind_group(0, &bg, &[]);
193            pass.draw(0..3, 0..1);
194        }
195        app.queue().submit(std::iter::once(encoder.finish()));
196    }
197}