Skip to content

Commit

Permalink
Changes for review
Browse files Browse the repository at this point in the history
* Remove fragment tests for now
* Add helpers to generate input data and add new class of cases
* Increase number of cases to 15 per variants
* Export data sentinel value
  • Loading branch information
alan-baker committed Aug 30, 2024
1 parent 35e8569 commit 6b4cc01
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 487 deletions.
225 changes: 45 additions & 180 deletions src/webgpu/shader/execution/expression/call/builtin/subgroupAll.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,50 @@ import {
kWGSizes,
kPredicateCases,
SubgroupTest,
kFramebufferSizes,
runComputeTest,
runFragmentTest,
kDataSentinel
} from './subgroup_util.js';

export const g = makeTestGroup(SubgroupTest);

const kNumCases = 10;
const kNumCases = 15;

/**
* Generate input data for testing.
*
* Data is generated in the following categories:
* Seed 0 generates all 0 data
* Seed 1 generates all 1 data
* Seeds 2-9 generates all 1s except for a zero randomly once per 32 elements
* Seeds 10+ generate all random data
* @param seed The seed for the PRNG
* @param num The number of data items to generate
* @param addCounter If true, treats the first index as an atomic counter
*/
function generateInputData(seed: number, num: number, addCounter: boolean): Uint32Array {
const prng = new PRNG(seed);

const bound = Math.min(num, 32);
const index = prng.uniformInt(bound);
//console.log(`bound = ${bound}, index = ${index}`);

return new Uint32Array([...iterRange(num, x => {
if (addCounter && x === 0) {
// Counter should start at 1 to avoid clear value.
return 1;
}

if (seed === 0) {
return 0;
} else if (seed === 1) {
return 1;
} else if (seed < 10) {
const bounded = (addCounter ? x + 1 : x) % bound;
return bounded === index ? 0 : 1;
}
return prng.uniformInt(2);
})]);
}

/**
* Checks the result of a subgroupAll operation
Expand Down Expand Up @@ -80,7 +116,7 @@ function checkAll(
- got: ${res}`);
}
} else {
if (res !== 999) {
if (res !== kDataSentinel) {
return new Error(`Invocation ${inv} unexpected write:
- subgroup invocation id: ${id}
- subgroup size: ${size}`);
Expand Down Expand Up @@ -139,20 +175,8 @@ fn main(
outputs[lid] = res;
}`;

const prng = new PRNG(t.params.case);
// Case 0 is all 0s.
// Case 1 is all 1s.
// Other cases are filled with random 0s and 1s.
const inputData = new Uint32Array([
...iterRange(wgThreads, x => {
if (t.params.case === 0) {
return 0;
} else if (t.params.case === 1) {
return 1;
}
return prng.uniformInt(2);
}),
]);
const includeCounter = false;
const inputData = generateInputData(t.params.case, wgThreads, includeCounter);

const uintsPerOutput = 2;
await runComputeTest(
Expand Down Expand Up @@ -223,20 +247,8 @@ fn main(
}
}`;

const prng = new PRNG(t.params.case);
// Case 0 is all 0s.
// Case 1 is all 1s.
// Other cases are filled with random 0s and 1s.
const inputData = new Uint32Array([
...iterRange(wgThreads, x => {
if (t.params.case === 0) {
return 0;
} else if (t.params.case === 1) {
return 1;
}
return prng.uniformInt(2);
}),
]);
const includeCounter = false;
const inputData = generateInputData(t.params.case, wgThreads, includeCounter);

const uintsPerOutput = 2;
await runComputeTest(
Expand All @@ -251,151 +263,4 @@ fn main(
);
});

/**
* Checks subgroupAll results from a fragment shader.
*
* @param data Framebuffer output
* * component 0 is result
* * component 1 is generated subgroup id
* @param input An array of input data offset by 1 uint
* @param format The framebuffer format
* @param width Framebuffer width
* @param height Framebuffer height
*/
function checkFragmentAll(
data: Uint32Array,
input: Uint32Array,
format: GPUTextureFormat,
width: number,
height: number
): Error | undefined {
const { blockWidth, blockHeight, bytesPerBlock } = kTextureFormatInfo[format];
const blocksPerRow = width / blockWidth;
// 256 minimum comes from image copy requirements.
const bytesPerRow = align(blocksPerRow * (bytesPerBlock ?? 1), 256);
const uintsPerRow = bytesPerRow / 4;
const uintsPerTexel = (bytesPerBlock ?? 1) / blockWidth / blockHeight / 4;

const expected = new Map<number, number>();
for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
const offset = uintsPerRow * row + col * uintsPerTexel;
const subgroup_id = data[offset + 1];

if (subgroup_id === 0) {
return new Error(`Internal error: helper invocation at (${col}, ${row})`);
}

let v = expected.get(subgroup_id) ?? 1;
// First index of input is an atomic counter.
v &= input[1 + row * width + col];
expected.set(subgroup_id, v);
}
}

