buffer_view CTS (#4652)
* Validation of buffer type and related builtin functions * Execution tests for buffer_view
This commit is contained in:
parent
70c1848940
commit
af9a578195
19 changed files with 5450 additions and 113 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -11,6 +11,9 @@
|
|||
*.tmp.txt
|
||||
/docs/tsdoc/
|
||||
|
||||
# vim swap files
|
||||
*.swp*
|
||||
|
||||
# `pnpm` lockfile
|
||||
pnpm-lock.yaml
|
||||
|
||||
|
|
|
|||
|
|
@ -373,6 +373,7 @@ interface TypedArrayMap {
|
|||
Uint8ClampedArray: Uint8ClampedArray;
|
||||
Int32Array: Int32Array;
|
||||
Uint32Array: Uint32Array;
|
||||
Float16Array: Float16Array;
|
||||
Float32Array: Float32Array;
|
||||
Float64Array: Float64Array;
|
||||
BigInt64Array: BigInt64Array;
|
||||
|
|
|
|||
220
src/resources/cache/hashes.json
generated
vendored
220
src/resources/cache/hashes.json
generated
vendored
|
|
@ -1,112 +1,112 @@
|
|||
{
|
||||
"webgpu/shader/execution/binary/af_addition.bin": "6e41f81e",
|
||||
"webgpu/shader/execution/binary/af_logical.bin": "254d66ec",
|
||||
"webgpu/shader/execution/binary/af_division.bin": "e15d545b",
|
||||
"webgpu/shader/execution/binary/af_matrix_addition.bin": "4bfc497f",
|
||||
"webgpu/shader/execution/binary/af_matrix_subtraction.bin": "2bbdd5e6",
|
||||
"webgpu/shader/execution/binary/af_multiplication.bin": "23c7c8ad",
|
||||
"webgpu/shader/execution/binary/af_remainder.bin": "9b84ecb1",
|
||||
"webgpu/shader/execution/binary/af_subtraction.bin": "bd4b2ec9",
|
||||
"webgpu/shader/execution/binary/f16_addition.bin": "5fe1fb4e",
|
||||
"webgpu/shader/execution/binary/f16_logical.bin": "225d12ee",
|
||||
"webgpu/shader/execution/binary/f16_division.bin": "dffabf5f",
|
||||
"webgpu/shader/execution/binary/f16_matrix_addition.bin": "acc779ee",
|
||||
"webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.bin": "dcfbfff9",
|
||||
"webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.bin": "4b73a544",
|
||||
"webgpu/shader/execution/binary/f16_matrix_subtraction.bin": "bfe9a25d",
|
||||
"webgpu/shader/execution/binary/f16_matrix_vector_multiplication.bin": "a2222aec",
|
||||
"webgpu/shader/execution/binary/f16_multiplication.bin": "5d5265c9",
|
||||
"webgpu/shader/execution/binary/f16_remainder.bin": "5284a25",
|
||||
"webgpu/shader/execution/binary/f16_subtraction.bin": "b98fc99f",
|
||||
"webgpu/shader/execution/binary/f32_addition.bin": "13d7f835",
|
||||
"webgpu/shader/execution/binary/f32_logical.bin": "c1f1b636",
|
||||
"webgpu/shader/execution/binary/f32_division.bin": "80fee721",
|
||||
"webgpu/shader/execution/binary/f32_matrix_addition.bin": "a0d20c36",
|
||||
"webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.bin": "3cabc829",
|
||||
"webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.bin": "aed8659",
|
||||
"webgpu/shader/execution/binary/f32_matrix_subtraction.bin": "6063545d",
|
||||
"webgpu/shader/execution/binary/f32_matrix_vector_multiplication.bin": "e36324e4",
|
||||
"webgpu/shader/execution/binary/f32_multiplication.bin": "40527d93",
|
||||
"webgpu/shader/execution/binary/f32_remainder.bin": "d8738967",
|
||||
"webgpu/shader/execution/binary/f32_subtraction.bin": "f5453a8",
|
||||
"webgpu/shader/execution/binary/i32_arithmetic.bin": "7fb5b26a",
|
||||
"webgpu/shader/execution/binary/i32_comparison.bin": "9679ab05",
|
||||
"webgpu/shader/execution/binary/u32_arithmetic.bin": "c9fe0078",
|
||||
"webgpu/shader/execution/binary/u32_comparison.bin": "7d0ba49",
|
||||
"webgpu/shader/execution/abs.bin": "320b5a02",
|
||||
"webgpu/shader/execution/acos.bin": "bb357762",
|
||||
"webgpu/shader/execution/acosh.bin": "1975ce7f",
|
||||
"webgpu/shader/execution/asin.bin": "74096576",
|
||||
"webgpu/shader/execution/asinh.bin": "cdb5dae3",
|
||||
"webgpu/shader/execution/atan.bin": "4d8cd242",
|
||||
"webgpu/shader/execution/atan2.bin": "213c0112",
|
||||
"webgpu/shader/execution/atanh.bin": "ca3cda5b",
|
||||
"webgpu/shader/execution/bitcast.bin": "2b639d22",
|
||||
"webgpu/shader/execution/ceil.bin": "9d91caf6",
|
||||
"webgpu/shader/execution/clamp.bin": "d7c7512f",
|
||||
"webgpu/shader/execution/cos.bin": "832af30d",
|
||||
"webgpu/shader/execution/cosh.bin": "1e9d346f",
|
||||
"webgpu/shader/execution/cross.bin": "8db02194",
|
||||
"webgpu/shader/execution/degrees.bin": "4e6926ca",
|
||||
"webgpu/shader/execution/determinant.bin": "46c05eb0",
|
||||
"webgpu/shader/execution/distance.bin": "d3d25ee0",
|
||||
"webgpu/shader/execution/dot.bin": "aac0064d",
|
||||
"webgpu/shader/execution/exp.bin": "4edc9995",
|
||||
"webgpu/shader/execution/exp2.bin": "71f9b9f1",
|
||||
"webgpu/shader/execution/faceForward.bin": "bbfa6f58",
|
||||
"webgpu/shader/execution/floor.bin": "46a8f6f6",
|
||||
"webgpu/shader/execution/fma.bin": "d8de76da",
|
||||
"webgpu/shader/execution/fract.bin": "ce59af66",
|
||||
"webgpu/shader/execution/frexp.bin": "2d856679",
|
||||
"webgpu/shader/execution/inverseSqrt.bin": "c2e19ec5",
|
||||
"webgpu/shader/execution/ldexp.bin": "85a3d6da",
|
||||
"webgpu/shader/execution/length.bin": "6462de73",
|
||||
"webgpu/shader/execution/log.bin": "9d3e5eac",
|
||||
"webgpu/shader/execution/log2.bin": "f0e6911",
|
||||
"webgpu/shader/execution/max.bin": "10d3b0f0",
|
||||
"webgpu/shader/execution/min.bin": "8150d732",
|
||||
"webgpu/shader/execution/mix.bin": "e80a07f6",
|
||||
"webgpu/shader/execution/modf.bin": "4ec56dfa",
|
||||
"webgpu/shader/execution/normalize.bin": "10f90ae0",
|
||||
"webgpu/shader/execution/pack2x16float.bin": "f238e86f",
|
||||
"webgpu/shader/execution/pow.bin": "342665d2",
|
||||
"webgpu/shader/execution/quantizeToF16.bin": "e2736816",
|
||||
"webgpu/shader/execution/radians.bin": "6520dcc1",
|
||||
"webgpu/shader/execution/reflect.bin": "4183175",
|
||||
"webgpu/shader/execution/refract.bin": "4915dbfe",
|
||||
"webgpu/shader/execution/round.bin": "99b75791",
|
||||
"webgpu/shader/execution/saturate.bin": "a14f35f0",
|
||||
"webgpu/shader/execution/sign.bin": "36761bf9",
|
||||
"webgpu/shader/execution/sin.bin": "74a2057b",
|
||||
"webgpu/shader/execution/sinh.bin": "8353f6ea",
|
||||
"webgpu/shader/execution/smoothstep.bin": "8122e6fb",
|
||||
"webgpu/shader/execution/sqrt.bin": "4b04e554",
|
||||
"webgpu/shader/execution/step.bin": "11ec30ed",
|
||||
"webgpu/shader/execution/tan.bin": "67da16db",
|
||||
"webgpu/shader/execution/tanh.bin": "2f62fb36",
|
||||
"webgpu/shader/execution/transpose.bin": "29a099da",
|
||||
"webgpu/shader/execution/trunc.bin": "ccf08bdc",
|
||||
"webgpu/shader/execution/unpack2x16float.bin": "7bfbbdf6",
|
||||
"webgpu/shader/execution/unpack2x16snorm.bin": "bed6a1e7",
|
||||
"webgpu/shader/execution/unpack2x16unorm.bin": "a26a06f0",
|
||||
"webgpu/shader/execution/unpack4x8snorm.bin": "756c46a0",
|
||||
"webgpu/shader/execution/unpack4x8unorm.bin": "575c7feb",
|
||||
"webgpu/shader/execution/unary/af_arithmetic.bin": "1176909d",
|
||||
"webgpu/shader/execution/unary/af_assignment.bin": "73927f22",
|
||||
"webgpu/shader/execution/unary/bool_conversion.bin": "e96f1762",
|
||||
"webgpu/shader/execution/unary/f16_arithmetic.bin": "e77cc4e8",
|
||||
"webgpu/shader/execution/unary/f16_conversion.bin": "4555bbc2",
|
||||
"webgpu/shader/execution/unary/f32_arithmetic.bin": "8e28dc17",
|
||||
"webgpu/shader/execution/unary/f32_conversion.bin": "1d219",
|
||||
"webgpu/shader/execution/unary/i32_arithmetic.bin": "ca6a70d",
|
||||
"webgpu/shader/execution/unary/i32_conversion.bin": "cf99c846",
|
||||
"webgpu/shader/execution/unary/u32_conversion.bin": "7f09c0ce",
|
||||
"webgpu/shader/execution/unary/ai_assignment.bin": "98de2fcc",
|
||||
"webgpu/shader/execution/binary/ai_arithmetic.bin": "6b88d962",
|
||||
"webgpu/shader/execution/unary/ai_arithmetic.bin": "e584bcb7",
|
||||
"webgpu/shader/execution/binary/af_matrix_matrix_multiplication.bin": "c82b9644",
|
||||
"webgpu/shader/execution/binary/af_matrix_scalar_multiplication.bin": "7ea675b6",
|
||||
"webgpu/shader/execution/binary/af_matrix_vector_multiplication.bin": "28f11a02",
|
||||
"webgpu/shader/execution/derivatives.bin": "93c7bf53",
|
||||
"webgpu/shader/execution/fwidth.bin": "e9668101"
|
||||
"webgpu/shader/execution/binary/af_addition.bin": "aa14ec26",
|
||||
"webgpu/shader/execution/binary/af_logical.bin": "8890f3f1",
|
||||
"webgpu/shader/execution/binary/af_division.bin": "ad842076",
|
||||
"webgpu/shader/execution/binary/af_matrix_addition.bin": "c6d8db63",
|
||||
"webgpu/shader/execution/binary/af_matrix_subtraction.bin": "c09b18cc",
|
||||
"webgpu/shader/execution/binary/af_multiplication.bin": "f0d2c49c",
|
||||
"webgpu/shader/execution/binary/af_remainder.bin": "781a9ff6",
|
||||
"webgpu/shader/execution/binary/af_subtraction.bin": "2cf4b54a",
|
||||
"webgpu/shader/execution/binary/f16_addition.bin": "3efff4fc",
|
||||
"webgpu/shader/execution/binary/f16_logical.bin": "de9424e7",
|
||||
"webgpu/shader/execution/binary/f16_division.bin": "7cd144a1",
|
||||
"webgpu/shader/execution/binary/f16_matrix_addition.bin": "3b986de1",
|
||||
"webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.bin": "4e135781",
|
||||
"webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.bin": "4c21775c",
|
||||
"webgpu/shader/execution/binary/f16_matrix_subtraction.bin": "47e76662",
|
||||
"webgpu/shader/execution/binary/f16_matrix_vector_multiplication.bin": "2b557015",
|
||||
"webgpu/shader/execution/binary/f16_multiplication.bin": "6a3e21d7",
|
||||
"webgpu/shader/execution/binary/f16_remainder.bin": "70c9358c",
|
||||
"webgpu/shader/execution/binary/f16_subtraction.bin": "4563dd62",
|
||||
"webgpu/shader/execution/binary/f32_addition.bin": "1f35a4dd",
|
||||
"webgpu/shader/execution/binary/f32_logical.bin": "723abe00",
|
||||
"webgpu/shader/execution/binary/f32_division.bin": "c95c68a",
|
||||
"webgpu/shader/execution/binary/f32_matrix_addition.bin": "75957d7a",
|
||||
"webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.bin": "baf57d3c",
|
||||
"webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.bin": "cdbe1ae4",
|
||||
"webgpu/shader/execution/binary/f32_matrix_subtraction.bin": "d9e7057",
|
||||
"webgpu/shader/execution/binary/f32_matrix_vector_multiplication.bin": "634f59e6",
|
||||
"webgpu/shader/execution/binary/f32_multiplication.bin": "a1b88039",
|
||||
"webgpu/shader/execution/binary/f32_remainder.bin": "51048d79",
|
||||
"webgpu/shader/execution/binary/f32_subtraction.bin": "aa74d81b",
|
||||
"webgpu/shader/execution/binary/i32_arithmetic.bin": "78c2f699",
|
||||
"webgpu/shader/execution/binary/i32_comparison.bin": "5c84159e",
|
||||
"webgpu/shader/execution/binary/u32_arithmetic.bin": "99c3cdcf",
|
||||
"webgpu/shader/execution/binary/u32_comparison.bin": "ee2050d9",
|
||||
"webgpu/shader/execution/abs.bin": "d3a0c673",
|
||||
"webgpu/shader/execution/acos.bin": "c6f18083",
|
||||
"webgpu/shader/execution/acosh.bin": "2499b5b7",
|
||||
"webgpu/shader/execution/asin.bin": "6b98a030",
|
||||
"webgpu/shader/execution/asinh.bin": "129f4a25",
|
||||
"webgpu/shader/execution/atan.bin": "6deed949",
|
||||
"webgpu/shader/execution/atan2.bin": "395d1e72",
|
||||
"webgpu/shader/execution/atanh.bin": "1a0f331a",
|
||||
"webgpu/shader/execution/bitcast.bin": "ca478e97",
|
||||
"webgpu/shader/execution/ceil.bin": "a002b286",
|
||||
"webgpu/shader/execution/clamp.bin": "664e4e71",
|
||||
"webgpu/shader/execution/cos.bin": "995ba625",
|
||||
"webgpu/shader/execution/cosh.bin": "860e0f00",
|
||||
"webgpu/shader/execution/cross.bin": "706065a0",
|
||||
"webgpu/shader/execution/degrees.bin": "bacef53d",
|
||||
"webgpu/shader/execution/determinant.bin": "a5fa820f",
|
||||
"webgpu/shader/execution/distance.bin": "ae309025",
|
||||
"webgpu/shader/execution/dot.bin": "6fe2eaad",
|
||||
"webgpu/shader/execution/exp.bin": "16c78e4a",
|
||||
"webgpu/shader/execution/exp2.bin": "aa3bd37a",
|
||||
"webgpu/shader/execution/faceForward.bin": "6ec092ba",
|
||||
"webgpu/shader/execution/floor.bin": "6c8c8506",
|
||||
"webgpu/shader/execution/fma.bin": "d0240050",
|
||||
"webgpu/shader/execution/fract.bin": "eedd46ad",
|
||||
"webgpu/shader/execution/frexp.bin": "4f86736f",
|
||||
"webgpu/shader/execution/inverseSqrt.bin": "c472a6d",
|
||||
"webgpu/shader/execution/ldexp.bin": "f79e5c4c",
|
||||
"webgpu/shader/execution/length.bin": "61c438dc",
|
||||
"webgpu/shader/execution/log.bin": "602e67ab",
|
||||
"webgpu/shader/execution/log2.bin": "59531d51",
|
||||
"webgpu/shader/execution/max.bin": "315ad00",
|
||||
"webgpu/shader/execution/min.bin": "5e67b47a",
|
||||
"webgpu/shader/execution/mix.bin": "7b9c30fb",
|
||||
"webgpu/shader/execution/modf.bin": "a926d2ab",
|
||||
"webgpu/shader/execution/normalize.bin": "7a0f4894",
|
||||
"webgpu/shader/execution/pack2x16float.bin": "5bda6ee2",
|
||||
"webgpu/shader/execution/pow.bin": "d860633f",
|
||||
"webgpu/shader/execution/quantizeToF16.bin": "e2cc5f7c",
|
||||
"webgpu/shader/execution/radians.bin": "a564cae8",
|
||||
"webgpu/shader/execution/reflect.bin": "ee6dda3f",
|
||||
"webgpu/shader/execution/refract.bin": "769439d5",
|
||||
"webgpu/shader/execution/round.bin": "e5c69d73",
|
||||
"webgpu/shader/execution/saturate.bin": "a2baca59",
|
||||
"webgpu/shader/execution/sign.bin": "c3031854",
|
||||
"webgpu/shader/execution/sin.bin": "32cdc685",
|
||||
"webgpu/shader/execution/sinh.bin": "807d1b08",
|
||||
"webgpu/shader/execution/smoothstep.bin": "643c83a",
|
||||
"webgpu/shader/execution/sqrt.bin": "ac7e5381",
|
||||
"webgpu/shader/execution/step.bin": "ac99a50e",
|
||||
"webgpu/shader/execution/tan.bin": "5efc9f6f",
|
||||
"webgpu/shader/execution/tanh.bin": "8385aa65",
|
||||
"webgpu/shader/execution/transpose.bin": "f3685a2e",
|
||||
"webgpu/shader/execution/trunc.bin": "7eb127a7",
|
||||
"webgpu/shader/execution/unpack2x16float.bin": "4eef663c",
|
||||
"webgpu/shader/execution/unpack2x16snorm.bin": "c48522c4",
|
||||
"webgpu/shader/execution/unpack2x16unorm.bin": "2af3269",
|
||||
"webgpu/shader/execution/unpack4x8snorm.bin": "d4dbf395",
|
||||
"webgpu/shader/execution/unpack4x8unorm.bin": "4c534802",
|
||||
"webgpu/shader/execution/unary/af_arithmetic.bin": "1fde55fa",
|
||||
"webgpu/shader/execution/unary/af_assignment.bin": "38273056",
|
||||
"webgpu/shader/execution/unary/bool_conversion.bin": "3a8b1d94",
|
||||
"webgpu/shader/execution/unary/f16_arithmetic.bin": "12ceb8ec",
|
||||
"webgpu/shader/execution/unary/f16_conversion.bin": "585809f2",
|
||||
"webgpu/shader/execution/unary/f32_arithmetic.bin": "8ebcea6b",
|
||||
"webgpu/shader/execution/unary/f32_conversion.bin": "d6681b46",
|
||||
"webgpu/shader/execution/unary/i32_arithmetic.bin": "b2cc8056",
|
||||
"webgpu/shader/execution/unary/i32_conversion.bin": "bd484400",
|
||||
"webgpu/shader/execution/unary/u32_conversion.bin": "82e99fb5",
|
||||
"webgpu/shader/execution/unary/ai_assignment.bin": "c2c1d4e1",
|
||||
"webgpu/shader/execution/binary/ai_arithmetic.bin": "b01bab0a",
|
||||
"webgpu/shader/execution/unary/ai_arithmetic.bin": "dd916b48",
|
||||
"webgpu/shader/execution/binary/af_matrix_matrix_multiplication.bin": "ad511107",
|
||||
"webgpu/shader/execution/binary/af_matrix_scalar_multiplication.bin": "b54158a8",
|
||||
"webgpu/shader/execution/binary/af_matrix_vector_multiplication.bin": "32c18616",
|
||||
"webgpu/shader/execution/derivatives.bin": "a7a6547c",
|
||||
"webgpu/shader/execution/fwidth.bin": "a4db2b2c"
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ Compute dispatch validation tests.
|
|||
|
||||
import { AllFeaturesMaxLimitsGPUTest } from '../.././gpu_test.js';
|
||||
import { makeTestGroup } from '../../../common/framework/test_group.js';
|
||||
import { keysOf } from '../../../common/util/data_tables.js';
|
||||
import { WGSLLanguageFeature } from '../../capability_info.js';
|
||||
|
||||
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
|
||||
|
||||
|
|
@ -141,3 +143,428 @@ fn main(@builtin(${t.params.builtin}) input : u32,
|
|||
const expected = t.params.size === 'max' ? 0 : kMagic;
|
||||
t.expectGPUBufferValuesEqual(outputBuffer, new Uint32Array([expected]));
|
||||
});
|
||||
|
||||
interface RequiredSizeCase {
|
||||
code: string;
|
||||
size: number; // Size must be greater than 4
|
||||
binding_type: GPUBufferBindingType;
|
||||
requires?: WGSLLanguageFeature[];
|
||||
}
|
||||
|
||||
const kRequiredSizeCases: Record<string, RequiredSizeCase> = {
|
||||
ro_storage_32bytes: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : array<vec4u, 2>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = v;
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'read-only-storage',
|
||||
},
|
||||
storage_32bytes: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : array<vec4u, 2>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = v;
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'storage',
|
||||
},
|
||||
uniform_32bytes: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<uniform> v : array<vec4u, 2>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = v;
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'uniform',
|
||||
},
|
||||
ro_storage_sized_buffer: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer<32>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = &v;
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_sized_buffer: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer<32>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = &v;
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
uniform_sized_buffer: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<uniform> v : buffer<32>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = &v;
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'uniform',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
ro_storage_unsized_buffer_bufferView1: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<array<u32, 16>>(&v, 0);
|
||||
}`,
|
||||
size: 16 * 4,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
ro_storage_unsized_buffer_bufferView2: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<array<u32, 16>>(&v, 56);
|
||||
}`,
|
||||
// Offset does not count toward min binding size.
|
||||
size: 16 * 4,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView3: {
|
||||
code: `
|
||||
struct S {
|
||||
a: vec4u,
|
||||
b: vec2u,
|
||||
}
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let x = 16u;
|
||||
let p = bufferView<S>(&v, x);
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView4: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<array<vec2u>>(&v, 0);
|
||||
}`,
|
||||
size: 8,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView5: {
|
||||
code: `
|
||||
struct S {
|
||||
a: array<u32, 4>
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<vec2u>(&v, 0);
|
||||
let q = bufferView<S>(&v, 0);
|
||||
}`,
|
||||
size: 16,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView6: {
|
||||
code: `
|
||||
struct S {
|
||||
a: array<u32, 4>
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<u32>(&v, 16);
|
||||
let q = bufferView<S>(&v, 0);
|
||||
}`,
|
||||
// Offset does not count toward min binding size.
|
||||
size: 16,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView7: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<vec2i>(&v, 16);
|
||||
}
|
||||
@fragment
|
||||
fn main2() {
|
||||
let q = bufferView<vec4u>(&v, 32);
|
||||
}`,
|
||||
// Offset does not count toward min binding size.
|
||||
size: 8,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView8: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<vec2f>(&v, 16);
|
||||
}
|
||||
fn foo() {
|
||||
let q = bufferView<vec4u>(&v, 32);
|
||||
}`,
|
||||
// Offset does not count toward min binding size.
|
||||
size: 8,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView9: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<u32>(&v, 16);
|
||||
foo(&v);
|
||||
}
|
||||
fn foo(p : ptr<storage, buffer, read_write>) {
|
||||
let q = bufferView<vec4u>(&v, 32);
|
||||
}`,
|
||||
// Offset does not count toward min binding size.
|
||||
size: 16,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view', 'unrestricted_pointer_parameters'],
|
||||
},
|
||||
ro_storage_unsized_buffer_bufferArrayView1: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferArrayView<array<vec2u>>(&v, 0, 32);
|
||||
}`,
|
||||
// Size does not count toward min binding size.
|
||||
size: 8,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
ro_storage_unsized_buffer_bufferArrayView2: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferArrayView<array<vec2f>>(&v, 16, 32);
|
||||
}`,
|
||||
// Offset and size do not count toward min binding size.
|
||||
size: 8,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
ro_storage_unsized_buffer_bufferArrayView3: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let o = 16;
|
||||
let s = 32;
|
||||
let p = bufferArrayView<array<vec2u>>(&v, o, s);
|
||||
}`,
|
||||
size: 8,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
ro_storage_unsized_buffer_bufferArrayView4: {
|
||||
code: `
|
||||
struct S {
|
||||
a: vec4u,
|
||||
b: u32,
|
||||
}
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let o = 16;
|
||||
let s = 32;
|
||||
let p = bufferArrayView<array<S>>(&v, o, s);
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferArrayView5: {
|
||||
code: `
|
||||
struct S {
|
||||
a : vec4f,
|
||||
b : vec4f,
|
||||
}
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let o = 0u;
|
||||
let s = 64;
|
||||
let p1 = bufferArrayView<array<u32>>(&v, o, s);
|
||||
let p2 = bufferArrayView<array<S>>(&v, o, s);
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferArrayView6: {
|
||||
code: `
|
||||
struct S {
|
||||
a : vec4f,
|
||||
b : vec4f,
|
||||
}
|
||||
var<private> o : i32;
|
||||
var<private> s : i32;
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, o, s);
|
||||
}
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<S>>(&v, o, s);
|
||||
}`,
|
||||
size: 16,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferArrayView7: {
|
||||
code: `
|
||||
struct S {
|
||||
a : vec4f,
|
||||
b : vec4f,
|
||||
}
|
||||
var<private> o : i32;
|
||||
var<private> s : i32;
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, o, s);
|
||||
}
|
||||
@fragment
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<S>>(&v, o, s);
|
||||
}`,
|
||||
size: 16,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferArrayView8: {
|
||||
code: `
|
||||
struct S {
|
||||
a : vec4f,
|
||||
b : vec4f,
|
||||
}
|
||||
var<private> o : i32;
|
||||
var<private> s : i32;
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
bar();
|
||||
foo();
|
||||
}
|
||||
fn bar() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, o, s);
|
||||
}
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<S>>(&v, o, s);
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
};
|
||||
|
||||
g.test('shader_required_buffer_size')
|
||||
.desc('Test that dispatch time validation occurs about the required buffer size')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('case', keysOf(kRequiredSizeCases))
|
||||
.beginSubcases()
|
||||
.combine('valid', [false, true] as const)
|
||||
.combine('layout', ['auto', 'explicit'] as const)
|
||||
)
|
||||
.fn(t => {
|
||||
const testcase = kRequiredSizeCases[t.params.case];
|
||||
const features = testcase.requires ?? [];
|
||||
features.forEach(f => {
|
||||
t.skipIfLanguageFeatureNotSupported(f);
|
||||
});
|
||||
|
||||
const buffer = t.createBufferTracked({
|
||||
size: t.params.valid ? testcase.size : testcase.size - 4,
|
||||
usage: testcase.binding_type === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE,
|
||||
});
|
||||
|
||||
const bgLayout = t.device.createBindGroupLayout({
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: testcase.binding_type,
|
||||
minBindingSize: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const layout = t.device.createPipelineLayout({ bindGroupLayouts: [bgLayout] });
|
||||
|
||||
const pipeline = t.device.createComputePipeline({
|
||||
layout: t.params.layout === 'auto' ? 'auto' : layout,
|
||||
compute: {
|
||||
module: t.device.createShaderModule({ code: testcase.code }),
|
||||
},
|
||||
});
|
||||
|
||||
if (t.params.layout === 'auto' && !t.params.valid) {
|
||||
// 'auto' layout get minBindingSize from the shader.
|
||||
t.expectValidationError(() => {
|
||||
t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
true;
|
||||
});
|
||||
} else {
|
||||
// Expect dispatch time validation.
|
||||
const bg = t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const commandEncoder = t.device.createCommandEncoder();
|
||||
const computePassEncoder = commandEncoder.beginComputePass();
|
||||
computePassEncoder.setPipeline(pipeline);
|
||||
computePassEncoder.setBindGroup(0, bg);
|
||||
computePassEncoder.dispatchWorkgroups(1);
|
||||
computePassEncoder.end();
|
||||
|
||||
t.expectValidationError(() => {
|
||||
commandEncoder.finish();
|
||||
}, !t.params.valid);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
658
src/webgpu/api/validation/min_buffer_binding_size.spec.ts
Normal file
658
src/webgpu/api/validation/min_buffer_binding_size.spec.ts
Normal file
|
|
@ -0,0 +1,658 @@
|
|||
export const description = `
|
||||
Minimum buffer binding size tests
|
||||
`;
|
||||
|
||||
import { AllFeaturesMaxLimitsGPUTest } from '../.././gpu_test.js';
|
||||
import { makeTestGroup } from '../../../common/framework/test_group.js';
|
||||
import { keysOf } from '../../../common/util/data_tables.js';
|
||||
import { WGSLLanguageFeature } from '../../capability_info.js';
|
||||
|
||||
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
|
||||
|
||||
interface RequiredSizeCase {
|
||||
code: string;
|
||||
size: number; // Size must be greater than 4
|
||||
binding_type: GPUBufferBindingType;
|
||||
requires?: WGSLLanguageFeature[];
|
||||
}
|
||||
|
||||
const kRequiredSizeCases: Record<string, RequiredSizeCase> = {
|
||||
ro_storage_32bytes: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : array<vec4u, 2>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = v;
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'read-only-storage',
|
||||
},
|
||||
storage_32bytes: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : array<vec4u, 2>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = v;
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'storage',
|
||||
},
|
||||
uniform_32bytes: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<uniform> v : array<vec4u, 2>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = v;
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'uniform',
|
||||
},
|
||||
ro_storage_sized_buffer: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer<32>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = &v;
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_sized_buffer: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer<32>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = &v;
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
uniform_sized_buffer: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<uniform> v : buffer<32>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = &v;
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'uniform',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
ro_storage_unsized_buffer_bufferView1: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<array<u32, 16>>(&v, 0);
|
||||
}`,
|
||||
size: 16 * 4,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
ro_storage_unsized_buffer_bufferView2: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<array<u32, 16>>(&v, 56);
|
||||
}`,
|
||||
// Offset does not count toward min binding size.
|
||||
size: 16 * 4,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView3: {
|
||||
code: `
|
||||
struct S {
|
||||
a: vec4u,
|
||||
b: vec2u,
|
||||
}
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let x = 16u;
|
||||
let p = bufferView<S>(&v, x);
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView4: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<array<vec2u>>(&v, 0);
|
||||
}`,
|
||||
size: 8,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView5: {
|
||||
code: `
|
||||
struct S {
|
||||
a: array<u32, 4>
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<vec2u>(&v, 0);
|
||||
let q = bufferView<S>(&v, 0);
|
||||
}`,
|
||||
size: 16,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView6: {
|
||||
code: `
|
||||
struct S {
|
||||
a: array<u32, 4>
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<u32>(&v, 16);
|
||||
let q = bufferView<S>(&v, 0);
|
||||
}`,
|
||||
// Offset does not count toward min binding size.
|
||||
size: 16,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView7: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<vec2i>(&v, 16);
|
||||
}
|
||||
@fragment
|
||||
fn main2() {
|
||||
let q = bufferView<vec4u>(&v, 32);
|
||||
}`,
|
||||
// Offset does not count toward min binding size.
|
||||
size: 8,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView8: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<vec2f>(&v, 16);
|
||||
}
|
||||
fn foo() {
|
||||
let q = bufferView<vec4u>(&v, 32);
|
||||
}`,
|
||||
// Offset does not count toward min binding size.
|
||||
size: 8,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferView9: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<u32>(&v, 16);
|
||||
foo(&v);
|
||||
}
|
||||
fn foo(p : ptr<storage, buffer, read_write>) {
|
||||
let q = bufferView<vec4u>(&v, 32);
|
||||
}`,
|
||||
// Offset does not count toward min binding size.
|
||||
size: 16,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view', 'unrestricted_pointer_parameters'],
|
||||
},
|
||||
ro_storage_unsized_buffer_bufferArrayView1: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferArrayView<array<vec2u>>(&v, 0, 32);
|
||||
}`,
|
||||
// Size does not count toward min binding size.
|
||||
size: 8,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
ro_storage_unsized_buffer_bufferArrayView2: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferArrayView<array<vec2f>>(&v, 16, 32);
|
||||
}`,
|
||||
// Offset and size do not count toward min binding size.
|
||||
size: 8,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
ro_storage_unsized_buffer_bufferArrayView3: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let o = 16;
|
||||
let s = 32;
|
||||
let p = bufferArrayView<array<vec2u>>(&v, o, s);
|
||||
}`,
|
||||
size: 8,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
ro_storage_unsized_buffer_bufferArrayView4: {
|
||||
code: `
|
||||
struct S {
|
||||
a: vec4u,
|
||||
b: u32,
|
||||
}
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let o = 16;
|
||||
let s = 32;
|
||||
let p = bufferArrayView<array<S>>(&v, o, s);
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferArrayView5: {
|
||||
code: `
|
||||
struct S {
|
||||
a : vec4f,
|
||||
b : vec4f,
|
||||
}
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let o = 0u;
|
||||
let s = 64;
|
||||
let p1 = bufferArrayView<array<u32>>(&v, o, s);
|
||||
let p2 = bufferArrayView<array<S>>(&v, o, s);
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferArrayView6: {
|
||||
code: `
|
||||
struct S {
|
||||
a : vec4f,
|
||||
b : vec4f,
|
||||
}
|
||||
var<private> o : i32;
|
||||
var<private> s : i32;
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, o, s);
|
||||
}
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<S>>(&v, o, s);
|
||||
}`,
|
||||
size: 16,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferArrayView7: {
|
||||
code: `
|
||||
struct S {
|
||||
a : vec4f,
|
||||
b : vec4f,
|
||||
}
|
||||
var<private> o : i32;
|
||||
var<private> s : i32;
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, o, s);
|
||||
}
|
||||
@fragment
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<S>>(&v, o, s);
|
||||
}`,
|
||||
size: 16,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
storage_unsized_buffer_bufferArrayView8: {
|
||||
code: `
|
||||
struct S {
|
||||
a : vec4f,
|
||||
b : vec4f,
|
||||
}
|
||||
var<private> o : i32;
|
||||
var<private> s : i32;
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
bar();
|
||||
foo();
|
||||
}
|
||||
fn bar() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, o, s);
|
||||
}
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<S>>(&v, o, s);
|
||||
}`,
|
||||
size: 32,
|
||||
binding_type: 'storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
};
|
||||
|
||||
g.test('compute,shader_required_buffer_size')
|
||||
.desc('Test that dispatch time validation occurs about the required buffer size')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('case', keysOf(kRequiredSizeCases))
|
||||
.beginSubcases()
|
||||
.combine('valid', [false, true] as const)
|
||||
.combine('layout', ['auto', 'explicit'] as const)
|
||||
)
|
||||
.fn(t => {
|
||||
const testcase = kRequiredSizeCases[t.params.case];
|
||||
const features = testcase.requires ?? [];
|
||||
features.forEach(f => {
|
||||
t.skipIfLanguageFeatureNotSupported(f);
|
||||
});
|
||||
|
||||
const buffer = t.createBufferTracked({
|
||||
size: t.params.valid ? testcase.size : testcase.size - 4,
|
||||
usage: testcase.binding_type === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE,
|
||||
});
|
||||
|
||||
const bgLayout = t.device.createBindGroupLayout({
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: testcase.binding_type,
|
||||
minBindingSize: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const layout = t.device.createPipelineLayout({
|
||||
bindGroupLayouts: [bgLayout],
|
||||
});
|
||||
|
||||
const pipeline = t.device.createComputePipeline({
|
||||
layout: t.params.layout === 'auto' ? 'auto' : layout,
|
||||
compute: {
|
||||
module: t.device.createShaderModule({ code: testcase.code }),
|
||||
},
|
||||
});
|
||||
|
||||
if (t.params.layout === 'auto' && !t.params.valid) {
|
||||
// 'auto' layout get minBindingSize from the shader.
|
||||
t.expectValidationError(() => {
|
||||
t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
true;
|
||||
});
|
||||
} else {
|
||||
// Expect dispatch time validation.
|
||||
const bg = t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const commandEncoder = t.device.createCommandEncoder();
|
||||
const computePassEncoder = commandEncoder.beginComputePass();
|
||||
computePassEncoder.setPipeline(pipeline);
|
||||
computePassEncoder.setBindGroup(0, bg);
|
||||
computePassEncoder.dispatchWorkgroups(1);
|
||||
computePassEncoder.end();
|
||||
|
||||
t.expectValidationError(() => {
|
||||
commandEncoder.finish();
|
||||
}, !t.params.valid);
|
||||
}
|
||||
});
|
||||
|
||||
const kRequiredRenderSizeCases: Record<string, RequiredSizeCase> = {
|
||||
fragment_larger_buffer_view_ro_storage: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> buf : buffer;
|
||||
@group(0) @binding(1) var<uniform> uni : u32;
|
||||
|
||||
@vertex
|
||||
fn vs() -> @builtin(position) vec4f {
|
||||
_ = uni;
|
||||
_ = bufferView<array<u32, 2>>(&buf, 0);
|
||||
return vec4f(0);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs() -> @location(0) vec4f {
|
||||
_ = uni;
|
||||
_ = bufferView<array<u32, 4>>(&buf, 0);
|
||||
return vec4f(0);
|
||||
}
|
||||
`,
|
||||
size: 16,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
vertex_larger_buffer_view_ro_storage: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> buf : buffer;
|
||||
@group(0) @binding(1) var<uniform> uni : u32;
|
||||
|
||||
@vertex
|
||||
fn vs() -> @builtin(position) vec4f {
|
||||
_ = uni;
|
||||
_ = bufferView<array<vec4f, 2>>(&buf, 0);
|
||||
return vec4f(0);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs() -> @location(0) vec4f {
|
||||
_ = uni;
|
||||
_ = bufferView<array<u32, 4>>(&buf, 0);
|
||||
return vec4f(0);
|
||||
}
|
||||
`,
|
||||
size: 32,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
fragment_larger_buffer_array_view_ro_storage: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> buf : buffer;
|
||||
@group(0) @binding(1) var<uniform> uni : u32;
|
||||
|
||||
@vertex
|
||||
fn vs() -> @builtin(position) vec4f {
|
||||
_ = bufferArrayView<array<u32>>(&buf, 0, uni);
|
||||
return vec4f(0);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs() -> @location(0) vec4f {
|
||||
_ = bufferArrayView<array<vec4u>>(&buf, 0, uni);
|
||||
return vec4f(0);
|
||||
}
|
||||
`,
|
||||
size: 16,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
vertex_larger_buffer_array_view_ro_storage: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> buf : buffer;
|
||||
@group(0) @binding(1) var<uniform> uni : u32;
|
||||
|
||||
@vertex
|
||||
fn vs() -> @builtin(position) vec4f {
|
||||
_ = bufferArrayView<array<vec4f>>(&buf, 0, uni);
|
||||
return vec4f(0);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs() -> @location(0) vec4f {
|
||||
_ = bufferArrayView<array<u32>>(&buf, 0, uni);
|
||||
return vec4f(0);
|
||||
}
|
||||
`,
|
||||
size: 16,
|
||||
binding_type: 'read-only-storage',
|
||||
requires: ['buffer_view'],
|
||||
},
|
||||
};
|
||||
|
||||
g.test('render,shader_required_buffer_size')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('case', keysOf(kRequiredRenderSizeCases))
|
||||
.beginSubcases()
|
||||
.combine('valid', [false, true] as const)
|
||||
.combine('layout', ['auto', 'explicit'] as const)
|
||||
)
|
||||
.fn(t => {
|
||||
const testcase = kRequiredRenderSizeCases[t.params.case];
|
||||
const features = testcase.requires ?? [];
|
||||
features.forEach(f => {
|
||||
t.skipIfLanguageFeatureNotSupported(f);
|
||||
});
|
||||
|
||||
const buffer = t.createBufferTracked({
|
||||
size: t.params.valid ? testcase.size : testcase.size - 4,
|
||||
usage: testcase.binding_type === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE,
|
||||
});
|
||||
|
||||
const uniform = t.createBufferTracked({
|
||||
size: 4,
|
||||
usage: GPUBufferUsage.UNIFORM,
|
||||
});
|
||||
|
||||
const bgLayout = t.device.createBindGroupLayout({
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
||||
buffer: {
|
||||
type: testcase.binding_type,
|
||||
minBindingSize: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
||||
buffer: {
|
||||
type: 'uniform',
|
||||
minBindingSize: 4,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const layout = t.device.createPipelineLayout({
|
||||
bindGroupLayouts: [bgLayout],
|
||||
});
|
||||
|
||||
const colorTexture = t.createTextureTracked({
|
||||
size: { width: 1, height: 1 },
|
||||
format: 'rgba8unorm',
|
||||
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
||||
});
|
||||
|
||||
const shaderModule = t.device.createShaderModule({ code: testcase.code });
|
||||
const pipeline = t.device.createRenderPipeline({
|
||||
layout: t.params.layout === 'auto' ? 'auto' : layout,
|
||||
vertex: { module: shaderModule },
|
||||
fragment: {
|
||||
module: shaderModule,
|
||||
targets: [{ format: 'rgba8unorm' }],
|
||||
},
|
||||
primitive: {
|
||||
topology: 'triangle-list',
|
||||
},
|
||||
});
|
||||
|
||||
if (t.params.layout === 'auto' && !t.params.valid) {
|
||||
// 'auto' layout get minBindingSize from shaders
|
||||
t.expectValidationError(() => {
|
||||
t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
resource: {
|
||||
buffer: uniform,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
true;
|
||||
});
|
||||
} else {
|
||||
const bg = t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
resource: {
|
||||
buffer: uniform,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginRenderPass({
|
||||
colorAttachments: [
|
||||
{
|
||||
view: colorTexture.createView(),
|
||||
clearValue: [0, 0, 0, 0],
|
||||
loadOp: 'clear',
|
||||
storeOp: 'store',
|
||||
},
|
||||
],
|
||||
});
|
||||
pass.setPipeline(pipeline);
|
||||
pass.setBindGroup(0, bg);
|
||||
pass.draw(1), pass.end();
|
||||
|
||||
t.expectValidationError(() => {
|
||||
encoder.finish();
|
||||
}, !t.params.valid);
|
||||
}
|
||||
});
|
||||
|
|
@ -991,6 +991,7 @@ export const kKnownWGSLLanguageFeatures = [
|
|||
'linear_indexing',
|
||||
'texture_formats_tier1',
|
||||
'immediate_address_space',
|
||||
'buffer_view',
|
||||
] as const;
|
||||
|
||||
export type WGSLLanguageFeature = (typeof kKnownWGSLLanguageFeatures)[number];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,618 @@
|
|||
export const description = `
|
||||
Execution tests for bufferArrayView
|
||||
`;
|
||||
|
||||
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
|
||||
import { keysOf } from '../../../../../../common/util/data_tables.js';
|
||||
import { assert } from '../../../../../../common/util/util.js';
|
||||
import { AllFeaturesMaxLimitsGPUTest } from '../../../../../gpu_test.js';
|
||||
import { Type } from '../../../../../util/conversion.js';
|
||||
|
||||
import {
|
||||
kBufferSizes,
|
||||
kArrayLengthTypes,
|
||||
kOffsets,
|
||||
kSizes,
|
||||
kCalls,
|
||||
kStructDecls,
|
||||
isValidArrayLengthCase,
|
||||
calculateArrayLength,
|
||||
runLengthTest,
|
||||
kLayoutCases,
|
||||
runReadLayoutTest,
|
||||
runWriteLayoutTest,
|
||||
runReadWriteTest,
|
||||
} from './buffer_view_utils.js';
|
||||
|
||||
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
|
||||
|
||||
g.test('array_length')
|
||||
.desc('Tests arrayLength from a bufferView')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('type', keysOf(kArrayLengthTypes))
|
||||
.combine('aspace', ['workgroup', 'storage', 'uniform'] as const)
|
||||
.beginSubcases()
|
||||
.combine('sized', [false, true] as const)
|
||||
.combine('override', [false, true] as const)
|
||||
.combine('bufferSize', kBufferSizes)
|
||||
.combine('dynamic_offset', [0, 256] as const)
|
||||
.filter(t => {
|
||||
if (t.aspace === 'workgroup' && t.dynamic_offset !== 0) {
|
||||
return false;
|
||||
}
|
||||
if (t.aspace !== 'workgroup' && t.override === true) {
|
||||
return false;
|
||||
}
|
||||
return t.sized || t.aspace === 'storage';
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
const testcase = kArrayLengthTypes[t.params.type];
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (t.params.aspace === 'uniform' && testcase.uniformStdLayout === true) {
|
||||
t.skipIfLanguageFeatureNotSupported('uniform_buffer_standard_layout');
|
||||
}
|
||||
if (testcase.f16) {
|
||||
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
|
||||
}
|
||||
|
||||
const bSize = t.params.override ? 'bufferSize' : `${t.params.bufferSize}`;
|
||||
const buffer_ty = `buffer${t.params.sized ? `<${bSize}>` : ``}`;
|
||||
let decl = '';
|
||||
switch (t.params.aspace) {
|
||||
case 'workgroup':
|
||||
decl = `var<workgroup> v : ${buffer_ty};\n@group(0) @binding(0) var<storage> dummy : array<u32>;`;
|
||||
break;
|
||||
case 'uniform':
|
||||
decl = `@group(0) @binding(0) var<uniform> v : ${buffer_ty};`;
|
||||
break;
|
||||
case 'storage':
|
||||
decl = `@group(0) @binding(0) var<storage> v : ${buffer_ty};`;
|
||||
break;
|
||||
}
|
||||
|
||||
const values: number[] = [];
|
||||
const offsets: number[] = [];
|
||||
const sizes: number[] = [];
|
||||
// Limit the number of assignments.
|
||||
for (let o = 0; o < kOffsets.length; o += 2) {
|
||||
for (const size of kSizes) {
|
||||
const offset = kOffsets[o];
|
||||
|
||||
// Skip any case that results in an invalid memory reference.
|
||||
// Split the writes up one per thread.
|
||||
if (isValidArrayLengthCase(testcase, offset, size, t.params.bufferSize)) {
|
||||
values.push(calculateArrayLength(testcase, offset, size, t.params.bufferSize));
|
||||
// Setup the offset and size buffer values so they can be indexed by invocation id.
|
||||
offsets.push(offset);
|
||||
sizes.push(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(values.length > 0, 'no tests run');
|
||||
|
||||
const access = testcase.access ?? '';
|
||||
const addrOf = access === '' ? '' : '&';
|
||||
|
||||
const enables = testcase.f16 === true ? 'enable f16;' : '';
|
||||
|
||||
const wgsl = `
|
||||
${enables}
|
||||
|
||||
${kStructDecls}
|
||||
|
||||
override bufferSize : u32 = 0u;
|
||||
${decl}
|
||||
@group(0) @binding(1) var<storage> offsets : array<u32>;
|
||||
@group(0) @binding(2) var<storage> sizes : array<u32>; // unused
|
||||
@group(0) @binding(3) var<storage, read_write> out : array<u32>;
|
||||
|
||||
override wgx : u32;
|
||||
@compute @workgroup_size(wgx)
|
||||
fn main(@builtin(global_invocation_id) gid : vec3u,
|
||||
@builtin(local_invocation_index) lid : u32) {
|
||||
${t.params.aspace === 'workgroup' ? '_ = dummy[0];' : ''}
|
||||
|
||||
if gid.x >= ${values.length} {
|
||||
return;
|
||||
}
|
||||
|
||||
out[gid.x] = arrayLength(${addrOf}bufferArrayView<${
|
||||
testcase.type
|
||||
}>(&v, offsets[gid.x], sizes[gid.x])${access});
|
||||
}`;
|
||||
|
||||
runLengthTest(
|
||||
t,
|
||||
wgsl,
|
||||
t.params.aspace === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE,
|
||||
t.params.bufferSize,
|
||||
t.params.dynamic_offset,
|
||||
offsets,
|
||||
sizes,
|
||||
values
|
||||
);
|
||||
});
|
||||
|
||||
g.test('array_length,functions')
|
||||
.desc('Tests arrayLength from a bufferView')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('type', keysOf(kArrayLengthTypes))
|
||||
.combine('aspace', ['workgroup', 'storage', 'uniform'] as const)
|
||||
.beginSubcases()
|
||||
.combine('call', kCalls)
|
||||
.combine('bufferSize', kBufferSizes)
|
||||
.combine('dynamic_offset', [0, 256] as const)
|
||||
.filter(t => {
|
||||
if (t.bufferSize === 128) {
|
||||
return false;
|
||||
}
|
||||
return t.aspace !== 'workgroup' || t.dynamic_offset === 0;
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
const testcase = kArrayLengthTypes[t.params.type];
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
|
||||
if (t.params.aspace === 'uniform' && testcase.uniformStdLayout === true) {
|
||||
t.skipIfLanguageFeatureNotSupported('uniform_buffer_standard_layout');
|
||||
}
|
||||
if (testcase.f16) {
|
||||
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
|
||||
}
|
||||
|
||||
const buffer_ty = `buffer<${t.params.bufferSize}>`;
|
||||
let decl = '';
|
||||
switch (t.params.aspace) {
|
||||
case 'workgroup':
|
||||
decl = `var<workgroup> v : ${buffer_ty};\n@group(0) @binding(0) var<storage> dummy : array<u32>;`;
|
||||
break;
|
||||
case 'uniform':
|
||||
decl = `@group(0) @binding(0) var<uniform> v : ${buffer_ty};`;
|
||||
break;
|
||||
case 'storage':
|
||||
decl = `@group(0) @binding(0) var<storage> v : ${buffer_ty};`;
|
||||
break;
|
||||
}
|
||||
|
||||
const values: number[] = [];
|
||||
const offsets: number[] = [];
|
||||
const sizes: number[] = [];
|
||||
// Limit the number of assignments.
|
||||
for (let o = 0; o < kOffsets.length; o += 2) {
|
||||
for (const size of kSizes) {
|
||||
const offset = kOffsets[o];
|
||||
const usedSize =
|
||||
t.params.call === 'unsized' ? t.params.bufferSize : t.params.bufferSize / 2;
|
||||
|
||||
// Skip any case that results in an invalid memory reference.
|
||||
// Split the writes up one per thread.
|
||||
if (isValidArrayLengthCase(testcase, offset, size, usedSize)) {
|
||||
values.push(calculateArrayLength(testcase, offset, size, usedSize));
|
||||
// Setup the offset and size buffer values so they can be indexed by invocation id.
|
||||
offsets.push(offset);
|
||||
sizes.push(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(values.length > 0, 'no tests run');
|
||||
|
||||
const access = testcase.access ?? '';
|
||||
const addrOf = access === '' ? '' : '&';
|
||||
|
||||
const enables = testcase.f16 === true ? 'enable f16;' : '';
|
||||
|
||||
const wgsl = `
|
||||
${enables}
|
||||
|
||||
${kStructDecls}
|
||||
|
||||
fn unsizedLength(p : ptr<${t.params.aspace}, buffer>, gidx : u32) -> u32 {
|
||||
return arrayLength(${addrOf}bufferArrayView<${
|
||||
testcase.type
|
||||
}>(p, offsets[gidx], sizes[gidx])${access});
|
||||
}
|
||||
|
||||
fn sizedLength(p : ptr<${t.params.aspace}, buffer<${t.params.bufferSize / 2}>>, gidx : u32) -> u32 {
|
||||
return arrayLength(${addrOf}bufferArrayView<${
|
||||
testcase.type
|
||||
}>(p, offsets[gidx], sizes[gidx])${access});
|
||||
}
|
||||
|
||||
fn sized_indirectLength(p : ptr<${t.params.aspace}, buffer<${
|
||||
t.params.bufferSize / 2
|
||||
}>>, gidx : u32) -> u32 {
|
||||
return unsizedLength(p, gidx);
|
||||
}
|
||||
|
||||
fn sized_sized_indirectLength(p : ptr<${t.params.aspace}, buffer<${
|
||||
t.params.bufferSize
|
||||
}>>, gidx : u32) -> u32 {
|
||||
return sizedLength(p, gidx);
|
||||
}
|
||||
|
||||
override bufferSize : u32 = 0u;
|
||||
${decl}
|
||||
@group(0) @binding(1) var<storage> offsets : array<u32>;
|
||||
@group(0) @binding(2) var<storage> sizes : array<u32>; // unused
|
||||
@group(0) @binding(3) var<storage, read_write> out : array<u32>;
|
||||
|
||||
override wgx : u32;
|
||||
@compute @workgroup_size(wgx)
|
||||
fn main(@builtin(global_invocation_id) gid : vec3u,
|
||||
@builtin(local_invocation_index) lid : u32) {
|
||||
${t.params.aspace === 'workgroup' ? '_ = dummy[0];' : ''}
|
||||
|
||||
if gid.x >= ${values.length} {
|
||||
return;
|
||||
}
|
||||
|
||||
out[gid.x] = ${t.params.call}Length(&v, gid.x);
|
||||
}`;
|
||||
|
||||
runLengthTest(
|
||||
t,
|
||||
wgsl,
|
||||
t.params.aspace === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE,
|
||||
t.params.bufferSize,
|
||||
t.params.dynamic_offset,
|
||||
offsets,
|
||||
sizes,
|
||||
values
|
||||
);
|
||||
});
|
||||
|
||||
g.test('read_layout')
|
||||
.desc('Test reading memory layout from a bufferArrayView')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('case', keysOf(kLayoutCases))
|
||||
.beginSubcases()
|
||||
.combine('aspace', ['storage', 'ro_storage', 'uniform', 'workgroup'] as const)
|
||||
.combine('offset', kOffsets)
|
||||
.filter(t => {
|
||||
const testcase = kLayoutCases[t.case];
|
||||
if ((t.offset & (testcase.align - 1)) !== 0) {
|
||||
return false;
|
||||
}
|
||||
return testcase.offset + t.offset < 252;
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
const testcase = kLayoutCases[t.params.case];
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (t.params.aspace === 'uniform') {
|
||||
if (testcase.uniformStdLayoutArrayView === true || testcase.uniformStdLayoutView === true) {
|
||||
t.skipIfLanguageFeatureNotSupported('uniform_buffer_standard_layout');
|
||||
}
|
||||
}
|
||||
if (testcase.f16) {
|
||||
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
|
||||
}
|
||||
|
||||
const enables = testcase.f16 === true ? 'enable f16;' : '';
|
||||
const v = t.params.aspace === 'workgroup' ? 'wg_var' : 'in';
|
||||
|
||||
const wgsl = `
|
||||
${enables}
|
||||
|
||||
${testcase.decl ?? ''}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<${t.params.aspace === 'uniform' ? 'uniform' : 'storage'}${
|
||||
t.params.aspace === 'storage' ? ', read_write' : ''
|
||||
}> in : buffer<256 * 4>;
|
||||
|
||||
@group(0) @binding(1) var<storage, read_write> out : u32;
|
||||
|
||||
var<workgroup> wg_var : buffer<256 * 4>;
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let in_ptr = bufferView<array<vec4u, 256 / 4>>(&in, 0);
|
||||
let wg_ptr = bufferView<array<vec4u, 256 / 4>>(&wg_var, 0);
|
||||
for (var i = 0; i < 256 / 4; i++) {
|
||||
(*wg_ptr)[i] = (*in_ptr)[i];
|
||||
}
|
||||
|
||||
workgroupBarrier();
|
||||
|
||||
let p = bufferArrayView<array<${testcase.type}>>(&${v}, ${
|
||||
t.params.offset
|
||||
}, bufferLength(&${v}) - ${t.params.offset});
|
||||
out = u32((*p)[0]${testcase.access});
|
||||
}
|
||||
`;
|
||||
|
||||
runReadLayoutTest(t, testcase, wgsl, t.params.aspace, t.params.offset);
|
||||
});
|
||||
|
||||
g.test('write_layout')
|
||||
.desc('Test writing memory layout via a bufferView')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('case', keysOf(kLayoutCases))
|
||||
.beginSubcases()
|
||||
.combine('assign', ['let', 'call'] as const)
|
||||
.combine('aspace', ['storage', 'workgroup'] as const)
|
||||
.combine('offset', kOffsets)
|
||||
.filter(t => {
|
||||
const testcase = kLayoutCases[t.case];
|
||||
if ((t.offset & (testcase.align - 1)) !== 0) {
|
||||
return false;
|
||||
}
|
||||
return testcase.offset + t.offset < 252;
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
const testcase = kLayoutCases[t.params.case];
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (testcase.f16) {
|
||||
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
|
||||
}
|
||||
|
||||
const enables = testcase.f16 === true ? 'enable f16;' : '';
|
||||
const v = t.params.aspace === 'workgroup' ? 'wg_var' : 'out';
|
||||
const call_expr = `bufferArrayView<array<${testcase.type}>>(&${v}, ${t.params.offset}, bufferLength(&${v}) - ${t.params.offset})`;
|
||||
|
||||
let post_assign = '';
|
||||
if (t.params.aspace === 'workgroup') {
|
||||
post_assign = `
|
||||
workgroupBarrier();
|
||||
|
||||
let out_ptr = bufferView<array<u32>>(&out, 0);
|
||||
let wg_ptr = bufferView<array<u32>>(&wg_var, 0);
|
||||
for (var i = 0; i < 256; i++) {
|
||||
(*out_ptr)[i] = (*wg_ptr)[i];
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
const wgsl = `
|
||||
${enables}
|
||||
|
||||
${testcase.decl ?? ''}
|
||||
|
||||
@group(0) @binding(0) var<storage> in : u32;
|
||||
@group(0) @binding(1) var<storage, read_write> out : buffer<256 * 4>;
|
||||
|
||||
|
||||
var<workgroup> wg_var : buffer<256 * 4>;
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let val = ${testcase.f16 ? 'f16' : testcase.f32 ? 'f32' : 'u32'}(in);
|
||||
let ptr = ${call_expr};
|
||||
(*${t.params.assign === 'call' ? call_expr : 'ptr'})[0]${testcase.access} = val;
|
||||
|
||||
${post_assign}
|
||||
}
|
||||
`;
|
||||
|
||||
runWriteLayoutTest(t, testcase, wgsl, t.params.offset);
|
||||
});
|
||||
|
||||
g.test('read')
|
||||
.desc('Test reading various types from bufferArrayView')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('base_type', ['u32', 'i32', 'f32', 'f16'] as const)
|
||||
.beginSubcases()
|
||||
.combine('wrap', ['none', 'vector', 'array', 'matrix'] as const)
|
||||
.combine('width', [1, 2, 3, 4] as const)
|
||||
.combine('aspace', ['workgroup', 'storage', 'ro_storage', 'uniform'] as const)
|
||||
.combine('offset', [0, 4, 8, 12, 16, 32, 48, 64] as const)
|
||||
.filter(t => {
|
||||
if (t.wrap !== 'none' && t.width === 1) {
|
||||
return false;
|
||||
}
|
||||
if (t.wrap === 'none' && t.width !== 1) {
|
||||
return false;
|
||||
}
|
||||
if (t.wrap === 'matrix' && t.base_type !== 'f32' && t.base_type !== 'f16') {
|
||||
return false;
|
||||
}
|
||||
if (t.aspace === 'uniform' && t.wrap === 'array') {
|
||||
return false;
|
||||
}
|
||||
const ty = Type[t.base_type];
|
||||
let align = ty.alignment;
|
||||
switch (t.wrap) {
|
||||
case 'vector':
|
||||
align = Type.vec(t.width, ty).alignment;
|
||||
break;
|
||||
case 'array':
|
||||
align = Type.array(2, ty).alignment;
|
||||
break;
|
||||
case 'matrix':
|
||||
align = Type.mat(t.width, 2, ty).alignment;
|
||||
break;
|
||||
case 'none':
|
||||
break;
|
||||
}
|
||||
if (t.offset % align !== 0) {
|
||||
return false;
|
||||
}
|
||||
if (t.aspace === 'uniform' && align % 16 !== 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (t.params.base_type === 'f16') {
|
||||
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
|
||||
}
|
||||
|
||||
const ele_ty = Type[t.params.base_type];
|
||||
let ty: Type = ele_ty;
|
||||
switch (t.params.wrap) {
|
||||
case 'vector':
|
||||
ty = Type.vec(t.params.width, ele_ty);
|
||||
break;
|
||||
case 'array':
|
||||
ty = Type.array(t.params.width, ele_ty);
|
||||
break;
|
||||
case 'matrix':
|
||||
ty = Type.mat(t.params.width, 2, ele_ty);
|
||||
break;
|
||||
case 'none':
|
||||
break;
|
||||
}
|
||||
|
||||
const bufferSize = 128;
|
||||
const enables = t.params.base_type === 'f16' ? 'enable f16;' : '';
|
||||
const v = t.params.aspace === 'workgroup' ? 'wg_var' : 'in';
|
||||
const wgsl = `
|
||||
${enables}
|
||||
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<${t.params.aspace === 'uniform' ? 'uniform' : 'storage'}${
|
||||
t.params.aspace === 'storage' ? ', read_write' : ''
|
||||
}> in : buffer<${bufferSize}>;
|
||||
|
||||
@group(0) @binding(1) var<storage, read_write> out : ${ty.toString()};
|
||||
|
||||
var<workgroup> wg_var : buffer<${bufferSize}>;
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let in_ptr = bufferView<array<vec4u, ${bufferSize} / 16>>(&in, 0);
|
||||
let wg_ptr = bufferView<array<vec4u, ${bufferSize} / 16>>(&wg_var, 0);
|
||||
for (var i = 0; i < 256 / 4; i++) {
|
||||
(*wg_ptr)[i] = (*in_ptr)[i];
|
||||
}
|
||||
|
||||
workgroupBarrier();
|
||||
|
||||
let p = bufferArrayView<array<${ty.toString()}>>(&${v}, ${
|
||||
t.params.offset
|
||||
}, bufferLength(&${v}) - ${t.params.offset});
|
||||
out = (*p)[0];
|
||||
}
|
||||
`;
|
||||
|
||||
runReadWriteTest(true, t, wgsl, ele_ty, ty, t.params.aspace, t.params.offset, bufferSize);
|
||||
});
|
||||
|
||||
g.test('write')
|
||||
.desc('Test writing various types via bufferArrayView')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('base_type', ['u32', 'i32', 'f32', 'f16'] as const)
|
||||
.beginSubcases()
|
||||
.combine('wrap', ['none', 'vector', 'array', 'matrix'] as const)
|
||||
.combine('width', [1, 2, 3, 4] as const)
|
||||
.combine('aspace', ['workgroup', 'storage'] as const)
|
||||
.combine('offset', [0, 4, 8, 12, 16, 32, 48, 64] as const)
|
||||
.combine('assign', ['let', 'call'] as const)
|
||||
.combine('swizzle', [false, true] as const)
|
||||
.filter(t => {
|
||||
if (t.wrap !== 'none' && t.width === 1) {
|
||||
return false;
|
||||
}
|
||||
if (t.wrap === 'none' && t.width !== 1) {
|
||||
return false;
|
||||
}
|
||||
if (t.wrap === 'matrix' && t.base_type !== 'f32' && t.base_type !== 'f16') {
|
||||
return false;
|
||||
}
|
||||
if (t.wrap !== 'vector' && t.swizzle) {
|
||||
return false;
|
||||
}
|
||||
const ty = Type[t.base_type];
|
||||
let align = ty.alignment;
|
||||
switch (t.wrap) {
|
||||
case 'vector':
|
||||
align = Type.vec(t.width, ty).alignment;
|
||||
break;
|
||||
case 'array':
|
||||
align = Type.array(2, ty).alignment;
|
||||
break;
|
||||
case 'matrix':
|
||||
align = Type.mat(t.width, 2, ty).alignment;
|
||||
break;
|
||||
case 'none':
|
||||
break;
|
||||
}
|
||||
if (t.offset % align !== 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (t.params.base_type === 'f16') {
|
||||
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
|
||||
}
|
||||
if (t.params.swizzle) {
|
||||
t.skipIfLanguageFeatureNotSupported('swizzle_assignment');
|
||||
}
|
||||
|
||||
let swizzle = '';
|
||||
const ele_ty = Type[t.params.base_type];
|
||||
let ty: Type = ele_ty;
|
||||
switch (t.params.wrap) {
|
||||
case 'vector':
|
||||
ty = Type.vec(t.params.width, ele_ty);
|
||||
if (t.params.swizzle) {
|
||||
swizzle = '.xyzw'.substring(0, 1 + t.params.width);
|
||||
}
|
||||
break;
|
||||
case 'array':
|
||||
ty = Type.array(t.params.width, ele_ty);
|
||||
break;
|
||||
case 'matrix':
|
||||
ty = Type.mat(t.params.width, 2, ele_ty);
|
||||
break;
|
||||
case 'none':
|
||||
break;
|
||||
}
|
||||
|
||||
const bufferSize = 128;
|
||||
const enables = t.params.base_type === 'f16' ? 'enable f16;' : '';
|
||||
const v = t.params.aspace === 'workgroup' ? 'wg_var' : 'out';
|
||||
|
||||
let post_assign = '';
|
||||
if (t.params.aspace === 'workgroup') {
|
||||
post_assign = `
|
||||
workgroupBarrier();
|
||||
|
||||
let out_ptr = bufferView<array<u32>>(&out, 0);
|
||||
let wg_ptr = bufferView<array<u32>>(&wg_var, 0);
|
||||
for (var i = 0; i < 256; i++) {
|
||||
(*out_ptr)[i] = (*wg_ptr)[i];
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
const call_expr = `bufferArrayView<array<${ty.toString()}>>(&${v}, ${
|
||||
t.params.offset
|
||||
}, bufferLength(&${v}) - ${t.params.offset})`;
|
||||
const wgsl = `
|
||||
${enables}
|
||||
|
||||
@group(0) @binding(0) var<storage> in : ${ty.toString()};
|
||||
@group(0) @binding(1) var<storage, read_write> out : buffer<${bufferSize}>;
|
||||
|
||||
|
||||
var<workgroup> wg_var : buffer<${bufferSize}>;
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let ptr = ${call_expr};
|
||||
(*${t.params.assign === 'call' ? call_expr : 'ptr'})[0]${swizzle} = in;
|
||||
|
||||
${post_assign}
|
||||
}
|
||||
`;
|
||||
|
||||
runReadWriteTest(false, t, wgsl, ele_ty, ty, t.params.aspace, t.params.offset, bufferSize);
|
||||
});
|
||||
|
|
@ -0,0 +1,485 @@
|
|||
export const description = `
|
||||
Execution tests for bufferLength
|
||||
|
||||
Tests sized and unsized buffers across all address spaces (where applicable).
|
||||
Tested against buffers with static and dynamic offsets.
|
||||
`;
|
||||
|
||||
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
|
||||
import { GPUTest } from '../../../../../gpu_test.js';
|
||||
|
||||
export const g = makeTestGroup(GPUTest);
|
||||
|
||||
g.test('sized_buffer')
|
||||
.desc('Tests bufferLength directly on sized buffer variables')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('size', [256, 512, 600, 1024, 2048] as const)
|
||||
.beginSubcases()
|
||||
.combine('param', ['none', 'unsized', 'sized'] as const)
|
||||
.combine('padding', [false, true] as const)
|
||||
.combine('dynamic_offset', [false, true] as const)
|
||||
.combine('offset', [0, 256] as const)
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (t.params.param !== 'none') {
|
||||
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
|
||||
}
|
||||
|
||||
let fn_decls = '';
|
||||
const assigns = ['', '', '', ''];
|
||||
switch (t.params.param) {
|
||||
case 'none':
|
||||
assigns[0] = 'bufferLength(&uniform_buffer)';
|
||||
assigns[1] = 'bufferLength(&ro_storage_buffer)';
|
||||
assigns[2] = 'bufferLength(&storage_buffer)';
|
||||
assigns[3] = 'bufferLength(&workgroup_buffer)';
|
||||
break;
|
||||
case 'unsized':
|
||||
fn_decls = `
|
||||
fn uniformUnsized(p : ptr<uniform, buffer>) -> u32 {
|
||||
return bufferLength(p);
|
||||
}
|
||||
fn roStorageUnsized(p : ptr<storage, buffer>) -> u32 {
|
||||
return bufferLength(p);
|
||||
}
|
||||
fn storageUnsized(p : ptr<storage, buffer, read_write>) -> u32 {
|
||||
return bufferLength(p);
|
||||
}
|
||||
fn workgroupUnsized(p : ptr<workgroup, buffer>) -> u32 {
|
||||
return bufferLength(p);
|
||||
}`;
|
||||
assigns[0] = 'uniformUnsized(&uniform_buffer)';
|
||||
assigns[1] = 'roStorageUnsized(&ro_storage_buffer)';
|
||||
assigns[2] = 'storageUnsized(&storage_buffer)';
|
||||
assigns[3] = 'workgroupUnsized(&workgroup_buffer)';
|
||||
break;
|
||||
case 'sized':
|
||||
fn_decls = `
|
||||
fn uniformSized(p : ptr<uniform, buffer<${t.params.size / 2}>>) -> u32 {
|
||||
return bufferLength(p);
|
||||
}
|
||||
fn roStorageSized(p : ptr<storage, buffer<${t.params.size / 2}>>) -> u32 {
|
||||
return bufferLength(p);
|
||||
}
|
||||
fn storageSized(p : ptr<storage, buffer<${t.params.size / 2}>, read_write>) -> u32 {
|
||||
return bufferLength(p);
|
||||
}
|
||||
fn workgroupSized(p : ptr<workgroup, buffer<${t.params.size / 2}>>) -> u32 {
|
||||
return bufferLength(p);
|
||||
}`;
|
||||
assigns[0] = 'uniformSized(&uniform_buffer)';
|
||||
assigns[1] = 'roStorageSized(&ro_storage_buffer)';
|
||||
assigns[2] = 'storageSized(&storage_buffer)';
|
||||
assigns[3] = 'workgroupSized(&workgroup_buffer)';
|
||||
break;
|
||||
}
|
||||
|
||||
const wgsl = `
|
||||
@group(0) @binding(0) var<uniform> uniform_buffer : buffer<${t.params.size}>;
|
||||
@group(0) @binding(1) var<storage> ro_storage_buffer : buffer<${t.params.size}>;
|
||||
@group(0) @binding(2) var<storage, read_write> storage_buffer : buffer<${t.params.size}>;
|
||||
var<workgroup> workgroup_buffer : buffer<${t.params.size}>;
|
||||
|
||||
@group(0) @binding(3) var<storage, read_write> out : array<u32, 4>;
|
||||
|
||||
${fn_decls}
|
||||
|
||||
@compute @workgroup_size(4)
|
||||
fn main(@builtin(local_invocation_index) lid : u32) {
|
||||
out[0] = ${assigns[0]};
|
||||
out[1] = ${assigns[1]};
|
||||
out[2] = ${assigns[2]};
|
||||
out[3] = ${assigns[3]};
|
||||
}`;
|
||||
|
||||
const padding = t.params.padding ? 256 : 0;
|
||||
const offset = t.params.offset;
|
||||
const dynOffset = t.params.dynamic_offset ? 256 : 0;
|
||||
const bufferSize = t.params.size + padding + offset + dynOffset;
|
||||
const noOffsetSize = t.params.size + padding;
|
||||
|
||||
const uniformBuffer = t.createBufferTracked({
|
||||
size: bufferSize,
|
||||
usage: GPUBufferUsage.UNIFORM,
|
||||
});
|
||||
const roStorageBuffer = t.createBufferTracked({
|
||||
size: bufferSize,
|
||||
usage: GPUBufferUsage.STORAGE,
|
||||
});
|
||||
const storageBuffer = t.createBufferTracked({
|
||||
size: bufferSize,
|
||||
usage: GPUBufferUsage.STORAGE,
|
||||
});
|
||||
const outputBuffer = t.createBufferTracked({
|
||||
size: 4 * 4,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
|
||||
const bgLayout = t.device.createBindGroupLayout({
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'uniform',
|
||||
hasDynamicOffset: t.params.dynamic_offset,
|
||||
minBindingSize: t.params.size,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'read-only-storage',
|
||||
hasDynamicOffset: t.params.dynamic_offset,
|
||||
minBindingSize: t.params.size,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'storage',
|
||||
hasDynamicOffset: t.params.dynamic_offset,
|
||||
minBindingSize: t.params.size,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 3,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'storage',
|
||||
minBindingSize: 4 * 4,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const pipelineLayout = t.device.createPipelineLayout({
|
||||
bindGroupLayouts: [bgLayout],
|
||||
});
|
||||
const pipeline = t.device.createComputePipeline({
|
||||
layout: pipelineLayout,
|
||||
compute: {
|
||||
module: t.device.createShaderModule({ code: wgsl }),
|
||||
},
|
||||
});
|
||||
|
||||
const bg = t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer: uniformBuffer,
|
||||
offset,
|
||||
size: noOffsetSize,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
resource: {
|
||||
buffer: roStorageBuffer,
|
||||
offset,
|
||||
size: noOffsetSize,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
resource: {
|
||||
buffer: storageBuffer,
|
||||
offset,
|
||||
size: noOffsetSize,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 3,
|
||||
resource: {
|
||||
buffer: outputBuffer,
|
||||
size: 4 * 4,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginComputePass();
|
||||
pass.setPipeline(pipeline);
|
||||
if (t.params.dynamic_offset) {
|
||||
pass.setBindGroup(0, bg, [dynOffset, dynOffset, dynOffset]);
|
||||
} else {
|
||||
pass.setBindGroup(0, bg);
|
||||
}
|
||||
pass.dispatchWorkgroups(1, 1, 1);
|
||||
pass.end();
|
||||
t.queue.submit([encoder.finish()]);
|
||||
|
||||
const expectedSize = t.params.param === 'sized' ? t.params.size / 2 : t.params.size;
|
||||
const expected = new Uint32Array([expectedSize, expectedSize, expectedSize, expectedSize]);
|
||||
t.expectGPUBufferValuesEqual(outputBuffer, expected);
|
||||
});
|
||||
|
||||
g.test('unsized_buffer')
|
||||
.desc('Tests bufferLength directly on unsized buffer variables')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('size', [256, 512, 600, 1024, 2048] as const)
|
||||
.beginSubcases()
|
||||
.combine('param', ['none', 'unsized'] as const)
|
||||
.combine('padding', [false, true] as const)
|
||||
.combine('dynamic_offset', [false, true] as const)
|
||||
.combine('offset', [0, 256] as const)
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (t.params.param !== 'none') {
|
||||
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
|
||||
}
|
||||
|
||||
let fn_decls = '';
|
||||
const assigns = ['', '', '', ''];
|
||||
switch (t.params.param) {
|
||||
case 'none':
|
||||
assigns[0] = 'bufferLength(&ro_storage_buffer)';
|
||||
assigns[1] = 'bufferLength(&storage_buffer)';
|
||||
break;
|
||||
case 'unsized':
|
||||
fn_decls = `
|
||||
fn roStorageUnsized(p : ptr<storage, buffer>) -> u32 {
|
||||
return bufferLength(p);
|
||||
}
|
||||
fn storageUnsized(p : ptr<storage, buffer, read_write>) -> u32 {
|
||||
return bufferLength(p);
|
||||
}`;
|
||||
assigns[0] = 'roStorageUnsized(&ro_storage_buffer)';
|
||||
assigns[1] = 'storageUnsized(&storage_buffer)';
|
||||
break;
|
||||
}
|
||||
|
||||
const wgsl = `
|
||||
@group(0) @binding(0) var<storage> ro_storage_buffer : buffer;
|
||||
@group(0) @binding(1) var<storage, read_write> storage_buffer : buffer;
|
||||
|
||||
@group(0) @binding(2) var<storage, read_write> out : array<u32, 4>;
|
||||
|
||||
${fn_decls}
|
||||
|
||||
@compute @workgroup_size(4)
|
||||
fn main(@builtin(local_invocation_index) lid : u32) {
|
||||
out[0] = ${assigns[0]};
|
||||
out[1] = ${assigns[1]};
|
||||
}`;
|
||||
|
||||
const padding = t.params.padding ? 256 : 0;
|
||||
const offset = t.params.offset;
|
||||
const dynOffset = t.params.dynamic_offset ? 256 : 0;
|
||||
const bufferSize = t.params.size + padding + offset + dynOffset;
|
||||
const noOffsetSize = t.params.size + padding;
|
||||
|
||||
const roStorageBuffer = t.createBufferTracked({
|
||||
size: bufferSize,
|
||||
usage: GPUBufferUsage.STORAGE,
|
||||
});
|
||||
const storageBuffer = t.createBufferTracked({
|
||||
size: bufferSize,
|
||||
usage: GPUBufferUsage.STORAGE,
|
||||
});
|
||||
const outputBuffer = t.createBufferTracked({
|
||||
size: 4 * 4,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
|
||||
const bgLayout = t.device.createBindGroupLayout({
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'read-only-storage',
|
||||
hasDynamicOffset: t.params.dynamic_offset,
|
||||
minBindingSize: t.params.size,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'storage',
|
||||
hasDynamicOffset: t.params.dynamic_offset,
|
||||
minBindingSize: t.params.size,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'storage',
|
||||
minBindingSize: 4 * 4,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const pipelineLayout = t.device.createPipelineLayout({
|
||||
bindGroupLayouts: [bgLayout],
|
||||
});
|
||||
const pipeline = t.device.createComputePipeline({
|
||||
layout: pipelineLayout,
|
||||
compute: {
|
||||
module: t.device.createShaderModule({ code: wgsl }),
|
||||
},
|
||||
});
|
||||
|
||||
const bg = t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer: roStorageBuffer,
|
||||
offset,
|
||||
size: noOffsetSize,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
resource: {
|
||||
buffer: storageBuffer,
|
||||
offset,
|
||||
size: noOffsetSize,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
resource: {
|
||||
buffer: outputBuffer,
|
||||
size: 4 * 4,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginComputePass();
|
||||
pass.setPipeline(pipeline);
|
||||
if (t.params.dynamic_offset) {
|
||||
pass.setBindGroup(0, bg, [dynOffset, dynOffset]);
|
||||
} else {
|
||||
pass.setBindGroup(0, bg);
|
||||
}
|
||||
pass.dispatchWorkgroups(1, 1, 1);
|
||||
pass.end();
|
||||
t.queue.submit([encoder.finish()]);
|
||||
|
||||
const expected = new Uint32Array([noOffsetSize, noOffsetSize]);
|
||||
t.expectGPUBufferValuesEqual(outputBuffer, expected);
|
||||
});
|
||||
|
||||
g.test('max_size_buffer')
|
||||
.desc('Test with a maximum sized buffer')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('aspace', ['workgroup', 'uniform', 'storage', 'ro_storage'] as const)
|
||||
.combine('sized', [false, true] as const)
|
||||
.filter(t => {
|
||||
return t.sized === true || t.aspace === 'storage' || t.aspace === 'ro_storage';
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
|
||||
const {
|
||||
maxUniformBufferBindingSize,
|
||||
maxStorageBufferBindingSize,
|
||||
maxComputeWorkgroupStorageSize,
|
||||
} = t.device.limits;
|
||||
|
||||
let size = 16;
|
||||
switch (t.params.aspace) {
|
||||
case 'workgroup':
|
||||
size = maxComputeWorkgroupStorageSize;
|
||||
break;
|
||||
case 'uniform':
|
||||
size = maxUniformBufferBindingSize;
|
||||
break;
|
||||
case 'storage':
|
||||
case 'ro_storage':
|
||||
size = maxStorageBufferBindingSize;
|
||||
break;
|
||||
}
|
||||
|
||||
const type = `buffer${t.params.sized ? `<${size}u>` : ``}`;
|
||||
let decl = '';
|
||||
switch (t.params.aspace) {
|
||||
case 'workgroup':
|
||||
decl = `var<workgroup> v : ${type};\n@group(0) @binding(1) var<storage> dummy : u32;`;
|
||||
break;
|
||||
case 'uniform':
|
||||
decl = `@group(0) @binding(1) var<uniform> v: ${type};`;
|
||||
break;
|
||||
case 'storage':
|
||||
decl = `@group(0) @binding(1) var<storage, read_write> v : ${type};`;
|
||||
break;
|
||||
case 'ro_storage':
|
||||
decl = `@group(0) @binding(1) var<storage> v : ${type};`;
|
||||
break;
|
||||
}
|
||||
|
||||
const wgsl = `
|
||||
@group(0) @binding(0) var<storage, read_write> out : u32;
|
||||
${decl}
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
out = bufferLength(&v);
|
||||
${t.params.aspace === 'workgroup' ? '_ = dummy;' : ''}
|
||||
}`;
|
||||
|
||||
const outputBuffer = t.createBufferTracked({
|
||||
size: 4,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
// For the workgroup case we create a dummy storage buffer to simplify bindings.
|
||||
const inputBuffer = t.createBufferTracked({
|
||||
size,
|
||||
usage: t.params.aspace === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE,
|
||||
});
|
||||
|
||||
const pipeline = t.device.createComputePipeline({
|
||||
layout: 'auto',
|
||||
compute: {
|
||||
module: t.device.createShaderModule({ code: wgsl }),
|
||||
},
|
||||
});
|
||||
|
||||
const bg = t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer: outputBuffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
resource: {
|
||||
buffer: inputBuffer,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginComputePass();
|
||||
pass.setPipeline(pipeline);
|
||||
pass.setBindGroup(0, bg);
|
||||
pass.dispatchWorkgroups(1, 1, 1);
|
||||
pass.end();
|
||||
t.queue.submit([encoder.finish()]);
|
||||
|
||||
const expected = new Uint32Array([size]);
|
||||
t.expectGPUBufferValuesEqual(outputBuffer, expected);
|
||||
});
|
||||
|
|
@ -0,0 +1,584 @@
|
|||
export const description = `
|
||||
Execution tests for bufferView
|
||||
`;
|
||||
|
||||
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
|
||||
import { keysOf } from '../../../../../../common/util/data_tables.js';
|
||||
import { assert } from '../../../../../../common/util/util.js';
|
||||
import { AllFeaturesMaxLimitsGPUTest } from '../../../../../gpu_test.js';
|
||||
import { Type } from '../../../../../util/conversion.js';
|
||||
|
||||
import {
|
||||
kBufferSizes,
|
||||
kArrayLengthTypes,
|
||||
kOffsets,
|
||||
kCalls,
|
||||
kStructDecls,
|
||||
isValidArrayLengthCase,
|
||||
calculateArrayLength,
|
||||
runLengthTest,
|
||||
kLayoutCases,
|
||||
runReadLayoutTest,
|
||||
runWriteLayoutTest,
|
||||
runReadWriteTest,
|
||||
} from './buffer_view_utils.js';
|
||||
|
||||
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
|
||||
|
||||
g.test('array_length')
|
||||
.desc('Tests arrayLength from a bufferView')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('type', keysOf(kArrayLengthTypes))
|
||||
.combine('aspace', ['workgroup', 'storage', 'uniform'] as const)
|
||||
.beginSubcases()
|
||||
.combine('sized', [false, true] as const)
|
||||
.combine('override', [false, true] as const)
|
||||
.combine('bufferSize', kBufferSizes)
|
||||
.combine('dynamic_offset', [0, 256] as const)
|
||||
.filter(t => {
|
||||
if (t.aspace === 'workgroup' && t.dynamic_offset !== 0) {
|
||||
return false;
|
||||
}
|
||||
if (t.aspace !== 'workgroup' && t.override === true) {
|
||||
return false;
|
||||
}
|
||||
return t.sized || t.aspace === 'storage';
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
const testcase = kArrayLengthTypes[t.params.type];
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (t.params.aspace === 'uniform' && testcase.uniformStdLayout === true) {
|
||||
t.skipIfLanguageFeatureNotSupported('uniform_buffer_standard_layout');
|
||||
}
|
||||
if (testcase.f16) {
|
||||
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
|
||||
}
|
||||
|
||||
const bSize = t.params.override ? 'bufferSize' : `${t.params.bufferSize}`;
|
||||
const buffer_ty = `buffer${t.params.sized ? `<${bSize}>` : ``}`;
|
||||
let decl = '';
|
||||
switch (t.params.aspace) {
|
||||
case 'workgroup':
|
||||
decl = `var<workgroup> v : ${buffer_ty};\n@group(0) @binding(0) var<storage> dummy : array<u32>;`;
|
||||
break;
|
||||
case 'uniform':
|
||||
decl = `@group(0) @binding(0) var<uniform> v : ${buffer_ty};`;
|
||||
break;
|
||||
case 'storage':
|
||||
decl = `@group(0) @binding(0) var<storage> v : ${buffer_ty};`;
|
||||
break;
|
||||
}
|
||||
|
||||
const values: number[] = [];
|
||||
const offsets: number[] = [];
|
||||
const sizes: number[] = [0];
|
||||
for (const offset of kOffsets) {
|
||||
// Skip any case that results in an invalid memory reference.
|
||||
// Split the writes up one per thread.
|
||||
if (isValidArrayLengthCase(testcase, offset, 0, t.params.bufferSize)) {
|
||||
values.push(calculateArrayLength(testcase, offset, 0, t.params.bufferSize));
|
||||
// Setup the offset and size buffer values so they can be indexed by invocation id.
|
||||
offsets.push(offset);
|
||||
}
|
||||
}
|
||||
assert(values.length > 0, 'no tests run');
|
||||
|
||||
const access = testcase.access ?? '';
|
||||
const addrOf = access === '' ? '' : '&';
|
||||
|
||||
const enables = testcase.f16 === true ? 'enable f16;' : '';
|
||||
|
||||
const wgsl = `
|
||||
${enables}
|
||||
|
||||
${kStructDecls}
|
||||
|
||||
override bufferSize : u32 = 0u;
|
||||
${decl}
|
||||
@group(0) @binding(1) var<storage> offsets : array<u32>;
|
||||
@group(0) @binding(2) var<storage> sizes : array<u32>; // unused
|
||||
@group(0) @binding(3) var<storage, read_write> out : array<u32>;
|
||||
|
||||
override wgx : u32;
|
||||
@compute @workgroup_size(wgx)
|
||||
fn main(@builtin(global_invocation_id) gid : vec3u) {
|
||||
${t.params.aspace === 'workgroup' ? '_ = dummy[0];' : ''}
|
||||
|
||||
if gid.x >= ${values.length} {
|
||||
return;
|
||||
}
|
||||
|
||||
out[gid.x] = arrayLength(${addrOf}bufferView<${testcase.type}>(&v, offsets[gid.x])${access});
|
||||
}`;
|
||||
|
||||
runLengthTest(
|
||||
t,
|
||||
wgsl,
|
||||
t.params.aspace === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE,
|
||||
t.params.bufferSize,
|
||||
t.params.dynamic_offset,
|
||||
offsets,
|
||||
sizes,
|
||||
values
|
||||
);
|
||||
});
|
||||
|
||||
g.test('array_length,functions')
|
||||
.desc('Tests arrayLength from a bufferView through various function calls')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('type', keysOf(kArrayLengthTypes))
|
||||
.combine('aspace', ['workgroup', 'storage', 'uniform'] as const)
|
||||
.beginSubcases()
|
||||
.combine('call', kCalls)
|
||||
.combine('bufferSize', kBufferSizes)
|
||||
.combine('dynamic_offset', [0, 256] as const)
|
||||
.filter(t => {
|
||||
if (t.type === 'array_S_2' || t.type === 'mat4x4f' || t.type === 'mat4x3f') {
|
||||
return t.bufferSize > 128;
|
||||
}
|
||||
return t.aspace !== 'workgroup' || t.dynamic_offset === 0;
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
const testcase = kArrayLengthTypes[t.params.type];
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
|
||||
if (t.params.aspace === 'uniform' && testcase.uniformStdLayout === true) {
|
||||
t.skipIfLanguageFeatureNotSupported('uniform_buffer_standard_layout');
|
||||
}
|
||||
if (testcase.f16) {
|
||||
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
|
||||
}
|
||||
|
||||
const buffer_ty = `buffer<${t.params.bufferSize}>`;
|
||||
let decl = '';
|
||||
switch (t.params.aspace) {
|
||||
case 'workgroup':
|
||||
decl = `var<workgroup> v : ${buffer_ty};\n@group(0) @binding(0) var<storage> dummy : array<u32>;`;
|
||||
break;
|
||||
case 'uniform':
|
||||
decl = `@group(0) @binding(0) var<uniform> v : ${buffer_ty};`;
|
||||
break;
|
||||
case 'storage':
|
||||
decl = `@group(0) @binding(0) var<storage> v : ${buffer_ty};`;
|
||||
break;
|
||||
}
|
||||
|
||||
const values: number[] = [];
|
||||
const offsets: number[] = [];
|
||||
const sizes: number[] = [0];
|
||||
for (const offset of kOffsets) {
|
||||
const usedSize = t.params.call === 'unsized' ? t.params.bufferSize : t.params.bufferSize / 2;
|
||||
// Skip any case that results in an invalid memory reference.
|
||||
// Split the writes up one per thread.
|
||||
if (isValidArrayLengthCase(testcase, offset, 0, usedSize)) {
|
||||
values.push(calculateArrayLength(testcase, offset, 0, usedSize));
|
||||
// Setup the offset and size buffer values so they can be indexed by invocation id.
|
||||
offsets.push(offset);
|
||||
}
|
||||
}
|
||||
|
||||
assert(values.length > 0, 'no tests run');
|
||||
|
||||
const access = testcase.access ?? '';
|
||||
const addrOf = access === '' ? '' : '&';
|
||||
|
||||
const enables = testcase.f16 === true ? 'enable f16;' : '';
|
||||
|
||||
const wgsl = `
|
||||
${enables}
|
||||
|
||||
${kStructDecls}
|
||||
|
||||
fn unsizedLength(p : ptr<${t.params.aspace}, buffer>, gidx : u32) -> u32 {
|
||||
return arrayLength(${addrOf}bufferView<${testcase.type}>(p, offsets[gidx])${access});
|
||||
}
|
||||
|
||||
fn sizedLength(p : ptr<${t.params.aspace}, buffer<${t.params.bufferSize / 2}>>, gidx : u32) -> u32 {
|
||||
return arrayLength(${addrOf}bufferView<${testcase.type}>(p, offsets[gidx])${access});
|
||||
}
|
||||
|
||||
fn sized_indirectLength(p : ptr<${t.params.aspace}, buffer<${
|
||||
t.params.bufferSize / 2
|
||||
}>>, gidx : u32) -> u32 {
|
||||
return unsizedLength(p, gidx);
|
||||
}
|
||||
|
||||
fn sized_sized_indirectLength(p : ptr<${t.params.aspace}, buffer<${
|
||||
t.params.bufferSize
|
||||
}>>, gidx : u32) -> u32 {
|
||||
return sizedLength(p, gidx);
|
||||
}
|
||||
|
||||
override bufferSize : u32 = 0u;
|
||||
${decl}
|
||||
@group(0) @binding(1) var<storage> offsets : array<u32>;
|
||||
@group(0) @binding(2) var<storage> sizes : array<u32>; // unused
|
||||
@group(0) @binding(3) var<storage, read_write> out : array<u32>;
|
||||
|
||||
override wgx : u32;
|
||||
@compute @workgroup_size(wgx)
|
||||
fn main(@builtin(global_invocation_id) gid : vec3u) {
|
||||
${t.params.aspace === 'workgroup' ? '_ = dummy[0];' : ''}
|
||||
|
||||
if gid.x >= ${values.length} {
|
||||
return;
|
||||
}
|
||||
|
||||
out[gid.x] = ${t.params.call}Length(&v, gid.x);
|
||||
}`;
|
||||
|
||||
runLengthTest(
|
||||
t,
|
||||
wgsl,
|
||||
t.params.aspace === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE,
|
||||
t.params.bufferSize,
|
||||
t.params.dynamic_offset,
|
||||
offsets,
|
||||
sizes,
|
||||
values
|
||||
);
|
||||
});
|
||||
|
||||
g.test('read_layout')
|
||||
.desc('Test reading memory layout from a bufferView')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('case', keysOf(kLayoutCases))
|
||||
.beginSubcases()
|
||||
.combine('aspace', ['storage', 'ro_storage', 'uniform', 'workgroup'] as const)
|
||||
.combine('offset', kOffsets)
|
||||
.filter(t => {
|
||||
const testcase = kLayoutCases[t.case];
|
||||
if ((t.offset & (testcase.align - 1)) !== 0) {
|
||||
return false;
|
||||
}
|
||||
return testcase.offset + t.offset < 252;
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
const testcase = kLayoutCases[t.params.case];
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (t.params.aspace === 'uniform' && testcase.uniformStdLayoutView === true) {
|
||||
t.skipIfLanguageFeatureNotSupported('uniform_buffer_standard_layout');
|
||||
}
|
||||
if (testcase.f16) {
|
||||
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
|
||||
}
|
||||
|
||||
const enables = testcase.f16 === true ? 'enable f16;' : '';
|
||||
const v = t.params.aspace === 'workgroup' ? 'wg_var' : 'in';
|
||||
|
||||
const wgsl = `
|
||||
${enables}
|
||||
|
||||
${testcase.decl ?? ''}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<${t.params.aspace === 'uniform' ? 'uniform' : 'storage'}${
|
||||
t.params.aspace === 'storage' ? ', read_write' : ''
|
||||
}> in : buffer<256 * 4>;
|
||||
|
||||
@group(0) @binding(1) var<storage, read_write> out : u32;
|
||||
|
||||
var<workgroup> wg_var : buffer<256 * 4>;
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let in_ptr = bufferView<array<vec4u, 256 / 4>>(&in, 0);
|
||||
let wg_ptr = bufferView<array<vec4u, 256 / 4>>(&wg_var, 0);
|
||||
for (var i = 0; i < 256 / 4; i++) {
|
||||
(*wg_ptr)[i] = (*in_ptr)[i];
|
||||
}
|
||||
|
||||
workgroupBarrier();
|
||||
|
||||
let p = bufferView<${testcase.type}>(&${v}, ${t.params.offset});
|
||||
out = u32((*p)${testcase.access});
|
||||
}
|
||||
`;
|
||||
|
||||
runReadLayoutTest(t, testcase, wgsl, t.params.aspace, t.params.offset);
|
||||
});
|
||||
|
||||
g.test('write_layout')
|
||||
.desc('Test writing memory layout via a bufferView')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('case', keysOf(kLayoutCases))
|
||||
.beginSubcases()
|
||||
.combine('assign', ['let', 'call'] as const)
|
||||
.combine('aspace', ['storage', 'workgroup'] as const)
|
||||
.combine('offset', kOffsets)
|
||||
.filter(t => {
|
||||
const testcase = kLayoutCases[t.case];
|
||||
if ((t.offset & (testcase.align - 1)) !== 0) {
|
||||
return false;
|
||||
}
|
||||
return testcase.offset + t.offset < 252;
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
const testcase = kLayoutCases[t.params.case];
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (testcase.f16) {
|
||||
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
|
||||
}
|
||||
|
||||
const enables = testcase.f16 === true ? 'enable f16;' : '';
|
||||
const v = t.params.aspace === 'workgroup' ? 'wg_var' : 'out';
|
||||
const call_expr = `bufferView<${testcase.type}>(&${v}, ${t.params.offset})`;
|
||||
|
||||
let post_assign = '';
|
||||
if (t.params.aspace === 'workgroup') {
|
||||
post_assign = `
|
||||
workgroupBarrier();
|
||||
|
||||
let out_ptr = bufferView<array<u32>>(&out, 0);
|
||||
let wg_ptr = bufferView<array<u32>>(&wg_var, 0);
|
||||
for (var i = 0; i < 256; i++) {
|
||||
(*out_ptr)[i] = (*wg_ptr)[i];
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
const wgsl = `
|
||||
${enables}
|
||||
|
||||
${testcase.decl ?? ''}
|
||||
|
||||
@group(0) @binding(0) var<storage> in : u32;
|
||||
@group(0) @binding(1) var<storage, read_write> out : buffer<256 * 4>;
|
||||
|
||||
|
||||
var<workgroup> wg_var : buffer<256 * 4>;
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let val = ${testcase.f16 ? 'f16' : testcase.f32 ? 'f32' : 'u32'}(in);
|
||||
let ptr = ${call_expr};
|
||||
(*${t.params.assign === 'call' ? call_expr : 'ptr'})${testcase.access} = val;
|
||||
|
||||
${post_assign}
|
||||
}
|
||||
`;
|
||||
|
||||
runWriteLayoutTest(t, testcase, wgsl, t.params.offset);
|
||||
});
|
||||
|
||||
g.test('read')
|
||||
.desc('Test reading various types from bufferView')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('base_type', ['u32', 'i32', 'f32', 'f16'] as const)
|
||||
.beginSubcases()
|
||||
.combine('wrap', ['none', 'vector', 'array', 'matrix'] as const)
|
||||
.combine('width', [1, 2, 3, 4] as const)
|
||||
.combine('aspace', ['workgroup', 'storage', 'ro_storage', 'uniform'] as const)
|
||||
.combine('offset', [0, 4, 8, 12, 16, 32, 48, 64] as const)
|
||||
.filter(t => {
|
||||
if (t.wrap !== 'none' && t.width === 1) {
|
||||
return false;
|
||||
}
|
||||
if (t.wrap === 'none' && t.width !== 1) {
|
||||
return false;
|
||||
}
|
||||
if (t.wrap === 'matrix' && t.base_type !== 'f32' && t.base_type !== 'f16') {
|
||||
return false;
|
||||
}
|
||||
if (t.aspace === 'uniform' && t.wrap === 'array') {
|
||||
return false;
|
||||
}
|
||||
const ty = Type[t.base_type];
|
||||
let align = ty.alignment;
|
||||
switch (t.wrap) {
|
||||
case 'vector':
|
||||
align = Type.vec(t.width, ty).alignment;
|
||||
break;
|
||||
case 'array':
|
||||
align = Type.array(2, ty).alignment;
|
||||
break;
|
||||
case 'matrix':
|
||||
align = Type.mat(t.width, 2, ty).alignment;
|
||||
break;
|
||||
case 'none':
|
||||
break;
|
||||
}
|
||||
if (t.offset % align !== 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (t.params.base_type === 'f16') {
|
||||
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
|
||||
}
|
||||
|
||||
const ele_ty = Type[t.params.base_type];
|
||||
let ty: Type = ele_ty;
|
||||
switch (t.params.wrap) {
|
||||
case 'vector':
|
||||
ty = Type.vec(t.params.width, ele_ty);
|
||||
break;
|
||||
case 'array':
|
||||
ty = Type.array(t.params.width, ele_ty);
|
||||
break;
|
||||
case 'matrix':
|
||||
ty = Type.mat(t.params.width, 2, ele_ty);
|
||||
break;
|
||||
case 'none':
|
||||
break;
|
||||
}
|
||||
|
||||
const bufferSize = 128;
|
||||
const enables = t.params.base_type === 'f16' ? 'enable f16;' : '';
|
||||
const v = t.params.aspace === 'workgroup' ? 'wg_var' : 'in';
|
||||
const wgsl = `
|
||||
${enables}
|
||||
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<${t.params.aspace === 'uniform' ? 'uniform' : 'storage'}${
|
||||
t.params.aspace === 'storage' ? ', read_write' : ''
|
||||
}> in : buffer<${bufferSize}>;
|
||||
|
||||
@group(0) @binding(1) var<storage, read_write> out : ${ty.toString()};
|
||||
|
||||
var<workgroup> wg_var : buffer<${bufferSize}>;
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let in_ptr = bufferView<array<vec4u, ${bufferSize} / 16>>(&in, 0);
|
||||
let wg_ptr = bufferView<array<vec4u, ${bufferSize} / 16>>(&wg_var, 0);
|
||||
for (var i = 0; i < 256 / 4; i++) {
|
||||
(*wg_ptr)[i] = (*in_ptr)[i];
|
||||
}
|
||||
|
||||
workgroupBarrier();
|
||||
|
||||
let p = bufferView<${ty.toString()}>(&${v}, ${t.params.offset});
|
||||
out = (*p);
|
||||
}
|
||||
`;
|
||||
|
||||
runReadWriteTest(true, t, wgsl, ele_ty, ty, t.params.aspace, t.params.offset, bufferSize);
|
||||
});
|
||||
|
||||
g.test('write')
|
||||
.desc('Test writing various types via bufferView')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('base_type', ['u32', 'i32', 'f32', 'f16'] as const)
|
||||
.beginSubcases()
|
||||
.combine('wrap', ['none', 'vector', 'array', 'matrix'] as const)
|
||||
.combine('width', [1, 2, 3, 4] as const)
|
||||
.combine('aspace', ['workgroup', 'storage'] as const)
|
||||
.combine('offset', [0, 4, 8, 12, 16, 32, 48, 64] as const)
|
||||
.combine('assign', ['let', 'call'] as const)
|
||||
.combine('swizzle', [false, true] as const)
|
||||
.filter(t => {
|
||||
if (t.wrap !== 'none' && t.width === 1) {
|
||||
return false;
|
||||
}
|
||||
if (t.wrap === 'none' && t.width !== 1) {
|
||||
return false;
|
||||
}
|
||||
if (t.wrap === 'matrix' && t.base_type !== 'f32' && t.base_type !== 'f16') {
|
||||
return false;
|
||||
}
|
||||
if (t.wrap !== 'vector' && t.swizzle) {
|
||||
return false;
|
||||
}
|
||||
const ty = Type[t.base_type];
|
||||
let align = ty.alignment;
|
||||
switch (t.wrap) {
|
||||
case 'vector':
|
||||
align = Type.vec(t.width, ty).alignment;
|
||||
break;
|
||||
case 'array':
|
||||
align = Type.array(2, ty).alignment;
|
||||
break;
|
||||
case 'matrix':
|
||||
align = Type.mat(t.width, 2, ty).alignment;
|
||||
break;
|
||||
case 'none':
|
||||
break;
|
||||
}
|
||||
if (t.offset % align !== 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (t.params.base_type === 'f16') {
|
||||
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
|
||||
}
|
||||
if (t.params.swizzle) {
|
||||
t.skipIfLanguageFeatureNotSupported('swizzle_assignment');
|
||||
}
|
||||
|
||||
let swizzle = '';
|
||||
const ele_ty = Type[t.params.base_type];
|
||||
let ty: Type = ele_ty;
|
||||
switch (t.params.wrap) {
|
||||
case 'vector':
|
||||
ty = Type.vec(t.params.width, ele_ty);
|
||||
if (t.params.swizzle) {
|
||||
swizzle = '.xyzw'.substring(0, 1 + t.params.width);
|
||||
}
|
||||
break;
|
||||
case 'array':
|
||||
ty = Type.array(t.params.width, ele_ty);
|
||||
break;
|
||||
case 'matrix':
|
||||
ty = Type.mat(t.params.width, 2, ele_ty);
|
||||
break;
|
||||
case 'none':
|
||||
break;
|
||||
}
|
||||
|
||||
const bufferSize = 128;
|
||||
const enables = t.params.base_type === 'f16' ? 'enable f16;' : '';
|
||||
const v = t.params.aspace === 'workgroup' ? 'wg_var' : 'out';
|
||||
|
||||
let post_assign = '';
|
||||
if (t.params.aspace === 'workgroup') {
|
||||
post_assign = `
|
||||
workgroupBarrier();
|
||||
|
||||
let out_ptr = bufferView<array<u32>>(&out, 0);
|
||||
let wg_ptr = bufferView<array<u32>>(&wg_var, 0);
|
||||
for (var i = 0; i < 256; i++) {
|
||||
(*out_ptr)[i] = (*wg_ptr)[i];
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
const call_expr = `bufferView<${ty.toString()}>(&${v}, ${t.params.offset})`;
|
||||
const wgsl = `
|
||||
${enables}
|
||||
|
||||
@group(0) @binding(0) var<storage> in : ${ty.toString()};
|
||||
@group(0) @binding(1) var<storage, read_write> out : buffer<${bufferSize}>;
|
||||
|
||||
|
||||
var<workgroup> wg_var : buffer<${bufferSize}>;
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let ptr = ${call_expr};
|
||||
(*${t.params.assign === 'call' ? call_expr : 'ptr'})${swizzle} = in;
|
||||
|
||||
${post_assign}
|
||||
}
|
||||
`;
|
||||
|
||||
runReadWriteTest(false, t, wgsl, ele_ty, ty, t.params.aspace, t.params.offset, bufferSize);
|
||||
});
|
||||
|
|
@ -0,0 +1,975 @@
|
|||
import {
|
||||
iterRange,
|
||||
typedArrayParam,
|
||||
typedArrayFromParam,
|
||||
TypedArrayBufferView,
|
||||
} from '../../../../../../common/util/util.js';
|
||||
import { GPUTest } from '../../../../../gpu_test.js';
|
||||
import {
|
||||
Type,
|
||||
ScalarType,
|
||||
ArrayType,
|
||||
MatrixType,
|
||||
VectorType,
|
||||
} from '../../../../../util/conversion.js';
|
||||
|
||||
export const kBufferSizes = [128, 256, 512, 1024] as const;
|
||||
export const kOffsets = [0, 1, 2, 3, 4, 8, 12, 16, 32, 100, 156, 480, 768] as const;
|
||||
export const kSizes = [...kBufferSizes, 32, 48, 64] as const;
|
||||
export const kCalls = ['unsized', 'sized', 'sized_indirect', 'sized_sized_indirect'] as const;
|
||||
|
||||
interface ArrayLengthType {
|
||||
type: string;
|
||||
access?: string;
|
||||
align: number;
|
||||
stride: number;
|
||||
arrayOffset?: number;
|
||||
f16?: boolean;
|
||||
uniformStdLayout?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the case is valid
|
||||
*
|
||||
* @param type The testcase
|
||||
* @param offset The offset to the view call
|
||||
* @param size The size to the view call
|
||||
* @param bufferSize The size of the buffer
|
||||
* @returns true if case is valid.
|
||||
*/
|
||||
export function isValidArrayLengthCase(
|
||||
type: ArrayLengthType,
|
||||
offset: number,
|
||||
size: number,
|
||||
bufferSize: number
|
||||
): boolean {
|
||||
if (size === 0) {
|
||||
// bufferView case
|
||||
return offset + (type.arrayOffset ?? 0) + type.stride <= bufferSize;
|
||||
} else {
|
||||
// bufferArrayView case
|
||||
if ((type.arrayOffset ?? 0) + type.stride > size) {
|
||||
return false;
|
||||
}
|
||||
return offset + size <= bufferSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the return length in elements
|
||||
*
|
||||
* @param type The testcase
|
||||
* @param offset The offset to the view call
|
||||
* @param size The size to the view call
|
||||
* @param bufferSize The buffer size
|
||||
* @returns The number of elements in the arrayLength call
|
||||
*/
|
||||
export function calculateArrayLength(
|
||||
type: ArrayLengthType,
|
||||
offset: number,
|
||||
size: number,
|
||||
bufferSize: number
|
||||
): number {
|
||||
const alignOffset = offset & ~(type.align - 1);
|
||||
if (size === 0) {
|
||||
// bufferView case
|
||||
return Math.floor((bufferSize - alignOffset - (type.arrayOffset ?? 0)) / type.stride);
|
||||
} else {
|
||||
// bufferArrayView case
|
||||
return Math.floor((size - (type.arrayOffset ?? 0)) / type.stride);
|
||||
}
|
||||
}
|
||||
|
||||
export const kStructDecls = `
|
||||
struct S {
|
||||
a: vec4u,
|
||||
b: u32,
|
||||
}
|
||||
struct T {
|
||||
a : u32,
|
||||
b : array<u32, 5>
|
||||
}
|
||||
struct S_arr {
|
||||
a : vec4u,
|
||||
b : array<u32>,
|
||||
}
|
||||
struct T_arr {
|
||||
a : S,
|
||||
b : array<T>,
|
||||
}
|
||||
`;
|
||||
|
||||
export const kArrayLengthTypes: Record<string, ArrayLengthType> = {
|
||||
// Scalars and vectors
|
||||
u32: {
|
||||
type: 'array<u32>',
|
||||
align: 4,
|
||||
stride: 4,
|
||||
uniformStdLayout: true,
|
||||
},
|
||||
vec2f: {
|
||||
type: 'array<vec2f>',
|
||||
align: 8,
|
||||
stride: 8,
|
||||
uniformStdLayout: true,
|
||||
},
|
||||
vec3i: {
|
||||
type: 'array<vec3i>',
|
||||
align: 16,
|
||||
stride: 16,
|
||||
},
|
||||
vec4u: {
|
||||
type: 'array<vec4u>',
|
||||
align: 16,
|
||||
stride: 16,
|
||||
},
|
||||
f16: {
|
||||
type: 'array<f16>',
|
||||
align: 2,
|
||||
stride: 2,
|
||||
f16: true,
|
||||
uniformStdLayout: true,
|
||||
},
|
||||
vec2h: {
|
||||
type: 'array<vec2h>',
|
||||
align: 4,
|
||||
stride: 4,
|
||||
f16: true,
|
||||
uniformStdLayout: true,
|
||||
},
|
||||
vec3h: {
|
||||
type: 'array<vec3h>',
|
||||
align: 8,
|
||||
stride: 8,
|
||||
f16: true,
|
||||
uniformStdLayout: true,
|
||||
},
|
||||
vec4h: {
|
||||
type: 'array<vec4h>',
|
||||
align: 8,
|
||||
stride: 8,
|
||||
f16: true,
|
||||
uniformStdLayout: true,
|
||||
},
|
||||
// Matrices
|
||||
mat2x2f: {
|
||||
type: 'array<mat2x2f>',
|
||||
align: 8,
|
||||
stride: 16,
|
||||
},
|
||||
mat2x3f: {
|
||||
type: 'array<mat2x3f>',
|
||||
align: 16,
|
||||
stride: 32,
|
||||
},
|
||||
mat2x4f: {
|
||||
type: 'array<mat2x4f>',
|
||||
align: 16,
|
||||
stride: 32,
|
||||
},
|
||||
mat4x2f: {
|
||||
type: 'array<mat4x2f>',
|
||||
align: 8,
|
||||
stride: 32,
|
||||
},
|
||||
mat4x3f: {
|
||||
type: 'array<mat4x3f>',
|
||||
align: 16,
|
||||
stride: 64,
|
||||
},
|
||||
mat4x4f: {
|
||||
type: 'array<mat4x4f>',
|
||||
align: 16,
|
||||
stride: 64,
|
||||
},
|
||||
mat3x2h: {
|
||||
type: 'array<mat3x2h>',
|
||||
align: 4,
|
||||
stride: 12,
|
||||
f16: true,
|
||||
},
|
||||
mat3x3h: {
|
||||
type: 'array<mat3x3h>',
|
||||
align: 8,
|
||||
stride: 24,
|
||||
f16: true,
|
||||
uniformStdLayout: true,
|
||||
},
|
||||
mat3x4h: {
|
||||
type: 'array<mat3x4h>',
|
||||
align: 8,
|
||||
stride: 24,
|
||||
f16: true,
|
||||
uniformStdLayout: true,
|
||||
},
|
||||
// Structs without arrays
|
||||
S: {
|
||||
type: 'array<S>',
|
||||
align: 16,
|
||||
stride: 32,
|
||||
},
|
||||
T: {
|
||||
type: 'array<T>',
|
||||
align: 4,
|
||||
stride: 24,
|
||||
uniformStdLayout: true,
|
||||
},
|
||||
// Sized arrays
|
||||
array_u32_4: {
|
||||
type: 'array<array<u32, 4>>',
|
||||
align: 4,
|
||||
stride: 16,
|
||||
uniformStdLayout: true,
|
||||
},
|
||||
array_f16_6: {
|
||||
type: 'array<array<f16, 6>>',
|
||||
align: 2,
|
||||
stride: 12,
|
||||
f16: true,
|
||||
uniformStdLayout: true,
|
||||
},
|
||||
array_vec2f_2: {
|
||||
type: 'array<array<vec2f, 2>>',
|
||||
align: 8,
|
||||
stride: 16,
|
||||
uniformStdLayout: true,
|
||||
},
|
||||
array_S_2: {
|
||||
type: 'array<array<S, 2>>',
|
||||
align: 16,
|
||||
stride: 64,
|
||||
},
|
||||
array_T_2: {
|
||||
type: 'array<array<T, 2>>',
|
||||
align: 4,
|
||||
stride: 48,
|
||||
},
|
||||
// Structs with runtime arrays
|
||||
S_arr: {
|
||||
type: 'S_arr',
|
||||
access: '.b',
|
||||
align: 16,
|
||||
stride: 4,
|
||||
arrayOffset: 16,
|
||||
},
|
||||
T_arr: {
|
||||
type: 'T_arr',
|
||||
access: '.b',
|
||||
align: 16,
|
||||
stride: 24,
|
||||
arrayOffset: 32,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Run arrayLength tests for bufferView and bufferArrayView
|
||||
*
|
||||
* @param t The test
|
||||
* @param wgsl The shader code. Interface requirements:
|
||||
* * Overrides:
|
||||
* * wgx: workgroup size x
|
||||
* * bufferSize: size of the buffer
|
||||
* * (0, 0): input buffer with 'usage' usage
|
||||
* * (0, 1): read-only-storage-buffer for offsets
|
||||
* * (0, 2): read-only-storage-buffer for sizes
|
||||
* * (0, 3): storage-buffer for output
|
||||
* @param usage The test buffer usage
|
||||
* @param bufferSize The size of the test buffer
|
||||
* @param dynOffset The size of the dynamic offset for the test buffer
|
||||
* @param offsets The values for the offset buffer
|
||||
* @param sizes The values for the size buffer
|
||||
* @param values The expected results
|
||||
*/
|
||||
export function runLengthTest(
|
||||
t: GPUTest,
|
||||
wgsl: string,
|
||||
usage: GPUBufferUsageFlags,
|
||||
bufferSize: number,
|
||||
dynOffset: number,
|
||||
offsets: number[],
|
||||
sizes: number[],
|
||||
values: number[]
|
||||
) {
|
||||
const fullBufferSize = bufferSize + dynOffset;
|
||||
const inputBuffer = t.createBufferTracked({
|
||||
size: fullBufferSize,
|
||||
usage,
|
||||
});
|
||||
const offsetBuffer = t.makeBufferWithContents(
|
||||
new Uint32Array(offsets),
|
||||
GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
||||
);
|
||||
const sizeBuffer = t.makeBufferWithContents(
|
||||
new Uint32Array(sizes),
|
||||
GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
||||
);
|
||||
const outputBuffer = t.createBufferTracked({
|
||||
size: values.length * 4,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
|
||||
const bgLayout = t.device.createBindGroupLayout({
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: usage === GPUBufferUsage.UNIFORM ? 'uniform' : 'read-only-storage',
|
||||
hasDynamicOffset: dynOffset !== 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'read-only-storage',
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'read-only-storage',
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 3,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'storage',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Limit the number of invocations per workgroup to 128 to fit the default compat limits. Launch extra workgroups to fit all the required invocations.
|
||||
const wgx = Math.min(values.length, 128);
|
||||
const num_wgs = Math.ceil(values.length / wgx);
|
||||
|
||||
const pipelineLayout = t.device.createPipelineLayout({
|
||||
bindGroupLayouts: [bgLayout],
|
||||
});
|
||||
const pipeline = t.device.createComputePipeline({
|
||||
layout: pipelineLayout,
|
||||
compute: {
|
||||
module: t.device.createShaderModule({ code: wgsl }),
|
||||
constants: { wgx, bufferSize },
|
||||
},
|
||||
});
|
||||
|
||||
const bg = t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer: inputBuffer,
|
||||
size: bufferSize,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
resource: {
|
||||
buffer: offsetBuffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
resource: {
|
||||
buffer: sizeBuffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 3,
|
||||
resource: {
|
||||
buffer: outputBuffer,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginComputePass();
|
||||
pass.setPipeline(pipeline);
|
||||
pass.setBindGroup(0, bg, dynOffset === 0 ? [] : [dynOffset]);
|
||||
pass.dispatchWorkgroups(num_wgs, 1, 1);
|
||||
pass.end();
|
||||
t.queue.submit([encoder.finish()]);
|
||||
|
||||
t.expectGPUBufferValuesEqual(outputBuffer, new Uint32Array(values));
|
||||
}
|
||||
|
||||
interface LayoutCase {
|
||||
type: string;
|
||||
decl?: string;
|
||||
access: string;
|
||||
align: number;
|
||||
offset: number;
|
||||
f16?: boolean;
|
||||
f32?: boolean;
|
||||
uniformStdLayoutView?: boolean;
|
||||
uniformStdLayoutArrayView?: boolean;
|
||||
}
|
||||
|
||||
export const kLayoutCases: Record<string, LayoutCase> = {
|
||||
vec2u_align8: {
|
||||
type: 'S_vec2u_align',
|
||||
decl: 'struct S_vec2u_align { x : u32, y : vec2u, }',
|
||||
access: '.y[1]',
|
||||
align: 8,
|
||||
offset: 12,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
vec3u_align16: {
|
||||
type: 'S_vec3u_align',
|
||||
decl: 'struct S_vec3u_align { x : u32, y : vec3u, }',
|
||||
access: '.y[2]',
|
||||
align: 16,
|
||||
offset: 24,
|
||||
},
|
||||
vec4u_align16: {
|
||||
type: 'S_vec4u_align',
|
||||
decl: 'struct S_vec4u_align { x : u32, y : vec4u, }',
|
||||
access: '.y[0]',
|
||||
align: 16,
|
||||
offset: 16,
|
||||
},
|
||||
struct_align32: {
|
||||
type: 'S_align32',
|
||||
decl: 'struct S_align32 { x : u32, @align(32) y : u32, }',
|
||||
access: '.y',
|
||||
align: 32,
|
||||
offset: 32,
|
||||
},
|
||||
vec2h_align4: {
|
||||
type: 'S_vec2h_align',
|
||||
decl: 'struct S_vec2h_align { x : f16, y : vec2h }',
|
||||
access: '.y[0]',
|
||||
align: 4,
|
||||
offset: 4,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
vec3h_align8: {
|
||||
type: 'S_vec3h_align',
|
||||
decl: 'struct S_vec3h_align { x : f16, y : vec3h }',
|
||||
access: '.y.z',
|
||||
align: 8,
|
||||
offset: 12,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
vec4h_align8: {
|
||||
type: 'S_vec4h_align',
|
||||
decl: 'struct S_vec4h_align { x : f16, y : vec4h }',
|
||||
access: '.y.z',
|
||||
align: 8,
|
||||
offset: 12,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
vec3i_size12: {
|
||||
type: 'S_vec3i_size',
|
||||
decl: 'struct S_vec3i_size { x : vec3i, y : u32 }',
|
||||
access: '.y',
|
||||
align: 16,
|
||||
offset: 12,
|
||||
},
|
||||
vec3h_size6: {
|
||||
type: 'S_vec3h_size',
|
||||
decl: 'struct S_vec3h_size { x : vec3h, y : f16, z : f16 }',
|
||||
access: '.z',
|
||||
align: 8,
|
||||
offset: 8,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
size80: {
|
||||
type: 'S_size80',
|
||||
decl: 'struct S_size80 { @size(80) x : u32, y : u32 }',
|
||||
access: '.y',
|
||||
align: 4,
|
||||
offset: 80,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
mat2x2f_align8: {
|
||||
type: 'S_mat2x2f_align',
|
||||
decl: 'struct S_mat2x2f_align { x : u32, y : mat2x2f }',
|
||||
access: '.y[0][0]',
|
||||
align: 8,
|
||||
offset: 8,
|
||||
f32: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
mat3x3f_align16: {
|
||||
type: 'S_mat3x3f_align',
|
||||
decl: 'struct S_mat3x3f_align { x : u32, y : mat3x3f }',
|
||||
access: '.y[0][0]',
|
||||
align: 16,
|
||||
offset: 16,
|
||||
f32: true,
|
||||
},
|
||||
mat4x4f_align16: {
|
||||
type: 'S_mat4x4f_align',
|
||||
decl: 'struct S_mat4x4f_align { x : u32, y : mat4x4f }',
|
||||
access: '.y[1][0]',
|
||||
align: 16,
|
||||
offset: 32,
|
||||
f32: true,
|
||||
},
|
||||
mat3x2h_align4: {
|
||||
type: 'S_mat3x2h_align',
|
||||
decl: 'struct S_mat3x2h_align { x : f16, y : mat3x2h }',
|
||||
access: '.y[0][0]',
|
||||
align: 4,
|
||||
offset: 4,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
mat4x3h_align8: {
|
||||
type: 'S_mat4x3h_align',
|
||||
decl: 'struct S_mat4x3h_align { x : f16, y : mat4x3h }',
|
||||
access: '.y[0][0]',
|
||||
align: 8,
|
||||
offset: 8,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
mat2x4h_align8: {
|
||||
type: 'S_mat2x4h_align',
|
||||
decl: 'struct S_mat2x4h_align { x : f16, y : mat2x4h }',
|
||||
access: '.y[0][0]',
|
||||
align: 8,
|
||||
offset: 8,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
mat2x2f_size: {
|
||||
type: 'S_mat2x2f_size',
|
||||
decl: 'struct S_mat2x2f_size { x : mat2x2f, y : u32 }',
|
||||
access: '.y',
|
||||
align: 8,
|
||||
offset: 16,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
mat3x2f_size: {
|
||||
type: 'S_mat3x2f_size',
|
||||
decl: 'struct S_mat3x2f_size { x : mat3x2f, y : u32 }',
|
||||
access: '.y',
|
||||
align: 8,
|
||||
offset: 24,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
mat2x3f_size: {
|
||||
type: 'S_mat2x3f_size',
|
||||
decl: 'struct S_mat2x3f_size { x : mat2x3f, y : u32 }',
|
||||
access: '.y',
|
||||
align: 16,
|
||||
offset: 32,
|
||||
},
|
||||
mat3x3f_size: {
|
||||
type: 'S_mat3x3f_size',
|
||||
decl: 'struct S_mat3x3f_size { x : mat3x3f, y : u32 }',
|
||||
access: '.y',
|
||||
align: 16,
|
||||
offset: 48,
|
||||
},
|
||||
mat2x4f_size: {
|
||||
type: 'S_mat2x4f_size',
|
||||
decl: 'struct S_mat2x4f_size { x : mat2x4f, y : u32 }',
|
||||
access: '.y',
|
||||
align: 16,
|
||||
offset: 32,
|
||||
},
|
||||
mat3x4f_size: {
|
||||
type: 'S_mat3x4f_size',
|
||||
decl: 'struct S_mat3x4f_size { x : mat3x4f, y : u32 }',
|
||||
access: '.y',
|
||||
align: 16,
|
||||
offset: 48,
|
||||
},
|
||||
mat2x2h_size: {
|
||||
type: 'S_mat2x2h_size',
|
||||
decl: 'struct S_mat2x2h_size { x : mat2x2h, y : f16 }',
|
||||
access: '.y',
|
||||
align: 4,
|
||||
offset: 8,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
mat4x2h_size: {
|
||||
type: 'S_mat4x2h_size',
|
||||
decl: 'struct S_mat4x2h_size { x : mat4x2h, y : f16 }',
|
||||
access: '.y',
|
||||
align: 4,
|
||||
offset: 16,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
mat2x3h_size: {
|
||||
type: 'S_mat2x3h_size',
|
||||
decl: 'struct S_mat2x3h_size { x : mat2x3h, y : f16 }',
|
||||
access: '.y',
|
||||
align: 8,
|
||||
offset: 16,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
mat4x3h_size: {
|
||||
type: 'S_mat4x3h_size',
|
||||
decl: 'struct S_mat4x3h_size { x : mat4x3h, y : f16 }',
|
||||
access: '.y',
|
||||
align: 8,
|
||||
offset: 32,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
mat2x4h_size: {
|
||||
type: 'S_mat2x4h_size',
|
||||
decl: 'struct S_mat2x4h_size { x : mat2x4h, y : f16 }',
|
||||
access: '.y',
|
||||
align: 8,
|
||||
offset: 16,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
mat4x4h_size: {
|
||||
type: 'S_mat4x4h_size',
|
||||
decl: 'struct S_mat4x4h_size { x : mat4x4h, y : f16 }',
|
||||
access: '.y',
|
||||
align: 8,
|
||||
offset: 32,
|
||||
f16: true,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
struct_size_roundup: {
|
||||
type: 'S_struct_size_roundup',
|
||||
decl: `struct Inner { x : vec3u }
|
||||
struct S_struct_size_roundup { x : Inner, y : u32 }`,
|
||||
access: '.y',
|
||||
align: 16,
|
||||
offset: 16,
|
||||
},
|
||||
struct_inner_size: {
|
||||
type: 'S_struct_inner_size',
|
||||
decl: `struct Inner { @size(112) x : u32 }
|
||||
struct S_struct_inner_size { x : Inner, y : u32 }`,
|
||||
access: '.y',
|
||||
align: 4,
|
||||
offset: 112,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
struct_inner_align: {
|
||||
type: 'S_struct_inner_align',
|
||||
decl: `struct Inner { @align(64) x : u32 }
|
||||
struct S_struct_inner_align { x : Inner, y : u32 }`,
|
||||
access: '.y',
|
||||
align: 64,
|
||||
offset: 64,
|
||||
},
|
||||
struct_inner_size_and_align: {
|
||||
type: 'S_struct_inner_size_and_align',
|
||||
decl: `struct Inner { @align(32) @size(33) x : u32 }
|
||||
struct S_struct_inner_size_and_align { x : Inner, y : u32 }`,
|
||||
access: '.y',
|
||||
align: 32,
|
||||
offset: 64,
|
||||
},
|
||||
struct_override_size: {
|
||||
type: 'S_struct_override_size',
|
||||
decl: `struct Inner { @size(32) x : u32 }
|
||||
struct S_struct_override_size { @size(64) x : Inner, y : u32 }`,
|
||||
access: '.y',
|
||||
align: 4,
|
||||
offset: 64,
|
||||
uniformStdLayoutArrayView: true,
|
||||
},
|
||||
struct_double_align: {
|
||||
type: 'S_struct_double_align',
|
||||
decl: `struct Inner { x : u32, @align(32) y : u32 }
|
||||
struct S_struct_double_align { x : u32, @align(64) y : Inner }`,
|
||||
access: '.y.y',
|
||||
align: 64,
|
||||
offset: 96,
|
||||
},
|
||||
array_stride_size: {
|
||||
type: 'array<S_stride, 4>',
|
||||
decl: 'struct S_stride { @size(16) x : u32 }',
|
||||
access: '[2].x',
|
||||
align: 4,
|
||||
offset: 32,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs a read test for memory layouts from bufferView and bufferArrayView
|
||||
*
|
||||
* @param t The GPUTest
|
||||
* @param testcase the LayoutCase
|
||||
* @param wgsl the shader code
|
||||
* * (0, 0) - uniform or storage buffer
|
||||
* * (0, 1) - storage buffer
|
||||
* @param aspace the address space being tested
|
||||
* @param offset the offset to the view call
|
||||
*/
|
||||
export function runReadLayoutTest(
|
||||
t: GPUTest,
|
||||
testcase: LayoutCase,
|
||||
wgsl: string,
|
||||
aspace: string,
|
||||
offset: number
|
||||
) {
|
||||
let usage = GPUBufferUsage.COPY_DST;
|
||||
if (aspace === 'uniform') {
|
||||
usage |= GPUBufferUsage.UNIFORM;
|
||||
} else {
|
||||
usage |= GPUBufferUsage.STORAGE;
|
||||
}
|
||||
|
||||
const kBufferLength = 256;
|
||||
// Magic number is 42 in various representations.
|
||||
const inMagicNumber = testcase.f16 ? 0x5140 : testcase.f32 ? 0x42280000 : 42;
|
||||
const inputBuffer = t.makeBufferWithContents(
|
||||
new Uint32Array([
|
||||
...iterRange(kBufferLength, x => {
|
||||
if (x * 4 === testcase.offset + offset) {
|
||||
return inMagicNumber;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}),
|
||||
]),
|
||||
usage
|
||||
);
|
||||
|
||||
const outputBuffer = t.createBufferTracked({
|
||||
size: 4,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
|
||||
const pipeline = t.device.createComputePipeline({
|
||||
layout: 'auto',
|
||||
compute: {
|
||||
module: t.device.createShaderModule({ code: wgsl }),
|
||||
},
|
||||
});
|
||||
|
||||
const bg = t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer: inputBuffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
resource: {
|
||||
buffer: outputBuffer,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginComputePass();
|
||||
pass.setPipeline(pipeline);
|
||||
pass.setBindGroup(0, bg);
|
||||
pass.dispatchWorkgroups(1, 1, 1);
|
||||
pass.end();
|
||||
t.queue.submit([encoder.finish()]);
|
||||
|
||||
t.expectGPUBufferValuesEqual(outputBuffer, new Uint32Array([42]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a write test for memory layout from bufferView and bufferArrayView
|
||||
*
|
||||
* @param t The GPUTest
|
||||
* @param testcase the LayoutCase
|
||||
* @param wgsl The shader code
|
||||
* * (0, 0) - a read-only storage buffer
|
||||
* * (0, 1) - a read-write storage buffer
|
||||
* @param offset The offset for the view call
|
||||
*/
|
||||
export function runWriteLayoutTest(t: GPUTest, testcase: LayoutCase, wgsl: string, offset: number) {
|
||||
const inputBuffer = t.makeBufferWithContents(
|
||||
new Uint32Array([42]),
|
||||
GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
|
||||
);
|
||||
|
||||
const outputBuffer = t.createBufferTracked({
|
||||
size: 256 * 4,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
|
||||
const pipeline = t.device.createComputePipeline({
|
||||
layout: 'auto',
|
||||
compute: {
|
||||
module: t.device.createShaderModule({ code: wgsl }),
|
||||
},
|
||||
});
|
||||
|
||||
const bg = t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer: inputBuffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
resource: {
|
||||
buffer: outputBuffer,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginComputePass();
|
||||
pass.setPipeline(pipeline);
|
||||
pass.setBindGroup(0, bg);
|
||||
pass.dispatchWorkgroups(1, 1, 1);
|
||||
pass.end();
|
||||
t.queue.submit([encoder.finish()]);
|
||||
|
||||
// Magic number is 42 in various representations.
|
||||
const outMagicNumber = testcase.f16 ? 0x5140 : testcase.f32 ? 0x42280000 : 42;
|
||||
const expect = new Uint32Array([
|
||||
...iterRange(128, x => {
|
||||
if (x * 4 === testcase.offset + offset) {
|
||||
return outMagicNumber;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}),
|
||||
]);
|
||||
t.expectGPUBufferValuesEqual(outputBuffer, expect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a type-based read or write test for bufferView and bufferArrayView
|
||||
*
|
||||
* Note: This only covers types with no padding. Padding is covered by the layout tests.
|
||||
* @param read Test read if true
|
||||
* @param t The GPUTest
|
||||
* @param wgsl The shader code
|
||||
* @param eleTy The base element type
|
||||
* @param ty The test type
|
||||
* @param aspace The address space to test
|
||||
* @param offset The offset for the call
|
||||
* @param bufferSize The test buffer size
|
||||
*/
|
||||
export function runReadWriteTest(
|
||||
read: boolean,
|
||||
t: GPUTest,
|
||||
wgsl: string,
|
||||
eleTy: ScalarType,
|
||||
ty: ScalarType | VectorType | MatrixType | ArrayType,
|
||||
aspace: string,
|
||||
offset: number,
|
||||
bufferSize: number
|
||||
) {
|
||||
let num_eles = 1;
|
||||
if (ty instanceof VectorType) {
|
||||
num_eles *= ty.width;
|
||||
} else if (ty instanceof ArrayType) {
|
||||
num_eles *= ty.count;
|
||||
} else if (ty instanceof MatrixType) {
|
||||
num_eles *= ty.rows * ty.cols;
|
||||
}
|
||||
|
||||
const bufferElements = bufferSize / eleTy.size;
|
||||
|
||||
const start = offset / eleTy.size;
|
||||
const end = start + num_eles;
|
||||
const ins: number[] = [];
|
||||
const outs: number[] = [];
|
||||
for (let i = 0; i < bufferElements; i++) {
|
||||
if (i >= start && i < end) {
|
||||
ins.push(i + 4);
|
||||
outs.push(i + 4);
|
||||
} else {
|
||||
if (read) {
|
||||
ins.push(0);
|
||||
} else {
|
||||
outs.push(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!read && eleTy === Type.f16 && ins.length % 2 === 1) {
|
||||
ins.push(0);
|
||||
}
|
||||
if (read && eleTy === Type.f16 && outs.length % 2 === 1) {
|
||||
outs.push(0);
|
||||
}
|
||||
|
||||
const inMap = {
|
||||
u32: typedArrayParam('Uint32Array', ins),
|
||||
i32: typedArrayParam('Int32Array', ins),
|
||||
f32: typedArrayParam('Float32Array', ins),
|
||||
f16: typedArrayParam('Float16Array', ins),
|
||||
};
|
||||
const inputData: TypedArrayBufferView = typedArrayFromParam(
|
||||
inMap[eleTy.toString() as 'u32' | 'i32' | 'f32' | 'f16']
|
||||
);
|
||||
|
||||
const outMap = {
|
||||
u32: typedArrayParam('Uint32Array', outs),
|
||||
i32: typedArrayParam('Int32Array', outs),
|
||||
f32: typedArrayParam('Float32Array', outs),
|
||||
f16: typedArrayParam('Float16Array', outs),
|
||||
};
|
||||
const outputData: TypedArrayBufferView = typedArrayFromParam(
|
||||
outMap[eleTy.toString() as 'u32' | 'i32' | 'f32' | 'f16']
|
||||
);
|
||||
|
||||
const inputBuffer = t.makeBufferWithContents(
|
||||
inputData,
|
||||
GPUBufferUsage.COPY_SRC |
|
||||
(aspace === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE)
|
||||
);
|
||||
const outputBuffer = t.createBufferTracked({
|
||||
size: outs.length * eleTy.size,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
|
||||
const pipeline = t.device.createComputePipeline({
|
||||
layout: 'auto',
|
||||
compute: {
|
||||
module: t.device.createShaderModule({ code: wgsl }),
|
||||
},
|
||||
});
|
||||
|
||||
const bg = t.device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer: inputBuffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
resource: {
|
||||
buffer: outputBuffer,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginComputePass();
|
||||
pass.setPipeline(pipeline);
|
||||
pass.setBindGroup(0, bg);
|
||||
pass.dispatchWorkgroups(1, 1, 1);
|
||||
pass.end();
|
||||
t.queue.submit([encoder.finish()]);
|
||||
|
||||
t.expectGPUBufferValuesEqual(outputBuffer, outputData);
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ export const g = makeTestGroup(ShaderValidationTest);
|
|||
|
||||
interface Case {
|
||||
code: string;
|
||||
valid: boolean | 'texture_and_sampler_let';
|
||||
valid: boolean | 'texture_and_sampler_let' | 'buffer_view';
|
||||
decls?: string;
|
||||
}
|
||||
|
||||
|
|
@ -109,6 +109,36 @@ const kTypeCases: Record<string, Case> = {
|
|||
valid: 'texture_and_sampler_let',
|
||||
decls: `@group(0) @binding(0) var samp_comp : sampler_comparison;`,
|
||||
},
|
||||
buffer: {
|
||||
code: `let x : buffer = b;`,
|
||||
valid: false,
|
||||
decls: `@group(0) @binding(0) var<storage> b : buffer;`,
|
||||
},
|
||||
buffer_sized: {
|
||||
code: `let x : buffer<128> = b;`,
|
||||
valid: false,
|
||||
decls: `@group(0) @binding(0) var<uniform> b : buffer<128>;`,
|
||||
},
|
||||
buffer_override_sized: {
|
||||
code: `let x : buffer<o> = b;`,
|
||||
valid: false,
|
||||
decls: `override o = 16u; var<workgroup> b : buffer<o>;`,
|
||||
},
|
||||
ptr_buffer: {
|
||||
code: `let x : ptr<storage, buffer> = &b;`,
|
||||
valid: 'buffer_view',
|
||||
decls: `@group(0) @binding(0) var<storage> b : buffer;`,
|
||||
},
|
||||
ptr_buffer_sized: {
|
||||
code: `let x : ptr<uniform, buffer<128>> = &b;`,
|
||||
valid: 'buffer_view',
|
||||
decls: `@group(0) @binding(0) var<uniform> b : buffer<128>;`,
|
||||
},
|
||||
ptr_buffer_override_sized: {
|
||||
code: `let x : ptr<workgroup, buffer<o>> = &b;`,
|
||||
valid: 'buffer_view',
|
||||
decls: `override o = 16u; var<workgroup> b : buffer<o>;`,
|
||||
},
|
||||
};
|
||||
|
||||
g.test('type')
|
||||
|
|
@ -129,6 +159,8 @@ fn foo() {
|
|||
let expect: boolean = testcase.valid === true;
|
||||
if (testcase.valid === 'texture_and_sampler_let') {
|
||||
expect = t.hasLanguageFeature('texture_and_sampler_let');
|
||||
} else if (testcase.valid === 'buffer_view') {
|
||||
expect = t.hasLanguageFeature('buffer_view');
|
||||
}
|
||||
t.expectCompileResult(expect, code);
|
||||
});
|
||||
|
|
@ -178,6 +210,21 @@ const kInitCases: Record<string, Case> = {
|
|||
code: `var x = 1;\nlet y = x << 1;`,
|
||||
valid: true,
|
||||
},
|
||||
buffer_mismatch: {
|
||||
code: `let x : ptr<uniform, buffer> = &b;`,
|
||||
valid: false,
|
||||
decls: `@group(0) @binding(0) var<uniform> b : buffer<128>;`,
|
||||
},
|
||||
buffer_smaller_size: {
|
||||
code: `let x : ptr<uniform, buffer<64>> = &b;`,
|
||||
valid: false,
|
||||
decls: `@group(0) @binding(0) var<uniform> b : buffer<128>;`,
|
||||
},
|
||||
buffer_mismatched_overrides: {
|
||||
code: `let p : ptr<workgroup, buffer<o1>> = &b;`,
|
||||
valid: false,
|
||||
decls: `override o1 : u32;\noverride o2 : u32;\nvar<workgroup> b : buffer<o2>;`,
|
||||
},
|
||||
};
|
||||
|
||||
g.test('initializer')
|
||||
|
|
|
|||
|
|
@ -29,30 +29,35 @@ const kTypes = {
|
|||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
i32: {
|
||||
isHostShareable: true,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
u32: {
|
||||
isHostShareable: true,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
f32: {
|
||||
isHostShareable: true,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
f16: {
|
||||
isHostShareable: true,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: true,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
|
||||
// Vectors.
|
||||
|
|
@ -61,30 +66,35 @@ const kTypes = {
|
|||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
vec3i: {
|
||||
isHostShareable: true,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
vec4u: {
|
||||
isHostShareable: true,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
vec2f: {
|
||||
isHostShareable: true,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
vec3h: {
|
||||
isHostShareable: true,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: true,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
|
||||
// Matrices.
|
||||
|
|
@ -93,12 +103,14 @@ const kTypes = {
|
|||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
mat3x4h: {
|
||||
isHostShareable: true,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: true,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
|
||||
// Atomics.
|
||||
|
|
@ -107,12 +119,14 @@ const kTypes = {
|
|||
isConstructible: false,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
'atomic<u32>': {
|
||||
isHostShareable: true,
|
||||
isConstructible: false,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
|
||||
// Arrays.
|
||||
|
|
@ -121,36 +135,42 @@ const kTypes = {
|
|||
isConstructible: false,
|
||||
isFixedFootprint: false,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
'array<vec4<bool>, 4>': {
|
||||
isHostShareable: false,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
'array<vec4u>': {
|
||||
isHostShareable: true,
|
||||
isConstructible: false,
|
||||
isFixedFootprint: false,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
'array<vec4u, 4>': {
|
||||
isHostShareable: true,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
'array<vec4u, array_size_const>': {
|
||||
isHostShareable: true,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
'array<vec4u, array_size_override>': {
|
||||
isHostShareable: false,
|
||||
isConstructible: false,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
|
||||
// Structures.
|
||||
|
|
@ -159,36 +179,42 @@ const kTypes = {
|
|||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
S_bool: {
|
||||
isHostShareable: false,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
S_S_bool: {
|
||||
isHostShareable: false,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
S_array_vec4u: {
|
||||
isHostShareable: true,
|
||||
isConstructible: false,
|
||||
isFixedFootprint: false,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
S_array_vec4u_4: {
|
||||
isHostShareable: true,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
S_array_bool_4: {
|
||||
isHostShareable: false,
|
||||
isConstructible: true,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
|
||||
// Misc.
|
||||
|
|
@ -197,18 +223,44 @@ const kTypes = {
|
|||
isConstructible: false,
|
||||
isFixedFootprint: false,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
sampler: {
|
||||
isHostShareable: false,
|
||||
isConstructible: false,
|
||||
isFixedFootprint: false,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
'texture_2d<f32>': {
|
||||
isHostShareable: false,
|
||||
isConstructible: false,
|
||||
isFixedFootprint: false,
|
||||
requiresF16: false,
|
||||
requiresBufferView: false,
|
||||
},
|
||||
|
||||
// Buffers
|
||||
buffer: {
|
||||
isHostShareable: true,
|
||||
isConstructible: false,
|
||||
isFixedFootprint: false,
|
||||
requiresF16: false,
|
||||
requiresBufferView: true,
|
||||
},
|
||||
'buffer<128>': {
|
||||
isHostShareable: true,
|
||||
isConstructible: false,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: true,
|
||||
},
|
||||
'buffer<array_size_override>': {
|
||||
isHostShareable: false,
|
||||
isConstructible: false,
|
||||
isFixedFootprint: true,
|
||||
requiresF16: false,
|
||||
requiresBufferView: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -288,7 +340,8 @@ g.test('module_scope_types')
|
|||
break;
|
||||
case 'uniform':
|
||||
decl = '@group(0) @binding(0) var<uniform> foo : ';
|
||||
shouldPass = type.isHostShareable && type.isConstructible;
|
||||
shouldPass =
|
||||
type.isHostShareable && (type.isConstructible || t.params.type === 'buffer<128>');
|
||||
break;
|
||||
case 'workgroup':
|
||||
decl = 'var<workgroup> foo : ';
|
||||
|
|
@ -319,6 +372,7 @@ g.test('module_scope_types')
|
|||
${decl} ${t.params.via_alias ? 'MyType' : t.params.type};
|
||||
`;
|
||||
|
||||
shouldPass &&= !kTypes[t.params.type].requiresBufferView || t.hasLanguageFeature('buffer_view');
|
||||
t.expectCompileResult(shouldPass, wgsl);
|
||||
});
|
||||
|
||||
|
|
@ -368,6 +422,7 @@ g.test('function_scope_types')
|
|||
${decl} ${t.params.via_alias ? 'MyType' : t.params.type};
|
||||
}`;
|
||||
|
||||
shouldPass &&= !kTypes[t.params.type].requiresBufferView || t.hasLanguageFeature('buffer_view');
|
||||
t.expectCompileResult(shouldPass, wgsl);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,20 @@ fn main() {
|
|||
t.expectCompileResult(false, code);
|
||||
});
|
||||
|
||||
g.test('buffer_type')
|
||||
.desc('Validates buffers are rejected')
|
||||
.params(u => u.combine('sized', [false, true] as const))
|
||||
.fn(t => {
|
||||
const code = `
|
||||
@group(0) @binding(0) var<storage> b : buffer${t.params.sized ? '<1024>' : ''};
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = arrayLength(&b);
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(false, code);
|
||||
});
|
||||
|
||||
const atomic_types = ['u32', 'i32'].map(j => `atomic<${j}>`);
|
||||
const vec_types = [2, 3, 4]
|
||||
.map(i => ['i32', 'u32', 'f32', 'f16'].map(j => `vec${i}<${j}>`))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,540 @@
|
|||
export const description = `
|
||||
Validation tests for bufferArrayView
|
||||
`;
|
||||
|
||||
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
|
||||
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
|
||||
import { WGSLLanguageFeature } from '../../../../../capability_info.js';
|
||||
import { Type, elementTypeOf, kAllScalarsAndVectors } from '../../../../../util/conversion.js';
|
||||
import { ShaderValidationTest } from '../../../shader_validation_test.js';
|
||||
|
||||
export const g = makeTestGroup(ShaderValidationTest);
|
||||
|
||||
g.test('must_use')
|
||||
.desc('Tests that the builtin has the @must_use attribute')
|
||||
.params(u => u.combine('must_use', [true, false] as const))
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
const wgsl = `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
${t.params.must_use ? 'let p = ' : ''}bufferArrayView<array<u32>>(&v, 0, 64);
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(t.params.must_use, wgsl);
|
||||
});
|
||||
|
||||
const kTypes = objectsToRecord(kAllScalarsAndVectors);
|
||||
|
||||
g.test('return_store_type')
|
||||
.desc('Validates return type')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('template_type', keysOf(kTypes))
|
||||
.filter(t => {
|
||||
const type = kTypes[t.template_type];
|
||||
const eleType = elementTypeOf(type);
|
||||
return (
|
||||
eleType !== Type.abstractInt && eleType !== Type.abstractFloat && eleType !== Type.bool
|
||||
);
|
||||
})
|
||||
.combine('return_type', keysOf(kTypes))
|
||||
.filter(t => {
|
||||
const type = kTypes[t.return_type];
|
||||
const eleType = elementTypeOf(type);
|
||||
return (
|
||||
eleType !== Type.abstractInt && eleType !== Type.abstractFloat && eleType !== Type.bool
|
||||
);
|
||||
})
|
||||
.beginSubcases()
|
||||
.combine('ptr', [false, true] as const)
|
||||
.combine('array', [false, true] as const)
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
const template_type = kTypes[t.params.template_type];
|
||||
const return_type = kTypes[t.params.return_type];
|
||||
let ret_type = return_type.toString();
|
||||
let temp_type = template_type.toString();
|
||||
if (t.params.array) {
|
||||
ret_type = `array<${return_type.toString()}>`;
|
||||
temp_type = `array<${temp_type}>`;
|
||||
}
|
||||
if (t.params.ptr) {
|
||||
ret_type = `ptr<storage, ${ret_type}>`;
|
||||
}
|
||||
let enables = ``;
|
||||
if (template_type.requiresF16() || return_type.requiresF16()) {
|
||||
enables = `enable f16;`;
|
||||
}
|
||||
const wgsl = `
|
||||
${enables}
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let res : ${ret_type} = bufferArrayView<${temp_type}>(&v, 0, 128);
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(t.params.ptr && t.params.array && template_type === return_type, wgsl);
|
||||
});
|
||||
|
||||
g.test('buffer_type')
|
||||
.desc('Validates the buffer parameter type')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('type', [
|
||||
'unsized_ro_storage',
|
||||
'unsized_storage',
|
||||
'sized_ro_storage',
|
||||
'sized_storage',
|
||||
'sized_uniform',
|
||||
'sized_workgroup',
|
||||
'override_workgroup',
|
||||
] as const)
|
||||
.beginSubcases()
|
||||
.combine('ptr', [false, true] as const)
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
const aspace = t.params.type.substr(t.params.type.lastIndexOf('_') + 1);
|
||||
let access = '';
|
||||
switch (t.params.type) {
|
||||
case 'unsized_ro_storage':
|
||||
case 'sized_ro_storage':
|
||||
access = ', read';
|
||||
break;
|
||||
case 'unsized_storage':
|
||||
case 'sized_storage':
|
||||
access = ', read_write';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const wgsl = `
|
||||
@group(0) @binding(0) var<storage> unsized_ro_storage : buffer;
|
||||
@group(0) @binding(1) var<storage, read_write> unsized_storage : buffer;
|
||||
@group(0) @binding(2) var<storage> sized_ro_storage : buffer<128>;
|
||||
@group(0) @binding(3) var<storage, read_write> sized_storage : buffer<128>;
|
||||
@group(0) @binding(4) var<uniform> sized_uniform : buffer<128>;
|
||||
var<workgroup> sized_workgroup : buffer<128>;
|
||||
override o : u32;
|
||||
var<workgroup> override_workgroup : buffer<o>;
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p : ptr<${aspace}, array<u32>${access}> = bufferArrayView<array<u32>>(${
|
||||
t.params.ptr ? '&' : ''
|
||||
}${t.params.type}, 0, 128);
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(t.params.ptr, wgsl);
|
||||
});
|
||||
|
||||
g.test('offset_type')
|
||||
.desc('Validates the offset parameter type')
|
||||
.params(u => u.combine('type', keysOf(kTypes)))
|
||||
.fn(t => {
|
||||
const type = kTypes[t.params.type];
|
||||
let enables = `enable subgroups;\n`;
|
||||
if (type.requiresF16()) {
|
||||
enables += `enable f16;`;
|
||||
}
|
||||
const wgsl = `
|
||||
${enables}
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferArrayView<array<u32>>(&v, ${type.create(0).wgsl()}, 128);
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(
|
||||
type === Type.abstractInt || type === Type.u32 || type === Type.i32,
|
||||
wgsl
|
||||
);
|
||||
});
|
||||
|
||||
g.test('size_type')
|
||||
.desc('Validates the offset parameter type')
|
||||
.params(u => u.combine('type', keysOf(kTypes)))
|
||||
.fn(t => {
|
||||
const type = kTypes[t.params.type];
|
||||
let enables = `enable subgroups;\n`;
|
||||
if (type.requiresF16()) {
|
||||
enables += `enable f16;`;
|
||||
}
|
||||
const wgsl = `
|
||||
${enables}
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferArrayView<array<u32>>(&v, 0, ${type.create(128).wgsl()});
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(
|
||||
type === Type.abstractInt || type === Type.u32 || type === Type.i32,
|
||||
wgsl
|
||||
);
|
||||
});
|
||||
|
||||
interface EarlyEvalCase {
|
||||
code: string;
|
||||
valid: boolean | 'pipeline';
|
||||
constants?: Record<string, GPUPipelineConstantValue>;
|
||||
ptr_param?: boolean;
|
||||
}
|
||||
|
||||
const kEarlyEvalCases: Record<string, EarlyEvalCase> = {
|
||||
ro_storage_buffer: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer<16>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, 0, 16);
|
||||
}`,
|
||||
valid: true,
|
||||
},
|
||||
storage_buffer: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer<16>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, 0, 16);
|
||||
}`,
|
||||
valid: true,
|
||||
},
|
||||
uniform_buffer: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<uniform> v : buffer<16>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, 0, 16);
|
||||
}`,
|
||||
valid: true,
|
||||
},
|
||||
workgroup_buffer: {
|
||||
code: `
|
||||
var<workgroup> v : buffer<16>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, 0, 16);
|
||||
}`,
|
||||
valid: true,
|
||||
},
|
||||
const_size_too_small_for_type: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, 0, 12);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
buffer_too_small_for_const_size: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer<12>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<u32>>(&v, 0, 16);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
buffer_too_small_for_type: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer<12>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, 0, 16);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
const_size_too_small_for_const_offset_and_type: {
|
||||
code: `
|
||||
var<workgroup> v : buffer<128>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, 16, 30);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
buffer_size_too_small_for_const_offset_and_type: {
|
||||
code: `
|
||||
var<workgroup> v : buffer<30>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, 16, 128);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
override_size_too_small_for_type: {
|
||||
code: `
|
||||
override size : u32;
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, 0, size);
|
||||
}`,
|
||||
valid: 'pipeline',
|
||||
constants: { size: 12 },
|
||||
},
|
||||
buffer_too_small_for_override_size: {
|
||||
code: `
|
||||
override buffer_size : u32;
|
||||
override size : u32;
|
||||
var<workgroup> v : buffer<buffer_size>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<u32>>(&v, 0, size);
|
||||
}`,
|
||||
valid: 'pipeline',
|
||||
constants: { buffer_size: 12, size: 16 },
|
||||
},
|
||||
override_buffer_too_small_for_type: {
|
||||
code: `
|
||||
override buffer_size : u32;
|
||||
override size : u32;
|
||||
var<workgroup> v : buffer<buffer_size>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, 0, size);
|
||||
}`,
|
||||
valid: 'pipeline',
|
||||
constants: { buffer_size: 12, size: 16 },
|
||||
},
|
||||
override_size_too_small_for_override_offset_and_type: {
|
||||
code: `
|
||||
override offset : u32;
|
||||
override size : u32;
|
||||
@group(0) @binding(0) var<uniform> v : buffer<128>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, offset, size);
|
||||
}`,
|
||||
valid: 'pipeline',
|
||||
constants: { offset: 16, size: 30 },
|
||||
},
|
||||
buffer_size_too_small_for_override_offset_and_type: {
|
||||
code: `
|
||||
override buffer_size : u32;
|
||||
override offset : u32;
|
||||
var<workgroup> v : buffer<buffer_size>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<array<vec4u>>(&v, offset, 128);
|
||||
}`,
|
||||
valid: 'pipeline',
|
||||
constants: { buffer_size: 30, offset: 16 },
|
||||
},
|
||||
const_offset_not_aligned: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
fn foo() {
|
||||
let p = bufferarrayview<array<vec4u>>(&v, 12, 128);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
override_offset_not_aligned: {
|
||||
code: `
|
||||
override offset : u32;
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
fn foo() {
|
||||
let p = bufferarrayview<array<vec4u>>(&v, offset, 128);
|
||||
}`,
|
||||
valid: false,
|
||||
constants: { offset: 12 },
|
||||
},
|
||||
const_offset_plus_type_size_out_of_range: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
struct S {
|
||||
a: vec4u,
|
||||
b: vec4f,
|
||||
}
|
||||
fn foo() {
|
||||
let p = buffer<array<S>>(&v, 4294967279, 128);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
override_offset_plus_type_size_out_of_range: {
|
||||
code: `
|
||||
override offset : u32;
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
struct S {
|
||||
a: vec4u,
|
||||
b: vec4f,
|
||||
}
|
||||
fn foo() {
|
||||
let p = buffer<array<S>>(&v, offset, 128);
|
||||
}`,
|
||||
valid: false,
|
||||
constants: { offset: 4294967279 },
|
||||
},
|
||||
const_multiple_of_stride: {
|
||||
code: `
|
||||
struct S {
|
||||
a : vec4u,
|
||||
b : array<vec2u>,
|
||||
}
|
||||
var<workgroup> v : buffer<256>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<S>(&v, 0, 24);
|
||||
}`,
|
||||
valid: true,
|
||||
},
|
||||
const_not_multiple_of_stride: {
|
||||
code: `
|
||||
struct S {
|
||||
a : vec4u,
|
||||
b : array<vec2u>,
|
||||
}
|
||||
var<workgroup> v : buffer<256>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<S>(&v, 0, 26);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
override_not_multiple_of_stride: {
|
||||
code: `
|
||||
override size : u32;
|
||||
struct S {
|
||||
a : vec4u,
|
||||
b : array<vec2u>,
|
||||
}
|
||||
var<workgroup> v : buffer<256>;
|
||||
fn foo() {
|
||||
let p = bufferArrayView<S>(&v, 0, size);
|
||||
}`,
|
||||
valid: 'pipeline',
|
||||
constants: { size: 26 },
|
||||
},
|
||||
const_buffer_too_small_through_unsized_function: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> buffer<16>;
|
||||
fn bar(p : ptr<storage, buffer>) {
|
||||
let q = bufferArrayView<array<u32>>(p, 0, 32);
|
||||
}
|
||||
fn foo() {
|
||||
bar(&v);
|
||||
}`,
|
||||
valid: false,
|
||||
ptr_param: true,
|
||||
},
|
||||
override_buffer_too_small_through_unsized_function: {
|
||||
code: `
|
||||
override size : u32;
|
||||
@group(0) @binding(0) var<storage> v : buffer<16>;
|
||||
fn bar(p : ptr<storage, buffer>) {
|
||||
let q = bufferArrayView<array<u32>>(p, 0, size);
|
||||
}
|
||||
fn foo() {
|
||||
bar(&v);
|
||||
}`,
|
||||
valid: 'pipeline',
|
||||
constants: { size: 32 },
|
||||
ptr_param: true,
|
||||
},
|
||||
sized_buffer_to_smaller_buffer_too_small: {
|
||||
code: `
|
||||
var<workgroup> v : buffer<256>;
|
||||
fn bar(p : ptr<workgroup, buffer<16>>) {
|
||||
let q = bufferArrayView<array<u32>>(q, 16, 4);
|
||||
}
|
||||
fn foo() {
|
||||
bar(&v);
|
||||
}`,
|
||||
valid: false,
|
||||
ptr_param: true,
|
||||
},
|
||||
};
|
||||
|
||||
g.test('early_eval_errors')
|
||||
.desc('Test shader-creation and pipeline-creation errors')
|
||||
.params(u => u.combine('case', keysOf(kEarlyEvalCases)))
|
||||
.fn(t => {
|
||||
const testcase = kEarlyEvalCases[t.params.case];
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (testcase.ptr_param === true) {
|
||||
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
|
||||
}
|
||||
|
||||
t.expectCompileResult(testcase.valid !== false, testcase.code);
|
||||
if (testcase.valid !== false) {
|
||||
t.expectPipelineResult({
|
||||
expectedResult: testcase.valid === true,
|
||||
code: testcase.code,
|
||||
constants: testcase.constants,
|
||||
statements: ['foo();'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
interface LHSCallCase {
|
||||
code: string;
|
||||
requires?: WGSLLanguageFeature[];
|
||||
}
|
||||
|
||||
const kLHSCallCases: Record<string, LHSCallCase> = {
|
||||
u32_array: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
(*bufferArrayView<array<u32>>(&v, 0, 128))[0] = 123u;
|
||||
}`,
|
||||
},
|
||||
f32_vector_letter: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
(*bufferArrayView<array<vec4f>>(&v, 0, 128))[0].x = 13.3f;
|
||||
}`,
|
||||
},
|
||||
f32_struct_access: {
|
||||
code: `
|
||||
struct S {
|
||||
member_a: f32,
|
||||
}
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
(*bufferArrayView<array<S>>(&v, 0, 128))[0].member_a = 13.3f;
|
||||
}`,
|
||||
},
|
||||
u32_array_ptr_composite: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
bufferArrayView<array<u32>>(&v, 0, 128)[0] = 123u;
|
||||
}`,
|
||||
requires: ['pointer_composite_access'],
|
||||
},
|
||||
vec4u_swizzle_assign: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
(*bufferArrayView<array<vec4u>>(&v, 0, 128))[0].zxy = vec3u(42);
|
||||
}`,
|
||||
requires: ['swizzle_assignment'],
|
||||
},
|
||||
vec4u_ptr_composite_swizzle_assign: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
bufferArrayView<array<vec4u>>(&v, 0, 128)[0].zxy = vec3u(42);
|
||||
}`,
|
||||
requires: ['pointer_composite_access', 'swizzle_assignment'],
|
||||
},
|
||||
compound_assign: {
|
||||
code: `
|
||||
var<workgroup> v : buffer<256>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
(*bufferArrayView<array<u32>>(&v, 0, 128))[0] += 123u;
|
||||
}`,
|
||||
},
|
||||
};
|
||||
|
||||
g.test('lhs_call')
|
||||
.desc('Validate that bufferView can be on the LHS of an assignment')
|
||||
.params(u => u.combine('case', keysOf(kLHSCallCases)))
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
|
||||
const testcase = kLHSCallCases[t.params.case];
|
||||
let valid = true;
|
||||
const features = testcase.requires ?? [];
|
||||
features.forEach(f => {
|
||||
valid &&= t.hasLanguageFeature(f);
|
||||
});
|
||||
t.expectCompileResult(valid, testcase.code);
|
||||
});
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
export const description = `
|
||||
Validation tests for bufferLength
|
||||
`;
|
||||
|
||||
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
|
||||
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
|
||||
import { Type, elementTypeOf, kAllScalarsAndVectors } from '../../../../../util/conversion.js';
|
||||
import { ShaderValidationTest } from '../../../shader_validation_test.js';
|
||||
|
||||
export const g = makeTestGroup(ShaderValidationTest);
|
||||
|
||||
g.test('must_use')
|
||||
.desc('Tests that the builtin has the @must_use attribute')
|
||||
.params(u => u.combine('must_use', [true, false] as const))
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
const wgsl = `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
${t.params.must_use ? '_ = ' : ''}bufferLength(&v);
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(t.params.must_use, wgsl);
|
||||
});
|
||||
|
||||
const kTypes = objectsToRecord(kAllScalarsAndVectors);
|
||||
|
||||
g.test('return_type')
|
||||
.desc('Validates return type')
|
||||
.params(u =>
|
||||
u.combine('type', keysOf(kTypes)).filter(t => {
|
||||
const type = kTypes[t.type];
|
||||
const eleType = elementTypeOf(type);
|
||||
return eleType !== Type.abstractInt && eleType !== Type.abstractFloat;
|
||||
})
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
const type = kTypes[t.params.type];
|
||||
let enables = ``;
|
||||
if (type.requiresF16()) {
|
||||
enables = `enable f16;`;
|
||||
}
|
||||
const wgsl = `
|
||||
${enables}
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let res : ${type.toString()} = bufferLength(&v);
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(type === Type.u32, wgsl);
|
||||
});
|
||||
|
||||
g.test('data_type')
|
||||
.desc('Validates the input parameter type')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('type', [
|
||||
'unsized_ro_storage',
|
||||
'unsized_storage',
|
||||
'sized_ro_storage',
|
||||
'sized_storage',
|
||||
'sized_uniform',
|
||||
'sized_workgroup',
|
||||
'override_workgroup',
|
||||
] as const)
|
||||
.beginSubcases()
|
||||
.combine('ptr', [false, true] as const)
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
const wgsl = `
|
||||
@group(0) @binding(0) var<storage> unsized_ro_storage : buffer;
|
||||
@group(0) @binding(1) var<storage, read_write> unsized_storage : buffer;
|
||||
@group(0) @binding(2) var<storage> sized_ro_storage : buffer<128>;
|
||||
@group(0) @binding(3) var<storage, read_write> sized_storage : buffer<128>;
|
||||
@group(0) @binding(4) var<uniform> sized_uniform : buffer<128>;
|
||||
var<workgroup> sized_workgroup : buffer<128>;
|
||||
override o : u32;
|
||||
var<workgroup> override_workgroup : buffer<o>;
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = bufferLength(${t.params.ptr ? '&' : ''}${t.params.type});
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(t.params.ptr, wgsl);
|
||||
});
|
||||
|
||||
const kInvalidCases = {
|
||||
baseline: `&b`,
|
||||
no_args: `bufferLength()`,
|
||||
two_args: `bufferLength(&b, 0)`,
|
||||
scalar: `bufferLength(0u)`,
|
||||
ptr_to_scalar: `bufferLength(&v[0])`,
|
||||
ptr_to_array: `bufferLength(&v)`,
|
||||
};
|
||||
|
||||
g.test('invalid_cases')
|
||||
.desc('Test a smattering of invalid bufferLength calls')
|
||||
.params(u => u.combine('test', keysOf(kInvalidCases)))
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
const code = `
|
||||
var<workgroup> v : array<u32, 4>;
|
||||
var<workgroup> b : buffer<128>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
_ = ${kInvalidCases[t.params.test]};
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(t.params.test === 'baseline', code);
|
||||
});
|
||||
|
|
@ -0,0 +1,451 @@
|
|||
export const description = `
|
||||
Validation tests for bufferView
|
||||
`;
|
||||
|
||||
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
|
||||
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
|
||||
import { WGSLLanguageFeature } from '../../../../../capability_info.js';
|
||||
import { Type, elementTypeOf, kAllScalarsAndVectors } from '../../../../../util/conversion.js';
|
||||
import { ShaderValidationTest } from '../../../shader_validation_test.js';
|
||||
|
||||
export const g = makeTestGroup(ShaderValidationTest);
|
||||
|
||||
g.test('must_use')
|
||||
.desc('Tests that the builtin has the @must_use attribute')
|
||||
.params(u => u.combine('must_use', [true, false] as const))
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
const wgsl = `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
${t.params.must_use ? 'let p = ' : ''}bufferView<u32>(&v, 0);
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(t.params.must_use, wgsl);
|
||||
});
|
||||
|
||||
const kTypes = objectsToRecord(kAllScalarsAndVectors);
|
||||
|
||||
g.test('return_store_type')
|
||||
.desc('Validates return type')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('template_type', keysOf(kTypes))
|
||||
.filter(t => {
|
||||
const type = kTypes[t.template_type];
|
||||
const eleType = elementTypeOf(type);
|
||||
return (
|
||||
eleType !== Type.abstractInt && eleType !== Type.abstractFloat && eleType !== Type.bool
|
||||
);
|
||||
})
|
||||
.combine('return_type', keysOf(kTypes))
|
||||
.filter(t => {
|
||||
const type = kTypes[t.return_type];
|
||||
const eleType = elementTypeOf(type);
|
||||
return (
|
||||
eleType !== Type.abstractInt && eleType !== Type.abstractFloat && eleType !== Type.bool
|
||||
);
|
||||
})
|
||||
.beginSubcases()
|
||||
.combine('ptr', [false, true] as const)
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
const template_type = kTypes[t.params.template_type];
|
||||
const return_type = kTypes[t.params.return_type];
|
||||
let ret_type = return_type.toString();
|
||||
if (t.params.ptr) {
|
||||
ret_type = `ptr<storage, ${ret_type}>`;
|
||||
}
|
||||
let enables = ``;
|
||||
if (template_type.requiresF16() || return_type.requiresF16()) {
|
||||
enables = `enable f16;`;
|
||||
}
|
||||
const wgsl = `
|
||||
${enables}
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let res : ${ret_type} = bufferView<${template_type.toString()}>(&v, 0);
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(t.params.ptr && template_type === return_type, wgsl);
|
||||
});
|
||||
|
||||
g.test('buffer_type')
|
||||
.desc('Validates the buffer parameter type')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('type', [
|
||||
'unsized_ro_storage',
|
||||
'unsized_storage',
|
||||
'sized_ro_storage',
|
||||
'sized_storage',
|
||||
'sized_uniform',
|
||||
'sized_workgroup',
|
||||
'override_workgroup',
|
||||
] as const)
|
||||
.beginSubcases()
|
||||
.combine('ptr', [false, true] as const)
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
const aspace = t.params.type.substr(t.params.type.lastIndexOf('_') + 1);
|
||||
let access = '';
|
||||
switch (t.params.type) {
|
||||
case 'unsized_ro_storage':
|
||||
case 'sized_ro_storage':
|
||||
access = ', read';
|
||||
break;
|
||||
case 'unsized_storage':
|
||||
case 'sized_storage':
|
||||
access = ', read_write';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const wgsl = `
|
||||
@group(0) @binding(0) var<storage> unsized_ro_storage : buffer;
|
||||
@group(0) @binding(1) var<storage, read_write> unsized_storage : buffer;
|
||||
@group(0) @binding(2) var<storage> sized_ro_storage : buffer<128>;
|
||||
@group(0) @binding(3) var<storage, read_write> sized_storage : buffer<128>;
|
||||
@group(0) @binding(4) var<uniform> sized_uniform : buffer<128>;
|
||||
var<workgroup> sized_workgroup : buffer<128>;
|
||||
override o : u32;
|
||||
var<workgroup> override_workgroup : buffer<o>;
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p : ptr<${aspace}, u32${access}> = bufferView<u32>(${t.params.ptr ? '&' : ''}${
|
||||
t.params.type
|
||||
}, 0);
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(t.params.ptr, wgsl);
|
||||
});
|
||||
|
||||
g.test('offset_type')
|
||||
.desc('Validates the offset parameter type')
|
||||
.params(u => u.combine('type', keysOf(kTypes)))
|
||||
.fn(t => {
|
||||
const type = kTypes[t.params.type];
|
||||
let enables = `enable subgroups;\n`;
|
||||
if (type.requiresF16()) {
|
||||
enables += `enable f16;`;
|
||||
}
|
||||
const wgsl = `
|
||||
${enables}
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
let p = bufferView<u32>(&v, ${type.create(0).wgsl()});
|
||||
}`;
|
||||
|
||||
t.expectCompileResult(
|
||||
type === Type.abstractInt || type === Type.u32 || type === Type.i32,
|
||||
wgsl
|
||||
);
|
||||
});
|
||||
|
||||
interface EarlyEvalCase {
|
||||
code: string;
|
||||
valid: boolean | 'pipeline';
|
||||
constants?: Record<string, GPUPipelineConstantValue>;
|
||||
ptr_param?: boolean;
|
||||
}
|
||||
|
||||
const kEarlyEvalCases: Record<string, EarlyEvalCase> = {
|
||||
ro_storage_buffer: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer<16>;
|
||||
fn foo() {
|
||||
let p = bufferView<vec4u>(&v, 0);
|
||||
}`,
|
||||
valid: true,
|
||||
},
|
||||
storage_buffer: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer<16>;
|
||||
fn foo() {
|
||||
let p = bufferView<vec4u>(&v, 0);
|
||||
}`,
|
||||
valid: true,
|
||||
},
|
||||
uniform_buffer: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<uniform> v : buffer<16>;
|
||||
fn foo() {
|
||||
let p = bufferView<vec4u>(&v, 0);
|
||||
}`,
|
||||
valid: true,
|
||||
},
|
||||
workgroup_buffer: {
|
||||
code: `
|
||||
var<workgroup> v : buffer<16>;
|
||||
fn foo() {
|
||||
let p = bufferView<vec4u>(&v, 0);
|
||||
}`,
|
||||
valid: true,
|
||||
},
|
||||
ro_storage_buffer_too_small_for_type: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer<8>;
|
||||
fn foo() {
|
||||
let p = bufferView<vec4u>(&v, 0);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
uniform_buffer_too_small_with_const_offset: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<uniform> v : buffer<24>;
|
||||
fn foo() {
|
||||
let p = bufferView<vec4u>(&v, 16);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
uniform_buffer_too_small_with_override_offset: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<uniform> v : buffer<24>;
|
||||
override offset : u32;
|
||||
fn foo() {
|
||||
let p = bufferView<vec4u>(&v, offset);
|
||||
}`,
|
||||
valid: 'pipeline',
|
||||
constants: { offset: 16 },
|
||||
},
|
||||
workgroup_buffer_too_small_with_override: {
|
||||
code: `
|
||||
override size : u32;
|
||||
var<workgroup> v : buffer<size>;
|
||||
fn foo() {
|
||||
let p = bufferView<vec4u>(&v, 0);
|
||||
}`,
|
||||
valid: 'pipeline',
|
||||
constants: { size: 12 },
|
||||
},
|
||||
storage_buffer_too_small_for_one_array_element: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer<28>;
|
||||
struct S {
|
||||
a: vec4u,
|
||||
b: vec4f,
|
||||
}
|
||||
fn foo() {
|
||||
let p = bufferView<array<S>>(&v, 0);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
storage_buffer_out_of_range_u32: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
struct S {
|
||||
a: vec4u,
|
||||
b: vec4f,
|
||||
}
|
||||
fn foo() {
|
||||
let p = buffer<S>(&v, 4294967279);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
uniform_buffer_too_small_through_unsized_function: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<uniform> v : buffer<28>;
|
||||
fn bar(p : ptr<uniform, buffer>) {
|
||||
let q = bufferView<vec4u>(p, 16);
|
||||
}
|
||||
fn foo() {
|
||||
bar(&v);
|
||||
}`,
|
||||
valid: false,
|
||||
ptr_param: true,
|
||||
},
|
||||
uniform_buffer_too_small_through_unsized_function_override: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<uniform> v : buffer<28>;
|
||||
override offset : u32;
|
||||
fn bar(p : ptr<uniform, buffer>) {
|
||||
let q = bufferView<vec4u>(p, offset);
|
||||
}
|
||||
fn foo() {
|
||||
bar(&v);
|
||||
}`,
|
||||
valid: 'pipeline',
|
||||
constants: { offset: 16 },
|
||||
ptr_param: true,
|
||||
},
|
||||
workgroup_to_smaller_size_param: {
|
||||
code: `
|
||||
var<workgroup> v : buffer<128>;
|
||||
fn bar(p : ptr<workgroup, buffer<16>) {
|
||||
let q = bufferView<array<vec2u, 3>(p, 0);
|
||||
}
|
||||
fn foo() {
|
||||
bar(&v);
|
||||
}`,
|
||||
valid: false,
|
||||
ptr_param: true,
|
||||
},
|
||||
ro_storage_unsized_buffer_to_sized_param: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage> v : buffer;
|
||||
fn bar(p : ptr<storage, buffer<16>) {
|
||||
let q = bufferView<array<vec2u, 3>(p, 0);
|
||||
}
|
||||
fn foo() {
|
||||
bar(&v);
|
||||
}`,
|
||||
valid: false,
|
||||
ptr_param: true,
|
||||
},
|
||||
offset_not_aligned: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<uniform> v : buffer<128>;
|
||||
fn foo() {
|
||||
let p = bufferView<vec4u>(&v, 12);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
offset_not_aligned_override: {
|
||||
code: `
|
||||
override offset : u32;
|
||||
@group(0) @binding(0) var<uniform> v : buffer<128>;
|
||||
fn foo() {
|
||||
let p = bufferView<vec4u>(&v, offset);
|
||||
}`,
|
||||
valid: 'pipeline',
|
||||
constants: { offset: 12 },
|
||||
},
|
||||
offset_negative: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<uniform> v : buffer<128>;
|
||||
fn foo() {
|
||||
let p = bufferView<vec4u>(&v, -16);
|
||||
}`,
|
||||
valid: false,
|
||||
},
|
||||
offset_negative_override: {
|
||||
code: `
|
||||
override offset : u32;
|
||||
@group(0) @binding(0) var<uniform> v : buffer<128>;
|
||||
fn foo() {
|
||||
let p = bufferView<vec4u>(&v, offset);
|
||||
}`,
|
||||
valid: 'pipeline',
|
||||
constants: { offset: -16 },
|
||||
},
|
||||
};
|
||||
|
||||
g.test('early_eval_errors')
|
||||
.desc('Test shader-creation and pipeline-creation errors')
|
||||
.params(u => u.combine('case', keysOf(kEarlyEvalCases)))
|
||||
.fn(t => {
|
||||
const testcase = kEarlyEvalCases[t.params.case];
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
if (testcase.ptr_param === true) {
|
||||
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
|
||||
}
|
||||
|
||||
t.expectCompileResult(testcase.valid !== false, testcase.code);
|
||||
if (testcase.valid !== false) {
|
||||
t.expectPipelineResult({
|
||||
expectedResult: testcase.valid === true,
|
||||
code: testcase.code,
|
||||
constants: testcase.constants,
|
||||
statements: ['foo();'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
interface LHSCallCase {
|
||||
code: string;
|
||||
requires?: WGSLLanguageFeature[];
|
||||
}
|
||||
|
||||
const kLHSCallCases: Record<string, LHSCallCase> = {
|
||||
u32: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
*bufferView<u32>(&v, 0) = 123u;
|
||||
}`,
|
||||
},
|
||||
u32_array: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
(*bufferView<array<u32, 4>>(&v, 0))[0] = 123u;
|
||||
}`,
|
||||
},
|
||||
f32_vector_letter: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
(*bufferView<vec4f>(&v, 0)).x = 13.3f;
|
||||
}`,
|
||||
},
|
||||
f32_struct_access: {
|
||||
code: `
|
||||
struct S {
|
||||
member_a: f32,
|
||||
}
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
(*bufferView<S>(&v, 0)).member_a = 13.3f;
|
||||
}`,
|
||||
},
|
||||
u32_array_ptr_composite: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
bufferView<array<u32, 4>>(&v, 0)[0] = 123u;
|
||||
}`,
|
||||
requires: ['pointer_composite_access'],
|
||||
},
|
||||
vec4u_swizzle_assign: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
(*bufferView<vec4u>(&v, 0)).zxy = vec3u(42);
|
||||
}`,
|
||||
requires: ['swizzle_assignment'],
|
||||
},
|
||||
vec4u_ptr_composite_swizzle_assign: {
|
||||
code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v : buffer;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
bufferView<vec4u>(&v, 0).zxy = vec3u(42);
|
||||
}`,
|
||||
requires: ['pointer_composite_access', 'swizzle_assignment'],
|
||||
},
|
||||
compound_assign: {
|
||||
code: `
|
||||
var<workgroup> v : buffer<256>;
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
*bufferView<u32>(&v, 0) += 123u;
|
||||
}`,
|
||||
},
|
||||
};
|
||||
|
||||
g.test('lhs_call')
|
||||
.desc('Validate that bufferView can be on the LHS of an assignment')
|
||||
.params(u => u.combine('case', keysOf(kLHSCallCases)))
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
|
||||
const testcase = kLHSCallCases[t.params.case];
|
||||
let valid = true;
|
||||
const features = testcase.requires ?? [];
|
||||
features.forEach(f => {
|
||||
valid &&= t.hasLanguageFeature(f);
|
||||
});
|
||||
t.expectCompileResult(valid, testcase.code);
|
||||
});
|
||||
|
|
@ -202,7 +202,7 @@ fn foo() -> ${testcase.name} {
|
|||
|
||||
interface ParamTypeCase {
|
||||
name: string;
|
||||
valid: boolean | 'with_unrestricted_pointer_parameters';
|
||||
valid: boolean | 'with_unrestricted_pointer_parameters' | 'with_buffer_view';
|
||||
}
|
||||
|
||||
const kFunctionParamTypeCases: Record<string, ParamTypeCase> = {
|
||||
|
|
@ -304,6 +304,68 @@ const kFunctionParamTypeCases: Record<string, ParamTypeCase> = {
|
|||
invalid_ptr6: { name: `ptr<private,u32,read_write>`, valid: false }, // Can't specify access mode
|
||||
invalid_ptr7: { name: `ptr<private,clamp>`, valid: false }, // Invalid store type
|
||||
invalid_ptr8: { name: `ptr<function, texture_external>`, valid: false }, // non-constructible pointer type
|
||||
|
||||
// Buffers (need buffer_view and unrestricted_pointer_parameters)
|
||||
ptrBufferUnsized_storage: {
|
||||
name: `ptr<storage, buffer, read_write>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferSized_storage: {
|
||||
name: `ptr<storage, buffer<128>, read_write>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferSizedSmall_storage: {
|
||||
name: `ptr<storage, buffer<64>, read_write>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferUnsized_ro_storage: {
|
||||
name: `ptr<storage, buffer>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferSized_ro_storage: {
|
||||
name: `ptr<storage, buffer<128>>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferSizedSmall_ro_storage: {
|
||||
name: `ptr<storage, buffer<64>>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferUnsized_uniform: {
|
||||
name: `ptr<uniform, buffer>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferSized_uniform: {
|
||||
name: `ptr<uniform, buffer<128>>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferSizedSmall_uniform: {
|
||||
name: `ptr<uniform, buffer<64>>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferUnsized_workgroup: {
|
||||
name: `ptr<workgroup, buffer>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferSized_workgroup: {
|
||||
name: `ptr<workgroup, buffer<128>>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferSizedSmall_workgroup: {
|
||||
name: `ptr<workgroup, buffer<64>>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferOverrideNoDefault_workgroup: {
|
||||
name: `ptr<workgroup, buffer<override_no_default>>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferOverrideDefault_workgroup: {
|
||||
name: `ptr<workgroup, buffer<override_default>>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
ptrBufferOverrideExpr_workgroup: {
|
||||
name: `ptr<workgroup, buffer<override_expr>>`,
|
||||
valid: 'with_buffer_view',
|
||||
},
|
||||
};
|
||||
|
||||
g.test('function_parameter_types')
|
||||
|
|
@ -324,6 +386,10 @@ fn foo(param : ${testcase.name}) {
|
|||
let isValid = testcase.valid;
|
||||
if (isValid === 'with_unrestricted_pointer_parameters') {
|
||||
isValid = t.hasLanguageFeature('unrestricted_pointer_parameters');
|
||||
} else if (isValid === 'with_buffer_view') {
|
||||
isValid =
|
||||
t.hasLanguageFeature('unrestricted_pointer_parameters') &&
|
||||
t.hasLanguageFeature('buffer_view');
|
||||
}
|
||||
|
||||
t.expectCompileResult(isValid, code);
|
||||
|
|
@ -333,6 +399,7 @@ interface ParamValueCase {
|
|||
value: string;
|
||||
matches: string[];
|
||||
needsUnrestrictedPointerParameters?: boolean;
|
||||
needsBufferView?: boolean;
|
||||
}
|
||||
|
||||
const kFunctionParamValueCases: Record<string, ParamValueCase> = {
|
||||
|
|
@ -519,6 +586,81 @@ const kFunctionParamValueCases: Record<string, ParamValueCase> = {
|
|||
matches: ['ptrWorkgroupOverrideExpr'],
|
||||
needsUnrestrictedPointerParameters: true,
|
||||
},
|
||||
|
||||
// Buffer view
|
||||
ptrStorageBufferUnsized: {
|
||||
value: `&storage_buffer_unsized`,
|
||||
matches: ['ptrBufferUnsized_storage'],
|
||||
needsBufferView: true,
|
||||
},
|
||||
ptrStorageBufferSized: {
|
||||
value: `&storage_buffer_sized`,
|
||||
matches: ['ptrBufferUnsized_storage', 'ptrBufferSized_storage', 'ptrBufferSizedSmall_storage'],
|
||||
needsBufferView: true,
|
||||
},
|
||||
ptrStorageBufferSizedSmall: {
|
||||
value: `&storage_buffer_sized_small`,
|
||||
matches: ['ptrBufferUnsized_storage', 'ptrBufferSizedSmall_storage'],
|
||||
needsBufferView: true,
|
||||
},
|
||||
ptrROStorageBufferUnsized: {
|
||||
value: `&ro_storage_buffer_unsized`,
|
||||
matches: ['ptrBufferUnsized_ro_storage'],
|
||||
needsBufferView: true,
|
||||
},
|
||||
ptrROStorageBufferSized: {
|
||||
value: `&ro_storage_buffer_sized`,
|
||||
matches: [
|
||||
'ptrBufferUnsized_ro_storage',
|
||||
'ptrBufferSized_ro_storage',
|
||||
'ptrBufferSizedSmall_ro_storage',
|
||||
],
|
||||
needsBufferView: true,
|
||||
},
|
||||
ptrROStorageBufferSizedSmall: {
|
||||
value: `&ro_storage_buffer_sized_small`,
|
||||
matches: ['ptrBufferUnsized_ro_storage', 'ptrBufferSizedSmall_ro_storage'],
|
||||
needsBufferView: true,
|
||||
},
|
||||
ptrUniformBufferSized: {
|
||||
value: `&uniform_buffer_sized`,
|
||||
matches: ['ptrBufferUnsized_uniform', 'ptrBufferSized_uniform', 'ptrBufferSizedSmall_uniform'],
|
||||
needsBufferView: true,
|
||||
},
|
||||
ptrUniformBufferSizedSmall: {
|
||||
value: `&uniform_buffer_sized_small`,
|
||||
matches: ['ptrBufferUnsized_uniform', 'ptrBufferSizedSmall_uniform'],
|
||||
needsBufferView: true,
|
||||
},
|
||||
ptrWorkgroupBufferSized: {
|
||||
value: `&wg_buffer_sized`,
|
||||
matches: [
|
||||
'ptrBufferUnsized_workgroup',
|
||||
'ptrBufferSized_workgroup',
|
||||
'ptrBufferSizedSmall_workgroup',
|
||||
],
|
||||
needsBufferView: true,
|
||||
},
|
||||
ptrWorkgroupBufferSizedSmall: {
|
||||
value: `&wg_buffer_sized_small`,
|
||||
matches: ['ptrBufferUnsized_workgroup', 'ptrBufferSizedSmall_workgroup'],
|
||||
needsBufferView: true,
|
||||
},
|
||||
ptrWorkgroupBufferOverrideNoDefault: {
|
||||
value: `&wg_buffer_override_no_default`,
|
||||
matches: ['ptrBufferUnsized_workgroup', 'ptrBufferOverrideNoDefault_workgroup'],
|
||||
needsBufferView: true,
|
||||
},
|
||||
ptrWorkgroupBufferOverrideDefault: {
|
||||
value: `&wg_buffer_override_default`,
|
||||
matches: ['ptrBufferUnsized_workgroup', 'ptrBufferOverrideDefault_workgroup'],
|
||||
needsBufferView: true,
|
||||
},
|
||||
ptrWorkgroupBufferOverrideExpr: {
|
||||
value: `&wg_buffer_override_expr`,
|
||||
matches: ['ptrBufferUnsized_workgroup', 'ptrBufferOverrideExpr_workgroup'],
|
||||
needsBufferView: true,
|
||||
},
|
||||
};
|
||||
|
||||
function parameterMatches(decl: string, matches: string[]): boolean {
|
||||
|
|
@ -548,6 +690,26 @@ g.test('function_parameter_matching')
|
|||
const param = kFunctionParamTypeCases[t.params.decl];
|
||||
const arg = kFunctionParamValueCases[t.params.arg];
|
||||
const enable = param.name === 'f16' ? 'enable f16;' : '';
|
||||
let buffer_decls = ``;
|
||||
if (param.valid === 'with_buffer_view') {
|
||||
buffer_decls = `
|
||||
@group(2) @binding(0) var<storage, read_write> storage_buffer_unsized : buffer;
|
||||
@group(2) @binding(1) var<storage, read_write> storage_buffer_sized : buffer<128>;
|
||||
@group(2) @binding(2) var<storage, read_write> storage_buffer_sized_small : buffer<64>;
|
||||
@group(2) @binding(3) var<storage> ro_storage_buffer_unsized : buffer;
|
||||
@group(2) @binding(4) var<storage> ro_storage_buffer_sized : buffer<128>;
|
||||
@group(2) @binding(5) var<storage> ro_storage_buffer_sized_small : buffer<64>;
|
||||
@group(2) @binding(6) var<uniform> uniform_buffer_sized : buffer<128>;
|
||||
@group(2) @binding(7) var<uniform> uniform_buffer_sized_small : buffer<64>;
|
||||
|
||||
var<workgroup> wg_buffer_sized : buffer<128>;
|
||||
var<workgroup> wg_buffer_sized_small : buffer<64>;
|
||||
var<workgroup> wg_buffer_override_no_default : buffer<override_no_default>;
|
||||
var<workgroup> wg_buffer_override_default : buffer<override_default>;
|
||||
var<workgroup> wg_buffer_override_expr : buffer<override_expr>;
|
||||
`;
|
||||
}
|
||||
|
||||
const code = `
|
||||
${enable}
|
||||
|
||||
|
|
@ -574,6 +736,8 @@ var<storage, read_write> rw_host_shareable : host_shareable;
|
|||
@group(1) @binding(2)
|
||||
var<uniform> uniform_host_shareable : host_shareable;
|
||||
|
||||
${buffer_decls}
|
||||
|
||||
fn bar(param : ${param.name}) { }
|
||||
|
||||
var<private> g_u32 : u32;
|
||||
|
|
@ -642,11 +806,20 @@ fn foo() {
|
|||
(kFunctionParamTypeCases[t.params.decl].valid === 'with_unrestricted_pointer_parameters' ||
|
||||
arg.needsUnrestrictedPointerParameters) ??
|
||||
false;
|
||||
const needsBufferView =
|
||||
(kFunctionParamTypeCases[t.params.decl].valid === 'with_buffer_view' ||
|
||||
arg.needsBufferView) ??
|
||||
false;
|
||||
|
||||
let isValid = parameterMatches(t.params.decl, arg.matches);
|
||||
if (isValid && needsUnrestrictedPointerParameters) {
|
||||
isValid = t.hasLanguageFeature('unrestricted_pointer_parameters');
|
||||
}
|
||||
if (isValid && needsBufferView) {
|
||||
isValid =
|
||||
t.hasLanguageFeature('unrestricted_pointer_parameters') &&
|
||||
t.hasLanguageFeature('buffer_view');
|
||||
}
|
||||
|
||||
t.expectCompileResult(isValid, code);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ const kInvalidLocationTypes = new Set([
|
|||
'texture_depth_multisampled_2d',
|
||||
'sampler',
|
||||
'sampler_comparison',
|
||||
'buffer',
|
||||
'buffer<16>',
|
||||
]);
|
||||
|
||||
g.test('stage_inout')
|
||||
|
|
|
|||
188
src/webgpu/shader/validation/types/buffer.spec.ts
Normal file
188
src/webgpu/shader/validation/types/buffer.spec.ts
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
export const description = `
|
||||
Validation tests for buffer types
|
||||
`;
|
||||
|
||||
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
||||
import { keysOf } from '../../../../common/util/data_tables.js';
|
||||
import { ShaderValidationTest } from '../shader_validation_test.js';
|
||||
|
||||
export const g = makeTestGroup(ShaderValidationTest);
|
||||
|
||||
const kParseCases = {
|
||||
unsized: {
|
||||
code: `alias T = buffer;`,
|
||||
valid: true,
|
||||
},
|
||||
literal: {
|
||||
code: `alias T = buffer<16>;`,
|
||||
valid: true,
|
||||
},
|
||||
literal_negative: {
|
||||
code: `alias T = buffer<-1>;`,
|
||||
valid: false,
|
||||
},
|
||||
literal_zero: {
|
||||
code: `alias T = buffer<0>;`,
|
||||
valid: false,
|
||||
},
|
||||
const: {
|
||||
code: `const x = 16; alias T = buffer<x>;`,
|
||||
valid: true,
|
||||
},
|
||||
const_negative: {
|
||||
code: `const x = -1; alias T = buffer<x>;`,
|
||||
valid: false,
|
||||
},
|
||||
const_zero: {
|
||||
code: `const x = 0; alias T = buffer<x>;`,
|
||||
valid: false,
|
||||
},
|
||||
const_function: {
|
||||
code: `const x = 16; const y = 32; alias T = buffer<min(x, y)>;`,
|
||||
valid: true,
|
||||
},
|
||||
override: {
|
||||
code: `override x = 16; alias T = buffer<x>;`,
|
||||
valid: true,
|
||||
},
|
||||
override_negative: {
|
||||
code: `override x = -1; alias T = buffer<x>;`,
|
||||
valid: true,
|
||||
},
|
||||
override_zero: {
|
||||
code: `override x = 0; alias T = buffer<x>;`,
|
||||
valid: true,
|
||||
},
|
||||
override_function: {
|
||||
code: `override x = 16; override y = 32; alias T = buffer<max(x, y)>;`,
|
||||
valid: true,
|
||||
},
|
||||
empty_template: {
|
||||
code: `alias T = buffer<>;`,
|
||||
valid: false,
|
||||
},
|
||||
missing_rparen: {
|
||||
code: `alias T = buffer<16;`,
|
||||
valid: false,
|
||||
},
|
||||
subtype: {
|
||||
code: `alias T = buffer<u32>;`,
|
||||
valid: false,
|
||||
},
|
||||
pointer_workgroup_unsized_buffer: {
|
||||
code: `alias T = buffer; alias PT = ptr<workgroup, T>;`,
|
||||
valid: true,
|
||||
},
|
||||
pointer_uniform_unsized_buffer: {
|
||||
code: `alias T = buffer; alias PT = ptr<uniform, T>;`,
|
||||
valid: true,
|
||||
},
|
||||
pointer_storage_unsized_buffer: {
|
||||
code: `alias T = buffer; alias PT = ptr<storage, T>;`,
|
||||
valid: true,
|
||||
},
|
||||
pointer_workgroup_sized_buffer: {
|
||||
code: `alias T = buffer<32>; alias PT = ptr<workgroup, T>;`,
|
||||
valid: true,
|
||||
},
|
||||
pointer_uniform_sized_buffer: {
|
||||
code: `alias T = buffer<32>; alias PT = ptr<uniform, T>;`,
|
||||
valid: true,
|
||||
},
|
||||
pointer_storage_sized_buffer: {
|
||||
code: `alias T = buffer<32>; alias PT = ptr<storage, T>;`,
|
||||
valid: true,
|
||||
},
|
||||
pointer_workgroup_override_sized_buffer: {
|
||||
code: `override o : i32; alias T = buffer<o>; alias PT = ptr<workgroup, T>;`,
|
||||
valid: true,
|
||||
},
|
||||
pointer_uniform_override_sized_buffer: {
|
||||
code: `override o : i32; alias T = buffer<o>; alias PT = ptr<uniform, T>;`,
|
||||
valid: false,
|
||||
},
|
||||
pointer_storage_override_sized_buffer: {
|
||||
code: `override o : i32; alias T = buffer<o>; alias PT = ptr<storage, T>;`,
|
||||
valid: false,
|
||||
},
|
||||
buffer_3bytes: {
|
||||
code: `alias T = buffer<3>;`,
|
||||
valid: false,
|
||||
},
|
||||
buffer_2bytes_f16: {
|
||||
code: `enable f16;\nalias T = buffer<2>;`,
|
||||
valid: true,
|
||||
},
|
||||
buffer_2bytes_no_f16: {
|
||||
code: `alias T = buffer<2>;`,
|
||||
valid: false,
|
||||
},
|
||||
};
|
||||
|
||||
g.test('parse')
|
||||
.desc('Test buffer type parsing')
|
||||
.params(u => u.combine('case', keysOf(kParseCases)))
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
const testcase = kParseCases[t.params.case];
|
||||
t.expectCompileResult(testcase.valid, testcase.code);
|
||||
});
|
||||
|
||||
g.test('address_space')
|
||||
.desc('Test buffer type validity for each address space')
|
||||
.params(u =>
|
||||
u
|
||||
.combine('case', keysOf(kParseCases))
|
||||
.filter(t => {
|
||||
return kParseCases[t.case].valid && !t.case.includes('pointer');
|
||||
})
|
||||
.beginSubcases()
|
||||
.combine('aspace', [
|
||||
'function',
|
||||
'private',
|
||||
'storage',
|
||||
'uniform',
|
||||
'workgroup',
|
||||
'immediate',
|
||||
] as const)
|
||||
)
|
||||
.fn(t => {
|
||||
t.skipIfLanguageFeatureNotSupported('buffer_view');
|
||||
const testcase = kParseCases[t.params.case];
|
||||
|
||||
let mvar = '';
|
||||
let fvar = '';
|
||||
switch (t.params.aspace) {
|
||||
case 'function':
|
||||
fvar = `var v : T;`;
|
||||
break;
|
||||
case 'private':
|
||||
case 'workgroup':
|
||||
case 'immediate':
|
||||
mvar = `var<${t.params.aspace}> v : T;`;
|
||||
break;
|
||||
case 'storage':
|
||||
case 'uniform':
|
||||
mvar = `@group(0) @binding(0) var<${t.params.aspace}> v : T;`;
|
||||
break;
|
||||
}
|
||||
|
||||
const wgsl = `
|
||||
${testcase.code}
|
||||
${mvar}
|
||||
@compute @workgroup_size(1)
|
||||
fn main() {
|
||||
${fvar}
|
||||
}`;
|
||||
|
||||
let expected =
|
||||
t.params.aspace !== 'function' &&
|
||||
t.params.aspace !== 'private' &&
|
||||
t.params.aspace !== 'immediate';
|
||||
if (t.params.case === 'unsized') {
|
||||
expected = t.params.aspace === 'storage';
|
||||
} else if (t.params.case.includes('override')) {
|
||||
expected = t.params.aspace === 'workgroup';
|
||||
}
|
||||
t.expectCompileResult(expected, wgsl);
|
||||
});
|
||||
Loading…
Reference in a new issue