Skip to content

Commit

Permalink
Improve fragment shader checks in subgroupBitwise (#4070)
Browse files Browse the repository at this point in the history
* Modifies the fragment tests in subgroupBitwise to avoid any subgroup
  in the last row or column
  * this fixes failures seen on some devices performing subgroupXor when
    helpers are in bounds, but not on the edge of the framebuffer
* Tests skip cases that could not possibly yield useful results, but
  checking is also terminated early if no useful results can be found
  * Enough framebuffer variants are swept to ensure some tests will
    cover useful subgroups, but the sizes are only known after a pipeline
    compile
  • Loading branch information
alan-baker authored Dec 4, 2024
1 parent ca6d65d commit 096f9fd
Showing 1 changed file with 52 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ fn main(
/**
* Checks bitwise ops results from a fragment shader.
*
* Avoids the last row and column to skip potential helper invocations.
* Avoids subgroups in last row or column to skip potential helper invocations.
* @param data Framebuffer output
* * component 0 is result
* * component 1 is generated subgroup id
Expand All @@ -453,26 +453,58 @@ function checkBitwiseFragment(
): Error | undefined {
const { uintsPerRow, uintsPerTexel } = getUintsPerFramebuffer(format, width, height);

// Iteration skips last row and column to avoid helper invocations because it is not
// guaranteed whether or not they participate in the subgroup operation.
// Determine if the subgroup should be included in the checks.
const inBounds = new Map<number, boolean>();
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 ok = inBounds.get(subgroup_id) ?? true;
ok = ok && row !== height - 1 && col !== width - 1;
inBounds.set(subgroup_id, ok);
}
}

let anyInBounds = false;
for (const [_, value] of inBounds) {
const ok = Boolean(value);
anyInBounds = anyInBounds || ok;
}
if (!anyInBounds) {
// This variant would not reliably test behavior.
return undefined;
}

// Iteration skips subgroups in the last row or column to avoid helper
// invocations because it is not guaranteed whether or not they participate
// in the subgroup operation.
const expected = new Map<number, number>();
for (let row = 0; row < height - 1; row++) {
for (let col = 0; col < width - 1; col++) {
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})`);
}

const subgroupInBounds = inBounds.get(subgroup_id) ?? true;
if (!subgroupInBounds) {
continue;
}

let v = expected.get(subgroup_id) ?? identity(op);
v = bitwise(op, v, input[row * width + col]);
expected.set(subgroup_id, v);
}
}

for (let row = 0; row < height - 1; row++) {
for (let col = 0; col < width - 1; col++) {
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];
Expand All @@ -482,6 +514,11 @@ function checkBitwiseFragment(
continue;
}

const subgroupInBounds = inBounds.get(subgroup_id) ?? true;
if (!subgroupInBounds) {
continue;
}

const expected_v = expected.get(subgroup_id) ?? 0;
if (expected_v !== res) {
return new Error(`Row ${row}, col ${col}: incorrect results:
Expand Down Expand Up @@ -509,6 +546,14 @@ g.test('fragment,all_active')
})
.fn(async t => {
const numInputs = t.params.size[0] * t.params.size[1];

interface SubgroupLimits extends GPUSupportedLimits {
minSubgroupSize: number;
}
const { minSubgroupSize } = t.device.limits as SubgroupLimits;
const innerTexels = (t.params.size[0] - 1) * (t.params.size[1] - 1);
t.skipIf(innerTexels < minSubgroupSize, 'Too few texels to be reliable');

const inputData = generateInputData(t.params.case, numInputs, identity(t.params.op));

const ident = identity(t.params.op) === 0 ? '0' : '0xffffffff';
Expand Down

0 comments on commit 096f9fd

Please sign in to comment.