Skip to content

Commit

Permalink
Test usage field of GPUTextureViewDescriptor (#3954)
Browse files Browse the repository at this point in the history
* Test usage field of GPUTextureViewDescriptor

Add variants of existing view format writing tests to cover end2end
inherited and minimal view usage.

Add validation tests for texture view creation with usage and texture
view usage compatibility in render passes/bind groups.
  • Loading branch information
vonture authored Sep 30, 2024
1 parent 3f6f6b7 commit 1746bcb
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 7 deletions.
36 changes: 29 additions & 7 deletions src/webgpu/api/operation/texture_view/write.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const kTextureViewWriteMethods = [
] as const;
type TextureViewWriteMethod = (typeof kTextureViewWriteMethods)[number];

const kTextureViewUsageMethods = ['inherit', 'minimal'] as const;
type TextureViewUsageMethod = (typeof kTextureViewUsageMethods)[number];

// Src color values to read from a shader array.
const kColorsFloat = [
{ R: 1.0, G: 0.0, B: 0.0, A: 0.8 },
Expand Down Expand Up @@ -271,6 +274,22 @@ function writeTextureAndGetExpectedTexelView(
return expectedTexelView;
}

function getTextureViewUsage(
viewUsageMethod: TextureViewUsageMethod,
minimalUsageForTest: GPUTextureUsageFlags
) {
switch (viewUsageMethod) {
case 'inherit':
return 0;

case 'minimal':
return minimalUsageForTest;

default:
unreachable();
}
}

g.test('format')
.desc(
`Views of every allowed format.
Expand All @@ -280,6 +299,7 @@ Read values from color array in the shader, and write it to the texture view via
- x= every texture format
- x= sampleCount {1, 4} if valid
- x= every possible view write method (see above)
- x= inherited or minimal texture view usage
TODO: Test sampleCount > 1 for 'render-pass-store' after extending copySinglePixelTextureToBufferUsingComputePass
to read multiple pixels from multisampled textures. [1]
Expand Down Expand Up @@ -318,6 +338,7 @@ TODO: Test rgb10a2uint when TexelRepresentation.numericRange is made per-compone
}
return true;
})
.combine('viewUsageMethod', kTextureViewUsageMethods)
)
.beforeAllSubcases(t => {
const { format, method } = t.params;
Expand All @@ -332,13 +353,12 @@ TODO: Test rgb10a2uint when TexelRepresentation.numericRange is made per-compone
}
})
.fn(t => {
const { format, method, sampleCount } = t.params;
const { format, method, sampleCount, viewUsageMethod } = t.params;

const usage =
GPUTextureUsage.COPY_SRC |
(method.includes('storage')
? GPUTextureUsage.STORAGE_BINDING
: GPUTextureUsage.RENDER_ATTACHMENT);
const textureUsageForMethod = method.includes('storage')
? GPUTextureUsage.STORAGE_BINDING
: GPUTextureUsage.RENDER_ATTACHMENT;
const usage = GPUTextureUsage.COPY_SRC | textureUsageForMethod;

const texture = t.createTextureTracked({
format,
Expand All @@ -347,7 +367,9 @@ TODO: Test rgb10a2uint when TexelRepresentation.numericRange is made per-compone
sampleCount,
});

const view = texture.createView();
const view = texture.createView({
usage: getTextureViewUsage(viewUsageMethod, textureUsageForMethod),
});
const expectedTexelView = writeTextureAndGetExpectedTexelView(
t,
method,
Expand Down
72 changes: 72 additions & 0 deletions src/webgpu/api/validation/createView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { unreachable } from '../../../common/util/util.js';
import {
kTextureAspects,
kTextureDimensions,
kTextureUsages,
kTextureViewDimensions,
} from '../../capability_info.js';
import { GPUConst } from '../../constants.js';
import {
kTextureFormatInfo,
kAllTextureFormats,
Expand Down Expand Up @@ -339,3 +341,73 @@ g.test('texture_state')
texture.createView();
}, state === 'invalid');
});

g.test('texture_view_usage')
.desc(
`Test texture view usage (single, combined, inherited) for every texture format and texture usage`
)
.params(u =>
u //
.combine('format', kAllTextureFormats)
.combine('textureUsage0', kTextureUsages)
.combine('textureUsage1', kTextureUsages)
.filter(({ format, textureUsage0, textureUsage1 }) => {
const info = kTextureFormatInfo[format];
const textureUsage = textureUsage0 | textureUsage1;

if (
(textureUsage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 &&
info.color &&
!info.colorRender
) {
return false;
}

return true;
})
.beginSubcases()
.combine('textureViewUsage0', [0, ...kTextureUsages])
.combine('textureViewUsage1', [0, ...kTextureUsages])
)
.beforeAllSubcases(t => {
const { format, textureUsage0, textureUsage1 } = t.params;
const info = kTextureFormatInfo[format];
const textureUsage = textureUsage0 | textureUsage1;
t.skipIfTextureFormatNotSupported(format);
t.selectDeviceOrSkipTestCase(info.feature);
if (textureUsage & GPUTextureUsage.STORAGE_BINDING) {
t.skipIfTextureFormatNotUsableAsStorageTexture(format);
}
})
.fn(t => {
const { format, textureUsage0, textureUsage1, textureViewUsage0, textureViewUsage1 } = t.params;
const info = kTextureFormatInfo[format];

const size = [info.blockWidth, info.blockHeight, 1];
const dimension = '2d';
const mipLevelCount = 1;
const usage = textureUsage0 | textureUsage1;

const textureDescriptor: GPUTextureDescriptor = {
size,
mipLevelCount,
dimension,
format,
usage,
};

const texture = t.createTextureTracked(textureDescriptor);

let success = true;

const textureViewUsage = textureViewUsage0 | textureViewUsage1;

// Texture view usage must be a subset of texture usage
if ((~usage & textureViewUsage) !== 0) success = false;

t.expectValidationError(() => {
texture.createView({
usage: textureViewUsage,
});
}, !success);
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Texture Usages Validation Tests on All Kinds of WebGPU Subresource Usage Scopes.

import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { unreachable } from '../../../../../common/util/util.js';
import { kTextureUsages } from '../../../../capability_info.js';
import { ValidationTest } from '../../validation_test.js';
import {
TextureBindingType,
Expand Down Expand Up @@ -571,3 +572,79 @@ g.test('subresources,texture_usages_in_copy_and_render_pass')
encoder.finish();
}, false);
});

g.test('subresources,texture_view_usages')
.desc(
`
Test that the usages of the texture view are used to validate compatibility in command encoding
instead of the usages of the base texture.`
)
.params(u =>
u
.combine('bindingType', ['color-attachment', ...kTextureBindingTypes] as const)
.combine('viewUsage', [0, ...kTextureUsages])
)
.fn(t => {
const { bindingType, viewUsage } = t.params;

const texture = t.createTextureTracked({
format: 'r32float',
usage:
GPUTextureUsage.COPY_SRC |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.STORAGE_BINDING |
GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
...(t.isCompatibility && {
textureBindingViewDimension: '2d-array',
}),
});

switch (bindingType) {
case 'color-attachment': {
const encoder = t.device.createCommandEncoder();
const renderPassEncoder = encoder.beginRenderPass({
colorAttachments: [
{ view: texture.createView({ usage: viewUsage }), loadOp: 'load', storeOp: 'store' },
],
});
renderPassEncoder.end();

const success = viewUsage === 0 || (viewUsage & GPUTextureUsage.RENDER_ATTACHMENT) !== 0;

t.expectValidationError(() => {
encoder.finish();
}, !success);
break;
}
case 'sampled-texture':
case 'readonly-storage-texture':
case 'writeonly-storage-texture':
case 'readwrite-storage-texture':
{
let success = true;
if (viewUsage !== 0) {
if (bindingType === 'sampled-texture') {
if ((viewUsage & GPUTextureUsage.TEXTURE_BINDING) === 0) success = false;
} else {
if ((viewUsage & GPUTextureUsage.STORAGE_BINDING) === 0) success = false;
}
}

t.expectValidationError(() => {
t.createBindGroupForTest(
texture.createView({
dimension: '2d-array',
usage: viewUsage,
}),
bindingType,
'unfilterable-float'
);
}, !success);
}
break;
default:
unreachable();
}
});

0 comments on commit 1746bcb

Please sign in to comment.