Skip to content

Commit acfa8c5

Browse files
jamienicoljimblandy
authored andcommitted
[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
1 parent 43a4d53 commit acfa8c5

File tree

19 files changed

+592
-28
lines changed

19 files changed

+592
-28
lines changed

examples/standalone/custom_backend/src/custom.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,14 @@ impl DeviceInterface for CustomDevice {
184184
unimplemented!()
185185
}
186186

187+
fn create_external_texture(
188+
&self,
189+
_desc: &wgpu::ExternalTextureDescriptor<'_>,
190+
_planes: &[&wgpu::TextureView],
191+
) -> wgpu::custom::DispatchExternalTexture {
192+
unimplemented!()
193+
}
194+
187195
fn create_blas(
188196
&self,
189197
_desc: &wgpu::CreateBlasDescriptor<'_>,

player/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,19 @@ impl GlobalPlay for wgc::global::Global {
244244
Action::DestroyTextureView(id) => {
245245
self.texture_view_drop(id).unwrap();
246246
}
247+
Action::CreateExternalTexture { id, desc, planes } => {
248+
let (_, error) =
249+
self.device_create_external_texture(device, &desc, &planes, Some(id));
250+
if let Some(e) = error {
251+
panic!("{e}");
252+
}
253+
}
254+
Action::FreeExternalTexture(id) => {
255+
self.external_texture_destroy(id);
256+
}
257+
Action::DestroyExternalTexture(id) => {
258+
self.external_texture_drop(id);
259+
}
247260
Action::CreateSampler(id, desc) => {
248261
let (_, error) = self.device_create_sampler(device, &desc, Some(id));
249262
if let Some(e) = error {

wgpu-core/Cargo.toml

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,7 @@ observe_locks = ["std", "dep:ron", "serde/serde_derive"]
7272
serde = ["dep:serde", "wgpu-types/serde", "arrayvec/serde", "hashbrown/serde"]
7373

7474
## Enable API tracing.
75-
trace = [
76-
"serde",
77-
"std",
78-
"dep:ron",
79-
"naga/serialize",
80-
"wgpu-types/trace",
81-
"dep:bytemuck",
82-
]
75+
trace = ["serde", "std", "dep:ron", "naga/serialize", "wgpu-types/trace"]
8376

8477
## Enable API replaying
8578
replay = ["serde", "naga/deserialize"]
@@ -100,7 +93,7 @@ wgsl = ["naga/wgsl-in"]
10093
glsl = ["naga/glsl-in"]
10194

10295
## Enable `ShaderModuleSource::SpirV`
103-
spirv = ["naga/spv-in", "dep:bytemuck"]
96+
spirv = ["naga/spv-in"]
10497

10598
#! ### Other
10699
# --------------------------------------------------------------------
@@ -180,7 +173,7 @@ arrayvec.workspace = true
180173
bit-vec.workspace = true
181174
bit-set.workspace = true
182175
bitflags.workspace = true
183-
bytemuck = { workspace = true, optional = true }
176+
bytemuck.workspace = true
184177
document-features.workspace = true
185178
hashbrown.workspace = true
186179
indexmap.workspace = true

wgpu-core/src/device/global.rs

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ impl Global {
178178
fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string())));
179179
}
180180

181+
/// Assign `id_in` an error with the given `label`.
182+
///
183+
/// See [`Self::create_buffer_error`] for more context and explanation.
181184
pub fn create_render_bundle_error(
182185
&self,
183186
id_in: Option<id::RenderBundleId>,
@@ -189,7 +192,7 @@ impl Global {
189192

190193
/// Assign `id_in` an error with the given `label`.
191194
///
192-
/// See `create_buffer_error` for more context and explanation.
195+
/// See [`Self::create_buffer_error`] for more context and explanation.
193196
pub fn create_texture_error(
194197
&self,
195198
id_in: Option<id::TextureId>,
@@ -199,6 +202,18 @@ impl Global {
199202
fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string())));
200203
}
201204

205+
/// Assign `id_in` an error with the given `label`.
206+
///
207+
/// See [`Self::create_buffer_error`] for more context and explanation.
208+
pub fn create_external_texture_error(
209+
&self,
210+
id_in: Option<id::ExternalTextureId>,
211+
desc: &resource::ExternalTextureDescriptor,
212+
) {
213+
let fid = self.hub.external_textures.prepare(id_in);
214+
fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string())));
215+
}
216+
202217
#[cfg(feature = "replay")]
203218
pub fn device_set_buffer_data(
204219
&self,
@@ -512,6 +527,94 @@ impl Global {
512527
Ok(())
513528
}
514529

530+
pub fn device_create_external_texture(
531+
&self,
532+
device_id: DeviceId,
533+
desc: &resource::ExternalTextureDescriptor,
534+
planes: &[id::TextureViewId],
535+
id_in: Option<id::ExternalTextureId>,
536+
) -> (
537+
id::ExternalTextureId,
538+
Option<resource::CreateExternalTextureError>,
539+
) {
540+
profiling::scope!("Device::create_external_texture");
541+
542+
let hub = &self.hub;
543+
544+
let fid = hub.external_textures.prepare(id_in);
545+
546+
let error = 'error: {
547+
let device = self.hub.devices.get(device_id);
548+
549+
#[cfg(feature = "trace")]
550+
if let Some(ref mut trace) = *device.trace.lock() {
551+
let planes = Box::from(planes);
552+
trace.add(trace::Action::CreateExternalTexture {
553+
id: fid.id(),
554+
desc: desc.clone(),
555+
planes,
556+
});
557+
}
558+
559+
let planes = planes
560+
.iter()
561+
.map(|plane_id| self.hub.texture_views.get(*plane_id).get())
562+
.collect::<Result<Vec<_>, _>>();
563+
let planes = match planes {
564+
Ok(planes) => planes,
565+
Err(error) => break 'error error.into(),
566+
};
567+
568+
let external_texture = match device.create_external_texture(desc, &planes) {
569+
Ok(external_texture) => external_texture,
570+
Err(error) => break 'error error,
571+
};
572+
573+
let id = fid.assign(Fallible::Valid(external_texture));
574+
api_log!("Device::create_external_texture({desc:?}) -> {id:?}");
575+
576+
return (id, None);
577+
};
578+
579+
let id = fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string())));
580+
(id, Some(error))
581+
}
582+
583+
pub fn external_texture_destroy(&self, external_texture_id: id::ExternalTextureId) {
584+
profiling::scope!("ExternalTexture::destroy");
585+
api_log!("ExternalTexture::destroy {external_texture_id:?}");
586+
587+
let hub = &self.hub;
588+
589+
let Ok(external_texture) = hub.external_textures.get(external_texture_id).get() else {
590+
// If the external texture is already invalid, there's nothing to do.
591+
return;
592+
};
593+
594+
#[cfg(feature = "trace")]
595+
if let Some(trace) = external_texture.device.trace.lock().as_mut() {
596+
trace.add(trace::Action::FreeExternalTexture(external_texture_id));
597+
}
598+
599+
external_texture.destroy();
600+
}
601+
602+
pub fn external_texture_drop(&self, external_texture_id: id::ExternalTextureId) {
603+
profiling::scope!("ExternalTexture::drop");
604+
api_log!("ExternalTexture::drop {external_texture_id:?}");
605+
606+
let hub = &self.hub;
607+
608+
let _external_texture = hub.external_textures.remove(external_texture_id);
609+
610+
#[cfg(feature = "trace")]
611+
if let Ok(external_texture) = _external_texture.get() {
612+
if let Some(t) = external_texture.device.trace.lock().as_mut() {
613+
t.add(trace::Action::DestroyExternalTexture(external_texture_id));
614+
}
615+
}
616+
}
617+
515618
pub fn device_create_sampler(
516619
&self,
517620
device_id: DeviceId,

wgpu-core/src/device/resource.rs

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ use crate::{
4141
pipeline,
4242
pool::ResourcePool,
4343
resource::{
44-
self, Buffer, Fallible, Labeled, ParentDevice, QuerySet, RawResourceAccess, Sampler,
45-
StagingBuffer, Texture, TextureView, TextureViewNotRenderableReason, Tlas, TrackingData,
44+
self, Buffer, ExternalTexture, Fallible, Labeled, ParentDevice, QuerySet,
45+
RawResourceAccess, Sampler, StagingBuffer, Texture, TextureView,
46+
TextureViewNotRenderableReason, Tlas, TrackingData,
4647
},
4748
resource_log,
4849
snatch::{SnatchGuard, SnatchLock, Snatchable},
@@ -75,6 +76,42 @@ pub(crate) struct CommandIndices {
7576
pub(crate) next_acceleration_structure_build_command_index: u64,
7677
}
7778

79+
/// Parameters provided to shaders via a uniform buffer, describing an
80+
/// ExternalTexture resource binding.
81+
#[repr(C)]
82+
#[derive(Copy, Clone, bytemuck::Zeroable, bytemuck::Pod)]
83+
pub struct ExternalTextureParams {
84+
/// 4x4 column-major matrix with which to convert sampled YCbCr values
85+
/// to RGBA.
86+
/// This is ignored when `num_planes` is 1.
87+
pub yuv_conversion_matrix: [f32; 16],
88+
/// 3x2 column-major matrix with which to multiply texture coordinates
89+
/// prior to sampling from the external texture.
90+
pub sample_transform: [f32; 6],
91+
pub load_transform: [f32; 6],
92+
/// Size of the external texture. This value should be returned by size
93+
/// queries in shader code. Note that this may not match the dimensions of
94+
/// the underlying texture(s). A value of [0, 0] indicates that the actual
95+
/// size of plane 0 should be used.
96+
pub size: [u32; 2],
97+
/// Number of planes. 1 indicates a single RGBA plane. 2 indicates a Y
98+
/// plane and an interleaved CbCr plane. 3 indicates separate Y, Cb, and Cr
99+
/// planes.
100+
pub num_planes: u32,
101+
}
102+
103+
impl ExternalTextureParams {
104+
pub fn from_desc<L>(desc: &wgt::ExternalTextureDescriptor<L>) -> Self {
105+
Self {
106+
yuv_conversion_matrix: desc.yuv_conversion_matrix,
107+
size: [desc.width, desc.height],
108+
sample_transform: desc.sample_transform,
109+
load_transform: desc.load_transform,
110+
num_planes: desc.num_planes() as u32,
111+
}
112+
}
113+
}
114+
78115
/// Structure describing a logical device. Some members are internally mutable,
79116
/// stored behind mutexes.
80117
pub struct Device {
@@ -1550,6 +1587,101 @@ impl Device {
15501587
Ok(view)
15511588
}
15521589

1590+
pub(crate) fn create_external_texture(
1591+
self: &Arc<Self>,
1592+
desc: &resource::ExternalTextureDescriptor,
1593+
planes: &[Arc<TextureView>],
1594+
) -> Result<Arc<ExternalTexture>, resource::CreateExternalTextureError> {
1595+
use resource::CreateExternalTextureError;
1596+
self.require_features(wgt::Features::EXTERNAL_TEXTURE)?;
1597+
self.check_is_valid()?;
1598+
1599+
if desc.num_planes() != planes.len() {
1600+
return Err(CreateExternalTextureError::IncorrectPlaneCount {
1601+
format: desc.format,
1602+
expected: desc.num_planes(),
1603+
provided: planes.len(),
1604+
});
1605+
}
1606+
1607+
let planes = planes
1608+
.iter()
1609+
.enumerate()
1610+
.map(|(i, plane)| {
1611+
if plane.samples != 1 {
1612+
return Err(CreateExternalTextureError::InvalidPlaneMultisample(
1613+
plane.samples,
1614+
));
1615+
}
1616+
1617+
let sample_type = plane
1618+
.desc
1619+
.format
1620+
.sample_type(Some(plane.desc.range.aspect), Some(self.features))
1621+
.unwrap();
1622+
if !matches!(sample_type, TextureSampleType::Float { filterable: true }) {
1623+
return Err(CreateExternalTextureError::InvalidPlaneSampleType {
1624+
format: plane.desc.format,
1625+
sample_type,
1626+
});
1627+
}
1628+
1629+
if plane.desc.dimension != TextureViewDimension::D2 {
1630+
return Err(CreateExternalTextureError::InvalidPlaneDimension(
1631+
plane.desc.dimension,
1632+
));
1633+
}
1634+
1635+
let expected_components = match desc.format {
1636+
wgt::ExternalTextureFormat::Rgba => 4,
1637+
wgt::ExternalTextureFormat::Nv12 => match i {
1638+
0 => 1,
1639+
1 => 2,
1640+
_ => unreachable!(),
1641+
},
1642+
wgt::ExternalTextureFormat::Yu12 => 1,
1643+
};
1644+
if plane.desc.format.components() != expected_components {
1645+
return Err(CreateExternalTextureError::InvalidPlaneFormat {
1646+
format: desc.format,
1647+
plane: i,
1648+
expected: expected_components,
1649+
provided: plane.desc.format,
1650+
});
1651+
}
1652+
1653+
plane.check_usage(wgt::TextureUsages::TEXTURE_BINDING)?;
1654+
Ok(plane.clone())
1655+
})
1656+
.collect::<Result<_, _>>()?;
1657+
1658+
let params_data = ExternalTextureParams::from_desc(desc);
1659+
let label = desc.label.as_ref().map(|l| alloc::format!("{l} params"));
1660+
let params_desc = resource::BufferDescriptor {
1661+
label: label.map(Cow::Owned),
1662+
size: size_of_val(&params_data) as wgt::BufferAddress,
1663+
usage: wgt::BufferUsages::UNIFORM | wgt::BufferUsages::COPY_DST,
1664+
mapped_at_creation: false,
1665+
};
1666+
let params = self.create_buffer(&params_desc)?;
1667+
self.get_queue().unwrap().write_buffer(
1668+
Fallible::Valid(params.clone()),
1669+
0,
1670+
bytemuck::bytes_of(&params_data),
1671+
)?;
1672+
1673+
let external_texture = ExternalTexture {
1674+
device: self.clone(),
1675+
planes,
1676+
params,
1677+
label: desc.label.to_string(),
1678+
tracking_data: TrackingData::new(self.tracker_indices.external_textures.clone()),
1679+
};
1680+
let external_texture = Arc::new(external_texture);
1681+
1682+
Ok(external_texture)
1683+
}
1684+
15531685
pub(crate) fn create_sampler(
15541686
self: &Arc<Self>,
15551687
desc: &resource::SamplerDescriptor,

wgpu-core/src/device/trace.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ pub enum Action<'a> {
5858
desc: crate::resource::TextureViewDescriptor<'a>,
5959
},
6060
DestroyTextureView(id::TextureViewId),
61+
CreateExternalTexture {
62+
id: id::ExternalTextureId,
63+
desc: crate::resource::ExternalTextureDescriptor<'a>,
64+
planes: alloc::boxed::Box<[id::TextureViewId]>,
65+
},
66+
FreeExternalTexture(id::ExternalTextureId),
67+
DestroyExternalTexture(id::ExternalTextureId),
6168
CreateSampler(id::SamplerId, crate::resource::SamplerDescriptor<'a>),
6269
DestroySampler(id::SamplerId),
6370
GetSurfaceTexture {

0 commit comments

Comments
 (0)