diff --git a/deno_webgpu/texture.rs b/deno_webgpu/texture.rs index 5eadd5b3c2..fb9640f59f 100644 --- a/deno_webgpu/texture.rs +++ b/deno_webgpu/texture.rs @@ -124,6 +124,7 @@ pub fn op_webgpu_create_texture_view( format: args.format, dimension: args.dimension, range: args.range, + plane: None, }; gfx_put!(texture => instance.texture_create_view( diff --git a/examples/src/mipmap/mod.rs b/examples/src/mipmap/mod.rs index c6dc7fa251..59c9aab4d4 100644 --- a/examples/src/mipmap/mod.rs +++ b/examples/src/mipmap/mod.rs @@ -133,6 +133,7 @@ impl Example { mip_level_count: Some(1), base_array_layer: 0, array_layer_count: None, + ..Default::default() }) }) .collect::>(); diff --git a/examples/src/shadow/mod.rs b/examples/src/shadow/mod.rs index 485d0d78d6..3c4aa88067 100644 --- a/examples/src/shadow/mod.rs +++ b/examples/src/shadow/mod.rs @@ -398,6 +398,7 @@ impl crate::framework::Example for Example { mip_level_count: None, base_array_layer: i as u32, array_layer_count: Some(1), + ..Default::default() })) }) .collect::>(); diff --git a/tests/tests/bgra8unorm_storage.rs b/tests/tests/bgra8unorm_storage.rs index 5cfcb27a29..0caf7f80e3 100644 --- a/tests/tests/bgra8unorm_storage.rs +++ b/tests/tests/bgra8unorm_storage.rs @@ -49,6 +49,7 @@ static BGRA8_UNORM_STORAGE: GpuTestConfiguration = GpuTestConfiguration::new() base_array_layer: 0, mip_level_count: Some(1), array_layer_count: Some(1), + ..Default::default() }); let readback_buffer = device.create_buffer(&wgpu::BufferDescriptor { diff --git a/tests/tests/nv12_texture/mod.rs b/tests/tests/nv12_texture/mod.rs new file mode 100644 index 0000000000..0d9b4b5d4d --- /dev/null +++ b/tests/tests/nv12_texture/mod.rs @@ -0,0 +1,250 @@ +//! Tests for nv12 texture creation and sampling. + +use wgpu_test::{fail, gpu_test, GpuTestConfiguration, TestParameters}; + +#[gpu_test] +static NV12_TEXTURE_CREATION_SAMPLING: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::TEXTURE_FORMAT_NV12)) + .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: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + 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, + }); + + 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: &[wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::Rg8Unorm], + }); + let y_view = tex.create_view(&wgpu::TextureViewDescriptor { + format: Some(wgpu::TextureFormat::R8Unorm), + plane: Some(0), + ..Default::default() + }); + let uv_view = tex.create_view(&wgpu::TextureViewDescriptor { + format: Some(wgpu::TextureFormat::Rg8Unorm), + plane: Some(1), + ..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_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_CREATION_BAD_VIEW_FORMATS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::TEXTURE_FORMAT_NV12)) + .run_sync(|ctx| { + let size = wgpu::Extent3d { + width: 256, + height: 256, + 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: &[wgpu::TextureFormat::Rgba8Unorm], + }); + }); + }); + +#[gpu_test] +static NV12_TEXTURE_VIEW_PLANE_ON_NON_PLANAR_FORMAT: GpuTestConfiguration = + GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::TEXTURE_FORMAT_NV12)) + .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 { + plane: Some(0), + ..Default::default() + }); + }); + }); + +#[gpu_test] +static NV12_TEXTURE_VIEW_PLANE_OUT_OF_BOUNDS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::TEXTURE_FORMAT_NV12)) + .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: &[wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::Rg8Unorm], + }); + fail(&ctx.device, || { + let _ = tex.create_view(&wgpu::TextureViewDescriptor { + format: Some(wgpu::TextureFormat::R8Unorm), + plane: Some(2), + ..Default::default() + }); + }); + }); + +#[gpu_test] +static NV12_TEXTURE_BAD_FORMAT_VIEW_PLANE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::TEXTURE_FORMAT_NV12)) + .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: &[wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::Rg8Unorm], + }); + fail(&ctx.device, || { + let _ = tex.create_view(&wgpu::TextureViewDescriptor { + format: Some(wgpu::TextureFormat::Rg8Unorm), + plane: Some(0), + ..Default::default() + }); + }); + }); + +#[gpu_test] +static NV12_TEXTURE_BAD_SIZE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::TEXTURE_FORMAT_NV12)) + .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: &[wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::Rg8Unorm], + }); + }); + }); diff --git a/tests/tests/nv12_texture/nv12_texture.wgsl b/tests/tests/nv12_texture/nv12_texture.wgsl new file mode 100644 index 0000000000..ea974100b1 --- /dev/null +++ b/tests/tests/nv12_texture/nv12_texture.wgsl @@ -0,0 +1,33 @@ +struct VertexOutput { + @builtin(position) pos: vec4, + @location(0) uv: vec2, +} + +@vertex +fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput { + var output: VertexOutput; + // 0, 0 + // 2, 0 + // 0, 2 + // 2, 2 + let v_data = vec2(f32((vertexIndex << 1u) & 2u), f32(vertexIndex & 2u)); + output.pos = vec4(v_data - 1.0, 0.0, 1.0); + output.uv = v_data / 2.0; + return output; +} + +@group(0) @binding(0) var s: sampler; +@group(0) @binding(1) var tex_y: texture_2d; +@group(0) @binding(2) var tex_uv: texture_2d; + +@fragment +fn fs_main(v_ouput: VertexOutput) -> @location(0) vec4 { + let luminance = textureSample(tex_y, s, v_ouput.uv).r; + let chrominance = textureSample(tex_uv, s, v_ouput.uv).rg; + let rgb = mat3x3( + 1.000000, 1.000000, 1.000000, + 0.000000,-0.187324, 1.855600, + 1.574800,-0.468124, 0.000000, + ) * vec3(luminance, chrominance.r - 0.5, chrominance.g - 0.5); + return vec4(rgb, 1.0); +} diff --git a/tests/tests/root.rs b/tests/tests/root.rs index fbb56a7b2f..5fd119b2c9 100644 --- a/tests/tests/root.rs +++ b/tests/tests/root.rs @@ -18,6 +18,7 @@ mod external_texture; mod instance; mod life_cycle; mod mem_leaks; +mod nv12_texture; mod occlusion_query; mod partially_bounded_arrays; mod pipeline; diff --git a/wgpu-core/src/command/clear.rs b/wgpu-core/src/command/clear.rs index 5405c926e5..620911c181 100644 --- a/wgpu-core/src/command/clear.rs +++ b/wgpu-core/src/command/clear.rs @@ -337,6 +337,11 @@ fn clear_texture_via_buffer_copies( hal::FormatAspects::COLOR ); + if texture_desc.format == wgt::TextureFormat::NV12 { + // TODO: Currently COPY_DST for NV12 textures is unsupported. + return; + } + // Gather list of zero_buffer copies and issue a single command then to perform them let mut zero_buffer_copy_regions = Vec::new(); let buffer_copy_pitch = alignments.buffer_copy_pitch.get() as u32; diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index d9d00bd6b3..6d2e140a8b 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -750,7 +750,8 @@ impl Device { if desc.format == *format { continue; } - if desc.format.remove_srgb_suffix() != format.remove_srgb_suffix() { + + if !check_texture_view_format_compatible(desc.format, *format) { return Err(CreateTextureError::InvalidViewFormat(*format, desc.format)); } hal_view_formats.push(*format); @@ -803,23 +804,35 @@ impl Device { let mut clear_views = SmallVec::new(); for mip_level in 0..desc.mip_level_count { for array_layer in 0..desc.size.depth_or_array_layers { - let desc = hal::TextureViewDescriptor { - label: clear_label, - format: desc.format, - dimension, - usage, - range: wgt::ImageSubresourceRange { - aspect: wgt::TextureAspect::All, - base_mip_level: mip_level, - mip_level_count: Some(1), - base_array_layer: array_layer, - array_layer_count: Some(1), - }, - }; - clear_views.push(Some( - unsafe { self.raw().create_texture_view(&raw_texture, &desc) } - .map_err(DeviceError::from)?, - )); + macro_rules! push_clear_view { + ($format:expr, $plane:expr) => { + let desc = hal::TextureViewDescriptor { + label: clear_label, + format: $format, + dimension, + usage, + range: wgt::ImageSubresourceRange { + aspect: wgt::TextureAspect::All, + base_mip_level: mip_level, + mip_level_count: Some(1), + base_array_layer: array_layer, + array_layer_count: Some(1), + }, + plane: $plane, + }; + clear_views.push(Some( + unsafe { self.raw().create_texture_view(&raw_texture, &desc) } + .map_err(DeviceError::from)?, + )); + }; + } + + if desc.format == wgt::TextureFormat::NV12 { + push_clear_view!(wgt::TextureFormat::R8Unorm, Some(0)); + push_clear_view!(wgt::TextureFormat::Rg8Unorm, Some(1)); + } else { + push_clear_view!(desc.format, None); + } } } resource::TextureClearMode::RenderPass { @@ -1014,6 +1027,8 @@ impl Device { }); }; + validate_texture_view_plane(texture.desc.format, resolved_format, desc.plane)?; + // https://gpuweb.github.io/gpuweb/#abstract-opdef-renderable-texture-view let render_extent = 'b: loop { if !texture @@ -1105,6 +1120,7 @@ impl Device { dimension: resolved_dimension, usage, range: resolved_range, + plane: desc.plane, }; let raw = unsafe { @@ -3348,3 +3364,39 @@ impl Resource for Device { &mut self.info } } + +fn check_texture_view_format_compatible( + texture_format: TextureFormat, + view_format: TextureFormat, +) -> bool { + use TextureFormat::*; + + match (texture_format, view_format) { + (NV12, R8Unorm | R8Uint | Rg8Unorm | Rg8Uint) => true, + _ => texture_format.remove_srgb_suffix() == view_format.remove_srgb_suffix(), + } +} + +fn validate_texture_view_plane( + texture_format: TextureFormat, + view_format: TextureFormat, + plane: Option, +) -> Result<(), resource::CreateTextureViewError> { + use TextureFormat::*; + + match (texture_format, view_format, plane) { + (NV12, R8Unorm | R8Uint, Some(0)) => Ok(()), + (NV12, Rg8Unorm | Rg8Uint, Some(1)) => Ok(()), + (NV12, _, _) => { + Err(resource::CreateTextureViewError::InvalidTextureViewPlane { plane, view_format }) + } + + (_, _, Some(_)) => Err( + resource::CreateTextureViewError::InvalidTextureViewPlaneOnNonplanarTexture { + plane, + texture_format, + }, + ), + _ => Ok(()), + } +} diff --git a/wgpu-core/src/present.rs b/wgpu-core/src/present.rs index eb78630da2..fe76fdc8ed 100644 --- a/wgpu-core/src/present.rs +++ b/wgpu-core/src/present.rs @@ -196,6 +196,7 @@ impl Global { dimension: wgt::TextureViewDimension::D2, usage: hal::TextureUses::COLOR_TARGET, range: wgt::ImageSubresourceRange::default(), + plane: None, }; let clear_view = unsafe { hal::Device::create_texture_view( diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index 2d4f142aff..49d36a7acc 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -988,6 +988,8 @@ pub struct TextureViewDescriptor<'a> { pub dimension: Option, /// Range within the texture that is accessible via this view. pub range: wgt::ImageSubresourceRange, + /// The plane of the texture view. + pub plane: Option, } #[derive(Debug)] @@ -1098,6 +1100,16 @@ pub enum CreateTextureViewError { texture: wgt::TextureFormat, view: wgt::TextureFormat, }, + #[error("Invalid texture view plane `{plane:?}` with view format `{view_format:?}`")] + InvalidTextureViewPlane { + plane: Option, + view_format: wgt::TextureFormat, + }, + #[error("Invalid texture view plane `{plane:?}` on non-planar texture `{texture_format:?}`")] + InvalidTextureViewPlaneOnNonplanarTexture { + plane: Option, + texture_format: wgt::TextureFormat, + }, } #[derive(Clone, Debug, Error)] diff --git a/wgpu-core/src/validation.rs b/wgpu-core/src/validation.rs index b523bf9a73..687f75e150 100644 --- a/wgpu-core/src/validation.rs +++ b/wgpu-core/src/validation.rs @@ -690,6 +690,7 @@ impl NumericType { | Tf::Depth24PlusStencil8 => { panic!("Unexpected depth format") } + Tf::NV12 => panic!("Unexpected nv12 format"), Tf::Rgb9e5Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32), Tf::Bc1RgbaUnorm | Tf::Bc1RgbaUnormSrgb diff --git a/wgpu-hal/examples/halmark/main.rs b/wgpu-hal/examples/halmark/main.rs index 609146454a..60b2c144f2 100644 --- a/wgpu-hal/examples/halmark/main.rs +++ b/wgpu-hal/examples/halmark/main.rs @@ -422,6 +422,7 @@ impl Example { dimension: wgt::TextureViewDimension::D2, usage: hal::TextureUses::RESOURCE, range: wgt::ImageSubresourceRange::default(), + plane: None, }; let texture_view = unsafe { device.create_texture_view(&texture, &view_desc).unwrap() }; @@ -658,6 +659,7 @@ impl Example { dimension: wgt::TextureViewDimension::D2, usage: hal::TextureUses::COLOR_TARGET, range: wgt::ImageSubresourceRange::default(), + plane: None, }; let surface_tex_view = unsafe { self.device diff --git a/wgpu-hal/examples/raw-gles.rs b/wgpu-hal/examples/raw-gles.rs index 81ab4171e3..71e08443ea 100644 --- a/wgpu-hal/examples/raw-gles.rs +++ b/wgpu-hal/examples/raw-gles.rs @@ -142,6 +142,7 @@ fn fill_screen(exposed: &hal::ExposedAdapter, width: u32, height dimension: wgt::TextureViewDimension::D2, usage: hal::TextureUses::COLOR_TARGET, range: wgt::ImageSubresourceRange::default(), + plane: None, }, ) .unwrap() diff --git a/wgpu-hal/src/auxil/dxgi/conv.rs b/wgpu-hal/src/auxil/dxgi/conv.rs index f3f4a18317..eb74b5adf8 100644 --- a/wgpu-hal/src/auxil/dxgi/conv.rs +++ b/wgpu-hal/src/auxil/dxgi/conv.rs @@ -62,6 +62,7 @@ pub fn map_texture_format_failable(format: wgt::TextureFormat) -> Option DXGI_FORMAT_D24_UNORM_S8_UINT, Tf::Depth32Float => DXGI_FORMAT_D32_FLOAT, Tf::Depth32FloatStencil8 => DXGI_FORMAT_D32_FLOAT_S8X24_UINT, + Tf::NV12 => DXGI_FORMAT_NV12, Tf::Bc1RgbaUnorm => DXGI_FORMAT_BC1_UNORM, Tf::Bc1RgbaUnormSrgb => DXGI_FORMAT_BC1_UNORM_SRGB, Tf::Bc2RgbaUnorm => DXGI_FORMAT_BC2_UNORM, diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 04295508a3..c2ffda8429 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -249,7 +249,8 @@ impl super::Adapter { | wgt::Features::PUSH_CONSTANTS | wgt::Features::SHADER_PRIMITIVE_INDEX | wgt::Features::RG11B10UFLOAT_RENDERABLE - | wgt::Features::DUAL_SOURCE_BLENDING; + | wgt::Features::DUAL_SOURCE_BLENDING + | wgt::Features::TEXTURE_FORMAT_NV12; //TODO: in order to expose this, we need to run a compute shader // that extract the necessary statistics out of the D3D12 result. diff --git a/wgpu-hal/src/dx12/device.rs b/wgpu-hal/src/dx12/device.rs index 649fdb4f8b..90af93661e 100644 --- a/wgpu-hal/src/dx12/device.rs +++ b/wgpu-hal/src/dx12/device.rs @@ -467,7 +467,11 @@ impl crate::Device for super::Device { aspects: view_desc.aspects, target_base: ( texture.resource.clone(), - texture.calc_subresource(desc.range.base_mip_level, desc.range.base_array_layer, 0), + texture.calc_subresource( + desc.range.base_mip_level, + desc.range.base_array_layer, + desc.plane.unwrap_or(0), + ), ), handle_srv: if desc.usage.intersects(crate::TextureUses::RESOURCE) { let raw_desc = unsafe { view_desc.to_srv() }; diff --git a/wgpu-hal/src/dx12/view.rs b/wgpu-hal/src/dx12/view.rs index e7a051b535..9a597e464c 100644 --- a/wgpu-hal/src/dx12/view.rs +++ b/wgpu-hal/src/dx12/view.rs @@ -14,6 +14,7 @@ pub(super) struct ViewDescriptor { array_layer_count: u32, mip_level_base: u32, mip_level_count: u32, + plane: u32, } impl crate::TextureViewDescriptor<'_> { @@ -30,6 +31,7 @@ impl crate::TextureViewDescriptor<'_> { mip_level_count: self.range.mip_level_count.unwrap_or(!0), array_layer_base: self.range.base_array_layer, array_layer_count: self.range.array_layer_count.unwrap_or(!0), + plane: self.plane.unwrap_or(0), } } } @@ -79,7 +81,7 @@ impl ViewDescriptor { *desc.u.Texture2D_mut() = d3d12_ty::D3D12_TEX2D_SRV { MostDetailedMip: self.mip_level_base, MipLevels: self.mip_level_count, - PlaneSlice: 0, + PlaneSlice: self.plane, ResourceMinLODClamp: 0.0, } } @@ -103,7 +105,7 @@ impl ViewDescriptor { MipLevels: self.mip_level_count, FirstArraySlice: self.array_layer_base, ArraySize: self.array_layer_count, - PlaneSlice: 0, + PlaneSlice: self.plane, ResourceMinLODClamp: 0.0, } } @@ -179,7 +181,7 @@ impl ViewDescriptor { unsafe { *desc.u.Texture2D_mut() = d3d12_ty::D3D12_TEX2D_UAV { MipSlice: self.mip_level_base, - PlaneSlice: 0, + PlaneSlice: self.plane, } } } @@ -190,7 +192,7 @@ impl ViewDescriptor { MipSlice: self.mip_level_base, FirstArraySlice: self.array_layer_base, ArraySize: self.array_layer_count, - PlaneSlice: 0, + PlaneSlice: self.plane, } } } @@ -250,7 +252,7 @@ impl ViewDescriptor { unsafe { *desc.u.Texture2D_mut() = d3d12_ty::D3D12_TEX2D_RTV { MipSlice: self.mip_level_base, - PlaneSlice: 0, + PlaneSlice: self.plane, } } } @@ -272,7 +274,7 @@ impl ViewDescriptor { MipSlice: self.mip_level_base, FirstArraySlice: self.array_layer_base, ArraySize: self.array_layer_count, - PlaneSlice: 0, + PlaneSlice: self.plane, } } } diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index ed1fa9cd54..b58b8dee1b 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -1073,6 +1073,7 @@ impl crate::Adapter for super::Adapter { | Tf::Depth32FloatStencil8 | Tf::Depth24Plus | Tf::Depth24PlusStencil8 => depth, + Tf::NV12 => unreachable!(), Tf::Rgb9e5Ufloat => filterable, Tf::Bc1RgbaUnorm | Tf::Bc1RgbaUnormSrgb diff --git a/wgpu-hal/src/gles/conv.rs b/wgpu-hal/src/gles/conv.rs index 3fb8383a51..ebf0c65f52 100644 --- a/wgpu-hal/src/gles/conv.rs +++ b/wgpu-hal/src/gles/conv.rs @@ -87,6 +87,7 @@ impl super::AdapterShared { glow::DEPTH_STENCIL, glow::UNSIGNED_INT_24_8, ), + Tf::NV12 => 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), diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index bc7a908ce9..fb4aaf2580 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -981,6 +981,7 @@ pub struct TextureViewDescriptor<'a> { pub dimension: wgt::TextureViewDimension, pub usage: TextureUses, pub range: wgt::ImageSubresourceRange, + pub plane: Option, } #[derive(Clone, Debug)] diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index 99efa280a1..e05091053a 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -232,6 +232,7 @@ impl crate::Adapter for super::Adapter { } flags } + Tf::NV12 => return Tfc::empty(), Tf::Rgb9e5Ufloat => { if pc.msaa_apple3 { all_caps @@ -1022,6 +1023,7 @@ impl super::PrivateCapabilities { Depth32Float_Stencil8 } } + Tf::NV12 => unreachable!(), Tf::Rgb9e5Ufloat => RGB9E5Float, Tf::Bc1RgbaUnorm => BC1_RGBA, Tf::Bc1RgbaUnormSrgb => BC1_RGBA_sRGB, diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 5b1cf2fccf..0af87eb072 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -301,6 +301,7 @@ impl PhysicalDeviceFeatures { fn to_wgpu( &self, + adapter_info: &wgt::AdapterInfo, instance: &ash::Instance, phd: vk::PhysicalDevice, caps: &PhysicalDeviceCapabilities, @@ -535,6 +536,22 @@ impl PhysicalDeviceFeatures { supports_bgra8unorm_storage(instance, phd, caps.device_api_version), ); + features.set( + F::TEXTURE_FORMAT_NV12, + (caps.device_api_version >= vk::API_VERSION_1_1 + || caps.supports_extension(vk::KhrSamplerYcbcrConversionFn::name())) + && supports_format( + instance, + phd, + vk::Format::G8_B8R8_2PLANE_420_UNORM, + vk::ImageTiling::OPTIMAL, + vk::FormatFeatureFlags::SAMPLED_IMAGE + | vk::FormatFeatureFlags::TRANSFER_SRC + | vk::FormatFeatureFlags::TRANSFER_DST, + ) + && !adapter_info.driver.contains("MoltenVK"), + ); + (features, dl_flags) } @@ -969,7 +986,7 @@ impl super::Instance { }; let (available_features, downlevel_flags) = - phd_features.to_wgpu(&self.shared.raw, phd, &phd_capabilities); + phd_features.to_wgpu(&info, &self.shared.raw, phd, &phd_capabilities); let mut workarounds = super::Workarounds::empty(); { // see https://github.com/gfx-rs/gfx/issues/1930 @@ -1551,14 +1568,14 @@ impl crate::Adapter for super::Adapter { .framebuffer_stencil_sample_counts .min(limits.sampled_image_stencil_sample_counts) } else { - match format.sample_type(None).unwrap() { - wgt::TextureSampleType::Float { filterable: _ } => limits + match format.sample_type(None) { + Some(wgt::TextureSampleType::Float { filterable: _ }) => limits .framebuffer_color_sample_counts .min(limits.sampled_image_color_sample_counts), - wgt::TextureSampleType::Sint | wgt::TextureSampleType::Uint => { + Some(wgt::TextureSampleType::Sint) | Some(wgt::TextureSampleType::Uint) => { limits.sampled_image_integer_sample_counts } - _ => unreachable!(), + _ => vk::SampleCountFlags::TYPE_1, } }; diff --git a/wgpu-hal/src/vulkan/conv.rs b/wgpu-hal/src/vulkan/conv.rs index c27fdf9f72..e2d33b20e7 100644 --- a/wgpu-hal/src/vulkan/conv.rs +++ b/wgpu-hal/src/vulkan/conv.rs @@ -74,6 +74,7 @@ impl super::PrivateCapabilities { } } Tf::Depth16Unorm => F::D16_UNORM, + Tf::NV12 => F::G8_B8R8_2PLANE_420_UNORM, Tf::Rgb9e5Ufloat => F::E5B9G9R9_UFLOAT_PACK32, Tf::Bc1RgbaUnorm => F::BC1_RGBA_UNORM_BLOCK, Tf::Bc1RgbaUnormSrgb => F::BC1_RGBA_SRGB_BLOCK, @@ -401,10 +402,18 @@ pub fn map_vertex_format(vertex_format: wgt::VertexFormat) -> vk::Format { } } -pub fn map_aspects(aspects: crate::FormatAspects) -> vk::ImageAspectFlags { +pub fn map_aspects(aspects: crate::FormatAspects, plane: Option) -> vk::ImageAspectFlags { let mut flags = vk::ImageAspectFlags::empty(); - if aspects.contains(crate::FormatAspects::COLOR) { - flags |= vk::ImageAspectFlags::COLOR; + match plane { + Some(0) => flags |= vk::ImageAspectFlags::PLANE_0, + Some(1) => flags |= vk::ImageAspectFlags::PLANE_1, + Some(2) => flags |= vk::ImageAspectFlags::PLANE_2, + Some(plane) => panic!("Unexpected plane {}", plane), + None => { + if aspects.contains(crate::FormatAspects::COLOR) { + flags |= vk::ImageAspectFlags::COLOR; + } + } } if aspects.contains(crate::FormatAspects::DEPTH) { flags |= vk::ImageAspectFlags::DEPTH; @@ -586,9 +595,10 @@ pub fn map_copy_extent(extent: &crate::CopyExtent) -> vk::Extent3D { pub fn map_subresource_range( range: &wgt::ImageSubresourceRange, format: wgt::TextureFormat, + plane: Option, ) -> vk::ImageSubresourceRange { vk::ImageSubresourceRange { - aspect_mask: map_aspects(crate::FormatAspects::new(format, range.aspect)), + aspect_mask: map_aspects(crate::FormatAspects::new(format, range.aspect), plane), base_mip_level: range.base_mip_level, level_count: range.mip_level_count.unwrap_or(vk::REMAINING_MIP_LEVELS), base_array_layer: range.base_array_layer, @@ -605,7 +615,7 @@ pub(super) fn map_subresource_range_combined_aspect( format: wgt::TextureFormat, private_caps: &super::PrivateCapabilities, ) -> vk::ImageSubresourceRange { - let mut range = map_subresource_range(range, format); + let mut range = map_subresource_range(range, format, None); if !private_caps.texture_s8 && format == wgt::TextureFormat::Stencil8 { range.aspect_mask |= vk::ImageAspectFlags::DEPTH; } @@ -621,7 +631,7 @@ pub fn map_subresource_layers( z: base.origin.z as i32, }; let subresource = vk::ImageSubresourceLayers { - aspect_mask: map_aspects(base.aspect), + aspect_mask: map_aspects(base.aspect, None), mip_level: base.mip_level, base_array_layer: base.array_layer, layer_count: 1, diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index 5f7fbac47a..d9aba50281 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -1062,7 +1062,7 @@ impl crate::Device for super::Device { texture: &super::Texture, desc: &crate::TextureViewDescriptor, ) -> Result { - let subresource_range = conv::map_subresource_range(&desc.range, desc.format); + let subresource_range = conv::map_subresource_range(&desc.range, desc.format, desc.plane); let mut vk_info = vk::ImageViewCreateInfo::builder() .flags(vk::ImageViewCreateFlags::empty()) .image(texture.raw) diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 356eb65876..3e9ab37a24 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -771,7 +771,16 @@ bitflags::bitflags! { /// - OpenGL const SHADER_UNUSED_VERTEX_OUTPUT = 1 << 54; - // 54..59 available + /// Allows for creation of textures of format [`TextureFormat::NV12`] + /// + /// Supported platforms: + /// - DX12 + /// - Vulkan + /// + /// This is a native only feature. + const TEXTURE_FORMAT_NV12 = 1 << 55; + + // 55..59 available // Shader: @@ -2377,6 +2386,21 @@ pub enum TextureFormat { /// [`Features::DEPTH32FLOAT_STENCIL8`] must be enabled to use this texture format. Depth32FloatStencil8, + /// YUV 4:2:0 chroma subsampled format. + /// + /// Contains two planes: + /// - 0: Single 8 bit channel luminance. + /// - 1: Dual 8 bit channel chrominance at half width and half height. + /// + /// Valid view formats for luminance are [`TextureFormat::R8Unorm`] and [`TextureFormat::R8Uint`]. + /// + /// Valid view formats for chrominance are [`TextureFormat::Rg8Unorm`] and [`TextureFormat::Rg8Uint`]. + /// + /// Width and height must be even. + /// + /// [`Features::TEXTURE_FORMAT_NV12`] must be enabled to use this texture format. + NV12, + // Compressed textures usable with `TEXTURE_COMPRESSION_BC` feature. /// 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. @@ -2606,6 +2630,7 @@ impl<'de> Deserialize<'de> for TextureFormat { "depth16unorm" => TextureFormat::Depth16Unorm, "depth24plus" => TextureFormat::Depth24Plus, "depth24plus-stencil8" => TextureFormat::Depth24PlusStencil8, + "nv12" => TextureFormat::NV12, "rgb9e5ufloat" => TextureFormat::Rgb9e5Ufloat, "bc1-rgba-unorm" => TextureFormat::Bc1RgbaUnorm, "bc1-rgba-unorm-srgb" => TextureFormat::Bc1RgbaUnormSrgb, @@ -2733,6 +2758,7 @@ impl Serialize for TextureFormat { TextureFormat::Depth32FloatStencil8 => "depth32float-stencil8", TextureFormat::Depth24Plus => "depth24plus", TextureFormat::Depth24PlusStencil8 => "depth24plus-stencil8", + TextureFormat::NV12 => "nv12", TextureFormat::Rgb9e5Ufloat => "rgb9e5ufloat", TextureFormat::Bc1RgbaUnorm => "bc1-rgba-unorm", TextureFormat::Bc1RgbaUnormSrgb => "bc1-rgba-unorm-srgb", @@ -2927,6 +2953,8 @@ impl TextureFormat { | Self::Depth32Float | Self::Depth32FloatStencil8 => (1, 1), + Self::NV12 => (2, 2), + Self::Bc1RgbaUnorm | Self::Bc1RgbaUnormSrgb | Self::Bc2RgbaUnorm @@ -3025,6 +3053,8 @@ impl TextureFormat { Self::Depth32FloatStencil8 => Features::DEPTH32FLOAT_STENCIL8, + Self::NV12 => Features::TEXTURE_FORMAT_NV12, + Self::R16Unorm | Self::R16Snorm | Self::Rg16Unorm @@ -3079,6 +3109,7 @@ impl TextureFormat { TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING; let attachment = basic | TextureUsages::RENDER_ATTACHMENT; let storage = basic | TextureUsages::STORAGE_BINDING; + let binding = TextureUsages::TEXTURE_BINDING; let all_flags = TextureUsages::all(); let rg11b10f = if device_features.contains(Features::RG11B10UFLOAT_RENDERABLE) { attachment @@ -3140,6 +3171,9 @@ impl TextureFormat { Self::Depth32Float => ( msaa, attachment), Self::Depth32FloatStencil8 => ( msaa, attachment), + // We only support sampling nv12 textures until we implement transfer plane data. + Self::NV12 => ( noaa, binding), + Self::R16Unorm => ( msaa, storage), Self::R16Snorm => ( msaa, storage), Self::Rg16Unorm => ( msaa, storage), @@ -3247,6 +3281,8 @@ impl TextureFormat { Some(TextureAspect::StencilOnly) => Some(uint), }, + Self::NV12 => None, + Self::R16Unorm | Self::R16Snorm | Self::Rg16Unorm @@ -3363,6 +3399,8 @@ impl TextureFormat { Some(TextureAspect::StencilOnly) => Some(1), }, + Self::NV12 => None, + Self::Bc1RgbaUnorm | Self::Bc1RgbaUnormSrgb | Self::Bc4RUnorm | Self::Bc4RSnorm => { Some(8) } @@ -3454,6 +3492,8 @@ impl TextureFormat { TextureAspect::DepthOnly | TextureAspect::StencilOnly => 1, }, + Self::NV12 => 3, + Self::Bc4RUnorm | Self::Bc4RSnorm => 1, Self::Bc5RgUnorm | Self::Bc5RgSnorm => 2, Self::Bc6hRgbUfloat | Self::Bc6hRgbFloat => 3, diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index 763ae4780d..1e9701634b 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -1610,6 +1610,7 @@ impl crate::Context for Context { base_array_layer: desc.base_array_layer, array_layer_count: desc.array_layer_count, }, + plane: desc.plane, }; let global = &self.0; let (id, error) = wgc::gfx_select!( diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index c85e955e7c..639d02e728 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -1301,6 +1301,8 @@ pub struct TextureViewDescriptor<'a> { /// If `Some(count)`, `base_array_layer + count` must be less or equal to the underlying array count. /// If `None`, considered to include the rest of the array layers, but at least 1 in total. pub array_layer_count: Option, + /// The index (plane slice number) of the plane to use in the texture. + pub plane: Option, } static_assertions::assert_impl_all!(TextureViewDescriptor<'_>: Send, Sync);