update cts to include copy validation

This commit is contained in:
Kunal Mohan 2020-08-16 21:34:58 +05:30
parent 732efdacbd
commit 44173014e0
27 changed files with 1886 additions and 256 deletions

View file

@ -58,7 +58,7 @@
],
"gpu": {
"device_pool.js": [
"c6be73f9918be979816bf02978e8429ce8b4d63d",
"f72b6b734c671ff0316fadfec49e0a93bd16fd10",
[]
],
"implementation.js": [
@ -102,7 +102,7 @@
[]
],
"json_param_value.js": [
"1bb32d06dfdf95be6953faac150b767585198cf7",
"921b3ddce6aacde8e3b05d4133dc07ffb90842f8",
[]
],
"parseQuery.js": [
@ -161,7 +161,7 @@
]
},
"version.js": [
"4d5e72c0442fdbdd4d5ceeefa14dcbaafae4ffaa",
"794e3cdef44214801f15234a61aaf5af338f97fc",
[]
]
},
@ -229,7 +229,7 @@
],
"render_pass": {
"storeOp.spec.js": [
"c02e99db655933431cf8f310c21eac4febaf380d",
"83ae8657c1c822b6cfc8f511089de88dfd2d8fdc",
[]
]
},
@ -239,18 +239,36 @@
[]
],
"texture_zero_init_test.js": [
"e3c21011bb288492bd137be9e2e43b5efd02aa36",
"381d7dcb0a8e9574834629223308c7ef0e375b4b",
[]
]
}
},
"validation": {
"copyBufferToBuffer.spec.js": [
"5fd4877383a47b1e3ca27af8e0843781dbf5a443",
[]
],
"copy_between_linear_data_and_texture": {
"copyBetweenLinearDataAndTexture.js": [
"0db6e68d4af6e395ba07157fd4b1ed0c567fd182",
[]
],
"copyBetweenLinearDataAndTexture_dataRelated.spec.js": [
"be1395b664276f13a728a60acee835d96753e6e1",
[]
],
"copyBetweenLinearDataAndTexture_textureRelated.spec.js": [
"bef71943fae2bc6f0dcaa4ae53b744190ccdc00c",
[]
]
},
"createBindGroup.spec.js": [
"fa585038e39ff6208787ee89bc177bdb13cdc385",
[]
],
"createBindGroupLayout.spec.js": [
"9c0df538b278aaa38d11f3922b749799f72f4c3f",
"43fd923687b68d9214305b04ea1573e044245928",
[]
],
"createPipelineLayout.spec.js": [
@ -258,7 +276,7 @@
[]
],
"createTexture.spec.js": [
"a9c7a8be01e03bcda1aa2f96374c30c0ce0bbe45",
"ceff54e136ff321bf22a8647790c3263404f6afc",
[]
],
"createView.spec.js": [
@ -266,11 +284,11 @@
[]
],
"error_scope.spec.js": [
"5523237daeba5d0c1c292dd3da8dead86a99f8e4",
"64575540e8bde1545565389108067bf4b078cae9",
[]
],
"fences.spec.js": [
"cc04848410fed9f20b863aadbedf3482787b6de9",
"b50ebae572d9af8f4edd3f82c051877b6068374e",
[]
],
"queue_submit.spec.js": [
@ -293,7 +311,7 @@
],
"resource_usages": {
"textureUsageInRender.spec.js": [
"6322ec25ee537584030c322ab99ec3d69e02b30e",
"12efa65ce2d9e9ed67cb2be398a0466424d38015",
[]
]
},
@ -318,13 +336,13 @@
[]
],
"validation_test.js": [
"d816fd0594bd5097f4a7e3d2de91c3e0aa6e3f7b",
"c6cd14779bbb7b95e37bdb93d3f5be33902be6c2",
[]
]
}
},
"capability_info.js": [
"2e4264fe229c11bb387d300cebce6a4e69780cd1",
"10a8c5aa4b62c05830eabb33734d866939258ade",
[]
],
"examples.spec.js": [
@ -332,23 +350,23 @@
[]
],
"gpu_test.js": [
"92f0e41840bd8e74eda57bbeda60757af798e2f9",
"f350131af3babe9729dec35261f7c445e2bf41d2",
[]
],
"idl": {
"constants": {
"flags.spec.js": [
"1de75c84e5daeaafd3ac47b31b6011223b03e360",
"51b80aa8aa23cb987079f6dd228bb0bb68816624",
[]
]
},
"idl_test.js": [
"231fc0945f87fe6aede03ec7a03c1ecaf7fe04db",
"3943563d2ac2c779e6cfb0ab033b92bca24e5749",
[]
]
},
"listing.js": [
"8736de378a6a02235dfff5796f4fe4a963769001",
"3134cf0dd5693705ed82f0828f4dbd93fedf358e",
[]
],
"util": {
@ -357,12 +375,12 @@
[]
],
"math.js": [
"a76f1a00158ab50df82c74187de859dfbef41d60",
"4761467b586691662834dd0fbf3e86643a5eb823",
[]
],
"texture": {
"layout.js": [
"26d82a7be0aea4483b4b72d6faa2b9233a5f0039",
"c3c610bf0d93ebe66abf81d8f2017deba62641a3",
[]
],
"subresource.js": [
@ -370,7 +388,7 @@
[]
],
"texelData.js": [
"8a827c47d21e1c1a89e081b2e70d1759c9208b0e",
"eadaa09633022e6c82355b07a4798c802c56a8e8",
[]
]
}
@ -383,7 +401,7 @@
]
},
"copyImageBitmapToTexture.spec.js": [
"1432ff05e1daa192d5898bf1c7f42e84008b23b4",
"9df5ff94419baa92ff27f8b39cdc0ee99439dc06",
[]
],
"reftests": {
@ -396,7 +414,7 @@
[]
],
"gpu_ref_test.js": [
"dd58b543b01ec86c1a820d9d661a5c3f9b68b323",
"a45002835df48518eacb8d7887791ee200c3e3c1",
[]
],
"ref": {
@ -417,7 +435,7 @@
"testharness": {
"webgpu": {
"cts.html": [
"e87ceb79a409fc3e3ddcccf38e48b865740e0809",
"28d69b38d20367b4c61e72e834f518a11e8de411",
[
"webgpu/cts.html?q=webgpu:api,operation,buffers,map_detach:*",
{}
@ -446,6 +464,18 @@
"webgpu/cts.html?q=webgpu:api,operation,render_pass,storeOp:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,copyBufferToBuffer:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,copy_between_linear_data_and_texture,copyBetweenLinearDataAndTexture_dataRelated:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,copy_between_linear_data_and_texture,copyBetweenLinearDataAndTexture_textureRelated:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,createBindGroup:*",
{}

View file

@ -91,10 +91,13 @@ export class DevicePool {
static async makeHolder() {
const gpu = getGPU();
const adapter = await gpu.requestAdapter();
assert(adapter !== null);
const device = await adapter.requestDevice();
assert(device !== null);
const holder = {
acquired: false,
device: await adapter.requestDevice(),
device,
lostReason: undefined,
};

View file

@ -3,10 +3,11 @@
**/ import { assert } from '../util/util.js';
// JSON can't represent `undefined` and by default stores it as `null`.
// Instead, store `undefined` as this magic string value in JSON.
const jsUndefinedMagicValue = '✗undefined';
const jsUndefinedMagicValue = '_undef_';
export function stringifyParamValue(value) {
return JSON.stringify(value, (k, v) => {
// Make sure no one actually uses the magic value as a parameter.
assert(v !== jsUndefinedMagicValue);
return v === undefined ? jsUndefinedMagicValue : v;

View file

@ -1,3 +1,3 @@
// AUTO-GENERATED - DO NOT EDIT. See tools/gen_version.
export const version = 'a4499c8ced40640242a40448c4d4258e3fb830d3';
export const version = 'fa4873f0a303566ca6f34744a253d96f5e462d3d';

View file

@ -36,6 +36,9 @@
<meta name=variant content='?q=webgpu:api,operation,fences:*'>
<meta name=variant content='?q=webgpu:api,operation,render_pass,storeOp:*'>
<!--<meta name=variant content='?q=webgpu:api,operation,resource_init,copied_texture_clear:*'>-->
<meta name=variant content='?q=webgpu:api,validation,copyBufferToBuffer:*'>
<meta name=variant content='?q=webgpu:api,validation,copy_between_linear_data_and_texture,copyBetweenLinearDataAndTexture_dataRelated:*'>
<meta name=variant content='?q=webgpu:api,validation,copy_between_linear_data_and_texture,copyBetweenLinearDataAndTexture_textureRelated:*'>
<meta name=variant content='?q=webgpu:api,validation,createBindGroup:*'>
<!--<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:*'>-->
<meta name=variant content='?q=webgpu:api,validation,createPipelineLayout:*'>

View file

@ -30,7 +30,11 @@
TODO: test with more interesting loadOp values`;
import { params, poptions } from '../../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { kTextureFormatInfo, kTextureFormats } from '../../../capability_info.js';
import {
kEncodableTextureFormatInfo,
kEncodableTextureFormats,
kSizedDepthStencilFormats,
} from '../../../capability_info.js';
import { GPUTest } from '../../../gpu_test.js';
// Test with a zero and non-zero mip.
@ -140,20 +144,16 @@ g.test('render_pass_store_op,color_attachment_with_depth_stencil_attachment')
g.test('render_pass_store_op,color_attachment_only')
.params(
params()
.combine(poptions('colorFormat', kTextureFormats))
// Filter out any depth/stencil or non-renderable formats
.filter(
({ colorFormat }) =>
kTextureFormatInfo[colorFormat].color && kTextureFormatInfo[colorFormat].renderable
)
.combine(poptions('colorFormat', kEncodableTextureFormats))
// Filter out any non-renderable formats
.filter(({ colorFormat }) => kEncodableTextureFormatInfo[colorFormat].renderable)
.combine(poptions('storeOperation', kStoreOps))
.combine(poptions('mipLevel', kMipLevel))
.combine(poptions('arrayLayer', kArrayLayers))
)
.fn(t => {
const kColorFormat = 'rgba8unorm';
const colorAttachment = t.device.createTexture({
format: kColorFormat,
format: t.params.colorFormat,
size: { width: kWidth, height: kHeight, depth: t.params.arrayLayer + 1 },
mipLevelCount: kMipLevelCount,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
@ -194,7 +194,7 @@ g.test('render_pass_store_op,color_attachment_only')
expectedValue = { R: 1.0, G: 0.0, B: 0.0, A: 1.0 };
}
t.expectSingleColor(colorAttachment, kColorFormat, {
t.expectSingleColor(colorAttachment, t.params.colorFormat, {
size: [kHeight, kWidth, 1],
slice: t.params.arrayLayer,
exp: expectedValue,
@ -266,15 +266,8 @@ g.test('render_pass_store_op,multiple_color_attachments')
g.test('render_pass_store_op,depth_stencil_attachment_only')
.params(
params()
.combine(poptions('depthStencilFormat', kTextureFormats))
// Filter out color and non-renderable formats.
.filter(
({ depthStencilFormat }) =>
(kTextureFormatInfo[depthStencilFormat].depth ||
kTextureFormatInfo[depthStencilFormat].stencil) &&
kTextureFormatInfo[depthStencilFormat].renderable &&
kTextureFormatInfo[depthStencilFormat].copyable
)
// TODO: Also test unsized depth/stencil formats
.combine(poptions('depthStencilFormat', kSizedDepthStencilFormats))
.combine(poptions('storeOperation', kStoreOps))
.combine(poptions('mipLevel', kMipLevel))
.combine(poptions('arrayLayer', kArrayLayers))

View file

@ -15,7 +15,11 @@
}
import { params, poptions, pbool } from '../../../../common/framework/params_builder.js';
import { assert, unreachable } from '../../../../common/framework/util/util.js';
import { kTextureAspects, kTextureFormatInfo, kTextureFormats } from '../../../capability_info.js';
import {
kTextureAspects,
kUncompressedTextureFormatInfo,
kUncompressedTextureFormats,
} from '../../../capability_info.js';
import { GPUTest } from '../../../gpu_test.js';
import { createTextureUploadBuffer } from '../../../util/texture/layout.js';
import { SubresourceRange } from '../../../util/texture/subresource.js';
@ -192,10 +196,10 @@ function getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMe
usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;
}
if (!kTextureFormatInfo[format].copyable) {
if (!kUncompressedTextureFormatInfo[format].copyDst) {
// Copies are not possible. We need OutputAttachment to initialize
// canary data.
assert(kTextureFormatInfo[format].renderable);
assert(kUncompressedTextureFormatInfo[format].renderable);
usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;
}
@ -306,7 +310,7 @@ export class TextureZeroInitTest extends GPUTest {
this.params.aspect,
subresourceRange
)) {
if (kTextureFormatInfo[this.params.format].color) {
if (kUncompressedTextureFormatInfo[this.params.format].color) {
commandEncoder
.beginRenderPass({
colorAttachments: [
@ -383,10 +387,13 @@ export class TextureZeroInitTest extends GPUTest {
}
initializeTexture(texture, state, subresourceRange) {
if (this.params.sampleCount > 1 || !kTextureFormatInfo[this.params.format].copyable) {
if (
this.params.sampleCount > 1 ||
!kUncompressedTextureFormatInfo[this.params.format].copyDst
) {
// Copies to multisampled textures not yet specified.
// Use a storeOp for now.
assert(kTextureFormatInfo[this.params.format].renderable);
assert(kUncompressedTextureFormatInfo[this.params.format].renderable);
this.initializeWithStoreOp(state, texture, subresourceRange);
} else {
this.initializeWithCopy(texture, state, subresourceRange);
@ -400,7 +407,7 @@ export class TextureZeroInitTest extends GPUTest {
this.params.aspect,
subresourceRange
)) {
if (kTextureFormatInfo[this.params.format].color) {
if (kUncompressedTextureFormatInfo[this.params.format].color) {
commandEncoder
.beginRenderPass({
colorAttachments: [
@ -434,12 +441,12 @@ export class TextureZeroInitTest extends GPUTest {
return (
// TODO: Consider making a list of "valid" texture descriptors in capability_info.
params()
.combine(poptions('format', kTextureFormats))
.combine(poptions('format', kUncompressedTextureFormats))
.combine(poptions('aspect', kTextureAspects))
.unless(
({ format, aspect }) =>
(aspect === 'depth-only' && !kTextureFormatInfo[format].depth) ||
(aspect === 'stencil-only' && !kTextureFormatInfo[format].stencil)
(aspect === 'depth-only' && !kUncompressedTextureFormatInfo[format].depth) ||
(aspect === 'stencil-only' && !kUncompressedTextureFormatInfo[format].stencil)
)
.combine(poptions('mipLevelCount', kMipLevelCounts))
.combine(poptions('sampleCount', kSampleCounts))
@ -456,14 +463,16 @@ export class TextureZeroInitTest extends GPUTest {
(readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture) &&
(format === 'depth24plus' || format === 'depth24plus-stencil8')
)
.unless(
({ readMethod, format }) =>
(readMethod === ReadMethod.DepthTest && !kTextureFormatInfo[format].depth) ||
(readMethod === ReadMethod.StencilTest && !kTextureFormatInfo[format].stencil) ||
(readMethod === ReadMethod.ColorBlending && !kTextureFormatInfo[format].color) ||
.unless(({ readMethod, format }) => {
const info = kUncompressedTextureFormatInfo[format];
return (
(readMethod === ReadMethod.DepthTest && !info.depth) ||
(readMethod === ReadMethod.StencilTest && !info.stencil) ||
(readMethod === ReadMethod.ColorBlending && !info.color) ||
// TODO: Test with depth sampling
(readMethod === ReadMethod.Sample && kTextureFormatInfo[format].depth)
)
(readMethod === ReadMethod.Sample && info.depth)
);
})
.unless(
({ readMethod, sampleCount }) =>
// We can only read from multisampled textures by sampling.
@ -481,11 +490,13 @@ export class TextureZeroInitTest extends GPUTest {
readMethod
);
if (usage & GPUTextureUsage.OUTPUT_ATTACHMENT && !kTextureFormatInfo[format].renderable) {
const info = kUncompressedTextureFormatInfo[format];
if (usage & GPUTextureUsage.OUTPUT_ATTACHMENT && !info.renderable) {
return false;
}
if (usage & GPUTextureUsage.STORAGE && !kTextureFormatInfo[format].storage) {
if (usage & GPUTextureUsage.STORAGE && !info.storage) {
return false;
}

View file

@ -0,0 +1,271 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
copyBufferToBuffer tests.
Test Plan:
* Buffer is valid/invalid
- the source buffer is invalid
- the destination buffer is invalid
* Buffer usages
- the source buffer is created without GPUBufferUsage::COPY_SRC
- the destination buffer is created without GPUBufferUsage::COPY_DEST
* CopySize
- copySize is not a multiple of 4
- copySize is 0
* copy offsets
- sourceOffset is not a multiple of 4
- destinationOffset is not a multiple of 4
* Arthimetic overflow
- (sourceOffset + copySize) is overflow
- (destinationOffset + copySize) is overflow
* Out of bounds
- (sourceOffset + copySize) > size of source buffer
- (destinationOffset + copySize) > size of destination buffer
* Source buffer and destination buffer are the same buffer
`;
import { poptions, params } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { kBufferUsages } from '../../capability_info.js';
import { kMaxSafeMultipleOf8 } from '../../util/math.js';
import { ValidationTest } from './validation_test.js';
class F extends ValidationTest {
TestCopyBufferToBuffer(options) {
const { srcBuffer, srcOffset, dstBuffer, dstOffset, copySize, isSuccess } = options;
const commandEncoder = this.device.createCommandEncoder();
commandEncoder.copyBufferToBuffer(srcBuffer, srcOffset, dstBuffer, dstOffset, copySize);
this.expectValidationError(() => {
commandEncoder.finish();
}, !isSuccess);
}
}
export const g = makeTestGroup(F);
g.test('copy_with_invalid_buffer').fn(async t => {
const validBuffer = t.device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
const errorBuffer = t.getErrorBuffer();
t.TestCopyBufferToBuffer({
srcBuffer: errorBuffer,
srcOffset: 0,
dstBuffer: validBuffer,
dstOffset: 0,
copySize: 8,
isSuccess: false,
});
t.TestCopyBufferToBuffer({
srcBuffer: validBuffer,
srcOffset: 0,
dstBuffer: errorBuffer,
dstOffset: 0,
copySize: 8,
isSuccess: false,
});
});
g.test('buffer_usage')
.params(
params()
.combine(poptions('srcUsage', kBufferUsages))
.combine(poptions('dstUsage', kBufferUsages))
)
.fn(async t => {
const { srcUsage, dstUsage } = t.params;
const srcBuffer = t.device.createBuffer({
size: 16,
usage: srcUsage,
});
const dstBuffer = t.device.createBuffer({
size: 16,
usage: dstUsage,
});
const isSuccess = srcUsage === GPUBufferUsage.COPY_SRC && dstUsage === GPUBufferUsage.COPY_DST;
t.TestCopyBufferToBuffer({
srcBuffer,
srcOffset: 0,
dstBuffer,
dstOffset: 0,
copySize: 8,
isSuccess,
});
});
g.test('copy_size_alignment')
.params([
{ copySize: 0, _isSuccess: true },
{ copySize: 2, _isSuccess: false },
{ copySize: 4, _isSuccess: true },
{ copySize: 5, _isSuccess: false },
{ copySize: 8, _isSuccess: true },
])
.fn(async t => {
const { copySize, _isSuccess: isSuccess } = t.params;
const srcBuffer = t.device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_SRC,
});
const dstBuffer = t.device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_DST,
});
t.TestCopyBufferToBuffer({
srcBuffer,
srcOffset: 0,
dstBuffer,
dstOffset: 0,
copySize,
isSuccess,
});
});
g.test('copy_offset_alignment')
.params([
{ srcOffset: 0, dstOffset: 0, _isSuccess: true },
{ srcOffset: 2, dstOffset: 0, _isSuccess: false },
{ srcOffset: 4, dstOffset: 0, _isSuccess: true },
{ srcOffset: 5, dstOffset: 0, _isSuccess: false },
{ srcOffset: 8, dstOffset: 0, _isSuccess: true },
{ srcOffset: 0, dstOffset: 2, _isSuccess: false },
{ srcOffset: 0, dstOffset: 4, _isSuccess: true },
{ srcOffset: 0, dstOffset: 5, _isSuccess: false },
{ srcOffset: 0, dstOffset: 8, _isSuccess: true },
{ srcOffset: 4, dstOffset: 4, _isSuccess: true },
])
.fn(async t => {
const { srcOffset, dstOffset, _isSuccess: isSuccess } = t.params;
const srcBuffer = t.device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_SRC,
});
const dstBuffer = t.device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_DST,
});
t.TestCopyBufferToBuffer({
srcBuffer,
srcOffset,
dstBuffer,
dstOffset,
copySize: 8,
isSuccess,
});
});
g.test('copy_overflow')
.params([
{ srcOffset: 0, dstOffset: 0, copySize: kMaxSafeMultipleOf8 },
{ srcOffset: 16, dstOffset: 0, copySize: kMaxSafeMultipleOf8 },
{ srcOffset: 0, dstOffset: 16, copySize: kMaxSafeMultipleOf8 },
{ srcOffset: kMaxSafeMultipleOf8, dstOffset: 0, copySize: 16 },
{ srcOffset: 0, dstOffset: kMaxSafeMultipleOf8, copySize: 16 },
{ srcOffset: kMaxSafeMultipleOf8, dstOffset: 0, copySize: kMaxSafeMultipleOf8 },
{ srcOffset: 0, dstOffset: kMaxSafeMultipleOf8, copySize: kMaxSafeMultipleOf8 },
{
srcOffset: kMaxSafeMultipleOf8,
dstOffset: kMaxSafeMultipleOf8,
copySize: kMaxSafeMultipleOf8,
},
])
.fn(async t => {
const { srcOffset, dstOffset, copySize } = t.params;
const srcBuffer = t.device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_SRC,
});
const dstBuffer = t.device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_DST,
});
t.TestCopyBufferToBuffer({
srcBuffer,
srcOffset,
dstBuffer,
dstOffset,
copySize,
isSuccess: false,
});
});
g.test('copy_out_of_bounds')
.params([
{ srcOffset: 0, dstOffset: 0, copySize: 32, _isSuccess: true },
{ srcOffset: 0, dstOffset: 0, copySize: 36 },
{ srcOffset: 36, dstOffset: 0, copySize: 4 },
{ srcOffset: 0, dstOffset: 36, copySize: 4 },
{ srcOffset: 36, dstOffset: 0, copySize: 0 },
{ srcOffset: 0, dstOffset: 36, copySize: 0 },
{ srcOffset: 20, dstOffset: 0, copySize: 16 },
{ srcOffset: 20, dstOffset: 0, copySize: 12, _isSuccess: true },
{ srcOffset: 0, dstOffset: 20, copySize: 16 },
{ srcOffset: 0, dstOffset: 20, copySize: 12, _isSuccess: true },
])
.fn(async t => {
const { srcOffset, dstOffset, copySize, _isSuccess = false } = t.params;
const srcBuffer = t.device.createBuffer({
size: 32,
usage: GPUBufferUsage.COPY_SRC,
});
const dstBuffer = t.device.createBuffer({
size: 32,
usage: GPUBufferUsage.COPY_DST,
});
t.TestCopyBufferToBuffer({
srcBuffer,
srcOffset,
dstBuffer,
dstOffset,
copySize,
isSuccess: _isSuccess,
});
});
g.test('copy_within_same_buffer')
.params([
{ srcOffset: 0, dstOffset: 8, copySize: 4 },
{ srcOffset: 8, dstOffset: 0, copySize: 4 },
{ srcOffset: 0, dstOffset: 4, copySize: 8 },
{ srcOffset: 4, dstOffset: 0, copySize: 8 },
])
.fn(async t => {
const { srcOffset, dstOffset, copySize } = t.params;
const buffer = t.device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
t.TestCopyBufferToBuffer({
srcBuffer: buffer,
srcOffset,
dstBuffer: buffer,
dstOffset,
copySize,
isSuccess: false,
});
});

