[dx12,vulkan] Add support for P010 texture format (#8086)

P010 is a 4:2:0 chroma subsampled planar format, similar to NV12. Each
component uses 16 bits of storage, of which only the high 10 bits are
used. On DX12 this maps to DXGI_FORMAT_P010, and on Vulkan this maps to
G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16.

The existing "nv12" gpu test module has been renamed to
"planar_texture", and a new test P010_TEXTURE_CREATION_SAMPLING has
been added similar to the existing NV12_TEXTURE_CREATION_SAMPLING. The
remaining tests in this module have been converted to validation tests,
and now test both NV12 and P010 formats.
This commit is contained in:
Jamie Nicol 2025-08-13 14:35:20 +01:00 committed by GitHub
parent 01b3204e7f
commit 486c151772
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 570 additions and 310 deletions

View File

@ -37,7 +37,7 @@ pub use wgpu_macros::gpu_test;
pub fn fail<T>(
device: &wgpu::Device,
callback: impl FnOnce() -> T,
expected_msg_substring: Option<&'static str>,
expected_msg_substring: Option<&str>,
) -> T {
device.push_error_scope(wgpu::ErrorFilter::Validation);
let result = callback();

View File

@ -36,12 +36,12 @@ mod instance;
mod life_cycle;
mod mem_leaks;
mod mesh_shader;
mod nv12_texture;
mod occlusion_query;
mod oob_indexing;
mod oom;
mod pipeline;
mod pipeline_cache;
mod planar_texture;
mod poll;
mod push_constants;
mod query_set;
@ -96,12 +96,12 @@ fn all_tests() -> Vec<wgpu_test::GpuTestInitializer> {
life_cycle::all_tests(&mut tests);
mem_leaks::all_tests(&mut tests);
mesh_shader::all_tests(&mut tests);
nv12_texture::all_tests(&mut tests);
occlusion_query::all_tests(&mut tests);
oob_indexing::all_tests(&mut tests);
oom::all_tests(&mut tests);
pipeline_cache::all_tests(&mut tests);
pipeline::all_tests(&mut tests);
planar_texture::all_tests(&mut tests);
poll::all_tests(&mut tests);
push_constants::all_tests(&mut tests);
query_set::all_tests(&mut tests);

View File

@ -1,277 +0,0 @@
//! Tests for nv12 texture creation and sampling.
use wgpu_test::{fail, gpu_test, GpuTestConfiguration, GpuTestInitializer, TestParameters};
pub fn all_tests(tests: &mut Vec<GpuTestInitializer>) {
tests.extend([
NV12_TEXTURE_CREATION_SAMPLING,
NV12_TEXTURE_VIEW_PLANE_ON_NON_PLANAR_FORMAT,
NV12_TEXTURE_VIEW_PLANE_OUT_OF_BOUNDS,
NV12_TEXTURE_BAD_FORMAT_VIEW_PLANE,
NV12_TEXTURE_BAD_SIZE,
]);
}
#[gpu_test]
static NV12_TEXTURE_CREATION_SAMPLING: GpuTestConfiguration = GpuTestConfiguration::new()
.parameters(
TestParameters::default()
.features(wgpu::Features::TEXTURE_FORMAT_NV12)
.enable_noop(),
)
.run_sync(|ctx| {
let size = wgpu::Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
};
let target_format = wgpu::TextureFormat::Bgra8UnormSrgb;
let shader = ctx
.device
.create_shader_module(wgpu::include_wgsl!("nv12_texture.wgsl"));
let pipeline = ctx
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("nv12 pipeline"),
layout: None,
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(target_format.into())],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
strip_index_format: Some(wgpu::IndexFormat::Uint32),
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let tex = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: wgpu::TextureFormat::NV12,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
let y_view = tex.create_view(&wgpu::TextureViewDescriptor {
format: Some(wgpu::TextureFormat::R8Unorm),
aspect: wgpu::TextureAspect::Plane0,
..Default::default()
});
let uv_view = tex.create_view(&wgpu::TextureViewDescriptor {
format: Some(wgpu::TextureFormat::Rg8Unorm),
aspect: wgpu::TextureAspect::Plane1,
..Default::default()
});
let sampler = ctx.device.create_sampler(&wgpu::SamplerDescriptor {
min_filter: wgpu::FilterMode::Linear,
mag_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &pipeline.get_bind_group_layout(0),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&y_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&uv_view),
},
],
});
let target_tex = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: target_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let target_view = target_tex.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
ops: wgpu::Operations::default(),
resolve_target: None,
view: &target_view,
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&pipeline);
rpass.set_bind_group(0, &bind_group, &[]);
rpass.draw(0..4, 0..1);
drop(rpass);
ctx.queue.submit(Some(encoder.finish()));
});
#[gpu_test]
static NV12_TEXTURE_VIEW_PLANE_ON_NON_PLANAR_FORMAT: GpuTestConfiguration =
GpuTestConfiguration::new()
.parameters(
TestParameters::default()
.features(wgpu::Features::TEXTURE_FORMAT_NV12)
.enable_noop(),
)
.run_sync(|ctx| {
let size = wgpu::Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
};
let tex = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: wgpu::TextureFormat::R8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
fail(
&ctx.device,
|| {
let _ = tex.create_view(&wgpu::TextureViewDescriptor {
aspect: wgpu::TextureAspect::Plane0,
..Default::default()
});
},
Some("aspect plane0 is not in the source texture format r8unorm"),
);
});
#[gpu_test]
static NV12_TEXTURE_VIEW_PLANE_OUT_OF_BOUNDS: GpuTestConfiguration = GpuTestConfiguration::new()
.parameters(
TestParameters::default()
.features(wgpu::Features::TEXTURE_FORMAT_NV12)
.enable_noop(),
)
.run_sync(|ctx| {
let size = wgpu::Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
};
let tex = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: wgpu::TextureFormat::NV12,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
fail(
&ctx.device,
|| {
let _ = tex.create_view(&wgpu::TextureViewDescriptor {
format: Some(wgpu::TextureFormat::R8Unorm),
aspect: wgpu::TextureAspect::Plane2,
..Default::default()
});
},
Some("aspect plane2 is not in the source texture format nv12"),
);
});
#[gpu_test]
static NV12_TEXTURE_BAD_FORMAT_VIEW_PLANE: GpuTestConfiguration = GpuTestConfiguration::new()
.parameters(
TestParameters::default()
.features(wgpu::Features::TEXTURE_FORMAT_NV12)
.enable_noop(),
)
.run_sync(|ctx| {
let size = wgpu::Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
};
let tex = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: wgpu::TextureFormat::NV12,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
fail(
&ctx.device,
|| {
let _ = tex.create_view(&wgpu::TextureViewDescriptor {
format: Some(wgpu::TextureFormat::Rg8Unorm),
aspect: wgpu::TextureAspect::Plane0,
..Default::default()
});
},
Some("unable to view texture nv12 as rg8unorm"),
);
});
#[gpu_test]
static NV12_TEXTURE_BAD_SIZE: GpuTestConfiguration = GpuTestConfiguration::new()
.parameters(
TestParameters::default()
.features(wgpu::Features::TEXTURE_FORMAT_NV12)
.enable_noop(),
)
.run_sync(|ctx| {
let size = wgpu::Extent3d {
width: 255,
height: 255,
depth_or_array_layers: 1,
};
fail(
&ctx.device,
|| {
let _ = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: wgpu::TextureFormat::NV12,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
},
Some("width 255 is not a multiple of nv12's width multiple requirement"),
);
});

