mirror of
https://github.com/gfx-rs/wgpu.git
synced 2024-11-21 22:33:49 +00:00
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:
parent
964c94a02d
commit
95a760bb42
@ -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
48
Cargo.lock
generated
@ -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]]
|
||||
|
@ -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"
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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`.
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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)]
|
||||
|
@ -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"
|
||||
] }
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
BIN
wgpu/tests/3x3_colors.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 87 B |
355
wgpu/tests/external_texture.rs
Normal file
355
wgpu/tests/external_texture.rs
Normal 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:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -10,6 +10,7 @@ mod clear_texture;
|
||||
mod device;
|
||||
mod encoder;
|
||||
mod example_wgsl;
|
||||
mod external_texture;
|
||||
mod instance;
|
||||
mod poll;
|
||||
mod queue_transfer;
|
||||
|
Loading…
Reference in New Issue
Block a user