View file

@ -0,0 +1,179 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { poptions } from '../../../../common/framework/params_builder.js';
import { assert } from '../../../../common/framework/util/util.js';
import { kSizedTextureFormatInfo } from '../../../capability_info.js';
import { ValidationTest } from '../validation_test.js';
export const kAllTestMethods = ['WriteTexture', 'CopyBufferToTexture', 'CopyTextureToBuffer'];
export class CopyBetweenLinearDataAndTextureTest extends ValidationTest {
bytesInACompleteRow(copyWidth, format) {
const info = kSizedTextureFormatInfo[format];
assert(copyWidth % info.blockWidth === 0);
return (info.bytesPerBlock * copyWidth) / info.blockWidth;
}
requiredBytesInCopy(layout, format, copyExtent) {
const info = kSizedTextureFormatInfo[format];
assert(layout.rowsPerImage % info.blockHeight === 0);
assert(copyExtent.height % info.blockHeight === 0);
assert(copyExtent.width % info.blockWidth === 0);
if (copyExtent.width === 0 || copyExtent.height === 0 || copyExtent.depth === 0) {
return 0;
} else {
const texelBlockRowsPerImage = layout.rowsPerImage / info.blockHeight;
const bytesPerImage = layout.bytesPerRow * texelBlockRowsPerImage;
const bytesInLastSlice =
layout.bytesPerRow * (copyExtent.height / info.blockHeight - 1) +
(copyExtent.width / info.blockWidth) * info.bytesPerBlock;
return bytesPerImage * (copyExtent.depth - 1) + bytesInLastSlice;
}
}
testRun(
textureCopyView,
textureDataLayout,
size,
{
dataSize,
method,
success,
submit = false, // If submit is true, the validaton error is expected to come from the submit and encoding should succeed.
}
) {
switch (method) {
case 'WriteTexture': {
const data = new Uint8Array(dataSize);
this.expectValidationError(() => {
this.device.defaultQueue.writeTexture(textureCopyView, data, textureDataLayout, size);
}, !success);
break;
}
case 'CopyBufferToTexture': {
const buffer = this.device.createBuffer({
size: dataSize,
usage: GPUBufferUsage.COPY_SRC,
});
const encoder = this.device.createCommandEncoder();
encoder.copyBufferToTexture({ buffer, ...textureDataLayout }, textureCopyView, size);
if (submit) {
const cmd = encoder.finish();
this.expectValidationError(() => {
this.device.defaultQueue.submit([cmd]);
}, !success);
} else {
this.expectValidationError(() => {
encoder.finish();
}, !success);
}
break;
}
case 'CopyTextureToBuffer': {
const buffer = this.device.createBuffer({
size: dataSize,
usage: GPUBufferUsage.COPY_DST,
});
const encoder = this.device.createCommandEncoder();
encoder.copyTextureToBuffer(textureCopyView, { buffer, ...textureDataLayout }, size);
if (submit) {
const cmd = encoder.finish();
this.expectValidationError(() => {
this.device.defaultQueue.submit([cmd]);
}, !success);
} else {
this.expectValidationError(() => {
encoder.finish();
}, !success);
}
break;
}
}
}
// This is a helper function used for creating a texture when we don't have to be very
// precise about its size as long as it's big enough and properly aligned.
createAlignedTexture(
format,
copySize = { width: 1, height: 1, depth: 1 },
origin = { x: 0, y: 0, z: 0 }
) {
const info = kSizedTextureFormatInfo[format];
return this.device.createTexture({
size: {
width: Math.max(1, copySize.width + origin.x) * info.blockWidth,
height: Math.max(1, copySize.height + origin.y) * info.blockHeight,
depth: Math.max(1, copySize.depth + origin.z),
},
format,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
});
}
}
// For testing divisibility by a number we test all the values returned by this function:
function valuesToTestDivisibilityBy(number) {
const values = [];
for (let i = 0; i <= 2 * number; ++i) {
values.push(i);
}
values.push(3 * number);
return values;
}
// This is a helper function used for expanding test parameters for texel block alignment tests on offset
export function texelBlockAlignmentTestExpanderForOffset({ format }) {
return poptions(
'offset',
valuesToTestDivisibilityBy(kSizedTextureFormatInfo[format].bytesPerBlock)
);
}
// This is a helper function used for expanding test parameters for texel block alignment tests on rowsPerImage
export function texelBlockAlignmentTestExpanderForRowsPerImage({ format }) {
return poptions(
'rowsPerImage',
valuesToTestDivisibilityBy(kSizedTextureFormatInfo[format].blockHeight)
);
}
// This is a helper function used for expanding test parameters for texel block alignment tests on origin and size
export function texelBlockAlignmentTestExpanderForValueToCoordinate({ format, coordinateToTest }) {
switch (coordinateToTest) {
case 'x':
case 'width':
return poptions(
'valueToCoordinate',
valuesToTestDivisibilityBy(kSizedTextureFormatInfo[format].blockWidth)
);
case 'y':
case 'height':
return poptions(
'valueToCoordinate',
valuesToTestDivisibilityBy(kSizedTextureFormatInfo[format].blockHeight)
);
case 'z':
case 'depth':
return poptions('valueToCoordinate', valuesToTestDivisibilityBy(1));
}
}
// This is a helper function used for filtering test parameters
export function formatCopyableWithMethod({ format, method }) {
if (method === 'CopyTextureToBuffer') {
return kSizedTextureFormatInfo[format].copySrc;
} else {
return kSizedTextureFormatInfo[format].copyDst;
}
}

