Skip to main content

renderling/compositor/
cpu.rs

1//! CPU-side compositor for alpha-blending a source texture onto a target.
2
3/// Alpha-blends a source texture onto a target framebuffer using a
4/// fullscreen quad.
5///
6/// This is useful for overlaying MSAA-resolved UI content on top of an
7/// existing 3D scene without overwriting the scene's pixels.
8///
9/// ```ignore
10/// let compositor = Compositor::new(device, format);
11/// // ... render 3D scene to `view` ...
12/// // ... render UI to `ui_texture` ...
13/// compositor.composite(device, queue, &ui_texture_view, &view);
14/// ```
15pub struct Compositor {
16    pipeline: wgpu::RenderPipeline,
17    bindgroup_layout: wgpu::BindGroupLayout,
18    sampler: wgpu::Sampler,
19}
20
21impl Compositor {
22    /// Create a new compositor targeting the given texture format.
23    pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
24        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
25            label: Some("compositor"),
26            address_mode_u: wgpu::AddressMode::ClampToEdge,
27            address_mode_v: wgpu::AddressMode::ClampToEdge,
28            address_mode_w: wgpu::AddressMode::ClampToEdge,
29            mag_filter: wgpu::FilterMode::Linear,
30            min_filter: wgpu::FilterMode::Linear,
31            ..Default::default()
32        });
33
34        let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
35            label: Some("compositor"),
36            entries: &[
37                wgpu::BindGroupLayoutEntry {
38                    binding: 0,
39                    visibility: wgpu::ShaderStages::FRAGMENT,
40                    ty: wgpu::BindingType::Texture {
41                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
42                        view_dimension: wgpu::TextureViewDimension::D2,
43                        multisampled: false,
44                    },
45                    count: None,
46                },
47                wgpu::BindGroupLayoutEntry {
48                    binding: 1,
49                    visibility: wgpu::ShaderStages::FRAGMENT,
50                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
51                    count: None,
52                },
53            ],
54        });
55
56        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
57            label: Some("compositor"),
58            bind_group_layouts: &[&bindgroup_layout],
59            push_constant_ranges: &[],
60        });
61
62        let vertex = crate::linkage::compositor_vertex::linkage(device);
63        let fragment = crate::linkage::compositor_fragment::linkage(device);
64
65        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
66            label: Some("compositor"),
67            layout: Some(&pipeline_layout),
68            vertex: wgpu::VertexState {
69                module: &vertex.module,
70                entry_point: Some(vertex.entry_point),
71                compilation_options: wgpu::PipelineCompilationOptions::default(),
72                buffers: &[],
73            },
74            primitive: wgpu::PrimitiveState {
75                topology: wgpu::PrimitiveTopology::TriangleList,
76                strip_index_format: None,
77                front_face: wgpu::FrontFace::Ccw,
78                cull_mode: None,
79                unclipped_depth: false,
80                polygon_mode: wgpu::PolygonMode::Fill,
81                conservative: false,
82            },
83            depth_stencil: None,
84            multisample: wgpu::MultisampleState::default(),
85            fragment: Some(wgpu::FragmentState {
86                module: &fragment.module,
87                entry_point: Some(fragment.entry_point),
88                compilation_options: wgpu::PipelineCompilationOptions::default(),
89                targets: &[Some(wgpu::ColorTargetState {
90                    format,
91                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
92                    write_mask: wgpu::ColorWrites::ALL,
93                })],
94            }),
95            multiview: None,
96            cache: None,
97        });
98
99        Self {
100            pipeline,
101            bindgroup_layout,
102            sampler,
103        }
104    }
105
106    /// Alpha-blend the `source` texture onto the `target` framebuffer.
107    ///
108    /// The existing content of `target` is preserved (`LoadOp::Load`)
109    /// and the source is drawn on top using the pipeline's alpha blend
110    /// state.
111    pub fn composite(
112        &self,
113        device: &wgpu::Device,
114        queue: &wgpu::Queue,
115        source: &wgpu::TextureView,
116        target: &wgpu::TextureView,
117    ) {
118        let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor {
119            label: Some("compositor"),
120            layout: &self.bindgroup_layout,
121            entries: &[
122                wgpu::BindGroupEntry {
123                    binding: 0,
124                    resource: wgpu::BindingResource::TextureView(source),
125                },
126                wgpu::BindGroupEntry {
127                    binding: 1,
128                    resource: wgpu::BindingResource::Sampler(&self.sampler),
129                },
130            ],
131        });
132
133        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
134            label: Some("compositor"),
135        });
136
137        {
138            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
139                label: Some("compositor"),
140                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
141                    view: target,
142                    resolve_target: None,
143                    ops: wgpu::Operations {
144                        load: wgpu::LoadOp::Load,
145                        store: wgpu::StoreOp::Store,
146                    },
147                    depth_slice: None,
148                })],
149                depth_stencil_attachment: None,
150                timestamp_writes: None,
151                occlusion_query_set: None,
152            });
153
154            pass.set_pipeline(&self.pipeline);
155            pass.set_bind_group(0, Some(&bindgroup), &[]);
156            pass.draw(0..6, 0..1);
157        }
158
159        queue.submit(std::iter::once(encoder.finish()));
160    }
161}