summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/statement/discard.spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/statement/discard.spec.js')
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/statement/discard.spec.js645
1 files changed, 645 insertions, 0 deletions
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/statement/discard.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/statement/discard.spec.js
new file mode 100644
index 0000000000..bcd4fd9e52
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/statement/discard.spec.js
@@ -0,0 +1,645 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/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 { iterRange } from '../../../../common/util/util.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<storage, read_write> output: array<vec2f>;
+@group(0) @binding(1) var<storage, read_write> atomicIndex : atomic<u32>;
+@group(0) @binding(2) var<storage> uniformValues : array<u32, 5>;
+
+@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,
+code,
+dataChecker,
+framebufferChecker)
+{
+ 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.COPY_DST |
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.TEXTURE_BINDING,
+ format: 'r32uint'
+ });
+ t.trackForCleanup(framebuffer);
+
+ // Create a buffer to copy the framebuffer contents into.
+ // 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
+ // (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(
+ // Loop bound, [derivative constants].
+ 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();
+ 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: 'load',
+ 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) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx, value) => {
+ return value === 0;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx) => {
+ return 0;
+ }
+ }]
+
+ }
+ );
+ };
+
+ // No fragment outputs occur.
+ const fbChecker = (a) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx, value) => {
+ return value === kWidth * kHeight;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx) => {
+ return 0;
+ }
+ }]
+
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+});
+
+g.test('three_quarters').
+desc('Test a shader that discards all but the upper-left quadrant fragments').
+fn((t) => {
+ const code = `
+${kSharedCode}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ _ = uniformValues[0];
+ if (pos.x >= 0.5 * ${kWidth} || pos.y >= 0.5 * ${kHeight}) {
+ discard;
+ }
+ let idx = atomicAdd(&atomicIndex, 1);
+ output[idx] = pos.xy;
+ return idx;
+}
+`;
+
+ // Only the the upper left quadrant is kept.
+ const dataChecker = (a) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx, value) => {
+ const is_x = idx % 2 === 0;
+ if (is_x) {
+ return value < 0.5 * kWidth;
+ } else {
+ return value < 0.5 * kHeight;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx) => {
+ 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;
+ }
+ }
+ if (is_x) {
+ return `< ${0.5 * kWidth}`;
+ } else {
+ return `< ${0.5 * kHeight}`;
+ }
+ }
+ }]
+
+ }
+ );
+ };
+ const fbChecker = (a) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx, value) => {
+ 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;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx) => {
+ 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) {
+ let p = vec2i(pos);
+ if p.x <= ${kWidth} / 2 && p.y <= ${kHeight} / 2 {
+ discard;
+ }
+ if p.x >= ${kWidth} / 2 && p.y >= ${kHeight} / 2 {
+ 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) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx, value) => {
+ const is_x = idx % 2 === 0;
+ if (value === 0.0) {
+ return is_x ? a[idx + 1] === 0 : a[idx - 1] === 0;
+ }
+
+ 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 * kWidth : a[idx - 1] < 0.5 * kHeight;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx) => {
+ if (idx < kWidth * kHeight / 2) {
+ return 'any';
+ } else {
+ return 0;
+ }
+ }
+ }]
+
+ }
+ );
+ };
+ const fbChecker = (a) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx, value) => {
+ 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: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (
+ x <= kWidth / 2 && y <= kHeight / 2 ||
+ x >= kWidth / 2 && y >= kHeight / 2)
+ {
+ return kWidth * kHeight;
+ }
+ 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) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx, value) => {
+ return value === 0;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx) => {
+ return 0;
+ }
+ }]
+
+ }
+ );
+ };
+
+ // No fragment outputs occur.
+ const fbChecker = (a) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx, value) => {
+ return value === kWidth * kHeight;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx) => {
+ return kWidth * kHeight;
+ }
+ }]
+
+ }
+ );
+ };
+
+ 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) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx, value) => {
+ return value === 0;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx) => {
+ return 0;
+ }
+ }]
+
+ }
+ );
+ };
+
+ // No fragment outputs occur.
+ const fbChecker = (a) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx, value) => {
+ return value === kWidth * kHeight;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx) => {
+ return kWidth * kHeight;
+ }
+ }]
+
+ }
+ );
+ };
+
+ 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;
+}
+`;
+
+ // One pixel per quad is discarded. The derivatives values are always the same +/- 3.
+ const dataChecker = (a) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx, value) => {
+ if (idx < 3 * (2 * kWidth * kHeight) / 4) {
+ return value === -3 || value === 3;
+ } else {
+ return value === 0;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx) => {
+ if (idx < 3 * (2 * kWidth * kHeight) / 4) {
+ return '+/- 3';
+ } else {
+ return 0;
+ }
+ }
+ }]
+
+ }
+ );
+ };
+
+ // 3/4 of the fragments are written.
+ const fbChecker = (a) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx, value) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (((x | y) & 0x1) === 0) {
+ return value === kWidth * kHeight;
+ } else {
+ return value < 3 * (kWidth * kHeight) / 4;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (((x | y) & 0x1) === 0) {
+ return kWidth * kHeight;
+ } else {
+ return 'any';
+ }
+ }
+ }]
+
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+}); \ No newline at end of file