Skip to content

Commit

Permalink
Add test for different read write usages combinations (#253)
Browse files Browse the repository at this point in the history
* Add test for different read write usages combinations

Texture read usages include sampled-texture and readonly storage
texture. Texture write usages include writeonly storage texture
and render target. Different usages upon the same subresource
should follow this rule:
- multiple read usages upon the same subresource is valid.
- multiple write usages, or read/write usages upon the same
  subresource is invalid, with one exception that multiple
  writeonly storage texture usages are valid.

* Add a comment

* Addressed feedback from Corentin and Kai

* Addressed Kai's feedback

* Addressed Kai's feedback: add test for depth/stencil

* rename a test, update test file description

* Address feedback from Corentin

Co-authored-by: Kai Ninomiya <[email protected]>
  • Loading branch information
Richard-Yunchao and kainino0x authored Aug 15, 2020
1 parent 04c4dd4 commit fa4873f
Showing 1 changed file with 228 additions and 76 deletions.
304 changes: 228 additions & 76 deletions src/webgpu/api/validation/resource_usages/textureUsageInRender.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@ export const description = `
Texture Usages Validation Tests in Render Pass.
Test Coverage:
- Tests that read and write usages upon the same texture subresource, or different subresources
of the same texture. Different subresources of the same texture includes different mip levels,
different array layers, and different aspects.
- When read and write usages are binding to the same texture subresource, an error should be
generated. Otherwise, no error should be generated.
- For each combination of two texture usages:
- For various subresource ranges (different mip levels or array layers) that overlap a given
subresources or not for color formats:
- Check that an error is generated when read-write or write-write usages are binding to the
same texture subresource. Otherwise, no error should be generated. One exception is race
condition upon two writeonly-storage-texture usages, which is valid.
- For each combination of two texture usages:
- For various aspects (all, depth-only, stencil-only) that overlap a given subresources or not
for depth/stencil formats:
- Check that an error is generated when read-write or write-write usages are binding to the
same aspect. Otherwise, no error should be generated.
- Test combinations of two shader stages:
- Texture usages in bindings with invisible shader stages should be tracked. Invisible shader
stages include shader stage with visibility none and compute shader stage in render pass.
`;

import { poptions, params } from '../../../../common/framework/params_builder.js';
Expand Down Expand Up @@ -53,64 +65,173 @@ class TextureUsageTracking extends ValidationTest {

export const g = makeTestGroup(TextureUsageTracking);

const READ_BASE_LEVEL = 3;
const READ_BASE_LAYER = 0;

g.test('readwrite_upon_subresources')
.params([
// read and write usages are binding to the same texture subresource.
{
writeBaseLevel: READ_BASE_LEVEL,
writeBaseLayer: READ_BASE_LAYER,
_success: false,
},

// read and write usages are binding to different mip levels of the same texture.
{
writeBaseLevel: READ_BASE_LEVEL + 1,
writeBaseLayer: READ_BASE_LAYER,
_success: true,
},

// read and write usages are binding to different array layers of the same texture.
{
writeBaseLevel: READ_BASE_LEVEL,
writeBaseLayer: READ_BASE_LAYER + 1,
_success: true,
},
])
.fn(async t => {
const { writeBaseLevel, writeBaseLayer, _success } = t.params;
const BASE_LEVEL = 3;
const BASE_LAYER = 0;
const TOTAL_LEVELS = 6;
const TOTAL_LAYERS = 2;

const texture = t.createTexture({ arrayLayerCount: 2, mipLevelCount: 6 });
g.test('subresources_and_binding_types_combination_for_color')
.params(
params()
.combine([
// Two texture usages are binding to the same texture subresource.
{
baseLevel: BASE_LEVEL,
baseLayer: BASE_LAYER,
levelCount: 1,
layerCount: 1,
_resourceSuccess: false,
},

const sampleView = texture.createView({
baseMipLevel: READ_BASE_LEVEL,
mipLevelCount: 1,
baseArrayLayer: READ_BASE_LAYER,
arrayLayerCount: 1,
// Two texture usages are binding to different mip levels of the same texture.
{
baseLevel: BASE_LEVEL + 1,
baseLayer: BASE_LAYER,
levelCount: 1,
layerCount: 1,
_resourceSuccess: true,
},

// Two texture usages are binding to different array layers of the same texture.
{
baseLevel: BASE_LEVEL,
baseLayer: BASE_LAYER + 1,
levelCount: 1,
layerCount: 1,
_resourceSuccess: true,
},

// The second texture usage contains the whole mip chain where the first texture usage is using.
{
baseLevel: 0,
baseLayer: BASE_LAYER,
levelCount: TOTAL_LEVELS,
layerCount: 1,
_resourceSuccess: false,
},

// The second texture usage contains the all layers where the first texture usage is using.
{
baseLevel: BASE_LEVEL,
baseLayer: 0,
levelCount: 1,
layerCount: TOTAL_LAYERS,
_resourceSuccess: false,
},
])
.combine([
{
type0: 'sampled-texture',
type1: 'sampled-texture',
_usageSuccess: true,
},
{
type0: 'sampled-texture',
type1: 'readonly-storage-texture',
_usageSuccess: true,
},
{
type0: 'sampled-texture',
type1: 'writeonly-storage-texture',
_usageSuccess: false,
},
{
type0: 'sampled-texture',
type1: 'render-target',
_usageSuccess: false,
},
{
type0: 'readonly-storage-texture',
type1: 'readonly-storage-texture',
_usageSuccess: true,
},
{
type0: 'readonly-storage-texture',
type1: 'writeonly-storage-texture',
_usageSuccess: false,
},
{
type0: 'readonly-storage-texture',
type1: 'render-target',
_usageSuccess: false,
},
// Race condition upon multiple writable storage texture is valid.
{
type0: 'writeonly-storage-texture',
type1: 'writeonly-storage-texture',
_usageSuccess: true,
},
{
type0: 'writeonly-storage-texture',
type1: 'render-target',
_usageSuccess: false,
},
] as const)
)
.fn(async t => {
const {
baseLevel,
baseLayer,
levelCount,
layerCount,
type0,
type1,
_usageSuccess,
_resourceSuccess,
} = t.params;

const texture = t.createTexture({
arrayLayerCount: TOTAL_LAYERS,
mipLevelCount: TOTAL_LEVELS,
usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.STORAGE | GPUTextureUsage.OUTPUT_ATTACHMENT,
});
const renderView = texture.createView({
baseMipLevel: writeBaseLevel,

const view0 = texture.createView({
baseMipLevel: BASE_LEVEL,
mipLevelCount: 1,
baseArrayLayer: writeBaseLayer,
baseArrayLayer: BASE_LAYER,
arrayLayerCount: 1,
});

const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type: 'sampled-texture' }],
const view1Dimension = layerCount !== 1 ? '2d-array' : '2d';
const view1 = texture.createView({
dimension: view1Dimension,
baseMipLevel: baseLevel,
mipLevelCount: levelCount,
baseArrayLayer: baseLayer,
arrayLayerCount: layerCount,
});

// TODO: Add two 'render-target' usages for color attachments.
const bglEntries: GPUBindGroupLayoutEntry[] = [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
type: type0,
storageTextureFormat: type0 === 'sampled-texture' ? undefined : 'rgba8unorm',
},
];
const bgEntries: GPUBindGroupEntry[] = [{ binding: 0, resource: view0 }];
if (type1 !== 'render-target') {
bglEntries.push({
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
type: type1,
viewDimension: view1Dimension,
storageTextureFormat: type1 === 'sampled-texture' ? undefined : 'rgba8unorm',
});
bgEntries.push({ binding: 1, resource: view1 });
}
const bindGroup = t.device.createBindGroup({
entries: [{ binding: 0, resource: sampleView }],
layout: bindGroupLayout,
entries: bgEntries,
layout: t.device.createBindGroupLayout({ entries: bglEntries }),
});

const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
attachment: renderView,
attachment: type1 === 'render-target' ? view1 : t.createTexture().createView(),
loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
storeOp: 'store',
},
Expand All @@ -119,46 +240,69 @@ g.test('readwrite_upon_subresources')
pass.setBindGroup(0, bindGroup);
pass.endPass();

const success = _resourceSuccess || _usageSuccess;
t.expectValidationError(() => {
encoder.finish();
}, !_success);
}, !success);
});

g.test('readwrite_upon_aspects')
g.test('subresources_and_binding_types_combination_for_aspect')
.params(
params()
.combine(poptions('format', kDepthStencilFormats))
.combine(poptions('readAspect', ['all', 'depth-only', 'stencil-only'] as const))
.combine(poptions('writeAspect', ['all', 'depth-only', 'stencil-only'] as const))
.combine(poptions('aspect0', ['all', 'depth-only', 'stencil-only'] as const))
.combine(poptions('aspect1', ['all', 'depth-only', 'stencil-only'] as const))
.unless(
({ format, readAspect, writeAspect }) =>
(readAspect === 'stencil-only' && !kDepthStencilFormatInfo[format].stencil) ||
(writeAspect === 'stencil-only' && !kDepthStencilFormatInfo[format].stencil)
({ format, aspect0, aspect1 }) =>
(aspect0 === 'stencil-only' && !kDepthStencilFormatInfo[format].stencil) ||
(aspect1 === 'stencil-only' && !kDepthStencilFormatInfo[format].stencil)
)
.unless(
({ format, readAspect, writeAspect }) =>
(readAspect === 'depth-only' && !kDepthStencilFormatInfo[format].depth) ||
(writeAspect === 'depth-only' && !kDepthStencilFormatInfo[format].depth)
({ format, aspect0, aspect1 }) =>
(aspect0 === 'depth-only' && !kDepthStencilFormatInfo[format].depth) ||
(aspect1 === 'depth-only' && !kDepthStencilFormatInfo[format].depth)
)
.combine([
{
type0: 'sampled-texture',
type1: 'sampled-texture',
_usageSuccess: true,
},
{
type0: 'sampled-texture',
type1: 'render-target',
_usageSuccess: false,
},
] as const)
)
.fn(async t => {
const { format, readAspect, writeAspect } = t.params;
const { format, aspect0, aspect1, type0, type1, _usageSuccess } = t.params;

const view = t.createTexture({ format }).createView();

const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type: 'sampled-texture' }],
});
const texture = t.createTexture({ format });
const view0 = texture.createView({ aspect: aspect0 });
const view1 = texture.createView({ aspect: aspect1 });

