mirror of
https://github.com/gfx-rs/wgpu.git
synced 2025-12-08 21:26:17 +00:00
Make multi-planar textures renderable (#8307)
This commit is contained in:
parent
7a2afeb014
commit
e7fcb94888
@ -84,6 +84,7 @@ SamplerDescriptor {
|
||||
- Texture now has `from_custom`. By @R-Cramer4 in [#8315](https://github.com/gfx-rs/wgpu/pull/8315).
|
||||
- Using both the wgpu command encoding APIs and `CommandEncoder::as_hal_mut` on the same encoder will now result in a panic.
|
||||
- Allow `include_spirv!` and `include_spirv_raw!` macros to be used in constants and statics. By @clarfonthey in [#8250](https://github.com/gfx-rs/wgpu/pull/8250).
|
||||
- Added support for rendering onto multi-planar textures. By @noituri in [#8307](https://github.com/gfx-rs/wgpu/pull/8307).
|
||||
|
||||
### Added/New Features
|
||||
|
||||
|
||||
@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
@ -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
|
||||
@ -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),
|
||||
);
|
||||
});
|
||||
|
||||
@ -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));
|
||||
}
|
||||
@ -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(
|
||||
|
||||
@ -1207,11 +1207,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),
|
||||
));
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -590,7 +590,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()
|
||||
|
||||
@ -134,6 +134,32 @@ fn test_uniqueness_in_texture_format_list() {
|
||||
assert_eq!(duplicated, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_render_extent() {
|
||||
for format in TEXTURE_FORMAT_LIST {
|
||||
let desc = wgpu::TextureDescriptor {
|
||||
label: None,
|
||||
size: wgpu::Extent3d {
|
||||
width: 1280,
|
||||
height: 720,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::empty(),
|
||||
view_formats: &[],
|
||||
};
|
||||
|
||||
if format.is_multi_planar_format() {
|
||||
let _ = desc.compute_render_extent(0, Some(0));
|
||||
} else {
|
||||
let _ = desc.compute_render_extent(0, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_texture_format_string_size() -> usize {
|
||||
TEXTURE_FORMAT_LIST
|
||||
.into_iter()
|
||||
|
||||
@ -2772,6 +2772,17 @@ impl TextureAspect {
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the plane for a given texture aspect.
|
||||
#[must_use]
|
||||
pub fn to_plane(&self) -> Option<u32> {
|
||||
match self {
|
||||
TextureAspect::Plane0 => Some(0),
|
||||
TextureAspect::Plane1 => Some(1),
|
||||
TextureAspect::Plane2 => Some(2),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There are some additional texture format helpers in `wgpu-core/src/conv.rs`,
|
||||
@ -6416,10 +6427,22 @@ impl<L, V> TextureDescriptor<L, V> {
|
||||
///
|
||||
/// <https://gpuweb.github.io/gpuweb/#abstract-opdef-compute-render-extent>
|
||||
#[must_use]
|
||||
pub fn compute_render_extent(&self, mip_level: u32) -> Extent3d {
|
||||
pub fn compute_render_extent(&self, mip_level: u32, plane: Option<u32>) -> Extent3d {
|
||||
let width = self.size.width >> mip_level;
|
||||
let height = self.size.height >> mip_level;
|
||||
|
||||
let (width, height) = match (self.format, plane) {
|
||||
(TextureFormat::NV12 | TextureFormat::P010, Some(0)) => (width, height),
|
||||
(TextureFormat::NV12 | TextureFormat::P010, Some(1)) => (width / 2, height / 2),
|
||||
_ => {
|
||||
debug_assert!(!self.format.is_multi_planar_format());
|
||||
(width, height)
|
||||
}
|
||||
};
|
||||
|
||||
Extent3d {
|
||||
width: u32::max(1, self.size.width >> mip_level),
|
||||
height: u32::max(1, self.size.height >> mip_level),
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user