Skip to content

Commit a9b592c

Browse files
committed
Make multi-planar textures renderable
1 parent 3efd640 commit a9b592c

File tree

10 files changed

+329
-14
lines changed

10 files changed

+329
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ By @cwfitzgerald in [#8162](https://github.com/gfx-rs/wgpu/pull/8162).
166166

167167
- 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).
168168

169+
- Added support for rendering onto multi-planar textures. By @noituri in [#TODO](https://github.com/gfx-rs/wgpu/pull/TODO).
170+
169171
### Changes
170172

171173
#### General

tests/tests/wgpu-gpu/planar_texture/mod.rs

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub fn all_tests(tests: &mut Vec<GpuTestInitializer>) {
88
tests.extend([
99
NV12_TEXTURE_CREATION_SAMPLING,
1010
P010_TEXTURE_CREATION_SAMPLING,
11+
NV12_TEXTURE_RENDERING,
1112
]);
1213
}
1314

@@ -21,7 +22,7 @@ fn test_planar_texture_creation_sampling(
2122

2223
let shader = ctx
2324
.device
24-
.create_shader_module(wgpu::include_wgsl!("planar_texture.wgsl"));
25+
.create_shader_module(wgpu::include_wgsl!("planar_texture_sampling.wgsl"));
2526
let pipeline = ctx
2627
.device
2728
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@@ -108,6 +109,111 @@ fn test_planar_texture_creation_sampling(
108109
ctx.queue.submit(Some(encoder.finish()));
109110
}
110111

112+
// Helper function to test rendering onto planar texture.
113+
fn test_planar_texture_rendering(
114+
ctx: &TestingContext,
115+
(y_view, y_format): (&wgpu::TextureView, wgpu::TextureFormat),
116+
(uv_view, uv_format): (&wgpu::TextureView, wgpu::TextureFormat),
117+
) {
118+
let shader = ctx
119+
.device
120+
.create_shader_module(wgpu::include_wgsl!("planar_texture_rendering.wgsl"));
121+
let y_pipeline = ctx
122+
.device
123+
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
124+
label: Some("y plane pipeline"),
125+
layout: None,
126+
vertex: wgpu::VertexState {
127+
module: &shader,
128+
entry_point: Some("vs_main"),
129+
compilation_options: Default::default(),
130+
buffers: &[],
131+
},
132+
fragment: Some(wgpu::FragmentState {
133+
module: &shader,
134+
entry_point: Some("fs_y_main"),
135+
compilation_options: Default::default(),
136+
targets: &[Some(y_format.into())],
137+
}),
138+
primitive: wgpu::PrimitiveState {
139+
topology: wgpu::PrimitiveTopology::TriangleStrip,
140+
strip_index_format: Some(wgpu::IndexFormat::Uint32),
141+
..Default::default()
142+
},
143+
depth_stencil: None,
144+
multisample: wgpu::MultisampleState::default(),
145+
multiview: None,
146+
cache: None,
147+
});
148+
149+
let uv_pipeline = ctx
150+
.device
151+
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
152+
label: Some("uv plane pipeline"),
153+
layout: None,
154+
vertex: wgpu::VertexState {
155+
module: &shader,
156+
entry_point: Some("vs_main"),
157+
compilation_options: Default::default(),
158+
buffers: &[],
159+
},
160+
fragment: Some(wgpu::FragmentState {
161+
module: &shader,
162+
entry_point: Some("fs_uv_main"),
163+
compilation_options: Default::default(),
164+
targets: &[Some(uv_format.into())],
165+
}),
166+
primitive: wgpu::PrimitiveState {
167+
topology: wgpu::PrimitiveTopology::TriangleStrip,
168+
strip_index_format: Some(wgpu::IndexFormat::Uint32),
169+
..Default::default()
170+
},
171+
depth_stencil: None,
172+
multisample: wgpu::MultisampleState::default(),
173+
multiview: None,
174+
cache: None,
175+
});
176+
177+
let mut encoder = ctx
178+
.device
179+
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
180+
181+
{
182+
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
183+
label: None,
184+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
185+
ops: wgpu::Operations::default(),
186+
resolve_target: None,
187+
view: &y_view,
188+
depth_slice: None,
189+
})],
190+
depth_stencil_attachment: None,
191+
timestamp_writes: None,
192+
occlusion_query_set: None,
193+
});
194+
rpass.set_pipeline(&y_pipeline);
195+
rpass.draw(0..3, 0..1);
196+
}
197+
{
198+
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
199+
label: None,
200+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
201+
ops: wgpu::Operations::default(),
202+
resolve_target: None,
203+
view: &uv_view,
204+
depth_slice: None,
205+
})],
206+
depth_stencil_attachment: None,
207+
timestamp_writes: None,
208+
occlusion_query_set: None,
209+
});
210+
rpass.set_pipeline(&uv_pipeline);
211+
rpass.draw(0..3, 0..1);
212+
}
213+
214+
ctx.queue.submit(Some(encoder.finish()));
215+
}
216+
111217
/// Ensures that creation and sampling of an NV12 format texture works as
112218
/// expected.
113219
#[gpu_test]
@@ -187,3 +293,45 @@ static P010_TEXTURE_CREATION_SAMPLING: GpuTestConfiguration = GpuTestConfigurati
187293

