Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ SamplerDescriptor {
#### General

- Texture now has `from_custom`. By @R-Cramer4 in [#8315](https://github.com/gfx-rs/wgpu/pull/8315).
- Added support for rendering onto multi-planar textures. By @noituri in [#8307](https://github.com/gfx-rs/wgpu/pull/8307).

### Bug Fixes

Expand Down
152 changes: 150 additions & 2 deletions tests/tests/wgpu-gpu/planar_texture/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub fn all_tests(tests: &mut Vec<GpuTestInitializer>) {
tests.extend([
NV12_TEXTURE_CREATION_SAMPLING,
P010_TEXTURE_CREATION_SAMPLING,
NV12_TEXTURE_RENDERING,
]);
}

Expand All @@ -21,7 +22,7 @@ fn test_planar_texture_creation_sampling(

let shader = ctx
.device
.create_shader_module(wgpu::include_wgsl!("planar_texture.wgsl"));
.create_shader_module(wgpu::include_wgsl!("planar_texture_sampling.wgsl"));
let pipeline = ctx
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
Expand Down Expand Up @@ -105,7 +106,112 @@ fn test_planar_texture_creation_sampling(
rpass.set_bind_group(0, &bind_group, &[]);
rpass.draw(0..4, 0..1);
drop(rpass);
ctx.queue.submit(Some(encoder.finish()));
ctx.queue.submit([encoder.finish()]);
}

// Helper function to test rendering onto planar texture.
fn test_planar_texture_rendering(
ctx: &TestingContext,
(y_view, y_format): (&wgpu::TextureView, wgpu::TextureFormat),
(uv_view, uv_format): (&wgpu::TextureView, wgpu::TextureFormat),
) {
let shader = ctx
.device
.create_shader_module(wgpu::include_wgsl!("planar_texture_rendering.wgsl"));
let y_pipeline = ctx
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("y plane 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_y_main"),
compilation_options: Default::default(),
targets: &[Some(y_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 uv_pipeline = ctx
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("uv plane 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_uv_main"),
compilation_options: Default::default(),
targets: &[Some(uv_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 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: y_view,
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&y_pipeline);
rpass.draw(0..3, 0..1);
}
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
ops: wgpu::Operations::default(),
resolve_target: None,
view: uv_view,
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&uv_pipeline);
rpass.draw(0..3, 0..1);
}

ctx.queue.submit([encoder.finish()]);
}

/// Ensures that creation and sampling of an NV12 format texture works as
Expand Down Expand Up @@ -187,3 +293,45 @@ static P010_TEXTURE_CREATION_SAMPLING: GpuTestConfiguration = GpuTestConfigurati

test_planar_texture_creation_sampling(&ctx, &y_view, &uv_view);
});

/// Ensures that rendering on to NV12 format texture works as expected.
#[gpu_test]
static NV12_TEXTURE_RENDERING: 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::RENDER_ATTACHMENT,
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_rendering(
&ctx,
(&y_view, wgpu::TextureFormat::R8Unorm),
(&uv_view, wgpu::TextureFormat::Rg8Unorm),
);
});
35 changes: 35 additions & 0 deletions tests/tests/wgpu-gpu/planar_texture/planar_texture_rendering.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
struct VertexOutput {
@builtin(position) position: vec4<f32>,
}

const VERTICES: array<vec3<f32>, 3> = array<vec3<f32>, 3>(
vec3<f32>(-0.5, 0.0, 0.0),
vec3<f32>(0.5, 0.0, 0.0),
vec3<f32>(0.0, 1.0, 0.0),
);

@vertex
fn vs_main(@builtin(vertex_index) idx: u32) -> VertexOutput {
var output: VertexOutput;
output.position = vec4(VERTICES[idx], 1.0);
return output;
}

@fragment
fn fs_y_main(input: VertexOutput) -> @location(0) f32 {
let color = vec3<f32>(1.0);
let conversion_weights = vec3<f32>(0.2126, 0.7152, 0.0722);
return clamp(dot(color, conversion_weights), 0.0, 1.0);
}

@fragment
fn fs_uv_main(input: VertexOutput) -> @location(0) vec2<f32> {
let color = vec3<f32>(1.0);
let conversion_weights = mat3x2<f32>(
-0.1146, 0.5,
-0.3854, -0.4542,
0.5, -0.0458,
);
let conversion_bias = vec2<f32>(0.5, 0.5);
return clamp(conversion_weights * color + conversion_bias, vec2(0.0, 0.0), vec2(1.0, 1.0));
}
84 changes: 84 additions & 0 deletions tests/tests/wgpu-validation/api/texture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,90 @@ fn planar_texture_bad_size() {
}
}

/// Ensures that creating a planar textures that support `RENDER_ATTACHMENT` usage
/// is possible.
#[test]
fn planar_texture_render_attachment() {
let required_features = wgpu::Features::TEXTURE_FORMAT_NV12;
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,
),
] {
valid(&device, || {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: tex_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});

let _ = texture.create_view(&wgpu::TextureViewDescriptor {
format: Some(view_format),
aspect: view_aspect,
..Default::default()
});
});
}
}

