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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ By @cwfitzgerald in [#8162](https://github.com/gfx-rs/wgpu/pull/8162).

- Added support for external textures based on WebGPU's [`GPUExternalTexture`](https://www.w3.org/TR/webgpu/#gpuexternaltexture). These allow shaders to transparently operate on potentially multiplanar source texture data in either RGB or YCbCr formats via WGSL's `texture_external` type. This is gated behind the `Features::EXTERNAL_TEXTURE` feature, which is currently only supported on DX12. By @jamienicol in [#4386](https://github.com/gfx-rs/wgpu/issues/4386).

- Added support for rendering onto multi-planar textures. By @noituri in [#8307](https://github.com/gfx-rs/wgpu/pull/8307).

### Changes

#### General
Expand Down
150 changes: 149 additions & 1 deletion 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 @@ -108,6 +109,111 @@ fn test_planar_texture_creation_sampling(
ctx.queue.submit(Some(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(Some(encoder.finish()));
}

/// Ensures that creation and sampling of an NV12 format texture works as
/// expected.
#[gpu_test]
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 @@ -299,6 +299,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 @@ -1193,11 +1193,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 @@ -1327,7 +1327,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 @@ -1692,13 +1709,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;
}

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