View file

@ -0,0 +1,299 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = '';
import { params, poptions } from '../../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import {
kUncompressedTextureFormatInfo,
kSizedTextureFormats,
kSizedTextureFormatInfo,
} from '../../../capability_info.js';
import { align } from '../../../util/math.js';
import {
CopyBetweenLinearDataAndTextureTest,
kAllTestMethods,
texelBlockAlignmentTestExpanderForOffset,
texelBlockAlignmentTestExpanderForRowsPerImage,
formatCopyableWithMethod,
} from './copyBetweenLinearDataAndTexture.js';
export const g = makeTestGroup(CopyBetweenLinearDataAndTextureTest);
g.test('bound_on_rows_per_image')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine(poptions('rowsPerImageInBlocks', [0, 1, 2]))
.combine(poptions('copyHeightInBlocks', [0, 1, 2]))
.combine(poptions('copyDepth', [1, 3]))
)
.fn(async t => {
const { rowsPerImageInBlocks, copyHeightInBlocks, copyDepth, method } = t.params;
const format = 'rgba8unorm';
const rowsPerImage = rowsPerImageInBlocks * kUncompressedTextureFormatInfo[format].blockHeight;
const copyHeight = copyHeightInBlocks * kUncompressedTextureFormatInfo[format].blockHeight;
const texture = t.device.createTexture({
size: { width: 4, height: 4, depth: 3 },
format,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
});
// The WebGPU spec:
// If layout.rowsPerImage is not 0, it must be greater than or equal to copyExtent.height.
// If copyExtent.depth is greater than 1: layout.rowsPerImage must be greater than or equal to copyExtent.height.
// TODO: Update this if https://github.com/gpuweb/gpuweb/issues/984 changes the spec.
let success = true;
if (rowsPerImage !== 0 && rowsPerImage < copyHeight) {
success = false;
}
if (copyDepth > 1 && rowsPerImage < copyHeight) {
success = false;
}
t.testRun(
{ texture },
{ bytesPerRow: 1024, rowsPerImage },
{ width: 0, height: copyHeight, depth: copyDepth },
{ dataSize: 1, method, success }
);
});
// Test with offset + requiredBytesIsCopy overflowing GPUSize64.
g.test('offset_plus_required_bytes_in_copy_overflow')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine([
{ bytesPerRow: 2 ** 31, rowsPerImage: 2 ** 31, depth: 1, _success: true }, // success case
{ bytesPerRow: 2 ** 31, rowsPerImage: 2 ** 31, depth: 16, _success: false }, // bytesPerRow * rowsPerImage * (depth - 1) overflows.
])
)
.fn(async t => {
const { method, bytesPerRow, rowsPerImage, depth, _success } = t.params;
const texture = t.device.createTexture({
size: [1, 1, depth],
format: 'rgba8unorm',
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
});
t.testRun(
{ texture },
{ bytesPerRow, rowsPerImage },
{ width: 1, height: 1, depth },
{
dataSize: 10000,
method,
success: _success,
}
);
});
// Testing that the minimal data size condition is checked correctly.
// In the success case, we test the exact value.
// In the failing case, we test the exact value minus 1.
g.test('required_bytes_in_copy')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine([
{ bytesPerRowPadding: 0, rowsPerImagePaddingInBlocks: 0 }, // no padding
{ bytesPerRowPadding: 0, rowsPerImagePaddingInBlocks: 6 }, // rowsPerImage padding
{ bytesPerRowPadding: 6, rowsPerImagePaddingInBlocks: 0 }, // bytesPerRow padding
{ bytesPerRowPadding: 15, rowsPerImagePaddingInBlocks: 17 }, // both paddings
])
.combine([
{ copyWidthInBlocks: 3, copyHeightInBlocks: 4, copyDepth: 5, offsetInBlocks: 0 }, // standard copy
{ copyWidthInBlocks: 5, copyHeightInBlocks: 4, copyDepth: 3, offsetInBlocks: 11 }, // standard copy, offset > 0
{ copyWidthInBlocks: 256, copyHeightInBlocks: 3, copyDepth: 2, offsetInBlocks: 0 }, // copyWidth is 256-aligned
{ copyWidthInBlocks: 0, copyHeightInBlocks: 4, copyDepth: 5, offsetInBlocks: 0 }, // empty copy because of width
{ copyWidthInBlocks: 3, copyHeightInBlocks: 0, copyDepth: 5, offsetInBlocks: 0 }, // empty copy because of height
{ copyWidthInBlocks: 3, copyHeightInBlocks: 4, copyDepth: 0, offsetInBlocks: 13 }, // empty copy because of depth, offset > 0
{ copyWidthInBlocks: 1, copyHeightInBlocks: 4, copyDepth: 5, offsetInBlocks: 0 }, // copyWidth = 1
{ copyWidthInBlocks: 3, copyHeightInBlocks: 1, copyDepth: 5, offsetInBlocks: 15 }, // copyHeight = 1, offset > 0
{ copyWidthInBlocks: 5, copyHeightInBlocks: 4, copyDepth: 1, offsetInBlocks: 0 }, // copyDepth = 1
{ copyWidthInBlocks: 7, copyHeightInBlocks: 1, copyDepth: 1, offsetInBlocks: 0 }, // copyHeight = 1 and copyDepth = 1
])
.combine(poptions('format', kSizedTextureFormats))
.filter(formatCopyableWithMethod)
)
.fn(async t => {
const {
offsetInBlocks,
bytesPerRowPadding,
rowsPerImagePaddingInBlocks,
copyWidthInBlocks,
copyHeightInBlocks,
copyDepth,
format,
method,
} = t.params;
// In the CopyB2T and CopyT2B cases we need to have bytesPerRow 256-aligned,
// to make this happen we align the bytesInACompleteRow value and multiply
// bytesPerRowPadding by 256.
const bytesPerRowAlignment = method === 'WriteTexture' ? 1 : 256;
const info = kSizedTextureFormatInfo[format];
const copyWidth = copyWidthInBlocks * info.blockWidth;
const copyHeight = copyHeightInBlocks * info.blockHeight;
const offset = offsetInBlocks * info.bytesPerBlock;
const rowsPerImage = copyHeight + rowsPerImagePaddingInBlocks * info.blockHeight;
const bytesPerRow =
align(t.bytesInACompleteRow(copyWidth, format), bytesPerRowAlignment) +
bytesPerRowPadding * bytesPerRowAlignment;
const size = { width: copyWidth, height: copyHeight, depth: copyDepth };
const minDataSize =
offset + t.requiredBytesInCopy({ offset, bytesPerRow, rowsPerImage }, format, size);
const texture = t.createAlignedTexture(format, size);
t.testRun({ texture }, { offset, bytesPerRow, rowsPerImage }, size, {
dataSize: minDataSize,
method,
success: true,
});
if (minDataSize > 0) {
t.testRun({ texture }, { offset, bytesPerRow, rowsPerImage }, size, {
dataSize: minDataSize - 1,
method,
success: false,
});
}
});
g.test('texel_block_alignment_on_rows_per_image')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine(poptions('format', kSizedTextureFormats))
.filter(formatCopyableWithMethod)
.expand(texelBlockAlignmentTestExpanderForRowsPerImage)
)
.fn(async t => {
const { rowsPerImage, format, method } = t.params;
const size = { width: 0, height: 0, depth: 0 };
const texture = t.createAlignedTexture(format, size);
const success = rowsPerImage % kSizedTextureFormatInfo[format].blockHeight === 0;
t.testRun({ texture }, { bytesPerRow: 0, rowsPerImage }, size, {
dataSize: 1,
method,
success,
});
});
// TODO: Update this if https://github.com/gpuweb/gpuweb/issues/985 changes the spec.
g.test('texel_block_alignment_on_offset')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine(poptions('format', kSizedTextureFormats))
.filter(formatCopyableWithMethod)
.expand(texelBlockAlignmentTestExpanderForOffset)
)
.fn(async t => {
const { format, offset, method } = t.params;
const size = { width: 0, height: 0, depth: 0 };
const texture = t.createAlignedTexture(format, size);
const success = offset % kSizedTextureFormatInfo[format].bytesPerBlock === 0;
t.testRun({ texture }, { offset, bytesPerRow: 0 }, size, { dataSize: offset, method, success });
});
g.test('bound_on_bytes_per_row')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine([
{ blocksPerRow: 2, additionalPaddingPerRow: 0, copyWidthInBlocks: 2 }, // success
{ blocksPerRow: 2, additionalPaddingPerRow: 5, copyWidthInBlocks: 3 }, // success if bytesPerBlock <= 5
{ blocksPerRow: 1, additionalPaddingPerRow: 0, copyWidthInBlocks: 2 }, // failure, bytesPerRow > 0
{ blocksPerRow: 0, additionalPaddingPerRow: 0, copyWidthInBlocks: 1 }, // failure, bytesPerRow = 0
])
.combine([
{ copyHeightInBlocks: 0, copyDepth: 1 }, // we don't have to check the bound
{ copyHeightInBlocks: 1, copyDepth: 0 }, // we don't have to check the bound
{ copyHeightInBlocks: 2, copyDepth: 1 }, // we have to check the bound
{ copyHeightInBlocks: 0, copyDepth: 2 }, // we have to check the bound
])
.combine(poptions('format', kSizedTextureFormats))
.filter(formatCopyableWithMethod)
)
.fn(async t => {
const {
blocksPerRow,
additionalPaddingPerRow,
copyWidthInBlocks,
copyHeightInBlocks,
copyDepth,
format,
method,
} = t.params;
// In the CopyB2T and CopyT2B cases we need to have bytesPerRow 256-aligned,
// to make this happen we multiply copyWidth and bytesPerRow by 256, so that
// the appropriate inequalities still hold.
const bytesPerRowAlignment = method === 'WriteTexture' ? 1 : 256;
const info = kSizedTextureFormatInfo[format];
const copyWidth = copyWidthInBlocks * info.blockWidth * bytesPerRowAlignment;
const copyHeight = copyHeightInBlocks * info.blockHeight;
const bytesPerRow =
(blocksPerRow * info.bytesPerBlock + additionalPaddingPerRow) * bytesPerRowAlignment;
const size = { width: copyWidth, height: copyHeight, depth: copyDepth };
const texture = t.createAlignedTexture(format, size);
let success = true;
if (copyHeight > 1 || copyDepth > 1) {
success = bytesPerRow >= t.bytesInACompleteRow(copyWidth, format);
}
t.testRun({ texture }, { bytesPerRow, rowsPerImage: copyHeight }, size, {
dataSize: 1024,
method,
success,
});
});
g.test('bound_on_offset')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine(poptions('offsetInBlocks', [0, 1, 2]))
.combine(poptions('dataSizeInBlocks', [0, 1, 2]))
)
.fn(async t => {
const { offsetInBlocks, dataSizeInBlocks, method } = t.params;
const format = 'rgba8unorm';
const info = kSizedTextureFormatInfo[format];
const offset = offsetInBlocks * info.bytesPerBlock;
const dataSize = dataSizeInBlocks * info.bytesPerBlock;
const texture = t.device.createTexture({
size: { width: 4, height: 4, depth: 1 },
format,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
});
const success = offset <= dataSize;
t.testRun(
{ texture },
{ offset, bytesPerRow: 0 },
{ width: 0, height: 0, depth: 0 },
{ dataSize, method, success }
);
});

