diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-05-12 15:59:20 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-05-25 06:57:22 +0000 |
commit | f7eaed5286974984ba5f9e3189d8f49d03e99f81 (patch) | |
tree | caed19b2af2024f35449fb0b781d0a25e09d4f8f /chromium/third_party/webgpu-cts | |
parent | 9729c4479fe23554eae6e6dd1f30ff488f470c84 (diff) | |
download | qtwebengine-chromium-f7eaed5286974984ba5f9e3189d8f49d03e99f81.tar.gz |
BASELINE: Update Chromium to 100.0.4896.167
Change-Id: I98cbeb5d7543d966ffe04d8cefded0c493a11333
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/third_party/webgpu-cts')
252 files changed, 7099 insertions, 2203 deletions
diff --git a/chromium/third_party/webgpu-cts/scripts/compile_src.py b/chromium/third_party/webgpu-cts/scripts/compile_src.py index 80e26860af3..b2b20e32e3f 100755 --- a/chromium/third_party/webgpu-cts/scripts/compile_src.py +++ b/chromium/third_party/webgpu-cts/scripts/compile_src.py @@ -44,11 +44,13 @@ def compile_src(out_dir): ]) -def compile_src_for_node(out_dir): - # First, clean the output directory so deleted files are pruned from old builds. - shutil.rmtree(out_dir) +def compile_src_for_node(out_dir, additional_args=None, clean=True): + additional_args = additional_args or [] + if clean: + # First, clean the output directory so deleted files are pruned from old builds. + shutil.rmtree(out_dir) - run_tsc_ignore_errors([ + args = [ '--project', os.path.join(webgpu_cts_dir, 'src', 'node.tsconfig.json'), '--outDir', @@ -63,7 +65,10 @@ def compile_src_for_node(out_dir): 'false', '--target', 'ES6', - ]) + ] + args.extend(additional_args) + + run_tsc_ignore_errors(args) if __name__ == '__main__': diff --git a/chromium/third_party/webgpu-cts/scripts/generate_telemetry_expectations.js b/chromium/third_party/webgpu-cts/scripts/generate_telemetry_expectations.js new file mode 100644 index 00000000000..c3a8e76de60 --- /dev/null +++ b/chromium/third_party/webgpu-cts/scripts/generate_telemetry_expectations.js @@ -0,0 +1,58 @@ +// Copyright 2022 The Chromium Authors.All rights reserved. +// Use of this source code is governed by a BSD - style license that can be +// found in the LICENSE file. + +// Note: Prefer to run this file via generate_telemetry_expecations.py which +// parses arguments and forwards them to this .js script. +const ctsRoot = process.argv[2]; +const { expectations } = require(process.argv[3]); +const expectationsOut = process.argv[4] // Optional + +const fs = require('fs'); + +const { DefaultTestFileLoader } = require(`${ctsRoot}/common/internal/file_loader`); +const { parseQuery } = require(`${ctsRoot}/common/internal/query/parseQuery`); + +(async () => { + const outStream = expectationsOut ? fs.createWriteStream(expectationsOut) + : process.stdout; + + try { + const loader = new DefaultTestFileLoader(); + for (const entry of expectations) { + for (const testcase of await loader.loadCases(parseQuery(entry.q))) { + const name = testcase.query.toString(); + if (entry.b) { + outStream.write(entry.b); + outStream.write(' '); + } + + if (entry.t) { + outStream.write('[') + for (const tag of entry.t) { + outStream.write(' '); + outStream.write(tag); + } + outStream.write(' ] ') + } + + outStream.write(name); + + if (entry.e) { + outStream.write(' [') + for (const exp of entry.e) { + outStream.write(' '); + outStream.write(exp); + } + outStream.write(' ]') + } + + outStream.write('\n'); + } + } + } finally { + // An error may have happened. Wait for the stream to finish writing + // before exiting in case seeing the intermediate results is helpful. + await new Promise(resolve => outStream.once('finish', resolve)); + } +})(); diff --git a/chromium/third_party/webgpu-cts/scripts/generate_telemetry_expectations.py b/chromium/third_party/webgpu-cts/scripts/generate_telemetry_expectations.py new file mode 100755 index 00000000000..5410ad4d64e --- /dev/null +++ b/chromium/third_party/webgpu-cts/scripts/generate_telemetry_expectations.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# Copyright 2022 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import argparse +import logging +import os +import shutil +import sys +import tempfile + +third_party_dir = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from compile_src import compile_src_for_node + + +def generate_telemetry_expectations(cts_expectation_queries, + expectations_out, + js_out_dir=None): + if js_out_dir is None: + js_out_dir = tempfile.mkdtemp() + delete_js_out_dir = True + else: + delete_js_out_dir = False + + try: + logging.info('WebGPU CTS: Transpiling tools...') + compile_src_for_node(js_out_dir, [ + '--incremental', '--tsBuildInfoFile', + os.path.join(js_out_dir, 'build.tsbuildinfo') + ], + clean=False) + + old_sys_path = sys.path + try: + sys.path = old_sys_path + [os.path.join(third_party_dir, 'node')] + from node import RunNode + finally: + sys.path = old_sys_path + + args = [ + os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'generate_telemetry_expectations.js'), + os.path.abspath(js_out_dir), + os.path.abspath(cts_expectation_queries), + ] + if expectations_out: + args.append(os.path.abspath(expectations_out)) + + return RunNode(args) + finally: + if delete_js_out_dir: + shutil.rmtree(js_out_dir) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('cts_expectation_queries', + help="Path to the CTS expectation queries") + parser.add_argument( + '--expectations-out', + default=None, + help="Path to output expectations. If not passed, prints to stdout.") + parser.add_argument( + '--js-out-dir', + default=None, + help='Output directory for intermediate compiled JS sources') + args = parser.parse_args() + + print( + generate_telemetry_expectations(args.cts_expectation_queries, + args.expectations_out, + args.js_out_dir)) diff --git a/chromium/third_party/webgpu-cts/scripts/list.py b/chromium/third_party/webgpu-cts/scripts/list.py new file mode 100755 index 00000000000..fc161ea5b5e --- /dev/null +++ b/chromium/third_party/webgpu-cts/scripts/list.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# Copyright 2022 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import argparse +import logging +import os +import shutil +import sys +import tempfile + +third_party_dir = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from compile_src import compile_src_for_node + + +def list_testcases(query, js_out_dir=None): + if js_out_dir is None: + js_out_dir = tempfile.mkdtemp() + delete_js_out_dir = True + else: + delete_js_out_dir = False + + try: + logging.info('WebGPU CTS: Transpiling tools...') + compile_src_for_node(js_out_dir, [ + '--incremental', '--tsBuildInfoFile', + os.path.join(js_out_dir, 'build.tsbuildinfo') + ], + clean=False) + + old_sys_path = sys.path + try: + sys.path = old_sys_path + [os.path.join(third_party_dir, 'node')] + from node import RunNode + finally: + sys.path = old_sys_path + + return RunNode([ + os.path.join(js_out_dir, 'common', 'runtime', 'cmdline.js'), query, + '--list' + ]) + finally: + if delete_js_out_dir: + shutil.rmtree(js_out_dir) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--query', default='webgpu:*', help='WebGPU CTS Query') + parser.add_argument( + '--js-out-dir', + default=None, + help='Output directory for intermediate compiled JS sources') + args = parser.parse_args() + + print(list_testcases(args.query, args.js_out_dir)) diff --git a/chromium/third_party/webgpu-cts/scripts/run_webgpu_cts.py b/chromium/third_party/webgpu-cts/scripts/run_webgpu_cts.py index 4d4f2c0f731..0892226b746 100644 --- a/chromium/third_party/webgpu-cts/scripts/run_webgpu_cts.py +++ b/chromium/third_party/webgpu-cts/scripts/run_webgpu_cts.py @@ -34,7 +34,7 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.send_header('Access-Control-Allow-Origin', '*') self.send_header('Content-Type', 'application/javascript') self.end_headers() - self.wfile.write(self.server.expectations_js) + self.wfile.write(self.server.expectations_js.encode()) elif self.path == '/_start' or self.path == '/_stop': self.send_response(200) self.end_headers() diff --git a/chromium/third_party/webgpu-cts/src/.eslintrc.json b/chromium/third_party/webgpu-cts/src/.eslintrc.json index 145f83c5bae..6824bbbe31d 100644 --- a/chromium/third_party/webgpu-cts/src/.eslintrc.json +++ b/chromium/third_party/webgpu-cts/src/.eslintrc.json @@ -21,6 +21,11 @@ "object-shorthand": "warn", "quotes": ["warn", "single", { "avoidEscape": true, "allowTemplateLiterals": true }], + // All test TODOs must be tracked inside file/test descriptions or READMEs. + // Comments relating to TODOs in descriptions can be marked with references like "[1]". + // TODOs not relating to test coverage can be marked MAINTENANCE_TODO or similar. + "no-warning-comments": ["warn", { "terms": ["todo", "fixme", "xxx"], "location": "anywhere" }], + // Plugin: @typescript-eslint "@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-unused-vars": ["warn", { "vars": "all", "args": "none" }], diff --git a/chromium/third_party/webgpu-cts/src/.github/pull_request_template.md b/chromium/third_party/webgpu-cts/src/.github/pull_request_template.md index 31bcdfef81c..7fadba0fc3b 100644 --- a/chromium/third_party/webgpu-cts/src/.github/pull_request_template.md +++ b/chromium/third_party/webgpu-cts/src/.github/pull_request_template.md @@ -1,19 +1,21 @@ +Issue: #<!-- Fill in the issue number here. See docs/intro/life_of.md --> <hr> -**Author checklist for test code/plans:** +**Requirements for PR author:** -- [ ] All outstanding work is tracked with "TODO" in a test/file description or `.unimplemented()` on a test. -- [ ] New helpers, if any, are documented using `/** doc comments */` and can be found via `helper_index.txt`. -- [ ] (Optional, sometimes not possible.) Tests pass (or partially pass without unexpected issues) in an implementation. (Add any extra details above.) +- [ ] All missing test coverage is tracked with "TODO" or `.unimplemented()`. +- [ ] New helpers are `/** documented */` and new helper files are found in `helper_index.txt`. +- [ ] Test behaves as expected in a WebGPU implementation. (If not passing, explain above.) -**[Reviewer sign-off](https://github.com/gpuweb/cts/blob/main/docs/reviews.md) for test code/plans:** (Note: feel free to pull in other reviewers at any time for any reason.) +**Requirements for [reviewer sign-off](https://github.com/gpuweb/cts/blob/main/docs/reviews.md):** -- [ ] The test path is reasonable, the [description](https://github.com/gpuweb/cts/blob/main/docs/intro/plans.md) "describes the test, succinctly, but in enough detail that a reader can read only the test plans in a file or directory and evaluate the completeness of the test coverage." -- [ ] Tests appear to cover this area completely, except for outstanding TODOs. Validation tests use control cases. - (This is critical for coverage. Assume anything without a TODO will be forgotten about forever.) -- [ ] Existing (or new) test helpers are used where they would reduce complexity. -- [ ] TypeScript code is readable and understandable (is unobtrusive, has reasonable type-safety/verbosity/dynamicity). +- [ ] Tests are properly located in the test tree. +- [ ] [Test descriptions](https://github.com/gpuweb/cts/blob/main/docs/intro/plans.md) allow a reader to "read only the test plans and evaluate coverage completeness", and accurately reflect the test code. +- [ ] Tests provide complete coverage (including validation control cases). **Missing coverage MUST be covered by TODOs.** +- [ ] Helpers and types promote readability and maintainability. + +When landing this PR, be sure to make any necessary issue status updates. diff --git a/chromium/third_party/webgpu-cts/src/Gruntfile.js b/chromium/third_party/webgpu-cts/src/Gruntfile.js index 680b51c5dbf..da2a7b177ec 100644 --- a/chromium/third_party/webgpu-cts/src/Gruntfile.js +++ b/chromium/third_party/webgpu-cts/src/Gruntfile.js @@ -18,7 +18,7 @@ module.exports = function (grunt) { }, 'generate-listings': { cmd: 'node', - args: ['tools/gen_listings', 'out/', 'src/webgpu', 'src/unittests', 'src/demo'], + args: ['tools/gen_listings', 'out/', 'src/webgpu', 'src/stress', 'src/manual', 'src/unittests', 'src/demo'], }, 'generate-wpt-cts-html': { cmd: 'node', diff --git a/chromium/third_party/webgpu-cts/src/cts.code-workspace b/chromium/third_party/webgpu-cts/src/cts.code-workspace index b45dfa0cb29..aeca61e7125 100644 --- a/chromium/third_party/webgpu-cts/src/cts.code-workspace +++ b/chromium/third_party/webgpu-cts/src/cts.code-workspace @@ -96,6 +96,15 @@ "command": "npm run tsdoc", "problemMatcher": [] }, + { + "group": "build", + "label": "grunt: run:lint", + "detail": "Run eslint", + + "type": "shell", + "command": "npx grunt run:lint", + "problemMatcher": ["$eslint-stylish"] + }, ] } } diff --git a/chromium/third_party/webgpu-cts/src/docs/intro/README.md b/chromium/third_party/webgpu-cts/src/docs/intro/README.md index f73cb5c1cd6..e5f8bcedc69 100644 --- a/chromium/third_party/webgpu-cts/src/docs/intro/README.md +++ b/chromium/third_party/webgpu-cts/src/docs/intro/README.md @@ -22,6 +22,9 @@ to do. ## Contributing +Testing tasks are tracked in the [CTS project tracker](https://github.com/orgs/gpuweb/projects/3). +Go here if you're looking for tasks, or if you have a test idea that isn't already covered. + If contributing conformance tests, the directory you'll work in is [`src/webgpu/`](../src/webgpu/). This directory is organized according to the goal of the test (API validation behavior vs actual results) and its target (API entry points and spec areas, e.g. texture sampling). @@ -30,7 +33,7 @@ The contents of a test file (`src/webgpu/**/*.spec.ts`) are twofold: - Documentation ("test plans") on what tests do, how they do it, and what cases they cover. Some test plans are fully or partially unimplemented: - they either contain "TODO:" in a description or are `.unimplemented()`. + they either contain "TODO" in a description or are `.unimplemented()`. - Actual tests. **Please read the following short documents before contributing.** diff --git a/chromium/third_party/webgpu-cts/src/docs/intro/convert_to_issue.png b/chromium/third_party/webgpu-cts/src/docs/intro/convert_to_issue.png Binary files differnew file mode 100644 index 00000000000..672324a9d9b --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/docs/intro/convert_to_issue.png diff --git a/chromium/third_party/webgpu-cts/src/docs/intro/developing.md b/chromium/third_party/webgpu-cts/src/docs/intro/developing.md index 538f208a819..1f4aa9747a5 100644 --- a/chromium/third_party/webgpu-cts/src/docs/intro/developing.md +++ b/chromium/third_party/webgpu-cts/src/docs/intro/developing.md @@ -66,6 +66,12 @@ You can make local configuration changes in `.vscode/`, which is untracked by Gi ## Pull Requests +When opening a pull request, fill out the PR checklist and attach the issue number. +If an issue hasn't been opened, find the draft issue on the +[project tracker](https://github.com/orgs/gpuweb/projects/3) and choose "Convert to issue": + +![convert to issue button screenshot](convert_to_issue.png) + Opening a pull request will automatically notify reviewers. To make the review process smoother, once a reviewer has started looking at your change: @@ -80,3 +86,14 @@ To make the review process smoother, once a reviewer has started looking at your - When you address a review comment, mark the thread as "Resolved". Pull requests will (usually) be landed with the "Squash and merge" option. + +### TODOs + +The word "TODO" refers to missing test coverage. It may only appear inside file/test descriptions +and README files (enforced by linting). + +To use comments to refer to TODOs inside the description, use a backreference, e.g., in the +description, `TODO: Also test the FROBNICATE usage flag [1]`, and somewhere in the code, `[1]: +Need to add FROBNICATE to this list.`. + +Use `MAINTENANCE_TODO` for TODOs which don't impact test coverage. diff --git a/chromium/third_party/webgpu-cts/src/docs/intro/life_of.md b/chromium/third_party/webgpu-cts/src/docs/intro/life_of.md index 85994ca3ac6..2fd741c3013 100644 --- a/chromium/third_party/webgpu-cts/src/docs/intro/life_of.md +++ b/chromium/third_party/webgpu-cts/src/docs/intro/life_of.md @@ -1,41 +1,46 @@ # Life of a Test Change -## Lifetime of new test plans +A "test change" could be a new test, an expansion of an existing test, a test bug fix, or a +modification to existing tests to make them match new spec changes. -For anything in the spec/API/ToC that is not currently covered by -the test plan. Note that (the completed portions of) the initial version of -this document, is based on parts of the planned -[Table of Contents](https://github.com/gpuweb/gpuweb/wiki/Table-of-Contents) -as of ~2020-08. +**CTS contributors should contribute to the tracker and strive to keep it up to date, especially +relating to their own changes.** -1. Test plan is written, reviewed, and landed (if the implementer is different from the planner, - they should review), with TODOs as needed. -1. Tests are implemented, reviewed, and landed, with TODOs as needed. +Filing new draft issues in the CTS project tracker is very lightweight. +Anyone with access should do this eagerly, to ensure no testing ideas are forgotten. +(And if you don't have access, just file a regular issue.) -## Lifetime of test plan changes to match spec changes +1. Enter a draft issue with "Status" "New (not in repo)" is in the + [CTS project tracker](https://github.com/orgs/gpuweb/projects/3) with any available info + (notes/plans to ensure full test coverage of the change). The source of this may be: -For changes that come through the specification process. + - Anything in the spec/API that is found not to be covered by the CTS yet. + - Any test is found to be outdated or otherwise buggy. + - A spec change from the "Needs CTS Issue" column in the + [spec project tracker](https://github.com/orgs/gpuweb/projects/1). + Once information on the required test changes is entered into the CTS project tracker, + the spec issue moves to "Specification Done". -1. Spec changes go through the [WebGPU project tracker](https://github.com/orgs/gpuweb/projects/1). -1. Once they reach the "Needs Test Plan" column, they can be added into the CTS. - The item doesn't have to be fully tested, but must be reflected in the test plan in enough - detail (with any necessary TODOs) to ensure the tests are implemented fully, later on. - Then, the item can move to the "Specification Done" column. - - Some features may have tests written before their specification is complete. - If they are still in the "Needs Specification" column just make sure there - is a note on the issue that tests are being written, and make sure any spec - changes get reflected in the tests. -1. Plan and implement as above. + Note: at some point, someone may make a PR to flush "New (not in repo)" issues into `TODO`s in + CTS file/test description text, changing their "Status" to "Open". + These may be done in bulk without linking back to the issue. -## Lifetime of additions to existing test plans +1. As necessary: -For any new cases or testing found by any test plan author, WebGPU spec author, -WebGPU implementer, WebGPU user, etc. For example, inspiration could come from -reading an existing test suite (like dEQP or WebGL). + - Convert the draft issue to a full, numbered issue for linking from later PRs. -1. Add notes, plans, or implementation to the CTS, with TODOs as needed to ensure the addition - gets implemented fully. + ![convert to issue button screenshot](convert_to_issue.png) -A change may (or may not) have an associated issue on the spec or CTS repository on GitHub. -If it is otherwise resolved, then it can be closed once reflected in the CTS, so that test work -is tracked in just one place. + - Update the "Assignees" of the issue when an issue is assigned or unassigned + (you can assign yourself). + - Change the "Status" of the issue to "Started" once you start the task. + +1. Open one or more PRs, **each linking to the associated issue**. + Each PR may is reviewed and landed, and may leave further TODOs for parts it doesn't complete. + + 1. Test are "planned" in test descriptions. (For complex tests, open a separate PR with the + tests `.unimplemented()` so a reviewer can evaluate the plan before you implement tests.) + 1. Tests are implemented. + +1. When **no TODOs remain** for an issue, close it and change its status to "Complete". + (Enter a new more, specific draft issue into the tracker if you need to track related TODOs.) diff --git a/chromium/third_party/webgpu-cts/src/docs/intro/plans.md b/chromium/third_party/webgpu-cts/src/docs/intro/plans.md index faba1718b41..f8d7af3a786 100644 --- a/chromium/third_party/webgpu-cts/src/docs/intro/plans.md +++ b/chromium/third_party/webgpu-cts/src/docs/intro/plans.md @@ -12,7 +12,7 @@ and potentially useful [helpers](../helper_index.txt). **A test plan must serve two functions:** - Describes the test, succinctly, but in enough detail that a reader can read *only* the test - plans in a file or directory and evaluate the completeness of the test coverage. + plans and evaluate coverage completeness of a file/directory. - Describes the test precisely enough that, when code is added, the reviewer can ensure that the test really covers what the test plan says. @@ -60,8 +60,8 @@ For any notes which are not specific to a single test, or for preliminary notes haven't been planned in full detail, put them in the test file's `description` variable at the top. Or, if they aren't associated with a test file, put them in a `README.txt` file. -**Any notes about things which are unimplemented must be marked with `TODO:` inside -the description string.** +**Any notes about missing test coverage must be marked with the word `TODO` inside a +description or README.** This makes them appear on the `/standalone/` page. ## 2. Open a pull request diff --git a/chromium/third_party/webgpu-cts/src/package-lock.json b/chromium/third_party/webgpu-cts/src/package-lock.json index 3716e680bc4..1667ced10c2 100644 --- a/chromium/third_party/webgpu-cts/src/package-lock.json +++ b/chromium/third_party/webgpu-cts/src/package-lock.json @@ -20,7 +20,7 @@ "@types/offscreencanvas": "^2019.6.2", "@types/serve-index": "^1.9.1", "@typescript-eslint/parser": "^4.22.0", - "@webgpu/types": "0.1.9", + "@webgpu/types": "0.1.11", "ansi-colors": "4.1.1", "babel-plugin-add-header-comment": "^1.0.3", "babel-plugin-const-enum": "^1.0.1", @@ -1374,9 +1374,9 @@ } }, "node_modules/@webgpu/types": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.9.tgz", - "integrity": "sha512-CGG93bZ4znugJ7KWdvWYGTemt7rUmdpzt1BOMCHI/5Kgt08tOtnn9sFmk4ZT26aGgwxBT5iC/djCxzbfxVdldA==", + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.11.tgz", + "integrity": "sha512-hz4skElgAhpWOWWDCJSGVlzTAcJ3TBuIA+NI4VZNI13yfm+zhgOg5lTTw5YaYhxWcS3dsLNd7xmNoLkeksW9fQ==", "dev": true }, "node_modules/abbrev": { @@ -10591,9 +10591,9 @@ } }, "@webgpu/types": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.9.tgz", - "integrity": "sha512-CGG93bZ4znugJ7KWdvWYGTemt7rUmdpzt1BOMCHI/5Kgt08tOtnn9sFmk4ZT26aGgwxBT5iC/djCxzbfxVdldA==", + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.11.tgz", + "integrity": "sha512-hz4skElgAhpWOWWDCJSGVlzTAcJ3TBuIA+NI4VZNI13yfm+zhgOg5lTTw5YaYhxWcS3dsLNd7xmNoLkeksW9fQ==", "dev": true }, "abbrev": { diff --git a/chromium/third_party/webgpu-cts/src/package.json b/chromium/third_party/webgpu-cts/src/package.json index da0164e5453..7b4c4b0d929 100644 --- a/chromium/third_party/webgpu-cts/src/package.json +++ b/chromium/third_party/webgpu-cts/src/package.json @@ -40,7 +40,7 @@ "@types/offscreencanvas": "^2019.6.2", "@types/serve-index": "^1.9.1", "@typescript-eslint/parser": "^4.22.0", - "@webgpu/types": "0.1.9", + "@webgpu/types": "0.1.11", "ansi-colors": "4.1.1", "babel-plugin-add-header-comment": "^1.0.3", "babel-plugin-const-enum": "^1.0.1", diff --git a/chromium/third_party/webgpu-cts/src/src/common/framework/fixture.ts b/chromium/third_party/webgpu-cts/src/src/common/framework/fixture.ts index 702f1c9186f..61f179ab21e 100644 --- a/chromium/third_party/webgpu-cts/src/src/common/framework/fixture.ts +++ b/chromium/third_party/webgpu-cts/src/src/common/framework/fixture.ts @@ -101,7 +101,8 @@ export class Fixture { /** * Tracks an object to be cleaned up after the test finishes. * - * TODO: Use this in more places. (Will be easier once .destroy() is allowed on invalid objects.) + * MAINTENANCE_TODO: Use this in more places. (Will be easier once .destroy() is allowed on + * invalid objects.) */ trackForCleanup<T extends DestroyableObject>(o: T): T { this.objectsToCleanUp.push(o); diff --git a/chromium/third_party/webgpu-cts/src/src/common/internal/logging/result.ts b/chromium/third_party/webgpu-cts/src/src/common/internal/logging/result.ts index ad427c4ccb3..0de661b50ce 100644 --- a/chromium/third_party/webgpu-cts/src/src/common/internal/logging/result.ts +++ b/chromium/third_party/webgpu-cts/src/src/common/internal/logging/result.ts @@ -1,6 +1,6 @@ import { LogMessageWithStack } from './log_message.js'; -// TODO: Add warn expectations +// MAINTENANCE_TODO: Add warn expectations export type Expectation = 'pass' | 'skip' | 'fail'; export type Status = 'running' | 'warn' | Expectation; diff --git a/chromium/third_party/webgpu-cts/src/src/common/internal/query/json_param_value.ts b/chromium/third_party/webgpu-cts/src/src/common/internal/query/json_param_value.ts index d2dc01e0f61..f4be7642d33 100644 --- a/chromium/third_party/webgpu-cts/src/src/common/internal/query/json_param_value.ts +++ b/chromium/third_party/webgpu-cts/src/src/common/internal/query/json_param_value.ts @@ -1,15 +1,54 @@ import { assert, sortObjectByKey } from '../../util/util.js'; import { JSONWithUndefined } from '../params_utils.js'; -// JSON can't represent `undefined` and by default stores it as `null`. -// Instead, store `undefined` as this magic string value in JSON. +// JSON can't represent various values and by default stores them as `null`. +// Instead, storing them as a magic string values in JSON. const jsUndefinedMagicValue = '_undef_'; +const jsNaNMagicValue = '_nan_'; +const jsPositiveInfinityMagicValue = '_posinfinity_'; +const jsNegativeInfinityMagicValue = '_neginfinity_'; + +// -0 needs to be handled separately, because -0 === +0 returns true. Not +// special casing +0/0, since it behaves intuitively. Assuming that if -0 is +// being used, the differentiation from +0 is desired. +const jsNegativeZeroMagicValue = '_negzero_'; + +const toStringMagicValue = new Map<unknown, string>([ + [undefined, jsUndefinedMagicValue], + [NaN, jsNaNMagicValue], + [Number.POSITIVE_INFINITY, jsPositiveInfinityMagicValue], + [Number.NEGATIVE_INFINITY, jsNegativeInfinityMagicValue], + // No -0 handling because it is special cased. +]); + +const fromStringMagicValue = new Map<string, unknown>([ + [jsUndefinedMagicValue, undefined], + [jsNaNMagicValue, NaN], + [jsPositiveInfinityMagicValue, Number.POSITIVE_INFINITY], + [jsNegativeInfinityMagicValue, Number.NEGATIVE_INFINITY], + // -0 is handled in this direction because there is no comparison issue. + [jsNegativeZeroMagicValue, -0], +]); function stringifyFilter(k: string, v: unknown): unknown { - // Make sure no one actually uses the magic value as a parameter. - assert(v !== jsUndefinedMagicValue); + // Make sure no one actually uses a magic value as a parameter. + if (typeof v === 'string') { + assert( + !fromStringMagicValue.has(v), + `${v} is a magic value for stringification, so cannot be used` + ); + + assert( + v !== jsNegativeZeroMagicValue, + `${v} is a magic value for stringification, so cannot be used` + ); + } - return v === undefined ? jsUndefinedMagicValue : v; + if (Object.is(v, -0)) { + return jsNegativeZeroMagicValue; + } + + return toStringMagicValue.has(v) ? toStringMagicValue.get(v) : v; } export function stringifyParamValue(value: JSONWithUndefined): string { @@ -29,6 +68,16 @@ export function stringifyParamValueUniquely(value: JSONWithUndefined): string { }); } +// 'any' is part of the JSON.parse reviver interface, so cannot be avoided. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function parseParamValueReviver(k: string, v: any): any { + if (fromStringMagicValue.has(v)) { + return fromStringMagicValue.get(v); + } + + return v; +} + export function parseParamValue(s: string): JSONWithUndefined { - return JSON.parse(s, (k, v) => (v === jsUndefinedMagicValue ? undefined : v)); + return JSON.parse(s, parseParamValueReviver); } diff --git a/chromium/third_party/webgpu-cts/src/src/common/internal/test_group.ts b/chromium/third_party/webgpu-cts/src/src/common/internal/test_group.ts index daac0270fbf..f23f68d4932 100644 --- a/chromium/third_party/webgpu-cts/src/src/common/internal/test_group.ts +++ b/chromium/third_party/webgpu-cts/src/src/common/internal/test_group.ts @@ -95,7 +95,6 @@ export class TestGroup<F extends Fixture> implements TestGroupBuilder<F> { this.seen.add(name); } - // TODO: This could take a fixture, too, to override the one for the group. test(name: string): TestBuilderWithName<F> { const testCreationStack = new Error(`Test created: ${name}`); @@ -207,8 +206,10 @@ class TestBuilder { } fn(fn: TestFn<Fixture, {}>): void { - // TODO: add TODO if there's no description? (and make sure it only ends up on actual tests, - // not on test parents in the tree, which is what happens if you do it here, not sure why) + // eslint-disable-next-line no-warning-comments + // MAINTENANCE_TODO: add "TODO" if there's no description? (and make sure it only ends up on + // actual tests, not on test parents in the tree, which is what happens if you do it here, not + // sure why) assert(this.testFn === undefined); this.testFn = fn; } diff --git a/chromium/third_party/webgpu-cts/src/src/common/internal/tree.ts b/chromium/third_party/webgpu-cts/src/src/common/internal/tree.ts index f405a8f2c09..702f63619a7 100644 --- a/chromium/third_party/webgpu-cts/src/src/common/internal/tree.ts +++ b/chromium/third_party/webgpu-cts/src/src/common/internal/tree.ts @@ -29,7 +29,7 @@ import { StacklessError } from './util.js'; // arbitrarily-fine suppressions (instead of having to suppress entire test files, which would // lose a lot of coverage). // -// `iterateCollapsedQueries()` produces the list of queries for the variants list. +// `iterateCollapsedNodes()` produces the list of queries for the variants list. // // Though somewhat complicated, this system has important benefits: // - Avoids having to suppress entire test files, which would cause large test coverage loss. @@ -39,27 +39,27 @@ import { StacklessError } from './util.js'; // - Enables developers to put any number of tests in one file as appropriate, without worrying // about expectation granularity. -export interface TestSubtree<T extends TestQuery = TestQuery> { +interface TestTreeNodeBase<T extends TestQuery> { + readonly query: T; /** * Readable "relative" name for display in standalone runner. * Not always the exact relative name, because sometimes there isn't * one (e.g. s:f:* relative to s:f,*), but something that is readable. */ readonly readableRelativeName: string; - readonly query: T; + subtreeCounts?: { tests: number; nodesWithTODO: number }; +} + +export interface TestSubtree<T extends TestQuery = TestQuery> extends TestTreeNodeBase<T> { readonly children: Map<string, TestTreeNode>; readonly collapsible: boolean; description?: string; readonly testCreationStack?: Error; } -export interface TestTreeLeaf { - /** - * Readable "relative" name for display in standalone runner. - */ - readonly readableRelativeName: string; - readonly query: TestQuerySingleCase; +export interface TestTreeLeaf extends TestTreeNodeBase<TestQuerySingleCase> { readonly run: RunFn; + subtreeCounts?: undefined; } export type TestTreeNode = TestSubtree | TestTreeLeaf; @@ -81,7 +81,7 @@ export class TestTree { * Test trees are always rooted at `suite:*`, but they only contain nodes that fit * within `forQuery`. * - * This is used for `iterateCollapsedQueries` which only starts collapsing at the next + * This is used for `iterateCollapsedNodes` which only starts collapsing at the next * `TestQueryLevel` after `forQuery`. */ readonly forQuery: TestQuery; @@ -89,6 +89,7 @@ export class TestTree { constructor(forQuery: TestQuery, root: TestSubtree) { this.forQuery = forQuery; + TestTree.propagateCounts(root); this.root = root; assert( root.query.level === 1 && root.query.depthInLevel === 0, @@ -102,15 +103,27 @@ export class TestTree { * - are at a deeper `TestQueryLevel` than `this.forQuery`, and * - were not a `Ordering.StrictSubset` of any of the `subqueriesToExpand` during tree creation. */ - iterateCollapsedQueries( - includeEmptySubtrees: boolean, - alwaysExpandThroughLevel: ExpandThroughLevel - ): IterableIterator<TestQuery> { - const expandThrough = Math.max(this.forQuery.level, alwaysExpandThroughLevel); - return TestTree.iterateSubtreeCollapsedQueries(this.root, includeEmptySubtrees, expandThrough); + iterateCollapsedNodes({ + includeIntermediateNodes = false, + includeEmptySubtrees = false, + alwaysExpandThroughLevel, + }: { + /** Whether to include intermediate tree nodes or only collapsed-leaves. */ + includeIntermediateNodes?: boolean; + /** Whether to include collapsed-leaves with no children. */ + includeEmptySubtrees?: boolean; + /** Never collapse nodes up through this level. */ + alwaysExpandThroughLevel: ExpandThroughLevel; + }): IterableIterator<Readonly<TestTreeNode>> { + const expandThroughLevel = Math.max(this.forQuery.level, alwaysExpandThroughLevel); + return TestTree.iterateSubtreeNodes(this.root, { + includeIntermediateNodes, + includeEmptySubtrees, + expandThroughLevel, + }); } - iterateLeaves(): IterableIterator<TestTreeLeaf> { + iterateLeaves(): IterableIterator<Readonly<TestTreeLeaf>> { return TestTree.iterateSubtreeLeaves(this.root); } @@ -130,28 +143,31 @@ export class TestTree { return TestTree.subtreeToString('(root)', this.root, ''); } - static *iterateSubtreeCollapsedQueries( + static *iterateSubtreeNodes( subtree: TestSubtree, - includeEmptySubtrees: boolean, - expandThroughLevel: number - ): IterableIterator<TestQuery> { + opts: { + includeIntermediateNodes: boolean; + includeEmptySubtrees: boolean; + expandThroughLevel: number; + } + ): IterableIterator<TestTreeNode> { + if (opts.includeIntermediateNodes) { + yield subtree; + } + for (const [, child] of subtree.children) { if ('children' in child) { // Is a subtree - const collapsible = child.collapsible && child.query.level > expandThroughLevel; + const collapsible = child.collapsible && child.query.level > opts.expandThroughLevel; if (child.children.size > 0 && !collapsible) { - yield* TestTree.iterateSubtreeCollapsedQueries( - child, - includeEmptySubtrees, - expandThroughLevel - ); - } else if (child.children.size > 0 || includeEmptySubtrees) { + yield* TestTree.iterateSubtreeNodes(child, opts); + } else if (child.children.size > 0 || opts.includeEmptySubtrees) { // Don't yield empty subtrees (e.g. files with no tests) unless includeEmptySubtrees - yield child.query; + yield child; } } else { // Is a leaf - yield child.query; + yield child; } } } @@ -166,9 +182,33 @@ export class TestTree { } } + /** Propagate the subtreeTODOs/subtreeTests state upward from leaves to parent nodes. */ + static propagateCounts(subtree: TestSubtree): { tests: number; nodesWithTODO: number } { + subtree.subtreeCounts ??= { tests: 0, nodesWithTODO: 0 }; + for (const [, child] of subtree.children) { + if ('children' in child) { + const counts = TestTree.propagateCounts(child); + subtree.subtreeCounts.tests += counts.tests; + subtree.subtreeCounts.nodesWithTODO += counts.nodesWithTODO; + } + } + return subtree.subtreeCounts; + } + + /** Displays counts in the format `(Nodes with TODOs) / (Total test count)`. */ + static countsToString(tree: TestTreeNode): string { + if (tree.subtreeCounts) { + return `${tree.subtreeCounts.nodesWithTODO} / ${tree.subtreeCounts.tests}`; + } else { + return ''; + } + } + static subtreeToString(name: string, tree: TestTreeNode, indent: string): string { const collapsible = 'run' in tree ? '>' : tree.collapsible ? '+' : '-'; - let s = indent + `${collapsible} ${JSON.stringify(name)} => ${tree.query}`; + let s = + indent + + `${collapsible} ${TestTree.countsToString(tree)} ${JSON.stringify(name)} => ${tree.query}`; if ('children' in tree) { if (tree.description !== undefined) { s += `\n${indent} | ${JSON.stringify(tree.description)}`; @@ -182,7 +222,8 @@ export class TestTree { } } -// TODO: Consider having subqueriesToExpand actually impact the depth-order of params in the tree. +// MAINTENANCE_TODO: Consider having subqueriesToExpand actually impact the depth-order of params +// in the tree. export async function loadTreeForQuery( loader: TestFileLoader, queryToLoad: TestQuery, @@ -214,8 +255,7 @@ export async function loadTreeForQuery( for (const entry of specs) { if (entry.file.length === 0 && 'readme' in entry) { // Suite-level readme. - assert(subtreeL0.description === undefined); - subtreeL0.description = entry.readme.trim(); + setSubtreeDescriptionAndCountTODOs(subtreeL0, entry.readme); continue; } @@ -240,23 +280,23 @@ export async function loadTreeForQuery( entry.file, isCollapsible ); - assert(readmeSubtree.description === undefined); - readmeSubtree.description = entry.readme.trim(); + setSubtreeDescriptionAndCountTODOs(readmeSubtree, entry.readme); continue; } // Entry is a spec file. const spec = await loader.importSpecFile(queryToLoad.suite, entry.file); - const description = spec.description.trim(); // subtreeL1 is suite:a,b:* const subtreeL1: TestSubtree<TestQueryMultiTest> = addSubtreeForFilePath( subtreeL0, entry.file, - description, isCollapsible ); + setSubtreeDescriptionAndCountTODOs(subtreeL1, spec.description); + let groupHasTests = false; for (const t of spec.g.iterate()) { + groupHasTests = true; { const queryL2 = new TestQueryMultiCase(suite, entry.file, t.testPath, {}); const orderingL2 = compareQueries(queryL2, queryToLoad); @@ -270,13 +310,15 @@ export async function loadTreeForQuery( const subtreeL2: TestSubtree<TestQueryMultiCase> = addSubtreeForTestPath( subtreeL1, t.testPath, - t.description, t.testCreationStack, isCollapsible ); + // This is 1 test. Set tests=1 then count TODOs. + subtreeL2.subtreeCounts ??= { tests: 1, nodesWithTODO: 0 }; + if (t.description) setSubtreeDescriptionAndCountTODOs(subtreeL2, t.description); - // TODO: If tree generation gets too slow, avoid actually iterating the cases in a file - // if there's no need to (based on the subqueriesToExpand). + // MAINTENANCE_TODO: If tree generation gets too slow, avoid actually iterating the cases in a + // file if there's no need to (based on the subqueriesToExpand). for (const c of t.iterate()) { { const queryL3 = new TestQuerySingleCase(suite, entry.file, c.id.test, c.id.params); @@ -293,6 +335,11 @@ export async function loadTreeForQuery( foundCase = true; } } + if (!groupHasTests && !subtreeL1.subtreeCounts) { + throw new StacklessError( + `${subtreeL1.query} has no tests - it must have "TODO" in its description` + ); + } } for (const [i, sq] of subqueriesToExpandEntries) { @@ -304,11 +351,23 @@ export async function loadTreeForQuery( ); } } - assert(foundCase, 'Query does not match any cases'); + assert(foundCase, `Query \`${queryToLoad.toString()}\` does not match any cases`); return new TestTree(queryToLoad, subtreeL0); } +function setSubtreeDescriptionAndCountTODOs( + subtree: TestSubtree<TestQueryMultiFile>, + description: string +) { + assert(subtree.description === undefined); + subtree.description = description.trim(); + subtree.subtreeCounts ??= { tests: 0, nodesWithTODO: 0 }; + if (subtree.description.indexOf('TODO') !== -1) { + subtree.subtreeCounts.nodesWithTODO++; + } +} + function makeTreeForSuite( suite: string, isCollapsible: (sq: TestQuery) => boolean @@ -347,7 +406,6 @@ function addSubtreeForDirPath( function addSubtreeForFilePath( tree: TestSubtree<TestQueryMultiFile>, file: string[], - description: string, isCollapsible: (sq: TestQuery) => boolean ): TestSubtree<TestQueryMultiTest> { // To start, tree is suite:* @@ -360,7 +418,6 @@ function addSubtreeForFilePath( return { readableRelativeName: file[file.length - 1] + kBigSeparator + kWildcard, query, - description, collapsible: isCollapsible(query), }; }); @@ -370,7 +427,6 @@ function addSubtreeForFilePath( function addSubtreeForTestPath( tree: TestSubtree<TestQueryMultiTest>, test: readonly string[], - description: string | undefined, testCreationStack: Error, isCollapsible: (sq: TestQuery) => boolean ): TestSubtree<TestQueryMultiCase> { @@ -405,7 +461,6 @@ function addSubtreeForTestPath( readableRelativeName: subqueryTest[subqueryTest.length - 1] + kBigSeparator + kWildcard, kWildcard, query, - description, testCreationStack, collapsible: isCollapsible(query), }; diff --git a/chromium/third_party/webgpu-cts/src/src/common/runtime/cmdline.ts b/chromium/third_party/webgpu-cts/src/src/common/runtime/cmdline.ts index 800571ddf64..7669e6af597 100644 --- a/chromium/third_party/webgpu-cts/src/src/common/runtime/cmdline.ts +++ b/chromium/third_party/webgpu-cts/src/src/common/runtime/cmdline.ts @@ -28,11 +28,6 @@ function usage(rc: number): never { return sys.exit(rc); } -if (!sys.existsSync('src/common/runtime/cmdline.ts')) { - console.log('Must be run from repository root'); - usage(1); -} - interface GPUProviderModule { create(flags: string[]): GPU; } @@ -140,7 +135,7 @@ if (queries.length === 0) { assert(total > 0, 'found no tests!'); - // TODO: write results out somewhere (a file?) + // MAINTENANCE_TODO: write results out somewhere (a file?) if (printJSON) { console.log(log.asJSON(2)); } diff --git a/chromium/third_party/webgpu-cts/src/src/common/runtime/helper/test_worker.ts b/chromium/third_party/webgpu-cts/src/src/common/runtime/helper/test_worker.ts index 26da9ce715e..2ddc3a951b6 100644 --- a/chromium/third_party/webgpu-cts/src/src/common/runtime/helper/test_worker.ts +++ b/chromium/third_party/webgpu-cts/src/src/common/runtime/helper/test_worker.ts @@ -25,8 +25,8 @@ export class TestWorker { } this.resolvers.get(query)!(result as LiveTestCaseResult); - // TODO(kainino0x): update the Logger with this result (or don't have a logger and update the - // entire results JSON somehow at some point). + // MAINTENANCE_TODO(kainino0x): update the Logger with this result (or don't have a logger and + // update the entire results JSON somehow at some point). }; } diff --git a/chromium/third_party/webgpu-cts/src/src/common/runtime/standalone.ts b/chromium/third_party/webgpu-cts/src/src/common/runtime/standalone.ts index 4ab7f034d57..bb84e2c646e 100644 --- a/chromium/third_party/webgpu-cts/src/src/common/runtime/standalone.ts +++ b/chromium/third_party/webgpu-cts/src/src/common/runtime/standalone.ts @@ -6,7 +6,7 @@ import { Logger } from '../internal/logging/logger.js'; import { LiveTestCaseResult } from '../internal/logging/result.js'; import { parseQuery } from '../internal/query/parseQuery.js'; import { TestQueryLevel } from '../internal/query/query.js'; -import { TestTreeNode, TestSubtree, TestTreeLeaf } from '../internal/tree.js'; +import { TestTreeNode, TestSubtree, TestTreeLeaf, TestTree } from '../internal/tree.js'; import { assert, ErrorWithExtra } from '../util/util.js'; import { optionEnabled } from './helper/options.js'; @@ -348,12 +348,21 @@ function makeTreeNodeHeaderHTML( }); } const nodetitle = $('<div>').addClass('nodetitle').appendTo(header); - $('<input>') - .attr('type', 'text') - .prop('readonly', true) - .addClass('nodequery') - .val(n.query.toString()) - .appendTo(nodetitle); + const nodecolumns = $('<span>').addClass('nodecolumns').appendTo(nodetitle); + { + $('<input>') + .attr('type', 'text') + .prop('readonly', true) + .addClass('nodequery') + .val(n.query.toString()) + .appendTo(nodecolumns); + if (n.subtreeCounts) { + $('<span>') + .attr('title', '(Nodes with TODOs) / (Total test count)') + .text(TestTree.countsToString(n)) + .appendTo(nodecolumns); + } + } if ('description' in n && n.description) { nodetitle.append(' '); $('<pre>') // @@ -370,7 +379,7 @@ let lastQueryLevelToExpand: TestQueryLevel = 2; (async () => { const loader = new DefaultTestFileLoader(); - // TODO: start populating page before waiting for everything to load? + // MAINTENANCE_TODO: start populating page before waiting for everything to load? const qs = new URLSearchParams(window.location.search).getAll('q'); if (qs.length === 0) { qs.push('webgpu:*'); diff --git a/chromium/third_party/webgpu-cts/src/src/common/tools/checklist.ts b/chromium/third_party/webgpu-cts/src/src/common/tools/checklist.ts index 8df5684a5fd..393990e26f9 100644 --- a/chromium/third_party/webgpu-cts/src/src/common/tools/checklist.ts +++ b/chromium/third_party/webgpu-cts/src/src/common/tools/checklist.ts @@ -19,50 +19,71 @@ function usage(rc: number): void { if (process.argv.length === 2) usage(0); if (process.argv.length !== 3) usage(1); -type QueriesBySuite = Map<string, TestQuery[]>; +type QueryInSuite = { readonly query: TestQuery; readonly done: boolean }; +type QueriesInSuite = QueryInSuite[]; +type QueriesBySuite = Map<string, QueriesInSuite>; async function loadQueryListFromTextFile(filename: string): Promise<QueriesBySuite> { const lines = (await fs.promises.readFile(filename, 'utf8')).split(/\r?\n/); - const allQueries = lines.filter(l => l).map(l => parseQuery(l.trim())); + const allQueries = lines + .filter(l => l) + .map(l => { + const [doneStr, q] = l.split(/\s+/); + assert(doneStr === 'DONE' || doneStr === 'TODO', 'first column must be DONE or TODO'); + return { query: parseQuery(q), done: doneStr === 'DONE' } as const; + }); const queriesBySuite: QueriesBySuite = new Map(); - for (const query of allQueries) { - let suiteQueries = queriesBySuite.get(query.suite); + for (const q of allQueries) { + let suiteQueries = queriesBySuite.get(q.query.suite); if (suiteQueries === undefined) { suiteQueries = []; - queriesBySuite.set(query.suite, suiteQueries); + queriesBySuite.set(q.query.suite, suiteQueries); } - suiteQueries.push(query); + suiteQueries.push(q); } return queriesBySuite; } -function checkForOverlappingQueries(queries: TestQuery[]): void { - for (const q1 of queries) { - for (const q2 of queries) { - if (q1 !== q2 && compareQueries(q1, q2) !== Ordering.Unordered) { - throw new StacklessError(`The following checklist items overlap:\n ${q1}\n ${q2}`); +function checkForOverlappingQueries(queries: QueriesInSuite): void { + for (let i1 = 0; i1 < queries.length; ++i1) { + for (let i2 = i1 + 1; i2 < queries.length; ++i2) { + const q1 = queries[i1].query; + const q2 = queries[i2].query; + if (compareQueries(q1, q2) !== Ordering.Unordered) { + console.log(` FYI, the following checklist items overlap:\n ${q1}\n ${q2}`); } } } } -function checkForUnmatchedSubtrees(tree: TestTree, matchQueries: TestQuery[]): number { +function checkForUnmatchedSubtreesAndDoneness( + tree: TestTree, + matchQueries: QueriesInSuite +): number { let subtreeCount = 0; const unmatchedSubtrees: TestQuery[] = []; const overbroadMatches: [TestQuery, TestQuery][] = []; + const donenessMismatches: QueryInSuite[] = []; const alwaysExpandThroughLevel = 1; // expand to, at minimum, every file. - for (const collapsedSubtree of tree.iterateCollapsedQueries(true, alwaysExpandThroughLevel)) { + for (const subtree of tree.iterateCollapsedNodes({ + includeIntermediateNodes: true, + includeEmptySubtrees: true, + alwaysExpandThroughLevel, + })) { subtreeCount++; + const subtreeDone = !subtree.subtreeCounts?.nodesWithTODO; + let subtreeMatched = false; for (const q of matchQueries) { - const comparison = compareQueries(q, collapsedSubtree); - assert(comparison !== Ordering.StrictSubset); // shouldn't happen, due to subqueriesToExpand - if (comparison === Ordering.StrictSuperset) overbroadMatches.push([q, collapsedSubtree]); + const comparison = compareQueries(q.query, subtree.query); if (comparison !== Ordering.Unordered) subtreeMatched = true; + if (comparison === Ordering.StrictSubset) continue; + if (comparison === Ordering.StrictSuperset) overbroadMatches.push([q.query, subtree.query]); + if (comparison === Ordering.Equal && q.done !== subtreeDone) donenessMismatches.push(q); } - if (!subtreeMatched) unmatchedSubtrees.push(collapsedSubtree); + if (!subtreeMatched) unmatchedSubtrees.push(subtree.query); } if (overbroadMatches.length) { @@ -74,8 +95,18 @@ function checkForUnmatchedSubtrees(tree: TestTree, matchQueries: TestQuery[]): n } if (unmatchedSubtrees.length) { - throw new StacklessError(`Found unmatched tests:\n ${unmatchedSubtrees.join('\n ')}`); + throw new StacklessError(`Found unmatched tests:\n ${unmatchedSubtrees.join('\n ')}`); + } + + if (donenessMismatches.length) { + throw new StacklessError( + 'Found done/todo mismatches:\n ' + + donenessMismatches + .map(q => `marked ${q.done ? 'DONE, but is TODO' : 'TODO, but is DONE'}: ${q.query}`) + .join('\n ') + ); } + return subtreeCount; } @@ -91,10 +122,14 @@ function checkForUnmatchedSubtrees(tree: TestTree, matchQueries: TestQuery[]): n checkForOverlappingQueries(queriesInSuite); const suiteQuery = new TestQueryMultiFile(suite, []); console.log(` Loading tree ${suiteQuery}...`); - const tree = await loadTreeForQuery(loader, suiteQuery, queriesInSuite); + const tree = await loadTreeForQuery( + loader, + suiteQuery, + queriesInSuite.map(q => q.query) + ); console.log(' Found no invalid queries in the checklist. Checking for unmatched tests...'); - const subtreeCount = checkForUnmatchedSubtrees(tree, queriesInSuite); - console.log(` No unmatched tests among ${subtreeCount} subtrees!`); + const subtreeCount = checkForUnmatchedSubtreesAndDoneness(tree, queriesInSuite); + console.log(` No unmatched tests or done/todo mismatches among ${subtreeCount} subtrees!`); } console.log(`Checklist looks good!`); })().catch(ex => { diff --git a/chromium/third_party/webgpu-cts/src/src/common/tools/gen_wpt_cts_html.ts b/chromium/third_party/webgpu-cts/src/src/common/tools/gen_wpt_cts_html.ts index 7dc8f8a9a94..8b68f43978e 100644 --- a/chromium/third_party/webgpu-cts/src/src/common/tools/gen_wpt_cts_html.ts +++ b/chromium/third_party/webgpu-cts/src/src/common/tools/gen_wpt_cts_html.ts @@ -84,8 +84,8 @@ const [ lines.push(undefined); // output blank line between prefixes const alwaysExpandThroughLevel = 2; // expand to, at minimum, every test. - for (const q of tree.iterateCollapsedQueries(false, alwaysExpandThroughLevel)) { - const urlQueryString = prefix + q.toString(); // "?worker=0&q=..." + for (const { query } of tree.iterateCollapsedNodes({ alwaysExpandThroughLevel })) { + const urlQueryString = prefix + query.toString(); // "?worker=0&q=..." // Check for a safe-ish path length limit. Filename must be <= 255, and on Windows the whole // path must be <= 259. Leave room for e.g.: // 'c:\b\s\w\xxxxxxxx\layout-test-results\external\wpt\webgpu\cts_worker=0_q=...-actual.txt' diff --git a/chromium/third_party/webgpu-cts/src/src/common/tools/setup-ts-in-node.js b/chromium/third_party/webgpu-cts/src/src/common/tools/setup-ts-in-node.js index df74d8e3628..79a22154b22 100644 --- a/chromium/third_party/webgpu-cts/src/src/common/tools/setup-ts-in-node.js +++ b/chromium/third_party/webgpu-cts/src/src/common/tools/setup-ts-in-node.js @@ -1,5 +1,9 @@ +const path = require('path'); + // Automatically transpile .ts imports require('ts-node').register({ + // Specify the project file so ts-node doesn't try to find it itself based on the CWD. + project: path.resolve(__dirname, '../../../tsconfig.json'), compilerOptions: { module: 'commonjs', }, @@ -15,13 +19,13 @@ Module._resolveFilename = (request, parentModule, isMain) => { // can't do any kind of file resolution). if (request.endsWith('/index.js')) { throw new Error( - "Avoid the name `index.js`; we don't have Node - style path resolution: " + request + "Avoid the name `index.js`; we don't have Node-style path resolution: " + request ); } if (!request.endsWith('.js')) { throw new Error('All relative imports must end in .js: ' + request); } - request = request.substring(0, request.length - '.ts'.length) + '.ts'; + request = request.substring(0, request.length - '.js'.length) + '.ts'; } return resolveFilename.call(this, request, parentModule, isMain); }; diff --git a/chromium/third_party/webgpu-cts/src/src/stress/adapter/device_allocation.spec.ts b/chromium/third_party/webgpu-cts/src/src/stress/adapter/device_allocation.spec.ts new file mode 100644 index 00000000000..eccc1ee0422 --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/stress/adapter/device_allocation.spec.ts @@ -0,0 +1,291 @@ +export const description = ` +Stress tests for GPUAdapter.requestDevice. +`; + +import { Fixture } from '../../common/framework/fixture.js'; +import { makeTestGroup } from '../../common/framework/test_group.js'; +import { attemptGarbageCollection } from '../../common/util/collect_garbage.js'; +import { keysOf } from '../../common/util/data_tables.js'; +import { getGPU } from '../../common/util/navigator_gpu.js'; +import { assert, iterRange } from '../../common/util/util.js'; +import { DefaultLimits } from '../../webgpu/constants.js'; + +/** Adapter preference identifier to option. */ +const kAdapterTypeOptions: { + readonly [k in GPUPowerPreference | 'fallback']: GPURequestAdapterOptions; +} = /* prettier-ignore */ { + 'low-power': { powerPreference: 'low-power', forceFallbackAdapter: false }, + 'high-performance': { powerPreference: 'high-performance', forceFallbackAdapter: false }, + 'fallback': { powerPreference: undefined, forceFallbackAdapter: true }, +}; +/** List of all adapter hint types. */ +const kAdapterTypes = keysOf(kAdapterTypeOptions); + +class DeviceAllocationTests extends Fixture { + /** + * Creates a device, a valid compute pipeline, valid resources for the pipeline, and + * ties them together into a set of compute commands ready to be submitted to the GPU + * queue. Does not submit the commands in order to make sure that all resources are + * kept alive until the device is destroyed. + */ + async createDeviceAndComputeCommands(adapter: GPUAdapter) { + // Constants are computed such that per run, this function should allocate roughly 2G + // worth of data. This should be sufficient as we run these creation functions many + // times. If the data backing the created objects is not recycled we should OOM. + const kNumPipelines = 64; + const kNumBindgroups = 128; + const kNumBufferElements = + DefaultLimits.maxComputeWorkgroupSizeX * DefaultLimits.maxComputeWorkgroupSizeY; + const kBufferSize = kNumBufferElements * 4; + const kBufferData = new Uint32Array([...iterRange(kNumBufferElements, x => x)]); + + const device: GPUDevice = await adapter.requestDevice(); + const commands = []; + + for (let pipelineIndex = 0; pipelineIndex < kNumPipelines; ++pipelineIndex) { + const pipeline = device.createComputePipeline({ + compute: { + module: device.createShaderModule({ + code: ` + struct Buffer { data: array<u32>; }; + + @group(0) @binding(0) var<storage, read_write> buffer: Buffer; + @stage(compute) @workgroup_size(1) fn main( + @builtin(global_invocation_id) id: vec3<u32>) { + buffer.data[id.x * ${DefaultLimits.maxComputeWorkgroupSizeX}u + id.y] = + buffer.data[id.x * ${DefaultLimits.maxComputeWorkgroupSizeX}u + id.y] + + ${pipelineIndex}u; + } + `, + }), + entryPoint: 'main', + }, + }); + for (let bindgroupIndex = 0; bindgroupIndex < kNumBindgroups; ++bindgroupIndex) { + const buffer = device.createBuffer({ + size: kBufferSize, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, + }); + device.queue.writeBuffer(buffer, 0, kBufferData, 0, kBufferData.length); + const bindgroup = device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [{ binding: 0, resource: { buffer } }], + }); + + const encoder = device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindgroup); + pass.dispatch( + DefaultLimits.maxComputeWorkgroupSizeX, + DefaultLimits.maxComputeWorkgroupSizeY + ); + pass.endPass(); + commands.push(encoder.finish()); + } + } + return { device, objects: commands }; + } + + /** + * Creates a device, a valid render pipeline, valid resources for the pipeline, and + * ties them together into a set of render commands ready to be submitted to the GPU + * queue. Does not submit the commands in order to make sure that all resources are + * kept alive until the device is destroyed. + */ + async createDeviceAndRenderCommands(adapter: GPUAdapter) { + // Constants are computed such that per run, this function should allocate roughly 2G + // worth of data. This should be sufficient as we run these creation functions many + // times. If the data backing the created objects is not recycled we should OOM. + const kNumPipelines = 128; + const kNumBindgroups = 128; + const kSize = 128; + const kBufferData = new Uint32Array([...iterRange(kSize * kSize, x => x)]); + + const device: GPUDevice = await adapter.requestDevice(); + const commands = []; + + for (let pipelineIndex = 0; pipelineIndex < kNumPipelines; ++pipelineIndex) { + const module = device.createShaderModule({ + code: ` + struct Buffer { data: array<vec4<u32>, ${(kSize * kSize) / 4}>; }; + + @group(0) @binding(0) var<uniform> buffer: Buffer; + @stage(vertex) fn vmain( + @builtin(vertex_index) vertexIndex: u32 + ) -> @builtin(position) vec4<f32> { + let index = buffer.data[vertexIndex / 4u][vertexIndex % 4u]; + let position = vec2<f32>(f32(index % ${kSize}u), f32(index / ${kSize}u)); + let r = vec2<f32>(1.0 / f32(${kSize})); + let a = 2.0 * r; + let b = r - vec2<f32>(1.0); + return vec4<f32>(fma(position, a, b), 0.0, 1.0); + } + + @stage(fragment) fn fmain() -> @location(0) vec4<f32> { + return vec4<f32>(${pipelineIndex}.0 / ${kNumPipelines}.0, 0.0, 0.0, 1.0); + } + `, + }); + const pipeline = device.createRenderPipeline({ + layout: device.createPipelineLayout({ + bindGroupLayouts: [ + device.createBindGroupLayout({ + entries: [ + { + binding: 0, + visibility: GPUShaderStage.VERTEX, + buffer: { type: 'uniform' }, + }, + ], + }), + ], + }), + vertex: { module, entryPoint: 'vmain', buffers: [] }, + primitive: { topology: 'point-list' }, + fragment: { + targets: [{ format: 'rgba8unorm' }], + module, + entryPoint: 'fmain', + }, + }); + for (let bindgroupIndex = 0; bindgroupIndex < kNumBindgroups; ++bindgroupIndex) { + const buffer = device.createBuffer({ + size: kSize * kSize * 4, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + device.queue.writeBuffer(buffer, 0, kBufferData, 0, kBufferData.length); + const bindgroup = device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [{ binding: 0, resource: { buffer } }], + }); + const texture = device.createTexture({ + size: [kSize, kSize], + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + format: 'rgba8unorm', + }); + + const encoder = device.createCommandEncoder(); + const pass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: texture.createView(), + loadValue: 'load', + storeOp: 'store', + }, + ], + }); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindgroup); + pass.draw(kSize * kSize); + pass.endPass(); + commands.push(encoder.finish()); + } + } + return { device, objects: commands }; + } + + /** + * Creates a device and a large number of buffers which are immediately written to. The + * buffers are expected to be kept alive until they or the device are destroyed. + */ + async createDeviceAndBuffers(adapter: GPUAdapter) { + // Currently we just allocate 2G of memory using 512MB blocks. We may be able to + // increase this to hit OOM instead, but on integrated GPUs on Metal, this can cause + // kernel panics at the moment, and it can greatly increase the time needed. + const kTotalMemorySize = 2 * 1024 * 1024 * 1024; + const kMemoryBlockSize = 512 * 1024 * 1024; + const kMemoryBlockData = new Uint8Array(kMemoryBlockSize); + + const device: GPUDevice = await adapter.requestDevice(); + const buffers = []; + for (let memory = 0; memory < kTotalMemorySize; memory += kMemoryBlockSize) { + const buffer = device.createBuffer({ + size: kMemoryBlockSize, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + }); + + // Write out to the buffer to make sure that it has backing memory. + device.queue.writeBuffer(buffer, 0, kMemoryBlockData, 0, kMemoryBlockData.length); + buffers.push(buffer); + } + return { device, objects: buffers }; + } +} + +export const g = makeTestGroup(DeviceAllocationTests); + +g.test('coexisting') + .desc(`Tests allocation of many coexisting GPUDevice objects.`) + .params(u => u.combine('adapterType', kAdapterTypes)) + .fn(async t => { + const { adapterType } = t.params; + const adapter = await getGPU().requestAdapter(kAdapterTypeOptions[adapterType]); + assert(adapter !== null, 'Failed to get adapter.'); + + // Based on Vulkan comformance test requirement to be able to create multiple devices. + const kNumDevices = 5; + + const devices = []; + for (let i = 0; i < kNumDevices; ++i) { + const device: GPUDevice = await adapter.requestDevice(); + devices.push(device); + } + }); + +g.test('continuous,with_destroy') + .desc( + `Tests allocation and destruction of many GPUDevice objects over time. Device objects +are sequentially requested with a series of device allocated objects created on each +device. The devices are then destroyed to verify that the device and the device allocated +objects are recycled over a very large number of iterations.` + ) + .params(u => u.combine('adapterType', kAdapterTypes)) + .fn(async t => { + const { adapterType } = t.params; + const adapter = await getGPU().requestAdapter(kAdapterTypeOptions[adapterType]); + assert(adapter !== null, 'Failed to get adapter.'); + + // Since devices are being destroyed, we should be able to create many devices. + const kNumDevices = 100; + const kFunctions = [ + t.createDeviceAndBuffers, + t.createDeviceAndComputeCommands, + t.createDeviceAndRenderCommands, + ]; + + const deviceList = []; + const objectLists = []; + for (let i = 0; i < kNumDevices; ++i) { + const { device, objects } = await kFunctions[i % kFunctions.length](adapter); + t.expect(objects.length > 0, 'unable to allocate any objects'); + deviceList.push(device); + objectLists.push(objects); + device.destroy(); + } + }); + +g.test('continuous,no_destroy') + .desc( + `Tests allocation and implicit GC of many GPUDevice objects over time. Objects are +sequentially requested and dropped for GC over a very large number of iterations. Note +that without destroy, we do not create device allocated objects because that will +implicitly keep the device in scope.` + ) + .params(u => u.combine('adapterType', kAdapterTypes)) + .fn(async t => { + const { adapterType } = t.params; + const adapter = await getGPU().requestAdapter(kAdapterTypeOptions[adapterType]); + assert(adapter !== null, 'Failed to get adapter.'); + + const kNumDevices = 10_000; + for (let i = 1; i <= kNumDevices; ++i) { + await (async () => { + t.expect((await adapter.requestDevice()) !== null, 'unexpected null device'); + })(); + if (i % 10 === 0) { + // We need to occassionally wait for GC to clear out stale devices. + await attemptGarbageCollection(); + } + } + }); diff --git a/chromium/third_party/webgpu-cts/src/src/stress/adapter/device_allocation.ts b/chromium/third_party/webgpu-cts/src/src/stress/adapter/device_allocation.ts deleted file mode 100644 index 47be1b40660..00000000000 --- a/chromium/third_party/webgpu-cts/src/src/stress/adapter/device_allocation.ts +++ /dev/null @@ -1,36 +0,0 @@ -export const description = ` -Stress tests for GPUAdapter.requestDevice. -`; - -import { Fixture } from '../../common/framework/fixture.js'; -import { makeTestGroup } from '../../common/framework/test_group.js'; - -export const g = makeTestGroup(Fixture); - -g.test('coexisting') - .desc( - `Tests allocation of many coexisting GPUDevice objects. - -TODO: These stress tests might not make sense. Allocating lots of GPUDevices is -currently crashy in Chrome, and there's not a great reason for applications to -do it. UAs should probably limit the number of simultaneously active devices, -but this is effectively blocked on implementing GPUDevice.destroy() to give -applications the necessary controls.` - ) - .unimplemented(); - -g.test('continuous,with_destroy') - .desc( - `Tests allocation and destruction of many GPUDevice objects over time. Objects -are sequentially requested and destroyed over a very large number of -iterations.` - ) - .unimplemented(); - -g.test('continuous,no_destroy') - .desc( - `Tests allocation and implicit GC of many GPUDevice objects over time. Objects -are sequentially requested and dropped for GC over a very large number of -iterations.` - ) - .unimplemented(); diff --git a/chromium/third_party/webgpu-cts/src/src/stress/compute/compute_pass.spec.ts b/chromium/third_party/webgpu-cts/src/src/stress/compute/compute_pass.spec.ts index 2e1f203f67e..7c16ab83b8b 100644 --- a/chromium/third_party/webgpu-cts/src/src/stress/compute/compute_pass.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/stress/compute/compute_pass.spec.ts @@ -21,10 +21,10 @@ GPUComputePipeline.` compute: { module: t.device.createShaderModule({ code: ` - [[block]] struct Buffer { data: array<u32>; }; - [[group(0), binding(0)]] var<storage, read_write> buffer: Buffer; - [[stage(compute), workgroup_size(1)]] fn main( - [[builtin(global_invocation_id)]] id: vec3<u32>) { + struct Buffer { data: array<u32>; }; + @group(0) @binding(0) var<storage, read_write> buffer: Buffer; + @stage(compute) @workgroup_size(1) fn main( + @builtin(global_invocation_id) id: vec3<u32>) { buffer.data[id.x] = buffer.data[id.x] + 1u; } `, @@ -66,9 +66,9 @@ GPUComputePipeline.` const stages = iterRange(kNumIterations, i => ({ module: t.device.createShaderModule({ code: ` - [[block]] struct Buffer { data: u32; }; - [[group(0), binding(0)]] var<storage, read_write> buffer: Buffer; - [[stage(compute), workgroup_size(1)]] fn main${i}() { + struct Buffer { data: u32; }; + @group(0) @binding(0) var<storage, read_write> buffer: Buffer; + @stage(compute) @workgroup_size(1) fn main${i}() { buffer.data = buffer.data + 1u; } `, @@ -110,11 +110,11 @@ groups.` ); const module = t.device.createShaderModule({ code: ` - [[block]] struct Buffer { data: array<u32>; }; - [[group(0), binding(0)]] var<storage, read_write> buffer1: Buffer; - [[group(0), binding(1)]] var<storage, read_write> buffer2: Buffer; - [[stage(compute), workgroup_size(1)]] fn main( - [[builtin(global_invocation_id)]] id: vec3<u32>) { + struct Buffer { data: array<u32>; }; + @group(0) @binding(0) var<storage, read_write> buffer1: Buffer; + @group(0) @binding(1) var<storage, read_write> buffer2: Buffer; + @stage(compute) @workgroup_size(1) fn main( + @builtin(global_invocation_id) id: vec3<u32>) { buffer1.data[id.x] = buffer1.data[id.x] + 1u; buffer2.data[id.x] = buffer2.data[id.x] + 2u; } @@ -159,10 +159,10 @@ g.test('many_dispatches') const buffer = t.makeBufferWithContents(data, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC); const module = t.device.createShaderModule({ code: ` - [[block]] struct Buffer { data: array<u32>; }; - [[group(0), binding(0)]] var<storage, read_write> buffer: Buffer; - [[stage(compute), workgroup_size(1)]] fn main( - [[builtin(global_invocation_id)]] id: vec3<u32>) { + struct Buffer { data: array<u32>; }; + @group(0) @binding(0) var<storage, read_write> buffer: Buffer; + @stage(compute) @workgroup_size(1) fn main( + @builtin(global_invocation_id) id: vec3<u32>) { buffer.data[id.x] = buffer.data[id.x] + 1u; } `, @@ -201,10 +201,10 @@ g.test('huge_dispatches') const buffer = t.makeBufferWithContents(data, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC); const module = t.device.createShaderModule({ code: ` - [[block]] struct Buffer { data: array<u32>; }; - [[group(0), binding(0)]] var<storage, read_write> buffer: Buffer; - [[stage(compute), workgroup_size(1)]] fn main( - [[builtin(global_invocation_id)]] id: vec3<u32>) { + struct Buffer { data: array<u32>; }; + @group(0) @binding(0) var<storage, read_write> buffer: Buffer; + @stage(compute) @workgroup_size(1) fn main( + @builtin(global_invocation_id) id: vec3<u32>) { let index = (id.z * 512u + id.y) * 512u + id.x; buffer.data[index] = buffer.data[index] + 1u; } diff --git a/chromium/third_party/webgpu-cts/src/src/stress/queries/pipeline_statistics.spec.ts b/chromium/third_party/webgpu-cts/src/src/stress/queries/pipeline_statistics.spec.ts index cbab37b7a4f..ce8a16f4625 100644 --- a/chromium/third_party/webgpu-cts/src/src/stress/queries/pipeline_statistics.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/stress/queries/pipeline_statistics.spec.ts @@ -1,5 +1,7 @@ export const description = ` Stress tests for pipeline statistics queries. + +TODO: pipeline statistics queries are removed from core; consider moving tests to another suite. `; import { makeTestGroup } from '../../common/framework/test_group.js'; diff --git a/chromium/third_party/webgpu-cts/src/src/stress/queue/submit.spec.ts b/chromium/third_party/webgpu-cts/src/src/stress/queue/submit.spec.ts index 69ea2c371e5..d43a5784d3f 100644 --- a/chromium/third_party/webgpu-cts/src/src/stress/queue/submit.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/stress/queue/submit.spec.ts @@ -22,10 +22,10 @@ results verified at the end of the test.` compute: { module: t.device.createShaderModule({ code: ` - [[block]] struct Buffer { data: array<u32>; }; - [[group(0), binding(0)]] var<storage, read_write> buffer: Buffer; - [[stage(compute), workgroup_size(1)]] fn main( - [[builtin(global_invocation_id)]] id: vec3<u32>) { + struct Buffer { data: array<u32>; }; + @group(0) @binding(0) var<storage, read_write> buffer: Buffer; + @stage(compute) @workgroup_size(1) fn main( + @builtin(global_invocation_id) id: vec3<u32>) { buffer.data[id.x] = buffer.data[id.x] + 1u; } `, @@ -66,10 +66,10 @@ submit() call.` compute: { module: t.device.createShaderModule({ code: ` - [[block]] struct Buffer { data: array<u32>; }; - [[group(0), binding(0)]] var<storage, read_write> buffer: Buffer; - [[stage(compute), workgroup_size(1)]] fn main( - [[builtin(global_invocation_id)]] id: vec3<u32>) { + struct Buffer { data: array<u32>; }; + @group(0) @binding(0) var<storage, read_write> buffer: Buffer; + @stage(compute) @workgroup_size(1) fn main( + @builtin(global_invocation_id) id: vec3<u32>) { buffer.data[id.x] = buffer.data[id.x] + 1u; } `, diff --git a/chromium/third_party/webgpu-cts/src/src/stress/render/render_pass.spec.ts b/chromium/third_party/webgpu-cts/src/src/stress/render/render_pass.spec.ts index 5adb91c807d..7000b36a09d 100644 --- a/chromium/third_party/webgpu-cts/src/src/stress/render/render_pass.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/stress/render/render_pass.spec.ts @@ -17,15 +17,15 @@ a single render pass for every output fragment, with each pass executing a one-v const kSize = 1024; const module = t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn vmain([[builtin(vertex_index)]] index: u32) - -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn vmain(@builtin(vertex_index) index: u32) + -> @builtin(position) vec4<f32> { let position = vec2<f32>(f32(index % ${kSize}u), f32(index / ${kSize}u)); let r = vec2<f32>(1.0 / f32(${kSize})); let a = 2.0 * r; let b = r - vec2<f32>(1.0); return vec4<f32>(fma(position, a, b), 0.0, 1.0); } - [[stage(fragment)]] fn fmain() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn fmain() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 1.0, 1.0); } `, @@ -77,8 +77,8 @@ pass does a single draw call, with one pass per output fragment.` const kHeight = 8; const module = t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn vmain([[builtin(vertex_index)]] index: u32) - -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn vmain(@builtin(vertex_index) index: u32) + -> @builtin(position) vec4<f32> { let position = vec2<f32>(f32(index % ${kWidth}u), f32(index / ${kWidth}u)); let size = vec2<f32>(f32(${kWidth}), f32(${kHeight})); let r = vec2<f32>(1.0) / size; @@ -86,7 +86,7 @@ pass does a single draw call, with one pass per output fragment.` let b = r - vec2<f32>(1.0); return vec4<f32>(fma(position, a, b), 0.0, 1.0); } - [[stage(fragment)]] fn fmain() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn fmain() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 1.0, 1.0); } `, @@ -157,9 +157,9 @@ buffer.` const kSize = 128; const module = t.device.createShaderModule({ code: ` - [[block]] struct Uniforms { index: u32; }; - [[group(0), binding(0)]] var<uniform> uniforms: Uniforms; - [[stage(vertex)]] fn vmain() -> [[builtin(position)]] vec4<f32> { + struct Uniforms { index: u32; }; + @group(0) @binding(0) var<uniform> uniforms: Uniforms; + @stage(vertex) fn vmain() -> @builtin(position) vec4<f32> { let index = uniforms.index; let position = vec2<f32>(f32(index % ${kSize}u), f32(index / ${kSize}u)); let r = vec2<f32>(1.0 / f32(${kSize})); @@ -167,7 +167,7 @@ buffer.` let b = r - vec2<f32>(1.0); return vec4<f32>(fma(position, a, b), 0.0, 1.0); } - [[stage(fragment)]] fn fmain() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn fmain() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 1.0, 1.0); } `, @@ -239,15 +239,15 @@ render pass with a single pipeline, and one draw call per fragment of the output const kSize = 4096; const module = t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn vmain([[builtin(vertex_index)]] index: u32) - -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn vmain(@builtin(vertex_index) index: u32) + -> @builtin(position) vec4<f32> { let position = vec2<f32>(f32(index % ${kSize}u), f32(index / ${kSize}u)); let r = vec2<f32>(1.0 / f32(${kSize})); let a = 2.0 * r; let b = r - vec2<f32>(1.0); return vec4<f32>(fma(position, a, b), 0.0, 1.0); } - [[stage(fragment)]] fn fmain() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn fmain() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 1.0, 1.0); } `, @@ -298,8 +298,8 @@ call which draws multiple vertices for each fragment of a large output texture.` const kVertsPerFragment = (kSize * kSize) / (kTextureSize * kTextureSize); const module = t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn vmain([[builtin(vertex_index)]] vert_index: u32) - -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn vmain(@builtin(vertex_index) vert_index: u32) + -> @builtin(position) vec4<f32> { let index = vert_index / ${kVertsPerFragment}u; let position = vec2<f32>(f32(index % ${kTextureSize}u), f32(index / ${kTextureSize}u)); let r = vec2<f32>(1.0 / f32(${kTextureSize})); @@ -307,7 +307,7 @@ call which draws multiple vertices for each fragment of a large output texture.` let b = r - vec2<f32>(1.0); return vec4<f32>(fma(position, a, b), 0.0, 1.0); } - [[stage(fragment)]] fn fmain() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn fmain() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 1.0, 1.0); } `, diff --git a/chromium/third_party/webgpu-cts/src/src/stress/render/vertex_buffers.spec.ts b/chromium/third_party/webgpu-cts/src/src/stress/render/vertex_buffers.spec.ts index f5b37faec01..d112dedc294 100644 --- a/chromium/third_party/webgpu-cts/src/src/stress/render/vertex_buffers.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/stress/render/vertex_buffers.spec.ts @@ -17,10 +17,10 @@ function createHugeVertexBuffer(t: GPUTest, size: number) { compute: { module: t.device.createShaderModule({ code: ` - [[block]] struct Buffer { data: array<vec2<u32>>; }; - [[group(0), binding(0)]] var<storage, read_write> buffer: Buffer; - [[stage(compute), workgroup_size(1)]] fn main( - [[builtin(global_invocation_id)]] id: vec3<u32>) { + struct Buffer { data: array<vec2<u32>>; }; + @group(0) @binding(0) var<storage, read_write> buffer: Buffer; + @stage(compute) @workgroup_size(1) fn main( + @builtin(global_invocation_id) id: vec3<u32>) { let base = id.x * ${size}u; for (var x: u32 = 0u; x < ${size}u; x = x + 1u) { buffer.data[base + x] = vec2<u32>(x, id.x); @@ -63,14 +63,14 @@ g.test('many') const buffer = createHugeVertexBuffer(t, kSize); const module = t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn vmain([[location(0)]] position: vec2<u32>) - -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn vmain(@location(0) position: vec2<u32>) + -> @builtin(position) vec4<f32> { let r = vec2<f32>(1.0 / f32(${kSize})); let a = 2.0 * r; let b = r - vec2<f32>(1.0); return vec4<f32>(fma(vec2<f32>(position), a, b), 0.0, 1.0); } - [[stage(fragment)]] fn fmain() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn fmain() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 1.0, 1.0); } `, diff --git a/chromium/third_party/webgpu-cts/src/src/stress/shaders/entry_points.spec.ts b/chromium/third_party/webgpu-cts/src/src/stress/shaders/entry_points.spec.ts index 46fb6ce1886..d76b429855e 100644 --- a/chromium/third_party/webgpu-cts/src/src/stress/shaders/entry_points.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/stress/shaders/entry_points.spec.ts @@ -10,12 +10,12 @@ export const g = makeTestGroup(GPUTest); const makeCode = (numEntryPoints: number) => { const kBaseCode = ` - [[block]] struct Buffer { data: u32; }; - [[group(0), binding(0)]] var<storage, read_write> buffer: Buffer; + struct Buffer { data: u32; }; + @group(0) @binding(0) var<storage, read_write> buffer: Buffer; fn main() { buffer.data = buffer.data + 1u; } `; const makeEntryPoint = (i: number) => ` - [[stage(compute), workgroup_size(1)]] fn computeMain${i}() { main(); } + @stage(compute) @workgroup_size(1) fn computeMain${i}() { main(); } `; return kBaseCode + range(numEntryPoints, makeEntryPoint).join(''); }; diff --git a/chromium/third_party/webgpu-cts/src/src/stress/shaders/non_halting.spec.ts b/chromium/third_party/webgpu-cts/src/src/stress/shaders/non_halting.spec.ts index 70c89ac01ee..622595d711f 100644 --- a/chromium/third_party/webgpu-cts/src/src/stress/shaders/non_halting.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/stress/shaders/non_halting.spec.ts @@ -19,9 +19,9 @@ device loss.` const buffer = t.makeBufferWithContents(data, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC); const module = t.device.createShaderModule({ code: ` - [[block]] struct Buffer { data: u32; }; - [[group(0), binding(0)]] var<storage, read_write> buffer: Buffer; - [[stage(compute), workgroup_size(1)]] fn main() { + struct Buffer { data: u32; }; + @group(0) @binding(0) var<storage, read_write> buffer: Buffer; + @stage(compute) @workgroup_size(1) fn main() { loop { if (buffer.data == 1u) { break; @@ -56,9 +56,9 @@ device loss.` .fn(async t => { const module = t.device.createShaderModule({ code: ` - [[block]] struct Data { counter: u32; increment: u32; }; - [[group(0), binding(0)]] var<uniform> data: Data; - [[stage(vertex)]] fn vmain() -> [[builtin(position)]] vec4<f32> { + struct Data { counter: u32; increment: u32; }; + @group(0) @binding(0) var<uniform> data: Data; + @stage(vertex) fn vmain() -> @builtin(position) vec4<f32> { var counter: u32 = data.counter; loop { if (counter % 2u == 1u) { @@ -68,7 +68,7 @@ device loss.` } return vec4<f32>(1.0, 1.0, 0.0, f32(counter)); } - [[stage(fragment)]] fn fmain() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn fmain() -> @location(0) vec4<f32> { return vec4<f32>(1.0); } `, @@ -126,12 +126,12 @@ device loss.` .fn(async t => { const module = t.device.createShaderModule({ code: ` - [[block]] struct Data { counter: u32; increment: u32; }; - [[group(0), binding(0)]] var<uniform> data: Data; - [[stage(vertex)]] fn vmain() -> [[builtin(position)]] vec4<f32> { + struct Data { counter: u32; increment: u32; }; + @group(0) @binding(0) var<uniform> data: Data; + @stage(vertex) fn vmain() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); } - [[stage(fragment)]] fn fmain() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn fmain() -> @location(0) vec4<f32> { var counter: u32 = data.counter; loop { if (counter % 2u == 1u) { diff --git a/chromium/third_party/webgpu-cts/src/src/stress/shaders/slow.spec.ts b/chromium/third_party/webgpu-cts/src/src/stress/shaders/slow.spec.ts index c1d901d6af6..b34dbac34e9 100644 --- a/chromium/third_party/webgpu-cts/src/src/stress/shaders/slow.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/stress/shaders/slow.spec.ts @@ -15,10 +15,10 @@ g.test('compute') const buffer = t.makeBufferWithContents(data, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC); const module = t.device.createShaderModule({ code: ` - [[block]] struct Buffer { data: array<u32>; }; - [[group(0), binding(0)]] var<storage, read_write> buffer: Buffer; - [[stage(compute), workgroup_size(1)]] fn main( - [[builtin(global_invocation_id)]] id: vec3<u32>) { + struct Buffer { data: array<u32>; }; + @group(0) @binding(0) var<storage, read_write> buffer: Buffer; + @stage(compute) @workgroup_size(1) fn main( + @builtin(global_invocation_id) id: vec3<u32>) { loop { if (buffer.data[id.x] == 1000000u) { break; @@ -48,9 +48,9 @@ g.test('vertex') .fn(async t => { const module = t.device.createShaderModule({ code: ` - [[block]] struct Data { counter: u32; increment: u32; }; - [[group(0), binding(0)]] var<uniform> data: Data; - [[stage(vertex)]] fn vmain() -> [[builtin(position)]] vec4<f32> { + struct Data { counter: u32; increment: u32; }; + @group(0) @binding(0) var<uniform> data: Data; + @stage(vertex) fn vmain() -> @builtin(position) vec4<f32> { var counter: u32 = data.counter; loop { counter = counter + data.increment; @@ -60,7 +60,7 @@ g.test('vertex') } return vec4<f32>(1.0, 1.0, 0.0, f32(counter)); } - [[stage(fragment)]] fn fmain() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn fmain() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 1.0, 0.0, 1.0); } `, @@ -120,12 +120,12 @@ g.test('fragment') .fn(async t => { const module = t.device.createShaderModule({ code: ` - [[block]] struct Data { counter: u32; increment: u32; }; - [[group(0), binding(0)]] var<uniform> data: Data; - [[stage(vertex)]] fn vmain() -> [[builtin(position)]] vec4<f32> { + struct Data { counter: u32; increment: u32; }; + @group(0) @binding(0) var<uniform> data: Data; + @stage(vertex) fn vmain() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); } - [[stage(fragment)]] fn fmain() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn fmain() -> @location(0) vec4<f32> { var counter: u32 = data.counter; loop { counter = counter + data.increment; diff --git a/chromium/third_party/webgpu-cts/src/src/unittests/getStackTrace.spec.ts b/chromium/third_party/webgpu-cts/src/src/unittests/getStackTrace.spec.ts index 044c51a6c3a..5090fe3f9db 100644 --- a/chromium/third_party/webgpu-cts/src/src/unittests/getStackTrace.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/unittests/getStackTrace.spec.ts @@ -21,7 +21,7 @@ g.test('stacks') at processTicksAndRejections (internal/process/task_queues.js:86:5)`, }, { - // TODO: make sure this test case actually matches what happens on windows + // MAINTENANCE_TODO: make sure this test case actually matches what happens on windows case: 'node_fail_backslash', _expectedLines: 3, _stack: `Error: diff --git a/chromium/third_party/webgpu-cts/src/src/unittests/loaders_and_trees.spec.ts b/chromium/third_party/webgpu-cts/src/src/unittests/loaders_and_trees.spec.ts index 8a904b3de66..b98eda5f7cd 100644 --- a/chromium/third_party/webgpu-cts/src/src/unittests/loaders_and_trees.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/unittests/loaders_and_trees.spec.ts @@ -43,16 +43,18 @@ const specsData: { [k: string]: SpecFile } = { const g = makeTestGroupForUnitTesting(UnitTest); g.test('hello').fn(() => {}); g.test('bonjour').fn(() => {}); - g.test('hola').fn(() => {}); + g.test('hola') + .desc('TODO TODO') + .fn(() => {}); return g; })(), }, 'suite1/bar/biz.spec.js': { - description: 'desc 1f', + description: 'desc 1f TODO TODO', g: makeTestGroupForUnitTesting(UnitTest), // file with no tests }, 'suite1/bar/buzz/buzz.spec.js': { - description: 'desc 1d', + description: 'desc 1d TODO', g: (() => { const g = makeTestGroupForUnitTesting(UnitTest); g.test('zap').fn(() => {}); @@ -646,7 +648,7 @@ async function testIterateCollapsed( t: LoadingTest, alwaysExpandThroughLevel: ExpandThroughLevel, expectations: string[], - expectedResult: 'throws' | string[], + expectedResult: 'throws' | string[] | [string, number | undefined][], includeEmptySubtrees = false ) { t.debug(`expandThrough=${alwaysExpandThroughLevel} expectations=${expectations}`); @@ -659,13 +661,19 @@ async function testIterateCollapsed( return; } const tree = await treePromise; - const actualIter = tree.iterateCollapsedQueries(includeEmptySubtrees, alwaysExpandThroughLevel); - const actual = Array.from(actualIter, q => q.toString()); + const actualIter = tree.iterateCollapsedNodes({ + includeEmptySubtrees, + alwaysExpandThroughLevel, + }); + const testingTODOs = expectedResult.length > 0 && expectedResult[0] instanceof Array; + const actual = Array.from(actualIter, ({ query, subtreeCounts }) => + testingTODOs ? [query.toString(), subtreeCounts?.nodesWithTODO] : query.toString() + ); if (!objectEquals(actual, expectedResult)) { t.fail( `iterateCollapsed failed: - got [${actual.join(', ')}] - exp [${expectedResult.join(', ')}] + got ${JSON.stringify(actual)} + exp ${JSON.stringify(expectedResult)} ${tree.toString()}` ); } @@ -677,18 +685,27 @@ g.test('print').fn(async () => { }); g.test('iterateCollapsed').fn(async t => { - await testIterateCollapsed(t, 1, [], ['suite1:foo:*', 'suite1:bar,buzz,buzz:*', 'suite1:baz:*']); + await testIterateCollapsed( + t, + 1, + [], + [ + ['suite1:foo:*', 1], // to-do propagated up from foo:hola + ['suite1:bar,buzz,buzz:*', 1], // to-do in file description + ['suite1:baz:*', 0], + ] + ); await testIterateCollapsed( t, 2, [], [ - 'suite1:foo:hello:*', - 'suite1:foo:bonjour:*', - 'suite1:foo:hola:*', - 'suite1:bar,buzz,buzz:zap:*', - 'suite1:baz:wye:*', - 'suite1:baz:zed:*', + ['suite1:foo:hello:*', 0], + ['suite1:foo:bonjour:*', 0], + ['suite1:foo:hola:*', 1], // to-do in test description + ['suite1:bar,buzz,buzz:zap:*', 0], + ['suite1:baz:wye:*', 0], + ['suite1:baz:zed:*', 0], ] ); await testIterateCollapsed( @@ -696,14 +713,14 @@ g.test('iterateCollapsed').fn(async t => { 3, [], [ - 'suite1:foo:hello:', - 'suite1:foo:bonjour:', - 'suite1:foo:hola:', - 'suite1:bar,buzz,buzz:zap:', - 'suite1:baz:wye:', - 'suite1:baz:wye:x=1', - 'suite1:baz:zed:a=1;b=2', - 'suite1:baz:zed:b=3;a=1', + ['suite1:foo:hello:', undefined], + ['suite1:foo:bonjour:', undefined], + ['suite1:foo:hola:', undefined], + ['suite1:bar,buzz,buzz:zap:', undefined], + ['suite1:baz:wye:', undefined], + ['suite1:baz:wye:x=1', undefined], + ['suite1:baz:zed:a=1;b=2', undefined], + ['suite1:baz:zed:b=3;a=1', undefined], ] ); diff --git a/chromium/third_party/webgpu-cts/src/src/unittests/maths.spec.ts b/chromium/third_party/webgpu-cts/src/src/unittests/maths.spec.ts index f55c79b8bbc..2b752f7920b 100644 --- a/chromium/third_party/webgpu-cts/src/src/unittests/maths.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/unittests/maths.spec.ts @@ -4,23 +4,55 @@ Util math unit tests. import { makeTestGroup } from '../common/framework/test_group.js'; import { kBit } from '../webgpu/shader/execution/builtin/builtin.js'; -import { f32, f32Bits, Scalar } from '../webgpu/util/conversion.js'; -import { diffULP, nextAfter } from '../webgpu/util/math.js'; +import { f32, f32Bits, Scalar, u32 } from '../webgpu/util/conversion.js'; +import { + biasedRange, + correctlyRounded, + diffULP, + lerp, + linearRange, + nextAfter, +} from '../webgpu/util/math.js'; import { UnitTest } from './unit_test.js'; export const g = makeTestGroup(UnitTest); +/** Converts a 32-bit hex values to a 32-bit float value */ +function hexToF32(hex: number): number { + return new Float32Array(new Uint32Array([hex]).buffer)[0]; +} + +/** Converts two 32-bit hex values to a 64-bit float value */ +function hexToFloat64(h32: number, l32: number): number { + const u32Arr = new Uint32Array(2); + u32Arr[0] = l32; + u32Arr[1] = h32; + const f64Arr = new Float64Array(u32Arr.buffer); + return f64Arr[0]; +} + +/** + * @returns true if arrays are equal, doing element-wise comparison as needed, and considering NaNs to be equal. + * + * Depends on the correctness of diffULP, which is tested in this file. + **/ +function compareArrayOfNumbers(got: Array<number>, expect: Array<number>): boolean { + return ( + got.length === expect.length && + got.every((value, index) => { + const expected = expect[index]; + return 1 >= diffULP(value, expected) || Number.isNaN(value && Number.isNaN(expected)); + }) + ); +} + interface DiffULPCase { a: number; b: number; ulp: number; } -function hexToF32(hex: number): number { - return new Float32Array(new Uint32Array([hex]).buffer)[0]; -} - g.test('test,math,diffULP') .paramsSimple<DiffULPCase>([ { a: 0, b: 0, ulp: 0 }, @@ -87,23 +119,84 @@ interface nextAfterCase { result: Scalar; } -g.test('test,math,nextAfter') - .paramsSimple<nextAfterCase>([ +g.test('test,math,nextAfterFlushToZero') + .paramsSubcasesOnly<nextAfterCase>([ // Edge Cases - { val: NaN, dir: true, result: f32Bits(0x7fffffff) }, - { val: NaN, dir: false, result: f32Bits(0x7fffffff) }, + { val: Number.NaN, dir: true, result: f32Bits(0x7fffffff) }, + { val: Number.NaN, dir: false, result: f32Bits(0x7fffffff) }, { val: Number.POSITIVE_INFINITY, dir: true, result: f32Bits(kBit.f32.infinity.positive) }, { val: Number.POSITIVE_INFINITY, dir: false, result: f32Bits(kBit.f32.infinity.positive) }, { val: Number.NEGATIVE_INFINITY, dir: true, result: f32Bits(kBit.f32.infinity.negative) }, { val: Number.NEGATIVE_INFINITY, dir: false, result: f32Bits(kBit.f32.infinity.negative) }, // Zeroes - { val: -0, dir: true, result: f32Bits(kBit.f32.subnormal.positive.min) }, + { val: +0, dir: true, result: f32Bits(kBit.f32.positive.min) }, + { val: +0, dir: false, result: f32Bits(kBit.f32.negative.max) }, + { val: -0, dir: true, result: f32Bits(kBit.f32.positive.min) }, + { val: -0, dir: false, result: f32Bits(kBit.f32.negative.max) }, + + // Subnormals + // prettier-ignore + { val: hexToF32(kBit.f32.subnormal.positive.min), dir: true, result: f32Bits(kBit.f32.positive.min) }, + // prettier-ignore + { val: hexToF32(kBit.f32.subnormal.positive.min), dir: false, result: f32Bits(kBit.f32.negative.max) }, + // prettier-ignore + { val: hexToF32(kBit.f32.subnormal.positive.max), dir: true, result: f32Bits(kBit.f32.positive.min) }, + // prettier-ignore + { val: hexToF32(kBit.f32.subnormal.positive.max), dir: false, result: f32Bits(kBit.f32.negative.max) }, + // prettier-ignore + { val: hexToF32(kBit.f32.subnormal.negative.min), dir: true, result: f32Bits(kBit.f32.positive.min) }, + // prettier-ignore + { val: hexToF32(kBit.f32.subnormal.negative.min), dir: false, result: f32Bits(kBit.f32.negative.max) }, + // prettier-ignore + { val: hexToF32(kBit.f32.subnormal.negative.max), dir: true, result: f32Bits(kBit.f32.positive.min) }, + // prettier-ignore + { val: hexToF32(kBit.f32.subnormal.negative.max), dir: false, result: f32Bits(kBit.f32.negative.max) }, + + // Normals + // prettier-ignore + { val: hexToF32(kBit.f32.positive.max), dir: true, result: f32Bits(kBit.f32.infinity.positive) }, + { val: hexToF32(kBit.f32.positive.max), dir: false, result: f32Bits(0x7f7ffffe) }, + { val: hexToF32(kBit.f32.positive.min), dir: true, result: f32Bits(0x00800001) }, + { val: hexToF32(kBit.f32.positive.min), dir: false, result: f32(0) }, + { val: hexToF32(kBit.f32.negative.max), dir: true, result: f32(0) }, + { val: hexToF32(kBit.f32.negative.max), dir: false, result: f32Bits(0x80800001) }, + { val: hexToF32(kBit.f32.negative.min), dir: true, result: f32Bits(0xff7ffffe) }, + // prettier-ignore + { val: hexToF32(kBit.f32.negative.min), dir: false, result: f32Bits(kBit.f32.infinity.negative) }, + { val: hexToF32(0x03800000), dir: true, result: f32Bits(0x03800001) }, + { val: hexToF32(0x03800000), dir: false, result: f32Bits(0x037fffff) }, + { val: hexToF32(0x83800000), dir: true, result: f32Bits(0x837fffff) }, + { val: hexToF32(0x83800000), dir: false, result: f32Bits(0x83800001) }, + ]) + .fn(t => { + const val = t.params.val; + const dir = t.params.dir; + const expect = t.params.result; + const expect_type = typeof expect; + const got = nextAfter(val, dir, true); + const got_type = typeof got; + t.expect( + got.value === expect.value || (Number.isNaN(got.value) && Number.isNaN(expect.value)), + `nextAfter(${val}, ${dir}, true) returned ${got} (${got_type}). Expected ${expect} (${expect_type})` + ); + }); + +g.test('test,math,nextAfterNoFlush') + .paramsSubcasesOnly<nextAfterCase>([ + // Edge Cases + { val: Number.NaN, dir: true, result: f32Bits(0x7fffffff) }, + { val: Number.NaN, dir: false, result: f32Bits(0x7fffffff) }, + { val: Number.POSITIVE_INFINITY, dir: true, result: f32Bits(kBit.f32.infinity.positive) }, + { val: Number.POSITIVE_INFINITY, dir: false, result: f32Bits(kBit.f32.infinity.positive) }, + { val: Number.NEGATIVE_INFINITY, dir: true, result: f32Bits(kBit.f32.infinity.negative) }, + { val: Number.NEGATIVE_INFINITY, dir: false, result: f32Bits(kBit.f32.infinity.negative) }, + + // Zeroes + { val: +0, dir: true, result: f32Bits(kBit.f32.subnormal.positive.min) }, { val: +0, dir: false, result: f32Bits(kBit.f32.subnormal.negative.max) }, - // Skipping these, since the testing framework does not distinguish between - // +0 and -0, so throws an error about duplicate cases. - // { val: +0, dir: true, result: f32Bits(kBit.f32.subnormal.positive.min) }, - // { val: -0, dir: false, result: f32Bits(kBit.f32.subnormal.negative.max) }, + { val: -0, dir: true, result: f32Bits(kBit.f32.subnormal.positive.min) }, + { val: -0, dir: false, result: f32Bits(kBit.f32.subnormal.negative.max) }, // Subnormals { val: hexToF32(kBit.f32.subnormal.positive.min), dir: true, result: f32Bits(0x00000002) }, @@ -140,10 +233,836 @@ g.test('test,math,nextAfter') const dir = t.params.dir; const expect = t.params.result; const expect_type = typeof expect; - const got = nextAfter(val, dir); + const got = nextAfter(val, dir, false); const got_type = typeof got; t.expect( got.value === expect.value || (Number.isNaN(got.value) && Number.isNaN(expect.value)), - `nextAfter(${val}, ${dir}) returned ${got} (${got_type}). Expected ${expect} (${expect_type})` + `nextAfter(${val}, ${dir}, false) returned ${got} (${got_type}). Expected ${expect} (${expect_type})` + ); + }); + +interface correctlyRoundedCase { + test_val: Scalar; + target: number; + is_correct: boolean; +} + +g.test('test,math,correctlyRounded') + .paramsSubcasesOnly<correctlyRoundedCase>([ + // NaN Cases + { test_val: f32Bits(kBit.f32.nan.positive.s), target: NaN, is_correct: true }, + { test_val: f32Bits(kBit.f32.nan.positive.q), target: NaN, is_correct: true }, + { test_val: f32Bits(kBit.f32.nan.negative.s), target: NaN, is_correct: true }, + { test_val: f32Bits(kBit.f32.nan.negative.q), target: NaN, is_correct: true }, + { test_val: f32Bits(0x7fffffff), target: NaN, is_correct: true }, + { test_val: f32Bits(0xffffffff), target: NaN, is_correct: true }, + { test_val: f32Bits(kBit.f32.infinity.positive), target: NaN, is_correct: false }, + { test_val: f32Bits(kBit.f32.infinity.negative), target: NaN, is_correct: false }, + { test_val: f32Bits(kBit.f32.positive.zero), target: NaN, is_correct: false }, + { test_val: f32Bits(kBit.f32.negative.zero), target: NaN, is_correct: false }, + + // Infinities + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.positive.s), target: Number.POSITIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.positive.q), target: Number.POSITIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.negative.s), target: Number.POSITIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.negative.q), target: Number.POSITIVE_INFINITY, is_correct: false }, + { test_val: f32Bits(0x7fffffff), target: Number.POSITIVE_INFINITY, is_correct: false }, + { test_val: f32Bits(0xffffffff), target: Number.POSITIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.infinity.positive), target: Number.POSITIVE_INFINITY, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.infinity.negative), target: Number.POSITIVE_INFINITY, is_correct: false }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.positive.s), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.positive.q), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.negative.s), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.negative.q), target: Number.NEGATIVE_INFINITY, is_correct: false }, + { test_val: f32Bits(0x7fffffff), target: Number.NEGATIVE_INFINITY, is_correct: false }, + { test_val: f32Bits(0xffffffff), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.infinity.positive), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.infinity.negative), target: Number.NEGATIVE_INFINITY, is_correct: true }, + + // Zeros + { test_val: f32Bits(kBit.f32.positive.zero), target: 0, is_correct: true }, + { test_val: f32Bits(kBit.f32.negative.zero), target: 0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: 0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: 0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: 0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: 0, is_correct: true }, + + { test_val: f32Bits(kBit.f32.positive.zero), target: -0, is_correct: true }, + { test_val: f32Bits(kBit.f32.negative.zero), target: -0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: -0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: -0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: -0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: -0, is_correct: true }, + + // 32-bit subnormals + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + + // 64-bit subnormals + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: hexToFloat64(0x00000000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: hexToFloat64(0x00000000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: hexToFloat64(0x00000000, 0x00000001), is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: hexToFloat64(0x00000000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: hexToFloat64(0x00000000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: hexToFloat64(0x00000000, 0x00000002), is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: hexToFloat64(0x800fffff, 0xffffffff), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: hexToFloat64(0x800fffff, 0xffffffff), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: hexToFloat64(0x800fffff, 0xffffffff), is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: hexToFloat64(0x800fffff, 0xfffffffe), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: hexToFloat64(0x800fffff, 0xfffffffe), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: hexToFloat64(0x800fffff, 0xfffffffe), is_correct: true }, + + // 32-bit normals + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.max), target: hexToF32(kBit.f32.positive.max), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.min), target: hexToF32(kBit.f32.positive.min), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.max), target: hexToF32(kBit.f32.negative.max), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.min), target: hexToF32(kBit.f32.negative.min), is_correct: true }, + { test_val: f32Bits(0x03800000), target: hexToF32(0x03800000), is_correct: true }, + { test_val: f32Bits(0x03800000), target: hexToF32(0x03800002), is_correct: false }, + { test_val: f32Bits(0x03800001), target: hexToF32(0x03800001), is_correct: true }, + { test_val: f32Bits(0x03800001), target: hexToF32(0x03800010), is_correct: false }, + { test_val: f32Bits(0x83800000), target: hexToF32(0x83800000), is_correct: true }, + { test_val: f32Bits(0x83800000), target: hexToF32(0x83800002), is_correct: false }, + { test_val: f32Bits(0x83800001), target: hexToF32(0x83800001), is_correct: true }, + { test_val: f32Bits(0x83800001), target: hexToF32(0x83800010), is_correct: false }, + + // 64-bit normals + // prettier-ignore + { test_val: f32Bits(0x3f800000), target: hexToFloat64(0x3ff00000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0x3f800000), target: hexToFloat64(0x3ff00010, 0x00000001), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0x3f800001), target: hexToFloat64(0x3ff00000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0x3f800001), target: hexToFloat64(0x3ff00020, 0x00000001), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0x3f800000), target: hexToFloat64(0x3ff00000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0x3f800000), target: hexToFloat64(0x3ff00030, 0x00000002), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0x3f800001), target: hexToFloat64(0x3ff00000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0x3f800001), target: hexToFloat64(0x3ff00040, 0x00000002), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0xbf800000), target: hexToFloat64(0xbff00000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0xbf800000), target: hexToFloat64(0xbff00050, 0x00000001), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0xbf800001), target: hexToFloat64(0xbff00000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0xbf800001), target: hexToFloat64(0xbff00060, 0x00000001), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0xbf800000), target: hexToFloat64(0xbff00000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0xbf800000), target: hexToFloat64(0xbff00070, 0x00000002), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0xbf800001), target: hexToFloat64(0xbff00000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0xbf800001), target: hexToFloat64(0xbff00080, 0x00000002), is_correct: false }, + ]) + .fn(t => { + const test_val = t.params.test_val; + const target = t.params.target; + const is_correct = t.params.is_correct; + + const got = correctlyRounded(test_val, target); + t.expect( + got === is_correct, + `correctlyRounded(${test_val}, ${target}) returned ${got}. Expected ${is_correct}` + ); + }); + +g.test('test,math,correctlyRoundedNoFlushOnly') + .paramsSubcasesOnly<correctlyRoundedCase>([ + // NaN Cases + { test_val: f32Bits(kBit.f32.nan.positive.s), target: NaN, is_correct: true }, + { test_val: f32Bits(kBit.f32.nan.positive.q), target: NaN, is_correct: true }, + { test_val: f32Bits(kBit.f32.nan.negative.s), target: NaN, is_correct: true }, + { test_val: f32Bits(kBit.f32.nan.negative.q), target: NaN, is_correct: true }, + { test_val: f32Bits(0x7fffffff), target: NaN, is_correct: true }, + { test_val: f32Bits(0xffffffff), target: NaN, is_correct: true }, + { test_val: f32Bits(kBit.f32.infinity.positive), target: NaN, is_correct: false }, + { test_val: f32Bits(kBit.f32.infinity.negative), target: NaN, is_correct: false }, + { test_val: f32Bits(kBit.f32.positive.zero), target: NaN, is_correct: false }, + { test_val: f32Bits(kBit.f32.negative.zero), target: NaN, is_correct: false }, + + // Infinities + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.positive.s), target: Number.POSITIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.positive.q), target: Number.POSITIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.negative.s), target: Number.POSITIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.negative.q), target: Number.POSITIVE_INFINITY, is_correct: false }, + { test_val: f32Bits(0x7fffffff), target: Number.POSITIVE_INFINITY, is_correct: false }, + { test_val: f32Bits(0xffffffff), target: Number.POSITIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.infinity.positive), target: Number.POSITIVE_INFINITY, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.infinity.negative), target: Number.POSITIVE_INFINITY, is_correct: false }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.positive.s), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.positive.q), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.negative.s), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.negative.q), target: Number.NEGATIVE_INFINITY, is_correct: false }, + { test_val: f32Bits(0x7fffffff), target: Number.NEGATIVE_INFINITY, is_correct: false }, + { test_val: f32Bits(0xffffffff), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.infinity.positive), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.infinity.negative), target: Number.NEGATIVE_INFINITY, is_correct: true }, + + // Zeros + { test_val: f32Bits(kBit.f32.positive.zero), target: 0, is_correct: true }, + { test_val: f32Bits(kBit.f32.negative.zero), target: 0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: 0, is_correct: false }, + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: 0, is_correct: false }, + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: 0, is_correct: false }, + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: 0, is_correct: false }, + + { test_val: f32Bits(kBit.f32.positive.zero), target: -0, is_correct: true }, + { test_val: f32Bits(kBit.f32.negative.zero), target: -0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: -0, is_correct: false }, + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: -0, is_correct: false }, + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: -0, is_correct: false }, + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: -0, is_correct: false }, + + // 32-bit subnormals + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: false }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: false }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: false }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + + // 64-bit subnormals + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: hexToFloat64(0x00000000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: hexToFloat64(0x00000000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: hexToFloat64(0x00000000, 0x00000001), is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: hexToFloat64(0x00000000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: hexToFloat64(0x00000000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: hexToFloat64(0x00000000, 0x00000002), is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: hexToFloat64(0x800fffff, 0xffffffff), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: hexToFloat64(0x800fffff, 0xffffffff), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: hexToFloat64(0x800fffff, 0xffffffff), is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: hexToFloat64(0x800fffff, 0xfffffffe), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: hexToFloat64(0x800fffff, 0xfffffffe), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: hexToFloat64(0x800fffff, 0xfffffffe), is_correct: true }, + + // 32-bit normals + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.max), target: hexToF32(kBit.f32.positive.max), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.min), target: hexToF32(kBit.f32.positive.min), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.max), target: hexToF32(kBit.f32.negative.max), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.min), target: hexToF32(kBit.f32.negative.min), is_correct: true }, + { test_val: f32Bits(0x03800000), target: hexToF32(0x03800000), is_correct: true }, + { test_val: f32Bits(0x03800000), target: hexToF32(0x03800002), is_correct: false }, + { test_val: f32Bits(0x03800001), target: hexToF32(0x03800001), is_correct: true }, + { test_val: f32Bits(0x03800001), target: hexToF32(0x03800010), is_correct: false }, + { test_val: f32Bits(0x83800000), target: hexToF32(0x83800000), is_correct: true }, + { test_val: f32Bits(0x83800000), target: hexToF32(0x83800002), is_correct: false }, + { test_val: f32Bits(0x83800001), target: hexToF32(0x83800001), is_correct: true }, + { test_val: f32Bits(0x83800001), target: hexToF32(0x83800010), is_correct: false }, + + // 64-bit normals + // prettier-ignore + { test_val: f32Bits(0x3f800000), target: hexToFloat64(0x3ff00000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0x3f800000), target: hexToFloat64(0x3ff00010, 0x00000001), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0x3f800001), target: hexToFloat64(0x3ff00000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0x3f800001), target: hexToFloat64(0x3ff00020, 0x00000001), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0x3f800000), target: hexToFloat64(0x3ff00000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0x3f800000), target: hexToFloat64(0x3ff00030, 0x00000002), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0x3f800001), target: hexToFloat64(0x3ff00000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0x3f800001), target: hexToFloat64(0x3ff00040, 0x00000002), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0xbf800000), target: hexToFloat64(0xbff00000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0xbf800000), target: hexToFloat64(0xbff00050, 0x00000001), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0xbf800001), target: hexToFloat64(0xbff00000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0xbf800001), target: hexToFloat64(0xbff00060, 0x00000001), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0xbf800000), target: hexToFloat64(0xbff00000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0xbf800000), target: hexToFloat64(0xbff00070, 0x00000002), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0xbf800001), target: hexToFloat64(0xbff00000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0xbf800001), target: hexToFloat64(0xbff00080, 0x00000002), is_correct: false }, + ]) + .fn(t => { + const test_val = t.params.test_val; + const target = t.params.target; + const is_correct = t.params.is_correct; + + const got = correctlyRounded(test_val, target, false, true); + t.expect( + got === is_correct, + `correctlyRounded(${test_val}, ${target}) returned ${got}. Expected ${is_correct}` + ); + }); + +g.test('test,math,correctlyRoundedFlushToZeroOnly') + .paramsSubcasesOnly<correctlyRoundedCase>([ + // NaN Cases + { test_val: f32Bits(kBit.f32.nan.positive.s), target: NaN, is_correct: true }, + { test_val: f32Bits(kBit.f32.nan.positive.q), target: NaN, is_correct: true }, + { test_val: f32Bits(kBit.f32.nan.negative.s), target: NaN, is_correct: true }, + { test_val: f32Bits(kBit.f32.nan.negative.q), target: NaN, is_correct: true }, + { test_val: f32Bits(0x7fffffff), target: NaN, is_correct: true }, + { test_val: f32Bits(0xffffffff), target: NaN, is_correct: true }, + { test_val: f32Bits(kBit.f32.infinity.positive), target: NaN, is_correct: false }, + { test_val: f32Bits(kBit.f32.infinity.negative), target: NaN, is_correct: false }, + { test_val: f32Bits(kBit.f32.positive.zero), target: NaN, is_correct: false }, + { test_val: f32Bits(kBit.f32.negative.zero), target: NaN, is_correct: false }, + + // Infinities + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.positive.s), target: Number.POSITIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.positive.q), target: Number.POSITIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.negative.s), target: Number.POSITIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.negative.q), target: Number.POSITIVE_INFINITY, is_correct: false }, + { test_val: f32Bits(0x7fffffff), target: Number.POSITIVE_INFINITY, is_correct: false }, + { test_val: f32Bits(0xffffffff), target: Number.POSITIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.infinity.positive), target: Number.POSITIVE_INFINITY, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.infinity.negative), target: Number.POSITIVE_INFINITY, is_correct: false }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.positive.s), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.positive.q), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.negative.s), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.nan.negative.q), target: Number.NEGATIVE_INFINITY, is_correct: false }, + { test_val: f32Bits(0x7fffffff), target: Number.NEGATIVE_INFINITY, is_correct: false }, + { test_val: f32Bits(0xffffffff), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.infinity.positive), target: Number.NEGATIVE_INFINITY, is_correct: false }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.infinity.negative), target: Number.NEGATIVE_INFINITY, is_correct: true }, + + // Zeros + { test_val: f32Bits(kBit.f32.positive.zero), target: 0, is_correct: true }, + { test_val: f32Bits(kBit.f32.negative.zero), target: 0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: 0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: 0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: 0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: 0, is_correct: true }, + + { test_val: f32Bits(kBit.f32.positive.zero), target: -0, is_correct: true }, + { test_val: f32Bits(kBit.f32.negative.zero), target: -0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: -0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: -0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: -0, is_correct: true }, + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: -0, is_correct: true }, + + // 32-bit subnormals + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: f32Bits(kBit.f32.subnormal.positive.min).value as number, is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: f32Bits(kBit.f32.subnormal.positive.max).value as number, is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: f32Bits(kBit.f32.subnormal.negative.max).value as number, is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.max), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.min), target: f32Bits(kBit.f32.subnormal.negative.min).value as number, is_correct: true }, + + // 64-bit subnormals + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: hexToFloat64(0x00000000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: hexToFloat64(0x00000000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: hexToFloat64(0x00000000, 0x00000001), is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.positive.min), target: hexToFloat64(0x00000000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: hexToFloat64(0x00000000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: hexToFloat64(0x00000000, 0x00000002), is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: hexToFloat64(0x800fffff, 0xffffffff), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: hexToFloat64(0x800fffff, 0xffffffff), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: hexToFloat64(0x800fffff, 0xffffffff), is_correct: true }, + + // prettier-ignore + { test_val: f32Bits(kBit.f32.subnormal.negative.max), target: hexToFloat64(0x800fffff, 0xfffffffe), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.zero), target: hexToFloat64(0x800fffff, 0xfffffffe), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.zero), target: hexToFloat64(0x800fffff, 0xfffffffe), is_correct: true }, + + // 32-bit normals + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.max), target: hexToF32(kBit.f32.positive.max), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.positive.min), target: hexToF32(kBit.f32.positive.min), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.max), target: hexToF32(kBit.f32.negative.max), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(kBit.f32.negative.min), target: hexToF32(kBit.f32.negative.min), is_correct: true }, + { test_val: f32Bits(0x03800000), target: hexToF32(0x03800000), is_correct: true }, + { test_val: f32Bits(0x03800000), target: hexToF32(0x03800002), is_correct: false }, + { test_val: f32Bits(0x03800001), target: hexToF32(0x03800001), is_correct: true }, + { test_val: f32Bits(0x03800001), target: hexToF32(0x03800010), is_correct: false }, + { test_val: f32Bits(0x83800000), target: hexToF32(0x83800000), is_correct: true }, + { test_val: f32Bits(0x83800000), target: hexToF32(0x83800002), is_correct: false }, + { test_val: f32Bits(0x83800001), target: hexToF32(0x83800001), is_correct: true }, + { test_val: f32Bits(0x83800001), target: hexToF32(0x83800010), is_correct: false }, + + // 64-bit normals + // prettier-ignore + { test_val: f32Bits(0x3f800000), target: hexToFloat64(0x3ff00000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0x3f800000), target: hexToFloat64(0x3ff00010, 0x00000001), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0x3f800001), target: hexToFloat64(0x3ff00000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0x3f800001), target: hexToFloat64(0x3ff00020, 0x00000001), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0x3f800000), target: hexToFloat64(0x3ff00000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0x3f800000), target: hexToFloat64(0x3ff00030, 0x00000002), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0x3f800001), target: hexToFloat64(0x3ff00000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0x3f800001), target: hexToFloat64(0x3ff00040, 0x00000002), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0xbf800000), target: hexToFloat64(0xbff00000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0xbf800000), target: hexToFloat64(0xbff00050, 0x00000001), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0xbf800001), target: hexToFloat64(0xbff00000, 0x00000001), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0xbf800001), target: hexToFloat64(0xbff00060, 0x00000001), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0xbf800000), target: hexToFloat64(0xbff00000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0xbf800000), target: hexToFloat64(0xbff00070, 0x00000002), is_correct: false }, + // prettier-ignore + { test_val: f32Bits(0xbf800001), target: hexToFloat64(0xbff00000, 0x00000002), is_correct: true }, + // prettier-ignore + { test_val: f32Bits(0xbf800001), target: hexToFloat64(0xbff00080, 0x00000002), is_correct: false }, + ]) + .fn(t => { + const test_val = t.params.test_val; + const target = t.params.target; + const is_correct = t.params.is_correct; + + const got = correctlyRounded(test_val, target, true, false); + t.expect( + got === is_correct, + `correctlyRounded(${test_val}, ${target}) returned ${got}. Expected ${is_correct}` + ); + }); + +interface lerpCase { + min: number; + max: number; + t: number; + result: number; +} + +g.test('test,math,lerp') + .paramsSimple<lerpCase>([ + // Infinite cases + { min: 0.0, max: Number.POSITIVE_INFINITY, t: 0.5, result: Number.NaN }, + { min: Number.NEGATIVE_INFINITY, max: 1.0, t: 0.5, result: Number.NaN }, + { min: Number.NEGATIVE_INFINITY, max: Number.POSITIVE_INFINITY, t: 0.5, result: Number.NaN }, + { min: 0.0, max: 1.0, t: Number.NEGATIVE_INFINITY, result: Number.NaN }, + { min: 0.0, max: 1.0, t: Number.POSITIVE_INFINITY, result: Number.NaN }, + + // [0.0, 1.0] cases + { min: 0.0, max: 1.0, t: -1.0, result: -1.0 }, + { min: 0.0, max: 1.0, t: 0.0, result: 0.0 }, + { min: 0.0, max: 1.0, t: 0.1, result: 0.1 }, + { min: 0.0, max: 1.0, t: 0.01, result: 0.01 }, + { min: 0.0, max: 1.0, t: 0.001, result: 0.001 }, + { min: 0.0, max: 1.0, t: 0.25, result: 0.25 }, + { min: 0.0, max: 1.0, t: 0.5, result: 0.5 }, + { min: 0.0, max: 1.0, t: 0.9, result: 0.9 }, + { min: 0.0, max: 1.0, t: 0.99, result: 0.99 }, + { min: 0.0, max: 1.0, t: 0.999, result: 0.999 }, + { min: 0.0, max: 1.0, t: 1.0, result: 1.0 }, + { min: 0.0, max: 1.0, t: 2.0, result: 2.0 }, + + // [0.0, 10.0] cases + { min: 0.0, max: 10.0, t: -1.0, result: -10.0 }, + { min: 0.0, max: 10.0, t: 0.0, result: 0.0 }, + { min: 0.0, max: 10.0, t: 0.1, result: 1.0 }, + { min: 0.0, max: 10.0, t: 0.01, result: 0.1 }, + { min: 0.0, max: 10.0, t: 0.001, result: 0.01 }, + { min: 0.0, max: 10.0, t: 0.25, result: 2.5 }, + { min: 0.0, max: 10.0, t: 0.5, result: 5.0 }, + { min: 0.0, max: 10.0, t: 0.9, result: 9.0 }, + { min: 0.0, max: 10.0, t: 0.99, result: 9.9 }, + { min: 0.0, max: 10.0, t: 0.999, result: 9.99 }, + { min: 0.0, max: 10.0, t: 1.0, result: 10.0 }, + { min: 0.0, max: 10.0, t: 2.0, result: 20.0 }, + + // [2.0, 10.0] cases + { min: 2.0, max: 10.0, t: -1.0, result: -6.0 }, + { min: 2.0, max: 10.0, t: 0.0, result: 2.0 }, + { min: 2.0, max: 10.0, t: 0.1, result: 2.8 }, + { min: 2.0, max: 10.0, t: 0.01, result: 2.08 }, + { min: 2.0, max: 10.0, t: 0.001, result: 2.008 }, + { min: 2.0, max: 10.0, t: 0.25, result: 4.0 }, + { min: 2.0, max: 10.0, t: 0.5, result: 6.0 }, + { min: 2.0, max: 10.0, t: 0.9, result: 9.2 }, + { min: 2.0, max: 10.0, t: 0.99, result: 9.92 }, + { min: 2.0, max: 10.0, t: 0.999, result: 9.992 }, + { min: 2.0, max: 10.0, t: 1.0, result: 10.0 }, + { min: 2.0, max: 10.0, t: 2.0, result: 18.0 }, + + // [-1.0, 1.0] cases + { min: -1.0, max: 1.0, t: -2.0, result: -5.0 }, + { min: -1.0, max: 1.0, t: 0.0, result: -1.0 }, + { min: -1.0, max: 1.0, t: 0.1, result: -0.8 }, + { min: -1.0, max: 1.0, t: 0.01, result: -0.98 }, + { min: -1.0, max: 1.0, t: 0.001, result: -0.998 }, + { min: -1.0, max: 1.0, t: 0.25, result: -0.5 }, + { min: -1.0, max: 1.0, t: 0.5, result: 0.0 }, + { min: -1.0, max: 1.0, t: 0.9, result: 0.8 }, + { min: -1.0, max: 1.0, t: 0.99, result: 0.98 }, + { min: -1.0, max: 1.0, t: 0.999, result: 0.998 }, + { min: -1.0, max: 1.0, t: 1.0, result: 1.0 }, + { min: -1.0, max: 1.0, t: 2.0, result: 3.0 }, + + // [-1.0, 0.0] cases + { min: -1.0, max: 0.0, t: -1.0, result: -2.0 }, + { min: -1.0, max: 0.0, t: 0.0, result: -1.0 }, + { min: -1.0, max: 0.0, t: 0.1, result: -0.9 }, + { min: -1.0, max: 0.0, t: 0.01, result: -0.99 }, + { min: -1.0, max: 0.0, t: 0.001, result: -0.999 }, + { min: -1.0, max: 0.0, t: 0.25, result: -0.75 }, + { min: -1.0, max: 0.0, t: 0.5, result: -0.5 }, + { min: -1.0, max: 0.0, t: 0.9, result: -0.1 }, + { min: -1.0, max: 0.0, t: 0.99, result: -0.01 }, + { min: -1.0, max: 0.0, t: 0.999, result: -0.001 }, + { min: -1.0, max: 0.0, t: 1.0, result: 0.0 }, + { min: -1.0, max: 0.0, t: 2.0, result: 1.0 }, + + // [0.0, -1.0] cases + { min: 0.0, max: -1.0, t: -1.0, result: 1.0 }, + { min: 0.0, max: -1.0, t: 0.0, result: 0.0 }, + { min: 0.0, max: -1.0, t: 0.1, result: -0.1 }, + { min: 0.0, max: -1.0, t: 0.01, result: -0.01 }, + { min: 0.0, max: -1.0, t: 0.001, result: -0.001 }, + { min: 0.0, max: -1.0, t: 0.25, result: -0.25 }, + { min: 0.0, max: -1.0, t: 0.5, result: -0.5 }, + { min: 0.0, max: -1.0, t: 0.9, result: -0.9 }, + { min: 0.0, max: -1.0, t: 0.99, result: -0.99 }, + { min: 0.0, max: -1.0, t: 0.999, result: -0.999 }, + { min: 0.0, max: -1.0, t: 1.0, result: -1.0 }, + { min: 0.0, max: -1.0, t: 2.0, result: -2.0 }, + ]) + .fn(test => { + const min = test.params.min; + const max = test.params.max; + const t = test.params.t; + const got = lerp(min, max, t); + const expect = test.params.result; + + test.expect( + 1 >= diffULP(got, expect) || (Number.isNaN(got) && Number.isNaN(expect)), + `lerp(${min}, ${max}, ${t}) returned ${got}. Expected ${expect}` + ); + }); + +interface rangeCase { + min: Scalar; + max: Scalar; + num_steps: Scalar; + result: Array<number>; +} + +g.test('test,math,linearRange') + .paramsSimple<rangeCase>([ + // prettier-ignore + { min: f32(0.0), max: f32(Number.POSITIVE_INFINITY), num_steps: u32(10), result: new Array<number>(10).fill(Number.NaN) }, + // prettier-ignore + { min: f32(Number.NEGATIVE_INFINITY), max: f32(1.0), num_steps: u32(10), result: new Array<number>(10).fill(Number.NaN) }, + // prettier-ignore + { min: f32(Number.NEGATIVE_INFINITY), max: f32(Number.POSITIVE_INFINITY), num_steps: u32(10), result: new Array<number>(10).fill(Number.NaN) }, + // prettier-ignore + { min: f32(0.0), max: f32(0.0), num_steps: u32(10), result: new Array<number>(10).fill(0.0) }, + // prettier-ignore + { min: f32(10.0), max: f32(10.0), num_steps: u32(10), result: new Array<number>(10).fill(10.0) }, + { min: f32(0.0), max: f32(10.0), num_steps: u32(1), result: [0.0] }, + // prettier-ignore + { min: f32(0.0), max: f32(10.0), num_steps: u32(11), result: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] }, + // prettier-ignore + { min: f32(0.0), max: f32(1000.0), num_steps: u32(11), result: [0.0, 100.0, 200.0, 300.0, 400.0, 500.0, 600.0, 700.0, 800.0, 900.0, 1000.0] }, + // prettier-ignore + { min: f32(1.0), max: f32(5.0), num_steps: u32(5), result: [1.0, 2.0, 3.0, 4.0, 5.0] }, + // prettier-ignore + { min: f32(0.0), max: f32(1.0), num_steps: u32(11), result: [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] }, + // prettier-ignore + { min: f32(0.0), max: f32(1.0), num_steps: u32(5), result: [0.0, 0.25, 0.5, 0.75, 1.0] }, + // prettier-ignore + { min: f32(-1.0), max: f32(1.0), num_steps: u32(11), result: [-1.0, -0.8, -0.6, -0.4, -0.2, 0.0, 0.2, 0.4, 0.6, 0.8, 1.0] }, + // prettier-ignore + { min: f32(-1.0), max: f32(0), num_steps: u32(11), result: [-1.0, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1, 0.0] }, + ]) + .fn(test => { + const min = test.params.min; + const max = test.params.max; + const num_steps = test.params.num_steps; + const got = linearRange(min, max, num_steps); + const expect = test.params.result; + + test.expect( + compareArrayOfNumbers(got, expect), + `linearRange(${min}, ${max}, ${num_steps}) returned ${got}. Expected ${expect}` + ); + }); + +g.test('test,math,biasedRange') + .paramsSimple<rangeCase>([ + // prettier-ignore + { min: f32(0.0), max: f32(Number.POSITIVE_INFINITY), num_steps: u32(10), result: new Array<number>(10).fill(Number.NaN) }, + // prettier-ignore + { min: f32(Number.NEGATIVE_INFINITY), max: f32(1.0), num_steps: u32(10), result: new Array<number>(10).fill(Number.NaN) }, + // prettier-ignore + { min: f32(Number.NEGATIVE_INFINITY), max: f32(Number.POSITIVE_INFINITY), num_steps: u32(10), result: new Array<number>(10).fill(Number.NaN) }, + // prettier-ignore + { min: f32(0.0), max: f32(0.0), num_steps: u32(10), result: new Array<number>(10).fill(0.0) }, + // prettier-ignore + { min: f32(10.0), max: f32(10.0), num_steps: u32(10), result: new Array<number>(10).fill(10.0) }, + { min: f32(0.0), max: f32(10.0), num_steps: u32(1), result: [0.0] }, + // prettier-ignore + { min: f32(0.0), max: f32(10.0), num_steps: u32(11), result: [0, 0.1, 0.4, 0.9, 1.6,2.5,3.6,4.9,6.4,8.1,10] }, + // prettier-ignore + { min: f32(0.0), max: f32(1000.0), num_steps: u32(11), result: [0.0, 10.0, 40.0, 90.0, 160.0, 250.0, 360.0, 490.0, 640.0, 810.0, 1000.0] }, + // prettier-ignore + { min: f32(1.0), max: f32(5.0), num_steps: u32(5), result: [1.0, 1.25, 2.0, 3.25, 5.0] }, + // prettier-ignore + { min: f32(0.0), max: f32(1.0), num_steps: u32(11), result: [0, 0.01, 0.04, 0.09, 0.16, 0.25 , 0.36, 0.49, 0.64, 0.81, 1.0] }, + // prettier-ignore + { min: f32(0.0), max: f32(1.0), num_steps: u32(5), result: [0.0, 0.0625, 0.25, 0.5625, 1.0] }, + // prettier-ignore + { min: f32(-1.0), max: f32(1.0), num_steps: u32(11), result: [-1.0, -0.98, -0.92, -0.82, -0.68, -0.5, -0.28 ,-0.02, 0.28, 0.62, 1.0] }, + // prettier-ignore + { min: f32(-1.0), max: f32(0), num_steps: u32(11), result: [-1.0 , -0.99, -0.96, -0.91, -0.84, -0.75, -0.64, -0.51, -0.36, -0.19, 0.0] }, + ]) + .fn(test => { + const min = test.params.min; + const max = test.params.max; + const num_steps = test.params.num_steps; + const got = biasedRange(min, max, num_steps); + const expect = test.params.result; + + test.expect( + compareArrayOfNumbers(got, expect), + `biasedRange(${min}, ${max}, ${num_steps}) returned ${got}. Expected ${expect}` ); }); diff --git a/chromium/third_party/webgpu-cts/src/src/unittests/test_query.spec.ts b/chromium/third_party/webgpu-cts/src/src/unittests/test_query.spec.ts index ed197dda4c9..4a744c49e9a 100644 --- a/chromium/third_party/webgpu-cts/src/src/unittests/test_query.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/unittests/test_query.spec.ts @@ -1,7 +1,9 @@ export const description = ` Tests for TestQuery `; + import { makeTestGroup } from '../common/framework/test_group.js'; +import { parseQuery } from '../common/internal/query/parseQuery.js'; import { TestQueryMultiFile, TestQueryMultiTest, @@ -16,6 +18,52 @@ class F extends UnitTest { expectToString(q: TestQuery, exp: string) { this.expect(q.toString() === exp); } + + expectQueriesEqual(q1: TestQuery, q2: TestQuery) { + this.expect(q1.level === q2.level); + + if (q1.level >= 1) { + this.expect(q1.isMultiFile === q2.isMultiFile); + this.expect(q1.suite === q2.suite); + this.expect(q1.filePathParts.length === q2.filePathParts.length); + for (let i = 0; i < q1.filePathParts.length; i++) { + this.expect(q1.filePathParts[i] === q2.filePathParts[i]); + } + } + + if (q1.level >= 2) { + const p1 = q1 as TestQueryMultiTest; + const p2 = q2 as TestQueryMultiTest; + + this.expect(p1.isMultiTest === p2.isMultiTest); + this.expect(p1.testPathParts.length === p2.testPathParts.length); + for (let i = 0; i < p1.testPathParts.length; i++) { + this.expect(p1.testPathParts[i] === p2.testPathParts[i]); + } + } + + if (q1.level >= 3) { + const p1 = q1 as TestQueryMultiCase; + const p2 = q2 as TestQueryMultiCase; + + this.expect(p1.isMultiCase === p2.isMultiCase); + this.expect(Object.keys(p1.params).length === Object.keys(p2.params).length); + for (const key of Object.keys(p1.params)) { + this.expect(key in p2.params); + const v1 = p1.params[key]; + const v2 = p2.params[key]; + this.expect( + v1 === v2 || + (typeof v1 === 'number' && isNaN(v1)) === (typeof v2 === 'number' && isNaN(v2)) + ); + this.expect(Object.is(v1, -0) === Object.is(v2, -0)); + } + } + } + + expectQueryParse(s: string, q: TestQuery) { + this.expectQueriesEqual(q, parseQuery(s)); + } } export const g = makeTestGroup(F); @@ -53,4 +101,43 @@ g.test('toString').fn(t => { 's:a,b:c,d:x=1;y=2' ); t.expectToString(new TestQuerySingleCase('s', ['a', 'b'], ['c', 'd'], {}), 's:a,b:c,d:'); + + // Test handling of magic param value that convert to NaN/undefined/Infinity/etc. + t.expectToString(new TestQuerySingleCase('s', ['a'], ['b'], { c: NaN }), 's:a:b:c="_nan_"'); + t.expectToString( + new TestQuerySingleCase('s', ['a'], ['b'], { c: undefined }), + 's:a:b:c="_undef_"' + ); + t.expectToString(new TestQuerySingleCase('s', ['a'], ['b'], { c: -0 }), 's:a:b:c="_negzero_"'); +}); + +g.test('parseQuery').fn(t => { + t.expectQueryParse('s:*', new TestQueryMultiFile('s', [])); + t.expectQueryParse('s:a,*', new TestQueryMultiFile('s', ['a'])); + t.expectQueryParse('s:a,b,*', new TestQueryMultiFile('s', ['a', 'b'])); + t.expectQueryParse('s:a,b:*', new TestQueryMultiTest('s', ['a', 'b'], [])); + t.expectQueryParse('s:a,b:c,*', new TestQueryMultiTest('s', ['a', 'b'], ['c'])); + t.expectQueryParse('s:a,b:c,d,*', new TestQueryMultiTest('s', ['a', 'b'], ['c', 'd'])); + t.expectQueryParse('s:a,b:c,d:*', new TestQueryMultiCase('s', ['a', 'b'], ['c', 'd'], {})); + t.expectQueryParse( + 's:a,b:c,d:x=1;*', + new TestQueryMultiCase('s', ['a', 'b'], ['c', 'd'], { x: 1 }) + ); + t.expectQueryParse( + 's:a,b:c,d:x=1;y=2;*', + new TestQueryMultiCase('s', ['a', 'b'], ['c', 'd'], { x: 1, y: 2 }) + ); + t.expectQueryParse( + 's:a,b:c,d:x=1;y=2', + new TestQuerySingleCase('s', ['a', 'b'], ['c', 'd'], { x: 1, y: 2 }) + ); + t.expectQueryParse('s:a,b:c,d:', new TestQuerySingleCase('s', ['a', 'b'], ['c', 'd'], {})); + + // Test handling of magic param value that convert to NaN/undefined/Infinity/etc. + t.expectQueryParse('s:a:b:c="_nan_"', new TestQuerySingleCase('s', ['a'], ['b'], { c: NaN })); + t.expectQueryParse( + 's:a:b:c="_undef_"', + new TestQuerySingleCase('s', ['a'], ['b'], { c: undefined }) + ); + t.expectQueryParse('s:a:b:c="_negzero_"', new TestQuerySingleCase('s', ['a'], ['b'], { c: -0 })); }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/buffers/map_ArrayBuffer.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/buffers/map_ArrayBuffer.spec.ts index 6c5dec877b5..42868393092 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/buffers/map_ArrayBuffer.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/buffers/map_ArrayBuffer.spec.ts @@ -18,7 +18,7 @@ g.test('postMessage') `Using postMessage to send a getMappedRange-returned ArrayBuffer should throw an exception iff the ArrayBuffer is in the transfer list (x= map read, map write). - TODO: Determine what the exception.name is expected to be.` + TODO: Determine what the exception.name is expected to be. [1]` ) .params(u => u // @@ -41,7 +41,7 @@ g.test('postMessage') const ab2Promise = new Promise<ArrayBuffer>(resolve => { mc.port2.onmessage = ev => resolve(ev.data); }); - // TODO: Pass an exception name here instead of `true`, once we figure out what the exception + // [1]: Pass an exception name here instead of `true`, once we figure out what the exception // is supposed to be. t.shouldThrow( transfer ? true : false, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/buffers/threading.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/buffers/threading.spec.ts index f1146ab46a1..b69404508d1 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/buffers/threading.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/buffers/threading.spec.ts @@ -1,11 +1,29 @@ export const description = ` -TODO: -- Copy GPUBuffer to another thread while {pending, mapped mappedAtCreation} on {same,diff} thread -- Destroy on one thread while {pending, mapped, mappedAtCreation, mappedAtCreation+unmap+mapped} - on another thread. +Tests for valid operations with various client-side thread-shared state of GPUBuffers. + +States to test: +- mapping pending +- mapped +- mapped at creation +- mapped at creation, then unmapped +- mapped at creation, then unmapped, then re-mapped +- destroyed + +TODO: Look for more things to test. `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { GPUTest } from '../../../gpu_test.js'; export const g = makeTestGroup(GPUTest); + +g.test('serialize') + .desc( + `Copy a GPUBuffer to another thread while it is in various states on +{the sending thread, yet another thread}.` + ) + .unimplemented(); + +g.test('destroyed') + .desc(`Destroy on one thread while in various states in another thread.`) + .unimplemented(); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts index 9f56fa4a745..93286e544c9 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts @@ -1,7 +1,4 @@ -export const description = `copyTexturetoTexture operation tests - -TODO: remove fragment stage in InitializeDepthAspect() when browsers support null fragment stage. -`; +export const description = `copyTexturetoTexture operation tests`; import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { assert, memcpy } from '../../../../common/util/util.js'; @@ -15,6 +12,8 @@ import { kBufferSizeAlignment, kDepthStencilFormats, kMinDynamicBufferOffsetAlignment, + kTextureDimensions, + textureDimensionAndFormatCompatible, } from '../../../capability_info.js'; import { GPUTest } from '../../../gpu_test.js'; import { makeBufferWithContents } from '../../../util/buffer.js'; @@ -26,18 +25,18 @@ class F extends GPUTest { GetInitialData(byteSize: number): Uint8Array { const initialData = new Uint8Array(byteSize); for (let i = 0; i < initialData.length; ++i) { - initialData[i] = (i ** 3 + i) % 251; + initialData[i] = ((i ** 3 + i) % 251) + 1; // Have all initialData be non zero. } return initialData; } GetInitialDataPerMipLevel( + dimension: GPUTextureDimension, textureSize: Required<GPUExtent3DDict>, format: SizedTextureFormat, mipLevel: number ): Uint8Array { - // TODO(jiawei.shao@intel.com): support 3D textures - const textureSizeAtLevel = physicalMipSize(textureSize, format, '2d', mipLevel); + const textureSizeAtLevel = physicalMipSize(textureSize, format, dimension, mipLevel); const bytesPerBlock = kTextureFormatInfo[format].bytesPerBlock; const blockWidthInTexel = kTextureFormatInfo[format].blockWidth; const blockHeightInTexel = kTextureFormatInfo[format].blockHeight; @@ -65,9 +64,11 @@ class F extends GPUTest { } DoCopyTextureToTextureTest( + dimension: GPUTextureDimension, srcTextureSize: Required<GPUExtent3DDict>, dstTextureSize: Required<GPUExtent3DDict>, - format: SizedTextureFormat, + srcFormat: SizedTextureFormat, + dstFormat: SizedTextureFormat, copyBoxOffsets: { srcOffset: { x: number; y: number; z: number }; dstOffset: { x: number; y: number; z: number }; @@ -76,30 +77,42 @@ class F extends GPUTest { srcCopyLevel: number, dstCopyLevel: number ): void { - const kMipLevelCount = 4; + const mipLevelCount = dimension === '1d' ? 1 : 4; // Create srcTexture and dstTexture const srcTextureDesc: GPUTextureDescriptor = { + dimension, size: srcTextureSize, - format, + format: srcFormat, usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, - mipLevelCount: kMipLevelCount, + mipLevelCount, }; const srcTexture = this.device.createTexture(srcTextureDesc); const dstTextureDesc: GPUTextureDescriptor = { + dimension, size: dstTextureSize, - format, + format: dstFormat, usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, - mipLevelCount: kMipLevelCount, + mipLevelCount, }; const dstTexture = this.device.createTexture(dstTextureDesc); // Fill the whole subresource of srcTexture at srcCopyLevel with initialSrcData. - const initialSrcData = this.GetInitialDataPerMipLevel(srcTextureSize, format, srcCopyLevel); - const srcTextureSizeAtLevel = physicalMipSize(srcTextureSize, format, '2d', srcCopyLevel); - const bytesPerBlock = kTextureFormatInfo[format].bytesPerBlock; - const blockWidth = kTextureFormatInfo[format].blockWidth; - const blockHeight = kTextureFormatInfo[format].blockHeight; + const initialSrcData = this.GetInitialDataPerMipLevel( + dimension, + srcTextureSize, + srcFormat, + srcCopyLevel + ); + const srcTextureSizeAtLevel = physicalMipSize( + srcTextureSize, + srcFormat, + dimension, + srcCopyLevel + ); + const bytesPerBlock = kTextureFormatInfo[srcFormat].bytesPerBlock; + const blockWidth = kTextureFormatInfo[srcFormat].blockWidth; + const blockHeight = kTextureFormatInfo[srcFormat].blockHeight; const srcBlocksPerRow = srcTextureSizeAtLevel.width / blockWidth; const srcBlockRowsPerImage = srcTextureSizeAtLevel.height / blockHeight; this.device.queue.writeTexture( @@ -113,19 +126,28 @@ class F extends GPUTest { ); // Copy the region specified by copyBoxOffsets from srcTexture to dstTexture. - const dstTextureSizeAtLevel = physicalMipSize(dstTextureSize, format, '2d', dstCopyLevel); + const dstTextureSizeAtLevel = physicalMipSize( + dstTextureSize, + dstFormat, + dimension, + dstCopyLevel + ); const minWidth = Math.min(srcTextureSizeAtLevel.width, dstTextureSizeAtLevel.width); const minHeight = Math.min(srcTextureSizeAtLevel.height, dstTextureSizeAtLevel.height); + const minDepth = Math.min( + srcTextureSizeAtLevel.depthOrArrayLayers, + dstTextureSizeAtLevel.depthOrArrayLayers + ); const appliedSrcOffset = { x: Math.min(copyBoxOffsets.srcOffset.x * blockWidth, minWidth), y: Math.min(copyBoxOffsets.srcOffset.y * blockHeight, minHeight), - z: copyBoxOffsets.srcOffset.z, + z: Math.min(copyBoxOffsets.srcOffset.z, minDepth), }; const appliedDstOffset = { x: Math.min(copyBoxOffsets.dstOffset.x * blockWidth, minWidth), y: Math.min(copyBoxOffsets.dstOffset.y * blockHeight, minHeight), - z: copyBoxOffsets.dstOffset.z, + z: Math.min(copyBoxOffsets.dstOffset.z, minDepth), }; const appliedCopyWidth = Math.max( @@ -142,10 +164,12 @@ class F extends GPUTest { ); assert(appliedCopyWidth % blockWidth === 0 && appliedCopyHeight % blockHeight === 0); - const appliedCopyDepth = - srcTextureSize.depthOrArrayLayers + - copyBoxOffsets.copyExtent.depthOrArrayLayers - - Math.max(appliedSrcOffset.z, appliedDstOffset.z); + const appliedCopyDepth = Math.max( + 0, + minDepth + + copyBoxOffsets.copyExtent.depthOrArrayLayers - + Math.max(appliedSrcOffset.z, appliedDstOffset.z) + ); assert(appliedCopyDepth >= 0); const encoder = this.device.createCommandEncoder(); @@ -309,12 +333,12 @@ class F extends GPUTest { vertex: { module: this.device.createShaderModule({ code: ` - [[block]] struct Params { + struct Params { copyLayer: f32; }; - [[group(0), binding(0)]] var<uniform> param: Params; - [[stage(vertex)]] - fn main([[builtin(vertex_index)]] VertexIndex : u32)-> [[builtin(position)]] vec4<f32> { + @group(0) @binding(0) var<uniform> param: Params; + @stage(vertex) + fn main(@builtin(vertex_index) VertexIndex : u32)-> @builtin(position) vec4<f32> { var depthValue = 0.5 + 0.2 * sin(param.copyLayer); var pos : array<vec3<f32>, 6> = array<vec3<f32>, 6>( vec3<f32>(-1.0, 1.0, depthValue), @@ -334,8 +358,8 @@ class F extends GPUTest { renderPipelineDescriptor.fragment = { module: this.device.createShaderModule({ code: ` - [[stage(fragment)]] - fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) + fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.0, 1.0, 0.0, 1.0); }`, }), @@ -612,41 +636,89 @@ g.test('color_textures,non_compressed,non_array') - covers the corners of the dstTexture - doesn't cover any texels that are on the edge of the dstTexture - covers the mipmap level > 0 + + Tests for all pairs of valid source/destination formats, and all texture dimensions. ` ) .params(u => u - .combine('format', kRegularTextureFormats) + .combine('srcFormat', kRegularTextureFormats) + .combine('dstFormat', kRegularTextureFormats) + .filter(({ srcFormat, dstFormat }) => { + const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat; + const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat; + return ( + srcFormat === dstFormat || + (srcBaseFormat !== undefined && + dstBaseFormat !== undefined && + srcBaseFormat === dstBaseFormat) + ); + }) + .combine('dimension', kTextureDimensions) + .filter( + ({ dimension, srcFormat, dstFormat }) => + textureDimensionAndFormatCompatible(dimension, srcFormat) && + textureDimensionAndFormatCompatible(dimension, dstFormat) + ) .beginSubcases() - .combine('textureSize', [ - { - srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 }, - dstTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 }, - }, - { - srcTextureSize: { width: 31, height: 33, depthOrArrayLayers: 1 }, - dstTextureSize: { width: 31, height: 33, depthOrArrayLayers: 1 }, - }, - { - srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 }, - dstTextureSize: { width: 64, height: 64, depthOrArrayLayers: 1 }, - }, - { - srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 }, - dstTextureSize: { width: 63, height: 61, depthOrArrayLayers: 1 }, - }, - ]) + .expandWithParams(p => { + const params = [ + { + srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 }, + dstTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 }, + }, + { + srcTextureSize: { width: 31, height: 33, depthOrArrayLayers: 1 }, + dstTextureSize: { width: 31, height: 33, depthOrArrayLayers: 1 }, + }, + { + srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 }, + dstTextureSize: { width: 64, height: 64, depthOrArrayLayers: 1 }, + }, + { + srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 }, + dstTextureSize: { width: 63, height: 61, depthOrArrayLayers: 1 }, + }, + ]; + if (p.dimension === '1d') { + for (const param of params) { + param.srcTextureSize.height = 1; + param.dstTextureSize.height = 1; + } + } + + return params; + }) .combine('copyBoxOffsets', kCopyBoxOffsetsForWholeDepth) + .unless( + p => + p.dimension === '1d' && + (p.copyBoxOffsets.copyExtent.height !== 0 || + p.copyBoxOffsets.srcOffset.y !== 0 || + p.copyBoxOffsets.dstOffset.y !== 0) + ) .combine('srcCopyLevel', [0, 3]) .combine('dstCopyLevel', [0, 3]) + .unless(p => p.dimension === '1d' && (p.srcCopyLevel !== 0 || p.dstCopyLevel !== 0)) ) .fn(async t => { - const { textureSize, format, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params; + const { + dimension, + srcTextureSize, + dstTextureSize, + srcFormat, + dstFormat, + copyBoxOffsets, + srcCopyLevel, + dstCopyLevel, + } = t.params; t.DoCopyTextureToTextureTest( - textureSize.srcTextureSize, - textureSize.dstTextureSize, - format, + dimension, + srcTextureSize, + dstTextureSize, + srcFormat, + dstFormat, copyBoxOffsets, srcCopyLevel, dstCopyLevel @@ -659,11 +731,30 @@ g.test('color_textures,compressed,non_array') Validate the correctness of the copy by filling the srcTexture with testable data and any compressed color format supported by WebGPU, doing CopyTextureToTexture() copy, and verifying the content of the whole dstTexture. + + Tests for all pairs of valid source/destination formats, and all texture dimensions. ` ) .params(u => u - .combine('format', kCompressedTextureFormats) + .combine('srcFormat', kCompressedTextureFormats) + .combine('dstFormat', kCompressedTextureFormats) + .filter(({ srcFormat, dstFormat }) => { + const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat; + const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat; + return ( + srcFormat === dstFormat || + (srcBaseFormat !== undefined && + dstBaseFormat !== undefined && + srcBaseFormat === dstBaseFormat) + ); + }) + .combine('dimension', kTextureDimensions) + .filter( + ({ dimension, srcFormat, dstFormat }) => + textureDimensionAndFormatCompatible(dimension, srcFormat) && + textureDimensionAndFormatCompatible(dimension, dstFormat) + ) .beginSubcases() .combine('textureSizeInBlocks', [ // The heights and widths in blocks are all power of 2 @@ -686,22 +777,38 @@ g.test('color_textures,compressed,non_array') .combine('dstCopyLevel', [0, 2]) ) .fn(async t => { - const { textureSizeInBlocks, format, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params; - await t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature); - const { blockWidth, blockHeight } = kTextureFormatInfo[format]; + const { + dimension, + textureSizeInBlocks, + srcFormat, + dstFormat, + copyBoxOffsets, + srcCopyLevel, + dstCopyLevel, + } = t.params; + await t.selectDeviceOrSkipTestCase([ + kTextureFormatInfo[srcFormat].feature, + kTextureFormatInfo[dstFormat].feature, + ]); + const srcBlockWidth = kTextureFormatInfo[srcFormat].blockWidth; + const srcBlockHeight = kTextureFormatInfo[srcFormat].blockHeight; + const dstBlockWidth = kTextureFormatInfo[dstFormat].blockWidth; + const dstBlockHeight = kTextureFormatInfo[dstFormat].blockHeight; t.DoCopyTextureToTextureTest( + dimension, { - width: textureSizeInBlocks.src.width * blockWidth, - height: textureSizeInBlocks.src.height * blockHeight, + width: textureSizeInBlocks.src.width * srcBlockWidth, + height: textureSizeInBlocks.src.height * srcBlockHeight, depthOrArrayLayers: 1, }, { - width: textureSizeInBlocks.dst.width * blockWidth, - height: textureSizeInBlocks.dst.height * blockHeight, + width: textureSizeInBlocks.dst.width * dstBlockWidth, + height: textureSizeInBlocks.dst.height * dstBlockHeight, depthOrArrayLayers: 1, }, - format, + srcFormat, + dstFormat, copyBoxOffsets, srcCopyLevel, dstCopyLevel @@ -718,7 +825,24 @@ g.test('color_textures,non_compressed,array') ) .params(u => u - .combine('format', kRegularTextureFormats) + .combine('srcFormat', kRegularTextureFormats) + .combine('dstFormat', kRegularTextureFormats) + .filter(({ srcFormat, dstFormat }) => { + const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat; + const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat; + return ( + srcFormat === dstFormat || + (srcBaseFormat !== undefined && + dstBaseFormat !== undefined && + srcBaseFormat === dstBaseFormat) + ); + }) + .combine('dimension', ['2d', '3d'] as const) + .filter( + ({ dimension, srcFormat, dstFormat }) => + textureDimensionAndFormatCompatible(dimension, srcFormat) && + textureDimensionAndFormatCompatible(dimension, dstFormat) + ) .beginSubcases() .combine('textureSize', [ { @@ -729,6 +853,10 @@ g.test('color_textures,non_compressed,array') srcTextureSize: { width: 31, height: 33, depthOrArrayLayers: 5 }, dstTextureSize: { width: 31, height: 33, depthOrArrayLayers: 5 }, }, + { + srcTextureSize: { width: 31, height: 32, depthOrArrayLayers: 33 }, + dstTextureSize: { width: 31, height: 32, depthOrArrayLayers: 33 }, + }, ]) .combine('copyBoxOffsets', kCopyBoxOffsetsFor2DArrayTextures) @@ -736,12 +864,22 @@ g.test('color_textures,non_compressed,array') .combine('dstCopyLevel', [0, 3]) ) .fn(async t => { - const { textureSize, format, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params; + const { + dimension, + textureSize, + srcFormat, + dstFormat, + copyBoxOffsets, + srcCopyLevel, + dstCopyLevel, + } = t.params; t.DoCopyTextureToTextureTest( + dimension, textureSize.srcTextureSize, textureSize.dstTextureSize, - format, + srcFormat, + dstFormat, copyBoxOffsets, srcCopyLevel, dstCopyLevel @@ -754,11 +892,30 @@ g.test('color_textures,compressed,array') Validate the correctness of the texture-to-texture copy on 2D array textures by filling the srcTexture with testable data and any compressed color format supported by WebGPU, doing CopyTextureToTexture() copy, and verifying the content of the whole dstTexture. + + Tests for all pairs of valid source/destination formats, and all texture dimensions. ` ) .params(u => u - .combine('format', kCompressedTextureFormats) + .combine('srcFormat', kCompressedTextureFormats) + .combine('dstFormat', kCompressedTextureFormats) + .filter(({ srcFormat, dstFormat }) => { + const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat; + const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat; + return ( + srcFormat === dstFormat || + (srcBaseFormat !== undefined && + dstBaseFormat !== undefined && + srcBaseFormat === dstBaseFormat) + ); + }) + .combine('dimension', ['2d', '3d'] as const) + .filter( + ({ dimension, srcFormat, dstFormat }) => + textureDimensionAndFormatCompatible(dimension, srcFormat) && + textureDimensionAndFormatCompatible(dimension, dstFormat) + ) .beginSubcases() .combine('textureSizeInBlocks', [ // The heights and widths in blocks are all power of 2 @@ -771,22 +928,38 @@ g.test('color_textures,compressed,array') .combine('dstCopyLevel', [0, 2]) ) .fn(async t => { - const { textureSizeInBlocks, format, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params; - await t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature); - const { blockWidth, blockHeight } = kTextureFormatInfo[format]; + const { + dimension, + textureSizeInBlocks, + srcFormat, + dstFormat, + copyBoxOffsets, + srcCopyLevel, + dstCopyLevel, + } = t.params; + await t.selectDeviceOrSkipTestCase([ + kTextureFormatInfo[srcFormat].feature, + kTextureFormatInfo[dstFormat].feature, + ]); + const srcBlockWidth = kTextureFormatInfo[srcFormat].blockWidth; + const srcBlockHeight = kTextureFormatInfo[srcFormat].blockHeight; + const dstBlockWidth = kTextureFormatInfo[dstFormat].blockWidth; + const dstBlockHeight = kTextureFormatInfo[dstFormat].blockHeight; t.DoCopyTextureToTextureTest( + dimension, { - width: textureSizeInBlocks.src.width * blockWidth, - height: textureSizeInBlocks.src.height * blockHeight, + width: textureSizeInBlocks.src.width * srcBlockWidth, + height: textureSizeInBlocks.src.height * srcBlockHeight, depthOrArrayLayers: 5, }, { - width: textureSizeInBlocks.dst.width * blockWidth, - height: textureSizeInBlocks.dst.height * blockHeight, + width: textureSizeInBlocks.dst.width * dstBlockWidth, + height: textureSizeInBlocks.dst.height * dstBlockHeight, depthOrArrayLayers: 5, }, - format, + srcFormat, + dstFormat, copyBoxOffsets, srcCopyLevel, dstCopyLevel @@ -798,12 +971,18 @@ g.test('zero_sized') ` Validate the correctness of zero-sized copies (should be no-ops). + - For each texture dimension. - Copies that are zero-sized in only one dimension {x, y, z}, each touching the {lower, upper} end of that dimension. ` ) .paramsSubcasesOnly(u => u // + .combineWithParams([ + { dimension: '1d', textureSize: { width: 32, height: 1, depthOrArrayLayers: 1 } }, + { dimension: '2d', textureSize: { width: 32, height: 32, depthOrArrayLayers: 5 } }, + { dimension: '3d', textureSize: { width: 32, height: 32, depthOrArrayLayers: 5 } }, + ] as const) .combine('copyBoxOffset', [ // copyExtent.width === 0 { @@ -860,19 +1039,29 @@ g.test('zero_sized') copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 }, }, ]) + .unless( + p => + p.dimension === '1d' && + (p.copyBoxOffset.copyExtent.height !== 0 || + p.copyBoxOffset.srcOffset.y !== 0 || + p.copyBoxOffset.dstOffset.y !== 0) + ) .combine('srcCopyLevel', [0, 3]) .combine('dstCopyLevel', [0, 3]) + .unless(p => p.dimension === '1d' && (p.srcCopyLevel !== 0 || p.dstCopyLevel !== 0)) ) .fn(async t => { - const { copyBoxOffset, srcCopyLevel, dstCopyLevel } = t.params; + const { dimension, textureSize, copyBoxOffset, srcCopyLevel, dstCopyLevel } = t.params; - const format = 'rgba8unorm'; - const textureSize = { width: 64, height: 32, depthOrArrayLayers: 5 }; + const srcFormat = 'rgba8unorm'; + const dstFormat = 'rgba8unorm'; t.DoCopyTextureToTextureTest( + dimension, textureSize, textureSize, - format, + srcFormat, + dstFormat, copyBoxOffset, srcCopyLevel, dstCopyLevel @@ -1045,8 +1234,8 @@ g.test('copy_multisampled_color') vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] - fn main([[builtin(vertex_index)]] VertexIndex : u32) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) + fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(-1.0, 1.0), vec2<f32>( 1.0, 1.0), @@ -1060,8 +1249,8 @@ g.test('copy_multisampled_color') fragment: { module: t.device.createShaderModule({ code: ` - [[stage(fragment)]] - fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) + fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.3, 0.5, 0.8, 1.0); }`, }), @@ -1106,8 +1295,8 @@ g.test('copy_multisampled_color') vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] - fn main([[builtin(vertex_index)]] VertexIndex : u32) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) + fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 6>( vec2<f32>(-1.0, 1.0), vec2<f32>(-1.0, -1.0), @@ -1123,10 +1312,10 @@ g.test('copy_multisampled_color') fragment: { module: t.device.createShaderModule({ code: ` - [[group(0), binding(0)]] var sourceTexture : texture_multisampled_2d<f32>; - [[group(0), binding(1)]] var destinationTexture : texture_multisampled_2d<f32>; - [[stage(fragment)]] - fn main([[builtin(position)]] coord_in: vec4<f32>) -> [[location(0)]] vec4<f32> { + @group(0) @binding(0) var sourceTexture : texture_multisampled_2d<f32>; + @group(0) @binding(1) var destinationTexture : texture_multisampled_2d<f32>; + @stage(fragment) + fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> { var coord_in_vec2 = vec2<i32>(i32(coord_in.x), i32(coord_in.y)); for (var sampleIndex = 0; sampleIndex < ${kSampleCount}; sampleIndex = sampleIndex + 1) { @@ -1218,8 +1407,8 @@ g.test('copy_multisampled_depth') const vertexState: GPUVertexState = { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] - fn main([[builtin(vertex_index)]] VertexIndex : u32)-> [[builtin(position)]] vec4<f32> { + @stage(vertex) + fn main(@builtin(vertex_index) VertexIndex : u32)-> @builtin(position) vec4<f32> { var pos : array<vec3<f32>, 6> = array<vec3<f32>, 6>( vec3<f32>(-1.0, 1.0, 0.5), vec3<f32>(-1.0, -1.0, 0.0), @@ -1283,8 +1472,8 @@ g.test('copy_multisampled_depth') fragment: { module: t.device.createShaderModule({ code: ` - [[stage(fragment)]] - fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) + fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.0, 1.0, 0.0, 1.0); }`, }), diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/image_copy.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/image_copy.spec.ts index 8d2f4454da5..98061b2a598 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/image_copy.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/image_copy.spec.ts @@ -24,14 +24,15 @@ export const description = `writeTexture + copyBufferToTexture + copyTextureToBu slice we can set rowsPerImage to 0. Also test setting offset, rowsPerImage, mipLevel, origin, origin.{x,y,z} to undefined. * TODO: - - add another initMethod which renders the texture + - add another initMethod which renders the texture [3] - test copyT2B with buffer size not divisible by 4 (not done because expectContents 4-byte alignment) - - add tests for 1d / 3d textures - Convert the float32 values in initialData into the ones compatible to the depth aspect of depthFormats when depth16unorm and depth24unorm-stencil8 are supported by the browsers in DoCopyTextureToBufferWithDepthAspectTest(). -TODO: Fix this test for the various skipped formats: +TODO: Expand tests of GPUExtent3D [1] + +TODO: Fix this test for the various skipped formats [2]: - snorm tests failing due to rounding - float tests failing because float values are not byte-preserved - compressed formats @@ -49,6 +50,8 @@ import { DepthStencilFormat, depthStencilBufferTextureCopySupported, depthStencilFormatAspectSize, + kTextureDimensions, + textureDimensionAndFormatCompatible, } from '../../../capability_info.js'; import { GPUTest } from '../../../gpu_test.js'; import { makeBufferWithContents } from '../../../util/buffer.js'; @@ -84,7 +87,7 @@ type CheckMethod = 'PartialCopyT2B' | 'FullCopyT2B'; * undefined, then default values are passed as undefined instead of default values. If arrays, then * `GPUOrigin3D` and `GPUExtent3D` are passed as `[number, number, number]`. * * - * TODO: Try to expand this with something like: + * [1]: Try to expand this with something like: * ```ts * function encodeExtent3D( * mode: 'partial-array' | 'full-array' | 'extra-array' | 'partial-dict' | 'full-dict', @@ -96,8 +99,6 @@ type ChangeBeforePass = 'none' | 'undefined' | 'arrays'; /** Each combination of methods assume that the ones before it were tested and work correctly. */ const kMethodsToTest = [ - // We make sure that CopyT2B works when copying the whole texture for renderable formats: - // TODO // Then we make sure that WriteTexture works for all formats: { initMethod: 'WriteTexture', checkMethod: 'FullCopyT2B' }, // Then we make sure that CopyB2T works for all formats: @@ -106,7 +107,7 @@ const kMethodsToTest = [ { initMethod: 'WriteTexture', checkMethod: 'PartialCopyT2B' }, ] as const; -// TODO: Fix things so this list can be reduced to zero (see file description) +// [2]: Fix things so this list can be reduced to zero (see file description) const kExcludedFormats: Set<SizedTextureFormat> = new Set([ 'r8snorm', 'rg8snorm', @@ -351,10 +352,7 @@ class ImageCopyTest extends GPUTest { break; } case 'CopyB2T': { - const buffer = this.makeBufferWithContents(partialData, GPUBufferUsage.COPY_SRC, { - padToMultipleOf4: true, - }); - + const buffer = this.makeBufferWithContents(partialData, GPUBufferUsage.COPY_SRC); const encoder = this.device.createCommandEncoder(); encoder.copyBufferToTexture( { buffer, ...appliedDataLayout }, @@ -540,7 +538,7 @@ class ImageCopyTest extends GPUTest { origin = { x: 0, y: 0, z: 0 }, textureSize, format, - dimension = '2d', + dimension, initMethod, checkMethod, changeBeforePass = 'none', @@ -552,7 +550,7 @@ class ImageCopyTest extends GPUTest { origin?: Required<GPUOrigin3DDict>; textureSize: readonly [number, number, number]; format: SizedTextureFormat; - dimension?: GPUTextureDimension; + dimension: GPUTextureDimension; initMethod: InitMethod; checkMethod: CheckMethod; changeBeforePass?: ChangeBeforePass; @@ -764,7 +762,7 @@ class ImageCopyTest extends GPUTest { this.expectGPUBufferValuesEqual(outputBuffer, expectedData); } - // TODO(crbug.com/dawn/868): Revisit this when consolidating texture helpers. + // MAINTENANCE_TODO(crbug.com/dawn/868): Revisit this when consolidating texture helpers. async checkStencilTextureContent( stencilTexture: GPUTexture, stencilTextureSize: readonly [number, number, number], @@ -809,8 +807,8 @@ class ImageCopyTest extends GPUTest { vertex: { module: this.device.createShaderModule({ code: ` - [[stage(vertex)]] - fn main([[builtin(vertex_index)]] VertexIndex : u32)-> [[builtin(position)]] vec4<f32> { + @stage(vertex) + fn main(@builtin(vertex_index) VertexIndex : u32)-> @builtin(position) vec4<f32> { var pos : array<vec2<f32>, 6> = array<vec2<f32>, 6>( vec2<f32>(-1.0, 1.0), vec2<f32>(-1.0, -1.0), @@ -827,12 +825,12 @@ class ImageCopyTest extends GPUTest { fragment: { module: this.device.createShaderModule({ code: ` - [[block]] struct Params { + struct Params { stencilBitIndex: u32; }; - [[group(0), binding(0)]] var<uniform> param: Params; - [[stage(fragment)]] - fn main() -> [[location(0)]] vec4<f32> { + @group(0) @binding(0) var<uniform> param: Params; + @stage(fragment) + fn main() -> @location(0) vec4<f32> { return vec4<f32>(f32(1u << param.stencilBitIndex) / 255.0, 0.0, 0.0, 0.0); }`, }), @@ -979,7 +977,7 @@ class ImageCopyTest extends GPUTest { } } - // TODO(crbug.com/dawn/868): Revisit this when consolidating texture helpers. + // MAINTENANCE_TODO(crbug.com/dawn/868): Revisit this when consolidating texture helpers. initializeDepthAspectWithRendering( depthTexture: GPUTexture, depthFormat: GPUTextureFormat, @@ -1008,8 +1006,8 @@ class ImageCopyTest extends GPUTest { vertex: { module: this.device.createShaderModule({ code: ` - [[stage(vertex)]] - fn main([[builtin(vertex_index)]] VertexIndex : u32)-> [[builtin(position)]] vec4<f32> { + @stage(vertex) + fn main(@builtin(vertex_index) VertexIndex : u32)-> @builtin(position) vec4<f32> { var pos : array<vec2<f32>, 6> = array<vec2<f32>, 6>( vec2<f32>(-1.0, 1.0), vec2<f32>(-1.0, -1.0), @@ -1025,9 +1023,9 @@ class ImageCopyTest extends GPUTest { fragment: { module: this.device.createShaderModule({ code: ` - [[group(0), binding(0)]] var inputTexture: texture_2d<f32>; - [[stage(fragment)]] fn main([[builtin(position)]] fragcoord : vec4<f32>) -> - [[builtin(frag_depth)]] f32 { + @group(0) @binding(0) var inputTexture: texture_2d<f32>; + @stage(fragment) fn main(@builtin(position) fragcoord : vec4<f32>) -> + @builtin(frag_depth) f32 { var depthValue : vec4<f32> = textureLoad(inputTexture, vec2<i32>(fragcoord.xy), 0); return depthValue.x; }`, @@ -1095,7 +1093,7 @@ class ImageCopyTest extends GPUTest { dataPaddingInBytes: number, mipLevel: number ): void { - // TODO(crbug.com/dawn/868): convert the float32 values in initialData into the ones compatible + // [2]: need to convert the float32 values in initialData into the ones compatible // to the depth aspect of depthFormats when depth16unorm and depth24unorm-stencil8 are supported // by the browsers. assert(format !== 'depth16unorm' && format !== 'depth24unorm-stencil8'); @@ -1177,7 +1175,7 @@ class ImageCopyTest extends GPUTest { /** * This is a helper function used for filtering test parameters * - * TODO: Modify this after introducing tests with rendering. + * [3]: Modify this after introducing tests with rendering. */ function formatCanBeTested({ format }: { format: SizedTextureFormat }): boolean { return kTextureFormatInfo[format].copyDst && kTextureFormatInfo[format].copySrc; @@ -1216,6 +1214,16 @@ const kRowsPerImageAndBytesPerRowParams = { { copyWidthInBlocks: 7, copyHeightInBlocks: 1, copyDepth: 1 }, // copyHeight = 1 and copyDepth = 1 ], + + // Copy sizes that are suitable for 1D texture and check both some copy sizes and empty copies. + copySizes1D: [ + { copyWidthInBlocks: 3, copyHeightInBlocks: 1, copyDepth: 1 }, + { copyWidthInBlocks: 5, copyHeightInBlocks: 1, copyDepth: 1 }, + + { copyWidthInBlocks: 3, copyHeightInBlocks: 0, copyDepth: 1 }, + { copyWidthInBlocks: 0, copyHeightInBlocks: 1, copyDepth: 1 }, + { copyWidthInBlocks: 5, copyHeightInBlocks: 1, copyDepth: 0 }, + ], }; g.test('rowsPerImage_and_bytesPerRow') @@ -1228,6 +1236,8 @@ bytes in copy works for every format. Covers a special code path for D3D12: when bytesPerRow is not a multiple of 512 and copyExtent.depthOrArrayLayers > 1: copyExtent.depthOrArrayLayers % 2 == { 0, 1 } bytesPerRow == bytesInACompleteCopyImage + + TODO: Cover the special code paths for 3D textures in D3D12. ` ) .params(u => @@ -1235,9 +1245,16 @@ bytes in copy works for every format. .combineWithParams(kMethodsToTest) .combine('format', kWorkingTextureFormats) .filter(formatCanBeTested) + .combine('dimension', kTextureDimensions) + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .beginSubcases() .combineWithParams(kRowsPerImageAndBytesPerRowParams.paddings) - .combineWithParams(kRowsPerImageAndBytesPerRowParams.copySizes) + .expandWithParams(p => { + if (p.dimension === '1d') { + return kRowsPerImageAndBytesPerRowParams.copySizes1D; + } + return kRowsPerImageAndBytesPerRowParams.copySizes; + }) ) .fn(async t => { const { @@ -1247,6 +1264,7 @@ bytes in copy works for every format. copyHeightInBlocks, copyDepth, format, + dimension, initMethod, checkMethod, } = t.params; @@ -1284,6 +1302,7 @@ bytes in copy works for every format. Math.max(copyDepth, 1), ] /* making sure the texture is non-empty */, format, + dimension, initMethod, checkMethod, }); @@ -1314,17 +1333,21 @@ works for every format with 2d and 2d-array textures. Covers two special code paths for D3D12: offset + bytesInCopyExtentPerRow { ==, > } bytesPerRow offset > bytesInACompleteCopyImage + + TODO: Cover the special code paths for 3D textures in D3D12. ` ) - .params( - u => - u - .combineWithParams(kMethodsToTest) - .combine('format', kWorkingTextureFormats) - .filter(formatCanBeTested) - .beginSubcases() - .combineWithParams(kOffsetsAndSizesParams.offsetsAndPaddings) - .combine('copyDepth', kOffsetsAndSizesParams.copyDepth) // 2d and 2d-array textures + .params(u => + u + .combineWithParams(kMethodsToTest) + .combine('format', kWorkingTextureFormats) + .filter(formatCanBeTested) + .combine('dimension', kTextureDimensions) + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) + .beginSubcases() + .combineWithParams(kOffsetsAndSizesParams.offsetsAndPaddings) + .combine('copyDepth', kOffsetsAndSizesParams.copyDepth) // 2d and 2d-array textures + .unless(p => p.dimension === '1d' && p.copyDepth !== 1) ) .fn(async t => { const { @@ -1332,6 +1355,7 @@ works for every format with 2d and 2d-array textures. dataPaddingInBytes, copyDepth, format, + dimension, initMethod, checkMethod, } = t.params; @@ -1344,9 +1368,17 @@ works for every format with 2d and 2d-array textures. height: 3 * info.blockHeight, depthOrArrayLayers: copyDepth, }; - const rowsPerImage = 3; + let textureHeight = 4 * info.blockHeight; + let rowsPerImage = 3; const bytesPerRow = 256; + if (dimension === '1d') { + copySize.height = 1; + textureHeight = info.blockHeight; + rowsPerImage = 1; + } + const textureSize = [4 * info.blockWidth, textureHeight, copyDepth] as const; + const minDataSize = dataBytesForCopyOrFail({ layout: { offset, bytesPerRow, rowsPerImage }, format, @@ -1361,8 +1393,9 @@ works for every format with 2d and 2d-array textures. textureDataLayout: { offset, bytesPerRow, rowsPerImage }, copySize, dataSize, - textureSize: [4 * info.blockWidth, 4 * info.blockHeight, copyDepth], + textureSize, format, + dimension, initMethod, checkMethod, }); @@ -1378,6 +1411,8 @@ for all formats. We pass origin and copyExtent as [number, number, number].` .combineWithParams(kMethodsToTest) .combine('format', kWorkingTextureFormats) .filter(formatCanBeTested) + .combine('dimension', kTextureDimensions) + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .beginSubcases() .combine('originValueInBlocks', [0, 7, 8]) .combine('copySizeValueInBlocks', [0, 7, 8]) @@ -1388,6 +1423,7 @@ for all formats. We pass origin and copyExtent as [number, number, number].` p.copySizeValueInBlocks + p.originValueInBlocks + p.textureSizePaddingValueInBlocks === 0 ) .combine('coordinateToTest', [0, 1, 2] as const) + .unless(p => p.dimension === '1d' && p.coordinateToTest !== 0) ) .fn(async t => { const { @@ -1395,15 +1431,21 @@ for all formats. We pass origin and copyExtent as [number, number, number].` copySizeValueInBlocks, textureSizePaddingValueInBlocks, format, + dimension, initMethod, checkMethod, } = t.params; const info = kTextureFormatInfo[format]; await t.selectDeviceOrSkipTestCase(info.feature); - const originBlocks = [1, 1, 1]; - const copySizeBlocks = [2, 2, 2]; - const texSizeBlocks = [3, 3, 3]; + let originBlocks = [1, 1, 1]; + let copySizeBlocks = [2, 2, 2]; + let texSizeBlocks = [3, 3, 3]; + if (dimension === '1d') { + originBlocks = [1, 0, 0]; + copySizeBlocks = [2, 1, 1]; + texSizeBlocks = [3, 1, 1]; + } { const ctt = t.params.coordinateToTest; @@ -1449,6 +1491,7 @@ for all formats. We pass origin and copyExtent as [number, number, number].` origin, textureSize, format, + dimension, initMethod, checkMethod, changeBeforePass: 'arrays', @@ -1461,13 +1504,16 @@ for all formats. We pass origin and copyExtent as [number, number, number].` */ function* generateTestTextureSizes({ format, + dimension, mipLevel, _mipSizeInBlocks, }: { format: SizedTextureFormat; + dimension: GPUTextureDimension; mipLevel: number; _mipSizeInBlocks: Required<GPUExtent3DDict>; }): Generator<[number, number, number]> { + assert(dimension !== '1d'); // textureSize[1] would be wrong for 1D mipped textures. const info = kTextureFormatInfo[format]; const widthAtThisLevel = _mipSizeInBlocks.width * info.blockWidth; @@ -1475,7 +1521,7 @@ function* generateTestTextureSizes({ const textureSize: [number, number, number] = [ widthAtThisLevel << mipLevel, heightAtThisLevel << mipLevel, - _mipSizeInBlocks.depthOrArrayLayers, + _mipSizeInBlocks.depthOrArrayLayers << (dimension === '3d' ? mipLevel : 0), ]; yield textureSize; @@ -1503,6 +1549,10 @@ function* generateTestTextureSizes({ if (modifyWidth && modifyHeight) { yield [modifiedWidth, modifiedHeight, textureSize[2]]; } + + if (dimension === '3d') { + yield [textureSize[0], textureSize[1], textureSize[2] + 1]; + } } g.test('mip_levels') @@ -1510,6 +1560,9 @@ g.test('mip_levels') `Test that copying various mip levels works. Covers two special code paths: - The physical size of the subresource is not equal to the logical size. - bufferSize - offset < bytesPerImage * copyExtent.depthOrArrayLayers, and copyExtent needs to be clamped for all block formats. + - For 3D textures test copying to a sub-range of the depth. + +Tests both 2D and 3D textures. 1D textures are skipped because they can only have one mip level. ` ) .params(u => @@ -1517,6 +1570,8 @@ g.test('mip_levels') .combineWithParams(kMethodsToTest) .combine('format', kWorkingTextureFormats) .filter(formatCanBeTested) + .combine('dimension', ['2d', '3d'] as const) + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .beginSubcases() .combineWithParams([ // origin + copySize = texturePhysicalSizeAtMipLevel for all coordinates, 2d texture */ @@ -1571,6 +1626,7 @@ g.test('mip_levels') textureSize, mipLevel, format, + dimension, initMethod, checkMethod, } = t.params; @@ -1606,6 +1662,7 @@ g.test('mip_levels') mipLevel, textureSize, format, + dimension, initMethod, checkMethod, }); @@ -1621,11 +1678,13 @@ g.test('undefined_params') .params(u => u .combineWithParams(kMethodsToTest) + .combine('dimension', kTextureDimensions) .beginSubcases() .combineWithParams([ // copying one row: bytesPerRow and rowsPerImage can be undefined { copySize: [3, 1, 1], origin: [UND, UND, UND], bytesPerRow: UND, rowsPerImage: UND }, // copying one slice: rowsPerImage can be undefined + { copySize: [3, 1, 1], origin: [UND, UND, UND], bytesPerRow: 256, rowsPerImage: UND }, { copySize: [3, 3, 1], origin: [UND, UND, UND], bytesPerRow: 256, rowsPerImage: UND }, // copying two slices { copySize: [3, 3, 2], origin: [UND, UND, UND], bytesPerRow: 256, rowsPerImage: 3 }, @@ -1636,9 +1695,28 @@ g.test('undefined_params') // origin.z = undefined { copySize: [1, 1, 1], origin: [1, 1, UND], bytesPerRow: UND, rowsPerImage: UND }, ]) + .expandWithParams(p => [ + { + _textureSize: [ + 100, + p.copySize[1] + (p.origin[1] ?? 0), + p.copySize[2] + (p.origin[2] ?? 0), + ] as const, + }, + ]) + .unless(p => p.dimension === '1d' && (p._textureSize[1] > 1 || p._textureSize[2] > 1)) ) .fn(async t => { - const { bytesPerRow, rowsPerImage, copySize, origin, initMethod, checkMethod } = t.params; + const { + dimension, + _textureSize, + bytesPerRow, + rowsPerImage, + copySize, + origin, + initMethod, + checkMethod, + } = t.params; t.uploadTextureAndVerifyCopy({ textureDataLayout: { @@ -1650,10 +1728,11 @@ g.test('undefined_params') }, copySize: { width: copySize[0], height: copySize[1], depthOrArrayLayers: copySize[2] }, dataSize: 2000, - textureSize: [100, 3, 2], + textureSize: _textureSize, // Zeros will get turned back into undefined later. origin: { x: origin[0] ?? 0, y: origin[1] ?? 0, z: origin[2] ?? 0 }, format: 'rgba8unorm', + dimension, initMethod, checkMethod, changeBeforePass: 'undefined', diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/programmable/programmable_state_test.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/programmable/programmable_state_test.ts index 56acc8041a0..d7680aa2cca 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/programmable/programmable_state_test.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/programmable/programmable_state_test.ts @@ -43,15 +43,15 @@ export class ProgrammableStateTest extends GPUTest { ): GPUComputePipeline | GPURenderPipeline { switch (encoderType) { case 'compute pass': { - const wgsl = `[[block]] struct Data { + const wgsl = `struct Data { value : i32; }; - - [[group(${groups.a}), binding(0)]] var<storage> a : Data; - [[group(${groups.b}), binding(0)]] var<storage> b : Data; - [[group(${groups.out}), binding(0)]] var<storage, read_write> out : Data; - - [[stage(compute), workgroup_size(1)]] fn main() { + + @group(${groups.a}) @binding(0) var<storage> a : Data; + @group(${groups.b}) @binding(0) var<storage> b : Data; + @group(${groups.out}) @binding(0) var<storage, read_write> out : Data; + + @stage(compute) @workgroup_size(1) fn main() { out.value = ${algorithm}; return; } @@ -73,21 +73,21 @@ export class ProgrammableStateTest extends GPUTest { case 'render bundle': { const wgslShaders = { vertex: ` - [[stage(vertex)]] fn vert_main() -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn vert_main() -> @builtin(position) vec4<f32> { return vec4<f32>(0.5, 0.5, 0.0, 1.0); } `, fragment: ` - [[block]] struct Data { + struct Data { value : i32; }; - - [[group(${groups.a}), binding(0)]] var<storage> a : Data; - [[group(${groups.b}), binding(0)]] var<storage> b : Data; - [[group(${groups.out}), binding(0)]] var<storage, read_write> out : Data; - - [[stage(fragment)]] fn frag_main() -> [[location(0)]] vec4<f32> { + + @group(${groups.a}) @binding(0) var<storage> a : Data; + @group(${groups.b}) @binding(0) var<storage> b : Data; + @group(${groups.out}) @binding(0) var<storage, read_write> out : Data; + + @stage(fragment) fn frag_main() -> @location(0) vec4<f32> { out.value = ${algorithm}; return vec4<f32>(1.0, 0.0, 0.0, 1.0); } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/queries/README.txt b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/queries/README.txt index 4cd2643beeb..0118a6c5378 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/queries/README.txt +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/queries/README.txt @@ -1,6 +1,7 @@ TODO: test the behavior of creating/using/resolving queries. - occlusion - pipeline statistics + TODO: pipeline statistics queries are removed from core; consider moving tests to another suite. - timestamp - nested (e.g. timestamp or PS query inside occlusion query), if any such cases are valid. Try writing to the same query set (at same or different indices), if valid. Check results make sense. diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/render/state_tracking.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/render/state_tracking.spec.ts index b3c4e867af3..ca0f5758707 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/render/state_tracking.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/command_buffer/render/state_tracking.spec.ts @@ -3,18 +3,621 @@ Ensure state is set correctly. Tries to stress state caching (setting different times in different orders) for setIndexBuffer and setVertexBuffer. Equivalent tests for setBindGroup and setPipeline are in programmable/state_tracking.spec.ts. Equivalent tests for viewport/scissor/blend/reference are in render/dynamic_state.spec.ts - -TODO: plan and implement -- try setting states multiple times in different orders, check state is correct in a draw call. - - setIndexBuffer: specifically test changing the format, offset, size, without changing the buffer - - setVertexBuffer: specifically test changing the offset, size, without changing the buffer -- try changing the pipeline {before,after} the vertex/index buffers. - (In D3D12, the vertex buffer stride is part of SetVertexBuffer instead of the pipeline.) -- Test that drawing after having set vertex buffer slots not used by the pipeline. -- Test that setting / not setting the index buffer does not impact a non-indexed draw. `; import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -export const g = makeTestGroup(GPUTest); +class VertexAndIndexStateTrackingTest extends GPUTest { + GetRenderPipelineForTest(arrayStride: number): GPURenderPipeline { + return this.device.createRenderPipeline({ + vertex: { + module: this.device.createShaderModule({ + code: ` + struct Inputs { + @location(0) vertexPosition : f32; + @location(1) vertexColor : vec4<f32>; + }; + struct Outputs { + @builtin(position) position : vec4<f32>; + @location(0) color : vec4<f32>; + }; + @stage(vertex) + fn main(input : Inputs)-> Outputs { + var outputs : Outputs; + outputs.position = + vec4<f32>(input.vertexPosition, 0.5, 0.0, 1.0); + outputs.color = input.vertexColor; + return outputs; + }`, + }), + entryPoint: 'main', + buffers: [ + { + arrayStride, + attributes: [ + { + format: 'float32', + offset: 0, + shaderLocation: 0, + }, + { + format: 'unorm8x4', + offset: 4, + shaderLocation: 1, + }, + ], + }, + ], + }, + fragment: { + module: this.device.createShaderModule({ + code: ` + struct Input { + @location(0) color : vec4<f32>; + }; + @stage(fragment) + fn main(input : Input) -> @location(0) vec4<f32> { + return input.color; + }`, + }), + entryPoint: 'main', + targets: [{ format: 'rgba8unorm' }], + }, + primitive: { + topology: 'point-list', + }, + }); + } + + kVertexAttributeSize = 8; +} + +export const g = makeTestGroup(VertexAndIndexStateTrackingTest); + +g.test('set_index_buffer_without_changing_buffer') + .desc( + ` + Test that setting index buffer states (index format, offset, size) multiple times in different + orders still keeps the correctness of each draw call. +` + ) + .fn(async t => { + // Initialize the index buffer with 5 uint16 indices (0, 1, 2, 3, 4). + const indexBuffer = t.makeBufferWithContents( + new Uint16Array([0, 1, 2, 3, 4]), + GPUBufferUsage.INDEX + ); + + // Initialize the vertex buffer with required vertex attributes (position: f32, color: f32x4) + // Note that the maximum index in the test is 0x10000. + const kVertexAttributesCount = 0x10000 + 1; + const vertexBuffer = t.device.createBuffer({ + usage: GPUBufferUsage.VERTEX, + size: t.kVertexAttributeSize * kVertexAttributesCount, + mappedAtCreation: true, + }); + t.trackForCleanup(vertexBuffer); + const vertexAttributes = vertexBuffer.getMappedRange(); + const kPositions = [-0.8, -0.4, 0.0, 0.4, 0.8, -0.4]; + const kColors = [ + new Uint8Array([255, 0, 0, 255]), + new Uint8Array([255, 255, 255, 255]), + new Uint8Array([0, 0, 255, 255]), + new Uint8Array([255, 0, 255, 255]), + new Uint8Array([0, 255, 255, 255]), + new Uint8Array([0, 255, 0, 255]), + ]; + // Set vertex attributes at index {0..4} in Uint16. + // Note that the vertex attribute at index 1 will not be used. + for (let i = 0; i < kPositions.length - 1; ++i) { + const baseOffset = t.kVertexAttributeSize * i; + const vertexPosition = new Float32Array(vertexAttributes, baseOffset, 1); + vertexPosition[0] = kPositions[i]; + const vertexColor = new Uint8Array(vertexAttributes, baseOffset + 4, 4); + vertexColor.set(kColors[i]); + } + // Set vertex attributes at index 0x10000. + const lastOffset = t.kVertexAttributeSize * (kVertexAttributesCount - 1); + const lastVertexPosition = new Float32Array(vertexAttributes, lastOffset, 1); + lastVertexPosition[0] = kPositions[kPositions.length - 1]; + const lastVertexColor = new Uint8Array(vertexAttributes, lastOffset + 4, 4); + lastVertexColor.set(kColors[kColors.length - 1]); + + vertexBuffer.unmap(); + + const renderPipeline = t.GetRenderPipelineForTest(t.kVertexAttributeSize); + + const outputTexture = t.device.createTexture({ + format: 'rgba8unorm', + size: [kPositions.length - 1, 1, 1], + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const encoder = t.device.createCommandEncoder(); + const renderPass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: outputTexture.createView(), + loadValue: [0, 0, 0, 1], + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(renderPipeline); + renderPass.setVertexBuffer(0, vertexBuffer); + + // 1st draw: indexFormat = 'uint32', offset = 0, size = 4 (index value: 0x10000) + renderPass.setIndexBuffer(indexBuffer, 'uint32', 0, 4); + renderPass.drawIndexed(1); + + // 2nd draw: indexFormat = 'uint16', offset = 0, size = 4 (index value: 0) + renderPass.setIndexBuffer(indexBuffer, 'uint16', 0, 4); + renderPass.drawIndexed(1); + + // 3rd draw: indexFormat = 'uint16', offset = 4, size = 2 (index value: 2) + renderPass.setIndexBuffer(indexBuffer, 'uint16', 0, 2); + renderPass.setIndexBuffer(indexBuffer, 'uint16', 4, 2); + renderPass.drawIndexed(1); + + // 4th draw: indexformat = 'uint16', offset = 6, size = 4 (index values: 3, 4) + renderPass.setIndexBuffer(indexBuffer, 'uint16', 6, 2); + renderPass.setIndexBuffer(indexBuffer, 'uint16', 6, 4); + renderPass.drawIndexed(2); + + renderPass.endPass(); + t.queue.submit([encoder.finish()]); + + for (let i = 0; i < kPositions.length - 1; ++i) { + const expectedColor = i === 1 ? kColors[kPositions.length - 1] : kColors[i]; + t.expectSinglePixelIn2DTexture( + outputTexture, + 'rgba8unorm', + { x: i, y: 0 }, + { exp: expectedColor } + ); + } + }); + +g.test('set_vertex_buffer_without_changing_buffer') + .desc( + ` + Test that setting vertex buffer states (offset, size) multiple times in different orders still + keeps the correctness of each draw call. + - Tries several different sequences of setVertexBuffer+draw commands, each of which draws vertices + in all 4 output pixels, and check they were drawn correctly. +` + ) + .fn(async t => { + const kPositions = [-0.875, -0.625, -0.375, -0.125, 0.125, 0.375, 0.625, 0.875]; + const kColors = [ + new Uint8Array([255, 0, 0, 255]), + new Uint8Array([0, 255, 0, 255]), + new Uint8Array([0, 0, 255, 255]), + new Uint8Array([51, 0, 0, 255]), + new Uint8Array([0, 51, 0, 255]), + new Uint8Array([0, 0, 51, 255]), + new Uint8Array([255, 0, 255, 255]), + new Uint8Array([255, 255, 0, 255]), + ]; + + // Initialize the vertex buffer with required vertex attributes (position: f32, color: f32x4) + const kVertexAttributesCount = 8; + const vertexBuffer = t.device.createBuffer({ + usage: GPUBufferUsage.VERTEX, + size: t.kVertexAttributeSize * kVertexAttributesCount, + mappedAtCreation: true, + }); + t.trackForCleanup(vertexBuffer); + const vertexAttributes = vertexBuffer.getMappedRange(); + for (let i = 0; i < kPositions.length; ++i) { + const baseOffset = t.kVertexAttributeSize * i; + const vertexPosition = new Float32Array(vertexAttributes, baseOffset, 1); + vertexPosition[0] = kPositions[i]; + const vertexColor = new Uint8Array(vertexAttributes, baseOffset + 4, 4); + vertexColor.set(kColors[i]); + } + + vertexBuffer.unmap(); + + const renderPipeline = t.GetRenderPipelineForTest(t.kVertexAttributeSize); + + const outputTexture = t.device.createTexture({ + format: 'rgba8unorm', + size: [kPositions.length, 1, 1], + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const encoder = t.device.createCommandEncoder(); + const renderPass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: outputTexture.createView(), + loadValue: [0, 0, 0, 1], + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(renderPipeline); + + // Change 'size' in setVertexBuffer() + renderPass.setVertexBuffer(0, vertexBuffer, 0, t.kVertexAttributeSize); + renderPass.setVertexBuffer(0, vertexBuffer, 0, t.kVertexAttributeSize * 2); + renderPass.draw(2); + + // Change 'offset' in setVertexBuffer() + renderPass.setVertexBuffer( + 0, + vertexBuffer, + t.kVertexAttributeSize * 2, + t.kVertexAttributeSize * 2 + ); + renderPass.draw(2); + + // Change 'size' again in setVertexBuffer() + renderPass.setVertexBuffer( + 0, + vertexBuffer, + t.kVertexAttributeSize * 4, + t.kVertexAttributeSize * 2 + ); + renderPass.setVertexBuffer( + 0, + vertexBuffer, + t.kVertexAttributeSize * 4, + t.kVertexAttributeSize * 4 + ); + renderPass.draw(4); + + renderPass.endPass(); + t.queue.submit([encoder.finish()]); + + for (let i = 0; i < kPositions.length; ++i) { + t.expectSinglePixelIn2DTexture( + outputTexture, + 'rgba8unorm', + { x: i, y: 0 }, + { exp: kColors[i] } + ); + } + }); + +g.test('change_pipeline_before_and_after_vertex_buffer') + .desc( + ` + Test that changing the pipeline {before,after} the vertex buffers still keeps the correctness of + each draw call (In D3D12, the vertex buffer stride is part of SetVertexBuffer instead of the + pipeline.) +` + ) + .fn(async t => { + const kPositions = [-0.8, -0.4, 0.0, 0.4, 0.8, 0.9]; + const kColors = [ + new Uint8Array([255, 0, 0, 255]), + new Uint8Array([255, 255, 255, 255]), + new Uint8Array([0, 255, 0, 255]), + new Uint8Array([0, 0, 255, 255]), + new Uint8Array([255, 0, 255, 255]), + new Uint8Array([0, 255, 255, 255]), + ]; + + // Initialize the vertex buffer with required vertex attributes (position: f32, color: f32x4) + const vertexBuffer = t.device.createBuffer({ + usage: GPUBufferUsage.VERTEX, + size: t.kVertexAttributeSize * kPositions.length, + mappedAtCreation: true, + }); + t.trackForCleanup(vertexBuffer); + // Note that kPositions[1], kColors[1], kPositions[5] and kColors[5] are not used. + const vertexAttributes = vertexBuffer.getMappedRange(); + for (let i = 0; i < kPositions.length; ++i) { + const baseOffset = t.kVertexAttributeSize * i; + const vertexPosition = new Float32Array(vertexAttributes, baseOffset, 1); + vertexPosition[0] = kPositions[i]; + const vertexColor = new Uint8Array(vertexAttributes, baseOffset + 4, 4); + vertexColor.set(kColors[i]); + } + vertexBuffer.unmap(); + + // Create two render pipelines with different vertex attribute strides + const renderPipeline1 = t.GetRenderPipelineForTest(t.kVertexAttributeSize); + const renderPipeline2 = t.GetRenderPipelineForTest(t.kVertexAttributeSize * 2); + + const kPointsCount = kPositions.length - 1; + const outputTexture = t.device.createTexture({ + format: 'rgba8unorm', + size: [kPointsCount, 1, 1], + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const encoder = t.device.createCommandEncoder(); + const renderPass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: outputTexture.createView(), + loadValue: [0, 0, 0, 1], + storeOp: 'store', + }, + ], + }); + + // Update render pipeline before setVertexBuffer. The applied vertex attribute stride should be + // 2 * kVertexAttributeSize. + renderPass.setPipeline(renderPipeline1); + renderPass.setPipeline(renderPipeline2); + renderPass.setVertexBuffer(0, vertexBuffer); + renderPass.draw(2); + + // Update render pipeline after setVertexBuffer. The applied vertex attribute stride should be + // kVertexAttributeSize. + renderPass.setVertexBuffer(0, vertexBuffer, 3 * t.kVertexAttributeSize); + renderPass.setPipeline(renderPipeline1); + renderPass.draw(2); + + renderPass.endPass(); + + t.queue.submit([encoder.finish()]); + + for (let i = 0; i < kPointsCount; ++i) { + const expectedColor = i === 1 ? new Uint8Array([0, 0, 0, 255]) : kColors[i]; + t.expectSinglePixelIn2DTexture( + outputTexture, + 'rgba8unorm', + { x: i, y: 0 }, + { exp: expectedColor } + ); + } + }); + +g.test('set_vertex_buffer_but_not_used_in_draw') + .desc( + ` + Test that drawing after having set vertex buffer slots not used by the pipeline works correctly. + - In the test there are 2 draw calls in the render pass. The first draw call uses 2 vertex buffers + (position and color), and the second draw call only uses 1 vertex buffer (for color, the vertex + position is defined as constant values in the vertex shader). The test verifies if both of these + two draw calls work correctly. + ` + ) + .fn(async t => { + const kPositions = new Float32Array([-0.75, -0.25]); + const kColors = new Uint8Array([255, 0, 0, 255, 0, 255, 0, 255]); + + // Initialize the vertex buffers with required vertex attributes (position: f32, color: f32x4) + const kAttributeStride = 4; + const positionBuffer = t.makeBufferWithContents(kPositions, GPUBufferUsage.VERTEX); + const colorBuffer = t.makeBufferWithContents(kColors, GPUBufferUsage.VERTEX); + + const fragmentState: GPUFragmentState = { + module: t.device.createShaderModule({ + code: ` + struct Input { + @location(0) color : vec4<f32>; + }; + @stage(fragment) + fn main(input : Input) -> @location(0) vec4<f32> { + return input.color; + }`, + }), + entryPoint: 'main', + targets: [{ format: 'rgba8unorm' }], + }; + + // Create renderPipeline1 that uses both positionBuffer and colorBuffer. + const renderPipeline1 = t.device.createRenderPipeline({ + vertex: { + module: t.device.createShaderModule({ + code: ` + struct Inputs { + @location(0) vertexColor : vec4<f32>; + @location(1) vertexPosition : f32; + }; + struct Outputs { + @builtin(position) position : vec4<f32>; + @location(0) color : vec4<f32>; + }; + @stage(vertex) + fn main(input : Inputs)-> Outputs { + var outputs : Outputs; + outputs.position = + vec4<f32>(input.vertexPosition, 0.5, 0.0, 1.0); + outputs.color = input.vertexColor; + return outputs; + }`, + }), + entryPoint: 'main', + buffers: [ + { + arrayStride: kAttributeStride, + attributes: [ + { + format: 'unorm8x4', + offset: 0, + shaderLocation: 0, + }, + ], + }, + { + arrayStride: kAttributeStride, + attributes: [ + { + format: 'float32', + offset: 0, + shaderLocation: 1, + }, + ], + }, + ], + }, + fragment: fragmentState, + primitive: { + topology: 'point-list', + }, + }); + + const renderPipeline2 = t.device.createRenderPipeline({ + vertex: { + module: t.device.createShaderModule({ + code: ` + struct Inputs { + @builtin(vertex_index) vertexIndex : u32; + @location(0) vertexColor : vec4<f32>; + }; + struct Outputs { + @builtin(position) position : vec4<f32>; + @location(0) color : vec4<f32>; + }; + @stage(vertex) + fn main(input : Inputs)-> Outputs { + var kPositions = array<f32, 2> (0.25, 0.75); + var outputs : Outputs; + outputs.position = + vec4(kPositions[input.vertexIndex], 0.5, 0.0, 1.0); + outputs.color = input.vertexColor; + return outputs; + }`, + }), + entryPoint: 'main', + buffers: [ + { + arrayStride: kAttributeStride, + attributes: [ + { + format: 'unorm8x4', + offset: 0, + shaderLocation: 0, + }, + ], + }, + ], + }, + fragment: fragmentState, + primitive: { + topology: 'point-list', + }, + }); + + const kPointsCount = 4; + const outputTexture = t.device.createTexture({ + format: 'rgba8unorm', + size: [kPointsCount, 1, 1], + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const encoder = t.device.createCommandEncoder(); + const renderPass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: outputTexture.createView(), + loadValue: [0, 0, 0, 1], + storeOp: 'store', + }, + ], + }); + + renderPass.setVertexBuffer(0, colorBuffer); + renderPass.setVertexBuffer(1, positionBuffer); + renderPass.setPipeline(renderPipeline1); + renderPass.draw(2); + + renderPass.setPipeline(renderPipeline2); + renderPass.draw(2); + + renderPass.endPass(); + + t.queue.submit([encoder.finish()]); + + const kExpectedColors = [ + kColors.subarray(0, 4), + kColors.subarray(4), + kColors.subarray(0, 4), + kColors.subarray(4), + ]; + + for (let i = 0; i < kPointsCount; ++i) { + t.expectSinglePixelIn2DTexture( + outputTexture, + 'rgba8unorm', + { x: i, y: 0 }, + { exp: kExpectedColors[i] } + ); + } + }); + +g.test('set_index_buffer_before_non_indexed_draw') + .desc( + ` + Test that setting / not setting the index buffer does not impact a non-indexed draw. + ` + ) + .fn(async t => { + const kPositions = [-0.75, -0.25, 0.25, 0.75]; + const kColors = [ + new Uint8Array([255, 0, 0, 255]), + new Uint8Array([0, 255, 0, 255]), + new Uint8Array([0, 0, 255, 255]), + new Uint8Array([255, 0, 255, 255]), + ]; + + // Initialize the vertex buffer with required vertex attributes (position: f32, color: f32x4) + const vertexBuffer = t.device.createBuffer({ + usage: GPUBufferUsage.VERTEX, + size: t.kVertexAttributeSize * kPositions.length, + mappedAtCreation: true, + }); + t.trackForCleanup(vertexBuffer); + const vertexAttributes = vertexBuffer.getMappedRange(); + for (let i = 0; i < kPositions.length; ++i) { + const baseOffset = t.kVertexAttributeSize * i; + const vertexPosition = new Float32Array(vertexAttributes, baseOffset, 1); + vertexPosition[0] = kPositions[i]; + const vertexColor = new Uint8Array(vertexAttributes, baseOffset + 4, 4); + vertexColor.set(kColors[i]); + } + vertexBuffer.unmap(); + + // Initialize the index buffer with 2 uint16 indices (2, 3). + const indexBuffer = t.makeBufferWithContents(new Uint16Array([2, 3]), GPUBufferUsage.INDEX); + + const renderPipeline = t.GetRenderPipelineForTest(t.kVertexAttributeSize); + + const kPointsCount = 4; + const outputTexture = t.device.createTexture({ + format: 'rgba8unorm', + size: [kPointsCount, 1, 1], + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const encoder = t.device.createCommandEncoder(); + const renderPass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: outputTexture.createView(), + loadValue: [0, 0, 0, 1], + storeOp: 'store', + }, + ], + }); + + // The first draw call is an indexed one (the third and fourth color are involved) + renderPass.setVertexBuffer(0, vertexBuffer); + renderPass.setIndexBuffer(indexBuffer, 'uint16'); + renderPass.setPipeline(renderPipeline); + renderPass.drawIndexed(2); + + // The second draw call is a non-indexed one (the first and second color are involved) + renderPass.draw(2); + + renderPass.endPass(); + + t.queue.submit([encoder.finish()]); + + for (let i = 0; i < kPointsCount; ++i) { + t.expectSinglePixelIn2DTexture( + outputTexture, + 'rgba8unorm', + { x: i, y: 0 }, + { exp: kColors[i] } + ); + } + }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/compute/basic.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/compute/basic.spec.ts index c98db5bbf70..75dd678ba5a 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/compute/basic.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/compute/basic.spec.ts @@ -29,14 +29,14 @@ g.test('memcpy').fn(async t => { compute: { module: t.device.createShaderModule({ code: ` - [[block]] struct Data { + struct Data { value : u32; }; - [[group(0), binding(0)]] var<storage, read> src : Data; - [[group(0), binding(1)]] var<storage, read_write> dst : Data; + @group(0) @binding(0) var<storage, read> src : Data; + @group(0) @binding(1) var<storage, read_write> dst : Data; - [[stage(compute), workgroup_size(1)]] fn main() { + @stage(compute) @workgroup_size(1) fn main() { dst.value = src.value; return; } @@ -107,15 +107,15 @@ g.test('large_dispatch') compute: { module: t.device.createShaderModule({ code: ` - [[block]] struct OutputBuffer { + struct OutputBuffer { value : array<u32>; }; - [[group(0), binding(0)]] var<storage, read_write> dst : OutputBuffer; + @group(0) @binding(0) var<storage, read_write> dst : OutputBuffer; - [[stage(compute), workgroup_size(${wgSizes[0]}, ${wgSizes[1]}, ${wgSizes[2]})]] + @stage(compute) @workgroup_size(${wgSizes[0]}, ${wgSizes[1]}, ${wgSizes[2]}) fn main( - [[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32> + @builtin(global_invocation_id) GlobalInvocationID : vec3<u32> ) { var xExtent : u32 = ${dims[0]}u * ${wgSizes[0]}u; var yExtent : u32 = ${dims[1]}u * ${wgSizes[1]}u; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/device/lost.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/device/lost.spec.ts index fa04d5a7889..836e44307e9 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/device/lost.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/device/lost.spec.ts @@ -4,22 +4,85 @@ Tests for GPUDevice.lost. import { Fixture } from '../../../../common/framework/fixture.js'; import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { attemptGarbageCollection } from '../../../../common/util/collect_garbage.js'; +import { getGPU } from '../../../../common/util/navigator_gpu.js'; +import { assert, raceWithRejectOnTimeout } from '../../../../common/util/util.js'; -export const g = makeTestGroup(Fixture); +class DeviceLostTests extends Fixture { + // Default timeout for waiting for device lost is 2 seconds. + readonly kDeviceLostTimeoutMS = 2000; + + getDeviceLostWithTimeout(lost: Promise<GPUDeviceLostInfo>): Promise<GPUDeviceLostInfo> { + return raceWithRejectOnTimeout(lost, this.kDeviceLostTimeoutMS, 'device was not lost'); + } + + expectDeviceDestroyed(device: GPUDevice): void { + this.eventualAsyncExpectation(async niceStack => { + try { + const lost = await this.getDeviceLostWithTimeout(device.lost); + this.expect(lost.reason === 'destroyed', 'device was lost from destroy'); + } catch (ex) { + niceStack.message = 'device was not lost'; + this.rec.expectationFailed(niceStack); + } + }); + } +} + +export const g = makeTestGroup(DeviceLostTests); g.test('not_lost_on_gc') .desc( `'lost' is never resolved by GPUDevice being garbage collected (with attemptGarbageCollection).` ) - .unimplemented(); + .fn(async t => { + // Wraps a lost promise object creation in a function scope so that the device has the best + // chance of being gone and ready for GC before trying to resolve the lost promise. + const { lost } = await (async () => { + const adapter = await getGPU().requestAdapter(); + assert(adapter !== null); + const lost = (await adapter.requestDevice()).lost; + return { lost }; + })(); + t.shouldReject('Error', t.getDeviceLostWithTimeout(lost), 'device was unexpectedly lost'); + + await attemptGarbageCollection(); + }); g.test('lost_on_destroy') .desc(`'lost' is resolved, with reason='destroyed', on GPUDevice.destroy().`) - .unimplemented(); + .fn(async t => { + const adapter = await getGPU().requestAdapter(); + assert(adapter !== null); + const device: GPUDevice = await adapter.requestDevice(); + t.expectDeviceDestroyed(device); + device.destroy(); + }); g.test('same_object') - .desc( - `'lost' provides a [new? same?] Promise object with a [new? same?] GPUDeviceLostInfo object -each time it's accessed. (Not sure what the semantic is supposed to be, need to investigate.)` - ) - .unimplemented(); + .desc(`'lost' provides the same Promise and GPUDeviceLostInfo objects each time it's accessed.`) + .fn(async t => { + const adapter = await getGPU().requestAdapter(); + assert(adapter !== null); + const device: GPUDevice = await adapter.requestDevice(); + + // The promises should be the same promise object. + const lostPromise1 = device.lost; + const lostPromise2 = device.lost; + t.expect(lostPromise1 === lostPromise2); + + // Promise object should still be the same after destroy. + device.destroy(); + const lostPromise3 = device.lost; + t.expect(lostPromise1 === lostPromise3); + + // The results should also be the same result object. + const lost1 = await t.getDeviceLostWithTimeout(lostPromise1); + const lost2 = await t.getDeviceLostWithTimeout(lostPromise2); + const lost3 = await t.getDeviceLostWithTimeout(lostPromise3); + // Promise object should still be the same after we've been notified about device loss. + const lostPromise4 = device.lost; + t.expect(lostPromise1 === lostPromise4); + const lost4 = await t.getDeviceLostWithTimeout(lostPromise4); + t.expect(lost1 === lost2 && lost2 === lost3 && lost3 === lost4); + }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/labels.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/labels.spec.ts index ea0cac9b771..045e40711c5 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/labels.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/labels.spec.ts @@ -1,10 +1,12 @@ export const description = ` -For every create function, the descriptor.label is carried over to the object.label. - -TODO: implement +Tests for object labels. `; import { makeTestGroup } from '../../../common/framework/test_group.js'; import { GPUTest } from '../../gpu_test.js'; export const g = makeTestGroup(GPUTest); + +g.test('object_has_descriptor_label') + .desc(`For every create function, the descriptor.label is carried over to the object.label.`) + .unimplemented(); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/buffer/buffer_sync_test.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/buffer/buffer_sync_test.ts index b62a23b76cc..1892cc60153 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/buffer/buffer_sync_test.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/buffer/buffer_sync_test.ts @@ -53,12 +53,12 @@ export class BufferSyncTest extends GPUTest { // Create a compute pipeline and write given data into storage buffer. createStorageWriteComputePipeline(value: number): GPUComputePipeline { const wgslCompute = ` - [[block]] struct Data { + struct Data { a : i32; }; - [[group(0), binding(0)]] var<storage, read_write> data : Data; - [[stage(compute), workgroup_size(1)]] fn main() { + @group(0) @binding(0) var<storage, read_write> data : Data; + @stage(compute) @workgroup_size(1) fn main() { data.a = ${value}; return; } @@ -78,18 +78,18 @@ export class BufferSyncTest extends GPUTest { createStorageWriteRenderPipeline(value: number): GPURenderPipeline { const wgslShaders = { vertex: ` - [[stage(vertex)]] fn vert_main() -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn vert_main() -> @builtin(position) vec4<f32> { return vec4<f32>(0.5, 0.5, 0.0, 1.0); } `, fragment: ` - [[block]] struct Data { + struct Data { a : i32; }; - [[group(0), binding(0)]] var<storage, read_write> data : Data; - [[stage(fragment)]] fn frag_main() -> [[location(0)]] vec4<f32> { + @group(0) @binding(0) var<storage, read_write> data : Data; + @stage(fragment) fn frag_main() -> @location(0) vec4<f32> { data.a = ${value}; return vec4<f32>(1.0, 0.0, 0.0, 1.0); } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/texture/rw_and_wr.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/texture/rw_and_wr.spec.ts deleted file mode 100644 index 8e7749469d4..00000000000 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/texture/rw_and_wr.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const description = ` -Memory Synchronization Tests for Texture: read before write and read after write. - -TODO -`; - -import { makeTestGroup } from '../../../../../common/framework/test_group.js'; -import { GPUTest } from '../../../../gpu_test.js'; - -export const g = makeTestGroup(GPUTest); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/texture/same_subresource.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/texture/same_subresource.spec.ts new file mode 100644 index 00000000000..967163305a5 --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/texture/same_subresource.spec.ts @@ -0,0 +1,99 @@ +export const description = ` +Memory Synchronization Tests for Texture: read before write, read after write, and write after write to the same subresource. + +- TODO: Test synchronization between multiple queues. +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../gpu_test.js'; + +import { + kOperationBoundaries, + kBoundaryInfo, + kAllReadOps, + kAllWriteOps, + checkOpsValidForContext, +} from './texture_sync_test.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('rw') + .desc( + ` + Perform a 'read' operations on a texture subresource, followed by a 'write' operation. + Operations are separated by a 'boundary' (pass, encoder, queue-op, etc.). + Test that the results are synchronized. + The read should not see the contents written by the subsequent write.` + ) + .params(u => + u + .combine('boundary', kOperationBoundaries) + .expand('_context', p => kBoundaryInfo[p.boundary].contexts) + .expandWithParams(function* ({ _context }) { + for (const read of kAllReadOps) { + for (const write of kAllWriteOps) { + if (checkOpsValidForContext([read, write], _context)) { + yield { + read: { op: read, in: _context[0] }, + write: { op: write, in: _context[1] }, + }; + } + } + } + }) + ) + .unimplemented(); + +g.test('wr') + .desc( + ` + Perform a 'write' operation on a texture subresource, followed by a 'read' operation. + Operations are separated by a 'boundary' (pass, encoder, queue-op, etc.). + Test that the results are synchronized. + The read should see exactly the contents written by the previous write.` + ) + .params(u => + u + .combine('boundary', kOperationBoundaries) + .expand('_context', p => kBoundaryInfo[p.boundary].contexts) + .expandWithParams(function* ({ _context }) { + for (const read of kAllReadOps) { + for (const write of kAllWriteOps) { + if (checkOpsValidForContext([write, read], _context)) { + yield { + write: { op: write, in: _context[0] }, + read: { op: read, in: _context[1] }, + }; + } + } + } + }) + ) + .unimplemented(); + +g.test('ww') + .desc( + ` + Perform a 'first' write operation on a texture subresource, followed by a 'second' write operation. + Operations are separated by a 'boundary' (pass, encoder, queue-op, etc.). + Test that the results are synchronized. + The second write should overwrite the contents of the first.` + ) + .params(u => + u + .combine('boundary', kOperationBoundaries) + .expand('_context', p => kBoundaryInfo[p.boundary].contexts) + .expandWithParams(function* ({ _context }) { + for (const first of kAllWriteOps) { + for (const second of kAllWriteOps) { + if (checkOpsValidForContext([first, second], _context)) { + yield { + first: { op: first, in: _context[0] }, + second: { op: second, in: _context[1] }, + }; + } + } + } + }) + ) + .unimplemented(); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/texture/texture_sync_test.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/texture/texture_sync_test.ts new file mode 100644 index 00000000000..ec8f4bf41f7 --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/texture/texture_sync_test.ts @@ -0,0 +1,145 @@ +/** + * Boundary between the first operation, and the second operation. + */ +export const kOperationBoundaries = [ + 'queue-op', // Operations are performed in different queue operations (submit, writeTexture). + 'command-buffer', // Operations are in different command buffers. + 'pass', // Operations are in different passes. + 'render-bundle', // Operations are in different render bundles. + 'dispatch', // Operations are in different dispatches. + 'draw', // Operations are in different draws. +] as const; +export type OperationBoundary = typeof kOperationBoundaries[number]; + +/** + * Context a particular operation is permitted in. + */ +export const kOperationContexts = [ + 'queue', // Operation occurs on the GPUQueue object + 'command-encoder', // Operation may be encoded in a GPUCommandEncoder. + 'compute-pass-encoder', // Operation may be encoded in a GPUComputePassEncoder. + 'render-pass-encoder', // Operation may be encoded in a GPURenderPassEncoder. + 'render-bundle-encoder', // Operation may be encoded in a GPURenderBundleEncoder. +] as const; +export type OperationContext = typeof kOperationContexts[number]; + +interface BoundaryInfo { + readonly contexts: [OperationContext, OperationContext][]; + // Add fields as needed +} + +function combineContexts( + as: readonly OperationContext[], + bs: readonly OperationContext[] +): [OperationContext, OperationContext][] { + const result: [OperationContext, OperationContext][] = []; + for (const a of as) { + for (const b of bs) { + result.push([a, b]); + } + } + return result; +} + +const queueContexts = combineContexts(kOperationContexts, kOperationContexts); +const commandBufferContexts = combineContexts( + kOperationContexts.filter(c => c !== 'queue'), + kOperationContexts.filter(c => c !== 'queue') +); + +/** + * Mapping of OperationBoundary => to a set of OperationContext pairs. + * The boundary is capable of separating operations in those two contexts. + */ +export const kBoundaryInfo: { + readonly [k in OperationBoundary]: BoundaryInfo; +} = /* prettier-ignore */ { + 'queue-op': { + contexts: queueContexts, + }, + 'command-buffer': { + contexts: commandBufferContexts, + }, + 'pass': { + contexts: [ + ['compute-pass-encoder', 'compute-pass-encoder'], + ['compute-pass-encoder', 'render-pass-encoder'], + ['render-pass-encoder', 'compute-pass-encoder'], + ['render-pass-encoder', 'render-pass-encoder'], + ['render-bundle-encoder', 'render-pass-encoder'], + ['render-pass-encoder', 'render-bundle-encoder'], + ['render-bundle-encoder', 'render-bundle-encoder'], + ], + }, + 'render-bundle': { + contexts: [ + ['render-bundle-encoder', 'render-pass-encoder'], + ['render-pass-encoder', 'render-bundle-encoder'], + ['render-bundle-encoder', 'render-bundle-encoder'], + ], + }, + 'dispatch': { + contexts: [ + ['compute-pass-encoder', 'compute-pass-encoder'], + ], + }, + 'draw': { + contexts: [ + ['render-pass-encoder', 'render-pass-encoder'], + ['render-bundle-encoder', 'render-pass-encoder'], + ], + }, +}; + +export const kAllWriteOps = [ + 'write-texture', + 'b2t-copy', + 't2t-copy', + 'storage', + 'attachment-store', + 'attachment-resolve', +] as const; +export type WriteOp = typeof kAllWriteOps[number]; + +export const kAllReadOps = [ + 't2b-copy', + 't2t-copy', + 'attachment-load', + 'storage', + 'sample', +] as const; +export type ReadOp = typeof kAllReadOps[number]; + +export type Op = ReadOp | WriteOp; + +interface OpInfo { + readonly contexts: OperationContext[]; + // Add fields as needed +} + +/** + * Mapping of Op to the OperationContext(s) it is valid in + */ +const kOpInfo: { + readonly [k in Op]: OpInfo; +} = /* prettier-ignore */ { + 'write-texture': { contexts: [ 'queue' ] }, + 'b2t-copy': { contexts: [ 'command-encoder' ] }, + 't2t-copy': { contexts: [ 'command-encoder' ] }, + 't2b-copy': { contexts: [ 'command-encoder' ] }, + 'storage': { contexts: [ 'compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder' ] }, + 'sample': { contexts: [ 'compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder' ] }, + 'attachment-store': { contexts: [ 'render-pass-encoder' ] }, + 'attachment-resolve': { contexts: [ 'render-pass-encoder' ] }, + 'attachment-load': { contexts: [ 'render-pass-encoder' ] }, +}; + +export function checkOpsValidForContext( + ops: [Op, Op], + context: [OperationContext, OperationContext] +) { + return ( + kOpInfo[ops[0]].contexts.indexOf(context[0]) !== -1 && + kOpInfo[ops[1]].contexts.indexOf(context[1]) !== -1 + ); +} diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/texture/ww.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/texture/ww.spec.ts deleted file mode 100644 index c7ff12b8ae9..00000000000 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/memory_sync/texture/ww.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const description = ` -Memory Synchronization Tests for Texture: write after write. - -TODO -`; - -import { makeTestGroup } from '../../../../../common/framework/test_group.js'; -import { GPUTest } from '../../../../gpu_test.js'; - -export const g = makeTestGroup(GPUTest); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/pipeline/default_layout.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/pipeline/default_layout.spec.ts new file mode 100644 index 00000000000..f303b2737f5 --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/pipeline/default_layout.spec.ts @@ -0,0 +1,27 @@ +export const description = ` +Tests for default pipeline layouts. +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../gpu_test.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('getBindGroupLayout_js_object') + .desc( + `Test that getBindGroupLayout returns [TODO: the same or a different, needs spec] object +each time.` + ) + .unimplemented(); + +g.test('incompatible_with_explicit') + .desc(`Test that default bind group layouts are never compatible with explicitly created ones.`) + .unimplemented(); + +g.test('layout') + .desc( + `Test that bind group layouts of the default pipeline layout are correct by passing various +shaders and then checking their computed bind group layouts are compatible with particular bind +groups.` + ) + .unimplemented(); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/clear_value.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/clear_value.spec.ts new file mode 100644 index 00000000000..23304e54775 --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/clear_value.spec.ts @@ -0,0 +1,34 @@ +export const description = ` +Tests for render pass clear values. +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../gpu_test.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('stored') + .desc(`Test render pass clear values are stored at the end of an empty pass.`) + .unimplemented(); + +g.test('loaded') + .desc( + `Test render pass clear values are visible during the pass by doing some trivial blending +with the attachment (e.g. add [0,0,0,0] to the color and verify the stored result).` + ) + .unimplemented(); + +g.test('srgb') + .desc( + `Test that clear values on '-srgb' type attachments are interpreted as unencoded (linear), +not decoded from srgb to linear.` + ) + .unimplemented(); + +g.test('layout') + .desc( + `Test that bind group layouts of the default pipeline layout are correct by passing various +shaders and then checking their computed bind group layouts are compatible with particular bind +groups.` + ) + .unimplemented(); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/resolve.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/resolve.spec.ts index b0bbe146c5a..8898f56fed3 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/resolve.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/resolve.spec.ts @@ -52,9 +52,9 @@ g.test('render_pass_resolve') vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main( - [[builtin(vertex_index)]] VertexIndex : u32 - ) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main( + @builtin(vertex_index) VertexIndex : u32 + ) -> @builtin(position) vec4<f32> { var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>( vec2<f32>(-1.0, -1.0), vec2<f32>(-1.0, 1.0), @@ -68,13 +68,13 @@ g.test('render_pass_resolve') module: t.device.createShaderModule({ code: ` struct Output { - [[location(0)]] fragColor0 : vec4<f32>; - [[location(1)]] fragColor1 : vec4<f32>; - [[location(2)]] fragColor2 : vec4<f32>; - [[location(3)]] fragColor3 : vec4<f32>; + @location(0) fragColor0 : vec4<f32>; + @location(1) fragColor1 : vec4<f32>; + @location(2) fragColor2 : vec4<f32>; + @location(3) fragColor3 : vec4<f32>; }; - [[stage(fragment)]] fn main() -> Output { + @stage(fragment) fn main() -> Output { return Output( vec4<f32>(1.0, 1.0, 1.0, 1.0), vec4<f32>(1.0, 1.0, 1.0, 1.0), diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/storeOp.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/storeOp.spec.ts index 298a2b39ca7..7e9edba8a76 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/storeOp.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/storeOp.spec.ts @@ -269,12 +269,12 @@ formats, mip levels and array layers. - x= all (sized) depth stencil formats, all store ops, multiple mip levels, multiple array layers -TODO: Also test unsized depth/stencil formats +TODO: Also test unsized depth/stencil formats [1] ` ) .params(u => u - .combine('depthStencilFormat', kSizedDepthStencilFormats) // TODO + .combine('depthStencilFormat', kSizedDepthStencilFormats) // [1] .combine('storeOperation', kStoreOps) .beginSubcases() .combine('mipLevel', kMipLevel) diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/storeop2.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/storeop2.spec.ts index 8bc89bb0124..2f4f60c2f6f 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/storeop2.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pass/storeop2.spec.ts @@ -1,7 +1,5 @@ export const description = ` renderPass store op test that drawn quad is either stored or cleared based on storeop - -TODO: is this duplicated with api,operation,render_pass,storeOp? `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; @@ -10,6 +8,12 @@ import { GPUTest } from '../../../gpu_test.js'; export const g = makeTestGroup(GPUTest); g.test('storeOp_controls_whether_1x1_drawn_quad_is_stored') + .desc( + ` +TODO: is this duplicated with api,operation,render_pass,storeOp? +TODO: needs review and rename +` + ) .paramsSimple([ { storeOp: 'store', _expected: 1 }, // { storeOp: 'discard', _expected: 0 }, @@ -26,9 +30,9 @@ g.test('storeOp_controls_whether_1x1_drawn_quad_is_stored') vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main( - [[builtin(vertex_index)]] VertexIndex : u32 - ) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main( + @builtin(vertex_index) VertexIndex : u32 + ) -> @builtin(position) vec4<f32> { var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>( vec2<f32>( 1.0, -1.0), vec2<f32>( 1.0, 1.0), @@ -42,7 +46,7 @@ g.test('storeOp_controls_whether_1x1_drawn_quad_is_stored') fragment: { module: t.device.createShaderModule({ code: ` - [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 0.0, 1.0); } `, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pipeline/culling_tests.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pipeline/culling_tests.spec.ts index 404b6120843..faf1f3e7761 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pipeline/culling_tests.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pipeline/culling_tests.spec.ts @@ -40,19 +40,25 @@ function faceColor(face: 'cw' | 'ccw', frontFace: GPUFrontFace, cullMode: GPUCul export const g = makeTestGroup(GPUTest); g.test('culling') - .params(u => - u - .combine('frontFace', ['ccw', 'cw'] as const) - .combine('cullMode', ['none', 'front', 'back'] as const) - .beginSubcases() - .combine('depthStencilFormat', [ - null, - 'depth24plus', - 'depth32float', - 'depth24plus-stencil8', - ] as const) - // TODO: test triangle-strip as well - .combine('primitiveTopology', ['triangle-list'] as const) + .desc( + ` +TODO: test triangle-strip as well [1] +TODO: check the contents of the depth and stencil outputs [2] +` + ) + .params( + u => + u + .combine('frontFace', ['ccw', 'cw'] as const) + .combine('cullMode', ['none', 'front', 'back'] as const) + .beginSubcases() + .combine('depthStencilFormat', [ + null, + 'depth24plus', + 'depth32float', + 'depth24plus-stencil8', + ] as const) + .combine('primitiveTopology', ['triangle-list'] as const) // [1] ) .fn(t => { const size = 4; @@ -100,9 +106,9 @@ g.test('culling') vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main( - [[builtin(vertex_index)]] VertexIndex : u32 - ) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main( + @builtin(vertex_index) VertexIndex : u32 + ) -> @builtin(position) vec4<f32> { var pos : array<vec2<f32>, 6> = array<vec2<f32>, 6>( vec2<f32>(-1.0, 1.0), vec2<f32>(-1.0, 0.0), @@ -118,9 +124,9 @@ g.test('culling') fragment: { module: t.device.createShaderModule({ code: ` - [[stage(fragment)]] fn main( - [[builtin(front_facing)]] FrontFacing : bool - ) -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main( + @builtin(front_facing) FrontFacing : bool + ) -> @location(0) vec4<f32> { var color : vec4<f32>; if (FrontFacing) { color = vec4<f32>(0.0, 1.0, 0.0, 1.0); @@ -165,5 +171,5 @@ g.test('culling') { x: size - 1, y: size - 1 }, { exp: kCWTriangleBottomRightColor } ); - // TODO: check the contents of the depth and stencil outputs + // [2]: check the contents of the depth and stencil outputs }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pipeline/pipeline_output_targets.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pipeline/pipeline_output_targets.spec.ts index 74b6736f933..c4dd23b1017 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pipeline/pipeline_output_targets.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pipeline/pipeline_output_targets.spec.ts @@ -27,6 +27,7 @@ class F extends GPUTest { suffix = 'u'; break; case 'float': + case 'unfilterable-float': fragColorType = 'f32'; suffix = ''; fractionDigits = 4; @@ -61,7 +62,7 @@ class F extends GPUTest { } return ` - [[stage(fragment)]] fn main() -> [[location(0)]] ${outputType} { + @stage(fragment) fn main() -> @location(0) ${outputType} { return ${result}; }`; } @@ -99,9 +100,9 @@ g.test('color,component_count') vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main( - [[builtin(vertex_index)]] VertexIndex : u32 - ) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main( + @builtin(vertex_index) VertexIndex : u32 + ) -> @builtin(position) vec4<f32> { var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>( vec2<f32>(-1.0, -3.0), vec2<f32>(3.0, 1.0), @@ -309,9 +310,9 @@ The attachment has a load value of [1, 0, 0, 1] vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main( - [[builtin(vertex_index)]] VertexIndex : u32 - ) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main( + @builtin(vertex_index) VertexIndex : u32 + ) -> @builtin(position) vec4<f32> { var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>( vec2<f32>(-1.0, -3.0), vec2<f32>(3.0, 1.0), diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts index 450614fc3bc..399778131bf 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts @@ -332,9 +332,9 @@ class PrimitiveTopologyTest extends GPUTest { vertex: { module: this.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main( - [[location(0)]] pos : vec4<f32> - ) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main( + @location(0) pos : vec4<f32> + ) -> @builtin(position) vec4<f32> { return pos; }`, }), @@ -355,7 +355,7 @@ class PrimitiveTopologyTest extends GPUTest { fragment: { module: this.device.createShaderModule({ code: ` - [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.0, 1.0, 0.0, 1.0); }`, }), diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/basic.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/basic.spec.ts index 6d65e886151..6f6bf37a637 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/basic.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/basic.spec.ts @@ -60,9 +60,9 @@ g.test('fullscreen_quad').fn(async t => { vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main( - [[builtin(vertex_index)]] VertexIndex : u32 - ) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main( + @builtin(vertex_index) VertexIndex : u32 + ) -> @builtin(position) vec4<f32> { var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>( vec2<f32>(-1.0, -3.0), vec2<f32>(3.0, 1.0), @@ -76,7 +76,7 @@ g.test('fullscreen_quad').fn(async t => { fragment: { module: t.device.createShaderModule({ code: ` - [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.0, 1.0, 0.0, 1.0); } `, @@ -216,7 +216,7 @@ g.test('large_draw') vertex: { module: t.device.createShaderModule({ code: ` - [[block]] struct Params { + struct Params { numVertices: u32; numInstances: u32; }; @@ -226,12 +226,12 @@ g.test('large_draw') return select(highOrMid, -2.0 / 3.0, index == 0u); } - [[group(0), binding(0)]] var<uniform> params: Params; + @group(0) @binding(0) var<uniform> params: Params; - [[stage(vertex)]] fn main( - [[builtin(vertex_index)]] v: u32, - [[builtin(instance_index)]] i: u32) - -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main( + @builtin(vertex_index) v: u32, + @builtin(instance_index) i: u32) + -> @builtin(position) vec4<f32> { let x = selectValue(v, params.numVertices); let y = -selectValue(i, params.numInstances); return vec4<f32>(x, y, 0.0, 1.0); @@ -243,7 +243,7 @@ g.test('large_draw') fragment: { module: t.device.createShaderModule({ code: ` - [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 1.0, 0.0, 1.0); } `, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/blending.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/blending.spec.ts index 22c34626d7e..050beff2457 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/blending.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/blending.spec.ts @@ -9,35 +9,12 @@ TODO: import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { assert, unreachable } from '../../../../common/util/util.js'; +import { kBlendFactors, kBlendOperations } from '../../../capability_info.js'; import { GPUTest } from '../../../gpu_test.js'; import { float32ToFloat16Bits } from '../../../util/conversion.js'; export const g = makeTestGroup(GPUTest); -const kBlendFactors: GPUBlendFactor[] = [ - 'zero', - 'one', - 'src', - 'one-minus-src', - 'src-alpha', - 'one-minus-src-alpha', - 'dst', - 'one-minus-dst', - 'dst-alpha', - 'one-minus-dst-alpha', - 'src-alpha-saturated', - 'constant', - 'one-minus-constant', -]; - -const kBlendOperations: GPUBlendOperation[] = [ - 'add', // - 'subtract', - 'reverse-subtract', - 'min', - 'max', -]; - function mapColor( col: GPUColorDict, f: (v: number, k: keyof GPUColorDict) => number @@ -133,6 +110,12 @@ g.test('GPUBlendComponent') .combine('srcFactor', kBlendFactors) .combine('dstFactor', kBlendFactors) .combine('operation', kBlendOperations) + .filter(t => { + if (t.operation === 'min' || t.operation === 'max') { + return t.srcFactor === 'one' && t.dstFactor === 'one'; + } + return true; + }) .beginSubcases() .combine('srcColor', [{ r: 0.11, g: 0.61, b: 0.81, a: 0.44 }]) .combine('dstColor', [ @@ -196,12 +179,12 @@ g.test('GPUBlendComponent') ], module: t.device.createShaderModule({ code: ` -[[block]] struct Uniform { +struct Uniform { color: vec4<f32>; }; -[[group(0), binding(0)]] var<uniform> u : Uniform; +@group(0) @binding(0) var<uniform> u : Uniform; -[[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { +@stage(fragment) fn main() -> @location(0) vec4<f32> { return u.color; } `, @@ -211,7 +194,7 @@ g.test('GPUBlendComponent') vertex: { module: t.device.createShaderModule({ code: ` -[[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { +@stage(vertex) fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); } `, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/depth.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/depth.spec.ts index 131a2876b3e..44d6c72ae16 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/depth.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/depth.spec.ts @@ -83,8 +83,8 @@ g.test('depth_compare_func') vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main( - [[builtin(vertex_index)]] VertexIndex : u32) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main( + @builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> { return vec4<f32>(0.5, 0.5, 0.5, 1.0); } `, @@ -94,7 +94,7 @@ g.test('depth_compare_func') fragment: { module: t.device.createShaderModule({ code: ` - [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 1.0, 1.0, 1.0); } `, @@ -171,13 +171,13 @@ g.test('reverse_depth') module: t.device.createShaderModule({ code: ` struct Output { - [[builtin(position)]] Position : vec4<f32>; - [[location(0)]] color : vec4<f32>; + @builtin(position) Position : vec4<f32>; + @location(0) color : vec4<f32>; }; - [[stage(vertex)]] fn main( - [[builtin(vertex_index)]] VertexIndex : u32, - [[builtin(instance_index)]] InstanceIndex : u32) -> Output { + @stage(vertex) fn main( + @builtin(vertex_index) VertexIndex : u32, + @builtin(instance_index) InstanceIndex : u32) -> Output { // TODO: remove workaround for Tint unary array access broke var zv : array<vec2<f32>, 4> = array<vec2<f32>, 4>( vec2<f32>(0.2, 0.2), @@ -204,9 +204,9 @@ g.test('reverse_depth') fragment: { module: t.device.createShaderModule({ code: ` - [[stage(fragment)]] fn main( - [[location(0)]] color : vec4<f32> - ) -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main( + @location(0) color : vec4<f32> + ) -> @location(0) vec4<f32> { return color; } `, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts index ba944cbd01f..bec0d41ef36 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts @@ -89,36 +89,36 @@ have unexpected values then get drawn to the color buffer, which is later checke //////// "Test" entry points struct VFTest { - [[builtin(position)]] pos: vec4<f32>; - [[location(0)]] vertexIndex: u32; + @builtin(position) pos: vec4<f32>; + @location(0) @interpolate(flat) vertexIndex: u32; }; - [[stage(vertex)]] - fn vtest([[builtin(vertex_index)]] idx: u32) -> VFTest { + @stage(vertex) + fn vtest(@builtin(vertex_index) idx: u32) -> VFTest { var vf: VFTest; vf.pos = vec4<f32>(vertexX(idx), 0.0, vertexZ(idx), 1.0); vf.vertexIndex = idx; return vf; } - [[block]] struct Output { + struct Output { // Each fragment (that didn't get clipped) writes into one element of this output. // (Anything that doesn't get written is already zero.) fragInputZDiff: array<f32, ${kNumTestPoints}>; }; - [[group(0), binding(0)]] var <storage, read_write> output: Output; + @group(0) @binding(0) var <storage, read_write> output: Output; fn checkZ(vf: VFTest) { output.fragInputZDiff[vf.vertexIndex] = vf.pos.z - expectedFragPosZ(vf.vertexIndex); } - [[stage(fragment)]] - fn ftest_WriteDepth(vf: VFTest) -> [[builtin(frag_depth)]] f32 { + @stage(fragment) + fn ftest_WriteDepth(vf: VFTest) -> @builtin(frag_depth) f32 { checkZ(vf); return kDepths[vf.vertexIndex % ${kNumDepthValues}u]; } - [[stage(fragment)]] + @stage(fragment) fn ftest_NoWriteDepth(vf: VFTest) { checkZ(vf); } @@ -126,12 +126,12 @@ have unexpected values then get drawn to the color buffer, which is later checke //////// "Check" entry points struct VFCheck { - [[builtin(position)]] pos: vec4<f32>; - [[location(0)]] vertexIndex: u32; + @builtin(position) pos: vec4<f32>; + @location(0) @interpolate(flat) vertexIndex: u32; }; - [[stage(vertex)]] - fn vcheck([[builtin(vertex_index)]] idx: u32) -> VFCheck { + @stage(vertex) + fn vcheck(@builtin(vertex_index) idx: u32) -> VFCheck { var vf: VFCheck; // Depth=0.5 because we want to render every point, not get clipped. vf.pos = vec4<f32>(vertexX(idx), 0.0, 0.5, 1.0); @@ -140,11 +140,11 @@ have unexpected values then get drawn to the color buffer, which is later checke } struct FCheck { - [[builtin(frag_depth)]] depth: f32; - [[location(0)]] color: f32; + @builtin(frag_depth) depth: f32; + @location(0) color: f32; }; - [[stage(fragment)]] + @stage(fragment) fn fcheck(vf: VFCheck) -> FCheck { let vertZ = vertexZ(vf.vertexIndex); let outOfRange = vertZ < 0.0 || vertZ > 1.0; @@ -193,8 +193,8 @@ have unexpected values then get drawn to the color buffer, which is later checke primitive: { topology: 'point-list' }, depthStencil: { format, - // TODO: This check is probably very susceptible to floating point error. - // Replace it with two checks (less + greater) with an epsilon applied in the check shader? + // NOTE: This check is probably very susceptible to floating point error. If it fails, maybe + // replace it with two checks (less + greater) with an epsilon applied in the check shader? depthCompare: 'not-equal', // Expect every depth value to be exactly equal. depthWriteEnabled: true, // If the check failed, overwrite with the expected result. }, @@ -348,6 +348,7 @@ to be empty.` .params(u => u // .combine('format', kDepthStencilFormats) + .filter(p => kTextureFormatInfo[p.format].depth) .combine('clampDepth', [false, true]) .combine('multisampled', [false, true]) ) @@ -376,12 +377,12 @@ to be empty.` } struct VF { - [[builtin(position)]] pos: vec4<f32>; - [[location(0)]] vertexIndex: u32; + @builtin(position) pos: vec4<f32>; + @location(0) @interpolate(flat) vertexIndex: u32; }; - [[stage(vertex)]] - fn vmain([[builtin(vertex_index)]] idx: u32) -> VF { + @stage(vertex) + fn vmain(@builtin(vertex_index) idx: u32) -> VF { var vf: VF; // Depth=0.5 because we want to render every point, not get clipped. vf.pos = vec4<f32>(vertexX(idx), 0.0, 0.5, 1.0); @@ -389,18 +390,18 @@ to be empty.` return vf; } - [[stage(fragment)]] - fn finit(vf: VF) -> [[builtin(frag_depth)]] f32 { + @stage(fragment) + fn finit(vf: VF) -> @builtin(frag_depth) f32 { // Expected values of the ftest pipeline. return clamp(kDepths[vf.vertexIndex], vpMin, vpMax); } struct FTest { - [[builtin(frag_depth)]] depth: f32; - [[location(0)]] color: f32; + @builtin(frag_depth) depth: f32; + @location(0) color: f32; }; - [[stage(fragment)]] + @stage(fragment) fn ftest(vf: VF) -> FTest { var f: FTest; f.depth = kDepths[vf.vertexIndex]; // Should get clamped to the viewport. diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/draw.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/draw.spec.ts index c2684f58ed7..446820ad9ce 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/draw.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/draw.spec.ts @@ -3,13 +3,6 @@ Tests for the general aspects of draw/drawIndexed/drawIndirect/drawIndexedIndire Primitive topology tested in api/operation/render_pipeline/primitive_topology.spec.ts. Index format tested in api/operation/command_buffer/render/state_tracking.spec.ts. - -* arguments - Test that draw arguments are passed correctly. - -TODO: -* default_arguments - Test defaults to draw / drawIndexed. - - arg= {instance_count, first, first_instance, base_vertex} - - mode= {draw, drawIndexed} `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; @@ -59,8 +52,7 @@ Params: ) .fn(async t => { if (t.params.first_instance > 0 && t.params.indirect) { - // TODO: 'as' cast because types don't have this feature name yet - await t.selectDeviceOrSkipTestCase('indirect-first-instance' as GPUFeatureName); + await t.selectDeviceOrSkipTestCase('indirect-first-instance'); } const renderTargetSize = [72, 36]; @@ -93,13 +85,13 @@ Params: const vertexModule = t.device.createShaderModule({ code: ` struct Inputs { - [[builtin(vertex_index)]] vertex_index : u32; - [[builtin(instance_index)]] instance_id : u32; - [[location(0)]] vertexPosition : vec2<f32>; + @builtin(vertex_index) vertex_index : u32; + @builtin(instance_index) instance_id : u32; + @location(0) vertexPosition : vec2<f32>; }; -[[stage(vertex)]] fn vert_main(input : Inputs - ) -> [[builtin(position)]] vec4<f32> { +@stage(vertex) fn vert_main(input : Inputs + ) -> @builtin(position) vec4<f32> { // 3u is the number of points in a triangle to convert from index // to id. var vertex_id : u32 = input.vertex_index / 3u; @@ -117,13 +109,13 @@ struct Inputs { const fragmentModule = t.device.createShaderModule({ code: ` -[[block]] struct Output { +struct Output { value : u32; }; -[[group(0), binding(0)]] var<storage, read_write> output : Output; +@group(0) @binding(0) var<storage, read_write> output : Output; -[[stage(fragment)]] fn frag_main() -> [[location(0)]] vec4<f32> { +@stage(fragment) fn frag_main() -> @location(0) vec4<f32> { output.value = 1u; return vec4<f32>(0.0, 1.0, 0.0, 1.0); } @@ -327,6 +319,15 @@ struct Inputs { } }); +g.test('default_arguments') + .desc( + `TODO: Test defaults to draw / drawIndexed. Maybe merge with the 'arguments' test. +- arg= {instance_count, first, first_instance, base_vertex} +- mode= {draw, drawIndexed} + ` + ) + .unimplemented(); + g.test('vertex_attributes,basic') .desc( `Test basic fetching of vertex attributes. @@ -496,14 +497,14 @@ g.test('vertex_attributes,basic') // The remaining 3 vertex attributes if (t.params.vertex_attribute_count === 16) { accumulateVariableDeclarationsInVertexShader = ` - [[location(13)]] outAttrib13 : vec4<${wgslFormat}>; + @location(13) @interpolate(flat) outAttrib13 : vec4<${wgslFormat}>; `; accumulateVariableAssignmentsInVertexShader = ` output.outAttrib13 = vec4<${wgslFormat}>(input.attrib12, input.attrib13, input.attrib14, input.attrib15); `; accumulateVariableDeclarationsInFragmentShader = ` - [[location(13)]] attrib13 : vec4<${wgslFormat}>; + @location(13) @interpolate(flat) attrib13 : vec4<${wgslFormat}>; `; accumulateVariableAssignmentsInFragmentShader = ` outBuffer.primitives[input.primitiveId].attrib12 = input.attrib13.x; @@ -518,23 +519,21 @@ g.test('vertex_attributes,basic') module: t.device.createShaderModule({ code: ` struct Inputs { - [[builtin(vertex_index)]] vertexIndex : u32; - [[builtin(instance_index)]] instanceIndex : u32; -${vertexInputShaderLocations - .map(i => ` [[location(${i})]] attrib${i} : ${wgslFormat};`) - .join('\n')} + @builtin(vertex_index) vertexIndex : u32; + @builtin(instance_index) instanceIndex : u32; +${vertexInputShaderLocations.map(i => ` @location(${i}) attrib${i} : ${wgslFormat};`).join('\n')} }; struct Outputs { - [[builtin(position)]] Position : vec4<f32>; + @builtin(position) Position : vec4<f32>; ${interStageScalarShaderLocations - .map(i => ` [[location(${i})]] outAttrib${i} : ${wgslFormat};`) + .map(i => ` @location(${i}) @interpolate(flat) outAttrib${i} : ${wgslFormat};`) .join('\n')} - [[location(${interStageScalarShaderLocations.length})]] primitiveId : u32; + @location(${interStageScalarShaderLocations.length}) @interpolate(flat) primitiveId : u32; ${accumulateVariableDeclarationsInVertexShader} }; -[[stage(vertex)]] fn main(input : Inputs) -> Outputs { +@stage(vertex) fn main(input : Inputs) -> Outputs { var output : Outputs; ${interStageScalarShaderLocations.map(i => ` output.outAttrib${i} = input.attrib${i};`).join('\n')} ${accumulateVariableAssignmentsInVertexShader} @@ -553,21 +552,21 @@ ${accumulateVariableAssignmentsInVertexShader} code: ` struct Inputs { ${interStageScalarShaderLocations - .map(i => ` [[location(${i})]] attrib${i} : ${wgslFormat};`) + .map(i => ` @location(${i}) @interpolate(flat) attrib${i} : ${wgslFormat};`) .join('\n')} - [[location(${interStageScalarShaderLocations.length})]] primitiveId : u32; + @location(${interStageScalarShaderLocations.length}) @interpolate(flat) primitiveId : u32; ${accumulateVariableDeclarationsInFragmentShader} }; struct OutPrimitive { ${vertexInputShaderLocations.map(i => ` attrib${i} : ${wgslFormat};`).join('\n')} }; -[[block]] struct OutBuffer { - primitives : [[stride(${vertexInputShaderLocations.length * 4})]] array<OutPrimitive>; +struct OutBuffer { + primitives : @stride(${vertexInputShaderLocations.length * 4}) array<OutPrimitive>; }; -[[group(0), binding(0)]] var<storage, read_write> outBuffer : OutBuffer; +@group(0) @binding(0) var<storage, read_write> outBuffer : OutBuffer; -[[stage(fragment)]] fn main(input : Inputs) { +@stage(fragment) fn main(input : Inputs) { ${interStageScalarShaderLocations .map(i => ` outBuffer.primitives[input.primitiveId].attrib${i} = input.attrib${i};`) .join('\n')} diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/indirect_draw.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/indirect_draw.spec.ts index 3be519d610e..b95f3602c2b 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/indirect_draw.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/indirect_draw.spec.ts @@ -3,18 +3,133 @@ Tests for the indirect-specific aspects of drawIndirect/drawIndexedIndirect. `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { + kDrawIndirectParametersSize, + kDrawIndexedIndirectParametersSize, +} from '../../../capability_info.js'; import { GPUTest } from '../../../gpu_test.js'; -export const g = makeTestGroup(GPUTest); - const filled = new Uint8Array([0, 255, 0, 255]); const notFilled = new Uint8Array([0, 0, 0, 0]); -const kDrawIndirectParametersSize = 4 * Uint32Array.BYTES_PER_ELEMENT; +const kRenderTargetFormat = 'rgba8unorm'; + +class F extends GPUTest { + MakeIndexBuffer(): GPUBuffer { + return this.makeBufferWithContents( + /* prettier-ignore */ + new Uint32Array([ + 0, 1, 2, // The bottom left triangle + 1, 2, 3, // The top right triangle + ]), + GPUBufferUsage.INDEX + ); + } + + MakeVertexBuffer(isIndexed: boolean): GPUBuffer { + /* prettier-ignore */ + const vextices = isIndexed + ? [ + -1.0, -1.0, + -1.0, 1.0, + 1.0, -1.0, + 1.0, 1.0, + ] + : [ + // The bottom left triangle + -1.0, 1.0, + 1.0, -1.0, + -1.0, -1.0, + + // The top right triangle + -1.0, 1.0, + 1.0, -1.0, + 1.0, 1.0, + ]; + return this.makeBufferWithContents(new Float32Array(vextices), GPUBufferUsage.VERTEX); + } + + MakeIndirectBuffer(isIndexed: boolean, indirectOffset: number): GPUBuffer { + const o = indirectOffset / Uint32Array.BYTES_PER_ELEMENT; + + const parametersSize = isIndexed + ? kDrawIndexedIndirectParametersSize + : kDrawIndirectParametersSize; + const arraySize = o + parametersSize * 2; + + const indirectBuffer = [...Array(arraySize)].map(() => Math.floor(Math.random() * 100)); + + if (isIndexed) { + // draw args that will draw the left bottom triangle (expected call) + indirectBuffer[o] = 3; // indexCount + indirectBuffer[o + 1] = 1; // instanceCount + indirectBuffer[o + 2] = 0; // firstIndex + indirectBuffer[o + 3] = 0; // baseVertex + indirectBuffer[o + 4] = 0; // firstInstance + + // draw args that will draw both triangles + indirectBuffer[o + 5] = 6; // indexCount + indirectBuffer[o + 6] = 1; // instanceCount + indirectBuffer[o + 7] = 0; // firstIndex + indirectBuffer[o + 8] = 0; // baseVertex + indirectBuffer[o + 9] = 0; // firstInstance + + if (o >= parametersSize) { + // draw args that will draw the right top triangle + indirectBuffer[o - 5] = 3; // indexCount + indirectBuffer[o - 4] = 1; // instanceCount + indirectBuffer[o - 3] = 3; // firstIndex + indirectBuffer[o - 2] = 0; // baseVertex + indirectBuffer[o - 1] = 0; // firstInstance + } + + if (o >= parametersSize * 2) { + // draw args that will draw nothing + indirectBuffer[0] = 0; // indexCount + indirectBuffer[1] = 0; // instanceCount + indirectBuffer[2] = 0; // firstIndex + indirectBuffer[3] = 0; // baseVertex + indirectBuffer[4] = 0; // firstInstance + } + } else { + // draw args that will draw the left bottom triangle (expected call) + indirectBuffer[o] = 3; // vertexCount + indirectBuffer[o + 1] = 1; // instanceCount + indirectBuffer[o + 2] = 0; // firstVertex + indirectBuffer[o + 3] = 0; // firstInstance + + // draw args that will draw both triangles + indirectBuffer[o + 4] = 6; // vertexCount + indirectBuffer[o + 5] = 1; // instanceCount + indirectBuffer[o + 6] = 0; // firstVertex + indirectBuffer[o + 7] = 0; // firstInstance + + if (o >= parametersSize) { + // draw args that will draw the right top triangle + indirectBuffer[o - 4] = 3; // vertexCount + indirectBuffer[o - 3] = 1; // instanceCount + indirectBuffer[o - 2] = 3; // firstVertex + indirectBuffer[o - 1] = 0; // firstInstance + } + + if (o >= parametersSize * 2) { + // draw args that will draw nothing + indirectBuffer[0] = 0; // vertexCount + indirectBuffer[1] = 0; // instanceCount + indirectBuffer[2] = 0; // firstVertex + indirectBuffer[3] = 0; // firstInstance + } + } + + return this.makeBufferWithContents(new Uint32Array(indirectBuffer), GPUBufferUsage.INDIRECT); + } +} -g.test('basics,drawIndirect') +export const g = makeTestGroup(F); + +g.test('basics') .desc( - `Test that the indirect draw parameters are tightly packed for drawIndirect. + `Test that the indirect draw parameters are tightly packed for drawIndirect and drawIndexedIndirect. An indirectBuffer is created based on indirectOffset. The actual draw args being used indicated by the indirectOffset is going to draw a left bottom triangle. While the remaining indirectBuffer is populated with random numbers or draw args @@ -23,62 +138,40 @@ The test will check render target to see if only the left bottom area is filled, meaning the expected draw args is uploaded correctly by the indirectBuffer and indirectOffset. Params: + - draw{Indirect, IndexedIndirect} - indirectOffset= {0, 4, k * sizeof(args struct), k * sizeof(args struct) + 4} ` ) - .paramsSubcasesOnly(u => - u // - .combine('indirectOffset', [ - 0, - Uint32Array.BYTES_PER_ELEMENT, - 1 * kDrawIndirectParametersSize, - 1 * kDrawIndirectParametersSize + Uint32Array.BYTES_PER_ELEMENT, - 3 * kDrawIndirectParametersSize, - 3 * kDrawIndirectParametersSize + Uint32Array.BYTES_PER_ELEMENT, - 99 * kDrawIndirectParametersSize, - 99 * kDrawIndirectParametersSize + Uint32Array.BYTES_PER_ELEMENT, - ] as const) + .params(u => + u + .combine('isIndexed', [true, false]) + .beginSubcases() + .expand('indirectOffset', p => { + const indirectDrawParametersSize = p.isIndexed + ? kDrawIndexedIndirectParametersSize * Uint32Array.BYTES_PER_ELEMENT + : kDrawIndirectParametersSize * Uint32Array.BYTES_PER_ELEMENT; + return [ + 0, + Uint32Array.BYTES_PER_ELEMENT, + 1 * indirectDrawParametersSize, + 1 * indirectDrawParametersSize + Uint32Array.BYTES_PER_ELEMENT, + 3 * indirectDrawParametersSize, + 3 * indirectDrawParametersSize + Uint32Array.BYTES_PER_ELEMENT, + 99 * indirectDrawParametersSize, + 99 * indirectDrawParametersSize + Uint32Array.BYTES_PER_ELEMENT, + ] as const; + }) ) .fn(t => { - const { indirectOffset } = t.params; - - const o = indirectOffset / Uint32Array.BYTES_PER_ELEMENT; - const arraySize = o + 8; - const indirectBuffer = [...Array(arraySize)].map(() => Math.floor(Math.random() * 100)); - - // draw args that will draw the left bottom triangle (expected call) - indirectBuffer[o] = 3; // vertexCount - indirectBuffer[o + 1] = 1; // instanceCount - indirectBuffer[o + 2] = 0; // firstVertex - indirectBuffer[o + 3] = 0; // firstInstance - - // draw args that will draw both triangles - indirectBuffer[o + 4] = 6; // vertexCount - indirectBuffer[o + 5] = 1; // instanceCount - indirectBuffer[o + 6] = 0; // firstVertex - indirectBuffer[o + 7] = 0; // firstInstance - - if (o >= 4) { - // draw args that will draw the right top triangle - indirectBuffer[o - 4] = 3; // vertexCount - indirectBuffer[o - 3] = 1; // instanceCount - indirectBuffer[o - 2] = 3; // firstVertex - indirectBuffer[o - 1] = 0; // firstInstance - } + const { isIndexed, indirectOffset } = t.params; - if (o >= 8) { - // draw args that will draw nothing - indirectBuffer[0] = 0; // vertexCount - indirectBuffer[1] = 0; // instanceCount - indirectBuffer[2] = 0; // firstVertex - indirectBuffer[3] = 0; // firstInstance - } + const vertexBuffer = t.MakeVertexBuffer(isIndexed); + const indirectBuffer = t.MakeIndirectBuffer(isIndexed, indirectOffset); - const kRenderTargetFormat = 'rgba8unorm'; const pipeline = t.device.createRenderPipeline({ vertex: { module: t.device.createShaderModule({ - code: `[[stage(vertex)]] fn main([[location(0)]] pos : vec2<f32>) -> [[builtin(position)]] vec4<f32> { + code: `@stage(vertex) fn main(@location(0) pos : vec2<f32>) -> @builtin(position) vec4<f32> { return vec4<f32>(pos, 0.0, 1.0); }`, }), @@ -98,7 +191,7 @@ Params: }, fragment: { module: t.device.createShaderModule({ - code: `[[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { + code: `@stage(fragment) fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.0, 1.0, 0.0, 1.0); }`, }), @@ -128,29 +221,14 @@ Params: ], }); renderPass.setPipeline(pipeline); - renderPass.setVertexBuffer( - 0, - t.makeBufferWithContents( - /* prettier-ignore */ - new Float32Array([ - // The bottom left triangle - -1.0, 1.0, - 1.0, -1.0, - -1.0, -1.0, + renderPass.setVertexBuffer(0, vertexBuffer, 0); - // The top right triangle - -1.0, 1.0, - 1.0, -1.0, - 1.0, 1.0, - ]), - GPUBufferUsage.VERTEX - ), - 0 - ); - renderPass.drawIndirect( - t.makeBufferWithContents(new Uint32Array(indirectBuffer), GPUBufferUsage.INDIRECT), - indirectOffset - ); + if (isIndexed) { + renderPass.setIndexBuffer(t.MakeIndexBuffer(), 'uint32', 0); + renderPass.drawIndexedIndirect(indirectBuffer, indirectOffset); + } else { + renderPass.drawIndirect(indirectBuffer, indirectOffset); + } renderPass.endPass(); t.queue.submit([commandEncoder.finish()]); @@ -169,10 +247,3 @@ Params: { exp: notFilled } ); }); - -g.test('basics,drawIndexedIndirect') - .desc( - `Test that the indirect draw parameters are tightly packed for drawIndexedIndirect. - ` - ) - .unimplemented(); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/vertex_state/basic.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/robust_access_index.spec.ts index 2a00df844f2..68d7bc795da 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/vertex_state/basic.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/rendering/robust_access_index.spec.ts @@ -1,7 +1,5 @@ export const description = ` -- Baseline tests checking vertex/instance IDs, with: - - No vertexState at all (i.e. no vertex buffers) - - One vertex buffer with no attributes +TODO: Test that drawIndexedIndirect accesses the index buffer robustly. `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/buffer.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/buffer.spec.ts index 83a136b66f6..78af3dc3e89 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/buffer.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/buffer.spec.ts @@ -101,8 +101,8 @@ class F extends GPUTest { fragment: { module: this.device.createShaderModule({ code: ` - [[stage(fragment)]] - fn main([[location(0)]] i_color : vec4<f32>) -> [[location(0)]] vec4<f32> { + @stage(fragment) + fn main(@location(0) i_color : vec4<f32>) -> @location(0) vec4<f32> { return i_color; }`, }), @@ -504,13 +504,13 @@ g.test('uniform_buffer') const computeShaderModule = t.device.createShaderModule({ code: ` - [[block]] struct UBO { + struct UBO { value : vec4<u32>; }; - [[group(0), binding(0)]] var<uniform> ubo : UBO; - [[group(0), binding(1)]] var outImage : texture_storage_2d<rgba8unorm, write>; + @group(0) @binding(0) var<uniform> ubo : UBO; + @group(0) @binding(1) var outImage : texture_storage_2d<rgba8unorm, write>; - [[stage(compute), workgroup_size(1)]] fn main() { + @stage(compute) @workgroup_size(1) fn main() { if (all(ubo.value == vec4<u32>(0u, 0u, 0u, 0u))) { textureStore(outImage, vec2<i32>(0, 0), vec4<f32>(0.0, 1.0, 0.0, 1.0)); } else { @@ -539,13 +539,13 @@ g.test('readonly_storage_buffer') const computeShaderModule = t.device.createShaderModule({ code: ` - [[block]] struct SSBO { + struct SSBO { value : vec4<u32>; }; - [[group(0), binding(0)]] var<storage, read> ssbo : SSBO; - [[group(0), binding(1)]] var outImage : texture_storage_2d<rgba8unorm, write>; + @group(0) @binding(0) var<storage, read> ssbo : SSBO; + @group(0) @binding(1) var outImage : texture_storage_2d<rgba8unorm, write>; - [[stage(compute), workgroup_size(1)]] fn main() { + @stage(compute) @workgroup_size(1) fn main() { if (all(ssbo.value == vec4<u32>(0u, 0u, 0u, 0u))) { textureStore(outImage, vec2<i32>(0, 0), vec4<f32>(0.0, 1.0, 0.0, 1.0)); } else { @@ -574,13 +574,13 @@ g.test('storage_buffer') const computeShaderModule = t.device.createShaderModule({ code: ` - [[block]] struct SSBO { + struct SSBO { value : vec4<u32>; }; - [[group(0), binding(0)]] var<storage, read_write> ssbo : SSBO; - [[group(0), binding(1)]] var outImage : texture_storage_2d<rgba8unorm, write>; + @group(0) @binding(0) var<storage, read_write> ssbo : SSBO; + @group(0) @binding(1) var outImage : texture_storage_2d<rgba8unorm, write>; - [[stage(compute), workgroup_size(1)]] fn main() { + @stage(compute) @workgroup_size(1) fn main() { if (all(ssbo.value == vec4<u32>(0u, 0u, 0u, 0u))) { textureStore(outImage, vec2<i32>(0, 0), vec4<f32>(0.0, 1.0, 0.0, 1.0)); } else { @@ -606,11 +606,11 @@ g.test('vertex_buffer') t.device.createShaderModule({ code: ` struct VertexOut { - [[location(0)]] color : vec4<f32>; - [[builtin(position)]] position : vec4<f32>; + @location(0) color : vec4<f32>; + @builtin(position) position : vec4<f32>; }; - [[stage(vertex)]] fn main([[location(0)]] pos : vec4<f32>) -> VertexOut { + @stage(vertex) fn main(@location(0) pos : vec4<f32>) -> VertexOut { var output : VertexOut; if (all(pos == vec4<f32>(0.0, 0.0, 0.0, 0.0))) { output.color = vec4<f32>(0.0, 1.0, 0.0, 1.0); @@ -668,12 +668,12 @@ GPUBuffer, all the contents in that GPUBuffer have been initialized to 0.` t.device.createShaderModule({ code: ` struct VertexOut { - [[location(0)]] color : vec4<f32>; - [[builtin(position)]] position : vec4<f32>; + @location(0) color : vec4<f32>; + @builtin(position) position : vec4<f32>; }; - [[stage(vertex)]] - fn main([[builtin(vertex_index)]] VertexIndex : u32) -> VertexOut { + @stage(vertex) + fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOut { var output : VertexOut; if (VertexIndex == 0u) { output.color = vec4<f32>(0.0, 1.0, 0.0, 1.0); @@ -735,11 +735,11 @@ have been initialized to 0.` t.device.createShaderModule({ code: ` struct VertexOut { - [[location(0)]] color : vec4<f32>; - [[builtin(position)]] position : vec4<f32>; + @location(0) color : vec4<f32>; + @builtin(position) position : vec4<f32>; }; - [[stage(vertex)]] fn main() -> VertexOut { + @stage(vertex) fn main() -> VertexOut { var output : VertexOut; output.color = vec4<f32>(1.0, 0.0, 0.0, 1.0); output.position = vec4<f32>(0.0, 0.0, 0.0, 1.0); @@ -812,9 +812,9 @@ creation of that GPUBuffer, all the contents in that GPUBuffer have been initial compute: { module: t.device.createShaderModule({ code: ` - [[group(0), binding(0)]] var outImage : texture_storage_2d<rgba8unorm, write>; + @group(0) @binding(0) var outImage : texture_storage_2d<rgba8unorm, write>; - [[stage(compute), workgroup_size(1)]] fn main() { + @stage(compute) @workgroup_size(1) fn main() { textureStore(outImage, vec2<i32>(0, 0), vec4<f32>(1.0, 0.0, 0.0, 1.0)); }`, }), diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts index f7cc5f8bf84..812ca94416c 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts @@ -11,7 +11,6 @@ export const checkContentsByBufferCopy: CheckContents = ( subresourceRange ) => { for (const { level: mipLevel, layer } of subresourceRange.each()) { - assert(params.dimension !== '1d'); assert(params.format in kTextureFormatInfo); const format = params.format as EncodableTextureFormat; @@ -33,7 +32,6 @@ export const checkContentsByTextureCopy: CheckContents = ( subresourceRange ) => { for (const { level, layer } of subresourceRange.each()) { - assert(params.dimension !== '1d'); assert(params.format in kTextureFormatInfo); const format = params.format as EncodableTextureFormat; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts index 8ccf492c310..f72bf1d82ca 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts @@ -6,9 +6,9 @@ import { CheckContents } from '../texture_zero.spec.js'; function makeFullscreenVertexModule(device: GPUDevice) { return device.createShaderModule({ code: ` - [[stage(vertex)]] - fn main([[builtin(vertex_index)]] VertexIndex : u32) - -> [[builtin(position)]] vec4<f32> { + @stage(vertex) + fn main(@builtin(vertex_index) VertexIndex : u32) + -> @builtin(position) vec4<f32> { var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>( vec2<f32>(-1.0, -3.0), vec2<f32>( 3.0, 1.0), @@ -35,11 +35,11 @@ function getDepthTestEqualPipeline( module: t.device.createShaderModule({ code: ` struct Outputs { - [[builtin(frag_depth)]] FragDepth : f32; - [[location(0)]] outSuccess : f32; + @builtin(frag_depth) FragDepth : f32; + @location(0) outSuccess : f32; }; - [[stage(fragment)]] + @stage(fragment) fn main() -> Outputs { var output : Outputs; output.FragDepth = f32(${expected}); @@ -73,8 +73,8 @@ function getStencilTestEqualPipeline( entryPoint: 'main', module: t.device.createShaderModule({ code: ` - [[stage(fragment)]] - fn main() -> [[location(0)]] f32 { + @stage(fragment) + fn main() -> @location(0) f32 { return 1.0; } `, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts index a27573fda7c..168e3f1bd1c 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts @@ -15,7 +15,6 @@ export const checkContentsBySampling: CheckContents = ( state, subresourceRange ) => { - assert(params.dimension !== '1d'); assert(params.format in kTextureFormatInfo); const format = params.format as EncodableTextureFormat; const rep = kTexelRepresentationInfo[format]; @@ -44,38 +43,40 @@ export const checkContentsBySampling: CheckContents = ( const _xd = '_' + params.dimension; const _multisampled = params.sampleCount > 1 ? '_multisampled' : ''; - const texelIndexExpresion = + const texelIndexExpression = params.dimension === '2d' ? 'vec2<i32>(GlobalInvocationID.xy)' : params.dimension === '3d' ? 'vec3<i32>(GlobalInvocationID.xyz)' + : params.dimension === '1d' + ? 'i32(GlobalInvocationID.x)' : unreachable(); const computePipeline = t.device.createComputePipeline({ compute: { entryPoint: 'main', module: t.device.createShaderModule({ code: ` - [[block]] struct Constants { + struct Constants { level : i32; }; - [[group(0), binding(0)]] var<uniform> constants : Constants; - [[group(0), binding(1)]] var myTexture : texture${_multisampled}${_xd}<${shaderType}>; + @group(0) @binding(0) var<uniform> constants : Constants; + @group(0) @binding(1) var myTexture : texture${_multisampled}${_xd}<${shaderType}>; - [[block]] struct Result { - values : [[stride(4)]] array<${shaderType}>; + struct Result { + values : @stride(4) array<${shaderType}>; }; - [[group(0), binding(3)]] var<storage, read_write> result : Result; + @group(0) @binding(3) var<storage, read_write> result : Result; - [[stage(compute), workgroup_size(1)]] - fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) { + @stage(compute) @workgroup_size(1) + fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) { let flatIndex : u32 = ${componentCount}u * ( ${width}u * ${height}u * GlobalInvocationID.z + ${width}u * GlobalInvocationID.y + GlobalInvocationID.x ); let texel : vec4<${shaderType}> = textureLoad( - myTexture, ${texelIndexExpresion}, constants.level); + myTexture, ${texelIndexExpression}, constants.level); for (var i : u32 = 0u; i < ${componentCount}u; i = i + 1u) { result.values[flatIndex + i] = texel.${indexExpression}; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/texture_zero.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/texture_zero.spec.ts index 6ceb3933fe1..cebb9120715 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/texture_zero.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/resource_init/texture_zero.spec.ts @@ -2,12 +2,12 @@ export const description = ` Test uninitialized textures are initialized to zero when read. TODO: -- 1d -- test by sampling depth/stencil -- test by copying out of stencil +- test by sampling depth/stencil [1] +- test by copying out of stencil [2] +- test compressed texture formats [3] `; -// TODO: This is a test file, it probably shouldn't export anything. +// MAINTENANCE_TODO: This is a test file, it probably shouldn't export anything. // Everything that's exported should be moved to another file. import { TestCaseRecorder, TestParams } from '../../../../common/framework/fixture.js'; @@ -23,6 +23,8 @@ import { kUncompressedTextureFormats, EncodableTextureFormat, UncompressedTextureFormat, + textureDimensionAndFormatCompatible, + kTextureDimensions, } from '../../../capability_info.js'; import { GPUConst } from '../../../constants.js'; import { GPUTest } from '../../../gpu_test.js'; @@ -213,6 +215,10 @@ export class TextureZeroInitTest extends GPUTest { } get textureHeight(): number { + if (this.p.dimension === '1d') { + return 1; + } + let height = 1 << this.p.mipLevelCount; if (this.p.nonPowerOfTwo) { height = 2 * height - 1; @@ -331,9 +337,6 @@ export class TextureZeroInitTest extends GPUTest { state: InitializedState, subresourceRange: SubresourceRange ): void { - // TODO: 1D texture - assert(this.p.dimension !== '1d'); - assert(this.p.format in kTextureFormatInfo); const format = this.p.format as EncodableTextureFormat; @@ -433,8 +436,7 @@ export class TextureZeroInitTest extends GPUTest { } const kTestParams = kUnitCaseParamsBuilder - // TODO: 1d textures - .combine('dimension', ['2d', '3d'] as GPUTextureDimension[]) + .combine('dimension', kTextureDimensions) .combine('readMethod', [ ReadMethod.CopyToBuffer, ReadMethod.CopyToTexture, @@ -442,7 +444,9 @@ const kTestParams = kUnitCaseParamsBuilder ReadMethod.DepthTest, ReadMethod.StencilTest, ]) + // [3] compressed formats .combine('format', kUncompressedTextureFormats) + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .beginSubcases() .combine('aspect', kTextureAspects) .unless(({ readMethod, format, aspect }) => { @@ -451,18 +455,20 @@ const kTestParams = kUnitCaseParamsBuilder (readMethod === ReadMethod.DepthTest && (!info.depth || aspect === 'stencil-only')) || (readMethod === ReadMethod.StencilTest && (!info.stencil || aspect === 'depth-only')) || (readMethod === ReadMethod.ColorBlending && !info.color) || - // TODO: Test with depth/stencil sampling + // [1]: Test with depth/stencil sampling (readMethod === ReadMethod.Sample && (info.depth || info.stencil)) || (aspect === 'depth-only' && !info.depth) || (aspect === 'stencil-only' && !info.stencil) || (aspect === 'all' && info.depth && info.stencil) || // Cannot copy from a packed depth format. - // TODO: Test copying out of the stencil aspect. + // [2]: Test copying out of the stencil aspect. ((readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture) && (format === 'depth24plus' || format === 'depth24plus-stencil8')) ); }) .combine('mipLevelCount', kMipLevelCounts) + // 1D texture can only have a single mip level + .unless(p => p.dimension === '1d' && p.mipLevelCount !== 1) .combine('sampleCount', kSampleCounts) .unless( ({ readMethod, sampleCount }) => @@ -476,7 +482,7 @@ const kTestParams = kUnitCaseParamsBuilder .unless(({ dimension, readMethod, uninitializeMethod, format, sampleCount }) => { const formatInfo = kTextureFormatInfo[format]; return ( - dimension === '3d' && + dimension !== '2d' && (sampleCount > 1 || formatInfo.depth || formatInfo.stencil || @@ -492,11 +498,10 @@ const kTestParams = kUnitCaseParamsBuilder yield { layerCount: 1 as LayerCounts }; yield { layerCount: 7 as LayerCounts }; break; + case '1d': case '3d': yield { layerCount: 1 as LayerCounts }; break; - default: - unreachable(); } }) // Multisampled 3D / 2D array textures not supported. diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/sampling/anisotropy.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/sampling/anisotropy.spec.ts index fe0aa2f25e8..43f7a99b386 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/sampling/anisotropy.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/sampling/anisotropy.spec.ts @@ -59,12 +59,12 @@ class SamplerAnisotropicFilteringSlantedPlaneTest extends GPUTest { module: this.device.createShaderModule({ code: ` struct Outputs { - [[builtin(position)]] Position : vec4<f32>; - [[location(0)]] fragUV : vec2<f32>; + @builtin(position) Position : vec4<f32>; + @location(0) fragUV : vec2<f32>; }; - [[stage(vertex)]] fn main( - [[builtin(vertex_index)]] VertexIndex : u32) -> Outputs { + @stage(vertex) fn main( + @builtin(vertex_index) VertexIndex : u32) -> Outputs { var position : array<vec3<f32>, 6> = array<vec3<f32>, 6>( vec3<f32>(-0.5, 0.5, -0.5), vec3<f32>(0.5, 0.5, -0.5), @@ -99,13 +99,13 @@ class SamplerAnisotropicFilteringSlantedPlaneTest extends GPUTest { fragment: { module: this.device.createShaderModule({ code: ` - [[group(0), binding(0)]] var sampler0 : sampler; - [[group(0), binding(1)]] var texture0 : texture_2d<f32>; + @group(0) @binding(0) var sampler0 : sampler; + @group(0) @binding(1) var texture0 : texture_2d<f32>; - [[stage(fragment)]] fn main( - [[builtin(position)]] FragCoord : vec4<f32>, - [[location(0)]] fragUV: vec2<f32>) - -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main( + @builtin(position) FragCoord : vec4<f32>, + @location(0) fragUV: vec2<f32>) + -> @location(0) vec4<f32> { return textureSample(texture0, sampler0, fragUV); } `, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/shader_module/compilation_info.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/shader_module/compilation_info.spec.ts index d605df76903..b89a509b613 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/shader_module/compilation_info.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/shader_module/compilation_info.spec.ts @@ -13,7 +13,7 @@ const kValidShaderSources = [ valid: true, unicode: false, _code: ` - [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); }`, }, @@ -22,7 +22,7 @@ const kValidShaderSources = [ unicode: true, _code: ` // 頂点シェーダー 👩💻 - [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); }`, }, @@ -34,9 +34,9 @@ const kInvalidShaderSources = [ unicode: false, _errorLine: 4, _code: ` - [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { - // Expected Error: vec4 should be vec4<f32> - return vec4(0.0, 0.0, 0.0, 1.0); + @stage(vertex) fn main() -> @builtin(position) vec4<f32> { + // Expected Error: unknown function 'unknown' + return unknown(0.0, 0.0, 0.0, 1.0); }`, }, { @@ -45,9 +45,9 @@ const kInvalidShaderSources = [ _errorLine: 5, _code: ` // 頂点シェーダー 👩💻 - [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { - // Expected Error: vec4 should be vec4<f32> - return vec4(0.0, 0.0, 0.0, 1.0); + @stage(vertex) fn main() -> @builtin(position) vec4<f32> { + // Expected Error: unknown function 'unknown' + return unknown(0.0, 0.0, 0.0, 1.0); }`, }, ]; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/threading/README.txt b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/threading/README.txt index d8c4d0508bd..caccf6f69d8 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/threading/README.txt +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/threading/README.txt @@ -1,9 +1,11 @@ Tests for behavior with multiple threads (main thread + workers). TODO: plan and implement -- Try postMessage'ing an object of every type (to same or different thread) +- 'postMessage' + Try postMessage'ing an object of every type (to same or different thread) - {main -> main, main -> worker, worker -> main, worker1 -> worker1, worker1 -> worker2} - through {global postMessage, MessageChannel} - {in, not in} transferrable object list, when valid -- Short tight loop doing many of an action from two threads at the same time +- 'concurrency' + Short tight loop doing many of an action from two threads at the same time - e.g. {create {buffer, texture, shader, pipeline}} diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/vertex_state/correctness.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/vertex_state/correctness.spec.ts index e2d82e82038..d16af553869 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/vertex_state/correctness.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/vertex_state/correctness.spec.ts @@ -126,9 +126,9 @@ class VertexStateTest extends GPUTest { storageType = 'storage, read'; } - vsInputs += ` [[location(${i})]] attrib${i} : ${shaderType};\n`; - vsBindings += `[[block]] struct S${i} { data : array<vec4<${a.shaderBaseType}>, ${maxCount}>; };\n`; - vsBindings += `[[group(0), binding(${i})]] var<${storageType}> providedData${i} : S${i};\n`; + vsInputs += ` @location(${i}) attrib${i} : ${shaderType};\n`; + vsBindings += `struct S${i} { data : array<vec4<${a.shaderBaseType}>, ${maxCount}>; };\n`; + vsBindings += `@group(0) @binding(${i}) var<${storageType}> providedData${i} : S${i};\n`; // Generate the all the checks for the attributes. for (let component = 0; component < shaderComponentCount; component++) { @@ -157,8 +157,8 @@ class VertexStateTest extends GPUTest { return ` struct Inputs { ${vsInputs} - [[builtin(vertex_index)]] vertexIndex: u32; - [[builtin(instance_index)]] instanceIndex: u32; + @builtin(vertex_index) vertexIndex: u32; + @builtin(instance_index) instanceIndex: u32; }; ${vsBindings} @@ -194,11 +194,11 @@ ${vsChecks} } struct VSOutputs { - [[location(0), interpolate(flat)]] result : i32; - [[builtin(position)]] position : vec4<f32>; + @location(0) @interpolate(flat) result : i32; + @builtin(position) position : vec4<f32>; }; -[[stage(vertex)]] fn vsMain(input : Inputs) -> VSOutputs { +@stage(vertex) fn vsMain(input : Inputs) -> VSOutputs { doTest(input); // Place that point at pixel (vertexIndex, instanceIndex) in a framebuffer of size @@ -213,8 +213,8 @@ struct VSOutputs { return output; } -[[stage(fragment)]] fn fsMain([[location(0), interpolate(flat)]] result : i32) - -> [[location(0)]] i32 { +@stage(fragment) fn fsMain(@location(0) @interpolate(flat) result : i32) + -> @location(0) i32 { return result; } `; @@ -307,7 +307,7 @@ struct VSOutputs { } // Generate TestData for the format with interesting test values. - // TODO cache the result on the fixture? + // MAINTENANCE_TODO cache the result on the fixture? // Note that the test data always starts with an interesting value, so that using the first // test value in a test is still meaningful. generateTestData(format: GPUVertexFormat): TestData { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/vertex_state/index_format.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/vertex_state/index_format.spec.ts index 017f947d9bf..29f8ea1e694 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/vertex_state/index_format.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/operation/vertex_state/index_format.spec.ts @@ -54,12 +54,12 @@ class IndexFormatTest extends GPUTest { stripIndexFormat?: GPUIndexFormat ): GPURenderPipeline { const vertexModule = this.device.createShaderModule({ - // TODO?: These positions will create triangles that cut right through pixel centers. If this + // NOTE: These positions will create triangles that cut right through pixel centers. If this // results in different rasterization results on different hardware, tweak to avoid this. code: ` - [[stage(vertex)]] - fn main([[builtin(vertex_index)]] VertexIndex : u32) - -> [[builtin(position)]] vec4<f32> { + @stage(vertex) + fn main(@builtin(vertex_index) VertexIndex : u32) + -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 4>( vec2<f32>(0.01, 0.98), vec2<f32>(0.99, -0.98), @@ -76,8 +76,8 @@ class IndexFormatTest extends GPUTest { const fragmentModule = this.device.createShaderModule({ code: ` - [[stage(fragment)]] - fn main() -> [[location(0)]] u32 { + @stage(fragment) + fn main() -> @location(0) u32 { return 1u; } `, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/attachment_compatibility.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/attachment_compatibility.spec.ts index 64b3b07e2cf..9c8c275369b 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/attachment_compatibility.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/attachment_compatibility.spec.ts @@ -70,7 +70,7 @@ class F extends ValidationTest { vertex: { module: this.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 0.0); }`, }), @@ -78,7 +78,7 @@ class F extends ValidationTest { }, fragment: { module: this.device.createShaderModule({ - code: '[[stage(fragment)]] fn main() {}', + code: '@stage(fragment) fn main() {}', }), entryPoint: 'main', targets, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/buffer/create.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/buffer/create.spec.ts index 113987b83b2..490086dfef3 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/buffer/create.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/buffer/create.spec.ts @@ -4,7 +4,11 @@ Tests for validation in createBuffer. import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { assert } from '../../../../common/util/util.js'; -import { kBufferSizeAlignment } from '../../../capability_info.js'; +import { + kAllBufferUsageBits, + kBufferSizeAlignment, + kBufferUsages, +} from '../../../capability_info.js'; import { GPUConst } from '../../../constants.js'; import { kMaxSafeMultipleOf8 } from '../../../util/math.js'; import { ValidationTest } from '../validation_test.js'; @@ -13,7 +17,9 @@ export const g = makeTestGroup(ValidationTest); assert(kBufferSizeAlignment === 4); g.test('size') - .desc('Test buffer size alignment.') + .desc( + 'Test buffer size alignment is validated to be a multiple of 4 if mappedAtCreation is true.' + ) .params(u => u .combine('mappedAtCreation', [false, true]) @@ -26,21 +32,50 @@ g.test('size') kBufferSizeAlignment * 2, ]) ) - .unimplemented(); + .fn(t => { + const { mappedAtCreation, size } = t.params; + const isValid = !mappedAtCreation || size % kBufferSizeAlignment === 0; + const usage = BufferUsage.COPY_SRC; + t.expectGPUError( + 'validation', + () => t.device.createBuffer({ size, usage, mappedAtCreation }), + !isValid + ); + }); + +const kInvalidUsage = 0x8000; +assert((kInvalidUsage & kAllBufferUsageBits) === 0); g.test('usage') - .desc('Test combinations of (one to two?) usage flags.') + .desc('Test combinations of zero to two usage flags are validated to be valid.') .params(u => - u // + u + .combine('usage1', [0, ...kBufferUsages, kInvalidUsage]) + .combine('usage2', [0, ...kBufferUsages, kInvalidUsage]) .beginSubcases() .combine('mappedAtCreation', [false, true]) - .combine('usage', [ - // TODO - ]) ) - .unimplemented(); + .fn(t => { + const { mappedAtCreation, usage1, usage2 } = t.params; + const usage = usage1 | usage2; + + const isValid = + usage !== 0 && + (usage & ~kAllBufferUsageBits) === 0 && + ((usage & GPUBufferUsage.MAP_READ) === 0 || + (usage & ~(GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ)) === 0) && + ((usage & GPUBufferUsage.MAP_WRITE) === 0 || + (usage & ~(GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE)) === 0); + + t.expectGPUError( + 'validation', + () => t.device.createBuffer({ size: kBufferSizeAlignment * 2, usage, mappedAtCreation }), + !isValid + ); + }); const BufferUsage = GPUConst.BufferUsage; + g.test('createBuffer_invalid_and_oom') .desc( `When creating a mappable buffer, it's expected that shmem may be immediately allocated diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/features/depth_clamping.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/features/depth_clip_control.spec.ts index a7c840f8f58..4e86a1db4c9 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/features/depth_clamping.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/features/depth_clip_control.spec.ts @@ -1,8 +1,12 @@ export const description = ` -Tests 'depth-clamping' must be enabled for GPUDepthStencilState.clampDepth to be enabled. +Tests checks that depend on 'depth-clip-control' being enabled. `; import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { ValidationTest } from '../../validation_test.js'; export const g = makeTestGroup(ValidationTest); + +g.test('createRenderPipeline') + .desc(`unclippedDepth=true requires 'depth-clip-control'; false or undefined does not.`) + .unimplemented(); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts index 11bcc7f2116..76e61972cc3 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts @@ -1,5 +1,7 @@ export const description = ` Tests for capability checking for features enabling optional query types. + +TODO: pipeline statistics queries are removed from core; consider moving tests to another suite. `; import { makeTestGroup } from '../../../../../common/framework/test_group.js'; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/features/texture_formats.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/features/texture_formats.spec.ts index 3300c209548..95b1047b300 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/features/texture_formats.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/features/texture_formats.spec.ts @@ -1,11 +1,11 @@ export const description = ` Tests for capability checking for features enabling optional texture formats. -TODO: -- x= every optional texture format. -- x= every place in the API that takes a GPUTextureFormat ( - GPUTextureViewDescriptor (format reinterpretation rules haven't been finalized), - maybe GPUSwapChainDescriptor (the supported swapchain format list can't be queried right now)). +TODO(#902): test GPUTextureViewDescriptor.format +TODO(#902): test GPUCanvasConfiguration.format (it doesn't allow any optional formats today but the + error might still be different - exception instead of validation. + +TODO(#920): test GPUTextureDescriptor.viewFormats (if/when it takes formats) `; import { makeTestGroup } from '../../../../../common/framework/test_group.js'; @@ -23,6 +23,8 @@ g.test('texture_descriptor') ` Test creating a texture with an optional texture format will fail if the required optional feature is not enabled. + + TODO(#919): Actually it should throw an exception, not fail with a validation error. ` ) .params(u => @@ -53,6 +55,8 @@ g.test('storage_texture_binding_layout') ` Test creating a GPUStorageTextureBindingLayout with an optional texture format will fail if the required optional feature are not enabled. + + Note: This test has no cases if there are no optional texture formats supporting storage. ` ) .params(u => @@ -90,6 +94,8 @@ g.test('color_target_state') ` Test creating a render pipeline with an optional texture format set in GPUColorTargetState will fail if the required optional feature is not enabled. + + Note: This test has no cases if there are no optional texture formats supporting color rendering. ` ) .params(u => @@ -112,8 +118,8 @@ g.test('color_target_state') vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] - fn main()-> [[builtin(position)]] vec4<f32> { + @stage(vertex) + fn main()-> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); }`, }), @@ -122,8 +128,8 @@ g.test('color_target_state') fragment: { module: t.device.createShaderModule({ code: ` - [[stage(fragment)]] - fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) + fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.0, 1.0, 0.0, 1.0); }`, }), @@ -165,8 +171,8 @@ g.test('depth_stencil_state') vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] - fn main()-> [[builtin(position)]] vec4<f32> { + @stage(vertex) + fn main()-> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); }`, }), @@ -178,8 +184,8 @@ g.test('depth_stencil_state') fragment: { module: t.device.createShaderModule({ code: ` - [[stage(fragment)]] - fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) + fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.0, 1.0, 0.0, 1.0); }`, }), @@ -195,6 +201,8 @@ g.test('render_bundle_encoder_descriptor_color_format') ` Test creating a render bundle encoder with an optional texture format set as one of the color format will fail if the required optional feature is not enabled. + + Note: This test has no cases if there are no optional texture formats supporting color rendering. ` ) .params(u => diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/limits/README.txt b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/limits/README.txt index 3a5091ec666..3f2434d4ed0 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/limits/README.txt +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/capability_checks/limits/README.txt @@ -5,3 +5,4 @@ Test everything that shouldn't be valid without a higher-than-specified limit. One file for each limit name. TODO: implement +TODO: Also test that "alignment" limits require a power of 2. diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createComputePipeline.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createComputePipeline.spec.ts index edfa994c1ed..6faff31be65 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createComputePipeline.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createComputePipeline.spec.ts @@ -16,19 +16,19 @@ class F extends ValidationTest { let code; switch (shaderStage) { case 'compute': { - code = `[[stage(compute), workgroup_size(1)]] fn ${entryPoint}() {}`; + code = `@stage(compute) @workgroup_size(1) fn ${entryPoint}() {}`; break; } case 'vertex': { code = ` - [[stage(vertex)]] fn ${entryPoint}() -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn ${entryPoint}() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); }`; break; } case 'fragment': { code = ` - [[stage(fragment)]] fn ${entryPoint}() -> [[location(0)]] vec4<i32> { + @stage(fragment) fn ${entryPoint}() -> @location(0) vec4<i32> { return vec4<i32>(0, 1, 0, 1); }`; break; @@ -211,7 +211,7 @@ g.test('shader_module,device_mismatch') await t.selectMismatchedDeviceOrSkipTestCase(undefined); } - const code = '[[stage(compute), workgroup_size(1)]] fn main() {}'; + const code = '@stage(compute) @workgroup_size(1) fn main() {}'; const module = mismatched ? t.mismatchedDevice.createShaderModule({ code }) : t.device.createShaderModule({ code }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createRenderPipeline.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createRenderPipeline.spec.ts index 06097ebab13..cb80d2ec5c2 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createRenderPipeline.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createRenderPipeline.spec.ts @@ -29,6 +29,11 @@ import { kTextureFormats, kRenderableColorTextureFormats, kTextureFormatInfo, + kDepthStencilFormats, + kCompareFunctions, + kStencilOperations, + kBlendFactors, + kBlendOperations, } from '../../capability_info.js'; import { kTexelRepresentationInfo } from '../../util/texture/texel_data.js'; @@ -79,7 +84,7 @@ class F extends ValidationTest { } return ` - [[stage(fragment)]] fn main() -> [[location(0)]] ${outputType} { + @stage(fragment) fn main() -> @location(0) ${outputType} { return ${result}; }`; } @@ -111,7 +116,7 @@ class F extends ValidationTest { vertex: { module: this.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); }`, }), @@ -170,6 +175,7 @@ class F extends ValidationTest { export const g = makeTestGroup(F); g.test('basic_use_of_createRenderPipeline') + .desc(`TODO: review and add description; shorten name`) .params(u => u.combine('isAsync', [false, true])) .fn(async t => { const { isAsync } = t.params; @@ -181,7 +187,9 @@ g.test('basic_use_of_createRenderPipeline') g.test('create_vertex_only_pipeline_with_without_depth_stencil_state') .desc( `Test creating vertex-only render pipeline. A vertex-only render pipeline have no fragment -state (and thus have no color state), and can be create with or without depth stencil state.` +state (and thus have no color state), and can be create with or without depth stencil state. + +TODO: review and shorten name` ) .params(u => u @@ -217,6 +225,7 @@ state (and thus have no color state), and can be create with or without depth st }); g.test('at_least_one_color_state_is_required_for_complete_pipeline') + .desc(`TODO: review and add description; shorten name`) .params(u => u.combine('isAsync', [false, true])) .fn(async t => { const { isAsync } = t.params; @@ -237,6 +246,7 @@ g.test('at_least_one_color_state_is_required_for_complete_pipeline') }); g.test('color_formats_must_be_renderable') + .desc(`TODO: review and add description; shorten name`) .params(u => u.combine('isAsync', [false, true]).combine('format', kTextureFormats)) .fn(async t => { const { isAsync, format } = t.params; @@ -248,7 +258,144 @@ g.test('color_formats_must_be_renderable') t.doCreateRenderPipelineTest(isAsync, info.renderable && info.color, descriptor); }); +g.test('depth_stencil_state,format') + .desc(`The texture format in depthStencilState must be a depth/stencil format`) + .params(u => u.combine('isAsync', [false, true]).combine('format', kTextureFormats)) + .fn(async t => { + const { isAsync, format } = t.params; + const info = kTextureFormatInfo[format]; + await t.selectDeviceOrSkipTestCase(info.feature); + + const descriptor = t.getDescriptor({ depthStencil: { format } }); + + t.doCreateRenderPipelineTest(isAsync, info.depth || info.stencil, descriptor); + }); + +g.test('depth_stencil_state,depth_aspect,depth_test') + .desc( + `Depth aspect must be contained in the format if depth test is enabled in depthStencilState.` + ) + .params(u => + u + .combine('isAsync', [false, true]) + .combine('format', kDepthStencilFormats) + .combine('depthCompare', [undefined, ...kCompareFunctions]) + ) + .fn(async t => { + const { isAsync, format, depthCompare } = t.params; + const info = kTextureFormatInfo[format]; + await t.selectDeviceOrSkipTestCase(info.feature); + + const descriptor = t.getDescriptor({ + depthStencil: { format, depthCompare }, + }); + + const depthTestEnabled = depthCompare !== undefined && depthCompare !== 'always'; + t.doCreateRenderPipelineTest(isAsync, !depthTestEnabled || info.depth, descriptor); + }); + +g.test('depth_stencil_state,depth_aspect,depth_write') + .desc( + `Depth aspect must be contained in the format if depth write is enabled in depthStencilState.` + ) + .params(u => + u + .combine('isAsync', [false, true]) + .combine('format', kDepthStencilFormats) + .combine('depthWriteEnabled', [false, true]) + ) + .fn(async t => { + const { isAsync, format, depthWriteEnabled } = t.params; + const info = kTextureFormatInfo[format]; + await t.selectDeviceOrSkipTestCase(info.feature); + + const descriptor = t.getDescriptor({ + depthStencil: { format, depthWriteEnabled }, + }); + t.doCreateRenderPipelineTest(isAsync, !depthWriteEnabled || info.depth, descriptor); + }); + +g.test('depth_stencil_state,stencil_aspect,stencil_test') + .desc( + `Stencil aspect must be contained in the format if stencil test is enabled in depthStencilState.` + ) + .params(u => + u + .combine('isAsync', [false, true]) + .combine('format', kDepthStencilFormats) + .combine('face', ['front', 'back'] as const) + .combine('compare', [undefined, ...kCompareFunctions]) + ) + .fn(async t => { + const { isAsync, format, face, compare } = t.params; + const info = kTextureFormatInfo[format]; + await t.selectDeviceOrSkipTestCase(info.feature); + + let descriptor: GPURenderPipelineDescriptor; + if (face === 'front') { + descriptor = t.getDescriptor({ depthStencil: { format, stencilFront: { compare } } }); + } else { + descriptor = t.getDescriptor({ depthStencil: { format, stencilBack: { compare } } }); + } + + const stencilTestEnabled = compare !== undefined && compare !== 'always'; + t.doCreateRenderPipelineTest(isAsync, !stencilTestEnabled || info.stencil, descriptor); + }); + +g.test('depth_stencil_state,stencil_aspect,stencil_write') + .desc( + `Stencil aspect must be contained in the format if stencil write is enabled in depthStencilState.` + ) + .params(u => + u + .combine('isAsync', [false, true]) + .combine('format', kDepthStencilFormats) + .combine('faceAndOpType', [ + 'frontFailOp', + 'frontDepthFailOp', + 'frontPassOp', + 'backFailOp', + 'backDepthFailOp', + 'backPassOp', + ] as const) + .combine('op', [undefined, ...kStencilOperations]) + ) + .fn(async t => { + const { isAsync, format, faceAndOpType, op } = t.params; + const info = kTextureFormatInfo[format]; + await t.selectDeviceOrSkipTestCase(info.feature); + + let depthStencil: GPUDepthStencilState; + switch (faceAndOpType) { + case 'frontFailOp': + depthStencil = { format, stencilFront: { failOp: op } }; + break; + case 'frontDepthFailOp': + depthStencil = { format, stencilFront: { depthFailOp: op } }; + break; + case 'frontPassOp': + depthStencil = { format, stencilFront: { passOp: op } }; + break; + case 'backFailOp': + depthStencil = { format, stencilBack: { failOp: op } }; + break; + case 'backDepthFailOp': + depthStencil = { format, stencilBack: { depthFailOp: op } }; + break; + case 'backPassOp': + depthStencil = { format, stencilBack: { passOp: op } }; + break; + default: + unreachable(); + } + const descriptor = t.getDescriptor({ depthStencil }); + + const stencilWriteEnabled = op !== undefined && op !== 'keep'; + t.doCreateRenderPipelineTest(isAsync, !stencilWriteEnabled || info.stencil, descriptor); + }); + g.test('sample_count_must_be_valid') + .desc(`TODO: review and add description; shorten name`) .params(u => u.combine('isAsync', [false, true]).combineWithParams([ { sampleCount: 0, _success: false }, @@ -274,7 +421,9 @@ g.test('pipeline_output_targets') - The scalar type (f32, i32, or u32) must match the sample type of the format. - The componentCount of the fragment output (e.g. f32, vec2, vec3, vec4) must not have fewer channels than that of the color attachment texture formats. Extra components are allowed and are discarded. - ` + + MAINTAINENCE_TODO: update this test after the WebGPU SPEC ISSUE 50 "define what 'compatible' means + for render target formats" is resolved.` ) .params(u => u @@ -294,9 +443,13 @@ g.test('pipeline_output_targets') fragmentShaderCode: t.getFragmentShaderCode(sampleType, componentCount), }); + const sampleTypeSuccess = + info.sampleType === 'float' || info.sampleType === 'unfilterable-float' + ? sampleType === 'float' + : info.sampleType === sampleType; + const _success = - info.sampleType === sampleType && - componentCount >= kTexelRepresentationInfo[format].componentOrder.length; + sampleTypeSuccess && componentCount >= kTexelRepresentationInfo[format].componentOrder.length; t.doCreateRenderPipelineTest(isAsync, _success, descriptor); }); @@ -410,6 +563,99 @@ g.test('pipeline_output_targets,blend') t.doCreateRenderPipelineTest(isAsync, _success, descriptor); }); +g.test('pipeline_output_targets,format_blendable') + .desc( + ` +Tests if blending is used, the target's format must be blendable (support "float" sample type). +- For all the formats, test that blending can be enabled if and only if the format is blendable.` + ) + .params(u => + u.combine('isAsync', [false, true]).combine('format', kRenderableColorTextureFormats) + ) + .fn(async t => { + const { isAsync, format } = t.params; + const info = kTextureFormatInfo[format]; + await t.selectDeviceOrSkipTestCase(info.feature); + + const _success = info.sampleType === 'float'; + + const blendComponent: GPUBlendComponent = { + srcFactor: 'src-alpha', + dstFactor: 'dst-alpha', + operation: 'add', + }; + t.doCreateRenderPipelineTest( + isAsync, + _success, + t.getDescriptor({ + targets: [ + { + format, + blend: { + color: blendComponent, + alpha: blendComponent, + }, + }, + ], + fragmentShaderCode: t.getFragmentShaderCode('float', 4), + }) + ); + }); + +g.test('pipeline_output_targets,blend_min_max') + .desc( + ` + For the blend components on either GPUBlendState.color or GPUBlendState.alpha: + - Tests if the combination of 'srcFactor', 'dstFactor' and 'operation' is valid (if the blend + operation is "min" or "max", srcFactor and dstFactor must be "one"). + ` + ) + .params(u => + u + .combine('isAsync', [false, true]) + .combine('component', ['color', 'alpha'] as const) + .beginSubcases() + .combine('srcFactor', kBlendFactors) + .combine('dstFactor', kBlendFactors) + .combine('operation', kBlendOperations) + ) + .fn(async t => { + const { isAsync, component, srcFactor, dstFactor, operation } = t.params; + + const defaultBlendComponent: GPUBlendComponent = { + srcFactor: 'src-alpha', + dstFactor: 'dst-alpha', + operation: 'add', + }; + const blendComponentToTest = { + srcFactor, + dstFactor, + operation, + }; + const fragmentShaderCode = t.getFragmentShaderCode('float', 4); + const format = 'rgba8unorm'; + + const descriptor = t.getDescriptor({ + targets: [ + { + format, + blend: { + color: component === 'color' ? blendComponentToTest : defaultBlendComponent, + alpha: component === 'alpha' ? blendComponentToTest : defaultBlendComponent, + }, + }, + ], + fragmentShaderCode, + }); + + if (operation === 'min' || operation === 'max') { + const _success = srcFactor === 'one' && dstFactor === 'one'; + t.doCreateRenderPipelineTest(isAsync, _success, descriptor); + } else { + t.doCreateRenderPipelineTest(isAsync, true, descriptor); + } + }); + g.test('pipeline_layout,device_mismatch') .desc( 'Tests createRenderPipeline(Async) cannot be called with a pipeline layout created from another device' @@ -432,7 +678,7 @@ g.test('pipeline_layout,device_mismatch') vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); } `, @@ -468,7 +714,7 @@ g.test('shader_module,device_mismatch') } const code = ` - [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); } `; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createTexture.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createTexture.spec.ts index aca0756efcc..f2847acb456 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createTexture.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createTexture.spec.ts @@ -121,29 +121,35 @@ g.test('mipLevelCount,format') .combine('dimension', [undefined, ...kTextureDimensions]) .beginSubcases() .combine('format', kTextureFormats) - .combine('mipLevelCount', [1, 3, 6, 7]) + .combine('mipLevelCount', [1, 2, 3, 6, 7]) // Filter out incompatible dimension type and format combinations. .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) + .combine('largestDimension', [0, 1, 2]) + .unless(({ dimension, largestDimension }) => dimension === '1d' && largestDimension > 0) ) .fn(async t => { - const { dimension, format, mipLevelCount } = t.params; + const { dimension, format, mipLevelCount, largestDimension } = t.params; const info = kTextureFormatInfo[format]; await t.selectDeviceOrSkipTestCase(info.feature); // Compute dimensions such that the dimensions are in range [17, 32] and aligned with the // format block size so that there will be exactly 6 mip levels. - const maxMipLevelCount = 5; - const textureWidth = - Math.floor(((1 << maxMipLevelCount) - 1) / info.blockWidth) * info.blockWidth; - const textureHeight = - Math.floor(((1 << maxMipLevelCount) - 1) / info.blockHeight) * info.blockHeight; - assert(17 <= textureWidth && textureWidth <= 32); - assert(17 <= textureHeight && textureHeight <= 32); + const kTargetMipLevelCount = 5; + const kTargetLargeSize = (1 << kTargetMipLevelCount) - 1; + const largeSize = [ + Math.floor(kTargetLargeSize / info.blockWidth) * info.blockWidth, + Math.floor(kTargetLargeSize / info.blockHeight) * info.blockHeight, + kTargetLargeSize, + ]; + assert(17 <= largeSize[0] && largeSize[0] <= 32); + assert(17 <= largeSize[1] && largeSize[1] <= 32); // Note that compressed formats are not valid for 1D. They have already been filtered out for 1D // in this test. So there is no dilemma about size.width equals 1 vs // size.width % info.blockHeight equals 0 for 1D compressed formats. - const size = dimension === '1d' ? [textureWidth, 1, 1] : [textureWidth, textureHeight, 1]; + const size = [info.blockWidth, info.blockHeight, 1]; + size[largestDimension] = largeSize[largestDimension]; + const descriptor = { size, mipLevelCount, @@ -152,7 +158,7 @@ g.test('mipLevelCount,format') usage: GPUTextureUsage.TEXTURE_BINDING, }; - const success = mipLevelCount <= maxMipLevelCount; + const success = mipLevelCount <= maxMipLevelCount(descriptor); t.expectValidationError(() => { t.device.createTexture(descriptor); @@ -284,11 +290,12 @@ g.test('sampleCount,valid_sampleCount_with_other_parameter_varies') .combine('usage', kTextureUsages) // Filter out incompatible dimension type and format combinations. .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) - .unless(({ usage, format }) => { + .unless(({ usage, format, mipLevelCount, dimension }) => { const info = kTextureFormatInfo[format]; return ( ((usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 && !info.renderable) || - ((usage & GPUConst.TextureUsage.STORAGE_BINDING) !== 0 && !info.storage) + ((usage & GPUConst.TextureUsage.STORAGE_BINDING) !== 0 && !info.storage) || + (mipLevelCount !== 1 && dimension === '1d') ); }) ) @@ -398,8 +405,8 @@ g.test('texture_size,1d_texture') .desc(`Test texture size requirement for 1D texture`) .paramsSubcasesOnly(u => u // - // Compressed textures are invalid for 1D. - .combine('format', kUncompressedTextureFormats) + // Compressed and depth-stencil textures are invalid for 1D. + .combine('format', kRegularTextureFormats) .combine('width', [ DefaultLimits.maxTextureDimension1D - 1, DefaultLimits.maxTextureDimension1D, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createView.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createView.spec.ts index e26ce370baf..0a8dd416f9e 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createView.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/createView.spec.ts @@ -163,6 +163,7 @@ g.test('array_layers') .beginSubcases() .expand('textureLayers', ({ textureDimension: d }) => (d === '2d' ? [1, 6, 18] : [1])) .combine('textureLevels', [1, kLevels]) + .unless(p => p.textureDimension === '1d' && p.textureLevels !== 1) .expand( 'baseArrayLayer', ({ textureLayers: l }) => new Set([undefined, 0, 1, 5, 6, 7, l - 1, l, l + 1]) @@ -222,6 +223,7 @@ g.test('mip_levels') kTextureAndViewDimensions .beginSubcases() .combine('textureLevels', [1, kLevels - 2, kLevels]) + .unless(p => p.textureDimension === '1d' && p.textureLevels !== 1) .expand( 'baseMipLevel', ({ textureLevels: l }) => new Set([undefined, 0, 1, 5, 6, 7, l - 1, l, l + 1]) diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/compute_pass.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/compute_pass.spec.ts index d5fb06d34db..91aaa687a8c 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/compute_pass.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/compute_pass.spec.ts @@ -123,9 +123,6 @@ indirectBuffer with 6 elements. - valid, within the buffer: {beginning, middle, end} of the buffer - invalid, non-multiple of 4 - invalid, the last element is outside the buffer - -TODO: test specifically which call the validation error occurs in. - (Should be finish() for invalid, but submit() for destroyed.) ` ) .paramsSubcasesOnly(u => diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts index 0a46b6ebbaa..a23ee398dc4 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts @@ -1,39 +1,5 @@ export const description = ` copyTextureToTexture tests. - -Test Plan: (TODO(jiawei.shao@intel.com): add tests on 1D/3D textures) -* the source and destination texture - - the {source, destination} texture is {invalid, valid}. - - mipLevel {>, =, <} the mipmap level count of the {source, destination} texture. - - the source texture is created {with, without} GPUTextureUsage::CopySrc. - - the destination texture is created {with, without} GPUTextureUsage::CopyDst. -* sample count - - the sample count of the source texture {is, isn't} equal to the one of the destination texture - - when the sample count is greater than 1: - - it {is, isn't} a copy of the whole subresource of the source texture. - - it {is, isn't} a copy of the whole subresource of the destination texture. -* texture format - - the format of the source texture {is, isn't} equal to the one of the destination texture. - - including: depth24plus-stencil8 to/from {depth24plus, stencil8}. - - for each depth and/or stencil format: a copy between two textures with same format: - - it {is, isn't} a copy of the whole subresource of the {source, destination} texture. -* copy ranges - - if the texture dimension is 2D: - - (srcOrigin.x + copyExtent.width) {>, =, <} the width of the subresource size of source - textureCopyView. - - (srcOrigin.y + copyExtent.height) {>, =, <} the height of the subresource size of source - textureCopyView. - - (srcOrigin.z + copyExtent.depthOrArrayLayers) {>, =, <} the depthOrArrayLayers of the subresource size of source - textureCopyView. - - (dstOrigin.x + copyExtent.width) {>, =, <} the width of the subresource size of destination - textureCopyView. - - (dstOrigin.y + copyExtent.height) {>, =, <} the height of the subresource size of destination - textureCopyView. - - (dstOrigin.z + copyExtent.depthOrArrayLayers) {>, =, <} the depthOrArrayLayers of the subresource size of destination - textureCopyView. -* when the source and destination texture are the same one: - - the set of source texture subresources {has, doesn't have} overlaps with the one of destination - texture subresources. `; import { makeTestGroup } from '../../../../../common/framework/test_group.js'; @@ -43,6 +9,8 @@ import { kCompressedTextureFormats, kDepthStencilFormats, kTextureUsages, + textureDimensionAndFormatCompatible, + kTextureDimensions, } from '../../../../capability_info.js'; import { align } from '../../../../util/math.js'; import { ValidationTest } from '../../validation_test.js'; @@ -63,6 +31,7 @@ class F extends ValidationTest { } GetPhysicalSubresourceSize( + dimension: GPUTextureDimension, textureSize: Required<GPUExtent3DDict>, format: GPUTextureFormat, mipLevel: number @@ -74,42 +43,56 @@ class F extends ValidationTest { virtualHeightAtLevel, kTextureFormatInfo[format].blockHeight ); - return { - width: physicalWidthAtLevel, - height: physicalHeightAtLevel, - depthOrArrayLayers: textureSize.depthOrArrayLayers, - }; + + switch (dimension) { + case '1d': + return { width: physicalWidthAtLevel, height: 1, depthOrArrayLayers: 1 }; + case '2d': + return { + width: physicalWidthAtLevel, + height: physicalHeightAtLevel, + depthOrArrayLayers: textureSize.depthOrArrayLayers, + }; + case '3d': + return { + width: physicalWidthAtLevel, + height: physicalHeightAtLevel, + depthOrArrayLayers: Math.max(textureSize.depthOrArrayLayers >> mipLevel, 1), + }; + } } } export const g = makeTestGroup(F); -g.test('copy_with_invalid_texture').fn(async t => { - const validTexture = t.device.createTexture({ - size: { width: 4, height: 4, depthOrArrayLayers: 1 }, - format: 'rgba8unorm', - usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, - }); +g.test('copy_with_invalid_texture') + .desc('Test copyTextureToTexture is an error when one of the textures is invalid.') + .fn(async t => { + const validTexture = t.device.createTexture({ + size: { width: 4, height: 4, depthOrArrayLayers: 1 }, + format: 'rgba8unorm', + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, + }); - const errorTexture = t.getErrorTexture(); - - t.TestCopyTextureToTexture( - { texture: errorTexture }, - { texture: validTexture }, - { width: 1, height: 1, depthOrArrayLayers: 1 }, - false - ); - t.TestCopyTextureToTexture( - { texture: validTexture }, - { texture: errorTexture }, - { width: 1, height: 1, depthOrArrayLayers: 1 }, - false - ); -}); + const errorTexture = t.getErrorTexture(); + + t.TestCopyTextureToTexture( + { texture: errorTexture }, + { texture: validTexture }, + { width: 1, height: 1, depthOrArrayLayers: 1 }, + false + ); + t.TestCopyTextureToTexture( + { texture: validTexture }, + { texture: errorTexture }, + { width: 1, height: 1, depthOrArrayLayers: 1 }, + false + ); + }); g.test('texture,device_mismatch') .desc( - 'Tests copyTextureToTexture cannot be called with src texture or dst texture created from another device' + 'Tests copyTextureToTexture cannot be called with src texture or dst texture created from another device.' ) .paramsSubcasesOnly([ { srcMismatched: false, dstMismatched: false }, // control case @@ -119,28 +102,44 @@ g.test('texture,device_mismatch') .unimplemented(); g.test('mipmap_level') - .paramsSubcasesOnly([ - { srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 0, dstCopyLevel: 0 }, - { srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 1, dstCopyLevel: 0 }, - { srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 0, dstCopyLevel: 1 }, - { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 0 }, - { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 2, dstCopyLevel: 0 }, - { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 3, dstCopyLevel: 0 }, - { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 2 }, - { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 3 }, - ] as const) + .desc( + ` +Test copyTextureToTexture must specify mipLevels that are in range. +- for various dimensions +- for varioues mip level count in the texture +- for various copy target mip level (in range and not in range) +` + ) + .params(u => + u // + .combine('dimension', kTextureDimensions) + .beginSubcases() + .combineWithParams([ + { srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 0, dstCopyLevel: 0 }, + { srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 1, dstCopyLevel: 0 }, + { srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 0, dstCopyLevel: 1 }, + { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 0 }, + { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 2, dstCopyLevel: 0 }, + { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 3, dstCopyLevel: 0 }, + { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 2 }, + { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 3 }, + ] as const) + .unless(p => p.dimension === '1d' && (p.srcLevelCount !== 1 || p.dstLevelCount !== 1)) + ) .fn(async t => { - const { srcLevelCount, dstLevelCount, srcCopyLevel, dstCopyLevel } = t.params; + const { srcLevelCount, dstLevelCount, srcCopyLevel, dstCopyLevel, dimension } = t.params; const srcTexture = t.device.createTexture({ - size: { width: 32, height: 32, depthOrArrayLayers: 1 }, + size: { width: 32, height: 1, depthOrArrayLayers: 1 }, + dimension, format: 'rgba8unorm', usage: GPUTextureUsage.COPY_SRC, mipLevelCount: srcLevelCount, }); const dstTexture = t.device.createTexture({ - size: { width: 32, height: 32, depthOrArrayLayers: 1 }, + size: { width: 32, height: 1, depthOrArrayLayers: 1 }, + dimension, format: 'rgba8unorm', usage: GPUTextureUsage.COPY_DST, mipLevelCount: dstLevelCount, @@ -156,6 +155,13 @@ g.test('mipmap_level') }); g.test('texture_usage') + .desc( + ` +Test that copyTextureToTexture source/destination need COPY_SRC/COPY_DST usages. +- for all possible source texture usages +- for all possible destination texture usages +` + ) .paramsSubcasesOnly(u => u // .combine('srcUsage', kTextureUsages) @@ -187,6 +193,13 @@ g.test('texture_usage') }); g.test('sample_count') + .desc( + ` +Test that textures in copyTextureToTexture must have the same sample count. +- for various source texture sample count +- for various destination texture sample count +` + ) .paramsSubcasesOnly(u => u // .combine('srcSampleCount', [1, 4]) @@ -218,6 +231,15 @@ g.test('sample_count') }); g.test('multisampled_copy_restrictions') + .desc( + ` +Test that copyTextureToTexture of multisampled texture must copy a whole subresource to a whole subresource. +- for various origin for the source and destination of the copies. + +Note: this is only tested for 2D textures as it is the only dimension compatible with multisampling. +TODO: Check the source and destination constraints separately. +` + ) .paramsSubcasesOnly(u => u // .combine('srcCopyOrigin', [ @@ -265,7 +287,14 @@ g.test('multisampled_copy_restrictions') ); }); -g.test('texture_format_equality') +g.test('texture_format_compatibility') + .desc( + ` +Test the formats of textures in copyTextureToTexture must be copy-compatible. +- for all source texture formats +- for all destination texture formats +` + ) .paramsSubcasesOnly(u => u // .combine('srcFormat', kTextureFormats) @@ -291,7 +320,11 @@ g.test('texture_format_equality') usage: GPUTextureUsage.COPY_DST, }); - const isSuccess = srcFormat === dstFormat; + // Allow copy between compatible format textures. + const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat ?? srcFormat; + const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat ?? dstFormat; + const isSuccess = srcBaseFormat === dstBaseFormat; + t.TestCopyTextureToTexture( { texture: srcTexture }, { texture: dstTexture }, @@ -301,6 +334,17 @@ g.test('texture_format_equality') }); g.test('depth_stencil_copy_restrictions') + .desc( + ` +Test that depth textures subresources must be entirely copied in copyTextureToTexture +- for various depth-stencil formats +- for various copy origin and size offsets +- for various source and destination texture sizes +- for various source and destination mip levels + +Note: this is only tested for 2D textures as it is the only dimension compatible with depth-stencil. +` + ) .params(u => u .combine('format', kDepthStencilFormats) @@ -351,8 +395,8 @@ g.test('depth_stencil_copy_restrictions') usage: GPUTextureUsage.COPY_DST, }); - const srcSizeAtLevel = t.GetPhysicalSubresourceSize(srcTextureSize, format, srcCopyLevel); - const dstSizeAtLevel = t.GetPhysicalSubresourceSize(dstTextureSize, format, dstCopyLevel); + const srcSizeAtLevel = t.GetPhysicalSubresourceSize('2d', srcTextureSize, format, srcCopyLevel); + const dstSizeAtLevel = t.GetPhysicalSubresourceSize('2d', dstTextureSize, format, dstCopyLevel); const copyOrigin = { x: copyBoxOffsets.x, y: copyBoxOffsets.y, z: 0 }; @@ -384,8 +428,18 @@ g.test('depth_stencil_copy_restrictions') }); g.test('copy_ranges') - .paramsSubcasesOnly(u => - u // + .desc( + ` +Test that copyTextureToTexture copy boxes must be in range of the subresource. +- for various dimensions +- for various offsets to a full copy for the copy origin/size +- for various copy mip levels +` + ) + .params(u => + u + .combine('dimension', kTextureDimensions) + //.beginSubcases() .combine('copyBoxOffsets', [ { x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 }, { x: 1, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 }, @@ -401,31 +455,57 @@ g.test('copy_ranges') { x: 0, y: 0, z: 1, width: 0, height: 0, depthOrArrayLayers: -1 }, { x: 0, y: 0, z: 2, width: 0, height: 0, depthOrArrayLayers: -1 }, ]) + .unless( + p => + p.dimension === '1d' && + (p.copyBoxOffsets.y !== 0 || + p.copyBoxOffsets.z !== 0 || + p.copyBoxOffsets.height !== 0 || + p.copyBoxOffsets.depthOrArrayLayers !== 0) + ) .combine('srcCopyLevel', [0, 1, 3]) .combine('dstCopyLevel', [0, 1, 3]) + .unless(p => p.dimension === '1d' && (p.srcCopyLevel !== 0 || p.dstCopyLevel !== 0)) ) .fn(async t => { - const { copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params; - - const kTextureSize = { width: 16, height: 8, depthOrArrayLayers: 3 }; - const kMipLevelCount = 4; + const { dimension, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params; + + const textureSize = { width: 16, height: 8, depthOrArrayLayers: 3 }; + let mipLevelCount = 4; + if (dimension === '1d') { + mipLevelCount = 1; + textureSize.height = 1; + textureSize.depthOrArrayLayers = 1; + } const kFormat = 'rgba8unorm'; const srcTexture = t.device.createTexture({ - size: kTextureSize, + size: textureSize, format: kFormat, - mipLevelCount: kMipLevelCount, + dimension, + mipLevelCount, usage: GPUTextureUsage.COPY_SRC, }); const dstTexture = t.device.createTexture({ - size: kTextureSize, + size: textureSize, format: kFormat, - mipLevelCount: kMipLevelCount, + dimension, + mipLevelCount, usage: GPUTextureUsage.COPY_DST, }); - const srcSizeAtLevel = t.GetPhysicalSubresourceSize(kTextureSize, kFormat, srcCopyLevel); - const dstSizeAtLevel = t.GetPhysicalSubresourceSize(kTextureSize, kFormat, dstCopyLevel); + const srcSizeAtLevel = t.GetPhysicalSubresourceSize( + dimension, + textureSize, + kFormat, + srcCopyLevel + ); + const dstSizeAtLevel = t.GetPhysicalSubresourceSize( + dimension, + textureSize, + kFormat, + dstCopyLevel + ); const copyOrigin = { x: copyBoxOffsets.x, y: copyBoxOffsets.y, z: copyBoxOffsets.z }; @@ -438,15 +518,26 @@ g.test('copy_ranges') 0 ); const copyDepth = - kTextureSize.depthOrArrayLayers + copyBoxOffsets.depthOrArrayLayers - copyOrigin.z; + textureSize.depthOrArrayLayers + copyBoxOffsets.depthOrArrayLayers - copyOrigin.z; { - const isSuccess = + let isSuccess = copyWidth <= srcSizeAtLevel.width && copyHeight <= srcSizeAtLevel.height && copyOrigin.x + copyWidth <= dstSizeAtLevel.width && - copyOrigin.y + copyHeight <= dstSizeAtLevel.height && - copyOrigin.z + copyDepth <= kTextureSize.depthOrArrayLayers; + copyOrigin.y + copyHeight <= dstSizeAtLevel.height; + + if (dimension === '3d') { + isSuccess = + isSuccess && + copyDepth <= srcSizeAtLevel.depthOrArrayLayers && + copyOrigin.z + copyDepth <= dstSizeAtLevel.depthOrArrayLayers; + } else { + isSuccess = + isSuccess && + copyDepth <= textureSize.depthOrArrayLayers && + copyOrigin.z + copyDepth <= textureSize.depthOrArrayLayers; + } t.TestCopyTextureToTexture( { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: srcCopyLevel }, @@ -457,12 +548,23 @@ g.test('copy_ranges') } { - const isSuccess = + let isSuccess = copyOrigin.x + copyWidth <= srcSizeAtLevel.width && copyOrigin.y + copyHeight <= srcSizeAtLevel.height && copyWidth <= dstSizeAtLevel.width && - copyHeight <= dstSizeAtLevel.height && - copyOrigin.z + copyDepth <= kTextureSize.depthOrArrayLayers; + copyHeight <= dstSizeAtLevel.height; + + if (dimension === '3d') { + isSuccess = + isSuccess && + copyDepth <= dstSizeAtLevel.depthOrArrayLayers && + copyOrigin.z + copyDepth <= srcSizeAtLevel.depthOrArrayLayers; + } else { + isSuccess = + isSuccess && + copyDepth <= textureSize.depthOrArrayLayers && + copyOrigin.z + copyDepth <= textureSize.depthOrArrayLayers; + } t.TestCopyTextureToTexture( { texture: srcTexture, origin: copyOrigin, mipLevel: srcCopyLevel }, @@ -474,6 +576,15 @@ g.test('copy_ranges') }); g.test('copy_within_same_texture') + .desc( + ` +Test that it is an error to use copyTextureToTexture from one subresource to itself. +- for various starting source/destination array layers. +- for various copy sizes in number of array layers + +TODO: Extend to check the copy is allowed between different mip levels. +TODO: Extend to 1D and 3D textures.` + ) .paramsSubcasesOnly(u => u // .combine('srcCopyOriginZ', [0, 2, 4]) @@ -535,7 +646,7 @@ Test the validations on the member 'aspect' of GPUImageCopyTexture in CopyTextur usage: GPUTextureUsage.COPY_DST, }); - // TODO(jiawei.shao@intel.com): get the valid aspects from capability_info.ts. + // MAINTENANCE_TODO: get the valid aspects from capability_info.ts. const kValidAspectsForFormat = { rgba8unorm: ['all'], @@ -563,9 +674,21 @@ Test the validations on the member 'aspect' of GPUImageCopyTexture in CopyTextur }); g.test('copy_ranges_with_compressed_texture_formats') + .desc( + ` +Test that copyTextureToTexture copy boxes must be in range of the subresource and aligned to the block size +- for various dimensions +- for various offsets to a full copy for the copy origin/size +- for various copy mip levels + +TODO: Express the offsets in "block size" so as to be able to test non-4x4 compressed formats +` + ) .params(u => u .combine('format', kCompressedTextureFormats) + .combine('dimension', kTextureDimensions) + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .beginSubcases() .combine('copyBoxOffsets', [ { x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 }, @@ -584,7 +707,7 @@ g.test('copy_ranges_with_compressed_texture_formats') .combine('dstCopyLevel', [0, 1, 2]) ) .fn(async t => { - const { format, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params; + const { format, dimension, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params; await t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature); const { blockWidth, blockHeight } = kTextureFormatInfo[format]; @@ -598,18 +721,30 @@ g.test('copy_ranges_with_compressed_texture_formats') const srcTexture = t.device.createTexture({ size: kTextureSize, format, + dimension, mipLevelCount: kMipLevelCount, usage: GPUTextureUsage.COPY_SRC, }); const dstTexture = t.device.createTexture({ size: kTextureSize, format, + dimension, mipLevelCount: kMipLevelCount, usage: GPUTextureUsage.COPY_DST, }); - const srcSizeAtLevel = t.GetPhysicalSubresourceSize(kTextureSize, format, srcCopyLevel); - const dstSizeAtLevel = t.GetPhysicalSubresourceSize(kTextureSize, format, dstCopyLevel); + const srcSizeAtLevel = t.GetPhysicalSubresourceSize( + dimension, + kTextureSize, + format, + srcCopyLevel + ); + const dstSizeAtLevel = t.GetPhysicalSubresourceSize( + dimension, + kTextureSize, + format, + dstCopyLevel + ); const copyOrigin = { x: copyBoxOffsets.x, y: copyBoxOffsets.y, z: copyBoxOffsets.z }; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/index_access.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/index_access.spec.ts index 904eafed336..93a49881ed6 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/index_access.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/index_access.spec.ts @@ -1,10 +1,5 @@ export const description = ` -indexed draws validation tests. - -TODO: Implement robustness test or operation test for drawIndexedIndirect. This validation test -only test that drawIndexed can catch index buffer OOB and generate a validation error, but for -drawIndexedIndirect no CPU validation is available, and the robustness access in that case should -be tested. +Validation tests for indexed draws accessing the index buffer. `; import { makeTestGroup } from '../../../../../common/framework/test_group.js'; @@ -20,7 +15,7 @@ class F extends ValidationTest { vertex: { module: this.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); }`, }), @@ -29,7 +24,7 @@ class F extends ValidationTest { fragment: { module: this.device.createShaderModule({ code: ` - [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.0, 1.0, 0.0, 1.0); }`, }), diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/render/draw.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/render/draw.spec.ts index a78ccd1060b..bbb8ac6368c 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/render/draw.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/render/draw.spec.ts @@ -5,13 +5,50 @@ and parameters as expect. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; import { ValidationTest } from '../../../validation_test.js'; -class F extends ValidationTest { - // TODO: Implement the helper functions +interface DrawIndexedParameter { + indexCount: number; + instanceCount?: number; + firstIndex?: number; + baseVertex?: number; + firstInstance?: number; } -export const g = makeTestGroup(F); +function callDrawIndexed( + test: GPUTest, + encoder: GPURenderEncoderBase, + drawType: 'drawIndexed' | 'drawIndexedIndirect', + param: DrawIndexedParameter +) { + switch (drawType) { + case 'drawIndexed': { + encoder.drawIndexed( + param.indexCount, + param.instanceCount ?? 1, + param.firstIndex ?? 0, + param.baseVertex ?? 0, + param.firstInstance ?? 0 + ); + break; + } + case 'drawIndexedIndirect': { + const indirectArray = new Int32Array([ + param.indexCount, + param.instanceCount ?? 1, + param.firstIndex ?? 0, + param.baseVertex ?? 0, + param.firstInstance ?? 0, + ]); + const indirectBuffer = test.makeBufferWithContents(indirectArray, GPUBufferUsage.INDIRECT); + encoder.drawIndexedIndirect(indirectBuffer, 0); + break; + } + } +} + +export const g = makeTestGroup(ValidationTest); g.test(`unused_buffer_bound`) .desc( @@ -35,7 +72,59 @@ drawIndexedIndirect as it is GPU-validated. - range and GPUBuffer are both too small ` ) - .unimplemented(); + .params(u => + u + .combine('bufferSizeInElements', [10, 100]) + // Binding size is always no larger than buffer size, make sure that setIndexBuffer succeed + .combine('bindingSizeInElements', [10]) + .combine('drawIndexCount', [10, 11]) + .combine('drawType', ['drawIndexed', 'drawIndexedIndirect'] as const) + .beginSubcases() + .combine('indexFormat', ['uint16', 'uint32'] as const) + .combine('useBundle', [false, true]) + ) + .fn(t => { + const { + indexFormat, + bindingSizeInElements, + bufferSizeInElements, + drawIndexCount, + drawType, + useBundle, + } = t.params; + + const indexElementSize = indexFormat === 'uint16' ? 2 : 4; + const bindingSize = bindingSizeInElements * indexElementSize; + const bufferSize = bufferSizeInElements * indexElementSize; + + const desc: GPUBufferDescriptor = { + size: bufferSize, + usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST, + }; + const indexBuffer = t.device.createBuffer(desc); + + const drawCallParam: DrawIndexedParameter = { + indexCount: drawIndexCount, + }; + + // Encoder finish will succeed if no index buffer access OOB when calling drawIndexed, + // and always succeed when calling drawIndexedIndirect. + const isFinishSuccess = + drawIndexCount <= bindingSizeInElements || drawType === 'drawIndexedIndirect'; + + const renderPipeline = t.createNoOpRenderPipeline(); + + const commandBufferMaker = t.createEncoder(useBundle ? 'render bundle' : 'render pass'); + const renderEncoder = commandBufferMaker.encoder; + + renderEncoder.setIndexBuffer(indexBuffer, indexFormat, 0, bindingSize); + + renderEncoder.setPipeline(renderPipeline); + + callDrawIndexed(t, renderEncoder, drawType, drawCallParam); + + commandBufferMaker.validateFinishAndSubmit(isFinishSuccess, true); + }); g.test(`vertex_buffer_OOB`) .desc( diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/render/dynamic_state.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/render/dynamic_state.spec.ts index 821312c832f..9345f3dea8c 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/render/dynamic_state.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/render/dynamic_state.spec.ts @@ -131,7 +131,12 @@ class F extends ValidationTest { export const g = makeTestGroup(F); g.test('setViewport,x_y_width_height_nonnegative') - .desc('Test that the parameters of setViewport to define the box must be non-negative.') + .desc( + `Test that the parameters of setViewport to define the box must be non-negative. + +TODO Test -0 (it should be valid) but can't be tested because the harness complains about duplicate parameters. +TODO Test the first value smaller than -0` + ) .paramsSubcasesOnly([ // Control case: everything to 0 is ok, covers the empty viewport case. { x: 0, y: 0, w: 0, h: 0 }, @@ -141,9 +146,6 @@ g.test('setViewport,x_y_width_height_nonnegative') { x: 0, y: -1, w: 0, h: 0 }, { x: 0, y: 0, w: -1, h: 0 }, { x: 0, y: 0, w: 0, h: -1 }, - - // TODO Test -0 (it should be valid) but can't be tested because the harness complains about duplicate parameters. - // TODO Test the first value smaller than -0 ]) .fn(t => { const { x, y, w, h } = t.params; @@ -220,7 +222,10 @@ g.test('setViewport,depth_rangeAndOrder') g.test('setScissorRect,x_y_width_height_nonnegative') .desc( - 'Test that the parameters of setScissorRect to define the box must be non-negative or a TypeError is thrown.' + `Test that the parameters of setScissorRect to define the box must be non-negative or a TypeError is thrown. + +TODO Test -0 (it should be valid) but can't be tested because the harness complains about duplicate parameters. +TODO Test the first value smaller than -0` ) .paramsSubcasesOnly([ // Control case: everything to 0 is ok, covers the empty scissor case. @@ -231,9 +236,6 @@ g.test('setScissorRect,x_y_width_height_nonnegative') { x: 0, y: -1, w: 0, h: 0 }, { x: 0, y: 0, w: -1, h: 0 }, { x: 0, y: 0, w: 0, h: -1 }, - - // TODO Test -0 (it should be valid) but can't be tested because the harness complains about duplicate parameters. - // TODO Test the first value smaller than -0 ]) .fn(t => { const { x, y, w, h } = t.params; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/render/state_tracking.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/render/state_tracking.spec.ts index 1973012914d..4ad14b86fc2 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/render/state_tracking.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/render/state_tracking.spec.ts @@ -20,10 +20,10 @@ class F extends ValidationTest { module: this.device.createShaderModule({ code: ` struct Inputs { - ${range(bufferCount, i => `\n[[location(${i})]] a_position${i} : vec3<f32>;`).join('')} + ${range(bufferCount, i => `\n@location(${i}) a_position${i} : vec3<f32>;`).join('')} }; - [[stage(vertex)]] fn main(input : Inputs - ) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main(input : Inputs + ) -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 1.0); }`, }), @@ -42,7 +42,7 @@ class F extends ValidationTest { fragment: { module: this.device.createShaderModule({ code: ` - [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.0, 1.0, 0.0, 1.0); }`, }), diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/setBindGroup.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/setBindGroup.spec.ts index ee64fab44f1..3d888c1761e 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/setBindGroup.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/cmds/setBindGroup.spec.ts @@ -119,7 +119,7 @@ g.test('state_and_binding_index') validateFinishAndSubmit(state !== 'invalid' && index < maxBindGroups, state !== 'destroyed'); } - // TODO: move to subcases() once we can query the device limits + // MAINTENANCE_TODO: move to subcases() once we can query the device limits for (const index of [1, maxBindGroups - 1, maxBindGroups]) { t.debug(`test bind group index ${index}`); await runTest(index); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts index 29ec076ffe9..fc8ba255b1e 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts @@ -8,6 +8,7 @@ TODO: - x= all relevant stages TODO: subsume existing test, rewrite fixture as needed. +TODO: Add externalTexture to kResourceTypes [1] `; import { kUnitCaseParamsBuilder } from '../../../../../common/framework/params_builder.js'; @@ -32,7 +33,7 @@ const kRenderCmds = ['draw', 'drawIndexed', 'drawIndirect', 'drawIndexedIndirect type RenderCmd = typeof kRenderCmds[number]; // Test resource type compatibility in pipeline and bind group -// TODO: Add externalTexture +// [1]: Need to add externalTexture const kResourceTypes: ValidBindableResource[] = [ 'uniformBuf', 'filtSamp', @@ -82,11 +83,11 @@ class F extends ValidationTest { bindGroups: Array<Array<GPUBindGroupLayoutEntry>> ): GPURenderPipeline { const shader = ` - [[stage(vertex)]] fn vs_main() -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn vs_main() -> @builtin(position) vec4<f32> { return vec4<f32>(1.0, 1.0, 0.0, 1.0); } - [[stage(fragment)]] fn fs_main() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(0.0, 1.0, 0.0, 1.0); } `; @@ -114,7 +115,7 @@ class F extends ValidationTest { ): GPUComputePipeline { const shader = ` [[stage(compute), workgroup_size(1, 1, 1)]] - fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) { + fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) { } `; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/queries/begin_end.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/queries/begin_end.spec.ts index 7ec04d788b5..ec7dc906c86 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/queries/begin_end.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/queries/begin_end.spec.ts @@ -1,6 +1,7 @@ export const description = ` Validation for encoding begin/endable queries. +TODO: pipeline statistics queries are removed from core; consider moving tests to another suite. TODO: tests for pipeline statistics queries: - balance: { - begin 0, end 1 diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/queries/general.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/queries/general.spec.ts index bdd0d2488bd..673c40606fb 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/queries/general.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/queries/general.spec.ts @@ -1,6 +1,6 @@ export const description = ` +TODO: pipeline statistics queries are removed from core; consider moving tests to another suite. TODO: - - Start a pipeline statistics query in all possible encoders: - queryIndex {in, out of} range for GPUQuerySet - GPUQuerySet {valid, invalid, device mismatched} diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/queries/pipeline_statistics.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/queries/pipeline_statistics.spec.ts index 9acd23b883d..5827f460581 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/queries/pipeline_statistics.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/encoding/queries/pipeline_statistics.spec.ts @@ -3,6 +3,7 @@ Validation for encoding pipeline statistics queries. Excludes query begin/end balance and nesting (begin_end.spec.ts) and querySet/queryIndex (general.spec.ts). +TODO: pipeline statistics queries are removed from core; consider moving tests to another suite. TODO: - Test pipelineStatistics with {undefined, empty, duplicated, full (control case)} values `; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/error_scope.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/error_scope.spec.ts index e95c480c390..f6cc82720b2 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/error_scope.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/error_scope.spec.ts @@ -43,7 +43,7 @@ class F extends Fixture { size: 1024, usage: 0xffff, // Invalid GPUBufferUsage }); - // TODO: Remove when chrome does it automatically. + // MAINTENANCE_TODO: This is a workaround for Chromium not flushing. Remove when not needed. this.device.queue.submit([]); } @@ -51,7 +51,7 @@ class F extends Fixture { // otherwise it could erroneously pass by capturing an error from later in the test. async expectUncapturedError(fn: Function): Promise<GPUUncapturedErrorEvent> { return this.immediateAsyncExpectation(() => { - // TODO: Make arbitrary timeout value a test runner variable + // MAINTENANCE_TODO: Make arbitrary timeout value a test runner variable const TIMEOUT_IN_MS = 1000; const promise: Promise<GPUUncapturedErrorEvent> = new Promise(resolve => { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/buffer_related.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/buffer_related.spec.ts index cb76a9d1e68..b2cefd49c35 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/buffer_related.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/buffer_related.spec.ts @@ -1,8 +1,9 @@ -export const description = `Validation tests for buffer related parameters for copies`; +export const description = `Validation tests for buffer related parameters for buffer <-> texture copies`; import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { kSizedTextureFormats, + kTextureDimensions, kTextureFormatInfo, textureDimensionAndFormatCompatible, } from '../../../capability_info.js'; @@ -15,7 +16,13 @@ import { ImageCopyTest, formatCopyableWithMethod } from './image_copy.js'; export const g = makeTestGroup(ImageCopyTest); g.test('valid') - .desc(`The buffer must be valid and not destroyed.`) + .desc( + ` +Test that the buffer must be valid and not destroyed. +- for all buffer <-> texture copy methods +- for various buffer states +` + ) .params(u => u // // B2B copy validations are at api,validation,encoding,cmds,copyBufferToBuffer.spec.ts @@ -50,7 +57,14 @@ g.test('valid') }); g.test('usage') - .desc(`The buffer must have the appropriate COPY_SRC/COPY_DST usage.`) + .desc( + ` +Test the buffer must have the appropriate COPY_SRC/COPY_DST usage. +TODO update such that it tests +- for all buffer source usages +- for all buffer destintation usages +` + ) .params(u => u // B2B copy validations are at api,validation,encoding,cmds,copyBufferToBuffer.spec.ts @@ -92,21 +106,29 @@ g.test('usage') g.test('bytes_per_row_alignment') .desc( - `Test that bytesPerRow must be a multiple of 256 for CopyB2T and CopyT2B if it is required.` + ` +Test that bytesPerRow must be a multiple of 256 for CopyB2T and CopyT2B if it is required. +- for all copy methods between linear data and textures +- for all texture dimensions +- for all sized formats. +- for various bytesPerRow aligned to 256 or not +- for various number of blocks rows copied +` ) .params(u => u // .combine('method', kImageCopyTypes) - .combine('dimension', ['2d', '3d'] as const) .combine('format', kSizedTextureFormats) - .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .filter(formatCopyableWithMethod) + .combine('dimension', kTextureDimensions) + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .beginSubcases() .combine('bytesPerRow', [undefined, 0, 1, 255, 256, 257, 512]) .combine('copyHeightInBlocks', [0, 1, 2, 3]) .expand('_textureHeightInBlocks', p => [ p.copyHeightInBlocks === 0 ? 1 : p.copyHeightInBlocks, ]) + .unless(p => p.dimension === '1d' && p.copyHeightInBlocks > 1) // Depth/stencil format copies must copy the whole subresource. .unless(p => { const info = kTextureFormatInfo[p.format]; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/image_copy.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/image_copy.ts index 01306c1512f..baf77ddad64 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/image_copy.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/image_copy.ts @@ -129,7 +129,7 @@ export class ImageCopyTest extends ValidationTest { method: ImageCopyType; dataSize: number; success: boolean; - /** If submit is true, the validaton error is expected to come from the submit and encoding + /** If submit is true, the validation error is expected to come from the submit and encoding * should succeed. */ submit?: boolean; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/layout_related.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/layout_related.spec.ts index 1f4a2efcee8..95b8fd1fc37 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/layout_related.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/layout_related.spec.ts @@ -1,8 +1,15 @@ -export const description = ''; +export const description = `Validation tests for the linear data layout of linear data <-> texture copies + +TODO check if the tests need to be updated to support aspects of depth-stencil textures`; import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { assert } from '../../../../common/util/util.js'; -import { kTextureFormatInfo, kSizedTextureFormats } from '../../../capability_info.js'; +import { + kTextureFormatInfo, + kSizedTextureFormats, + textureDimensionAndFormatCompatible, + kTextureDimensions, +} from '../../../capability_info.js'; import { align } from '../../../util/math.js'; import { bytesInACompleteRow, @@ -21,22 +28,41 @@ import { export const g = makeTestGroup(ImageCopyTest); g.test('bound_on_rows_per_image') + .desc( + ` +Test that rowsPerImage must be at least the copy height (if defined). +- for various copy methods +- for all texture dimensions +- for various values of rowsPerImage including undefined +- for various copy heights +- for various copy depths +` + ) .params(u => u .combine('method', kImageCopyTypes) + .combineWithParams([ + { dimension: '1d', size: [4, 1, 1] }, + { dimension: '2d', size: [4, 4, 1] }, + { dimension: '2d', size: [4, 4, 3] }, + { dimension: '3d', size: [4, 4, 3] }, + ] as const) .beginSubcases() .combine('rowsPerImage', [undefined, 0, 1, 2, 1024]) .combine('copyHeightInBlocks', [0, 1, 2]) .combine('copyDepth', [1, 3]) + .unless(p => p.dimension === '1d' && p.copyHeightInBlocks !== 1) + .unless(p => p.copyDepth > p.size[2]) ) .fn(async t => { - const { rowsPerImage, copyHeightInBlocks, copyDepth, method } = t.params; + const { rowsPerImage, copyHeightInBlocks, copyDepth, dimension, size, method } = t.params; const format = 'rgba8unorm'; const copyHeight = copyHeightInBlocks * kTextureFormatInfo[format].blockHeight; const texture = t.device.createTexture({ - size: { width: 4, height: 4, depthOrArrayLayers: 3 }, + size, + dimension, format, usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, }); @@ -58,7 +84,12 @@ g.test('bound_on_rows_per_image') }); g.test('copy_end_overflows_u64') - .desc(`Test what happens when offset+requiredBytesInCopy overflows GPUSize64.`) + .desc( + ` +Test an error is produced when offset+requiredBytesInCopy overflows GPUSize64. +- for various copy methods +` + ) .params(u => u .combine('method', kImageCopyTypes) @@ -91,17 +122,23 @@ g.test('copy_end_overflows_u64') g.test('required_bytes_in_copy') .desc( - `Test that the min data size condition (requiredBytesInCopy) is checked correctly. - - - Exact requiredBytesInCopy should succeed. - - requiredBytesInCopy - 1 should fail. - ` + ` +Test the computation of requiredBytesInCopy by computing the minumum data size for the copy and checking success/error at the boundary. +- for various copy methods +- for all formats +- for all dimensions +- for various extra bytesPerRow/rowsPerIamge +- for various copy sizes +- for various offets in the linear data +` ) .params(u => u .combine('method', kImageCopyTypes) .combine('format', kSizedTextureFormats) .filter(formatCopyableWithMethod) + .combine('dimension', kTextureDimensions) + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .beginSubcases() .combineWithParams([ { bytesPerRowPadding: 0, rowsPerImagePaddingInBlocks: 0 }, // no padding @@ -131,6 +168,7 @@ g.test('required_bytes_in_copy') (copyWidthInBlocks > 0 && copyHeightInBlocks > 0 && copyDepth > 0) ); }) + .unless(p => p.dimension === '1d' && (p.copyHeightInBlocks > 1 || p.copyDepth > 1)) ) .fn(async t => { const { @@ -141,6 +179,7 @@ g.test('required_bytes_in_copy') copyHeightInBlocks, copyDepth, format, + dimension, method, } = t.params; const info = kTextureFormatInfo[format]; @@ -162,7 +201,7 @@ g.test('required_bytes_in_copy') const layout = { offset, bytesPerRow, rowsPerImage }; const minDataSize = dataBytesForCopyOrFail({ layout, format, copySize, method }); - const texture = t.createAlignedTexture(format, copySize); + const texture = t.createAlignedTexture(format, copySize, undefined, dimension); t.testRun({ texture }, { offset, bytesPerRow, rowsPerImage }, copySize, { dataSize: minDataSize, @@ -180,12 +219,22 @@ g.test('required_bytes_in_copy') }); g.test('rows_per_image_alignment') - .desc(`rowsPerImage is measured in multiples of block height, so has no alignment constraints.`) + .desc( + ` +Test that rowsPerImage has no alignment constraints. +- for various copy methods +- for all sized format +- for all dimensions +- for various rowsPerImage +` + ) .params(u => u .combine('method', kImageCopyTypes) .combine('format', kSizedTextureFormats) .filter(formatCopyableWithMethod) + .combine('dimension', kTextureDimensions) + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .beginSubcases() .expand('rowsPerImage', texelBlockAlignmentTestExpanderForRowsPerImage) // Copy height is info.blockHeight, so rowsPerImage must be equal or greater than it. @@ -212,13 +261,21 @@ g.test('rows_per_image_alignment') g.test('offset_alignment') .desc( - `If texture format is not depth/stencil format, offset should be aligned with texture block. If texture format is depth/stencil format, offset should be a multiple of 4.` + ` +Test the alignment requirement on the linear data offset (block size, or 4 for depth-stencil). +- for various copy methods +- for all sized formats +- for all dimensions +- for various linear data offsets +` ) .params(u => u .combine('method', kImageCopyTypes) .combine('format', kSizedTextureFormats) .filter(formatCopyableWithMethod) + .combine('dimension', kTextureDimensions) + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .beginSubcases() .expand('offset', texelBlockAlignmentTestExpanderForOffset) ) @@ -250,15 +307,28 @@ g.test('offset_alignment') }); g.test('bound_on_bytes_per_row') - .desc(`For all formats, verify image copy validations w.r.t bytesPerRow.`) + .desc( + ` +Test that bytesPerRow, if specified must be big enough for a full copy row. +- for various copy methods +- for all sized formats +- for all dimension +- for various copy heights +- for various copy depths +- for various combinations of bytesPerRow and copy width. +` + ) .params(u => u .combine('method', kImageCopyTypes) .combine('format', kSizedTextureFormats) .filter(formatCopyableWithMethod) + .combine('dimension', kTextureDimensions) + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .beginSubcases() .combine('copyHeightInBlocks', [1, 2]) .combine('copyDepth', [1, 2]) + .unless(p => p.dimension === '1d' && (p.copyHeightInBlocks > 1 || p.copyDepth > 1)) .expandWithParams(p => { const info = kTextureFormatInfo[p.format]; // We currently have a built-in assumption that for all formats, 128 % bytesPerBlock === 0. @@ -353,6 +423,12 @@ g.test('bound_on_bytes_per_row') }); g.test('bound_on_offset') + .desc( + ` +Test that the offset cannot be larger than the linear data size (even for an empty copy). +- for various offsets and data sizes +` + ) .params(u => u .combine('method', kImageCopyTypes) diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/texture_related.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/texture_related.spec.ts index 82c318b37ff..5c910710b67 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/texture_related.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/image_copy/texture_related.spec.ts @@ -1,16 +1,11 @@ -export const description = ` - texture related validation tests for B2T copy and T2B copy and writeTexture. - - Note: see api,validation,encoding,cmds,copyTextureToTexture:* for validation tests of T2T copy. - - TODO: expand the tests below to 1d texture. -`; +export const description = `Texture related validation tests for B2T copy and T2B copy and writeTexture.`; import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { assert } from '../../../../common/util/util.js'; import { kColorTextureFormats, kSizedTextureFormats, + kTextureDimensions, kTextureFormatInfo, textureDimensionAndFormatCompatible, } from '../../../capability_info.js'; @@ -29,23 +24,31 @@ import { export const g = makeTestGroup(ImageCopyTest); g.test('valid') - .desc(`The texture must be valid and not destroyed.`) + .desc( + ` +Test that the texture must be valid and not destroyed. +- for all copy methods +- for all texture states +- for various dimensions +` + ) .params(u => u // .combine('method', kImageCopyTypes) .combine('textureState', ['valid', 'destroyed', 'error']) .combineWithParams([ - { depthOrArrayLayers: 1, dimension: '2d' }, - { depthOrArrayLayers: 3, dimension: '2d' }, - { depthOrArrayLayers: 3, dimension: '3d' }, + { dimension: '1d', size: [4, 1, 1] }, + { dimension: '2d', size: [4, 4, 1] }, + { dimension: '2d', size: [4, 4, 3] }, + { dimension: '3d', size: [4, 4, 3] }, ] as const) ) .fn(async t => { - const { method, textureState, depthOrArrayLayers, dimension } = t.params; + const { method, textureState, size, dimension } = t.params; // A valid texture. let texture = t.device.createTexture({ - size: { width: 4, height: 4, depthOrArrayLayers }, + size, dimension, format: 'rgba8unorm', usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, @@ -88,14 +91,23 @@ g.test('buffer,device_mismatch') .unimplemented(); g.test('usage') - .desc(`The texture must have the appropriate COPY_SRC/COPY_DST usage.`) + .desc( + ` +The texture must have the appropriate COPY_SRC/COPY_DST usage. +- for various copy methods +- for various dimensions +- for various usages + +TODO: update to test all texture usages` + ) .params(u => u .combine('method', kImageCopyTypes) .combineWithParams([ - { depthOrArrayLayers: 1, dimension: '2d' }, - { depthOrArrayLayers: 3, dimension: '2d' }, - { depthOrArrayLayers: 3, dimension: '3d' }, + { dimension: '1d', size: [4, 1, 1] }, + { dimension: '2d', size: [4, 4, 1] }, + { dimension: '2d', size: [4, 4, 3] }, + { dimension: '3d', size: [4, 4, 3] }, ] as const) .beginSubcases() .combine('usage', [ @@ -105,10 +117,10 @@ g.test('usage') ]) ) .fn(async t => { - const { usage, method, depthOrArrayLayers, dimension } = t.params; + const { usage, method, size, dimension } = t.params; const texture = t.device.createTexture({ - size: { width: 4, height: 4, depthOrArrayLayers }, + size, dimension, format: 'rgba8unorm', usage, @@ -129,7 +141,13 @@ g.test('usage') g.test('sample_count') .desc( - `Multisampled textures cannot be copied. Note that we don't test 2D array and 3D textures because multisample is not supported for 2D array and 3D texture creation` + ` +Test that multisampled textures cannot be copied. +- for various copy methods +- multisampled or not + +Note: we don't test 1D, 2D array and 3D textures because multisample is not supported them. +` ) .params(u => u // @@ -158,24 +176,33 @@ g.test('sample_count') }); g.test('mip_level') - .desc(`The mipLevel of the copy must be in range of the texture.`) + .desc( + ` +Test that the mipLevel of the copy must be in range of the texture. +- for various copy methods +- for various dimensions +- for several mipLevelCounts +- for several target/source mipLevels` + ) .params(u => u .combine('method', kImageCopyTypes) .combineWithParams([ - { depthOrArrayLayers: 1, dimension: '2d' }, - { depthOrArrayLayers: 3, dimension: '2d' }, - { depthOrArrayLayers: 3, dimension: '3d' }, + { dimension: '1d', size: [32, 1, 1] }, + { dimension: '2d', size: [32, 32, 1] }, + { dimension: '2d', size: [32, 32, 3] }, + { dimension: '3d', size: [32, 32, 3] }, ] as const) .beginSubcases() - .combine('mipLevelCount', [3, 5]) - .combine('mipLevel', [3, 4]) + .combine('mipLevelCount', [1, 3, 5]) + .unless(p => p.dimension === '1d' && p.mipLevelCount !== 1) + .combine('mipLevel', [0, 1, 3, 4]) ) .fn(async t => { - const { mipLevelCount, mipLevel, method, depthOrArrayLayers, dimension } = t.params; + const { mipLevelCount, mipLevel, method, size, dimension } = t.params; const texture = t.device.createTexture({ - size: { width: 32, height: 32, depthOrArrayLayers }, + size, dimension, mipLevelCount, format: 'rgba8unorm', @@ -193,11 +220,21 @@ g.test('mip_level') }); g.test('format') - .desc(`Test that it must be a full copy if the texture's format is depth/stencil format`) + .desc( + ` +Test the copy must be a full subresource if the texture's format is depth/stencil format. +- for various copy methods +- for various dimensions +- for all sized formats +- for a couple target/source mipLevels +- for some modifier (or not) for the full copy size +` + ) .params(u => u // .combine('method', kImageCopyTypes) .combineWithParams([ + { depthOrArrayLayers: 1, dimension: '1d' }, { depthOrArrayLayers: 1, dimension: '2d' }, { depthOrArrayLayers: 3, dimension: '2d' }, { depthOrArrayLayers: 32, dimension: '3d' }, @@ -207,6 +244,7 @@ g.test('format') .filter(formatCopyableWithMethod) .beginSubcases() .combine('mipLevel', [0, 2]) + .unless(p => p.dimension === '1d' && p.mipLevel !== 0) .combine('copyWidthModifier', [0, -1]) .combine('copyHeightModifier', [0, -1]) // If the texture has multiple depth/array slices and it is not a 3D texture, which means it is an array texture, @@ -232,11 +270,15 @@ g.test('format') await t.selectDeviceOrSkipTestCase(info.feature); const size = { width: 32 * info.blockWidth, height: 32 * info.blockHeight, depthOrArrayLayers }; + if (dimension === '1d') { + size.height = 1; + } + const texture = t.device.createTexture({ size, dimension, format, - mipLevelCount: 5, + mipLevelCount: dimension === '1d' ? 1 : 5, usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, }); @@ -273,7 +315,15 @@ g.test('format') }); g.test('origin_alignment') - .desc(`Copy origin must be aligned to block size.`) + .desc( + ` +Test that the texture copy origin must be aligned to the format's block size. +- for various copy methods +- for all color formats (depth stencil formats require a full copy) +- for X, Y and Z coordinates +- for various values for that coordinate depending on the block size +` + ) .params(u => u .combine('method', kImageCopyTypes) @@ -281,6 +331,7 @@ g.test('origin_alignment') .combine('format', kColorTextureFormats) .filter(formatCopyableWithMethod) .combineWithParams([ + { depthOrArrayLayers: 1, dimension: '1d' }, { depthOrArrayLayers: 1, dimension: '2d' }, { depthOrArrayLayers: 3, dimension: '2d' }, { depthOrArrayLayers: 3, dimension: '3d' }, @@ -288,6 +339,7 @@ g.test('origin_alignment') .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .beginSubcases() .combine('coordinateToTest', ['x', 'y', 'z'] as const) + .unless(p => p.dimension === '1d' && p.coordinateToTest !== 'x') .expand('valueToCoordinate', texelBlockAlignmentTestExpanderForValueToCoordinate) ) .fn(async t => { @@ -327,55 +379,28 @@ g.test('origin_alignment') }); }); -g.test('1d') - .desc(`1d texture copies must have height=depth=1.`) - .params(u => - u - .combine('method', kImageCopyTypes) - .beginSubcases() - .combine('width', [0, 1]) - .combineWithParams([ - { height: 1, depthOrArrayLayers: 1 }, - { height: 1, depthOrArrayLayers: 0 }, - { height: 1, depthOrArrayLayers: 2 }, - { height: 0, depthOrArrayLayers: 1 }, - { height: 2, depthOrArrayLayers: 1 }, - ]) - ) - .fn(async t => { - const { method, width, height, depthOrArrayLayers } = t.params; - const size = { width, height, depthOrArrayLayers }; - - const texture = t.device.createTexture({ - size: { width: 2, height: 1, depthOrArrayLayers: 1 }, - dimension: '1d', - format: 'rgba8unorm', - usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, - }); - - // For 1d textures we require copyHeight and copyDepth to be 1, - // copyHeight or copyDepth being 0 should cause a validation error. - const success = size.height === 1 && size.depthOrArrayLayers === 1; - - t.testRun({ texture }, { bytesPerRow: 256, rowsPerImage: 4 }, size, { - dataSize: 16, - method, - success, - }); - }); - g.test('size_alignment') - .desc(`Copy size must be aligned to block size.`) + .desc( + ` +Test that the copy size must be aligned to the texture's format's block size. +- for various copy methods +- for all formats (depth-stencil formats require a full copy) +- for all texture dimensions +- for the size's parameters to test (width / height / dpeth) +- for various values for that copy size parameters, depending on the block size +` + ) .params(u => u .combine('method', kImageCopyTypes) // No need to test depth/stencil formats because its copy size must be subresource's size, which is already aligned with block size. .combine('format', kColorTextureFormats) .filter(formatCopyableWithMethod) - .combine('dimension', ['2d', '3d'] as const) + .combine('dimension', kTextureDimensions) .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) .beginSubcases() .combine('coordinateToTest', ['width', 'height', 'depthOrArrayLayers'] as const) + .unless(p => p.dimension === '1d' && p.coordinateToTest !== 'width') .expand('valueToCoordinate', texelBlockAlignmentTestExpanderForValueToCoordinate) ) .fn(async t => { @@ -414,17 +439,27 @@ g.test('size_alignment') }); g.test('copy_rectangle') - .desc(`The max corner of the copy rectangle (origin+copySize) must be inside the texture.`) + .desc( + ` +Test that the max corner of the copy rectangle (origin+copySize) must be inside the texture. +- for various copy methods +- for all dimensions +- for the X, Y and Z dimensions +- for various origin and copy size values (and texture sizes) +- for various mip levels +` + ) .params(u => u .combine('method', kImageCopyTypes) - .combine('dimension', ['2d', '3d'] as const) + .combine('dimension', kTextureDimensions) .beginSubcases() .combine('originValue', [7, 8]) .combine('copySizeValue', [7, 8]) .combine('textureSizeValue', [14, 15]) .combine('mipLevel', [0, 2]) .combine('coordinateToTest', [0, 1, 2] as const) + .unless(p => p.dimension === '1d' && (p.coordinateToTest !== 0 || p.mipLevel !== 0)) ) .fn(async t => { const { @@ -442,6 +477,10 @@ g.test('copy_rectangle') const origin = [0, 0, 0]; const copySize = [0, 0, 0]; const textureSize = { width: 16 << mipLevel, height: 16 << mipLevel, depthOrArrayLayers: 16 }; + if (dimension === '1d') { + textureSize.height = 1; + textureSize.depthOrArrayLayers = 1; + } const success = originValue + copySizeValue <= textureSizeValue; origin[coordinateToTest] = originValue; @@ -465,7 +504,7 @@ g.test('copy_rectangle') const texture = t.device.createTexture({ size: textureSize, dimension, - mipLevelCount: 3, + mipLevelCount: dimension === '1d' ? 1 : 3, format, usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/query_set/create.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/query_set/create.spec.ts index f4e37358209..3c53394823b 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/query_set/create.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/query_set/create.spec.ts @@ -1,5 +1,7 @@ export const description = ` Tests for validation in createQuerySet. + +TODO: pipeline statistics queries are removed from core; consider moving tests to another suite. `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts index b7dbab0eaa9..0855a92d488 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts @@ -26,6 +26,11 @@ const kDefaultHeight = 32; const kDefaultDepth = 1; const kDefaultMipLevelCount = 6; +/** Valid contextId for HTMLCanvasElement/OffscreenCanvas, + * spec: https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext + */ +export const kValidContextId = ['2d', 'bitmaprenderer', 'webgl', 'webgl2', 'webgpu'] as const; + function computeMipMapSize(width: number, height: number, mipLevel: number) { return { mipWidth: Math.max(width >> mipLevel, 1), @@ -121,9 +126,9 @@ function generateCopySizeForDstOOB({ mipLevel, dstOrigin }: WithDstOriginMipLeve function canCopyFromContextType(contextName: string) { switch (contextName) { case '2d': - case 'experimental-webgl': case 'webgl': case 'webgl2': + case 'webgpu': return true; default: return false; @@ -196,14 +201,7 @@ g.test('source_canvas,contexts') ) .params(u => u // - .combine('contextType', [ - '2d', - 'bitmaprenderer', - 'experimental-webgl', - 'webgpu', - 'webgl', - 'webgl2', - ] as const) + .combine('contextType', kValidContextId) .beginSubcases() .combine('copySize', [ { width: 0, height: 0, depthOrArrayLayers: 0 }, @@ -241,14 +239,14 @@ g.test('source_offscreenCanvas,contexts') Test OffscreenCanvas as source image with different contexts. Call OffscreenCanvas.getContext() with different context type. - Only '2d', 'webgl', 'webgl2' is valid context type. + Only '2d', 'webgl', 'webgl2', 'webgpu' is valid context type. Check whether 'OperationError' is generated when context type is invalid. ` ) .params(u => u // - .combine('contextType', ['2d', 'bitmaprenderer', 'webgl', 'webgl2'] as const) + .combine('contextType', kValidContextId) .beginSubcases() .combine('copySize', [ { width: 0, height: 0, depthOrArrayLayers: 0 }, @@ -264,7 +262,9 @@ g.test('source_offscreenCanvas,contexts') usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, }); - const ctx = canvas.getContext(contextType); + // Workaround the compile error that 'webgpu' is not a valid + // OffscreenRenderingContextId. + const ctx = canvas.getContext(contextType as OffscreenRenderingContextId); if (ctx === null) { t.skip('Failed to get context for canvas element'); return; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts index 3d5ffab0407..abe75608fc3 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts @@ -1,7 +1,5 @@ export const description = ` Tests using a destroyed query set on a queue. - -TODO: Test with pipeline statistics queries on {compute, render} as well. `; import { makeTestGroup } from '../../../../../common/framework/test_group.js'; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts index 28603ef1567..f9fe265ecd1 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts @@ -863,7 +863,7 @@ g.test('replaced_binding') pass.setBindGroup(0, bindGroup1); pass.endPass(); - // TODO: If the Compatible Usage List (https://gpuweb.github.io/gpuweb/#compatible-usage-list) + // MAINTENANCE_TODO: If the Compatible Usage List (https://gpuweb.github.io/gpuweb/#compatible-usage-list) // gets programmatically defined in capability_info, use it here, instead of this logic, for clarity. let success = entry.storageTexture?.access !== 'write-only'; // Replaced bindings should not be validated in compute pass, because validation only occurs @@ -1022,7 +1022,9 @@ g.test('unused_bindings_in_pipeline') setPipeline, callDrawOrDispatch, } = t.params; - const view = t.createTexture({ usage: GPUTextureUsage.STORAGE_BINDING }).createView(); + const view = t + .createTexture({ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING }) + .createView(); const bindGroup0 = t.createBindGroup(0, view, 'sampled-texture', '2d', { format: 'rgba8unorm', }); @@ -1030,27 +1032,27 @@ g.test('unused_bindings_in_pipeline') format: 'rgba8unorm', }); - const wgslVertex = `[[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { + const wgslVertex = `@stage(vertex) fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); }`; const wgslFragment = pp` ${pp._if(useBindGroup0)} - [[group(0), binding(0)]] var image0 : texture_storage_2d<rgba8unorm, read>; + @group(0) @binding(0) var image0 : texture_storage_2d<rgba8unorm, write>; ${pp._endif} ${pp._if(useBindGroup1)} - [[group(1), binding(0)]] var image1 : texture_storage_2d<rgba8unorm, read>; + @group(1) @binding(0) var image1 : texture_storage_2d<rgba8unorm, write>; ${pp._endif} - [[stage(fragment)]] fn main() {} + @stage(fragment) fn main() {} `; const wgslCompute = pp` ${pp._if(useBindGroup0)} - [[group(0), binding(0)]] var image0 : texture_storage_2d<rgba8unorm, read>; + @group(0) @binding(0) var image0 : texture_storage_2d<rgba8unorm, write>; ${pp._endif} ${pp._if(useBindGroup1)} - [[group(1), binding(0)]] var image1 : texture_storage_2d<rgba8unorm, read>; + @group(1) @binding(0) var image1 : texture_storage_2d<rgba8unorm, write>; ${pp._endif} - [[stage(compute), workgroup_size(1)]] fn main() {} + @stage(compute) @workgroup_size(1) fn main() {} `; const pipeline = compute diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/validation_test.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/validation_test.ts index ec07957925e..7ceeb35c279 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/validation_test.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/validation_test.ts @@ -1,89 +1,10 @@ -import { attemptGarbageCollection } from '../../../common/util/collect_garbage.js'; -import { assert } from '../../../common/util/util.js'; import { ValidBindableResource, BindableResource, kMaxQueryCount } from '../../capability_info.js'; -import { GPUTest, ResourceState, initUncanonicalizedDeviceDescriptor } from '../../gpu_test.js'; -import { - DevicePool, - DeviceProvider, - TestOOMedShouldAttemptGC, - UncanonicalizedDeviceDescriptor, -} from '../../util/device_pool.js'; - -// TODO: When DevicePool becomes able to provide multiple devices at once, use the usual one instead of a new one. -const mismatchedDevicePool = new DevicePool(); +import { GPUTest, ResourceState } from '../../gpu_test.js'; /** * Base fixture for WebGPU validation tests. */ export class ValidationTest extends GPUTest { - // Device mismatched validation tests require another GPUDevice different from the default - // GPUDevice of GPUTest. It is only used to create device mismatched objects. - private mismatchedProvider: DeviceProvider | undefined; - private mismatchedAcquiredDevice: GPUDevice | undefined; - - /** GPUDevice for creating mismatched objects required by device mismatched validation tests. */ - get mismatchedDevice(): GPUDevice { - assert( - this.mismatchedProvider !== undefined, - 'No provider available right now; did you "await" selectMismatchedDeviceOrSkipTestCase?' - ); - if (!this.mismatchedAcquiredDevice) { - this.mismatchedAcquiredDevice = this.mismatchedProvider.acquire(); - } - return this.mismatchedAcquiredDevice; - } - - /** - * Create other device different with current test device, which could be got by `.mismatchedDevice`. - * A `descriptor` may be undefined, which returns a `default` mismatched device. - * If the request descriptor or feature name can't be supported, throws an exception to skip the entire test case. - */ - async selectMismatchedDeviceOrSkipTestCase( - descriptor: - | UncanonicalizedDeviceDescriptor - | GPUFeatureName - | undefined - | Array<GPUFeatureName | undefined> - ): Promise<void> { - assert( - this.mismatchedProvider === undefined, - "Can't selectMismatchedDeviceOrSkipTestCase() multiple times" - ); - - this.mismatchedProvider = - descriptor === undefined - ? await mismatchedDevicePool.reserve() - : await mismatchedDevicePool.reserve(initUncanonicalizedDeviceDescriptor(descriptor)); - - this.mismatchedAcquiredDevice = this.mismatchedProvider.acquire(); - } - - protected async finalize(): Promise<void> { - await super.finalize(); - - if (this.mismatchedProvider) { - // TODO(kainino0x): Deduplicate this with code in GPUTest.finalize - let threw: undefined | Error; - { - const provider = this.mismatchedProvider; - this.mismatchedProvider = undefined; - try { - await mismatchedDevicePool.release(provider); - } catch (ex) { - threw = ex; - } - } - - if (threw) { - if (threw instanceof TestOOMedShouldAttemptGC) { - // Try to clean up, in case there are stray GPU resources in need of collection. - await attemptGarbageCollection(); - } - throw threw; - } - } - } - /** * Create a GPUTexture in the specified state. * A `descriptor` may optionally be passed, which is used when `state` is not `'invalid'`. @@ -372,7 +293,7 @@ export class ValidationTest extends GPUTest { return this.device.createRenderPipeline({ vertex: { module: this.device.createShaderModule({ - code: `[[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { + code: `@stage(vertex) fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); }`, }), @@ -380,7 +301,7 @@ export class ValidationTest extends GPUTest { }, fragment: { module: this.device.createShaderModule({ - code: '[[stage(fragment)]] fn main() {}', + code: '@stage(fragment) fn main() {}', }), entryPoint: 'main', targets: [{ format: 'rgba8unorm', writeMask: 0 }], @@ -410,7 +331,7 @@ export class ValidationTest extends GPUTest { layout, compute: { module: this.device.createShaderModule({ - code: '[[stage(compute), workgroup_size(1)]] fn main() {}', + code: '@stage(compute) @workgroup_size(1) fn main() {}', }), entryPoint: 'main', }, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/vertex_state.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/vertex_state.spec.ts index e10b3c452ce..511daeaac3b 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/vertex_state.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/api/validation/vertex_state.spec.ts @@ -12,7 +12,7 @@ import { import { ValidationTest } from './validation_test.js'; const VERTEX_SHADER_CODE_WITH_NO_INPUT = ` - [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0, 0.0, 0.0, 0.0); } `; @@ -70,7 +70,7 @@ class F extends ValidationTest { fragment: { module: this.device.createShaderModule({ code: ` - [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.0, 1.0, 0.0, 1.0); }`, }), @@ -90,7 +90,7 @@ class F extends ValidationTest { const vsModule = this.device.createShaderModule({ code: vertexShader }); const fsModule = this.device.createShaderModule({ code: ` - [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main() -> @location(0) vec4<f32> { return vec4<f32>(0.0, 1.0, 0.0, 1.0); }`, }); @@ -118,7 +118,7 @@ class F extends ValidationTest { let count = 0; for (const input of inputs) { - interfaces += `[[location(${input.location})]] input${count} : ${input.type};\n`; + interfaces += `@location(${input.location}) input${count} : ${input.type};\n`; body += `var i${count} : ${input.type} = input.input${count};\n`; count++; } @@ -127,7 +127,7 @@ class F extends ValidationTest { struct Inputs { ${interfaces} }; - [[stage(vertex)]] fn main(input : Inputs) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main(input : Inputs) -> @builtin(position) vec4<f32> { ${body} return vec4<f32>(0.0, 0.0, 0.0, 0.0); } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/capability_info.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/capability_info.ts index 8d42001f683..1f1651e8e03 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/capability_info.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/capability_info.ts @@ -1,5 +1,5 @@ -// TODO: The generated Typedoc for this file is hard to navigate because it's alphabetized. -// Consider using namespaces or renames to fix this? +// MAINTENANCE_TODO: The generated Typedoc for this file is hard to navigate because it's +// alphabetized. Consider using namespaces or renames to fix this? /* eslint-disable no-sparse-arrays */ @@ -55,6 +55,10 @@ export const kBufferUsageInfo: { }; /** List of all GPUBufferUsage values. */ export const kBufferUsages = numericKeysOf<GPUBufferUsageFlags>(kBufferUsageInfo); +export const kAllBufferUsageBits = kBufferUsages.reduce( + (previousSet, currentUsage) => previousSet | currentUsage, + 0 +); // Textures @@ -62,8 +66,8 @@ export const kBufferUsages = numericKeysOf<GPUBufferUsageFlags>(kBufferUsageInfo // Note that we repeat the header multiple times in order to make it easier to read. const kRegularTextureFormatInfo = /* prettier-ignore */ makeTable( - ['renderable', 'multisample', 'color', 'depth', 'stencil', 'storage', 'copySrc', 'copyDst', 'sampleType', 'bytesPerBlock', 'blockWidth', 'blockHeight', 'feature'] as const, - [ , , true, false, false, , true, true, , , 1, 1, ] as const, { + ['renderable', 'multisample', 'color', 'depth', 'stencil', 'storage', 'copySrc', 'copyDst', 'sampleType', 'bytesPerBlock', 'blockWidth', 'blockHeight', 'feature', 'baseFormat'] as const, + [ , , true, false, false, , true, true, , , 1, 1, , undefined ] as const, { // 8-bit formats 'r8unorm': [ true, true, , , , false, , , 'float', 1], 'r8snorm': [ false, false, , , , false, , , 'float', 1], @@ -80,17 +84,17 @@ const kRegularTextureFormatInfo = /* prettier-ignore */ makeTable( // 32-bit formats 'r32uint': [ true, true, , , , true, , , 'uint', 4], 'r32sint': [ true, true, , , , true, , , 'sint', 4], - 'r32float': [ true, true, , , , true, , , 'float', 4], + 'r32float': [ true, true, , , , true, , , 'unfilterable-float', 4], 'rg16uint': [ true, true, , , , false, , , 'uint', 4], 'rg16sint': [ true, true, , , , false, , , 'sint', 4], 'rg16float': [ true, true, , , , false, , , 'float', 4], - 'rgba8unorm': [ true, true, , , , true, , , 'float', 4], - 'rgba8unorm-srgb': [ true, true, , , , false, , , 'float', 4], + 'rgba8unorm': [ true, true, , , , true, , , 'float', 4, , , , 'rgba8unorm'], + 'rgba8unorm-srgb': [ true, true, , , , false, , , 'float', 4, , , , 'rgba8unorm'], 'rgba8snorm': [ false, false, , , , true, , , 'float', 4], 'rgba8uint': [ true, true, , , , true, , , 'uint', 4], 'rgba8sint': [ true, true, , , , true, , , 'sint', 4], - 'bgra8unorm': [ true, true, , , , false, , , 'float', 4], - 'bgra8unorm-srgb': [ true, true, , , , false, , , 'float', 4], + 'bgra8unorm': [ true, true, , , , false, , , 'float', 4, , , , 'bgra8unorm'], + 'bgra8unorm-srgb': [ true, true, , , , false, , , 'float', 4, , , , 'bgra8unorm'], // Packed 32-bit formats 'rgb10a2unorm': [ true, true, , , , false, , , 'float', 4], 'rg11b10ufloat': [ false, false, , , , false, , , 'float', 4], @@ -98,19 +102,19 @@ const kRegularTextureFormatInfo = /* prettier-ignore */ makeTable( // 64-bit formats 'rg32uint': [ true, true, , , , true, , , 'uint', 8], 'rg32sint': [ true, true, , , , true, , , 'sint', 8], - 'rg32float': [ true, true, , , , true, , , 'float', 8], + 'rg32float': [ true, true, , , , true, , , 'unfilterable-float', 8], 'rgba16uint': [ true, true, , , , true, , , 'uint', 8], 'rgba16sint': [ true, true, , , , true, , , 'sint', 8], 'rgba16float': [ true, true, , , , true, , , 'float', 8], // 128-bit formats 'rgba32uint': [ true, true, , , , true, , , 'uint', 16], 'rgba32sint': [ true, true, , , , true, , , 'sint', 16], - 'rgba32float': [ true, true, , , , true, , , 'float', 16], + 'rgba32float': [ true, true, , , , true, , , 'unfilterable-float', 16], } as const); /* prettier-ignore */ -const kTexFmtInfoHeader = ['renderable', 'multisample', 'color', 'depth', 'stencil', 'storage', 'copySrc', 'copyDst', 'sampleType', 'bytesPerBlock', 'blockWidth', 'blockHeight', 'feature'] as const; +const kTexFmtInfoHeader = ['renderable', 'multisample', 'color', 'depth', 'stencil', 'storage', 'copySrc', 'copyDst', 'sampleType', 'bytesPerBlock', 'blockWidth', 'blockHeight', 'feature', 'baseFormat'] as const; const kSizedDepthStencilFormatInfo = /* prettier-ignore */ makeTable(kTexFmtInfoHeader, - [ true, true, false, , , false, false, false, , , 1, 1, ] as const, { + [ true, true, false, , , false, false, false, , , 1, 1, , undefined ] as const, { 'depth32float': [ , , , true, false, , , , 'depth', 4], 'depth16unorm': [ , , , true, false, , , , 'depth', 2], 'stencil8': [ , , , false, true, , , , 'uint', 1], @@ -118,83 +122,83 @@ const kSizedDepthStencilFormatInfo = /* prettier-ignore */ makeTable(kTexFmtInfo // Multi aspect sample type are now set to their first aspect const kUnsizedDepthStencilFormatInfo = /* prettier-ignore */ makeTable(kTexFmtInfoHeader, - [ true, true, false, , , false, false, false, , undefined, 1, 1, ] as const, { + [ true, true, false, , , false, false, false, , undefined, 1, 1, , undefined ] as const, { 'depth24plus': [ , , , true, false, , , , 'depth'], 'depth24plus-stencil8': [ , , , true, true, , , , 'depth'], - // These should really be sized formats; see below TODO about multi-aspect formats. + // MAINTENANCE_TODO: These should really be sized formats; see below MAINTENANCE_TODO about multi-aspect formats. 'depth24unorm-stencil8': [ , , , true, true, , , , 'depth', , , , 'depth24unorm-stencil8'], 'depth32float-stencil8': [ , , , true, true, , , , 'depth', , , , 'depth32float-stencil8'], } as const); // Separated compressed formats by type const kBCTextureFormatInfo = /* prettier-ignore */ makeTable(kTexFmtInfoHeader, - [ false, false, true, false, false, false, true, true, , , 4, 4, ] as const, { + [ false, false, true, false, false, false, true, true, , , 4, 4, , undefined ] as const, { // Block Compression (BC) formats - 'bc1-rgba-unorm': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-bc'], - 'bc1-rgba-unorm-srgb': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-bc'], - 'bc2-rgba-unorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc'], - 'bc2-rgba-unorm-srgb': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc'], - 'bc3-rgba-unorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc'], - 'bc3-rgba-unorm-srgb': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc'], + 'bc1-rgba-unorm': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-bc', 'bc1-rgba-unorm'], + 'bc1-rgba-unorm-srgb': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-bc', 'bc1-rgba-unorm'], + 'bc2-rgba-unorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc', 'bc2-rgba-unorm'], + 'bc2-rgba-unorm-srgb': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc', 'bc2-rgba-unorm'], + 'bc3-rgba-unorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc', 'bc3-rgba-unorm'], + 'bc3-rgba-unorm-srgb': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc', 'bc3-rgba-unorm'], 'bc4-r-unorm': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-bc'], 'bc4-r-snorm': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-bc'], 'bc5-rg-unorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc'], 'bc5-rg-snorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc'], 'bc6h-rgb-ufloat': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc'], 'bc6h-rgb-float': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc'], - 'bc7-rgba-unorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc'], - 'bc7-rgba-unorm-srgb': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc'], + 'bc7-rgba-unorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc', 'bc7-rgba-unorm'], + 'bc7-rgba-unorm-srgb': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-bc', 'bc7-rgba-unorm'], } as const); const kETC2TextureFormatInfo = /* prettier-ignore */ makeTable(kTexFmtInfoHeader, - [ false, false, true, false, false, false, true, true, , , 4, 4, ] as const, { + [ false, false, true, false, false, false, true, true, , , 4, 4, , undefined ] as const, { // Ericsson Compression (ETC2) formats - 'etc2-rgb8unorm': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-etc2'], - 'etc2-rgb8unorm-srgb': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-etc2'], - 'etc2-rgb8a1unorm': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-etc2'], - 'etc2-rgb8a1unorm-srgb': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-etc2'], - 'etc2-rgba8unorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-etc2'], - 'etc2-rgba8unorm-srgb': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-etc2'], + 'etc2-rgb8unorm': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-etc2', 'etc2-rgb8unorm'], + 'etc2-rgb8unorm-srgb': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-etc2', 'etc2-rgb8unorm'], + 'etc2-rgb8a1unorm': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-etc2', 'etc2-rgb8a1unorm'], + 'etc2-rgb8a1unorm-srgb': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-etc2', 'etc2-rgb8a1unorm'], + 'etc2-rgba8unorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-etc2', 'etc2-rgba8unorm'], + 'etc2-rgba8unorm-srgb': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-etc2', 'etc2-rgba8unorm'], 'eac-r11unorm': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-etc2'], 'eac-r11snorm': [ , , , , , , , , 'float', 8, 4, 4, 'texture-compression-etc2'], 'eac-rg11unorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-etc2'], 'eac-rg11snorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-etc2'], } as const); const kASTCTextureFormatInfo = /* prettier-ignore */ makeTable(kTexFmtInfoHeader, - [ false, false, true, false, false, false, true, true, , , , , ] as const, { + [ false, false, true, false, false, false, true, true, , , , , , undefined] as const, { // Adaptable Scalable Compression (ASTC) formats - 'astc-4x4-unorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-astc'], - 'astc-4x4-unorm-srgb': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-astc'], - 'astc-5x4-unorm': [ , , , , , , , , 'float', 16, 5, 4, 'texture-compression-astc'], - 'astc-5x4-unorm-srgb': [ , , , , , , , , 'float', 16, 5, 4, 'texture-compression-astc'], - 'astc-5x5-unorm': [ , , , , , , , , 'float', 16, 5, 5, 'texture-compression-astc'], - 'astc-5x5-unorm-srgb': [ , , , , , , , , 'float', 16, 5, 5, 'texture-compression-astc'], - 'astc-6x5-unorm': [ , , , , , , , , 'float', 16, 6, 5, 'texture-compression-astc'], - 'astc-6x5-unorm-srgb': [ , , , , , , , , 'float', 16, 6, 5, 'texture-compression-astc'], - 'astc-6x6-unorm': [ , , , , , , , , 'float', 16, 6, 6, 'texture-compression-astc'], - 'astc-6x6-unorm-srgb': [ , , , , , , , , 'float', 16, 6, 6, 'texture-compression-astc'], - 'astc-8x5-unorm': [ , , , , , , , , 'float', 16, 8, 5, 'texture-compression-astc'], - 'astc-8x5-unorm-srgb': [ , , , , , , , , 'float', 16, 8, 5, 'texture-compression-astc'], - 'astc-8x6-unorm': [ , , , , , , , , 'float', 16, 8, 6, 'texture-compression-astc'], - 'astc-8x6-unorm-srgb': [ , , , , , , , , 'float', 16, 8, 6, 'texture-compression-astc'], - 'astc-8x8-unorm': [ , , , , , , , , 'float', 16, 8, 8, 'texture-compression-astc'], - 'astc-8x8-unorm-srgb': [ , , , , , , , , 'float', 16, 8, 8, 'texture-compression-astc'], - 'astc-10x5-unorm': [ , , , , , , , , 'float', 16, 10, 5, 'texture-compression-astc'], - 'astc-10x5-unorm-srgb': [ , , , , , , , , 'float', 16, 10, 5, 'texture-compression-astc'], - 'astc-10x6-unorm': [ , , , , , , , , 'float', 16, 10, 6, 'texture-compression-astc'], - 'astc-10x6-unorm-srgb': [ , , , , , , , , 'float', 16, 10, 6, 'texture-compression-astc'], - 'astc-10x8-unorm': [ , , , , , , , , 'float', 16, 10, 8, 'texture-compression-astc'], - 'astc-10x8-unorm-srgb': [ , , , , , , , , 'float', 16, 10, 8, 'texture-compression-astc'], - 'astc-10x10-unorm': [ , , , , , , , , 'float', 16, 10, 10, 'texture-compression-astc'], - 'astc-10x10-unorm-srgb': [ , , , , , , , , 'float', 16, 10, 10, 'texture-compression-astc'], - 'astc-12x10-unorm': [ , , , , , , , , 'float', 16, 12, 10, 'texture-compression-astc'], - 'astc-12x10-unorm-srgb': [ , , , , , , , , 'float', 16, 12, 10, 'texture-compression-astc'], - 'astc-12x12-unorm': [ , , , , , , , , 'float', 16, 12, 12, 'texture-compression-astc'], - 'astc-12x12-unorm-srgb': [ , , , , , , , , 'float', 16, 12, 12, 'texture-compression-astc'], + 'astc-4x4-unorm': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-astc', 'astc-4x4-unorm'], + 'astc-4x4-unorm-srgb': [ , , , , , , , , 'float', 16, 4, 4, 'texture-compression-astc', 'astc-4x4-unorm'], + 'astc-5x4-unorm': [ , , , , , , , , 'float', 16, 5, 4, 'texture-compression-astc', 'astc-5x4-unorm'], + 'astc-5x4-unorm-srgb': [ , , , , , , , , 'float', 16, 5, 4, 'texture-compression-astc', 'astc-5x4-unorm'], + 'astc-5x5-unorm': [ , , , , , , , , 'float', 16, 5, 5, 'texture-compression-astc', 'astc-5x5-unorm'], + 'astc-5x5-unorm-srgb': [ , , , , , , , , 'float', 16, 5, 5, 'texture-compression-astc', 'astc-5x5-unorm'], + 'astc-6x5-unorm': [ , , , , , , , , 'float', 16, 6, 5, 'texture-compression-astc', 'astc-6x5-unorm'], + 'astc-6x5-unorm-srgb': [ , , , , , , , , 'float', 16, 6, 5, 'texture-compression-astc', 'astc-6x5-unorm'], + 'astc-6x6-unorm': [ , , , , , , , , 'float', 16, 6, 6, 'texture-compression-astc', 'astc-6x6-unorm'], + 'astc-6x6-unorm-srgb': [ , , , , , , , , 'float', 16, 6, 6, 'texture-compression-astc', 'astc-6x6-unorm'], + 'astc-8x5-unorm': [ , , , , , , , , 'float', 16, 8, 5, 'texture-compression-astc', 'astc-8x5-unorm'], + 'astc-8x5-unorm-srgb': [ , , , , , , , , 'float', 16, 8, 5, 'texture-compression-astc', 'astc-8x5-unorm'], + 'astc-8x6-unorm': [ , , , , , , , , 'float', 16, 8, 6, 'texture-compression-astc', 'astc-8x6-unorm'], + 'astc-8x6-unorm-srgb': [ , , , , , , , , 'float', 16, 8, 6, 'texture-compression-astc', 'astc-8x6-unorm'], + 'astc-8x8-unorm': [ , , , , , , , , 'float', 16, 8, 8, 'texture-compression-astc', 'astc-8x8-unorm'], + 'astc-8x8-unorm-srgb': [ , , , , , , , , 'float', 16, 8, 8, 'texture-compression-astc', 'astc-8x8-unorm'], + 'astc-10x5-unorm': [ , , , , , , , , 'float', 16, 10, 5, 'texture-compression-astc', 'astc-10x5-unorm'], + 'astc-10x5-unorm-srgb': [ , , , , , , , , 'float', 16, 10, 5, 'texture-compression-astc', 'astc-10x5-unorm'], + 'astc-10x6-unorm': [ , , , , , , , , 'float', 16, 10, 6, 'texture-compression-astc', 'astc-10x6-unorm'], + 'astc-10x6-unorm-srgb': [ , , , , , , , , 'float', 16, 10, 6, 'texture-compression-astc', 'astc-10x6-unorm'], + 'astc-10x8-unorm': [ , , , , , , , , 'float', 16, 10, 8, 'texture-compression-astc', 'astc-10x8-unorm'], + 'astc-10x8-unorm-srgb': [ , , , , , , , , 'float', 16, 10, 8, 'texture-compression-astc', 'astc-10x8-unorm'], + 'astc-10x10-unorm': [ , , , , , , , , 'float', 16, 10, 10, 'texture-compression-astc', 'astc-10x10-unorm'], + 'astc-10x10-unorm-srgb': [ , , , , , , , , 'float', 16, 10, 10, 'texture-compression-astc', 'astc-10x10-unorm'], + 'astc-12x10-unorm': [ , , , , , , , , 'float', 16, 12, 10, 'texture-compression-astc', 'astc-12x10-unorm'], + 'astc-12x10-unorm-srgb': [ , , , , , , , , 'float', 16, 12, 10, 'texture-compression-astc', 'astc-12x10-unorm'], + 'astc-12x12-unorm': [ , , , , , , , , 'float', 16, 12, 12, 'texture-compression-astc', 'astc-12x12-unorm'], + 'astc-12x12-unorm-srgb': [ , , , , , , , , 'float', 16, 12, 12, 'texture-compression-astc', 'astc-12x12-unorm'], } as const); // Definitions for use locally. To access the table entries, use `kTextureFormatInfo`. -// TODO: Consider generating the exports below programmatically by filtering the big list, instead +// MAINTENANCE_TODO: Consider generating the exports below programmatically by filtering the big list, instead // of using these local constants? Requires some type magic though. /* prettier-ignore */ const kCompressedTextureFormatInfo = { ...kBCTextureFormatInfo, ...kETC2TextureFormatInfo, ...kASTCTextureFormatInfo } as const; /* prettier-ignore */ const kColorTextureFormatInfo = { ...kRegularTextureFormatInfo, ...kCompressedTextureFormatInfo } as const; @@ -241,9 +245,12 @@ export const kRenderableColorTextureFormats = kRegularTextureFormats.filter( v => kColorTextureFormatInfo[v].renderable ); +// The formats of GPUTextureFormat for canvas context. +export const kCanvasTextureFormats = ['bgra8unorm', 'rgba8unorm'] as const; + /** Per-GPUTextureFormat info. */ // Exists just for documentation. Otherwise could be inferred by `makeTable`. -// TODO: Refactor this to separate per-aspect data for multi-aspect formats. In particular: +// MAINTENANCE_TODO: Refactor this to separate per-aspect data for multi-aspect formats. In particular: // - bytesPerBlock only makes sense on a per-aspect basis. But this table can't express that. // So we put depth24unorm-stencil8 and depth32float-stencil8 to be unsized formats for now. export type TextureFormatInfo = { @@ -324,6 +331,38 @@ export const kTextureAspectInfo: { /** List of all GPUTextureAspect values. */ export const kTextureAspects = keysOf(kTextureAspectInfo); +/** Per-GPUCompareFunction info. */ +export const kCompareFunctionInfo: { + readonly [k in GPUCompareFunction]: {}; +} = /* prettier-ignore */ { + 'never': {}, + 'less': {}, + 'equal': {}, + 'less-equal': {}, + 'greater': {}, + 'not-equal': {}, + 'greater-equal': {}, + 'always': {}, +}; +/** List of all GPUCompareFunction values. */ +export const kCompareFunctions = keysOf(kCompareFunctionInfo); + +/** Per-GPUStencilOperation info. */ +export const kStencilOperationInfo: { + readonly [k in GPUStencilOperation]: {}; +} = /* prettier-ignore */ { + 'keep': {}, + 'zero': {}, + 'replace': {}, + 'invert': {}, + 'increment-clamp': {}, + 'decrement-clamp': {}, + 'increment-wrap': {}, + 'decrement-wrap': {}, +}; +/** List of all GPUStencilOperation values. */ +export const kStencilOperations = keysOf(kStencilOperationInfo); + const kDepthStencilFormatCapabilityInBufferTextureCopy = { // kUnsizedDepthStencilFormats depth24plus: { @@ -350,8 +389,8 @@ const kDepthStencilFormatCapabilityInBufferTextureCopy = { }, 'depth24unorm-stencil8': { CopyB2T: ['stencil-only'], - CopyT2B: ['depth-only', 'stencil-only'], - texelAspectSize: { 'depth-only': 4, 'stencil-only': 1 }, + CopyT2B: ['stencil-only'], + texelAspectSize: { 'depth-only': -1, 'stencil-only': 1 }, }, 'depth32float-stencil8': { CopyB2T: ['stencil-only'], @@ -806,23 +845,50 @@ export const kShaderStageCombinations: readonly GPUShaderStageFlags[] = [0, 1, 2 /** * List of all possible texture sampleCount values. * - * - TODO: Update with all possible sample counts when defined - * - TODO: Switch existing tests to use kTextureSampleCounts + * MAINTENANCE_TODO: Switch existing tests to use kTextureSampleCounts */ export const kTextureSampleCounts = [1, 4] as const; -// Pipeline limits +// Blend factors and Blend components + +/** List of all GPUBlendFactor values. */ +export const kBlendFactors: readonly GPUBlendFactor[] = [ + 'zero', + 'one', + 'src', + 'one-minus-src', + 'src-alpha', + 'one-minus-src-alpha', + 'dst', + 'one-minus-dst', + 'dst-alpha', + 'one-minus-dst-alpha', + 'src-alpha-saturated', + 'constant', + 'one-minus-constant', +]; -/** - * Maximum number of color attachments to a render pass. - * - * - TODO: Update maximum color attachments when defined in the spec. - */ -export const kMaxColorAttachments = 4; +/** List of all GPUBlendOperation values. */ +export const kBlendOperations: readonly GPUBlendOperation[] = [ + 'add', // + 'subtract', + 'reverse-subtract', + 'min', + 'max', +]; +// Pipeline limits + +/** Maximum number of color attachments to a render pass, by spec. */ +export const kMaxColorAttachments = 8; /** `maxVertexBuffers` per GPURenderPipeline, by spec. */ export const kMaxVertexBuffers = 8; /** `maxVertexAttributes` per GPURenderPipeline, by spec. */ export const kMaxVertexAttributes = 16; /** `maxVertexBufferArrayStride` in a vertex buffer in a GPURenderPipeline, by spec. */ export const kMaxVertexBufferArrayStride = 2048; + +/** The size of indirect draw parameters in the indirectBuffer of drawIndirect */ +export const kDrawIndirectParametersSize = 4; +/** The size of indirect drawIndexed parameters in the indirectBuffer of drawIndexedIndirect */ +export const kDrawIndexedIndirectParametersSize = 5; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/gpu_test.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/gpu_test.ts index 2e9a7aaf59f..8d849440519 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/gpu_test.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/gpu_test.ts @@ -36,6 +36,10 @@ import { PerTexelComponent, kTexelRepresentationInfo } from './util/texture/texe const devicePool = new DevicePool(); +// MAINTENANCE_TODO: When DevicePool becomes able to provide multiple devices at once, use the +// usual one instead of a new one. +const mismatchedDevicePool = new DevicePool(); + const kResourceStateValues = ['valid', 'invalid', 'destroyed'] as const; export type ResourceState = typeof kResourceStateValues[number]; export const kResourceStates: readonly ResourceState[] = kResourceStateValues; @@ -62,6 +66,12 @@ export class GPUTest extends Fixture { /** Must not be replaced once acquired. */ private acquiredDevice: GPUDevice | undefined; + // Some tests(e.g. Device mismatched validation) require another GPUDevice + // different from the default GPUDevice of GPUTest. It is only used to + //create device mismatched objects. + private mismatchedProvider: DeviceProvider | undefined; + private mismatchedAcquiredDevice: GPUDevice | undefined; + /** GPUDevice for the test to use. */ get device(): GPUDevice { assert( @@ -74,6 +84,46 @@ export class GPUTest extends Fixture { return this.acquiredDevice; } + /** GPUDevice for tests requires another device from default one. + * e.g. creating objects required creating mismatched objects required + * by device mismatched validation tests. + */ + get mismatchedDevice(): GPUDevice { + assert( + this.mismatchedProvider !== undefined, + 'No provider available right now; did you "await" selectMismatchedDeviceOrSkipTestCase?' + ); + if (!this.mismatchedAcquiredDevice) { + this.mismatchedAcquiredDevice = this.mismatchedProvider.acquire(); + } + return this.mismatchedAcquiredDevice; + } + + /** + * Create other device different with current test device, which could be got by `.mismatchedDevice`. + * A `descriptor` may be undefined, which returns a `default` mismatched device. + * If the request descriptor or feature name can't be supported, throws an exception to skip the entire test case. + */ + async selectMismatchedDeviceOrSkipTestCase( + descriptor: + | UncanonicalizedDeviceDescriptor + | GPUFeatureName + | undefined + | Array<GPUFeatureName | undefined> + ): Promise<void> { + assert( + this.mismatchedProvider === undefined, + "Can't selectMismatchedDeviceOrSkipTestCase() multiple times" + ); + + this.mismatchedProvider = + descriptor === undefined + ? await mismatchedDevicePool.reserve() + : await mismatchedDevicePool.reserve(initUncanonicalizedDeviceDescriptor(descriptor)); + + this.mismatchedAcquiredDevice = this.mismatchedProvider.acquire(); + } + /** GPUQueue for the test to use. (Same as `t.device.queue`.) */ get queue(): GPUQueue { return this.device.queue; @@ -109,6 +159,28 @@ export class GPUTest extends Fixture { throw threw; } } + + if (this.mismatchedProvider) { + // MAINTENANCE_TODO(kainino0x): Deduplicate this with code in GPUTest.finalize + let threw: undefined | Error; + { + const provider = this.mismatchedProvider; + this.mismatchedProvider = undefined; + try { + await mismatchedDevicePool.release(provider); + } catch (ex) { + threw = ex; + } + } + + if (threw) { + if (threw instanceof TestOOMedShouldAttemptGC) { + // Try to clean up, in case there are stray GPU resources in need of collection. + await attemptGarbageCollection(); + } + throw threw; + } + } } /** @@ -410,12 +482,12 @@ export class GPUTest extends Fixture { const readsPerRow = Math.ceil(minBytesPerRow / expectedDataSize); const reducer = ` - [[block]] struct Buffer { data: array<u32>; }; - [[group(0), binding(0)]] var<storage, read> expected: Buffer; - [[group(0), binding(1)]] var<storage, read> in: Buffer; - [[group(0), binding(2)]] var<storage, read_write> out: Buffer; - [[stage(compute), workgroup_size(1)]] fn reduce( - [[builtin(global_invocation_id)]] id: vec3<u32>) { + struct Buffer { data: array<u32>; }; + @group(0) @binding(0) var<storage, read> expected: Buffer; + @group(0) @binding(1) var<storage, read> in: Buffer; + @group(0) @binding(2) var<storage, read_write> out: Buffer; + @stage(compute) @workgroup_size(1) fn reduce( + @builtin(global_invocation_id) id: vec3<u32>) { let rowBaseIndex = id.x * ${bytesPerRow / 4}u; let readSize = ${expectedDataSize / 4}u; out.data[id.x] = 1u; @@ -460,7 +532,7 @@ export class GPUTest extends Fixture { this.expectGPUBufferValuesEqual(resultBuffer, new Uint32Array(expectedResults)); } - // TODO: add an expectContents for textures, which logs data: uris on failure + // MAINTENANCE_TODO: add an expectContents for textures, which logs data: uris on failure /** * Expect a whole GPUTexture to have the single provided color. @@ -546,8 +618,8 @@ export class GPUTest extends Fixture { /** * Expect a single pixel of a 2D texture to have a particular byte representation. * - * TODO: Add check for values of depth/stencil, probably through sampling of shader - * TODO: Can refactor this and expectSingleColor to use a similar base expect + * MAINENANCE_TODO: Add check for values of depth/stencil, probably through sampling of shader + * MAINENANCE_TODO: Can refactor this and expectSingleColor to use a similar base expect */ expectSinglePixelIn2DTexture( src: GPUTexture, @@ -731,14 +803,10 @@ export class GPUTest extends Fixture { /** * Create a GPUBuffer with the specified contents and usage. * - * TODO: Several call sites would be simplified if this took ArrayBuffer as well. + * MAINTENANCE_TODO: Several call sites would be simplified if this took ArrayBuffer as well. */ - makeBufferWithContents( - dataArray: TypedArrayBufferView, - usage: GPUBufferUsageFlags, - opts: { padToMultipleOf4?: boolean } = {} - ): GPUBuffer { - return this.trackForCleanup(makeBufferWithContents(this.device, dataArray, usage, opts)); + makeBufferWithContents(dataArray: TypedArrayBufferView, usage: GPUBufferUsageFlags): GPUBuffer { + return this.trackForCleanup(makeBufferWithContents(this.device, dataArray, usage)); } /** diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/idl/README.txt b/chromium/third_party/webgpu-cts/src/src/webgpu/idl/README.txt index 0e9678d81d3..aa7a983b04b 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/idl/README.txt +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/idl/README.txt @@ -2,3 +2,6 @@ Tests to check that the WebGPU IDL is correctly implemented, for examples that o exactly the correct members, and that methods throw when passed incomplete dictionaries. See https://github.com/gpuweb/cts/issues/332 + +TODO: exposed.html.ts: Test all WebGPU interfaces instead of just some of them. +TODO: Check prototype chains. (Add a helper in IDLTest for this.) diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/idl/exposed.html.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/idl/exposed.html.ts index 2a192e29597..8a9f5a7ca6a 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/idl/exposed.html.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/idl/exposed.html.ts @@ -2,8 +2,6 @@ import { assert } from '../../common/util/util.js'; -// TODO: Test all WebGPU interfaces. - const items = [ globalThis.navigator.gpu, globalThis.GPU, @@ -17,6 +15,7 @@ const items = [ globalThis.GPURenderPipeline, globalThis.GPUDeviceLostInfo, globalThis.GPUValidationError, + // Need to test the rest of the interfaces. ]; for (const item of items) { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/idl/idl_test.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/idl/idl_test.ts index c67b708b439..82111c6d4f1 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/idl/idl_test.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/idl/idl_test.ts @@ -10,8 +10,6 @@ interface UnknownObject { * Base fixture for testing the exposed interface is correct (without actually using WebGPU). */ export class IDLTest extends Fixture { - // TODO: add a helper to check prototype chains - async init(): Promise<void> { // Ensure the GPU provider is initialized getGPU(); @@ -28,8 +26,8 @@ export class IDLTest extends Fixture { /** * Asserts that an IDL interface has the same number of keys as the * - * TODO: add a way to check for the types of keys with unknown values, like methods and attributes - * TODO: handle extensions + * MAINTENANCE_TODO: add a way to check for the types of keys with unknown values, like methods and attributes + * MAINTENANCE_TODO: handle extensions */ assertMemberCount(act: UnknownObject, exp: UnknownObject) { const expKeys = Object.keys(exp); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/abs.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/abs.spec.ts index 0809f46e4aa..8a4cea7c300 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/abs.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/abs.spec.ts @@ -14,7 +14,7 @@ import { u32Bits, } from '../../../util/conversion.js'; -import { anyOf, kBit, kValue, run } from './builtin.js'; +import { anyOf, Config, correctlyRoundedThreshold, kBit, kValue, run } from './builtin.js'; export const g = makeTestGroup(GPUTest); @@ -95,7 +95,10 @@ If e evaluates to the largest negative value, then the result is e. .combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - run(t, 'abs', [TypeI32], TypeI32, t.params, [ + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + run(t, 'abs', [TypeI32], TypeI32, cfg, [ // Min and max i32 // If e evaluates to the largest negative value, then the result is e. { input: i32Bits(kBit.i32.negative.min), expected: i32Bits(kBit.i32.negative.min) }, @@ -148,6 +151,8 @@ abs(e: T ) -> T T is f32 or vecN<f32> Returns the absolute value of e (e.g. e with a positive sign bit). Component-wise when T is a vector. (GLSLstd450Fabs) + +TODO(sarahM0): Check if this is needed (or if it has to fail). If yes add other values. [1] ` ) .params(u => @@ -156,7 +161,10 @@ Component-wise when T is a vector. (GLSLstd450Fabs) .combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - run(t, 'abs', [TypeF32], TypeF32, t.params, [ + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + run(t, 'abs', [TypeF32], TypeF32, cfg, [ // Min and Max f32 { input: f32Bits(kBit.f32.negative.max), expected: f32Bits(0x0080_0000) }, { input: f32Bits(kBit.f32.negative.min), expected: f32Bits(0x7f7f_ffff) }, @@ -164,7 +172,7 @@ Component-wise when T is a vector. (GLSLstd450Fabs) { input: f32Bits(kBit.f32.positive.max), expected: f32Bits(kBit.f32.positive.max) }, // Subnormal f32 - // TODO(sarahM0): Check if this is needed (or if it has to fail). If yes add other values. + // [1] If needed add other values. { input: f32Bits(kBit.f32.subnormal.positive.max), expected: anyOf(f32Bits(kBit.f32.subnormal.positive.max), f32(0)), diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/atan.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/atan.spec.ts index e21976856d2..15ac4743481 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/atan.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/atan.spec.ts @@ -4,7 +4,8 @@ Execution Tests for the 'atan' builtin function import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { GPUTest } from '../../../gpu_test.js'; -import { f32, f32Bits, TypeF32 } from '../../../util/conversion.js'; +import { f32, f32Bits, TypeF32, u32 } from '../../../util/conversion.js'; +import { biasedRange, linearRange } from '../../../util/math.js'; import { Case, Config, kBit, run, ulpThreshold } from './builtin.js'; @@ -17,6 +18,8 @@ g.test('float_builtin_functions,atan') ` atan: T is f32 or vecN<f32> atan(e: T ) -> T Returns the arc tangent of e. Component-wise when T is a vector. (GLSLstd450Atan) + +TODO(#792): Decide what the ground-truth is for these tests. [1] ` ) .params(u => @@ -25,13 +28,13 @@ T is f32 or vecN<f32> atan(e: T ) -> T Returns the arc tangent of e. Component-w .combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - // TODO(https://github.com/gpuweb/cts/issues/792): Decide what the ground-truth is for these tests. - const truthFunc = (x: number): number => { - return Math.atan(x); + // [1]: Need to decide what the ground-truth is. + const truthFunc = (x: number): Case => { + return { input: f32(x), expected: f32(Math.atan(x)) }; }; // Well defined/border cases - const manual: Array<Case> = [ + let cases: Array<Case> = [ { input: f32Bits(kBit.f32.infinity.negative), expected: f32(-Math.PI / 2) }, { input: f32(-Math.sqrt(3)), expected: f32(-Math.PI / 3) }, { input: f32(-1), expected: f32(-Math.PI / 4) }, @@ -40,6 +43,7 @@ T is f32 or vecN<f32> atan(e: T ) -> T Returns the arc tangent of e. Component-w { input: f32(1), expected: f32(Math.PI / 4) }, { input: f32(Math.sqrt(3)), expected: f32(Math.PI / 3) }, { input: f32Bits(kBit.f32.infinity.positive), expected: f32(Math.PI / 2) }, + // Zero-like cases { input: f32(0), expected: f32(0) }, { input: f32Bits(kBit.f32.positive.min), expected: f32(0) }, @@ -56,17 +60,23 @@ T is f32 or vecN<f32> atan(e: T ) -> T Returns the arc tangent of e. Component-w { input: f32Bits(kBit.f32.negative.zero), expected: f32Bits(kBit.f32.positive.zero) }, ]; - // Spread of cases over wide domain - const automatic = new Array<Case>(1000); - const f32Min = f32Bits(kBit.f32.positive.min).value as number; - const f32Max = f32Bits(kBit.f32.positive.max).value as number; - const increment = (f32Max - f32Min) / automatic.length; - for (let i = 0; i < automatic.length; i++) { - const x = f32Min + increment * i; - automatic[i] = { input: f32(x), expected: f32(truthFunc(x)) }; - } + // -2^32 < x <= -1, biased towards -1 + cases = cases.concat(biasedRange(f32(1), f32(2 ** 32), u32(1000)).map(x => truthFunc(-x))); + + // -1 <= x < 0, linearly spread + cases = cases.concat( + linearRange(f32Bits(kBit.f32.positive.min), f32(1), u32(100)).map(x => truthFunc(-x)) + ); + + // 0 < x <= 1, linearly spread + cases = cases.concat( + linearRange(f32Bits(kBit.f32.positive.min), f32(1), u32(100)).map(x => truthFunc(x)) + ); + + // 1 <= x < 2^32, biased towards 1 + cases = cases.concat(biasedRange(f32(1), f32(2 ** 32), u32(1000)).map(x => truthFunc(x))); const cfg: Config = t.params; cfg.cmpFloats = ulpThreshold(4096); - run(t, 'atan', [TypeF32], TypeF32, cfg, manual.concat(automatic)); + run(t, 'atan', [TypeF32], TypeF32, cfg, cases); }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/builtin.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/builtin.ts index 1dcd10787d7..9643f1af6d9 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/builtin.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/builtin.ts @@ -1,6 +1,7 @@ import { Colors } from '../../../../common/util/colors.js'; import { GPUTest } from '../../../gpu_test.js'; import { + f32, ScalarType, Scalar, Vector, @@ -10,7 +11,7 @@ import { TypeU32, VectorType, } from '../../../util/conversion.js'; -import { diffULP } from '../../../util/math.js'; +import { correctlyRounded, diffULP } from '../../../util/math.js'; /** Comparison describes the result of a Comparator function. */ export interface Comparison { @@ -58,6 +59,17 @@ export function ulpThreshold(ulp: number): FloatMatch { }; } +/** + * @returns a FloatMatch that returns true iff |expected| is a correctly round + * to |got|. + * |got| must be expressible as a float32. + */ +export function correctlyRoundedThreshold(): FloatMatch { + return (got, expected) => { + return correctlyRounded(f32(got), expected); + }; +} + // compare() compares 'got' to 'expected', returning the Comparison information. function compare(got: Value, expected: Value, cmpFloats: FloatMatch): Comparison { { @@ -295,30 +307,28 @@ function runBatch( const source = ` struct Parameters { ${parameterTypes - .map((ty, i) => ` [[size(${kValueStride})]] param${i} : ${storageType(ty)};`) + .map((ty, i) => ` @size(${kValueStride}) param${i} : ${storageType(ty)};`) .join('\n')} }; -[[block]] struct Inputs { test : array<Parameters, ${cases.length}>; }; -[[block]] struct Outputs { - test : [[stride(${kValueStride})]] array<${storageType(returnType)}, ${cases.length}>; + test : @stride(${kValueStride}) array<${storageType(returnType)}, ${cases.length}>; }; ${ storageClass === 'uniform' - ? `[[group(0), binding(0)]] var<uniform> inputs : Inputs;` - : `[[group(0), binding(0)]] var<storage, ${ + ? `@group(0) @binding(0) var<uniform> inputs : Inputs;` + : `@group(0) @binding(0) var<storage, ${ storageClass === 'storage_r' ? 'read' : 'read_write' }> inputs : Inputs;` } -[[group(0), binding(1)]] var<storage, write> outputs : Outputs; +@group(0) @binding(1) var<storage, write> outputs : Outputs; -[[stage(compute), workgroup_size(1)]] +@stage(compute) @workgroup_size(1) fn main() { for(var i = 0; i < ${cases.length}; i = i + 1) { outputs.test[i] = ${expr}; @@ -494,7 +504,7 @@ function packScalarsToVector( }; } -// TODO(sarahM0): Perhaps instead of kBit and kValue tables we could have one table +// MAINTENANCE_TODO(sarahM0): Perhaps instead of kBit and kValue tables we could have one table // where every value is a Scalar instead of either bits or value? // Then tests wouldn't need most of the Scalar.fromX calls, // and you would probably need fewer table entries in total diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/ceil.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/ceil.spec.ts index 3c741b6a28a..217c3ab64b7 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/ceil.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/ceil.spec.ts @@ -6,7 +6,7 @@ import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { GPUTest } from '../../../gpu_test.js'; import { f32, f32Bits, TypeF32 } from '../../../util/conversion.js'; -import { anyOf, kBit, kValue, run } from './builtin.js'; +import { anyOf, Config, correctlyRoundedThreshold, kBit, kValue, run } from './builtin.js'; export const g = makeTestGroup(GPUTest); @@ -28,7 +28,10 @@ https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md .combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - run(t, 'ceil', [TypeF32], TypeF32, t.params, [ + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + run(t, 'ceil', [TypeF32], TypeF32, cfg, [ // Small positive numbers { input: f32(0.1), expected: f32(1.0) }, { input: f32(0.9), expected: f32(1.0) }, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/clamp.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/clamp.spec.ts new file mode 100644 index 00000000000..536574356f1 --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/clamp.spec.ts @@ -0,0 +1,167 @@ +export const description = ` +Execution Tests for the 'clamp' builtin function +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../gpu_test.js'; +import { + f32, + f32Bits, + i32, + i32Bits, + Scalar, + TypeF32, + TypeI32, + TypeU32, + u32, + u32Bits, +} from '../../../util/conversion.js'; +import { isSubnormalScalar } from '../../../util/math.js'; + +import { anyOf, Case, Config, correctlyRoundedThreshold, kBit, run } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +/** Generate set of clamp test cases from an ascending list of values */ +function generateTestCases(test_values: Array<Scalar>): Array<Case> { + const cases = new Array<Case>(); + test_values.forEach((e, ei) => { + test_values.forEach((f, fi) => { + test_values.forEach((g, gi) => { + // This is enforcing that low <= high, since most backends languages + // have undefined behaviour for high < low, so implementations would + // need to polyfill, and it is unclear if this was intended. + // + // https://github.com/gpuweb/gpuweb/issues/2557 discusses changing the + // spec to explicitly require low <= high. + if (fi <= gi) { + // Intentionally not using clamp from math.ts or other TypeScript + // defined clamp to avoid rounding issues from going in/out of + // `number` type. + const precise_expected = ei <= fi ? f : ei <= gi ? e : g; + const expected = isSubnormalScalar(precise_expected) + ? anyOf(precise_expected, f32(0.0)) + : precise_expected; + cases.push({ input: [e, f, g], expected }); + } + }); + }); + }); + return cases; +} + +g.test('integer_builtin_functions,unsigned_clamp') + .uniqueId('386458e12e52645b') + .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions') + .desc( + ` +unsigned clamp: +T is u32 or vecN<u32> clamp(e: T , low: T, high: T) -> T Returns min(max(e, low), high). Component-wise when T is a vector. (GLSLstd450UClamp) + +Please read the following guidelines before contributing: +https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md +` + ) + .params(u => + u + .combine('storageClass', ['uniform', 'storage_r', 'storage_rw'] as const) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + // This array must be strictly increasing, since that ordering determines + // the expected values. + const test_values: Array<Scalar> = [ + u32Bits(kBit.u32.min), + u32(1), + u32(2), + u32(0x70000000), + u32(0x80000000), + u32Bits(kBit.u32.max), + ]; + + run(t, 'clamp', [TypeU32, TypeU32, TypeU32], TypeU32, cfg, generateTestCases(test_values)); + }); + +g.test('integer_builtin_functions,signed_clamp') + .uniqueId('da51d3c8cc902ab2') + .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions') + .desc( + ` +signed clamp: +T is i32 or vecN<i32> clamp(e: T , low: T, high: T) -> T Returns min(max(e, low), high). Component-wise when T is a vector. (GLSLstd450SClamp) + +Please read the following guidelines before contributing: +https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md +` + ) + .params(u => + u + .combine('storageClass', ['uniform', 'storage_r', 'storage_rw'] as const) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + // This array must be strictly increasing, since that ordering determines + // the expected values. + const test_values: Array<Scalar> = [ + i32Bits(kBit.i32.negative.min), + i32(-2), + i32(-1), + i32(0), + i32(1), + i32(2), + i32Bits(0x70000000), + i32Bits(kBit.i32.positive.max), + ]; + + run(t, 'clamp', [TypeI32, TypeI32, TypeI32], TypeI32, cfg, generateTestCases(test_values)); + }); + +g.test('float_builtin_functions,clamp') + .uniqueId('88e39c61e6dbd26f') + .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#float-builtin-functions') + .desc( + ` +clamp: +T is f32 or vecN<f32> clamp(e: T , low: T, high: T) -> T Returns min(max(e, low), high). Component-wise when T is a vector. (GLSLstd450NClamp) + +Please read the following guidelines before contributing: +https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md +` + ) + .params(u => + u + .combine('storageClass', ['uniform', 'storage_r', 'storage_rw'] as const) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + // This array must be strictly increasing, since that ordering determines + // the expected values. + const test_values: Array<Scalar> = [ + f32Bits(kBit.f32.infinity.negative), + f32Bits(kBit.f32.negative.min), + f32(-10.0), + f32(-1.0), + f32Bits(kBit.f32.negative.max), + f32Bits(kBit.f32.subnormal.negative.min), + f32Bits(kBit.f32.subnormal.negative.max), + f32(0.0), + f32Bits(kBit.f32.subnormal.positive.min), + f32Bits(kBit.f32.subnormal.positive.max), + f32Bits(kBit.f32.positive.min), + f32(1.0), + f32(10.0), + f32Bits(kBit.f32.positive.max), + f32Bits(kBit.f32.infinity.positive), + ]; + + run(t, 'clamp', [TypeF32, TypeF32, TypeF32], TypeF32, cfg, generateTestCases(test_values)); + }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/cos.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/cos.spec.ts index fe49d0a1f50..d67c6e1f9c5 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/cos.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/cos.spec.ts @@ -4,7 +4,8 @@ Execution Tests for the 'cos' builtin function import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { GPUTest } from '../../../gpu_test.js'; -import { f32, TypeF32 } from '../../../util/conversion.js'; +import { f32, TypeF32, u32 } from '../../../util/conversion.js'; +import { linearRange } from '../../../util/math.js'; import { absThreshold, Case, Config, run } from './builtin.js'; @@ -20,6 +21,8 @@ T is f32 or vecN<f32> cos(e: T ) -> T Returns the cosine of e. Component-wise wh Please read the following guidelines before contributing: https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md + +TODO(#792): Decide what the ground-truth is for these tests. [1] ` ) .params(u => @@ -28,12 +31,13 @@ https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md .combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - const cases = new Array<Case>(1000); - for (let i = 0; i < cases.length; i++) { - // TODO(https://github.com/gpuweb/cts/issues/792): Decide what the ground-truth is for these tests. - const angle = -Math.PI + (2.0 * Math.PI * i) / (cases.length - 1); - cases[i] = { input: f32(angle), expected: f32(Math.cos(angle)) }; - } + // [1]: Need to decide what the ground-truth is. + const truthFunc = (x: number): Case => { + return { input: f32(x), expected: f32(Math.cos(x)) }; + }; + + // Spec defines accuracy on [-π, π] + const cases = linearRange(f32(-Math.PI), f32(Math.PI), u32(1000)).map(x => truthFunc(x)); const cfg: Config = t.params; cfg.cmpFloats = absThreshold(2 ** -11); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/float_built_functions.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/float_built_functions.spec.ts index 6a5a8e7e0e4..da78d01b0e0 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/float_built_functions.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/float_built_functions.spec.ts @@ -50,21 +50,6 @@ https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md .params(u => u.combine('placeHolder1', ['placeHolder2', 'placeHolder3'])) .unimplemented(); -g.test('float_builtin_functions,clamp') - .uniqueId('88e39c61e6dbd26f') - .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#float-builtin-functions') - .desc( - ` -clamp: -T is f32 or vecN<f32> clamp(e1: T ,e2: T ,e3: T) -> T Returns min(max(e1,e2),e3). Component-wise when T is a vector. (GLSLstd450NClamp) - -Please read the following guidelines before contributing: -https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md -` - ) - .params(u => u.combine('placeHolder1', ['placeHolder2', 'placeHolder3'])) - .unimplemented(); - g.test('float_builtin_functions,cosh') .uniqueId('e4499ece6f25610d') .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#float-builtin-functions') @@ -170,21 +155,6 @@ https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md .params(u => u.combine('placeHolder1', ['placeHolder2', 'placeHolder3'])) .unimplemented(); -g.test('float_builtin_functions,fract') - .uniqueId('58222ecf6f963798') - .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#float-builtin-functions') - .desc( - ` -fract: -T is f32 or vecN<f32> fract(e: T ) -> T Returns the fractional bits of e (e.g. e - floor(e)). Component-wise when T is a vector. (GLSLstd450Fract) - -Please read the following guidelines before contributing: -https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md -` - ) - .params(u => u.combine('placeHolder1', ['placeHolder2', 'placeHolder3'])) - .unimplemented(); - g.test('float_builtin_functions,scalar_case_frexp') .uniqueId('c5df46977f5b77a0') .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#float-builtin-functions') @@ -292,36 +262,6 @@ https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md .params(u => u.combine('placeHolder1', ['placeHolder2', 'placeHolder3'])) .unimplemented(); -g.test('float_builtin_functions,max') - .uniqueId('bcb6c69b4ec703b1') - .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#float-builtin-functions') - .desc( - ` -max: -T is f32 or vecN<f32> max(e1: T ,e2: T ) -> T Returns e2 if e1 is less than e2, and e1 otherwise. If one operand is a NaN, the other is returned. If both operands are NaNs, a NaN is returned. Component-wise when T is a vector. (GLSLstd450NMax) - -Please read the following guidelines before contributing: -https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md -` - ) - .params(u => u.combine('placeHolder1', ['placeHolder2', 'placeHolder3'])) - .unimplemented(); - -g.test('float_builtin_functions,min') - .uniqueId('53efc46faad0f380') - .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#float-builtin-functions') - .desc( - ` -min: -T is f32 or vecN<f32> min(e1: T ,e2: T ) -> T Returns e2 if e2 is less than e1, and e1 otherwise. If one operand is a NaN, the other is returned. If both operands are NaNs, a NaN is returned. Component-wise when T is a vector. (GLSLstd450NMin) - -Please read the following guidelines before contributing: -https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md -` - ) - .params(u => u.combine('placeHolder1', ['placeHolder2', 'placeHolder3'])) - .unimplemented(); - g.test('float_builtin_functions,mix_all_same_type_operands') .uniqueId('f17861e71386bb59') .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#float-builtin-functions') diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/floor.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/floor.spec.ts index 702750840f7..0ca9d542746 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/floor.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/floor.spec.ts @@ -6,7 +6,7 @@ import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { GPUTest } from '../../../gpu_test.js'; import { f32, f32Bits, TypeF32 } from '../../../util/conversion.js'; -import { anyOf, kBit, kValue, run } from './builtin.js'; +import { anyOf, Config, correctlyRoundedThreshold, kBit, kValue, run } from './builtin.js'; export const g = makeTestGroup(GPUTest); @@ -25,7 +25,10 @@ T is f32 or vecN<f32> floor(e: T ) -> T Returns the floor of e. Component-wise w .combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - run(t, 'floor', [TypeF32], TypeF32, t.params, [ + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + run(t, 'floor', [TypeF32], TypeF32, cfg, [ // Small positive numbers { input: f32(0.1), expected: f32(0.0) }, { input: f32(0.9), expected: f32(0.0) }, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/fract.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/fract.spec.ts new file mode 100644 index 00000000000..e87cf85e270 --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/fract.spec.ts @@ -0,0 +1,75 @@ +export const description = ` +Execution Tests for the 'fract' builtin function +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../gpu_test.js'; +import { f32, f32Bits, TypeF32 } from '../../../util/conversion.js'; + +import { anyOf, Config, correctlyRoundedThreshold, kBit, run } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('float_builtin_functions,fract') + .uniqueId('58222ecf6f963798') + .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#float-builtin-functions') + .desc( + ` +fract: +T is f32 or vecN<f32> fract(e: T ) -> T Returns the fractional bits of e (e.g. e - floor(e)). Component-wise when T is a vector. (GLSLstd450Fract) +` + ) + .params(u => + u + .combine('storageClass', ['uniform', 'storage_r', 'storage_rw'] as const) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + run(t, 'fract', [TypeF32], TypeF32, cfg, [ + // Zeroes + { input: f32Bits(kBit.f32.positive.zero), expected: f32(0) }, + { input: f32Bits(kBit.f32.negative.zero), expected: f32(0) }, + + // Positive numbers + { input: f32Bits(0x3dcccccd), expected: f32Bits(0x3dcccccd) }, // ~0.1 -> ~0.1 + { input: f32(0.5), expected: f32(0.5) }, // 0.5 -> 0.5 + { input: f32Bits(0x3f666666), expected: f32Bits(0x3f666666) }, // ~0.9 -> ~0.9 + { input: f32Bits(0x3f800000), expected: f32(0) }, // 1 -> 0 + { input: f32Bits(0x40000000), expected: f32(0) }, // 2 -> 0 + { input: f32Bits(0x3f8e147b), expected: f32Bits(0x3de147b0) }, // ~1.11 -> ~0.11 + { input: f32Bits(0x41200069), expected: f32Bits(0x38d20000) }, // ~10.0001 -> ~0.0001 + + // Negative numbers + { input: f32Bits(0xbdcccccd), expected: f32Bits(0x3f666666) }, // ~-0.1 -> ~0.9 + { input: f32(-0.5), expected: f32(0.5) }, // -0.5 -> 0.5 + { input: f32Bits(0xbf666666), expected: f32Bits(0x3dccccd0) }, // ~-0.9 -> ~0.1 + { input: f32Bits(0xbf800000), expected: f32(0) }, // -1 -> 0 + { input: f32Bits(0xc0000000), expected: f32(0) }, // -2 -> 0 + { input: f32Bits(0xbf8e147b), expected: f32Bits(0x3f63d70a) }, // ~-1.11 -> ~0.89 + { input: f32Bits(0xc1200419), expected: f32Bits(0x3f7fbe70) }, // ~-10.0001 -> ~0.999 + + // Min and Max f32 + { input: f32Bits(kBit.f32.positive.min), expected: f32Bits(kBit.f32.positive.min) }, + { input: f32Bits(kBit.f32.positive.max), expected: f32(0) }, + // For negative numbers on the extremes, here and below, different backends disagree on if this should be 1 + // exactly vs very close to 1. I think what is occurring is that if the calculation is internally done in f32, + // i.e. rounding/flushing each step, you end up with one value, but if all of the math is done in a higher + // precision, and then converted at the end, you end up with a different value. + { input: f32Bits(kBit.f32.negative.max), expected: anyOf(f32Bits(0x3f7fffff), f32(1)) }, + { input: f32Bits(kBit.f32.negative.min), expected: f32(0) }, + + // Subnormal f32 + // prettier-ignore + { input: f32Bits(kBit.f32.subnormal.positive.max), expected: anyOf(f32(0), f32Bits(kBit.f32.subnormal.positive.max)) }, + // prettier-ignore + { input: f32Bits(kBit.f32.subnormal.positive.min), expected: anyOf(f32(0), f32Bits(kBit.f32.subnormal.positive.min)) }, + // Similar to above when these values are not immediately flushed to zero, how the back end internally calculates + // the value will dictate if the end value is 1 or very close to 1. + // prettier-ignore + { input: f32Bits(kBit.f32.subnormal.negative.max), expected: anyOf(f32(0), f32Bits(0x3f7fffff), f32(1)) }, + // prettier-ignore + { input: f32Bits(kBit.f32.subnormal.negative.min), expected: anyOf(f32(0), f32Bits(0x3f7fffff), f32(1)) }, + ]); + }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/integer_built_in_functions.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/integer_built_in_functions.spec.ts index dc1d4b5af1e..98538942836 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/integer_built_in_functions.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/integer_built_in_functions.spec.ts @@ -5,36 +5,6 @@ import { GPUTest } from '../../../gpu_test.js'; export const g = makeTestGroup(GPUTest); -g.test('integer_builtin_functions,unsigned_clamp') - .uniqueId('386458e12e52645b') - .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions') - .desc( - ` -unsigned clamp: -T is u32 or vecN<u32> clamp(e1: T ,e2: T,e3: T) -> T Returns min(max(e1,e2),e3). Component-wise when T is a vector. (GLSLstd450UClamp) - -Please read the following guidelines before contributing: -https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md -` - ) - .params(u => u.combine('placeHolder1', ['placeHolder2', 'placeHolder3'])) - .unimplemented(); - -g.test('integer_builtin_functions,signed_clamp') - .uniqueId('da51d3c8cc902ab2') - .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions') - .desc( - ` -signed clamp: -T is i32 or vecN<i32> clamp(e1: T ,e2: T,e3: T) -> T Returns min(max(e1,e2),e3). Component-wise when T is a vector. (GLSLstd450SClamp) - -Please read the following guidelines before contributing: -https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md -` - ) - .params(u => u.combine('placeHolder1', ['placeHolder2', 'placeHolder3'])) - .unimplemented(); - g.test('integer_builtin_functions,count_1_bits') .uniqueId('259605bdcc180a4b') .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions') @@ -50,36 +20,6 @@ https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md .params(u => u.combine('placeHolder1', ['placeHolder2', 'placeHolder3'])) .unimplemented(); -g.test('integer_builtin_functions,unsigned_max') - .uniqueId('2cce54f65e71b3a3') - .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions') - .desc( - ` -unsigned max: -T is u32 or vecN<u32> max(e1: T ,e2: T) -> T Returns e2 if e1 is less than e2, and e1 otherwise. Component-wise when T is a vector. (GLSLstd450UMax) - -Please read the following guidelines before contributing: -https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md -` - ) - .params(u => u.combine('placeHolder1', ['placeHolder2', 'placeHolder3'])) - .unimplemented(); - -g.test('integer_builtin_functions,signed_max') - .uniqueId('ef8c37107946a69e') - .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions') - .desc( - ` -signed max: -T is i32 or vecN<i32> max(e1: T ,e2: T) -> T Returns e2 if e1 is less than e2, and e1 otherwise. Component-wise when T is a vector. (GLSLstd450SMax) - -Please read the following guidelines before contributing: -https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md -` - ) - .params(u => u.combine('placeHolder1', ['placeHolder2', 'placeHolder3'])) - .unimplemented(); - g.test('integer_builtin_functions,bit_reversal') .uniqueId('8a7550f1097993f8') .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions') diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/inversesqrt.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/inversesqrt.spec.ts new file mode 100644 index 00000000000..64571ecf6e1 --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/inversesqrt.spec.ts @@ -0,0 +1,53 @@ +export const description = ` +Execution Tests for the 'inverseSqrt' builtin function +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../gpu_test.js'; +import { f32, f32Bits, TypeF32, u32 } from '../../../util/conversion.js'; +import { biasedRange, linearRange } from '../../../util/math.js'; + +import { Case, Config, kBit, run, ulpThreshold } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('float_builtin_functions,inverseSqrt') + .uniqueId('84fc180ad82c5618') + .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#float-builtin-functions') + .desc( + ` +inverseSqrt: +T is f32 or vecN<f32> inverseSqrt(e: T ) -> T Returns the reciprocal of sqrt(e). Component-wise when T is a vector. (GLSLstd450InverseSqrt) + +Please read the following guidelines before contributing: +https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md +` + ) + .params(u => + u + .combine('storageClass', ['uniform', 'storage_r', 'storage_rw'] as const) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + // [1]: Need to decide what the ground-truth is. + const truthFunc = (x: number): Case => { + return { input: f32(x), expected: f32(1 / Math.sqrt(x)) }; + }; + + // Well defined cases + let cases: Array<Case> = [ + { input: f32Bits(kBit.f32.infinity.positive), expected: f32(0) }, + { input: f32(1), expected: f32(1) }, + ]; + + // 0 < x <= 1 linearly spread + cases = cases.concat( + linearRange(f32Bits(kBit.f32.positive.min), f32(1), u32(100)).map(x => truthFunc(x)) + ); + // 1 <= x < 2^32, biased towards 1 + cases = cases.concat(biasedRange(f32(1), f32(2 ** 32), u32(1000)).map(x => truthFunc(x))); + + const cfg: Config = t.params; + cfg.cmpFloats = ulpThreshold(2); + run(t, 'inverseSqrt', [TypeF32], TypeF32, cfg, cases); + }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/max.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/max.spec.ts new file mode 100644 index 00000000000..01ef3268da1 --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/max.spec.ts @@ -0,0 +1,152 @@ +export const description = ` +Execution Tests for the 'max' builtin function +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../gpu_test.js'; +import { + f32, + f32Bits, + i32, + i32Bits, + Scalar, + TypeF32, + TypeI32, + TypeU32, + u32, +} from '../../../util/conversion.js'; +import { isSubnormalScalar } from '../../../util/math.js'; + +import { anyOf, Case, Config, correctlyRoundedThreshold, kBit, run } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +/** Generate set of max test cases from an ascending list of values */ +function generateTestCases(test_values: Array<Scalar>) { + const cases = new Array<Case>(); + test_values.forEach((e, ei) => { + test_values.forEach((f, fi) => { + const precise_expected = ei >= fi ? e : f; + const expected = isSubnormalScalar(precise_expected) + ? anyOf(precise_expected, f32(0.0)) + : precise_expected; + cases.push({ input: [e, f], expected }); + }); + }); + return cases; +} + +g.test('integer_builtin_functions,unsigned_max') + .uniqueId('2cce54f65e71b3a3') + .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions') + .desc( + ` +unsigned max: +T is u32 or vecN<u32> max(e1: T ,e2: T) -> T Returns e2 if e1 is less than e2, and e1 otherwise. Component-wise when T is a vector. (GLSLstd450UMax) + +Please read the following guidelines before contributing: +https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md +` + ) + .params(u => + u + .combine('storageClass', ['uniform', 'storage_r', 'storage_rw'] as const) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + // This array must be strictly increasing, since that ordering determines + // the expected values. + const test_values: Array<Scalar> = [ + u32(0), + u32(1), + u32(2), + u32(0x70000000), + u32(0x80000000), + u32(0xffffffff), + ]; + + run(t, 'max', [TypeU32, TypeU32], TypeU32, cfg, generateTestCases(test_values)); + }); + +g.test('integer_builtin_functions,signed_max') + .uniqueId('ef8c37107946a69e') + .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions') + .desc( + ` +signed max: +T is i32 or vecN<i32> max(e1: T ,e2: T) -> T Returns e2 if e1 is less than e2, and e1 otherwise. Component-wise when T is a vector. (GLSLstd450SMax) + +Please read the following guidelines before contributing: +https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md +` + ) + .params(u => + u + .combine('storageClass', ['uniform', 'storage_r', 'storage_rw'] as const) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + // This array must be strictly increasing, since that ordering determines + // the expected values. + const test_values: Array<Scalar> = [ + i32Bits(0x80000000), + i32(-2), + i32(-1), + i32(0), + i32(1), + i32(2), + i32Bits(0x70000000), + ]; + + run(t, 'max', [TypeI32, TypeI32], TypeI32, cfg, generateTestCases(test_values)); + }); + +g.test('float_builtin_functions,max') + .uniqueId('bcb6c69b4ec703b1') + .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#float-builtin-functions') + .desc( + ` +max: +T is f32 or vecN<f32> max(e1: T ,e2: T ) -> T Returns e2 if e1 is less than e2, and e1 otherwise. If one operand is a NaN, the other is returned. If both operands are NaNs, a NaN is returned. Component-wise when T is a vector. (GLSLstd450NMax) + +Please read the following guidelines before contributing: +https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md +` + ) + .params(u => + u + .combine('storageClass', ['uniform', 'storage_r', 'storage_rw'] as const) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + // This array must be strictly increasing, since that ordering determines + // the expected values. + const test_values: Array<Scalar> = [ + f32Bits(kBit.f32.infinity.negative), + f32Bits(kBit.f32.negative.min), + f32(-10.0), + f32(-1.0), + f32Bits(kBit.f32.negative.max), + f32Bits(kBit.f32.subnormal.negative.min), + f32Bits(kBit.f32.subnormal.negative.max), + f32(0.0), + f32Bits(kBit.f32.subnormal.positive.min), + f32Bits(kBit.f32.subnormal.positive.max), + f32Bits(kBit.f32.positive.min), + f32(1.0), + f32(10.0), + f32Bits(kBit.f32.positive.max), + f32Bits(kBit.f32.infinity.positive), + ]; + + run(t, 'max', [TypeF32, TypeF32], TypeF32, cfg, generateTestCases(test_values)); + }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/min.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/min.spec.ts index 9b6305b69ab..70544be05e9 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/min.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/builtin/min.spec.ts @@ -4,12 +4,38 @@ Execution Tests for the 'min' builtin function import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { GPUTest } from '../../../gpu_test.js'; -import { i32, i32Bits, TypeI32, TypeU32, u32 } from '../../../util/conversion.js'; +import { + f32, + f32Bits, + i32, + i32Bits, + Scalar, + TypeF32, + TypeI32, + TypeU32, + u32, +} from '../../../util/conversion.js'; +import { isSubnormalScalar } from '../../../util/math.js'; -import { run } from './builtin.js'; +import { anyOf, Case, Config, correctlyRoundedThreshold, kBit, run } from './builtin.js'; export const g = makeTestGroup(GPUTest); +/** Generate set of min test cases from an ascending list of values */ +function generateTestCases(test_values: Array<Scalar>): Array<Case> { + const cases = new Array<Case>(); + test_values.forEach((e, ei) => { + test_values.forEach((f, fi) => { + const precise_expected = ei <= fi ? e : f; + const expected = isSubnormalScalar(precise_expected) + ? anyOf(precise_expected, f32(0.0)) + : precise_expected; + cases.push({ input: [e, f], expected }); + }); + }); + return cases; +} + g.test('integer_builtin_functions,unsigned_min') .uniqueId('29aba7ede5b93cdd') .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions') @@ -28,18 +54,21 @@ https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md .combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - run(t, 'min', [TypeU32, TypeU32], TypeU32, t.params, [ - { input: [u32(1), u32(1)], expected: u32(1) }, - { input: [u32(0), u32(0)], expected: u32(0) }, - { input: [u32(0xffffffff), u32(0xffffffff)], expected: u32(0xffffffff) }, - { input: [u32(1), u32(2)], expected: u32(1) }, - { input: [u32(2), u32(1)], expected: u32(1) }, - { input: [u32(0x70000000), u32(0x80000000)], expected: u32(0x70000000) }, - { input: [u32(0x80000000), u32(0x70000000)], expected: u32(0x70000000) }, - { input: [u32(0), u32(0xffffffff)], expected: u32(0) }, - { input: [u32(0xffffffff), u32(0)], expected: u32(0) }, - { input: [u32(0), u32(0xffffffff)], expected: u32(0) }, - ]); + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + // This array must be strictly increasing, since that ordering determines + // the expected values. + const test_values: Array<Scalar> = [ + u32(0), + u32(1), + u32(2), + u32(0x70000000), + u32(0x80000000), + u32(0xffffffff), + ]; + + run(t, 'min', [TypeU32, TypeU32], TypeU32, cfg, generateTestCases(test_values)); }); g.test('integer_builtin_functions,signed_min') @@ -60,19 +89,64 @@ https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md .combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - run(t, 'min', [TypeI32, TypeI32], TypeI32, t.params, [ - { input: [i32(1), i32(1)], expected: i32(1) }, - { input: [i32(0), i32(0)], expected: i32(0) }, - { input: [i32(-1), i32(-1)], expected: i32(-1) }, - { input: [i32(1), i32(2)], expected: i32(1) }, - { input: [i32(2), i32(1)], expected: i32(1) }, - { input: [i32(-1), i32(-2)], expected: i32(-2) }, - { input: [i32(-2), i32(-1)], expected: i32(-2) }, - { input: [i32(1), i32(-1)], expected: i32(-1) }, - { input: [i32(-1), i32(1)], expected: i32(-1) }, - { input: [i32Bits(0x70000000), i32Bits(0x80000000)], expected: i32Bits(0x80000000) }, - { input: [i32Bits(0x80000000), i32Bits(0x70000000)], expected: i32Bits(0x80000000) }, - { input: [i32Bits(0xffffffff), i32(0)], expected: i32Bits(0xffffffff) }, - { input: [i32(0), i32Bits(0xffffffff)], expected: i32Bits(0xffffffff) }, - ]); + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + // This array must be strictly increasing, since that ordering determines + // the expected values. + const test_values: Array<Scalar> = [ + i32Bits(0x80000000), + i32(-2), + i32(-1), + i32(0), + i32(1), + i32(2), + i32Bits(0x70000000), + ]; + + run(t, 'min', [TypeI32, TypeI32], TypeI32, cfg, generateTestCases(test_values)); + }); + +g.test('float_builtin_functions,min') + .uniqueId('53efc46faad0f380') + .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#float-builtin-functions') + .desc( + ` +min: +T is f32 or vecN<f32> min(e1: T ,e2: T ) -> T Returns e2 if e2 is less than e1, and e1 otherwise. If one operand is a NaN, the other is returned. If both operands are NaNs, a NaN is returned. Component-wise when T is a vector. (GLSLstd450NMin) + +Please read the following guidelines before contributing: +https://github.com/gpuweb/cts/blob/main/docs/plan_autogen.md +` + ) + .params(u => + u + .combine('storageClass', ['uniform', 'storage_r', 'storage_rw'] as const) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + cfg.cmpFloats = correctlyRoundedThreshold(); + + // This array must be strictly increasing, since that ordering determines + // the expected values. + const test_values: Array<Scalar> = [ + f32Bits(kBit.f32.infinity.negative), + f32Bits(kBit.f32.negative.min), + f32(-10.0), + f32(-1.0), + f32Bits(kBit.f32.negative.max), + f32Bits(kBit.f32.subnormal.negative.min), + f32Bits(kBit.f32.subnormal.negative.max), + f32(0.0), + f32Bits(kBit.f32.subnormal.positive.min), + f32Bits(kBit.f32.subnormal.positive.max), + f32Bits(kBit.f32.positive.min), + f32(1.0), + f32(10.0), + f32Bits(kBit.f32.positive.max), + f32Bits(kBit.f32.infinity.positive), + ]; + + run(t, 'min', [TypeF32, TypeF32], TypeF32, cfg, generateTestCases(test_values)); }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/memory_model/atomicity.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/memory_model/atomicity.spec.ts new file mode 100644 index 00000000000..e587bb3a047 --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/memory_model/atomicity.spec.ts @@ -0,0 +1,13 @@ +export const description = `Tests for the atomicity of atomic read-modify-write instructions.`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../gpu_test.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('atomicity') + .desc( + `Checks whether a store on one thread can interrupt an atomic RMW on a second thread. + ` + ) + .unimplemented(); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/memory_model/barrier.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/memory_model/barrier.spec.ts new file mode 100644 index 00000000000..2f39e301a15 --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/memory_model/barrier.spec.ts @@ -0,0 +1,15 @@ +export const description = ` +Tests for non-atomic memory synchronization within a workgroup in the presence of a WebGPU barrier`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../gpu_test.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('workgroup_barrier_store_load') + .desc( + `Checks whether the workgroup barrier properly synchronizes a non-atomic write and read on + separate threads in the same workgroup. + ` + ) + .unimplemented(); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/memory_model/coherence.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/memory_model/coherence.spec.ts new file mode 100644 index 00000000000..98714ca1e4a --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/memory_model/coherence.spec.ts @@ -0,0 +1,16 @@ +export const description = ` +Tests that all threads see a sequentially consistent view of the order of memory +accesses to a single memory location. Uses a parallel testing strategy along with stressing +threads to increase coverage of possible bugs.`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../gpu_test.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('corr') + .desc( + `Ensures two reads on one thread cannot observe an inconsistent view of a write on a second thread. + ` + ) + .unimplemented(); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/memory_model/weak.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/memory_model/weak.spec.ts new file mode 100644 index 00000000000..67ad1fc8dbb --- /dev/null +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/memory_model/weak.spec.ts @@ -0,0 +1,17 @@ +export const description = ` +Tests for properties of the WebGPU memory model involving two memory locations. +Specifically, the acquire/release ordering provided by WebGPU's barriers can be used to disallow +weak behaviors in several classic memory model litmus tests.`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../gpu_test.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('message_passing_workgroup_memory') + .desc( + `Checks whether two reads on one thread can observe two writes in another thread in a way + that is inconsistent with sequential consistency. + ` + ) + .unimplemented(); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/robust_access.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/robust_access.spec.ts index 73920d055fa..4ab2995de7c 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/robust_access.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/robust_access.spec.ts @@ -41,21 +41,21 @@ function runShaderTest( }); const source = ` - [[block]] struct Constants { + struct Constants { zero: u32; }; - [[group(1), binding(0)]] var<uniform> constants: Constants; + @group(1) @binding(0) var<uniform> constants: Constants; - [[block]] struct Result { + struct Result { value: u32; }; - [[group(1), binding(1)]] var<storage, write> result: Result; + @group(1) @binding(1) var<storage, write> result: Result; ${testSource} - [[stage(compute), workgroup_size(1)]] + @stage(compute) @workgroup_size(1) fn main() { - ignore(constants.zero); // Ensure constants buffer is statically-accessed + _ = constants.zero; // Ensure constants buffer is statically-accessed result.value = runTest(); }`; @@ -211,10 +211,10 @@ g.test('linear_memory') bufferBindingSize = layout.size; const qualifiers = storageClass === 'storage' ? `storage, ${storageMode}` : storageClass; globalSource += ` - [[block]] struct TestData { + struct TestData { data: ${type}; }; - [[group(0), binding(0)]] var<${qualifiers}> s: TestData;`; + @group(0) @binding(0) var<${qualifiers}> s: TestData;`; testGroupBGLEntires.push({ binding: 0, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/robust_access_vertex.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/robust_access_vertex.spec.ts index aaacb599e1b..696e54fbe7a 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/robust_access_vertex.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/robust_access_vertex.spec.ts @@ -351,7 +351,7 @@ class F extends GPUTest { let currAttribute = 0; for (let i = 0; i < bufferCount; i++) { for (let j = 0; j < attributesPerBuffer; j++) { - layoutStr += `[[location(${currAttribute})]] a_${currAttribute} : ${typeInfo.wgslType};\n`; + layoutStr += `@location(${currAttribute}) a_${currAttribute} : ${typeInfo.wgslType};\n`; attributeNames.push(`a_${currAttribute}`); currAttribute++; } @@ -370,10 +370,10 @@ class F extends GPUTest { ${typeInfo.validationFunc} } - [[stage(vertex)]] fn main( - [[builtin(vertex_index)]] VertexIndex : u32, + @stage(vertex) fn main( + @builtin(vertex_index) VertexIndex : u32, attributes : Attributes - ) -> [[builtin(position)]] vec4<f32> { + ) -> @builtin(position) vec4<f32> { var attributesInBounds = ${attributeNames .map(a => `validationFunc(attributes.${a})`) .join(' && ')}; @@ -434,7 +434,7 @@ class F extends GPUTest { fragment: { module: this.device.createShaderModule({ code: ` - [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 0.0, 1.0); }`, }), diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/sampling/gradients_in_varying_loop.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/sampling/gradients_in_varying_loop.spec.ts index f9ed4b11707..284c872ce81 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/sampling/gradients_in_varying_loop.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/sampling/gradients_in_varying_loop.spec.ts @@ -42,12 +42,12 @@ class DerivativesTest extends GPUTest { module: this.device.createShaderModule({ code: ` struct Outputs { - [[builtin(position)]] Position : vec4<f32>; - [[location(0)]] fragUV : vec2<f32>; + @builtin(position) Position : vec4<f32>; + @location(0) fragUV : vec2<f32>; }; - [[stage(vertex)]] fn main( - [[builtin(vertex_index)]] VertexIndex : u32) -> Outputs { + @stage(vertex) fn main( + @builtin(vertex_index) VertexIndex : u32) -> Outputs { // Full screen quad var position : array<vec3<f32>, 6> = array<vec3<f32>, 6>( vec3<f32>(-1.0, 1.0, 0.0), @@ -81,26 +81,26 @@ class DerivativesTest extends GPUTest { fragment: { module: this.device.createShaderModule({ code: ` - [[block]] struct Uniforms { + struct Uniforms { numIterations : i32; }; - [[binding(0), group(0)]] var<uniform> uniforms : Uniforms; + @binding(0) @group(0) var<uniform> uniforms : Uniforms; + + @stage(fragment) fn main( + @builtin(position) FragCoord : vec4<f32>, + @location(0) fragUV: vec2<f32>) -> @location(0) vec4<f32> { - [[stage(fragment)]] fn main( - [[builtin(position)]] FragCoord : vec4<f32>, - [[location(0)]] fragUV: vec2<f32>) -> [[location(0)]] vec4<f32> { - // Loop to exercise uniform control flow of gradient operations, to trip FXC's // warning X3570: gradient instruction used in a loop with varying iteration, attempting to unroll the loop var summed_dx : f32 = 0.0; var summed_dy : f32 = 0.0; for (var i = 0; i < uniforms.numIterations; i = i + 1) { - + // Bogus condition to make this a "loop with varying iteration". if (fragUV.x > 500.0) { break; } - + // Do the gradient operations within the loop let dx = dpdxCoarse(fragUV.x); let dy = dpdyCoarse(fragUV.y); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts index ca53f21d6db..6106b5dc396 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts @@ -49,11 +49,11 @@ g.test('inputs') switch (t.params.method) { case 'param': params = ` - [[builtin(local_invocation_id)]] local_id : vec3<u32>, - [[builtin(local_invocation_index)]] local_index : u32, - [[builtin(global_invocation_id)]] global_id : vec3<u32>, - [[builtin(workgroup_id)]] group_id : vec3<u32>, - [[builtin(num_workgroups)]] num_groups : vec3<u32>, + @builtin(local_invocation_id) local_id : vec3<u32>, + @builtin(local_invocation_index) local_index : u32, + @builtin(global_invocation_id) global_id : vec3<u32>, + @builtin(workgroup_id) group_id : vec3<u32>, + @builtin(num_workgroups) num_groups : vec3<u32>, `; local_id = 'local_id'; local_index = 'local_index'; @@ -63,11 +63,11 @@ g.test('inputs') break; case 'struct': structures = `struct Inputs { - [[builtin(local_invocation_id)]] local_id : vec3<u32>; - [[builtin(local_invocation_index)]] local_index : u32; - [[builtin(global_invocation_id)]] global_id : vec3<u32>; - [[builtin(workgroup_id)]] group_id : vec3<u32>; - [[builtin(num_workgroups)]] num_groups : vec3<u32>; + @builtin(local_invocation_id) local_id : vec3<u32>; + @builtin(local_invocation_index) local_index : u32; + @builtin(global_invocation_id) global_id : vec3<u32>; + @builtin(workgroup_id) group_id : vec3<u32>; + @builtin(num_workgroups) num_groups : vec3<u32>; };`; params = `inputs : Inputs`; local_id = 'inputs.local_id'; @@ -78,16 +78,16 @@ g.test('inputs') break; case 'mixed': structures = `struct InputsA { - [[builtin(local_invocation_index)]] local_index : u32; - [[builtin(global_invocation_id)]] global_id : vec3<u32>; + @builtin(local_invocation_index) local_index : u32; + @builtin(global_invocation_id) global_id : vec3<u32>; }; struct InputsB { - [[builtin(workgroup_id)]] group_id : vec3<u32>; + @builtin(workgroup_id) group_id : vec3<u32>; };`; - params = `[[builtin(local_invocation_id)]] local_id : vec3<u32>, + params = `@builtin(local_invocation_id) local_id : vec3<u32>, inputsA : InputsA, inputsB : InputsB, - [[builtin(num_workgroups)]] num_groups : vec3<u32>,`; + @builtin(num_workgroups) num_groups : vec3<u32>,`; local_id = 'local_id'; local_index = 'inputsA.local_index'; global_id = 'inputsA.global_id'; @@ -98,19 +98,17 @@ g.test('inputs') // WGSL shader that stores every builtin value to a buffer, for every invocation in the grid. const wgsl = ` - [[block]] struct S { data : array<u32>; }; - [[block]] struct V { data : array<vec3<u32>>; }; - [[group(0), binding(0)]] var<storage, write> local_id_out : V; - [[group(0), binding(1)]] var<storage, write> local_index_out : S; - [[group(0), binding(2)]] var<storage, write> global_id_out : V; - [[group(0), binding(3)]] var<storage, write> group_id_out : V; - [[group(0), binding(4)]] var<storage, write> num_groups_out : V; + @group(0) @binding(0) var<storage, write> local_id_out : V; + @group(0) @binding(1) var<storage, write> local_index_out : S; + @group(0) @binding(2) var<storage, write> global_id_out : V; + @group(0) @binding(3) var<storage, write> group_id_out : V; + @group(0) @binding(4) var<storage, write> num_groups_out : V; ${structures} @@ -118,7 +116,7 @@ g.test('inputs') let group_height = ${t.params.groupSize.y}u; let group_depth = ${t.params.groupSize.z}u; - [[stage(compute), workgroup_size(group_width, group_height, group_depth)]] + @stage(compute) @workgroup_size(group_width, group_height, group_depth) fn main( ${params} ) { @@ -255,20 +253,20 @@ g.test('inputs') return undefined; }; - // Check [[builtin(local_invocation_index)]] values. + // Check @builtin(local_invocation_index) values. t.expectGPUBufferValuesEqual( localIndexBuffer, new Uint32Array([...iterRange(totalInvocations, x => x % invocationsPerGroup)]) ); - // Check [[builtin(local_invocation_id)]] values. + // Check @builtin(local_invocation_id) values. t.expectGPUBufferValuesPassCheck( localIdBuffer, outputData => checkEachIndex(outputData, 'local_invocation_id', (_, localId) => localId), { type: Uint32Array, typedLength: totalInvocations * 4 } ); - // Check [[builtin(global_invocation_id)]] values. + // Check @builtin(global_invocation_id) values. const getGlobalId = (groupId: vec3, localId: vec3) => { return { x: groupId.x * t.params.groupSize.x + localId.x, @@ -282,14 +280,14 @@ g.test('inputs') { type: Uint32Array, typedLength: totalInvocations * 4 } ); - // Check [[builtin(workgroup_id)]] values. + // Check @builtin(workgroup_id) values. t.expectGPUBufferValuesPassCheck( groupIdBuffer, outputData => checkEachIndex(outputData, 'workgroup_id', (groupId, _) => groupId), { type: Uint32Array, typedLength: totalInvocations * 4 } ); - // Check [[builtin(num_workgroups)]] values. + // Check @builtin(num_workgroups) values. t.expectGPUBufferValuesPassCheck( numGroupsBuffer, outputData => checkEachIndex(outputData, 'num_workgroups', () => t.params.numGroups), diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/shader_io/shared_structs.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/shader_io/shared_structs.spec.ts index 09fbd230492..44b631d01b7 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/shader_io/shared_structs.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/shader_io/shared_structs.spec.ts @@ -28,17 +28,16 @@ g.test('shared_with_buffer') // The test shader defines a structure that contains members decorated with built-in variable // attributes, and also layout attributes for the storage buffer. const wgsl = ` - [[block]] struct S { - /* byte offset: 0 */ [[size(32)]] [[builtin(workgroup_id)]] group_id : vec3<u32>; - /* byte offset: 32 */ [[builtin(local_invocation_index)]] local_index : u32; - /* byte offset: 64 */ [[align(64)]] [[builtin(num_workgroups)]] numGroups : vec3<u32>; + /* byte offset: 0 */ @size(32) @builtin(workgroup_id) group_id : vec3<u32>; + /* byte offset: 32 */ @builtin(local_invocation_index) local_index : u32; + /* byte offset: 64 */ @align(64) @builtin(num_workgroups) numGroups : vec3<u32>; }; - [[group(0), binding(0)]] + @group(0) @binding(0) var<storage, read_write> outputs : S; - [[stage(compute), workgroup_size(${wgsize[0]}, ${wgsize[1]}, ${wgsize[2]})]] + @stage(compute) @workgroup_size(${wgsize[0]}, ${wgsize[1]}, ${wgsize[2]}) fn main(inputs : S) { if (inputs.group_id.x == ${targetGroup[0]}u && inputs.group_id.y == ${targetGroup[1]}u && @@ -119,8 +118,8 @@ g.test('shared_between_stages') const size = [31, 31]; const wgsl = ` struct Interface { - [[builtin(position)]] position : vec4<f32>; - [[location(0)]] color : f32; + @builtin(position) position : vec4<f32>; + @location(0) color : f32; }; var<private> vertices : array<vec2<f32>, 3> = array<vec2<f32>, 3>( @@ -129,13 +128,13 @@ g.test('shared_between_stages') vec2<f32>( 0.7, -0.7), ); - [[stage(vertex)]] - fn vert_main([[builtin(vertex_index)]] index : u32) -> Interface { + @stage(vertex) + fn vert_main(@builtin(vertex_index) index : u32) -> Interface { return Interface(vec4<f32>(vertices[index], 0.0, 1.0), 1.0); } - [[stage(fragment)]] - fn frag_main(inputs : Interface) -> [[location(0)]] vec4<f32> { + @stage(fragment) + fn frag_main(inputs : Interface) -> @location(0) vec4<f32> { // Toggle red vs green based on the x position. var color = vec4<f32>(0.0, 0.0, 0.0, 1.0); if (inputs.position.x > f32(${size[0] / 2})) { @@ -234,12 +233,12 @@ g.test('shared_with_non_entry_point_function') // functions. const wgsl = ` struct Inputs { - [[builtin(vertex_index)]] index : u32; - [[location(0)]] color : vec4<f32>; + @builtin(vertex_index) index : u32; + @location(0) color : vec4<f32>; }; struct Outputs { - [[builtin(position)]] position : vec4<f32>; - [[location(0)]] color : vec4<f32>; + @builtin(position) position : vec4<f32>; + @location(0) color : vec4<f32>; }; var<private> vertices : array<vec2<f32>, 3> = array<vec2<f32>, 3>( @@ -255,13 +254,13 @@ g.test('shared_with_non_entry_point_function') return out; } - [[stage(vertex)]] + @stage(vertex) fn vert_main(inputs : Inputs) -> Outputs { return process(inputs); } - [[stage(fragment)]] - fn frag_main([[location(0)]] color : vec4<f32>) -> [[location(0)]] vec4<f32> { + @stage(fragment) + fn frag_main(@location(0) color : vec4<f32>) -> @location(0) vec4<f32> { return color; } `; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/zero_init.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/zero_init.spec.ts index b252ec2259d..cf56258bfdc 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/zero_init.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/execution/zero_init.spec.ts @@ -231,10 +231,10 @@ g.test('compute,zero_init') .batch(15) .fn(async t => { let moduleScope = ` - [[block]] struct Output { + struct Output { failed : atomic<u32>; }; - [[group(0), binding(0)]] var <storage, read_write> output : Output; + @group(0) @binding(0) var <storage, read_write> output : Output; `; let functionScope = ''; @@ -385,7 +385,7 @@ g.test('compute,zero_init') const wgsl = ` ${moduleScope} - [[stage(compute), workgroup_size(${t.params.workgroupSize})]] + @stage(compute) @workgroup_size(${t.params.workgroupSize}) fn main() { ${functionScope} ${checkZeroCode} diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/types.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/types.ts index 9b3ed249d99..bb09af82e6d 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/types.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/types.ts @@ -134,7 +134,7 @@ export function* generateTypes({ // Sized if (storageClass === 'uniform') { yield { - type: `[[stride(16)]] array<${scalarType},${kArrayLength}>`, + type: `@stride(16) array<${scalarType},${kArrayLength}>`, _kTypeInfo: arrayTypeInfo, }; } else { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/builtins.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/builtins.spec.ts index fb0a712cccb..b6af93636fd 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/builtins.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/builtins.spec.ts @@ -64,7 +64,7 @@ const kTestTypes = [ g.test('stage_inout') .desc( - `Test that each [[builtin]] attribute is validated against the required stage and in/out usage for that built-in variable.` + `Test that each @builtin]] attribute is validated against the required stage and in/out usage for that built-in variable.` ) .params(u => u @@ -76,7 +76,7 @@ g.test('stage_inout') ) .fn(t => { const code = generateShader({ - attribute: `[[builtin(${t.params.name})]]`, + attribute: `@builtin(${t.params.name})`, type: t.params.type, stage: t.params.target_stage, io: t.params.target_io, @@ -96,7 +96,7 @@ g.test('stage_inout') g.test('type') .desc( - `Test that each [[builtin]] attribute is validated against the required type of that built-in variable.` + `Test that each @builtin]] attribute is validated against the required type of that built-in variable.` ) .params(u => u @@ -116,7 +116,7 @@ g.test('type') } code += generateShader({ - attribute: `[[builtin(${t.params.name})]]`, + attribute: `@builtin(${t.params.name})`, type: t.params.target_type, stage: t.params.stage, io: t.params.io, @@ -146,7 +146,7 @@ g.test('nesting') // Generate a struct that contains a sample_mask builtin, nested inside another struct. let code = ` struct Inner { - [[builtin(sample_mask)]] value : u32; + @builtin(sample_mask) value : u32; }; struct Outer { inner : Inner; @@ -168,26 +168,34 @@ g.test('duplicates') .desc(`Test that duplicated built-in variables are validated.`) .params(u => u - // Place two [[builtin(sample_mask)]] attributes onto the entry point function. + // Place two @builtin(sample_mask) attributes onto the entry point function. // We use `sample_mask` as it is valid as both an input and output for the same entry point. // The function: // - has two non-struct parameters (`p1` and `p2`) // - has two struct parameters each with two members (`s1{a,b}` and `s2{a,b}`) // - returns a struct with two members (`ra` and `rb`) - // By default, all of these variables will have unique [[location()]] attributes. + // By default, all of these variables will have unique @location() attributes. .combine('first', ['p1', 's1a', 's2a', 'ra'] as const) .combine('second', ['p2', 's1b', 's2b', 'rb'] as const) .beginSubcases() ) .fn(t => { - const p1 = t.params.first === 'p1' ? '[[builtin(sample_mask)]]' : '[[location(1)]]'; - const p2 = t.params.second === 'p2' ? '[[builtin(sample_mask)]]' : '[[location(2)]]'; - const s1a = t.params.first === 's1a' ? '[[builtin(sample_mask)]]' : '[[location(3)]]'; - const s1b = t.params.second === 's1b' ? '[[builtin(sample_mask)]]' : '[[location(4)]]'; - const s2a = t.params.first === 's2a' ? '[[builtin(sample_mask)]]' : '[[location(5)]]'; - const s2b = t.params.second === 's2b' ? '[[builtin(sample_mask)]]' : '[[location(6)]]'; - const ra = t.params.first === 'ra' ? '[[builtin(sample_mask)]]' : '[[location(1)]]'; - const rb = t.params.second === 'rb' ? '[[builtin(sample_mask)]]' : '[[location(2)]]'; + const p1 = + t.params.first === 'p1' ? '@builtin(sample_mask)' : '@location(1) @interpolate(flat)'; + const p2 = + t.params.second === 'p2' ? '@builtin(sample_mask)' : '@location(2) @interpolate(flat)'; + const s1a = + t.params.first === 's1a' ? '@builtin(sample_mask)' : '@location(3) @interpolate(flat)'; + const s1b = + t.params.second === 's1b' ? '@builtin(sample_mask)' : '@location(4) @interpolate(flat)'; + const s2a = + t.params.first === 's2a' ? '@builtin(sample_mask)' : '@location(5) @interpolate(flat)'; + const s2b = + t.params.second === 's2b' ? '@builtin(sample_mask)' : '@location(6) @interpolate(flat)'; + const ra = + t.params.first === 'ra' ? '@builtin(sample_mask)' : '@location(1) @interpolate(flat)'; + const rb = + t.params.second === 'rb' ? '@builtin(sample_mask)' : '@location(2) @interpolate(flat)'; const code = ` struct S1 { ${s1a} a : u32; @@ -201,7 +209,7 @@ g.test('duplicates') ${ra} a : u32; ${rb} b : u32; }; - [[stage(fragment)]] + @stage(fragment) fn main(${p1} p1 : u32, ${p2} p2 : u32, s1 : S1, @@ -211,7 +219,7 @@ g.test('duplicates') } `; - // The test should fail if both [[builtin(sample_mask)]] attributes are on the input parameters + // The test should fail if both @builtin(sample_mask) attributes are on the input parameters // or structures, or it they are both on the output struct. Otherwise it should pass. const firstIsRet = t.params.first === 'ra'; const secondIsRet = t.params.second === 'rb'; @@ -220,11 +228,11 @@ g.test('duplicates') }); g.test('missing_vertex_position') - .desc(`Test that vertex shaders are required to output [[builtin(position)]].`) + .desc(`Test that vertex shaders are required to output @builtin(position).`) .params(u => u .combine('use_struct', [true, false] as const) - .combine('attribute', ['[[builtin(position)]]', '[[location(0)]]'] as const) + .combine('attribute', ['@builtin(position)', '@location(0)'] as const) .beginSubcases() ) .fn(t => { @@ -233,12 +241,12 @@ g.test('missing_vertex_position') ${t.params.attribute} value : vec4<f32>; }; - [[stage(vertex)]] + @stage(vertex) fn main() -> ${t.params.use_struct ? 'S' : `${t.params.attribute} vec4<f32>`} { return ${t.params.use_struct ? 'S' : 'vec4<f32>'}(); } `; - // Expect to pass only when using [[builtin(position)]]. - t.expectCompileResult(t.params.attribute === '[[builtin(position)]]', code); + // Expect to pass only when using @builtin(position). + t.expectCompileResult(t.params.attribute === '@builtin(position)', code); }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/generic.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/generic.spec.ts index f40b20dbbc8..daa703efd3e 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/generic.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/generic.spec.ts @@ -11,27 +11,27 @@ g.test('missing_attribute_on_param') u.combine('target_stage', ['', 'vertex', 'fragment', 'compute'] as const).beginSubcases() ) .fn(t => { - const vertex_attr = t.params.target_stage === 'vertex' ? '' : '[[location(1)]]'; - const fragment_attr = t.params.target_stage === 'fragment' ? '' : '[[location(1)]]'; - const compute_attr = t.params.target_stage === 'compute' ? '' : '[[builtin(workgroup_id)]]'; + const vertex_attr = t.params.target_stage === 'vertex' ? '' : '@location(1)'; + const fragment_attr = t.params.target_stage === 'fragment' ? '' : '@location(1)'; + const compute_attr = t.params.target_stage === 'compute' ? '' : '@builtin(workgroup_id)'; const code = ` -[[stage(vertex)]] -fn vert_main([[location(0)]] a : f32, +@stage(vertex) +fn vert_main(@location(0) a : f32, ${vertex_attr} b : f32, - [[location(2)]] c : f32) -> [[builtin(position)]] vec4<f32> { +@ location(2) c : f32) -> @builtin(position) vec4<f32> { return vec4<f32>(); } -[[stage(fragment)]] -fn frag_main([[location(0)]] a : f32, +@stage(fragment) +fn frag_main(@location(0) a : f32, ${fragment_attr} b : f32, - [[location(2)]] c : f32) { +@ location(2) c : f32) { } -[[stage(compute), workgroup_size(1)]] -fn comp_main([[builtin(global_invocation_id)]] a : vec3<u32>, +@stage(compute) @workgroup_size(1) +fn comp_main(@builtin(global_invocation_id) a : vec3<u32>, ${compute_attr} b : vec3<u32>, - [[builtin(local_invocation_id)]] c : vec3<u32>) { + @builtin(local_invocation_id) c : vec3<u32>) { } `; t.expectCompileResult(t.params.target_stage === '', code); @@ -45,36 +45,36 @@ g.test('missing_attribute_on_param_struct') u.combine('target_stage', ['', 'vertex', 'fragment', 'compute'] as const).beginSubcases() ) .fn(t => { - const vertex_attr = t.params.target_stage === 'vertex' ? '' : '[[location(1)]]'; - const fragment_attr = t.params.target_stage === 'fragment' ? '' : '[[location(1)]]'; - const compute_attr = t.params.target_stage === 'compute' ? '' : '[[builtin(workgroup_id)]]'; + const vertex_attr = t.params.target_stage === 'vertex' ? '' : '@location(1)'; + const fragment_attr = t.params.target_stage === 'fragment' ? '' : '@location(1)'; + const compute_attr = t.params.target_stage === 'compute' ? '' : '@builtin(workgroup_id)'; const code = ` struct VertexInputs { - [[location(0)]] a : f32; + @location(0) a : f32; ${vertex_attr} b : f32; - [[location(2)]] c : f32; +@ location(2) c : f32; }; struct FragmentInputs { - [[location(0)]] a : f32; + @location(0) a : f32; ${fragment_attr} b : f32; - [[location(2)]] c : f32; +@ location(2) c : f32; }; struct ComputeInputs { - [[builtin(global_invocation_id)]] a : vec3<u32>; + @builtin(global_invocation_id) a : vec3<u32>; ${compute_attr} b : vec3<u32>; - [[builtin(local_invocation_id)]] c : vec3<u32>; + @builtin(local_invocation_id) c : vec3<u32>; }; -[[stage(vertex)]] -fn vert_main(inputs : VertexInputs) -> [[builtin(position)]] vec4<f32> { +@stage(vertex) +fn vert_main(inputs : VertexInputs) -> @builtin(position) vec4<f32> { return vec4<f32>(); } -[[stage(fragment)]] +@stage(fragment) fn frag_main(inputs : FragmentInputs) { } -[[stage(compute), workgroup_size(1)]] +@stage(compute) @workgroup_size(1) fn comp_main(inputs : ComputeInputs) { } `; @@ -85,15 +85,15 @@ g.test('missing_attribute_on_return_type') .desc(`Test that an entry point without an IO attribute on its return type is rejected.`) .params(u => u.combine('target_stage', ['', 'vertex', 'fragment'] as const).beginSubcases()) .fn(t => { - const vertex_attr = t.params.target_stage === 'vertex' ? '' : '[[builtin(position)]]'; - const fragment_attr = t.params.target_stage === 'fragment' ? '' : '[[location(0)]]'; + const vertex_attr = t.params.target_stage === 'vertex' ? '' : '@builtin(position)'; + const fragment_attr = t.params.target_stage === 'fragment' ? '' : '@location(0)'; const code = ` -[[stage(vertex)]] +@stage(vertex) fn vert_main() -> ${vertex_attr} vec4<f32> { return vec4<f32>(); } -[[stage(fragment)]] +@stage(fragment) fn frag_main() -> ${fragment_attr} vec4<f32> { return vec4<f32>(); } @@ -107,26 +107,26 @@ g.test('missing_attribute_on_return_type_struct') ) .params(u => u.combine('target_stage', ['', 'vertex', 'fragment'] as const).beginSubcases()) .fn(t => { - const vertex_attr = t.params.target_stage === 'vertex' ? '' : '[[location(1)]]'; - const fragment_attr = t.params.target_stage === 'fragment' ? '' : '[[location(1)]]'; + const vertex_attr = t.params.target_stage === 'vertex' ? '' : '@location(1)'; + const fragment_attr = t.params.target_stage === 'fragment' ? '' : '@location(1)'; const code = ` struct VertexOutputs { - [[location(0)]] a : f32; + @location(0) a : f32; ${vertex_attr} b : f32; - [[builtin(position)]] c : vec4<f32>; + @builtin(position) c : vec4<f32>; }; struct FragmentOutputs { - [[location(0)]] a : f32; + @location(0) a : f32; ${fragment_attr} b : f32; - [[location(2)]] c : f32; +@ location(2) c : f32; }; -[[stage(vertex)]] +@stage(vertex) fn vert_main() -> VertexOutputs { return VertexOutputs(); } -[[stage(fragment)]] +@stage(fragment) fn frag_main() -> FragmentOutputs { return FragmentOutputs(); } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/interpolate.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/interpolate.spec.ts index 836f55da627..4527a873291 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/interpolate.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/interpolate.spec.ts @@ -10,15 +10,15 @@ export const g = makeTestGroup(ShaderValidationTest); // List of valid interpolation attributes. const kValidInterpolationAttributes = new Set([ '', - '[[interpolate(flat)]]', - '[[interpolate(perspective)]]', - '[[interpolate(perspective, center)]]', - '[[interpolate(perspective, centroid)]]', - '[[interpolate(perspective, sample)]]', - '[[interpolate(linear)]]', - '[[interpolate(linear, center)]]', - '[[interpolate(linear, centroid)]]', - '[[interpolate(linear, sample)]]', + '@interpolate(flat)', + '@interpolate(perspective)', + '@interpolate(perspective, center)', + '@interpolate(perspective, centroid)', + '@interpolate(perspective, sample)', + '@interpolate(linear)', + '@interpolate(linear, center)', + '@interpolate(linear, centroid)', + '@interpolate(linear, sample)', ]); g.test('type_and_sampling') @@ -39,14 +39,14 @@ g.test('type_and_sampling') let interpolate = ''; if (t.params.type !== '' || t.params.sampling !== '') { - interpolate = '[[interpolate('; + interpolate = '@interpolate('; if (t.params.type !== '') { interpolate += `${t.params.type}, `; } - interpolate += `${t.params.sampling})]]`; + interpolate += `${t.params.sampling})`; } const code = generateShader({ - attribute: '[[location(0)]]' + interpolate, + attribute: '@location(0)' + interpolate, type: 'f32', stage: t.params.stage, io: t.params.io, @@ -57,9 +57,55 @@ g.test('type_and_sampling') }); g.test('require_location') - .desc(`Test that [[interpolate]] is only accepted with user-defined IO.`) - .unimplemented(); + .desc(`Test that the interpolate attribute is only accepted with user-defined IO.`) + .params(u => + u + .combine('stage', ['vertex', 'fragment'] as const) + .combine('attribute', ['@location(0)', '@builtin(position)'] as const) + .combine('use_struct', [true, false] as const) + .beginSubcases() + ) + .fn(t => { + if ( + t.params.stage === 'vertex' && + t.params.use_struct === false && + !t.params.attribute.includes('position') + ) { + t.skip('vertex output must include a position builtin, so must use a struct'); + } + + const code = generateShader({ + attribute: t.params.attribute + `@interpolate(flat)`, + type: 'vec4<f32>', + stage: t.params.stage, + io: t.params.stage === 'fragment' ? 'in' : 'out', + use_struct: t.params.use_struct, + }); + t.expectCompileResult(t.params.attribute === '@location(0)', code); + }); g.test('integral_types') - .desc(`Test that the implementation requires [[interpolate(flat)]] for integral user-defined IO.`) - .unimplemented(); + .desc(`Test that the implementation requires @interpolate(flat) for integral user-defined IO.`) + .params(u => + u + .combine('stage', ['vertex', 'fragment'] as const) + .combine('type', ['i32', 'u32', 'vec2<i32>', 'vec4<u32>'] as const) + .combine('use_struct', [true, false] as const) + .combine('attribute', kValidInterpolationAttributes) + .beginSubcases() + ) + .fn(t => { + if (t.params.stage === 'vertex' && t.params.use_struct === false) { + t.skip('vertex output must include a position builtin, so must use a struct'); + } + + const code = generateShader({ + attribute: '@location(0)' + t.params.attribute, + type: t.params.type, + stage: t.params.stage, + io: t.params.stage === 'vertex' ? 'out' : 'in', + use_struct: t.params.use_struct, + }); + + t.expectCompileResult(t.params.attribute === '@interpolate(flat)', code); + }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/invariant.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/invariant.spec.ts index ffd4714c94d..cd0b26a446c 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/invariant.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/invariant.spec.ts @@ -18,7 +18,7 @@ g.test('valid_only_with_vertex_position_builtin') ) .fn(t => { const code = generateShader({ - attribute: `[[builtin(${t.params.name}), invariant]]`, + attribute: `@builtin(${t.params.name}) @invariant`, type: t.params.type, stage: t.params.stage, io: t.params.io, @@ -32,13 +32,13 @@ g.test('not_valid_on_user_defined_io') .desc(`Test that the invariant attribute is not accepted on user-defined IO attributes.`) .params(u => u.combine('use_invariant', [true, false] as const).beginSubcases()) .fn(t => { - const invariant = t.params.use_invariant ? '[[invariant]]' : ''; + const invariant = t.params.use_invariant ? '@invariant' : ''; const code = ` struct VertexOut { - [[location(0)]] ${invariant} loc0 : vec4<f32>; - [[builtin(position)]] position : vec4<f32>; + @location(0) ${invariant} loc0 : vec4<f32>; + @builtin(position) position : vec4<f32>; }; - [[stage(vertex)]] + @stage(vertex) fn main() -> VertexOut { return VertexOut(); } @@ -52,9 +52,9 @@ g.test('invalid_use_of_parameters') .fn(t => { const code = ` struct VertexOut { - [[builtin(position), invariant${t.params.suffix}]] position : vec4<f32>; + @builtin(position) @invariant${t.params.suffix} position : vec4<f32>; }; - [[stage(vertex)]] + @stage(vertex) fn main() -> VertexOut { return VertexOut(); } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/locations.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/locations.spec.ts index 8706b0f0b34..b2f9fb95a95 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/locations.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/locations.spec.ts @@ -54,7 +54,7 @@ g.test('stage_inout') ) .fn(t => { const code = generateShader({ - attribute: '[[location(0)]]', + attribute: '@location(0)', type: 'f32', stage: t.params.target_stage, io: t.params.target_io, @@ -88,7 +88,7 @@ g.test('type') } code += generateShader({ - attribute: '[[location(0)]]', + attribute: '@location(0) @interpolate(flat)', type: t.params.type, stage: 'fragment', io: 'in', @@ -112,7 +112,7 @@ g.test('nesting') // Generate a struct that contains a valid type. code += 'struct Inner {\n'; - code += ` [[location(0)]] value : f32;\n`; + code += ` @location(0) value : f32;\n`; code += '};\n\n'; code += 'struct Outer {\n'; code += ` inner : Inner;\n`; @@ -134,7 +134,7 @@ g.test('duplicates') .desc(`Test that duplicated user-defined IO attributes are validated.`) .params(u => u - // Place two [[location(0)]] attributes onto the entry point function. + // Place two @location(0) attributes onto the entry point function. // The function: // - has two non-struct parameters (`p1` and `p2`) // - has two struct parameters each with two members (`s1{a,b}` and `s2{a,b}`) @@ -155,20 +155,20 @@ g.test('duplicates') const rb = t.params.second === 'rb' ? '0' : '2'; const code = ` struct S1 { - [[location(${s1a})]] a : f32; - [[location(${s1b})]] b : f32; + @location(${s1a}) a : f32; + @location(${s1b}) b : f32; }; struct S2 { - [[location(${s2a})]] a : f32; - [[location(${s2b})]] b : f32; + @location(${s2a}) a : f32; + @location(${s2b}) b : f32; }; struct R { - [[location(${ra})]] a : f32; - [[location(${rb})]] b : f32; + @location(${ra}) a : f32; + @location(${rb}) b : f32; }; - [[stage(fragment)]] - fn main([[location(${p1})]] p1 : f32, - [[location(${p2})]] p2 : f32, + @stage(fragment) + fn main(@location(${p1}) p1 : f32, + @location(${p2}) p2 : f32, s1 : S1, s2 : S2, ) -> R { @@ -176,7 +176,7 @@ g.test('duplicates') } `; - // The test should fail if both [[location(0)]] attributes are on the input parameters or + // The test should fail if both @location(0) attributes are on the input parameters or // structures, or it they are both on the output struct. Otherwise it should pass. const firstIsRet = t.params.first === 'ra'; const secondIsRet = t.params.second === 'rb'; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/util.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/util.ts index fc370674cd2..594fb2c93cf 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/util.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_io/util.ts @@ -30,16 +30,16 @@ export function generateShader({ code += ` ${attribute} value : ${type};\n`; if (stage === 'vertex' && io === 'out' && !attribute.includes('builtin(position)')) { // Add position builtin for vertex outputs. - code += ` [[builtin(position)]] position : vec4<f32>;\n`; + code += ` @builtin(position) position : vec4<f32>;\n`; } code += '};\n\n'; } if (stage !== '') { // Generate the entry point attributes. - code += `[[stage(${stage})]]`; + code += `@stage(${stage})`; if (stage === 'compute') { - code += ' [[workgroup_size(1)]]'; + code += ' @workgroup_size(1)'; } } @@ -56,7 +56,7 @@ export function generateShader({ // Vertex shaders must always return `builtin(position)`. if (stage === 'vertex') { - retType = `-> [[builtin(position)]] vec4<f32>`; + retType = `-> @builtin(position) vec4<f32>`; retVal = `return vec4<f32>();`; } } else if (io === 'out') { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_validation_test.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_validation_test.ts index 2c90c878ed0..27da9b33f3f 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_validation_test.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/shader_validation_test.ts @@ -15,7 +15,7 @@ export class ShaderValidationTest extends GPUTest { * t.expectCompileResult('substr', `wgsl code`); // Expect validation error containing 'substr' * ``` * - * TODO(gpuweb/gpuweb#1813): Remove the "string" overload if there are no standard error codes. + * MAINTENANCE_TODO(gpuweb/gpuweb#1813): Remove the "string" overload if there are no standard error codes. */ expectCompileResult(expectedResult: boolean | string, code: string) { let shaderModule: GPUShaderModule; @@ -31,7 +31,7 @@ export class ShaderValidationTest extends GPUTest { this.eventualAsyncExpectation(async () => { const compilationInfo = await shaderModule!.compilationInfo(); - // TODO: Pretty-print error messages with source context. + // MAINTENANCE_TODO: Pretty-print error messages with source context. const messagesLog = compilationInfo.messages .map(m => `${m.lineNum}:${m.linePos}: ${m.type}: ${m.message}`) .join('\n'); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/variable_and_const.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/variable_and_const.spec.ts index 85f249b3fb0..6086f904920 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/variable_and_const.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/variable_and_const.spec.ts @@ -1,5 +1,7 @@ export const description = ` Positive and negative validation tests for variable and const. + +TODO: Find a better way to test arrays than using a single arbitrary size. [1] `; import { makeTestGroup } from '../../../common/framework/test_group.js'; @@ -34,7 +36,7 @@ const kTestTypes = [ 'mat4x2<f32>', 'mat4x3<f32>', 'mat4x4<f32>', - // TODO(sarahM0): 12 is a random number here. find a solution to replace it. + // [1]: 12 is a random number here. find a solution to replace it. 'array<f32, 12>', 'array<i32, 12>', 'array<u32, 12>', @@ -60,7 +62,7 @@ g.test('initializer_type') const { variableOrConstant, lhsType, rhsType } = t.params; const code = ` - [[stage(fragment)]] + @stage(fragment) fn main() { ${variableOrConstant} a : ${lhsType} = ${rhsType}(); } @@ -103,20 +105,20 @@ g.test('io_shareable_type') if (`${storageClass}` === 'in') { code = ` struct MyInputs { - [[location(0)]] a : ${type}; + @location(0) @interpolate(flat) a : ${type}; }; - [[stage(fragment)]] + @stage(fragment) fn main(inputs : MyInputs) { } `; } else if (`${storageClass}` === 'out') { code = ` struct MyOutputs { - [[location(0)]] a : ${type}; + @location(0) a : ${type}; }; - [[stage(fragment)]] + @stage(fragment) fn main() -> MyOutputs { return MyOutputs(); } @@ -125,7 +127,7 @@ g.test('io_shareable_type') code = ` var<${storageClass}> a : ${type} = ${type}(); - [[stage(fragment)]] + @stage(fragment) fn main() { } `; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/access-decoration-is-required.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/access-decoration-is-required.fail.wgsl index d34c00d0fc1..0c4b16eaae5 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/access-decoration-is-required.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/access-decoration-is-required.fail.wgsl @@ -1,13 +1,12 @@ // v-0035: The access decoration is required for 'particles'. -[[block]] struct Particles { - particles : [[stride(16)]] array<f32, 4>; + particles : @stride(16) array<f32, 4>; }; -[[group(0), binding(1)]] var<storage> particles : Particles; +@group(0) @binding(1) var<storage> particles : Particles; -[[stage(vertex)]] -fn main() -> [[builtin(position)]] vec4<f32> { +@stage(vertex) +fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/access-decoration-storage-storage-class.pass.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/access-decoration-storage-storage-class.pass.wgsl index ce6a3fb52c5..f414970671d 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/access-decoration-storage-storage-class.pass.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/access-decoration-storage-storage-class.pass.wgsl @@ -1,14 +1,13 @@ // pass v-0034: The access decoration appears on a type used as the store type of variable // 'particles', which is in 'storage' storage class. -[[block]] struct Particles { - particles : [[stride(16)]] array<f32, 4>; + particles : @stride(16) array<f32, 4>; }; -[[group(1), binding(0)]] var<storage, read_write> particles : Particles; +@group(1) @binding(0) var<storage, read_write> particles : Particles; -[[stage(vertex)]] -fn main() -> [[builtin(position)]] vec4<f32> { +@stage(vertex) +fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/access-decoration-uniform-storage-class.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/access-decoration-uniform-storage-class.fail.wgsl index e254013ccb7..f9355eb7ea7 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/access-decoration-uniform-storage-class.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/access-decoration-uniform-storage-class.fail.wgsl @@ -1,14 +1,13 @@ // v-0034: The access decoration appears on a type used as the store type of variable // 'particles', which is in the 'uniform' storage class. -[[block]] struct Particles { - particles : [[stride(16)]] array<f32, 4>; + particles : @stride(16) array<f32, 4>; }; -[[group(0), binding(0)]] var<uniform, read_write> particles : Particles; +@group(0) @binding(0) var<uniform, read_write> particles : Particles; -[[stage(vertex)]] -fn main() -> [[builtin(position)]] vec4<f32> { +@stage(vertex) +fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/basic.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/basic.spec.ts index 633a7e825b6..b10b9dde5c3 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/basic.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/basic.spec.ts @@ -12,11 +12,11 @@ g.test('trivial') .fn(t => { t.expectCompileResult( true, - `[[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { + `@stage(vertex) fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); }` ); - t.expectCompileResult(false, `[[stage(vertex), stage(fragment)]] fn main() {}`); + t.expectCompileResult(false, `@stage(vertex) @stage(fragment) fn main() {}`); }); g.test('nonsense') diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/break-outside-for-or-switch.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/break-outside-for-or-switch.fail.wgsl index 560caec3ca1..04cec61f6bb 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/break-outside-for-or-switch.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/break-outside-for-or-switch.fail.wgsl @@ -1,6 +1,6 @@ // v-0009 - This fails because the break is used outside a for or switch block. -[[stage(fragment)]] +@stage(fragment) fn main() { if (true) { break; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/continue-outside-for.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/continue-outside-for.fail.wgsl index 1b12e426b18..6a08bf48d99 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/continue-outside-for.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/continue-outside-for.fail.wgsl @@ -1,6 +1,6 @@ // v-0010 - This fails because the continue is used outside of a for block. -[[stage(fragment)]] +@stage(fragment) fn main() { continue; return; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-entry-point.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-entry-point.fail.wgsl index 98c7e1c8226..5df597a0355 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-entry-point.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-entry-point.fail.wgsl @@ -1,7 +1,7 @@ // v-0020 - Duplicate entry point -[[stage(vertex)]] -[[stage(fragment)]] +@stage(vertex) +@stage(fragment) fn main() { return; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-func-name.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-func-name.fail.wgsl index 229dd8b703f..e9781a3d6c5 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-func-name.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-func-name.fail.wgsl @@ -8,7 +8,7 @@ fn my_func() { return; } -[[stage(fragment)]] +@stage(fragment) fn main() { return; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-name-between-global-and-func-vars.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-name-between-global-and-func-vars.fail.wgsl index be36bc5ab15..364d3de0aad 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-name-between-global-and-func-vars.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-name-between-global-and-func-vars.fail.wgsl @@ -2,7 +2,7 @@ let a : vec2<f32> = vec2<f32>(0.1, 1.0); -[[stage(fragment)]] +@stage(fragment) fn main() { var a : f32; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-stuct-name-v2.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-stuct-name-v2.fail.wgsl index a6ca70f1719..03f9cfab71e 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-stuct-name-v2.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-stuct-name-v2.fail.wgsl @@ -9,7 +9,7 @@ fn Foo() { return; } -[[stage(fragment)]] +@stage(fragment) fn main() { var Foo : f32; var f : Foo; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-stuct-name.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-stuct-name.fail.wgsl index 3dda8d12cd5..4fcf5bc9afb 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-stuct-name.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-stuct-name.fail.wgsl @@ -8,7 +8,7 @@ struct foo { b : f32; }; -[[stage(fragment)]] +@stage(fragment) fn main() { return; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-nested-scopes.pass.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-nested-scopes.pass.wgsl index 39a4cb32f0e..0ddf2e2c7fa 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-nested-scopes.pass.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-nested-scopes.pass.wgsl @@ -1,6 +1,6 @@ // This passes because inter-scope shadowing is supported -[[stage(fragment)]] +@stage(fragment) fn main() { var a : u32 = 1u; if (true) { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-one-scope-v2.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-one-scope-v2.fail.wgsl index 7bdffaf8d44..41b7c52367e 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-one-scope-v2.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-one-scope-v2.fail.wgsl @@ -1,6 +1,6 @@ // This fails because variable `i` is redeclared -[[stage(fragment)]] +@stage(fragment) fn main() { for (var i: i32 = 0; i < 10; i = i + 1) { var i: i32 = 1; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-one-scope-v3.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-one-scope-v3.fail.wgsl index 3c735940ca5..1c50b044ccb 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-one-scope-v3.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-one-scope-v3.fail.wgsl @@ -5,7 +5,7 @@ fn func(a: i32) -> i32 { return 0; } -[[stage(fragment)]] +@stage(fragment) fn main() { return; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-one-scope.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-one-scope.fail.wgsl index a53e0292801..765ab6b46b8 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-one-scope.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-one-scope.fail.wgsl @@ -1,6 +1,6 @@ // This fails because variable `a` is redeclared. -[[stage(fragment)]] +@stage(fragment) fn main() { var a : u32 = 1u; var a : u32 = 2u; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-sibling-scopes.pass.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-sibling-scopes.pass.wgsl index 051f64902d3..aa29429023a 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-sibling-scopes.pass.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/duplicate-var-in-sibling-scopes.pass.wgsl @@ -1,6 +1,6 @@ // This passes because variable `a` is redeclared in a sibling scope. -[[stage(fragment)]] +@stage(fragment) fn main() { if (true) { var a : i32 = -1; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/fn-use-before-def.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/fn-use-before-def.fail.wgsl index 2addcbe04a5..5841e6ba553 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/fn-use-before-def.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/fn-use-before-def.fail.wgsl @@ -12,7 +12,7 @@ fn c() -> i32 { return a(); } -[[stage(fragment)]] +@stage(fragment) fn main() { return; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/function-return-missing-return-empty-body.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/function-return-missing-return-empty-body.fail.wgsl index 2cd2811bbb7..b5e4fec6322 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/function-return-missing-return-empty-body.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/function-return-missing-return-empty-body.fail.wgsl @@ -4,6 +4,6 @@ fn func() -> i32 { } -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/function-return-missing-return.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/function-return-missing-return.fail.wgsl index f1cb6c68d1f..a2cd45452b1 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/function-return-missing-return.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/function-return-missing-return.fail.wgsl @@ -5,7 +5,7 @@ fn func() -> i32 { var a : i32 = 0; } -[[stage(fragment)]] +@stage(fragment) fn main() { func(); } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/global-vars-must-be-unique-v2.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/global-vars-must-be-unique-v2.fail.wgsl index f363434d698..5345640e38f 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/global-vars-must-be-unique-v2.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/global-vars-must-be-unique-v2.fail.wgsl @@ -6,7 +6,7 @@ fn a() { return; } -[[stage(fragment)]] +@stage(fragment) fn main() { return; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/global-vars-must-be-unique-v3.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/global-vars-must-be-unique-v3.fail.wgsl index 2a72016465c..c9f26f2a7d8 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/global-vars-must-be-unique-v3.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/global-vars-must-be-unique-v3.fail.wgsl @@ -2,7 +2,7 @@ let a : vec2<f32> = vec2<f32>(0.1, 1.0); -[[stage(fragment)]] +@stage(fragment) fn main() { var a: u32; return; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/global-vars-must-be-unique.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/global-vars-must-be-unique.fail.wgsl index a7fb9e5442c..6a1b75d564d 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/global-vars-must-be-unique.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/global-vars-must-be-unique.fail.wgsl @@ -3,7 +3,7 @@ let a : vec2<f32> = vec2<f32>(0.1, 1.0); var<private> a : vec4<f32>; -[[stage(compute), workgroup_size(1)]] +@stage(compute) @workgroup_size(1) fn main() { return; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/module-scope-variable-function-storage-class.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/module-scope-variable-function-storage-class.fail.wgsl index 72fb3bf5df2..6c3e81af3f6 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/module-scope-variable-function-storage-class.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/module-scope-variable-function-storage-class.fail.wgsl @@ -2,6 +2,6 @@ var<function> f : vec2<f32>; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/module-scope-variable-no-explicit-storage-decoration.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/module-scope-variable-no-explicit-storage-decoration.fail.wgsl index 79e2c38889a..3c5526e9f36 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/module-scope-variable-no-explicit-storage-decoration.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/module-scope-variable-no-explicit-storage-decoration.fail.wgsl @@ -3,6 +3,6 @@ var f : vec2<f32>; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/reassign-let.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/reassign-let.fail.wgsl index b21322d9450..064aa9161b4 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/reassign-let.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/reassign-let.fail.wgsl @@ -1,6 +1,6 @@ // v-0021 - Fails because `c` is a const and can not be re-assigned. -[[stage(fragment)]] +@stage(fragment) fn main() { let c : f32 = 0.1; c = 0.2; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-is-expression-type.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-is-expression-type.fail.wgsl index 94e5addfe2a..b267030355a 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-is-expression-type.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-is-expression-type.fail.wgsl @@ -1,11 +1,11 @@ // v-0031: in 'y = x', x is a runtime array and it's used as an expression. -type RTArr = [[stride (16)]] array<i32>; +type RTArr = @stride(16) array<i32>; struct S{ data : RTArr; }; -[[stage(fragment)]] +@stage(fragment) fn main() { var <storage> x : S; var y : array<i32,2>; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-is-store-type-v2.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-is-store-type-v2.fail.wgsl index 40d6a5efee3..2fd414ac02a 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-is-store-type-v2.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-is-store-type-v2.fail.wgsl @@ -1,9 +1,9 @@ // v-0030 - This fails because 'RTArr' is a runtime array alias and it is used as store type. -type RTArr = [[stride(4)]] array<f32>; -[[group(0), binding(1)]] var<storage> x : RTArr; +type RTArr = @stride(4) array<f32>; +@group(0) @binding(1) var<storage> x : RTArr; -[[stage(fragment)]] +@stage(fragment) fn main() { return; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-is-store-type.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-is-store-type.fail.wgsl index b72aa3dc923..b0e804dda50 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-is-store-type.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-is-store-type.fail.wgsl @@ -1,12 +1,11 @@ // v-0015: variable 's' store type is struct 'SArr' that has a runtime-sized member but its storage class is not 'storage'. -type RTArr = [[stride (16)]] array<vec4<f32>>; -[[block]] +type RTArr = @stride(16) array<vec4<f32>>; struct SArr{ data : RTArr; }; -[[stage(fragment)]] +@stage(fragment) fn main() { var s : SArr; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-block-decorated.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-block-decorated.fail.wgsl index 65729f8acb0..5a391f9ce31 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-block-decorated.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-block-decorated.fail.wgsl @@ -1,10 +1,10 @@ // v-0031: struct 'S' has runtime-sized member but it's storage class is not 'storage'. -type RTArr = [[stride (16)]] array<vec4<f32>>; +type RTArr = @stride(16) array<vec4<f32>>; struct S{ data : RTArr; }; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-last-v2.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-last-v2.fail.wgsl index 849bdae5c12..2cc90061ff8 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-last-v2.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-last-v2.fail.wgsl @@ -1,12 +1,11 @@ // v-0015 - This fails because of the aliased runtime array is not last member of the struct. -type RTArr = [[stride (16)]] array<vec4<f32>>; -[[block]] +type RTArr = @stride(16) array<vec4<f32>>; struct S { data : RTArr; b : f32; }; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-last-v3.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-last-v3.fail.wgsl index 6d958605d28..da7ed882f9f 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-last-v3.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-last-v3.fail.wgsl @@ -1,7 +1,7 @@ // v-0030 - This fails because the runtime array must not be used as a store type. -type RTArr = [[stride (16)]] array<vec4<f32>>; +type RTArr = @stride(16) array<vec4<f32>>; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-last.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-last.fail.wgsl index 9d0e9d47609..8d01f3066a0 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-last.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-not-last.fail.wgsl @@ -1,12 +1,11 @@ // v-0015 - This fails because of the runtime array is not last member of the struct. -[[block]] struct Foo { - a : [[stride (16)]] array<f32>; + a : @stride(16) array<f32>; b : f32; }; -[[stage(fragment)]] +@stage(fragment) fn main() { return; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-without-stride.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-without-stride.fail.wgsl index 68e71d7ad85..387e0afe8b7 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-without-stride.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/runtime-array-without-stride.fail.wgsl @@ -1,11 +1,10 @@ // v-0032 - This fails because the runtime array does not have a stride attribute. -[[block]] struct Foo { a : array<f32>; }; -[[stage(fragment)]] +@stage(fragment) fn main() { return; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/self-recursion-v2.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/self-recursion-v2.fail.wgsl index 670e43fb2a3..f45cbc745e5 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/self-recursion-v2.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/self-recursion-v2.fail.wgsl @@ -16,7 +16,7 @@ fn a() -> i32 { return b(); } -[[stage(fragment)]] +@stage(fragment) fn main() { var v : i32 = a(); return; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/self-recursion.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/self-recursion.fail.wgsl index 37cab1bc392..b63fd2d6467 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/self-recursion.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/self-recursion.fail.wgsl @@ -1,6 +1,6 @@ // v-0004 - Self recursion is dis-allowed and `other` calls itself. -[[stage(fragment)]] +@stage(fragment) fn other() -> i32 { return other(); } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-def-before-use.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-def-before-use.fail.wgsl index 7783a37c715..94757e8c08c 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-def-before-use.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-def-before-use.fail.wgsl @@ -1,6 +1,6 @@ // v-0007 - fails because 'f' is not a struct. -[[stage(fragment)]] +@stage(fragment) fn main() { var f : f32; f.a = 4.0; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v2.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v2.fail.wgsl index de61353133f..669997dc345 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v2.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v2.fail.wgsl @@ -12,7 +12,7 @@ struct foo { x : goo; }; -[[stage(fragment)]] +@stage(fragment) fn main() { var f : foo; f.x.y.t = 2.0; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v3.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v3.fail.wgsl index 6e1fd8a2117..154d35b45d3 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v3.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v3.fail.wgsl @@ -12,7 +12,7 @@ fn Foo() -> goo { return a; } -[[stage(fragment)]] +@stage(fragment) fn main() { var r : i32 = Foo().s.z; return; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v4.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v4.fail.wgsl index d939a3aaa47..1e27675f8ad 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v4.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v4.fail.wgsl @@ -6,7 +6,7 @@ struct foo { a : array<f32>; }; -[[stage(fragment)]] +@stage(fragment) fn main() { var f : foo; f.c = 2; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v5.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v5.fail.wgsl index 996402baca5..caa4d768808 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v5.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use-v5.fail.wgsl @@ -9,7 +9,7 @@ struct foo { a : f32; }; -[[stage(fragment)]] +@stage(fragment) fn main() { var f : foo; f.a = 2.0; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use.fail.wgsl index 4abada348cc..777a860b3c7 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-member-def-before-use.fail.wgsl @@ -1,6 +1,6 @@ // v-0007 - Fails because 'f' is not a struct. -[[stage(fragment)]] +@stage(fragment) fn main() { var f : f32; f.a = 4.0; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-use-before-def.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-use-before-def.fail.wgsl index a39b25dfc6b..e23fe22ad5d 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-use-before-def.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/struct-use-before-def.fail.wgsl @@ -6,7 +6,7 @@ struct Foo { a : i32; }; -[[stage(fragment)]] +@stage(fragment) fn main() { return; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case-selector-must-have-the-same-type-as-the-selector-expression-2.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case-selector-must-have-the-same-type-as-the-selector-expression-2.fail.wgsl index 188b83390c6..9d851424a30 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case-selector-must-have-the-same-type-as-the-selector-expression-2.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case-selector-must-have-the-same-type-as-the-selector-expression-2.fail.wgsl @@ -1,6 +1,6 @@ // v-0026: line 7: the case selector values must have the same type as the selector expression -[[stage(fragment)]] +@stage(fragment) fn main() { var a: i32 = -2; switch (a) { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case-selector-must-have-the-same-type-as-the-selector-expression.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case-selector-must-have-the-same-type-as-the-selector-expression.fail.wgsl index 0ae89e8fab7..625b72e3a8a 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case-selector-must-have-the-same-type-as-the-selector-expression.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case-selector-must-have-the-same-type-as-the-selector-expression.fail.wgsl @@ -1,6 +1,6 @@ // v-0026: line 7: the case selector values must have the same type as the selector expression -[[stage(fragment)]] +@stage(fragment) fn main() { var a: u32 = 2; switch (a) { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case-selector-value-must-be-unique.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case-selector-value-must-be-unique.fail.wgsl index 24fa19d6589..bea5efad52c 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case-selector-value-must-be-unique.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case-selector-value-must-be-unique.fail.wgsl @@ -1,7 +1,7 @@ // v-0027: line 9: a literal value must not appear more than once in the case selectors for a // switch statement: '0' -[[stage(fragment)]] +@stage(fragment) fn main() { var a: u32 = 2; switch (a) { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case.pass.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case.pass.wgsl index 97d4555287e..383b937d803 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case.pass.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-case.pass.wgsl @@ -1,6 +1,6 @@ // correct use of switch statement -[[stage(fragment)]] +@stage(fragment) fn main() { var a: i32 = 2; switch (a) { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-fallthrough-must-not-be-last-stmt-of-last-clause.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-fallthrough-must-not-be-last-stmt-of-last-clause.fail.wgsl index 6308775235d..62dffcf30bd 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-fallthrough-must-not-be-last-stmt-of-last-clause.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-fallthrough-must-not-be-last-stmt-of-last-clause.fail.wgsl @@ -1,7 +1,7 @@ // v-0028: line 9: a fallthrough statement must not appear as the last statement in last clause // of a switch -[[stage(fragment)]] +@stage(fragment) fn main() { var a: i32 = -2; switch (a) { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-must-have-exactly-one-default-clause-2.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-must-have-exactly-one-default-clause-2.fail.wgsl index 2511b09e475..7e1a261b323 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-must-have-exactly-one-default-clause-2.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-must-have-exactly-one-default-clause-2.fail.wgsl @@ -1,6 +1,6 @@ // v-0008: line 6: switch statement must have exactly one default clause -[[stage(fragment)]] +@stage(fragment) fn main() { var a: i32 = 2; switch (a) { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-must-have-exactly-one-default-clause.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-must-have-exactly-one-default-clause.fail.wgsl index 9ac1920ff3a..d0cba3f7b96 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-must-have-exactly-one-default-clause.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-must-have-exactly-one-default-clause.fail.wgsl @@ -1,6 +1,6 @@ // v-0008: line 6: switch statement must have exactly one default clause -[[stage(fragment)]] +@stage(fragment) fn main() { var a: i32 = 2; switch (a) { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-selector-expression-must-be-scalar-integer-type.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-selector-expression-must-be-scalar-integer-type.fail.wgsl index 9f3d704c475..d94cb24be53 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-selector-expression-must-be-scalar-integer-type.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/switch-selector-expression-must-be-scalar-integer-type.fail.wgsl @@ -1,6 +1,6 @@ // v-0025: line 6: switch statement selector expression must be of a scalar integer type -[[stage(fragment)]] +@stage(fragment) fn main() { var a: f32 = 3.14; switch (a) { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/var-def-before-use.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/var-def-before-use.fail.wgsl index 1e53d0bc1c5..814cdb02624 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/var-def-before-use.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/var-def-before-use.fail.wgsl @@ -1,6 +1,6 @@ // v-0006 - Fails because 'a' is not defined. -[[stage(fragment)]] +@stage(fragment) fn main() { var b : f32 = 1.0; a = 4; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool-f32.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool-f32.fail.wgsl index 0c3e6e2d362..67bbf0d1ce5 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool-f32.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool-f32.fail.wgsl @@ -2,6 +2,6 @@ var<private> flag : bool = 0.0; -[[stage(compute), workgroup_size(1)]] +@stage(compute) @workgroup_size(1) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool-i32.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool-i32.fail.wgsl index 9a6ce071230..9749f1f395a 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool-i32.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool-i32.fail.wgsl @@ -2,6 +2,6 @@ var<private> flag : bool = 0; -[[stage(compute), workgroup_size(1)]] +@stage(compute) @workgroup_size(1) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool-u32.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool-u32.fail.wgsl index 4ef88133046..80a6020e494 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool-u32.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool-u32.fail.wgsl @@ -2,6 +2,6 @@ var<private> flag : bool = 1u; -[[stage(compute), workgroup_size(1)]] +@stage(compute) @workgroup_size(1) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool.pass.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool.pass.wgsl index 1e75f3cd51e..738b73a2309 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool.pass.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-bool.pass.wgsl @@ -2,6 +2,6 @@ var<private> flag : bool = true; -[[stage(compute), workgroup_size(1)]] +@stage(compute) @workgroup_size(1) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32-bool.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32-bool.fail.wgsl index b5cae7af139..832408ffce5 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32-bool.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32-bool.fail.wgsl @@ -2,6 +2,6 @@ var<private> f : f32 = true; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32-i32.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32-i32.fail.wgsl index 368a8bd387d..fcba3a24988 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32-i32.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32-i32.fail.wgsl @@ -2,6 +2,6 @@ var<private> f : f32 = 0; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32-u32.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32-u32.fail.wgsl index e2017daa117..80ccae013eb 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32-u32.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32-u32.fail.wgsl @@ -2,6 +2,6 @@ var<private> f : f32 = 0u; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32.pass.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32.pass.wgsl index c819c5170e7..b1a19ff9ba9 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32.pass.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-f32.pass.wgsl @@ -2,6 +2,6 @@ var<private> a : f32 = 0.0; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-function.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-function.wgsl index 0126b2a394c..42181fcaa02 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-function.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-function.wgsl @@ -1,6 +1,6 @@ // v-0033: variable 'a' store type is 'f32', however its initializer type is 'i32'. -[[stage(fragment)]] +@stage(fragment) fn main() { var<function> a : f32 = 1; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32-bool.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32-bool.fail.wgsl index 2306ef0da56..36d828ed2d5 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32-bool.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32-bool.fail.wgsl @@ -2,6 +2,6 @@ var<private> a : i32 = true; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32-f32.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32-f32.fail.wgsl index 052cfb15178..6921f733043 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32-f32.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32-f32.fail.wgsl @@ -2,6 +2,6 @@ var<private> a : i32 = 123.0; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32-u32.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32-u32.fail.wgsl index 85943f8c576..642a3cd9c43 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32-u32.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32-u32.fail.wgsl @@ -2,6 +2,6 @@ var<private> a : i32 = 1u; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32.pass.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32.pass.wgsl index f568862c16a..3de71d82b2d 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32.pass.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-i32.pass.wgsl @@ -2,6 +2,6 @@ var<private> a : i32 = 0; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-out.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-out.wgsl index c68bfae588e..e67dbc114f8 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-out.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-out.wgsl @@ -2,6 +2,6 @@ var<private> a : i32 = 1.0; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-private.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-private.wgsl index c68bfae588e..e67dbc114f8 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-private.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-private.wgsl @@ -2,6 +2,6 @@ var<private> a : i32 = 1.0; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32-bool.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32-bool.fail.wgsl index 582f1b6293d..60f239d2e25 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32-bool.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32-bool.fail.wgsl @@ -2,6 +2,6 @@ var<private> u : u32 = true; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32-f32.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32-f32.fail.wgsl index fec3f1e2240..82202db621f 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32-f32.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32-f32.fail.wgsl @@ -2,6 +2,6 @@ var<private> f : u32 = 0.0; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32-i32.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32-i32.fail.wgsl index 10fe4317d4e..b0ba9addde3 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32-i32.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32-i32.fail.wgsl @@ -2,6 +2,6 @@ var<private> f : u32 = 0; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32.pass.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32.pass.wgsl index cae9c26e517..86f702760e6 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32.pass.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-initializer-mismatch-u32.pass.wgsl @@ -2,6 +2,6 @@ var<private> a : u32 = 0u; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-storage-group-binding-decoration.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-storage-group-binding-decoration.fail.wgsl index 7e7beada184..03443b896cd 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-storage-group-binding-decoration.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-storage-group-binding-decoration.fail.wgsl @@ -1,13 +1,12 @@ # v-0039: variable 's' is in storage storage class so it must be declared with group and binding # decoration. -[[block]] struct PositionBuffer { pos: vec2<f32>; }; var<storage> s : PositionBuffer; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-uniform-group-binding-decoration.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-uniform-group-binding-decoration.fail.wgsl index 6e6b6dc6b00..4d6cec546e0 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-uniform-group-binding-decoration.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-uniform-group-binding-decoration.fail.wgsl @@ -1,13 +1,12 @@ # v-0040: variable 'u' is in uniform storage class so it must be declared with group and binding # decoration. -[[block]] struct Params { count: i32; }; var<uniform> u : Params; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-function.pass.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-function.pass.wgsl index 56ce0d3494a..c1157478015 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-function.pass.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-function.pass.wgsl @@ -1,6 +1,6 @@ // pass v-0032: variable 'a' has an initializer and its storage class is 'function'. -[[stage(fragment)]] +@stage(fragment) fn main() { var<function> a : i32 = 1; } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-out.pass.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-out.pass.wgsl index b9bb8140aa7..547d8d60296 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-out.pass.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-out.pass.wgsl @@ -2,6 +2,6 @@ var<private> a : i32 = 1; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-private.pass.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-private.pass.wgsl index c08859c16ae..a742d9e5ffd 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-private.pass.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-private.pass.wgsl @@ -2,6 +2,6 @@ var<private> a : i32 = 1; -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-storage.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-storage.fail.wgsl index 9e45cc6cee8..59ffeb9df59 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-storage.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-storage.fail.wgsl @@ -1,13 +1,12 @@ // v-0032: variable 'u' has an initializer, however its storage class is 'storage'. -[[block]] struct PositionBuffer { pos: vec2<f32>; }; -[[group(0), binding(0)]] +@group(0) @binding(0) var<storage> s : PositionBuffer = PositionBuffer(vec2<f32>(0.0, 0.0)); -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-uniform.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-uniform.fail.wgsl index 035e3cb6203..574cfcb2b9a 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-uniform.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-uniform.fail.wgsl @@ -1,13 +1,12 @@ // v-0032: variable 'u' has an initializer, however its storage class is 'uniform'. -[[block]] struct Params { count: i32; }; -[[group(0), binding(0)]] +@group(0) @binding(0) var<uniform> u : Params = Params(1); -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-workgroup-array.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-workgroup-array.fail.wgsl index 4af38d2d42e..27fe1943d43 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-workgroup-array.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-workgroup-array.fail.wgsl @@ -2,7 +2,7 @@ var<workgroup> w : array<i32, 4> = array<i32, 4>(0, 1, 0, 1); -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-workgroup.fail.wgsl b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-workgroup.fail.wgsl index d6f223cdc37..53bea0db020 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-workgroup.fail.wgsl +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/shader/validation/wgsl/variable-with-initilizer-workgroup.fail.wgsl @@ -2,7 +2,7 @@ var<workgroup> w : vec3<i32> = vec3<i32>(0, 1, 0); -[[stage(fragment)]] +@stage(fragment) fn main() { } diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/util/buffer.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/util/buffer.ts index 838d5c76149..a7d154a7e65 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/util/buffer.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/util/buffer.ts @@ -4,16 +4,17 @@ import { align } from './math.js'; /** * Creates a buffer with the contents of some TypedArray. + * The buffer size will always be aligned to 4 as we set mappedAtCreation === true when creating the + * buffer. */ export function makeBufferWithContents( device: GPUDevice, dataArray: TypedArrayBufferView, - usage: GPUBufferUsageFlags, - opts: { padToMultipleOf4?: boolean } = {} + usage: GPUBufferUsageFlags ): GPUBuffer { const buffer = device.createBuffer({ mappedAtCreation: true, - size: align(dataArray.byteLength, opts.padToMultipleOf4 ? 4 : 1), + size: align(dataArray.byteLength, 4), usage, }); memcpy({ src: dataArray }, { dst: buffer.getMappedRange() }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/util/conversion.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/util/conversion.ts index 604e3513c33..bc4fb0724f7 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/util/conversion.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/util/conversion.ts @@ -163,7 +163,8 @@ export function packRGB9E5UFloat(r: number, g: number, b: number): number { * Asserts that a number is within the representable (inclusive) of the integer type with the * specified number of bits and signedness. * - * TODO: Assert isInteger? Then this function "asserts that a number is representable" by the type + * MAINTENANCE_TODO: Assert isInteger? Then this function "asserts that a number is representable" + * by the type. */ export function assertInIntegerRange(n: number, bits: number, signed: boolean): void { if (signed) { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/util/copy_to_texture.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/util/copy_to_texture.ts index 4ab548e30ac..3a5b684c28c 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/util/copy_to_texture.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/util/copy_to_texture.ts @@ -1,8 +1,11 @@ +import { assert, memcpy } from '../../common/util/util.js'; +import { RegularTextureFormat, kTextureFormatInfo } from '../capability_info.js'; import { GPUTest } from '../gpu_test.js'; import { checkElementsEqual, checkElementsBetween } from './check_contents.js'; import { align } from './math.js'; import { kBytesPerRowAlignment } from './texture/layout.js'; +import { kTexelRepresentationInfo } from './texture/texel_data.js'; export function isFp16Format(format: GPUTextureFormat): boolean { switch (format) { @@ -16,7 +19,115 @@ export function isFp16Format(format: GPUTextureFormat): boolean { } export class CopyToTextureUtils extends GPUTest { - // TODO(crbug.com/dawn/868): Should be possible to consolidate this along with texture checking + doFlipY( + sourcePixels: Uint8ClampedArray, + width: number, + height: number, + bytesPerPixel: number + ): Uint8ClampedArray { + const dstPixels = new Uint8ClampedArray(width * height * bytesPerPixel); + for (let i = 0; i < height; ++i) { + for (let j = 0; j < width; ++j) { + const srcPixelPos = i * width + j; + // WebGL readPixel returns pixels from bottom-left origin. Using CopyExternalImageToTexture + // to copy from WebGL Canvas keeps top-left origin. So the expectation from webgl.readPixel should + // be flipped. + const dstPixelPos = (height - i - 1) * width + j; + + memcpy( + { src: sourcePixels, start: srcPixelPos * bytesPerPixel, length: bytesPerPixel }, + { dst: dstPixels, start: dstPixelPos * bytesPerPixel } + ); + } + } + + return dstPixels; + } + + /** + * If the destination format specifies a transfer function, + * copyExternalImageToTexture (like B2T/T2T copies) should ignore it. + */ + formatForExpectedPixels(format: RegularTextureFormat): RegularTextureFormat { + return format === 'rgba8unorm-srgb' + ? 'rgba8unorm' + : format === 'bgra8unorm-srgb' + ? 'bgra8unorm' + : format; + } + + getSourceImageBitmapPixels( + sourcePixels: Uint8ClampedArray, + width: number, + height: number, + isPremultiplied: boolean, + isFlipY: boolean + ): Uint8ClampedArray { + return this.getExpectedPixels( + sourcePixels, + width, + height, + 'rgba8unorm', + false, + isPremultiplied, + isFlipY + ); + } + + getExpectedPixels( + sourcePixels: Uint8ClampedArray, + width: number, + height: number, + format: RegularTextureFormat, + srcPremultiplied: boolean, + dstPremultiplied: boolean, + isFlipY: boolean + ): Uint8ClampedArray { + const bytesPerPixel = kTextureFormatInfo[format].bytesPerBlock; + + const orientedPixels = isFlipY ? this.doFlipY(sourcePixels, width, height, 4) : sourcePixels; + const expectedPixels = new Uint8ClampedArray(bytesPerPixel * width * height); + + // Generate expectedPixels + // Use getImageData and readPixels to get canvas contents. + const rep = kTexelRepresentationInfo[format]; + const divide = 255.0; + let rgba: { R: number; G: number; B: number; A: number }; + for (let i = 0; i < height; ++i) { + for (let j = 0; j < width; ++j) { + const pixelPos = i * width + j; + + rgba = { + R: orientedPixels[pixelPos * 4] / divide, + G: orientedPixels[pixelPos * 4 + 1] / divide, + B: orientedPixels[pixelPos * 4 + 2] / divide, + A: orientedPixels[pixelPos * 4 + 3] / divide, + }; + + if (!srcPremultiplied && dstPremultiplied) { + rgba.R *= rgba.A; + rgba.G *= rgba.A; + rgba.B *= rgba.A; + } + + if (srcPremultiplied && !dstPremultiplied) { + assert(rgba.A !== 0.0); + rgba.R /= rgba.A; + rgba.G /= rgba.A; + rgba.B /= rgba.A; + } + + memcpy( + { src: rep.pack(rep.encode(rgba)) }, + { dst: expectedPixels, start: pixelPos * bytesPerPixel } + ); + } + } + + return expectedPixels; + } + + // MAINTENANCE_TODO(crbug.com/dawn/868): Should be possible to consolidate this along with texture checking checkCopyExternalImageResult( src: GPUBuffer, expected: ArrayBufferView, @@ -52,7 +163,7 @@ export class CopyToTextureUtils extends GPUTest { }); } - // TODO(crbug.com/dawn/868): Should be possible to consolidate this along with texture checking + // MAINTENANCE_TODO(crbug.com/dawn/868): Should be possible to consolidate this along with texture checking checkBufferWithRowPitch( actual: Uint8Array, exp: Uint8Array, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/util/device_pool.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/util/device_pool.ts index 71c43aa035e..8cf362b8ccf 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/util/device_pool.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/util/device_pool.ts @@ -1,11 +1,6 @@ import { SkipTestCase } from '../../common/framework/fixture.js'; import { getGPU } from '../../common/util/navigator_gpu.js'; -import { - assert, - raceWithRejectOnTimeout, - unreachable, - assertReject, -} from '../../common/util/util.js'; +import { assert, raceWithRejectOnTimeout, assertReject } from '../../common/util/util.js'; import { DefaultLimits } from '../constants.js'; export interface DeviceProvider { @@ -60,11 +55,7 @@ export class DevicePool { // (Hopefully if the device was lost, it has been reported by the time endErrorScopes() // has finished (or timed out). If not, it could cause a finite number of extra test // failures following this one (but should recover eventually).) - const lostReason = holder.lostReason; - if (lostReason !== undefined) { - // Fail the current test. - unreachable(`Device was lost: ${lostReason}`); - } + assert(holder.lostInfo === undefined, `Device was unexpectedly lost: ${holder.lostInfo}`); } catch (ex) { // Any error that isn't explicitly TestFailedButDeviceReusable forces a new device to be // created for the next test. @@ -74,7 +65,9 @@ export class DevicePool { } else { this.nonDefaultHolders.deleteByDevice(holder.device); } - // TODO: device.destroy() + if ('destroy' in holder.device) { + holder.device.destroy(); + } } throw ex; } finally { @@ -243,10 +236,11 @@ type DeviceHolderState = 'free' | 'reserved' | 'acquired'; class DeviceHolder implements DeviceProvider { readonly device: GPUDevice; state: DeviceHolderState = 'free'; - lostReason?: string; // initially undefined; becomes set when the device is lost + // initially undefined; becomes set when the device is lost + lostInfo?: GPUDeviceLostInfo; // Gets a device and creates a DeviceHolder. - // If the device is lost, DeviceHolder.lostReason gets set. + // If the device is lost, DeviceHolder.lost gets set. static async create(descriptor: CanonicalDeviceDescriptor | undefined): Promise<DeviceHolder> { const gpu = getGPU(); const adapter = await gpu.requestAdapter(); @@ -263,7 +257,7 @@ class DeviceHolder implements DeviceProvider { private constructor(device: GPUDevice) { this.device = device; this.device.lost.then(ev => { - this.lostReason = ev.message; + this.lostInfo = ev; }); } @@ -306,7 +300,7 @@ class DeviceHolder implements DeviceProvider { gpuOutOfMemoryError = await this.device.popErrorScope(); } catch (ex) { assert( - this.lostReason !== undefined, + this.lostInfo !== undefined, 'popErrorScope failed; should only happen if device has been lost' ); throw ex; diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/util/math.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/util/math.ts index 8cd244f56dd..485368f9b56 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/util/math.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/util/math.ts @@ -1,7 +1,7 @@ import { assert } from '../../common/util/util.js'; import { kBit } from '../shader/execution/builtin/builtin.js'; -import { f32Bits, Scalar } from './conversion.js'; +import { f32, f32Bits, Scalar } from './conversion.js'; /** * A multiple of 8 guaranteed to be way too large to allocate (just under 8 pebibytes). @@ -13,7 +13,7 @@ import { f32Bits, Scalar } from './conversion.js'; export const kMaxSafeMultipleOf8 = Number.MAX_SAFE_INTEGER - 7; /** Round `n` up to the next multiple of `alignment` (inclusive). */ -// TODO: Rename to `roundUp` +// MAINTENANCE_TODO: Rename to `roundUp` export function align(n: number, alignment: number): number { assert(Number.isInteger(n) && n >= 0, 'n must be a non-negative integer'); assert(Number.isInteger(alignment) && alignment > 0, 'alignment must be a positive integer'); @@ -67,12 +67,57 @@ export function diffULP(a: number, b: number): number { } /** + * @returns 0 if |val| is a subnormal f32 number, otherwise returns |val| + */ +function flushSubnormalNumber(val: number): number { + const u32_val = new Uint32Array(new Float32Array([val]).buffer)[0]; + return (u32_val & 0x7f800000) === 0 ? 0 : val; +} + +/** + * @returns 0 if |val| is a bit field for a subnormal f32 number, otherwise + * returns |val| + * |val| is assumed to be a u32 value representing a f32 + */ +function flushSubnormalBits(val: number): number { + return (val & 0x7f800000) === 0 ? 0 : val; +} + +/** + * @returns 0 if |val| is a subnormal f32 number, otherwise returns |val| + */ +function flushSubnormalScalar(val: Scalar): Scalar { + return isSubnormalScalar(val) ? f32(0) : val; +} + +/** + * @returns true if |val| is a subnormal f32 number, otherwise returns false + * 0 is considered a non-subnormal number by this function. + */ +export function isSubnormalScalar(val: Scalar): boolean { + if (val.type.kind !== 'f32') { + return false; + } + + if (val === f32(0)) { + return false; + } + + const u32_val = new Uint32Array(new Float32Array([val.value.valueOf() as number]).buffer)[0]; + return (u32_val & 0x7f800000) === 0; +} + +/** * @returns the next single precision floating point value after |val|, * towards +inf if |dir| is true, otherwise towards -inf. - * For -/+0 the nextAfter will be the closest subnormal in the correct - * direction, since -0 === +0. + * If |flush| is true, all subnormal values will be flushed to 0, + * before processing. + * If |flush| is false, the next subnormal will be calculated when appropriate, + * and for -/+0 the nextAfter will be the closest subnormal in the correct + * direction. + * |val| must be expressible as a f32. */ -export function nextAfter(val: number, dir: boolean = true): Scalar { +export function nextAfter(val: number, dir: boolean = true, flush: boolean): Scalar { if (Number.isNaN(val)) { return f32Bits(kBit.f32.nan.positive.s); } @@ -85,22 +130,30 @@ export function nextAfter(val: number, dir: boolean = true): Scalar { return f32Bits(kBit.f32.infinity.negative); } - const u32_val = new Uint32Array(new Float32Array([val]).buffer)[0]; - if (u32_val === kBit.f32.positive.zero || u32_val === kBit.f32.negative.zero) { + val = flush ? flushSubnormalNumber(val) : val; + + // -/+0 === 0 returns true + if (val === 0) { if (dir) { - return f32Bits(kBit.f32.subnormal.positive.min); + return flush ? f32Bits(kBit.f32.positive.min) : f32Bits(kBit.f32.subnormal.positive.min); } else { - return f32Bits(kBit.f32.subnormal.negative.max); + return flush ? f32Bits(kBit.f32.negative.max) : f32Bits(kBit.f32.subnormal.negative.max); } } - let result = u32_val; + // number is float64 internally, so need to test if value is expressible as a float32. + const converted: number = new Float32Array([val])[0]; + assert(val === converted, `${val} is not expressible as a f32.`); + + const u32_val = new Uint32Array(new Float32Array([val]).buffer)[0]; const is_positive = (u32_val & 0x80000000) === 0; + let result = u32_val; if (dir === is_positive) { result += 1; } else { result -= 1; } + result = flush ? flushSubnormalBits(result) : result; // Checking for overflow if ((result & 0x7f800000) === 0x7f800000) { @@ -112,3 +165,145 @@ export function nextAfter(val: number, dir: boolean = true): Scalar { } return f32Bits(result); } + +/** + * @returns if a test value is correctly rounded to an target value. Only + * defined for |test_values| being a float32. target values may be any number. + * + * Correctly rounded means that if the target value is precisely expressible + * as a float32, then |test_value| === |target|. + * Otherwise |test_value| needs to be either the closest expressible number + * greater or less than |target|. + * + * By default internally tests with both subnormals being flushed to 0 and not + * being flushed, but |accept_to_zero| and |accept_no_flush| can be used to + * control that behaviour. At least one accept flag must be true. + */ +export function correctlyRounded( + test_value: Scalar, + target: number, + accept_to_zero: boolean = true, + accept_no_flush: boolean = true +): boolean { + assert( + accept_to_zero || accept_no_flush, + `At least one of |accept_to_zero| & |accept_no_flush| must be true` + ); + + let result: boolean = false; + if (accept_to_zero) { + result = result || correctlyRoundedImpl(test_value, target, true); + } + if (accept_no_flush) { + result = result || correctlyRoundedImpl(test_value, target, false); + } + return result; +} + +function correctlyRoundedImpl(test_value: Scalar, target: number, flush: boolean): boolean { + assert(test_value.type.kind === 'f32', `${test_value} is expected to be a 'f32'`); + + if (Number.isNaN(target)) { + return Number.isNaN(test_value.value.valueOf() as number); + } + + if (target === Number.POSITIVE_INFINITY) { + return test_value.value === f32Bits(kBit.f32.infinity.positive).value; + } + + if (target === Number.NEGATIVE_INFINITY) { + return test_value.value === f32Bits(kBit.f32.infinity.negative).value; + } + + test_value = flush ? flushSubnormalScalar(test_value) : test_value; + target = flush ? flushSubnormalNumber(target) : target; + + const target32 = new Float32Array([target])[0]; + const converted: number = target32; + if (target === converted) { + // expected is precisely expressible in float32 + return test_value.value === f32(target32).value; + } + + let after_target: Scalar; + let before_target: Scalar; + + if (converted > target) { + // target32 is rounded towards +inf, so is after_target + after_target = f32(target32); + before_target = nextAfter(target32, false, flush); + } else { + // target32 is rounded towards -inf, so is before_target + after_target = nextAfter(target32, true, flush); + before_target = f32(target32); + } + + return test_value.value === before_target.value || test_value.value === after_target.value; +} + +/** + * Calculates the linear interpolation between two values of a given fractional. + * + * If |t| is 0, |a| is returned, if |t| is 1, |b| is returned, otherwise + * interpolation/extrapolation equivalent to a + t(b - a) is performed. + * + * Numerical stable version is adapted from http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0811r2.html + */ +export function lerp(a: number, b: number, t: number) { + if (!Number.isFinite(a) || !Number.isFinite(b)) { + return Number.NaN; + } + + if ((a <= 0.0 && b >= 0.0) || (a >= 0.0 && b <= 0.0)) { + return t * b + (1 - t) * a; + } + + if (t === 1.0) { + return b; + } + + const x = a + t * (b - a); + return t > 1.0 === b > a ? Math.max(b, x) : Math.min(b, x); +} + +/** Unwrap Scalar params into numbers and check preconditions */ +function unwrapRangeParams(min: Scalar, max: Scalar, num_steps: Scalar) { + assert(min.type.kind === 'f32', '|min| needs to be a f32'); + assert(max.type.kind === 'f32', '|max| needs to be a f32'); + assert(num_steps.type.kind === 'u32', '|num_steps| needs to be a u32'); + + const f32_min = min.value as number; + const f32_max = max.value as number; + const u32_num_steps = num_steps.value as number; + + assert(f32_max >= f32_min, '|max| must be greater than |min|'); + assert(u32_num_steps > 0, '|num_steps| must be greater than 0'); + + return { f32_min, f32_max, u32_num_steps }; +} + +/** @returns a linear increasing range of numbers. */ +export function linearRange(min: Scalar, max: Scalar, num_steps: Scalar): Array<number> { + const { f32_min, f32_max, u32_num_steps } = unwrapRangeParams(min, max, num_steps); + + return Array.from(Array(u32_num_steps).keys()).map(i => + lerp(f32_min, f32_max, i / (u32_num_steps - 1)) + ); +} + +/** + * @returns a non-linear increasing range of numbers, with a bias towards min. + * + * Generates a linear range on [0,1] with |num_steps|, then squares all the values to make the curve be quadratic, + * thus biasing towards 0, but remaining on the [0, 1] range. + * This biased range is then scaled to the desired range using lerp. + * Different curves could be generated by changing c, where greater values of c will bias more towards 0. + * */ +export function biasedRange(min: Scalar, max: Scalar, num_steps: Scalar): Array<number> { + const c = 2; + const { f32_min, f32_max, u32_num_steps } = unwrapRangeParams(min, max, num_steps); + + return Array.from(Array(u32_num_steps).keys()).map(i => + lerp(f32_min, f32_max, Math.pow(lerp(0, 1, i / (u32_num_steps - 1)), c)) + ); +} diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/util/texture/base.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/util/texture/base.ts index b568aabf863..0ee9c1aa266 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/util/texture/base.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/util/texture/base.ts @@ -15,10 +15,19 @@ export function maxMipLevelCount({ }): number { const sizeDict = reifyExtent3D(size); - let maxMippedDimension = sizeDict.width; - if (dimension !== '1d') maxMippedDimension = Math.max(maxMippedDimension, sizeDict.height); - if (dimension === '3d') - maxMippedDimension = Math.max(maxMippedDimension, sizeDict.depthOrArrayLayers); + let maxMippedDimension = 0; + switch (dimension) { + case '1d': + maxMippedDimension = 1; // No mipmaps allowed. + break; + case '2d': + maxMippedDimension = Math.max(sizeDict.width, sizeDict.height); + break; + case '3d': + maxMippedDimension = Math.max(sizeDict.width, sizeDict.height, sizeDict.depthOrArrayLayers); + break; + } + return Math.floor(Math.log2(maxMippedDimension)) + 1; } @@ -32,18 +41,43 @@ export function physicalMipSize( dimension: GPUTextureDimension, level: number ): Required<GPUExtent3DDict> { - assert(dimension === '2d'); - assert(Math.max(baseSize.width, baseSize.height) >> level > 0); + switch (dimension) { + case '1d': + assert(level === 0 && baseSize.height === 1 && baseSize.depthOrArrayLayers === 1); + return { width: baseSize.width, height: 1, depthOrArrayLayers: 1 }; - const virtualWidthAtLevel = Math.max(baseSize.width >> level, 1); - const virtualHeightAtLevel = Math.max(baseSize.height >> level, 1); - const physicalWidthAtLevel = align(virtualWidthAtLevel, kTextureFormatInfo[format].blockWidth); - const physicalHeightAtLevel = align(virtualHeightAtLevel, kTextureFormatInfo[format].blockHeight); - return { - width: physicalWidthAtLevel, - height: physicalHeightAtLevel, - depthOrArrayLayers: baseSize.depthOrArrayLayers, - }; + case '2d': { + assert(Math.max(baseSize.width, baseSize.height) >> level > 0); + + const virtualWidthAtLevel = Math.max(baseSize.width >> level, 1); + const virtualHeightAtLevel = Math.max(baseSize.height >> level, 1); + const physicalWidthAtLevel = align( + virtualWidthAtLevel, + kTextureFormatInfo[format].blockWidth + ); + const physicalHeightAtLevel = align( + virtualHeightAtLevel, + kTextureFormatInfo[format].blockHeight + ); + return { + width: physicalWidthAtLevel, + height: physicalHeightAtLevel, + depthOrArrayLayers: baseSize.depthOrArrayLayers, + }; + } + + case '3d': { + assert(Math.max(baseSize.width, baseSize.height, baseSize.depthOrArrayLayers) >> level > 0); + assert( + kTextureFormatInfo[format].blockWidth === 1 && kTextureFormatInfo[format].blockHeight === 1 + ); + return { + width: Math.max(baseSize.width >> level, 1), + height: Math.max(baseSize.height >> level, 1), + depthOrArrayLayers: Math.max(baseSize.depthOrArrayLayers >> level, 1), + }; + } + } } /** diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/util/texture/texel_data.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/util/texture/texel_data.spec.ts index 3bcbe1fcb3d..f775fd79e72 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/util/texture/texel_data.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/util/texture/texel_data.spec.ts @@ -53,14 +53,14 @@ function doTest( const { ReadbackTypedArray, shaderType } = getComponentReadbackTraits(getSingleDataType(format)); const shader = ` - [[group(0), binding(0)]] var tex : texture_2d<${shaderType}>; + @group(0) @binding(0) var tex : texture_2d<${shaderType}>; - [[block]] struct Output { + struct Output { ${rep.componentOrder.map(C => `result${C} : ${shaderType};`).join('\n')} }; - [[group(0), binding(1)]] var<storage, read_write> output : Output; + @group(0) @binding(1) var<storage, read_write> output : Output; - [[stage(compute), workgroup_size(1)]] + @stage(compute) @workgroup_size(1) fn main() { var texel : vec4<${shaderType}> = textureLoad(tex, vec2<i32>(0, 0), 0); ${rep.componentOrder.map(C => `output.result${C} = texel.${C.toLowerCase()};`).join('\n')} @@ -266,6 +266,10 @@ g.test('sint_texel_data_in_shader') .fn(doTest); g.test('float_texel_data_in_shader') + .desc( + ` +TODO: Test NaN, Infinity, -Infinity [1]` + ) .params(u => u .combine('format', kEncodableTextureFormats) @@ -282,7 +286,7 @@ g.test('float_texel_data_in_shader') // Test extrema makeParam(format, () => 0), - // TODO: Test NaN, Infinity, -Infinity + // [1]: Test NaN, Infinity, -Infinity // Test some values makeParam(format, () => 0.1199951171875), @@ -302,6 +306,10 @@ g.test('float_texel_data_in_shader') .fn(doTest); g.test('ufloat_texel_data_in_shader') + .desc( + ` +TODO: Test NaN, Infinity [1]` + ) .params(u => u .combine('format', kEncodableTextureFormats) @@ -318,7 +326,7 @@ g.test('ufloat_texel_data_in_shader') // Test extrema makeParam(format, () => 0), - // TODO: Test NaN, Infinity + // [2]: Test NaN, Infinity // Test some values makeParam(format, () => 0.119140625), diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/util/texture/texel_data.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/util/texture/texel_data.ts index 1cf90497359..61fb53545c2 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/util/texture/texel_data.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/util/texture/texel_data.ts @@ -311,7 +311,7 @@ function makeIntegerInfo( * @param {number} bitLength - The number of bits in each component. */ function makeFloatInfo(componentOrder: TexelComponent[], bitLength: number) { - // TODO: Use |bitLength| to round float values based on precision. + // MAINTENANCE_TODO: Use |bitLength| to round float values based on precision. const encode = applyEach(identity, componentOrder); const decode = applyEach(identity, componentOrder); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/README.txt b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/README.txt index 97c3f5f0b25..802f5b17a2b 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/README.txt +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/README.txt @@ -1,2 +1,5 @@ Tests for Web platform-specific interactions like GPUCanvasContext and canvas, WebXR, ImageBitmaps, and video APIs. + +TODO(#922): Also hopefully tests for user-initiated readbacks from WebGPU canvases +(printing, save image as, etc.) diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts index abc7c05de8e..b3a74671ccc 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts @@ -4,6 +4,7 @@ Tests for readback from WebGPU Canvas. import { makeTestGroup } from '../../../common/framework/test_group.js'; import { assert, raceWithRejectOnTimeout, unreachable } from '../../../common/util/util.js'; +import { kCanvasTextureFormats } from '../../capability_info.js'; import { GPUTest } from '../../gpu_test.js'; import { checkElementsEqual } from '../../util/check_contents.js'; import { @@ -20,48 +21,25 @@ export const g = makeTestGroup(GPUTest); // green: top-right; // red: bottom-left; // yellow: bottom-right; -const expect = new Uint8ClampedArray([ - 0x00, - 0x00, - 0xff, - 0xff, // blue - 0x00, - 0xff, - 0x00, - 0xff, // green - 0xff, - 0x00, - 0x00, - 0xff, // red - 0xff, - 0xff, - 0x00, - 0xff, // yellow +const expect = /* prettier-ignore */ new Uint8ClampedArray([ + 0x00, 0x00, 0xff, 0xff, // blue + 0x00, 0xff, 0x00, 0xff, // green + 0xff, 0x00, 0x00, 0xff, // red + 0xff, 0xff, 0x00, 0xff, // yellow ]); // WebGL has opposite Y direction so we need to // flipY to get correct expects. -const webglExpect = new Uint8ClampedArray([ - 0xff, - 0x00, - 0x00, - 0xff, // red - 0xff, - 0xff, - 0x00, - 0xff, // yellow - 0x00, - 0x00, - 0xff, - 0xff, // blue - 0x00, - 0xff, - 0x00, - 0xff, // green +const webglExpect = /* prettier-ignore */ new Uint8ClampedArray([ + 0xff, 0x00, 0x00, 0xff, // red + 0xff, 0xff, 0x00, 0xff, // yellow + 0x00, 0x00, 0xff, 0xff, // blue + 0x00, 0xff, 0x00, 0xff, // green ]); async function initCanvasContent( t: GPUTest, + format: GPUTextureFormat, canvasType: canvasTypes ): Promise<HTMLCanvasElement | OffscreenCanvas> { const canvas = createCanvas(t, canvasType, 2, 2); @@ -70,29 +48,39 @@ async function initCanvasContent( ctx.configure({ device: t.device, - format: 'bgra8unorm', - usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC, + format, + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, }); - const rows = 2; - const bytesPerRow = 256; - const buffer = t.device.createBuffer({ - mappedAtCreation: true, - size: rows * bytesPerRow, - usage: GPUBufferUsage.COPY_SRC, + const canvasTexture = ctx.getCurrentTexture(); + const tempTexture = t.device.createTexture({ + size: { width: 1, height: 1, depthOrArrayLayers: 1 }, + format, + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, }); - const mapping = buffer.getMappedRange(); - const data = new Uint8Array(mapping); - data.set(new Uint8Array([0xff, 0x00, 0x00, 0xff]), 0); // blue - data.set(new Uint8Array([0x00, 0xff, 0x00, 0xff]), 4); // green - data.set(new Uint8Array([0x00, 0x00, 0xff, 0xff]), 256 + 0); // red - data.set(new Uint8Array([0x00, 0xff, 0xff, 0xff]), 256 + 4); // yellow - buffer.unmap(); - - const texture = ctx.getCurrentTexture(); + const tempTextureView = tempTexture.createView(); const encoder = t.device.createCommandEncoder(); - encoder.copyBufferToTexture({ buffer, bytesPerRow }, { texture }, [2, 2, 1]); + + const clearOnePixel = (origin: GPUOrigin3D, color: GPUColor) => { + const pass = encoder.beginRenderPass({ + colorAttachments: [{ view: tempTextureView, loadValue: color, storeOp: 'store' }], + }); + pass.endPass(); + encoder.copyTextureToTexture( + { texture: tempTexture }, + { texture: canvasTexture, origin }, + { width: 1, height: 1 } + ); + }; + + clearOnePixel([0, 0], [0, 0, 1, 1]); + clearOnePixel([1, 0], [0, 1, 0, 1]); + clearOnePixel([0, 1], [1, 0, 0, 1]); + clearOnePixel([1, 1], [1, 1, 0, 1]); + t.device.queue.submit([encoder.finish()]); + tempTexture.destroy(); + await t.device.queue.onSubmittedWorkDone(); return canvas; @@ -120,17 +108,18 @@ g.test('onscreenCanvas,snapshot') .desc( ` Ensure snapshot of canvas with WebGPU context is correct - + TODO: Snapshot canvas to jpeg, webp and other mime type and different quality. Maybe we should test them in reftest. ` ) .params(u => u // + .combine('format', kCanvasTextureFormats) .combine('snapshotType', ['toDataURL', 'toBlob', 'imageBitmap']) ) .fn(async t => { - const canvas = (await initCanvasContent(t, 'onscreen')) as HTMLCanvasElement; + const canvas = (await initCanvasContent(t, t.params.format, 'onscreen')) as HTMLCanvasElement; let snapshot: HTMLImageElement | ImageBitmap; switch (t.params.snapshotType) { @@ -169,17 +158,22 @@ g.test('offscreenCanvas,snapshot') .desc( ` Ensure snapshot of offscreenCanvas with WebGPU context is correct - + TODO: Snapshot offscreenCanvas to jpeg, webp and other mime type and different quality. Maybe we should test them in reftest. ` ) .params(u => u // + .combine('format', kCanvasTextureFormats) .combine('snapshotType', ['convertToBlob', 'transferToImageBitmap', 'imageBitmap']) ) .fn(async t => { - const offscreenCanvas = (await initCanvasContent(t, 'offscreen')) as OffscreenCanvas; + const offscreenCanvas = (await initCanvasContent( + t, + t.params.format, + 'offscreen' + )) as OffscreenCanvas; let snapshot: HTMLImageElement | ImageBitmap; switch (t.params.snapshotType) { @@ -223,12 +217,13 @@ g.test('onscreenCanvas,uploadToWebGL') ) .params(u => u // + .combine('format', kCanvasTextureFormats) .combine('webgl', ['webgl', 'webgl2']) .combine('upload', ['texImage2D', 'texSubImage2D']) ) .fn(async t => { - const { webgl, upload } = t.params; - const canvas = (await initCanvasContent(t, 'onscreen')) as HTMLCanvasElement; + const { format, webgl, upload } = t.params; + const canvas = (await initCanvasContent(t, format, 'onscreen')) as HTMLCanvasElement; const expectCanvas: HTMLCanvasElement = createOnscreenCanvas(t, canvas.width, canvas.height); const gl = expectCanvas.getContext(webgl) as WebGLRenderingContext | WebGL2RenderingContext; @@ -282,13 +277,14 @@ g.test('drawTo2DCanvas') ) .params(u => u // + .combine('format', kCanvasTextureFormats) .combine('webgpuCanvasType', allCanvasTypes) .combine('canvas2DType', allCanvasTypes) ) .fn(async t => { - const { webgpuCanvasType, canvas2DType } = t.params; + const { format, webgpuCanvasType, canvas2DType } = t.params; - const canvas = await initCanvasContent(t, webgpuCanvasType); + const canvas = await initCanvasContent(t, format, webgpuCanvasType); const expectCanvas = createCanvas(t, canvas2DType, canvas.width, canvas.height); const ctx = expectCanvas.getContext('2d'); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts index ba59f92543a..0940ff58f26 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts @@ -24,26 +24,19 @@ enum Color { Red, Green, Blue, + Black, White, - OpaqueBlack, - TransparentBlack, + SemitransparentWhite, } -// These two types correspond to |premultiplyAlpha| and |imageOrientation| in |ImageBitmapOptions|. -type TransparentOp = 'premultiply' | 'none' | 'non-transparent'; -type OrientationOp = 'flipY' | 'none'; - // Cache for generated pixels. -const generatedPixelCache: Map< - RegularTextureFormat, - Map<Color, Map<TransparentOp, Uint8Array>> -> = new Map(); +const generatedPixelCache: Map<RegularTextureFormat, Map<Color, Uint8Array>> = new Map(); class F extends CopyToTextureUtils { generatePixel( color: Color, format: RegularTextureFormat, - transparentOp: TransparentOp + hasTransparentPixels: boolean ): Uint8Array { let formatEntry = generatedPixelCache.get(format); if (formatEntry === undefined) { @@ -51,14 +44,9 @@ class F extends CopyToTextureUtils { generatedPixelCache.set(format, formatEntry); } - let colorEntry = formatEntry.get(color); + const colorEntry = formatEntry.get(color); if (colorEntry === undefined) { - colorEntry = new Map(); - formatEntry.set(color, colorEntry); - } - - // None of the dst texture format is 'uint' or 'sint', so we can always use float value. - if (!colorEntry.has(transparentOp)) { + // None of the dst texture format is 'uint' or 'sint', so we can always use float value. const rep = kTexelRepresentationInfo[format]; let rgba: { R: number; G: number; B: number; A: number }; switch (color) { @@ -71,30 +59,24 @@ class F extends CopyToTextureUtils { case Color.Blue: rgba = { R: 0.0, G: 0.0, B: 1.0, A: 1.0 }; break; - case Color.White: + case Color.Black: rgba = { R: 0.0, G: 0.0, B: 0.0, A: 1.0 }; break; - case Color.OpaqueBlack: + case Color.White: rgba = { R: 1.0, G: 1.0, B: 1.0, A: 1.0 }; break; - case Color.TransparentBlack: - rgba = { R: 1.0, G: 1.0, B: 1.0, A: 0.0 }; + case Color.SemitransparentWhite: + rgba = { R: 1.0, G: 1.0, B: 1.0, A: 0.6 }; break; default: unreachable(); } - if (transparentOp === 'premultiply') { - rgba.R *= rgba.A; - rgba.G *= rgba.A; - rgba.B *= rgba.A; - } - const pixels = new Uint8Array(rep.pack(rep.encode(rgba))); - colorEntry.set(transparentOp, pixels); + formatEntry.set(color, pixels); } - return colorEntry.get(transparentOp)!; + return formatEntry.get(color)!; } // Helper functions to generate imagePixels based input configs. @@ -102,29 +84,25 @@ class F extends CopyToTextureUtils { format, width, height, - transparentOp, - orientationOp, + hasTransparentPixels, }: { format: RegularTextureFormat; width: number; height: number; - transparentOp: TransparentOp; - orientationOp: OrientationOp; + hasTransparentPixels: boolean; }): Uint8ClampedArray { const bytesPerPixel = kTextureFormatInfo[format].bytesPerBlock; // Generate input contents by iterating 'Color' enum const imagePixels = new Uint8ClampedArray(bytesPerPixel * width * height); - const testColors = [Color.Red, Color.Green, Color.Blue, Color.White, Color.OpaqueBlack]; - if (transparentOp !== 'non-transparent') testColors.push(Color.TransparentBlack); + const testColors = [Color.Red, Color.Green, Color.Blue, Color.Black, Color.White]; + if (hasTransparentPixels) testColors.push(Color.SemitransparentWhite); for (let i = 0; i < height; ++i) { for (let j = 0; j < width; ++j) { const pixelPos = i * width + j; - const currentColorIndex = - orientationOp === 'flipY' ? (height - i - 1) * width + j : pixelPos; - const currentPixel = testColors[currentColorIndex % testColors.length]; - const pixelData = this.generatePixel(currentPixel, format, transparentOp); + const currentPixel = testColors[pixelPos % testColors.length]; + const pixelData = this.generatePixel(currentPixel, format, hasTransparentPixels); imagePixels.set(pixelData, pixelPos * bytesPerPixel); } } @@ -141,12 +119,37 @@ g.test('from_ImageData') Test ImageBitmap generated from ImageData can be copied to WebGPU texture correctly. These imageBitmaps are highly possible living in CPU back resource. + + It generates pixels in ImageData one by one based on a color list: + [Red, Green, Blue, Black, White, SemitransparentWhite]. + + Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel + of dst texture, and read the contents out to compare with the ImageBitmap contents. + + Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged' + is set to 'true' and do unpremultiply alpha if it is set to 'false'. + + If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result + is flipped. + + The tests covers: + - Valid canvas type + - Source WebGPU Canvas lives in the same GPUDevice or different GPUDevice as test + - Valid dstColorFormat of copyExternalImageToTexture() + - Valid source image alphaMode + - Valid dest alphaMode + - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases) + - TODO(#913): color space tests need to be added + - TODO: Add error tolerance for rgb10a2unorm dst texture format + + And the expected results are all passed. ` ) .params(u => u .combine('alpha', ['none', 'premultiply'] as const) .combine('orientation', ['none', 'flipY'] as const) + .combine('srcDoFlipYDuringCopy', [true, false]) .combine('dstColorFormat', kValidTextureFormatsForCopyE2T) .combine('dstPremultiplied', [true, false]) .beginSubcases() @@ -154,15 +157,22 @@ g.test('from_ImageData') .combine('height', [1, 2, 4, 15, 255, 256]) ) .fn(async t => { - const { width, height, alpha, orientation, dstColorFormat, dstPremultiplied } = t.params; + const { + width, + height, + alpha, + orientation, + dstColorFormat, + dstPremultiplied, + srcDoFlipYDuringCopy, + } = t.params; // Generate input contents by iterating 'Color' enum const imagePixels = t.getImagePixels({ format: 'rgba8unorm', width, height, - transparentOp: 'none', - orientationOp: 'none', + hasTransparentPixels: true, }); // Generate correct expected values @@ -185,19 +195,29 @@ g.test('from_ImageData') // Construct expected value for different dst color format const dstBytesPerPixel = kTextureFormatInfo[dstColorFormat].bytesPerBlock; - const expectedTransparentOP = - alpha === 'premultiply' || dstPremultiplied ? 'premultiply' : 'none'; + const srcPremultiplied = alpha === 'premultiply'; + const sourceImageBitmapPixels = t.getSourceImageBitmapPixels( + imagePixels, + width, + height, + srcPremultiplied, + orientation === 'flipY' + ); - const expectedPixels = t.getImagePixels({ - format: dstColorFormat, + const format = kTextureFormatInfo[dstColorFormat].baseFormat ?? dstColorFormat; + + const expectedPixels = t.getExpectedPixels( + sourceImageBitmapPixels, width, height, - transparentOp: expectedTransparentOP, - orientationOp: orientation, - }); + format, + srcPremultiplied, + dstPremultiplied, + srcDoFlipYDuringCopy + ); t.doTestAndCheckResult( - { source: imageBitmap, origin: { x: 0, y: 0 } }, + { source: imageBitmap, origin: { x: 0, y: 0 }, flipY: srcDoFlipYDuringCopy }, { texture: dst, origin: { x: 0, y: 0 }, @@ -216,11 +236,36 @@ g.test('from_canvas') ` Test ImageBitmap generated from canvas/offscreenCanvas can be copied to WebGPU texture correctly. These imageBitmaps are highly possible living in GPU back resource. + + It generates pixels in ImageData one by one based on a color list: + [Red, Green, Blue, Black, White]. + + Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel + of dst texture, and read the contents out to compare with the ImageBitmap contents. + + Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged' + is set to 'true' and do unpremultiply alpha if it is set to 'false'. + + If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result + is flipped. + + The tests covers: + - Valid canvas type + - Source WebGPU Canvas lives in the same GPUDevice or different GPUDevice as test + - Valid dstColorFormat of copyExternalImageToTexture() + - Valid source image alphaMode + - Valid dest alphaMode + - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases) + - TODO(#913): color space tests need to be added + - TODO: Add error tolerance for rgb10a2unorm dst texture format + + And the expected results are all passed. ` ) .params(u => u .combine('orientation', ['none', 'flipY'] as const) + .combine('srcDoFlipYDuringCopy', [true, false]) .combine('dstColorFormat', kValidTextureFormatsForCopyE2T) .combine('dstPremultiplied', [true, false]) .beginSubcases() @@ -228,7 +273,14 @@ g.test('from_canvas') .combine('height', [1, 2, 4, 15, 255, 256]) ) .fn(async t => { - const { width, height, orientation, dstColorFormat, dstPremultiplied } = t.params; + const { + width, + height, + orientation, + dstColorFormat, + dstPremultiplied, + srcDoFlipYDuringCopy, + } = t.params; // CTS sometimes runs on worker threads, where document is not available. // In this case, OffscreenCanvas can be used instead of <canvas>. @@ -259,8 +311,7 @@ g.test('from_canvas') format: 'rgba8unorm', width, height, - transparentOp: 'non-transparent', - orientationOp: 'none', + hasTransparentPixels: false, }); const imageData = new ImageData(imagePixels, width, height); @@ -285,16 +336,29 @@ g.test('from_canvas') }); const dstBytesPerPixel = kTextureFormatInfo[dstColorFormat].bytesPerBlock; - const expectedPixels = t.getImagePixels({ - format: dstColorFormat, + + const sourceImageBitmapPixels = t.getSourceImageBitmapPixels( + imagePixels, width, height, - transparentOp: 'non-transparent', - orientationOp: orientation, - }); + true, + orientation === 'flipY' + ); + + const format = kTextureFormatInfo[dstColorFormat].baseFormat ?? dstColorFormat; + + const expectedPixels = t.getExpectedPixels( + sourceImageBitmapPixels, + width, + height, + format, + true, + dstPremultiplied, + srcDoFlipYDuringCopy + ); t.doTestAndCheckResult( - { source: imageBitmap, origin: { x: 0, y: 0 } }, + { source: imageBitmap, origin: { x: 0, y: 0 }, flipY: srcDoFlipYDuringCopy }, { texture: dst, origin: { x: 0, y: 0 }, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/copyToTexture/canvas.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/copyToTexture/canvas.spec.ts index 26956ab65dc..692dde92209 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/copyToTexture/canvas.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/copyToTexture/canvas.spec.ts @@ -1,11 +1,10 @@ export const description = ` copyToTexture with HTMLCanvasElement and OffscreenCanvas sources. -TODO: consider whether external_texture and copyToTexture video tests should be in the same file +TODO: Add tests for flipY `; import { makeTestGroup } from '../../../common/framework/test_group.js'; -import { unreachable, assert, memcpy } from '../../../common/util/util.js'; import { RegularTextureFormat, kTextureFormatInfo, @@ -13,22 +12,9 @@ import { } from '../../capability_info.js'; import { CopyToTextureUtils, isFp16Format } from '../../util/copy_to_texture.js'; import { canvasTypes, allCanvasTypes, createCanvas } from '../../util/create_elements.js'; -import { kTexelRepresentationInfo } from '../../util/texture/texel_data.js'; - -/** - * If the destination format specifies a transfer function, - * copyExternalImageToTexture (like B2T/T2T copies) should ignore it. - */ -function formatForExpectedPixels(format: RegularTextureFormat): RegularTextureFormat { - return format === 'rgba8unorm-srgb' - ? 'rgba8unorm' - : format === 'bgra8unorm-srgb' - ? 'bgra8unorm' - : format; -} class F extends CopyToTextureUtils { - // TODO: Cache the generated canvas to avoid duplicated initialization. + // MAINTENANCE_TODO: Cache the generated canvas to avoid duplicated initialization. init2DCanvasContent({ canvasType, width, @@ -79,7 +65,7 @@ class F extends CopyToTextureUtils { return { canvas, canvasContext }; } - // TODO: Cache the generated canvas to avoid duplicated initialization. + // MAINTENANCE_TODO: Cache the generated canvas to avoid duplicated initialization. initGLCanvasContent({ canvasType, contextName, @@ -140,84 +126,168 @@ class F extends CopyToTextureUtils { return { canvas, canvasContext: gl }; } - getExpectedPixels({ - context, + getInitGPUCanvasData( + width: number, + height: number, + premultiplied: boolean, + paintOpaqueRects: boolean + ): Uint8ClampedArray { + const rectWidth = Math.floor(width / 2); + const rectHeight = Math.floor(height / 2); + + const alphaValue = paintOpaqueRects ? 255 : 153; + const colorValue = premultiplied ? alphaValue : 255; + + // BGRA8Unorm texture + const initialData = new Uint8ClampedArray(4 * width * height); + const maxRectHeightIndex = width * rectHeight; + for (let pixelIndex = 0; pixelIndex < initialData.length / 4; ++pixelIndex) { + const index = pixelIndex * 4; + + // Top-half two rectangles + if (pixelIndex < maxRectHeightIndex) { + // top-left side rectangle + if (pixelIndex % width < rectWidth) { + // top-left side rectangle + initialData[index] = colorValue; + initialData[index + 1] = 0; + initialData[index + 2] = 0; + initialData[index + 3] = alphaValue; + } else { + // top-right side rectangle + initialData[index] = 0; + initialData[index + 1] = colorValue; + initialData[index + 2] = 0; + initialData[index + 3] = alphaValue; + } + } else { + // Bottom-half two rectangles + // bottom-left side rectangle + if (pixelIndex % width < rectWidth) { + initialData[index] = 0; + initialData[index + 1] = 0; + initialData[index + 2] = colorValue; + initialData[index + 3] = alphaValue; + } else { + // bottom-right side rectangle + initialData[index] = colorValue; + initialData[index + 1] = colorValue; + initialData[index + 2] = colorValue; + initialData[index + 3] = alphaValue; + } + } + } + return initialData; + } + + initGPUCanvasContent({ + device, + canvasType, width, height, - format, - contextType, - srcPremultiplied, - dstPremultiplied, + premultiplied, + paintOpaqueRects, }: { - context: - | CanvasRenderingContext2D - | OffscreenCanvasRenderingContext2D - | WebGLRenderingContext - | WebGL2RenderingContext; + device: GPUDevice; + canvasType: canvasTypes; width: number; height: number; - format: RegularTextureFormat; - contextType: '2d' | 'gl'; - srcPremultiplied: boolean; - dstPremultiplied: boolean; - }): Uint8ClampedArray { - const bytesPerPixel = kTextureFormatInfo[format].bytesPerBlock; - - const expectedPixels = new Uint8ClampedArray(bytesPerPixel * width * height); - let sourcePixels; - if (contextType === '2d') { - const ctx = context as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D; - sourcePixels = ctx.getImageData(0, 0, width, height).data; - } else if (contextType === 'gl') { - sourcePixels = new Uint8ClampedArray(width * height * 4); - const gl = context as WebGLRenderingContext | WebGL2RenderingContext; - gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, sourcePixels); - } else { - unreachable(); + premultiplied: boolean; + paintOpaqueRects: boolean; + }): { + canvas: HTMLCanvasElement | OffscreenCanvas; + } { + const canvas = createCanvas(this, canvasType, width, height); + + const gpuContext = canvas.getContext('webgpu') as GPUCanvasContext | null; + + if (gpuContext === null) { + this.skip(canvasType + ' canvas webgpu context not available'); } - // Generate expectedPixels - // Use getImageData and readPixels to get canvas contents. - const rep = kTexelRepresentationInfo[format]; - const divide = 255.0; - let rgba: { R: number; G: number; B: number; A: number }; + const alphaMode = premultiplied ? 'premultiplied' : 'opaque'; + + gpuContext.configure({ + device, + format: 'bgra8unorm', + usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC, + compositingAlphaMode: alphaMode, + }); + + // BGRA8Unorm texture + const initialData = this.getInitGPUCanvasData(width, height, premultiplied, paintOpaqueRects); + const canvasTexture = gpuContext.getCurrentTexture(); + device.queue.writeTexture( + { texture: canvasTexture }, + initialData, + { + bytesPerRow: width * 4, + rowsPerImage: height, + }, + { + width, + height, + depthOrArrayLayers: 1, + } + ); + + return { canvas }; + } + + getSourceCanvas2DContent( + context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, + width: number, + height: number + ): Uint8ClampedArray { + return context.getImageData(0, 0, width, height).data; + } + + getSourceCanvasGLContent( + gl: WebGLRenderingContext | WebGL2RenderingContext, + width: number, + height: number + ): Uint8ClampedArray { + const bytesPerPixel = 4; + + const sourcePixels = new Uint8ClampedArray(width * height * bytesPerPixel); + gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, sourcePixels); + + return this.doFlipY(sourcePixels, width, height, bytesPerPixel); + } + + calculateSourceContentOnCPU( + width: number, + height: number, + premultipliedAlpha: boolean, + paintOpaqueRects: boolean + ): Uint8ClampedArray { + const bytesPerPixel = 4; + + const rgbaPixels = this.getInitGPUCanvasData( + width, + height, + premultipliedAlpha, + paintOpaqueRects + ); + + // The source canvas has bgra8unorm back resource. We + // swizzle the channels to align with 2d/webgl canvas and + // clear alpha to opaque when context compositingAlphaMode + // is set to opaque (follow webgpu spec). for (let i = 0; i < height; ++i) { for (let j = 0; j < width; ++j) { const pixelPos = i * width + j; - - rgba = { - R: sourcePixels[pixelPos * 4] / divide, - G: sourcePixels[pixelPos * 4 + 1] / divide, - B: sourcePixels[pixelPos * 4 + 2] / divide, - A: sourcePixels[pixelPos * 4 + 3] / divide, - }; - - if (!srcPremultiplied && dstPremultiplied) { - rgba.R *= rgba.A; - rgba.G *= rgba.A; - rgba.B *= rgba.A; + const r = rgbaPixels[pixelPos * bytesPerPixel + 2]; + if (!premultipliedAlpha) { + rgbaPixels[pixelPos * bytesPerPixel + 3] = 255; } - if (srcPremultiplied && !dstPremultiplied) { - assert(rgba.A !== 0.0); - rgba.R /= rgba.A; - rgba.G /= rgba.A; - rgba.B /= rgba.A; - } - - // WebGL readPixel returns pixels from bottom-left origin. Using CopyExternalImageToTexture - // to copy from WebGL Canvas keeps top-left origin. So the expectation from webgl.readPixel should - // be flipped. - const dstPixelPos = contextType === 'gl' ? (height - i - 1) * width + j : pixelPos; - - memcpy( - { src: rep.pack(rep.encode(rgba)) }, - { dst: expectedPixels, start: dstPixelPos * bytesPerPixel } - ); + rgbaPixels[pixelPos * bytesPerPixel + 2] = rgbaPixels[pixelPos * bytesPerPixel]; + rgbaPixels[pixelPos * bytesPerPixel] = r; } } - return expectedPixels; + return rgbaPixels; } } @@ -236,10 +306,19 @@ g.test('copy_contents_from_2d_context_canvas') Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel of dst texture, and read the contents out to compare with the canvas contents. + Do premultiply alpha in advance if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged' + is set to 'ture' and do unpremultiply alpha if it is set to 'false'. + + If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result + is flipped. + The tests covers: - Valid canvas type - Valid 2d context type - - TODO: color space tests need to be added + - Valid dstColorFormat of copyExternalImageToTexture() + - Valid dest alphaMode + - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases) + - TODO(#913): color space tests need to be added - TODO: Add error tolerance for rgb10a2unorm dst texture format And the expected results are all passed. @@ -250,12 +329,20 @@ g.test('copy_contents_from_2d_context_canvas') .combine('canvasType', allCanvasTypes) .combine('dstColorFormat', kValidTextureFormatsForCopyE2T) .combine('dstPremultiplied', [true, false]) + .combine('srcDoFlipYDuringCopy', [true, false]) .beginSubcases() .combine('width', [1, 2, 4, 15, 255, 256]) .combine('height', [1, 2, 4, 15, 255, 256]) ) .fn(async t => { - const { width, height, canvasType, dstColorFormat, dstPremultiplied } = t.params; + const { + width, + height, + canvasType, + dstColorFormat, + dstPremultiplied, + srcDoFlipYDuringCopy, + } = t.params; // When dst texture format is rgb10a2unorm, the generated expected value of the result // may have tiny errors compared to the actual result when the channel value is @@ -286,22 +373,26 @@ g.test('copy_contents_from_2d_context_canvas') // Construct expected value for different dst color format const dstBytesPerPixel = kTextureFormatInfo[dstColorFormat].bytesPerBlock; - const format: RegularTextureFormat = formatForExpectedPixels(dstColorFormat); + const format: RegularTextureFormat = + kTextureFormatInfo[dstColorFormat].baseFormat !== undefined + ? kTextureFormatInfo[dstColorFormat].baseFormat! + : dstColorFormat; // For 2d canvas, get expected pixels with getImageData(), which returns unpremultiplied // values. - const expectedPixels = t.getExpectedPixels({ - context: canvasContext, + const sourcePixels = t.getSourceCanvas2DContent(canvasContext, width, height); + const expectedPixels = t.getExpectedPixels( + sourcePixels, width, height, format, - contextType: '2d', - srcPremultiplied: false, + false, dstPremultiplied, - }); + srcDoFlipYDuringCopy + ); t.doTestAndCheckResult( - { source: canvas, origin: { x: 0, y: 0 } }, + { source: canvas, origin: { x: 0, y: 0 }, flipY: srcDoFlipYDuringCopy }, { texture: dst, origin: { x: 0, y: 0 }, @@ -322,7 +413,7 @@ g.test('copy_contents_from_gl_context_canvas') can be copied to WebGPU texture correctly. It creates HTMLCanvasElement/OffscreenCanvas with webgl'/'webgl2'. - Use stencil + clear to render red rect for top-left, green rect + Use scissor + clear to render red rect for top-left, green rect for top-right, blue rect for bottom-left and white for bottom-right. And do premultiply alpha in advance if the webgl/webgl2 context is created with premultipliedAlpha : true. @@ -330,9 +421,19 @@ g.test('copy_contents_from_gl_context_canvas') Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel of dst texture, and read the contents out to compare with the canvas contents. + Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged' + is set to 'ture' and do unpremultiply alpha if it is set to 'false'. + + If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result + is flipped. + The tests covers: - Valid canvas type - Valid webgl/webgl2 context type + - Valid dstColorFormat of copyExternalImageToTexture() + - Valid source image alphaMode + - Valid dest alphaMode + - Valid 'flipY' config in 'GPUImageCopyExternalImage'(named 'srcDoFlipYDuringCopy' in cases) - TODO: color space tests need to be added - TODO: Add error tolerance for rgb10a2unorm dst texture format @@ -346,6 +447,7 @@ g.test('copy_contents_from_gl_context_canvas') .combine('dstColorFormat', kValidTextureFormatsForCopyE2T) .combine('srcPremultiplied', [true, false]) .combine('dstPremultiplied', [true, false]) + .combine('srcDoFlipYDuringCopy', [true, false]) .beginSubcases() .combine('width', [1, 2, 4, 15, 255, 256]) .combine('height', [1, 2, 4, 15, 255, 256]) @@ -359,6 +461,7 @@ g.test('copy_contents_from_gl_context_canvas') dstColorFormat, srcPremultiplied, dstPremultiplied, + srcDoFlipYDuringCopy, } = t.params; // When dst texture format is rgb10a2unorm, the generated expected value of the result @@ -392,19 +495,155 @@ g.test('copy_contents_from_gl_context_canvas') // Construct expected value for different dst color format const dstBytesPerPixel = kTextureFormatInfo[dstColorFormat].bytesPerBlock; - const format: RegularTextureFormat = formatForExpectedPixels(dstColorFormat); - const expectedPixels = t.getExpectedPixels({ - context: canvasContext, + const format: RegularTextureFormat = + kTextureFormatInfo[dstColorFormat].baseFormat !== undefined + ? kTextureFormatInfo[dstColorFormat].baseFormat! + : dstColorFormat; + const sourcePixels = t.getSourceCanvasGLContent(canvasContext, width, height); + const expectedPixels = t.getExpectedPixels( + sourcePixels, width, height, format, - contextType: 'gl', srcPremultiplied, dstPremultiplied, + srcDoFlipYDuringCopy + ); + + t.doTestAndCheckResult( + { source: canvas, origin: { x: 0, y: 0 }, flipY: srcDoFlipYDuringCopy }, + { + texture: dst, + origin: { x: 0, y: 0 }, + colorSpace: 'srgb', + premultipliedAlpha: dstPremultiplied, + }, + { width: canvas.width, height: canvas.height, depthOrArrayLayers: 1 }, + dstBytesPerPixel, + expectedPixels, + isFp16Format(dstColorFormat) + ); + }); + +g.test('copy_contents_from_gpu_context_canvas') + .desc( + ` + Test HTMLCanvasElement and OffscreenCanvas with webgpu context + can be copied to WebGPU texture correctly. + + It creates HTMLCanvasElement/OffscreenCanvas with 'webgpu'. + Use writeTexture to copy pixels to back buffer. The results are: + red rect for top-left, green rect for top-right, blue rect for bottom-left + and white for bottom-right. + + And do premultiply alpha in advance if the webgpu context is created + with compositingAlphaMode="premultiplied". + + Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel + of dst texture, and read the contents out to compare with the canvas contents. + + Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged' + is set to 'ture' and do unpremultiply alpha if it is set to 'false'. + + If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result + is flipped. + + The tests covers: + - Valid canvas type + - Source WebGPU Canvas lives in the same GPUDevice or different GPUDevice as test + - Valid dstColorFormat of copyExternalImageToTexture() + - Valid source image alphaMode + - Valid dest alphaMode + - Valid 'flipY' config in 'GPUImageCopyExternalImage'(named 'srcDoFlipYDuringCopy' in cases) + - TODO: color space tests need to be added + - TODO: Add error tolerance for rgb10a2unorm dst texture format + + And the expected results are all passed. + ` + ) + .params(u => + u + .combine('canvasType', allCanvasTypes) + .combine('srcAndDstInSameGPUDevice', [true, false]) + .combine('dstColorFormat', kValidTextureFormatsForCopyE2T) + .combine('srcPremultiplied', [true]) + .combine('dstPremultiplied', [true, false]) + .combine('srcDoFlipYDuringCopy', [true, false]) + .beginSubcases() + .combine('width', [1, 2, 4, 15, 255, 256]) + .combine('height', [1, 2, 4, 15, 255, 256]) + ) + .fn(async t => { + const { + width, + height, + canvasType, + srcAndDstInSameGPUDevice, + dstColorFormat, + srcPremultiplied, + dstPremultiplied, + srcDoFlipYDuringCopy, + } = t.params; + + let device: GPUDevice; + + if (!srcAndDstInSameGPUDevice) { + await t.selectMismatchedDeviceOrSkipTestCase(undefined); + device = t.mismatchedDevice; + } else { + device = t.device; + } + + // When dst texture format is rgb10a2unorm, the generated expected value of the result + // may have tiny errors compared to the actual result when the channel value is + // not 1.0 or 0.0. + // For example, we init the pixel with r channel to 0.6. And the denormalized value for + // 10-bit channel is 613.8, which needs to call "round" or other function to get an integer. + // It is possible that gpu adopt different "round" as our cpu implementation(we use Math.round()) + // and it will generate tiny errors. + // So the cases with rgb10a2unorm dst texture format are handled specially by by painting opaque rects + // to ensure they will have stable result after alphaOps(should keep the same value). + const { canvas } = t.initGPUCanvasContent({ + device, + canvasType, + width, + height, + premultiplied: srcPremultiplied, + paintOpaqueRects: dstColorFormat === 'rgb10a2unorm', }); + const dst = t.device.createTexture({ + size: { + width, + height, + depthOrArrayLayers: 1, + }, + format: dstColorFormat, + usage: + GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + // Construct expected value for different dst color format + const dstBytesPerPixel = kTextureFormatInfo[dstColorFormat].bytesPerBlock; + const format = kTextureFormatInfo[dstColorFormat].baseFormat ?? dstColorFormat; + const sourcePixels = t.calculateSourceContentOnCPU( + width, + height, + srcPremultiplied, + dstColorFormat === 'rgb10a2unorm' + ); + const expectedPixels = t.getExpectedPixels( + sourcePixels, + width, + height, + format, + srcPremultiplied, + dstPremultiplied, + srcDoFlipYDuringCopy + ); + t.doTestAndCheckResult( - { source: canvas, origin: { x: 0, y: 0 } }, + { source: canvas, origin: { x: 0, y: 0 }, flipY: srcDoFlipYDuringCopy }, { texture: dst, origin: { x: 0, y: 0 }, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/external_texture/README.txt b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/external_texture/README.txt index d34c9de5819..c0ec3fb7451 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/external_texture/README.txt +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/external_texture/README.txt @@ -1 +1 @@ -Tests for external textures from video/canvas. +Tests for external textures. diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/external_texture/canvas.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/external_texture/canvas.spec.ts deleted file mode 100644 index 27bffc7fe55..00000000000 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/external_texture/canvas.spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const description = ` -Tests for external textures with HTMLCanvasElement and OffscreenCanvas sources. - -- x= {HTMLCanvasElement, OffscreenCanvas} -- x= {2d, webgl, webgl2, webgpu, bitmaprenderer} context, {various context creation attributes} - -TODO: consider whether external_texture and copyToTexture video tests should be in the same file -TODO: plan -`; - -import { makeTestGroup } from '../../../common/framework/test_group.js'; -import { GPUTest } from '../../gpu_test.js'; - -export const g = makeTestGroup(GPUTest); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/external_texture/video.spec.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/external_texture/video.spec.ts index 920c13d7651..f0e50a1f441 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/external_texture/video.spec.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/external_texture/video.spec.ts @@ -28,7 +28,7 @@ function createExternalTextureSamplingTestPipeline(t: GPUTest): GPURenderPipelin vertex: { module: t.device.createShaderModule({ code: ` - [[stage(vertex)]] fn main([[builtin(vertex_index)]] VertexIndex : u32) -> [[builtin(position)]] vec4<f32> { + @stage(vertex) fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> { var pos = array<vec4<f32>, 6>( vec4<f32>( 1.0, 1.0, 0.0, 1.0), vec4<f32>( 1.0, -1.0, 0.0, 1.0), @@ -46,11 +46,11 @@ function createExternalTextureSamplingTestPipeline(t: GPUTest): GPURenderPipelin fragment: { module: t.device.createShaderModule({ code: ` - [[group(0), binding(0)]] var s : sampler; - [[group(0), binding(1)]] var t : texture_external; + @group(0) @binding(0) var s : sampler; + @group(0) @binding(1) var t : texture_external; - [[stage(fragment)]] fn main([[builtin(position)]] FragCoord : vec4<f32>) - -> [[location(0)]] vec4<f32> { + @stage(fragment) fn main(@builtin(position) FragCoord : vec4<f32>) + -> @location(0) vec4<f32> { return textureSampleLevel(t, s, FragCoord.xy / vec2<f32>(16.0, 16.0)); } `, @@ -256,10 +256,10 @@ Tests that we can import an HTMLVideoElement into a GPUExternalTexture and use i // stored in storage texture. module: t.device.createShaderModule({ code: ` - [[group(0), binding(0)]] var t : texture_external; - [[group(0), binding(1)]] var outImage : texture_storage_2d<rgba8unorm, write>; + @group(0) @binding(0) var t : texture_external; + @group(0) @binding(1) var outImage : texture_storage_2d<rgba8unorm, write>; - [[stage(compute), workgroup_size(1)]] fn main() { + @stage(compute) @workgroup_size(1) fn main() { var red : vec4<f32> = textureLoad(t, vec2<i32>(10,10)); textureStore(outImage, vec2<i32>(0, 0), red); var green : vec4<f32> = textureLoad(t, vec2<i32>(70,118)); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/README.txt b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/README.txt index cd50eea1f91..389947bbd75 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/README.txt +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/README.txt @@ -8,8 +8,10 @@ This tests things like: - The canvas renders with the correct transfer function. - The canvas blends and interpolates in the correct color encoding. -TODO: Test all possible output texture formats (currently only testing bgra8unorm). -TODO: Test all possible ways to write into those formats (currently only testing B2T copy). +TODO(#915): Test all possible output texture formats (currently only needs rgba16float). +TODO(#916): Test all possible ways to write into those formats (currently only testing B2T copy). +TODO(#917): Test compositingAlphaMode options +TODO(#918): Test all possible color spaces (once we have more than 1) -TODO: Why is there sometimes a difference of 1 (e.g. 3f vs 40) in canvas_size_different_with_back_buffer_size? +TODO(#921): Why is there sometimes a difference of 1 (e.g. 3f vs 40) in canvas_size_different_with_back_buffer_size? And why does chromium's image_diff show diffs on other pixels that don't seem to have diffs? diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_clear.html.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_clear.html.ts index df41de00bb6..4e621f5a49b 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_clear.html.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_clear.html.ts @@ -1,27 +1,32 @@ import { runRefTest } from './gpu_ref_test.js'; runRefTest(async t => { - const canvas = document.getElementById('gpucanvas') as HTMLCanvasElement; + function draw(canvasId: string, format: GPUTextureFormat) { + const canvas = document.getElementById(canvasId) as HTMLCanvasElement; - const ctx = (canvas.getContext('webgpu') as unknown) as GPUCanvasContext; - ctx.configure({ - device: t.device, - format: 'bgra8unorm', - }); + const ctx = (canvas.getContext('webgpu') as unknown) as GPUCanvasContext; + ctx.configure({ + device: t.device, + format, + }); - const colorAttachment = ctx.getCurrentTexture(); - const colorAttachmentView = colorAttachment.createView(); + const colorAttachment = ctx.getCurrentTexture(); + const colorAttachmentView = colorAttachment.createView(); - const encoder = t.device.createCommandEncoder(); - const pass = encoder.beginRenderPass({ - colorAttachments: [ - { - view: colorAttachmentView, - loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, - storeOp: 'store', - }, - ], - }); - pass.endPass(); - t.device.queue.submit([encoder.finish()]); + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: colorAttachmentView, + loadValue: { r: 0.4, g: 1.0, b: 0.0, a: 1.0 }, + storeOp: 'store', + }, + ], + }); + pass.endPass(); + t.device.queue.submit([encoder.finish()]); + } + + draw('cvs0', 'bgra8unorm'); + draw('cvs1', 'rgba8unorm'); }); diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_clear.https.html b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_clear.https.html index 8209a3bf692..e61e97ec16b 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_clear.https.html +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_clear.https.html @@ -4,7 +4,8 @@ <link rel="help" href="https://gpuweb.github.io/gpuweb/" /> <meta name="assert" content="WebGPU cleared canvas should be presented correctly" /> <link rel="match" href="./ref/canvas_clear-ref.html" /> - <canvas id="gpucanvas" width="10" height="10"></canvas> + <canvas id="cvs0" width="20" height="20" style="width: 20px; height: 20px;"></canvas> + <canvas id="cvs1" width="20" height="20" style="width: 20px; height: 20px;"></canvas> <script src="/common/reftest-wait.js"></script> <script type="module" src="canvas_clear.html.js"></script> </html> diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_complex.html.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_complex.html.ts index 5c51c60345f..676ce68c9d0 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_complex.html.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_complex.html.ts @@ -24,8 +24,9 @@ export function run( break; case 'bgra8unorm-srgb': case 'rgba8unorm-srgb': - // TODO: srgb output format is untested - // reverse gammaCompress to get same value shader output as non-srgb formats + // NOTE: "-srgb" cases haven't been tested (there aren't any .html files that use them). + + // Reverse gammaCompress to get same value shader output as non-srgb formats: shaderValue = gammaDecompress(shaderValue); isOutputSrgb = true; break; @@ -139,12 +140,12 @@ export function run( module: t.device.createShaderModule({ code: ` struct VertexOutput { - [[builtin(position)]] Position : vec4<f32>; - [[location(0)]] fragUV : vec2<f32>; + @builtin(position) Position : vec4<f32>; + @location(0) fragUV : vec2<f32>; }; -[[stage(vertex)]] -fn main([[builtin(vertex_index)]] VertexIndex : u32) -> VertexOutput { +@stage(vertex) +fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput { var pos = array<vec2<f32>, 6>( vec2<f32>( 1.0, 1.0), vec2<f32>( 1.0, -1.0), @@ -172,10 +173,10 @@ fn main([[builtin(vertex_index)]] VertexIndex : u32) -> VertexOutput { }, fragment: { module: t.device.createShaderModule({ - // TODO: srgb output format is untested + // NOTE: "-srgb" cases haven't been tested (there aren't any .html files that use them). code: ` -[[group(0), binding(0)]] var mySampler: sampler; -[[group(0), binding(1)]] var myTexture: texture_2d<f32>; +@group(0) @binding(0) var mySampler: sampler; +@group(0) @binding(1) var myTexture: texture_2d<f32>; fn gammaDecompress(n: f32) -> f32 { var r = n; @@ -188,8 +189,8 @@ fn gammaDecompress(n: f32) -> f32 { return r; } -[[stage(fragment)]] -fn srgbMain([[location(0)]] fragUV: vec2<f32>) -> [[location(0)]] vec4<f32> { +@stage(fragment) +fn srgbMain(@location(0) fragUV: vec2<f32>) -> @location(0) vec4<f32> { var result = textureSample(myTexture, mySampler, fragUV); result.r = gammaDecompress(result.r); result.g = gammaDecompress(result.g); @@ -197,8 +198,8 @@ fn srgbMain([[location(0)]] fragUV: vec2<f32>) -> [[location(0)]] vec4<f32> { return result; } -[[stage(fragment)]] -fn linearMain([[location(0)]] fragUV: vec2<f32>) -> [[location(0)]] vec4<f32> { +@stage(fragment) +fn linearMain(@location(0) fragUV: vec2<f32>) -> @location(0) vec4<f32> { return textureSample(myTexture, mySampler, fragUV); } `, @@ -256,12 +257,12 @@ fn linearMain([[location(0)]] fragUV: vec2<f32>) -> [[location(0)]] vec4<f32> { module: t.device.createShaderModule({ code: ` struct VertexOutput { - [[builtin(position)]] Position : vec4<f32>; - [[location(0)]] fragColor : vec4<f32>; + @builtin(position) Position : vec4<f32>; + @location(0) fragColor : vec4<f32>; }; -[[stage(vertex)]] -fn main([[builtin(vertex_index)]] VertexIndex : u32) -> VertexOutput { +@stage(vertex) +fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput { var pos = array<vec2<f32>, 6>( vec2<f32>( 0.5, 0.5), vec2<f32>( 0.5, -0.5), @@ -294,8 +295,8 @@ fn main([[builtin(vertex_index)]] VertexIndex : u32) -> VertexOutput { fragment: { module: t.device.createShaderModule({ code: ` -[[stage(fragment)]] -fn main([[location(0)]] fragColor: vec4<f32>) -> [[location(0)]] vec4<f32> { +@stage(fragment) +fn main(@location(0) fragColor: vec4<f32>) -> @location(0) vec4<f32> { return fragColor; } `, @@ -333,11 +334,11 @@ fn main([[location(0)]] fragColor: vec4<f32>) -> [[location(0)]] vec4<f32> { module: t.device.createShaderModule({ code: ` struct VertexOutput { - [[builtin(position)]] Position : vec4<f32>; + @builtin(position) Position : vec4<f32>; }; -[[stage(vertex)]] -fn main([[builtin(vertex_index)]] VertexIndex : u32) -> VertexOutput { +@stage(vertex) +fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput { var pos = array<vec2<f32>, 6>( vec2<f32>( 1.0, 1.0), vec2<f32>( 1.0, -1.0), @@ -357,11 +358,11 @@ fn main([[builtin(vertex_index)]] VertexIndex : u32) -> VertexOutput { fragment: { module: t.device.createShaderModule({ code: ` -[[group(0), binding(0)]] var mySampler: sampler; -[[group(0), binding(1)]] var myTexture: texture_2d<f32>; +@group(0) @binding(0) var mySampler: sampler; +@group(0) @binding(1) var myTexture: texture_2d<f32>; -[[stage(fragment)]] -fn main([[builtin(position)]] fragcoord: vec4<f32>) -> [[location(0)]] vec4<f32> { +@stage(fragment) +fn main(@builtin(position) fragcoord: vec4<f32>) -> @location(0) vec4<f32> { var coord = vec2<u32>(floor(fragcoord.xy)); var color = vec4<f32>(0.0, 0.0, 0.0, 1.0); if (coord.x == 0u) { diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_composite_alpha.html.ts b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_composite_alpha.html.ts index d2187b92f05..f3d59539c65 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_composite_alpha.html.ts +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/canvas_composite_alpha.html.ts @@ -35,12 +35,12 @@ export function run(format: GPUTextureFormat, compositingAlphaMode: GPUCanvasCom module: t.device.createShaderModule({ code: ` struct VertexOutput { -[[builtin(position)]] Position : vec4<f32>; -[[location(0)]] fragColor : vec4<f32>; +@builtin(position) Position : vec4<f32>; +@location(0) fragColor : vec4<f32>; }; -[[stage(vertex)]] -fn main([[builtin(vertex_index)]] VertexIndex : u32) -> VertexOutput { +@stage(vertex) +fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput { var pos = array<vec2<f32>, 6>( vec2<f32>( 0.75, 0.75), vec2<f32>( 0.75, -0.75), @@ -73,8 +73,8 @@ return output; fragment: { module: t.device.createShaderModule({ code: ` -[[stage(fragment)]] -fn main([[location(0)]] fragColor: vec4<f32>) -> [[location(0)]] vec4<f32> { +@stage(fragment) +fn main(@location(0) fragColor: vec4<f32>) -> @location(0) vec4<f32> { return fragColor; } `, diff --git a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/ref/canvas_clear-ref.html b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/ref/canvas_clear-ref.html index 2e078118627..7aeb57ef864 100644 --- a/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/ref/canvas_clear-ref.html +++ b/chromium/third_party/webgpu-cts/src/src/webgpu/web_platform/reftests/ref/canvas_clear-ref.html @@ -2,11 +2,18 @@ <title>WebGPU canvas_clear (ref)</title> <meta charset="utf-8" /> <link rel="help" href="https://gpuweb.github.io/gpuweb/" /> - <canvas id="myCanvas" width="10" height="10"></canvas> + <canvas id="cvs0" width="20" height="20" style="width: 20px; height: 20px;"></canvas> + <canvas id="cvs1" width="20" height="20" style="width: 20px; height: 20px;"></canvas> <script> - var c = document.getElementById('myCanvas'); - var ctx = c.getContext('2d'); - ctx.fillStyle = '#00FF00'; - ctx.fillRect(0, 0, c.width, c.height); - </script> + function draw(canvas) { + var c = document.getElementById(canvas); + var ctx = c.getContext('2d'); + ctx.fillStyle = '#66FF00'; + ctx.fillRect(0, 0, c.width, c.height); + } + + draw('cvs0'); + draw('cvs1'); + + </script> </html> diff --git a/chromium/third_party/webgpu-cts/src/standalone/index.html b/chromium/third_party/webgpu-cts/src/standalone/index.html index f014981dab0..2edede9df75 100644 --- a/chromium/third_party/webgpu-cts/src/standalone/index.html +++ b/chromium/third_party/webgpu-cts/src/standalone/index.html @@ -109,16 +109,17 @@ display: inline; flex: 10 0 4em; } - .nodequery { + .nodecolumns { position: absolute; left: 220px; - + } + .nodequery { font-weight: bold; background: transparent; border: none; padding: 2px; margin: 0 0.5em; - width: calc(100vw - 260px); + width: calc(100vw - 360px); } .nodedescription { margin: 0 0 0 1em; diff --git a/chromium/third_party/webgpu-cts/ts_sources.txt b/chromium/third_party/webgpu-cts/ts_sources.txt index 66ce380c52c..899f193ac47 100644 --- a/chromium/third_party/webgpu-cts/ts_sources.txt +++ b/chromium/third_party/webgpu-cts/ts_sources.txt @@ -52,8 +52,8 @@ src/demo/a/b/c.spec.ts src/demo/a/b/d.spec.ts src/demo/file_depth_2/in_single_child_dir/r.spec.ts src/stress/listing.ts -src/stress/adapter/device_allocation.ts src/webgpu/constants.ts +src/stress/adapter/device_allocation.spec.ts src/webgpu/util/conversion.ts src/webgpu/shader/execution/builtin/builtin.ts src/webgpu/util/math.ts @@ -139,9 +139,11 @@ src/webgpu/api/operation/device/lost.spec.ts src/webgpu/api/operation/memory_sync/buffer/buffer_sync_test.ts src/webgpu/api/operation/memory_sync/buffer/rw_and_wr.spec.ts src/webgpu/api/operation/memory_sync/buffer/ww.spec.ts -src/webgpu/api/operation/memory_sync/texture/rw_and_wr.spec.ts -src/webgpu/api/operation/memory_sync/texture/ww.spec.ts +src/webgpu/api/operation/memory_sync/texture/texture_sync_test.ts +src/webgpu/api/operation/memory_sync/texture/same_subresource.spec.ts +src/webgpu/api/operation/pipeline/default_layout.spec.ts src/webgpu/api/operation/queue/writeBuffer.spec.ts +src/webgpu/api/operation/render_pass/clear_value.spec.ts src/webgpu/api/operation/render_pass/resolve.spec.ts src/webgpu/api/operation/render_pass/storeOp.spec.ts src/webgpu/api/operation/render_pass/storeop2.spec.ts @@ -158,6 +160,7 @@ src/webgpu/api/operation/rendering/depth.spec.ts src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts src/webgpu/api/operation/rendering/draw.spec.ts src/webgpu/api/operation/rendering/indirect_draw.spec.ts +src/webgpu/api/operation/rendering/robust_access_index.spec.ts src/webgpu/api/operation/resource_init/buffer.spec.ts src/webgpu/util/texture/subresource.ts src/webgpu/api/operation/resource_init/check_texture/by_copy.ts @@ -170,7 +173,6 @@ src/webgpu/api/operation/sampling/lod_clamp.spec.ts src/webgpu/api/operation/shader_module/compilation_info.spec.ts src/webgpu/api/operation/texture_view/read.spec.ts src/webgpu/api/operation/texture_view/write.spec.ts -src/webgpu/api/operation/vertex_state/basic.spec.ts src/webgpu/api/operation/vertex_state/correctness.spec.ts src/webgpu/api/operation/vertex_state/index_format.spec.ts src/webgpu/api/validation/validation_test.ts @@ -192,7 +194,7 @@ src/webgpu/api/validation/buffer/create.spec.ts src/webgpu/api/validation/buffer/destroy.spec.ts src/webgpu/api/validation/buffer/mapping.spec.ts src/webgpu/api/validation/buffer/threading.spec.ts -src/webgpu/api/validation/capability_checks/features/depth_clamping.spec.ts +src/webgpu/api/validation/capability_checks/features/depth_clip_control.spec.ts src/webgpu/api/validation/capability_checks/features/query_types.spec.ts src/webgpu/api/validation/capability_checks/features/texture_formats.spec.ts src/webgpu/api/validation/encoding/beginRenderPass.spec.ts @@ -255,14 +257,22 @@ src/webgpu/shader/execution/builtin/all.spec.ts src/webgpu/shader/execution/builtin/any.spec.ts src/webgpu/shader/execution/builtin/atan.spec.ts src/webgpu/shader/execution/builtin/ceil.spec.ts +src/webgpu/shader/execution/builtin/clamp.spec.ts src/webgpu/shader/execution/builtin/cos.spec.ts src/webgpu/shader/execution/builtin/float_built_functions.spec.ts src/webgpu/shader/execution/builtin/floor.spec.ts +src/webgpu/shader/execution/builtin/fract.spec.ts src/webgpu/shader/execution/builtin/integer_built_in_functions.spec.ts +src/webgpu/shader/execution/builtin/inversesqrt.spec.ts src/webgpu/shader/execution/builtin/logical_built_in_functions.spec.ts +src/webgpu/shader/execution/builtin/max.spec.ts src/webgpu/shader/execution/builtin/min.spec.ts src/webgpu/shader/execution/builtin/select.spec.ts src/webgpu/shader/execution/builtin/value_testing_built_in_functions.spec.ts +src/webgpu/shader/execution/memory_model/atomicity.spec.ts +src/webgpu/shader/execution/memory_model/barrier.spec.ts +src/webgpu/shader/execution/memory_model/coherence.spec.ts +src/webgpu/shader/execution/memory_model/weak.spec.ts src/webgpu/shader/execution/sampling/gradients_in_varying_loop.spec.ts src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts src/webgpu/shader/execution/shader_io/shared_structs.spec.ts @@ -286,7 +296,6 @@ src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts src/webgpu/web_platform/copyToTexture/canvas.spec.ts src/webgpu/web_platform/copyToTexture/video.spec.ts -src/webgpu/web_platform/external_texture/canvas.spec.ts src/webgpu/web_platform/external_texture/video.spec.ts src/webgpu/web_platform/reftests/gpu_ref_test.ts src/webgpu/web_platform/reftests/canvas_clear.html.ts |