188294
test_planar_texture_creation_sampling(&ctx, &y_view, &uv_view);
189295
});
296+
297+
/// Ensures that rendering on to NV12 format texture works as expected.
298+
#[gpu_test]
299+
static NV12_TEXTURE_RENDERING: GpuTestConfiguration = GpuTestConfiguration::new()
300+
.parameters(
301+
TestParameters::default()
302+
.features(wgpu::Features::TEXTURE_FORMAT_NV12)
303+
.enable_noop(),
304+
)
305+
.run_sync(|ctx| {
306+
let size = wgpu::Extent3d {
307+
width: 256,
308+
height: 256,
309+
depth_or_array_layers: 1,
310+
};
311+
let tex = ctx.device.create_texture(&wgpu::TextureDescriptor {
312+
label: None,
313+
dimension: wgpu::TextureDimension::D2,
314+
size,
315+
format: wgpu::TextureFormat::NV12,
316+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
317+
mip_level_count: 1,
318+
sample_count: 1,
319+
view_formats: &[],
320+
});
321+
let y_view = tex.create_view(&wgpu::TextureViewDescriptor {
322+
format: Some(wgpu::TextureFormat::R8Unorm),
323+
aspect: wgpu::TextureAspect::Plane0,
324+
..Default::default()
325+
});
326+
let uv_view = tex.create_view(&wgpu::TextureViewDescriptor {
327+
format: Some(wgpu::TextureFormat::Rg8Unorm),
328+
aspect: wgpu::TextureAspect::Plane1,
329+
..Default::default()
330+
});
331+
332+
test_planar_texture_rendering(
333+
&ctx,
334+
(&y_view, wgpu::TextureFormat::R8Unorm),
335+
(&uv_view, wgpu::TextureFormat::Rg8Unorm),
336+
);
337+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
struct VertexOutput {
2+
@builtin(position) position: vec4<f32>,
3+
}
4+
5+
const VERTICES: array<vec3<f32>, 3> = array<vec3<f32>, 3>(
6+
vec3<f32>(-0.5, 0.0, 0.0),
7+
vec3<f32>(0.5, 0.0, 0.0),
8+
vec3<f32>(0.0, 1.0, 0.0),
9+
);
10+
11+
@vertex
12+
fn vs_main(@builtin(vertex_index) idx: u32) -> VertexOutput {
13+
var output: VertexOutput;
14+
output.position = vec4(VERTICES[idx], 1.0);
15+
return output;
16+
}
17+
18+
@fragment
19+
fn fs_y_main(input: VertexOutput) -> @location(0) f32 {
20+
let color = vec3<f32>(1.0);
21+
let conversion_weights = vec3<f32>(0.2126, 0.7152, 0.0722);
22+
return clamp(dot(color, conversion_weights), 0.0, 1.0);
23+
}
24+
25+
@fragment
26+
fn fs_uv_main(input: VertexOutput) -> @location(0) vec2<f32> {
27+
let color = vec3<f32>(1.0);
28+
let conversion_weights = mat3x2<f32>(
29+
-0.1146, 0.5,
30+
-0.3854, -0.4542,
31+
0.5, -0.0458,
32+
);
33+
let conversion_bias = vec2<f32>(0.5, 0.5);
34+
return clamp(conversion_weights * color + conversion_bias, vec2(0.0, 0.0), vec2(1.0, 1.0));
35+
}
File renamed without changes.

tests/tests/wgpu-validation/api/texture.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,90 @@ fn planar_texture_bad_size() {
299299
}
300300
}
301301

302+
/// Ensures that creating a planar textures that support `RENDER_ATTACHMENT` usage
303+
/// is possible.
304+
#[test]
305+
fn planar_texture_render_attachment() {
306+
let required_features = wgpu::Features::TEXTURE_FORMAT_NV12;
307+
let device_desc = wgpu::DeviceDescriptor {
308+
required_features,
309+
..Default::default()
310+
};
311+
let (device, _queue) = wgpu::Device::noop(&device_desc);
312+
let size = wgpu::Extent3d {
313+
width: 256,
314+
height: 256,
315+
depth_or_array_layers: 1,
316+
};
317+
318+
for (tex_format, view_format, view_aspect) in [
319+
(
320+
wgpu::TextureFormat::NV12,
321+
wgpu::TextureFormat::R8Unorm,
322+
wgpu::TextureAspect::Plane0,
323+
),
324+
(
325+
wgpu::TextureFormat::NV12,
326+
wgpu::TextureFormat::Rg8Unorm,
327+
wgpu::TextureAspect::Plane1,
328+
),
329+
] {
330+
valid(&device, || {
331+
let texture = device.create_texture(&wgpu::TextureDescriptor {
332+
label: None,
333+
dimension: wgpu::TextureDimension::D2,
334+
size,
335+
format: tex_format,
336+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
337+
mip_level_count: 1,
338+
sample_count: 1,
339+
view_formats: &[],
340+
});
341+
342+
let _ = texture.create_view(&wgpu::TextureViewDescriptor {
343+
format: Some(view_format),
344+
aspect: view_aspect,
345+
..Default::default()
346+
});
347+
});
348+
}
349+
}
350+
351+
/// Ensures that creating a planar textures with `RENDER_ATTACHMENT`
352+
/// for non renderable planar formats fails validation.
353+
#[test]
354+
fn planar_texture_render_attachment_unsupported() {
355+
let required_features =
356+
wgpu::Features::TEXTURE_FORMAT_P010 | wgpu::Features::TEXTURE_FORMAT_16BIT_NORM;
357+
let device_desc = wgpu::DeviceDescriptor {
358+
required_features,
359+
..Default::default()
360+
};
361+
let (device, _queue) = wgpu::Device::noop(&device_desc);
362+
let size = wgpu::Extent3d {
363+
width: 256,
364+
height: 256,
365+
depth_or_array_layers: 1,
366+
};
367+
368+
fail(
369+
&device,
370+
|| {
371+
let _ = device.create_texture(&wgpu::TextureDescriptor {
372+
label: None,
373+
dimension: wgpu::TextureDimension::D2,
374+
size,
375+
format: wgpu::TextureFormat::P010,
376+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
377+
mip_level_count: 1,
378+
sample_count: 1,
379+
view_formats: &[],
380+
});
381+
},
382+
Some("Texture usages TextureUsages(RENDER_ATTACHMENT) are not allowed on a texture of type P010"),
383+
);
384+
}
385+
302386
/// Creates a texture and a buffer, and encodes a copy from the texture to the
303387
/// buffer.
304388
fn encode_copy_texture_to_buffer(

wgpu-core/src/command/render.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,11 +1193,12 @@ impl RenderPassInfo {
11931193
},
11941194
)?;
11951195

1196-
if !color_view
1197-
.desc
1198-
.aspects()
1199-
.contains(hal::FormatAspects::COLOR)
1200-
{
1196+
if !color_view.desc.aspects().intersects(
1197+
hal::FormatAspects::COLOR
1198+
| hal::FormatAspects::PLANE_0
1199+
| hal::FormatAspects::PLANE_1
1200+
| hal::FormatAspects::PLANE_2,
1201+
) {
12011202
return Err(RenderPassErrorInner::ColorAttachment(
12021203
ColorAttachmentError::InvalidFormat(color_view.desc.format),
12031204
));

wgpu-core/src/conv.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,12 @@ pub fn map_texture_usage(
111111
flags.contains(wgt::TextureFormatFeatureFlags::STORAGE_READ_WRITE),
112112
);
113113
}
114-
let is_color = aspect.contains(hal::FormatAspects::COLOR);
114+
let is_color = aspect.intersects(
115+
hal::FormatAspects::COLOR
116+
| hal::FormatAspects::PLANE_0
117+
| hal::FormatAspects::PLANE_1
118+
| hal::FormatAspects::PLANE_2,
119+
);
115120
u.set(
116121
wgt::TextureUses::COLOR_TARGET,
117122
usage.contains(wgt::TextureUsages::RENDER_ATTACHMENT) && is_color,

wgpu-core/src/device/resource.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,7 +1327,24 @@ impl Device {
13271327
});
13281328
}
13291329

1330-
let missing_allowed_usages = desc.usage - format_features.allowed_usages;
1330+
let missing_allowed_usages = match desc.format.planes() {
1331+
Some(planes) => {
1332+
let mut planes_usages = wgt::TextureUsages::all();
1333+
for plane in 0..planes {
1334+
let aspect = wgt::TextureAspect::from_plane(plane).unwrap();
1335+
let format = desc.format.aspect_specific_format(aspect).unwrap();
1336+
let format_features = self
1337+
.describe_format_features(format)
1338+
.map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?;
1339+
1340+
planes_usages &= format_features.allowed_usages;
1341+
}
1342+
1343+
desc.usage - planes_usages
1344+
}
1345+
None => desc.usage - format_features.allowed_usages,
1346+
};
1347+
13311348
if !missing_allowed_usages.is_empty() {
13321349
// detect downlevel incompatibilities
13331350
let wgpu_allowed_usages = desc
@@ -1692,13 +1709,15 @@ impl Device {
16921709
));
16931710
}
16941711

1695-
if aspects != hal::FormatAspects::from(texture.desc.format) {
1712+
if !texture.desc.format.is_multi_planar_format()
1713+
&& aspects != hal::FormatAspects::from(texture.desc.format)
1714+
{
16961715
break 'error Err(TextureViewNotRenderableReason::Aspects(aspects));
16971716
}
16981717

16991718
Ok(texture
17001719
.desc
1701-
.compute_render_extent(desc.range.base_mip_level))
1720+
.compute_render_extent(desc.range.base_mip_level, desc.range.aspect.to_plane()))
17021721
};
17031722

17041723
// filter the usages based on the other criteria

wgpu-hal/src/vulkan/device.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,8 @@ impl super::Device {
706706
}
707707
}
708708
if desc.format.is_multi_planar_format() {
709-
raw_flags |= vk::ImageCreateFlags::MUTABLE_FORMAT;
709+
raw_flags |=
710+
vk::ImageCreateFlags::MUTABLE_FORMAT | vk::ImageCreateFlags::EXTENDED_USAGE;
710711
}
711712

712713
let mut vk_info = vk::ImageCreateInfo::default()

0 commit comments

Comments
 (0)