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