View File

@ -0,0 +1,189 @@
//! Tests for nv12 texture creation and sampling.
use wgpu_test::{
gpu_test, GpuTestConfiguration, GpuTestInitializer, TestParameters, TestingContext,
};
pub fn all_tests(tests: &mut Vec<GpuTestInitializer>) {
tests.extend([
NV12_TEXTURE_CREATION_SAMPLING,
P010_TEXTURE_CREATION_SAMPLING,
]);
}
// Helper function to test planar texture creation and sampling.
fn test_planar_texture_creation_sampling(
ctx: &TestingContext,
y_view: &wgpu::TextureView,
uv_view: &wgpu::TextureView,
) {
let target_format = wgpu::TextureFormat::Bgra8UnormSrgb;
let shader = ctx
.device
.create_shader_module(wgpu::include_wgsl!("planar_texture.wgsl"));
let pipeline = ctx
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("planar texture pipeline"),
layout: None,
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(target_format.into())],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
strip_index_format: Some(wgpu::IndexFormat::Uint32),
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let sampler = ctx.device.create_sampler(&wgpu::SamplerDescriptor {
min_filter: wgpu::FilterMode::Linear,
mag_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &pipeline.get_bind_group_layout(0),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(y_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(uv_view),
},
],
});
let target_tex = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: y_view.texture().size(),
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: target_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let target_view = target_tex.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
ops: wgpu::Operations::default(),
resolve_target: None,
view: &target_view,
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&pipeline);
rpass.set_bind_group(0, &bind_group, &[]);
rpass.draw(0..4, 0..1);
drop(rpass);
ctx.queue.submit(Some(encoder.finish()));
}
/// Ensures that creation and sampling of an NV12 format texture works as
/// expected.
#[gpu_test]
static NV12_TEXTURE_CREATION_SAMPLING: GpuTestConfiguration = GpuTestConfiguration::new()
.parameters(
TestParameters::default()
.features(wgpu::Features::TEXTURE_FORMAT_NV12)
.enable_noop(),
)
.run_sync(|ctx| {
let size = wgpu::Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
};
let tex = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: wgpu::TextureFormat::NV12,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
let y_view = tex.create_view(&wgpu::TextureViewDescriptor {
format: Some(wgpu::TextureFormat::R8Unorm),
aspect: wgpu::TextureAspect::Plane0,
..Default::default()
});
let uv_view = tex.create_view(&wgpu::TextureViewDescriptor {
format: Some(wgpu::TextureFormat::Rg8Unorm),
aspect: wgpu::TextureAspect::Plane1,
..Default::default()
});
test_planar_texture_creation_sampling(&ctx, &y_view, &uv_view);
});
/// Ensures that creation and sampling of a P010 format texture works as
/// expected.
#[gpu_test]
static P010_TEXTURE_CREATION_SAMPLING: GpuTestConfiguration = GpuTestConfiguration::new()
.parameters(
TestParameters::default()
.features(
wgpu::Features::TEXTURE_FORMAT_P010 | wgpu::Features::TEXTURE_FORMAT_16BIT_NORM,
)
.enable_noop(),
)
.run_sync(|ctx| {
let size = wgpu::Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
};
let tex = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: wgpu::TextureFormat::P010,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
let y_view = tex.create_view(&wgpu::TextureViewDescriptor {
format: Some(wgpu::TextureFormat::R16Unorm),
aspect: wgpu::TextureAspect::Plane0,
..Default::default()
});
let uv_view = tex.create_view(&wgpu::TextureViewDescriptor {
format: Some(wgpu::TextureFormat::Rg16Unorm),
aspect: wgpu::TextureAspect::Plane1,
..Default::default()
});
test_planar_texture_creation_sampling(&ctx, &y_view, &uv_view);
});

