|
| 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 | +} |
0 commit comments