From 8265b3cd404e6246054e7db87a0c381ffcf03ad1 Mon Sep 17 00:00:00 2001 From: Jamie Nicol Date: Tue, 27 May 2025 13:15:03 +0100 Subject: [PATCH 1/4] [wgpu-core] Add ExternalTexture type to wgpu-core `ExternalTexture` will form the basis of wgpu's implementation of WebGPU's `GPUExternalTexture`. [1] The application will be responsible for creating `Texture`(s) and `TextureView`(s) from the external texture source and managing their lifecycle. It may have a single RGBA texture, or it may have multiple textures for separate Y and Cb/Cr planes. It can then create an external texture by calling `create_external_texture()`, providing the texture views and a descriptor. The descriptor provides the following required information: * Whether the texture data is RGBA, or multiplanar or interleaved YCbCr. * The purpoted size of the external texture, which may not match the actual size of the underlying textures. * A matrix for converting from YCbCr to RGBA, if required. * A transform to apply to texture sample coordinates, allowing for rotation and crop rects. The external texture stores a reference to the provided texture views, and additionally owns a `Buffer`. This buffer holds data of the type `ExternalTextureParams`, and will be provided as a uniform buffer to shaders containing external textures. This contains information that will be required by the shaders to handle external textures correctly. Note that attempting to create an external texture will fail unless the `Feature::EXTERNAL_TEXTURE` feature is enabled, which as of yet is not supported by any HAL backends. Additionally add the relevant API to wgpu, implemented for the wgpu-core backend. The web and custom backends are unimplemented. [1] https://www.w3.org/TR/webgpu/#gpuexternaltexture --- .../standalone/custom_backend/src/custom.rs | 8 ++ player/src/lib.rs | 13 ++ wgpu-core/Cargo.toml | 13 +- wgpu-core/src/device/global.rs | 105 +++++++++++++- wgpu-core/src/device/resource.rs | 136 +++++++++++++++++- wgpu-core/src/device/trace.rs | 7 + wgpu-core/src/hub.rs | 7 +- wgpu-core/src/id.rs | 1 + wgpu-core/src/resource.rs | 96 +++++++++++++ wgpu-core/src/track/mod.rs | 2 + wgpu-types/src/lib.rs | 71 +++++++++ wgpu/src/api/device.rs | 14 ++ wgpu/src/api/external_texture.rs | 31 ++++ wgpu/src/api/mod.rs | 2 + wgpu/src/backend/custom.rs | 1 + wgpu/src/backend/webgpu.rs | 27 ++++ wgpu/src/backend/wgpu_core.rs | 49 +++++++ wgpu/src/dispatch.rs | 9 ++ wgpu/src/lib.rs | 28 ++-- 19 files changed, 592 insertions(+), 28 deletions(-) create mode 100644 wgpu/src/api/external_texture.rs diff --git a/examples/standalone/custom_backend/src/custom.rs b/examples/standalone/custom_backend/src/custom.rs index ec57413ece..8ca7df80bd 100644 --- a/examples/standalone/custom_backend/src/custom.rs +++ b/examples/standalone/custom_backend/src/custom.rs @@ -184,6 +184,14 @@ impl DeviceInterface for CustomDevice { unimplemented!() } + fn create_external_texture( + &self, + _desc: &wgpu::ExternalTextureDescriptor<'_>, + _planes: &[&wgpu::TextureView], + ) -> wgpu::custom::DispatchExternalTexture { + unimplemented!() + } + fn create_blas( &self, _desc: &wgpu::CreateBlasDescriptor<'_>, diff --git a/player/src/lib.rs b/player/src/lib.rs index 8b5040c90f..24c520c276 100644 --- a/player/src/lib.rs +++ b/player/src/lib.rs @@ -244,6 +244,19 @@ impl GlobalPlay for wgc::global::Global { Action::DestroyTextureView(id) => { self.texture_view_drop(id).unwrap(); } + Action::CreateExternalTexture { id, desc, planes } => { + let (_, error) = + self.device_create_external_texture(device, &desc, &planes, Some(id)); + if let Some(e) = error { + panic!("{e}"); + } + } + Action::FreeExternalTexture(id) => { + self.external_texture_destroy(id); + } + Action::DestroyExternalTexture(id) => { + self.external_texture_drop(id); + } Action::CreateSampler(id, desc) => { let (_, error) = self.device_create_sampler(device, &desc, Some(id)); if let Some(e) = error { diff --git a/wgpu-core/Cargo.toml b/wgpu-core/Cargo.toml index ee131dc9f8..d4772efa2d 100644 --- a/wgpu-core/Cargo.toml +++ b/wgpu-core/Cargo.toml @@ -72,14 +72,7 @@ observe_locks = ["std", "dep:ron", "serde/serde_derive"] serde = ["dep:serde", "wgpu-types/serde", "arrayvec/serde", "hashbrown/serde"] ## Enable API tracing. -trace = [ - "serde", - "std", - "dep:ron", - "naga/serialize", - "wgpu-types/trace", - "dep:bytemuck", -] +trace = ["serde", "std", "dep:ron", "naga/serialize", "wgpu-types/trace"] ## Enable API replaying replay = ["serde", "naga/deserialize"] @@ -100,7 +93,7 @@ wgsl = ["naga/wgsl-in"] glsl = ["naga/glsl-in"] ## Enable `ShaderModuleSource::SpirV` -spirv = ["naga/spv-in", "dep:bytemuck"] +spirv = ["naga/spv-in"] #! ### Other # -------------------------------------------------------------------- @@ -180,7 +173,7 @@ arrayvec.workspace = true bit-vec.workspace = true bit-set.workspace = true bitflags.workspace = true -bytemuck = { workspace = true, optional = true } +bytemuck.workspace = true document-features.workspace = true hashbrown.workspace = true indexmap.workspace = true diff --git a/wgpu-core/src/device/global.rs b/wgpu-core/src/device/global.rs index 74a750f0de..3b3cc889f5 100644 --- a/wgpu-core/src/device/global.rs +++ b/wgpu-core/src/device/global.rs @@ -178,6 +178,9 @@ impl Global { fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string()))); } + /// Assign `id_in` an error with the given `label`. + /// + /// See [`Self::create_buffer_error`] for more context and explanation. pub fn create_render_bundle_error( &self, id_in: Option, @@ -189,7 +192,7 @@ impl Global { /// Assign `id_in` an error with the given `label`. /// - /// See `create_buffer_error` for more context and explanation. + /// See [`Self::create_buffer_error`] for more context and explanation. pub fn create_texture_error( &self, id_in: Option, @@ -199,6 +202,18 @@ impl Global { fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string()))); } + /// Assign `id_in` an error with the given `label`. + /// + /// See [`Self::create_buffer_error`] for more context and explanation. + pub fn create_external_texture_error( + &self, + id_in: Option, + desc: &resource::ExternalTextureDescriptor, + ) { + let fid = self.hub.external_textures.prepare(id_in); + fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string()))); + } + #[cfg(feature = "replay")] pub fn device_set_buffer_data( &self, @@ -512,6 +527,94 @@ impl Global { Ok(()) } + pub fn device_create_external_texture( + &self, + device_id: DeviceId, + desc: &resource::ExternalTextureDescriptor, + planes: &[id::TextureViewId], + id_in: Option, + ) -> ( + id::ExternalTextureId, + Option, + ) { + profiling::scope!("Device::create_external_texture"); + + let hub = &self.hub; + + let fid = hub.external_textures.prepare(id_in); + + let error = 'error: { + let device = self.hub.devices.get(device_id); + + #[cfg(feature = "trace")] + if let Some(ref mut trace) = *device.trace.lock() { + let planes = Box::from(planes); + trace.add(trace::Action::CreateExternalTexture { + id: fid.id(), + desc: desc.clone(), + planes, + }); + } + + let planes = planes + .iter() + .map(|plane_id| self.hub.texture_views.get(*plane_id).get()) + .collect::, _>>(); + let planes = match planes { + Ok(planes) => planes, + Err(error) => break 'error error.into(), + }; + + let external_texture = match device.create_external_texture(desc, &planes) { + Ok(external_texture) => external_texture, + Err(error) => break 'error error, + }; + + let id = fid.assign(Fallible::Valid(external_texture)); + api_log!("Device::create_external_texture({desc:?}) -> {id:?}"); + + return (id, None); + }; + + let id = fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string()))); + (id, Some(error)) + } + + pub fn external_texture_destroy(&self, external_texture_id: id::ExternalTextureId) { + profiling::scope!("ExternalTexture::destroy"); + api_log!("ExternalTexture::destroy {external_texture_id:?}"); + + let hub = &self.hub; + + let Ok(external_texture) = hub.external_textures.get(external_texture_id).get() else { + // If the external texture is already invalid, there's nothing to do. + return; + }; + + #[cfg(feature = "trace")] + if let Some(trace) = external_texture.device.trace.lock().as_mut() { + trace.add(trace::Action::FreeExternalTexture(external_texture_id)); + } + + external_texture.destroy(); + } + + pub fn external_texture_drop(&self, external_texture_id: id::ExternalTextureId) { + profiling::scope!("ExternalTexture::drop"); + api_log!("ExternalTexture::drop {external_texture_id:?}"); + + let hub = &self.hub; + + let _external_texture = hub.external_textures.remove(external_texture_id); + + #[cfg(feature = "trace")] + if let Ok(external_texture) = _external_texture.get() { + if let Some(t) = external_texture.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyExternalTexture(external_texture_id)); + } + } + } + pub fn device_create_sampler( &self, device_id: DeviceId, diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index 52f0560849..6bcc8b873a 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -41,8 +41,9 @@ use crate::{ pipeline, pool::ResourcePool, resource::{ - self, Buffer, Fallible, Labeled, ParentDevice, QuerySet, RawResourceAccess, Sampler, - StagingBuffer, Texture, TextureView, TextureViewNotRenderableReason, Tlas, TrackingData, + self, Buffer, ExternalTexture, Fallible, Labeled, ParentDevice, QuerySet, + RawResourceAccess, Sampler, StagingBuffer, Texture, TextureView, + TextureViewNotRenderableReason, Tlas, TrackingData, }, resource_log, snatch::{SnatchGuard, SnatchLock, Snatchable}, @@ -75,6 +76,42 @@ pub(crate) struct CommandIndices { pub(crate) next_acceleration_structure_build_command_index: u64, } +/// Parameters provided to shaders via a uniform buffer, describing an +/// ExternalTexture resource binding. +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Zeroable, bytemuck::Pod)] +pub struct ExternalTextureParams { + /// 4x4 column-major matrix with which to convert sampled YCbCr values + /// to RGBA. + /// This is ignored when `num_planes` is 1. + pub yuv_conversion_matrix: [f32; 16], + /// 3x2 column-major matrix with which to multiply texture coordinates + /// prior to sampling from the external texture. + pub sample_transform: [f32; 6], + pub load_transform: [f32; 6], + /// Size of the external texture. This value should be returned by size + /// queries in shader code. Note that this may not match the dimensions of + /// the underlying texture(s). A value of [0, 0] indicates that the actual + /// size of plane 0 should be used. + pub size: [u32; 2], + /// Number of planes. 1 indicates a single RGBA plane. 2 indicates a Y + /// plane and an interleaved CbCr plane. 3 indicates separate Y, Cb, and Cr + /// planes. + pub num_planes: u32, +} + +impl ExternalTextureParams { + pub fn from_desc(desc: &wgt::ExternalTextureDescriptor) -> Self { + Self { + yuv_conversion_matrix: desc.yuv_conversion_matrix, + size: [desc.width, desc.height], + sample_transform: desc.sample_transform, + load_transform: desc.load_transform, + num_planes: desc.num_planes() as u32, + } + } +} + /// Structure describing a logical device. Some members are internally mutable, /// stored behind mutexes. pub struct Device { @@ -1550,6 +1587,101 @@ impl Device { Ok(view) } + pub(crate) fn create_external_texture( + self: &Arc, + desc: &resource::ExternalTextureDescriptor, + planes: &[Arc], + ) -> Result, resource::CreateExternalTextureError> { + use resource::CreateExternalTextureError; + self.require_features(wgt::Features::EXTERNAL_TEXTURE)?; + self.check_is_valid()?; + + if desc.num_planes() != planes.len() { + return Err(CreateExternalTextureError::IncorrectPlaneCount { + format: desc.format, + expected: desc.num_planes(), + provided: planes.len(), + }); + } + + let planes = planes + .iter() + .enumerate() + .map(|(i, plane)| { + if plane.samples != 1 { + return Err(CreateExternalTextureError::InvalidPlaneMultisample( + plane.samples, + )); + } + + let sample_type = plane + .desc + .format + .sample_type(Some(plane.desc.range.aspect), Some(self.features)) + .unwrap(); + if !matches!(sample_type, TextureSampleType::Float { filterable: true }) { + return Err(CreateExternalTextureError::InvalidPlaneSampleType { + format: plane.desc.format, + sample_type, + }); + } + + if plane.desc.dimension != TextureViewDimension::D2 { + return Err(CreateExternalTextureError::InvalidPlaneDimension( + plane.desc.dimension, + )); + } + + let expected_components = match desc.format { + wgt::ExternalTextureFormat::Rgba => 4, + wgt::ExternalTextureFormat::Nv12 => match i { + 0 => 1, + 1 => 2, + _ => unreachable!(), + }, + wgt::ExternalTextureFormat::Yu12 => 1, + }; + if plane.desc.format.components() != expected_components { + return Err(CreateExternalTextureError::InvalidPlaneFormat { + format: desc.format, + plane: i, + expected: expected_components, + provided: plane.desc.format, + }); + } + + plane.check_usage(wgt::TextureUsages::TEXTURE_BINDING)?; + Ok(plane.clone()) + }) + .collect::>()?; + + let params_data = ExternalTextureParams::from_desc(desc); + let label = desc.label.as_ref().map(|l| alloc::format!("{l} params")); + let params_desc = resource::BufferDescriptor { + label: label.map(Cow::Owned), + size: size_of_val(¶ms_data) as wgt::BufferAddress, + usage: wgt::BufferUsages::UNIFORM | wgt::BufferUsages::COPY_DST, + mapped_at_creation: false, + }; + let params = self.create_buffer(¶ms_desc)?; + self.get_queue().unwrap().write_buffer( + Fallible::Valid(params.clone()), + 0, + bytemuck::bytes_of(¶ms_data), + )?; + + let external_texture = ExternalTexture { + device: self.clone(), + planes, + params, + label: desc.label.to_string(), + tracking_data: TrackingData::new(self.tracker_indices.external_textures.clone()), + }; + let external_texture = Arc::new(external_texture); + + Ok(external_texture) + } + pub(crate) fn create_sampler( self: &Arc, desc: &resource::SamplerDescriptor, diff --git a/wgpu-core/src/device/trace.rs b/wgpu-core/src/device/trace.rs index 220a2406df..602264b5c3 100644 --- a/wgpu-core/src/device/trace.rs +++ b/wgpu-core/src/device/trace.rs @@ -58,6 +58,13 @@ pub enum Action<'a> { desc: crate::resource::TextureViewDescriptor<'a>, }, DestroyTextureView(id::TextureViewId), + CreateExternalTexture { + id: id::ExternalTextureId, + desc: crate::resource::ExternalTextureDescriptor<'a>, + planes: alloc::boxed::Box<[id::TextureViewId]>, + }, + FreeExternalTexture(id::ExternalTextureId), + DestroyExternalTexture(id::ExternalTextureId), CreateSampler(id::SamplerId, crate::resource::SamplerDescriptor<'a>), DestroySampler(id::SamplerId), GetSurfaceTexture { diff --git a/wgpu-core/src/hub.rs b/wgpu-core/src/hub.rs index 398f30d7b5..840e2aa004 100644 --- a/wgpu-core/src/hub.rs +++ b/wgpu-core/src/hub.rs @@ -111,7 +111,8 @@ use crate::{ pipeline::{ComputePipeline, PipelineCache, RenderPipeline, ShaderModule}, registry::{Registry, RegistryReport}, resource::{ - Blas, Buffer, Fallible, QuerySet, Sampler, StagingBuffer, Texture, TextureView, Tlas, + Blas, Buffer, ExternalTexture, Fallible, QuerySet, Sampler, StagingBuffer, Texture, + TextureView, Tlas, }, }; @@ -134,6 +135,7 @@ pub struct HubReport { pub buffers: RegistryReport, pub textures: RegistryReport, pub texture_views: RegistryReport, + pub external_textures: RegistryReport, pub samplers: RegistryReport, } @@ -183,6 +185,7 @@ pub struct Hub { pub(crate) staging_buffers: Registry, pub(crate) textures: Registry>, pub(crate) texture_views: Registry>, + pub(crate) external_textures: Registry>, pub(crate) samplers: Registry>, pub(crate) blas_s: Registry>, pub(crate) tlas_s: Registry>, @@ -209,6 +212,7 @@ impl Hub { staging_buffers: Registry::new(), textures: Registry::new(), texture_views: Registry::new(), + external_textures: Registry::new(), samplers: Registry::new(), blas_s: Registry::new(), tlas_s: Registry::new(), @@ -234,6 +238,7 @@ impl Hub { buffers: self.buffers.generate_report(), textures: self.textures.generate_report(), texture_views: self.texture_views.generate_report(), + external_textures: self.external_textures.generate_report(), samplers: self.samplers.generate_report(), } } diff --git a/wgpu-core/src/id.rs b/wgpu-core/src/id.rs index 75ac193596..ca864f1d1a 100644 --- a/wgpu-core/src/id.rs +++ b/wgpu-core/src/id.rs @@ -261,6 +261,7 @@ ids! { pub type StagingBufferId StagingBuffer; pub type TextureViewId TextureView; pub type TextureId Texture; + pub type ExternalTextureId ExternalTexture; pub type SamplerId Sampler; pub type BindGroupLayoutId BindGroupLayout; pub type PipelineLayoutId PipelineLayout; diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index afdf73e3f0..b316f68ff5 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -1795,6 +1795,102 @@ crate::impl_parent_device!(TextureView); crate::impl_storage_item!(TextureView); crate::impl_trackable!(TextureView); +pub type ExternalTextureDescriptor<'a> = wgt::ExternalTextureDescriptor>; + +#[derive(Debug)] +pub struct ExternalTexture { + pub(crate) device: Arc, + /// Between 1 and 3 (inclusive) planes of texture data. + #[allow(dead_code)] + pub(crate) planes: arrayvec::ArrayVec, 3>, + /// Buffer containing a [`crate::device::resource::ExternalTextureParams`] + /// describing the external texture. + #[allow(dead_code)] + pub(crate) params: Arc, + /// The `label` from the descriptor used to create the resource. + pub(crate) label: String, + pub(crate) tracking_data: TrackingData, +} + +impl Drop for ExternalTexture { + fn drop(&mut self) { + resource_log!("Destroy raw {}", self.error_ident()); + } +} + +impl ExternalTexture { + pub(crate) fn destroy(self: &Arc) { + self.params.destroy(); + } +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum CreateExternalTextureError { + #[error(transparent)] + Device(#[from] DeviceError), + #[error(transparent)] + MissingFeatures(#[from] MissingFeatures), + #[error(transparent)] + InvalidResource(#[from] InvalidResourceError), + #[error(transparent)] + CreateBuffer(#[from] CreateBufferError), + #[error(transparent)] + QueueWrite(#[from] queue::QueueWriteError), + #[error("External texture format {format:?} expects {expected} planes, but given {provided}")] + IncorrectPlaneCount { + format: wgt::ExternalTextureFormat, + expected: usize, + provided: usize, + }, + #[error("External texture planes cannot be multisampled, but given view with samples = {0}")] + InvalidPlaneMultisample(u32), + #[error("External texture planes expect a filterable float sample type, but given view with format {format:?} (sample type {sample_type:?})")] + InvalidPlaneSampleType { + format: wgt::TextureFormat, + sample_type: wgt::TextureSampleType, + }, + #[error("External texture planes expect 2D dimension, but given view with dimension = {0:?}")] + InvalidPlaneDimension(wgt::TextureViewDimension), + #[error(transparent)] + MissingTextureUsage(#[from] MissingTextureUsageError), + #[error("External texture format {format:?} plane {plane} expects format with {expected} components but given view with format {provided:?} ({} components)", + provided.components())] + InvalidPlaneFormat { + format: wgt::ExternalTextureFormat, + plane: usize, + expected: u8, + provided: wgt::TextureFormat, + }, +} + +impl WebGpuError for CreateExternalTextureError { + fn webgpu_error_type(&self) -> ErrorType { + let e: &dyn WebGpuError = match self { + CreateExternalTextureError::Device(e) => e, + CreateExternalTextureError::MissingFeatures(e) => e, + CreateExternalTextureError::InvalidResource(e) => e, + CreateExternalTextureError::CreateBuffer(e) => e, + CreateExternalTextureError::QueueWrite(e) => e, + CreateExternalTextureError::MissingTextureUsage(e) => e, + CreateExternalTextureError::IncorrectPlaneCount { .. } + | CreateExternalTextureError::InvalidPlaneMultisample(_) + | CreateExternalTextureError::InvalidPlaneSampleType { .. } + | CreateExternalTextureError::InvalidPlaneDimension(_) + | CreateExternalTextureError::InvalidPlaneFormat { .. } => { + return ErrorType::Validation + } + }; + e.webgpu_error_type() + } +} + +crate::impl_resource_type!(ExternalTexture); +crate::impl_labeled!(ExternalTexture); +crate::impl_parent_device!(ExternalTexture); +crate::impl_storage_item!(ExternalTexture); +crate::impl_trackable!(ExternalTexture); + /// Describes a [`Sampler`] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/wgpu-core/src/track/mod.rs b/wgpu-core/src/track/mod.rs index 52c68db994..9b4ca31731 100644 --- a/wgpu-core/src/track/mod.rs +++ b/wgpu-core/src/track/mod.rs @@ -225,6 +225,7 @@ pub(crate) struct TrackerIndexAllocators { pub buffers: Arc, pub textures: Arc, pub texture_views: Arc, + pub external_textures: Arc, pub samplers: Arc, pub bind_groups: Arc, pub compute_pipelines: Arc, @@ -241,6 +242,7 @@ impl TrackerIndexAllocators { buffers: Arc::new(SharedTrackerIndexAllocator::new()), textures: Arc::new(SharedTrackerIndexAllocator::new()), texture_views: Arc::new(SharedTrackerIndexAllocator::new()), + external_textures: Arc::new(SharedTrackerIndexAllocator::new()), samplers: Arc::new(SharedTrackerIndexAllocator::new()), bind_groups: Arc::new(SharedTrackerIndexAllocator::new()), compute_pipelines: Arc::new(SharedTrackerIndexAllocator::new()), diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index e05822736a..256e5fa457 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -6202,6 +6202,77 @@ impl TextureDescriptor { } } +/// Format of an `ExternalTexture`. This indicates the number of underlying +/// planes used by the `ExternalTexture` as well as each plane's format. +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ExternalTextureFormat { + /// Single [`TextureFormat::Rgba8Unorm`] or [`TextureFormat::Bgra8Unorm`] format plane. + Rgba, + /// [`TextureFormat::R8Unorm`] Y plane, and [`TextureFormat::Rg8Unorm`] + /// interleaved CbCr plane. + Nv12, + /// Separate [`TextureFormat::R8Unorm`] Y, Cb, and Cr planes. + Yu12, +} + +/// Describes an [`ExternalTexture`](../wgpu/struct.ExternalTexture.html). +/// +/// Corresponds to [WebGPU `GPUExternalTextureDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuexternaltexturedescriptor). +#[repr(C)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct ExternalTextureDescriptor { + /// Debug label of the external texture. This will show up in graphics + /// debuggers for easy identification. + pub label: L, + /// Width of the external texture. Note that both this and `height` may + /// not match the dimensions of the underlying texture(s). This could be + /// due to a crop rect or rotation. + pub width: u32, + /// Height of the external texture. + pub height: u32, + /// Format of the external texture. + pub format: ExternalTextureFormat, + /// 4x4 column-major matrix with which to convert sampled YCbCr values + /// to RGBA. + /// This is ignored when `format` is [`ExternalTextureFormat::Rgba`]. + pub yuv_conversion_matrix: [f32; 16], + /// 3x2 column-major matrix with which to multiply normalized texture + /// coordinates prior to sampling from the external texture. + pub sample_transform: [f32; 6], + /// 3x2 column-major matrix with which to multiply unnormalized texture + /// coordinates prior to loading from the external texture. + pub load_transform: [f32; 6], +} + +impl ExternalTextureDescriptor { + /// Takes a closure and maps the label of the external texture descriptor into another. + #[must_use] + pub fn map_label(&self, fun: impl FnOnce(&L) -> K) -> ExternalTextureDescriptor { + ExternalTextureDescriptor { + label: fun(&self.label), + width: self.width, + height: self.height, + format: self.format, + yuv_conversion_matrix: self.yuv_conversion_matrix, + sample_transform: self.sample_transform, + load_transform: self.load_transform, + } + } + + /// The number of underlying planes used by the external texture. + pub fn num_planes(&self) -> usize { + match self.format { + ExternalTextureFormat::Rgba => 1, + ExternalTextureFormat::Nv12 => 2, + ExternalTextureFormat::Yu12 => 3, + } + } +} + /// Describes a `Sampler`. /// /// For use with `Device::create_sampler`. diff --git a/wgpu/src/api/device.rs b/wgpu/src/api/device.rs index b5544b69af..2a192e2b3b 100644 --- a/wgpu/src/api/device.rs +++ b/wgpu/src/api/device.rs @@ -326,6 +326,20 @@ impl Device { } } + /// Creates a new [`ExternalTexture`]. + #[must_use] + pub fn create_external_texture( + &self, + desc: &ExternalTextureDescriptor<'_>, + planes: &[&TextureView], + ) -> ExternalTexture { + let external_texture = self.inner.create_external_texture(desc, planes); + + ExternalTexture { + inner: external_texture, + } + } + /// Creates a [`Buffer`] from a wgpu-hal Buffer. /// /// # Types diff --git a/wgpu/src/api/external_texture.rs b/wgpu/src/api/external_texture.rs new file mode 100644 index 0000000000..d41f41c560 --- /dev/null +++ b/wgpu/src/api/external_texture.rs @@ -0,0 +1,31 @@ +use crate::*; + +/// Handle to an external texture on the GPU. +/// +/// It can be created with [`Device::create_external_texture`]. +/// +/// Corresponds to [WebGPU `GPUExternalTexture`](https://gpuweb.github.io/gpuweb/#gpuexternaltexture). +#[derive(Debug, Clone)] +pub struct ExternalTexture { + pub(crate) inner: dispatch::DispatchExternalTexture, +} +#[cfg(send_sync)] +static_assertions::assert_impl_all!(ExternalTexture: Send, Sync); + +crate::cmp::impl_eq_ord_hash_proxy!(ExternalTexture => .inner); + +impl ExternalTexture { + /// Destroy the associated native resources as soon as possible. + pub fn destroy(&self) { + self.inner.destroy(); + } +} + +/// Describes an [`ExternalTexture`]. +/// +/// For use with [`Device::create_external_texture`]. +/// +/// Corresponds to [WebGPU `GPUExternalTextureDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuexternaltexturedescriptor). +pub type ExternalTextureDescriptor<'a> = wgt::ExternalTextureDescriptor>; +static_assertions::assert_impl_all!(ExternalTextureDescriptor<'_>: Send, Sync); diff --git a/wgpu/src/api/mod.rs b/wgpu/src/api/mod.rs index c4804d8b61..8178d2be57 100644 --- a/wgpu/src/api/mod.rs +++ b/wgpu/src/api/mod.rs @@ -32,6 +32,7 @@ mod common_pipeline; mod compute_pass; mod compute_pipeline; mod device; +mod external_texture; mod instance; mod pipeline_cache; mod pipeline_layout; @@ -60,6 +61,7 @@ pub use common_pipeline::*; pub use compute_pass::*; pub use compute_pipeline::*; pub use device::*; +pub use external_texture::*; pub use instance::*; pub use pipeline_cache::*; pub use pipeline_layout::*; diff --git a/wgpu/src/backend/custom.rs b/wgpu/src/backend/custom.rs index f82e1150b2..767281b5c6 100644 --- a/wgpu/src/backend/custom.rs +++ b/wgpu/src/backend/custom.rs @@ -79,6 +79,7 @@ dyn_type!(pub ref struct DynTextureView(dyn TextureViewInterface)); dyn_type!(pub ref struct DynSampler(dyn SamplerInterface)); dyn_type!(pub ref struct DynBuffer(dyn BufferInterface)); dyn_type!(pub ref struct DynTexture(dyn TextureInterface)); +dyn_type!(pub ref struct DynExternalTexture(dyn ExternalTextureInterface)); dyn_type!(pub ref struct DynBlas(dyn BlasInterface)); dyn_type!(pub ref struct DynTlas(dyn TlasInterface)); dyn_type!(pub ref struct DynQuerySet(dyn QuerySetInterface)); diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index deaea79e78..e2ceb57a6f 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -1259,6 +1259,12 @@ pub struct WebTexture { ident: crate::cmp::Identifier, } +#[derive(Debug)] +pub struct WebExternalTexture { + /// Unique identifier for this ExternalTexture. + ident: crate::cmp::Identifier, +} + #[derive(Debug)] pub struct WebBlas { /// Unique identifier for this Blas. @@ -1392,6 +1398,7 @@ impl_send_sync!(WebTextureView); impl_send_sync!(WebSampler); impl_send_sync!(WebBuffer); impl_send_sync!(WebTexture); +impl_send_sync!(WebExternalTexture); impl_send_sync!(WebBlas); impl_send_sync!(WebTlas); impl_send_sync!(WebQuerySet); @@ -1421,6 +1428,7 @@ crate::cmp::impl_eq_ord_hash_proxy!(WebTextureView => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebSampler => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebBuffer => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebTexture => .ident); +crate::cmp::impl_eq_ord_hash_proxy!(WebExternalTexture => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebBlas => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebTlas => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebQuerySet => .ident); @@ -2243,6 +2251,14 @@ impl dispatch::DeviceInterface for WebDevice { .into() } + fn create_external_texture( + &self, + _desc: &crate::ExternalTextureDescriptor<'_>, + _planes: &[&crate::TextureView], + ) -> dispatch::DispatchExternalTexture { + unimplemented!("ExternalTexture not implemented for web"); + } + fn create_blas( &self, _desc: &crate::CreateBlasDescriptor<'_>, @@ -2751,6 +2767,17 @@ impl Drop for WebTexture { } } +impl dispatch::ExternalTextureInterface for WebExternalTexture { + fn destroy(&self) { + unimplemented!("ExternalTexture not implemented for web"); + } +} +impl Drop for WebExternalTexture { + fn drop(&mut self) { + unimplemented!("ExternalTexture not implemented for web"); + } +} + impl dispatch::BlasInterface for WebBlas { fn prepare_compact_async(&self, _callback: BlasCompactCallback) { unimplemented!("Raytracing not implemented for web") diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index fa645f8559..245aa5911e 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -496,6 +496,12 @@ pub struct CoreTextureView { id: wgc::id::TextureViewId, } +#[derive(Debug)] +pub struct CoreExternalTexture { + pub(crate) context: ContextWgpuCore, + id: wgc::id::ExternalTextureId, +} + #[derive(Debug)] pub struct CoreSampler { pub(crate) context: ContextWgpuCore, @@ -728,6 +734,7 @@ crate::cmp::impl_eq_ord_hash_proxy!(CoreTextureView => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreSampler => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreBuffer => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreTexture => .id); +crate::cmp::impl_eq_ord_hash_proxy!(CoreExternalTexture => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreBlas => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreTlas => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreQuerySet => .id); @@ -1472,6 +1479,36 @@ impl dispatch::DeviceInterface for CoreDevice { .into() } + fn create_external_texture( + &self, + desc: &crate::ExternalTextureDescriptor<'_>, + planes: &[&crate::TextureView], + ) -> dispatch::DispatchExternalTexture { + let wgt_desc = desc.map_label(|l| l.map(Borrowed)); + let planes = planes + .iter() + .map(|plane| plane.inner.as_core().id) + .collect::>(); + let (id, error) = self + .context + .0 + .device_create_external_texture(self.id, &wgt_desc, &planes, None); + if let Some(cause) = error { + self.context.handle_error( + &self.error_sink, + cause, + desc.label, + "Device::create_external_texture", + ); + } + + CoreExternalTexture { + context: self.context.clone(), + id, + } + .into() + } + fn create_blas( &self, desc: &crate::CreateBlasDescriptor<'_>, @@ -1938,6 +1975,18 @@ impl Drop for CoreTextureView { } } +impl dispatch::ExternalTextureInterface for CoreExternalTexture { + fn destroy(&self) { + self.context.0.external_texture_destroy(self.id); + } +} + +impl Drop for CoreExternalTexture { + fn drop(&mut self) { + self.context.0.external_texture_drop(self.id); + } +} + impl dispatch::SamplerInterface for CoreSampler {} impl Drop for CoreSampler { diff --git a/wgpu/src/dispatch.rs b/wgpu/src/dispatch.rs index 558e48a112..f7af8c3751 100644 --- a/wgpu/src/dispatch.rs +++ b/wgpu/src/dispatch.rs @@ -157,6 +157,11 @@ pub trait DeviceInterface: CommonTraits { ) -> DispatchPipelineCache; fn create_buffer(&self, desc: &crate::BufferDescriptor<'_>) -> DispatchBuffer; fn create_texture(&self, desc: &crate::TextureDescriptor<'_>) -> DispatchTexture; + fn create_external_texture( + &self, + desc: &crate::ExternalTextureDescriptor<'_>, + planes: &[&crate::TextureView], + ) -> DispatchExternalTexture; fn create_blas( &self, desc: &crate::CreateBlasDescriptor<'_>, @@ -257,6 +262,9 @@ pub trait TextureInterface: CommonTraits { fn destroy(&self); } +pub trait ExternalTextureInterface: CommonTraits { + fn destroy(&self); +} pub trait BlasInterface: CommonTraits { fn prepare_compact_async(&self, callback: BlasCompactCallback); fn ready_for_compaction(&self) -> bool; @@ -840,6 +848,7 @@ dispatch_types! {ref type DispatchTextureView: TextureViewInterface = CoreTextur dispatch_types! {ref type DispatchSampler: SamplerInterface = CoreSampler, WebSampler, DynSampler} dispatch_types! {ref type DispatchBuffer: BufferInterface = CoreBuffer, WebBuffer, DynBuffer} dispatch_types! {ref type DispatchTexture: TextureInterface = CoreTexture, WebTexture, DynTexture} +dispatch_types! {ref type DispatchExternalTexture: ExternalTextureInterface = CoreExternalTexture, WebExternalTexture, DynExternalTexture} dispatch_types! {ref type DispatchBlas: BlasInterface = CoreBlas, WebBlas, DynBlas} dispatch_types! {ref type DispatchTlas: TlasInterface = CoreTlas, WebTlas, DynTlas} dispatch_types! {ref type DispatchQuerySet: QuerySetInterface = CoreQuerySet, WebQuerySet, DynQuerySet} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 8a0a64b145..893d8101bb 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -91,20 +91,20 @@ pub use wgt::{ CommandBufferDescriptor, CompareFunction, CompositeAlphaMode, CopyExternalImageDestInfo, CoreCounters, DepthBiasState, DepthStencilState, DeviceLostReason, DeviceType, DownlevelCapabilities, DownlevelFlags, DownlevelLimits, Dx12BackendOptions, Dx12Compiler, - DxcShaderModel, DynamicOffset, Extent3d, Face, Features, FeaturesWGPU, FeaturesWebGPU, - FilterMode, FrontFace, GlBackendOptions, GlFenceBehavior, Gles3MinorVersion, HalCounters, - ImageSubresourceRange, IndexFormat, InstanceDescriptor, InstanceFlags, InternalCounters, - Limits, MemoryBudgetThresholds, MemoryHints, MultisampleState, NoopBackendOptions, Origin2d, - Origin3d, PipelineStatisticsTypes, PollError, PollStatus, PolygonMode, PowerPreference, - PredefinedColorSpace, PresentMode, PresentationTimestamp, PrimitiveState, PrimitiveTopology, - PushConstantRange, QueryType, RenderBundleDepthStencil, RequestAdapterError, - SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel, ShaderRuntimeChecks, - ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, - SurfaceCapabilities, SurfaceStatus, TexelCopyBufferLayout, TextureAspect, TextureDimension, - TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, TextureSampleType, - TextureTransition, TextureUsages, TextureUses, TextureViewDimension, Trace, VertexAttribute, - VertexFormat, VertexStepMode, WasmNotSend, WasmNotSendSync, WasmNotSync, COPY_BUFFER_ALIGNMENT, - COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT, + DxcShaderModel, DynamicOffset, Extent3d, ExternalTextureFormat, Face, Features, FeaturesWGPU, + FeaturesWebGPU, FilterMode, FrontFace, GlBackendOptions, GlFenceBehavior, Gles3MinorVersion, + HalCounters, ImageSubresourceRange, IndexFormat, InstanceDescriptor, InstanceFlags, + InternalCounters, Limits, MemoryBudgetThresholds, MemoryHints, MultisampleState, + NoopBackendOptions, Origin2d, Origin3d, PipelineStatisticsTypes, PollError, PollStatus, + PolygonMode, PowerPreference, PredefinedColorSpace, PresentMode, PresentationTimestamp, + PrimitiveState, PrimitiveTopology, PushConstantRange, QueryType, RenderBundleDepthStencil, + RequestAdapterError, SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel, + ShaderRuntimeChecks, ShaderStages, StencilFaceState, StencilOperation, StencilState, + StorageTextureAccess, SurfaceCapabilities, SurfaceStatus, TexelCopyBufferLayout, TextureAspect, + TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, + TextureSampleType, TextureTransition, TextureUsages, TextureUses, TextureViewDimension, Trace, + VertexAttribute, VertexFormat, VertexStepMode, WasmNotSend, WasmNotSendSync, WasmNotSync, + COPY_BUFFER_ALIGNMENT, COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT, QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE, VERTEX_ALIGNMENT, }; From 254f685ec3278cd9258a73d0650fe8d163626bd7 Mon Sep 17 00:00:00 2001 From: Jamie Nicol Date: Tue, 27 May 2025 13:15:03 +0100 Subject: [PATCH 2/4] [wgpu-core] Create default ExternalTextureParams buffer In upcoming patches, wgpu will allowing the creation of bind groups with either `TextureView`s or `ExternalTexture`s bound to a `BindingType::ExternalTexture` bind group layout entry. Wgpu-hal and the Naga-generated shaders must be able to handle both of these cases. For external textures they will be provided a uniform buffer containing the external texture's `ExternalTextureParams`. For the texture view case, we must therefore provide the same. To do this, we create a single buffer per device which can be shared between all texture views. We initialize it with the required values in Device::late_init_resources_with_queue(). We know that texture views must have a single RGBA plane, with no rotation or crop-rect. The only thing that can vary between them is their size. We will therefore use the value of [0, 0] in the params buffer to indicate to the shader that it should query the actual texture's size rather than using the value provided in the buffer. --- wgpu-core/src/device/resource.rs | 102 ++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index 6bcc8b873a..ba50a654cb 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -176,6 +176,11 @@ pub struct Device { // Optional so that we can late-initialize this after the queue is created. pub(crate) timestamp_normalizer: OnceCellOrLock, + /// Uniform buffer containing [`ExternalTextureParams`] with values such + /// that a [`TextureView`] bound to a [`wgt::BindingType::ExternalTexture`] + /// binding point will be rendered correctly. Intended to be used as the + /// [`hal::ExternalTextureBinding::params`] field. + pub(crate) default_external_texture_params_buffer: ManuallyDrop>, // needs to be dropped last #[cfg(feature = "trace")] pub(crate) trace: Mutex>, @@ -203,6 +208,10 @@ impl Drop for Device { // SAFETY: We are in the Drop impl and we don't use self.zero_buffer anymore after this point. let zero_buffer = unsafe { ManuallyDrop::take(&mut self.zero_buffer) }; + // SAFETY: We are in the Drop impl and we don't use + // self.default_external_texture_params_buffer anymore after this point. + let default_external_texture_params_buffer = + unsafe { ManuallyDrop::take(&mut self.default_external_texture_params_buffer) }; // SAFETY: We are in the Drop impl and we don't use self.fence anymore after this point. let fence = unsafe { ManuallyDrop::take(&mut self.fence.write()) }; if let Some(indirect_validation) = self.indirect_validation.take() { @@ -213,6 +222,8 @@ impl Drop for Device { } unsafe { self.raw.destroy_buffer(zero_buffer); + self.raw + .destroy_buffer(default_external_texture_params_buffer); self.raw.destroy_fence(fence); } } @@ -292,6 +303,19 @@ impl Device { } .map_err(DeviceError::from_hal)?; + let default_external_texture_params_buffer = unsafe { + raw_device.create_buffer(&hal::BufferDescriptor { + label: hal_label( + Some("(wgpu internal) default external texture params buffer"), + instance_flags, + ), + size: size_of::() as _, + usage: wgt::BufferUses::COPY_DST | wgt::BufferUses::UNIFORM, + memory_flags: hal::MemoryFlags::empty(), + }) + } + .map_err(DeviceError::from_hal)?; + let alignments = adapter.raw.capabilities.alignments.clone(); let downlevel = adapter.raw.capabilities.downlevel.clone(); @@ -317,6 +341,9 @@ impl Device { adapter: adapter.clone(), queue: OnceCellOrLock::new(), zero_buffer: ManuallyDrop::new(zero_buffer), + default_external_texture_params_buffer: ManuallyDrop::new( + default_external_texture_params_buffer, + ), label: desc.label.to_string(), command_allocator, command_indices: RwLock::new( @@ -367,7 +394,78 @@ impl Device { }) } - pub fn late_init_resources_with_queue(&self) -> Result<(), RequestDeviceError> { + /// Initializes [`Device::default_external_texture_params_buffer`] with + /// required values such that a [`TextureView`] bound to a + /// [`wgt::BindingType::ExternalTexture`] binding point will be rendered + /// correctly. + fn init_default_external_texture_params_buffer(self: &Arc) -> Result<(), DeviceError> { + let data = ExternalTextureParams { + #[rustfmt::skip] + yuv_conversion_matrix: [ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0, + ], + size: [0, 0], + #[rustfmt::skip] + sample_transform: [ + 1.0, 0.0, + 0.0, 1.0, + 0.0, 0.0 + ], + #[rustfmt::skip] + load_transform: [ + 1.0, 0.0, + 0.0, 1.0, + 0.0, 0.0 + ], + num_planes: 1, + }; + let mut staging_buffer = + StagingBuffer::new(self, wgt::BufferSize::new(size_of_val(&data) as _).unwrap())?; + staging_buffer.write(bytemuck::bytes_of(&data)); + let staging_buffer = staging_buffer.flush(); + + let params_buffer = self.default_external_texture_params_buffer.as_ref(); + let queue = self.get_queue().unwrap(); + let mut pending_writes = queue.pending_writes.lock(); + + unsafe { + pending_writes + .command_encoder + .transition_buffers(&[hal::BufferBarrier { + buffer: params_buffer, + usage: hal::StateTransition { + from: wgt::BufferUses::MAP_WRITE, + to: wgt::BufferUses::COPY_SRC, + }, + }]); + pending_writes.command_encoder.copy_buffer_to_buffer( + staging_buffer.raw(), + params_buffer, + &[hal::BufferCopy { + src_offset: 0, + dst_offset: 0, + size: staging_buffer.size, + }], + ); + pending_writes.consume(staging_buffer); + pending_writes + .command_encoder + .transition_buffers(&[hal::BufferBarrier { + buffer: params_buffer, + usage: hal::StateTransition { + from: wgt::BufferUses::COPY_DST, + to: wgt::BufferUses::UNIFORM, + }, + }]); + } + + Ok(()) + } + + pub fn late_init_resources_with_queue(self: &Arc) -> Result<(), RequestDeviceError> { let queue = self.get_queue().unwrap(); let timestamp_normalizer = crate::timestamp_normalization::TimestampNormalizer::new( @@ -379,6 +477,8 @@ impl Device { .set(timestamp_normalizer) .unwrap_or_else(|_| panic!("Called late_init_resources_with_queue twice")); + self.init_default_external_texture_params_buffer()?; + Ok(()) } From 51b56a94efdf9e067372c13a90e69e0729fbe769 Mon Sep 17 00:00:00 2001 From: Jamie Nicol Date: Tue, 27 May 2025 13:15:03 +0100 Subject: [PATCH 3/4] [wgpu-core] Allow creation of bind groups containing external textures Adds a `BindingResource` variant for external textures. In core's create_bind_group() implementation, allow binding either external textures or texture views to `BindingType::ExternalTexture` layout entries. In either case, provide HAL with a `hal::ExternalTextureBinding`, consisting of 3 `hal::TextureBinding`s and a `hal::BufferBinding`. In the texture view case we use the device's default params buffer for the buffer. When there are fewer than 3 planes we can simply repeat an existing plane multiple times - the contents of the params buffer will ensure the shader only accesses the correct number of planes anyway. Track the view or external texture in `BindGroupStates` to ensure they remain alive whilst required. And finally, add the corresponding API to wgpu, with an implementation for the wgpu-core backend. --- wgpu-core/src/binding_model.rs | 64 +++++-- wgpu-core/src/device/global.rs | 12 ++ wgpu-core/src/device/resource.rs | 173 ++++++++++++++++-- wgpu-core/src/indirect_validation/dispatch.rs | 2 + wgpu-core/src/indirect_validation/draw.rs | 2 + wgpu-core/src/resource.rs | 2 - wgpu-core/src/timestamp_normalization/mod.rs | 1 + wgpu-core/src/track/mod.rs | 2 + wgpu-hal/examples/halmark/main.rs | 2 + wgpu-hal/examples/ray-traced-triangle/main.rs | 1 + wgpu-hal/src/dynamic/device.rs | 6 + wgpu-hal/src/dynamic/mod.rs | 13 +- wgpu-hal/src/lib.rs | 18 ++ wgpu/src/api/bind_group.rs | 6 + wgpu/src/backend/webgpu.rs | 3 + wgpu/src/backend/wgpu_core.rs | 3 + 16 files changed, 274 insertions(+), 36 deletions(-) diff --git a/wgpu-core/src/binding_model.rs b/wgpu-core/src/binding_model.rs index 656cc208a5..30d8a99a90 100644 --- a/wgpu-core/src/binding_model.rs +++ b/wgpu-core/src/binding_model.rs @@ -21,13 +21,13 @@ use crate::{ device::{ bgl, Device, DeviceError, MissingDownlevelFlags, MissingFeatures, SHADER_STAGE_COUNT, }, - id::{BindGroupLayoutId, BufferId, SamplerId, TextureViewId, TlasId}, + id::{BindGroupLayoutId, BufferId, ExternalTextureId, SamplerId, TextureViewId, TlasId}, init_tracker::{BufferInitTrackerAction, TextureInitTrackerAction}, pipeline::{ComputePipeline, RenderPipeline}, resource::{ - Buffer, DestroyedResourceError, InvalidResourceError, Labeled, MissingBufferUsageError, - MissingTextureUsageError, RawResourceAccess, ResourceErrorIdent, Sampler, TextureView, - Tlas, TrackingData, + Buffer, DestroyedResourceError, ExternalTexture, InvalidResourceError, Labeled, + MissingBufferUsageError, MissingTextureUsageError, RawResourceAccess, ResourceErrorIdent, + Sampler, TextureView, Tlas, TrackingData, }, resource_log, snatch::{SnatchGuard, Snatchable}, @@ -594,8 +594,14 @@ impl BindingTypeMaxCountValidator { /// cbindgen:ignore #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct BindGroupEntry<'a, B = BufferId, S = SamplerId, TV = TextureViewId, TLAS = TlasId> -where +pub struct BindGroupEntry< + 'a, + B = BufferId, + S = SamplerId, + TV = TextureViewId, + TLAS = TlasId, + ET = ExternalTextureId, +> where [BufferBinding]: ToOwned, [S]: ToOwned, [TV]: ToOwned, @@ -608,15 +614,21 @@ where pub binding: u32, #[cfg_attr( feature = "serde", - serde(bound(deserialize = "BindingResource<'a, B, S, TV, TLAS>: Deserialize<'de>")) + serde(bound(deserialize = "BindingResource<'a, B, S, TV, TLAS, ET>: Deserialize<'de>")) )] /// Resource to attach to the binding - pub resource: BindingResource<'a, B, S, TV, TLAS>, + pub resource: BindingResource<'a, B, S, TV, TLAS, ET>, } /// cbindgen:ignore -pub type ResolvedBindGroupEntry<'a> = - BindGroupEntry<'a, Arc, Arc, Arc, Arc>; +pub type ResolvedBindGroupEntry<'a> = BindGroupEntry< + 'a, + Arc, + Arc, + Arc, + Arc, + Arc, +>; /// Describes a group of bindings and the resources to be bound. #[derive(Clone, Debug)] @@ -628,6 +640,7 @@ pub struct BindGroupDescriptor< S = SamplerId, TV = TextureViewId, TLAS = TlasId, + ET = ExternalTextureId, > where [BufferBinding]: ToOwned, [S]: ToOwned, @@ -635,8 +648,8 @@ pub struct BindGroupDescriptor< <[BufferBinding] as ToOwned>::Owned: fmt::Debug, <[S] as ToOwned>::Owned: fmt::Debug, <[TV] as ToOwned>::Owned: fmt::Debug, - [BindGroupEntry<'a, B, S, TV, TLAS>]: ToOwned, - <[BindGroupEntry<'a, B, S, TV, TLAS>] as ToOwned>::Owned: fmt::Debug, + [BindGroupEntry<'a, B, S, TV, TLAS, ET>]: ToOwned, + <[BindGroupEntry<'a, B, S, TV, TLAS, ET>] as ToOwned>::Owned: fmt::Debug, { /// Debug label of the bind group. /// @@ -647,11 +660,12 @@ pub struct BindGroupDescriptor< #[cfg_attr( feature = "serde", serde(bound( - deserialize = "<[BindGroupEntry<'a, B, S, TV, TLAS>] as ToOwned>::Owned: Deserialize<'de>" + deserialize = "<[BindGroupEntry<'a, B, S, TV, TLAS, ET>] as ToOwned>::Owned: Deserialize<'de>" )) )] /// The resources to bind to this bind group. - pub entries: Cow<'a, [BindGroupEntry<'a, B, S, TV, TLAS>]>, + #[allow(clippy::type_complexity)] + pub entries: Cow<'a, [BindGroupEntry<'a, B, S, TV, TLAS, ET>]>, } /// cbindgen:ignore @@ -662,6 +676,7 @@ pub type ResolvedBindGroupDescriptor<'a> = BindGroupDescriptor< Arc, Arc, Arc, + Arc, >; /// Describes a [`BindGroupLayout`]. @@ -1005,8 +1020,14 @@ pub type ResolvedBufferBinding = BufferBinding>; // They're different enough that it doesn't make sense to share a common type #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum BindingResource<'a, B = BufferId, S = SamplerId, TV = TextureViewId, TLAS = TlasId> -where +pub enum BindingResource< + 'a, + B = BufferId, + S = SamplerId, + TV = TextureViewId, + TLAS = TlasId, + ET = ExternalTextureId, +> where [BufferBinding]: ToOwned, [S]: ToOwned, [TV]: ToOwned, @@ -1033,10 +1054,17 @@ where )] TextureViewArray(Cow<'a, [TV]>), AccelerationStructure(TLAS), + ExternalTexture(ET), } -pub type ResolvedBindingResource<'a> = - BindingResource<'a, Arc, Arc, Arc, Arc>; +pub type ResolvedBindingResource<'a> = BindingResource< + 'a, + Arc, + Arc, + Arc, + Arc, + Arc, +>; #[derive(Clone, Debug, Error)] #[non_exhaustive] diff --git a/wgpu-core/src/device/global.rs b/wgpu-core/src/device/global.rs index 3b3cc889f5..a76b24537c 100644 --- a/wgpu-core/src/device/global.rs +++ b/wgpu-core/src/device/global.rs @@ -846,6 +846,7 @@ impl Global { sampler_storage: &Storage>, texture_view_storage: &Storage>, tlas_storage: &Storage>, + external_texture_storage: &Storage>, ) -> Result, binding_model::CreateBindGroupError> { let resolve_buffer = |bb: &BufferBinding| { @@ -877,6 +878,12 @@ impl Global { .get() .map_err(binding_model::CreateBindGroupError::from) }; + let resolve_external_texture = |id: &id::ExternalTextureId| { + external_texture_storage + .get(*id) + .get() + .map_err(binding_model::CreateBindGroupError::from) + }; let resource = match e.resource { BindingResource::Buffer(ref buffer) => { ResolvedBindingResource::Buffer(resolve_buffer(buffer)?) @@ -911,6 +918,9 @@ impl Global { BindingResource::AccelerationStructure(ref tlas) => { ResolvedBindingResource::AccelerationStructure(resolve_tlas(tlas)?) } + BindingResource::ExternalTexture(ref et) => { + ResolvedBindingResource::ExternalTexture(resolve_external_texture(et)?) + } }; Ok(ResolvedBindGroupEntry { binding: e.binding, @@ -923,6 +933,7 @@ impl Global { let texture_view_guard = hub.texture_views.read(); let sampler_guard = hub.samplers.read(); let tlas_guard = hub.tlas_s.read(); + let external_texture_guard = hub.external_textures.read(); desc.entries .iter() .map(|e| { @@ -932,6 +943,7 @@ impl Global { &sampler_guard, &texture_view_guard, &tlas_guard, + &external_texture_guard, ) }) .collect::, _>>() diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index ba50a654cb..b653186e25 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -76,8 +76,8 @@ pub(crate) struct CommandIndices { pub(crate) next_acceleration_structure_build_command_index: u64, } -/// Parameters provided to shaders via a uniform buffer, describing an -/// ExternalTexture resource binding. +/// Parameters provided to shaders via a uniform buffer, describing a +/// [`binding_model::BindingResource::ExternalTexture`] resource binding. #[repr(C)] #[derive(Copy, Clone, bytemuck::Zeroable, bytemuck::Pod)] pub struct ExternalTextureParams { @@ -2612,6 +2612,121 @@ impl Device { Ok(tlas.try_raw(snatch_guard)?) } + fn create_external_texture_binding<'a>( + &'a self, + binding: u32, + decl: &wgt::BindGroupLayoutEntry, + external_texture: &'a Arc, + used: &mut BindGroupStates, + snatch_guard: &'a SnatchGuard, + ) -> Result< + hal::ExternalTextureBinding<'a, dyn hal::DynBuffer, dyn hal::DynTextureView>, + binding_model::CreateBindGroupError, + > { + use crate::binding_model::CreateBindGroupError as Error; + + external_texture.same_device(self)?; + + used.external_textures + .insert_single(external_texture.clone()); + + match decl.ty { + wgt::BindingType::ExternalTexture => {} + _ => { + return Err(Error::WrongBindingType { + binding, + actual: decl.ty, + expected: "ExternalTexture", + }); + } + } + + let planes = (0..3) + .map(|i| { + // We always need 3 bindings. If we have fewer than 3 planes + // just bind plane 0 multiple times. The shader will only + // sample from valid planes anyway. + let plane = external_texture + .planes + .get(i) + .unwrap_or(&external_texture.planes[0]); + let internal_use = wgt::TextureUses::RESOURCE; + used.views.insert_single(plane.clone(), internal_use); + let view = plane.try_raw(snatch_guard)?; + Ok(hal::TextureBinding { + view, + usage: internal_use, + }) + }) + // We can remove this intermediate Vec by using + // array::try_from_fn() above, once it stabilizes. + .collect::, Error>>()?; + let planes = planes.try_into().unwrap(); + + used.buffers + .insert_single(external_texture.params.clone(), wgt::BufferUses::UNIFORM); + let params = hal::BufferBinding { + buffer: external_texture.params.try_raw(snatch_guard)?, + offset: 0, + size: wgt::BufferSize::new(external_texture.params.size), + }; + + Ok(hal::ExternalTextureBinding { planes, params }) + } + + fn create_external_texture_binding_from_view<'a>( + &'a self, + binding: u32, + decl: &wgt::BindGroupLayoutEntry, + view: &'a Arc, + used: &mut BindGroupStates, + snatch_guard: &'a SnatchGuard, + ) -> Result< + hal::ExternalTextureBinding<'a, dyn hal::DynBuffer, dyn hal::DynTextureView>, + binding_model::CreateBindGroupError, + > { + use crate::binding_model::CreateBindGroupError as Error; + + view.same_device(self)?; + + let internal_use = self.texture_use_parameters(binding, decl, view, "SampledTexture")?; + used.views.insert_single(view.clone(), internal_use); + + match decl.ty { + wgt::BindingType::ExternalTexture => {} + _ => { + return Err(Error::WrongBindingType { + binding, + actual: decl.ty, + expected: "ExternalTexture", + }); + } + } + + // We need 3 bindings, so just repeat the same texture view 3 times. + let planes = [ + hal::TextureBinding { + view: view.try_raw(snatch_guard)?, + usage: internal_use, + }, + hal::TextureBinding { + view: view.try_raw(snatch_guard)?, + usage: internal_use, + }, + hal::TextureBinding { + view: view.try_raw(snatch_guard)?, + usage: internal_use, + }, + ]; + let params = hal::BufferBinding { + buffer: self.default_external_texture_params_buffer.as_ref(), + offset: 0, + size: None, + }; + + Ok(hal::ExternalTextureBinding { planes, params }) + } + // This function expects the provided bind group layout to be resolved // (not passing a duplicate) beforehand. pub(crate) fn create_bind_group( @@ -2652,6 +2767,7 @@ impl Device { let mut hal_samplers = Vec::new(); let mut hal_textures = Vec::new(); let mut hal_tlas_s = Vec::new(); + let mut hal_external_textures = Vec::new(); let snatch_guard = self.snatchable_lock.read(); for entry in desc.entries.iter() { let binding = entry.binding; @@ -2718,19 +2834,33 @@ impl Device { (res_index, num_bindings) } - Br::TextureView(ref view) => { - let tb = self.create_texture_binding( - binding, - decl, - view, - &mut used, - &mut used_texture_ranges, - &snatch_guard, - )?; - let res_index = hal_textures.len(); - hal_textures.push(tb); - (res_index, 1) - } + Br::TextureView(ref view) => match decl.ty { + wgt::BindingType::ExternalTexture => { + let et = self.create_external_texture_binding_from_view( + binding, + decl, + view, + &mut used, + &snatch_guard, + )?; + let res_index = hal_external_textures.len(); + hal_external_textures.push(et); + (res_index, 1) + } + _ => { + let tb = self.create_texture_binding( + binding, + decl, + view, + &mut used, + &mut used_texture_ranges, + &snatch_guard, + )?; + let res_index = hal_textures.len(); + hal_textures.push(tb); + (res_index, 1) + } + }, Br::TextureViewArray(ref views) => { let num_bindings = views.len(); Self::check_array_binding(self.features, decl.count, num_bindings)?; @@ -2758,6 +2888,18 @@ impl Device { hal_tlas_s.push(tlas); (res_index, 1) } + Br::ExternalTexture(ref et) => { + let et = self.create_external_texture_binding( + binding, + decl, + et, + &mut used, + &snatch_guard, + )?; + let res_index = hal_external_textures.len(); + hal_external_textures.push(et); + (res_index, 1) + } }; hal_entries.push(hal::BindGroupEntry { @@ -2783,6 +2925,7 @@ impl Device { samplers: &hal_samplers, textures: &hal_textures, acceleration_structures: &hal_tlas_s, + external_textures: &hal_external_textures, }; let raw = unsafe { self.raw().create_bind_group(&hal_desc) } .map_err(|e| self.handle_hal_error(e))?; diff --git a/wgpu-core/src/indirect_validation/dispatch.rs b/wgpu-core/src/indirect_validation/dispatch.rs index 04a283eb4b..7db9f9d8a1 100644 --- a/wgpu-core/src/indirect_validation/dispatch.rs +++ b/wgpu-core/src/indirect_validation/dispatch.rs @@ -241,6 +241,7 @@ impl Dispatch { samplers: &[], textures: &[], acceleration_structures: &[], + external_textures: &[], }; let dst_bind_group = unsafe { device @@ -284,6 +285,7 @@ impl Dispatch { samplers: &[], textures: &[], acceleration_structures: &[], + external_textures: &[], }; unsafe { device diff --git a/wgpu-core/src/indirect_validation/draw.rs b/wgpu-core/src/indirect_validation/draw.rs index bc26eb717f..886fc5bc9b 100644 --- a/wgpu-core/src/indirect_validation/draw.rs +++ b/wgpu-core/src/indirect_validation/draw.rs @@ -140,6 +140,7 @@ impl Draw { samplers: &[], textures: &[], acceleration_structures: &[], + external_textures: &[], }; unsafe { device @@ -690,6 +691,7 @@ fn create_buffer_and_bind_group( samplers: &[], textures: &[], acceleration_structures: &[], + external_textures: &[], }; let bind_group = unsafe { device.create_bind_group(&bind_group_desc) }?; Ok(BufferPoolEntry { buffer, bind_group }) diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index b316f68ff5..e9768798a7 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -1801,11 +1801,9 @@ pub type ExternalTextureDescriptor<'a> = wgt::ExternalTextureDescriptor, /// Between 1 and 3 (inclusive) planes of texture data. - #[allow(dead_code)] pub(crate) planes: arrayvec::ArrayVec, 3>, /// Buffer containing a [`crate::device::resource::ExternalTextureParams`] /// describing the external texture. - #[allow(dead_code)] pub(crate) params: Arc, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, diff --git a/wgpu-core/src/timestamp_normalization/mod.rs b/wgpu-core/src/timestamp_normalization/mod.rs index 651773c389..dd9272069b 100644 --- a/wgpu-core/src/timestamp_normalization/mod.rs +++ b/wgpu-core/src/timestamp_normalization/mod.rs @@ -289,6 +289,7 @@ impl TimestampNormalizer { samplers: &[], textures: &[], acceleration_structures: &[], + external_textures: &[], entries: &[hal::BindGroupEntry { binding: 0, resource_index: 0, diff --git a/wgpu-core/src/track/mod.rs b/wgpu-core/src/track/mod.rs index 9b4ca31731..22f13410ad 100644 --- a/wgpu-core/src/track/mod.rs +++ b/wgpu-core/src/track/mod.rs @@ -438,6 +438,7 @@ impl fmt::Display for InvalidUse { pub(crate) struct BindGroupStates { pub buffers: BufferBindGroupState, pub views: TextureViewBindGroupState, + pub external_textures: StatelessTracker, pub samplers: StatelessTracker, pub acceleration_structures: StatelessTracker, } @@ -447,6 +448,7 @@ impl BindGroupStates { Self { buffers: BufferBindGroupState::new(), views: TextureViewBindGroupState::new(), + external_textures: StatelessTracker::new(), samplers: StatelessTracker::new(), acceleration_structures: StatelessTracker::new(), } diff --git a/wgpu-hal/examples/halmark/main.rs b/wgpu-hal/examples/halmark/main.rs index 8f93cc5860..36675e3b70 100644 --- a/wgpu-hal/examples/halmark/main.rs +++ b/wgpu-hal/examples/halmark/main.rs @@ -464,6 +464,7 @@ impl Example { samplers: &[&sampler], textures: &[texture_binding], acceleration_structures: &[], + external_textures: &[], entries: &[ hal::BindGroupEntry { binding: 0, @@ -499,6 +500,7 @@ impl Example { samplers: &[], textures: &[], acceleration_structures: &[], + external_textures: &[], entries: &[hal::BindGroupEntry { binding: 0, resource_index: 0, diff --git a/wgpu-hal/examples/ray-traced-triangle/main.rs b/wgpu-hal/examples/ray-traced-triangle/main.rs index 4fcb669139..2947d1a601 100644 --- a/wgpu-hal/examples/ray-traced-triangle/main.rs +++ b/wgpu-hal/examples/ray-traced-triangle/main.rs @@ -622,6 +622,7 @@ impl Example { samplers: &[], textures: &[texture_binding], acceleration_structures: &[&tlas], + external_textures: &[], entries: &[ hal::BindGroupEntry { binding: 0, diff --git a/wgpu-hal/src/dynamic/device.rs b/wgpu-hal/src/dynamic/device.rs index fd7c10f254..de66b1619f 100644 --- a/wgpu-hal/src/dynamic/device.rs +++ b/wgpu-hal/src/dynamic/device.rs @@ -345,6 +345,11 @@ impl DynDevice for D { .iter() .map(|a| a.expect_downcast_ref()) .collect(); + let external_textures: Vec<_> = desc + .external_textures + .iter() + .map(|et| et.clone().expect_downcast()) + .collect(); let desc = BindGroupDescriptor { label: desc.label.to_owned(), @@ -354,6 +359,7 @@ impl DynDevice for D { textures: &textures, entries: desc.entries, acceleration_structures: &acceleration_structures, + external_textures: &external_textures, }; unsafe { D::create_bind_group(self, &desc) } diff --git a/wgpu-hal/src/dynamic/mod.rs b/wgpu-hal/src/dynamic/mod.rs index a8dbae94ee..85d8ca0045 100644 --- a/wgpu-hal/src/dynamic/mod.rs +++ b/wgpu-hal/src/dynamic/mod.rs @@ -23,7 +23,8 @@ use wgt::WasmNotSendSync; use crate::{ AccelerationStructureAABBs, AccelerationStructureEntries, AccelerationStructureInstances, AccelerationStructureTriangleIndices, AccelerationStructureTriangleTransform, - AccelerationStructureTriangles, BufferBinding, ProgrammableStage, TextureBinding, + AccelerationStructureTriangles, BufferBinding, ExternalTextureBinding, ProgrammableStage, + TextureBinding, }; /// Base trait for all resources, allows downcasting via [`Any`]. @@ -143,6 +144,16 @@ impl<'a> TextureBinding<'a, dyn DynTextureView> { } } +impl<'a> ExternalTextureBinding<'a, dyn DynBuffer, dyn DynTextureView> { + pub fn expect_downcast( + self, + ) -> ExternalTextureBinding<'a, B, T> { + let planes = self.planes.map(|plane| plane.expect_downcast()); + let params = self.params.expect_downcast(); + ExternalTextureBinding { planes, params } + } +} + impl<'a> ProgrammableStage<'a, dyn DynShaderModule> { fn expect_downcast(self) -> ProgrammableStage<'a, T> { ProgrammableStage { diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index db5269ad4a..1e92bbece3 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -2145,6 +2145,23 @@ impl<'a, T: DynTextureView + ?Sized> Clone for TextureBinding<'a, T> { } } +#[derive(Debug)] +pub struct ExternalTextureBinding<'a, B: DynBuffer + ?Sized, T: DynTextureView + ?Sized> { + pub planes: [TextureBinding<'a, T>; 3], + pub params: BufferBinding<'a, B>, +} + +impl<'a, B: DynBuffer + ?Sized, T: DynTextureView + ?Sized> Clone + for ExternalTextureBinding<'a, B, T> +{ + fn clone(&self) -> Self { + ExternalTextureBinding { + planes: self.planes.clone(), + params: self.params.clone(), + } + } +} + /// cbindgen:ignore #[derive(Clone, Debug)] pub struct BindGroupEntry { @@ -2178,6 +2195,7 @@ pub struct BindGroupDescriptor< pub textures: &'a [TextureBinding<'a, T>], pub entries: &'a [BindGroupEntry], pub acceleration_structures: &'a [&'a A], + pub external_textures: &'a [ExternalTextureBinding<'a, B, T>], } #[derive(Clone, Debug)] diff --git a/wgpu/src/api/bind_group.rs b/wgpu/src/api/bind_group.rs index 2f4ae007ff..8471fca53f 100644 --- a/wgpu/src/api/bind_group.rs +++ b/wgpu/src/api/bind_group.rs @@ -81,6 +81,12 @@ pub enum BindingResource<'a> { /// built using `build_acceleration_structures` a validation error is generated otherwise this is a part of the /// safety section of `build_acceleration_structures_unsafe_tlas` and so undefined behavior occurs. AccelerationStructure(&'a Tlas), + /// Binding is backed by an external texture. + /// + /// [`Features::EXTERNAL_TEXTURE`] must be supported to use this feature. + /// + /// Corresponds to [`wgt::BindingType::ExternalTexture`]. + ExternalTexture(&'a ExternalTexture), } #[cfg(send_sync)] static_assertions::assert_impl_all!(BindingResource<'_>: Send, Sync); diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index e2ceb57a6f..3d918f2459 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -2017,6 +2017,9 @@ impl dispatch::DeviceInterface for WebDevice { crate::BindingResource::AccelerationStructure(_) => { unimplemented!("Raytracing not implemented for web") } + crate::BindingResource::ExternalTexture(_) => { + unimplemented!("ExternalTexture not implemented for web") + } }; webgpu_sys::GpuBindGroupEntry::new(binding.binding, &mapped_resource) diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index 245aa5911e..0a5648227c 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -1190,6 +1190,9 @@ impl dispatch::DeviceInterface for CoreDevice { acceleration_structure.inner.as_core().id, ) } + BindingResource::ExternalTexture(external_texture) => { + bm::BindingResource::ExternalTexture(external_texture.inner.as_core().id) + } }, }) .collect::>(); From 3a2c3e496d6b079c6a5228d38c51333137643a89 Mon Sep 17 00:00:00 2001 From: Jamie Nicol Date: Mon, 2 Jun 2025 10:31:47 +0100 Subject: [PATCH 4/4] [wgpu] Add external texture validation tests Adds validation tests using the noop backend covering creation of external textures, and creation of bind groups containing external textures. --- .../wgpu-validation/api/external_texture.rs | 495 ++++++++++++++++++ wgpu-core/src/device/resource.rs | 16 +- 2 files changed, 501 insertions(+), 10 deletions(-) diff --git a/tests/tests/wgpu-validation/api/external_texture.rs b/tests/tests/wgpu-validation/api/external_texture.rs index ece0e060f4..b3dcffb93f 100644 --- a/tests/tests/wgpu-validation/api/external_texture.rs +++ b/tests/tests/wgpu-validation/api/external_texture.rs @@ -1,6 +1,365 @@ use wgpu::*; use wgpu_test::{fail, valid}; +/// Ensures an [`ExternalTexture`] can be created from a valid descriptor and planes, +/// but appropriate errors are returned for invalid descriptors and planes. +#[test] +fn create_external_texture() { + let (device, _queue) = wgpu::Device::noop(&DeviceDescriptor { + required_features: Features::EXTERNAL_TEXTURE, + ..Default::default() + }); + + let texture_descriptor = TextureDescriptor { + label: None, + size: Extent3d { + width: 512, + height: 512, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }; + + let r_texture = device.create_texture(&TextureDescriptor { + format: TextureFormat::R8Unorm, + ..texture_descriptor + }); + let r_view = r_texture.create_view(&TextureViewDescriptor::default()); + let rg_texture = device.create_texture(&TextureDescriptor { + format: TextureFormat::Rg8Unorm, + ..texture_descriptor + }); + let rg_view = rg_texture.create_view(&TextureViewDescriptor::default()); + let rgba_texture = device.create_texture(&TextureDescriptor { + format: TextureFormat::Rgba8Unorm, + ..texture_descriptor + }); + let rgba_view = rgba_texture.create_view(&TextureViewDescriptor::default()); + + let _ = valid(&device, || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&rgba_view], + ) + }); + let _ = valid(&device, || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Nv12, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&r_view, &rg_view], + ) + }); + let _ = valid(&device, || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Yu12, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&r_view, &r_view, &r_view], + ) + }); + + // Wrong number of planes for format + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&r_view, &r_view], + ) + }, + Some("External texture format Rgba expects 1 planes, but given 2"), + ); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Nv12, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&r_view], + ) + }, + Some("External texture format Nv12 expects 2 planes, but given 1"), + ); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Yu12, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&r_view, &r_view], + ) + }, + Some("External texture format Yu12 expects 3 planes, but given 2"), + ); + + // Wrong plane formats + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&r_view], + ) + }, + Some("External texture format Rgba plane 0 expects format with 4 components but given view with format R8Unorm (1 components)"), + ); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Nv12, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&r_view, &rgba_view], + ) + }, + Some("External texture format Nv12 plane 1 expects format with 2 components but given view with format Rgba8Unorm (4 components)"), + ); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Yu12, + label: None, + width: r_texture.width(), + height: r_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&r_view, &rg_view, &r_view], + ) + }, + Some("External texture format Yu12 plane 1 expects format with 1 components but given view with format Rg8Unorm (2 components)"), + ); + + // Wrong sample type + let uint_texture = device.create_texture(&TextureDescriptor { + format: TextureFormat::Rgba8Uint, + ..texture_descriptor + }); + let uint_view = uint_texture.create_view(&TextureViewDescriptor::default()); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: uint_texture.width(), + height: uint_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&uint_view], + ) + }, + Some("External texture planes expect a filterable float sample type, but given view with format Rgba8Uint (sample type Uint)"), + ); + + // Wrong texture dimension + let d3_texture = device.create_texture(&TextureDescriptor { + dimension: TextureDimension::D3, + ..texture_descriptor + }); + let d3_view = d3_texture.create_view(&TextureViewDescriptor::default()); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: d3_texture.width(), + height: d3_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&d3_view], + ) + }, + Some("External texture planes expect 2D dimension, but given view with dimension = D3"), + ); + + // Multisampled + let multisampled_texture = device.create_texture(&TextureDescriptor { + sample_count: 4, + usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, + ..texture_descriptor + }); + let multisampled_view = multisampled_texture.create_view(&TextureViewDescriptor::default()); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: multisampled_texture.width(), + height: multisampled_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&multisampled_view], + ) + }, + Some("External texture planes cannot be multisampled, but given view with samples = 4"), + ); + + // Missing TEXTURE_BINDING + let non_binding_texture = device.create_texture(&TextureDescriptor { + usage: TextureUsages::STORAGE_BINDING, + ..texture_descriptor + }); + let non_binding_view = non_binding_texture.create_view(&TextureViewDescriptor::default()); + let _ = fail( + &device, + || { + device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: non_binding_texture.width(), + height: non_binding_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&non_binding_view], + ) + }, + Some("Usage flags TextureUsages(STORAGE_BINDING) of TextureView with '' label do not contain required usage flags TextureUsages(TEXTURE_BINDING)"), + ); +} + +/// Ensures an [`ExternalTexture`] can be bound to a [`BindingType::ExternalTexture`] +/// resource binding. +#[test] +fn external_texture_binding() { + let (device, _queue) = wgpu::Device::noop(&DeviceDescriptor { + required_features: Features::EXTERNAL_TEXTURE, + ..Default::default() + }); + + let bgl = valid(&device, || { + device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: None, + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::ExternalTexture, + count: None, + }], + }) + }); + + let texture_descriptor = TextureDescriptor { + label: None, + size: Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }; + let external_texture_descriptor = ExternalTextureDescriptor { + label: None, + width: texture_descriptor.size.width, + height: texture_descriptor.size.height, + format: ExternalTextureFormat::Rgba, + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }; + + valid(&device, || { + let texture = device.create_texture(&texture_descriptor); + let view = texture.create_view(&TextureViewDescriptor::default()); + let external_texture = + device.create_external_texture(&external_texture_descriptor, &[&view]); + + device.create_bind_group(&BindGroupDescriptor { + label: None, + layout: &bgl, + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::ExternalTexture(&external_texture), + }], + }) + }); +} + /// Ensures a [`TextureView`] can be bound to a [`BindingType::ExternalTexture`] /// resource binding. #[test] @@ -160,3 +519,139 @@ fn external_texture_binding_texture_view() { Some("Texture binding 0 expects multisampled = false, but given a view with samples = 4"), ); } + +/// Ensures that submitting a command buffer referencing an external texture, any of +/// whose plane textures have already been destroyed, results in an error. +#[test] +fn destroyed_external_texture_plane() { + let (device, queue) = wgpu::Device::noop(&DeviceDescriptor { + required_features: Features::EXTERNAL_TEXTURE, + ..Default::default() + }); + + let target_texture = device.create_texture(&TextureDescriptor { + label: None, + size: Extent3d { + width: 512, + height: 512, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + let target_view = target_texture.create_view(&TextureViewDescriptor::default()); + + let plane_texture = device.create_texture(&TextureDescriptor { + label: Some("External texture plane"), + size: Extent3d { + width: 512, + height: 512, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + let plane_view = plane_texture.create_view(&TextureViewDescriptor::default()); + + let external_texture = device.create_external_texture( + &ExternalTextureDescriptor { + format: ExternalTextureFormat::Rgba, + label: None, + width: plane_texture.width(), + height: plane_texture.height(), + yuv_conversion_matrix: [0.0; 16], + sample_transform: [0.0; 6], + load_transform: [0.0; 6], + }, + &[&plane_view], + ); + + let module = device.create_shader_module(ShaderModuleDescriptor { + label: None, + source: ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + " +@group(0) @binding(0) +var tex: texture_external; +@vertex fn vert_main() -> @builtin(position) vec4 { return vec4(0); } +@fragment fn frag_main() -> @location(0) vec4 { return textureLoad(tex, vec2(0)); }", + )), + }); + + let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { + label: None, + layout: None, + vertex: VertexState { + module: &module, + entry_point: None, + compilation_options: PipelineCompilationOptions::default(), + buffers: &[], + }, + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + fragment: Some(FragmentState { + module: &module, + entry_point: None, + compilation_options: PipelineCompilationOptions::default(), + targets: &[Some(ColorTargetState { + format: target_texture.format(), + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + multiview: None, + cache: None, + }); + + let bind_group = device.create_bind_group(&BindGroupDescriptor { + label: None, + layout: &pipeline.get_bind_group_layout(0), + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::ExternalTexture(&external_texture), + }], + }); + + let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { label: None }); + let mut pass = encoder.begin_render_pass(&RenderPassDescriptor { + label: None, + color_attachments: &[Some(RenderPassColorAttachment { + view: &target_view, + depth_slice: None, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }), + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + pass.set_pipeline(&pipeline); + pass.set_bind_group(0, &bind_group, &[]); + pass.draw(0..0, 0..0); + drop(pass); + + plane_texture.destroy(); + + fail( + &device, + || queue.submit([encoder.finish()]), + Some("Texture with 'External texture plane' label has been destroyed"), + ); +} diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index b653186e25..3156769440 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -2665,11 +2665,7 @@ impl Device { used.buffers .insert_single(external_texture.params.clone(), wgt::BufferUses::UNIFORM); - let params = hal::BufferBinding { - buffer: external_texture.params.try_raw(snatch_guard)?, - offset: 0, - size: wgt::BufferSize::new(external_texture.params.size), - }; + let params = external_texture.params.binding(0, None, snatch_guard)?.0; Ok(hal::ExternalTextureBinding { planes, params }) } @@ -2718,11 +2714,11 @@ impl Device { usage: internal_use, }, ]; - let params = hal::BufferBinding { - buffer: self.default_external_texture_params_buffer.as_ref(), - offset: 0, - size: None, - }; + let params = hal::BufferBinding::new_unchecked( + self.default_external_texture_params_buffer.as_ref(), + 0, + None, + ); Ok(hal::ExternalTextureBinding { planes, params }) }