Skip to content

Commit 1e784bf

Browse files
committed
Add tests
1 parent 917e779 commit 1e784bf

File tree

9 files changed

+308
-12
lines changed

9 files changed

+308
-12
lines changed

Cargo.lock

+42
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ env_logger = "0.9"
5757
futures-intrusive = "0.4"
5858
fxhash = "0.2.1"
5959
glam = "0.21.3"
60+
image = { version = "0.24", default-features = false, features = ["png"] }
6061
libloading = "0.7"
6162
libc = "0.2"
6263
log = "0.4"

wgpu-core/src/device/queue.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
827827
return Ok(());
828828
}
829829

830-
if matches!(source.source, wgt::ExternalImageSource::OffscreenCanvas(_)) {
830+
if matches!(source.source, wgt::ExternalImageSource::OffscreenCanvas(_))
831+
|| source.origin != wgt::Origin2d::ZERO
832+
{
831833
device
832834
.require_downlevel_flags(wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES)
833835
.map_err(TransferError::from)?;

wgpu-types/src/lib.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1140,7 +1140,9 @@ bitflags::bitflags! {
11401140
/// Corresponds to Vulkan's `VkPhysicalDeviceFeatures.depthBiasClamp`
11411141
const DEPTH_BIAS_CLAMP = 1 << 18;
11421142

1143-
/// Supports copying from an OffscreenCanvas in `Queue::copy_external_image_to_texture`.
1143+
/// Supports the following on `Queue::copy_external_image_to_texture`:
1144+
/// - Copying from an OffscreenCanvas
1145+
/// - Non-zero [`ImageCopyExternalImage::origin`].
11441146
///
11451147
/// WebGL doesn't support this.
11461148
const UNRESTRICTED_EXTERNAL_TEXTURE_COPIES = 1 << 19;
@@ -5084,6 +5086,8 @@ pub struct ImageCopyExternalImage {
50845086
/// sub-region of the image to copy.
50855087
///
50865088
/// Relative to the top left of the image.
5089+
///
5090+
/// Must be [`Origin2d::ZERO`] if [`DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES`] is not supported.
50875091
pub origin: Origin2d,
50885092
/// If the Y coordinate of the image should be flipped. Even if this is
50895093
/// true, `origin` is still relative to the top left.

wgpu/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ glam.workspace = true
164164
ddsfile.workspace = true
165165
futures-intrusive.workspace = true
166166
env_logger.workspace = true
167+
image.workspace = true
167168
log.workspace = true
168169
noise = { workspace = true }
169170
obj.workspace = true

wgpu/src/lib.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ pub use wgt::{
3535
CommandBufferDescriptor, CompareFunction, CompositeAlphaMode, DepthBiasState,
3636
DepthStencilState, DeviceType, DownlevelCapabilities, DownlevelFlags, DynamicOffset, Extent3d,
3737
Face, Features, FilterMode, FrontFace, ImageDataLayout, ImageSubresourceRange, IndexFormat,
38-
Limits, MultisampleState, Origin3d, PipelineStatisticsTypes, PolygonMode, PowerPreference,
39-
PredefinedColorSpace, PresentMode, PresentationTimestamp, PrimitiveState, PrimitiveTopology,
40-
PushConstantRange, QueryType, RenderBundleDepthStencil, SamplerBindingType, SamplerBorderColor,
41-
ShaderLocation, ShaderModel, ShaderStages, StencilFaceState, StencilOperation, StencilState,
42-
StorageTextureAccess, SurfaceCapabilities, SurfaceConfiguration, SurfaceStatus, TextureAspect,
43-
TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures,
44-
TextureSampleType, TextureUsages, TextureViewDimension, VertexAttribute, VertexFormat,
45-
VertexStepMode, COPY_BUFFER_ALIGNMENT, COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT,
46-
PUSH_CONSTANT_ALIGNMENT, QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE,
47-
VERTEX_STRIDE_ALIGNMENT,
38+
Limits, MultisampleState, Origin2d, Origin3d, PipelineStatisticsTypes, PolygonMode,
39+
PowerPreference, PredefinedColorSpace, PresentMode, PresentationTimestamp, PrimitiveState,
40+
PrimitiveTopology, PushConstantRange, QueryType, RenderBundleDepthStencil, SamplerBindingType,
41+
SamplerBorderColor, ShaderLocation, ShaderModel, ShaderStages, StencilFaceState,
42+
StencilOperation, StencilState, StorageTextureAccess, SurfaceCapabilities,
43+
SurfaceConfiguration, SurfaceStatus, TextureAspect, TextureDimension, TextureFormat,
44+
TextureFormatFeatureFlags, TextureFormatFeatures, TextureSampleType, TextureUsages,
45+
TextureViewDimension, VertexAttribute, VertexFormat, VertexStepMode, COPY_BUFFER_ALIGNMENT,
46+
COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT,
47+
QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE, VERTEX_STRIDE_ALIGNMENT,
4848
};
4949

5050
// wasm-only types, we try to keep as many types non-platform

wgpu/tests/3x3_colors.png

105 Bytes
Loading

wgpu/tests/external_texture.rs

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
#![cfg(all(target_arch = "wasm32", not(features = "emscripten")))]
2+
3+
use std::num::NonZeroU32;
4+
5+
use crate::common::{fail_if, initialize_test, TestParameters};
6+
use wasm_bindgen::JsCast;
7+
use wasm_bindgen_test::*;
8+
9+
#[wasm_bindgen_test]
10+
async fn image_bitmap_import() {
11+
let image_encoded = include_bytes!("3x3_colors.png");
12+
13+
// Create an array-of-arrays for Blob's constructor
14+
let array = js_sys::Array::new();
15+
array.push(&js_sys::Uint8Array::from(&image_encoded[..]));
16+
17+
// We're passing an array of Uint8Arrays
18+
let blob = web_sys::Blob::new_with_u8_array_sequence(&array).unwrap();
19+
20+
// Parse the image from the blob
21+
let image_bitmap_promise = web_sys::window()
22+
.unwrap()
23+
.create_image_bitmap_with_blob(&blob)
24+
.unwrap();
25+
26+
// Wait for the parsing to be done
27+
let image_bitmap: web_sys::ImageBitmap =
28+
wasm_bindgen_futures::JsFuture::from(image_bitmap_promise)
29+
.await
30+
.unwrap()
31+
.dyn_into()
32+
.unwrap();
33+
34+
// Sanity checks
35+
assert_eq!(image_bitmap.width(), 3);
36+
assert_eq!(image_bitmap.height(), 3);
37+
38+
// Decode it cpu side
39+
let raw_image = image::load_from_memory_with_format(image_encoded, image::ImageFormat::Png)
40+
.unwrap()
41+
.into_rgba8();
42+
43+
// Set of test cases to test with image import
44+
#[derive(Debug)]
45+
enum TestCase {
46+
// Import the image as normal
47+
Normal,
48+
// Set both the input offset and output offset to 1 in x, so the first column is omitted.
49+
TrimLeft,
50+
// Set the size to 2 in x, so the last column is omitted
51+
TrimRight,
52+
// Set only the output offset to 1, so the second column gets the first column's data.
53+
SlideRight,
54+
// Try to copy from out of bounds of the source image
55+
SourceOutOfBounds,
56+
// Try to copy from out of bounds of the destination image
57+
DestOutOfBounds,
58+
// Try to copy more than one slice from the source
59+
MultiSliceCopy,
60+
// Copy into the second slice of a 2D array texture,
61+
SecondSliceCopy,
62+
}
63+
let cases = [
64+
TestCase::Normal,
65+
TestCase::TrimLeft,
66+
TestCase::TrimRight,
67+
TestCase::SlideRight,
68+
TestCase::SourceOutOfBounds,
69+
TestCase::DestOutOfBounds,
70+
TestCase::MultiSliceCopy,
71+
TestCase::SecondSliceCopy,
72+
];
73+
74+
initialize_test(TestParameters::default(), |ctx| {
75+
for case in cases {
76+
// Copy the data, so we can modify it for tests
77+
let mut raw_image = raw_image.clone();
78+
// The origin used for the external copy on the source side.
79+
let mut src_origin = wgpu::Origin2d::ZERO;
80+
// The origin used for the external copy on the destination side.
81+
let mut dest_origin = wgpu::Origin3d::ZERO;
82+
// The layer the external image's data should end up in.
83+
let mut dest_data_layer = 0;
84+
// Size of the external copy
85+
let mut copy_size = wgpu::Extent3d {
86+
width: 3,
87+
height: 3,
88+
depth_or_array_layers: 1,
89+
};
90+
// Width of the destination texture
91+
let mut dest_width = 3;
92+
// Layer count of the destination texture
93+
let mut dest_layers = 1;
94+
95+
// If the test is suppoed to be valid call to copyExternal.
96+
let mut valid = true;
97+
// If the result is incorrect
98+
let mut correct = true;
99+
match case {
100+
TestCase::Normal => {}
101+
TestCase::TrimLeft => {
102+
valid = ctx
103+
.adapter_downlevel_capabilities
104+
.flags
105+
.contains(wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES);
106+
src_origin.x = 1;
107+
dest_origin.x = 1;
108+
copy_size.width = 2;
109+
for y in 0..3 {
110+
raw_image[(0, y)].0 = [0; 4];
111+
}
112+
}
113+
TestCase::TrimRight => {
114+
copy_size.width = 2;
115+
for y in 0..3 {
116+
raw_image[(2, y)].0 = [0; 4];
117+
}
118+
}
119+
TestCase::SlideRight => {
120+
dest_origin.x = 1;
121+
copy_size.width = 2;
122+
for x in (1..3).rev() {
123+
for y in 0..3 {
124+
raw_image[(x, y)].0 = raw_image[(x - 1, y)].0;
125+
}
126+
}
127+
for y in 0..3 {
128+
raw_image[(0, y)].0 = [0; 4];
129+
}
130+
}
131+
TestCase::SourceOutOfBounds => {
132+
valid = false;
133+
// It's now in bounds for the destination
134+
dest_width = 4;
135+
copy_size.width = 4;
136+
}
137+
TestCase::DestOutOfBounds => {
138+
valid = false;
139+
// It's now out bounds for the destination
140+
dest_width = 2;
141+
}
142+
TestCase::MultiSliceCopy => {
143+
valid = false;
144+
copy_size.depth_or_array_layers = 2;
145+
dest_layers = 2;
146+
}
147+
TestCase::SecondSliceCopy => {
148+
correct = false; // TODO: what?
149+
dest_origin.z = 1;
150+
dest_data_layer = 1;
151+
dest_layers = 2;
152+
}
153+
}
154+
155+
let texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
156+
label: Some("import dest"),
157+
size: wgpu::Extent3d {
158+
width: dest_width,
159+
height: 3,
160+
depth_or_array_layers: dest_layers,
161+
},
162+
mip_level_count: 1,
163+
sample_count: 1,
164+
dimension: wgpu::TextureDimension::D2,
165+
format: wgpu::TextureFormat::Rgba8UnormSrgb,
166+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
167+
| wgpu::TextureUsages::COPY_DST
168+
| wgpu::TextureUsages::COPY_SRC,
169+
});
170+
171+
fail_if(&ctx.device, !valid, || {
172+
ctx.queue.copy_external_image_to_texture(
173+
&wgpu::ImageCopyExternalImage {
174+
source: wgpu::ExternalImageSource::ImageBitmap(image_bitmap.clone()),
175+
origin: src_origin,
176+
flip_y: false,
177+
},
178+
wgpu::ImageCopyTextureTagged {
179+
texture: &texture,
180+
mip_level: 0,
181+
origin: dest_origin,
182+
aspect: wgpu::TextureAspect::All,
183+
color_space: wgpu::PredefinedColorSpace::Srgb,
184+
premultiplied_alpha: false,
185+
},
186+
copy_size,
187+
);
188+
});
189+
190+
let readback_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
191+
label: Some("readback buffer"),
192+
size: 4 * 64 * 3,
193+
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
194+
mapped_at_creation: false,
195+
});
196+
197+
let mut encoder = ctx
198+
.device
199+
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
200+
encoder.copy_texture_to_buffer(
201+
wgpu::ImageCopyTexture {
202+
texture: &texture,
203+
mip_level: 0,
204+
origin: wgpu::Origin3d {
205+
x: 0,
206+
y: 0,
207+
z: dest_data_layer,
208+
},
209+
aspect: wgpu::TextureAspect::All,
210+
},
211+
wgpu::ImageCopyBuffer {
212+
buffer: &readback_buffer,
213+
layout: wgpu::ImageDataLayout {
214+
offset: 0,
215+
bytes_per_row: Some(NonZeroU32::new(256).unwrap()),
216+
rows_per_image: None,
217+
},
218+
},
219+
wgpu::Extent3d {
220+
width: dest_width,
221+
height: 3,
222+
depth_or_array_layers: 1,
223+
},
224+
);
225+
226+
ctx.queue.submit(Some(encoder.finish()));
227+
readback_buffer
228+
.slice(..)
229+
.map_async(wgpu::MapMode::Read, |_| ());
230+
ctx.device.poll(wgpu::Maintain::Wait);
231+
232+
let buffer = readback_buffer.slice(..).get_mapped_range();
233+
234+
// 64 because of 256 byte alignment / 4.
235+
let gpu_image = image::RgbaImage::from_vec(64, 3, buffer.to_vec()).unwrap();
236+
let gpu_image_cropped = image::imageops::crop_imm(&gpu_image, 0, 0, 3, 3).to_image();
237+
238+
if valid && correct {
239+
assert_eq!(raw_image, gpu_image_cropped, "Failed on test case {case:?}");
240+
} else {
241+
assert_ne!(raw_image, gpu_image_cropped, "Failed on test case {case:?}");
242+
}
243+
}
244+
})
245+
}

wgpu/tests/root.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod clear_texture;
1010
mod device;
1111
mod encoder;
1212
mod example_wgsl;
13+
mod external_texture;
1314
mod instance;
1415
mod poll;
1516
mod queue_transfer;

0 commit comments

Comments
 (0)