View File

@ -1,5 +1,7 @@
//! Tests of [`wgpu::Texture`] and related.
use wgpu_test::{fail, valid};
/// Ensures that submitting a command buffer referencing an already destroyed texture
/// results in an error.
#[test]
@ -54,3 +56,245 @@ fn destroyed_texture() {
queue.submit([encoder.finish()]);
}
/// Ensures that creating a texture view from a specific plane of a planar
/// texture works as expected.
#[test]
fn planar_texture_view_plane() {
let required_features = wgpu::Features::TEXTURE_FORMAT_NV12
| wgpu::Features::TEXTURE_FORMAT_P010
| wgpu::Features::TEXTURE_FORMAT_16BIT_NORM;
let device_desc = wgpu::DeviceDescriptor {
required_features,
..Default::default()
};
let (device, _queue) = wgpu::Device::noop(&device_desc);
let size = wgpu::Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
};
for (tex_format, view_format, view_aspect) in [
(
wgpu::TextureFormat::NV12,
wgpu::TextureFormat::R8Unorm,
wgpu::TextureAspect::Plane0,
),
(
wgpu::TextureFormat::NV12,
wgpu::TextureFormat::Rg8Unorm,
wgpu::TextureAspect::Plane1,
),
(
wgpu::TextureFormat::P010,
wgpu::TextureFormat::R16Unorm,
wgpu::TextureAspect::Plane0,
),
(
wgpu::TextureFormat::P010,
wgpu::TextureFormat::Rg16Unorm,
wgpu::TextureAspect::Plane1,
),
] {
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: tex_format,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
valid(&device, || {
let _ = tex.create_view(&wgpu::TextureViewDescriptor {
format: Some(view_format),
aspect: view_aspect,
..Default::default()
});
});
}
}
/// Ensures that attempting to create a texture view from a specific plane of a
/// non-planar texture fails validation.
#[test]
fn non_planar_texture_view_plane() {
let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
let size = wgpu::Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
};
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: wgpu::TextureFormat::R8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
fail(
&device,
|| {
let _ = tex.create_view(&wgpu::TextureViewDescriptor {
aspect: wgpu::TextureAspect::Plane0,
..Default::default()
});
},
Some("Aspect Plane0 is not in the source texture format R8Unorm"),
);
}
/// Ensures that attempting to create a texture view from an invalid plane of a
/// planar texture fails validation.
#[test]
fn planar_texture_view_plane_out_of_bounds() {
let required_features = wgpu::Features::TEXTURE_FORMAT_NV12
| wgpu::Features::TEXTURE_FORMAT_P010
| wgpu::Features::TEXTURE_FORMAT_16BIT_NORM;
let device_desc = wgpu::DeviceDescriptor {
required_features,
..Default::default()
};
let (device, _queue) = wgpu::Device::noop(&device_desc);
let size = wgpu::Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
};
for (tex_format, view_format, view_aspect) in [
(
wgpu::TextureFormat::NV12,
wgpu::TextureFormat::R8Unorm,
wgpu::TextureAspect::Plane2,
),
(
wgpu::TextureFormat::P010,
wgpu::TextureFormat::R16Unorm,
wgpu::TextureAspect::Plane2,
),
] {
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: tex_format,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
fail(
&device,
|| {
let _ = tex.create_view(&wgpu::TextureViewDescriptor {
format: Some(view_format),
aspect: view_aspect,
..Default::default()
});
},
Some(&format!(
"Aspect {view_aspect:?} is not in the source texture format {tex_format:?}"
)),
);
}
}
/// Ensures that attempting to create a texture view from a specific plane of a
/// planar texture with an invalid format fails validation.
#[test]
fn planar_texture_view_plane_bad_format() {
let required_features = wgpu::Features::TEXTURE_FORMAT_NV12
| wgpu::Features::TEXTURE_FORMAT_P010
| wgpu::Features::TEXTURE_FORMAT_16BIT_NORM;
let device_desc = wgpu::DeviceDescriptor {
required_features,
..Default::default()
};
let (device, _queue) = wgpu::Device::noop(&device_desc);
let size = wgpu::Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
};
for (tex_format, view_format, view_aspect) in [
(
wgpu::TextureFormat::NV12,
wgpu::TextureFormat::Rg8Unorm,
wgpu::TextureAspect::Plane0,
),
(
wgpu::TextureFormat::P010,
wgpu::TextureFormat::Rg16Unorm,
wgpu::TextureAspect::Plane0,
),
] {
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: tex_format,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
fail(
&device,
|| {
let _ = tex.create_view(&wgpu::TextureViewDescriptor {
format: Some(view_format),
aspect: view_aspect,
..Default::default()
});
},
Some(&format!(
"unable to view texture {tex_format:?} as {view_format:?}"
)),
);
}
}
/// Ensures that attempting to create a planar texture with an invalid size
/// fails validation.
#[test]
fn planar_texture_bad_size() {
let required_features =
wgpu::Features::TEXTURE_FORMAT_NV12 | wgpu::Features::TEXTURE_FORMAT_P010;
let device_desc = wgpu::DeviceDescriptor {
required_features,
..Default::default()
};
let (device, _queue) = wgpu::Device::noop(&device_desc);
let size = wgpu::Extent3d {
width: 255,
height: 255,
depth_or_array_layers: 1,
};
for format in [wgpu::TextureFormat::NV12, wgpu::TextureFormat::P010] {
fail(
&device,
|| {
let _ = device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
},
Some(&format!(
"width {} is not a multiple of {format:?}'s width multiple requirement",
size.width
)),
);
}
}

