Skip to content

Commit 55391f7

Browse files
Added Texture Blitting Utility (#6852)
* Added TextureBlitter Utility * Fixed Clippy and made TextureBlitter Public * Written Documentation * change cull_mode Co-authored-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com> * syntax error Co-authored-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com> * 1 formatting space in shader Co-authored-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com> * Applied suggestions * fix test * Fixed typos * Fixed the tests * Fixed the shader after trying it manually and looking at the output. * Added `BlendState` customization parameter. * Apply suggestions from code review Documentation! Co-authored-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com> * Made it Load instead of Clear(Black) * Added `TextureBlitterBuilder` * Added feature wgsl feature flag for `pub use texture_blitter...;` * Re-add file --------- Co-authored-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com>
1 parent d9cc727 commit 55391f7

File tree

5 files changed

+348
-0
lines changed

5 files changed

+348
-0
lines changed

tests/tests/root.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ mod shader;
4848
mod shader_primitive_index;
4949
mod shader_view_format;
5050
mod subgroup_operations;
51+
mod texture_blit;
5152
mod texture_bounds;
5253
mod texture_view_creation;
5354
mod transfer;

tests/tests/texture_blit.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use wgpu_test::{gpu_test, GpuTestConfiguration};
2+
3+
#[gpu_test]
4+
static TEXTURE_BLIT_WITH_LINEAR_FILTER_TEST: GpuTestConfiguration = GpuTestConfiguration::new()
5+
.run_sync(|ctx| {
6+
let source = ctx.device.create_texture(&wgpu::TextureDescriptor {
7+
label: None,
8+
size: wgpu::Extent3d {
9+
width: 100,
10+
height: 100,
11+
depth_or_array_layers: 1,
12+
},
13+
mip_level_count: 1,
14+
sample_count: 1,
15+
dimension: wgpu::TextureDimension::D2,
16+
format: wgpu::TextureFormat::Rgba16Float,
17+
usage: wgpu::TextureUsages::TEXTURE_BINDING,
18+
view_formats: &[],
19+
});
20+
21+
let target = ctx.device.create_texture(&wgpu::TextureDescriptor {
22+
label: None,
23+
size: wgpu::Extent3d {
24+
width: 100,
25+
height: 100,
26+
depth_or_array_layers: 1,
27+
},
28+
mip_level_count: 1,
29+
sample_count: 1,
30+
dimension: wgpu::TextureDimension::D2,
31+
format: wgpu::TextureFormat::Rgba8UnormSrgb,
32+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
33+
view_formats: &[],
34+
});
35+
36+
let blitter = wgpu::util::TextureBlitterBuilder::new(
37+
&ctx.device,
38+
wgpu::TextureFormat::Rgba8UnormSrgb,
39+
)
40+
.sample_type(wgpu::FilterMode::Linear)
41+
.build();
42+
let mut encoder = ctx
43+
.device
44+
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
45+
46+
blitter.copy(
47+
&ctx.device,
48+
&mut encoder,
49+
&source.create_view(&wgpu::TextureViewDescriptor::default()),
50+
&target.create_view(&wgpu::TextureViewDescriptor::default()),
51+
);
52+
});
53+
54+
#[gpu_test]
55+
static TEXTURE_BLIT_WITH_NEAREST_FILTER_TEST: GpuTestConfiguration = GpuTestConfiguration::new()
56+
.run_sync(|ctx| {
57+
let source = ctx.device.create_texture(&wgpu::TextureDescriptor {
58+
label: None,
59+
size: wgpu::Extent3d {
60+
width: 100,
61+
height: 100,
62+
depth_or_array_layers: 1,
63+
},
64+
mip_level_count: 1,
65+
sample_count: 1,
66+
dimension: wgpu::TextureDimension::D2,
67+
format: wgpu::TextureFormat::Rgba16Float,
68+
usage: wgpu::TextureUsages::TEXTURE_BINDING,
69+
view_formats: &[],
70+
});
71+
72+
let target = ctx.device.create_texture(&wgpu::TextureDescriptor {
73+
label: None,
74+
size: wgpu::Extent3d {
75+
width: 100,
76+
height: 100,
77+
depth_or_array_layers: 1,
78+
},
79+
mip_level_count: 1,
80+
sample_count: 1,
81+
dimension: wgpu::TextureDimension::D2,
82+
format: wgpu::TextureFormat::Rgba8UnormSrgb,
83+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
84+
view_formats: &[],
85+
});
86+
87+
let blitter = wgpu::util::TextureBlitterBuilder::new(
88+
&ctx.device,
89+
wgpu::TextureFormat::Rgba8UnormSrgb,
90+
)
91+
.sample_type(wgpu::FilterMode::Linear)
92+
.build();
93+
94+
let mut encoder = ctx
95+
.device
96+
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
97+
98+
blitter.copy(
99+
&ctx.device,
100+
&mut encoder,
101+
&source.create_view(&wgpu::TextureViewDescriptor::default()),
102+
&target.create_view(&wgpu::TextureViewDescriptor::default()),
103+
);
104+
});

wgpu/src/util/blit.wgsl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
struct VertexOutput {
2+
@builtin(position) position: vec4<f32>,
3+
@location(0) tex_coords: vec2<f32>,
4+
}
5+
6+
@vertex
7+
fn vs_main(@builtin(vertex_index) vi: u32) -> VertexOutput {
8+
var out: VertexOutput;
9+
10+
out.tex_coords = vec2<f32>(
11+
f32((vi << 1u) & 2u),
12+
f32(vi & 2u),
13+
);
14+
15+
out.position = vec4<f32>(out.tex_coords * 2.0 - 1.0, 0.0, 1.0);
16+
17+
// Invert y so the texture is not upside down
18+
out.tex_coords.y = 1.0 - out.tex_coords.y;
19+
return out;
20+
}
21+
22+
@group(0) @binding(0)
23+
var texture: texture_2d<f32>;
24+
@group(0) @binding(1)
25+
var texture_sampler: sampler;
26+
27+
@fragment
28+
fn fs_main(vs: VertexOutput) -> @location(0) vec4<f32> {
29+
return textureSample(texture, texture_sampler, vs.tex_coords);
30+
}

wgpu/src/util/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod belt;
77
mod device;
88
mod encoder;
99
mod init;
10+
mod texture_blitter;
1011

1112
use std::sync::Arc;
1213
use std::{borrow::Cow, ptr::copy_nonoverlapping};
@@ -15,6 +16,8 @@ pub use belt::StagingBelt;
1516
pub use device::{BufferInitDescriptor, DeviceExt};
1617
pub use encoder::RenderEncoder;
1718
pub use init::*;
19+
#[cfg(feature = "wgsl")]
20+
pub use texture_blitter::{TextureBlitter, TextureBlitterBuilder};
1821
pub use wgt::{
1922
math::*, DispatchIndirectArgs, DrawIndexedIndirectArgs, DrawIndirectArgs, TextureDataOrder,
2023
};

wgpu/src/util/texture_blitter.rs

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
#![cfg(feature = "wgsl")]
2+
3+
use wgt::BlendState;
4+
5+
use 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+
pub struct TextureBlitterBuilder<'a> {
18+
device: &'a Device,
19+
format: TextureFormat,
20+
sample_type: FilterMode,
21+
blend_state: Option<BlendState>,
22+
}
23+
24+
impl<'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.
151+
pub struct TextureBlitter {
152+
pipeline: RenderPipeline,
153+
bind_group_layout: BindGroupLayout,
154+
sampler: Sampler,
155+
}
156+
157+
impl 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+
}

0 commit comments

Comments
 (0)