Implement queue.copy_external_image_to_texture for WebGL2 and improve WebGPU Impl (#3288)

Co-authored-by: Teodor Tanasoaia <28601907+teoxoy@users.noreply.github.com>
Closes https://github.com/gfx-rs/wgpu/issues/1888
This commit is contained in:
Connor Fitzgerald 2023-01-24 13:44:15 -05:00 committed by GitHub
parent 964c94a02d
commit 95a760bb42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1220 additions and 63 deletions

View File

@ -103,6 +103,10 @@ Additionally `Surface::get_default_config` now returns an Option and returns Non
`Instance::create_surface()` now returns `Result<Surface, CreateSurfaceError>` instead of `Surface`. This allows an error to be returned instead of panicking if the given window is a HTML canvas and obtaining a WebGPU or WebGL 2 context fails. (No other platforms currently report any errors through this path.) By @kpreid in [#3052](https://github.com/gfx-rs/wgpu/pull/3052/)
#### `Queue::copy_external_image_to_texture` on WebAssembly
There's a new api `Queue::copy_external_image_to_texture` which allows you to create wgpu textures from various web image primitives. Specificically from HtmlVideoElement, HtmlCanvasElement, OffscreenCanvas, and ImageBitmap. This provides multiple low-copy ways of interacting with the browser. WebGL is also supported, though WebGL has some additional restrictions, represented by the UNRESTRICTED_EXTERNAL_IMAGE_COPIES downlevel flag. By @cwfitzgerald in [#3288](https://github.com/gfx-rs/wgpu/pull/3288)
#### Instance creation now takes `InstanceDescriptor` instead of `Backends`
`Instance::new()` and `hub::Global::new()` now take an `InstanceDescriptor` struct which cointains both the existing `Backends` selection as well as a new `Dx12Compiler` field for selecting which Dx12 shader compiler to use.
@ -300,14 +304,12 @@ let texture = device.create_texture(&wgpu::TextureDescriptor {
- Don't use a pointer to a local copy of a `PhysicalDeviceDriverProperties` struct after it has gone out of scope. In fact, don't make a local copy at all. Introduce a helper function for building `CStr`s from C character arrays, and remove some `unsafe` blocks. By @jimblandy in [#3076](https://github.com/gfx-rs/wgpu/pull/3076).
## wgpu-0.14.2 (2022-11-28)
### Bug Fixes
- Fix incorrect offset in `get_mapped_range` by @nical in [#3233](https://github.com/gfx-rs/wgpu/pull/3233)
## wgpu-0.14.1 (2022-11-02)
### Bug Fixes

48
Cargo.lock generated
View File

@ -340,6 +340,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "com-rs"
version = "0.2.1"
@ -1257,6 +1263,20 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
[[package]]
name = "image"
version = "0.24.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"num-rational",
"num-traits 0.2.15",
"png",
]
[[package]]
name = "indexmap"
version = "1.9.2"
@ -1566,6 +1586,27 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits 0.2.15",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-integer",
"num-traits 0.2.15",
]
[[package]]
name = "num-traits"
version = "0.1.43"
@ -2563,9 +2604,9 @@ dependencies = [
[[package]]
name = "v8"
version = "0.60.0"
version = "0.60.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5867543c19b87c45ed3f2bc49eb6135474ed6a1803cac40c278620b53e9865ef"
checksum = "07fd5b3ed559897ff02c0f62bc0a5f300bfe79bb4c77a50031b8df771701c628"
dependencies = [
"bitflags",
"fslock",
@ -2925,6 +2966,7 @@ dependencies = [
"env_logger",
"futures-intrusive",
"glam",
"image",
"js-sys",
"log",
"naga",
@ -3028,8 +3070,10 @@ name = "wgpu-types"
version = "0.14.0"
dependencies = [
"bitflags",
"js-sys",
"serde",
"serde_json",
"web-sys",
]
[[package]]

View File

@ -57,6 +57,7 @@ env_logger = "0.9"
futures-intrusive = "0.4"
fxhash = "0.2.1"
glam = "0.21.3"
image = { version = "0.24", default-features = false, features = ["png"] }
libloading = "0.7"
libc = "0.2"
log = "0.4"

View File

@ -24,6 +24,7 @@ use std::iter;
pub type ImageCopyBuffer = wgt::ImageCopyBuffer<BufferId>;
pub type ImageCopyTexture = wgt::ImageCopyTexture<TextureId>;
pub type ImageCopyTextureTagged = wgt::ImageCopyTextureTagged<TextureId>;
#[derive(Clone, Copy, Debug)]
pub enum CopySide {
@ -44,6 +45,8 @@ pub enum TransferError {
MissingCopySrcUsageFlag,
#[error("destination buffer/texture is missing the `COPY_DST` usage flag")]
MissingCopyDstUsageFlag(Option<BufferId>, Option<TextureId>),
#[error("destination texture is missing the `RENDER_ATTACHMENT` usage flag")]
MissingRenderAttachmentUsageFlag(TextureId),
#[error("copy of {start_offset}..{end_offset} would end up overrunning the bounds of the {side:?} buffer of size {buffer_size}")]
BufferOverrun {
start_offset: BufferAddress,
@ -66,6 +69,8 @@ pub enum TransferError {
},
#[error("unable to select texture mip level {level} out of {total}")]
InvalidTextureMipLevel { level: u32, total: u32 },
#[error("texture dimension must be 2D when copying from an external texture")]
InvalidDimensionExternal(TextureId),
#[error("buffer offset {0} is not aligned to block size or `COPY_BUFFER_ALIGNMENT`")]
UnalignedBufferOffset(BufferAddress),
#[error("copy size {0} does not respect `COPY_BUFFER_ALIGNMENT`")]
@ -102,6 +107,10 @@ pub enum TransferError {
format: wgt::TextureFormat,
aspect: wgt::TextureAspect,
},
#[error(
"copying to textures with format {0:?} is forbidden when copying from external texture"
)]
ExternalCopyToForbiddenTextureFormat(wgt::TextureFormat),
#[error("the entire texture must be copied when copying from depth texture")]
InvalidDepthTextureExtent,
#[error(
@ -701,8 +710,8 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
#[cfg(feature = "trace")]
if let Some(ref mut list) = cmd_buf.commands {
list.push(TraceCommand::CopyBufferToTexture {
src: source.clone(),
dst: destination.clone(),
src: *source,
dst: *destination,
size: *copy_size,
});
}
@ -837,8 +846,8 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
#[cfg(feature = "trace")]
if let Some(ref mut list) = cmd_buf.commands {
list.push(TraceCommand::CopyTextureToBuffer {
src: source.clone(),
dst: destination.clone(),
src: *source,
dst: *destination,
size: *copy_size,
});
}
@ -1002,8 +1011,8 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
#[cfg(feature = "trace")]
if let Some(ref mut list) = cmd_buf.commands {
list.push(TraceCommand::CopyTextureToTexture {
src: source.clone(),
dst: destination.clone(),
src: *source,
dst: *destination,
size: *copy_size,
});
}

View File

@ -33,6 +33,30 @@ pub fn is_valid_copy_dst_texture_format(
}
}
#[cfg_attr(
any(not(target_arch = "wasm32"), feature = "emscripten"),
allow(unused)
)]
pub fn is_valid_external_image_copy_dst_texture_format(format: wgt::TextureFormat) -> bool {
use wgt::TextureFormat as Tf;
match format {
Tf::R8Unorm
| Tf::R16Float
| Tf::R32Float
| Tf::Rg8Unorm
| Tf::Rg16Float
| Tf::Rg32Float
| Tf::Rgba8Unorm
| Tf::Rgba8UnormSrgb
| Tf::Bgra8Unorm
| Tf::Bgra8UnormSrgb
| Tf::Rgb10a2Unorm
| Tf::Rgba16Float
| Tf::Rgba32Float => true,
_ => false,
}
}
pub fn map_buffer_usage(usage: wgt::BufferUsages) -> hal::BufferUses {
let mut u = hal::BufferUses::empty();
u.set(

View File

@ -585,7 +585,7 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let mut trace = trace.lock();
let data_path = trace.make_binary("bin", data);
trace.add(Action::WriteTexture {
to: destination.clone(),
to: *destination,
data: data_path,
layout: *data_layout,
size: *size,
@ -663,12 +663,6 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
(size.depth_or_array_layers - 1) * block_rows_per_image + height_blocks;
let stage_size = stage_bytes_per_row as u64 * block_rows_in_copy as u64;
if !dst.desc.usage.contains(wgt::TextureUsages::COPY_DST) {
return Err(
TransferError::MissingCopyDstUsageFlag(None, Some(destination.texture)).into(),
);
}
let mut trackers = device.trackers.lock();
let encoder = device.pending_writes.activate();
@ -820,6 +814,208 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
Ok(())
}
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
pub fn queue_copy_external_image_to_texture<A: HalApi>(
&self,
queue_id: id::QueueId,
source: &wgt::ImageCopyExternalImage,
destination: crate::command::ImageCopyTextureTagged,
size: wgt::Extent3d,
) -> Result<(), QueueWriteError> {
profiling::scope!("Queue::copy_external_image_to_texture");
let hub = A::hub(self);
let mut token = Token::root();
let (mut device_guard, mut token) = hub.devices.write(&mut token);
let device = device_guard
.get_mut(queue_id)
.map_err(|_| DeviceError::Invalid)?;
if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 {
log::trace!("Ignoring write_texture of size 0");
return Ok(());
}
let mut needs_flag = false;
needs_flag |= matches!(source.source, wgt::ExternalImageSource::OffscreenCanvas(_));
needs_flag |= source.origin != wgt::Origin2d::ZERO;
needs_flag |= destination.color_space != wgt::PredefinedColorSpace::Srgb;
#[allow(clippy::bool_comparison)]
if matches!(source.source, wgt::ExternalImageSource::ImageBitmap(_)) {
needs_flag |= source.flip_y != false;
needs_flag |= destination.premultiplied_alpha != false;
}
if needs_flag {
device
.require_downlevel_flags(wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES)
.map_err(TransferError::from)?;
}
let src_width = source.source.width();
let src_height = source.source.height();
let (mut texture_guard, _) = hub.textures.write(&mut token); // For clear we need write access to the texture. TODO: Can we acquire write lock later?
let dst = texture_guard.get_mut(destination.texture).unwrap();
let (selector, dst_base, _) =
extract_texture_selector(&destination.to_untagged(), &size, dst)?;
if !conv::is_valid_external_image_copy_dst_texture_format(dst.desc.format) {
return Err(
TransferError::ExternalCopyToForbiddenTextureFormat(dst.desc.format).into(),
);
}
if dst.desc.dimension != wgt::TextureDimension::D2 {
return Err(TransferError::InvalidDimensionExternal(destination.texture).into());
}
if !dst.desc.usage.contains(wgt::TextureUsages::COPY_DST) {
return Err(
TransferError::MissingCopyDstUsageFlag(None, Some(destination.texture)).into(),
);
}
if !dst
.desc
.usage
.contains(wgt::TextureUsages::RENDER_ATTACHMENT)
{
return Err(
TransferError::MissingRenderAttachmentUsageFlag(destination.texture).into(),
);
}
if dst.desc.sample_count != 1 {
return Err(TransferError::InvalidSampleCount {
sample_count: dst.desc.sample_count,
}
.into());
}
if source.origin.x + size.width > src_width {
return Err(TransferError::TextureOverrun {
start_offset: source.origin.x,
end_offset: source.origin.x + size.width,
texture_size: src_width,
dimension: crate::resource::TextureErrorDimension::X,
side: CopySide::Source,
}
.into());
}
if source.origin.y + size.height > src_height {
return Err(TransferError::TextureOverrun {
start_offset: source.origin.y,
end_offset: source.origin.y + size.height,
texture_size: src_height,
dimension: crate::resource::TextureErrorDimension::Y,
side: CopySide::Source,
}
.into());
}
if size.depth_or_array_layers != 1 {
return Err(TransferError::TextureOverrun {
start_offset: 0,
end_offset: size.depth_or_array_layers,
texture_size: 1,
dimension: crate::resource::TextureErrorDimension::Z,
side: CopySide::Source,
}
.into());
}
// Note: Doing the copy range validation early is important because ensures that the
// dimensions are not going to cause overflow in other parts of the validation.
let (hal_copy_size, _) = validate_texture_copy_range(
&destination.to_untagged(),
&dst.desc,
CopySide::Destination,
&size,
)?;
let mut trackers = device.trackers.lock();
let encoder = device.pending_writes.activate();
// If the copy does not fully cover the layers, we need to initialize to
// zero *first* as we don't keep track of partial texture layer inits.
//
// Strictly speaking we only need to clear the areas of a layer
// untouched, but this would get increasingly messy.
let init_layer_range = if dst.desc.dimension == wgt::TextureDimension::D3 {
// volume textures don't have a layer range as array volumes aren't supported
0..1
} else {
destination.origin.z..destination.origin.z + size.depth_or_array_layers
};
if dst.initialization_status.mips[destination.mip_level as usize]
.check(init_layer_range.clone())
.is_some()
{
if has_copy_partial_init_tracker_coverage(&size, destination.mip_level, &dst.desc) {
for layer_range in dst.initialization_status.mips[destination.mip_level as usize]
.drain(init_layer_range)
.collect::<Vec<std::ops::Range<u32>>>()
{
crate::command::clear_texture(
&*texture_guard,
id::Valid(destination.texture),
TextureInitRange {
mip_range: destination.mip_level..(destination.mip_level + 1),
layer_range,
},
encoder,
&mut trackers.textures,
&device.alignments,
&device.zero_buffer,
)
.map_err(QueueWriteError::from)?;
}
} else {
dst.initialization_status.mips[destination.mip_level as usize]
.drain(init_layer_range);
}
}
let dst = texture_guard.get(destination.texture).unwrap();
let transitions = trackers
.textures
.set_single(
dst,
destination.texture,
selector,
hal::TextureUses::COPY_DST,
)
.ok_or(TransferError::InvalidTexture(destination.texture))?;
dst.life_guard.use_at(device.active_submission_index + 1);
let dst_raw = dst
.inner
.as_raw()
.ok_or(TransferError::InvalidTexture(destination.texture))?;
let regions = hal::TextureCopy {
src_base: hal::TextureCopyBase {
mip_level: 0,
array_layer: 0,
origin: source.origin.to_3d(0),
aspect: hal::FormatAspects::COLOR,
},
dst_base,
size: hal_copy_size,
};
unsafe {
encoder.transition_textures(transitions.map(|pending| pending.into_hal(dst)));
encoder.copy_external_image_to_texture(
source,
dst_raw,
destination.premultiplied_alpha,
iter::once(regions),
);
}
Ok(())
}
pub fn queue_submit<A: HalApi>(
&self,
queue_id: id::QueueId,

View File

@ -97,7 +97,8 @@ impl super::Adapter {
| wgt::Features::ADDRESS_MODE_CLAMP_TO_ZERO;
let mut downlevel = wgt::DownlevelFlags::BASE_VERTEX
| wgt::DownlevelFlags::READ_ONLY_DEPTH_STENCIL
| wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER;
| wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER
| wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES;
// Features from queries
downlevel.set(

View File

@ -264,6 +264,18 @@ impl crate::CommandEncoder<Api> for Encoder {
unsafe fn copy_buffer_to_buffer<T>(&mut self, src: &Resource, dst: &Resource, regions: T) {}
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
unsafe fn copy_external_image_to_texture<T>(
&mut self,
src: &wgt::ImageCopyExternalImage,
dst: &Resource,
dst_premultiplication: bool,
regions: T,
) where
T: Iterator<Item = crate::TextureCopy>,
{
}
unsafe fn copy_texture_to_texture<T>(
&mut self,
src: &Resource,

View File

@ -317,6 +317,10 @@ impl super::Adapter {
wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER,
!cfg!(target_arch = "wasm32"),
);
downlevel_flags.set(
wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES,
!cfg!(target_arch = "wasm32"),
);
downlevel_flags.set(
wgt::DownlevelFlags::FULL_DRAW_INDEX_UINT32,
max_element_index == u32::MAX,

View File

@ -309,6 +309,31 @@ impl crate::CommandEncoder<super::Api> for super::CommandEncoder {
}
}
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
unsafe fn copy_external_image_to_texture<T>(
&mut self,
src: &wgt::ImageCopyExternalImage,
dst: &super::Texture,
dst_premultiplication: bool,
regions: T,
) where
T: Iterator<Item = crate::TextureCopy>,
{
let (dst_raw, dst_target) = dst.inner.as_native();
for copy in regions {
self.cmd_buffer
.commands
.push(C::CopyExternalImageToTexture {
src: src.clone(),
dst: dst_raw,
dst_target,
dst_format: dst.format,
dst_premultiplication,
copy,
})
}
}
unsafe fn copy_texture_to_texture<T>(
&mut self,
src: &super::Texture,

View File

@ -684,6 +684,15 @@ enum Command {
dst_target: BindTarget,
copy: crate::BufferCopy,
},
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
CopyExternalImageToTexture {
src: wgt::ImageCopyExternalImage,
dst: glow::Texture,
dst_target: BindTarget,
dst_format: wgt::TextureFormat,
dst_premultiplication: bool,
copy: crate::TextureCopy,
},
CopyTextureToTexture {
src: glow::Texture,
src_target: BindTarget,

View File

@ -375,6 +375,134 @@ impl super::Queue {
unsafe { gl.bind_buffer(copy_dst_target, None) };
}
}
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
C::CopyExternalImageToTexture {
ref src,
dst,
dst_target,
dst_format,
dst_premultiplication,
ref copy,
} => {
const UNPACK_FLIP_Y_WEBGL: u32 =
web_sys::WebGl2RenderingContext::UNPACK_FLIP_Y_WEBGL;
const UNPACK_PREMULTIPLY_ALPHA_WEBGL: u32 =
web_sys::WebGl2RenderingContext::UNPACK_PREMULTIPLY_ALPHA_WEBGL;
unsafe {
if src.flip_y {
gl.pixel_store_bool(UNPACK_FLIP_Y_WEBGL, true);
}
if dst_premultiplication {
gl.pixel_store_bool(UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
}
}
unsafe { gl.bind_texture(dst_target, Some(dst)) };
let format_desc = self.shared.describe_texture_format(dst_format);
if is_layered_target(dst_target) {
match src.source {
wgt::ExternalImageSource::ImageBitmap(ref b) => unsafe {
gl.tex_sub_image_3d_with_image_bitmap(
dst_target,
copy.dst_base.mip_level as i32,
copy.dst_base.origin.x as i32,
copy.dst_base.origin.y as i32,
copy.dst_base.origin.z as i32,
copy.size.width as i32,
copy.size.height as i32,
copy.size.depth as i32,
format_desc.external,
format_desc.data_type,
b,
);
},
wgt::ExternalImageSource::HTMLVideoElement(ref v) => unsafe {
gl.tex_sub_image_3d_with_html_video_element(
dst_target,
copy.dst_base.mip_level as i32,
copy.dst_base.origin.x as i32,
copy.dst_base.origin.y as i32,
copy.dst_base.origin.z as i32,
copy.size.width as i32,
copy.size.height as i32,
copy.size.depth as i32,
format_desc.external,
format_desc.data_type,
v,
);
},
wgt::ExternalImageSource::HTMLCanvasElement(ref c) => unsafe {
gl.tex_sub_image_3d_with_html_canvas_element(
dst_target,
copy.dst_base.mip_level as i32,
copy.dst_base.origin.x as i32,
copy.dst_base.origin.y as i32,
copy.dst_base.origin.z as i32,
copy.size.width as i32,
copy.size.height as i32,
copy.size.depth as i32,
format_desc.external,
format_desc.data_type,
c,
);
},
wgt::ExternalImageSource::OffscreenCanvas(_) => unreachable!(),
}
} else {
match src.source {
wgt::ExternalImageSource::ImageBitmap(ref b) => unsafe {
gl.tex_sub_image_2d_with_image_bitmap_and_width_and_height(
dst_target,
copy.dst_base.mip_level as i32,
copy.dst_base.origin.x as i32,
copy.dst_base.origin.y as i32,
copy.size.width as i32,
copy.size.height as i32,
format_desc.external,
format_desc.data_type,
b,
);
},
wgt::ExternalImageSource::HTMLVideoElement(ref v) => unsafe {
gl.tex_sub_image_2d_with_html_video_and_width_and_height(
dst_target,
copy.dst_base.mip_level as i32,
copy.dst_base.origin.x as i32,
copy.dst_base.origin.y as i32,
copy.size.width as i32,
copy.size.height as i32,
format_desc.external,
format_desc.data_type,
v,
)
},
wgt::ExternalImageSource::HTMLCanvasElement(ref c) => unsafe {
gl.tex_sub_image_2d_with_html_canvas_and_width_and_height(
dst_target,
copy.dst_base.mip_level as i32,
copy.dst_base.origin.x as i32,
copy.dst_base.origin.y as i32,
copy.size.width as i32,
copy.size.height as i32,
format_desc.external,
format_desc.data_type,
c,
)
},
wgt::ExternalImageSource::OffscreenCanvas(_) => unreachable!(),
}
}
unsafe {
if src.flip_y {
gl.pixel_store_bool(UNPACK_FLIP_Y_WEBGL, false);
}
if dst_premultiplication {
gl.pixel_store_bool(UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
}
}
}
C::CopyTextureToTexture {
src,
src_target,

View File

@ -395,6 +395,20 @@ pub trait CommandEncoder<A: Api>: Send + Sync + fmt::Debug {
where
T: Iterator<Item = BufferCopy>;
/// Copy from an external image to an internal texture.
/// Works with a single array layer.
/// Note: `dst` current usage has to be `TextureUses::COPY_DST`.
/// Note: the copy extent is in physical size (rounded to the block size)
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
unsafe fn copy_external_image_to_texture<T>(
&mut self,
src: &wgt::ImageCopyExternalImage,
dst: &A::Texture,
dst_premultiplication: bool,
regions: T,
) where
T: Iterator<Item = TextureCopy>;
/// Copy from one texture to another.
/// Works with a single array layer.
/// Note: `dst` current usage has to be `TextureUses::COPY_DST`.

View File

@ -319,7 +319,8 @@ impl PhysicalDeviceFeatures {
| Df::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED
| Df::UNRESTRICTED_INDEX_BUFFER
| Df::INDIRECT_EXECUTION
| Df::VIEW_FORMATS;
| Df::VIEW_FORMATS
| Df::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES;
dl_flags.set(Df::CUBE_ARRAY_TEXTURES, self.core.image_cube_array != 0);
dl_flags.set(Df::ANISOTROPIC_FILTERING, self.core.sampler_anisotropy != 0);

View File

@ -225,7 +225,7 @@ mod inner {
let bit = wgpu::DownlevelFlags::from_bits(1 << i as u64);
if let Some(bit) = bit {
if wgpu::DownlevelFlags::all().contains(bit) {
println!("\t\t{:>36} {}", format!("{:?}:", bit), flags.contains(bit));
println!("\t\t{:>37} {}", format!("{:?}:", bit), flags.contains(bit));
}
}
}

View File

@ -24,6 +24,15 @@ strict_asserts = []
bitflags = "1"
serde = { version = "1", features = ["serde_derive"], optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys.workspace = true
web-sys = { workspace = true, features = [
"ImageBitmap",
"HtmlVideoElement",
"HtmlCanvasElement",
"OffscreenCanvas",
] }
[dev-dependencies]
serde = { version = "1", features = ["serde_derive"] }
serde_json = "1.0.85"

View File

@ -1129,7 +1129,7 @@ bitflags::bitflags! {
/// Supports buffers to combine [`BufferUsages::INDEX`] with usages other than [`BufferUsages::COPY_DST`] and [`BufferUsages::COPY_SRC`].
/// Furthermore, in absence of this feature it is not allowed to copy index buffers from/to buffers with a set of usage flags containing
/// [`BufferUsages::VERTEX`]/[`BufferUsages::UNIFORM`]/[`BufferUsages::STORAGE`] or [`BufferUsages::INDIRECT`].
/// [`BufferUsages::VERTEX`]/[`BufferUsages::UNIFORM`]/[`BufferUsages::STORAGE`] or [`BufferUsages::INDIRECT`].
///
/// WebGL doesn't support this.
const UNRESTRICTED_INDEX_BUFFER = 1 << 16;
@ -1148,6 +1148,17 @@ bitflags::bitflags! {
///
/// The WebGL and GLES backends doesn't support this.
const VIEW_FORMATS = 1 << 19;
/// With this feature not present, there are the following restrictions on `Queue::copy_external_image_to_texture`:
/// - The source must not be [`web_sys::OffscreenCanvas`]
/// - [`ImageCopyExternalImage::origin`] must be zero.
/// - [`ImageCopyTextureTagged::color_space`] must be srgb.
/// - If the source is an [`web_sys::ImageBitmap`]:
/// - [`ImageCopyExternalImage::flip_y`] must be false.
/// - [`ImageCopyTextureTagged::premultiplied_alpha`] must be false.
///
/// WebGL doesn't support this. WebGPU does.
const UNRESTRICTED_EXTERNAL_TEXTURE_COPIES = 1 << 20;
}
}
@ -4124,10 +4135,40 @@ pub enum TextureDimension {
D3,
}
/// Origin of a copy from a 2D image.
///
/// Corresponds to [WebGPU `GPUOrigin2D`](
/// https://gpuweb.github.io/gpuweb/#dictdef-gpuorigin2ddict).
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "trace", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Origin2d {
///
pub x: u32,
///
pub y: u32,
}
impl Origin2d {
/// Zero origin.
pub const ZERO: Self = Self { x: 0, y: 0 };
/// Adds the third dimension to this origin
pub fn to_3d(self, z: u32) -> Origin3d {
Origin3d {
x: self.x,
y: self.y,
z,
}
}
}
/// Origin of a copy to/from a texture.
///
/// Corresponds to [WebGPU `GPUOrigin3D`](
/// https://gpuweb.github.io/gpuweb/#typedefdef-gpuorigin3d).
/// https://gpuweb.github.io/gpuweb/#dictdef-gpuorigin3ddict).
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "trace", derive(Serialize))]
@ -4145,6 +4186,14 @@ pub struct Origin3d {
impl Origin3d {
/// Zero origin.
pub const ZERO: Self = Self { x: 0, y: 0, z: 0 };
/// Removes the third dimension from this origin
pub fn to_2d(self) -> Origin2d {
Origin2d {
x: self.x,
y: self.y,
}
}
}
impl Default for Origin3d {
@ -4156,7 +4205,7 @@ impl Default for Origin3d {
/// Extent of a texture related operation.
///
/// Corresponds to [WebGPU `GPUExtent3D`](
/// https://gpuweb.github.io/gpuweb/#typedefdef-gpuextent3d).
/// https://gpuweb.github.io/gpuweb/#dictdef-gpuextent3ddict).
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "trace", derive(Serialize))]
@ -5048,7 +5097,7 @@ pub struct BindGroupLayoutEntry {
/// Corresponds to [WebGPU `GPUImageCopyBuffer`](
/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopybuffer).
#[repr(C)]
#[derive(Clone, Debug)]
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "trace", derive(serde::Serialize))]
#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
pub struct ImageCopyBuffer<B> {
@ -5063,7 +5112,7 @@ pub struct ImageCopyBuffer<B> {
/// Corresponds to [WebGPU `GPUImageCopyTexture`](
/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopytexture).
#[repr(C)]
#[derive(Clone, Debug)]
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "trace", derive(serde::Serialize))]
#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
pub struct ImageCopyTexture<T> {
@ -5071,7 +5120,9 @@ pub struct ImageCopyTexture<T> {
pub texture: T,
/// The target mip level of the texture.
pub mip_level: u32,
/// The base texel of the texture in the selected `mip_level`.
/// The base texel of the texture in the selected `mip_level`. Together
/// with the `copy_size` argument to copy functions, defines the
/// sub-region of the texture to copy.
#[cfg_attr(any(feature = "trace", feature = "replay"), serde(default))]
pub origin: Origin3d,
/// The copy aspect.
@ -5079,6 +5130,159 @@ pub struct ImageCopyTexture<T> {
pub aspect: TextureAspect,
}
impl<T> ImageCopyTexture<T> {
/// Adds color space and premultiplied alpha information to make this
/// descriptor tagged.
pub fn to_tagged(
self,
color_space: PredefinedColorSpace,
premultiplied_alpha: bool,
) -> ImageCopyTextureTagged<T> {
ImageCopyTextureTagged {
texture: self.texture,
mip_level: self.mip_level,
origin: self.origin,
aspect: self.aspect,
color_space,
premultiplied_alpha,
}
}
}
/// View of an external texture that cna be used to copy to a texture.
///
/// Corresponds to [WebGPU `GPUImageCopyExternalImage`](
/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopyexternalimage).
#[cfg(target_arch = "wasm32")]
#[derive(Clone, Debug)]
pub struct ImageCopyExternalImage {
/// The texture to be copied from. The copy source data is captured at the moment
/// the copy is issued.
pub source: ExternalImageSource,
/// The base texel used for copying from the external image. Together
/// with the `copy_size` argument to copy functions, defines the
/// sub-region of the image to copy.
///
/// Relative to the top left of the image.
///
/// Must be [`Origin2d::ZERO`] if [`DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES`] is not supported.
pub origin: Origin2d,
/// If the Y coordinate of the image should be flipped. Even if this is
/// true, `origin` is still relative to the top left.
pub flip_y: bool,
}
/// Source of an external texture copy.
///
/// Corresponds to the [implicit union type on WebGPU `GPUImageCopyExternalImage.source`](
/// https://gpuweb.github.io/gpuweb/#dom-gpuimagecopyexternalimage-source).
#[cfg(target_arch = "wasm32")]
#[derive(Clone, Debug)]
pub enum ExternalImageSource {
/// Copy from a previously-decoded image bitmap.
ImageBitmap(web_sys::ImageBitmap),
/// Copy from a current frame of a video element.
HTMLVideoElement(web_sys::HtmlVideoElement),
/// Copy from a on-screen canvas.
HTMLCanvasElement(web_sys::HtmlCanvasElement),
/// Copy from a off-screen canvas.
///
/// Requies [`DownlevelFlags::EXTERNAL_TEXTURE_OFFSCREEN_CANVAS`]
OffscreenCanvas(web_sys::OffscreenCanvas),
}
#[cfg(target_arch = "wasm32")]
impl ExternalImageSource {
/// Gets the pixel, not css, width of the source.
pub fn width(&self) -> u32 {
match self {
ExternalImageSource::ImageBitmap(b) => b.width(),
ExternalImageSource::HTMLVideoElement(v) => v.video_width(),
ExternalImageSource::HTMLCanvasElement(c) => c.width(),
ExternalImageSource::OffscreenCanvas(c) => c.width(),
}
}
/// Gets the pixel, not css, height of the source.
pub fn height(&self) -> u32 {
match self {
ExternalImageSource::ImageBitmap(b) => b.height(),
ExternalImageSource::HTMLVideoElement(v) => v.video_height(),
ExternalImageSource::HTMLCanvasElement(c) => c.height(),
ExternalImageSource::OffscreenCanvas(c) => c.height(),
}
}
}
#[cfg(target_arch = "wasm32")]
impl std::ops::Deref for ExternalImageSource {
type Target = js_sys::Object;
fn deref(&self) -> &Self::Target {
match self {
Self::ImageBitmap(b) => b,
Self::HTMLVideoElement(v) => v,
Self::HTMLCanvasElement(c) => c,
Self::OffscreenCanvas(c) => c,
}
}
}
#[cfg(target_arch = "wasm32")]
unsafe impl Send for ExternalImageSource {}
#[cfg(target_arch = "wasm32")]
unsafe impl Sync for ExternalImageSource {}
/// Color spaces supported on the web.
///
/// Corresponds to [HTML Canvas `PredefinedColorSpace`](
/// https://html.spec.whatwg.org/multipage/canvas.html#predefinedcolorspace).
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "trace", derive(serde::Serialize))]
#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum PredefinedColorSpace {
/// sRGB color space
Srgb,
/// Display-P3 color space
DisplayP3,
}
/// View of a texture which can be used to copy to a texture, including
/// color space and alpha premultiplication information.
///
/// Corresponds to [WebGPU `GPUImageCopyTextureTagged`](
/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopytexturetagged).
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "trace", derive(serde::Serialize))]
#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
pub struct ImageCopyTextureTagged<T> {
/// The texture to be copied to/from.
pub texture: T,
/// The target mip level of the texture.
pub mip_level: u32,
/// The base texel of the texture in the selected `mip_level`.
pub origin: Origin3d,
/// The copy aspect.
pub aspect: TextureAspect,
/// The color space of this texture.
pub color_space: PredefinedColorSpace,
/// The premultiplication of this texture
pub premultiplied_alpha: bool,
}
impl<T: Copy> ImageCopyTextureTagged<T> {
/// Removes the colorspace information from the type.
pub fn to_untagged(self) -> ImageCopyTexture<T> {
ImageCopyTexture {
texture: self.texture,
mip_level: self.mip_level,
origin: self.origin,
aspect: self.aspect,
}
}
}
/// Subresource range within an image
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]

View File

@ -165,6 +165,7 @@ glam.workspace = true
ddsfile.workspace = true
futures-intrusive.workspace = true
env_logger.workspace = true
image.workspace = true
log.workspace = true
noise = { workspace = true }
obj.workspace = true
@ -241,6 +242,7 @@ web-sys = { workspace = true, features = [
"GpuDeviceLostReason",
"GpuError",
"GpuErrorFilter",
# "GpuExtent2dDict", Not yet implemented in web_sys
"GpuExtent3dDict",
"GpuFeatureName",
"GpuFilterMode",
@ -337,5 +339,6 @@ web-sys = { workspace = true, features = [
"RequestMode",
"Request",
"Response",
"WebGl2RenderingContext"
"WebGl2RenderingContext",
"CanvasRenderingContext2d"
] }

View File

@ -352,6 +352,23 @@ fn map_texture_copy_view(view: crate::ImageCopyTexture) -> wgc::command::ImageCo
}
}
#[cfg_attr(
any(not(target_arch = "wasm32"), feature = "emscripten"),
allow(unused)
)]
fn map_texture_tagged_copy_view(
view: crate::ImageCopyTextureTagged,
) -> wgc::command::ImageCopyTextureTagged {
wgc::command::ImageCopyTextureTagged {
texture: view.texture.id.into(),
mip_level: view.mip_level,
origin: view.origin,
aspect: view.aspect,
color_space: view.color_space,
premultiplied_alpha: view.premultiplied_alpha,
}
}
fn map_pass_channel<V: Copy + Default>(
ops: Option<&Operations<V>>,
) -> wgc::command::PassChannel<V> {
@ -2193,6 +2210,31 @@ impl crate::Context for Context {
}
}
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
fn queue_copy_external_image_to_texture(
&self,
queue: &Self::QueueId,
queue_data: &Self::QueueData,
source: &wgt::ImageCopyExternalImage,
dest: crate::ImageCopyTextureTagged,
size: wgt::Extent3d,
) {
let global = &self.0;
match wgc::gfx_select!(*queue => global.queue_copy_external_image_to_texture(
*queue,
source,
map_texture_tagged_copy_view(dest),
size
)) {
Ok(()) => (),
Err(err) => self.handle_error_nolabel(
&queue_data.error_sink,
err,
"Queue::copy_external_image_to_texture",
),
}
}
fn queue_submit<I: Iterator<Item = Self::CommandBufferId>>(
&self,
queue: &Self::QueueId,

View File

@ -419,6 +419,13 @@ fn map_extent_3d(extent: wgt::Extent3d) -> web_sys::GpuExtent3dDict {
mapped
}
fn map_origin_2d(extent: wgt::Origin2d) -> web_sys::GpuOrigin2dDict {
let mut mapped = web_sys::GpuOrigin2dDict::new();
mapped.x(extent.x);
mapped.y(extent.y);
mapped
}
fn map_origin_3d(origin: wgt::Origin3d) -> web_sys::GpuOrigin3dDict {
let mut mapped = web_sys::GpuOrigin3dDict::new();
mapped.x(origin.x);
@ -471,12 +478,24 @@ fn map_texture_copy_view(view: crate::ImageCopyTexture) -> web_sys::GpuImageCopy
}
fn map_tagged_texture_copy_view(
view: crate::ImageCopyTexture,
view: crate::ImageCopyTextureTagged,
) -> web_sys::GpuImageCopyTextureTagged {
let texture = &<<Context as crate::Context>::TextureId>::from(view.texture.id).0;
let mut mapped = web_sys::GpuImageCopyTextureTagged::new(texture);
mapped.mip_level(view.mip_level);
mapped.origin(&map_origin_3d(view.origin));
mapped.aspect(map_texture_aspect(view.aspect));
// mapped.color_space(map_color_space(view.color_space));
mapped.premultiplied_alpha(view.premultiplied_alpha);
mapped
}
fn map_external_texture_copy_view(
view: &crate::ImageCopyExternalImage,
) -> web_sys::GpuImageCopyExternalImage {
let mut mapped = web_sys::GpuImageCopyExternalImage::new(&view.source);
mapped.origin(&map_origin_2d(view.origin));
mapped.flip_y(view.flip_y);
mapped
}
@ -703,22 +722,6 @@ impl Context {
Ok(create_identified(context))
}
pub fn queue_copy_external_image_to_texture(
&self,
queue: &Identified<web_sys::GpuQueue>,
image: &web_sys::ImageBitmap,
texture: crate::ImageCopyTexture,
size: wgt::Extent3d,
) {
queue
.0
.copy_external_image_to_texture_with_gpu_extent_3d_dict(
&web_sys::GpuImageCopyExternalImage::new(image),
&map_tagged_texture_copy_view(texture),
&map_extent_3d(size),
);
}
}
// Represents the global object in the JavaScript context.
@ -2398,6 +2401,24 @@ impl crate::context::Context for Context {
);
}
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
fn queue_copy_external_image_to_texture(
&self,
queue: &Self::QueueId,
_queue_data: &Self::QueueData,
source: &wgt::ImageCopyExternalImage,
dest: crate::ImageCopyTextureTagged,
size: wgt::Extent3d,
) {
queue
.0
.copy_external_image_to_texture_with_gpu_extent_3d_dict(
&map_external_texture_copy_view(source),
&map_tagged_texture_copy_view(dest),
&map_extent_3d(size),
);
}
fn queue_submit<I: Iterator<Item = Self::CommandBufferId>>(
&self,
queue: &Self::QueueId,

View File

@ -561,6 +561,15 @@ pub trait Context: Debug + Send + Sized + Sync {
data_layout: ImageDataLayout,
size: Extent3d,
);
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
fn queue_copy_external_image_to_texture(
&self,
queue: &Self::QueueId,
queue_data: &Self::QueueData,
source: &wgt::ImageCopyExternalImage,
dest: crate::ImageCopyTextureTagged,
size: wgt::Extent3d,
);
fn queue_submit<I: Iterator<Item = Self::CommandBufferId>>(
&self,
queue: &Self::QueueId,
@ -1479,6 +1488,15 @@ pub(crate) trait DynContext: Debug + Send + Sync {
data_layout: ImageDataLayout,
size: Extent3d,
);
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
fn queue_copy_external_image_to_texture(
&self,
queue: &ObjectId,
queue_data: &crate::Data,
source: &wgt::ImageCopyExternalImage,
dest: crate::ImageCopyTextureTagged,
size: wgt::Extent3d,
);
fn queue_submit<'a>(
&self,
queue: &ObjectId,
@ -2866,6 +2884,20 @@ where
Context::queue_write_texture(self, &queue, queue_data, texture, data, data_layout, size)
}
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
fn queue_copy_external_image_to_texture(
&self,
queue: &ObjectId,
queue_data: &crate::Data,
source: &wgt::ImageCopyExternalImage,
dest: crate::ImageCopyTextureTagged,
size: wgt::Extent3d,
) {
let queue = <T::QueueId>::from(*queue);
let queue_data = downcast_ref(queue_data);
Context::queue_copy_external_image_to_texture(self, &queue, queue_data, source, dest, size)
}
fn queue_submit<'a>(
&self,
queue: &ObjectId,

View File

@ -35,18 +35,26 @@ pub use wgt::{
CommandBufferDescriptor, CompareFunction, CompositeAlphaMode, DepthBiasState,
DepthStencilState, DeviceType, DownlevelCapabilities, DownlevelFlags, Dx12Compiler,
DynamicOffset, Extent3d, Face, Features, FilterMode, FrontFace, ImageDataLayout,
ImageSubresourceRange, IndexFormat, InstanceDescriptor, Limits, MultisampleState, Origin3d,
PipelineStatisticsTypes, PolygonMode, PowerPreference, PresentMode, PresentationTimestamp,
PrimitiveState, PrimitiveTopology, PushConstantRange, QueryType, RenderBundleDepthStencil,
SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel, ShaderStages,
StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, SurfaceCapabilities,
SurfaceConfiguration, SurfaceStatus, TextureAspect, TextureDimension, TextureFormat,
TextureFormatFeatureFlags, TextureFormatFeatures, TextureSampleType, TextureUsages,
TextureViewDimension, VertexAttribute, VertexFormat, VertexStepMode, COPY_BUFFER_ALIGNMENT,
COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT,
QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE, VERTEX_STRIDE_ALIGNMENT,
ImageSubresourceRange, IndexFormat, InstanceDescriptor, Limits, MultisampleState, Origin2d,
Origin3d, PipelineStatisticsTypes, PolygonMode, PowerPreference, PredefinedColorSpace,
PresentMode, PresentationTimestamp, PrimitiveState, PrimitiveTopology, PushConstantRange,
QueryType, RenderBundleDepthStencil, SamplerBindingType, SamplerBorderColor, ShaderLocation,
ShaderModel, ShaderStages, StencilFaceState, StencilOperation, StencilState,
StorageTextureAccess, SurfaceCapabilities, SurfaceConfiguration, SurfaceStatus, TextureAspect,
TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures,
TextureSampleType, TextureUsages, TextureViewDimension, VertexAttribute, VertexFormat,
VertexStepMode, COPY_BUFFER_ALIGNMENT, COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT,
PUSH_CONSTANT_ALIGNMENT, QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE,
VERTEX_STRIDE_ALIGNMENT,
};
// wasm-only types, we try to keep as many types non-platform
// specific, but these need to depend on web-sys.
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
pub use wgt::{ExternalImageSource, ImageCopyExternalImage};
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
static_assertions::assert_impl_all!(ExternalImageSource: Send, Sync);
/// Filter for error scopes.
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
pub enum ErrorFilter {
@ -60,7 +68,7 @@ static_assertions::assert_impl_all!(ErrorFilter: Send, Sync);
type C = dyn DynContext;
type Data = dyn Any + Send + Sync;
/// Context for all other wgpu objects. Instan ce of wgpu.
/// Context for all other wgpu objects. Instance of wgpu.
///
/// This is the first thing you create when using wgpu.
/// Its primary use is to create [`Adapter`]s and [`Surface`]s.
@ -1201,6 +1209,15 @@ pub use wgt::ImageCopyTexture as ImageCopyTextureBase;
pub type ImageCopyTexture<'a> = ImageCopyTextureBase<&'a Texture>;
static_assertions::assert_impl_all!(ImageCopyTexture: Send, Sync);
pub use wgt::ImageCopyTextureTagged as ImageCopyTextureTaggedBase;
/// View of a texture which can be used to copy to a texture, including
/// color space and alpha premultiplication information.
///
/// Corresponds to [WebGPU `GPUImageCopyTextureTagged`](
/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopytexturetagged).
pub type ImageCopyTextureTagged<'a> = ImageCopyTextureTaggedBase<&'a Texture>;
static_assertions::assert_impl_all!(ImageCopyTexture: Send, Sync);
/// Describes a [`BindGroupLayout`].
///
/// For use with [`Device::create_bind_group_layout`].
@ -3949,18 +3966,21 @@ impl Queue {
}
/// Schedule a copy of data from `image` into `texture`.
#[cfg(all(target_arch = "wasm32", not(feature = "webgl")))]
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
pub fn copy_external_image_to_texture(
&self,
image: &web_sys::ImageBitmap,
texture: ImageCopyTexture,
source: &wgt::ImageCopyExternalImage,
dest: ImageCopyTextureTagged,
size: Extent3d,
) {
self.context
.as_any()
.downcast_ref::<crate::backend::Context>()
.unwrap()
.queue_copy_external_image_to_texture(&self.id.into(), image, texture, size)
DynContext::queue_copy_external_image_to_texture(
&*self.context,
&self.id,
self.data.as_ref(),
source,
dest,
size,
)
}
/// Submits a series of finished command buffers for execution.

BIN
wgpu/tests/3x3_colors.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

View File

@ -0,0 +1,355 @@
#![cfg(all(target_arch = "wasm32", not(features = "emscripten")))]
use std::num::NonZeroU32;
use crate::common::{fail_if, initialize_test, TestParameters};
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;
use wgpu::ExternalImageSource;
#[wasm_bindgen_test]
async fn image_bitmap_import() {
let image_encoded = include_bytes!("3x3_colors.png");
// Create an array-of-arrays for Blob's constructor
let array = js_sys::Array::new();
array.push(&js_sys::Uint8Array::from(&image_encoded[..]));
// We're passing an array of Uint8Arrays
let blob = web_sys::Blob::new_with_u8_array_sequence(&array).unwrap();
// Parse the image from the blob
// Because we need to call the function in a way that isn't bound by
// web_sys, we need to manually construct the options struct and call
// the function.
let image_bitmap_function: js_sys::Function = web_sys::window()
.unwrap()
.get("createImageBitmap")
.unwrap()
.dyn_into()
.unwrap();
let options_arg = js_sys::Object::new();
js_sys::Reflect::set(
&options_arg,
&wasm_bindgen::JsValue::from_str("premultiplyAlpha"),
&wasm_bindgen::JsValue::from_str("none"),
)
.unwrap();
let image_bitmap_promise: js_sys::Promise = image_bitmap_function
.call2(&wasm_bindgen::JsValue::UNDEFINED, &blob, &options_arg)
.unwrap()
.dyn_into()
.unwrap();
// Wait for the parsing to be done
let image_bitmap: web_sys::ImageBitmap =
wasm_bindgen_futures::JsFuture::from(image_bitmap_promise)
.await
.unwrap()
.dyn_into()
.unwrap();
// Sanity checks
assert_eq!(image_bitmap.width(), 3);
assert_eq!(image_bitmap.height(), 3);
// Due to restrictions with premultiplication with ImageBitmaps, we also create an HtmlCanvasElement
// by drawing the image bitmap onto the canvas.
let canvas: web_sys::HtmlCanvasElement = web_sys::window()
.unwrap()
.document()
.unwrap()
.create_element("canvas")
.unwrap()
.dyn_into()
.unwrap();
canvas.set_width(3);
canvas.set_height(3);
let d2_context: web_sys::CanvasRenderingContext2d = canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into()
.unwrap();
d2_context
.draw_image_with_image_bitmap(&image_bitmap, 0.0, 0.0)
.unwrap();
// Decode it cpu side
let raw_image = image::load_from_memory_with_format(image_encoded, image::ImageFormat::Png)
.unwrap()
.into_rgba8();
// Set of test cases to test with image import
#[derive(Debug, Copy, Clone)]
enum TestCase {
// Import the image as normal
Normal,
// Sets the FlipY flag. Deals with global state on GLES, so run before other tests to ensure it's reset.
//
// Only works on canvases.
FlipY,
// Sets the premultiplied alpha flag. Deals with global state on GLES, so run before other tests to ensure it's reset.
//
// Only works on canvases.
Premultiplied,
// Sets the color space to P3.
//
// Only works on canvases.
ColorSpace,
// Sets the premultiplied alpha flag. Deals with global state on GLES, so run before other tests to ensure it's reset.
// Set both the input offset and output offset to 1 in x, so the first column is omitted.
TrimLeft,
// Set the size to 2 in x, so the last column is omitted
TrimRight,
// Set only the output offset to 1, so the second column gets the first column's data.
SlideRight,
// Try to copy from out of bounds of the source image
SourceOutOfBounds,
// Try to copy from out of bounds of the destination image
DestOutOfBounds,
// Try to copy more than one slice from the source
MultiSliceCopy,
// Copy into the second slice of a 2D array texture,
SecondSliceCopy,
}
let sources = [
ExternalImageSource::ImageBitmap(image_bitmap),
ExternalImageSource::HTMLCanvasElement(canvas),
];
let cases = [
TestCase::Normal,
TestCase::FlipY,
TestCase::Premultiplied,
TestCase::ColorSpace,
TestCase::TrimLeft,
TestCase::TrimRight,
TestCase::SlideRight,
TestCase::SourceOutOfBounds,
TestCase::DestOutOfBounds,
TestCase::MultiSliceCopy,
TestCase::SecondSliceCopy,
];
initialize_test(TestParameters::default(), |ctx| {
for source in sources {
for case in cases {
// Copy the data, so we can modify it for tests
let mut raw_image = raw_image.clone();
// The origin used for the external copy on the source side.
let mut src_origin = wgpu::Origin2d::ZERO;
// If the source should be flipped in Y
let mut src_flip_y = false;
// The origin used for the external copy on the destination side.
let mut dest_origin = wgpu::Origin3d::ZERO;
// The layer the external image's data should end up in.
let mut dest_data_layer = 0;
// Color space the destination is in.
let mut dest_color_space = wgt::PredefinedColorSpace::Srgb;
// If the destination image is premultiplied.
let mut dest_premultiplied = false;
// Size of the external copy
let mut copy_size = wgpu::Extent3d {
width: 3,
height: 3,
depth_or_array_layers: 1,
};
// Width of the destination texture
let mut dest_width = 3;
// Layer count of the destination texture
let mut dest_layers = 1;
// If the test is suppoed to be valid call to copyExternal.
let mut valid = true;
// If the result is incorrect
let mut correct = true;
match case {
TestCase::Normal => {}
TestCase::FlipY => {
valid = !matches!(source, wgt::ExternalImageSource::ImageBitmap(_));
src_flip_y = true;
for x in 0..3 {
let top = raw_image[(x, 0)];
let bottom = raw_image[(x, 2)];
raw_image[(x, 0)] = bottom;
raw_image[(x, 2)] = top;
}
}
TestCase::Premultiplied => {
valid = !matches!(source, wgt::ExternalImageSource::ImageBitmap(_));
dest_premultiplied = true;
for pixel in raw_image.pixels_mut() {
let mut float_pix = pixel.0.map(|v| v as f32 / 255.0);
float_pix[0] *= float_pix[3];
float_pix[1] *= float_pix[3];
float_pix[2] *= float_pix[3];
pixel.0 = float_pix.map(|v| (v * 255.0).round() as u8);
}
}
TestCase::ColorSpace => {
valid = ctx
.adapter_downlevel_capabilities
.flags
.contains(wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES);
dest_color_space = wgt::PredefinedColorSpace::DisplayP3;
// As we don't test, we don't bother converting the color spaces
// in the image as that's relatively annoying.
}
TestCase::TrimLeft => {
valid = ctx
.adapter_downlevel_capabilities
.flags
.contains(wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES);
src_origin.x = 1;
dest_origin.x = 1;
copy_size.width = 2;
for y in 0..3 {
raw_image[(0, y)].0 = [0; 4];
}
}
TestCase::TrimRight => {
copy_size.width = 2;
for y in 0..3 {
raw_image[(2, y)].0 = [0; 4];
}
}
TestCase::SlideRight => {
dest_origin.x = 1;
copy_size.width = 2;
for x in (1..3).rev() {
for y in 0..3 {
raw_image[(x, y)].0 = raw_image[(x - 1, y)].0;
}
}
for y in 0..3 {
raw_image[(0, y)].0 = [0; 4];
}
}
TestCase::SourceOutOfBounds => {
valid = false;
// It's now in bounds for the destination
dest_width = 4;
copy_size.width = 4;
}
TestCase::DestOutOfBounds => {
valid = false;
// It's now out bounds for the destination
dest_width = 2;
}
TestCase::MultiSliceCopy => {
valid = false;
copy_size.depth_or_array_layers = 2;
dest_layers = 2;
}
TestCase::SecondSliceCopy => {
correct = false; // TODO: what?
dest_origin.z = 1;
dest_data_layer = 1;
dest_layers = 2;
}
}
let texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: Some("import dest"),
size: wgpu::Extent3d {
width: dest_width,
height: 3,
depth_or_array_layers: dest_layers,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
fail_if(&ctx.device, !valid, || {
ctx.queue.copy_external_image_to_texture(
&wgpu::ImageCopyExternalImage {
source: source.clone(),
origin: src_origin,
flip_y: src_flip_y,
},
wgpu::ImageCopyTextureTagged {
texture: &texture,
mip_level: 0,
origin: dest_origin,
aspect: wgpu::TextureAspect::All,
color_space: dest_color_space,
premultiplied_alpha: dest_premultiplied,
},
copy_size,
);
});
let readback_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("readback buffer"),
size: 4 * 64 * 3,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let mut encoder = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
encoder.copy_texture_to_buffer(
wgpu::ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0,
y: 0,
z: dest_data_layer,
},
aspect: wgpu::TextureAspect::All,
},
wgpu::ImageCopyBuffer {
buffer: &readback_buffer,
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(NonZeroU32::new(256).unwrap()),
rows_per_image: None,
},
},
wgpu::Extent3d {
width: dest_width,
height: 3,
depth_or_array_layers: 1,
},
);
ctx.queue.submit(Some(encoder.finish()));
readback_buffer
.slice(..)
.map_async(wgpu::MapMode::Read, |_| ());
ctx.device.poll(wgpu::Maintain::Wait);
let buffer = readback_buffer.slice(..).get_mapped_range();
// 64 because of 256 byte alignment / 4.
let gpu_image = image::RgbaImage::from_vec(64, 3, buffer.to_vec()).unwrap();
let gpu_image_cropped =
image::imageops::crop_imm(&gpu_image, 0, 0, 3, 3).to_image();
if valid && correct {
assert_eq!(
raw_image, gpu_image_cropped,
"Failed on test case {case:?} {source:?}"
);
} else {
assert_ne!(
raw_image, gpu_image_cropped,
"Failed on test case {case:?} {source:?}"
);
}
}
}
})
}

View File

@ -10,6 +10,7 @@ mod clear_texture;
mod device;
mod encoder;
mod example_wgsl;
mod external_texture;
mod instance;
mod poll;
mod queue_transfer;