View File

@ -373,8 +373,10 @@ fn clear_texture_via_buffer_copies(
) {
assert!(!texture_desc.format.is_depth_stencil_format());
if texture_desc.format == wgt::TextureFormat::NV12 {
// TODO: Currently COPY_DST for NV12 textures is unsupported.
if texture_desc.format == wgt::TextureFormat::NV12
|| texture_desc.format == wgt::TextureFormat::P010
{
// TODO: Currently COPY_DST for NV12 and P010 textures is unsupported.
return;
}

View File

@ -817,6 +817,7 @@ impl NumericType {
panic!("Unexpected depth format")
}
Tf::NV12 => panic!("Unexpected nv12 format"),
Tf::P010 => panic!("Unexpected p010 format"),
Tf::Rgb9e5Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
Tf::Bc1RgbaUnorm
| Tf::Bc1RgbaUnormSrgb

View File

@ -68,6 +68,7 @@ pub fn map_texture_format_failable(
Tf::Depth32Float => DXGI_FORMAT_D32_FLOAT,
Tf::Depth32FloatStencil8 => DXGI_FORMAT_D32_FLOAT_S8X24_UINT,
Tf::NV12 => DXGI_FORMAT_NV12,
Tf::P010 => DXGI_FORMAT_P010,
Tf::Bc1RgbaUnorm => DXGI_FORMAT_BC1_UNORM,
Tf::Bc1RgbaUnormSrgb => DXGI_FORMAT_BC1_UNORM_SRGB,
Tf::Bc2RgbaUnorm => DXGI_FORMAT_BC2_UNORM,

View File

@ -414,6 +414,35 @@ impl super::Adapter {
bgra8unorm_storage_supported,
);
let p010_format_supported = {
let mut p010_info = Direct3D12::D3D12_FEATURE_DATA_FORMAT_SUPPORT {
Format: Dxgi::Common::DXGI_FORMAT_P010,
..Default::default()
};
let hr = unsafe {
device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_FORMAT_SUPPORT,
<*mut _>::cast(&mut p010_info),
size_of_val(&p010_info) as u32,
)
};
if hr.is_ok() {
let supports_texture2d = p010_info
.Support1
.contains(Direct3D12::D3D12_FORMAT_SUPPORT1_TEXTURE2D);
let supports_shader_load = p010_info
.Support1
.contains(Direct3D12::D3D12_FORMAT_SUPPORT1_SHADER_LOAD);
let supports_shader_sample = p010_info
.Support1
.contains(Direct3D12::D3D12_FORMAT_SUPPORT1_SHADER_SAMPLE);
supports_texture2d && supports_shader_load && supports_shader_sample
} else {
false
}
};
features.set(wgt::Features::TEXTURE_FORMAT_P010, p010_format_supported);
let mut features1 = Direct3D12::D3D12_FEATURE_DATA_D3D12_OPTIONS1::default();
let hr = unsafe {
device.CheckFeatureSupport(

View File

@ -1165,6 +1165,7 @@ impl crate::Adapter for super::Adapter {
| Tf::Depth24Plus
| Tf::Depth24PlusStencil8 => depth,
Tf::NV12 => empty,
Tf::P010 => empty,
Tf::Rgb9e5Ufloat => filterable,
Tf::Bc1RgbaUnorm
| Tf::Bc1RgbaUnormSrgb

View File

@ -89,6 +89,7 @@ impl super::AdapterShared {
glow::UNSIGNED_INT_24_8,
),
Tf::NV12 => unreachable!(),
Tf::P010 => unreachable!(),
Tf::Rgb9e5Ufloat => (glow::RGB9_E5, glow::RGB, glow::UNSIGNED_INT_5_9_9_9_REV),
Tf::Bc1RgbaUnorm => (glow::COMPRESSED_RGBA_S3TC_DXT1_EXT, glow::RGBA, 0),
Tf::Bc1RgbaUnormSrgb => (glow::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, glow::RGBA, 0),

View File

@ -1734,7 +1734,7 @@ impl From<wgt::TextureFormat> for FormatAspects {
wgt::TextureFormat::Depth32FloatStencil8 | wgt::TextureFormat::Depth24PlusStencil8 => {
Self::DEPTH_STENCIL
}
wgt::TextureFormat::NV12 => Self::PLANE_0 | Self::PLANE_1,
wgt::TextureFormat::NV12 | wgt::TextureFormat::P010 => Self::PLANE_0 | Self::PLANE_1,
_ => Self::COLOR,
}
}

View File

@ -290,6 +290,7 @@ impl crate::Adapter for super::Adapter {
flags
}
Tf::NV12 => return Tfc::empty(),
Tf::P010 => return Tfc::empty(),
Tf::Rgb9e5Ufloat => {
if pc.msaa_apple3 {
all_caps
@ -1175,6 +1176,7 @@ impl super::PrivateCapabilities {
}
}
Tf::NV12 => unreachable!(),
Tf::P010 => unreachable!(),
Tf::Rgb9e5Ufloat => MTL::RGB9E5Float,
Tf::Bc1RgbaUnorm => MTL::BC1_RGBA,
Tf::Bc1RgbaUnormSrgb => MTL::BC1_RGBA_sRGB,

View File

@ -850,6 +850,24 @@ impl PhysicalDeviceFeatures {
);
}
if let Some(ref _sampler_ycbcr_conversion) = self.sampler_ycbcr_conversion {
features.set(
F::TEXTURE_FORMAT_P010,
supports_format(
instance,
phd,
vk::Format::G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16,
vk::ImageTiling::OPTIMAL,
vk::FormatFeatureFlags::SAMPLED_IMAGE
| vk::FormatFeatureFlags::TRANSFER_SRC
| vk::FormatFeatureFlags::TRANSFER_DST,
) && !caps
.driver
.map(|driver| driver.driver_id == vk::DriverId::MOLTENVK)
.unwrap_or_default(),
);
}
features.set(
F::VULKAN_GOOGLE_DISPLAY_TIMING,
caps.supports_extension(google::display_timing::NAME),

View File

@ -78,6 +78,7 @@ impl super::PrivateCapabilities {
}
Tf::Depth16Unorm => F::D16_UNORM,
Tf::NV12 => F::G8_B8R8_2PLANE_420_UNORM,
Tf::P010 => F::G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16,
Tf::Rgb9e5Ufloat => F::E5B9G9R9_UFLOAT_PACK32,
Tf::Bc1RgbaUnorm => F::BC1_RGBA_UNORM_BLOCK,
Tf::Bc1RgbaUnormSrgb => F::BC1_RGBA_SRGB_BLOCK,

View File

@ -1,6 +1,6 @@
// Lets keep these on one line
#[rustfmt::skip]
pub const TEXTURE_FORMAT_LIST: [wgpu::TextureFormat; 117] = [
pub const TEXTURE_FORMAT_LIST: [wgpu::TextureFormat; 118] = [
wgpu::TextureFormat::R8Unorm,
wgpu::TextureFormat::R8Snorm,
wgpu::TextureFormat::R8Uint,
@ -52,6 +52,7 @@ pub const TEXTURE_FORMAT_LIST: [wgpu::TextureFormat; 117] = [
wgpu::TextureFormat::Depth32Float,
wgpu::TextureFormat::Depth32FloatStencil8,
wgpu::TextureFormat::NV12,
wgpu::TextureFormat::P010,
wgpu::TextureFormat::Bc1RgbaUnorm,
wgpu::TextureFormat::Bc1RgbaUnormSrgb,
wgpu::TextureFormat::Bc2RgbaUnorm,

View File

@ -984,6 +984,16 @@ bitflags_array! {
///
/// [`TextureFormat::NV12`]: super::TextureFormat::NV12
const TEXTURE_FORMAT_NV12 = 1 << 29;
/// Allows for creation of textures of format [`TextureFormat::P010`]
///
/// Supported platforms:
/// - DX12
/// - Vulkan
///
/// This is a native only feature.
///
/// [`TextureFormat::P010`]: super::TextureFormat::P010
const TEXTURE_FORMAT_P010 = 1 << 30;
/// Allows for the creation and usage of `ExternalTexture`s, and bind
/// group layouts containing external texture `BindingType`s.
@ -998,7 +1008,7 @@ bitflags_array! {
///
/// Supported platforms:
/// - DX12
const EXTERNAL_TEXTURE = 1 << 30;
const EXTERNAL_TEXTURE = 1 << 31;
// Shader:
@ -1012,7 +1022,7 @@ bitflags_array! {
/// - Vulkan
///
/// This is a native-only feature.
const EXPERIMENTAL_RAY_QUERY = 1 << 31;
const EXPERIMENTAL_RAY_QUERY = 1 << 32;
/// Enables 64-bit floating point types in SPIR-V shaders.
///
/// Note: even when supported by GPU hardware, 64-bit floating point operations are
@ -1022,14 +1032,14 @@ bitflags_array! {
/// - Vulkan
///
/// This is a native only feature.
const SHADER_F64 = 1 << 32;
const SHADER_F64 = 1 << 33;
/// Allows shaders to use i16. Not currently supported in `naga`, only available through `spirv-passthrough`.
///
/// Supported platforms:
/// - Vulkan
///
/// This is a native only feature.
const SHADER_I16 = 1 << 33;
const SHADER_I16 = 1 << 34;
/// Enables `builtin(primitive_index)` in fragment shaders.
///
/// Note: enables geometry processing for pipelines using the builtin.
@ -1043,7 +1053,7 @@ bitflags_array! {
/// - OpenGL (some)
///
/// This is a native only feature.
const SHADER_PRIMITIVE_INDEX = 1 << 34;
const SHADER_PRIMITIVE_INDEX = 1 << 35;
/// Allows shaders to use the `early_depth_test` attribute.
///
/// The attribute is applied to the fragment shader entry point. It can be used in two
@ -1071,7 +1081,7 @@ bitflags_array! {
/// This is a native only feature.
///
/// [`EarlyDepthTest`]: https://docs.rs/naga/latest/naga/ir/enum.EarlyDepthTest.html
const SHADER_EARLY_DEPTH_TEST = 1 << 35;
const SHADER_EARLY_DEPTH_TEST = 1 << 36;
/// Allows shaders to use i64 and u64.
///
/// Supported platforms:
@ -1080,7 +1090,7 @@ bitflags_array! {
/// - Metal (with MSL 2.3+)
///
/// This is a native only feature.
const SHADER_INT64 = 1 << 36;
const SHADER_INT64 = 1 << 37;
/// Allows compute and fragment shaders to use the subgroup operation built-ins
///
/// Supported Platforms:
@ -1089,14 +1099,14 @@ bitflags_array! {
/// - Metal
///
/// This is a native only feature.
const SUBGROUP = 1 << 37;
const SUBGROUP = 1 << 38;
/// Allows vertex shaders to use the subgroup operation built-ins
///
/// Supported Platforms:
/// - Vulkan
///
/// This is a native only feature.
const SUBGROUP_VERTEX = 1 << 38;
const SUBGROUP_VERTEX = 1 << 39;
/// Allows shaders to use the subgroup barrier
///
/// Supported Platforms:
@ -1104,7 +1114,7 @@ bitflags_array! {
/// - Metal
///
/// This is a native only feature.
const SUBGROUP_BARRIER = 1 << 39;
const SUBGROUP_BARRIER = 1 << 40;
/// Allows the use of pipeline cache objects
///
/// Supported platforms:
@ -1113,7 +1123,7 @@ bitflags_array! {
/// Unimplemented Platforms:
/// - DX12
/// - Metal
const PIPELINE_CACHE = 1 << 40;
const PIPELINE_CACHE = 1 << 41;
/// Allows shaders to use i64 and u64 atomic min and max.
///
/// Supported platforms:
@ -1122,7 +1132,7 @@ bitflags_array! {
/// - Metal (with MSL 2.4+)
///
/// This is a native only feature.
const SHADER_INT64_ATOMIC_MIN_MAX = 1 << 41;
const SHADER_INT64_ATOMIC_MIN_MAX = 1 << 42;
/// Allows shaders to use all i64 and u64 atomic operations.
///
/// Supported platforms:
@ -1130,7 +1140,7 @@ bitflags_array! {
/// - DX12 (with SM 6.6+)
///
/// This is a native only feature.
const SHADER_INT64_ATOMIC_ALL_OPS = 1 << 42;
const SHADER_INT64_ATOMIC_ALL_OPS = 1 << 43;
/// Allows using the [VK_GOOGLE_display_timing] Vulkan extension.
///
/// This is used for frame pacing to reduce latency, and is generally only available on Android.
@ -1146,7 +1156,7 @@ bitflags_array! {
///
/// [VK_GOOGLE_display_timing]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_GOOGLE_display_timing.html
/// [`Surface::as_hal()`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html#method.as_hal
const VULKAN_GOOGLE_DISPLAY_TIMING = 1 << 43;
const VULKAN_GOOGLE_DISPLAY_TIMING = 1 << 44;
/// Allows using the [VK_KHR_external_memory_win32] Vulkan extension.
///
@ -1156,7 +1166,7 @@ bitflags_array! {
/// This is a native only feature.
///
/// [VK_KHR_external_memory_win32]: https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_external_memory_win32.html
const VULKAN_EXTERNAL_MEMORY_WIN32 = 1 << 44;
const VULKAN_EXTERNAL_MEMORY_WIN32 = 1 << 45;
/// Enables R64Uint image atomic min and max.
///
@ -1166,7 +1176,7 @@ bitflags_array! {
/// - Metal (with MSL 3.1+)
///
/// This is a native only feature.
const TEXTURE_INT64_ATOMIC = 1 << 45;
const TEXTURE_INT64_ATOMIC = 1 << 46;
/// Allows uniform buffers to be bound as binding arrays.
///
@ -1183,7 +1193,7 @@ bitflags_array! {
/// - Vulkan 1.2+ (or VK_EXT_descriptor_indexing)'s `shaderUniformBufferArrayNonUniformIndexing` feature)
///
/// This is a native only feature.
const UNIFORM_BUFFER_BINDING_ARRAYS = 1 << 46;
const UNIFORM_BUFFER_BINDING_ARRAYS = 1 << 47;
/// Enables mesh shaders and task shaders in mesh shader pipelines.
///
@ -1195,7 +1205,7 @@ bitflags_array! {
/// - Metal
///
/// This is a native only feature.
const EXPERIMENTAL_MESH_SHADER = 1 << 47;
const EXPERIMENTAL_MESH_SHADER = 1 << 48;
/// ***THIS IS EXPERIMENTAL:*** Features enabled by this may have
/// major bugs in them and are expected to be subject to breaking changes, suggestions
@ -1210,7 +1220,7 @@ bitflags_array! {
/// This is a native only feature
///
/// [`AccelerationStructureFlags::ALLOW_RAY_HIT_VERTEX_RETURN`]: super::AccelerationStructureFlags::ALLOW_RAY_HIT_VERTEX_RETURN
const EXPERIMENTAL_RAY_HIT_VERTEX_RETURN = 1 << 48;
const EXPERIMENTAL_RAY_HIT_VERTEX_RETURN = 1 << 49;
/// Enables multiview in mesh shader pipelines
///
@ -1222,7 +1232,7 @@ bitflags_array! {
/// - Metal
///
/// This is a native only feature.
const EXPERIMENTAL_MESH_SHADER_MULTIVIEW = 1 << 49;
const EXPERIMENTAL_MESH_SHADER_MULTIVIEW = 1 << 50;
/// Allows usage of additional vertex formats in [BlasTriangleGeometrySizeDescriptor::vertex_format]
///
@ -1231,7 +1241,7 @@ bitflags_array! {
/// - DX12
///
/// [BlasTriangleGeometrySizeDescriptor::vertex_format]: super::BlasTriangleGeometrySizeDescriptor
const EXTENDED_ACCELERATION_STRUCTURE_VERTEX_FORMATS = 1 << 50;
const EXTENDED_ACCELERATION_STRUCTURE_VERTEX_FORMATS = 1 << 51;
/// Enables creating shader modules from DirectX HLSL or DXIL shaders (unsafe)
///
@ -1241,7 +1251,7 @@ bitflags_array! {
/// - DX12
///
/// This is a native only feature.
const HLSL_DXIL_SHADER_PASSTHROUGH = 1 << 51;
const HLSL_DXIL_SHADER_PASSTHROUGH = 1 << 52;
}
/// Features that are not guaranteed to be supported.

View File

@ -2230,6 +2230,23 @@ pub enum TextureFormat {
/// [`Features::TEXTURE_FORMAT_NV12`] must be enabled to use this texture format.
NV12,
/// YUV 4:2:0 chroma subsampled format.
///
/// Contains two planes:
/// - 0: Single 16 bit channel luminance, of which only the high 10 bits
/// are used.
/// - 1: Dual 16 bit channel chrominance at half width and half height, of
/// which only the high 10 bits are used.
///
/// Valid view formats for luminance are [`TextureFormat::R16Unorm`].
///
/// Valid view formats for chrominance are [`TextureFormat::Rg16Unorm`].
///
/// Width and height must be even.
///
/// [`Features::TEXTURE_FORMAT_P010`] must be enabled to use this texture format.
P010,
// Compressed textures usable with `TEXTURE_COMPRESSION_BC` feature. `TEXTURE_COMPRESSION_SLICED_3D` is required to use with 3D textures.
/// 4x4 block compressed texture. 8 bytes per block (4 bit/px). 4 color + alpha pallet. 5 bit R + 6 bit G + 5 bit B + 1 bit alpha.
/// [0, 63] ([0, 1] for alpha) converted to/from float [0, 1] in shader.
@ -2475,6 +2492,7 @@ impl<'de> Deserialize<'de> for TextureFormat {
"depth24plus" => TextureFormat::Depth24Plus,
"depth24plus-stencil8" => TextureFormat::Depth24PlusStencil8,
"nv12" => TextureFormat::NV12,
"p010" => TextureFormat::P010,
"rgb9e5ufloat" => TextureFormat::Rgb9e5Ufloat,
"bc1-rgba-unorm" => TextureFormat::Bc1RgbaUnorm,
"bc1-rgba-unorm-srgb" => TextureFormat::Bc1RgbaUnormSrgb,
@ -2604,6 +2622,7 @@ impl Serialize for TextureFormat {
TextureFormat::Depth24Plus => "depth24plus",
TextureFormat::Depth24PlusStencil8 => "depth24plus-stencil8",
TextureFormat::NV12 => "nv12",
TextureFormat::P010 => "p010",
TextureFormat::Rgb9e5Ufloat => "rgb9e5ufloat",
TextureFormat::Bc1RgbaUnorm => "bc1-rgba-unorm",
TextureFormat::Bc1RgbaUnormSrgb => "bc1-rgba-unorm-srgb",
@ -2696,6 +2715,8 @@ impl TextureFormat {
(Self::Depth32FloatStencil8, TextureAspect::DepthOnly) => Some(Self::Depth32Float),
(Self::NV12, TextureAspect::Plane0) => Some(Self::R8Unorm),
(Self::NV12, TextureAspect::Plane1) => Some(Self::Rg8Unorm),
(Self::P010, TextureAspect::Plane0) => Some(Self::R16Unorm),
(Self::P010, TextureAspect::Plane1) => Some(Self::Rg16Unorm),
// views to multi-planar formats must specify the plane
(format, TextureAspect::All) if !format.is_multi_planar_format() => Some(format),
_ => None,
@ -2751,6 +2772,7 @@ impl TextureFormat {
pub fn planes(&self) -> Option<u32> {
match *self {
Self::NV12 => Some(2),
Self::P010 => Some(2),
_ => None,
}
}
@ -2788,6 +2810,7 @@ impl TextureFormat {
pub fn size_multiple_requirement(&self) -> (u32, u32) {
match *self {
Self::NV12 => (2, 2),
Self::P010 => (2, 2),
_ => self.block_dimensions(),
}
}
@ -2848,7 +2871,8 @@ impl TextureFormat {
| Self::Depth24PlusStencil8
| Self::Depth32Float
| Self::Depth32FloatStencil8
| Self::NV12 => (1, 1),
| Self::NV12
| Self::P010 => (1, 1),
Self::Bc1RgbaUnorm
| Self::Bc1RgbaUnormSrgb
@ -2966,6 +2990,7 @@ impl TextureFormat {
Self::Depth32FloatStencil8 => Features::DEPTH32FLOAT_STENCIL8,
Self::NV12 => Features::TEXTURE_FORMAT_NV12,
Self::P010 => Features::TEXTURE_FORMAT_P010,
Self::R16Unorm
| Self::R16Snorm
@ -3099,8 +3124,10 @@ impl TextureFormat {
Self::Depth32Float => ( msaa, attachment),
Self::Depth32FloatStencil8 => ( msaa, attachment),
// We only support sampling nv12 textures until we implement transfer plane data.
// We only support sampling nv12 and p010 textures until we
// implement transfer plane data.
Self::NV12 => ( none, binding),
Self::P010 => ( none, binding),
Self::R16Unorm => ( msaa | s_ro_wo, storage),
Self::R16Snorm => ( msaa | s_ro_wo, storage),
@ -3230,7 +3257,7 @@ impl TextureFormat {
_ => None,
},
Self::NV12 => match aspect {
Self::NV12 | Self::P010 => match aspect {
Some(TextureAspect::Plane0) | Some(TextureAspect::Plane1) => {
Some(unfilterable_float)
}
@ -3363,6 +3390,12 @@ impl TextureFormat {
_ => None,
},
Self::P010 => match aspect {
Some(TextureAspect::Plane0) => Some(2),
Some(TextureAspect::Plane1) => Some(4),
_ => None,
},
Self::Bc1RgbaUnorm | Self::Bc1RgbaUnormSrgb | Self::Bc4RUnorm | Self::Bc4RSnorm => {
Some(8)
}
@ -3448,6 +3481,7 @@ impl TextureFormat {
| Self::Depth32Float
| Self::Depth32FloatStencil8
| Self::NV12
| Self::P010
| Self::Rgb9e5Ufloat
| Self::Bc1RgbaUnorm
| Self::Bc1RgbaUnormSrgb
@ -3531,6 +3565,7 @@ impl TextureFormat {
| Self::Depth32Float
| Self::Depth32FloatStencil8
| Self::NV12
| Self::P010
| Self::Rgb9e5Ufloat
| Self::Bc1RgbaUnorm
| Self::Bc1RgbaUnormSrgb
@ -3625,7 +3660,7 @@ impl TextureFormat {
_ => 2,
},
Self::NV12 => match aspect {
Self::NV12 | Self::P010 => match aspect {
TextureAspect::Plane0 => 1,
TextureAspect::Plane1 => 2,
_ => 3,
@ -3735,6 +3770,8 @@ impl TextureFormat {
Self::Stencil8 => 1,
// Two chroma bytes per block, one luma byte per block
Self::NV12 => 3,
// Two chroma u16s and one luma u16 per block
Self::P010 => 6,
f => {
log::warn!("Memory footprint for format {f:?} is not implemented");
0