Skip to content

Add ExternalTexture BindingType behind new Feature flag #7732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions tests/tests/wgpu-validation/api/external_texture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
use wgpu::*;
use wgpu_test::{fail, valid};

/// Ensures a [`TextureView`] can be bound to a [`BindingType::ExternalTexture`]
/// resource binding.
#[test]
fn external_texture_binding_texture_view() {
let (device, _queue) = wgpu::Device::noop(&DeviceDescriptor {
required_features: Features::EXTERNAL_TEXTURE,
..Default::default()
});

let bgl = 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 texture = device.create_texture(&texture_descriptor);
let view = texture.create_view(&TextureViewDescriptor::default());
valid(&device, || {
device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &bgl,
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&view),
}],
})
});

// Invalid usages (must include TEXTURE_BINDING)
let texture = device.create_texture(&TextureDescriptor {
usage: TextureUsages::STORAGE_BINDING,
..texture_descriptor
});
let view = texture.create_view(&TextureViewDescriptor::default());
fail(
&device,
|| {
device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &bgl,
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&view),
}],
})
},
Some("Usage flags TextureUsages(STORAGE_BINDING) of TextureView with '' label do not contain required usage flags TextureUsages(TEXTURE_BINDING"),
);

// Invalid dimension (must be D2)
let texture = device.create_texture(&TextureDescriptor {
dimension: TextureDimension::D3,
..texture_descriptor
});
let view = texture.create_view(&TextureViewDescriptor::default());
fail(
&device,
|| {
device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &bgl,
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&view),
}],
})
},
Some("Texture binding 0 expects dimension = D2, but given a view with dimension = D3"),
);

// Invalid mip_level_count (must be 1)
let texture = device.create_texture(&TextureDescriptor {
mip_level_count: 2,
..texture_descriptor
});
let view = texture.create_view(&TextureViewDescriptor::default());
fail(
&device,
|| {

device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &bgl,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&view),
},
],
})
},
Some("External texture bindings must have a single mip level, but given a view with mip_level_count = 2 at binding 0")
);

// Invalid format (must be Rgba8Unorm, Bgra8Unorm, or Rgba16float)
let texture = device.create_texture(&TextureDescriptor {
format: TextureFormat::Rgba8Uint,
..texture_descriptor
});
let view = texture.create_view(&TextureViewDescriptor::default());
fail(
&device,
|| {

device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &bgl,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&view),
},
],
})
},
Some("External texture bindings must have a format of `rgba8unorm`, `bgra8unorm`, or `rgba16float, but given a view with format = Rgba8Uint at binding 0")
);

// Invalid sample count (must be 1)
let texture = device.create_texture(&TextureDescriptor {
sample_count: 4,
usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
..texture_descriptor
});
let view = texture.create_view(&TextureViewDescriptor::default());
fail(
&device,
|| {
device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &bgl,
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&view),
}],
})
},
Some("Texture binding 0 expects multisampled = false, but given a view with samples = 4"),
);
}
1 change: 1 addition & 0 deletions tests/tests/wgpu-validation/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod binding_arrays;
mod buffer;
mod buffer_slice;
mod external_texture;
mod instance;
mod texture;
20 changes: 20 additions & 0 deletions wgpu-core/src/binding_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ pub enum CreateBindGroupError {
},
#[error("Storage texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")]
InvalidStorageTextureMipLevelCount { binding: u32, mip_level_count: u32 },
#[error("External texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")]
InvalidExternalTextureMipLevelCount { binding: u32, mip_level_count: u32 },
#[error("External texture bindings must have a format of `rgba8unorm`, `bgra8unorm`, or `rgba16float, but given a view with format = {format:?} at binding {binding}")]
InvalidExternalTextureFormat {
binding: u32,
format: wgt::TextureFormat,
},
#[error("Sampler binding {binding} expects comparison = {layout_cmp}, but given a sampler with comparison = {sampler_cmp}")]
WrongSamplerComparison {
binding: u32,
Expand Down Expand Up @@ -382,6 +389,19 @@ impl BindingTypeMaxCountValidator {
wgt::BindingType::AccelerationStructure { .. } => {
self.acceleration_structures.add(binding.visibility, count);
}
wgt::BindingType::ExternalTexture => {
// https://www.w3.org/TR/webgpu/#gpuexternaltexture
// In order to account for many possible representations,
// the binding conservatively uses the following, for each
// external texture:
// * Three sampled textures for up to 3 planes
// * One additional sampled texture for a 3D LUT
// * One sampler to sample the LUT
// * One uniform buffer for metadata
self.sampled_textures.add(binding.visibility, count * 4);
self.samplers.add(binding.visibility, count);
self.uniform_buffers.add(binding.visibility, count);
}
}
}
}
Expand Down
43 changes: 43 additions & 0 deletions wgpu-core/src/device/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2011,6 +2011,14 @@ impl Device {
)
}
Bt::AccelerationStructure { .. } => (None, WritableStorage::No),
Bt::ExternalTexture => {
self.require_features(wgt::Features::EXTERNAL_TEXTURE)
.map_err(|e| binding_model::CreateBindGroupLayoutError::Entry {
binding: entry.binding,
error: e.into(),
})?;
(None, WritableStorage::No)
}
};