View file

@ -0,0 +1,307 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = '';
import { params, poptions } from '../../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { kSizedTextureFormats, kSizedTextureFormatInfo } from '../../../capability_info.js';
import {
CopyBetweenLinearDataAndTextureTest,
kAllTestMethods,
texelBlockAlignmentTestExpanderForValueToCoordinate,
formatCopyableWithMethod,
} from './copyBetweenLinearDataAndTexture.js';
export const g = makeTestGroup(CopyBetweenLinearDataAndTextureTest);
g.test('texture_must_be_valid')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine(poptions('textureState', ['valid', 'destroyed', 'error']))
)
.fn(async t => {
const { method, textureState } = t.params;
// A valid texture.
let texture = t.device.createTexture({
size: { width: 4, height: 4, depth: 1 },
format: 'rgba8unorm',
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
});
switch (textureState) {
case 'destroyed': {
texture.destroy();
break;
}
case 'error': {
texture = t.getErrorTexture();
break;
}
}
const success = textureState === 'valid';
const submit = textureState === 'destroyed';
t.testRun(
{ texture },
{ bytesPerRow: 0 },
{ width: 0, height: 0, depth: 0 },
{ dataSize: 1, method, success, submit }
);
});
g.test('texture_usage_must_be_valid')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine(
poptions('usage', [
GPUTextureUsage.COPY_SRC | GPUTextureUsage.SAMPLED,
GPUTextureUsage.COPY_DST | GPUTextureUsage.SAMPLED,
GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
])
)
)
.fn(async t => {
const { usage, method } = t.params;
const texture = t.device.createTexture({
size: { width: 4, height: 4, depth: 1 },
format: 'rgba8unorm',
usage,
});
const success =
method === 'CopyTextureToBuffer'
? (usage & GPUTextureUsage.COPY_SRC) !== 0
: (usage & GPUTextureUsage.COPY_DST) !== 0;
t.testRun(
{ texture },
{ bytesPerRow: 0 },
{ width: 0, height: 0, depth: 0 },
{ dataSize: 1, method, success }
);
});
g.test('sample_count_must_be_1')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine(poptions('sampleCount', [1, 4]))
)
.fn(async t => {
const { sampleCount, method } = t.params;
const texture = t.device.createTexture({
size: { width: 4, height: 4, depth: 1 },
sampleCount,
format: 'rgba8unorm',
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.SAMPLED,
});
const success = sampleCount === 1;
t.testRun(
{ texture },
{ bytesPerRow: 0 },
{ width: 0, height: 0, depth: 0 },
{ dataSize: 1, method, success }
);
});
g.test('mip_level_must_be_in_range')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine(poptions('mipLevelCount', [3, 5]))
.combine(poptions('mipLevel', [3, 4]))
)
.fn(async t => {
const { mipLevelCount, mipLevel, method } = t.params;
const texture = t.device.createTexture({
size: { width: 32, height: 32, depth: 1 },
mipLevelCount,
format: 'rgba8unorm',
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
});
const success = mipLevel < mipLevelCount;
t.testRun(
{ texture, mipLevel },
{ bytesPerRow: 0 },
{ width: 0, height: 0, depth: 0 },
{ dataSize: 1, method, success }
);
});
g.test('texel_block_alignments_on_origin')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine(poptions('coordinateToTest', ['x', 'y', 'z']))
.combine(poptions('format', kSizedTextureFormats))
.filter(formatCopyableWithMethod)
.expand(texelBlockAlignmentTestExpanderForValueToCoordinate)
)
.fn(async t => {
const { valueToCoordinate, coordinateToTest, format, method } = t.params;
const info = kSizedTextureFormatInfo[format];
const origin = { x: 0, y: 0, z: 0 };
const size = { width: 0, height: 0, depth: 0 };
let success = true;
origin[coordinateToTest] = valueToCoordinate;
switch (coordinateToTest) {
case 'x': {
success = origin.x % info.blockWidth === 0;
break;
}
case 'y': {
success = origin.y % info.blockHeight === 0;
break;
}
}
const texture = t.createAlignedTexture(format, size, origin);
t.testRun({ texture, origin }, { bytesPerRow: 0 }, size, {
dataSize: 1,
method,
success,
});
});
g.test('1d_texture')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine(poptions('width', [0, 1]))
.combine([
{ height: 1, depth: 1 },
{ height: 1, depth: 0 },
{ height: 1, depth: 2 },
{ height: 0, depth: 1 },
{ height: 2, depth: 1 },
])
)
.fn(async t => {
const { method, width, height, depth } = t.params;
const size = { width, height, depth };
const texture = t.device.createTexture({
size: { width: 2, height: 1, depth: 1 },
dimension: '1d',
format: 'rgba8unorm',
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
});
// For 1d textures we require copyHeight and copyDepth to be 1,
// copyHeight or copyDepth being 0 should cause a validation error.
const success = size.height === 1 && size.depth === 1;
t.testRun({ texture }, { bytesPerRow: 256, rowsPerImage: 4 }, size, {
dataSize: 16,
method,
success,
});
});
g.test('texel_block_alignments_on_size')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine(poptions('coordinateToTest', ['width', 'height', 'depth']))
.combine(poptions('format', kSizedTextureFormats))
.filter(formatCopyableWithMethod)
.expand(texelBlockAlignmentTestExpanderForValueToCoordinate)
)
.fn(async t => {
const { valueToCoordinate, coordinateToTest, format, method } = t.params;
const info = kSizedTextureFormatInfo[format];
const origin = { x: 0, y: 0, z: 0 };
const size = { width: 0, height: 0, depth: 0 };
let success = true;
size[coordinateToTest] = valueToCoordinate;
switch (coordinateToTest) {
case 'width': {
success = size.width % info.blockWidth === 0;
break;
}
case 'height': {
success = size.height % info.blockHeight === 0;
break;
}
}
const texture = t.createAlignedTexture(format, size, origin);
t.testRun({ texture, origin }, { bytesPerRow: 0 }, size, {
dataSize: 1,
method,
success,
});
});
g.test('texture_range_conditions')
.params(
params()
.combine(poptions('method', kAllTestMethods))
.combine(poptions('originValue', [7, 8]))
.combine(poptions('copySizeValue', [7, 8]))
.combine(poptions('textureSizeValue', [14, 15]))
.combine(poptions('mipLevel', [0, 2]))
.combine(poptions('coordinateToTest', [0, 1, 2]))
)
.fn(async t => {
const {
originValue,
copySizeValue,
textureSizeValue,
mipLevel,
coordinateToTest,
method,
} = t.params;
const origin = [0, 0, 0];
const copySize = [0, 0, 0];
const textureSize = { width: 16 << mipLevel, height: 16 << mipLevel, depth: 16 };
const success = originValue + copySizeValue <= textureSizeValue;
origin[coordinateToTest] = originValue;
copySize[coordinateToTest] = copySizeValue;
switch (coordinateToTest) {
case 0: {
textureSize.width = textureSizeValue << mipLevel;
break;
}
case 1: {
textureSize.height = textureSizeValue << mipLevel;
break;
}
case 2: {
textureSize.depth = textureSizeValue;
break;
}
}
const texture = t.device.createTexture({
size: textureSize,
mipLevelCount: 3,
format: 'rgba8unorm',
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
});
t.testRun({ texture, origin, mipLevel }, { bytesPerRow: 0 }, copySize, {
dataSize: 1,
method,
success,
});
});

