Skip to main content

wgpu/util/
texture_blitter.rs

1#![cfg(feature = "wgsl")]
2
3use wgt::BlendState;
4
5use crate::{
6    include_wgsl, AddressMode, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
7    BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, ColorTargetState, ColorWrites,
8    CommandEncoder, Device, FilterMode, FragmentState, FrontFace, LoadOp, MultisampleState,
9    PipelineCompilationOptions, PipelineLayoutDescriptor, PrimitiveState, PrimitiveTopology,
10    RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, Sampler, SamplerBindingType,
11    SamplerDescriptor, ShaderStages, StoreOp, TextureFormat, TextureSampleType, TextureView,
12    TextureViewDimension, VertexState,
13};
14
15/// A builder for the [`TextureBlitter`] utility.
16/// If you want the default [`TextureBlitter`] use [`TextureBlitter::new`] instead.
17#[derive(Debug)]
18pub struct TextureBlitterBuilder<'a> {
19    device: &'a Device,
20    format: TextureFormat,
21    sample_type: FilterMode,
22    blend_state: Option<BlendState>,
23}
24
25impl<'a> TextureBlitterBuilder<'a> {
26    /// Returns a new [`TextureBlitterBuilder`]
27    ///
28    /// # Arguments
29    /// - `device` - A [`Device`]
30    /// - `format` - The [`TextureFormat`] of the texture that will be copied to. This has to have the `RENDER_TARGET` usage.
31    pub fn new(device: &'a Device, format: TextureFormat) -> Self {
32        Self {
33            device,
34            format,
35            sample_type: FilterMode::Nearest,
36            blend_state: None,
37        }
38    }
39
40    /// Sets the [`Sampler`] Filtering Mode
41    pub fn sample_type(mut self, sample_type: FilterMode) -> Self {
42        self.sample_type = sample_type;
43        self
44    }
45
46    /// Sets the [`BlendState`] that is used.
47    pub fn blend_state(mut self, blend_state: BlendState) -> Self {
48        self.blend_state = Some(blend_state);
49        self
50    }
51
52    /// Returns a new [`TextureBlitter`] with given settings.
53    pub fn build(self) -> TextureBlitter {
54        let sampler = self.device.create_sampler(&SamplerDescriptor {
55            label: Some("wgpu::util::TextureBlitter::sampler"),
56            address_mode_u: AddressMode::ClampToEdge,
57            address_mode_v: AddressMode::ClampToEdge,
58            address_mode_w: AddressMode::ClampToEdge,
59            mag_filter: self.sample_type,
60            // Note: blitting employs `textureSampleLevel` which unconditionally uses mag_filter, so we don't need to set min_filter
61            ..Default::default()
62        });
63
64        let bind_group_layout = self
65            .device
66            .create_bind_group_layout(&BindGroupLayoutDescriptor {
67                label: Some("wgpu::util::TextureBlitter::bind_group_layout"),
68                entries: &[
69                    BindGroupLayoutEntry {
70                        binding: 0,
71                        visibility: ShaderStages::FRAGMENT,
72                        ty: BindingType::Texture {
73                            sample_type: TextureSampleType::Float {
74                                filterable: self.sample_type == FilterMode::Linear,
75                            },
76                            view_dimension: TextureViewDimension::D2,
77                            multisampled: false,
78                        },
79                        count: None,
80                    },
81                    BindGroupLayoutEntry {
82                        binding: 1,
83                        visibility: ShaderStages::FRAGMENT,
84                        ty: BindingType::Sampler(if self.sample_type == FilterMode::Linear {
85                            SamplerBindingType::Filtering
86                        } else {
87                            SamplerBindingType::NonFiltering
88                        }),
89                        count: None,
90                    },
91                ],
92            });
93
94        let pipeline_layout = self
95            .device
96            .create_pipeline_layout(&PipelineLayoutDescriptor {
97                label: Some("wgpu::util::TextureBlitter::pipeline_layout"),
98                bind_group_layouts: &[Some(&bind_group_layout)],
99                immediate_size: 0,
100            });
101
102        let shader = self.device.create_shader_module(include_wgsl!("blit.wgsl"));
103        let pipeline = self
104            .device
105            .create_render_pipeline(&RenderPipelineDescriptor {
106                label: Some("wgpu::util::TextureBlitter::pipeline"),
107                layout: Some(&pipeline_layout),
108                vertex: VertexState {
109                    module: &shader,
110                    entry_point: Some("vs_main"),
111                    compilation_options: PipelineCompilationOptions::default(),
112                    buffers: &[],
113                },
114                primitive: PrimitiveState {
115                    topology: PrimitiveTopology::TriangleList,
116                    strip_index_format: None,
117                    front_face: FrontFace::Ccw,
118                    cull_mode: None,
119                    unclipped_depth: false,
120                    polygon_mode: wgt::PolygonMode::Fill,
121                    conservative: false,
122                },
123                depth_stencil: None,
124                multisample: MultisampleState::default(),
125                fragment: Some(FragmentState {
126                    module: &shader,
127                    entry_point: Some("fs_main"),
128                    compilation_options: PipelineCompilationOptions::default(),
129                    targets: &[Some(ColorTargetState {
130                        format: self.format,
131                        blend: self.blend_state,
132                        write_mask: ColorWrites::ALL,
133                    })],
134                }),
135                multiview_mask: None,
136                cache: None,
137            });
138
139        TextureBlitter {
140            pipeline,
141            bind_group_layout,
142            sampler,
143        }
144    }
145}
146
147/// Texture Blitting (Copying) Utility
148///
149/// Use this if you want to just render/copy texture A to texture B where [`CommandEncoder::copy_texture_to_texture`] would not work because:
150/// - Textures are in incompatible formats.
151/// - Textures are of different sizes.
152/// - Your copy destination is the surface texture and does not have the `COPY_DST` usage.
153#[derive(Debug)]
154pub struct TextureBlitter {
155    pipeline: RenderPipeline,
156    bind_group_layout: BindGroupLayout,
157    sampler: Sampler,
158}
159
160impl TextureBlitter {
161    /// Returns a [`TextureBlitter`] with default settings.
162    ///
163    /// # Arguments
164    /// - `device` - A [`Device`]
165    /// - `format` - The [`TextureFormat`] of the texture that will be copied to. This has to have the `RENDER_TARGET` usage.
166    ///
167    /// Properties of the blitting (such as the [`BlendState`]) can be customised by using [`TextureBlitterBuilder`] instead.
168    pub fn new(device: &Device, format: TextureFormat) -> Self {
169        TextureBlitterBuilder::new(device, format).build()
170    }
171
172    /// Copies the data from the source [`TextureView`] to the target [`TextureView`]
173    ///
174    /// # Arguments
175    /// - `device` - A [`Device`]
176    /// - `encoder` - A [`CommandEncoder`]
177    /// - `source` - A [`TextureView`] that gets copied. The format does not matter.
178    /// - `target` - A [`TextureView`] that gets the data copied from the `source`. It has to be the same format as the format specified in [`TextureBlitter::new`]
179    pub fn copy(
180        &self,
181        device: &Device,
182        encoder: &mut CommandEncoder,
183        source: &TextureView,
184        target: &TextureView,
185    ) {
186        let bind_group = device.create_bind_group(&BindGroupDescriptor {
187            label: Some("wgpu::util::TextureBlitter::bind_group"),
188            layout: &self.bind_group_layout,
189            entries: &[
190                BindGroupEntry {
191                    binding: 0,
192                    resource: crate::BindingResource::TextureView(source),
193                },
194                BindGroupEntry {
195                    binding: 1,
196                    resource: crate::BindingResource::Sampler(&self.sampler),
197                },
198            ],
199        });
200
201        let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
202            label: Some("wgpu::util::TextureBlitter::pass"),
203            color_attachments: &[Some(crate::RenderPassColorAttachment {
204                view: target,
205                depth_slice: None,
206                resolve_target: None,
207                ops: wgt::Operations {
208                    load: LoadOp::Load,
209                    store: StoreOp::Store,
210                },
211            })],
212            depth_stencil_attachment: None,
213            timestamp_writes: None,
214            occlusion_query_set: None,
215            multiview_mask: None,
216        });
217        pass.set_pipeline(&self.pipeline);
218        pass.set_bind_group(0, &bind_group, &[]);
219        pass.draw(0..3, 0..1);
220    }
221}