// Validate the count parameter
Expand Down Expand Up @@ -2761,6 +2769,41 @@ impl Device {
view.check_usage(wgt::TextureUsages::STORAGE_BINDING)?;
Ok(internal_use)
}
wgt::BindingType::ExternalTexture => {
if view.desc.dimension != TextureViewDimension::D2 {
return Err(Error::InvalidTextureDimension {
binding,
layout_dimension: TextureViewDimension::D2,
view_dimension: view.desc.dimension,
});
}
let mip_level_count = view.selector.mips.end - view.selector.mips.start;
if mip_level_count != 1 {
return Err(Error::InvalidExternalTextureMipLevelCount {
binding,
mip_level_count,
});
}
if view.desc.format != TextureFormat::Rgba8Unorm
&& view.desc.format != TextureFormat::Bgra8Unorm
&& view.desc.format != TextureFormat::Rgba16Float
{
return Err(Error::InvalidExternalTextureFormat {
binding,
format: view.desc.format,
});
}
if view.samples != 1 {
return Err(Error::InvalidTextureMultisample {
binding,
layout_multisampled: false,
view_samples: view.samples,
});
}

view.check_usage(wgt::TextureUsages::TEXTURE_BINDING)?;
Ok(wgt::TextureUses::RESOURCE)
}
_ => Err(Error::WrongBindingType {
binding,
actual: decl.ty,
Expand Down
6 changes: 6 additions & 0 deletions wgpu-core/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub enum BindingTypeName {
Texture,
Sampler,
AccelerationStructure,
ExternalTexture,
}

impl From<&ResourceType> for BindingTypeName {
Expand All @@ -57,6 +58,7 @@ impl From<&BindingType> for BindingTypeName {
BindingType::StorageTexture { .. } => BindingTypeName::Texture,
BindingType::Sampler { .. } => BindingTypeName::Sampler,
BindingType::AccelerationStructure { .. } => BindingTypeName::AccelerationStructure,
BindingType::ExternalTexture => BindingTypeName::ExternalTexture,
}
}
}
Expand Down Expand Up @@ -466,6 +468,7 @@ impl Resource {
let view_dimension = match entry.ty {
BindingType::Texture { view_dimension, .. }
| BindingType::StorageTexture { view_dimension, .. } => view_dimension,
BindingType::ExternalTexture => wgt::TextureViewDimension::D2,
_ => {
return Err(BindingError::WrongTextureViewDimension {
dim,
Expand Down Expand Up @@ -1147,6 +1150,9 @@ impl Interface {
);
let texture_sample_type = match texture_layout.ty {
BindingType::Texture { sample_type, .. } => sample_type,
BindingType::ExternalTexture => {
wgt::TextureSampleType::Float { filterable: true }
}
_ => unreachable!(),
};

Expand Down
1 change: 1 addition & 0 deletions wgpu-hal/src/dx12/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ pub fn map_binding_type(ty: &wgt::BindingType) -> Direct3D12::D3D12_DESCRIPTOR_R
}
| Bt::StorageTexture { .. } => Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_UAV,
Bt::AccelerationStructure { .. } => Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
Bt::ExternalTexture => unimplemented!(),
}
}

Expand Down
2 changes: 2 additions & 0 deletions wgpu-hal/src/dx12/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,7 @@ impl crate::Device for super::Device {
| wgt::BindingType::StorageTexture { .. }
| wgt::BindingType::AccelerationStructure { .. } => num_views += count,
wgt::BindingType::Sampler { .. } => has_sampler_in_group = true,
wgt::BindingType::ExternalTexture => unimplemented!(),
}
}

Expand Down Expand Up @@ -1550,6 +1551,7 @@ impl crate::Device for super::Device {
inner.stage.push(handle);
}
}
wgt::BindingType::ExternalTexture => unimplemented!(),
}
}

Expand Down
2 changes: 2 additions & 0 deletions wgpu-hal/src/gles/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,7 @@ impl crate::Device for super::Device {
..
} => &mut num_storage_buffers,
wgt::BindingType::AccelerationStructure { .. } => unimplemented!(),
wgt::BindingType::ExternalTexture => unimplemented!(),
};

binding_to_slot[entry.binding as usize] = *counter;
Expand Down Expand Up @@ -1313,6 +1314,7 @@ impl crate::Device for super::Device {
})
}
wgt::BindingType::AccelerationStructure { .. } => unimplemented!(),
wgt::BindingType::ExternalTexture => unimplemented!(),
};
contents.push(binding);
}
Expand Down
2 changes: 2 additions & 0 deletions wgpu-hal/src/metal/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,7 @@ impl crate::Device for super::Device {
};
}
wgt::BindingType::AccelerationStructure { .. } => unimplemented!(),
wgt::BindingType::ExternalTexture => unimplemented!(),
}
}

Expand Down Expand Up @@ -979,6 +980,7 @@ impl crate::Device for super::Device {
counter.textures += 1;
}
wgt::BindingType::AccelerationStructure { .. } => unimplemented!(),
wgt::BindingType::ExternalTexture => unimplemented!(),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions wgpu-hal/src/vulkan/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,7 @@ pub fn map_binding_type(ty: wgt::BindingType) -> vk::DescriptorType {
wgt::BindingType::AccelerationStructure { .. } => {
vk::DescriptorType::ACCELERATION_STRUCTURE_KHR
}
wgt::BindingType::ExternalTexture => unimplemented!(),
}
}

Expand Down
1 change: 1 addition & 0 deletions wgpu-hal/src/vulkan/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1493,6 +1493,7 @@ impl crate::Device for super::Device {
wgt::BindingType::AccelerationStructure { .. } => {
desc_count.acceleration_structure += count;
}
wgt::BindingType::ExternalTexture => unimplemented!(),
}
}

Expand Down
Loading