/// Ensures that creating a planar textures with `RENDER_ATTACHMENT`
/// for non renderable planar formats fails validation.
#[test]
fn planar_texture_render_attachment_unsupported() {
let required_features =
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,
};

fail(
&device,
|| {
let _ = device.create_texture(&wgpu::TextureDescriptor {
label: None,
dimension: wgpu::TextureDimension::D2,
size,
format: wgpu::TextureFormat::P010,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
});
},
Some("Texture usages TextureUsages(RENDER_ATTACHMENT) are not allowed on a texture of type P010"),
);
}

/// Creates a texture and a buffer, and encodes a copy from the texture to the
/// buffer.
fn encode_copy_texture_to_buffer(
Expand Down
11 changes: 6 additions & 5 deletions wgpu-core/src/command/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1196,11 +1196,12 @@ impl RenderPassInfo {
},
)?;

if !color_view
.desc
.aspects()
.contains(hal::FormatAspects::COLOR)
{
if !color_view.desc.aspects().intersects(
hal::FormatAspects::COLOR
| hal::FormatAspects::PLANE_0
| hal::FormatAspects::PLANE_1
| hal::FormatAspects::PLANE_2,
) {
return Err(RenderPassErrorInner::ColorAttachment(
ColorAttachmentError::InvalidFormat(color_view.desc.format),
));
Expand Down
7 changes: 6 additions & 1 deletion wgpu-core/src/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,12 @@ pub fn map_texture_usage(
flags.contains(wgt::TextureFormatFeatureFlags::STORAGE_READ_WRITE),
);
}
let is_color = aspect.contains(hal::FormatAspects::COLOR);
let is_color = aspect.intersects(
hal::FormatAspects::COLOR
| hal::FormatAspects::PLANE_0
| hal::FormatAspects::PLANE_1
| hal::FormatAspects::PLANE_2,
);
u.set(
wgt::TextureUses::COLOR_TARGET,
usage.contains(wgt::TextureUsages::RENDER_ATTACHMENT) && is_color,
Expand Down
25 changes: 22 additions & 3 deletions wgpu-core/src/device/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1355,7 +1355,24 @@ impl Device {
});
}

let missing_allowed_usages = desc.usage - format_features.allowed_usages;
let missing_allowed_usages = match desc.format.planes() {
Some(planes) => {
let mut planes_usages = wgt::TextureUsages::all();
for plane in 0..planes {
let aspect = wgt::TextureAspect::from_plane(plane).unwrap();
let format = desc.format.aspect_specific_format(aspect).unwrap();
let format_features = self
.describe_format_features(format)
.map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?;

planes_usages &= format_features.allowed_usages;
}

desc.usage - planes_usages
}
None => desc.usage - format_features.allowed_usages,
};

if !missing_allowed_usages.is_empty() {
// detect downlevel incompatibilities
let wgpu_allowed_usages = desc
Expand Down Expand Up @@ -1722,13 +1739,15 @@ impl Device {
));
}

if aspects != hal::FormatAspects::from(texture.desc.format) {
if !texture.desc.format.is_multi_planar_format()
&& aspects != hal::FormatAspects::from(texture.desc.format)
{
break 'error Err(TextureViewNotRenderableReason::Aspects(aspects));
}

Ok(texture
.desc
.compute_render_extent(desc.range.base_mip_level))
.compute_render_extent(desc.range.base_mip_level, desc.range.aspect.to_plane()))
};

// filter the usages based on the other criteria
Expand Down
3 changes: 2 additions & 1 deletion wgpu-hal/src/vulkan/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,8 @@ impl super::Device {
}
}
if desc.format.is_multi_planar_format() {
raw_flags |= vk::ImageCreateFlags::MUTABLE_FORMAT;
raw_flags |=
vk::ImageCreateFlags::MUTABLE_FORMAT | vk::ImageCreateFlags::EXTENDED_USAGE;
}
Comment on lines 708 to 711
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit regarding extended usage, do we already ensure that we have vulkan 1.1 before we allow multi-planar textures? If not we should add that check.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm reading the code correctly, TEXTURE_FORMAT_NV12 and TEXTURE_FORMAT_P010 features require VK_KHR_sampler_ycbcr_conversion extension which depends on:

  • Vulkan 1.1, OR
  • VK_KHR_maintenance1 and VK_KHR_bind_memory2 and VK_KHR_get_memory_requirements2 and VK_KHR_get_physical_device_properties2

From what I checked, wgpu does not require all of those extensions so the only way we could use multi-planar textures would be if we had at least vulkan 1.1 and I think wgpu ensures that (unless I read the code wrong)


let mut vk_info = vk::ImageCreateInfo::default()
Expand Down
Loading