From 5fd5c0eede32b1cdb0a9f6d2617dfed9b0e9faf4 Mon Sep 17 00:00:00 2001 From: Alan Baker Date: Fri, 8 Mar 2024 10:37:10 -0500 Subject: [PATCH 1/5] Discard shader execution tests * Tests: * discard all * discard 3 quadrants * discard in function call * discard in various loops * derivatives and discard --- src/webgpu/listing_meta.json | 6 + .../execution/statement/discard.spec.ts | 606 ++++++++++++++++++ 2 files changed, 612 insertions(+) create mode 100644 src/webgpu/shader/execution/statement/discard.spec.ts diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index 99efc128226d..1364cdb4e4f2 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1762,6 +1762,12 @@ "webgpu:shader,execution,stage:basic_compute:*": { "subcaseMS": 1.000 }, "webgpu:shader,execution,stage:basic_render:*": { "subcaseMS": 1.000 }, "webgpu:shader,execution,statement,compound:decl:*": { "subcaseMS": 29.767 }, + "webgpu:shader,execution,statement,discard:all:*": { "subcaseMS": 36.094 }, + "webgpu:shader,execution,statement,discard:derivatives:*": { "subcaseMS": 15.287 }, + "webgpu:shader,execution,statement,discard:function_call:*": { "subcaseMS": 11.744 }, + "webgpu:shader,execution,statement,discard:loop:*": { "subcaseMS": 11.821 }, + "webgpu:shader,execution,statement,discard:three_quarters:*": { "subcaseMS": 34.735 }, + "webgpu:shader,execution,statement,discard:uniform_read_loop:*": { "subcaseMS": 13.095 }, "webgpu:shader,execution,statement,increment_decrement:frexp_exp_increment:*": { "subcaseMS": 4.700 }, "webgpu:shader,execution,statement,increment_decrement:scalar_i32_decrement:*": { "subcaseMS": 20.301 }, "webgpu:shader,execution,statement,increment_decrement:scalar_i32_decrement_underflow:*": { "subcaseMS": 4.900 }, diff --git a/src/webgpu/shader/execution/statement/discard.spec.ts b/src/webgpu/shader/execution/statement/discard.spec.ts new file mode 100644 index 000000000000..38d5cb52c8ce --- /dev/null +++ b/src/webgpu/shader/execution/statement/discard.spec.ts @@ -0,0 +1,606 @@ +export const description = ` +Execution tests for discard. + +The discard statement converts invocations into helpers. +This results in the following conditions: + * No outputs are written + * No resources are written + * Atomics are undefined + +Conditions that still occur: + * Derivative calculations are correct + * Reads + * Writes to non-external memory +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../gpu_test.js'; +import { checkElementsPassPredicate } from '../../../util/check_contents.js'; + +export const g = makeTestGroup(GPUTest); + +// Framebuffer dimensions +const kWidth = 64; +const kHeight = 64; + +const kSharedCode = ` +@group(0) @binding(0) var output: array; +@group(0) @binding(1) var atomicIndex : atomic; +@group(0) @binding(2) var uniformValues : array; + +@vertex +fn vsMain(@builtin(vertex_index) index : u32) -> @builtin(position) vec4f { + const vertices = array( + vec2(-1, -1), vec2(-1, 0), vec2( 0, -1), + vec2(-1, 0), vec2( 0, 0), vec2( 0, -1), + + vec2( 0, -1), vec2( 0, 0), vec2( 1, -1), + vec2( 0, 0), vec2( 1, 0), vec2( 1, -1), + + vec2(-1, 0), vec2(-1, 1), vec2( 0, 0), + vec2(-1, 1), vec2( 0, 1), vec2( 0, 0), + + vec2( 0, 0), vec2( 0, 1), vec2( 1, 0), + vec2( 0, 1), vec2( 1, 1), vec2( 1, 0), + ); + return vec4f(vec2f(vertices[index]), 0, 1); +} +`; + +function drawFullScreen( + t: GPUTest, + code: string, + dataChecker: (a: Float32Array) => Error | undefined, + framebufferChecker: (a: Uint32Array) => Error | undefined +) { + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { + module: t.device.createShaderModule({ code }), + entryPoint: 'vsMain', + }, + fragment: { + module: t.device.createShaderModule({ code }), + entryPoint: 'fsMain', + targets: [{ format: 'r32uint' }], + }, + primitive: { + topology: 'triangle-list', + }, + }); + + const bytesPerWord = 4; + const framebuffer = t.device.createTexture({ + size: [kWidth, kHeight], + usage: + GPUTextureUsage.COPY_SRC | + GPUTextureUsage.RENDER_ATTACHMENT | + GPUTextureUsage.TEXTURE_BINDING, + format: 'r32uint', + }); + t.trackForCleanup(framebuffer); + + // Create a buffer to copy the framebuffer contents into. + const fbBuffer = t.device.createBuffer({ + size: kWidth * kHeight * bytesPerWord, + usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, + }); + + // Create a buffer to hold the storage shader resources. + // (0,0) = vec2u width * height + // (0,1) = u32 + const dataSize = 2 * kWidth * kHeight * bytesPerWord + const dataBufferSize = dataSize + bytesPerWord + const dataBuffer = t.device.createBuffer({ + size: dataBufferSize, + usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE, + }); + + const uniformSize = bytesPerWord * 5; + const uniformBuffer = t.makeBufferWithContents( + new Uint32Array([4,1,4,4,7]), + GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE + ); + + // 'atomicIndex' packed at the end of the buffer. + const bg = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { + binding: 0, + resource: { + buffer: dataBuffer, + offset: 0, + size: dataSize, + }, + }, + { + binding: 1, + resource: { + buffer: dataBuffer, + offset: dataSize, + size: bytesPerWord, + }, + }, + { + binding: 2, + resource: { + buffer: uniformBuffer, + offset: 0, + size: uniformSize, + }, + }, + ], + }); + + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: framebuffer.createView(), + loadOp: 'clear', + storeOp: 'store', + }, + ], + }); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bg); + pass.draw(24); + pass.end(); + encoder.copyTextureToBuffer( + { texture: framebuffer }, + { + buffer: fbBuffer, + offset: 0, + bytesPerRow: kWidth * bytesPerWord, + rowsPerImage: kHeight, + }, + { width: kWidth, height: kHeight } + ); + t.queue.submit([encoder.finish()]); + + t.expectGPUBufferValuesPassCheck(dataBuffer, dataChecker, { + type: Float32Array, + typedLength: dataSize / bytesPerWord, + }); + + t.expectGPUBufferValuesPassCheck(fbBuffer, framebufferChecker, { + type: Uint32Array, + typedLength: kWidth * kHeight, + }); +} + +g.test('all') + .desc('Test a shader that discards all fragments') + .fn(t => { + const code = ` +${kSharedCode} + +@fragment +fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { + _ = uniformValues[0]; + discard; + let idx = atomicAdd(&atomicIndex, 1); + output[idx] = pos.xy; + return 1; +} +`; + + // No storage writes occur. + const dataChecker = (a: Float32Array) => { + return checkElementsPassPredicate( + a, + (idx: number, value: number | bigint) => { + return value === 0; + }, + { + predicatePrinter: [ + { + leftHeader: 'data exp ==', + getValueForCell: (idx: number) => { + return 0; + }, + }, + ], + } + ); + }; + + // No fragment outputs occur. + const fbChecker = (a: Uint32Array) => { + return checkElementsPassPredicate( + a, + (idx: number, value: number | bigint) => { + return value === 0; + }, + { + predicatePrinter: [ + { + leftHeader: 'fb exp ==', + getValueForCell: (idx: number) => { + return 0; + }, + }, + ], + } + ); + }; + + drawFullScreen(t, code, dataChecker, fbChecker); + }); + +g.test('three_quarters') + .desc('Test a shader that discards all fragments') + .fn(t => { + const code = ` +${kSharedCode} + +@fragment +fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { + _ = uniformValues[0]; + if (pos.x >= 0.5 || pos.y >= 0.5) { + discard; + } + let idx = atomicAdd(&atomicIndex, 1); + output[idx] = pos.xy; + return idx; +} +`; + + // Only the the upper left quadrant is kept. + const dataChecker = (a: Float32Array) => { + return checkElementsPassPredicate( + a, + (idx: number, value: number | bigint) => { + return value < 0.5; + }, + { + predicatePrinter: [ + { + leftHeader: 'data exp ==', + getValueForCell: (idx: number): number | string => { + const is_x = idx % 2 === 0; + if (is_x) { + const x = Math.floor(idx / 2) % kWidth; + if (x >= (kWidth / 2)) { + return 0; + } + } else { + const y = Math.floor((idx - 1) / kWidth); + if (y >= (kHeight / 2)) { + return 0; + } + } + return '< 0.5'; + }, + }, + ], + } + ); + }; + const fbChecker = (a: Uint32Array) => { + return checkElementsPassPredicate( + a, + (idx: number, value: number | bigint) => { + return value < (kWidth * kHeight / 4) && (value == a[idx - 1] || value == 0); + }, + { + predicatePrinter: [ + { + leftHeader: 'fb exp ==', + getValueForCell: (idx: number) => { + const x = idx % kWidth; + const y = Math.floor(idx / kWidth); + if (x < (kWidth / 2) && y < (kHeight / 2)) { + return 'any'; + } else { + return 0; + } + }, + }, + ], + } + ); + }; + + drawFullScreen(t, code, dataChecker, fbChecker); + }); + +g.test('function_call') + .desc('Test discards happening in a function call') + .fn(t => { + const code = ` +${kSharedCode} + +fn foo(pos : vec2f) { + if (pos.x <= 0.5 && pos.y <= 0.5) { + discard; + } + if (pos.x >= 0.5 && pos.y >= 0.5) { + discard; + } +} + +@fragment +fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { + _ = uniformValues[0]; + foo(pos.xy); + let idx = atomicAdd(&atomicIndex, 1); + output[idx] = pos.xy; + return idx; +} +`; + + // Only the upper right and bottom left quadrants are kept. + const dataChecker = (a: Float32Array) => { + return checkElementsPassPredicate( + a, + (idx: number, value: number | bigint) => { + const is_x = idx % 2 === 0; + if (value === 0.0) { + return is_x ? a[idx + 1] === 0 : a[idx - 1] === 0; + } + + if (value < 0.5) { + return is_x ? a[idx + 1] > 0.5 : a[idx - 1] > 0.5; + } else { + return is_x ? a[idx + 1] < 0.5 : a[idx - 1] < 0.5; + } + }, + { + predicatePrinter: [ + { + leftHeader: 'data exp ==', + getValueForCell: (idx: number): number | string => { + if (idx < (kWidth * kHeight) / 2) { + return 'any'; + } else { + return 0; + } + }, + }, + ], + } + ); + }; + const fbChecker = (a: Uint32Array) => { + return checkElementsPassPredicate( + a, + (idx: number, value: number | bigint) => { + return value < (kWidth * kHeight / 2) && (value == a[idx - 1] || value == 0); + }, + { + predicatePrinter: [ + { + leftHeader: 'fb exp ==', + getValueForCell: (idx: number) => { + const x = idx % kWidth; + const y = Math.floor(idx / kWidth); + if (x < 0.5 && y < 0.5) { + return 0; + } else if (x > 0.5 && y > 0.5) { + return 0; + } + return 'any'; + }, + }, + ], + } + ); + }; + + drawFullScreen(t, code, dataChecker, fbChecker); + }); + +g.test('loop') + .desc('Test discards in a loop') + .fn(t => { + const code = ` +${kSharedCode} + +@fragment +fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { + _ = uniformValues[0]; + for (var i = 0; i < 2; i++) { + if i > 0 { + discard; + } + } + let idx = atomicAdd(&atomicIndex, 1); + output[idx] = pos.xy; + return 1; +} +`; + + // No storage writes occur. + const dataChecker = (a: Float32Array) => { + return checkElementsPassPredicate( + a, + (idx: number, value: number | bigint) => { + return value === 0; + }, + { + predicatePrinter: [ + { + leftHeader: 'data exp ==', + getValueForCell: (idx: number) => { + return 0; + }, + }, + ], + } + ); + }; + + // No fragment outputs occur. + const fbChecker = (a: Uint32Array) => { + return checkElementsPassPredicate( + a, + (idx: number, value: number | bigint) => { + return value === 0; + }, + { + predicatePrinter: [ + { + leftHeader: 'fb exp ==', + getValueForCell: (idx: number) => { + return 0; + }, + }, + ], + } + ); + }; + + drawFullScreen(t, code, dataChecker, fbChecker); + }); + +g.test('uniform_read_loop') + .desc('Test that helpers read a uniform value in a loop') + .fn(t => { + const code = ` +${kSharedCode} + +@fragment +fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { + discard; + for (var i = 0u; i < uniformValues[0]; i++) { + } + let idx = atomicAdd(&atomicIndex, 1); + output[idx] = pos.xy; + return 1; +} +`; + + // No storage writes occur. + const dataChecker = (a: Float32Array) => { + return checkElementsPassPredicate( + a, + (idx: number, value: number | bigint) => { + return value === 0; + }, + { + predicatePrinter: [ + { + leftHeader: 'data exp ==', + getValueForCell: (idx: number) => { + return 0; + }, + }, + ], + } + ); + }; + + // No fragment outputs occur. + const fbChecker = (a: Uint32Array) => { + return checkElementsPassPredicate( + a, + (idx: number, value: number | bigint) => { + return value === 0; + }, + { + predicatePrinter: [ + { + leftHeader: 'fb exp ==', + getValueForCell: (idx: number) => { + return 0; + }, + }, + ], + } + ); + }; + + drawFullScreen(t, code, dataChecker, fbChecker); + }); + +g.test('derivatives') + .desc('Test that derivatives are correct in the presence of discard') + .fn(t => { + const code = ` +${kSharedCode} + +@fragment +fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { + let ipos = vec2i(pos.xy); + let lsb = ipos & vec2(0x1); + let left_sel = select(2, 4, lsb.y == 1); + let right_sel = select(1, 3, lsb.y == 1); + let uidx = select(left_sel, right_sel, lsb.x == 1); + if ((lsb.x | lsb.y) & 0x1) == 0 { + discard; + } + + let v = uniformValues[uidx]; + let idx = atomicAdd(&atomicIndex, 1); + let dx = dpdx(f32(v)); + let dy = dpdy(f32(v)); + output[idx] = vec2(dx, dy); + return idx; +} +`; + + // No storage writes occur. + const dataChecker = (a: Float32Array) => { + return checkElementsPassPredicate( + a, + (idx: number, value: number | bigint) => { + if (idx < 3 * (2 * kWidth * kHeight) / 4) { + return value === -3 || value === 3; + } else { + return value === 0; + } + }, + { + predicatePrinter: [ + { + leftHeader: 'data exp ==', + getValueForCell: (idx: number) => { + if (idx < 3 * (2 * kWidth * kHeight) / 4) { + return '+/- 3'; + } else { + return 0; + } + }, + }, + ], + } + ); + }; + + // No fragment outputs occur. + const fbChecker = (a: Uint32Array) => { + return checkElementsPassPredicate( + a, + (idx: number, value: number | bigint) => { + const x = idx % kWidth; + const y = Math.floor(idx / kWidth); + if (((x | y) & 0x1) == 0) { + return value === 0; + } else { + return value < 3 * (kWidth * kHeight) / 4; + } + }, + { + predicatePrinter: [ + { + leftHeader: 'fb exp ==', + getValueForCell: (idx: number) => { + const x = idx % kWidth; + const y = Math.floor(idx / kWidth); + if (((x | y) & 0x1) == 0) { + return 0; + } else { + return 'any'; + } + }, + }, + ], + } + ); + }; + + drawFullScreen(t, code, dataChecker, fbChecker); + }); + From ced94917b53b9c6b3736f5b889cca831fc49be9a Mon Sep 17 00:00:00 2001 From: Alan Baker Date: Fri, 8 Mar 2024 10:48:41 -0500 Subject: [PATCH 2/5] fixups --- .../execution/statement/discard.spec.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/webgpu/shader/execution/statement/discard.spec.ts b/src/webgpu/shader/execution/statement/discard.spec.ts index 38d5cb52c8ce..ca779fd50af7 100644 --- a/src/webgpu/shader/execution/statement/discard.spec.ts +++ b/src/webgpu/shader/execution/statement/discard.spec.ts @@ -89,8 +89,8 @@ function drawFullScreen( // Create a buffer to hold the storage shader resources. // (0,0) = vec2u width * height // (0,1) = u32 - const dataSize = 2 * kWidth * kHeight * bytesPerWord - const dataBufferSize = dataSize + bytesPerWord + const dataSize = 2 * kWidth * kHeight * bytesPerWord; + const dataBufferSize = dataSize + bytesPerWord; const dataBuffer = t.device.createBuffer({ size: dataBufferSize, usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE, @@ -98,7 +98,8 @@ function drawFullScreen( const uniformSize = bytesPerWord * 5; const uniformBuffer = t.makeBufferWithContents( - new Uint32Array([4,1,4,4,7]), + // Loop bound, [derivative constants]. + new Uint32Array([4, 1, 4, 4, 7]), GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE ); @@ -262,12 +263,12 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { const is_x = idx % 2 === 0; if (is_x) { const x = Math.floor(idx / 2) % kWidth; - if (x >= (kWidth / 2)) { + if (x >= kWidth / 2) { return 0; } } else { const y = Math.floor((idx - 1) / kWidth); - if (y >= (kHeight / 2)) { + if (y >= kHeight / 2) { return 0; } } @@ -282,7 +283,7 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { return checkElementsPassPredicate( a, (idx: number, value: number | bigint) => { - return value < (kWidth * kHeight / 4) && (value == a[idx - 1] || value == 0); + return value < (kWidth * kHeight) / 4 && (value === a[idx - 1] || value === 0); }, { predicatePrinter: [ @@ -291,7 +292,7 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { getValueForCell: (idx: number) => { const x = idx % kWidth; const y = Math.floor(idx / kWidth); - if (x < (kWidth / 2) && y < (kHeight / 2)) { + if (x < kWidth / 2 && y < kHeight / 2) { return 'any'; } else { return 0; @@ -367,7 +368,7 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { return checkElementsPassPredicate( a, (idx: number, value: number | bigint) => { - return value < (kWidth * kHeight / 2) && (value == a[idx - 1] || value == 0); + return value < (kWidth * kHeight) / 2 && (value === a[idx - 1] || value === 0); }, { predicatePrinter: [ @@ -541,12 +542,12 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { } `; - // No storage writes occur. + // One pixel per quad is discarded. The derivatives values are always the same +/- 3. const dataChecker = (a: Float32Array) => { return checkElementsPassPredicate( a, (idx: number, value: number | bigint) => { - if (idx < 3 * (2 * kWidth * kHeight) / 4) { + if (idx < (3 * (2 * kWidth * kHeight)) / 4) { return value === -3 || value === 3; } else { return value === 0; @@ -557,7 +558,7 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { { leftHeader: 'data exp ==', getValueForCell: (idx: number) => { - if (idx < 3 * (2 * kWidth * kHeight) / 4) { + if (idx < (3 * (2 * kWidth * kHeight)) / 4) { return '+/- 3'; } else { return 0; @@ -569,17 +570,17 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { ); }; - // No fragment outputs occur. + // 3/4 of the fragments are written. const fbChecker = (a: Uint32Array) => { return checkElementsPassPredicate( a, (idx: number, value: number | bigint) => { const x = idx % kWidth; const y = Math.floor(idx / kWidth); - if (((x | y) & 0x1) == 0) { + if (((x | y) & 0x1) === 0) { return value === 0; } else { - return value < 3 * (kWidth * kHeight) / 4; + return value < (3 * (kWidth * kHeight)) / 4; } }, { @@ -589,7 +590,7 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { getValueForCell: (idx: number) => { const x = idx % kWidth; const y = Math.floor(idx / kWidth); - if (((x | y) & 0x1) == 0) { + if (((x | y) & 0x1) === 0) { return 0; } else { return 'any'; @@ -603,4 +604,3 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { drawFullScreen(t, code, dataChecker, fbChecker); }); - From cb6a597c82e6f5c9b4876a3d7d81d06e92c23ecf Mon Sep 17 00:00:00 2001 From: Alan Baker Date: Wed, 13 Mar 2024 10:32:20 -0400 Subject: [PATCH 3/5] Changes for review * Fix test description * Fix coordinates used by fragment shader --- .../execution/statement/discard.spec.ts | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/webgpu/shader/execution/statement/discard.spec.ts b/src/webgpu/shader/execution/statement/discard.spec.ts index ca779fd50af7..eef2bb746691 100644 --- a/src/webgpu/shader/execution/statement/discard.spec.ts +++ b/src/webgpu/shader/execution/statement/discard.spec.ts @@ -231,7 +231,7 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { }); g.test('three_quarters') - .desc('Test a shader that discards all fragments') + .desc('Test a shader that discards all but the upper-left quadrant fragments') .fn(t => { const code = ` ${kSharedCode} @@ -239,7 +239,7 @@ ${kSharedCode} @fragment fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { _ = uniformValues[0]; - if (pos.x >= 0.5 || pos.y >= 0.5) { + if (pos.x >= 0.5 * ${kWidth} || pos.y >= 0.5 * ${kHeight}) { discard; } let idx = atomicAdd(&atomicIndex, 1); @@ -253,7 +253,12 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { return checkElementsPassPredicate( a, (idx: number, value: number | bigint) => { - return value < 0.5; + const is_x = idx % 2 === 0; + if (is_x) { + return value < 0.5 * kWidth; + } else { + return value < 0.5 * kHeight; + } }, { predicatePrinter: [ @@ -272,7 +277,11 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { return 0; } } - return '< 0.5'; + if (is_x) { + return `< ${0.5 * kWidth}`; + } else { + return `< ${0.5 * kHeight}`; + } }, }, ], @@ -283,7 +292,7 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { return checkElementsPassPredicate( a, (idx: number, value: number | bigint) => { - return value < (kWidth * kHeight) / 4 && (value === a[idx - 1] || value === 0); + return value < (kWidth * kHeight) / 4; }, { predicatePrinter: [ @@ -314,10 +323,10 @@ g.test('function_call') ${kSharedCode} fn foo(pos : vec2f) { - if (pos.x <= 0.5 && pos.y <= 0.5) { + if pos.x <= 0.5 * ${kWidth} && pos.y <= 0.5 * ${kHeight} { discard; } - if (pos.x >= 0.5 && pos.y >= 0.5) { + if pos.x >= 0.5 * ${kWidth} && pos.y >= 0.5 * ${kHeight} { discard; } } @@ -342,10 +351,12 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { return is_x ? a[idx + 1] === 0 : a[idx - 1] === 0; } - if (value < 0.5) { - return is_x ? a[idx + 1] > 0.5 : a[idx - 1] > 0.5; + let expect = is_x ? kWidth : kHeight; + expect = 0.5 * expect; + if (value < expect) { + return is_x ? a[idx + 1] > 0.5 * kWidth : a[idx - 1] > 0.5 * kHeight; } else { - return is_x ? a[idx + 1] < 0.5 : a[idx - 1] < 0.5; + return is_x ? a[idx + 1] < 0.5 * kWidth : a[idx - 1] < 0.5 * kHeight; } }, { @@ -368,7 +379,7 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { return checkElementsPassPredicate( a, (idx: number, value: number | bigint) => { - return value < (kWidth * kHeight) / 2 && (value === a[idx - 1] || value === 0); + return value < (kWidth * kHeight) / 2; }, { predicatePrinter: [ From 0fb2246b00eb2d81c4417bc6171a8ac2ff983bb0 Mon Sep 17 00:00:00 2001 From: Alan Baker Date: Thu, 14 Mar 2024 12:13:11 -0400 Subject: [PATCH 4/5] Changes for review * Initialize the framebuffer with a sentinel value * Modify checkers to detect sentinel value as no write instead of zero * Update function call test to use integer coords --- .../execution/statement/discard.spec.ts | 67 +++++++++++++------ 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/src/webgpu/shader/execution/statement/discard.spec.ts b/src/webgpu/shader/execution/statement/discard.spec.ts index eef2bb746691..110d70862b28 100644 --- a/src/webgpu/shader/execution/statement/discard.spec.ts +++ b/src/webgpu/shader/execution/statement/discard.spec.ts @@ -14,6 +14,7 @@ Conditions that still occur: `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { iterRange } from '../../../../common/util/util.js'; import { GPUTest } from '../../../gpu_test.js'; import { checkElementsPassPredicate } from '../../../util/check_contents.js'; @@ -74,6 +75,7 @@ function drawFullScreen( size: [kWidth, kHeight], usage: GPUTextureUsage.COPY_SRC | + GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, format: 'r32uint', @@ -81,10 +83,11 @@ function drawFullScreen( t.trackForCleanup(framebuffer); // Create a buffer to copy the framebuffer contents into. - const fbBuffer = t.device.createBuffer({ - size: kWidth * kHeight * bytesPerWord, - usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, - }); + // Initialize with a sentinel value and load this buffer to detect unintended writes. + const fbBuffer = t.makeBufferWithContents( + new Uint32Array([...iterRange(kWidth * kHeight, x => kWidth * kHeight)]), + GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST + ); // Create a buffer to hold the storage shader resources. // (0,0) = vec2u width * height @@ -135,11 +138,21 @@ function drawFullScreen( }); const encoder = t.device.createCommandEncoder(); + encoder.copyBufferToTexture( + { + buffer: fbBuffer, + offset: 0, + bytesPerRow: kWidth * bytesPerWord, + rowsPerImage: kHeight, + }, + { texture: framebuffer }, + { width: kWidth, height: kHeight } + ); const pass = encoder.beginRenderPass({ colorAttachments: [ { view: framebuffer.createView(), - loadOp: 'clear', + loadOp: 'load', storeOp: 'store', }, ], @@ -212,7 +225,8 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { return checkElementsPassPredicate( a, (idx: number, value: number | bigint) => { - return value === 0; + return value === kWidth * kHeight; + //return value === 0; }, { predicatePrinter: [ @@ -292,6 +306,13 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { return checkElementsPassPredicate( a, (idx: number, value: number | bigint) => { + const x = idx % kWidth; + const y = Math.floor(idx / kWidth); + if (x < kWidth / 2 && y < kHeight / 2) { + return value < (kWidth * kHeight) / 4; + } else { + return value === kWidth * kHeight; + } return value < (kWidth * kHeight) / 4; }, { @@ -323,10 +344,11 @@ g.test('function_call') ${kSharedCode} fn foo(pos : vec2f) { - if pos.x <= 0.5 * ${kWidth} && pos.y <= 0.5 * ${kHeight} { + let p = vec2i(pos); + if p.x <= ${kWidth} / 2 && p.y <= ${kHeight} / 2 { discard; } - if pos.x >= 0.5 * ${kWidth} && pos.y >= 0.5 * ${kHeight} { + if p.x >= ${kWidth} / 2 && p.y >= ${kHeight} / 2 { discard; } } @@ -379,7 +401,13 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { return checkElementsPassPredicate( a, (idx: number, value: number | bigint) => { - return value < (kWidth * kHeight) / 2; + const x = idx % kWidth; + const y = Math.floor(idx / kWidth); + if ((x >= kWidth / 2 && y >= kHeight / 2) || (x <= kWidth / 2 && y <= kHeight / 2)) { + return value === kWidth * kHeight; + } else { + return value < (kWidth * kHeight) / 2; + } }, { predicatePrinter: [ @@ -388,10 +416,11 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { getValueForCell: (idx: number) => { const x = idx % kWidth; const y = Math.floor(idx / kWidth); - if (x < 0.5 && y < 0.5) { - return 0; - } else if (x > 0.5 && y > 0.5) { - return 0; + if ( + (x <= kWidth / 2 && y <= kHeight / 2) || + (x >= kWidth / 2 && y >= kHeight / 2) + ) { + return kWidth * kHeight; } return 'any'; }, @@ -449,14 +478,14 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { return checkElementsPassPredicate( a, (idx: number, value: number | bigint) => { - return value === 0; + return value === kWidth * kHeight; }, { predicatePrinter: [ { leftHeader: 'fb exp ==', getValueForCell: (idx: number) => { - return 0; + return kWidth * kHeight; }, }, ], @@ -509,14 +538,14 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { return checkElementsPassPredicate( a, (idx: number, value: number | bigint) => { - return value === 0; + return value === kWidth * kHeight; }, { predicatePrinter: [ { leftHeader: 'fb exp ==', getValueForCell: (idx: number) => { - return 0; + return kWidth * kHeight; }, }, ], @@ -589,7 +618,7 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { const x = idx % kWidth; const y = Math.floor(idx / kWidth); if (((x | y) & 0x1) === 0) { - return value === 0; + return value === kWidth * kHeight; } else { return value < (3 * (kWidth * kHeight)) / 4; } @@ -602,7 +631,7 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { const x = idx % kWidth; const y = Math.floor(idx / kWidth); if (((x | y) & 0x1) === 0) { - return 0; + return kWidth * kHeight; } else { return 'any'; } From 632ba3c661ad41ef5b3f152d7d764ea5b6abd6ea Mon Sep 17 00:00:00 2001 From: Alan Baker Date: Thu, 14 Mar 2024 12:38:09 -0400 Subject: [PATCH 5/5] remove dead code --- src/webgpu/shader/execution/statement/discard.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/webgpu/shader/execution/statement/discard.spec.ts b/src/webgpu/shader/execution/statement/discard.spec.ts index 110d70862b28..058ff50f1746 100644 --- a/src/webgpu/shader/execution/statement/discard.spec.ts +++ b/src/webgpu/shader/execution/statement/discard.spec.ts @@ -226,7 +226,6 @@ fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 { a, (idx: number, value: number | bigint) => { return value === kWidth * kHeight; - //return value === 0; }, { predicatePrinter: [