const bglEntries: GPUBindGroupLayoutEntry[] = [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
type: type0,
},
];
const bgEntries: GPUBindGroupEntry[] = [{ binding: 0, resource: view0 }];
if (type1 !== 'render-target') {
bglEntries.push({
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
type: type1,
});
bgEntries.push({ binding: 1, resource: view1 });
}
const bindGroup = t.device.createBindGroup({
entries: [{ binding: 0, resource: view }],
layout: bindGroupLayout,
entries: bgEntries,
layout: t.device.createBindGroupLayout({ entries: bglEntries }),
});

const success =
(readAspect === 'depth-only' && writeAspect === 'stencil-only') ||
(readAspect === 'stencil-only' && writeAspect === 'depth-only');

const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
Expand All @@ -168,17 +312,25 @@ g.test('readwrite_upon_aspects')
storeOp: 'store',
},
],
depthStencilAttachment: {
attachment: view,
depthStoreOp: 'clear',
depthLoadValue: 'load',
stencilStoreOp: 'clear',
stencilLoadValue: 'load',
},
depthStencilAttachment:
type1 !== 'render-target'
? undefined
: {
attachment: view1,
depthStoreOp: 'clear',
depthLoadValue: 'load',
stencilStoreOp: 'clear',
stencilLoadValue: 'load',
},
});
pass.setBindGroup(0, bindGroup);
pass.endPass();

const disjointAspects =
(aspect0 === 'depth-only' && aspect1 === 'stencil-only') ||
(aspect0 === 'stencil-only' && aspect1 === 'depth-only');
const success = disjointAspects || _usageSuccess;

t.expectValidationError(() => {
encoder.finish();
}, !success);
Expand Down

0 comments on commit fa4873f

Please sign in to comment.