Skip to content

Commit a87815a

Browse files
committed
Make multi-planar textures renderable
1 parent a70b833 commit a87815a

File tree

11 files changed

+360
-15
lines changed

11 files changed

+360
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ SamplerDescriptor {
7777
#### General
7878

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

8182
### Bug Fixes
8283

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

Lines changed: 150 additions & 2 deletions
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 {
@@ -105,7 +106,112 @@ fn test_planar_texture_creation_sampling(
105106
rpass.set_bind_group(0, &bind_group, &[]);
106107
rpass.draw(0..4, 0..1);
107108
drop(rpass);
108-
ctx.queue.submit(Some(encoder.finish()));
109+
ctx.queue.submit([encoder.finish()]);
110+
}
111+
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([encoder.finish()]);
109215
}
110216

111217
/// Ensures that creation and sampling of an NV12 format texture works as
@@ -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+
}

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

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,90 @@ fn planar_texture_bad_size() {
287287
}
288288
}
289289

290+
/// Ensures that creating a planar textures that support `RENDER_ATTACHMENT` usage
291+
/// is possible.
292+
#[test]
293+
fn planar_texture_render_attachment() {
294+
let required_features = wgpu::Features::TEXTURE_FORMAT_NV12;
295+
let device_desc = wgpu::DeviceDescriptor {
296+
required_features,
297+
..Default::default()
298+
};
299+
let (device, _queue) = wgpu::Device::noop(&device_desc);
300+
let size = wgpu::Extent3d {
301+
width: 256,
302+
height: 256,
303+
depth_or_array_layers: 1,
304+
};
305+
306+
for (tex_format, view_format, view_aspect) in [
307+
(
308+
wgpu::TextureFormat::NV12,
309+
wgpu::TextureFormat::R8Unorm,
310+
wgpu::TextureAspect::Plane0,
311+
),
312+
(
313+
wgpu::TextureFormat::NV12,
314+
wgpu::TextureFormat::Rg8Unorm,
315+
wgpu::TextureAspect::Plane1,
316+
),
317+
] {
318+
valid(&device, || {
319+
let texture = device.create_texture(&wgpu::TextureDescriptor {
320+
label: None,
321+
dimension: wgpu::TextureDimension::D2,
322+
size,
323+
format: tex_format,
324+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
325+
mip_level_count: 1,
326+
sample_count: 1,
327+
view_formats: &[],
328+
});
329+
330+
let _ = texture.create_view(&wgpu::TextureViewDescriptor {
331+
format: Some(view_format),
332+
aspect: view_aspect,
333+
..Default::default()
334+
});
335+
});
336+
}
337+
}
338+
339+
/// Ensures that creating a planar textures with `RENDER_ATTACHMENT`
340+
/// for non renderable planar formats fails validation.
341+
#[test]
342+
fn planar_texture_render_attachment_unsupported() {
343+
let required_features =
344+
wgpu::Features::TEXTURE_FORMAT_P010 | wgpu::Features::TEXTURE_FORMAT_16BIT_NORM;
345+
let device_desc = wgpu::DeviceDescriptor {
346+
required_features,
347+
..Default::default()
348+
};
349+
let (device, _queue) = wgpu::Device::noop(&device_desc);
350+
let size = wgpu::Extent3d {
351+
width: 256,
352+
height: 256,
353+
depth_or_array_layers: 1,
354+
};
355+
356+
fail(
357+
&device,
358+
|| {
359+
let _ = device.create_texture(&wgpu::TextureDescriptor {
360+
label: None,
361+
dimension: wgpu::TextureDimension::D2,
362+
size,
363+
format: wgpu::TextureFormat::P010,
364+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
365+
mip_level_count: 1,
366+
sample_count: 1,
367+
view_formats: &[],
368+
});
369+
},
370+
Some("Texture usages TextureUsages(RENDER_ATTACHMENT) are not allowed on a texture of type P010"),
371+
);
372+
}
373+
290374
/// Creates a texture and a buffer, and encodes a copy from the texture to the
291375
/// buffer.
292376
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
@@ -1196,11 +1196,12 @@ impl RenderPassInfo {
11961196
},
11971197
)?;
11981198

1199-
if !color_view
1200-
.desc
1201-
.aspects()
1202-
.contains(hal::FormatAspects::COLOR)
1203-
{
1199+
if !color_view.desc.aspects().intersects(
1200+
hal::FormatAspects::COLOR
1201+
| hal::FormatAspects::PLANE_0
1202+
| hal::FormatAspects::PLANE_1
1203+
| hal::FormatAspects::PLANE_2,
1204+
) {
12041205
return Err(RenderPassErrorInner::ColorAttachment(
12051206
ColorAttachmentError::InvalidFormat(color_view.desc.format),
12061207
));

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
@@ -1355,7 +1355,24 @@ impl Device {
13551355
});
13561356
}
13571357

1358-
let missing_allowed_usages = desc.usage - format_features.allowed_usages;
1358+
let missing_allowed_usages = match desc.format.planes() {
1359+
Some(planes) => {
1360+
let mut planes_usages = wgt::TextureUsages::all();
1361+
for plane in 0..planes {
1362+
let aspect = wgt::TextureAspect::from_plane(plane).unwrap();
1363+
let format = desc.format.aspect_specific_format(aspect).unwrap();
1364+
let format_features = self
1365+
.describe_format_features(format)
1366+
.map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?;
1367+
1368+
planes_usages &= format_features.allowed_usages;
1369+
}
1370+
1371+
desc.usage - planes_usages
1372+
}
1373+
None => desc.usage - format_features.allowed_usages,
1374+
};
1375+
13591376
if !missing_allowed_usages.is_empty() {
13601377
// detect downlevel incompatibilities
13611378
let wgpu_allowed_usages = desc
@@ -1722,13 +1739,15 @@ impl Device {
17221739
));
17231740
}
17241741

1725-
if aspects != hal::FormatAspects::from(texture.desc.format) {
1742+
if !texture.desc.format.is_multi_planar_format()
1743+
&& aspects != hal::FormatAspects::from(texture.desc.format)
1744+
{
17261745
break 'error Err(TextureViewNotRenderableReason::Aspects(aspects));
17271746
}
17281747

17291748
Ok(texture
17301749
.desc
1731-
.compute_render_extent(desc.range.base_mip_level))
1750+
.compute_render_extent(desc.range.base_mip_level, desc.range.aspect.to_plane()))
17321751
};
17331752

17341753
// 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)