From 4500412f432805a3716f04c39fe5bbf991354239 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Mon, 5 Dec 2022 22:19:45 -0500 Subject: [PATCH 01/18] Implement queue.copy_external_image_to_texture for web --- Cargo.lock | 4 +- wgpu-types/Cargo.toml | 9 +++ wgpu-types/src/lib.rs | 123 +++++++++++++++++++++++++++++++++++-- wgpu/Cargo.toml | 1 + wgpu/src/backend/direct.rs | 11 ++++ wgpu/src/backend/web.rs | 53 +++++++++++----- wgpu/src/lib.rs | 47 ++++++++++---- 7 files changed, 214 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4bbd51a6a1..b32c91e0a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -594,7 +594,7 @@ dependencies = [ [[package]] name = "deno_webgpu" -version = "0.63.0" +version = "0.81.0" dependencies = [ "deno_core", "serde", @@ -2905,8 +2905,10 @@ name = "wgpu-types" version = "0.14.0" dependencies = [ "bitflags", + "js-sys", "serde", "serde_json", + "web-sys", ] [[package]] diff --git a/wgpu-types/Cargo.toml b/wgpu-types/Cargo.toml index 0748296a81..d41e0c8e16 100644 --- a/wgpu-types/Cargo.toml +++ b/wgpu-types/Cargo.toml @@ -23,5 +23,14 @@ replay = ["serde"] bitflags.workspace = true serde = { workspace = true, 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_json.workspace = true diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 82ffaf31f3..b7753f9e43 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -1123,10 +1123,15 @@ 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; + + /// External textures, on the web, support copying from an OffscreenCanvas. + /// + /// WebGL doesn't support this. + const EXTERNAL_TEXTURE_OFFSCREEN_CANVAS = 1 << 17; } } @@ -4031,10 +4036,26 @@ 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, +} + /// 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))] @@ -4063,7 +4084,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))] @@ -4937,7 +4958,9 @@ pub struct ImageCopyTexture { 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. @@ -4945,6 +4968,98 @@ pub struct ImageCopyTexture { pub aspect: TextureAspect, } +/// 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<'a> { + /// The texture to be copied from. The copy source data is captured at the moment + /// the copy is issued. + pub source: ExternalImageSource<'a>, + /// 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. + 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<'a> { + /// Copy from a previously-decoded image bitmap. + ImageBitmap(&'a web_sys::ImageBitmap), + /// Copy from a current frame of a video element. + HTMLVideoElement(&'a web_sys::HtmlVideoElement), + /// Copy from a on-screen canvas. + HTMLCanvasElement(&'a web_sys::HtmlCanvasElement), + /// Copy from a off-screen canvas. + /// + /// Requies [`DownlevelFlags::EXTERNAL_TEXTURE_OFFSCREEN_CANVAS`] + OffscreenCanvas(&'a web_sys::OffscreenCanvas), +} + +#[cfg(target_arch = "wasm32")] +impl<'a> std::ops::Deref for ExternalImageSource<'a> { + 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, + } + } +} + +/// Color spaces supported on the web. +/// +/// Corresponds to [HTML Canvas `PredefinedColorSpace`]( +/// https://html.spec.whatwg.org/multipage/canvas.html#predefinedcolorspace). +#[derive(Copy, Clone, Debug)] +#[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(Clone, Debug)] +#[cfg_attr(feature = "trace", derive(serde::Serialize))] +#[cfg_attr(feature = "replay", derive(serde::Deserialize))] +pub struct ImageCopyTextureTagged { + /// 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, +} + /// Subresource range within an image #[repr(C)] #[derive(Clone, Debug, Default, Eq, PartialEq)] diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 57bafc566a..86b176d4b4 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -199,6 +199,7 @@ web-sys = { workspace = true, features = [ "GpuDeviceLostReason", "GpuError", "GpuErrorFilter", + # "GpuExtent2dDict", Not yet implemented in web_sys "GpuExtent3dDict", "GpuFeatureName", "GpuFilterMode", diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index 6187bedb06..0f826fb6f3 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -2375,6 +2375,17 @@ impl crate::Context for Context { } } + #[cfg(target_arch = "wasm32")] + fn queue_copy_external_image_to_texture( + &self, + _queue: &Self::QueueId, + _source: wgt::ImageCopyExternalImage, + _dest: crate::ImageCopyTextureTagged, + _size: wgt::Extent3d, + ) { + todo!() + } + fn queue_submit>( &self, queue: &Self::QueueId, diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index a8fc828815..8829e1fdb5 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -827,6 +827,12 @@ fn map_vertex_step_mode(mode: wgt::VertexStepMode) -> web_sys::GpuVertexStepMode } } +// fn map_extent_2d(extent: wgt::Extent2d) -> web_sys::GpuExtent2dDict { +// let mut mapped = web_sys::GpuExtent2dDict::new(extent.width); +// mapped.height(extent.height); +// mapped +// } + fn map_extent_3d(extent: wgt::Extent3d) -> web_sys::GpuExtent3dDict { let mut mapped = web_sys::GpuExtent3dDict::new(extent.width); mapped.height(extent.height); @@ -884,11 +890,23 @@ 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 mut mapped = web_sys::GpuImageCopyTextureTagged::new(&view.texture.id.0); 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_extent_2d(view.extent)); + mapped.flip_y(view.flip_y); mapped } @@ -1072,22 +1090,6 @@ impl Context { Ok(create_identified(context)) } - - pub fn queue_copy_external_image_to_texture( - &self, - queue: &Identified, - 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. @@ -2488,6 +2490,23 @@ impl crate::Context for Context { ); } + #[cfg(target_arch = "wasm32")] + fn queue_copy_external_image_to_texture( + &self, + queue: &Self::QueueId, + 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>( &self, queue: &Self::QueueId, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index bda421ffd2..a4e716ac89 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -33,16 +33,22 @@ pub use wgt::{ DepthStencilState, DeviceType, DownlevelCapabilities, DownlevelFlags, DynamicOffset, Extent3d, Face, Features, FilterMode, FrontFace, ImageDataLayout, ImageSubresourceRange, IndexFormat, Limits, MultisampleState, Origin3d, PipelineStatisticsTypes, PolygonMode, PowerPreference, - PresentMode, 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, + PredefinedColorSpace, PresentMode, 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(target_arch = "wasm32")] +pub use wgt::{ExternalImageSource, ImageCopyExternalImage}; + use backend::{BufferMappedRange, Context as C, QueueWriteBuffer}; /// Filter for error scopes. @@ -515,6 +521,14 @@ trait Context: Debug + Send + Sized + Sync { data_layout: ImageDataLayout, size: Extent3d, ); + #[cfg(target_arch = "wasm32")] + fn queue_copy_external_image_to_texture( + &self, + queue: &Self::QueueId, + source: ImageCopyExternalImage, + dest: ImageCopyTextureTagged, + size: Extent3d, + ); fn queue_submit>( &self, queue: &Self::QueueId, @@ -1642,6 +1656,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`]. @@ -3695,15 +3718,15 @@ impl Queue { } /// Schedule a copy of data from `image` into `texture`. - #[cfg(all(target_arch = "wasm32", not(feature = "webgl")))] + #[cfg(target_arch = "wasm32")] pub fn copy_external_image_to_texture( &self, - image: &web_sys::ImageBitmap, - texture: ImageCopyTexture, + source: wgt::ImageCopyExternalImage, + dest: ImageCopyTextureTagged, size: Extent3d, ) { self.context - .queue_copy_external_image_to_texture(&self.id, image, texture, size) + .queue_copy_external_image_to_texture(&self.id, source, dest, size) } /// Submits a series of finished command buffers for execution. From bcc086df37645522686f30e9a4a36ecf956acd0a Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Tue, 6 Dec 2022 23:16:07 -0500 Subject: [PATCH 02/18] Body of the WebGL implementation --- wgpu-core/src/command/transfer.rs | 7 ++ wgpu-core/src/conv.rs | 20 ++++ wgpu-core/src/device/queue.rs | 189 +++++++++++++++++++++++++++++- wgpu-hal/src/empty.rs | 11 ++ wgpu-hal/src/gles/command.rs | 24 ++++ wgpu-hal/src/gles/mod.rs | 8 ++ wgpu-hal/src/gles/queue.rs | 103 ++++++++++++++++ wgpu-hal/src/lib.rs | 13 ++ wgpu-types/src/lib.rs | 101 ++++++++++++++-- wgpu/src/backend/direct.rs | 32 ++++- wgpu/src/backend/web.rs | 4 +- wgpu/src/lib.rs | 6 +- 12 files changed, 493 insertions(+), 25 deletions(-) diff --git a/wgpu-core/src/command/transfer.rs b/wgpu-core/src/command/transfer.rs index 063e33a0a0..8ba688b5f3 100644 --- a/wgpu-core/src/command/transfer.rs +++ b/wgpu-core/src/command/transfer.rs @@ -24,6 +24,7 @@ use std::iter; pub type ImageCopyBuffer = wgt::ImageCopyBuffer; pub type ImageCopyTexture = wgt::ImageCopyTexture; +pub type ImageCopyTextureTagged = wgt::ImageCopyTextureTagged; #[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, Option), + #[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`")] @@ -96,6 +101,8 @@ pub enum TransferError { CopyFromForbiddenTextureFormat(wgt::TextureFormat), #[error("copying to textures with format {0:?} is forbidden")] CopyToForbiddenTextureFormat(wgt::TextureFormat), + #[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( diff --git a/wgpu-core/src/conv.rs b/wgpu-core/src/conv.rs index 6a0da9d29a..cc689c727a 100644 --- a/wgpu-core/src/conv.rs +++ b/wgpu-core/src/conv.rs @@ -29,6 +29,26 @@ pub fn is_valid_copy_dst_texture_format(format: wgt::TextureFormat) -> bool { } } +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( diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index ccb6c8838f..8424d2148e 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -654,12 +654,6 @@ impl Global { (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(); @@ -807,6 +801,189 @@ impl Global { Ok(()) } + #[cfg(target_arch = "wasm32")] + pub fn queue_copy_external_image_to_texture( + &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 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, texture_format) = + 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, + )?; + + if !conv::is_valid_copy_dst_texture_format(texture_format) { + return Err(TransferError::CopyToForbiddenTextureFormat(texture_format).into()); + } + + 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::>>() + { + 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.source, dst_raw, iter::once(regions)); + } + + Ok(()) + } + pub fn queue_submit( &self, queue_id: id::QueueId, diff --git a/wgpu-hal/src/empty.rs b/wgpu-hal/src/empty.rs index a761ef7fb1..ba23ed7ed9 100644 --- a/wgpu-hal/src/empty.rs +++ b/wgpu-hal/src/empty.rs @@ -260,6 +260,17 @@ impl crate::CommandEncoder for Encoder { unsafe fn copy_buffer_to_buffer(&mut self, src: &Resource, dst: &Resource, regions: T) {} + #[cfg(target_arch = "wasm32")] + unsafe fn copy_external_image_to_texture( + &mut self, + src: &wgt::ExternalImageSource, + dst: &Resource, + regions: T, + ) where + T: Iterator, + { + } + unsafe fn copy_texture_to_texture( &mut self, src: &Resource, diff --git a/wgpu-hal/src/gles/command.rs b/wgpu-hal/src/gles/command.rs index 4a9ee22d07..1073c5bfa8 100644 --- a/wgpu-hal/src/gles/command.rs +++ b/wgpu-hal/src/gles/command.rs @@ -309,6 +309,30 @@ impl crate::CommandEncoder for super::CommandEncoder { } } + #[cfg(target_arch = "wasm32")] + unsafe fn copy_external_image_to_texture( + &mut self, + src: &wgt::ExternalImageSource, + dst: &super::Texture, + regions: T, + ) where + T: Iterator, + { + let (dst_raw, dst_target) = dst.inner.as_native(); + for copy in regions { + // TODO: Clamp size? + self.cmd_buffer + .commands + .push(C::CopyExternalImageToTexture { + src: src.clone(), + dst: dst_raw, + dst_target, + dst_format: dst.format, + copy, + }) + } + } + unsafe fn copy_texture_to_texture( &mut self, src: &super::Texture, diff --git a/wgpu-hal/src/gles/mod.rs b/wgpu-hal/src/gles/mod.rs index e57b05a979..d52c677fb4 100644 --- a/wgpu-hal/src/gles/mod.rs +++ b/wgpu-hal/src/gles/mod.rs @@ -661,6 +661,14 @@ enum Command { dst_target: BindTarget, copy: crate::BufferCopy, }, + #[cfg(target_arch = "wasm32")] + CopyExternalImageToTexture { + src: wgt::ExternalImageSource, + dst: glow::Texture, + dst_target: BindTarget, + dst_format: wgt::TextureFormat, + copy: crate::TextureCopy, + }, CopyTextureToTexture { src: glow::Texture, src_target: BindTarget, diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index 75770c501c..2772c5a334 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -375,6 +375,109 @@ impl super::Queue { unsafe { gl.bind_buffer(copy_dst_target, None) }; } } + C::CopyExternalImageToTexture { + ref src, + dst, + dst_target, + dst_format, + ref copy, + } => { + 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 { + 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, + 0, + format_desc.external, + format_desc.data_type, + b, + ); + }, + wgt::ExternalImageSource::HTMLVideoElement(ref _v) => { + // 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, + // 0, + // format_desc.external, + // format_desc.data_type, + // b, + // ); + } + 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, + 0, + format_desc.external, + format_desc.data_type, + c, + ); + }, + wgt::ExternalImageSource::OffscreenCanvas(_) => unreachable!(), + } + } else { + match *src { + wgt::ExternalImageSource::ImageBitmap(ref b) => unsafe { + gl.tex_sub_image_2d_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.size.width as i32, + // copy.size.height as i32, + format_desc.external, + format_desc.data_type, + b, + ); + }, + wgt::ExternalImageSource::HTMLVideoElement(ref _v) => { + // gl.tex_sub_image_2d_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.size.width as i32, + // // copy.size.height as i32, + // format_desc.external, + // format_desc.data_type, + // b, + // ); + } + wgt::ExternalImageSource::HTMLCanvasElement(ref c) => unsafe { + gl.tex_sub_image_2d_with_html_canvas( + 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!(), + } + } + } C::CopyTextureToTexture { src, src_target, diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index b688e326d6..1646356a7c 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -384,6 +384,19 @@ pub trait CommandEncoder: Send + Sync + fmt::Debug { where T: Iterator; + /// Copy from an external image to an internal textureanother. + /// 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(target_arch = "wasm32")] + unsafe fn copy_external_image_to_texture( + &mut self, + src: &wgt::ExternalImageSource, + dst: &A::Texture, + regions: T, + ) where + T: Iterator; + /// Copy from one texture to another. /// Works with a single array layer. /// Note: `dst` current usage has to be `TextureUses::COPY_DST`. diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index b7753f9e43..6e4fe121c9 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -4052,6 +4052,20 @@ pub struct Origin2d { 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`]( @@ -4073,6 +4087,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 { @@ -4950,7 +4972,7 @@ pub struct ImageCopyBuffer { /// 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 { @@ -4968,16 +4990,35 @@ pub struct ImageCopyTexture { pub aspect: TextureAspect, } +impl ImageCopyTexture { + /// Adds color space and premultiplied alpha information to make this + /// descriptor tagged. + pub fn to_tagged( + self, + color_space: PredefinedColorSpace, + premultiplied_alpha: bool, + ) -> ImageCopyTextureTagged { + 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<'a> { +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<'a>, + 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. @@ -4995,21 +5036,44 @@ pub struct ImageCopyExternalImage<'a> { /// https://gpuweb.github.io/gpuweb/#dom-gpuimagecopyexternalimage-source). #[cfg(target_arch = "wasm32")] #[derive(Clone, Debug)] -pub enum ExternalImageSource<'a> { +pub enum ExternalImageSource { /// Copy from a previously-decoded image bitmap. - ImageBitmap(&'a web_sys::ImageBitmap), + ImageBitmap(web_sys::ImageBitmap), /// Copy from a current frame of a video element. - HTMLVideoElement(&'a web_sys::HtmlVideoElement), + HTMLVideoElement(web_sys::HtmlVideoElement), /// Copy from a on-screen canvas. - HTMLCanvasElement(&'a web_sys::HtmlCanvasElement), + HTMLCanvasElement(web_sys::HtmlCanvasElement), /// Copy from a off-screen canvas. /// /// Requies [`DownlevelFlags::EXTERNAL_TEXTURE_OFFSCREEN_CANVAS`] - OffscreenCanvas(&'a web_sys::OffscreenCanvas), + 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<'a> std::ops::Deref for ExternalImageSource<'a> { +impl std::ops::Deref for ExternalImageSource { type Target = js_sys::Object; fn deref(&self) -> &Self::Target { @@ -5022,6 +5086,11 @@ impl<'a> std::ops::Deref for ExternalImageSource<'a> { } } +#[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`]( @@ -5042,7 +5111,7 @@ pub enum PredefinedColorSpace { /// /// Corresponds to [WebGPU `GPUImageCopyTextureTagged`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopytexturetagged). -#[derive(Clone, Debug)] +#[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "trace", derive(serde::Serialize))] #[cfg_attr(feature = "replay", derive(serde::Deserialize))] pub struct ImageCopyTextureTagged { @@ -5060,6 +5129,18 @@ pub struct ImageCopyTextureTagged { pub premultiplied_alpha: bool, } +impl ImageCopyTextureTagged { + /// Removes the colorspace information from the type. + pub fn to_untagged(self) -> ImageCopyTexture { + ImageCopyTexture { + texture: self.texture, + mip_level: self.mip_level, + origin: self.origin, + aspect: self.aspect, + } + } +} + /// Subresource range within an image #[repr(C)] #[derive(Clone, Debug, Default, Eq, PartialEq)] diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index 0f826fb6f3..4dfef0f025 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -765,6 +765,19 @@ fn map_texture_copy_view(view: crate::ImageCopyTexture) -> wgc::command::ImageCo } } +fn map_texture_tagged_copy_view( + view: crate::ImageCopyTextureTagged, +) -> wgc::command::ImageCopyTextureTagged { + wgc::command::ImageCopyTextureTagged { + texture: view.texture.id.id, + 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( ops: Option<&Operations>, ) -> wgc::command::PassChannel { @@ -2378,12 +2391,21 @@ impl crate::Context for Context { #[cfg(target_arch = "wasm32")] fn queue_copy_external_image_to_texture( &self, - _queue: &Self::QueueId, - _source: wgt::ImageCopyExternalImage, - _dest: crate::ImageCopyTextureTagged, - _size: wgt::Extent3d, + queue: &Self::QueueId, + source: &wgt::ImageCopyExternalImage, + dest: crate::ImageCopyTextureTagged, + size: wgt::Extent3d, ) { - todo!() + let global = &self.0; + match wgc::gfx_select!(*queue => global.queue_copy_external_image_to_texture( + queue.id, + source, + map_texture_tagged_copy_view(dest), + size + )) { + Ok(()) => (), + Err(err) => self.handle_error_nolabel(&queue.error_sink, err, "Queue::write_texture"), + } } fn queue_submit>( diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index 8829e1fdb5..281f9f5c79 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -902,7 +902,7 @@ fn map_tagged_texture_copy_view( } fn map_external_texture_copy_view( - view: crate::ImageCopyExternalImage, + view: &crate::ImageCopyExternalImage, ) -> web_sys::GpuImageCopyExternalImage { let mut mapped = web_sys::GpuImageCopyExternalImage::new(&view.source); // mapped.origin(&map_extent_2d(view.extent)); @@ -2494,7 +2494,7 @@ impl crate::Context for Context { fn queue_copy_external_image_to_texture( &self, queue: &Self::QueueId, - source: wgt::ImageCopyExternalImage, + source: &wgt::ImageCopyExternalImage, dest: crate::ImageCopyTextureTagged, size: wgt::Extent3d, ) { diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index a4e716ac89..40a12f9ce1 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -48,6 +48,8 @@ pub use wgt::{ // specific, but these need to depend on web-sys. #[cfg(target_arch = "wasm32")] pub use wgt::{ExternalImageSource, ImageCopyExternalImage}; +#[cfg(target_arch = "wasm32")] +static_assertions::assert_impl_all!(ExternalImageSource: Send, Sync); use backend::{BufferMappedRange, Context as C, QueueWriteBuffer}; @@ -525,7 +527,7 @@ trait Context: Debug + Send + Sized + Sync { fn queue_copy_external_image_to_texture( &self, queue: &Self::QueueId, - source: ImageCopyExternalImage, + source: &ImageCopyExternalImage, dest: ImageCopyTextureTagged, size: Extent3d, ); @@ -3721,7 +3723,7 @@ impl Queue { #[cfg(target_arch = "wasm32")] pub fn copy_external_image_to_texture( &self, - source: wgt::ImageCopyExternalImage, + source: &wgt::ImageCopyExternalImage, dest: ImageCopyTextureTagged, size: Extent3d, ) { From 2af9928397ab8e46ebf57a9dea1fba822c955c5b Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Sun, 11 Dec 2022 18:37:57 -0500 Subject: [PATCH 03/18] Finish wiring it up --- Cargo.lock | 2 +- Cargo.toml | 2 +- wgpu-core/src/command/transfer.rs | 4 +- wgpu-core/src/device/queue.rs | 4 +- wgpu-hal/src/gles/queue.rs | 70 +++++++++++++++---------------- 5 files changed, 43 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b32c91e0a5..80ef262518 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1000,7 +1000,7 @@ checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" [[package]] name = "glow" version = "0.11.2" -source = "git+https://github.com/grovesNL/glow?rev=c8a011fcd57a5c68cc917ed394baa484bdefc909#c8a011fcd57a5c68cc917ed394baa484bdefc909" +source = "git+https://github.com/cwfitzgerald/glow?rev=843c2e4bcad8726db7fb664a0dc8dedc15f31211#843c2e4bcad8726db7fb664a0dc8dedc15f31211" dependencies = [ "js-sys", "slotmap", diff --git a/Cargo.toml b/Cargo.toml index f59ca0b920..3089fb75a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,7 +103,7 @@ winapi = "0.3" egl = { package = "khronos-egl", version = "4.1" } # glow = { version = "0.11.2", optional = true } # TODO: New glow release -glow = { git = "https://github.com/grovesNL/glow", rev = "c8a011fcd57a5c68cc917ed394baa484bdefc909" } +glow = { git = "https://github.com/cwfitzgerald/glow", rev = "843c2e4bcad8726db7fb664a0dc8dedc15f31211" } glutin = "0.29.1" # wasm32 dependencies diff --git a/wgpu-core/src/command/transfer.rs b/wgpu-core/src/command/transfer.rs index 8ba688b5f3..cda4c34a3a 100644 --- a/wgpu-core/src/command/transfer.rs +++ b/wgpu-core/src/command/transfer.rs @@ -101,7 +101,9 @@ pub enum TransferError { CopyFromForbiddenTextureFormat(wgt::TextureFormat), #[error("copying to textures with format {0:?} is forbidden")] CopyToForbiddenTextureFormat(wgt::TextureFormat), - #[error("copying to textures with format {0:?} is forbidden when copying from external texture")] + #[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, diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index 8424d2148e..b7d720abe6 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -833,7 +833,9 @@ impl Global { 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()); + return Err( + TransferError::ExternalCopyToForbiddenTextureFormat(dst.desc.format).into(), + ); } if dst.desc.dimension != wgt::TextureDimension::D2 { return Err(TransferError::InvalidDimensionExternal(destination.texture).into()); diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index 2772c5a334..02ab28b276 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -395,27 +395,27 @@ impl super::Queue { copy.dst_base.origin.z as i32, copy.size.width as i32, copy.size.height as i32, - 0, + copy.size.depth as i32, format_desc.external, format_desc.data_type, b, ); }, - wgt::ExternalImageSource::HTMLVideoElement(ref _v) => { - // 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, - // 0, - // 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, @@ -425,7 +425,7 @@ impl super::Queue { copy.dst_base.origin.z as i32, copy.size.width as i32, copy.size.height as i32, - 0, + copy.size.depth as i32, format_desc.external, format_desc.data_type, c, @@ -441,38 +441,38 @@ impl super::Queue { 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, + copy.size.width as i32, + copy.size.height as i32, format_desc.external, format_desc.data_type, b, ); }, - wgt::ExternalImageSource::HTMLVideoElement(ref _v) => { - // gl.tex_sub_image_2d_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.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( + 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( 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, + copy.size.width as i32, + copy.size.height as i32, format_desc.external, format_desc.data_type, c, - ); + ) }, wgt::ExternalImageSource::OffscreenCanvas(_) => unreachable!(), } From e0f44f11314071330230b27bfe48711ffd228347 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Sun, 11 Dec 2022 18:56:02 -0500 Subject: [PATCH 04/18] CHANGELOG etc --- CHANGELOG.md | 7 ++++--- wgpu-core/src/conv.rs | 1 + wgpu-core/src/device/queue.rs | 6 ++++++ wgpu-types/src/lib.rs | 4 ++-- wgpu/src/backend/direct.rs | 1 + 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b29d6c68f..7ab761af03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,10 @@ Additionally `Surface::get_default_config` now returns an Option and returns Non `Instance::create_surface()` now returns `Result` 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) + ### Changes #### General @@ -156,21 +160,18 @@ Additionally `Surface::get_default_config` now returns an Option and returns Non - 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 - Make `wgpu::TextureFormat::Depth24PlusStencil8` available on all backends by making the feature unconditionally available and the feature unneeded to use the format. By @Healthire and @cwfitzgerald in [#3165](https://github.com/gfx-rs/wgpu/pull/3165) - ## wgpu-0.14.0 (2022-10-05) ### Major Changes diff --git a/wgpu-core/src/conv.rs b/wgpu-core/src/conv.rs index cc689c727a..e48b3af4e2 100644 --- a/wgpu-core/src/conv.rs +++ b/wgpu-core/src/conv.rs @@ -29,6 +29,7 @@ pub fn is_valid_copy_dst_texture_format(format: wgt::TextureFormat) -> bool { } } +#[cfg_attr(not(target_arch = "wasm32"), allow(unused))] pub fn is_valid_external_image_copy_dst_texture_format(format: wgt::TextureFormat) -> bool { use wgt::TextureFormat as Tf; match format { diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index b7d720abe6..7d37888501 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -823,6 +823,12 @@ impl Global { return Ok(()); } + if matches!(source.source, wgt::ExternalImageSource::OffscreenCanvas(_)) { + 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(); diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 6e4fe121c9..49ae518ca5 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -1128,10 +1128,10 @@ bitflags::bitflags! { /// WebGL doesn't support this. const UNRESTRICTED_INDEX_BUFFER = 1 << 16; - /// External textures, on the web, support copying from an OffscreenCanvas. + /// Supports copying from an OffscreenCanvas in `Queue::copy_external_image_to_texture`. /// /// WebGL doesn't support this. - const EXTERNAL_TEXTURE_OFFSCREEN_CANVAS = 1 << 17; + const UNRESTRICTED_EXTERNAL_TEXTURE_COPIES = 1 << 17; } } diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index 4dfef0f025..f717323d9c 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -765,6 +765,7 @@ fn map_texture_copy_view(view: crate::ImageCopyTexture) -> wgc::command::ImageCo } } +#[cfg_attr(not(target_arch = "wasm32"), allow(unused))] fn map_texture_tagged_copy_view( view: crate::ImageCopyTextureTagged, ) -> wgc::command::ImageCopyTextureTagged { From fdafca5c73de0787b0d23c52e2888b329e42c084 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Sun, 11 Dec 2022 19:00:44 -0500 Subject: [PATCH 05/18] Missed a wasm guard --- wgpu-hal/src/gles/queue.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index 02ab28b276..a1e13abd51 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -375,6 +375,7 @@ impl super::Queue { unsafe { gl.bind_buffer(copy_dst_target, None) }; } } + #[cfg(target_arch = "wasm32")] C::CopyExternalImageToTexture { ref src, dst, From bcdb524a12ebf4d1ef7ff90d7437fd2ef960b40e Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Sun, 11 Dec 2022 19:15:31 -0500 Subject: [PATCH 06/18] Clippy --- wgpu-core/src/command/transfer.rs | 12 ++++++------ wgpu-core/src/device/queue.rs | 2 +- wgpu-types/src/lib.rs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/wgpu-core/src/command/transfer.rs b/wgpu-core/src/command/transfer.rs index cda4c34a3a..99bd54bb85 100644 --- a/wgpu-core/src/command/transfer.rs +++ b/wgpu-core/src/command/transfer.rs @@ -704,8 +704,8 @@ impl Global { #[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, }); } @@ -838,8 +838,8 @@ impl Global { #[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, }); } @@ -1001,8 +1001,8 @@ impl Global { #[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, }); } diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index 7d37888501..83715c883c 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -582,7 +582,7 @@ impl Global { 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, diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 49ae518ca5..d29e9638f0 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -4957,7 +4957,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 { From b25909d40e5da14eaba22f0be237e6f65d1ffb71 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Thu, 5 Jan 2023 18:42:31 -0500 Subject: [PATCH 07/18] Format --- wgpu/src/backend/direct.rs | 6 +++++- wgpu/src/context.rs | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index bed15cb9fa..4fac5b3f76 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -2213,7 +2213,11 @@ impl crate::Context for Context { size )) { Ok(()) => (), - Err(err) => self.handle_error_nolabel(&queue_data.error_sink, err, "Queue::copy_external_image_to_texture"), + Err(err) => self.handle_error_nolabel( + &queue_data.error_sink, + err, + "Queue::copy_external_image_to_texture", + ), } } diff --git a/wgpu/src/context.rs b/wgpu/src/context.rs index 9e67796071..c75363db67 100644 --- a/wgpu/src/context.rs +++ b/wgpu/src/context.rs @@ -1487,7 +1487,7 @@ pub(crate) trait DynContext: Debug + Send + Sync { data: &[u8], data_layout: ImageDataLayout, size: Extent3d, - ); + ); #[cfg(target_arch = "wasm32")] fn queue_copy_external_image_to_texture( &self, @@ -2884,7 +2884,6 @@ where Context::queue_write_texture(self, &queue, queue_data, texture, data, data_layout, size) } - #[cfg(target_arch = "wasm32")] fn queue_copy_external_image_to_texture( &self, From 917e779d926ff26b268fb91a56b1de1d7d25bd4b Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Thu, 5 Jan 2023 18:51:03 -0500 Subject: [PATCH 08/18] Remove emscripten support --- wgpu-core/src/conv.rs | 5 ++++- wgpu-core/src/device/queue.rs | 2 +- wgpu-hal/src/empty.rs | 2 +- wgpu-hal/src/gles/command.rs | 2 +- wgpu-hal/src/gles/mod.rs | 2 +- wgpu-hal/src/gles/queue.rs | 2 +- wgpu-hal/src/lib.rs | 2 +- wgpu/src/backend/direct.rs | 7 +++++-- wgpu/src/backend/web.rs | 2 +- wgpu/src/context.rs | 6 +++--- wgpu/src/lib.rs | 6 +++--- 11 files changed, 22 insertions(+), 16 deletions(-) diff --git a/wgpu-core/src/conv.rs b/wgpu-core/src/conv.rs index 5f79c85573..52daa335a6 100644 --- a/wgpu-core/src/conv.rs +++ b/wgpu-core/src/conv.rs @@ -33,7 +33,10 @@ pub fn is_valid_copy_dst_texture_format( } } -#[cfg_attr(not(target_arch = "wasm32"), allow(unused))] +#[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 { diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index a462424470..e17fe06cc2 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -805,7 +805,7 @@ impl Global { Ok(()) } - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] pub fn queue_copy_external_image_to_texture( &self, queue_id: id::QueueId, diff --git a/wgpu-hal/src/empty.rs b/wgpu-hal/src/empty.rs index ec19591b11..2bc801b88d 100644 --- a/wgpu-hal/src/empty.rs +++ b/wgpu-hal/src/empty.rs @@ -264,7 +264,7 @@ impl crate::CommandEncoder for Encoder { unsafe fn copy_buffer_to_buffer(&mut self, src: &Resource, dst: &Resource, regions: T) {} - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] unsafe fn copy_external_image_to_texture( &mut self, src: &wgt::ExternalImageSource, diff --git a/wgpu-hal/src/gles/command.rs b/wgpu-hal/src/gles/command.rs index 1073c5bfa8..87783a4e4d 100644 --- a/wgpu-hal/src/gles/command.rs +++ b/wgpu-hal/src/gles/command.rs @@ -309,7 +309,7 @@ impl crate::CommandEncoder for super::CommandEncoder { } } - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] unsafe fn copy_external_image_to_texture( &mut self, src: &wgt::ExternalImageSource, diff --git a/wgpu-hal/src/gles/mod.rs b/wgpu-hal/src/gles/mod.rs index d52c677fb4..efb890f473 100644 --- a/wgpu-hal/src/gles/mod.rs +++ b/wgpu-hal/src/gles/mod.rs @@ -661,7 +661,7 @@ enum Command { dst_target: BindTarget, copy: crate::BufferCopy, }, - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] CopyExternalImageToTexture { src: wgt::ExternalImageSource, dst: glow::Texture, diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index 8004d896fa..6cc5c178a9 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -375,7 +375,7 @@ impl super::Queue { unsafe { gl.bind_buffer(copy_dst_target, None) }; } } - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] C::CopyExternalImageToTexture { ref src, dst, diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index a821afa932..2c9189f6e0 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -402,7 +402,7 @@ pub trait CommandEncoder: Send + Sync + fmt::Debug { /// 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(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] unsafe fn copy_external_image_to_texture( &mut self, src: &wgt::ExternalImageSource, diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index 4fac5b3f76..297f0799a9 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -342,7 +342,10 @@ fn map_texture_copy_view(view: crate::ImageCopyTexture) -> wgc::command::ImageCo } } -#[cfg_attr(not(target_arch = "wasm32"), allow(unused))] +#[cfg_attr( + any(not(target_arch = "wasm32"), feature = "emscripten"), + allow(unused) +)] fn map_texture_tagged_copy_view( view: crate::ImageCopyTextureTagged, ) -> wgc::command::ImageCopyTextureTagged { @@ -2196,7 +2199,7 @@ impl crate::Context for Context { } } - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] fn queue_copy_external_image_to_texture( &self, queue: &Self::QueueId, diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index 0f097ef69d..0b1fcb10b9 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -2357,7 +2357,7 @@ impl crate::context::Context for Context { ); } - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] fn queue_copy_external_image_to_texture( &self, queue: &Self::QueueId, diff --git a/wgpu/src/context.rs b/wgpu/src/context.rs index c75363db67..0ae7bb1650 100644 --- a/wgpu/src/context.rs +++ b/wgpu/src/context.rs @@ -561,7 +561,7 @@ pub trait Context: Debug + Send + Sized + Sync { data_layout: ImageDataLayout, size: Extent3d, ); - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] fn queue_copy_external_image_to_texture( &self, queue: &Self::QueueId, @@ -1488,7 +1488,7 @@ pub(crate) trait DynContext: Debug + Send + Sync { data_layout: ImageDataLayout, size: Extent3d, ); - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] fn queue_copy_external_image_to_texture( &self, queue: &ObjectId, @@ -2884,7 +2884,7 @@ where Context::queue_write_texture(self, &queue, queue_data, texture, data, data_layout, size) } - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] fn queue_copy_external_image_to_texture( &self, queue: &ObjectId, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1a7a5e86ad..b6863b6372 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -49,9 +49,9 @@ pub use wgt::{ // wasm-only types, we try to keep as many types non-platform // specific, but these need to depend on web-sys. -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] pub use wgt::{ExternalImageSource, ImageCopyExternalImage}; -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] static_assertions::assert_impl_all!(ExternalImageSource: Send, Sync); /// Filter for error scopes. @@ -3921,7 +3921,7 @@ impl Queue { } /// Schedule a copy of data from `image` into `texture`. - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] pub fn copy_external_image_to_texture( &self, source: &wgt::ImageCopyExternalImage, From 1e784bf73166e980a66135332c230886004e8b25 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Fri, 6 Jan 2023 19:46:06 -0500 Subject: [PATCH 09/18] Add tests --- Cargo.lock | 42 ++++++ Cargo.toml | 1 + wgpu-core/src/device/queue.rs | 4 +- wgpu-types/src/lib.rs | 6 +- wgpu/Cargo.toml | 1 + wgpu/src/lib.rs | 20 +-- wgpu/tests/3x3_colors.png | Bin 0 -> 105 bytes wgpu/tests/external_texture.rs | 245 +++++++++++++++++++++++++++++++++ wgpu/tests/root.rs | 1 + 9 files changed, 308 insertions(+), 12 deletions(-) create mode 100644 wgpu/tests/3x3_colors.png create mode 100644 wgpu/tests/external_texture.rs diff --git a/Cargo.lock b/Cargo.lock index 4db8f71c32..fe080f1f09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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 = "concurrent-queue" version = "2.0.0" @@ -1235,6 +1241,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" @@ -1544,6 +1564,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" @@ -2904,6 +2945,7 @@ dependencies = [ "env_logger", "futures-intrusive", "glam", + "image", "js-sys", "log", "naga", diff --git a/Cargo.toml b/Cargo.toml index e405c60757..d399f771e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index e17fe06cc2..3e7d4b44af 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -827,7 +827,9 @@ impl Global { return Ok(()); } - if matches!(source.source, wgt::ExternalImageSource::OffscreenCanvas(_)) { + if matches!(source.source, wgt::ExternalImageSource::OffscreenCanvas(_)) + || source.origin != wgt::Origin2d::ZERO + { device .require_downlevel_flags(wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES) .map_err(TransferError::from)?; diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index a02f05ff7c..7de3a37242 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -1140,7 +1140,9 @@ bitflags::bitflags! { /// Corresponds to Vulkan's `VkPhysicalDeviceFeatures.depthBiasClamp` const DEPTH_BIAS_CLAMP = 1 << 18; - /// Supports copying from an OffscreenCanvas in `Queue::copy_external_image_to_texture`. + /// Supports the following on `Queue::copy_external_image_to_texture`: + /// - Copying from an OffscreenCanvas + /// - Non-zero [`ImageCopyExternalImage::origin`]. /// /// WebGL doesn't support this. const UNRESTRICTED_EXTERNAL_TEXTURE_COPIES = 1 << 19; @@ -5084,6 +5086,8 @@ pub struct ImageCopyExternalImage { /// 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. diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 4685a7dfb4..97438e8e7c 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -164,6 +164,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 diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index b6863b6372..414f974840 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -35,16 +35,16 @@ pub use wgt::{ CommandBufferDescriptor, CompareFunction, CompositeAlphaMode, DepthBiasState, DepthStencilState, DeviceType, DownlevelCapabilities, DownlevelFlags, DynamicOffset, Extent3d, Face, Features, FilterMode, FrontFace, ImageDataLayout, ImageSubresourceRange, IndexFormat, - Limits, MultisampleState, 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, + 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 diff --git a/wgpu/tests/3x3_colors.png b/wgpu/tests/3x3_colors.png new file mode 100644 index 0000000000000000000000000000000000000000..c9b0f1f4c5b96f31c0daf180e3edf0feafd6651b GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1SD^IDZKzvoCO|{#S9F3${@^GvDCf{C@Age y;uyklo%J9iBLf4+VTF746F#aP5Ztk literal 0 HcmV?d00001 diff --git a/wgpu/tests/external_texture.rs b/wgpu/tests/external_texture.rs new file mode 100644 index 0000000000..9d76e27890 --- /dev/null +++ b/wgpu/tests/external_texture.rs @@ -0,0 +1,245 @@ +#![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::*; + +#[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 + let image_bitmap_promise = web_sys::window() + .unwrap() + .create_image_bitmap_with_blob(&blob) + .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); + + // 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)] + enum TestCase { + // Import the image as normal + Normal, + // 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 cases = [ + TestCase::Normal, + TestCase::TrimLeft, + TestCase::TrimRight, + TestCase::SlideRight, + TestCase::SourceOutOfBounds, + TestCase::DestOutOfBounds, + TestCase::MultiSliceCopy, + TestCase::SecondSliceCopy, + ]; + + initialize_test(TestParameters::default(), |ctx| { + 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; + // 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; + // 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::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, + }); + + fail_if(&ctx.device, !valid, || { + ctx.queue.copy_external_image_to_texture( + &wgpu::ImageCopyExternalImage { + source: wgpu::ExternalImageSource::ImageBitmap(image_bitmap.clone()), + origin: src_origin, + flip_y: false, + }, + wgpu::ImageCopyTextureTagged { + texture: &texture, + mip_level: 0, + origin: dest_origin, + aspect: wgpu::TextureAspect::All, + color_space: wgpu::PredefinedColorSpace::Srgb, + premultiplied_alpha: false, + }, + 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:?}"); + } else { + assert_ne!(raw_image, gpu_image_cropped, "Failed on test case {case:?}"); + } + } + }) +} diff --git a/wgpu/tests/root.rs b/wgpu/tests/root.rs index a3132eda81..2732fa636e 100644 --- a/wgpu/tests/root.rs +++ b/wgpu/tests/root.rs @@ -10,6 +10,7 @@ mod clear_texture; mod device; mod encoder; mod example_wgsl; +mod external_texture; mod instance; mod poll; mod queue_transfer; From f7fe90c61509494fb615131ad56fd9fcd539e572 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Fri, 6 Jan 2023 19:51:15 -0500 Subject: [PATCH 10/18] Update to merged glow --- wgpu-hal/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index 5df8cb069a..98f1cd2727 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -69,7 +69,7 @@ gpu-descriptor = { version = "0.2", optional = true } smallvec = { version = "1", optional = true, features = ["union"] } # backend: Gles -glow = { git = "https://github.com/cwfitzgerald/glow", rev = "0ab1b126aed6f6dacc9b3e11fb58bdc8d1b40b9a", optional = true } +glow = { git = "https://github.com/grovesNL/glow", rev = "d7037ef557e70ade837ae9e3c25122069bf88474", optional = true } # backend: Dx12 bit-set = { version = "0.5", optional = true } From afa3282be489c8d10a5c1e09056341c8e53ec37a Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Wed, 11 Jan 2023 15:51:26 -0500 Subject: [PATCH 11/18] Deal with flip_y and multiplication --- wgpu-core/src/device/queue.rs | 7 ++++++- wgpu-hal/src/empty.rs | 3 ++- wgpu-hal/src/gles/command.rs | 4 +++- wgpu-hal/src/gles/mod.rs | 3 ++- wgpu-hal/src/gles/queue.rs | 21 +++++++++++++++++++-- wgpu-hal/src/lib.rs | 3 ++- wgpu/tests/3x3_colors.png | Bin 105 -> 87 bytes wgpu/tests/external_texture.rs | 33 +++++++++++++++++++++++++++++++-- 8 files changed, 65 insertions(+), 9 deletions(-) diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index 3e7d4b44af..843bde1377 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -996,7 +996,12 @@ impl Global { unsafe { encoder.transition_textures(transitions.map(|pending| pending.into_hal(dst))); - encoder.copy_external_image_to_texture(&source.source, dst_raw, iter::once(regions)); + encoder.copy_external_image_to_texture( + source, + dst_raw, + destination.premultiplied_alpha, + iter::once(regions), + ); } Ok(()) diff --git a/wgpu-hal/src/empty.rs b/wgpu-hal/src/empty.rs index 2bc801b88d..5e112b126b 100644 --- a/wgpu-hal/src/empty.rs +++ b/wgpu-hal/src/empty.rs @@ -267,8 +267,9 @@ impl crate::CommandEncoder for Encoder { #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] unsafe fn copy_external_image_to_texture( &mut self, - src: &wgt::ExternalImageSource, + src: &wgt::ImageCopyExternalImage, dst: &Resource, + dst_premultiplication: bool, regions: T, ) where T: Iterator, diff --git a/wgpu-hal/src/gles/command.rs b/wgpu-hal/src/gles/command.rs index 87783a4e4d..b6ea4cddb2 100644 --- a/wgpu-hal/src/gles/command.rs +++ b/wgpu-hal/src/gles/command.rs @@ -312,8 +312,9 @@ impl crate::CommandEncoder for super::CommandEncoder { #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] unsafe fn copy_external_image_to_texture( &mut self, - src: &wgt::ExternalImageSource, + src: &wgt::ImageCopyExternalImage, dst: &super::Texture, + dst_premultiplication: bool, regions: T, ) where T: Iterator, @@ -328,6 +329,7 @@ impl crate::CommandEncoder for super::CommandEncoder { dst: dst_raw, dst_target, dst_format: dst.format, + dst_premultiplication, copy, }) } diff --git a/wgpu-hal/src/gles/mod.rs b/wgpu-hal/src/gles/mod.rs index efb890f473..6f1586ed10 100644 --- a/wgpu-hal/src/gles/mod.rs +++ b/wgpu-hal/src/gles/mod.rs @@ -663,10 +663,11 @@ enum Command { }, #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] CopyExternalImageToTexture { - src: wgt::ExternalImageSource, + src: wgt::ImageCopyExternalImage, dst: glow::Texture, dst_target: BindTarget, dst_format: wgt::TextureFormat, + dst_premultiplication: bool, copy: crate::TextureCopy, }, CopyTextureToTexture { diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index 6cc5c178a9..02626d77a1 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -381,12 +381,24 @@ impl super::Queue { dst, dst_target, dst_format, + dst_premultiplication, ref copy, } => { + const UNPACK_FLIP_Y_WEBGL: u32 = 0x9240; + const UNPACK_PREMULTIPLY_ALPHA_WEBGL: u32 = 0x9241; + + unsafe { + gl.pixel_store_i32(UNPACK_FLIP_Y_WEBGL, src.flip_y as i32); + gl.pixel_store_i32( + UNPACK_PREMULTIPLY_ALPHA_WEBGL, + dst_premultiplication as i32, + ); + } + 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 { + match src.source { wgt::ExternalImageSource::ImageBitmap(ref b) => unsafe { gl.tex_sub_image_3d_with_image_bitmap( dst_target, @@ -435,7 +447,7 @@ impl super::Queue { wgt::ExternalImageSource::OffscreenCanvas(_) => unreachable!(), } } else { - match *src { + match src.source { wgt::ExternalImageSource::ImageBitmap(ref b) => unsafe { gl.tex_sub_image_2d_with_image_bitmap_and_width_and_height( dst_target, @@ -478,6 +490,11 @@ impl super::Queue { wgt::ExternalImageSource::OffscreenCanvas(_) => unreachable!(), } } + + unsafe { + gl.pixel_store_i32(UNPACK_FLIP_Y_WEBGL, 0); + gl.pixel_store_i32(UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0); + } } C::CopyTextureToTexture { src, diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index 2c9189f6e0..911dcd04db 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -405,8 +405,9 @@ pub trait CommandEncoder: Send + Sync + fmt::Debug { #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] unsafe fn copy_external_image_to_texture( &mut self, - src: &wgt::ExternalImageSource, + src: &wgt::ImageCopyExternalImage, dst: &A::Texture, + dst_premultiplication: bool, regions: T, ) where T: Iterator; diff --git a/wgpu/tests/3x3_colors.png b/wgpu/tests/3x3_colors.png index c9b0f1f4c5b96f31c0daf180e3edf0feafd6651b..082158392aaaa091f59dde700636304c4840af49 100644 GIT binary patch delta 67 zcmc}{pCD<+#=yW3rm=NDkdpIsaSY+O&U%oMk%57uL80p2>SvM;?t<&Gyq!6ZF}uCv Ve{5oXl^dv%!PC{xWt~$(6991}6Q}?H delta 85 zcmWH~oFEy%#K6FC(@W_Ekm4-xh%9Dc&{GCs#)_r(Wk5k`PZ!4!j_a%k85tQEI1Ve^ kv!C!$?SSBpRXr`X?X{elVRLrO1}bClboFyt=akR{0Ifk7s{jB1 diff --git a/wgpu/tests/external_texture.rs b/wgpu/tests/external_texture.rs index 9d76e27890..e9e056805d 100644 --- a/wgpu/tests/external_texture.rs +++ b/wgpu/tests/external_texture.rs @@ -45,6 +45,10 @@ async fn image_bitmap_import() { 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. + FlipY, + // Sets the premultiplied alpha flag. Deals with global state on GLES, so run before other tests to ensure it's reset. + Premultiplied, // 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 @@ -62,6 +66,8 @@ async fn image_bitmap_import() { } let cases = [ TestCase::Normal, + TestCase::FlipY, + TestCase::Premultiplied, TestCase::TrimLeft, TestCase::TrimRight, TestCase::SlideRight, @@ -77,10 +83,14 @@ async fn image_bitmap_import() { 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; + // The layer the external image's data should end up in. + let mut dest_premultiplied = false; // Size of the external copy let mut copy_size = wgpu::Extent3d { width: 3, @@ -98,6 +108,25 @@ async fn image_bitmap_import() { let mut correct = true; match case { TestCase::Normal => {} + TestCase::FlipY => { + 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 => { + 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::TrimLeft => { valid = ctx .adapter_downlevel_capabilities @@ -173,7 +202,7 @@ async fn image_bitmap_import() { &wgpu::ImageCopyExternalImage { source: wgpu::ExternalImageSource::ImageBitmap(image_bitmap.clone()), origin: src_origin, - flip_y: false, + flip_y: src_flip_y, }, wgpu::ImageCopyTextureTagged { texture: &texture, @@ -181,7 +210,7 @@ async fn image_bitmap_import() { origin: dest_origin, aspect: wgpu::TextureAspect::All, color_space: wgpu::PredefinedColorSpace::Srgb, - premultiplied_alpha: false, + premultiplied_alpha: dest_premultiplied, }, copy_size, ); From 41fc00e787254379cd6890bbfb5caa39b07c5ba6 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Wed, 11 Jan 2023 16:38:13 -0500 Subject: [PATCH 12/18] Prevent premultiplication on image import --- wgpu-hal/src/gles/queue.rs | 19 ++++++++++++------- wgpu/tests/external_texture.rs | 19 +++++++++++++++++-- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index 02626d77a1..f82c4cd81e 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -388,11 +388,12 @@ impl super::Queue { const UNPACK_PREMULTIPLY_ALPHA_WEBGL: u32 = 0x9241; unsafe { - gl.pixel_store_i32(UNPACK_FLIP_Y_WEBGL, src.flip_y as i32); - gl.pixel_store_i32( - UNPACK_PREMULTIPLY_ALPHA_WEBGL, - dst_premultiplication as i32, - ); + 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)) }; @@ -492,8 +493,12 @@ impl super::Queue { } unsafe { - gl.pixel_store_i32(UNPACK_FLIP_Y_WEBGL, 0); - gl.pixel_store_i32(UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0); + 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 { diff --git a/wgpu/tests/external_texture.rs b/wgpu/tests/external_texture.rs index e9e056805d..4391d59fd4 100644 --- a/wgpu/tests/external_texture.rs +++ b/wgpu/tests/external_texture.rs @@ -18,9 +18,24 @@ async fn image_bitmap_import() { let blob = web_sys::Blob::new_with_u8_array_sequence(&array).unwrap(); // Parse the image from the blob - let image_bitmap_promise = web_sys::window() + let image_bitmap_function: js_sys::Function = web_sys::window() .unwrap() - .create_image_bitmap_with_blob(&blob) + .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 From 0096e3dbd45b803ad1847cfaa6b44c3236e12ad3 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Wed, 11 Jan 2023 19:24:06 -0500 Subject: [PATCH 13/18] Clear up downlevel requirements --- wgpu-core/src/device/queue.rs | 13 +- wgpu-hal/src/dx11/adapter.rs | 3 +- wgpu-hal/src/gles/adapter.rs | 4 + wgpu-hal/src/gles/queue.rs | 4 +- wgpu-hal/src/vulkan/adapter.rs | 3 +- wgpu-info/src/main.rs | 2 +- wgpu-types/src/lib.rs | 16 +- wgpu/Cargo.toml | 3 +- wgpu/tests/external_texture.rs | 385 ++++++++++++++++++--------------- 9 files changed, 249 insertions(+), 184 deletions(-) diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index 843bde1377..03630f41fe 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -827,9 +827,16 @@ impl Global { return Ok(()); } - if matches!(source.source, wgt::ExternalImageSource::OffscreenCanvas(_)) - || source.origin != wgt::Origin2d::ZERO - { + 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; + 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)?; diff --git a/wgpu-hal/src/dx11/adapter.rs b/wgpu-hal/src/dx11/adapter.rs index 6e14b42f5a..13b3e92cf5 100644 --- a/wgpu-hal/src/dx11/adapter.rs +++ b/wgpu-hal/src/dx11/adapter.rs @@ -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( diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 833ae36d2d..8f08f8f1c7 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -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, diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index f82c4cd81e..cdaa6a9a41 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -384,8 +384,8 @@ impl super::Queue { dst_premultiplication, ref copy, } => { - const UNPACK_FLIP_Y_WEBGL: u32 = 0x9240; - const UNPACK_PREMULTIPLY_ALPHA_WEBGL: u32 = 0x9241; + 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 { diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index a6571e678c..036f0d776f 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -328,7 +328,8 @@ impl PhysicalDeviceFeatures { | Df::WEBGPU_TEXTURE_FORMAT_SUPPORT | Df::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED | Df::UNRESTRICTED_INDEX_BUFFER - | Df::INDIRECT_EXECUTION; + | Df::INDIRECT_EXECUTION + | 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); diff --git a/wgpu-info/src/main.rs b/wgpu-info/src/main.rs index cb1cbb3f96..cf8620e65a 100644 --- a/wgpu-info/src/main.rs +++ b/wgpu-info/src/main.rs @@ -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)); } } } diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 7de3a37242..c39983920c 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -1140,11 +1140,15 @@ bitflags::bitflags! { /// Corresponds to Vulkan's `VkPhysicalDeviceFeatures.depthBiasClamp` const DEPTH_BIAS_CLAMP = 1 << 18; - /// Supports the following on `Queue::copy_external_image_to_texture`: - /// - Copying from an OffscreenCanvas - /// - Non-zero [`ImageCopyExternalImage::origin`]. - /// - /// WebGL doesn't support this. + /// 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 << 19; } } @@ -5159,7 +5163,7 @@ unsafe impl Sync for ExternalImageSource {} /// /// Corresponds to [HTML Canvas `PredefinedColorSpace`]( /// https://html.spec.whatwg.org/multipage/canvas.html#predefinedcolorspace). -#[derive(Copy, Clone, Debug)] +#[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"))] diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 97438e8e7c..61b65d8e18 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -338,5 +338,6 @@ web-sys = { workspace = true, features = [ "RequestMode", "Request", "Response", - "WebGl2RenderingContext" + "WebGl2RenderingContext", + "CanvasRenderingContext2d" ] } diff --git a/wgpu/tests/external_texture.rs b/wgpu/tests/external_texture.rs index 4391d59fd4..f0d48a1c29 100644 --- a/wgpu/tests/external_texture.rs +++ b/wgpu/tests/external_texture.rs @@ -5,6 +5,7 @@ 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() { @@ -18,6 +19,10 @@ async fn image_bitmap_import() { 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") @@ -50,19 +55,46 @@ async fn image_bitmap_import() { 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)] + #[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, // Set both the input offset and output offset to 1 in x, so the first column is omitted. TrimLeft, @@ -79,6 +111,10 @@ async fn image_bitmap_import() { // 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, @@ -93,196 +129,207 @@ async fn image_bitmap_import() { ]; initialize_test(TestParameters::default(), |ctx| { - 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; - // The layer the external image's data should end up in. - 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; + 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; + // The layer the external image's data should end up in. + 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 => { - 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; + // 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 = false; + 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 => { - 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::Premultiplied => { + valid = matches!(source, wgt::ExternalImageSource::HTMLCanvasElement(_)); + 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::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::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::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() { + 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[(x, y)].0 = raw_image[(x - 1, y)].0; + raw_image[(0, y)].0 = [0; 4]; } } - 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; } } - 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, - }); - - fail_if(&ctx.device, !valid, || { - ctx.queue.copy_external_image_to_texture( - &wgpu::ImageCopyExternalImage { - source: wgpu::ExternalImageSource::ImageBitmap(image_bitmap.clone()), - origin: src_origin, - flip_y: src_flip_y, + 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, }, - wgpu::ImageCopyTextureTagged { + 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, + }); + + 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: wgpu::PredefinedColorSpace::Srgb, + 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: dest_origin, + origin: wgpu::Origin3d { + x: 0, + y: 0, + z: dest_data_layer, + }, aspect: wgpu::TextureAspect::All, - color_space: wgpu::PredefinedColorSpace::Srgb, - 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, + wgpu::ImageCopyBuffer { + buffer: &readback_buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(NonZeroU32::new(256).unwrap()), + rows_per_image: None, + }, }, - 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, }, - }, - 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); + 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(); + 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(); + // 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:?}"); - } else { - assert_ne!(raw_image, gpu_image_cropped, "Failed on test case {case:?}"); + 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:?}" + ); + } } } }) From 51028c817a22b5d4cc03a52cd57ccfccceee555c Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Wed, 11 Jan 2023 19:29:47 -0500 Subject: [PATCH 14/18] Add color space test --- wgpu/tests/external_texture.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/wgpu/tests/external_texture.rs b/wgpu/tests/external_texture.rs index f0d48a1c29..819a0da440 100644 --- a/wgpu/tests/external_texture.rs +++ b/wgpu/tests/external_texture.rs @@ -96,6 +96,11 @@ async fn image_bitmap_import() { // // 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 @@ -119,6 +124,7 @@ async fn image_bitmap_import() { TestCase::Normal, TestCase::FlipY, TestCase::Premultiplied, + TestCase::ColorSpace, TestCase::TrimLeft, TestCase::TrimRight, TestCase::SlideRight, @@ -141,7 +147,9 @@ async fn image_bitmap_import() { let mut dest_origin = wgpu::Origin3d::ZERO; // The layer the external image's data should end up in. let mut dest_data_layer = 0; - // The layer the external image's data should end up in. + // 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 { @@ -161,7 +169,7 @@ async fn image_bitmap_import() { match case { TestCase::Normal => {} TestCase::FlipY => { - valid = false; + valid = !matches!(source, wgt::ExternalImageSource::ImageBitmap(_)); src_flip_y = true; for x in 0..3 { let top = raw_image[(x, 0)]; @@ -171,7 +179,7 @@ async fn image_bitmap_import() { } } TestCase::Premultiplied => { - valid = matches!(source, wgt::ExternalImageSource::HTMLCanvasElement(_)); + 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); @@ -181,6 +189,16 @@ async fn image_bitmap_import() { 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 @@ -263,7 +281,7 @@ async fn image_bitmap_import() { mip_level: 0, origin: dest_origin, aspect: wgpu::TextureAspect::All, - color_space: wgpu::PredefinedColorSpace::Srgb, + color_space: dest_color_space, premultiplied_alpha: dest_premultiplied, }, copy_size, From 3886b8ba4841d91b13fd91d80ef4a23692298d57 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Wed, 11 Jan 2023 19:36:15 -0500 Subject: [PATCH 15/18] Format --- wgpu-core/src/device/queue.rs | 1 + wgpu-hal/src/gles/queue.rs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index 03630f41fe..12cfe76963 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -831,6 +831,7 @@ impl Global { 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; diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index cdaa6a9a41..e57fecabbe 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -384,8 +384,10 @@ impl super::Queue { 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; + 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 { From d75c99bd40463f11f2e7eb1ed80ca0a1bdcd225f Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Mon, 23 Jan 2023 18:14:54 -0500 Subject: [PATCH 16/18] Update wgpu-hal/src/lib.rs Co-authored-by: Teodor Tanasoaia <28601907+teoxoy@users.noreply.github.com> --- wgpu-hal/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index 911dcd04db..e8f898223b 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -398,7 +398,7 @@ pub trait CommandEncoder: Send + Sync + fmt::Debug { where T: Iterator; - /// Copy from an external image to an internal textureanother. + /// 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) From 9b4b47082a423851fb385b774a6ea8620d9e9579 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Mon, 23 Jan 2023 18:46:45 -0500 Subject: [PATCH 17/18] Fixes --- Cargo.lock | 2 +- wgpu-core/src/device/queue.rs | 10 +--------- wgpu-hal/src/gles/command.rs | 1 - wgpu/src/backend/web.rs | 15 ++++++++------- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe080f1f09..b8e6632ff5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1049,7 +1049,7 @@ checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" [[package]] name = "glow" version = "0.11.2" -source = "git+https://github.com/cwfitzgerald/glow?rev=0ab1b126aed6f6dacc9b3e11fb58bdc8d1b40b9a#0ab1b126aed6f6dacc9b3e11fb58bdc8d1b40b9a" +source = "git+https://github.com/grovesNL/glow?rev=d7037ef557e70ade837ae9e3c25122069bf88474#d7037ef557e70ade837ae9e3c25122069bf88474" dependencies = [ "js-sys", "slotmap", diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index 12cfe76963..41e69e60e6 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -849,7 +849,7 @@ impl Global { 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, texture_format) = + 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) { @@ -921,14 +921,6 @@ impl Global { &size, )?; - if !conv::is_valid_copy_dst_texture_format(texture_format, destination.aspect) { - return Err(TransferError::CopyToForbiddenTextureFormat { - format: texture_format, - aspect: destination.aspect, - } - .into()); - } - let mut trackers = device.trackers.lock(); let encoder = device.pending_writes.activate(); diff --git a/wgpu-hal/src/gles/command.rs b/wgpu-hal/src/gles/command.rs index b6ea4cddb2..cdf788b8c2 100644 --- a/wgpu-hal/src/gles/command.rs +++ b/wgpu-hal/src/gles/command.rs @@ -321,7 +321,6 @@ impl crate::CommandEncoder for super::CommandEncoder { { let (dst_raw, dst_target) = dst.inner.as_native(); for copy in regions { - // TODO: Clamp size? self.cmd_buffer .commands .push(C::CopyExternalImageToTexture { diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index 0b1fcb10b9..b4019fffe4 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -412,12 +412,6 @@ fn map_vertex_step_mode(mode: wgt::VertexStepMode) -> web_sys::GpuVertexStepMode } } -// fn map_extent_2d(extent: wgt::Extent2d) -> web_sys::GpuExtent2dDict { -// let mut mapped = web_sys::GpuExtent2dDict::new(extent.width); -// mapped.height(extent.height); -// mapped -// } - fn map_extent_3d(extent: wgt::Extent3d) -> web_sys::GpuExtent3dDict { let mut mapped = web_sys::GpuExtent3dDict::new(extent.width); mapped.height(extent.height); @@ -425,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); @@ -493,7 +494,7 @@ fn map_external_texture_copy_view( view: &crate::ImageCopyExternalImage, ) -> web_sys::GpuImageCopyExternalImage { let mut mapped = web_sys::GpuImageCopyExternalImage::new(&view.source); - // mapped.origin(&map_extent_2d(view.extent)); + mapped.origin(&map_origin_2d(view.origin)); mapped.flip_y(view.flip_y); mapped } From 68287a489805e825b2ccb29acc57af890d607654 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Mon, 23 Jan 2023 18:54:18 -0500 Subject: [PATCH 18/18] Fix tests --- wgpu/tests/external_texture.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wgpu/tests/external_texture.rs b/wgpu/tests/external_texture.rs index 819a0da440..49640a5174 100644 --- a/wgpu/tests/external_texture.rs +++ b/wgpu/tests/external_texture.rs @@ -267,6 +267,7 @@ async fn image_bitmap_import() { usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], }); fail_if(&ctx.device, !valid, || {