View file

@ -14,10 +14,10 @@ import {
kShaderStageCombinations,
kTextureBindingTypeInfo,
kTextureComponentTypes,
kTextureFormats,
kTextureFormatInfo,
kTextureViewDimensions,
kTextureViewDimensionInfo,
kAllTextureFormats,
kAllTextureFormatInfo,
} from '../../capability_info.js';
import { ValidationTest } from './validation_test.js';
@ -83,7 +83,7 @@ g.test('bindingTypeSpecific_optional_members')
...poptions('textureComponentType', kTextureComponentTypes),
...pbool('multisampled'),
...poptions('viewDimension', kTextureViewDimensions),
...poptions('storageTextureFormat', kTextureFormats),
...poptions('storageTextureFormat', kAllTextureFormats),
])
)
.fn(t => {
@ -115,7 +115,10 @@ g.test('bindingTypeSpecific_optional_members')
if (viewDimension !== undefined && !kTextureViewDimensionInfo[viewDimension].storage) {
success = false;
}
if (storageTextureFormat !== undefined && !kTextureFormatInfo[storageTextureFormat].storage) {
if (
storageTextureFormat !== undefined &&
!kAllTextureFormatInfo[storageTextureFormat].storage
) {
success = false;
}
}

View file

@ -5,7 +5,7 @@ createTexture validation tests.
`;
import { poptions } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { kTextureFormatInfo, kTextureFormats } from '../../capability_info.js';
import { kAllTextureFormats, kAllTextureFormatInfo } from '../../capability_info.js';
import { ValidationTest } from './validation_test.js';
@ -130,10 +130,10 @@ g.test('it_is_invalid_to_submit_a_destroyed_texture_before_and_after_encode')
});
g.test('it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format')
.params(poptions('format', kTextureFormats))
.params(poptions('format', kAllTextureFormats))
.fn(async t => {
const format = t.params.format;
const info = kTextureFormatInfo[format];
const info = kAllTextureFormatInfo[format];
const descriptor = t.getDescriptor({ width: 1, height: 1, format });

View file

@ -36,7 +36,10 @@ class F extends Fixture {
super.init();
const gpu = getGPU();
const adapter = await gpu.requestAdapter();
this._device = await adapter.requestDevice();
assert(adapter !== null);
const device = await adapter.requestDevice();
assert(device !== null);
this._device = device;
}
createErrorBuffer() {

View file

@ -4,6 +4,7 @@
fences validation tests.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { assert } from '../../../common/framework/util/util.js';
import { ValidationTest } from './validation_test.js';
@ -60,6 +61,7 @@ g.test('signal_a_fence_on_a_different_device_than_it_was_created_on_is_invalid')
const fence = t.queue.createFence();
const anotherDevice = await t.device.adapter.requestDevice();
assert(anotherDevice !== null);
const anotherQueue = anotherDevice.defaultQueue;
t.expectValidationError(() => {
@ -71,6 +73,7 @@ g.test('signal_a_fence_on_a_different_device_does_not_update_fence_signaled_valu
const fence = t.queue.createFence({ initialValue: 1 });
const anotherDevice = await t.device.adapter.requestDevice();
assert(anotherDevice !== null);
const anotherQueue = anotherDevice.defaultQueue;
t.expectValidationError(() => {

View file

@ -4,15 +4,31 @@
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';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { kTextureFormatInfo, kShaderStages } from '../../../capability_info.js';
import {
kShaderStages,
kDepthStencilFormats,
kDepthStencilFormatInfo,
} from '../../../capability_info.js';
import { ValidationTest } from '../validation_test.js';
class TextureUsageTracking extends ValidationTest {
@ -40,65 +56,183 @@ class TextureUsageTracking extends ValidationTest {
export const g = makeTestGroup(TextureUsageTracking);
const READ_BASE_LEVEL = 3;
const READ_BASE_LAYER = 0;
const BASE_LEVEL = 3;
const BASE_LAYER = 0;
const TOTAL_LEVELS = 6;
const TOTAL_LAYERS = 2;
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,
},
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,
},
// 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,
},
// 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,
},
// 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,
},
])
// 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,
},
])
)
.fn(async t => {
const { writeBaseLevel, writeBaseLayer, _success } = t.params;
const {
baseLevel,
baseLayer,
levelCount,
layerCount,
type0,
type1,
_usageSuccess,
_resourceSuccess,
} = t.params;
const texture = t.createTexture({ arrayLayerCount: 2, mipLevelCount: 6 });
const texture = t.createTexture({
arrayLayerCount: TOTAL_LAYERS,
mipLevelCount: TOTAL_LEVELS,
usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.STORAGE | GPUTextureUsage.OUTPUT_ATTACHMENT,
});
const sampleView = texture.createView({
baseMipLevel: READ_BASE_LEVEL,
const view0 = texture.createView({
baseMipLevel: BASE_LEVEL,
mipLevelCount: 1,
baseArrayLayer: READ_BASE_LAYER,
baseArrayLayer: BASE_LAYER,
arrayLayerCount: 1,
});
const renderView = texture.createView({
baseMipLevel: writeBaseLevel,
mipLevelCount: 1,
baseArrayLayer: writeBaseLayer,
arrayLayerCount: 1,
const view1Dimension = layerCount !== 1 ? '2d-array' : '2d';
const view1 = texture.createView({
dimension: view1Dimension,
baseMipLevel: baseLevel,
mipLevelCount: levelCount,
baseArrayLayer: baseLayer,
arrayLayerCount: layerCount,
});
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type: 'sampled-texture' }],
});
// TODO: Add two 'render-target' usages for color attachments.
const bglEntries = [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
type: type0,
storageTextureFormat: type0 === 'sampled-texture' ? undefined : 'rgba8unorm',
},
];
const bgEntries = [{ 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',
},
@ -108,42 +242,72 @@ 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', ['depth32float', 'depth24plus', 'depth24plus-stencil8']))
.combine(poptions('readAspect', ['all', 'depth-only', 'stencil-only']))
.combine(poptions('writeAspect', ['all', 'depth-only', 'stencil-only']))
.combine(poptions('format', kDepthStencilFormats))
.combine(poptions('aspect0', ['all', 'depth-only', 'stencil-only']))
.combine(poptions('aspect1', ['all', 'depth-only', 'stencil-only']))
.unless(
({ format, readAspect, writeAspect }) =>
// TODO: Exclude depth-only aspect once WebGPU supports stencil-only texture format(s).
(readAspect === 'stencil-only' && !kTextureFormatInfo[format].stencil) ||
(writeAspect === 'stencil-only' && !kTextureFormatInfo[format].stencil)
({ format, aspect0, aspect1 }) =>
(aspect0 === 'stencil-only' && !kDepthStencilFormatInfo[format].stencil) ||
(aspect1 === 'stencil-only' && !kDepthStencilFormatInfo[format].stencil)
)
.unless(
({ 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,
},
])
)
.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 texture = t.createTexture({ format });
const view0 = texture.createView({ aspect: aspect0 });
const view1 = texture.createView({ aspect: aspect1 });
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type: 'sampled-texture' }],
});
const bglEntries = [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
type: type0,
},
];
const bgEntries = [{ 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: [
@ -154,18 +318,26 @@ g.test('readwrite_upon_aspects')
},
],
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);

View file

@ -53,15 +53,21 @@ export class ValidationTest extends GPUTest {
});
}
getErrorTexture() {
this.device.pushErrorScope('validation');
const texture = this.device.createTexture({
size: { width: 0, height: 0, depth: 0 },
format: 'rgba8unorm',
usage: GPUTextureUsage.SAMPLED,
});
this.device.popErrorScope();
return texture;
}
getErrorTextureView() {
this.device.pushErrorScope('validation');
const view = this.device
.createTexture({
size: { width: 0, height: 0, depth: 0 },
format: 'rgba8unorm',
usage: GPUTextureUsage.SAMPLED,
})
.createView();
const view = this.getErrorTexture().createView();
this.device.popErrorScope();
return view;
}

View file

@ -29,10 +29,7 @@ export const kBufferUsages = numericKeysOf(kBufferUsageInfo);
// Textures
export const kTextureFormatInfo = {
// Try to keep these manually-formatted in a readable grid.
// (Note: this list should always match the one in the spec.)
export const kRegularTextureFormatInfo = {
// 8-bit formats
r8unorm: {
renderable: true,
@ -40,7 +37,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 1,
blockWidth: 1,
blockHeight: 1,
@ -51,7 +49,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 1,
blockWidth: 1,
blockHeight: 1,
@ -62,7 +61,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 1,
blockWidth: 1,
blockHeight: 1,
@ -73,7 +73,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 1,
blockWidth: 1,
blockHeight: 1,
@ -85,7 +86,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
@ -96,7 +98,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
@ -107,7 +110,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
@ -118,7 +122,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
@ -129,7 +134,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
@ -140,7 +146,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
@ -151,7 +158,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
@ -163,7 +171,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -174,7 +183,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -185,7 +195,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -196,7 +207,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -207,7 +219,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -218,7 +231,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -229,7 +243,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -240,7 +255,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -251,7 +267,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -262,7 +279,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -273,7 +291,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -284,7 +303,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -295,7 +315,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -307,7 +328,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -318,7 +340,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
@ -330,7 +353,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 8,
blockWidth: 1,
blockHeight: 1,
@ -341,7 +365,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 8,
blockWidth: 1,
blockHeight: 1,
@ -352,7 +377,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 8,
blockWidth: 1,
blockHeight: 1,
@ -363,7 +389,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 8,
blockWidth: 1,
blockHeight: 1,
@ -374,7 +401,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 8,
blockWidth: 1,
blockHeight: 1,
@ -385,7 +413,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 8,
blockWidth: 1,
blockHeight: 1,
@ -397,7 +426,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 1,
blockHeight: 1,
@ -408,7 +438,8 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 1,
blockHeight: 1,
@ -419,30 +450,44 @@ export const kTextureFormatInfo = {
depth: false,
stencil: false,
storage: true,
copyable: true,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 1,
blockHeight: 1,
},
// Depth/stencil formats
};
export const kRegularTextureFormats = keysOf(kRegularTextureFormatInfo);
export const kSizedDepthStencilFormatInfo = {
depth32float: {
renderable: true,
color: false,
depth: true,
stencil: false,
storage: false,
copyable: true,
copySrc: true,
copyDst: false,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
};
export const kSizedDepthStencilFormats = keysOf(kSizedDepthStencilFormatInfo);
export const kUnsizedDepthStencilFormatInfo = {
depth24plus: {
renderable: true,
color: false,
depth: true,
stencil: false,
storage: false,
copyable: false,
copySrc: false,
copyDst: false,
blockWidth: 1,
blockHeight: 1,
},
'depth24plus-stencil8': {
renderable: true,
@ -450,11 +495,246 @@ export const kTextureFormatInfo = {
depth: true,
stencil: true,
storage: false,
copyable: false,
copySrc: false,
copyDst: false,
blockWidth: 1,
blockHeight: 1,
},
};
export const kTextureFormats = keysOf(kTextureFormatInfo);
export const kUnsizedDepthStencilFormats = keysOf(kUnsizedDepthStencilFormatInfo);
export const kCompressedTextureFormatInfo = {
// BC formats
'bc1-rgba-unorm': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 8,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc1-rgba-unorm-srgb': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 8,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc2-rgba-unorm': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc2-rgba-unorm-srgb': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc3-rgba-unorm': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc3-rgba-unorm-srgb': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc4-r-unorm': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 8,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc4-r-snorm': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 8,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc5-rg-unorm': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc5-rg-snorm': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc6h-rgb-ufloat': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc6h-rgb-sfloat': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc7-rgba-unorm': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
'bc7-rgba-unorm-srgb': {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copySrc: true,
copyDst: true,
bytesPerBlock: 16,
blockWidth: 4,
blockHeight: 4,
extension: 'texture-compression-bc',
},
};
export const kCompressedTextureFormats = keysOf(kCompressedTextureFormatInfo);
export const kColorTextureFormatInfo = {
...kRegularTextureFormatInfo,
...kCompressedTextureFormatInfo,
};
export const kColorTextureFormats = keysOf(kColorTextureFormatInfo);
export const kEncodableTextureFormatInfo = {
...kRegularTextureFormatInfo,
...kSizedDepthStencilFormatInfo,
};
export const kEncodableTextureFormats = keysOf(kEncodableTextureFormatInfo);
export const kSizedTextureFormatInfo = {
...kRegularTextureFormatInfo,
...kSizedDepthStencilFormatInfo,
...kCompressedTextureFormatInfo,
};
export const kSizedTextureFormats = keysOf(kSizedTextureFormatInfo);
export const kDepthStencilFormatInfo = {
...kSizedDepthStencilFormatInfo,
...kUnsizedDepthStencilFormatInfo,
};
export const kDepthStencilFormats = keysOf(kDepthStencilFormatInfo);
export const kUncompressedTextureFormatInfo = {
...kRegularTextureFormatInfo,
...kSizedDepthStencilFormatInfo,
...kUnsizedDepthStencilFormatInfo,
};
export const kUncompressedTextureFormats = keysOf(kUncompressedTextureFormatInfo);
export const kAllTextureFormatInfo = {
...kUncompressedTextureFormatInfo,
...kCompressedTextureFormatInfo,
};
export const kAllTextureFormats = keysOf(kAllTextureFormatInfo);
export const kTextureDimensionInfo = {
'1d': {},

View file

@ -223,9 +223,6 @@ got [${failedByteActualValues.join(', ')}]`;
let failed = false;
switch (filter) {
case 'none':
failed = error !== null;
break;
case 'out-of-memory':
failed = !(error instanceof GPUOutOfMemoryError);
break;

View file

@ -3,57 +3,83 @@
**/ export const description = `
Test the values of flags interfaces (e.g. GPUTextureUsage).
`;
import { poptions } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { IDLTest } from '../idl_test.js';
export const g = makeTestGroup(IDLTest);
g.test('BufferUsage').fn(t => {
const expected = {
MAP_READ: 0x0001,
MAP_WRITE: 0x0002,
COPY_SRC: 0x0004,
COPY_DST: 0x0008,
INDEX: 0x0010,
VERTEX: 0x0020,
UNIFORM: 0x0040,
STORAGE: 0x0080,
INDIRECT: 0x0100,
};
const kBufferUsageExp = {
MAP_READ: 0x0001,
MAP_WRITE: 0x0002,
COPY_SRC: 0x0004,
COPY_DST: 0x0008,
INDEX: 0x0010,
VERTEX: 0x0020,
UNIFORM: 0x0040,
STORAGE: 0x0080,
INDIRECT: 0x0100,
QUERY_RESOLVE: 0x0200,
};
t.assertMembers(GPUBufferUsage, expected);
g.test('BufferUsage,count').fn(t => {
t.assertMemberCount(GPUBufferUsage, kBufferUsageExp);
});
g.test('BufferUsage,values')
.params(poptions('key', Object.keys(kBufferUsageExp)))
.fn(t => {
const { key } = t.params;
t.assertMember(GPUBufferUsage, kBufferUsageExp, key);
});
g.test('TextureUsage').fn(t => {
const expected = {
COPY_SRC: 0x01,
COPY_DST: 0x02,
SAMPLED: 0x04,
STORAGE: 0x08,
OUTPUT_ATTACHMENT: 0x10,
};
const kTextureUsageExp = {
COPY_SRC: 0x01,
COPY_DST: 0x02,
SAMPLED: 0x04,
STORAGE: 0x08,
OUTPUT_ATTACHMENT: 0x10,
};
t.assertMembers(GPUTextureUsage, expected);
g.test('TextureUsage,count').fn(t => {
t.assertMemberCount(GPUTextureUsage, kTextureUsageExp);
});
g.test('TextureUsage,values')
.params(poptions('key', Object.keys(kTextureUsageExp)))
.fn(t => {
const { key } = t.params;
t.assertMember(GPUTextureUsage, kTextureUsageExp, key);
});
g.test('ColorWrite').fn(t => {
const expected = {
RED: 0x1,
GREEN: 0x2,
BLUE: 0x4,
ALPHA: 0x8,
ALL: 0xf,
};
const kColorWriteExp = {
RED: 0x1,
GREEN: 0x2,
BLUE: 0x4,
ALPHA: 0x8,
ALL: 0xf,
};
t.assertMembers(GPUColorWrite, expected);
g.test('ColorWrite,count').fn(t => {
t.assertMemberCount(GPUColorWrite, kColorWriteExp);
});
g.test('ColorWrite,values')
.params(poptions('key', Object.keys(kColorWriteExp)))
.fn(t => {
const { key } = t.params;
t.assertMember(GPUColorWrite, kColorWriteExp, key);
});
g.test('ShaderStage').fn(t => {
const expected = {
VERTEX: 0x1,
FRAGMENT: 0x2,
COMPUTE: 0x4,
};
const kShaderStageExp = {
VERTEX: 0x1,
FRAGMENT: 0x2,
COMPUTE: 0x4,
};
t.assertMembers(GPUShaderStage, expected);
g.test('ShaderStage,count').fn(t => {
t.assertMemberCount(GPUShaderStage, kShaderStageExp);
});
g.test('ShaderStage,values')
.params(poptions('key', Object.keys(kShaderStageExp)))
.fn(t => {
const { key } = t.params;
t.assertMember(GPUShaderStage, kShaderStageExp, key);
});

View file

@ -4,18 +4,24 @@
import { assert } from '../../common/framework/util/util.js';
export class IDLTest extends Fixture {
// TODO: add a helper to check prototype chains
/**
* Asserts that an IDL interface has the expected members.
* Asserts that a member of an IDL interface has the expected value.
*/
// TODO: exp should allow sentinel markers for unnameable values, such as methods and attributes
// TODO: handle extensions
// TODO: check prototype chains (maybe as separate method)
assertMembers(act, exp) {
assertMember(act, exp, key) {
assert(key in act, () => `Expected key ${key} missing`);
assert(act[key] === exp[key], () => `Value of [${key}] was ${act[key]}, expected ${exp[key]}`);
}
/**
* Asserts that an IDL interface has the same number of keys as the
*
* TODO: add a way to check for the types of keys with unknown values, like methods and attributes
* TODO: handle extensions
*/
assertMemberCount(act, exp) {
const expKeys = Object.keys(exp);
for (const k of expKeys) {
assert(k in act, () => `Expected key ${k} missing`);
assert(act[k] === exp[k], () => `Value of [${k}] was ${act[k]}, expected ${exp[k]}`);
}
const actKeys = Object.keys(act);
assert(
actKeys.length === expKeys.length,

View file

@ -121,6 +121,40 @@ export const listing = [
],
"readme": "Positive and negative tests for all the validation rules of the API."
},
{
"file": [
"api",
"validation",
"copyBufferToBuffer"
],
"description": "copyBufferToBuffer tests.\n\nTest Plan:\n* Buffer is valid/invalid\n - the source buffer is invalid\n - the destination buffer is invalid\n* Buffer usages\n - the source buffer is created without GPUBufferUsage::COPY_SRC\n - the destination buffer is created without GPUBufferUsage::COPY_DEST\n* CopySize\n - copySize is not a multiple of 4\n - copySize is 0\n* copy offsets\n - sourceOffset is not a multiple of 4\n - destinationOffset is not a multiple of 4\n* Arthimetic overflow\n - (sourceOffset + copySize) is overflow\n - (destinationOffset + copySize) is overflow\n* Out of bounds\n - (sourceOffset + copySize) > size of source buffer\n - (destinationOffset + copySize) > size of destination buffer\n* Source buffer and destination buffer are the same buffer"
},
{
"file": [
"api",
"validation",
"copy_between_linear_data_and_texture"
],
"readme": "writeTexture + copyBufferToTexture + copyTextureToBuffer validation tests.\n\nTest coverage:\n* resource usages:\n\t- texture_usage_must_be_valid: for GPUTextureUsage::COPY_SRC, GPUTextureUsage::COPY_DST flags.\n\t- TODO: buffer_usage_must_be_valid\n\n* textureCopyView:\n\t- texture_must_be_valid: for valid, destroyed, error textures.\n\t- sample_count_must_be_1: for sample count 1 and 4.\n\t- mip_level_must_be_in_range: for various combinations of mipLevel and mipLevelCount.\n\t- texel_block_alignment_on_origin: for all formats and coordinates.\n\n* bufferCopyView:\n\t- TODO: buffer_must_be_valid\n\t- TODO: bytes_per_row_alignment\n\n* linear texture data:\n\t- bound_on_rows_per_image: for various combinations of copyDepth (1, >1), copyHeight, rowsPerImage.\n\t- offset_plus_required_bytes_in_copy_overflow\n\t- required_bytes_in_copy: testing minimal data size and data size too small for various combinations of bytesPerRow, rowsPerImage, copyExtent and offset. for the copy method, bytesPerRow is computed as bytesInACompleteRow aligned to be a multiple of 256 + bytesPerRowPadding * 256.\n\t- texel_block_alignment_on_rows_per_image: for all formats.\n\t- texel_block_alignment_on_offset: for all formats.\n\t- bound_on_bytes_per_row: for all formats and various combinations of bytesPerRow and copyExtent. for writeTexture, bytesPerRow is computed as (blocksPerRow * blockWidth * bytesPerBlock + additionalBytesPerRow) and copyExtent.width is computed as copyWidthInBlocks * blockWidth. for the copy methods, both values are mutliplied by 256.\n\t- bound_on_offset: for various combinations of offset and dataSize.\n\n* texture copy range:\n\t- 1d_texture: copyExtent.height isn't 1, copyExtent.depth isn't 1.\n\t- texel_block_alignment_on_size: for all formats and coordinates.\n\t- texture_range_conditons: for all coordinate and various combinations of origin, copyExtent, textureSize and mipLevel.\n\nTODO: more test coverage for 1D and 3D textures."
},
{
"file": [
"api",
"validation",
"copy_between_linear_data_and_texture",
"copyBetweenLinearDataAndTexture_dataRelated"
],
"description": ""
},
{
"file": [
"api",
"validation",
"copy_between_linear_data_and_texture",
"copyBetweenLinearDataAndTexture_textureRelated"
],
"description": ""
},
{
"file": [
"api",
@ -218,7 +252,7 @@ export const listing = [
"resource_usages",
"textureUsageInRender"
],
"description": "Texture Usages Validation Tests in Render Pass.\n\nTest Coverage:\n - Tests that read and write usages upon the same texture subresource, or different subresources\n of the same texture. Different subresources of the same texture includes different mip levels,\n different array layers, and different aspects.\n - When read and write usages are binding to the same texture subresource, an error should be\n generated. Otherwise, no error should be generated."
"description": "Texture Usages Validation Tests in Render Pass.\n\nTest Coverage:\n\n - For each combination of two texture usages:\n - For various subresource ranges (different mip levels or array layers) that overlap a given\n subresources or not for color formats:\n - Check that an error is generated when read-write or write-write usages are binding to the\n same texture subresource. Otherwise, no error should be generated. One exception is race\n condition upon two writeonly-storage-texture usages, which is valid.\n\n - For each combination of two texture usages:\n - For various aspects (all, depth-only, stencil-only) that overlap a given subresources or not\n for depth/stencil formats:\n - Check that an error is generated when read-write or write-write usages are binding to the\n same aspect. Otherwise, no error should be generated.\n\n - Test combinations of two shader stages:\n - Texture usages in bindings with invisible shader stages should be tracked. Invisible shader\n stages include shader stage with visibility none and compute shader stage in render pass."
},
{
"file": [

View file

@ -7,3 +7,5 @@
export function isAligned(n, alignment) {
return n === align(n, alignment);
}
export const kMaxSafeMultipleOf8 = Number.MAX_SAFE_INTEGER - 7;

View file

@ -1,7 +1,7 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { assert, unreachable } from '../../../common/framework/util/util.js';
import { kTextureFormatInfo } from '../../capability_info.js';
import { kSizedTextureFormatInfo } from '../../capability_info.js';
import { align, isAligned } from '../math.js';
export const kBytesPerRowAlignment = 256;
@ -30,8 +30,7 @@ export function getTextureCopyLayout(format, dimension, size, options = kDefault
const mipSize = getMipSizePassthroughLayers(dimension, size, mipLevel);
const { blockWidth, blockHeight, bytesPerBlock } = kTextureFormatInfo[format];
assert(!!bytesPerBlock && !!blockWidth && !!blockHeight);
const { blockWidth, blockHeight, bytesPerBlock } = kSizedTextureFormatInfo[format];
assert(isAligned(mipSize[0], blockWidth));
const minBytesPerRow = (mipSize[0] / blockWidth) * bytesPerBlock;
@ -73,8 +72,7 @@ export function fillTextureDataWithTexelValue(
size,
options = kDefaultLayoutOptions
) {
const { blockWidth, blockHeight, bytesPerBlock } = kTextureFormatInfo[format];
assert(!!bytesPerBlock && !!blockWidth && !!blockHeight);
const { blockWidth, blockHeight, bytesPerBlock } = kSizedTextureFormatInfo[format];
assert(bytesPerBlock === texelValue.byteLength);
const { byteLength, rowsPerImage, bytesPerRow } = getTextureCopyLayout(

View file

@ -14,7 +14,7 @@
return obj;
}
import { assert, unreachable } from '../../../common/framework/util/util.js';
import { kTextureFormatInfo } from '../../capability_info.js';
import { kUncompressedTextureFormatInfo } from '../../capability_info.js';
import {
assertInIntegerRange,
float32ToFloatBits,
@ -333,7 +333,7 @@ class TexelDataRepresentationImpl {
];
}
const bytesPerBlock = kTextureFormatInfo[this.format].bytesPerBlock;
const bytesPerBlock = kUncompressedTextureFormatInfo[this.format].bytesPerBlock;
assert(!!bytesPerBlock);
const data = new ArrayBuffer(bytesPerBlock);

View file

@ -6,7 +6,7 @@ copy imageBitmap To texture tests.
import { poptions, params } from '../../common/framework/params_builder.js';
import { makeTestGroup } from '../../common/framework/test_group.js';
import { unreachable } from '../../common/framework/util/util.js';
import { kTextureFormatInfo } from '../capability_info.js';
import { kUncompressedTextureFormatInfo } from '../capability_info.js';
import { GPUTest } from '../gpu_test.js';
import { getTexelDataRepresentation } from '../util/texture/texelData.js';
@ -220,13 +220,14 @@ g.test('from_ImageData')
.fn(async t => {
const { width, height, alpha, orientation, dstColorFormat } = t.params;
const srcBytesPerPixel = kTextureFormatInfo['rgba8unorm'].bytesPerBlock;
const format = 'rgba8unorm';
const srcBytesPerPixel = kUncompressedTextureFormatInfo[format].bytesPerBlock;
// Generate input contents by iterating 'Color' enum
const imagePixels = new Uint8ClampedArray(srcBytesPerPixel * width * height);
const startPixel = Color.Red;
for (let i = 0, currentPixel = startPixel; i < width * height; ++i) {
const pixels = t.generatePixel(currentPixel, 'rgba8unorm');
const pixels = t.generatePixel(currentPixel, format);
if (currentPixel === Color.TransparentBlack) {
currentPixel = Color.Red;
} else {
@ -257,7 +258,7 @@ g.test('from_ImageData')
});
// Construct expected value for different dst color format
const dstBytesPerPixel = kTextureFormatInfo[dstColorFormat].bytesPerBlock;
const dstBytesPerPixel = kUncompressedTextureFormatInfo[dstColorFormat].bytesPerBlock;
const dstPixels = new Uint8ClampedArray(dstBytesPerPixel * width * height);
let expectedPixels = new Uint8ClampedArray(dstBytesPerPixel * width * height);
for (let i = 0, currentPixel = startPixel; i < width * height; ++i) {

View file

@ -9,7 +9,9 @@ export async function runRefTest(fn) {
);
const adapter = await navigator.gpu.requestAdapter();
assert(adapter !== null);
const device = await adapter.requestDevice();
assert(device !== null);
const queue = device.defaultQueue;
await fn({ device, queue });