for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
const offset = uintsPerRow * row + col * uintsPerTexel;
const res = data[offset];
const subgroup_id = data[offset + 1];

if (subgroup_id === 0) {
// Inactive in the fragment.
continue;
}

const expected_v = expected.get(subgroup_id) ?? 0;
if (expected_v !== res) {
return new Error(`Row ${row}, col ${col}: incorrect results:
- expected: ${expected_v}
- got: ${res}`);
}
}
}

return undefined;
}

g.test('fragment')
.desc('Tests subgroupAll in fragment shaders')
.params(u =>
u
.combine('size', kFramebufferSizes)
.beginSubcases()
.combine('case', [...iterRange(kNumCases, x => x)])
.combineWithParams([{ format: 'rg32uint' }] as const)
)
.beforeAllSubcases(t => {
t.selectDeviceOrSkipTestCase('subgroups' as GPUFeatureName);
})
.fn(async t => {
const prng = new PRNG(t.params.case);
// Case 0 is all 0s.
// Case 1 is all 1s.
// Other cases are filled with random 0s and 1s.
//
// Note: the first index is used as an atomic counter for subgroup ids.
const numInputs = t.params.size[0] * t.params.size[1] + 1;
const inputData = new Uint32Array([
...iterRange(numInputs, x => {
if (x === 0) {
// All subgroup ids start from index 1.
return 1;
} else if (t.params.case === 0) {
return 0;
} else if (t.params.case === 1) {
return 1;
}
return prng.uniformInt(2);
}),
]);

const fsShader = `
enable subgroups;
struct Inputs {
subgroup_id : atomic<u32>,
data : array<u32>,
}
@group(0) @binding(0)
var<storage, read_write> inputs : Inputs;
@fragment
fn main(
@builtin(position) pos : vec4f,
) -> @location(0) vec2u {
var subgroup_id = 0u;
if subgroupElect() {
subgroup_id = atomicAdd(&inputs.subgroup_id, 1);
}
subgroup_id = subgroupBroadcastFirst(subgroup_id);
// Filter out texels outside the frame (possible helper invocations).
var input = 1u;
if (u32(pos.x) >= 0 && u32(pos.x) < ${t.params.size[0]} &&
u32(pos.y) >= 0 && u32(pos.y) < ${t.params.size[1]}) {
input = inputs.data[u32(pos.y) * ${t.params.size[0]} + u32(pos.x)];
}
let res = select(0u, 1u, subgroupAll(bool(input)));
return vec2u(res, subgroup_id);
}`;

await runFragmentTest(
t,
t.params.format,
fsShader,
t.params.size[0],
t.params.size[1],
inputData,
(data: Uint32Array) => {
return checkFragmentAll(
data,
inputData,
t.params.format,
t.params.size[0],
t.params.size[1]
);
}
);
});
g.test('fragment').unimplemented()
Loading

0 comments on commit 6b4cc01

Please sign in to comment.