Bug 1864020: test(webgpu): bump CTS to 41f89e77b67e6b66cb017be4e00235a0a9429ca7 r=webgpu-reviewers,nical
Differential Revision: https://phabricator.services.mozilla.com/D193225
This commit is contained in:
@@ -54,7 +54,8 @@
|
|||||||
"@typescript-eslint/no-unnecessary-type-constraint": "warn",
|
"@typescript-eslint/no-unnecessary-type-constraint": "warn",
|
||||||
"@typescript-eslint/no-unused-vars": [
|
"@typescript-eslint/no-unused-vars": [
|
||||||
"warn",
|
"warn",
|
||||||
{ "vars": "all", "args": "none", "varsIgnorePattern": "^_" }
|
// MAINTENANCE_TODO: Enable warnings for args
|
||||||
|
{ "vars": "all", "args": "none", "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }
|
||||||
],
|
],
|
||||||
"@typescript-eslint/prefer-as-const": "warn",
|
"@typescript-eslint/prefer-as-const": "warn",
|
||||||
"@typescript-eslint/prefer-for-of": "warn",
|
"@typescript-eslint/prefer-for-of": "warn",
|
||||||
|
|||||||
@@ -8,21 +8,18 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.3.1
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- run: |
|
- uses: actions/setup-node@v3
|
||||||
git fetch origin ${{ github.event.pull_request.head.sha }}
|
|
||||||
git checkout ${{ github.event.pull_request.head.sha }}
|
|
||||||
- uses: actions/setup-node@v2-beta
|
|
||||||
with:
|
with:
|
||||||
node-version: "16.x"
|
node-version: "16.x"
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm test
|
- run: npm test
|
||||||
- run: |
|
- name: copy out-wpt to wpt tree
|
||||||
mkdir deploy-build/
|
run: |
|
||||||
cp -r README.md src standalone out docs deploy-build/
|
git clone --depth 2 https://github.com/web-platform-tests/wpt.git
|
||||||
- uses: actions/upload-artifact@v2
|
rsync -av out-wpt/ wpt/webgpu
|
||||||
with:
|
- name: test wpt lint
|
||||||
name: pr-artifact
|
run: ./wpt lint
|
||||||
path: deploy-build/
|
working-directory: ./wpt
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
name: Workflow CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows:
|
|
||||||
- "Pull Request CI"
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2.3.1
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- run: |
|
|
||||||
PR=$(curl https://api.github.com/search/issues?q=${{ github.event.workflow_run.head_sha }} |
|
|
||||||
grep -Po "(?<=${{ github.event.workflow_run.repository.full_name }}\/pulls\/)\d*" | head -1)
|
|
||||||
echo "PR=$PR" >> $GITHUB_ENV
|
|
||||||
- uses: actions/github-script@v3
|
|
||||||
id: pr-artifact
|
|
||||||
with:
|
|
||||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
result-encoding: string
|
|
||||||
script: |
|
|
||||||
const artifacts_url = context.payload.workflow_run.artifacts_url
|
|
||||||
const artifacts_req = await github.request(artifacts_url)
|
|
||||||
const artifact = artifacts_req.data.artifacts[0]
|
|
||||||
const download = await github.actions.downloadArtifact({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
artifact_id: artifact.id,
|
|
||||||
archive_format: "zip"
|
|
||||||
})
|
|
||||||
return download.url
|
|
||||||
- run: |
|
|
||||||
rm -rf *
|
|
||||||
curl -L -o "pr-artifact.zip" "${{ steps.pr-artifact.outputs.result }}"
|
|
||||||
unzip -o pr-artifact.zip
|
|
||||||
rm pr-artifact.zip
|
|
||||||
- run: |
|
|
||||||
cat << EOF >> firebase.json
|
|
||||||
{
|
|
||||||
"hosting": {
|
|
||||||
"public": ".",
|
|
||||||
"ignore": [
|
|
||||||
"firebase.json",
|
|
||||||
"**/.*",
|
|
||||||
"**/node_modules/**"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
cat << EOF >> .firebaserc
|
|
||||||
{
|
|
||||||
"projects": {
|
|
||||||
"default": "gpuweb-cts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
- id: deployment
|
|
||||||
continue-on-error: true
|
|
||||||
uses: FirebaseExtended/action-hosting-deploy@v0
|
|
||||||
with:
|
|
||||||
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_CTS }}
|
|
||||||
expires: 10d
|
|
||||||
channelId: cts-prs-${{ env.PR }}-${{ github.event.workflow_run.head_sha }}
|
|
||||||
- uses: peter-evans/create-or-update-comment@v1
|
|
||||||
continue-on-error: true
|
|
||||||
if: ${{ steps.deployment.outcome == 'success' }}
|
|
||||||
with:
|
|
||||||
issue-number: ${{ env.PR }}
|
|
||||||
body: |
|
|
||||||
Previews, as seen when this [build job](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}) started (${{ github.event.workflow_run.head_sha }}):
|
|
||||||
[**Run tests**](${{ steps.deployment.outputs.details_url }}/standalone/) | [**View tsdoc**](${{ steps.deployment.outputs.details_url }}/docs/tsdoc/)
|
|
||||||
<!--
|
|
||||||
pr;head;sha
|
|
||||||
${{ env.PR }};${{ github.event.workflow_run.head_repository.full_name }};${{ github.event.workflow_run.head_sha }}
|
|
||||||
-->
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
name: WPT Lint Test
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-wpt-lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: "16.x"
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm run wpt
|
|
||||||
- name: copy out-wpt to wpt tree
|
|
||||||
run: |
|
|
||||||
git clone --depth 2 https://github.com/web-platform-tests/wpt.git
|
|
||||||
rsync -av out-wpt/ wpt/webgpu
|
|
||||||
- name: test wpt lint
|
|
||||||
run: ./wpt lint
|
|
||||||
working-directory: ./wpt
|
|
||||||
@@ -2,7 +2,11 @@
|
|||||||
/* eslint-disable prettier/prettier */
|
/* eslint-disable prettier/prettier */
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
const timer = require('grunt-timer');
|
||||||
|
|
||||||
module.exports = function (grunt) {
|
module.exports = function (grunt) {
|
||||||
|
timer.init(grunt);
|
||||||
|
|
||||||
// Project configuration.
|
// Project configuration.
|
||||||
grunt.initConfig({
|
grunt.initConfig({
|
||||||
pkg: grunt.file.readJSON('package.json'),
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
@@ -26,6 +30,10 @@ module.exports = function (grunt) {
|
|||||||
cmd: 'node',
|
cmd: 'node',
|
||||||
args: ['tools/validate', 'src/webgpu', 'src/stress', 'src/manual', 'src/unittests', 'src/demo'],
|
args: ['tools/validate', 'src/webgpu', 'src/stress', 'src/manual', 'src/unittests', 'src/demo'],
|
||||||
},
|
},
|
||||||
|
'validate-cache': {
|
||||||
|
cmd: 'node',
|
||||||
|
args: ['tools/gen_cache', 'out', 'src/webgpu', '--validate'],
|
||||||
|
},
|
||||||
'generate-wpt-cts-html': {
|
'generate-wpt-cts-html': {
|
||||||
cmd: 'node',
|
cmd: 'node',
|
||||||
args: ['tools/gen_wpt_cts_html', 'tools/gen_wpt_cfg_unchunked.json'],
|
args: ['tools/gen_wpt_cts_html', 'tools/gen_wpt_cfg_unchunked.json'],
|
||||||
@@ -106,17 +114,14 @@ module.exports = function (grunt) {
|
|||||||
cmd: 'node',
|
cmd: 'node',
|
||||||
args: ['node_modules/eslint/bin/eslint', 'src/**/*.ts', '--max-warnings=0'],
|
args: ['node_modules/eslint/bin/eslint', 'src/**/*.ts', '--max-warnings=0'],
|
||||||
},
|
},
|
||||||
presubmit: {
|
|
||||||
cmd: 'node',
|
|
||||||
args: ['tools/presubmit'],
|
|
||||||
},
|
|
||||||
fix: {
|
fix: {
|
||||||
cmd: 'node',
|
cmd: 'node',
|
||||||
args: ['node_modules/eslint/bin/eslint', 'src/**/*.ts', '--fix'],
|
args: ['node_modules/eslint/bin/eslint', 'src/**/*.ts', '--fix'],
|
||||||
},
|
},
|
||||||
'autoformat-out-wpt': {
|
'autoformat-out-wpt': {
|
||||||
cmd: 'node',
|
cmd: 'node',
|
||||||
args: ['node_modules/prettier/bin-prettier', '--loglevel=warn', '--write', 'out-wpt/**/*.js'],
|
// MAINTENANCE_TODO(gpuweb/cts#3128): This autoformat step is broken after a dependencies upgrade.
|
||||||
|
args: ['node_modules/prettier/bin/prettier.cjs', '--log-level=warn', '--write', 'out-wpt/**/*.js'],
|
||||||
},
|
},
|
||||||
tsdoc: {
|
tsdoc: {
|
||||||
cmd: 'node',
|
cmd: 'node',
|
||||||
@@ -194,14 +199,13 @@ module.exports = function (grunt) {
|
|||||||
registerTaskAndAddToHelp('pre', 'Run all presubmit checks: standalone+wpt+typecheck+unittest+lint', [
|
registerTaskAndAddToHelp('pre', 'Run all presubmit checks: standalone+wpt+typecheck+unittest+lint', [
|
||||||
'clean',
|
'clean',
|
||||||
'run:validate',
|
'run:validate',
|
||||||
|
'run:validate-cache',
|
||||||
'build-standalone',
|
'build-standalone',
|
||||||
'run:generate-listings',
|
'run:generate-listings',
|
||||||
'build-wpt',
|
'build-wpt',
|
||||||
'run:build-out-node',
|
'run:build-out-node',
|
||||||
'run:generate-cache',
|
|
||||||
'build-done-message',
|
'build-done-message',
|
||||||
'ts:check',
|
'ts:check',
|
||||||
'run:presubmit',
|
|
||||||
'run:unittest',
|
'run:unittest',
|
||||||
'run:lint',
|
'run:lint',
|
||||||
'run:tsdoc-treatWarningsAsErrors',
|
'run:tsdoc-treatWarningsAsErrors',
|
||||||
|
|||||||
@@ -1,7 +1,32 @@
|
|||||||
|
# Adding Timing Metadata
|
||||||
|
|
||||||
|
## listing_meta.json files
|
||||||
|
|
||||||
|
`listing_meta.json` files are SEMI AUTO-GENERATED.
|
||||||
|
|
||||||
|
The raw data may be edited manually, to add entries or change timing values.
|
||||||
|
|
||||||
|
The **list** of tests must stay up to date, so it can be used by external
|
||||||
|
tools. This is verified by presubmit checks.
|
||||||
|
|
||||||
|
The `subcaseMS` values are estimates. They can be set to 0 if for some reason
|
||||||
|
you can't estimate the time (or there's an existing test with a long name and
|
||||||
|
slow subcases that would result in query strings that are too long), but this
|
||||||
|
will produce a non-fatal warning. Avoid creating new warnings whenever
|
||||||
|
possible. Any existing failures should be fixed (eventually).
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
Note this data is typically captured by developers using higher-end
|
||||||
|
computers, so typical test machines might execute more slowly. For this
|
||||||
|
reason, the WPT chunking should be configured to generate chunks much shorter
|
||||||
|
than 5 seconds (a typical default time limit in WPT test executors) so they
|
||||||
|
should still execute in under 5 seconds on lower-end computers.
|
||||||
|
|
||||||
## Problem
|
## Problem
|
||||||
|
|
||||||
When adding new tests to the CTS you may occasionally see an error like this
|
When adding new tests to the CTS you may occasionally see an error like this
|
||||||
when running `npm test` or `npm run standalone`
|
when running `npm test` or `npm run standalone`:
|
||||||
|
|
||||||
```
|
```
|
||||||
ERROR: Tests missing from listing_meta.json. Please add the new tests (set subcaseMS to 0 if you cannot estimate it):
|
ERROR: Tests missing from listing_meta.json. Please add the new tests (set subcaseMS to 0 if you cannot estimate it):
|
||||||
@@ -10,7 +35,7 @@ ERROR: Tests missing from listing_meta.json. Please add the new tests (set subca
|
|||||||
/home/runner/work/cts/cts/src/common/util/util.ts:38
|
/home/runner/work/cts/cts/src/common/util/util.ts:38
|
||||||
throw new Error(msg && (typeof msg === 'string' ? msg : msg()));
|
throw new Error(msg && (typeof msg === 'string' ? msg : msg()));
|
||||||
^
|
^
|
||||||
Error:
|
Error:
|
||||||
at assert (/home/runner/work/cts/cts/src/common/util/util.ts:38:11)
|
at assert (/home/runner/work/cts/cts/src/common/util/util.ts:38:11)
|
||||||
at crawl (/home/runner/work/cts/cts/src/common/tools/crawl.ts:155:11)
|
at crawl (/home/runner/work/cts/cts/src/common/tools/crawl.ts:155:11)
|
||||||
Warning: non-zero exit code 1
|
Warning: non-zero exit code 1
|
||||||
@@ -25,9 +50,10 @@ What this error message is trying to tell us, is that there is no entry for
|
|||||||
|
|
||||||
These entries are estimates for the amount of time that subcases take to run,
|
These entries are estimates for the amount of time that subcases take to run,
|
||||||
and are used as inputs into the WPT tooling to attempt to portion out tests into
|
and are used as inputs into the WPT tooling to attempt to portion out tests into
|
||||||
approximately same sized chunks.
|
approximately same-sized chunks.
|
||||||
|
|
||||||
|
If a value has been defaulted to 0 by someone, you will see warnings like this:
|
||||||
|
|
||||||
If a value has been defaulted to 0 by someone, you will see warnings like this
|
|
||||||
```
|
```
|
||||||
...
|
...
|
||||||
WARNING: subcaseMS≤0 found in listing_meta.json (allowed, but try to avoid):
|
WARNING: subcaseMS≤0 found in listing_meta.json (allowed, but try to avoid):
|
||||||
@@ -38,71 +64,98 @@ WARNING: subcaseMS≤0 found in listing_meta.json (allowed, but try to avoid):
|
|||||||
These messages should be resolved by adding appropriate entries to the JSON
|
These messages should be resolved by adding appropriate entries to the JSON
|
||||||
file.
|
file.
|
||||||
|
|
||||||
## Solution
|
## Solution 1 (manual, best for simple tests)
|
||||||
|
|
||||||
|
If you're developing new tests and need to update this file, it is sometimes
|
||||||
|
easiest to do so manually. Run your tests under your usual development workflow
|
||||||
|
and see how long they take. In the standalone web runner `npm start`, the total
|
||||||
|
time for a test case is reported on the right-hand side when the case logs are
|
||||||
|
expanded.
|
||||||
|
|
||||||
|
Record the average time per *subcase* across all cases of the test (you may need
|
||||||
|
to compute this) into the `listing_meta.json` file.
|
||||||
|
|
||||||
|
## Solution 2 (semi-automated)
|
||||||
|
|
||||||
There exists tooling in the CTS repo for generating appropriate estimates for
|
There exists tooling in the CTS repo for generating appropriate estimates for
|
||||||
these values, though they do require some manual intervention. The rest of this
|
these values, though they do require some manual intervention. The rest of this
|
||||||
doc will be a walkthrough of running these tools.
|
doc will be a walkthrough of running these tools.
|
||||||
|
|
||||||
### Default Value
|
Timing data can be captured in bulk and "merged" into this file using
|
||||||
|
the `merge_listing_times` tool. This is useful when a large number of tests
|
||||||
|
change or otherwise a lot of tests need to be updated, but it also automates the
|
||||||
|
manual steps above.
|
||||||
|
|
||||||
The first step is to add a default value for entry to
|
The tool can also be used without any inputs to reformat `listing_meta.json`.
|
||||||
`src/webgpu/listing_meta.json`, since there is a chicken-and-egg problem for
|
Please read the help message of `merge_listing_times` for more information.
|
||||||
updating these values.
|
|
||||||
|
### Placeholder Value
|
||||||
|
|
||||||
|
If your development workflow requires a clean build, the first step is to add a
|
||||||
|
placeholder value for entry to `src/webgpu/listing_meta.json`, since there is a
|
||||||
|
chicken-and-egg problem for updating these values.
|
||||||
|
|
||||||
```
|
```
|
||||||
"webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*": { "subcaseMS": 0 },
|
"webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*": { "subcaseMS": 0 },
|
||||||
```
|
```
|
||||||
|
|
||||||
(It should have a value of 0, since later tooling updates the value if the newer
|
(It should have a value of 0, since later tooling updates the value if the newer
|
||||||
value is higher)
|
value is higher.)
|
||||||
|
|
||||||
### Websocket Logger
|
### Websocket Logger
|
||||||
|
|
||||||
The first tool that needs to be run is `websocket-logger`, which uses a side
|
The first tool that needs to be run is `websocket-logger`, which receives data
|
||||||
channel from WPT to report timing data when CTS is run via a websocket. This
|
on a WebSocket channel to capture timing data when CTS is run. This
|
||||||
should be run in a separate process/terminal, since it needs to stay running
|
should be run in a separate process/terminal, since it needs to stay running
|
||||||
throughout the following steps.
|
throughout the following steps.
|
||||||
|
|
||||||
At `tools/websocket-logger/`
|
In the `tools/websocket-logger/` directory:
|
||||||
|
|
||||||
```
|
```
|
||||||
npm ci
|
npm ci
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
The output from this command will indicate where the results are being logged,
|
The output from this command will indicate where the results are being logged,
|
||||||
which will be needed later
|
which will be needed later. For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
...
|
...
|
||||||
Writing to wslog-2023-09-11T18-57-34.txt
|
Writing to wslog-2023-09-12T18-57-34.txt
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running CTS
|
### Running CTS
|
||||||
|
|
||||||
Now we need to run the specific cases in CTS, which requires serving the CTS
|
Now we need to run the specific cases in CTS that we need to time.
|
||||||
locally.
|
This should be possible under any development workflow (as long as its runtime environment, like Node, supports WebSockets), but the most well-tested way is using the standalone web runner.
|
||||||
|
|
||||||
|
This requires serving the CTS locally. In the project root:
|
||||||
|
|
||||||
At project root
|
|
||||||
```
|
```
|
||||||
npm run standalone
|
npm run standalone
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
Once this is started you can then direct a WebGPU enabled browser to the
|
Once this is started you can then direct a WebGPU enabled browser to the
|
||||||
specific CTS entry and run the tests, for example
|
specific CTS entry and run the tests, for example:
|
||||||
|
|
||||||
```
|
```
|
||||||
http://127.0.0.1:8080/standalone/q?webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*
|
http://localhost:8080/standalone/?q=webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If the tests have a high variance in runtime, you can run them multiple times.
|
||||||
|
The longest recorded time will be used.
|
||||||
|
|
||||||
### Merging metadata
|
### Merging metadata
|
||||||
|
|
||||||
The final step is to merge the new data that has been captured into the JSON
|
The final step is to merge the new data that has been captured into the JSON
|
||||||
file.
|
file.
|
||||||
|
|
||||||
This can be done using the following command
|
This can be done using the following command:
|
||||||
|
|
||||||
```
|
```
|
||||||
tools/merge_listing_times webgpu -- tools/websocket-logger/wslog-2023-09-11T18-57-34.txt
|
tools/merge_listing_times webgpu -- tools/websocket-logger/wslog-2023-09-12T18-57-34.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
where the text file is the result file from websocket-logger.
|
where the text file is the result file from websocket-logger.
|
||||||
|
|||||||
@@ -690,6 +690,42 @@ library, or if this turns out to be a significant issue in the future, this
|
|||||||
decision can be revisited.
|
decision can be revisited.
|
||||||
|
|
||||||
## Abstract Float
|
## Abstract Float
|
||||||
|
|
||||||
|
### Accuracy
|
||||||
|
|
||||||
|
For the concrete floating point types (f32 & f16) the accuracy of operations are
|
||||||
|
defined in terms of their own type. Specifically for f32, correctly rounded
|
||||||
|
refers to the nearest f32 values, and ULP is in terms of the distance between
|
||||||
|
f32 values.
|
||||||
|
|
||||||
|
AbstractFloat internally is defined as a f64, and this applies for exact and
|
||||||
|
correctly rounded accuracies. Thus, correctly rounded refers to the nearest f64
|
||||||
|
values. However, AbstractFloat differs for ULP and absolute errors. Reading
|
||||||
|
the spec strictly, these all have unbounded accuracies, but it is recommended
|
||||||
|
that their accuracies be at least as good as the f32 equivalent.
|
||||||
|
|
||||||
|
The difference between f32 and f64 ULP at a specific value X are significant, so
|
||||||
|
at least as good as f32 requirement is always less strict than if it was
|
||||||
|
calculated in terms of f64. Similarly, for absolute accuracies the interval
|
||||||
|
`[x - epsilon, x + epsilon]` is always equal or wider if calculated as f32s
|
||||||
|
vs f64s.
|
||||||
|
|
||||||
|
If an inherited accuracy is only defined in terms of correctly rounded
|
||||||
|
accuracies, then the interval is calculated in terms of f64s. If any of the
|
||||||
|
defining accuracies are ULP or absolute errors, then the result falls into the
|
||||||
|
unbounded accuracy, but recommended to be at least as good as f32 bucket.
|
||||||
|
|
||||||
|
What this means from a CTS implementation is that for these "at least as good as
|
||||||
|
f32" error intervals, if the infinitely accurate result is finite for f32, then
|
||||||
|
the error interval for f64 is just the f32 interval. If the result is not finite
|
||||||
|
for f32, then the accuracy interval is just the unbounded interval.
|
||||||
|
|
||||||
|
How this is implemented in the CTS is by having the FPTraits for AbstractFloat
|
||||||
|
forward to the f32 implementation for the operations that are tested to be as
|
||||||
|
good as f32.
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
|
||||||
AbstractFloats are a compile time construct that exist in WGSL. They are
|
AbstractFloats are a compile time construct that exist in WGSL. They are
|
||||||
expressible as literal values or the result of operations that return them, but
|
expressible as literal values or the result of operations that return them, but
|
||||||
a variable cannot be typed as an AbstractFloat. Instead, the variable needs be a
|
a variable cannot be typed as an AbstractFloat. Instead, the variable needs be a
|
||||||
@@ -703,15 +739,18 @@ operations that return AbstractFloats.
|
|||||||
As of the writing of this doc, this second option for testing AbstractFloats
|
As of the writing of this doc, this second option for testing AbstractFloats
|
||||||
is the one being pursued in the CTS.
|
is the one being pursued in the CTS.
|
||||||
|
|
||||||
### const_assert
|
#### const_assert
|
||||||
|
|
||||||
The first proposal is to lean on the `const_assert` statement that exists in
|
The first proposal is to lean on the `const_assert` statement that exists in
|
||||||
WGSL. For each test case a snippet of code would be written out that has a form
|
WGSL. For each test case a snippet of code would be written out that has a form
|
||||||
something like this
|
something like this
|
||||||
|
|
||||||
```
|
```
|
||||||
// foo(x) is the operation under test
|
// foo(x) is the operation under test
|
||||||
const_assert lower < foo(x) // Result was below the acceptance interval
|
const_assert lower < foo(x) // Result was below the acceptance interval
|
||||||
const_assert upper > foo(x) // Result was above the acceptance interval
|
const_assert upper > foo(x) // Result was above the acceptance interval
|
||||||
```
|
```
|
||||||
|
|
||||||
where lower and upper would actually be string replaced with literals for the
|
where lower and upper would actually be string replaced with literals for the
|
||||||
bounds of the acceptance interval when generating the shader text.
|
bounds of the acceptance interval when generating the shader text.
|
||||||
|
|
||||||
@@ -733,7 +772,8 @@ indicate something is working, we would be depending on a signal that it isn't
|
|||||||
working, and assuming if we don't receive that signal everything is good, not
|
working, and assuming if we don't receive that signal everything is good, not
|
||||||
that our signal mechanism was broken.
|
that our signal mechanism was broken.
|
||||||
|
|
||||||
### Extracting Bits
|
#### Extracting Bits
|
||||||
|
|
||||||
The other proposal that was developed depends on the fact that AbstractFloat is
|
The other proposal that was developed depends on the fact that AbstractFloat is
|
||||||
spec'd to be a f64 internally. So the CTS could store the result of an operation
|
spec'd to be a f64 internally. So the CTS could store the result of an operation
|
||||||
as two 32-bit unsigned integers (or broken up into sign, exponent, and
|
as two 32-bit unsigned integers (or broken up into sign, exponent, and
|
||||||
@@ -827,6 +867,5 @@ shader being run.
|
|||||||
- [binary16 on Wikipedia](https://en.wikipedia.org/wiki/Half-precision_floating-point_format)
|
- [binary16 on Wikipedia](https://en.wikipedia.org/wiki/Half-precision_floating-point_format)
|
||||||
- [IEEE-754 Floating Point Converter](https://www.h-schmidt.net/FloatConverter/IEEE754.html)
|
- [IEEE-754 Floating Point Converter](https://www.h-schmidt.net/FloatConverter/IEEE754.html)
|
||||||
- [IEEE 754 Calculator](http://weitz.de/ieee/)
|
- [IEEE 754 Calculator](http://weitz.de/ieee/)
|
||||||
- [Keisan High Precision Calculator](https://keisan.casio.com/calculator)
|
|
||||||
- [On the definition of ulp(x)](https://hal.inria.fr/inria-00070503/document)
|
- [On the definition of ulp(x)](https://hal.inria.fr/inria-00070503/document)
|
||||||
- [Float Exposed](https://float.exposed/)
|
- [Float Exposed](https://float.exposed/)
|
||||||
|
|||||||
6650
dom/webgpu/tests/cts/checkout/package-lock.json
generated
6650
dom/webgpu/tests/cts/checkout/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -31,48 +31,50 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/gpuweb/cts#readme",
|
"homepage": "https://github.com/gpuweb/cts#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.19.3",
|
"@babel/cli": "^7.23.0",
|
||||||
"@babel/core": "^7.20.5",
|
"@babel/core": "^7.23.2",
|
||||||
"@babel/preset-typescript": "^7.18.6",
|
"@babel/preset-typescript": "^7.23.2",
|
||||||
"@types/babel__core": "^7.1.20",
|
"@types/babel__core": "^7.20.3",
|
||||||
"@types/dom-mediacapture-transform": "^0.1.4",
|
"@types/dom-mediacapture-transform": "^0.1.8",
|
||||||
"@types/dom-webcodecs": "^0.1.5",
|
"@types/dom-webcodecs": "^0.1.9",
|
||||||
"@types/express": "^4.17.14",
|
"@types/express": "^4.17.20",
|
||||||
"@types/jquery": "^3.5.14",
|
"@types/jquery": "^3.5.25",
|
||||||
"@types/morgan": "^1.9.3",
|
"@types/morgan": "^1.9.7",
|
||||||
"@types/node": "^14.18.12",
|
"@types/node": "^20.8.10",
|
||||||
"@types/offscreencanvas": "^2019.7.0",
|
"@types/offscreencanvas": "^2019.7.2",
|
||||||
"@types/pngjs": "^6.0.1",
|
"@types/pngjs": "^6.0.3",
|
||||||
"@types/serve-index": "^1.9.1",
|
"@types/serve-index": "^1.9.3",
|
||||||
"@typescript-eslint/parser": "^4.33.0",
|
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
||||||
"@webgpu/types": "gpuweb/types#ca1a548178567e6021fd194380b97be1bf6b07b7",
|
"@typescript-eslint/parser": "^6.9.1",
|
||||||
"ansi-colors": "4.1.1",
|
"@webgpu/types": "^0.1.38",
|
||||||
|
"ansi-colors": "4.1.3",
|
||||||
"babel-plugin-add-header-comment": "^1.0.3",
|
"babel-plugin-add-header-comment": "^1.0.3",
|
||||||
"babel-plugin-const-enum": "^1.2.0",
|
"babel-plugin-const-enum": "^1.2.0",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"eslint": "^7.11.0",
|
"eslint": "^8.52.0",
|
||||||
"eslint-plugin-ban": "^1.6.0",
|
"eslint-plugin-ban": "^1.6.0",
|
||||||
"eslint-plugin-deprecation": "^1.3.3",
|
"eslint-plugin-deprecation": "^2.0.0",
|
||||||
"eslint-plugin-gpuweb-cts": "file:./tools/eslint-plugin-gpuweb-cts",
|
"eslint-plugin-gpuweb-cts": "file:./tools/eslint-plugin-gpuweb-cts",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.29.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"grunt": "^1.5.3",
|
"grunt": "^1.6.1",
|
||||||
"grunt-cli": "^1.4.3",
|
"grunt-cli": "^1.4.3",
|
||||||
"grunt-contrib-clean": "^2.0.1",
|
"grunt-contrib-clean": "^2.0.1",
|
||||||
"grunt-contrib-copy": "^1.0.0",
|
"grunt-contrib-copy": "^1.0.0",
|
||||||
"grunt-run": "^0.8.1",
|
"grunt-run": "^0.8.1",
|
||||||
|
"grunt-timer": "^0.6.0",
|
||||||
"grunt-ts": "^6.0.0-beta.22",
|
"grunt-ts": "^6.0.0-beta.22",
|
||||||
"gts": "^3.1.1",
|
"gts": "^5.2.0",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"playwright-core": "^1.29.2",
|
"playwright-core": "^1.39.0",
|
||||||
"pngjs": "^6.0.0",
|
"pngjs": "^7.0.0",
|
||||||
"portfinder": "^1.0.32",
|
"portfinder": "^1.0.32",
|
||||||
"prettier": "~2.1.2",
|
"prettier": "~3.0.3",
|
||||||
"screenshot-ftw": "^1.0.5",
|
"screenshot-ftw": "^1.0.5",
|
||||||
"serve-index": "^1.9.1",
|
"serve-index": "^1.9.1",
|
||||||
"ts-node": "^9.0.0",
|
"ts-node": "^10.9.1",
|
||||||
"typedoc": "^0.23.21",
|
"typedoc": "^0.25.3",
|
||||||
"typescript": "~4.7.4"
|
"typescript": "~5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,64 @@
|
|||||||
* expensive to build using a two-level cache (in-memory, pre-computed file).
|
* expensive to build using a two-level cache (in-memory, pre-computed file).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { assert } from '../util/util.js';
|
||||||
|
|
||||||
interface DataStore {
|
interface DataStore {
|
||||||
load(path: string): Promise<string>;
|
load(path: string): Promise<Uint8Array>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Logger is a basic debug logger function */
|
/** Logger is a basic debug logger function */
|
||||||
export type Logger = (s: string) => void;
|
export type Logger = (s: string) => void;
|
||||||
|
|
||||||
/** DataCache is an interface to a data store used to hold cached data */
|
/**
|
||||||
|
* DataCacheNode represents a single cache entry in the LRU DataCache.
|
||||||
|
* DataCacheNode is a doubly linked list, so that least-recently-used entries can be removed, and
|
||||||
|
* cache hits can move the node to the front of the list.
|
||||||
|
*/
|
||||||
|
class DataCacheNode {
|
||||||
|
public constructor(path: string, data: unknown) {
|
||||||
|
this.path = path;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** insertAfter() re-inserts this node in the doubly-linked list after `prev` */
|
||||||
|
public insertAfter(prev: DataCacheNode) {
|
||||||
|
this.unlink();
|
||||||
|
this.next = prev.next;
|
||||||
|
this.prev = prev;
|
||||||
|
prev.next = this;
|
||||||
|
if (this.next) {
|
||||||
|
this.next.prev = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** unlink() removes this node from the doubly-linked list */
|
||||||
|
public unlink() {
|
||||||
|
const prev = this.prev;
|
||||||
|
const next = this.next;
|
||||||
|
if (prev) {
|
||||||
|
prev.next = next;
|
||||||
|
}
|
||||||
|
if (next) {
|
||||||
|
next.prev = prev;
|
||||||
|
}
|
||||||
|
this.prev = null;
|
||||||
|
this.next = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly path: string; // The file path this node represents
|
||||||
|
public readonly data: unknown; // The deserialized data for this node
|
||||||
|
public prev: DataCacheNode | null = null; // The previous node in the doubly-linked list
|
||||||
|
public next: DataCacheNode | null = null; // The next node in the doubly-linked list
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DataCache is an interface to a LRU-cached data store used to hold data cached by path */
|
||||||
export class DataCache {
|
export class DataCache {
|
||||||
|
public constructor() {
|
||||||
|
this.lruHeadNode.next = this.lruTailNode;
|
||||||
|
this.lruTailNode.prev = this.lruHeadNode;
|
||||||
|
}
|
||||||
|
|
||||||
/** setDataStore() sets the backing data store used by the data cache */
|
/** setDataStore() sets the backing data store used by the data cache */
|
||||||
public setStore(dataStore: DataStore) {
|
public setStore(dataStore: DataStore) {
|
||||||
this.dataStore = dataStore;
|
this.dataStore = dataStore;
|
||||||
@@ -28,17 +77,20 @@ export class DataCache {
|
|||||||
* building the data and storing it in the cache.
|
* building the data and storing it in the cache.
|
||||||
*/
|
*/
|
||||||
public async fetch<Data>(cacheable: Cacheable<Data>): Promise<Data> {
|
public async fetch<Data>(cacheable: Cacheable<Data>): Promise<Data> {
|
||||||
// First check the in-memory cache
|
{
|
||||||
let data = this.cache.get(cacheable.path);
|
// First check the in-memory cache
|
||||||
if (data !== undefined) {
|
const node = this.cache.get(cacheable.path);
|
||||||
this.log('in-memory cache hit');
|
if (node !== undefined) {
|
||||||
return Promise.resolve(data as Data);
|
this.log('in-memory cache hit');
|
||||||
|
node.insertAfter(this.lruHeadNode);
|
||||||
|
return Promise.resolve(node.data as Data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.log('in-memory cache miss');
|
this.log('in-memory cache miss');
|
||||||
// In in-memory cache miss.
|
// In in-memory cache miss.
|
||||||
// Next, try the data store.
|
// Next, try the data store.
|
||||||
if (this.dataStore !== null && !this.unavailableFiles.has(cacheable.path)) {
|
if (this.dataStore !== null && !this.unavailableFiles.has(cacheable.path)) {
|
||||||
let serialized: string | undefined;
|
let serialized: Uint8Array | undefined;
|
||||||
try {
|
try {
|
||||||
serialized = await this.dataStore.load(cacheable.path);
|
serialized = await this.dataStore.load(cacheable.path);
|
||||||
this.log('loaded serialized');
|
this.log('loaded serialized');
|
||||||
@@ -49,16 +101,37 @@ export class DataCache {
|
|||||||
}
|
}
|
||||||
if (serialized !== undefined) {
|
if (serialized !== undefined) {
|
||||||
this.log(`deserializing`);
|
this.log(`deserializing`);
|
||||||
data = cacheable.deserialize(serialized);
|
const data = cacheable.deserialize(serialized);
|
||||||
this.cache.set(cacheable.path, data);
|
this.addToCache(cacheable.path, data);
|
||||||
return data as Data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Not found anywhere. Build the data, and cache for future lookup.
|
// Not found anywhere. Build the data, and cache for future lookup.
|
||||||
this.log(`cache: building (${cacheable.path})`);
|
this.log(`cache: building (${cacheable.path})`);
|
||||||
data = await cacheable.build();
|
const data = await cacheable.build();
|
||||||
this.cache.set(cacheable.path, data);
|
this.addToCache(cacheable.path, data);
|
||||||
return data as Data;
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* addToCache() creates a new node for `path` and `data`, inserting the new node at the front of
|
||||||
|
* the doubly-linked list. If the number of entries in the cache exceeds this.maxCount, then the
|
||||||
|
* least recently used entry is evicted
|
||||||
|
* @param path the file path for the data
|
||||||
|
* @param data the deserialized data
|
||||||
|
*/
|
||||||
|
private addToCache(path: string, data: unknown) {
|
||||||
|
if (this.cache.size >= this.maxCount) {
|
||||||
|
const toEvict = this.lruTailNode.prev;
|
||||||
|
assert(toEvict !== null);
|
||||||
|
toEvict.unlink();
|
||||||
|
this.cache.delete(toEvict.path);
|
||||||
|
this.log(`evicting ${toEvict.path}`);
|
||||||
|
}
|
||||||
|
const node = new DataCacheNode(path, data);
|
||||||
|
node.insertAfter(this.lruHeadNode);
|
||||||
|
this.cache.set(path, node);
|
||||||
|
this.log(`added ${path}. new count: ${this.cache.size}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private log(msg: string) {
|
private log(msg: string) {
|
||||||
@@ -67,7 +140,12 @@ export class DataCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private cache = new Map<string, unknown>();
|
// Max number of entries in the cache before LRU entries are evicted.
|
||||||
|
private readonly maxCount = 4;
|
||||||
|
|
||||||
|
private cache = new Map<string, DataCacheNode>();
|
||||||
|
private lruHeadNode = new DataCacheNode('', null); // placeholder node (no path or data)
|
||||||
|
private lruTailNode = new DataCacheNode('', null); // placeholder node (no path or data)
|
||||||
private unavailableFiles = new Set<string>();
|
private unavailableFiles = new Set<string>();
|
||||||
private dataStore: DataStore | null = null;
|
private dataStore: DataStore | null = null;
|
||||||
private debugLogger: Logger | null = null;
|
private debugLogger: Logger | null = null;
|
||||||
@@ -107,14 +185,13 @@ export interface Cacheable<Data> {
|
|||||||
build(): Promise<Data>;
|
build(): Promise<Data>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* serialize() transforms `data` to a string (usually JSON encoded) so that it
|
* serialize() encodes `data` to a binary representation so that it can be stored in a cache file.
|
||||||
* can be stored in a text cache file.
|
|
||||||
*/
|
*/
|
||||||
serialize(data: Data): string;
|
serialize(data: Data): Uint8Array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* deserialize() is the inverse of serialize(), transforming the string back
|
* deserialize() is the inverse of serialize(), decoding the binary representation back to a Data
|
||||||
* to the Data object.
|
* object.
|
||||||
*/
|
*/
|
||||||
deserialize(serialized: string): Data;
|
deserialize(binary: Uint8Array): Data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TestCaseRecorder } from '../internal/logging/test_case_recorder.js';
|
import { TestCaseRecorder } from '../internal/logging/test_case_recorder.js';
|
||||||
import { JSONWithUndefined } from '../internal/params_utils.js';
|
import { JSONWithUndefined } from '../internal/params_utils.js';
|
||||||
import { assert, unreachable } from '../util/util.js';
|
import { assert, ExceptionCheckOptions, unreachable } from '../util/util.js';
|
||||||
|
|
||||||
export class SkipTestCase extends Error {}
|
export class SkipTestCase extends Error {}
|
||||||
export class UnexpectedPassError extends Error {}
|
export class UnexpectedPassError extends Error {}
|
||||||
@@ -150,7 +150,7 @@ export class Fixture<S extends SubcaseBatchState = SubcaseBatchState> {
|
|||||||
o instanceof WebGLRenderingContext ||
|
o instanceof WebGLRenderingContext ||
|
||||||
o instanceof WebGL2RenderingContext
|
o instanceof WebGL2RenderingContext
|
||||||
) {
|
) {
|
||||||
this.objectsToCleanUp.push((o as unknown) as DestroyableObject);
|
this.objectsToCleanUp.push(o as unknown as DestroyableObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return o;
|
return o;
|
||||||
@@ -166,6 +166,13 @@ export class Fixture<S extends SubcaseBatchState = SubcaseBatchState> {
|
|||||||
throw new SkipTestCase(msg);
|
throw new SkipTestCase(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Throws an exception marking the subcase as skipped if condition is true */
|
||||||
|
skipIf(cond: boolean, msg: string | (() => string) = '') {
|
||||||
|
if (cond) {
|
||||||
|
this.skip(typeof msg === 'function' ? msg() : msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Log a warning and increase the result status to "Warn". */
|
/** Log a warning and increase the result status to "Warn". */
|
||||||
warn(msg?: string): void {
|
warn(msg?: string): void {
|
||||||
this.rec.warn(new Error(msg));
|
this.rec.warn(new Error(msg));
|
||||||
@@ -230,16 +237,26 @@ export class Fixture<S extends SubcaseBatchState = SubcaseBatchState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Expect that the provided promise rejects, with the provided exception name. */
|
/** Expect that the provided promise rejects, with the provided exception name. */
|
||||||
shouldReject(expectedName: string, p: Promise<unknown>, msg?: string): void {
|
shouldReject(
|
||||||
|
expectedName: string,
|
||||||
|
p: Promise<unknown>,
|
||||||
|
{ allowMissingStack = false, message }: ExceptionCheckOptions = {}
|
||||||
|
): void {
|
||||||
this.eventualAsyncExpectation(async niceStack => {
|
this.eventualAsyncExpectation(async niceStack => {
|
||||||
const m = msg ? ': ' + msg : '';
|
const m = message ? ': ' + message : '';
|
||||||
try {
|
try {
|
||||||
await p;
|
await p;
|
||||||
niceStack.message = 'DID NOT REJECT' + m;
|
niceStack.message = 'DID NOT REJECT' + m;
|
||||||
this.rec.expectationFailed(niceStack);
|
this.rec.expectationFailed(niceStack);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
niceStack.message = 'rejected as expected' + m;
|
|
||||||
this.expectErrorValue(expectedName, ex, niceStack);
|
this.expectErrorValue(expectedName, ex, niceStack);
|
||||||
|
if (!allowMissingStack) {
|
||||||
|
if (!(ex instanceof Error && typeof ex.stack === 'string')) {
|
||||||
|
const exMessage = ex instanceof Error ? ex.message : '?';
|
||||||
|
niceStack.message = `rejected as expected, but missing stack (${exMessage})${m}`;
|
||||||
|
this.rec.expectationFailed(niceStack);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -250,8 +267,12 @@ export class Fixture<S extends SubcaseBatchState = SubcaseBatchState> {
|
|||||||
*
|
*
|
||||||
* MAINTENANCE_TODO: Change to `string | false` so the exception name is always checked.
|
* MAINTENANCE_TODO: Change to `string | false` so the exception name is always checked.
|
||||||
*/
|
*/
|
||||||
shouldThrow(expectedError: string | boolean, fn: () => void, msg?: string): void {
|
shouldThrow(
|
||||||
const m = msg ? ': ' + msg : '';
|
expectedError: string | boolean,
|
||||||
|
fn: () => void,
|
||||||
|
{ allowMissingStack = false, message }: ExceptionCheckOptions = {}
|
||||||
|
) {
|
||||||
|
const m = message ? ': ' + message : '';
|
||||||
try {
|
try {
|
||||||
fn();
|
fn();
|
||||||
if (expectedError === false) {
|
if (expectedError === false) {
|
||||||
@@ -264,6 +285,11 @@ export class Fixture<S extends SubcaseBatchState = SubcaseBatchState> {
|
|||||||
this.rec.expectationFailed(new Error('threw unexpectedly' + m));
|
this.rec.expectationFailed(new Error('threw unexpectedly' + m));
|
||||||
} else {
|
} else {
|
||||||
this.expectErrorValue(expectedError, ex, new Error(m));
|
this.expectErrorValue(expectedError, ex, new Error(m));
|
||||||
|
if (!allowMissingStack) {
|
||||||
|
if (!(ex instanceof Error && typeof ex.stack === 'string')) {
|
||||||
|
this.rec.expectationFailed(new Error('threw as expected, but missing stack' + m));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Merged, mergeParams, mergeParamsChecked } from '../internal/params_utils.js';
|
import { Merged, mergeParams, mergeParamsChecked } from '../internal/params_utils.js';
|
||||||
import { comparePublicParamsPaths, Ordering } from '../internal/query/compare.js';
|
import { comparePublicParamsPaths, Ordering } from '../internal/query/compare.js';
|
||||||
import { stringifyPublicParams } from '../internal/query/stringify_params.js';
|
import { stringifyPublicParams } from '../internal/query/stringify_params.js';
|
||||||
|
import { DeepReadonly } from '../util/types.js';
|
||||||
import { assert, mapLazy, objectEquals } from '../util/util.js';
|
import { assert, mapLazy, objectEquals } from '../util/util.js';
|
||||||
|
|
||||||
import { TestParams } from './fixture.js';
|
import { TestParams } from './fixture.js';
|
||||||
@@ -81,7 +82,7 @@ export interface ParamsBuilder {
|
|||||||
*/
|
*/
|
||||||
export type ParamTypeOf<
|
export type ParamTypeOf<
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
T extends ParamsBuilder
|
T extends ParamsBuilder,
|
||||||
> = T extends SubcaseParamsBuilder<infer CaseP, infer SubcaseP>
|
> = T extends SubcaseParamsBuilder<infer CaseP, infer SubcaseP>
|
||||||
? Merged<CaseP, SubcaseP>
|
? Merged<CaseP, SubcaseP>
|
||||||
: T extends CaseParamsBuilder<infer CaseP>
|
: T extends CaseParamsBuilder<infer CaseP>
|
||||||
@@ -98,7 +99,7 @@ export type ParamTypeOf<
|
|||||||
* - `[case params, undefined]` if not.
|
* - `[case params, undefined]` if not.
|
||||||
*/
|
*/
|
||||||
export type CaseSubcaseIterable<CaseP, SubcaseP> = Iterable<
|
export type CaseSubcaseIterable<CaseP, SubcaseP> = Iterable<
|
||||||
readonly [CaseP, Iterable<SubcaseP> | undefined]
|
readonly [DeepReadonly<CaseP>, Iterable<DeepReadonly<SubcaseP>> | undefined]
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,7 +131,7 @@ export function builderIterateCasesWithSubcases(
|
|||||||
iterateCasesWithSubcases(caseFilter: TestParams | null): CaseSubcaseIterable<{}, {}>;
|
iterateCasesWithSubcases(caseFilter: TestParams | null): CaseSubcaseIterable<{}, {}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ((builder as unknown) as IterableParamsBuilder).iterateCasesWithSubcases(caseFilter);
|
return (builder as unknown as IterableParamsBuilder).iterateCasesWithSubcases(caseFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,7 +144,8 @@ export function builderIterateCasesWithSubcases(
|
|||||||
*/
|
*/
|
||||||
export class CaseParamsBuilder<CaseP extends {}>
|
export class CaseParamsBuilder<CaseP extends {}>
|
||||||
extends ParamsBuilderBase<CaseP, {}>
|
extends ParamsBuilderBase<CaseP, {}>
|
||||||
implements Iterable<CaseP>, ParamsBuilder {
|
implements Iterable<DeepReadonly<CaseP>>, ParamsBuilder
|
||||||
|
{
|
||||||
*iterateCasesWithSubcases(caseFilter: TestParams | null): CaseSubcaseIterable<CaseP, {}> {
|
*iterateCasesWithSubcases(caseFilter: TestParams | null): CaseSubcaseIterable<CaseP, {}> {
|
||||||
for (const caseP of this.cases(caseFilter)) {
|
for (const caseP of this.cases(caseFilter)) {
|
||||||
if (caseFilter) {
|
if (caseFilter) {
|
||||||
@@ -155,12 +157,12 @@ export class CaseParamsBuilder<CaseP extends {}>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yield [caseP, undefined];
|
yield [caseP as DeepReadonly<typeof caseP>, undefined];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Symbol.iterator](): Iterator<CaseP> {
|
[Symbol.iterator](): Iterator<DeepReadonly<CaseP>> {
|
||||||
return this.cases(null);
|
return this.cases(null) as Iterator<DeepReadonly<CaseP>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
@@ -229,7 +231,7 @@ export class CaseParamsBuilder<CaseP extends {}>
|
|||||||
values: Iterable<NewPValue>
|
values: Iterable<NewPValue>
|
||||||
): CaseParamsBuilder<Merged<CaseP, { [name in NewPKey]: NewPValue }>> {
|
): CaseParamsBuilder<Merged<CaseP, { [name in NewPKey]: NewPValue }>> {
|
||||||
assertNotGenerator(values);
|
assertNotGenerator(values);
|
||||||
const mapped = mapLazy(values, v => ({ [key]: v } as { [name in NewPKey]: NewPValue }));
|
const mapped = mapLazy(values, v => ({ [key]: v }) as { [name in NewPKey]: NewPValue });
|
||||||
return this.combineWithParams(mapped);
|
return this.combineWithParams(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +280,8 @@ export const kUnitCaseParamsBuilder = new CaseParamsBuilder(function* () {
|
|||||||
*/
|
*/
|
||||||
export class SubcaseParamsBuilder<CaseP extends {}, SubcaseP extends {}>
|
export class SubcaseParamsBuilder<CaseP extends {}, SubcaseP extends {}>
|
||||||
extends ParamsBuilderBase<CaseP, SubcaseP>
|
extends ParamsBuilderBase<CaseP, SubcaseP>
|
||||||
implements ParamsBuilder {
|
implements ParamsBuilder
|
||||||
|
{
|
||||||
protected readonly subcases: (_: CaseP) => Generator<SubcaseP>;
|
protected readonly subcases: (_: CaseP) => Generator<SubcaseP>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -302,7 +305,10 @@ export class SubcaseParamsBuilder<CaseP extends {}, SubcaseP extends {}>
|
|||||||
|
|
||||||
const subcases = Array.from(this.subcases(caseP));
|
const subcases = Array.from(this.subcases(caseP));
|
||||||
if (subcases.length) {
|
if (subcases.length) {
|
||||||
yield [caseP, subcases];
|
yield [
|
||||||
|
caseP as DeepReadonly<typeof caseP>,
|
||||||
|
subcases as DeepReadonly<(typeof subcases)[number]>[],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ interface TestFileLoaderEventMap {
|
|||||||
finish: MessageEvent<void>;
|
finish: MessageEvent<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override the types for addEventListener/removeEventListener so the callbacks can be used as
|
||||||
|
// strongly-typed.
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging */
|
||||||
export interface TestFileLoader extends EventTarget {
|
export interface TestFileLoader extends EventTarget {
|
||||||
addEventListener<K extends keyof TestFileLoaderEventMap>(
|
addEventListener<K extends keyof TestFileLoaderEventMap>(
|
||||||
type: K,
|
type: K,
|
||||||
@@ -53,19 +56,16 @@ export interface TestFileLoader extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Base class for DefaultTestFileLoader and FakeTestFileLoader.
|
// Base class for DefaultTestFileLoader and FakeTestFileLoader.
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging */
|
||||||
export abstract class TestFileLoader extends EventTarget {
|
export abstract class TestFileLoader extends EventTarget {
|
||||||
abstract listing(suite: string): Promise<TestSuiteListing>;
|
abstract listing(suite: string): Promise<TestSuiteListing>;
|
||||||
protected abstract import(path: string): Promise<SpecFile>;
|
protected abstract import(path: string): Promise<SpecFile>;
|
||||||
|
|
||||||
async importSpecFile(suite: string, path: string[]): Promise<SpecFile> {
|
async importSpecFile(suite: string, path: string[]): Promise<SpecFile> {
|
||||||
const url = `${suite}/${path.join('/')}.spec.js`;
|
const url = `${suite}/${path.join('/')}.spec.js`;
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(new MessageEvent<ImportInfo>('import', { data: { url } }));
|
||||||
new MessageEvent<ImportInfo>('import', { data: { url } })
|
|
||||||
);
|
|
||||||
const ret = await this.import(url);
|
const ret = await this.import(url);
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(new MessageEvent<ImportInfo>('imported', { data: { url } }));
|
||||||
new MessageEvent<ImportInfo>('imported', { data: { url } })
|
|
||||||
);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { LogMessageWithStack } from './log_message.js';
|
|||||||
// MAINTENANCE_TODO: Add warn expectations
|
// MAINTENANCE_TODO: Add warn expectations
|
||||||
export type Expectation = 'pass' | 'skip' | 'fail';
|
export type Expectation = 'pass' | 'skip' | 'fail';
|
||||||
|
|
||||||
export type Status = 'running' | 'warn' | Expectation;
|
export type Status = 'notrun' | 'running' | 'warn' | Expectation;
|
||||||
|
|
||||||
export interface TestCaseResult {
|
export interface TestCaseResult {
|
||||||
status: Status;
|
status: Status;
|
||||||
|
|||||||
@@ -3,27 +3,43 @@ import { globalTestConfig } from '../../framework/test_config.js';
|
|||||||
import { now, assert } from '../../util/util.js';
|
import { now, assert } from '../../util/util.js';
|
||||||
|
|
||||||
import { LogMessageWithStack } from './log_message.js';
|
import { LogMessageWithStack } from './log_message.js';
|
||||||
import { Expectation, LiveTestCaseResult } from './result.js';
|
import { Expectation, LiveTestCaseResult, Status } from './result.js';
|
||||||
|
|
||||||
enum LogSeverity {
|
enum LogSeverity {
|
||||||
Pass = 0,
|
NotRun = 0,
|
||||||
Skip = 1,
|
Skip = 1,
|
||||||
Warn = 2,
|
Pass = 2,
|
||||||
ExpectFailed = 3,
|
Warn = 3,
|
||||||
ValidationFailed = 4,
|
ExpectFailed = 4,
|
||||||
ThrewException = 5,
|
ValidationFailed = 5,
|
||||||
|
ThrewException = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
const kMaxLogStacks = 2;
|
const kMaxLogStacks = 2;
|
||||||
const kMinSeverityForStack = LogSeverity.Warn;
|
const kMinSeverityForStack = LogSeverity.Warn;
|
||||||
|
|
||||||
|
function logSeverityToString(status: LogSeverity): Status {
|
||||||
|
switch (status) {
|
||||||
|
case LogSeverity.NotRun:
|
||||||
|
return 'notrun';
|
||||||
|
case LogSeverity.Pass:
|
||||||
|
return 'pass';
|
||||||
|
case LogSeverity.Skip:
|
||||||
|
return 'skip';
|
||||||
|
case LogSeverity.Warn:
|
||||||
|
return 'warn';
|
||||||
|
default:
|
||||||
|
return 'fail'; // Everything else is an error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Holds onto a LiveTestCaseResult owned by the Logger, and writes the results into it. */
|
/** Holds onto a LiveTestCaseResult owned by the Logger, and writes the results into it. */
|
||||||
export class TestCaseRecorder {
|
export class TestCaseRecorder {
|
||||||
readonly result: LiveTestCaseResult;
|
readonly result: LiveTestCaseResult;
|
||||||
public nonskippedSubcaseCount: number = 0;
|
public nonskippedSubcaseCount: number = 0;
|
||||||
private inSubCase: boolean = false;
|
private inSubCase: boolean = false;
|
||||||
private subCaseStatus = LogSeverity.Pass;
|
private subCaseStatus = LogSeverity.NotRun;
|
||||||
private finalCaseStatus = LogSeverity.Pass;
|
private finalCaseStatus = LogSeverity.NotRun;
|
||||||
private hideStacksBelowSeverity = kMinSeverityForStack;
|
private hideStacksBelowSeverity = kMinSeverityForStack;
|
||||||
private startTime = -1;
|
private startTime = -1;
|
||||||
private logs: LogMessageWithStack[] = [];
|
private logs: LogMessageWithStack[] = [];
|
||||||
@@ -56,20 +72,13 @@ export class TestCaseRecorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert numeric enum back to string (but expose 'exception' as 'fail')
|
// Convert numeric enum back to string (but expose 'exception' as 'fail')
|
||||||
this.result.status =
|
this.result.status = logSeverityToString(this.finalCaseStatus);
|
||||||
this.finalCaseStatus === LogSeverity.Pass
|
|
||||||
? 'pass'
|
|
||||||
: this.finalCaseStatus === LogSeverity.Skip
|
|
||||||
? 'skip'
|
|
||||||
: this.finalCaseStatus === LogSeverity.Warn
|
|
||||||
? 'warn'
|
|
||||||
: 'fail'; // Everything else is an error
|
|
||||||
|
|
||||||
this.result.logs = this.logs;
|
this.result.logs = this.logs;
|
||||||
}
|
}
|
||||||
|
|
||||||
beginSubCase() {
|
beginSubCase() {
|
||||||
this.subCaseStatus = LogSeverity.Pass;
|
this.subCaseStatus = LogSeverity.NotRun;
|
||||||
this.inSubCase = true;
|
this.inSubCase = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +96,7 @@ export class TestCaseRecorder {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.inSubCase = false;
|
this.inSubCase = false;
|
||||||
if (this.subCaseStatus > this.finalCaseStatus) {
|
this.finalCaseStatus = Math.max(this.finalCaseStatus, this.subCaseStatus);
|
||||||
this.finalCaseStatus = this.subCaseStatus;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +110,8 @@ export class TestCaseRecorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info(ex: Error): void {
|
info(ex: Error): void {
|
||||||
this.logImpl(LogSeverity.Pass, 'INFO', ex);
|
// We need this to use the lowest LogSeverity so it doesn't override the current severity for this test case.
|
||||||
|
this.logImpl(LogSeverity.NotRun, 'INFO', ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
skipped(ex: SkipTestCase): void {
|
skipped(ex: SkipTestCase): void {
|
||||||
@@ -122,6 +130,14 @@ export class TestCaseRecorder {
|
|||||||
this.logImpl(LogSeverity.ValidationFailed, 'VALIDATION FAILED', ex);
|
this.logImpl(LogSeverity.ValidationFailed, 'VALIDATION FAILED', ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
passed(): void {
|
||||||
|
if (this.inSubCase) {
|
||||||
|
this.subCaseStatus = Math.max(this.subCaseStatus, LogSeverity.Pass);
|
||||||
|
} else {
|
||||||
|
this.finalCaseStatus = Math.max(this.finalCaseStatus, LogSeverity.Pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
threw(ex: unknown): void {
|
threw(ex: unknown): void {
|
||||||
if (ex instanceof SkipTestCase) {
|
if (ex instanceof SkipTestCase) {
|
||||||
this.skipped(ex);
|
this.skipped(ex);
|
||||||
@@ -137,9 +153,9 @@ export class TestCaseRecorder {
|
|||||||
|
|
||||||
// Final case status should be the "worst" of all log entries.
|
// Final case status should be the "worst" of all log entries.
|
||||||
if (this.inSubCase) {
|
if (this.inSubCase) {
|
||||||
if (level > this.subCaseStatus) this.subCaseStatus = level;
|
this.subCaseStatus = Math.max(this.subCaseStatus, level);
|
||||||
} else {
|
} else {
|
||||||
if (level > this.finalCaseStatus) this.finalCaseStatus = level;
|
this.finalCaseStatus = Math.max(this.finalCaseStatus, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
// setFirstLineOnly for all logs except `kMaxLogStacks` stacks at the highest severity
|
// setFirstLineOnly for all logs except `kMaxLogStacks` stacks at the highest severity
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export type FlattenUnionOfInterfaces<T> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||||
function typeAssert<T extends 'pass'>() {}
|
function typeAssert<_ extends 'pass'>() {}
|
||||||
{
|
{
|
||||||
type Test<T, U> = [T] extends [U]
|
type Test<T, U> = [T] extends [U]
|
||||||
? [U] extends [T]
|
? [U] extends [T]
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ export function comparePublicParamsPaths(a: TestParams, b: TestParams): Ordering
|
|||||||
const commonKeys = new Set(aKeys.filter(k => k in b));
|
const commonKeys = new Set(aKeys.filter(k => k in b));
|
||||||
|
|
||||||
for (const k of commonKeys) {
|
for (const k of commonKeys) {
|
||||||
if (!objectEquals(a[k], b[k])) {
|
// Treat +/-0.0 as different query by distinguishing them in objectEquals
|
||||||
|
if (!objectEquals(a[k], b[k], true)) {
|
||||||
return Ordering.Unordered;
|
return Ordering.Unordered;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const fromStringMagicValue = new Map<string, unknown>([
|
|||||||
[jsNegativeZeroMagicValue, -0],
|
[jsNegativeZeroMagicValue, -0],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function stringifyFilter(k: string, v: unknown): unknown {
|
function stringifyFilter(_k: string, v: unknown): unknown {
|
||||||
// Make sure no one actually uses a magic value as a parameter.
|
// Make sure no one actually uses a magic value as a parameter.
|
||||||
if (typeof v === 'string') {
|
if (typeof v === 'string') {
|
||||||
assert(
|
assert(
|
||||||
@@ -93,7 +93,7 @@ export function stringifyParamValueUniquely(value: JSONWithUndefined): string {
|
|||||||
|
|
||||||
// 'any' is part of the JSON.parse reviver interface, so cannot be avoided.
|
// 'any' is part of the JSON.parse reviver interface, so cannot be avoided.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
function parseParamValueReviver(k: string, v: any): any {
|
function parseParamValueReviver(_k: string, v: any): any {
|
||||||
if (fromStringMagicValue.has(v)) {
|
if (fromStringMagicValue.has(v)) {
|
||||||
return fromStringMagicValue.get(v);
|
return fromStringMagicValue.get(v);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ export class TestQueryMultiFile {
|
|||||||
* Immutable (makes copies of constructor args).
|
* Immutable (makes copies of constructor args).
|
||||||
*/
|
*/
|
||||||
export class TestQueryMultiTest extends TestQueryMultiFile {
|
export class TestQueryMultiTest extends TestQueryMultiFile {
|
||||||
readonly level: TestQueryLevel = 2;
|
override readonly level: TestQueryLevel = 2;
|
||||||
readonly isMultiFile: false = false;
|
override readonly isMultiFile = false as const;
|
||||||
readonly isMultiTest: boolean = true;
|
readonly isMultiTest: boolean = true;
|
||||||
readonly testPathParts: readonly string[];
|
readonly testPathParts: readonly string[];
|
||||||
|
|
||||||
@@ -79,11 +79,11 @@ export class TestQueryMultiTest extends TestQueryMultiFile {
|
|||||||
this.testPathParts = [...test];
|
this.testPathParts = [...test];
|
||||||
}
|
}
|
||||||
|
|
||||||
get depthInLevel() {
|
override get depthInLevel() {
|
||||||
return this.testPathParts.length;
|
return this.testPathParts.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected toStringHelper(): string[] {
|
protected override toStringHelper(): string[] {
|
||||||
return [
|
return [
|
||||||
this.suite,
|
this.suite,
|
||||||
this.filePathParts.join(kPathSeparator),
|
this.filePathParts.join(kPathSeparator),
|
||||||
@@ -99,8 +99,8 @@ export class TestQueryMultiTest extends TestQueryMultiFile {
|
|||||||
* (which aren't normally supposed to change; they're marked readonly in TestParams).
|
* (which aren't normally supposed to change; they're marked readonly in TestParams).
|
||||||
*/
|
*/
|
||||||
export class TestQueryMultiCase extends TestQueryMultiTest {
|
export class TestQueryMultiCase extends TestQueryMultiTest {
|
||||||
readonly level: TestQueryLevel = 3;
|
override readonly level: TestQueryLevel = 3;
|
||||||
readonly isMultiTest: false = false;
|
override readonly isMultiTest = false as const;
|
||||||
readonly isMultiCase: boolean = true;
|
readonly isMultiCase: boolean = true;
|
||||||
readonly params: TestParams;
|
readonly params: TestParams;
|
||||||
|
|
||||||
@@ -110,11 +110,11 @@ export class TestQueryMultiCase extends TestQueryMultiTest {
|
|||||||
this.params = { ...params };
|
this.params = { ...params };
|
||||||
}
|
}
|
||||||
|
|
||||||
get depthInLevel() {
|
override get depthInLevel() {
|
||||||
return Object.keys(this.params).length;
|
return Object.keys(this.params).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected toStringHelper(): string[] {
|
protected override toStringHelper(): string[] {
|
||||||
return [
|
return [
|
||||||
this.suite,
|
this.suite,
|
||||||
this.filePathParts.join(kPathSeparator),
|
this.filePathParts.join(kPathSeparator),
|
||||||
@@ -130,14 +130,14 @@ export class TestQueryMultiCase extends TestQueryMultiTest {
|
|||||||
* Immutable (makes copies of constructor args).
|
* Immutable (makes copies of constructor args).
|
||||||
*/
|
*/
|
||||||
export class TestQuerySingleCase extends TestQueryMultiCase {
|
export class TestQuerySingleCase extends TestQueryMultiCase {
|
||||||
readonly level: TestQueryLevel = 4;
|
override readonly level: TestQueryLevel = 4;
|
||||||
readonly isMultiCase: false = false;
|
override readonly isMultiCase = false as const;
|
||||||
|
|
||||||
get depthInLevel() {
|
override get depthInLevel() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected toStringHelper(): string[] {
|
protected override toStringHelper(): string[] {
|
||||||
return [
|
return [
|
||||||
this.suite,
|
this.suite,
|
||||||
this.filePathParts.join(kPathSeparator),
|
this.filePathParts.join(kPathSeparator),
|
||||||
|
|||||||
@@ -19,13 +19,19 @@ import { Expectation } from '../internal/logging/result.js';
|
|||||||
import { TestCaseRecorder } from '../internal/logging/test_case_recorder.js';
|
import { TestCaseRecorder } from '../internal/logging/test_case_recorder.js';
|
||||||
import { extractPublicParams, Merged, mergeParams } from '../internal/params_utils.js';
|
import { extractPublicParams, Merged, mergeParams } from '../internal/params_utils.js';
|
||||||
import { compareQueries, Ordering } from '../internal/query/compare.js';
|
import { compareQueries, Ordering } from '../internal/query/compare.js';
|
||||||
import { TestQuerySingleCase, TestQueryWithExpectation } from '../internal/query/query.js';
|
import {
|
||||||
|
TestQueryMultiFile,
|
||||||
|
TestQueryMultiTest,
|
||||||
|
TestQuerySingleCase,
|
||||||
|
TestQueryWithExpectation,
|
||||||
|
} from '../internal/query/query.js';
|
||||||
import { kPathSeparator } from '../internal/query/separators.js';
|
import { kPathSeparator } from '../internal/query/separators.js';
|
||||||
import {
|
import {
|
||||||
stringifyPublicParams,
|
stringifyPublicParams,
|
||||||
stringifyPublicParamsUniquely,
|
stringifyPublicParamsUniquely,
|
||||||
} from '../internal/query/stringify_params.js';
|
} from '../internal/query/stringify_params.js';
|
||||||
import { validQueryPart } from '../internal/query/validQueryPart.js';
|
import { validQueryPart } from '../internal/query/validQueryPart.js';
|
||||||
|
import { DeepReadonly } from '../util/types.js';
|
||||||
import { assert, unreachable } from '../util/util.js';
|
import { assert, unreachable } from '../util/util.js';
|
||||||
|
|
||||||
import { logToWebsocket } from './websocket_logger.js';
|
import { logToWebsocket } from './websocket_logger.js';
|
||||||
@@ -56,13 +62,13 @@ export interface TestGroupBuilder<F extends Fixture> {
|
|||||||
test(name: string): TestBuilderWithName<F>;
|
test(name: string): TestBuilderWithName<F>;
|
||||||
}
|
}
|
||||||
export function makeTestGroup<F extends Fixture>(fixture: FixtureClass<F>): TestGroupBuilder<F> {
|
export function makeTestGroup<F extends Fixture>(fixture: FixtureClass<F>): TestGroupBuilder<F> {
|
||||||
return new TestGroup((fixture as unknown) as FixtureClass);
|
return new TestGroup(fixture as unknown as FixtureClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interfaces for running tests
|
// Interfaces for running tests
|
||||||
export interface IterableTestGroup {
|
export interface IterableTestGroup {
|
||||||
iterate(): Iterable<IterableTest>;
|
iterate(): Iterable<IterableTest>;
|
||||||
validate(): void;
|
validate(fileQuery: TestQueryMultiFile): void;
|
||||||
/** Returns the file-relative test paths of tests which have >0 cases. */
|
/** Returns the file-relative test paths of tests which have >0 cases. */
|
||||||
collectNonEmptyTests(): { testPath: string[] }[];
|
collectNonEmptyTests(): { testPath: string[] }[];
|
||||||
}
|
}
|
||||||
@@ -79,12 +85,17 @@ export function makeTestGroupForUnitTesting<F extends Fixture>(
|
|||||||
return new TestGroup(fixture);
|
return new TestGroup(fixture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The maximum allowed length of a test query string. Checked by tools/validate. */
|
||||||
|
export const kQueryMaxLength = 375;
|
||||||
|
|
||||||
/** Parameter name for batch number (see also TestBuilder.batch). */
|
/** Parameter name for batch number (see also TestBuilder.batch). */
|
||||||
const kBatchParamName = 'batch__';
|
const kBatchParamName = 'batch__';
|
||||||
|
|
||||||
type TestFn<F extends Fixture, P extends {}> = (t: F & { params: P }) => Promise<void> | void;
|
type TestFn<F extends Fixture, P extends {}> = (
|
||||||
|
t: F & { params: DeepReadonly<P> }
|
||||||
|
) => Promise<void> | void;
|
||||||
type BeforeAllSubcasesFn<S extends SubcaseBatchState, P extends {}> = (
|
type BeforeAllSubcasesFn<S extends SubcaseBatchState, P extends {}> = (
|
||||||
s: S & { params: P }
|
s: S & { params: DeepReadonly<P> }
|
||||||
) => Promise<void> | void;
|
) => Promise<void> | void;
|
||||||
|
|
||||||
export class TestGroup<F extends Fixture> implements TestGroupBuilder<F> {
|
export class TestGroup<F extends Fixture> implements TestGroupBuilder<F> {
|
||||||
@@ -124,12 +135,17 @@ export class TestGroup<F extends Fixture> implements TestGroupBuilder<F> {
|
|||||||
|
|
||||||
const test = new TestBuilder(parts, this.fixture, testCreationStack);
|
const test = new TestBuilder(parts, this.fixture, testCreationStack);
|
||||||
this.tests.push(test);
|
this.tests.push(test);
|
||||||
return (test as unknown) as TestBuilderWithName<F>;
|
return test as unknown as TestBuilderWithName<F>;
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(): void {
|
validate(fileQuery: TestQueryMultiFile): void {
|
||||||
for (const test of this.tests) {
|
for (const test of this.tests) {
|
||||||
test.validate();
|
const testQuery = new TestQueryMultiTest(
|
||||||
|
fileQuery.suite,
|
||||||
|
fileQuery.filePathParts,
|
||||||
|
test.testPath
|
||||||
|
);
|
||||||
|
test.validate(testQuery);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +263,7 @@ class TestBuilder<S extends SubcaseBatchState, F extends Fixture> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
specURL(url: string): this {
|
specURL(_url: string): this {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +300,7 @@ class TestBuilder<S extends SubcaseBatchState, F extends Fixture> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Perform various validation/"lint" chenks. */
|
/** Perform various validation/"lint" chenks. */
|
||||||
validate(): void {
|
validate(testQuery: TestQueryMultiTest): void {
|
||||||
const testPathString = this.testPath.join(kPathSeparator);
|
const testPathString = this.testPath.join(kPathSeparator);
|
||||||
assert(this.testFn !== undefined, () => {
|
assert(this.testFn !== undefined, () => {
|
||||||
let s = `Test is missing .fn(): ${testPathString}`;
|
let s = `Test is missing .fn(): ${testPathString}`;
|
||||||
@@ -294,12 +310,30 @@ class TestBuilder<S extends SubcaseBatchState, F extends Fixture> {
|
|||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
assert(
|
||||||
|
testQuery.toString().length <= kQueryMaxLength,
|
||||||
|
() =>
|
||||||
|
`Test query ${testQuery} is too long. Max length is ${kQueryMaxLength} characters. Please shorten names or reduce parameters.`
|
||||||
|
);
|
||||||
|
|
||||||
if (this.testCases === undefined) {
|
if (this.testCases === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
for (const [caseParams, subcases] of builderIterateCasesWithSubcases(this.testCases, null)) {
|
for (const [caseParams, subcases] of builderIterateCasesWithSubcases(this.testCases, null)) {
|
||||||
|
const caseQuery = new TestQuerySingleCase(
|
||||||
|
testQuery.suite,
|
||||||
|
testQuery.filePathParts,
|
||||||
|
testQuery.testPathParts,
|
||||||
|
caseParams
|
||||||
|
).toString();
|
||||||
|
assert(
|
||||||
|
caseQuery.length <= kQueryMaxLength,
|
||||||
|
() =>
|
||||||
|
`Case query ${caseQuery} is too long. Max length is ${kQueryMaxLength} characters. Please shorten names or reduce parameters.`
|
||||||
|
);
|
||||||
|
|
||||||
for (const subcaseParams of subcases ?? [{}]) {
|
for (const subcaseParams of subcases ?? [{}]) {
|
||||||
const params = mergeParams(caseParams, subcaseParams);
|
const params = mergeParams(caseParams, subcaseParams);
|
||||||
assert(this.batchSize === 0 || !(kBatchParamName in params));
|
assert(this.batchSize === 0 || !(kBatchParamName in params));
|
||||||
@@ -316,7 +350,7 @@ class TestBuilder<S extends SubcaseBatchState, F extends Fixture> {
|
|||||||
const testcaseStringUnique = stringifyPublicParamsUniquely(params);
|
const testcaseStringUnique = stringifyPublicParamsUniquely(params);
|
||||||
assert(
|
assert(
|
||||||
!seen.has(testcaseStringUnique),
|
!seen.has(testcaseStringUnique),
|
||||||
`Duplicate public test case params for test ${testPathString}: ${testcaseString}`
|
`Duplicate public test case+subcase params for test ${testPathString}: ${testcaseString}`
|
||||||
);
|
);
|
||||||
seen.add(testcaseStringUnique);
|
seen.add(testcaseStringUnique);
|
||||||
}
|
}
|
||||||
@@ -491,6 +525,7 @@ class RunCaseSpecific implements RunCase {
|
|||||||
try {
|
try {
|
||||||
await inst.init();
|
await inst.init();
|
||||||
await this.fn(inst as Fixture & { params: {} });
|
await this.fn(inst as Fixture & { params: {} });
|
||||||
|
rec.passed();
|
||||||
} finally {
|
} finally {
|
||||||
// Runs as long as constructor succeeded, even if initialization or the test failed.
|
// Runs as long as constructor succeeded, even if initialization or the test failed.
|
||||||
await inst.finalize();
|
await inst.finalize();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { LiveTestCaseResult } from '../internal/logging/result.js';
|
|||||||
import { parseQuery } from '../internal/query/parseQuery.js';
|
import { parseQuery } from '../internal/query/parseQuery.js';
|
||||||
import { parseExpectationsForTestQuery } from '../internal/query/query.js';
|
import { parseExpectationsForTestQuery } from '../internal/query/query.js';
|
||||||
import { Colors } from '../util/colors.js';
|
import { Colors } from '../util/colors.js';
|
||||||
import { setGPUProvider } from '../util/navigator_gpu.js';
|
import { setDefaultRequestAdapterOptions, setGPUProvider } from '../util/navigator_gpu.js';
|
||||||
import { assert, unreachable } from '../util/util.js';
|
import { assert, unreachable } from '../util/util.js';
|
||||||
|
|
||||||
import sys from './helper/sys.js';
|
import sys from './helper/sys.js';
|
||||||
@@ -22,6 +22,7 @@ function usage(rc: number): never {
|
|||||||
tools/run_${sys.type} 'unittests:*' 'webgpu:buffers,*'
|
tools/run_${sys.type} 'unittests:*' 'webgpu:buffers,*'
|
||||||
Options:
|
Options:
|
||||||
--colors Enable ANSI colors in output.
|
--colors Enable ANSI colors in output.
|
||||||
|
--compat Runs tests in compatibility mode.
|
||||||
--coverage Emit coverage data.
|
--coverage Emit coverage data.
|
||||||
--verbose Print result/log of every test as it runs.
|
--verbose Print result/log of every test as it runs.
|
||||||
--list Print all testcase names that match the given query and exit.
|
--list Print all testcase names that match the given query and exit.
|
||||||
@@ -99,6 +100,8 @@ for (let i = 0; i < sys.args.length; ++i) {
|
|||||||
quiet = true;
|
quiet = true;
|
||||||
} else if (a === '--unroll-const-eval-loops') {
|
} else if (a === '--unroll-const-eval-loops') {
|
||||||
globalTestConfig.unrollConstEvalLoops = true;
|
globalTestConfig.unrollConstEvalLoops = true;
|
||||||
|
} else if (a === '--compat') {
|
||||||
|
globalTestConfig.compatibility = true;
|
||||||
} else {
|
} else {
|
||||||
console.log('unrecognized flag: ', a);
|
console.log('unrecognized flag: ', a);
|
||||||
usage(1);
|
usage(1);
|
||||||
@@ -110,6 +113,11 @@ for (let i = 0; i < sys.args.length; ++i) {
|
|||||||
|
|
||||||
let codeCoverage: CodeCoverageProvider | undefined = undefined;
|
let codeCoverage: CodeCoverageProvider | undefined = undefined;
|
||||||
|
|
||||||
|
if (globalTestConfig.compatibility) {
|
||||||
|
// MAINTENANCE_TODO: remove the cast once compatibilityMode is officially added
|
||||||
|
setDefaultRequestAdapterOptions({ compatibilityMode: true } as GPURequestAdapterOptions);
|
||||||
|
}
|
||||||
|
|
||||||
if (gpuProviderModule) {
|
if (gpuProviderModule) {
|
||||||
setGPUProvider(() => gpuProviderModule!.create(gpuProviderFlags));
|
setGPUProvider(() => gpuProviderModule!.create(gpuProviderFlags));
|
||||||
if (emitCoverage) {
|
if (emitCoverage) {
|
||||||
@@ -127,8 +135,8 @@ Did you remember to build with code coverage instrumentation enabled?`
|
|||||||
if (dataPath !== undefined) {
|
if (dataPath !== undefined) {
|
||||||
dataCache.setStore({
|
dataCache.setStore({
|
||||||
load: (path: string) => {
|
load: (path: string) => {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<Uint8Array>((resolve, reject) => {
|
||||||
fs.readFile(`${dataPath}/${path}`, 'utf8', (err, data) => {
|
fs.readFile(`${dataPath}/${path}`, (err, data) => {
|
||||||
if (err !== null) {
|
if (err !== null) {
|
||||||
reject(err.message);
|
reject(err.message);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ function getOptionsInfoFromSearchString<Type extends CTSOptions>(
|
|||||||
const parser = info.parser || optionEnabled;
|
const parser = info.parser || optionEnabled;
|
||||||
optionValues[optionName] = parser(camelCaseToSnakeCase(optionName), searchParams);
|
optionValues[optionName] = parser(camelCaseToSnakeCase(optionName), searchParams);
|
||||||
}
|
}
|
||||||
return (optionValues as unknown) as Type;
|
return optionValues as unknown as Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { parseQuery } from '../internal/query/parseQuery.js';
|
|||||||
import { TestQueryWithExpectation } from '../internal/query/query.js';
|
import { TestQueryWithExpectation } from '../internal/query/query.js';
|
||||||
import { TestTreeLeaf } from '../internal/tree.js';
|
import { TestTreeLeaf } from '../internal/tree.js';
|
||||||
import { Colors } from '../util/colors.js';
|
import { Colors } from '../util/colors.js';
|
||||||
import { setGPUProvider } from '../util/navigator_gpu.js';
|
import { setDefaultRequestAdapterOptions, setGPUProvider } from '../util/navigator_gpu.js';
|
||||||
|
|
||||||
import sys from './helper/sys.js';
|
import sys from './helper/sys.js';
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ function usage(rc: number): never {
|
|||||||
tools/run_${sys.type} [OPTIONS...]
|
tools/run_${sys.type} [OPTIONS...]
|
||||||
Options:
|
Options:
|
||||||
--colors Enable ANSI colors in output.
|
--colors Enable ANSI colors in output.
|
||||||
|
--compat Run tests in compatibility mode.
|
||||||
--coverage Add coverage data to each result.
|
--coverage Add coverage data to each result.
|
||||||
--data Path to the data cache directory.
|
--data Path to the data cache directory.
|
||||||
--verbose Print result/log of every test as it runs.
|
--verbose Print result/log of every test as it runs.
|
||||||
@@ -84,6 +85,8 @@ for (let i = 0; i < sys.args.length; ++i) {
|
|||||||
if (a.startsWith('-')) {
|
if (a.startsWith('-')) {
|
||||||
if (a === '--colors') {
|
if (a === '--colors') {
|
||||||
Colors.enabled = true;
|
Colors.enabled = true;
|
||||||
|
} else if (a === '--compat') {
|
||||||
|
globalTestConfig.compatibility = true;
|
||||||
} else if (a === '--coverage') {
|
} else if (a === '--coverage') {
|
||||||
emitCoverage = true;
|
emitCoverage = true;
|
||||||
} else if (a === '--data') {
|
} else if (a === '--data') {
|
||||||
@@ -107,6 +110,11 @@ for (let i = 0; i < sys.args.length; ++i) {
|
|||||||
|
|
||||||
let codeCoverage: CodeCoverageProvider | undefined = undefined;
|
let codeCoverage: CodeCoverageProvider | undefined = undefined;
|
||||||
|
|
||||||
|
if (globalTestConfig.compatibility) {
|
||||||
|
// MAINTENANCE_TODO: remove the cast once compatibilityMode is officially added
|
||||||
|
setDefaultRequestAdapterOptions({ compatibilityMode: true } as GPURequestAdapterOptions);
|
||||||
|
}
|
||||||
|
|
||||||
if (gpuProviderModule) {
|
if (gpuProviderModule) {
|
||||||
setGPUProvider(() => gpuProviderModule!.create(gpuProviderFlags));
|
setGPUProvider(() => gpuProviderModule!.create(gpuProviderFlags));
|
||||||
|
|
||||||
@@ -125,8 +133,8 @@ Did you remember to build with code coverage instrumentation enabled?`
|
|||||||
if (dataPath !== undefined) {
|
if (dataPath !== undefined) {
|
||||||
dataCache.setStore({
|
dataCache.setStore({
|
||||||
load: (path: string) => {
|
load: (path: string) => {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<Uint8Array>((resolve, reject) => {
|
||||||
fs.readFile(`${dataPath}/${path}`, 'utf8', (err, data) => {
|
fs.readFile(`${dataPath}/${path}`, (err, data) => {
|
||||||
if (err !== null) {
|
if (err !== null) {
|
||||||
reject(err.message);
|
reject(err.message);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { parseQuery } from '../internal/query/parseQuery.js';
|
|||||||
import { TestQueryLevel } from '../internal/query/query.js';
|
import { TestQueryLevel } from '../internal/query/query.js';
|
||||||
import { TestTreeNode, TestSubtree, TestTreeLeaf, TestTree } from '../internal/tree.js';
|
import { TestTreeNode, TestSubtree, TestTreeLeaf, TestTree } from '../internal/tree.js';
|
||||||
import { setDefaultRequestAdapterOptions } from '../util/navigator_gpu.js';
|
import { setDefaultRequestAdapterOptions } from '../util/navigator_gpu.js';
|
||||||
import { assert, ErrorWithExtra, unreachable } from '../util/util.js';
|
import { ErrorWithExtra, unreachable } from '../util/util.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
kCTSOptionsInfo,
|
kCTSOptionsInfo,
|
||||||
@@ -84,7 +84,7 @@ dataCache.setStore({
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return Promise.reject(response.statusText);
|
return Promise.reject(response.statusText);
|
||||||
}
|
}
|
||||||
return await response.text();
|
return new Uint8Array(await response.arrayBuffer());
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -427,11 +427,20 @@ function makeTreeNodeHeaderHTML(
|
|||||||
.attr('alt', runtext)
|
.attr('alt', runtext)
|
||||||
.attr('title', runtext)
|
.attr('title', runtext)
|
||||||
.on('click', async () => {
|
.on('click', async () => {
|
||||||
|
if (runDepth > 0) {
|
||||||
|
showInfo('tests are already running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showInfo('');
|
||||||
console.log(`Starting run for ${n.query}`);
|
console.log(`Starting run for ${n.query}`);
|
||||||
|
// turn off all run buttons
|
||||||
|
$('#resultsVis').addClass('disable-run');
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
await runSubtree();
|
await runSubtree();
|
||||||
const dt = performance.now() - startTime;
|
const dt = performance.now() - startTime;
|
||||||
const dtMinutes = dt / 1000 / 60;
|
const dtMinutes = dt / 1000 / 60;
|
||||||
|
// turn on all run buttons
|
||||||
|
$('#resultsVis').removeClass('disable-run');
|
||||||
console.log(`Finished run: ${dt.toFixed(1)} ms = ${dtMinutes.toFixed(1)} min`);
|
console.log(`Finished run: ${dt.toFixed(1)} ms = ${dtMinutes.toFixed(1)} min`);
|
||||||
})
|
})
|
||||||
.appendTo(header);
|
.appendTo(header);
|
||||||
@@ -528,7 +537,7 @@ function prepareParams(params: Record<string, ParamValue>): string {
|
|||||||
|
|
||||||
// This is just a cast in one place.
|
// This is just a cast in one place.
|
||||||
export function optionsToRecord(options: CTSOptions) {
|
export function optionsToRecord(options: CTSOptions) {
|
||||||
return (options as unknown) as Record<string, boolean | string>;
|
return options as unknown as Record<string, boolean | string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -543,6 +552,14 @@ function createSearchQuery(queries: string[], params?: string) {
|
|||||||
return `?${params}${params ? '&' : ''}${queries.map(q => 'q=' + q).join('&')}`;
|
return `?${params}${params ? '&' : ''}${queries.map(q => 'q=' + q).join('&')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an info message on the page.
|
||||||
|
* @param msg Message to show
|
||||||
|
*/
|
||||||
|
function showInfo(msg: string) {
|
||||||
|
$('#info')[0].textContent = msg;
|
||||||
|
}
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const loader = new DefaultTestFileLoader();
|
const loader = new DefaultTestFileLoader();
|
||||||
|
|
||||||
@@ -609,26 +626,37 @@ void (async () => {
|
|||||||
};
|
};
|
||||||
addOptionsToPage(options, kStandaloneOptionsInfos);
|
addOptionsToPage(options, kStandaloneOptionsInfos);
|
||||||
|
|
||||||
assert(qs.length === 1, 'currently, there must be exactly one ?q=');
|
if (qs.length !== 1) {
|
||||||
const rootQuery = parseQuery(qs[0]);
|
showInfo('currently, there must be exactly one ?q=');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rootQuery;
|
||||||
|
try {
|
||||||
|
rootQuery = parseQuery(qs[0]);
|
||||||
|
} catch (e) {
|
||||||
|
showInfo((e as Error).toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (rootQuery.level > lastQueryLevelToExpand) {
|
if (rootQuery.level > lastQueryLevelToExpand) {
|
||||||
lastQueryLevelToExpand = rootQuery.level;
|
lastQueryLevelToExpand = rootQuery.level;
|
||||||
}
|
}
|
||||||
loader.addEventListener('import', ev => {
|
loader.addEventListener('import', ev => {
|
||||||
$('#info')[0].textContent = `loading: ${ev.data.url}`;
|
showInfo(`loading: ${ev.data.url}`);
|
||||||
});
|
});
|
||||||
loader.addEventListener('imported', ev => {
|
loader.addEventListener('imported', ev => {
|
||||||
$('#info')[0].textContent = `imported: ${ev.data.url}`;
|
showInfo(`imported: ${ev.data.url}`);
|
||||||
});
|
});
|
||||||
loader.addEventListener('finish', () => {
|
loader.addEventListener('finish', () => {
|
||||||
$('#info')[0].textContent = '';
|
showInfo('');
|
||||||
});
|
});
|
||||||
|
|
||||||
let tree;
|
let tree;
|
||||||
try {
|
try {
|
||||||
tree = await loader.loadTree(rootQuery);
|
tree = await loader.loadTree(rootQuery);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$('#info')[0].textContent = (err as Error).toString();
|
showInfo((err as Error).toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": { "project": "./tsconfig.json" },
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"no-process-exit": "off",
|
"no-process-exit": "off",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as path from 'path';
|
|||||||
|
|
||||||
import { loadMetadataForSuite } from '../framework/metadata.js';
|
import { loadMetadataForSuite } from '../framework/metadata.js';
|
||||||
import { SpecFile } from '../internal/file_loader.js';
|
import { SpecFile } from '../internal/file_loader.js';
|
||||||
import { TestQueryMultiCase } from '../internal/query/query.js';
|
import { TestQueryMultiCase, TestQueryMultiFile } from '../internal/query/query.js';
|
||||||
import { validQueryPart } from '../internal/query/validQueryPart.js';
|
import { validQueryPart } from '../internal/query/validQueryPart.js';
|
||||||
import { TestSuiteListingEntry, TestSuiteListing } from '../internal/test_suite_listing.js';
|
import { TestSuiteListingEntry, TestSuiteListing } from '../internal/test_suite_listing.js';
|
||||||
import { assert, unreachable } from '../util/util.js';
|
import { assert, unreachable } from '../util/util.js';
|
||||||
@@ -83,6 +83,8 @@ export async function crawl(suiteDir: string, validate: boolean): Promise<TestSu
|
|||||||
assert(mod.description !== undefined, 'Test spec file missing description: ' + filename);
|
assert(mod.description !== undefined, 'Test spec file missing description: ' + filename);
|
||||||
assert(mod.g !== undefined, 'Test spec file missing TestGroup definition: ' + filename);
|
assert(mod.g !== undefined, 'Test spec file missing TestGroup definition: ' + filename);
|
||||||
|
|
||||||
|
mod.g.validate(new TestQueryMultiFile(suite, pathSegments));
|
||||||
|
|
||||||
for (const { testPath } of mod.g.collectNonEmptyTests()) {
|
for (const { testPath } of mod.g.collectNonEmptyTests()) {
|
||||||
const testQuery = new TestQueryMultiCase(suite, pathSegments, testPath, {}).toString();
|
const testQuery = new TestQueryMultiCase(suite, pathSegments, testPath, {}).toString();
|
||||||
if (validateTimingsEntries) {
|
if (validateTimingsEntries) {
|
||||||
|
|||||||
@@ -14,6 +14,19 @@ import { makeListing } from './crawl.js';
|
|||||||
// Make sure that makeListing doesn't cache imported spec files. See crawl().
|
// Make sure that makeListing doesn't cache imported spec files. See crawl().
|
||||||
process.env.STANDALONE_DEV_SERVER = '1';
|
process.env.STANDALONE_DEV_SERVER = '1';
|
||||||
|
|
||||||
|
function usage(rc: number): void {
|
||||||
|
console.error(`\
|
||||||
|
Usage:
|
||||||
|
tools/dev_server
|
||||||
|
tools/dev_server 0.0.0.0
|
||||||
|
npm start
|
||||||
|
npm start 0.0.0.0
|
||||||
|
|
||||||
|
By default, serves on localhost only. If the argument 0.0.0.0 is passed, serves on all interfaces.
|
||||||
|
`);
|
||||||
|
process.exit(rc);
|
||||||
|
}
|
||||||
|
|
||||||
const srcDir = path.resolve(__dirname, '../../');
|
const srcDir = path.resolve(__dirname, '../../');
|
||||||
|
|
||||||
// Import the project's babel.config.js. We'll use the same config for the runtime compiler.
|
// Import the project's babel.config.js. We'll use the same config for the runtime compiler.
|
||||||
@@ -92,7 +105,7 @@ watcher.on('change', dirtyCompileCache);
|
|||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
// Send Chrome Origin Trial tokens
|
// Send Chrome Origin Trial tokens
|
||||||
app.use((req, res, next) => {
|
app.use((_req, res, next) => {
|
||||||
res.header('Origin-Trial', [
|
res.header('Origin-Trial', [
|
||||||
// Token for http://localhost:8080
|
// Token for http://localhost:8080
|
||||||
'AvyDIV+RJoYs8fn3W6kIrBhWw0te0klraoz04mw/nPb8VTus3w5HCdy+vXqsSzomIH745CT6B5j1naHgWqt/tw8AAABJeyJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJmZWF0dXJlIjoiV2ViR1BVIiwiZXhwaXJ5IjoxNjYzNzE4Mzk5fQ==',
|
'AvyDIV+RJoYs8fn3W6kIrBhWw0te0klraoz04mw/nPb8VTus3w5HCdy+vXqsSzomIH745CT6B5j1naHgWqt/tw8AAABJeyJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJmZWF0dXJlIjoiV2ViR1BVIiwiZXhwaXJ5IjoxNjYzNzE4Mzk5fQ==',
|
||||||
@@ -110,7 +123,7 @@ app.use('/out-wpt', express.static(path.resolve(srcDir, '../out-wpt')));
|
|||||||
app.use('/docs/tsdoc', express.static(path.resolve(srcDir, '../docs/tsdoc')));
|
app.use('/docs/tsdoc', express.static(path.resolve(srcDir, '../docs/tsdoc')));
|
||||||
|
|
||||||
// Serve a suite's listing.js file by crawling the filesystem for all tests.
|
// Serve a suite's listing.js file by crawling the filesystem for all tests.
|
||||||
app.get('/out/:suite/listing.js', async (req, res, next) => {
|
app.get('/out/:suite([a-zA-Z0-9_-]+)/listing.js', async (req, res, next) => {
|
||||||
const suite = req.params['suite'];
|
const suite = req.params['suite'];
|
||||||
|
|
||||||
if (listingCache.has(suite)) {
|
if (listingCache.has(suite)) {
|
||||||
@@ -162,28 +175,40 @@ app.get('/out/**/*.js', async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const host = '0.0.0.0';
|
// Serve everything else (not .js) as static, and directories as directory listings.
|
||||||
const port = 8080;
|
app.use('/out', serveIndex(path.resolve(srcDir, '../src')));
|
||||||
// Find an available port, starting at 8080.
|
app.use('/out', express.static(path.resolve(srcDir, '../src')));
|
||||||
portfinder.getPort({ host, port }, (err, port) => {
|
|
||||||
if (err) {
|
void (async () => {
|
||||||
throw err;
|
let host = '127.0.0.1';
|
||||||
|
if (process.argv.length >= 3) {
|
||||||
|
if (process.argv.length !== 3) usage(1);
|
||||||
|
if (process.argv[2] === '0.0.0.0') {
|
||||||
|
host = '0.0.0.0';
|
||||||
|
} else {
|
||||||
|
usage(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`Finding an available port on ${host}...`);
|
||||||
|
const kPortFinderStart = 8080;
|
||||||
|
const port = await portfinder.getPortPromise({ host, port: kPortFinderStart });
|
||||||
|
|
||||||
watcher.on('ready', () => {
|
watcher.on('ready', () => {
|
||||||
// Listen on the available port.
|
// Listen on the available port.
|
||||||
app.listen(port, host, () => {
|
app.listen(port, host, () => {
|
||||||
console.log('Standalone test runner running at:');
|
console.log('Standalone test runner running at:');
|
||||||
for (const iface of Object.values(os.networkInterfaces())) {
|
if (host === '0.0.0.0') {
|
||||||
for (const details of iface || []) {
|
for (const iface of Object.values(os.networkInterfaces())) {
|
||||||
if (details.family === 'IPv4') {
|
for (const details of iface || []) {
|
||||||
console.log(` http://${details.address}:${port}/standalone/`);
|
if (details.family === 'IPv4') {
|
||||||
|
console.log(` http://${details.address}:${port}/standalone/`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.log(` http://${host}:${port}/standalone/`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
})();
|
||||||
|
|
||||||
// Serve everything else (not .js) as static, and directories as directory listings.
|
|
||||||
app.use('/out', serveIndex(path.resolve(srcDir, '../src')));
|
|
||||||
app.use('/out', express.static(path.resolve(srcDir, '../src')));
|
|
||||||
|
|||||||
@@ -14,33 +14,68 @@ DataCache will load this instead of building the expensive data at CTS runtime.
|
|||||||
Options:
|
Options:
|
||||||
--help Print this message and exit.
|
--help Print this message and exit.
|
||||||
--list Print the list of output files without writing them.
|
--list Print the list of output files without writing them.
|
||||||
|
--nth i/n Only process every file where (file_index % n == i)
|
||||||
|
--validate Check that cache should build (Tests for collisions).
|
||||||
--verbose Print each action taken.
|
--verbose Print each action taken.
|
||||||
`);
|
`);
|
||||||
process.exit(rc);
|
process.exit(rc);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mode: 'emit' | 'list' = 'emit';
|
let mode: 'emit' | 'list' | 'validate' = 'emit';
|
||||||
|
let nth = { i: 0, n: 1 };
|
||||||
let verbose = false;
|
let verbose = false;
|
||||||
|
|
||||||
const nonFlagsArgs: string[] = [];
|
const nonFlagsArgs: string[] = [];
|
||||||
for (const a of process.argv) {
|
|
||||||
if (a.startsWith('-')) {
|
for (let i = 0; i < process.argv.length; i++) {
|
||||||
switch (a) {
|
const arg = process.argv[i];
|
||||||
case '--list':
|
if (arg.startsWith('-')) {
|
||||||
|
switch (arg) {
|
||||||
|
case '--list': {
|
||||||
mode = 'list';
|
mode = 'list';
|
||||||
break;
|
break;
|
||||||
case '--help':
|
}
|
||||||
|
case '--help': {
|
||||||
usage(0);
|
usage(0);
|
||||||
break;
|
break;
|
||||||
case '--verbose':
|
}
|
||||||
|
case '--verbose': {
|
||||||
verbose = true;
|
verbose = true;
|
||||||
break;
|
break;
|
||||||
default:
|
}
|
||||||
console.log('unrecognized flag: ', a);
|
case '--validate': {
|
||||||
|
mode = 'validate';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '--nth': {
|
||||||
|
const err = () => {
|
||||||
|
console.error(
|
||||||
|
`--nth requires a value of the form 'i/n', where i and n are positive integers and i < n`
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
};
|
||||||
|
i++;
|
||||||
|
if (i >= process.argv.length) {
|
||||||
|
err();
|
||||||
|
}
|
||||||
|
const value = process.argv[i];
|
||||||
|
const parts = value.split('/');
|
||||||
|
if (parts.length !== 2) {
|
||||||
|
err();
|
||||||
|
}
|
||||||
|
nth = { i: parseInt(parts[0]), n: parseInt(parts[1]) };
|
||||||
|
if (nth.i < 0 || nth.n < 1 || nth.i > nth.n) {
|
||||||
|
err();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
console.log('unrecognized flag: ', arg);
|
||||||
usage(1);
|
usage(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
nonFlagsArgs.push(a);
|
nonFlagsArgs.push(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,8 +87,8 @@ const outRootDir = nonFlagsArgs[2];
|
|||||||
|
|
||||||
dataCache.setStore({
|
dataCache.setStore({
|
||||||
load: (path: string) => {
|
load: (path: string) => {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<Uint8Array>((resolve, reject) => {
|
||||||
fs.readFile(`data/${path}`, 'utf8', (err, data) => {
|
fs.readFile(`data/${path}`, (err, data) => {
|
||||||
if (err !== null) {
|
if (err !== null) {
|
||||||
reject(err.message);
|
reject(err.message);
|
||||||
} else {
|
} else {
|
||||||
@@ -105,34 +140,38 @@ async function build(suiteDir: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Crawl files and convert paths to be POSIX-style, relative to suiteDir.
|
// Crawl files and convert paths to be POSIX-style, relative to suiteDir.
|
||||||
const filesToEnumerate = (await crawlFilesRecursively(suiteDir)).sort();
|
let filesToEnumerate = (await crawlFilesRecursively(suiteDir)).sort();
|
||||||
|
|
||||||
|
// Filter out non-spec files
|
||||||
|
filesToEnumerate = filesToEnumerate.filter(f => f.endsWith(specFileSuffix));
|
||||||
|
|
||||||
const cacheablePathToTS = new Map<string, string>();
|
const cacheablePathToTS = new Map<string, string>();
|
||||||
|
|
||||||
|
let fileIndex = 0;
|
||||||
for (const file of filesToEnumerate) {
|
for (const file of filesToEnumerate) {
|
||||||
if (file.endsWith(specFileSuffix)) {
|
const pathWithoutExtension = file.substring(0, file.length - specFileSuffix.length);
|
||||||
const pathWithoutExtension = file.substring(0, file.length - specFileSuffix.length);
|
const mod = await import(`../../../${pathWithoutExtension}.spec.js`);
|
||||||
const mod = await import(`../../../${pathWithoutExtension}.spec.js`);
|
if (mod.d?.serialize !== undefined) {
|
||||||
if (mod.d?.serialize !== undefined) {
|
const cacheable = mod.d as Cacheable<unknown>;
|
||||||
const cacheable = mod.d as Cacheable<unknown>;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// Check for collisions
|
// Check for collisions
|
||||||
const existing = cacheablePathToTS.get(cacheable.path);
|
const existing = cacheablePathToTS.get(cacheable.path);
|
||||||
if (existing !== undefined) {
|
if (existing !== undefined) {
|
||||||
console.error(
|
console.error(
|
||||||
`error: Cacheable '${cacheable.path}' is emitted by both:
|
`error: Cacheable '${cacheable.path}' is emitted by both:
|
||||||
'${existing}'
|
'${existing}'
|
||||||
and
|
and
|
||||||
'${file}'`
|
'${file}'`
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
|
||||||
cacheablePathToTS.set(cacheable.path, file);
|
|
||||||
}
|
}
|
||||||
|
cacheablePathToTS.set(cacheable.path, file);
|
||||||
|
}
|
||||||
|
|
||||||
const outPath = `${outRootDir}/data/${cacheable.path}`;
|
const outPath = `${outRootDir}/data/${cacheable.path}`;
|
||||||
|
|
||||||
|
if (fileIndex++ % nth.n === nth.i) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 'emit': {
|
case 'emit': {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
@@ -141,13 +180,17 @@ and
|
|||||||
const data = await cacheable.build();
|
const data = await cacheable.build();
|
||||||
const serialized = cacheable.serialize(data);
|
const serialized = cacheable.serialize(data);
|
||||||
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
||||||
fs.writeFileSync(outPath, serialized);
|
fs.writeFileSync(outPath, serialized, 'binary');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'list': {
|
case 'list': {
|
||||||
console.log(outPath);
|
console.log(outPath);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'validate': {
|
||||||
|
// Only check currently performed is the collision detection above
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ How to generate TIMING_LOG_FILES files:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const kHeader = `{
|
const kHeader = `{
|
||||||
"_comment": "SEMI AUTO-GENERATED: Please read tools/merge_listing_times.",
|
"_comment": "SEMI AUTO-GENERATED: Please read docs/adding_timing_metadata.md.",
|
||||||
`;
|
`;
|
||||||
const kFooter = `\
|
const kFooter = `\
|
||||||
"_end": ""
|
"_end": ""
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { DefaultTestFileLoader } from '../internal/file_loader.js';
|
|
||||||
import { parseQuery } from '../internal/query/parseQuery.js';
|
|
||||||
import { assert } from '../util/util.js';
|
|
||||||
|
|
||||||
void (async () => {
|
|
||||||
for (const suite of ['unittests', 'webgpu']) {
|
|
||||||
const loader = new DefaultTestFileLoader();
|
|
||||||
const filterQuery = parseQuery(`${suite}:*`);
|
|
||||||
const testcases = await loader.loadCases(filterQuery);
|
|
||||||
for (const testcase of testcases) {
|
|
||||||
const name = testcase.query.toString();
|
|
||||||
const maxLength = 375;
|
|
||||||
assert(
|
|
||||||
name.length <= maxLength,
|
|
||||||
`Testcase ${name} is too long. Max length is ${maxLength} characters. Please shorten names or reduce parameters.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
@@ -11,6 +11,7 @@ For each suite in SUITE_DIRS, validate some properties about the file:
|
|||||||
- Has a test function (or is marked unimplemented)
|
- Has a test function (or is marked unimplemented)
|
||||||
- Has no duplicate cases
|
- Has no duplicate cases
|
||||||
- Configures batching correctly, if used
|
- Configures batching correctly, if used
|
||||||
|
- That each case query is not too long
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
tools/validate src/unittests/ src/webgpu/
|
tools/validate src/unittests/ src/webgpu/
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import { ResolveType, ZipKeysWithValues } from './types.js';
|
|||||||
export type valueof<K> = K[keyof K];
|
export type valueof<K> = K[keyof K];
|
||||||
|
|
||||||
export function keysOf<T extends string>(obj: { [k in T]: unknown }): readonly T[] {
|
export function keysOf<T extends string>(obj: { [k in T]: unknown }): readonly T[] {
|
||||||
return (Object.keys(obj) as unknown[]) as T[];
|
return Object.keys(obj) as unknown[] as T[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function numericKeysOf<T>(obj: object): readonly T[] {
|
export function numericKeysOf<T>(obj: object): readonly T[] {
|
||||||
return (Object.keys(obj).map(n => Number(n)) as unknown[]) as T[];
|
return Object.keys(obj).map(n => Number(n)) as unknown[] as T[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns a new Record from @p objects, using the string returned by Object.toString() as the keys
|
* @returns a new Record from `objects`, using the string returned by Object.toString() as the keys
|
||||||
* and the objects as the values.
|
* and the objects as the values.
|
||||||
*/
|
*/
|
||||||
export function objectsToRecord<T extends Object>(objects: readonly T[]): Record<string, T> {
|
export function objectsToRecord<T extends Object>(objects: readonly T[]): Record<string, T> {
|
||||||
@@ -32,7 +32,7 @@ export function objectsToRecord<T extends Object>(objects: readonly T[]): Record
|
|||||||
export function makeTable<
|
export function makeTable<
|
||||||
Members extends readonly string[],
|
Members extends readonly string[],
|
||||||
Defaults extends readonly unknown[],
|
Defaults extends readonly unknown[],
|
||||||
Table extends { readonly [k: string]: readonly unknown[] }
|
Table extends { readonly [k: string]: readonly unknown[] },
|
||||||
>(
|
>(
|
||||||
members: Members,
|
members: Members,
|
||||||
defaults: Defaults,
|
defaults: Defaults,
|
||||||
@@ -51,3 +51,79 @@ export function makeTable<
|
|||||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
return result as any;
|
return result as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an info lookup object from a more nicely-formatted table.
|
||||||
|
*
|
||||||
|
* Note: Using `as const` on the arguments to this function is necessary to infer the correct type.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* const t = makeTableWithDefaults(
|
||||||
|
* { c: 'default' }, // columnRenames
|
||||||
|
* ['a', 'default', 'd'], // columnsKept
|
||||||
|
* ['a', 'b', 'c', 'd'], // columns
|
||||||
|
* [123, 456, 789, 1011], // defaults
|
||||||
|
* { // table
|
||||||
|
* foo: [1, 2, 3, 4],
|
||||||
|
* bar: [5, , , 8],
|
||||||
|
* moo: [ , 9,10, ],
|
||||||
|
* }
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* // t = {
|
||||||
|
* // foo: { a: 1, default: 3, d: 4 },
|
||||||
|
* // bar: { a: 5, default: 789, d: 8 },
|
||||||
|
* // moo: { a: 123, default: 10, d: 1011 },
|
||||||
|
* // };
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* MAINTENANCE_TODO: `ZipKeysWithValues<Members, Table[k], Defaults>` is incorrect
|
||||||
|
* because Members no longer maps to Table[k]. It's not clear if this is even possible to fix
|
||||||
|
* because it requires mapping, not zipping. Maybe passing in a index mapping
|
||||||
|
* would fix it (which is gross) but if you have columnsKept as [0, 2, 3] then maybe it would
|
||||||
|
* be possible to generate the correct type? I don't think we can generate the map at compile time
|
||||||
|
* so we'd have to hand code it. Other ideas, don't generate kLimitsInfoCore and kLimitsInfoCompat
|
||||||
|
* where they are keys of infos. Instead, generate kLimitsInfoCoreDefaults, kLimitsInfoCoreMaximums,
|
||||||
|
* kLimitsInfoCoreClasses where each is just a `{[k: string]: type}`. Could zip those after or,
|
||||||
|
* maybe that suggests passing in the hard coded indices would work.
|
||||||
|
*
|
||||||
|
* @param columnRenames the name of the column in the table that will be assigned to the 'default' property of each entry.
|
||||||
|
* @param columnsKept the names of properties you want in the generated lookup table. This must be a subset of the columns of the tables except for the name 'default' which is looked from the previous argument.
|
||||||
|
* @param columns the names of the columns of the name
|
||||||
|
* @param defaults the default value by column for any element in a row of the table that is undefined
|
||||||
|
* @param table named table rows.
|
||||||
|
*/
|
||||||
|
export function makeTableRenameAndFilter<
|
||||||
|
Members extends readonly string[],
|
||||||
|
DataMembers extends readonly string[],
|
||||||
|
Defaults extends readonly unknown[],
|
||||||
|
Table extends { readonly [k: string]: readonly unknown[] },
|
||||||
|
>(
|
||||||
|
columnRenames: { [key: string]: string },
|
||||||
|
columnsKept: Members,
|
||||||
|
columns: DataMembers,
|
||||||
|
defaults: Defaults,
|
||||||
|
table: Table
|
||||||
|
): {
|
||||||
|
readonly [k in keyof Table]: ResolveType<ZipKeysWithValues<Members, Table[k], Defaults>>;
|
||||||
|
} {
|
||||||
|
const result: { [k: string]: { [m: string]: unknown } } = {};
|
||||||
|
const keyToIndex = new Map<string, number>(
|
||||||
|
columnsKept.map(name => {
|
||||||
|
const remappedName = columnRenames[name] === undefined ? name : columnRenames[name];
|
||||||
|
return [name, columns.indexOf(remappedName)];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
for (const [k, v] of Object.entries<readonly unknown[]>(table)) {
|
||||||
|
const item: { [m: string]: unknown } = {};
|
||||||
|
for (const member of columnsKept) {
|
||||||
|
const ndx = keyToIndex.get(member)!;
|
||||||
|
item[member] = v[ndx] ?? defaults[ndx];
|
||||||
|
}
|
||||||
|
result[k] = item;
|
||||||
|
}
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
|
return result as any;
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class If extends Directive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ElseIf extends If {
|
class ElseIf extends If {
|
||||||
applyTo(stack: StateStack) {
|
override applyTo(stack: StateStack) {
|
||||||
assert(stack.length >= 1);
|
assert(stack.length >= 1);
|
||||||
const { allowsFollowingElse, state: siblingState } = stack.pop()!;
|
const { allowsFollowingElse, state: siblingState } = stack.pop()!;
|
||||||
this.checkDepth(stack);
|
this.checkDepth(stack);
|
||||||
|
|||||||
@@ -11,7 +11,46 @@ export type TypeEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T
|
|||||||
: false;
|
: false;
|
||||||
|
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||||
export function assertTypeTrue<T extends true>() {}
|
export function assertTypeTrue<_ extends true>() {}
|
||||||
|
|
||||||
|
/** `ReadonlyArray` of `ReadonlyArray`s. */
|
||||||
|
export type ROArrayArray<T> = ReadonlyArray<ReadonlyArray<T>>;
|
||||||
|
/** `ReadonlyArray` of `ReadonlyArray`s of `ReadonlyArray`s. */
|
||||||
|
export type ROArrayArrayArray<T> = ReadonlyArray<ReadonlyArray<ReadonlyArray<T>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep version of the Readonly<> type, with support for tuples (up to length 7).
|
||||||
|
* <https://gist.github.com/masterkidan/7322752f569b1bba53e0426266768623>
|
||||||
|
*/
|
||||||
|
export type DeepReadonly<T> = T extends [infer A]
|
||||||
|
? DeepReadonlyObject<[A]>
|
||||||
|
: T extends [infer A, infer B]
|
||||||
|
? DeepReadonlyObject<[A, B]>
|
||||||
|
: T extends [infer A, infer B, infer C]
|
||||||
|
? DeepReadonlyObject<[A, B, C]>
|
||||||
|
: T extends [infer A, infer B, infer C, infer D]
|
||||||
|
? DeepReadonlyObject<[A, B, C, D]>
|
||||||
|
: T extends [infer A, infer B, infer C, infer D, infer E]
|
||||||
|
? DeepReadonlyObject<[A, B, C, D, E]>
|
||||||
|
: T extends [infer A, infer B, infer C, infer D, infer E, infer F]
|
||||||
|
? DeepReadonlyObject<[A, B, C, D, E, F]>
|
||||||
|
: T extends [infer A, infer B, infer C, infer D, infer E, infer F, infer G]
|
||||||
|
? DeepReadonlyObject<[A, B, C, D, E, F, G]>
|
||||||
|
: T extends Map<infer U, infer V>
|
||||||
|
? ReadonlyMap<DeepReadonlyObject<U>, DeepReadonlyObject<V>>
|
||||||
|
: T extends Set<infer U>
|
||||||
|
? ReadonlySet<DeepReadonlyObject<U>>
|
||||||
|
: T extends Promise<infer U>
|
||||||
|
? Promise<DeepReadonlyObject<U>>
|
||||||
|
: T extends Primitive
|
||||||
|
? T
|
||||||
|
: T extends (infer A)[]
|
||||||
|
? DeepReadonlyArray<A>
|
||||||
|
: DeepReadonlyObject<T>;
|
||||||
|
|
||||||
|
type Primitive = string | number | boolean | undefined | null | Function | symbol;
|
||||||
|
type DeepReadonlyArray<T> = ReadonlyArray<DeepReadonly<T>>;
|
||||||
|
type DeepReadonlyObject<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the intersection of a set of types, given the union of those types.
|
* Computes the intersection of a set of types, given the union of those types.
|
||||||
@@ -41,7 +80,7 @@ type TypeOr<T, Default> = T extends undefined ? Default : T;
|
|||||||
export type ZipKeysWithValues<
|
export type ZipKeysWithValues<
|
||||||
Keys extends readonly string[],
|
Keys extends readonly string[],
|
||||||
Values extends readonly unknown[],
|
Values extends readonly unknown[],
|
||||||
Defaults extends readonly unknown[]
|
Defaults extends readonly unknown[],
|
||||||
> =
|
> =
|
||||||
//
|
//
|
||||||
Keys extends readonly [infer KHead, ...infer KTail]
|
Keys extends readonly [infer KHead, ...infer KTail]
|
||||||
@@ -50,10 +89,9 @@ export type ZipKeysWithValues<
|
|||||||
TupleHeadOr<Values, undefined>,
|
TupleHeadOr<Values, undefined>,
|
||||||
TupleHeadOr<Defaults, undefined>
|
TupleHeadOr<Defaults, undefined>
|
||||||
>;
|
>;
|
||||||
} &
|
} & ZipKeysWithValues<
|
||||||
ZipKeysWithValues<
|
EnsureSubtype<KTail, readonly string[]>,
|
||||||
EnsureSubtype<KTail, readonly string[]>,
|
TupleTailOr<Values, []>,
|
||||||
TupleTailOr<Values, []>,
|
TupleTailOr<Defaults, []>
|
||||||
TupleTailOr<Defaults, []>
|
>
|
||||||
>
|
|
||||||
: {}; // K exhausted
|
: {}; // K exhausted
|
||||||
|
|||||||
@@ -47,15 +47,29 @@ export function assertOK<T>(value: Error | T): T {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Options for assertReject, shouldReject, and friends. */
|
||||||
|
export type ExceptionCheckOptions = { allowMissingStack?: boolean; message?: string };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves if the provided promise rejects; rejects if it does not.
|
* Resolves if the provided promise rejects; rejects if it does not.
|
||||||
*/
|
*/
|
||||||
export async function assertReject(p: Promise<unknown>, msg?: string): Promise<void> {
|
export async function assertReject(
|
||||||
|
expectedName: string,
|
||||||
|
p: Promise<unknown>,
|
||||||
|
{ allowMissingStack = false, message }: ExceptionCheckOptions = {}
|
||||||
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await p;
|
await p;
|
||||||
unreachable(msg);
|
unreachable(message);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
// Assertion OK
|
// Asserted as expected
|
||||||
|
if (!allowMissingStack) {
|
||||||
|
const m = message ? ` (${message})` : '';
|
||||||
|
assert(
|
||||||
|
ex instanceof Error && typeof ex.stack === 'string',
|
||||||
|
'threw as expected, but missing stack' + m
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +160,7 @@ export function assertNotSettledWithinTime(
|
|||||||
const handle = timeout(() => {
|
const handle = timeout(() => {
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
}, ms);
|
}, ms);
|
||||||
p.finally(() => clearTimeout(handle));
|
void p.finally(() => clearTimeout(handle));
|
||||||
});
|
});
|
||||||
return Promise.race([rejectWhenSettled, timeoutPromise]);
|
return Promise.race([rejectWhenSettled, timeoutPromise]);
|
||||||
}
|
}
|
||||||
@@ -182,14 +196,24 @@ export function sortObjectByKey(v: { [k: string]: unknown }): { [k: string]: unk
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether two JS values are equal, recursing into objects and arrays.
|
* Determines whether two JS values are equal, recursing into objects and arrays.
|
||||||
* NaN is treated specially, such that `objectEquals(NaN, NaN)`.
|
* NaN is treated specially, such that `objectEquals(NaN, NaN)`. +/-0.0 are treated as equal
|
||||||
|
* by default, but can be opted to be distinguished.
|
||||||
|
* @param x the first JS values that get compared
|
||||||
|
* @param y the second JS values that get compared
|
||||||
|
* @param distinguishSignedZero if set to true, treat 0.0 and -0.0 as unequal. Default to false.
|
||||||
*/
|
*/
|
||||||
export function objectEquals(x: unknown, y: unknown): boolean {
|
export function objectEquals(
|
||||||
|
x: unknown,
|
||||||
|
y: unknown,
|
||||||
|
distinguishSignedZero: boolean = false
|
||||||
|
): boolean {
|
||||||
if (typeof x !== 'object' || typeof y !== 'object') {
|
if (typeof x !== 'object' || typeof y !== 'object') {
|
||||||
if (typeof x === 'number' && typeof y === 'number' && Number.isNaN(x) && Number.isNaN(y)) {
|
if (typeof x === 'number' && typeof y === 'number' && Number.isNaN(x) && Number.isNaN(y)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return x === y;
|
// Object.is(0.0, -0.0) is false while (0.0 === -0.0) is true. Other than +/-0.0 and NaN cases,
|
||||||
|
// Object.is works in the same way as ===.
|
||||||
|
return distinguishSignedZero ? Object.is(x, y) : x === y;
|
||||||
}
|
}
|
||||||
if (x === null || y === null) return x === y;
|
if (x === null || y === null) return x === y;
|
||||||
if (x.constructor !== y.constructor) return false;
|
if (x.constructor !== y.constructor) return false;
|
||||||
@@ -282,28 +306,27 @@ const TypedArrayBufferViewInstances = [
|
|||||||
new Float64Array(),
|
new Float64Array(),
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type TypedArrayBufferView = typeof TypedArrayBufferViewInstances[number];
|
export type TypedArrayBufferView = (typeof TypedArrayBufferViewInstances)[number];
|
||||||
|
|
||||||
export type TypedArrayBufferViewConstructor<
|
export type TypedArrayBufferViewConstructor<A extends TypedArrayBufferView = TypedArrayBufferView> =
|
||||||
A extends TypedArrayBufferView = TypedArrayBufferView
|
{
|
||||||
> = {
|
// Interface copied from Uint8Array, and made generic.
|
||||||
// Interface copied from Uint8Array, and made generic.
|
readonly prototype: A;
|
||||||
readonly prototype: A;
|
readonly BYTES_PER_ELEMENT: number;
|
||||||
readonly BYTES_PER_ELEMENT: number;
|
|
||||||
|
|
||||||
new (): A;
|
new (): A;
|
||||||
new (elements: Iterable<number>): A;
|
new (elements: Iterable<number>): A;
|
||||||
new (array: ArrayLike<number> | ArrayBufferLike): A;
|
new (array: ArrayLike<number> | ArrayBufferLike): A;
|
||||||
new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): A;
|
new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): A;
|
||||||
new (length: number): A;
|
new (length: number): A;
|
||||||
|
|
||||||
from(arrayLike: ArrayLike<number>): A;
|
from(arrayLike: ArrayLike<number>): A;
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
from(arrayLike: Iterable<number>, mapfn?: (v: number, k: number) => number, thisArg?: any): A;
|
from(arrayLike: Iterable<number>, mapfn?: (v: number, k: number) => number, thisArg?: any): A;
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
from<T>(arrayLike: ArrayLike<T>, mapfn: (v: T, k: number) => number, thisArg?: any): A;
|
from<T>(arrayLike: ArrayLike<T>, mapfn: (v: T, k: number) => number, thisArg?: any): A;
|
||||||
of(...items: number[]): A;
|
of(...items: number[]): A;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const kTypedArrayBufferViews: {
|
export const kTypedArrayBufferViews: {
|
||||||
readonly [k: string]: TypedArrayBufferViewConstructor;
|
readonly [k: string]: TypedArrayBufferViewConstructor;
|
||||||
@@ -336,7 +359,7 @@ interface TypedArrayMap {
|
|||||||
|
|
||||||
type TypedArrayParam<K extends keyof TypedArrayMap> = {
|
type TypedArrayParam<K extends keyof TypedArrayMap> = {
|
||||||
type: K;
|
type: K;
|
||||||
data: number[];
|
data: readonly number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -377,7 +400,7 @@ export function typedArrayParam<K extends keyof TypedArrayMap>(
|
|||||||
|
|
||||||
export function createTypedArray<K extends keyof TypedArrayMap>(
|
export function createTypedArray<K extends keyof TypedArrayMap>(
|
||||||
type: K,
|
type: K,
|
||||||
data: number[]
|
data: readonly number[]
|
||||||
): TypedArrayMap[K] {
|
): TypedArrayMap[K] {
|
||||||
return new kTypedArrayBufferViews[type](data) as TypedArrayMap[K];
|
return new kTypedArrayBufferViews[type](data) as TypedArrayMap[K];
|
||||||
}
|
}
|
||||||
@@ -423,3 +446,31 @@ export function memcpy(
|
|||||||
): void {
|
): void {
|
||||||
subarrayAsU8(dst.dst, dst).set(subarrayAsU8(src.src, src));
|
subarrayAsU8(dst.dst, dst).set(subarrayAsU8(src.src, src));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to create a value that is specified by multiplying some runtime value
|
||||||
|
* by a constant and then adding a constant to it.
|
||||||
|
*/
|
||||||
|
export interface ValueTestVariant {
|
||||||
|
mult: number;
|
||||||
|
add: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters out SpecValues that are the same.
|
||||||
|
*/
|
||||||
|
export function filterUniqueValueTestVariants(valueTestVariants: ValueTestVariant[]) {
|
||||||
|
return new Map<string, ValueTestVariant>(
|
||||||
|
valueTestVariants.map(v => [`m:${v.mult},a:${v.add}`, v])
|
||||||
|
).values();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to create a value that is specified by multiplied some runtime value
|
||||||
|
* by a constant and then adding a constant to it. This happens often in test
|
||||||
|
* with limits that can only be known at runtime and yet we need a way to
|
||||||
|
* add parameters to a test and those parameters must be constants.
|
||||||
|
*/
|
||||||
|
export function makeValueTestVariant(base: number, variant: ValueTestVariant) {
|
||||||
|
return base * variant.mult + variant.add;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ import { attemptGarbageCollection } from '../../common/util/collect_garbage.js';
|
|||||||
import { keysOf } from '../../common/util/data_tables.js';
|
import { keysOf } from '../../common/util/data_tables.js';
|
||||||
import { getGPU } from '../../common/util/navigator_gpu.js';
|
import { getGPU } from '../../common/util/navigator_gpu.js';
|
||||||
import { assert, iterRange } from '../../common/util/util.js';
|
import { assert, iterRange } from '../../common/util/util.js';
|
||||||
import { kLimitInfo } from '../../webgpu/capability_info.js';
|
import { getDefaultLimitsForAdapter } from '../../webgpu/capability_info.js';
|
||||||
|
|
||||||
export const g = makeTestGroup(Fixture);
|
export const g = makeTestGroup(Fixture);
|
||||||
|
|
||||||
/** Adapter preference identifier to option. */
|
/** Adapter preference identifier to option. */
|
||||||
const kAdapterTypeOptions: {
|
const kAdapterTypeOptions: {
|
||||||
readonly [k in GPUPowerPreference | 'fallback']: GPURequestAdapterOptions;
|
readonly [k in GPUPowerPreference | 'fallback']: GPURequestAdapterOptions;
|
||||||
} = /* prettier-ignore */ {
|
} =
|
||||||
|
/* prettier-ignore */ {
|
||||||
'low-power': { powerPreference: 'low-power', forceFallbackAdapter: false },
|
'low-power': { powerPreference: 'low-power', forceFallbackAdapter: false },
|
||||||
'high-performance': { powerPreference: 'high-performance', forceFallbackAdapter: false },
|
'high-performance': { powerPreference: 'high-performance', forceFallbackAdapter: false },
|
||||||
'fallback': { powerPreference: undefined, forceFallbackAdapter: true },
|
'fallback': { powerPreference: undefined, forceFallbackAdapter: true },
|
||||||
@@ -33,10 +34,11 @@ async function createDeviceAndComputeCommands(adapter: GPUAdapter) {
|
|||||||
// Constants are computed such that per run, this function should allocate roughly 2G
|
// 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
|
// 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.
|
// times. If the data backing the created objects is not recycled we should OOM.
|
||||||
|
const limitInfo = getDefaultLimitsForAdapter(adapter);
|
||||||
const kNumPipelines = 64;
|
const kNumPipelines = 64;
|
||||||
const kNumBindgroups = 128;
|
const kNumBindgroups = 128;
|
||||||
const kNumBufferElements =
|
const kNumBufferElements =
|
||||||
kLimitInfo.maxComputeWorkgroupSizeX.default * kLimitInfo.maxComputeWorkgroupSizeY.default;
|
limitInfo.maxComputeWorkgroupSizeX.default * limitInfo.maxComputeWorkgroupSizeY.default;
|
||||||
const kBufferSize = kNumBufferElements * 4;
|
const kBufferSize = kNumBufferElements * 4;
|
||||||
const kBufferData = new Uint32Array([...iterRange(kNumBufferElements, x => x)]);
|
const kBufferData = new Uint32Array([...iterRange(kNumBufferElements, x => x)]);
|
||||||
|
|
||||||
@@ -54,8 +56,8 @@ async function createDeviceAndComputeCommands(adapter: GPUAdapter) {
|
|||||||
@group(0) @binding(0) var<storage, read_write> buffer: Buffer;
|
@group(0) @binding(0) var<storage, read_write> buffer: Buffer;
|
||||||
@compute @workgroup_size(1) fn main(
|
@compute @workgroup_size(1) fn main(
|
||||||
@builtin(global_invocation_id) id: vec3<u32>) {
|
@builtin(global_invocation_id) id: vec3<u32>) {
|
||||||
buffer.data[id.x * ${kLimitInfo.maxComputeWorkgroupSizeX.default}u + id.y] =
|
buffer.data[id.x * ${limitInfo.maxComputeWorkgroupSizeX.default}u + id.y] =
|
||||||
buffer.data[id.x * ${kLimitInfo.maxComputeWorkgroupSizeX.default}u + id.y] +
|
buffer.data[id.x * ${limitInfo.maxComputeWorkgroupSizeX.default}u + id.y] +
|
||||||
${pipelineIndex}u;
|
${pipelineIndex}u;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -79,8 +81,8 @@ async function createDeviceAndComputeCommands(adapter: GPUAdapter) {
|
|||||||
pass.setPipeline(pipeline);
|
pass.setPipeline(pipeline);
|
||||||
pass.setBindGroup(0, bindgroup);
|
pass.setBindGroup(0, bindgroup);
|
||||||
pass.dispatchWorkgroups(
|
pass.dispatchWorkgroups(
|
||||||
kLimitInfo.maxComputeWorkgroupSizeX.default,
|
limitInfo.maxComputeWorkgroupSizeX.default,
|
||||||
kLimitInfo.maxComputeWorkgroupSizeY.default
|
limitInfo.maxComputeWorkgroupSizeY.default
|
||||||
);
|
);
|
||||||
pass.end();
|
pass.end();
|
||||||
commands.push(encoder.finish());
|
commands.push(encoder.finish());
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
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';
|
|
||||||
import { GPUTest } from '../../webgpu/gpu_test.js';
|
|
||||||
|
|
||||||
export const g = makeTestGroup(GPUTest);
|
|
||||||
|
|
||||||
g.test('render_pass_one_query_set')
|
|
||||||
.desc(
|
|
||||||
`Tests a huge number of pipeline statistics queries over a single query set in a
|
|
||||||
single render pass.`
|
|
||||||
)
|
|
||||||
.unimplemented();
|
|
||||||
|
|
||||||
g.test('render_pass_many_query_sets')
|
|
||||||
.desc(
|
|
||||||
`Tests a huge number of pipeline statistics queries over a huge number of query
|
|
||||||
sets in a single render pass.`
|
|
||||||
)
|
|
||||||
.unimplemented();
|
|
||||||
|
|
||||||
g.test('compute_pass_one_query_set')
|
|
||||||
.desc(
|
|
||||||
`Tests a huge number of pipeline statistics queries over a single query set in a
|
|
||||||
single compute pass.`
|
|
||||||
)
|
|
||||||
.unimplemented();
|
|
||||||
|
|
||||||
g.test('compute_pass_many_query_sets')
|
|
||||||
.desc(
|
|
||||||
`Tests a huge number of pipeline statistics queries over a huge number of query
|
|
||||||
sets in a single compute pass.`
|
|
||||||
)
|
|
||||||
.unimplemented();
|
|
||||||
@@ -11,10 +11,10 @@ import { TestGroupTest } from './test_group_test.js';
|
|||||||
import { UnitTest } from './unit_test.js';
|
import { UnitTest } from './unit_test.js';
|
||||||
|
|
||||||
class FixtureToTest extends UnitTest {
|
class FixtureToTest extends UnitTest {
|
||||||
public immediateAsyncExpectation<T>(fn: () => Promise<T>): Promise<T> {
|
public override immediateAsyncExpectation<T>(fn: () => Promise<T>): Promise<T> {
|
||||||
return super.immediateAsyncExpectation(fn);
|
return super.immediateAsyncExpectation(fn);
|
||||||
}
|
}
|
||||||
public eventualAsyncExpectation<T>(fn: (niceStack: Error) => Promise<T>): void {
|
public override eventualAsyncExpectation<T>(fn: (niceStack: Error) => Promise<T>): void {
|
||||||
super.eventualAsyncExpectation(fn);
|
super.eventualAsyncExpectation(fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { UnitTest } from './unit_test.js';
|
|||||||
|
|
||||||
export const g = makeTestGroup(UnitTest);
|
export const g = makeTestGroup(UnitTest);
|
||||||
|
|
||||||
g.test('test,sync').fn(t => {});
|
g.test('test,sync').fn(_t => {});
|
||||||
|
|
||||||
g.test('test,async').fn(async t => {});
|
g.test('test,async').fn(async _t => {});
|
||||||
|
|
||||||
g.test('test_with_params,sync')
|
g.test('test_with_params,sync')
|
||||||
.paramsSimple([{}])
|
.paramsSimple([{}])
|
||||||
|
|||||||
@@ -428,15 +428,15 @@ g.test('pack2x16float')
|
|||||||
|
|
||||||
// f32 subnormals
|
// f32 subnormals
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
{ inputs: [kValue.f32.subnormal.positive.max, 1], result: [0x3c000000, 0x3c008000, 0x3c000001] },
|
{ inputs: [kValue.f32.positive.subnormal.max, 1], result: [0x3c000000, 0x3c008000, 0x3c000001] },
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
{ inputs: [kValue.f32.subnormal.negative.min, 1], result: [0x3c008001, 0x3c000000, 0x3c008000] },
|
{ inputs: [kValue.f32.negative.subnormal.min, 1], result: [0x3c008001, 0x3c000000, 0x3c008000] },
|
||||||
|
|
||||||
// f16 subnormals
|
// f16 subnormals
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
{ inputs: [kValue.f16.subnormal.positive.max, 1], result: [0x3c0003ff, 0x3c000000, 0x3c008000] },
|
{ inputs: [kValue.f16.positive.subnormal.max, 1], result: [0x3c0003ff, 0x3c000000, 0x3c008000] },
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
{ inputs: [kValue.f16.subnormal.negative.min, 1], result: [0x03c0083ff, 0x3c000000, 0x3c008000] },
|
{ inputs: [kValue.f16.negative.subnormal.min, 1], result: [0x03c0083ff, 0x3c000000, 0x3c008000] },
|
||||||
|
|
||||||
// f16 out of bounds
|
// f16 out of bounds
|
||||||
{ inputs: [kValue.f16.positive.max + 1, 1], result: [undefined] },
|
{ inputs: [kValue.f16.positive.max + 1, 1], result: [undefined] },
|
||||||
@@ -481,8 +481,8 @@ g.test('pack2x16snorm')
|
|||||||
{ inputs: [-0.1, -0.5], result: 0xc001f333 },
|
{ inputs: [-0.1, -0.5], result: 0xc001f333 },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ inputs: [kValue.f32.subnormal.positive.max, 1], result: 0x7fff0000 },
|
{ inputs: [kValue.f32.positive.subnormal.max, 1], result: 0x7fff0000 },
|
||||||
{ inputs: [kValue.f32.subnormal.negative.min, 1], result: 0x7fff0000 },
|
{ inputs: [kValue.f32.negative.subnormal.min, 1], result: 0x7fff0000 },
|
||||||
] as const)
|
] as const)
|
||||||
.fn(test => {
|
.fn(test => {
|
||||||
const inputs = test.params.inputs;
|
const inputs = test.params.inputs;
|
||||||
@@ -506,7 +506,7 @@ g.test('pack2x16unorm')
|
|||||||
{ inputs: [10, 10], result: 0xffffffff },
|
{ inputs: [10, 10], result: 0xffffffff },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ inputs: [kValue.f32.subnormal.positive.max, 1], result: 0xffff0000 },
|
{ inputs: [kValue.f32.positive.subnormal.max, 1], result: 0xffff0000 },
|
||||||
] as const)
|
] as const)
|
||||||
.fn(test => {
|
.fn(test => {
|
||||||
const inputs = test.params.inputs;
|
const inputs = test.params.inputs;
|
||||||
@@ -542,8 +542,8 @@ g.test('pack4x8snorm')
|
|||||||
{ inputs: [-0.1, -0.5, -0.1, -0.5], result: 0xc1f3c1f3 },
|
{ inputs: [-0.1, -0.5, -0.1, -0.5], result: 0xc1f3c1f3 },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ inputs: [kValue.f32.subnormal.positive.max, 1, 1, 1], result: 0x7f7f7f00 },
|
{ inputs: [kValue.f32.positive.subnormal.max, 1, 1, 1], result: 0x7f7f7f00 },
|
||||||
{ inputs: [kValue.f32.subnormal.negative.min, 1, 1, 1], result: 0x7f7f7f00 },
|
{ inputs: [kValue.f32.negative.subnormal.min, 1, 1, 1], result: 0x7f7f7f00 },
|
||||||
] as const)
|
] as const)
|
||||||
.fn(test => {
|
.fn(test => {
|
||||||
const inputs = test.params.inputs;
|
const inputs = test.params.inputs;
|
||||||
@@ -570,7 +570,7 @@ g.test('pack4x8unorm')
|
|||||||
{ inputs: [0.1, 0.5, 0.1, 0.5], result: 0x801a801a },
|
{ inputs: [0.1, 0.5, 0.1, 0.5], result: 0x801a801a },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ inputs: [kValue.f32.subnormal.positive.max, 1, 1, 1], result: 0xffffff00 },
|
{ inputs: [kValue.f32.positive.subnormal.max, 1, 1, 1], result: 0xffffff00 },
|
||||||
] as const)
|
] as const)
|
||||||
.fn(test => {
|
.fn(test => {
|
||||||
const inputs = test.params.inputs;
|
const inputs = test.params.inputs;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -124,7 +124,7 @@ class LoadingTest extends UnitTest {
|
|||||||
if (!this.isListenersAdded) {
|
if (!this.isListenersAdded) {
|
||||||
this.isListenersAdded = true;
|
this.isListenersAdded = true;
|
||||||
this.loader.addEventListener('import', ev => this.events.push(ev.data.url));
|
this.loader.addEventListener('import', ev => this.events.push(ev.data.url));
|
||||||
this.loader.addEventListener('finish', ev => this.events.push(null));
|
this.loader.addEventListener('finish', _ev => this.events.push(null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -703,7 +703,10 @@ async function testIterateCollapsed(
|
|||||||
subqueriesToExpand: expectations,
|
subqueriesToExpand: expectations,
|
||||||
});
|
});
|
||||||
if (expectedResult === 'throws') {
|
if (expectedResult === 'throws') {
|
||||||
t.shouldReject('Error', treePromise, 'loadTree should have thrown Error');
|
t.shouldReject('Error', treePromise, {
|
||||||
|
// Some errors here use StacklessError to print nicer command line outputs.
|
||||||
|
allowMissingStack: true,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tree = await treePromise;
|
const tree = await treePromise;
|
||||||
|
|||||||
@@ -36,6 +36,18 @@ g.test('empty').fn(t => {
|
|||||||
t.expect(res.status === 'running');
|
t.expect(res.status === 'running');
|
||||||
rec.finish();
|
rec.finish();
|
||||||
|
|
||||||
|
t.expect(res.status === 'notrun');
|
||||||
|
t.expect(res.timems >= 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
g.test('passed').fn(t => {
|
||||||
|
const mylog = new Logger({ overrideDebugMode: true });
|
||||||
|
const [rec, res] = mylog.record('one');
|
||||||
|
|
||||||
|
rec.start();
|
||||||
|
rec.passed();
|
||||||
|
rec.finish();
|
||||||
|
|
||||||
t.expect(res.status === 'pass');
|
t.expect(res.status === 'pass');
|
||||||
t.expect(res.timems >= 0);
|
t.expect(res.timems >= 0);
|
||||||
});
|
});
|
||||||
@@ -59,13 +71,27 @@ g.test('skip').fn(t => {
|
|||||||
|
|
||||||
rec.start();
|
rec.start();
|
||||||
rec.skipped(new SkipTestCase());
|
rec.skipped(new SkipTestCase());
|
||||||
rec.debug(new Error('hello'));
|
|
||||||
rec.finish();
|
rec.finish();
|
||||||
|
|
||||||
t.expect(res.status === 'skip');
|
t.expect(res.status === 'skip');
|
||||||
t.expect(res.timems >= 0);
|
t.expect(res.timems >= 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Tests if there's some skips and at least one pass it's pass.
|
||||||
|
g.test('skip_pass').fn(t => {
|
||||||
|
const mylog = new Logger({ overrideDebugMode: true });
|
||||||
|
const [rec, res] = mylog.record('one');
|
||||||
|
|
||||||
|
rec.start();
|
||||||
|
rec.skipped(new SkipTestCase());
|
||||||
|
rec.debug(new Error('hello'));
|
||||||
|
rec.skipped(new SkipTestCase());
|
||||||
|
rec.finish();
|
||||||
|
|
||||||
|
t.expect(res.status === 'pass');
|
||||||
|
t.expect(res.timems >= 0);
|
||||||
|
});
|
||||||
|
|
||||||
g.test('warn').fn(t => {
|
g.test('warn').fn(t => {
|
||||||
const mylog = new Logger({ overrideDebugMode: true });
|
const mylog = new Logger({ overrideDebugMode: true });
|
||||||
const [rec, res] = mylog.record('one');
|
const [rec, res] = mylog.record('one');
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ import {
|
|||||||
fullF16Range,
|
fullF16Range,
|
||||||
fullF32Range,
|
fullF32Range,
|
||||||
fullI32Range,
|
fullI32Range,
|
||||||
reinterpretU16AsF16,
|
|
||||||
reinterpretU32AsF32,
|
|
||||||
reinterpretU64AsF64,
|
|
||||||
lerp,
|
lerp,
|
||||||
linearRange,
|
linearRange,
|
||||||
nextAfterF16,
|
nextAfterF16,
|
||||||
@@ -40,6 +37,11 @@ import {
|
|||||||
lerpBigInt,
|
lerpBigInt,
|
||||||
linearRangeBigInt,
|
linearRangeBigInt,
|
||||||
} from '../webgpu/util/math.js';
|
} from '../webgpu/util/math.js';
|
||||||
|
import {
|
||||||
|
reinterpretU16AsF16,
|
||||||
|
reinterpretU32AsF32,
|
||||||
|
reinterpretU64AsF64,
|
||||||
|
} from '../webgpu/util/reinterpret.js';
|
||||||
|
|
||||||
import { UnitTest } from './unit_test.js';
|
import { UnitTest } from './unit_test.js';
|
||||||
|
|
||||||
@@ -70,8 +72,8 @@ function withinOneULPF32(got: number, expected: number, mode: FlushMode): boolea
|
|||||||
* FTZ occur during comparison
|
* FTZ occur during comparison
|
||||||
**/
|
**/
|
||||||
function compareArrayOfNumbersF32(
|
function compareArrayOfNumbersF32(
|
||||||
got: Array<number>,
|
got: readonly number[],
|
||||||
expect: Array<number>,
|
expect: readonly number[],
|
||||||
mode: FlushMode = 'flush'
|
mode: FlushMode = 'flush'
|
||||||
): boolean {
|
): boolean {
|
||||||
return (
|
return (
|
||||||
@@ -108,10 +110,10 @@ g.test('nextAfterF64FlushToZero')
|
|||||||
// Edge Cases
|
// Edge Cases
|
||||||
{ val: Number.NaN, dir: 'positive', result: Number.NaN },
|
{ val: Number.NaN, dir: 'positive', result: Number.NaN },
|
||||||
{ val: Number.NaN, dir: 'negative', result: Number.NaN },
|
{ val: Number.NaN, dir: 'negative', result: Number.NaN },
|
||||||
{ val: Number.POSITIVE_INFINITY, dir: 'positive', result: kValue.f64.infinity.positive },
|
{ val: Number.POSITIVE_INFINITY, dir: 'positive', result: kValue.f64.positive.infinity },
|
||||||
{ val: Number.POSITIVE_INFINITY, dir: 'negative', result: kValue.f64.infinity.positive },
|
{ val: Number.POSITIVE_INFINITY, dir: 'negative', result: kValue.f64.positive.infinity },
|
||||||
{ val: Number.NEGATIVE_INFINITY, dir: 'positive', result: kValue.f64.infinity.negative },
|
{ val: Number.NEGATIVE_INFINITY, dir: 'positive', result: kValue.f64.negative.infinity },
|
||||||
{ val: Number.NEGATIVE_INFINITY, dir: 'negative', result: kValue.f64.infinity.negative },
|
{ val: Number.NEGATIVE_INFINITY, dir: 'negative', result: kValue.f64.negative.infinity },
|
||||||
|
|
||||||
// Zeroes
|
// Zeroes
|
||||||
{ val: +0, dir: 'positive', result: kValue.f64.positive.min },
|
{ val: +0, dir: 'positive', result: kValue.f64.positive.min },
|
||||||
@@ -120,24 +122,24 @@ g.test('nextAfterF64FlushToZero')
|
|||||||
{ val: -0, dir: 'negative', result: kValue.f64.negative.max },
|
{ val: -0, dir: 'negative', result: kValue.f64.negative.max },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ val: kValue.f64.subnormal.positive.min, dir: 'positive', result: kValue.f64.positive.min },
|
{ val: kValue.f64.positive.subnormal.min, dir: 'positive', result: kValue.f64.positive.min },
|
||||||
{ val: kValue.f64.subnormal.positive.min, dir: 'negative', result: kValue.f64.negative.max },
|
{ val: kValue.f64.positive.subnormal.min, dir: 'negative', result: kValue.f64.negative.max },
|
||||||
{ val: kValue.f64.subnormal.positive.max, dir: 'positive', result: kValue.f64.positive.min },
|
{ val: kValue.f64.positive.subnormal.max, dir: 'positive', result: kValue.f64.positive.min },
|
||||||
{ val: kValue.f64.subnormal.positive.max, dir: 'negative', result: kValue.f64.negative.max },
|
{ val: kValue.f64.positive.subnormal.max, dir: 'negative', result: kValue.f64.negative.max },
|
||||||
{ val: kValue.f64.subnormal.negative.min, dir: 'positive', result: kValue.f64.positive.min },
|
{ val: kValue.f64.negative.subnormal.min, dir: 'positive', result: kValue.f64.positive.min },
|
||||||
{ val: kValue.f64.subnormal.negative.min, dir: 'negative', result: kValue.f64.negative.max },
|
{ val: kValue.f64.negative.subnormal.min, dir: 'negative', result: kValue.f64.negative.max },
|
||||||
{ val: kValue.f64.subnormal.negative.max, dir: 'positive', result: kValue.f64.positive.min },
|
{ val: kValue.f64.negative.subnormal.max, dir: 'positive', result: kValue.f64.positive.min },
|
||||||
{ val: kValue.f64.subnormal.negative.max, dir: 'negative', result: kValue.f64.negative.max },
|
{ val: kValue.f64.negative.subnormal.max, dir: 'negative', result: kValue.f64.negative.max },
|
||||||
|
|
||||||
// Normals
|
// Normals
|
||||||
{ val: kValue.f64.positive.max, dir: 'positive', result: kValue.f64.infinity.positive },
|
{ val: kValue.f64.positive.max, dir: 'positive', result: kValue.f64.positive.infinity },
|
||||||
{ val: kValue.f64.positive.max, dir: 'negative', result: kValue.f64.positive.nearest_max },
|
{ val: kValue.f64.positive.max, dir: 'negative', result: kValue.f64.positive.nearest_max },
|
||||||
{ val: kValue.f64.positive.min, dir: 'positive', result: reinterpretU64AsF64(0x0010_0000_0000_0001n ) },
|
{ val: kValue.f64.positive.min, dir: 'positive', result: reinterpretU64AsF64(0x0010_0000_0000_0001n ) },
|
||||||
{ val: kValue.f64.positive.min, dir: 'negative', result: 0 },
|
{ val: kValue.f64.positive.min, dir: 'negative', result: 0 },
|
||||||
{ val: kValue.f64.negative.max, dir: 'positive', result: 0 },
|
{ val: kValue.f64.negative.max, dir: 'positive', result: 0 },
|
||||||
{ val: kValue.f64.negative.max, dir: 'negative', result: reinterpretU64AsF64(0x8010_0000_0000_0001n) },
|
{ val: kValue.f64.negative.max, dir: 'negative', result: reinterpretU64AsF64(0x8010_0000_0000_0001n) },
|
||||||
{ val: kValue.f64.negative.min, dir: 'positive', result: kValue.f64.negative.nearest_min },
|
{ val: kValue.f64.negative.min, dir: 'positive', result: kValue.f64.negative.nearest_min },
|
||||||
{ val: kValue.f64.negative.min, dir: 'negative', result: kValue.f64.infinity.negative },
|
{ val: kValue.f64.negative.min, dir: 'negative', result: kValue.f64.negative.infinity },
|
||||||
{ val: reinterpretU64AsF64(0x0380_0000_0000_0000n), dir: 'positive', result: reinterpretU64AsF64(0x0380_0000_0000_0001n) },
|
{ val: reinterpretU64AsF64(0x0380_0000_0000_0000n), dir: 'positive', result: reinterpretU64AsF64(0x0380_0000_0000_0001n) },
|
||||||
{ val: reinterpretU64AsF64(0x0380_0000_0000_0000n), dir: 'negative', result: reinterpretU64AsF64(0x037f_ffff_ffff_ffffn) },
|
{ val: reinterpretU64AsF64(0x0380_0000_0000_0000n), dir: 'negative', result: reinterpretU64AsF64(0x037f_ffff_ffff_ffffn) },
|
||||||
{ val: reinterpretU64AsF64(0x8380_0000_0000_0000n), dir: 'positive', result: reinterpretU64AsF64(0x837f_ffff_ffff_ffffn) },
|
{ val: reinterpretU64AsF64(0x8380_0000_0000_0000n), dir: 'positive', result: reinterpretU64AsF64(0x837f_ffff_ffff_ffffn) },
|
||||||
@@ -162,36 +164,36 @@ g.test('nextAfterF64NoFlush')
|
|||||||
// Edge Cases
|
// Edge Cases
|
||||||
{ val: Number.NaN, dir: 'positive', result: Number.NaN },
|
{ val: Number.NaN, dir: 'positive', result: Number.NaN },
|
||||||
{ val: Number.NaN, dir: 'negative', result: Number.NaN },
|
{ val: Number.NaN, dir: 'negative', result: Number.NaN },
|
||||||
{ val: Number.POSITIVE_INFINITY, dir: 'positive', result: kValue.f64.infinity.positive },
|
{ val: Number.POSITIVE_INFINITY, dir: 'positive', result: kValue.f64.positive.infinity },
|
||||||
{ val: Number.POSITIVE_INFINITY, dir: 'negative', result: kValue.f64.infinity.positive },
|
{ val: Number.POSITIVE_INFINITY, dir: 'negative', result: kValue.f64.positive.infinity },
|
||||||
{ val: Number.NEGATIVE_INFINITY, dir: 'positive', result: kValue.f64.infinity.negative },
|
{ val: Number.NEGATIVE_INFINITY, dir: 'positive', result: kValue.f64.negative.infinity },
|
||||||
{ val: Number.NEGATIVE_INFINITY, dir: 'negative', result: kValue.f64.infinity.negative },
|
{ val: Number.NEGATIVE_INFINITY, dir: 'negative', result: kValue.f64.negative.infinity },
|
||||||
|
|
||||||
// Zeroes
|
// Zeroes
|
||||||
{ val: +0, dir: 'positive', result: kValue.f64.subnormal.positive.min },
|
{ val: +0, dir: 'positive', result: kValue.f64.positive.subnormal.min },
|
||||||
{ val: +0, dir: 'negative', result: kValue.f64.subnormal.negative.max },
|
{ val: +0, dir: 'negative', result: kValue.f64.negative.subnormal.max },
|
||||||
{ val: -0, dir: 'positive', result: kValue.f64.subnormal.positive.min },
|
{ val: -0, dir: 'positive', result: kValue.f64.positive.subnormal.min },
|
||||||
{ val: -0, dir: 'negative', result: kValue.f64.subnormal.negative.max },
|
{ val: -0, dir: 'negative', result: kValue.f64.negative.subnormal.max },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ val: kValue.f64.subnormal.positive.min, dir: 'positive', result: reinterpretU64AsF64(0x0000_0000_0000_0002n) },
|
{ val: kValue.f64.positive.subnormal.min, dir: 'positive', result: reinterpretU64AsF64(0x0000_0000_0000_0002n) },
|
||||||
{ val: kValue.f64.subnormal.positive.min, dir: 'negative', result: 0 },
|
{ val: kValue.f64.positive.subnormal.min, dir: 'negative', result: 0 },
|
||||||
{ val: kValue.f64.subnormal.positive.max, dir: 'positive', result: kValue.f64.positive.min },
|
{ val: kValue.f64.positive.subnormal.max, dir: 'positive', result: kValue.f64.positive.min },
|
||||||
{ val: kValue.f64.subnormal.positive.max, dir: 'negative', result: reinterpretU64AsF64(0x000f_ffff_ffff_fffen) },
|
{ val: kValue.f64.positive.subnormal.max, dir: 'negative', result: reinterpretU64AsF64(0x000f_ffff_ffff_fffen) },
|
||||||
{ val: kValue.f64.subnormal.negative.min, dir: 'positive', result: reinterpretU64AsF64(0x800f_ffff_ffff_fffen) },
|
{ val: kValue.f64.negative.subnormal.min, dir: 'positive', result: reinterpretU64AsF64(0x800f_ffff_ffff_fffen) },
|
||||||
{ val: kValue.f64.subnormal.negative.min, dir: 'negative', result: kValue.f64.negative.max },
|
{ val: kValue.f64.negative.subnormal.min, dir: 'negative', result: kValue.f64.negative.max },
|
||||||
{ val: kValue.f64.subnormal.negative.max, dir: 'positive', result: 0 },
|
{ val: kValue.f64.negative.subnormal.max, dir: 'positive', result: 0 },
|
||||||
{ val: kValue.f64.subnormal.negative.max, dir: 'negative', result: reinterpretU64AsF64(0x8000_0000_0000_0002n) },
|
{ val: kValue.f64.negative.subnormal.max, dir: 'negative', result: reinterpretU64AsF64(0x8000_0000_0000_0002n) },
|
||||||
|
|
||||||
// Normals
|
// Normals
|
||||||
{ val: kValue.f64.positive.max, dir: 'positive', result: kValue.f64.infinity.positive },
|
{ val: kValue.f64.positive.max, dir: 'positive', result: kValue.f64.positive.infinity },
|
||||||
{ val: kValue.f64.positive.max, dir: 'negative', result: kValue.f64.positive.nearest_max },
|
{ val: kValue.f64.positive.max, dir: 'negative', result: kValue.f64.positive.nearest_max },
|
||||||
{ val: kValue.f64.positive.min, dir: 'positive', result: reinterpretU64AsF64(0x0010_0000_0000_0001n ) },
|
{ val: kValue.f64.positive.min, dir: 'positive', result: reinterpretU64AsF64(0x0010_0000_0000_0001n ) },
|
||||||
{ val: kValue.f64.positive.min, dir: 'negative', result: reinterpretU64AsF64(0x000f_ffff_ffff_ffffn) },
|
{ val: kValue.f64.positive.min, dir: 'negative', result: reinterpretU64AsF64(0x000f_ffff_ffff_ffffn) },
|
||||||
{ val: kValue.f64.negative.max, dir: 'positive', result: reinterpretU64AsF64(0x800f_ffff_ffff_ffffn) },
|
{ val: kValue.f64.negative.max, dir: 'positive', result: reinterpretU64AsF64(0x800f_ffff_ffff_ffffn) },
|
||||||
{ val: kValue.f64.negative.max, dir: 'negative', result: reinterpretU64AsF64(0x8010_0000_0000_0001n) },
|
{ val: kValue.f64.negative.max, dir: 'negative', result: reinterpretU64AsF64(0x8010_0000_0000_0001n) },
|
||||||
{ val: kValue.f64.negative.min, dir: 'positive', result: kValue.f64.negative.nearest_min },
|
{ val: kValue.f64.negative.min, dir: 'positive', result: kValue.f64.negative.nearest_min },
|
||||||
{ val: kValue.f64.negative.min, dir: 'negative', result: kValue.f64.infinity.negative },
|
{ val: kValue.f64.negative.min, dir: 'negative', result: kValue.f64.negative.infinity },
|
||||||
{ val: reinterpretU64AsF64(0x0380_0000_0000_0000n), dir: 'positive', result: reinterpretU64AsF64(0x0380_0000_0000_0001n) },
|
{ val: reinterpretU64AsF64(0x0380_0000_0000_0000n), dir: 'positive', result: reinterpretU64AsF64(0x0380_0000_0000_0001n) },
|
||||||
{ val: reinterpretU64AsF64(0x0380_0000_0000_0000n), dir: 'negative', result: reinterpretU64AsF64(0x037f_ffff_ffff_ffffn) },
|
{ val: reinterpretU64AsF64(0x0380_0000_0000_0000n), dir: 'negative', result: reinterpretU64AsF64(0x037f_ffff_ffff_ffffn) },
|
||||||
{ val: reinterpretU64AsF64(0x8380_0000_0000_0000n), dir: 'positive', result: reinterpretU64AsF64(0x837f_ffff_ffff_ffffn) },
|
{ val: reinterpretU64AsF64(0x8380_0000_0000_0000n), dir: 'positive', result: reinterpretU64AsF64(0x837f_ffff_ffff_ffffn) },
|
||||||
@@ -218,10 +220,10 @@ g.test('nextAfterF32FlushToZero')
|
|||||||
// Edge Cases
|
// Edge Cases
|
||||||
{ val: Number.NaN, dir: 'positive', result: Number.NaN },
|
{ val: Number.NaN, dir: 'positive', result: Number.NaN },
|
||||||
{ val: Number.NaN, dir: 'negative', result: Number.NaN },
|
{ val: Number.NaN, dir: 'negative', result: Number.NaN },
|
||||||
{ val: Number.POSITIVE_INFINITY, dir: 'positive', result: kValue.f32.infinity.positive },
|
{ val: Number.POSITIVE_INFINITY, dir: 'positive', result: kValue.f32.positive.infinity },
|
||||||
{ val: Number.POSITIVE_INFINITY, dir: 'negative', result: kValue.f32.infinity.positive },
|
{ val: Number.POSITIVE_INFINITY, dir: 'negative', result: kValue.f32.positive.infinity },
|
||||||
{ val: Number.NEGATIVE_INFINITY, dir: 'positive', result: kValue.f32.infinity.negative },
|
{ val: Number.NEGATIVE_INFINITY, dir: 'positive', result: kValue.f32.negative.infinity },
|
||||||
{ val: Number.NEGATIVE_INFINITY, dir: 'negative', result: kValue.f32.infinity.negative },
|
{ val: Number.NEGATIVE_INFINITY, dir: 'negative', result: kValue.f32.negative.infinity },
|
||||||
|
|
||||||
// Zeroes
|
// Zeroes
|
||||||
{ val: +0, dir: 'positive', result: kValue.f32.positive.min },
|
{ val: +0, dir: 'positive', result: kValue.f32.positive.min },
|
||||||
@@ -230,24 +232,24 @@ g.test('nextAfterF32FlushToZero')
|
|||||||
{ val: -0, dir: 'negative', result: kValue.f32.negative.max },
|
{ val: -0, dir: 'negative', result: kValue.f32.negative.max },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ val: kValue.f32.subnormal.positive.min, dir: 'positive', result: kValue.f32.positive.min },
|
{ val: kValue.f32.positive.subnormal.min, dir: 'positive', result: kValue.f32.positive.min },
|
||||||
{ val: kValue.f32.subnormal.positive.min, dir: 'negative', result: kValue.f32.negative.max },
|
{ val: kValue.f32.positive.subnormal.min, dir: 'negative', result: kValue.f32.negative.max },
|
||||||
{ val: kValue.f32.subnormal.positive.max, dir: 'positive', result: kValue.f32.positive.min },
|
{ val: kValue.f32.positive.subnormal.max, dir: 'positive', result: kValue.f32.positive.min },
|
||||||
{ val: kValue.f32.subnormal.positive.max, dir: 'negative', result: kValue.f32.negative.max },
|
{ val: kValue.f32.positive.subnormal.max, dir: 'negative', result: kValue.f32.negative.max },
|
||||||
{ val: kValue.f32.subnormal.negative.min, dir: 'positive', result: kValue.f32.positive.min },
|
{ val: kValue.f32.negative.subnormal.min, dir: 'positive', result: kValue.f32.positive.min },
|
||||||
{ val: kValue.f32.subnormal.negative.min, dir: 'negative', result: kValue.f32.negative.max },
|
{ val: kValue.f32.negative.subnormal.min, dir: 'negative', result: kValue.f32.negative.max },
|
||||||
{ val: kValue.f32.subnormal.negative.max, dir: 'positive', result: kValue.f32.positive.min },
|
{ val: kValue.f32.negative.subnormal.max, dir: 'positive', result: kValue.f32.positive.min },
|
||||||
{ val: kValue.f32.subnormal.negative.max, dir: 'negative', result: kValue.f32.negative.max },
|
{ val: kValue.f32.negative.subnormal.max, dir: 'negative', result: kValue.f32.negative.max },
|
||||||
|
|
||||||
// Normals
|
// Normals
|
||||||
{ val: kValue.f32.positive.max, dir: 'positive', result: kValue.f32.infinity.positive },
|
{ val: kValue.f32.positive.max, dir: 'positive', result: kValue.f32.positive.infinity },
|
||||||
{ val: kValue.f32.positive.max, dir: 'negative', result: kValue.f32.positive.nearest_max },
|
{ val: kValue.f32.positive.max, dir: 'negative', result: kValue.f32.positive.nearest_max },
|
||||||
{ val: kValue.f32.positive.min, dir: 'positive', result: reinterpretU32AsF32(0x00800001) },
|
{ val: kValue.f32.positive.min, dir: 'positive', result: reinterpretU32AsF32(0x00800001) },
|
||||||
{ val: kValue.f32.positive.min, dir: 'negative', result: 0 },
|
{ val: kValue.f32.positive.min, dir: 'negative', result: 0 },
|
||||||
{ val: kValue.f32.negative.max, dir: 'positive', result: 0 },
|
{ val: kValue.f32.negative.max, dir: 'positive', result: 0 },
|
||||||
{ val: kValue.f32.negative.max, dir: 'negative', result: reinterpretU32AsF32(0x80800001) },
|
{ val: kValue.f32.negative.max, dir: 'negative', result: reinterpretU32AsF32(0x80800001) },
|
||||||
{ val: kValue.f32.negative.min, dir: 'positive', result: reinterpretU32AsF32(0xff7ffffe) },
|
{ val: kValue.f32.negative.min, dir: 'positive', result: reinterpretU32AsF32(0xff7ffffe) },
|
||||||
{ val: kValue.f32.negative.min, dir: 'negative', result: kValue.f32.infinity.negative },
|
{ val: kValue.f32.negative.min, dir: 'negative', result: kValue.f32.negative.infinity },
|
||||||
{ val: reinterpretU32AsF32(0x03800000), dir: 'positive', result: reinterpretU32AsF32(0x03800001) },
|
{ val: reinterpretU32AsF32(0x03800000), dir: 'positive', result: reinterpretU32AsF32(0x03800001) },
|
||||||
{ val: reinterpretU32AsF32(0x03800000), dir: 'negative', result: reinterpretU32AsF32(0x037fffff) },
|
{ val: reinterpretU32AsF32(0x03800000), dir: 'negative', result: reinterpretU32AsF32(0x037fffff) },
|
||||||
{ val: reinterpretU32AsF32(0x83800000), dir: 'positive', result: reinterpretU32AsF32(0x837fffff) },
|
{ val: reinterpretU32AsF32(0x83800000), dir: 'positive', result: reinterpretU32AsF32(0x837fffff) },
|
||||||
@@ -282,36 +284,36 @@ g.test('nextAfterF32NoFlush')
|
|||||||
// Edge Cases
|
// Edge Cases
|
||||||
{ val: Number.NaN, dir: 'positive', result: Number.NaN },
|
{ val: Number.NaN, dir: 'positive', result: Number.NaN },
|
||||||
{ val: Number.NaN, dir: 'negative', result: Number.NaN },
|
{ val: Number.NaN, dir: 'negative', result: Number.NaN },
|
||||||
{ val: Number.POSITIVE_INFINITY, dir: 'positive', result: kValue.f32.infinity.positive },
|
{ val: Number.POSITIVE_INFINITY, dir: 'positive', result: kValue.f32.positive.infinity },
|
||||||
{ val: Number.POSITIVE_INFINITY, dir: 'negative', result: kValue.f32.infinity.positive },
|
{ val: Number.POSITIVE_INFINITY, dir: 'negative', result: kValue.f32.positive.infinity },
|
||||||
{ val: Number.NEGATIVE_INFINITY, dir: 'positive', result: kValue.f32.infinity.negative },
|
{ val: Number.NEGATIVE_INFINITY, dir: 'positive', result: kValue.f32.negative.infinity },
|
||||||
{ val: Number.NEGATIVE_INFINITY, dir: 'negative', result: kValue.f32.infinity.negative },
|
{ val: Number.NEGATIVE_INFINITY, dir: 'negative', result: kValue.f32.negative.infinity },
|
||||||
|
|
||||||
// Zeroes
|
// Zeroes
|
||||||
{ val: +0, dir: 'positive', result: kValue.f32.subnormal.positive.min },
|
{ val: +0, dir: 'positive', result: kValue.f32.positive.subnormal.min },
|
||||||
{ val: +0, dir: 'negative', result: kValue.f32.subnormal.negative.max },
|
{ val: +0, dir: 'negative', result: kValue.f32.negative.subnormal.max },
|
||||||
{ val: -0, dir: 'positive', result: kValue.f32.subnormal.positive.min },
|
{ val: -0, dir: 'positive', result: kValue.f32.positive.subnormal.min },
|
||||||
{ val: -0, dir: 'negative', result: kValue.f32.subnormal.negative.max },
|
{ val: -0, dir: 'negative', result: kValue.f32.negative.subnormal.max },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ val:kValue.f32.subnormal.positive.min, dir: 'positive', result: reinterpretU32AsF32(0x00000002) },
|
{ val:kValue.f32.positive.subnormal.min, dir: 'positive', result: reinterpretU32AsF32(0x00000002) },
|
||||||
{ val:kValue.f32.subnormal.positive.min, dir: 'negative', result: 0 },
|
{ val:kValue.f32.positive.subnormal.min, dir: 'negative', result: 0 },
|
||||||
{ val:kValue.f32.subnormal.positive.max, dir: 'positive', result: kValue.f32.positive.min },
|
{ val:kValue.f32.positive.subnormal.max, dir: 'positive', result: kValue.f32.positive.min },
|
||||||
{ val:kValue.f32.subnormal.positive.max, dir: 'negative', result: reinterpretU32AsF32(0x007ffffe) },
|
{ val:kValue.f32.positive.subnormal.max, dir: 'negative', result: reinterpretU32AsF32(0x007ffffe) },
|
||||||
{ val:kValue.f32.subnormal.negative.min, dir: 'positive', result: reinterpretU32AsF32(0x807ffffe) },
|
{ val:kValue.f32.negative.subnormal.min, dir: 'positive', result: reinterpretU32AsF32(0x807ffffe) },
|
||||||
{ val:kValue.f32.subnormal.negative.min, dir: 'negative', result: kValue.f32.negative.max },
|
{ val:kValue.f32.negative.subnormal.min, dir: 'negative', result: kValue.f32.negative.max },
|
||||||
{ val:kValue.f32.subnormal.negative.max, dir: 'positive', result: 0 },
|
{ val:kValue.f32.negative.subnormal.max, dir: 'positive', result: 0 },
|
||||||
{ val:kValue.f32.subnormal.negative.max, dir: 'negative', result: reinterpretU32AsF32(0x80000002) },
|
{ val:kValue.f32.negative.subnormal.max, dir: 'negative', result: reinterpretU32AsF32(0x80000002) },
|
||||||
|
|
||||||
// Normals
|
// Normals
|
||||||
{ val: kValue.f32.positive.max, dir: 'positive', result: kValue.f32.infinity.positive },
|
{ val: kValue.f32.positive.max, dir: 'positive', result: kValue.f32.positive.infinity },
|
||||||
{ val: kValue.f32.positive.max, dir: 'negative', result: kValue.f32.positive.nearest_max },
|
{ val: kValue.f32.positive.max, dir: 'negative', result: kValue.f32.positive.nearest_max },
|
||||||
{ val: kValue.f32.positive.min, dir: 'positive', result: reinterpretU32AsF32(0x00800001) },
|
{ val: kValue.f32.positive.min, dir: 'positive', result: reinterpretU32AsF32(0x00800001) },
|
||||||
{ val: kValue.f32.positive.min, dir: 'negative', result: kValue.f32.subnormal.positive.max },
|
{ val: kValue.f32.positive.min, dir: 'negative', result: kValue.f32.positive.subnormal.max },
|
||||||
{ val: kValue.f32.negative.max, dir: 'positive', result: kValue.f32.subnormal.negative.min },
|
{ val: kValue.f32.negative.max, dir: 'positive', result: kValue.f32.negative.subnormal.min },
|
||||||
{ val: kValue.f32.negative.max, dir: 'negative', result: reinterpretU32AsF32(0x80800001) },
|
{ val: kValue.f32.negative.max, dir: 'negative', result: reinterpretU32AsF32(0x80800001) },
|
||||||
{ val: kValue.f32.negative.min, dir: 'positive', result: kValue.f32.negative.nearest_min },
|
{ val: kValue.f32.negative.min, dir: 'positive', result: kValue.f32.negative.nearest_min },
|
||||||
{ val: kValue.f32.negative.min, dir: 'negative', result: kValue.f32.infinity.negative },
|
{ val: kValue.f32.negative.min, dir: 'negative', result: kValue.f32.negative.infinity },
|
||||||
{ val: reinterpretU32AsF32(0x03800000), dir: 'positive', result: reinterpretU32AsF32(0x03800001) },
|
{ val: reinterpretU32AsF32(0x03800000), dir: 'positive', result: reinterpretU32AsF32(0x03800001) },
|
||||||
{ val: reinterpretU32AsF32(0x03800000), dir: 'negative', result: reinterpretU32AsF32(0x037fffff) },
|
{ val: reinterpretU32AsF32(0x03800000), dir: 'negative', result: reinterpretU32AsF32(0x037fffff) },
|
||||||
{ val: reinterpretU32AsF32(0x83800000), dir: 'positive', result: reinterpretU32AsF32(0x837fffff) },
|
{ val: reinterpretU32AsF32(0x83800000), dir: 'positive', result: reinterpretU32AsF32(0x837fffff) },
|
||||||
@@ -348,10 +350,10 @@ g.test('nextAfterF16FlushToZero')
|
|||||||
// Edge Cases
|
// Edge Cases
|
||||||
{ val: Number.NaN, dir: 'positive', result: Number.NaN },
|
{ val: Number.NaN, dir: 'positive', result: Number.NaN },
|
||||||
{ val: Number.NaN, dir: 'negative', result: Number.NaN },
|
{ val: Number.NaN, dir: 'negative', result: Number.NaN },
|
||||||
{ val: Number.POSITIVE_INFINITY, dir: 'positive', result: kValue.f16.infinity.positive },
|
{ val: Number.POSITIVE_INFINITY, dir: 'positive', result: kValue.f16.positive.infinity },
|
||||||
{ val: Number.POSITIVE_INFINITY, dir: 'negative', result: kValue.f16.infinity.positive },
|
{ val: Number.POSITIVE_INFINITY, dir: 'negative', result: kValue.f16.positive.infinity },
|
||||||
{ val: Number.NEGATIVE_INFINITY, dir: 'positive', result: kValue.f16.infinity.negative },
|
{ val: Number.NEGATIVE_INFINITY, dir: 'positive', result: kValue.f16.negative.infinity },
|
||||||
{ val: Number.NEGATIVE_INFINITY, dir: 'negative', result: kValue.f16.infinity.negative },
|
{ val: Number.NEGATIVE_INFINITY, dir: 'negative', result: kValue.f16.negative.infinity },
|
||||||
|
|
||||||
// Zeroes
|
// Zeroes
|
||||||
{ val: +0, dir: 'positive', result: kValue.f16.positive.min },
|
{ val: +0, dir: 'positive', result: kValue.f16.positive.min },
|
||||||
@@ -360,24 +362,24 @@ g.test('nextAfterF16FlushToZero')
|
|||||||
{ val: -0, dir: 'negative', result: kValue.f16.negative.max },
|
{ val: -0, dir: 'negative', result: kValue.f16.negative.max },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ val: kValue.f16.subnormal.positive.min, dir: 'positive', result: kValue.f16.positive.min },
|
{ val: kValue.f16.positive.subnormal.min, dir: 'positive', result: kValue.f16.positive.min },
|
||||||
{ val: kValue.f16.subnormal.positive.min, dir: 'negative', result: kValue.f16.negative.max },
|
{ val: kValue.f16.positive.subnormal.min, dir: 'negative', result: kValue.f16.negative.max },
|
||||||
{ val: kValue.f16.subnormal.positive.max, dir: 'positive', result: kValue.f16.positive.min },
|
{ val: kValue.f16.positive.subnormal.max, dir: 'positive', result: kValue.f16.positive.min },
|
||||||
{ val: kValue.f16.subnormal.positive.max, dir: 'negative', result: kValue.f16.negative.max },
|
{ val: kValue.f16.positive.subnormal.max, dir: 'negative', result: kValue.f16.negative.max },
|
||||||
{ val: kValue.f16.subnormal.negative.min, dir: 'positive', result: kValue.f16.positive.min },
|
{ val: kValue.f16.negative.subnormal.min, dir: 'positive', result: kValue.f16.positive.min },
|
||||||
{ val: kValue.f16.subnormal.negative.min, dir: 'negative', result: kValue.f16.negative.max },
|
{ val: kValue.f16.negative.subnormal.min, dir: 'negative', result: kValue.f16.negative.max },
|
||||||
{ val: kValue.f16.subnormal.negative.max, dir: 'positive', result: kValue.f16.positive.min },
|
{ val: kValue.f16.negative.subnormal.max, dir: 'positive', result: kValue.f16.positive.min },
|
||||||
{ val: kValue.f16.subnormal.negative.max, dir: 'negative', result: kValue.f16.negative.max },
|
{ val: kValue.f16.negative.subnormal.max, dir: 'negative', result: kValue.f16.negative.max },
|
||||||
|
|
||||||
// Normals
|
// Normals
|
||||||
{ val: kValue.f16.positive.max, dir: 'positive', result: kValue.f16.infinity.positive },
|
{ val: kValue.f16.positive.max, dir: 'positive', result: kValue.f16.positive.infinity },
|
||||||
{ val: kValue.f16.positive.max, dir: 'negative', result: reinterpretU16AsF16(0x7bfe) },
|
{ val: kValue.f16.positive.max, dir: 'negative', result: reinterpretU16AsF16(0x7bfe) },
|
||||||
{ val: kValue.f16.positive.min, dir: 'positive', result: reinterpretU16AsF16(0x0401) },
|
{ val: kValue.f16.positive.min, dir: 'positive', result: reinterpretU16AsF16(0x0401) },
|
||||||
{ val: kValue.f16.positive.min, dir: 'negative', result: 0 },
|
{ val: kValue.f16.positive.min, dir: 'negative', result: 0 },
|
||||||
{ val: kValue.f16.negative.max, dir: 'positive', result: 0 },
|
{ val: kValue.f16.negative.max, dir: 'positive', result: 0 },
|
||||||
{ val: kValue.f16.negative.max, dir: 'negative', result: reinterpretU16AsF16(0x8401) },
|
{ val: kValue.f16.negative.max, dir: 'negative', result: reinterpretU16AsF16(0x8401) },
|
||||||
{ val: kValue.f16.negative.min, dir: 'positive', result: reinterpretU16AsF16(0xfbfe) },
|
{ val: kValue.f16.negative.min, dir: 'positive', result: reinterpretU16AsF16(0xfbfe) },
|
||||||
{ val: kValue.f16.negative.min, dir: 'negative', result: kValue.f16.infinity.negative },
|
{ val: kValue.f16.negative.min, dir: 'negative', result: kValue.f16.negative.infinity },
|
||||||
{ val: reinterpretU16AsF16(0x1380), dir: 'positive', result: reinterpretU16AsF16(0x1381) },
|
{ val: reinterpretU16AsF16(0x1380), dir: 'positive', result: reinterpretU16AsF16(0x1381) },
|
||||||
{ val: reinterpretU16AsF16(0x1380), dir: 'negative', result: reinterpretU16AsF16(0x137f) },
|
{ val: reinterpretU16AsF16(0x1380), dir: 'negative', result: reinterpretU16AsF16(0x137f) },
|
||||||
{ val: reinterpretU16AsF16(0x9380), dir: 'positive', result: reinterpretU16AsF16(0x937f) },
|
{ val: reinterpretU16AsF16(0x9380), dir: 'positive', result: reinterpretU16AsF16(0x937f) },
|
||||||
@@ -412,36 +414,36 @@ g.test('nextAfterF16NoFlush')
|
|||||||
// Edge Cases
|
// Edge Cases
|
||||||
{ val: Number.NaN, dir: 'positive', result: Number.NaN },
|
{ val: Number.NaN, dir: 'positive', result: Number.NaN },
|
||||||
{ val: Number.NaN, dir: 'negative', result: Number.NaN },
|
{ val: Number.NaN, dir: 'negative', result: Number.NaN },
|
||||||
{ val: Number.POSITIVE_INFINITY, dir: 'positive', result: kValue.f16.infinity.positive },
|
{ val: Number.POSITIVE_INFINITY, dir: 'positive', result: kValue.f16.positive.infinity },
|
||||||
{ val: Number.POSITIVE_INFINITY, dir: 'negative', result: kValue.f16.infinity.positive },
|
{ val: Number.POSITIVE_INFINITY, dir: 'negative', result: kValue.f16.positive.infinity },
|
||||||
{ val: Number.NEGATIVE_INFINITY, dir: 'positive', result: kValue.f16.infinity.negative },
|
{ val: Number.NEGATIVE_INFINITY, dir: 'positive', result: kValue.f16.negative.infinity },
|
||||||
{ val: Number.NEGATIVE_INFINITY, dir: 'negative', result: kValue.f16.infinity.negative },
|
{ val: Number.NEGATIVE_INFINITY, dir: 'negative', result: kValue.f16.negative.infinity },
|
||||||
|
|
||||||
// Zeroes
|
// Zeroes
|
||||||
{ val: +0, dir: 'positive', result: kValue.f16.subnormal.positive.min },
|
{ val: +0, dir: 'positive', result: kValue.f16.positive.subnormal.min },
|
||||||
{ val: +0, dir: 'negative', result: kValue.f16.subnormal.negative.max },
|
{ val: +0, dir: 'negative', result: kValue.f16.negative.subnormal.max },
|
||||||
{ val: -0, dir: 'positive', result: kValue.f16.subnormal.positive.min },
|
{ val: -0, dir: 'positive', result: kValue.f16.positive.subnormal.min },
|
||||||
{ val: -0, dir: 'negative', result: kValue.f16.subnormal.negative.max },
|
{ val: -0, dir: 'negative', result: kValue.f16.negative.subnormal.max },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ val: kValue.f16.subnormal.positive.min, dir: 'positive', result: reinterpretU16AsF16(0x0002) },
|
{ val: kValue.f16.positive.subnormal.min, dir: 'positive', result: reinterpretU16AsF16(0x0002) },
|
||||||
{ val: kValue.f16.subnormal.positive.min, dir: 'negative', result: 0 },
|
{ val: kValue.f16.positive.subnormal.min, dir: 'negative', result: 0 },
|
||||||
{ val: kValue.f16.subnormal.positive.max, dir: 'positive', result: kValue.f16.positive.min },
|
{ val: kValue.f16.positive.subnormal.max, dir: 'positive', result: kValue.f16.positive.min },
|
||||||
{ val: kValue.f16.subnormal.positive.max, dir: 'negative', result: reinterpretU16AsF16(0x03fe) },
|
{ val: kValue.f16.positive.subnormal.max, dir: 'negative', result: reinterpretU16AsF16(0x03fe) },
|
||||||
{ val: kValue.f16.subnormal.negative.min, dir: 'positive', result: reinterpretU16AsF16(0x83fe) },
|
{ val: kValue.f16.negative.subnormal.min, dir: 'positive', result: reinterpretU16AsF16(0x83fe) },
|
||||||
{ val: kValue.f16.subnormal.negative.min, dir: 'negative', result: kValue.f16.negative.max },
|
{ val: kValue.f16.negative.subnormal.min, dir: 'negative', result: kValue.f16.negative.max },
|
||||||
{ val: kValue.f16.subnormal.negative.max, dir: 'positive', result: 0 },
|
{ val: kValue.f16.negative.subnormal.max, dir: 'positive', result: 0 },
|
||||||
{ val: kValue.f16.subnormal.negative.max, dir: 'negative', result: reinterpretU16AsF16(0x8002) },
|
{ val: kValue.f16.negative.subnormal.max, dir: 'negative', result: reinterpretU16AsF16(0x8002) },
|
||||||
|
|
||||||
// Normals
|
// Normals
|
||||||
{ val: kValue.f16.positive.max, dir: 'positive', result: kValue.f16.infinity.positive },
|
{ val: kValue.f16.positive.max, dir: 'positive', result: kValue.f16.positive.infinity },
|
||||||
{ val: kValue.f16.positive.max, dir: 'negative', result: reinterpretU16AsF16(0x7bfe) },
|
{ val: kValue.f16.positive.max, dir: 'negative', result: reinterpretU16AsF16(0x7bfe) },
|
||||||
{ val: kValue.f16.positive.min, dir: 'positive', result: reinterpretU16AsF16(0x0401) },
|
{ val: kValue.f16.positive.min, dir: 'positive', result: reinterpretU16AsF16(0x0401) },
|
||||||
{ val: kValue.f16.positive.min, dir: 'negative', result: kValue.f16.subnormal.positive.max },
|
{ val: kValue.f16.positive.min, dir: 'negative', result: kValue.f16.positive.subnormal.max },
|
||||||
{ val: kValue.f16.negative.max, dir: 'positive', result: kValue.f16.subnormal.negative.min },
|
{ val: kValue.f16.negative.max, dir: 'positive', result: kValue.f16.negative.subnormal.min },
|
||||||
{ val: kValue.f16.negative.max, dir: 'negative', result: reinterpretU16AsF16(0x8401) },
|
{ val: kValue.f16.negative.max, dir: 'negative', result: reinterpretU16AsF16(0x8401) },
|
||||||
{ val: kValue.f16.negative.min, dir: 'positive', result: reinterpretU16AsF16(0xfbfe) },
|
{ val: kValue.f16.negative.min, dir: 'positive', result: reinterpretU16AsF16(0xfbfe) },
|
||||||
{ val: kValue.f16.negative.min, dir: 'negative', result: kValue.f16.infinity.negative },
|
{ val: kValue.f16.negative.min, dir: 'negative', result: kValue.f16.negative.infinity },
|
||||||
{ val: reinterpretU16AsF16(0x1380), dir: 'positive', result: reinterpretU16AsF16(0x1381) },
|
{ val: reinterpretU16AsF16(0x1380), dir: 'positive', result: reinterpretU16AsF16(0x1381) },
|
||||||
{ val: reinterpretU16AsF16(0x1380), dir: 'negative', result: reinterpretU16AsF16(0x137f) },
|
{ val: reinterpretU16AsF16(0x1380), dir: 'negative', result: reinterpretU16AsF16(0x137f) },
|
||||||
{ val: reinterpretU16AsF16(0x9380), dir: 'positive', result: reinterpretU16AsF16(0x937f) },
|
{ val: reinterpretU16AsF16(0x9380), dir: 'positive', result: reinterpretU16AsF16(0x937f) },
|
||||||
@@ -452,10 +454,10 @@ g.test('nextAfterF16NoFlush')
|
|||||||
{ val: 0.01, dir: 'negative', result: reinterpretU16AsF16(0x211e) }, // positive normal
|
{ val: 0.01, dir: 'negative', result: reinterpretU16AsF16(0x211e) }, // positive normal
|
||||||
{ val: -0.01, dir: 'positive', result: reinterpretU16AsF16(0xa11e) }, // negative normal
|
{ val: -0.01, dir: 'positive', result: reinterpretU16AsF16(0xa11e) }, // negative normal
|
||||||
{ val: -0.01, dir: 'negative', result: reinterpretU16AsF16(0xa11f) }, // negative normal
|
{ val: -0.01, dir: 'negative', result: reinterpretU16AsF16(0xa11f) }, // negative normal
|
||||||
{ val: 2.82E-40, dir: 'positive', result: kValue.f16.subnormal.positive.min }, // positive subnormal
|
{ val: 2.82E-40, dir: 'positive', result: kValue.f16.positive.subnormal.min }, // positive subnormal
|
||||||
{ val: 2.82E-40, dir: 'negative', result: 0 }, // positive subnormal
|
{ val: 2.82E-40, dir: 'negative', result: 0 }, // positive subnormal
|
||||||
{ val: -2.82E-40, dir: 'positive', result: 0 }, // negative subnormal
|
{ val: -2.82E-40, dir: 'positive', result: 0 }, // negative subnormal
|
||||||
{ val: -2.82E-40, dir: 'negative', result: kValue.f16.subnormal.negative.max }, // negative subnormal
|
{ val: -2.82E-40, dir: 'negative', result: kValue.f16.negative.subnormal.max }, // negative subnormal
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
@@ -489,19 +491,19 @@ g.test('oneULPF64FlushToZero')
|
|||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{
|
{
|
||||||
target: kValue.f64.subnormal.positive.min,
|
target: kValue.f64.positive.subnormal.min,
|
||||||
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: kValue.f64.subnormal.positive.max,
|
target: kValue.f64.positive.subnormal.max,
|
||||||
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: kValue.f64.subnormal.negative.min,
|
target: kValue.f64.negative.subnormal.min,
|
||||||
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: kValue.f64.subnormal.negative.max,
|
target: kValue.f64.negative.subnormal.max,
|
||||||
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -542,19 +544,19 @@ g.test('oneULPF64NoFlush')
|
|||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{
|
{
|
||||||
target: kValue.f64.subnormal.positive.min,
|
target: kValue.f64.positive.subnormal.min,
|
||||||
expect: reinterpretU64AsF64(0x0000_0000_0000_0001n),
|
expect: reinterpretU64AsF64(0x0000_0000_0000_0001n),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: kValue.f64.subnormal.positive.max,
|
target: kValue.f64.positive.subnormal.max,
|
||||||
expect: reinterpretU64AsF64(0x0000_0000_0000_0001n),
|
expect: reinterpretU64AsF64(0x0000_0000_0000_0001n),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: kValue.f64.subnormal.negative.min,
|
target: kValue.f64.negative.subnormal.min,
|
||||||
expect: reinterpretU64AsF64(0x0000_0000_0000_0001n),
|
expect: reinterpretU64AsF64(0x0000_0000_0000_0001n),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: kValue.f64.subnormal.negative.max,
|
target: kValue.f64.negative.subnormal.max,
|
||||||
expect: reinterpretU64AsF64(0x0000_0000_0000_0001n),
|
expect: reinterpretU64AsF64(0x0000_0000_0000_0001n),
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -595,19 +597,19 @@ g.test('oneULPF64')
|
|||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{
|
{
|
||||||
target: kValue.f64.subnormal.positive.min,
|
target: kValue.f64.positive.subnormal.min,
|
||||||
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: kValue.f64.subnormal.positive.max,
|
target: kValue.f64.positive.subnormal.max,
|
||||||
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: kValue.f64.subnormal.negative.min,
|
target: kValue.f64.negative.subnormal.min,
|
||||||
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: kValue.f64.subnormal.negative.max,
|
target: kValue.f64.negative.subnormal.max,
|
||||||
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
expect: reinterpretU64AsF64(0x0010_0000_0000_0000n),
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -647,12 +649,12 @@ g.test('oneULPF32FlushToZero')
|
|||||||
{ target: -0, expect: reinterpretU32AsF32(0x00800000) },
|
{ target: -0, expect: reinterpretU32AsF32(0x00800000) },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ target: kValue.f32.subnormal.positive.min, expect: reinterpretU32AsF32(0x00800000) },
|
{ target: kValue.f32.positive.subnormal.min, expect: reinterpretU32AsF32(0x00800000) },
|
||||||
{ target: 2.82e-40, expect: reinterpretU32AsF32(0x00800000) }, // positive subnormal
|
{ target: 2.82e-40, expect: reinterpretU32AsF32(0x00800000) }, // positive subnormal
|
||||||
{ target: kValue.f32.subnormal.positive.max, expect: reinterpretU32AsF32(0x00800000) },
|
{ target: kValue.f32.positive.subnormal.max, expect: reinterpretU32AsF32(0x00800000) },
|
||||||
{ target: kValue.f32.subnormal.negative.min, expect: reinterpretU32AsF32(0x00800000) },
|
{ target: kValue.f32.negative.subnormal.min, expect: reinterpretU32AsF32(0x00800000) },
|
||||||
{ target: -2.82e-40, expect: reinterpretU32AsF32(0x00800000) }, // negative subnormal
|
{ target: -2.82e-40, expect: reinterpretU32AsF32(0x00800000) }, // negative subnormal
|
||||||
{ target: kValue.f32.subnormal.negative.max, expect: reinterpretU32AsF32(0x00800000) },
|
{ target: kValue.f32.negative.subnormal.max, expect: reinterpretU32AsF32(0x00800000) },
|
||||||
|
|
||||||
// Normals
|
// Normals
|
||||||
{ target: kValue.f32.positive.min, expect: reinterpretU32AsF32(0x00000001) },
|
{ target: kValue.f32.positive.min, expect: reinterpretU32AsF32(0x00000001) },
|
||||||
@@ -696,12 +698,12 @@ g.test('oneULPF32NoFlush')
|
|||||||
{ target: -0, expect: reinterpretU32AsF32(0x00000001) },
|
{ target: -0, expect: reinterpretU32AsF32(0x00000001) },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ target: kValue.f32.subnormal.positive.min, expect: reinterpretU32AsF32(0x00000001) },
|
{ target: kValue.f32.positive.subnormal.min, expect: reinterpretU32AsF32(0x00000001) },
|
||||||
{ target: -2.82e-40, expect: reinterpretU32AsF32(0x00000001) }, // negative subnormal
|
{ target: -2.82e-40, expect: reinterpretU32AsF32(0x00000001) }, // negative subnormal
|
||||||
{ target: kValue.f32.subnormal.positive.max, expect: reinterpretU32AsF32(0x00000001) },
|
{ target: kValue.f32.positive.subnormal.max, expect: reinterpretU32AsF32(0x00000001) },
|
||||||
{ target: kValue.f32.subnormal.negative.min, expect: reinterpretU32AsF32(0x00000001) },
|
{ target: kValue.f32.negative.subnormal.min, expect: reinterpretU32AsF32(0x00000001) },
|
||||||
{ target: 2.82e-40, expect: reinterpretU32AsF32(0x00000001) }, // positive subnormal
|
{ target: 2.82e-40, expect: reinterpretU32AsF32(0x00000001) }, // positive subnormal
|
||||||
{ target: kValue.f32.subnormal.negative.max, expect: reinterpretU32AsF32(0x00000001) },
|
{ target: kValue.f32.negative.subnormal.max, expect: reinterpretU32AsF32(0x00000001) },
|
||||||
|
|
||||||
// Normals
|
// Normals
|
||||||
{ target: kValue.f32.positive.min, expect: reinterpretU32AsF32(0x00000001) },
|
{ target: kValue.f32.positive.min, expect: reinterpretU32AsF32(0x00000001) },
|
||||||
@@ -745,12 +747,12 @@ g.test('oneULPF32')
|
|||||||
{ target: -0, expect: reinterpretU32AsF32(0x00800000) },
|
{ target: -0, expect: reinterpretU32AsF32(0x00800000) },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ target: kValue.f32.subnormal.negative.max, expect: reinterpretU32AsF32(0x00800000) },
|
{ target: kValue.f32.negative.subnormal.max, expect: reinterpretU32AsF32(0x00800000) },
|
||||||
{ target: -2.82e-40, expect: reinterpretU32AsF32(0x00800000) },
|
{ target: -2.82e-40, expect: reinterpretU32AsF32(0x00800000) },
|
||||||
{ target: kValue.f32.subnormal.negative.min, expect: reinterpretU32AsF32(0x00800000) },
|
{ target: kValue.f32.negative.subnormal.min, expect: reinterpretU32AsF32(0x00800000) },
|
||||||
{ target: kValue.f32.subnormal.positive.max, expect: reinterpretU32AsF32(0x00800000) },
|
{ target: kValue.f32.positive.subnormal.max, expect: reinterpretU32AsF32(0x00800000) },
|
||||||
{ target: 2.82e-40, expect: reinterpretU32AsF32(0x00800000) },
|
{ target: 2.82e-40, expect: reinterpretU32AsF32(0x00800000) },
|
||||||
{ target: kValue.f32.subnormal.positive.min, expect: reinterpretU32AsF32(0x00800000) },
|
{ target: kValue.f32.positive.subnormal.min, expect: reinterpretU32AsF32(0x00800000) },
|
||||||
|
|
||||||
// Normals
|
// Normals
|
||||||
{ target: kValue.f32.positive.min, expect: reinterpretU32AsF32(0x00000001) },
|
{ target: kValue.f32.positive.min, expect: reinterpretU32AsF32(0x00000001) },
|
||||||
@@ -794,12 +796,12 @@ g.test('oneULPF16FlushToZero')
|
|||||||
{ target: -0, expect: reinterpretU16AsF16(0x0400) },
|
{ target: -0, expect: reinterpretU16AsF16(0x0400) },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ target: kValue.f16.subnormal.positive.min, expect: reinterpretU16AsF16(0x0400) },
|
{ target: kValue.f16.positive.subnormal.min, expect: reinterpretU16AsF16(0x0400) },
|
||||||
{ target: 1.91e-6, expect: reinterpretU16AsF16(0x0400) }, // positive subnormal
|
{ target: 1.91e-6, expect: reinterpretU16AsF16(0x0400) }, // positive subnormal
|
||||||
{ target: kValue.f16.subnormal.positive.max, expect: reinterpretU16AsF16(0x0400) },
|
{ target: kValue.f16.positive.subnormal.max, expect: reinterpretU16AsF16(0x0400) },
|
||||||
{ target: kValue.f16.subnormal.negative.min, expect: reinterpretU16AsF16(0x0400) },
|
{ target: kValue.f16.negative.subnormal.min, expect: reinterpretU16AsF16(0x0400) },
|
||||||
{ target: -1.91e-6, expect: reinterpretU16AsF16(0x0400) }, // negative subnormal
|
{ target: -1.91e-6, expect: reinterpretU16AsF16(0x0400) }, // negative subnormal
|
||||||
{ target: kValue.f16.subnormal.negative.max, expect: reinterpretU16AsF16(0x0400) },
|
{ target: kValue.f16.negative.subnormal.max, expect: reinterpretU16AsF16(0x0400) },
|
||||||
|
|
||||||
// Normals
|
// Normals
|
||||||
{ target: kValue.f16.positive.min, expect: reinterpretU16AsF16(0x0001) },
|
{ target: kValue.f16.positive.min, expect: reinterpretU16AsF16(0x0001) },
|
||||||
@@ -843,12 +845,12 @@ g.test('oneULPF16NoFlush')
|
|||||||
{ target: -0, expect: reinterpretU16AsF16(0x0001) },
|
{ target: -0, expect: reinterpretU16AsF16(0x0001) },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ target: kValue.f16.subnormal.positive.min, expect: reinterpretU16AsF16(0x0001) },
|
{ target: kValue.f16.positive.subnormal.min, expect: reinterpretU16AsF16(0x0001) },
|
||||||
{ target: 1.91e-6, expect: reinterpretU16AsF16(0x0001) }, // positive subnormal
|
{ target: 1.91e-6, expect: reinterpretU16AsF16(0x0001) }, // positive subnormal
|
||||||
{ target: kValue.f16.subnormal.positive.max, expect: reinterpretU16AsF16(0x0001) },
|
{ target: kValue.f16.positive.subnormal.max, expect: reinterpretU16AsF16(0x0001) },
|
||||||
{ target: kValue.f16.subnormal.negative.min, expect: reinterpretU16AsF16(0x0001) },
|
{ target: kValue.f16.negative.subnormal.min, expect: reinterpretU16AsF16(0x0001) },
|
||||||
{ target: -1.91e-6, expect: reinterpretU16AsF16(0x0001) }, // negative subnormal
|
{ target: -1.91e-6, expect: reinterpretU16AsF16(0x0001) }, // negative subnormal
|
||||||
{ target: kValue.f16.subnormal.negative.max, expect: reinterpretU16AsF16(0x0001) },
|
{ target: kValue.f16.negative.subnormal.max, expect: reinterpretU16AsF16(0x0001) },
|
||||||
|
|
||||||
// Normals
|
// Normals
|
||||||
{ target: kValue.f16.positive.min, expect: reinterpretU16AsF16(0x0001) },
|
{ target: kValue.f16.positive.min, expect: reinterpretU16AsF16(0x0001) },
|
||||||
@@ -892,12 +894,12 @@ g.test('oneULPF16')
|
|||||||
{ target: -0, expect: reinterpretU16AsF16(0x0400) },
|
{ target: -0, expect: reinterpretU16AsF16(0x0400) },
|
||||||
|
|
||||||
// Subnormals
|
// Subnormals
|
||||||
{ target: kValue.f16.subnormal.positive.min, expect: reinterpretU16AsF16(0x0400) },
|
{ target: kValue.f16.positive.subnormal.min, expect: reinterpretU16AsF16(0x0400) },
|
||||||
{ target: 1.91e-6, expect: reinterpretU16AsF16(0x0400) }, // positive subnormal
|
{ target: 1.91e-6, expect: reinterpretU16AsF16(0x0400) }, // positive subnormal
|
||||||
{ target: kValue.f16.subnormal.positive.max, expect: reinterpretU16AsF16(0x0400) },
|
{ target: kValue.f16.positive.subnormal.max, expect: reinterpretU16AsF16(0x0400) },
|
||||||
{ target: kValue.f16.subnormal.negative.min, expect: reinterpretU16AsF16(0x0400) },
|
{ target: kValue.f16.negative.subnormal.min, expect: reinterpretU16AsF16(0x0400) },
|
||||||
{ target: -1.91e-6, expect: reinterpretU16AsF16(0x0400) }, // negative subnormal
|
{ target: -1.91e-6, expect: reinterpretU16AsF16(0x0400) }, // negative subnormal
|
||||||
{ target: kValue.f16.subnormal.negative.max, expect: reinterpretU16AsF16(0x0400) },
|
{ target: kValue.f16.negative.subnormal.max, expect: reinterpretU16AsF16(0x0400) },
|
||||||
|
|
||||||
// Normals
|
// Normals
|
||||||
{ target: kValue.f16.positive.min, expect: reinterpretU16AsF16(0x0001) },
|
{ target: kValue.f16.positive.min, expect: reinterpretU16AsF16(0x0001) },
|
||||||
@@ -947,20 +949,20 @@ g.test('correctlyRoundedF32')
|
|||||||
{ value: -(2 ** (kValue.f32.emax + 1)) + oneULPF64(kValue.f32.positive.max), expected: [Number.NEGATIVE_INFINITY, kValue.f32.negative.min] },
|
{ value: -(2 ** (kValue.f32.emax + 1)) + oneULPF64(kValue.f32.positive.max), expected: [Number.NEGATIVE_INFINITY, kValue.f32.negative.min] },
|
||||||
{ value: 2 ** (kValue.f32.emax + 1), expected: [Number.POSITIVE_INFINITY] },
|
{ value: 2 ** (kValue.f32.emax + 1), expected: [Number.POSITIVE_INFINITY] },
|
||||||
{ value: -(2 ** (kValue.f32.emax + 1)), expected: [Number.NEGATIVE_INFINITY] },
|
{ value: -(2 ** (kValue.f32.emax + 1)), expected: [Number.NEGATIVE_INFINITY] },
|
||||||
{ value: kValue.f32.infinity.positive, expected: [Number.POSITIVE_INFINITY] },
|
{ value: kValue.f32.positive.infinity, expected: [Number.POSITIVE_INFINITY] },
|
||||||
{ value: kValue.f32.infinity.negative, expected: [Number.NEGATIVE_INFINITY] },
|
{ value: kValue.f32.negative.infinity, expected: [Number.NEGATIVE_INFINITY] },
|
||||||
|
|
||||||
// 32-bit subnormals
|
// 32-bit subnormals
|
||||||
{ value: kValue.f32.subnormal.positive.min, expected: [kValue.f32.subnormal.positive.min] },
|
{ value: kValue.f32.positive.subnormal.min, expected: [kValue.f32.positive.subnormal.min] },
|
||||||
{ value: kValue.f32.subnormal.positive.max, expected: [kValue.f32.subnormal.positive.max] },
|
{ value: kValue.f32.positive.subnormal.max, expected: [kValue.f32.positive.subnormal.max] },
|
||||||
{ value: kValue.f32.subnormal.negative.min, expected: [kValue.f32.subnormal.negative.min] },
|
{ value: kValue.f32.negative.subnormal.min, expected: [kValue.f32.negative.subnormal.min] },
|
||||||
{ value: kValue.f32.subnormal.negative.max, expected: [kValue.f32.subnormal.negative.max] },
|
{ value: kValue.f32.negative.subnormal.max, expected: [kValue.f32.negative.subnormal.max] },
|
||||||
|
|
||||||
// 64-bit subnormals
|
// 64-bit subnormals
|
||||||
{ value: reinterpretU64AsF64(0x0000_0000_0000_0001n), expected: [0, kValue.f32.subnormal.positive.min] },
|
{ value: reinterpretU64AsF64(0x0000_0000_0000_0001n), expected: [0, kValue.f32.positive.subnormal.min] },
|
||||||
{ value: reinterpretU64AsF64(0x0000_0000_0000_0002n), expected: [0, kValue.f32.subnormal.positive.min] },
|
{ value: reinterpretU64AsF64(0x0000_0000_0000_0002n), expected: [0, kValue.f32.positive.subnormal.min] },
|
||||||
{ value: reinterpretU64AsF64(0x800f_ffff_ffff_ffffn), expected: [kValue.f32.subnormal.negative.max, 0] },
|
{ value: reinterpretU64AsF64(0x800f_ffff_ffff_ffffn), expected: [kValue.f32.negative.subnormal.max, 0] },
|
||||||
{ value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), expected: [kValue.f32.subnormal.negative.max, 0] },
|
{ value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), expected: [kValue.f32.negative.subnormal.max, 0] },
|
||||||
|
|
||||||
// 32-bit normals
|
// 32-bit normals
|
||||||
{ value: 0, expected: [0] },
|
{ value: 0, expected: [0] },
|
||||||
@@ -1008,20 +1010,20 @@ g.test('correctlyRoundedF16')
|
|||||||
{ value: -(2 ** (kValue.f16.emax + 1)) + oneULPF64(kValue.f16.positive.max), expected: [Number.NEGATIVE_INFINITY, kValue.f16.negative.min] },
|
{ value: -(2 ** (kValue.f16.emax + 1)) + oneULPF64(kValue.f16.positive.max), expected: [Number.NEGATIVE_INFINITY, kValue.f16.negative.min] },
|
||||||
{ value: 2 ** (kValue.f16.emax + 1), expected: [Number.POSITIVE_INFINITY] },
|
{ value: 2 ** (kValue.f16.emax + 1), expected: [Number.POSITIVE_INFINITY] },
|
||||||
{ value: -(2 ** (kValue.f16.emax + 1)), expected: [Number.NEGATIVE_INFINITY] },
|
{ value: -(2 ** (kValue.f16.emax + 1)), expected: [Number.NEGATIVE_INFINITY] },
|
||||||
{ value: kValue.f16.infinity.positive, expected: [Number.POSITIVE_INFINITY] },
|
{ value: kValue.f16.positive.infinity, expected: [Number.POSITIVE_INFINITY] },
|
||||||
{ value: kValue.f16.infinity.negative, expected: [Number.NEGATIVE_INFINITY] },
|
{ value: kValue.f16.negative.infinity, expected: [Number.NEGATIVE_INFINITY] },
|
||||||
|
|
||||||
// 16-bit subnormals
|
// 16-bit subnormals
|
||||||
{ value: kValue.f16.subnormal.positive.min, expected: [kValue.f16.subnormal.positive.min] },
|
{ value: kValue.f16.positive.subnormal.min, expected: [kValue.f16.positive.subnormal.min] },
|
||||||
{ value: kValue.f16.subnormal.positive.max, expected: [kValue.f16.subnormal.positive.max] },
|
{ value: kValue.f16.positive.subnormal.max, expected: [kValue.f16.positive.subnormal.max] },
|
||||||
{ value: kValue.f16.subnormal.negative.min, expected: [kValue.f16.subnormal.negative.min] },
|
{ value: kValue.f16.negative.subnormal.min, expected: [kValue.f16.negative.subnormal.min] },
|
||||||
{ value: kValue.f16.subnormal.negative.max, expected: [kValue.f16.subnormal.negative.max] },
|
{ value: kValue.f16.negative.subnormal.max, expected: [kValue.f16.negative.subnormal.max] },
|
||||||
|
|
||||||
// 32-bit subnormals
|
// 32-bit subnormals
|
||||||
{ value: kValue.f32.subnormal.positive.min, expected: [0, kValue.f16.subnormal.positive.min] },
|
{ value: kValue.f32.positive.subnormal.min, expected: [0, kValue.f16.positive.subnormal.min] },
|
||||||
{ value: kValue.f32.subnormal.positive.max, expected: [0, kValue.f16.subnormal.positive.min] },
|
{ value: kValue.f32.positive.subnormal.max, expected: [0, kValue.f16.positive.subnormal.min] },
|
||||||
{ value: kValue.f32.subnormal.negative.max, expected: [kValue.f16.subnormal.negative.max, 0] },
|
{ value: kValue.f32.negative.subnormal.max, expected: [kValue.f16.negative.subnormal.max, 0] },
|
||||||
{ value: kValue.f32.subnormal.negative.min, expected: [kValue.f16.subnormal.negative.max, 0] },
|
{ value: kValue.f32.negative.subnormal.min, expected: [kValue.f16.negative.subnormal.max, 0] },
|
||||||
|
|
||||||
// 16-bit normals
|
// 16-bit normals
|
||||||
{ value: 0, expected: [0] },
|
{ value: 0, expected: [0] },
|
||||||
@@ -1065,30 +1067,30 @@ const kFrexpCases = {
|
|||||||
{ input: kValue.f32.positive.min, fract: 0.5, exp: -125 },
|
{ input: kValue.f32.positive.min, fract: 0.5, exp: -125 },
|
||||||
{ input: kValue.f32.negative.max, fract: -0.5, exp: -125 },
|
{ input: kValue.f32.negative.max, fract: -0.5, exp: -125 },
|
||||||
{ input: kValue.f32.negative.min, fract: -0.9999999403953552, exp: 128 },
|
{ input: kValue.f32.negative.min, fract: -0.9999999403953552, exp: 128 },
|
||||||
{ input: kValue.f32.subnormal.positive.max, fract: 0.9999998807907104, exp: -126 },
|
{ input: kValue.f32.positive.subnormal.max, fract: 0.9999998807907104, exp: -126 },
|
||||||
{ input: kValue.f32.subnormal.positive.min, fract: 0.5, exp: -148 },
|
{ input: kValue.f32.positive.subnormal.min, fract: 0.5, exp: -148 },
|
||||||
{ input: kValue.f32.subnormal.negative.max, fract: -0.5, exp: -148 },
|
{ input: kValue.f32.negative.subnormal.max, fract: -0.5, exp: -148 },
|
||||||
{ input: kValue.f32.subnormal.negative.min, fract: -0.9999998807907104, exp: -126 },
|
{ input: kValue.f32.negative.subnormal.min, fract: -0.9999998807907104, exp: -126 },
|
||||||
] as frexpCase[],
|
] as frexpCase[],
|
||||||
f16: [
|
f16: [
|
||||||
{ input: kValue.f16.positive.max, fract: 0.99951171875, exp: 16 },
|
{ input: kValue.f16.positive.max, fract: 0.99951171875, exp: 16 },
|
||||||
{ input: kValue.f16.positive.min, fract: 0.5, exp: -13 },
|
{ input: kValue.f16.positive.min, fract: 0.5, exp: -13 },
|
||||||
{ input: kValue.f16.negative.max, fract: -0.5, exp: -13 },
|
{ input: kValue.f16.negative.max, fract: -0.5, exp: -13 },
|
||||||
{ input: kValue.f16.negative.min, fract: -0.99951171875, exp: 16 },
|
{ input: kValue.f16.negative.min, fract: -0.99951171875, exp: 16 },
|
||||||
{ input: kValue.f16.subnormal.positive.max, fract: 0.9990234375, exp: -14 },
|
{ input: kValue.f16.positive.subnormal.max, fract: 0.9990234375, exp: -14 },
|
||||||
{ input: kValue.f16.subnormal.positive.min, fract: 0.5, exp: -23 },
|
{ input: kValue.f16.positive.subnormal.min, fract: 0.5, exp: -23 },
|
||||||
{ input: kValue.f16.subnormal.negative.max, fract: -0.5, exp: -23 },
|
{ input: kValue.f16.negative.subnormal.max, fract: -0.5, exp: -23 },
|
||||||
{ input: kValue.f16.subnormal.negative.min, fract: -0.9990234375, exp: -14 },
|
{ input: kValue.f16.negative.subnormal.min, fract: -0.9990234375, exp: -14 },
|
||||||
] as frexpCase[],
|
] as frexpCase[],
|
||||||
f64: [
|
f64: [
|
||||||
{ input: kValue.f64.positive.max, fract: reinterpretU64AsF64(0x3fef_ffff_ffff_ffffn) /* ~0.9999999999999999 */, exp: 1024 },
|
{ input: kValue.f64.positive.max, fract: reinterpretU64AsF64(0x3fef_ffff_ffff_ffffn) /* ~0.9999999999999999 */, exp: 1024 },
|
||||||
{ input: kValue.f64.positive.min, fract: 0.5, exp: -1021 },
|
{ input: kValue.f64.positive.min, fract: 0.5, exp: -1021 },
|
||||||
{ input: kValue.f64.negative.max, fract: -0.5, exp: -1021 },
|
{ input: kValue.f64.negative.max, fract: -0.5, exp: -1021 },
|
||||||
{ input: kValue.f64.negative.min, fract: reinterpretU64AsF64(0xbfef_ffff_ffff_ffffn) /* ~-0.9999999999999999 */, exp: 1024 },
|
{ input: kValue.f64.negative.min, fract: reinterpretU64AsF64(0xbfef_ffff_ffff_ffffn) /* ~-0.9999999999999999 */, exp: 1024 },
|
||||||
{ input: kValue.f64.subnormal.positive.max, fract: reinterpretU64AsF64(0x3fef_ffff_ffff_fffen) /* ~0.9999999999999998 */, exp: -1022 },
|
{ input: kValue.f64.positive.subnormal.max, fract: reinterpretU64AsF64(0x3fef_ffff_ffff_fffen) /* ~0.9999999999999998 */, exp: -1022 },
|
||||||
{ input: kValue.f64.subnormal.positive.min, fract: 0.5, exp: -1073 },
|
{ input: kValue.f64.positive.subnormal.min, fract: 0.5, exp: -1073 },
|
||||||
{ input: kValue.f64.subnormal.negative.max, fract: -0.5, exp: -1073 },
|
{ input: kValue.f64.negative.subnormal.max, fract: -0.5, exp: -1073 },
|
||||||
{ input: kValue.f64.subnormal.negative.min, fract: reinterpretU64AsF64(0xbfef_ffff_ffff_fffen) /* ~-0.9999999999999998 */, exp: -1022 },
|
{ input: kValue.f64.negative.subnormal.min, fract: reinterpretU64AsF64(0xbfef_ffff_ffff_fffen) /* ~-0.9999999999999998 */, exp: -1022 },
|
||||||
] as frexpCase[],
|
] as frexpCase[],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -1535,19 +1537,19 @@ g.test('fullF32Range')
|
|||||||
.paramsSimple<fullF32RangeCase>(
|
.paramsSimple<fullF32RangeCase>(
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
[
|
[
|
||||||
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ 0.0 ] },
|
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ -0.0, 0.0 ] },
|
||||||
{ neg_norm: 1, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ kValue.f32.negative.min, 0.0] },
|
{ neg_norm: 1, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ kValue.f32.negative.min, -0.0, 0.0] },
|
||||||
{ neg_norm: 2, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ kValue.f32.negative.min, kValue.f32.negative.max, 0.0 ] },
|
{ neg_norm: 2, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ kValue.f32.negative.min, kValue.f32.negative.max, -0.0, 0.0 ] },
|
||||||
{ neg_norm: 3, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ kValue.f32.negative.min, -1.9999998807907104, kValue.f32.negative.max, 0.0 ] },
|
{ neg_norm: 3, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ kValue.f32.negative.min, -1.9999998807907104, kValue.f32.negative.max, -0.0, 0.0 ] },
|
||||||
{ neg_norm: 0, neg_sub: 1, pos_sub: 0, pos_norm: 0, expect: [ kValue.f32.subnormal.negative.min, 0.0 ] },
|
{ neg_norm: 0, neg_sub: 1, pos_sub: 0, pos_norm: 0, expect: [ kValue.f32.negative.subnormal.min, -0.0, 0.0 ] },
|
||||||
{ neg_norm: 0, neg_sub: 2, pos_sub: 0, pos_norm: 0, expect: [ kValue.f32.subnormal.negative.min, kValue.f32.subnormal.negative.max, 0.0 ] },
|
{ neg_norm: 0, neg_sub: 2, pos_sub: 0, pos_norm: 0, expect: [ kValue.f32.negative.subnormal.min, kValue.f32.negative.subnormal.max, -0.0, 0.0 ] },
|
||||||
{ neg_norm: 0, neg_sub: 0, pos_sub: 1, pos_norm: 0, expect: [ 0.0, kValue.f32.subnormal.positive.min ] },
|
{ neg_norm: 0, neg_sub: 0, pos_sub: 1, pos_norm: 0, expect: [ -0.0, 0.0, kValue.f32.positive.subnormal.min ] },
|
||||||
{ neg_norm: 0, neg_sub: 0, pos_sub: 2, pos_norm: 0, expect: [ 0.0, kValue.f32.subnormal.positive.min, kValue.f32.subnormal.positive.max ] },
|
{ neg_norm: 0, neg_sub: 0, pos_sub: 2, pos_norm: 0, expect: [ -0.0, 0.0, kValue.f32.positive.subnormal.min, kValue.f32.positive.subnormal.max ] },
|
||||||
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 1, expect: [ 0.0, kValue.f32.positive.min ] },
|
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 1, expect: [ -0.0, 0.0, kValue.f32.positive.min ] },
|
||||||
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 2, expect: [ 0.0, kValue.f32.positive.min, kValue.f32.positive.max ] },
|
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 2, expect: [ -0.0, 0.0, kValue.f32.positive.min, kValue.f32.positive.max ] },
|
||||||
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 3, expect: [ 0.0, kValue.f32.positive.min, 1.9999998807907104, kValue.f32.positive.max ] },
|
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 3, expect: [ -0.0, 0.0, kValue.f32.positive.min, 1.9999998807907104, kValue.f32.positive.max ] },
|
||||||
{ neg_norm: 1, neg_sub: 1, pos_sub: 1, pos_norm: 1, expect: [ kValue.f32.negative.min, kValue.f32.subnormal.negative.min, 0.0, kValue.f32.subnormal.positive.min, kValue.f32.positive.min ] },
|
{ neg_norm: 1, neg_sub: 1, pos_sub: 1, pos_norm: 1, expect: [ kValue.f32.negative.min, kValue.f32.negative.subnormal.min, -0.0, 0.0, kValue.f32.positive.subnormal.min, kValue.f32.positive.min ] },
|
||||||
{ neg_norm: 2, neg_sub: 2, pos_sub: 2, pos_norm: 2, expect: [ kValue.f32.negative.min, kValue.f32.negative.max, kValue.f32.subnormal.negative.min, kValue.f32.subnormal.negative.max, 0.0, kValue.f32.subnormal.positive.min, kValue.f32.subnormal.positive.max, kValue.f32.positive.min, kValue.f32.positive.max ] },
|
{ neg_norm: 2, neg_sub: 2, pos_sub: 2, pos_norm: 2, expect: [ kValue.f32.negative.min, kValue.f32.negative.max, kValue.f32.negative.subnormal.min, kValue.f32.negative.subnormal.max, -0.0, 0.0, kValue.f32.positive.subnormal.min, kValue.f32.positive.subnormal.max, kValue.f32.positive.min, kValue.f32.positive.max ] },
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
.fn(test => {
|
.fn(test => {
|
||||||
@@ -1576,20 +1578,20 @@ g.test('fullF16Range')
|
|||||||
.paramsSimple<fullF16RangeCase>(
|
.paramsSimple<fullF16RangeCase>(
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
[
|
[
|
||||||
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ 0.0 ] },
|
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ -0.0, 0.0 ] },
|
||||||
{ neg_norm: 1, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ kValue.f16.negative.min, 0.0] },
|
{ neg_norm: 1, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ kValue.f16.negative.min, -0.0, 0.0] },
|
||||||
{ neg_norm: 2, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ kValue.f16.negative.min, kValue.f16.negative.max, 0.0 ] },
|
{ neg_norm: 2, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ kValue.f16.negative.min, kValue.f16.negative.max, -0.0, 0.0 ] },
|
||||||
{ neg_norm: 3, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ kValue.f16.negative.min, -1.9990234375, kValue.f16.negative.max, 0.0 ] },
|
{ neg_norm: 3, neg_sub: 0, pos_sub: 0, pos_norm: 0, expect: [ kValue.f16.negative.min, -1.9990234375, kValue.f16.negative.max, -0.0, 0.0 ] },
|
||||||
{ neg_norm: 0, neg_sub: 1, pos_sub: 0, pos_norm: 0, expect: [ kValue.f16.subnormal.negative.min, 0.0 ] },
|
{ neg_norm: 0, neg_sub: 1, pos_sub: 0, pos_norm: 0, expect: [ kValue.f16.negative.subnormal.min, -0.0, 0.0 ] },
|
||||||
{ neg_norm: 0, neg_sub: 2, pos_sub: 0, pos_norm: 0, expect: [ kValue.f16.subnormal.negative.min, kValue.f16.subnormal.negative.max, 0.0 ] },
|
{ neg_norm: 0, neg_sub: 2, pos_sub: 0, pos_norm: 0, expect: [ kValue.f16.negative.subnormal.min, kValue.f16.negative.subnormal.max, -0.0, 0.0 ] },
|
||||||
{ neg_norm: 0, neg_sub: 0, pos_sub: 1, pos_norm: 0, expect: [ 0.0, kValue.f16.subnormal.positive.min ] },
|
{ neg_norm: 0, neg_sub: 0, pos_sub: 1, pos_norm: 0, expect: [ -0.0, 0.0, kValue.f16.positive.subnormal.min ] },
|
||||||
{ neg_norm: 0, neg_sub: 0, pos_sub: 2, pos_norm: 0, expect: [ 0.0, kValue.f16.subnormal.positive.min, kValue.f16.subnormal.positive.max ] },
|
{ neg_norm: 0, neg_sub: 0, pos_sub: 2, pos_norm: 0, expect: [ -0.0, 0.0, kValue.f16.positive.subnormal.min, kValue.f16.positive.subnormal.max ] },
|
||||||
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 1, expect: [ 0.0, kValue.f16.positive.min ] },
|
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 1, expect: [ -0.0, 0.0, kValue.f16.positive.min ] },
|
||||||
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 2, expect: [ 0.0, kValue.f16.positive.min, kValue.f16.positive.max ] },
|
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 2, expect: [ -0.0, 0.0, kValue.f16.positive.min, kValue.f16.positive.max ] },
|
||||||
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 3, expect: [ 0.0, kValue.f16.positive.min, 1.9990234375, kValue.f16.positive.max ] },
|
{ neg_norm: 0, neg_sub: 0, pos_sub: 0, pos_norm: 3, expect: [ -0.0, 0.0, kValue.f16.positive.min, 1.9990234375, kValue.f16.positive.max ] },
|
||||||
{ neg_norm: 1, neg_sub: 1, pos_sub: 1, pos_norm: 1, expect: [ kValue.f16.negative.min, kValue.f16.subnormal.negative.min, 0.0, kValue.f16.subnormal.positive.min, kValue.f16.positive.min ] },
|
{ neg_norm: 1, neg_sub: 1, pos_sub: 1, pos_norm: 1, expect: [ kValue.f16.negative.min, kValue.f16.negative.subnormal.min, -0.0, 0.0, kValue.f16.positive.subnormal.min, kValue.f16.positive.min ] },
|
||||||
{ neg_norm: 2, neg_sub: 2, pos_sub: 2, pos_norm: 2, expect: [ kValue.f16.negative.min, kValue.f16.negative.max, kValue.f16.subnormal.negative.min, kValue.f16.subnormal.negative.max, 0.0, kValue.f16.subnormal.positive.min, kValue.f16.subnormal.positive.max, kValue.f16.positive.min, kValue.f16.positive.max ] },
|
{ neg_norm: 2, neg_sub: 2, pos_sub: 2, pos_norm: 2, expect: [ kValue.f16.negative.min, kValue.f16.negative.max, kValue.f16.negative.subnormal.min, kValue.f16.negative.subnormal.max, -0.0, 0.0, kValue.f16.positive.subnormal.min, kValue.f16.positive.subnormal.max, kValue.f16.positive.min, kValue.f16.positive.max ] },
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
.fn(test => {
|
.fn(test => {
|
||||||
const neg_norm = test.params.neg_norm;
|
const neg_norm = test.params.neg_norm;
|
||||||
@@ -1667,12 +1669,12 @@ g.test('f64LimitsEquivalency')
|
|||||||
{ bits: kBit.f64.negative.pi.third, value: kValue.f64.negative.pi.third },
|
{ bits: kBit.f64.negative.pi.third, value: kValue.f64.negative.pi.third },
|
||||||
{ bits: kBit.f64.negative.pi.quarter, value: kValue.f64.negative.pi.quarter },
|
{ bits: kBit.f64.negative.pi.quarter, value: kValue.f64.negative.pi.quarter },
|
||||||
{ bits: kBit.f64.negative.pi.sixth, value: kValue.f64.negative.pi.sixth },
|
{ bits: kBit.f64.negative.pi.sixth, value: kValue.f64.negative.pi.sixth },
|
||||||
{ bits: kBit.f64.subnormal.positive.max, value: kValue.f64.subnormal.positive.max },
|
{ bits: kBit.f64.positive.subnormal.max, value: kValue.f64.positive.subnormal.max },
|
||||||
{ bits: kBit.f64.subnormal.positive.min, value: kValue.f64.subnormal.positive.min },
|
{ bits: kBit.f64.positive.subnormal.min, value: kValue.f64.positive.subnormal.min },
|
||||||
{ bits: kBit.f64.subnormal.negative.max, value: kValue.f64.subnormal.negative.max },
|
{ bits: kBit.f64.negative.subnormal.max, value: kValue.f64.negative.subnormal.max },
|
||||||
{ bits: kBit.f64.subnormal.negative.min, value: kValue.f64.subnormal.negative.min },
|
{ bits: kBit.f64.negative.subnormal.min, value: kValue.f64.negative.subnormal.min },
|
||||||
{ bits: kBit.f64.infinity.positive, value: kValue.f64.infinity.positive },
|
{ bits: kBit.f64.positive.infinity, value: kValue.f64.positive.infinity },
|
||||||
{ bits: kBit.f64.infinity.negative, value: kValue.f64.infinity.negative },
|
{ bits: kBit.f64.negative.infinity, value: kValue.f64.negative.infinity },
|
||||||
])
|
])
|
||||||
.fn(test => {
|
.fn(test => {
|
||||||
const bits = test.params.bits;
|
const bits = test.params.bits;
|
||||||
@@ -1715,12 +1717,12 @@ g.test('f32LimitsEquivalency')
|
|||||||
{ bits: kBit.f32.negative.pi.third, value: kValue.f32.negative.pi.third },
|
{ bits: kBit.f32.negative.pi.third, value: kValue.f32.negative.pi.third },
|
||||||
{ bits: kBit.f32.negative.pi.quarter, value: kValue.f32.negative.pi.quarter },
|
{ bits: kBit.f32.negative.pi.quarter, value: kValue.f32.negative.pi.quarter },
|
||||||
{ bits: kBit.f32.negative.pi.sixth, value: kValue.f32.negative.pi.sixth },
|
{ bits: kBit.f32.negative.pi.sixth, value: kValue.f32.negative.pi.sixth },
|
||||||
{ bits: kBit.f32.subnormal.positive.max, value: kValue.f32.subnormal.positive.max },
|
{ bits: kBit.f32.positive.subnormal.max, value: kValue.f32.positive.subnormal.max },
|
||||||
{ bits: kBit.f32.subnormal.positive.min, value: kValue.f32.subnormal.positive.min },
|
{ bits: kBit.f32.positive.subnormal.min, value: kValue.f32.positive.subnormal.min },
|
||||||
{ bits: kBit.f32.subnormal.negative.max, value: kValue.f32.subnormal.negative.max },
|
{ bits: kBit.f32.negative.subnormal.max, value: kValue.f32.negative.subnormal.max },
|
||||||
{ bits: kBit.f32.subnormal.negative.min, value: kValue.f32.subnormal.negative.min },
|
{ bits: kBit.f32.negative.subnormal.min, value: kValue.f32.negative.subnormal.min },
|
||||||
{ bits: kBit.f32.infinity.positive, value: kValue.f32.infinity.positive },
|
{ bits: kBit.f32.positive.infinity, value: kValue.f32.positive.infinity },
|
||||||
{ bits: kBit.f32.infinity.negative, value: kValue.f32.infinity.negative },
|
{ bits: kBit.f32.negative.infinity, value: kValue.f32.negative.infinity },
|
||||||
])
|
])
|
||||||
.fn(test => {
|
.fn(test => {
|
||||||
const bits = test.params.bits;
|
const bits = test.params.bits;
|
||||||
@@ -1758,12 +1760,12 @@ g.test('f16LimitsEquivalency')
|
|||||||
{ bits: kBit.f16.negative.pi.third, value: kValue.f16.negative.pi.third },
|
{ bits: kBit.f16.negative.pi.third, value: kValue.f16.negative.pi.third },
|
||||||
{ bits: kBit.f16.negative.pi.quarter, value: kValue.f16.negative.pi.quarter },
|
{ bits: kBit.f16.negative.pi.quarter, value: kValue.f16.negative.pi.quarter },
|
||||||
{ bits: kBit.f16.negative.pi.sixth, value: kValue.f16.negative.pi.sixth },
|
{ bits: kBit.f16.negative.pi.sixth, value: kValue.f16.negative.pi.sixth },
|
||||||
{ bits: kBit.f16.subnormal.positive.max, value: kValue.f16.subnormal.positive.max },
|
{ bits: kBit.f16.positive.subnormal.max, value: kValue.f16.positive.subnormal.max },
|
||||||
{ bits: kBit.f16.subnormal.positive.min, value: kValue.f16.subnormal.positive.min },
|
{ bits: kBit.f16.positive.subnormal.min, value: kValue.f16.positive.subnormal.min },
|
||||||
{ bits: kBit.f16.subnormal.negative.max, value: kValue.f16.subnormal.negative.max },
|
{ bits: kBit.f16.negative.subnormal.max, value: kValue.f16.negative.subnormal.max },
|
||||||
{ bits: kBit.f16.subnormal.negative.min, value: kValue.f16.subnormal.negative.min },
|
{ bits: kBit.f16.negative.subnormal.min, value: kValue.f16.negative.subnormal.min },
|
||||||
{ bits: kBit.f16.infinity.positive, value: kValue.f16.infinity.positive },
|
{ bits: kBit.f16.positive.infinity, value: kValue.f16.positive.infinity },
|
||||||
{ bits: kBit.f16.infinity.negative, value: kValue.f16.infinity.negative },
|
{ bits: kBit.f16.negative.infinity, value: kValue.f16.negative.infinity },
|
||||||
])
|
])
|
||||||
.fn(test => {
|
.fn(test => {
|
||||||
const bits = test.params.bits;
|
const bits = test.params.bits;
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ import { assert, objectEquals } from '../common/util/util.js';
|
|||||||
import { UnitTest } from './unit_test.js';
|
import { UnitTest } from './unit_test.js';
|
||||||
|
|
||||||
class ParamsTest extends UnitTest {
|
class ParamsTest extends UnitTest {
|
||||||
expectParams<CaseP, SubcaseP>(
|
expectParams<CaseP extends {}, SubcaseP extends {}>(
|
||||||
act: ParamsBuilderBase<CaseP, SubcaseP>,
|
act: ParamsBuilderBase<CaseP, SubcaseP>,
|
||||||
exp: CaseSubcaseIterable<{}, {}>,
|
exp: CaseSubcaseIterable<{}, {}>,
|
||||||
caseFilter: TestParams | null = null
|
caseFilter: TestParams | null = null
|
||||||
): void {
|
): void {
|
||||||
const a = Array.from(
|
const a = Array.from(builderIterateCasesWithSubcases(act, caseFilter)).map(
|
||||||
builderIterateCasesWithSubcases(act, caseFilter)
|
([caseP, subcases]) => [caseP, subcases ? Array.from(subcases) : undefined]
|
||||||
).map(([caseP, subcases]) => [caseP, subcases ? Array.from(subcases) : undefined]);
|
);
|
||||||
const e = Array.from(exp);
|
const e = Array.from(exp);
|
||||||
this.expect(
|
this.expect(
|
||||||
objectEquals(a, e),
|
objectEquals(a, e),
|
||||||
|
|||||||
@@ -130,4 +130,15 @@ g.test('unordered').fn(t => {
|
|||||||
new TestQuerySingleCase('suite1', ['bar', 'buzz', 'buzz'], ['zap'], {}),
|
new TestQuerySingleCase('suite1', ['bar', 'buzz', 'buzz'], ['zap'], {}),
|
||||||
new TestQueryMultiTest('suite1', ['bar'], [])
|
new TestQueryMultiTest('suite1', ['bar'], [])
|
||||||
);
|
);
|
||||||
|
// Expect that 0.0 and -0.0 are treated as different queries
|
||||||
|
t.expectUnordered(
|
||||||
|
new TestQueryMultiCase('suite', ['a', 'b'], ['c', 'd'], { x: 0.0 }),
|
||||||
|
new TestQueryMultiCase('suite', ['a', 'b'], ['c', 'd'], { x: -0.0 })
|
||||||
|
);
|
||||||
|
t.expectUnordered(
|
||||||
|
new TestQuerySingleCase('suite', ['a', 'b'], ['c', 'd'], { x: 0.0, y: 0.0 }),
|
||||||
|
new TestQuerySingleCase('suite', ['a', 'b'], ['c', 'd'], { x: 0.0, y: -0.0 }),
|
||||||
|
new TestQuerySingleCase('suite', ['a', 'b'], ['c', 'd'], { x: -0.0, y: 0.0 }),
|
||||||
|
new TestQuerySingleCase('suite', ['a', 'b'], ['c', 'd'], { x: -0.0, y: -0.0 })
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
deserializeExpectation,
|
deserializeExpectation,
|
||||||
serializeExpectation,
|
serializeExpectation,
|
||||||
} from '../webgpu/shader/execution/expression/case_cache.js';
|
} from '../webgpu/shader/execution/expression/case_cache.js';
|
||||||
|
import BinaryStream from '../webgpu/util/binary_stream.js';
|
||||||
import {
|
import {
|
||||||
anyOf,
|
anyOf,
|
||||||
deserializeComparator,
|
deserializeComparator,
|
||||||
@@ -104,12 +105,12 @@ g.test('value').fn(t => {
|
|||||||
f32(-0.5),
|
f32(-0.5),
|
||||||
f32(kValue.f32.positive.max),
|
f32(kValue.f32.positive.max),
|
||||||
f32(kValue.f32.positive.min),
|
f32(kValue.f32.positive.min),
|
||||||
f32(kValue.f32.subnormal.positive.max),
|
f32(kValue.f32.positive.subnormal.max),
|
||||||
f32(kValue.f32.subnormal.positive.min),
|
f32(kValue.f32.positive.subnormal.min),
|
||||||
f32(kValue.f32.subnormal.negative.max),
|
f32(kValue.f32.negative.subnormal.max),
|
||||||
f32(kValue.f32.subnormal.negative.min),
|
f32(kValue.f32.negative.subnormal.min),
|
||||||
f32(kValue.f32.infinity.positive),
|
f32(kValue.f32.positive.infinity),
|
||||||
f32(kValue.f32.infinity.negative),
|
f32(kValue.f32.negative.infinity),
|
||||||
|
|
||||||
f16(0),
|
f16(0),
|
||||||
f16(-0),
|
f16(-0),
|
||||||
@@ -117,14 +118,14 @@ g.test('value').fn(t => {
|
|||||||
f16(-1),
|
f16(-1),
|
||||||
f16(0.5),
|
f16(0.5),
|
||||||
f16(-0.5),
|
f16(-0.5),
|
||||||
f16(kValue.f32.positive.max),
|
f16(kValue.f16.positive.max),
|
||||||
f16(kValue.f32.positive.min),
|
f16(kValue.f16.positive.min),
|
||||||
f16(kValue.f32.subnormal.positive.max),
|
f16(kValue.f16.positive.subnormal.max),
|
||||||
f16(kValue.f32.subnormal.positive.min),
|
f16(kValue.f16.positive.subnormal.min),
|
||||||
f16(kValue.f32.subnormal.negative.max),
|
f16(kValue.f16.negative.subnormal.max),
|
||||||
f16(kValue.f32.subnormal.negative.min),
|
f16(kValue.f16.negative.subnormal.min),
|
||||||
f16(kValue.f32.infinity.positive),
|
f16(kValue.f16.positive.infinity),
|
||||||
f16(kValue.f32.infinity.negative),
|
f16(kValue.f16.negative.infinity),
|
||||||
|
|
||||||
bool(true),
|
bool(true),
|
||||||
bool(false),
|
bool(false),
|
||||||
@@ -145,7 +146,7 @@ g.test('value').fn(t => {
|
|||||||
[0.0, 1.0, 2.0],
|
[0.0, 1.0, 2.0],
|
||||||
[3.0, 4.0, 5.0],
|
[3.0, 4.0, 5.0],
|
||||||
],
|
],
|
||||||
f32
|
f16
|
||||||
),
|
),
|
||||||
toMatrix(
|
toMatrix(
|
||||||
[
|
[
|
||||||
@@ -160,7 +161,7 @@ g.test('value').fn(t => {
|
|||||||
[2.0, 3.0],
|
[2.0, 3.0],
|
||||||
[4.0, 5.0],
|
[4.0, 5.0],
|
||||||
],
|
],
|
||||||
f32
|
f16
|
||||||
),
|
),
|
||||||
toMatrix(
|
toMatrix(
|
||||||
[
|
[
|
||||||
@@ -176,7 +177,7 @@ g.test('value').fn(t => {
|
|||||||
[4.0, 5.0, 6.0, 7.0],
|
[4.0, 5.0, 6.0, 7.0],
|
||||||
[8.0, 9.0, 10.0, 11.0],
|
[8.0, 9.0, 10.0, 11.0],
|
||||||
],
|
],
|
||||||
f32
|
f16
|
||||||
),
|
),
|
||||||
toMatrix(
|
toMatrix(
|
||||||
[
|
[
|
||||||
@@ -194,7 +195,7 @@ g.test('value').fn(t => {
|
|||||||
[6.0, 7.0, 8.0],
|
[6.0, 7.0, 8.0],
|
||||||
[9.0, 10.0, 11.0],
|
[9.0, 10.0, 11.0],
|
||||||
],
|
],
|
||||||
f32
|
f16
|
||||||
),
|
),
|
||||||
toMatrix(
|
toMatrix(
|
||||||
[
|
[
|
||||||
@@ -206,11 +207,14 @@ g.test('value').fn(t => {
|
|||||||
f32
|
f32
|
||||||
),
|
),
|
||||||
]) {
|
]) {
|
||||||
const serialized = serializeValue(value);
|
const s = new BinaryStream(new Uint8Array(1024).buffer);
|
||||||
const deserialized = deserializeValue(serialized);
|
serializeValue(s, value);
|
||||||
|
const d = new BinaryStream(s.buffer().buffer);
|
||||||
|
const deserialized = deserializeValue(d);
|
||||||
t.expect(
|
t.expect(
|
||||||
objectEquals(value, deserialized),
|
objectEquals(value, deserialized),
|
||||||
`value ${value} -> serialize -> deserialize -> ${deserialized}`
|
`${value.type} ${value} -> serialize -> deserialize -> ${deserialized}
|
||||||
|
buffer: ${s.buffer()}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -225,23 +229,61 @@ g.test('fpinterval_f32').fn(t => {
|
|||||||
FP.f32.toInterval(-0.5),
|
FP.f32.toInterval(-0.5),
|
||||||
FP.f32.toInterval(kValue.f32.positive.max),
|
FP.f32.toInterval(kValue.f32.positive.max),
|
||||||
FP.f32.toInterval(kValue.f32.positive.min),
|
FP.f32.toInterval(kValue.f32.positive.min),
|
||||||
FP.f32.toInterval(kValue.f32.subnormal.positive.max),
|
FP.f32.toInterval(kValue.f32.positive.subnormal.max),
|
||||||
FP.f32.toInterval(kValue.f32.subnormal.positive.min),
|
FP.f32.toInterval(kValue.f32.positive.subnormal.min),
|
||||||
FP.f32.toInterval(kValue.f32.subnormal.negative.max),
|
FP.f32.toInterval(kValue.f32.negative.subnormal.max),
|
||||||
FP.f32.toInterval(kValue.f32.subnormal.negative.min),
|
FP.f32.toInterval(kValue.f32.negative.subnormal.min),
|
||||||
FP.f32.toInterval(kValue.f32.infinity.positive),
|
FP.f32.toInterval(kValue.f32.positive.infinity),
|
||||||
FP.f32.toInterval(kValue.f32.infinity.negative),
|
FP.f32.toInterval(kValue.f32.negative.infinity),
|
||||||
|
|
||||||
FP.f32.toInterval([-0, 0]),
|
FP.f32.toInterval([-0, 0]),
|
||||||
FP.f32.toInterval([-1, 1]),
|
FP.f32.toInterval([-1, 1]),
|
||||||
FP.f32.toInterval([-0.5, 0.5]),
|
FP.f32.toInterval([-0.5, 0.5]),
|
||||||
FP.f32.toInterval([kValue.f32.positive.min, kValue.f32.positive.max]),
|
FP.f32.toInterval([kValue.f32.positive.min, kValue.f32.positive.max]),
|
||||||
FP.f32.toInterval([kValue.f32.subnormal.positive.min, kValue.f32.subnormal.positive.max]),
|
FP.f32.toInterval([kValue.f32.positive.subnormal.min, kValue.f32.positive.subnormal.max]),
|
||||||
FP.f32.toInterval([kValue.f32.subnormal.negative.min, kValue.f32.subnormal.negative.max]),
|
FP.f32.toInterval([kValue.f32.negative.subnormal.min, kValue.f32.negative.subnormal.max]),
|
||||||
FP.f32.toInterval([kValue.f32.infinity.negative, kValue.f32.infinity.positive]),
|
FP.f32.toInterval([kValue.f32.negative.infinity, kValue.f32.positive.infinity]),
|
||||||
]) {
|
]) {
|
||||||
const serialized = serializeFPInterval(interval);
|
const s = new BinaryStream(new Uint8Array(1024).buffer);
|
||||||
const deserialized = deserializeFPInterval(serialized);
|
serializeFPInterval(s, interval);
|
||||||
|
const d = new BinaryStream(s.buffer().buffer);
|
||||||
|
const deserialized = deserializeFPInterval(d);
|
||||||
|
t.expect(
|
||||||
|
objectEquals(interval, deserialized),
|
||||||
|
`interval ${interval} -> serialize -> deserialize -> ${deserialized}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
g.test('fpinterval_f16').fn(t => {
|
||||||
|
for (const interval of [
|
||||||
|
FP.f16.toInterval(0),
|
||||||
|
FP.f16.toInterval(-0),
|
||||||
|
FP.f16.toInterval(1),
|
||||||
|
FP.f16.toInterval(-1),
|
||||||
|
FP.f16.toInterval(0.5),
|
||||||
|
FP.f16.toInterval(-0.5),
|
||||||
|
FP.f16.toInterval(kValue.f16.positive.max),
|
||||||
|
FP.f16.toInterval(kValue.f16.positive.min),
|
||||||
|
FP.f16.toInterval(kValue.f16.positive.subnormal.max),
|
||||||
|
FP.f16.toInterval(kValue.f16.positive.subnormal.min),
|
||||||
|
FP.f16.toInterval(kValue.f16.negative.subnormal.max),
|
||||||
|
FP.f16.toInterval(kValue.f16.negative.subnormal.min),
|
||||||
|
FP.f16.toInterval(kValue.f16.positive.infinity),
|
||||||
|
FP.f16.toInterval(kValue.f16.negative.infinity),
|
||||||
|
|
||||||
|
FP.f16.toInterval([-0, 0]),
|
||||||
|
FP.f16.toInterval([-1, 1]),
|
||||||
|
FP.f16.toInterval([-0.5, 0.5]),
|
||||||
|
FP.f16.toInterval([kValue.f16.positive.min, kValue.f16.positive.max]),
|
||||||
|
FP.f16.toInterval([kValue.f16.positive.subnormal.min, kValue.f16.positive.subnormal.max]),
|
||||||
|
FP.f16.toInterval([kValue.f16.negative.subnormal.min, kValue.f16.negative.subnormal.max]),
|
||||||
|
FP.f16.toInterval([kValue.f16.negative.infinity, kValue.f16.positive.infinity]),
|
||||||
|
]) {
|
||||||
|
const s = new BinaryStream(new Uint8Array(1024).buffer);
|
||||||
|
serializeFPInterval(s, interval);
|
||||||
|
const d = new BinaryStream(s.buffer().buffer);
|
||||||
|
const deserialized = deserializeFPInterval(d);
|
||||||
t.expect(
|
t.expect(
|
||||||
objectEquals(interval, deserialized),
|
objectEquals(interval, deserialized),
|
||||||
`interval ${interval} -> serialize -> deserialize -> ${deserialized}`
|
`interval ${interval} -> serialize -> deserialize -> ${deserialized}`
|
||||||
@@ -259,23 +301,25 @@ g.test('fpinterval_abstract').fn(t => {
|
|||||||
FP.abstract.toInterval(-0.5),
|
FP.abstract.toInterval(-0.5),
|
||||||
FP.abstract.toInterval(kValue.f64.positive.max),
|
FP.abstract.toInterval(kValue.f64.positive.max),
|
||||||
FP.abstract.toInterval(kValue.f64.positive.min),
|
FP.abstract.toInterval(kValue.f64.positive.min),
|
||||||
FP.abstract.toInterval(kValue.f64.subnormal.positive.max),
|
FP.abstract.toInterval(kValue.f64.positive.subnormal.max),
|
||||||
FP.abstract.toInterval(kValue.f64.subnormal.positive.min),
|
FP.abstract.toInterval(kValue.f64.positive.subnormal.min),
|
||||||
FP.abstract.toInterval(kValue.f64.subnormal.negative.max),
|
FP.abstract.toInterval(kValue.f64.negative.subnormal.max),
|
||||||
FP.abstract.toInterval(kValue.f64.subnormal.negative.min),
|
FP.abstract.toInterval(kValue.f64.negative.subnormal.min),
|
||||||
FP.abstract.toInterval(kValue.f64.infinity.positive),
|
FP.abstract.toInterval(kValue.f64.positive.infinity),
|
||||||
FP.abstract.toInterval(kValue.f64.infinity.negative),
|
FP.abstract.toInterval(kValue.f64.negative.infinity),
|
||||||
|
|
||||||
FP.abstract.toInterval([-0, 0]),
|
FP.abstract.toInterval([-0, 0]),
|
||||||
FP.abstract.toInterval([-1, 1]),
|
FP.abstract.toInterval([-1, 1]),
|
||||||
FP.abstract.toInterval([-0.5, 0.5]),
|
FP.abstract.toInterval([-0.5, 0.5]),
|
||||||
FP.abstract.toInterval([kValue.f64.positive.min, kValue.f64.positive.max]),
|
FP.abstract.toInterval([kValue.f64.positive.min, kValue.f64.positive.max]),
|
||||||
FP.abstract.toInterval([kValue.f64.subnormal.positive.min, kValue.f64.subnormal.positive.max]),
|
FP.abstract.toInterval([kValue.f64.positive.subnormal.min, kValue.f64.positive.subnormal.max]),
|
||||||
FP.abstract.toInterval([kValue.f64.subnormal.negative.min, kValue.f64.subnormal.negative.max]),
|
FP.abstract.toInterval([kValue.f64.negative.subnormal.min, kValue.f64.negative.subnormal.max]),
|
||||||
FP.abstract.toInterval([kValue.f64.infinity.negative, kValue.f64.infinity.positive]),
|
FP.abstract.toInterval([kValue.f64.negative.infinity, kValue.f64.positive.infinity]),
|
||||||
]) {
|
]) {
|
||||||
const serialized = serializeFPInterval(interval);
|
const s = new BinaryStream(new Uint8Array(1024).buffer);
|
||||||
const deserialized = deserializeFPInterval(serialized);
|
serializeFPInterval(s, interval);
|
||||||
|
const d = new BinaryStream(s.buffer().buffer);
|
||||||
|
const deserialized = deserializeFPInterval(d);
|
||||||
t.expect(
|
t.expect(
|
||||||
objectEquals(interval, deserialized),
|
objectEquals(interval, deserialized),
|
||||||
`interval ${interval} -> serialize -> deserialize -> ${deserialized}`
|
`interval ${interval} -> serialize -> deserialize -> ${deserialized}`
|
||||||
@@ -294,8 +338,10 @@ g.test('expression_expectation').fn(t => {
|
|||||||
// Intervals
|
// Intervals
|
||||||
[FP.f32.toInterval([-8.0, 0.5]), FP.f32.toInterval([2.0, 4.0])],
|
[FP.f32.toInterval([-8.0, 0.5]), FP.f32.toInterval([2.0, 4.0])],
|
||||||
]) {
|
]) {
|
||||||
const serialized = serializeExpectation(expectation);
|
const s = new BinaryStream(new Uint8Array(1024).buffer);
|
||||||
const deserialized = deserializeExpectation(serialized);
|
serializeExpectation(s, expectation);
|
||||||
|
const d = new BinaryStream(s.buffer().buffer);
|
||||||
|
const deserialized = deserializeExpectation(d);
|
||||||
t.expect(
|
t.expect(
|
||||||
objectEquals(expectation, deserialized),
|
objectEquals(expectation, deserialized),
|
||||||
`expectation ${expectation} -> serialize -> deserialize -> ${deserialized}`
|
`expectation ${expectation} -> serialize -> deserialize -> ${deserialized}`
|
||||||
@@ -322,8 +368,10 @@ g.test('anyOf').fn(t => {
|
|||||||
testCases: [f32(0), f32(10), f32(122), f32(123), f32(124), f32(200)],
|
testCases: [f32(0), f32(10), f32(122), f32(123), f32(124), f32(200)],
|
||||||
},
|
},
|
||||||
]) {
|
]) {
|
||||||
const serialized = serializeComparator(c.comparator);
|
const s = new BinaryStream(new Uint8Array(1024).buffer);
|
||||||
const deserialized = deserializeComparator(serialized);
|
serializeComparator(s, c.comparator);
|
||||||
|
const d = new BinaryStream(s.buffer().buffer);
|
||||||
|
const deserialized = deserializeComparator(d);
|
||||||
for (const val of c.testCases) {
|
for (const val of c.testCases) {
|
||||||
const got = deserialized.compare(val);
|
const got = deserialized.compare(val);
|
||||||
const expect = c.comparator.compare(val);
|
const expect = c.comparator.compare(val);
|
||||||
@@ -348,8 +396,10 @@ g.test('skipUndefined').fn(t => {
|
|||||||
testCases: [f32(0), f32(10), f32(122), f32(123), f32(124), f32(200)],
|
testCases: [f32(0), f32(10), f32(122), f32(123), f32(124), f32(200)],
|
||||||
},
|
},
|
||||||
]) {
|
]) {
|
||||||
const serialized = serializeComparator(c.comparator);
|
const s = new BinaryStream(new Uint8Array(1024).buffer);
|
||||||
const deserialized = deserializeComparator(serialized);
|
serializeComparator(s, c.comparator);
|
||||||
|
const d = new BinaryStream(s.buffer().buffer);
|
||||||
|
const deserialized = deserializeComparator(d);
|
||||||
for (const val of c.testCases) {
|
for (const val of c.testCases) {
|
||||||
const got = deserialized.compare(val);
|
const got = deserialized.compare(val);
|
||||||
const expect = c.comparator.compare(val);
|
const expect = c.comparator.compare(val);
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ Unit tests for TestGroup.
|
|||||||
|
|
||||||
import { Fixture } from '../common/framework/fixture.js';
|
import { Fixture } from '../common/framework/fixture.js';
|
||||||
import { makeTestGroup } from '../common/framework/test_group.js';
|
import { makeTestGroup } from '../common/framework/test_group.js';
|
||||||
import { makeTestGroupForUnitTesting } from '../common/internal/test_group.js';
|
import { TestQueryMultiFile } from '../common/internal/query/query.js';
|
||||||
|
import { kQueryMaxLength, makeTestGroupForUnitTesting } from '../common/internal/test_group.js';
|
||||||
import { assert } from '../common/util/util.js';
|
import { assert } from '../common/util/util.js';
|
||||||
|
|
||||||
import { TestGroupTest } from './test_group_test.js';
|
import { TestGroupTest } from './test_group_test.js';
|
||||||
@@ -15,7 +16,7 @@ export const g = makeTestGroup(TestGroupTest);
|
|||||||
|
|
||||||
g.test('UnitTest_fixture').fn(async t0 => {
|
g.test('UnitTest_fixture').fn(async t0 => {
|
||||||
let seen = 0;
|
let seen = 0;
|
||||||
function count(t: Fixture): void {
|
function count(_t: Fixture): void {
|
||||||
seen++;
|
seen++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,10 +66,10 @@ g.test('stack').fn(async t0 => {
|
|||||||
g.test('fail').fn(t => {
|
g.test('fail').fn(t => {
|
||||||
t.fail();
|
t.fail();
|
||||||
});
|
});
|
||||||
g.test('throw').fn(t => {
|
g.test('throw').fn(_t => {
|
||||||
throw new Error('hello');
|
throw new Error('hello');
|
||||||
});
|
});
|
||||||
g.test('throw_nested').fn(t => {
|
g.test('throw_nested').fn(_t => {
|
||||||
doNestedThrow2();
|
doNestedThrow2();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ g.test('no_fn').fn(t => {
|
|||||||
g.test('missing');
|
g.test('missing');
|
||||||
|
|
||||||
t.shouldThrow('Error', () => {
|
t.shouldThrow('Error', () => {
|
||||||
g.validate();
|
g.validate(new TestQueryMultiFile('s', ['f']));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -108,13 +109,13 @@ g.test('duplicate_test_params,none').fn(() => {
|
|||||||
g.test('abc')
|
g.test('abc')
|
||||||
.paramsSimple([])
|
.paramsSimple([])
|
||||||
.fn(() => {});
|
.fn(() => {});
|
||||||
g.validate();
|
g.validate(new TestQueryMultiFile('s', ['f']));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const g = makeTestGroupForUnitTesting(UnitTest);
|
const g = makeTestGroupForUnitTesting(UnitTest);
|
||||||
g.test('abc').fn(() => {});
|
g.test('abc').fn(() => {});
|
||||||
g.validate();
|
g.validate(new TestQueryMultiFile('s', ['f']));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -124,7 +125,7 @@ g.test('duplicate_test_params,none').fn(() => {
|
|||||||
{ a: 1 }, //
|
{ a: 1 }, //
|
||||||
])
|
])
|
||||||
.fn(() => {});
|
.fn(() => {});
|
||||||
g.validate();
|
g.validate(new TestQueryMultiFile('s', ['f']));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ g.test('duplicate_test_params,basic').fn(t => {
|
|||||||
{ a: 1 }, //
|
{ a: 1 }, //
|
||||||
{ a: 1 },
|
{ a: 1 },
|
||||||
]);
|
]);
|
||||||
g.validate();
|
g.validate(new TestQueryMultiFile('s', ['f']));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -151,7 +152,7 @@ g.test('duplicate_test_params,basic').fn(t => {
|
|||||||
)
|
)
|
||||||
.fn(() => {});
|
.fn(() => {});
|
||||||
t.shouldThrow('Error', () => {
|
t.shouldThrow('Error', () => {
|
||||||
g.validate();
|
g.validate(new TestQueryMultiFile('s', ['f']));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -163,7 +164,7 @@ g.test('duplicate_test_params,basic').fn(t => {
|
|||||||
])
|
])
|
||||||
.fn(() => {});
|
.fn(() => {});
|
||||||
t.shouldThrow('Error', () => {
|
t.shouldThrow('Error', () => {
|
||||||
g.validate();
|
g.validate(new TestQueryMultiFile('s', ['f']));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -190,7 +191,7 @@ g.test('duplicate_test_params,with_different_private_params').fn(t => {
|
|||||||
)
|
)
|
||||||
.fn(() => {});
|
.fn(() => {});
|
||||||
t.shouldThrow('Error', () => {
|
t.shouldThrow('Error', () => {
|
||||||
g.validate();
|
g.validate(new TestQueryMultiFile('s', ['f']));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -206,11 +207,72 @@ g.test('invalid_test_name').fn(t => {
|
|||||||
() => {
|
() => {
|
||||||
g.test(name).fn(() => {});
|
g.test(name).fn(() => {});
|
||||||
},
|
},
|
||||||
name
|
{ message: name }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
g.test('long_test_query,long_test_name').fn(t => {
|
||||||
|
const g = makeTestGroupForUnitTesting(UnitTest);
|
||||||
|
|
||||||
|
const long = Array(kQueryMaxLength - 5).join('a');
|
||||||
|
|
||||||
|
const fileQuery = new TestQueryMultiFile('s', ['f']);
|
||||||
|
g.test(long).unimplemented();
|
||||||
|
g.validate(fileQuery);
|
||||||
|
|
||||||
|
g.test(long + 'a').unimplemented();
|
||||||
|
t.shouldThrow(
|
||||||
|
'Error',
|
||||||
|
() => {
|
||||||
|
g.validate(fileQuery);
|
||||||
|
},
|
||||||
|
{ message: long }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
g.test('long_case_query,long_test_name').fn(t => {
|
||||||
|
const g = makeTestGroupForUnitTesting(UnitTest);
|
||||||
|
|
||||||
|
const long = Array(kQueryMaxLength - 5).join('a');
|
||||||
|
|
||||||
|
const fileQuery = new TestQueryMultiFile('s', ['f']);
|
||||||
|
g.test(long).fn(() => {});
|
||||||
|
g.validate(fileQuery);
|
||||||
|
|
||||||
|
g.test(long + 'a').fn(() => {});
|
||||||
|
t.shouldThrow(
|
||||||
|
'Error',
|
||||||
|
() => {
|
||||||
|
g.validate(fileQuery);
|
||||||
|
},
|
||||||
|
{ message: long }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
g.test('long_case_query,long_case_name').fn(t => {
|
||||||
|
const g = makeTestGroupForUnitTesting(UnitTest);
|
||||||
|
|
||||||
|
const long = Array(kQueryMaxLength - 9).join('a');
|
||||||
|
|
||||||
|
const fileQuery = new TestQueryMultiFile('s', ['f']);
|
||||||
|
g.test('t')
|
||||||
|
.paramsSimple([{ x: long }])
|
||||||
|
.fn(() => {});
|
||||||
|
g.validate(fileQuery);
|
||||||
|
|
||||||
|
g.test('u')
|
||||||
|
.paramsSimple([{ x: long + 'a' }])
|
||||||
|
.fn(() => {});
|
||||||
|
t.shouldThrow(
|
||||||
|
'Error',
|
||||||
|
() => {
|
||||||
|
g.validate(fileQuery);
|
||||||
|
},
|
||||||
|
{ message: long }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
g.test('param_value,valid').fn(() => {
|
g.test('param_value,valid').fn(() => {
|
||||||
const g = makeTestGroup(UnitTest);
|
const g = makeTestGroup(UnitTest);
|
||||||
g.test('a').paramsSimple([{ x: JSON.stringify({ a: 1, b: 2 }) }]);
|
g.test('a').paramsSimple([{ x: JSON.stringify({ a: 1, b: 2 }) }]);
|
||||||
@@ -262,6 +324,29 @@ g.test('subcases').fn(async t0 => {
|
|||||||
t0.expect(Array.from(result.values()).every(v => v.status === 'pass'));
|
t0.expect(Array.from(result.values()).every(v => v.status === 'pass'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
g.test('subcases,skip')
|
||||||
|
.desc(
|
||||||
|
'If all tests are skipped then status is "skip". If at least one test passed, status is "pass"'
|
||||||
|
)
|
||||||
|
.params(u => u.combine('allSkip', [false, true]))
|
||||||
|
.fn(async t0 => {
|
||||||
|
const { allSkip } = t0.params;
|
||||||
|
const g = makeTestGroupForUnitTesting(UnitTest);
|
||||||
|
g.test('a')
|
||||||
|
.params(u => u.beginSubcases().combine('do', ['pass', 'skip', 'pass']))
|
||||||
|
.fn(t => {
|
||||||
|
t.skipIf(allSkip || t.params.do === 'skip');
|
||||||
|
});
|
||||||
|
const result = await t0.run(g);
|
||||||
|
const values = Array.from(result.values());
|
||||||
|
t0.expect(values.length === 1);
|
||||||
|
const expectedStatus = allSkip ? 'skip' : 'pass';
|
||||||
|
t0.expect(
|
||||||
|
values[0].status === expectedStatus,
|
||||||
|
`expect: ${values[0].status} === ${expectedStatus}}, allSkip: ${allSkip}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
g.test('exceptions')
|
g.test('exceptions')
|
||||||
.params(u =>
|
.params(u =>
|
||||||
u
|
u
|
||||||
@@ -279,7 +364,7 @@ g.test('exceptions')
|
|||||||
} else {
|
} else {
|
||||||
b2 = b1.params(u => u);
|
b2 = b1.params(u => u);
|
||||||
}
|
}
|
||||||
b2.fn(t => {
|
b2.fn(_t => {
|
||||||
if (useDOMException) {
|
if (useDOMException) {
|
||||||
throw new DOMException('Message!', 'Name!');
|
throw new DOMException('Message!', 'Name!');
|
||||||
} else {
|
} else {
|
||||||
@@ -296,7 +381,7 @@ g.test('exceptions')
|
|||||||
g.test('throws').fn(async t0 => {
|
g.test('throws').fn(async t0 => {
|
||||||
const g = makeTestGroupForUnitTesting(UnitTest);
|
const g = makeTestGroupForUnitTesting(UnitTest);
|
||||||
|
|
||||||
g.test('a').fn(t => {
|
g.test('a').fn(_t => {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import { Fixture } from '../../../../common/framework/fixture.js';
|
|||||||
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
||||||
import { getGPU } from '../../../../common/util/navigator_gpu.js';
|
import { getGPU } from '../../../../common/util/navigator_gpu.js';
|
||||||
import { assert, assertReject, raceWithRejectOnTimeout } from '../../../../common/util/util.js';
|
import { assert, assertReject, raceWithRejectOnTimeout } from '../../../../common/util/util.js';
|
||||||
import { kFeatureNames, kLimitInfo, kLimits } from '../../../capability_info.js';
|
import {
|
||||||
|
getDefaultLimitsForAdapter,
|
||||||
|
kFeatureNames,
|
||||||
|
kLimits,
|
||||||
|
kLimitClasses,
|
||||||
|
} from '../../../capability_info.js';
|
||||||
import { clamp, isPowerOfTwo } from '../../../util/math.js';
|
import { clamp, isPowerOfTwo } from '../../../util/math.js';
|
||||||
|
|
||||||
export const g = makeTestGroup(Fixture);
|
export const g = makeTestGroup(Fixture);
|
||||||
@@ -40,10 +45,11 @@ g.test('default')
|
|||||||
// Default device should have no features.
|
// Default device should have no features.
|
||||||
t.expect(device.features.size === 0, 'Default device should not have any features');
|
t.expect(device.features.size === 0, 'Default device should not have any features');
|
||||||
// All limits should be defaults.
|
// All limits should be defaults.
|
||||||
|
const limitInfo = getDefaultLimitsForAdapter(adapter);
|
||||||
for (const limit of kLimits) {
|
for (const limit of kLimits) {
|
||||||
t.expect(
|
t.expect(
|
||||||
device.limits[limit] === kLimitInfo[limit].default,
|
device.limits[limit] === limitInfo[limit].default,
|
||||||
`Expected ${limit} == default: ${device.limits[limit]} != ${kLimitInfo[limit].default}`
|
`Expected ${limit} == default: ${device.limits[limit]} != ${limitInfo[limit].default}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +118,7 @@ g.test('stale')
|
|||||||
// Cause a type error by requesting with an unknown feature.
|
// Cause a type error by requesting with an unknown feature.
|
||||||
if (awaitInitialError) {
|
if (awaitInitialError) {
|
||||||
await assertReject(
|
await assertReject(
|
||||||
|
'TypeError',
|
||||||
adapter.requestDevice({ requiredFeatures: ['unknown-feature' as GPUFeatureName] })
|
adapter.requestDevice({ requiredFeatures: ['unknown-feature' as GPUFeatureName] })
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -125,6 +132,7 @@ g.test('stale')
|
|||||||
// Cause an operation error by requesting with an alignment limit that is not a power of 2.
|
// Cause an operation error by requesting with an alignment limit that is not a power of 2.
|
||||||
if (awaitInitialError) {
|
if (awaitInitialError) {
|
||||||
await assertReject(
|
await assertReject(
|
||||||
|
'OperationError',
|
||||||
adapter.requestDevice({ requiredLimits: { minUniformBufferOffsetAlignment: 255 } })
|
adapter.requestDevice({ requiredLimits: { minUniformBufferOffsetAlignment: 255 } })
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -239,10 +247,11 @@ g.test('limits,supported')
|
|||||||
const adapter = await gpu.requestAdapter();
|
const adapter = await gpu.requestAdapter();
|
||||||
assert(adapter !== null);
|
assert(adapter !== null);
|
||||||
|
|
||||||
|
const limitInfo = getDefaultLimitsForAdapter(adapter);
|
||||||
let value: number = -1;
|
let value: number = -1;
|
||||||
switch (limitValue) {
|
switch (limitValue) {
|
||||||
case 'default':
|
case 'default':
|
||||||
value = kLimitInfo[limit].default;
|
value = limitInfo[limit].default;
|
||||||
break;
|
break;
|
||||||
case 'adapter':
|
case 'adapter':
|
||||||
value = adapter.limits[limit];
|
value = adapter.limits[limit];
|
||||||
@@ -271,7 +280,7 @@ g.test('limit,better_than_supported')
|
|||||||
.combine('limit', kLimits)
|
.combine('limit', kLimits)
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.expandWithParams(p => {
|
.expandWithParams(p => {
|
||||||
switch (kLimitInfo[p.limit].class) {
|
switch (kLimitClasses[p.limit]) {
|
||||||
case 'maximum':
|
case 'maximum':
|
||||||
return [
|
return [
|
||||||
{ mul: 1, add: 1 },
|
{ mul: 1, add: 1 },
|
||||||
@@ -293,9 +302,10 @@ g.test('limit,better_than_supported')
|
|||||||
const adapter = await gpu.requestAdapter();
|
const adapter = await gpu.requestAdapter();
|
||||||
assert(adapter !== null);
|
assert(adapter !== null);
|
||||||
|
|
||||||
|
const limitInfo = getDefaultLimitsForAdapter(adapter);
|
||||||
const value = adapter.limits[limit] * mul + add;
|
const value = adapter.limits[limit] * mul + add;
|
||||||
const requiredLimits = {
|
const requiredLimits = {
|
||||||
[limit]: clamp(value, { min: 0, max: kLimitInfo[limit].maximumValue }),
|
[limit]: clamp(value, { min: 0, max: limitInfo[limit].maximumValue }),
|
||||||
};
|
};
|
||||||
|
|
||||||
t.shouldReject('OperationError', adapter.requestDevice({ requiredLimits }));
|
t.shouldReject('OperationError', adapter.requestDevice({ requiredLimits }));
|
||||||
@@ -314,7 +324,7 @@ g.test('limit,worse_than_default')
|
|||||||
.combine('limit', kLimits)
|
.combine('limit', kLimits)
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.expandWithParams(p => {
|
.expandWithParams(p => {
|
||||||
switch (kLimitInfo[p.limit].class) {
|
switch (kLimitClasses[p.limit]) {
|
||||||
case 'maximum':
|
case 'maximum':
|
||||||
return [
|
return [
|
||||||
{ mul: 1, add: -1 },
|
{ mul: 1, add: -1 },
|
||||||
@@ -336,13 +346,14 @@ g.test('limit,worse_than_default')
|
|||||||
const adapter = await gpu.requestAdapter();
|
const adapter = await gpu.requestAdapter();
|
||||||
assert(adapter !== null);
|
assert(adapter !== null);
|
||||||
|
|
||||||
const value = kLimitInfo[limit].default * mul + add;
|
const limitInfo = getDefaultLimitsForAdapter(adapter);
|
||||||
|
const value = limitInfo[limit].default * mul + add;
|
||||||
const requiredLimits = {
|
const requiredLimits = {
|
||||||
[limit]: clamp(value, { min: 0, max: kLimitInfo[limit].maximumValue }),
|
[limit]: clamp(value, { min: 0, max: limitInfo[limit].maximumValue }),
|
||||||
};
|
};
|
||||||
|
|
||||||
let success;
|
let success;
|
||||||
switch (kLimitInfo[limit].class) {
|
switch (limitInfo[limit].class) {
|
||||||
case 'alignment':
|
case 'alignment':
|
||||||
success = isPowerOfTwo(value);
|
success = isPowerOfTwo(value);
|
||||||
break;
|
break;
|
||||||
@@ -355,7 +366,7 @@ g.test('limit,worse_than_default')
|
|||||||
const device = await adapter.requestDevice({ requiredLimits });
|
const device = await adapter.requestDevice({ requiredLimits });
|
||||||
assert(device !== null);
|
assert(device !== null);
|
||||||
t.expect(
|
t.expect(
|
||||||
device.limits[limit] === kLimitInfo[limit].default,
|
device.limits[limit] === limitInfo[limit].default,
|
||||||
'Devices reported limit should match the default limit'
|
'Devices reported limit should match the default limit'
|
||||||
);
|
);
|
||||||
device.destroy();
|
device.destroy();
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ function reifyMapRange(bufferSize: number, range: readonly [number?, number?]):
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapRegionBoundModes = ['default-expand', 'explicit-expand', 'minimal'] as const;
|
const mapRegionBoundModes = ['default-expand', 'explicit-expand', 'minimal'] as const;
|
||||||
type MapRegionBoundMode = typeof mapRegionBoundModes[number];
|
type MapRegionBoundMode = (typeof mapRegionBoundModes)[number];
|
||||||
|
|
||||||
function getRegionForMap(
|
function getRegionForMap(
|
||||||
bufferSize: number,
|
bufferSize: number,
|
||||||
@@ -422,14 +422,8 @@ g.test('mapAsync,mapState')
|
|||||||
.combine('afterDestroy', [false, true])
|
.combine('afterDestroy', [false, true])
|
||||||
)
|
)
|
||||||
.fn(async t => {
|
.fn(async t => {
|
||||||
const {
|
const { usageType, mapModeType, beforeUnmap, beforeDestroy, afterUnmap, afterDestroy } =
|
||||||
usageType,
|
t.params;
|
||||||
mapModeType,
|
|
||||||
beforeUnmap,
|
|
||||||
beforeDestroy,
|
|
||||||
afterUnmap,
|
|
||||||
afterDestroy,
|
|
||||||
} = t.params;
|
|
||||||
const size = 8;
|
const size = 8;
|
||||||
const range = [0, 8];
|
const range = [0, 8];
|
||||||
const usage =
|
const usage =
|
||||||
|
|||||||
@@ -1088,16 +1088,19 @@ class ImageCopyTest extends TextureTestMixin(GPUTest) {
|
|||||||
|
|
||||||
// Check the valid data in outputStagingBuffer once per row.
|
// Check the valid data in outputStagingBuffer once per row.
|
||||||
for (let y = 0; y < copyFromOutputTextureLayout.mipSize[1]; ++y) {
|
for (let y = 0; y < copyFromOutputTextureLayout.mipSize[1]; ++y) {
|
||||||
|
const dataStart =
|
||||||
|
expectedStencilTextureDataOffset +
|
||||||
|
expectedStencilTextureDataBytesPerRow *
|
||||||
|
expectedStencilTextureDataRowsPerImage *
|
||||||
|
stencilTextureLayer +
|
||||||
|
expectedStencilTextureDataBytesPerRow * y;
|
||||||
this.expectGPUBufferValuesEqual(
|
this.expectGPUBufferValuesEqual(
|
||||||
outputStagingBuffer,
|
outputStagingBuffer,
|
||||||
expectedStencilTextureData.slice(
|
expectedStencilTextureData.slice(
|
||||||
expectedStencilTextureDataOffset +
|
dataStart,
|
||||||
expectedStencilTextureDataBytesPerRow *
|
dataStart + copyFromOutputTextureLayout.mipSize[0]
|
||||||
expectedStencilTextureDataRowsPerImage *
|
),
|
||||||
stencilTextureLayer +
|
copyFromOutputTextureLayout.bytesPerRow * y
|
||||||
expectedStencilTextureDataBytesPerRow * y,
|
|
||||||
copyFromOutputTextureLayout.mipSize[0]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2048,15 +2051,8 @@ copyTextureToBuffer() with depth aspect.
|
|||||||
t.selectDeviceOrSkipTestCase(info.feature);
|
t.selectDeviceOrSkipTestCase(info.feature);
|
||||||
})
|
})
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const {
|
const { format, copyMethod, aspect, offsetInBlocks, dataPaddingInBytes, copyDepth, mipLevel } =
|
||||||
format,
|
t.params;
|
||||||
copyMethod,
|
|
||||||
aspect,
|
|
||||||
offsetInBlocks,
|
|
||||||
dataPaddingInBytes,
|
|
||||||
copyDepth,
|
|
||||||
mipLevel,
|
|
||||||
} = t.params;
|
|
||||||
const bytesPerBlock = depthStencilFormatAspectSize(format, aspect);
|
const bytesPerBlock = depthStencilFormatAspectSize(format, aspect);
|
||||||
const initialDataOffset = offsetInBlocks * bytesPerBlock;
|
const initialDataOffset = offsetInBlocks * bytesPerBlock;
|
||||||
const copySize = [3, 3, copyDepth] as const;
|
const copySize = [3, 3, copyDepth] as const;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
TODO: test the behavior of creating/using/resolving queries.
|
TODO: test the behavior of creating/using/resolving queries.
|
||||||
- pipeline statistics
|
|
||||||
TODO: pipeline statistics queries are removed from core; consider moving tests to another suite.
|
|
||||||
- timestamp
|
- timestamp
|
||||||
- nested (e.g. timestamp or PS query inside occlusion query), if any such cases are valid. Try
|
- nested (e.g. timestamp 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.
|
writing to the same query set (at same or different indices), if valid. Check results make sense.
|
||||||
- start a query (all types) with no draw calls
|
- start a query (all types) with no draw calls
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ const kBytesPerQuery = 8;
|
|||||||
const kTextureSize = [4, 4];
|
const kTextureSize = [4, 4];
|
||||||
|
|
||||||
const kRenderModes = ['direct', 'render-bundle'] as const;
|
const kRenderModes = ['direct', 'render-bundle'] as const;
|
||||||
type RenderMode = typeof kRenderModes[number];
|
type RenderMode = (typeof kRenderModes)[number];
|
||||||
|
|
||||||
const kBufferOffsets = ['zero', 'non-zero'] as const;
|
const kBufferOffsets = ['zero', 'non-zero'] as const;
|
||||||
type BufferOffset = typeof kBufferOffsets[number];
|
type BufferOffset = (typeof kBufferOffsets)[number];
|
||||||
|
|
||||||
type SetupParams = {
|
type SetupParams = {
|
||||||
numQueries: number;
|
numQueries: number;
|
||||||
@@ -212,7 +212,9 @@ class QueryStarterRenderBundle implements QueryStarter {
|
|||||||
) {
|
) {
|
||||||
this._device = device;
|
this._device = device;
|
||||||
this._pass = pass;
|
this._pass = pass;
|
||||||
const colorAttachment = (renderPassDescriptor.colorAttachments as GPURenderPassColorAttachment[])[0];
|
const colorAttachment = (
|
||||||
|
renderPassDescriptor.colorAttachments as GPURenderPassColorAttachment[]
|
||||||
|
)[0];
|
||||||
this._renderBundleEncoderDescriptor = {
|
this._renderBundleEncoderDescriptor = {
|
||||||
colorFormats: ['rgba8unorm'],
|
colorFormats: ['rgba8unorm'],
|
||||||
depthStencilFormat: renderPassDescriptor.depthStencilAttachment?.depthLoadOp
|
depthStencilFormat: renderPassDescriptor.depthStencilAttachment?.depthLoadOp
|
||||||
|
|||||||
@@ -3,18 +3,11 @@ Basic command buffer compute tests.
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
||||||
import { kLimitInfo } from '../../../capability_info.js';
|
|
||||||
import { GPUTest } from '../../../gpu_test.js';
|
import { GPUTest } from '../../../gpu_test.js';
|
||||||
import { checkElementsEqualGenerated } from '../../../util/check_contents.js';
|
import { checkElementsEqualGenerated } from '../../../util/check_contents.js';
|
||||||
|
|
||||||
export const g = makeTestGroup(GPUTest);
|
export const g = makeTestGroup(GPUTest);
|
||||||
|
|
||||||
const kMaxComputeWorkgroupSize = [
|
|
||||||
kLimitInfo.maxComputeWorkgroupSizeX.default,
|
|
||||||
kLimitInfo.maxComputeWorkgroupSizeY.default,
|
|
||||||
kLimitInfo.maxComputeWorkgroupSizeZ.default,
|
|
||||||
];
|
|
||||||
|
|
||||||
g.test('memcpy').fn(t => {
|
g.test('memcpy').fn(t => {
|
||||||
const data = new Uint32Array([0x01020304]);
|
const data = new Uint32Array([0x01020304]);
|
||||||
|
|
||||||
@@ -71,27 +64,33 @@ g.test('large_dispatch')
|
|||||||
.params(u =>
|
.params(u =>
|
||||||
u
|
u
|
||||||
// Reasonably-sized powers of two, and some stranger larger sizes.
|
// Reasonably-sized powers of two, and some stranger larger sizes.
|
||||||
.combine('dispatchSize', [
|
.combine('dispatchSize', [256, 2048, 315, 628, 2179, 'maximum'] as const)
|
||||||
256,
|
|
||||||
2048,
|
|
||||||
315,
|
|
||||||
628,
|
|
||||||
2179,
|
|
||||||
kLimitInfo.maxComputeWorkgroupsPerDimension.default,
|
|
||||||
])
|
|
||||||
// Test some reasonable workgroup sizes.
|
// Test some reasonable workgroup sizes.
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
// 0 == x axis; 1 == y axis; 2 == z axis.
|
// 0 == x axis; 1 == y axis; 2 == z axis.
|
||||||
.combine('largeDimension', [0, 1, 2] as const)
|
.combine('largeDimension', [0, 1, 2] as const)
|
||||||
.expand('workgroupSize', p => [1, 2, 8, 32, kMaxComputeWorkgroupSize[p.largeDimension]])
|
.expand('workgroupSize', () => [1, 2, 8, 32, 'maximum'] as const)
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
// The output storage buffer is filled with this value.
|
// The output storage buffer is filled with this value.
|
||||||
const val = 0x01020304;
|
const val = 0x01020304;
|
||||||
const badVal = 0xbaadf00d;
|
const badVal = 0xbaadf00d;
|
||||||
|
|
||||||
const wgSize = t.params.workgroupSize;
|
const kMaxComputeWorkgroupSize = [
|
||||||
const bufferLength = t.params.dispatchSize * wgSize;
|
t.device.limits.maxComputeWorkgroupSizeX,
|
||||||
|
t.device.limits.maxComputeWorkgroupSizeY,
|
||||||
|
t.device.limits.maxComputeWorkgroupSizeZ,
|
||||||
|
];
|
||||||
|
|
||||||
|
const wgSize =
|
||||||
|
t.params.workgroupSize === 'maximum'
|
||||||
|
? kMaxComputeWorkgroupSize[t.params.largeDimension]
|
||||||
|
: t.params.workgroupSize;
|
||||||
|
const dispatchSize =
|
||||||
|
t.params.dispatchSize === 'maximum'
|
||||||
|
? t.device.limits.maxComputeWorkgroupsPerDimension
|
||||||
|
: t.params.dispatchSize;
|
||||||
|
const bufferLength = dispatchSize * wgSize;
|
||||||
const bufferByteSize = Uint32Array.BYTES_PER_ELEMENT * bufferLength;
|
const bufferByteSize = Uint32Array.BYTES_PER_ELEMENT * bufferLength;
|
||||||
const dst = t.device.createBuffer({
|
const dst = t.device.createBuffer({
|
||||||
size: bufferByteSize,
|
size: bufferByteSize,
|
||||||
@@ -101,9 +100,9 @@ g.test('large_dispatch')
|
|||||||
// Only use one large dimension and workgroup size in the dispatch
|
// Only use one large dimension and workgroup size in the dispatch
|
||||||
// call to keep the size of the test reasonable.
|
// call to keep the size of the test reasonable.
|
||||||
const dims = [1, 1, 1];
|
const dims = [1, 1, 1];
|
||||||
dims[t.params.largeDimension] = t.params.dispatchSize;
|
dims[t.params.largeDimension] = dispatchSize;
|
||||||
const wgSizes = [1, 1, 1];
|
const wgSizes = [1, 1, 1];
|
||||||
wgSizes[t.params.largeDimension] = t.params.workgroupSize;
|
wgSizes[t.params.largeDimension] = wgSize;
|
||||||
const pipeline = t.device.createComputePipeline({
|
const pipeline = t.device.createComputePipeline({
|
||||||
layout: 'auto',
|
layout: 'auto',
|
||||||
compute: {
|
compute: {
|
||||||
@@ -154,7 +153,7 @@ g.test('large_dispatch')
|
|||||||
pass.end();
|
pass.end();
|
||||||
t.device.queue.submit([encoder.finish()]);
|
t.device.queue.submit([encoder.finish()]);
|
||||||
|
|
||||||
t.expectGPUBufferValuesPassCheck(dst, a => checkElementsEqualGenerated(a, i => val), {
|
t.expectGPUBufferValuesPassCheck(dst, a => checkElementsEqualGenerated(a, _i => val), {
|
||||||
type: Uint32Array,
|
type: Uint32Array,
|
||||||
typedLength: bufferLength,
|
typedLength: bufferLength,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ g.test('precision')
|
|||||||
.params(u => u.combine('isAsync', [true, false]))
|
.params(u => u.combine('isAsync', [true, false]))
|
||||||
.fn(async t => {
|
.fn(async t => {
|
||||||
const c1 = 3.14159;
|
const c1 = 3.14159;
|
||||||
const c2 = 3.141592653589793238;
|
const c2 = 3.141592653589793;
|
||||||
await t.ExpectShaderOutputWithConstants(
|
await t.ExpectShaderOutputWithConstants(
|
||||||
t.params.isAsync,
|
t.params.isAsync,
|
||||||
// These values will get rounded to f32 and createComputePipeline, so the values coming out from the shader won't be the exact same one as shown here.
|
// These values will get rounded to f32 and createComputePipeline, so the values coming out from the shader won't be the exact same one as shown here.
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ export const kAllReadOps = [
|
|||||||
'b2t-copy',
|
'b2t-copy',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type ReadOp = typeof kAllReadOps[number];
|
export type ReadOp = (typeof kAllReadOps)[number];
|
||||||
export type WriteOp = typeof kAllWriteOps[number];
|
export type WriteOp = (typeof kAllWriteOps)[number];
|
||||||
|
|
||||||
export type Op = ReadOp | WriteOp;
|
export type Op = ReadOp | WriteOp;
|
||||||
|
|
||||||
@@ -31,42 +31,42 @@ interface OpInfo {
|
|||||||
|
|
||||||
const kOpInfo: {
|
const kOpInfo: {
|
||||||
readonly [k in Op]: OpInfo;
|
readonly [k in Op]: OpInfo;
|
||||||
} = /* prettier-ignore */ {
|
} = {
|
||||||
'write-buffer': {
|
'write-buffer': {
|
||||||
contexts: [ 'queue' ],
|
contexts: ['queue'],
|
||||||
},
|
},
|
||||||
'b2t-copy': {
|
'b2t-copy': {
|
||||||
contexts: [ 'command-encoder' ],
|
contexts: ['command-encoder'],
|
||||||
},
|
},
|
||||||
'b2b-copy': {
|
'b2b-copy': {
|
||||||
contexts: [ 'command-encoder' ],
|
contexts: ['command-encoder'],
|
||||||
},
|
},
|
||||||
't2b-copy': {
|
't2b-copy': {
|
||||||
contexts: [ 'command-encoder' ],
|
contexts: ['command-encoder'],
|
||||||
},
|
},
|
||||||
'storage': {
|
storage: {
|
||||||
contexts: [ 'compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder' ],
|
contexts: ['compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder'],
|
||||||
},
|
},
|
||||||
'storage-read': {
|
'storage-read': {
|
||||||
contexts: [ 'compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder' ],
|
contexts: ['compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder'],
|
||||||
},
|
},
|
||||||
'input-vertex': {
|
'input-vertex': {
|
||||||
contexts: [ 'render-pass-encoder', 'render-bundle-encoder' ],
|
contexts: ['render-pass-encoder', 'render-bundle-encoder'],
|
||||||
},
|
},
|
||||||
'input-index': {
|
'input-index': {
|
||||||
contexts: [ 'render-pass-encoder', 'render-bundle-encoder' ],
|
contexts: ['render-pass-encoder', 'render-bundle-encoder'],
|
||||||
},
|
},
|
||||||
'input-indirect': {
|
'input-indirect': {
|
||||||
contexts: [ 'render-pass-encoder', 'render-bundle-encoder' ],
|
contexts: ['render-pass-encoder', 'render-bundle-encoder'],
|
||||||
},
|
},
|
||||||
'input-indirect-index': {
|
'input-indirect-index': {
|
||||||
contexts: [ 'render-pass-encoder', 'render-bundle-encoder' ],
|
contexts: ['render-pass-encoder', 'render-bundle-encoder'],
|
||||||
},
|
},
|
||||||
'input-indirect-dispatch': {
|
'input-indirect-dispatch': {
|
||||||
contexts: [ 'compute-pass-encoder' ],
|
contexts: ['compute-pass-encoder'],
|
||||||
},
|
},
|
||||||
'constant-uniform': {
|
'constant-uniform': {
|
||||||
contexts: [ 'render-pass-encoder', 'render-bundle-encoder' ],
|
contexts: ['render-pass-encoder', 'render-bundle-encoder'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -285,6 +285,7 @@ export class BufferSyncTest extends GPUTest {
|
|||||||
|
|
||||||
// Create a 1x1 texture, and initialize it to a specified value for all elements.
|
// Create a 1x1 texture, and initialize it to a specified value for all elements.
|
||||||
async createTextureWithValue(initValue: number): Promise<GPUTexture> {
|
async createTextureWithValue(initValue: number): Promise<GPUTexture> {
|
||||||
|
// This is not hot in profiles; optimize if this gets used more heavily.
|
||||||
const data = new Uint32Array(1).fill(initValue);
|
const data = new Uint32Array(1).fill(initValue);
|
||||||
const texture = this.trackForCleanup(
|
const texture = this.trackForCleanup(
|
||||||
this.device.createTexture({
|
this.device.createTexture({
|
||||||
@@ -446,6 +447,7 @@ export class BufferSyncTest extends GPUTest {
|
|||||||
|
|
||||||
// Write buffer via writeBuffer API on queue
|
// Write buffer via writeBuffer API on queue
|
||||||
writeByWriteBuffer(buffer: GPUBuffer, value: number) {
|
writeByWriteBuffer(buffer: GPUBuffer, value: number) {
|
||||||
|
// This is not hot in profiles; optimize if this gets used more heavily.
|
||||||
const data = new Uint32Array(1).fill(value);
|
const data = new Uint32Array(1).fill(value);
|
||||||
this.device.queue.writeBuffer(buffer, 0, data);
|
this.device.queue.writeBuffer(buffer, 0, data);
|
||||||
}
|
}
|
||||||
@@ -919,12 +921,14 @@ export class BufferSyncTest extends GPUTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
verifyData(buffer: GPUBuffer, expectedValue: number) {
|
verifyData(buffer: GPUBuffer, expectedValue: number) {
|
||||||
|
// This is not hot in profiles; optimize if this gets used more heavily.
|
||||||
const bufferData = new Uint32Array(1);
|
const bufferData = new Uint32Array(1);
|
||||||
bufferData[0] = expectedValue;
|
bufferData[0] = expectedValue;
|
||||||
this.expectGPUBufferValuesEqual(buffer, bufferData);
|
this.expectGPUBufferValuesEqual(buffer, bufferData);
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyDataTwoValidValues(buffer: GPUBuffer, expectedValue1: number, expectedValue2: number) {
|
verifyDataTwoValidValues(buffer: GPUBuffer, expectedValue1: number, expectedValue2: number) {
|
||||||
|
// This is not hot in profiles; optimize if this gets used more heavily.
|
||||||
const bufferData1 = new Uint32Array(1);
|
const bufferData1 = new Uint32Array(1);
|
||||||
bufferData1[0] = expectedValue1;
|
bufferData1[0] = expectedValue1;
|
||||||
const bufferData2 = new Uint32Array(1);
|
const bufferData2 = new Uint32Array(1);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const kOperationBoundaries = [
|
|||||||
'dispatch', // Operations are in different dispatches.
|
'dispatch', // Operations are in different dispatches.
|
||||||
'draw', // Operations are in different draws.
|
'draw', // Operations are in different draws.
|
||||||
] as const;
|
] as const;
|
||||||
export type OperationBoundary = typeof kOperationBoundaries[number];
|
export type OperationBoundary = (typeof kOperationBoundaries)[number];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context a particular operation is permitted in.
|
* Context a particular operation is permitted in.
|
||||||
@@ -28,7 +28,7 @@ export const kOperationContexts = [
|
|||||||
'render-pass-encoder', // Operation may be encoded in a GPURenderPassEncoder.
|
'render-pass-encoder', // Operation may be encoded in a GPURenderPassEncoder.
|
||||||
'render-bundle-encoder', // Operation may be encoded in a GPURenderBundleEncoder.
|
'render-bundle-encoder', // Operation may be encoded in a GPURenderBundleEncoder.
|
||||||
] as const;
|
] as const;
|
||||||
export type OperationContext = typeof kOperationContexts[number];
|
export type OperationContext = (typeof kOperationContexts)[number];
|
||||||
|
|
||||||
interface BoundaryInfo {
|
interface BoundaryInfo {
|
||||||
readonly contexts: [OperationContext, OperationContext][];
|
readonly contexts: [OperationContext, OperationContext][];
|
||||||
@@ -60,14 +60,14 @@ const commandBufferContexts = combineContexts(
|
|||||||
*/
|
*/
|
||||||
export const kBoundaryInfo: {
|
export const kBoundaryInfo: {
|
||||||
readonly [k in OperationBoundary]: BoundaryInfo;
|
readonly [k in OperationBoundary]: BoundaryInfo;
|
||||||
} = /* prettier-ignore */ {
|
} = {
|
||||||
'queue-op': {
|
'queue-op': {
|
||||||
contexts: queueContexts,
|
contexts: queueContexts,
|
||||||
},
|
},
|
||||||
'command-buffer': {
|
'command-buffer': {
|
||||||
contexts: commandBufferContexts,
|
contexts: commandBufferContexts,
|
||||||
},
|
},
|
||||||
'pass': {
|
pass: {
|
||||||
contexts: [
|
contexts: [
|
||||||
['compute-pass-encoder', 'compute-pass-encoder'],
|
['compute-pass-encoder', 'compute-pass-encoder'],
|
||||||
['compute-pass-encoder', 'render-pass-encoder'],
|
['compute-pass-encoder', 'render-pass-encoder'],
|
||||||
@@ -79,9 +79,7 @@ export const kBoundaryInfo: {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
'execute-bundles': {
|
'execute-bundles': {
|
||||||
contexts: [
|
contexts: [['render-bundle-encoder', 'render-bundle-encoder']],
|
||||||
['render-bundle-encoder', 'render-bundle-encoder'],
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
'render-bundle': {
|
'render-bundle': {
|
||||||
contexts: [
|
contexts: [
|
||||||
@@ -90,12 +88,10 @@ export const kBoundaryInfo: {
|
|||||||
['render-bundle-encoder', 'render-bundle-encoder'],
|
['render-bundle-encoder', 'render-bundle-encoder'],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'dispatch': {
|
dispatch: {
|
||||||
contexts: [
|
contexts: [['compute-pass-encoder', 'compute-pass-encoder']],
|
||||||
['compute-pass-encoder', 'compute-pass-encoder'],
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
'draw': {
|
draw: {
|
||||||
contexts: [
|
contexts: [
|
||||||
['render-pass-encoder', 'render-pass-encoder'],
|
['render-pass-encoder', 'render-pass-encoder'],
|
||||||
['render-bundle-encoder', 'render-pass-encoder'],
|
['render-bundle-encoder', 'render-pass-encoder'],
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ const fullscreenQuadWGSL = `
|
|||||||
class TextureSyncTestHelper extends OperationContextHelper {
|
class TextureSyncTestHelper extends OperationContextHelper {
|
||||||
private texture: GPUTexture;
|
private texture: GPUTexture;
|
||||||
|
|
||||||
public readonly kTextureSize = [4, 4] as const;
|
public override readonly kTextureSize = [4, 4] as const;
|
||||||
public readonly kTextureFormat: EncodableTextureFormat = 'rgba8unorm';
|
public override readonly kTextureFormat: EncodableTextureFormat = 'rgba8unorm';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
t: GPUTest,
|
t: GPUTest,
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ export const kAllWriteOps = [
|
|||||||
'attachment-store',
|
'attachment-store',
|
||||||
'attachment-resolve',
|
'attachment-resolve',
|
||||||
] as const;
|
] as const;
|
||||||
export type WriteOp = typeof kAllWriteOps[number];
|
export type WriteOp = (typeof kAllWriteOps)[number];
|
||||||
|
|
||||||
export const kAllReadOps = ['t2b-copy', 't2t-copy', 'sample'] as const;
|
export const kAllReadOps = ['t2b-copy', 't2t-copy', 'sample'] as const;
|
||||||
export type ReadOp = typeof kAllReadOps[number];
|
export type ReadOp = (typeof kAllReadOps)[number];
|
||||||
|
|
||||||
export type Op = ReadOp | WriteOp;
|
export type Op = ReadOp | WriteOp;
|
||||||
|
|
||||||
@@ -28,44 +28,44 @@ interface OpInfo {
|
|||||||
*/
|
*/
|
||||||
export const kOpInfo: {
|
export const kOpInfo: {
|
||||||
readonly [k in Op]: OpInfo;
|
readonly [k in Op]: OpInfo;
|
||||||
} = /* prettier-ignore */ {
|
} = {
|
||||||
'write-texture': {
|
'write-texture': {
|
||||||
contexts: [ 'queue' ],
|
contexts: ['queue'],
|
||||||
readUsage: 0,
|
readUsage: 0,
|
||||||
writeUsage: GPUConst.TextureUsage.COPY_DST,
|
writeUsage: GPUConst.TextureUsage.COPY_DST,
|
||||||
},
|
},
|
||||||
'b2t-copy': {
|
'b2t-copy': {
|
||||||
contexts: [ 'command-encoder' ],
|
contexts: ['command-encoder'],
|
||||||
readUsage: 0,
|
readUsage: 0,
|
||||||
writeUsage: GPUConst.TextureUsage.COPY_DST,
|
writeUsage: GPUConst.TextureUsage.COPY_DST,
|
||||||
},
|
},
|
||||||
't2t-copy': {
|
't2t-copy': {
|
||||||
contexts: [ 'command-encoder' ],
|
contexts: ['command-encoder'],
|
||||||
readUsage: GPUConst.TextureUsage.COPY_SRC,
|
readUsage: GPUConst.TextureUsage.COPY_SRC,
|
||||||
writeUsage: GPUConst.TextureUsage.COPY_DST,
|
writeUsage: GPUConst.TextureUsage.COPY_DST,
|
||||||
},
|
},
|
||||||
't2b-copy': {
|
't2b-copy': {
|
||||||
contexts: [ 'command-encoder' ],
|
contexts: ['command-encoder'],
|
||||||
readUsage: GPUConst.TextureUsage.COPY_SRC,
|
readUsage: GPUConst.TextureUsage.COPY_SRC,
|
||||||
writeUsage: 0,
|
writeUsage: 0,
|
||||||
},
|
},
|
||||||
'storage': {
|
storage: {
|
||||||
contexts: [ 'compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder' ],
|
contexts: ['compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder'],
|
||||||
readUsage: 0,
|
readUsage: 0,
|
||||||
writeUsage: GPUConst.TextureUsage.STORAGE,
|
writeUsage: GPUConst.TextureUsage.STORAGE,
|
||||||
},
|
},
|
||||||
'sample': {
|
sample: {
|
||||||
contexts: [ 'compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder' ],
|
contexts: ['compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder'],
|
||||||
readUsage: GPUConst.TextureUsage.SAMPLED,
|
readUsage: GPUConst.TextureUsage.SAMPLED,
|
||||||
writeUsage: 0,
|
writeUsage: 0,
|
||||||
},
|
},
|
||||||
'attachment-store': {
|
'attachment-store': {
|
||||||
contexts: [ 'command-encoder' ],
|
contexts: ['command-encoder'],
|
||||||
readUsage: 0,
|
readUsage: 0,
|
||||||
writeUsage: GPUConst.TextureUsage.RENDER_ATTACHMENT,
|
writeUsage: GPUConst.TextureUsage.RENDER_ATTACHMENT,
|
||||||
},
|
},
|
||||||
'attachment-resolve': {
|
'attachment-resolve': {
|
||||||
contexts: [ 'command-encoder' ],
|
contexts: ['command-encoder'],
|
||||||
readUsage: 0,
|
readUsage: 0,
|
||||||
writeUsage: GPUConst.TextureUsage.RENDER_ATTACHMENT,
|
writeUsage: GPUConst.TextureUsage.RENDER_ATTACHMENT,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const kTypedArrays = [
|
|||||||
type WriteBufferSignature = {
|
type WriteBufferSignature = {
|
||||||
bufferOffset: number;
|
bufferOffset: number;
|
||||||
data: readonly number[];
|
data: readonly number[];
|
||||||
arrayType: typeof kTypedArrays[number];
|
arrayType: (typeof kTypedArrays)[number];
|
||||||
useArrayBuffer: boolean;
|
useArrayBuffer: boolean;
|
||||||
dataOffset?: number; // In elements when useArrayBuffer === false, bytes otherwise
|
dataOffset?: number; // In elements when useArrayBuffer === false, bytes otherwise
|
||||||
dataSize?: number; // In elements when useArrayBuffer === false, bytes otherwise
|
dataSize?: number; // In elements when useArrayBuffer === false, bytes otherwise
|
||||||
|
|||||||
@@ -59,11 +59,8 @@ g.test('stencil_clear_value')
|
|||||||
t.selectDeviceOrSkipTestCase(info.feature);
|
t.selectDeviceOrSkipTestCase(info.feature);
|
||||||
})
|
})
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const {
|
const { stencilFormat, stencilClearValue, applyStencilClearValueAsStencilReferenceValue } =
|
||||||
stencilFormat,
|
t.params;
|
||||||
stencilClearValue,
|
|
||||||
applyStencilClearValueAsStencilReferenceValue,
|
|
||||||
} = t.params;
|
|
||||||
|
|
||||||
const kSize = [1, 1, 1] as const;
|
const kSize = [1, 1, 1] as const;
|
||||||
const colorFormat = 'rgba8unorm';
|
const colorFormat = 'rgba8unorm';
|
||||||
|
|||||||
@@ -184,12 +184,9 @@ g.test('precision')
|
|||||||
fragmentConstants: { R: 3.14159 } as Record<string, GPUPipelineConstantValue>,
|
fragmentConstants: { R: 3.14159 } as Record<string, GPUPipelineConstantValue>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expected: { R: 3.141592653589793238, G: 1.0, B: 1.0, A: 1.0 },
|
expected: { R: 3.141592653589793, G: 1.0, B: 1.0, A: 1.0 },
|
||||||
vertexConstants: {},
|
vertexConstants: {},
|
||||||
fragmentConstants: { R: 3.141592653589793238 } as Record<
|
fragmentConstants: { R: 3.141592653589793 } as Record<string, GPUPipelineConstantValue>,
|
||||||
string,
|
|
||||||
GPUPipelineConstantValue
|
|
||||||
>,
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ export const description = `
|
|||||||
|
|
||||||
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
||||||
import { range } from '../../../../common/util/util.js';
|
import { range } from '../../../../common/util/util.js';
|
||||||
import { kLimitInfo } from '../../../capability_info.js';
|
import {
|
||||||
import { kRenderableColorTextureFormats, kTextureFormatInfo } from '../../../format_info.js';
|
computeBytesPerSampleFromFormats,
|
||||||
|
kRenderableColorTextureFormats,
|
||||||
|
kTextureFormatInfo,
|
||||||
|
} from '../../../format_info.js';
|
||||||
import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
|
import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
|
||||||
import { getFragmentShaderCodeWithOutput, getPlainTypeInfo } from '../../../util/shader.js';
|
import { getFragmentShaderCodeWithOutput, getPlainTypeInfo } from '../../../util/shader.js';
|
||||||
import { kTexelRepresentationInfo } from '../../../util/texture/texel_data.js';
|
import { kTexelRepresentationInfo } from '../../../util/texture/texel_data.js';
|
||||||
@@ -27,11 +30,14 @@ export const g = makeTestGroup(TextureTestMixin(GPUTest));
|
|||||||
// Values to write into each attachment
|
// Values to write into each attachment
|
||||||
// We make values different for each attachment index and each channel
|
// We make values different for each attachment index and each channel
|
||||||
// to make sure they didn't get mixed up
|
// to make sure they didn't get mixed up
|
||||||
|
|
||||||
|
// Clamp alpha to 3 to avoid comparing a large expected value with a max 3 value for rgb10a2uint
|
||||||
|
// MAINTENANCE_TODO: Make TexelRepresentation.numericRange per-component and use that.
|
||||||
const attachmentsIntWriteValues = [
|
const attachmentsIntWriteValues = [
|
||||||
{ R: 1, G: 2, B: 3, A: 4 },
|
{ R: 1, G: 2, B: 3, A: 1 },
|
||||||
{ R: 5, G: 6, B: 7, A: 8 },
|
{ R: 5, G: 6, B: 7, A: 2 },
|
||||||
{ R: 9, G: 10, B: 11, A: 12 },
|
{ R: 9, G: 10, B: 11, A: 3 },
|
||||||
{ R: 13, G: 14, B: 15, A: 16 },
|
{ R: 13, G: 14, B: 15, A: 0 },
|
||||||
];
|
];
|
||||||
const attachmentsFloatWriteValues = [
|
const attachmentsFloatWriteValues = [
|
||||||
{ R: 0.12, G: 0.34, B: 0.56, A: 0 },
|
{ R: 0.12, G: 0.34, B: 0.56, A: 0 },
|
||||||
@@ -47,14 +53,6 @@ g.test('color,attachments')
|
|||||||
.combine('format', kRenderableColorTextureFormats)
|
.combine('format', kRenderableColorTextureFormats)
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.combine('attachmentCount', [2, 3, 4])
|
.combine('attachmentCount', [2, 3, 4])
|
||||||
.filter(t => {
|
|
||||||
// We only need to test formats that have a valid color attachment bytes per sample.
|
|
||||||
const pixelByteCost = kTextureFormatInfo[t.format].colorRender?.byteCost;
|
|
||||||
return (
|
|
||||||
pixelByteCost !== undefined &&
|
|
||||||
pixelByteCost * t.attachmentCount <= kLimitInfo.maxColorAttachmentBytesPerSample.default
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.expand('emptyAttachmentId', p => range(p.attachmentCount, i => i))
|
.expand('emptyAttachmentId', p => range(p.attachmentCount, i => i))
|
||||||
)
|
)
|
||||||
.beforeAllSubcases(t => {
|
.beforeAllSubcases(t => {
|
||||||
@@ -67,6 +65,14 @@ g.test('color,attachments')
|
|||||||
const componentCount = kTexelRepresentationInfo[format].componentOrder.length;
|
const componentCount = kTexelRepresentationInfo[format].componentOrder.length;
|
||||||
const info = kTextureFormatInfo[format];
|
const info = kTextureFormatInfo[format];
|
||||||
|
|
||||||
|
// We only need to test formats that have a valid color attachment bytes per sample.
|
||||||
|
const pixelByteCost = kTextureFormatInfo[format].colorRender?.byteCost;
|
||||||
|
t.skipIf(
|
||||||
|
pixelByteCost === undefined ||
|
||||||
|
computeBytesPerSampleFromFormats(range(attachmentCount, () => format)) >
|
||||||
|
t.device.limits.maxColorAttachmentBytesPerSample
|
||||||
|
);
|
||||||
|
|
||||||
const writeValues =
|
const writeValues =
|
||||||
info.color.type === 'sint' || info.color.type === 'uint'
|
info.color.type === 'sint' || info.color.type === 'uint'
|
||||||
? attachmentsIntWriteValues
|
? attachmentsIntWriteValues
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ class F extends TextureTestMixin(GPUTest) {
|
|||||||
private sampleTexture: GPUTexture | undefined;
|
private sampleTexture: GPUTexture | undefined;
|
||||||
private sampler: GPUSampler | undefined;
|
private sampler: GPUSampler | undefined;
|
||||||
|
|
||||||
async init() {
|
override async init() {
|
||||||
await super.init();
|
await super.init();
|
||||||
if (this.isCompatibility) {
|
if (this.isCompatibility) {
|
||||||
this.skip('WGSL sample_mask is not supported in compatibility mode');
|
this.skip('WGSL sample_mask is not supported in compatibility mode');
|
||||||
@@ -520,26 +520,10 @@ textureLoad each sample index from the texture and write to a storage buffer to
|
|||||||
})
|
})
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.combine('sampleMask', [
|
.combine('sampleMask', [
|
||||||
0,
|
0, 0b0001, 0b0010, 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b11110,
|
||||||
0b0001,
|
|
||||||
0b0010,
|
|
||||||
0b0111,
|
|
||||||
0b1011,
|
|
||||||
0b1101,
|
|
||||||
0b1110,
|
|
||||||
0b1111,
|
|
||||||
0b11110,
|
|
||||||
] as const)
|
] as const)
|
||||||
.combine('fragmentShaderOutputMask', [
|
.combine('fragmentShaderOutputMask', [
|
||||||
0,
|
0, 0b0001, 0b0010, 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b11110,
|
||||||
0b0001,
|
|
||||||
0b0010,
|
|
||||||
0b0111,
|
|
||||||
0b1011,
|
|
||||||
0b1101,
|
|
||||||
0b1110,
|
|
||||||
0b1111,
|
|
||||||
0b11110,
|
|
||||||
] as const)
|
] as const)
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
|
|||||||
@@ -397,7 +397,7 @@ g.test('blending,formats')
|
|||||||
t.device.queue.submit([commandEncoder.finish()]);
|
t.device.queue.submit([commandEncoder.finish()]);
|
||||||
|
|
||||||
const expColor = { R: 0.6, G: 0.6, B: 0.6, A: 0.6 };
|
const expColor = { R: 0.6, G: 0.6, B: 0.6, A: 0.6 };
|
||||||
const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor);
|
const expTexelView = TexelView.fromTexelsAsColors(format, _coords => expColor);
|
||||||
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1, 1]);
|
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1, 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -445,7 +445,7 @@ g.test('blend_constant,initial')
|
|||||||
// Check that the initial blend constant is black(0,0,0,0) after setting testPipeline which has
|
// Check that the initial blend constant is black(0,0,0,0) after setting testPipeline which has
|
||||||
// a white color buffer data.
|
// a white color buffer data.
|
||||||
const expColor = { R: 0, G: 0, B: 0, A: 0 };
|
const expColor = { R: 0, G: 0, B: 0, A: 0 };
|
||||||
const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor);
|
const expTexelView = TexelView.fromTexelsAsColors(format, _coords => expColor);
|
||||||
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
|
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
|
||||||
kSize,
|
kSize,
|
||||||
kSize,
|
kSize,
|
||||||
@@ -503,7 +503,7 @@ g.test('blend_constant,setting')
|
|||||||
// Check that the blend constant is the same as the given constant after setting the constant
|
// Check that the blend constant is the same as the given constant after setting the constant
|
||||||
// via setBlendConstant.
|
// via setBlendConstant.
|
||||||
const expColor = { R: r, G: g, B: b, A: a };
|
const expColor = { R: r, G: g, B: b, A: a };
|
||||||
const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor);
|
const expTexelView = TexelView.fromTexelsAsColors(format, _coords => expColor);
|
||||||
|
|
||||||
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
|
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
|
||||||
kSize,
|
kSize,
|
||||||
@@ -576,7 +576,7 @@ g.test('blend_constant,not_inherited')
|
|||||||
|
|
||||||
// Check that the blend constant is not inherited from the first render pass.
|
// Check that the blend constant is not inherited from the first render pass.
|
||||||
const expColor = { R: 0, G: 0, B: 0, A: 0 };
|
const expColor = { R: 0, G: 0, B: 0, A: 0 };
|
||||||
const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor);
|
const expTexelView = TexelView.fromTexelsAsColors(format, _coords => expColor);
|
||||||
|
|
||||||
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
|
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
|
||||||
kSize,
|
kSize,
|
||||||
@@ -585,22 +585,7 @@ g.test('blend_constant,not_inherited')
|
|||||||
});
|
});
|
||||||
|
|
||||||
const kColorWriteCombinations: readonly GPUColorWriteFlags[] = [
|
const kColorWriteCombinations: readonly GPUColorWriteFlags[] = [
|
||||||
0,
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||||
1,
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
4,
|
|
||||||
5,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
8,
|
|
||||||
9,
|
|
||||||
10,
|
|
||||||
11,
|
|
||||||
12,
|
|
||||||
13,
|
|
||||||
14,
|
|
||||||
15,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
g.test('color_write_mask,channel_work')
|
g.test('color_write_mask,channel_work')
|
||||||
@@ -672,7 +657,7 @@ g.test('color_write_mask,channel_work')
|
|||||||
t.device.queue.submit([commandEncoder.finish()]);
|
t.device.queue.submit([commandEncoder.finish()]);
|
||||||
|
|
||||||
const expColor = { R: r, G: g, B: b, A: a };
|
const expColor = { R: r, G: g, B: b, A: a };
|
||||||
const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor);
|
const expTexelView = TexelView.fromTexelsAsColors(format, _coords => expColor);
|
||||||
|
|
||||||
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
|
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
|
||||||
kSize,
|
kSize,
|
||||||
@@ -730,7 +715,7 @@ g.test('color_write_mask,blending_disabled')
|
|||||||
t.device.queue.submit([commandEncoder.finish()]);
|
t.device.queue.submit([commandEncoder.finish()]);
|
||||||
|
|
||||||
const expColor = { R: 1, G: 0, B: 0, A: 0 };
|
const expColor = { R: 1, G: 0, B: 0, A: 0 };
|
||||||
const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor);
|
const expTexelView = TexelView.fromTexelsAsColors(format, _coords => expColor);
|
||||||
|
|
||||||
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
|
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
|
||||||
kSize,
|
kSize,
|
||||||
@@ -827,7 +812,7 @@ g.test('blending,clamping')
|
|||||||
}
|
}
|
||||||
|
|
||||||
const expColor = { R: expValue, G: expValue, B: expValue, A: expValue };
|
const expColor = { R: expValue, G: expValue, B: expValue, A: expValue };
|
||||||
const expTexelView = TexelView.fromTexelsAsColors(format, coords => expColor);
|
const expTexelView = TexelView.fromTexelsAsColors(format, _coords => expColor);
|
||||||
|
|
||||||
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1, 1]);
|
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1, 1]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class DepthTest extends TextureTestMixin(GPUTest) {
|
|||||||
B: expectedColor[2],
|
B: expectedColor[2],
|
||||||
A: expectedColor[3],
|
A: expectedColor[3],
|
||||||
};
|
};
|
||||||
const expTexelView = TexelView.fromTexelsAsColors(renderTargetFormat, coords => expColor);
|
const expTexelView = TexelView.fromTexelsAsColors(renderTargetFormat, _coords => expColor);
|
||||||
|
|
||||||
this.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1]);
|
this.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ class DepthBiasTest extends TextureTestMixin(GPUTest) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const expColor = { Depth: _expectedDepth };
|
const expColor = { Depth: _expectedDepth };
|
||||||
const expTexelView = TexelView.fromTexelsAsColors(depthFormat, coords => expColor);
|
const expTexelView = TexelView.fromTexelsAsColors(depthFormat, _coords => expColor);
|
||||||
this.expectTexelViewComparisonIsOkInTexture({ texture: depthTexture }, expTexelView, [1, 1]);
|
this.expectTexelViewComparisonIsOkInTexture({ texture: depthTexture }, expTexelView, [1, 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ class DepthBiasTest extends TextureTestMixin(GPUTest) {
|
|||||||
B: _expectedColor[2],
|
B: _expectedColor[2],
|
||||||
A: _expectedColor[3],
|
A: _expectedColor[3],
|
||||||
};
|
};
|
||||||
const expTexelView = TexelView.fromTexelsAsColors(renderTargetFormat, coords => expColor);
|
const expTexelView = TexelView.fromTexelsAsColors(renderTargetFormat, _coords => expColor);
|
||||||
this.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1]);
|
this.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ have unexpected values then get drawn to the color buffer, which is later checke
|
|||||||
|
|
||||||
const kCheckPassedValue = 0;
|
const kCheckPassedValue = 0;
|
||||||
const predicatePrinter: CheckElementsSupplementalTableRows = [
|
const predicatePrinter: CheckElementsSupplementalTableRows = [
|
||||||
{ leftHeader: 'expected ==', getValueForCell: index => kCheckPassedValue },
|
{ leftHeader: 'expected ==', getValueForCell: _index => kCheckPassedValue },
|
||||||
];
|
];
|
||||||
if (dsActual && dsExpected && format === 'depth32float') {
|
if (dsActual && dsExpected && format === 'depth32float') {
|
||||||
await Promise.all([dsActual.mapAsync(GPUMapMode.READ), dsExpected.mapAsync(GPUMapMode.READ)]);
|
await Promise.all([dsActual.mapAsync(GPUMapMode.READ), dsExpected.mapAsync(GPUMapMode.READ)]);
|
||||||
@@ -328,7 +328,7 @@ have unexpected values then get drawn to the color buffer, which is later checke
|
|||||||
t.expectGPUBufferValuesPassCheck(
|
t.expectGPUBufferValuesPassCheck(
|
||||||
checkBuffer,
|
checkBuffer,
|
||||||
a =>
|
a =>
|
||||||
checkElementsPassPredicate(a, (index, value) => value === kCheckPassedValue, {
|
checkElementsPassPredicate(a, (_index, value) => value === kCheckPassedValue, {
|
||||||
predicatePrinter,
|
predicatePrinter,
|
||||||
}),
|
}),
|
||||||
{ type: Uint8Array, typedLength: kNumTestPoints, method: 'map' }
|
{ type: Uint8Array, typedLength: kNumTestPoints, method: 'map' }
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ class DrawTest extends TextureTestMixin(GPUTest) {
|
|||||||
// | \
|
// | \
|
||||||
// |______\
|
// |______\
|
||||||
// Unit triangle shaped like this. 0-1 Y-down.
|
// Unit triangle shaped like this. 0-1 Y-down.
|
||||||
const triangleVertices = /* prettier-ignore */ [
|
/* prettier-ignore */
|
||||||
|
const triangleVertices = [
|
||||||
0.0, 0.0,
|
0.0, 0.0,
|
||||||
0.0, 1.0,
|
0.0, 1.0,
|
||||||
1.0, 1.0,
|
1.0, 1.0,
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ const kRenderTargetFormat = 'rgba8unorm';
|
|||||||
class F extends GPUTest {
|
class F extends GPUTest {
|
||||||
MakeIndexBuffer(): GPUBuffer {
|
MakeIndexBuffer(): GPUBuffer {
|
||||||
return this.makeBufferWithContents(
|
return this.makeBufferWithContents(
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */ new Uint32Array([
|
||||||
new Uint32Array([
|
0, 1, 2, // The bottom left triangle
|
||||||
0, 1, 2, // The bottom left triangle
|
1, 2, 3, // The top right triangle
|
||||||
1, 2, 3, // The top right triangle
|
|
||||||
]),
|
]),
|
||||||
GPUBufferUsage.INDEX
|
GPUBufferUsage.INDEX
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ class StencilTest extends TextureTestMixin(GPUTest) {
|
|||||||
B: expectedColor[2],
|
B: expectedColor[2],
|
||||||
A: expectedColor[3],
|
A: expectedColor[3],
|
||||||
};
|
};
|
||||||
const expTexelView = TexelView.fromTexelsAsColors(renderTargetFormat, coords => expColor);
|
const expTexelView = TexelView.fromTexelsAsColors(renderTargetFormat, _coords => expColor);
|
||||||
this.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1]);
|
this.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class SamplerAnisotropicFilteringSlantedPlaneTest extends GPUTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private pipeline: GPURenderPipeline | undefined;
|
private pipeline: GPURenderPipeline | undefined;
|
||||||
async init(): Promise<void> {
|
override async init(): Promise<void> {
|
||||||
await super.init();
|
await super.init();
|
||||||
|
|
||||||
this.pipeline = this.device.createRenderPipeline({
|
this.pipeline = this.device.createRenderPipeline({
|
||||||
@@ -287,7 +287,7 @@ g.test('anisotropic_filter_mipmap_color')
|
|||||||
])
|
])
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const texture = t.createTextureFromTexelViewsMultipleMipmaps(
|
const texture = t.createTextureFromTexelViewsMultipleMipmaps(
|
||||||
colors.map(value => TexelView.fromTexelsAsBytes(kTextureFormat, coords_ => value)),
|
colors.map(value => TexelView.fromTexelsAsBytes(kTextureFormat, _coords => value)),
|
||||||
{ size: [4, 4, 1], usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING }
|
{ size: [4, 4, 1], usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING }
|
||||||
);
|
);
|
||||||
const textureView = texture.createView();
|
const textureView = texture.createView();
|
||||||
|
|||||||
@@ -480,6 +480,7 @@ g.test('magFilter,nearest')
|
|||||||
.combine('addressModeV', kAddressModes)
|
.combine('addressModeV', kAddressModes)
|
||||||
)
|
)
|
||||||
.beforeAllSubcases(t => {
|
.beforeAllSubcases(t => {
|
||||||
|
t.skipIfTextureFormatNotSupported(t.params.format);
|
||||||
if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
|
if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
|
||||||
t.selectDeviceOrSkipTestCase('float32-filterable');
|
t.selectDeviceOrSkipTestCase('float32-filterable');
|
||||||
}
|
}
|
||||||
@@ -602,6 +603,7 @@ g.test('magFilter,linear')
|
|||||||
.combine('addressModeV', kAddressModes)
|
.combine('addressModeV', kAddressModes)
|
||||||
)
|
)
|
||||||
.beforeAllSubcases(t => {
|
.beforeAllSubcases(t => {
|
||||||
|
t.skipIfTextureFormatNotSupported(t.params.format);
|
||||||
if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
|
if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
|
||||||
t.selectDeviceOrSkipTestCase('float32-filterable');
|
t.selectDeviceOrSkipTestCase('float32-filterable');
|
||||||
}
|
}
|
||||||
@@ -736,6 +738,7 @@ g.test('minFilter,nearest')
|
|||||||
.combine('addressModeV', kAddressModes)
|
.combine('addressModeV', kAddressModes)
|
||||||
)
|
)
|
||||||
.beforeAllSubcases(t => {
|
.beforeAllSubcases(t => {
|
||||||
|
t.skipIfTextureFormatNotSupported(t.params.format);
|
||||||
if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
|
if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
|
||||||
t.selectDeviceOrSkipTestCase('float32-filterable');
|
t.selectDeviceOrSkipTestCase('float32-filterable');
|
||||||
}
|
}
|
||||||
@@ -868,6 +871,7 @@ g.test('minFilter,linear')
|
|||||||
.combine('addressModeV', kAddressModes)
|
.combine('addressModeV', kAddressModes)
|
||||||
)
|
)
|
||||||
.beforeAllSubcases(t => {
|
.beforeAllSubcases(t => {
|
||||||
|
t.skipIfTextureFormatNotSupported(t.params.format);
|
||||||
if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
|
if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
|
||||||
t.selectDeviceOrSkipTestCase('float32-filterable');
|
t.selectDeviceOrSkipTestCase('float32-filterable');
|
||||||
}
|
}
|
||||||
@@ -963,6 +967,7 @@ g.test('mipmapFilter')
|
|||||||
.combine('filterMode', kMipmapFilterModes)
|
.combine('filterMode', kMipmapFilterModes)
|
||||||
)
|
)
|
||||||
.beforeAllSubcases(t => {
|
.beforeAllSubcases(t => {
|
||||||
|
t.skipIfTextureFormatNotSupported(t.params.format);
|
||||||
if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
|
if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
|
||||||
t.selectDeviceOrSkipTestCase('float32-filterable');
|
t.selectDeviceOrSkipTestCase('float32-filterable');
|
||||||
}
|
}
|
||||||
@@ -986,7 +991,7 @@ g.test('mipmapFilter')
|
|||||||
TexelView.fromTexelsAsColors(format, () => {
|
TexelView.fromTexelsAsColors(format, () => {
|
||||||
return { R: 0.0, G: 0.0, B: 0.0, A: 1.0 };
|
return { R: 0.0, G: 0.0, B: 0.0, A: 1.0 };
|
||||||
}),
|
}),
|
||||||
TexelView.fromTexelsAsColors(format, coord => {
|
TexelView.fromTexelsAsColors(format, _coords => {
|
||||||
return { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
|
return { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ float tolerance.
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
||||||
import { assert, memcpy, unreachable } from '../../../../common/util/util.js';
|
|
||||||
import {
|
import {
|
||||||
kMaxVertexAttributes,
|
assert,
|
||||||
kMaxVertexBufferArrayStride,
|
filterUniqueValueTestVariants,
|
||||||
kMaxVertexBuffers,
|
makeValueTestVariant,
|
||||||
|
memcpy,
|
||||||
|
unreachable,
|
||||||
|
} from '../../../../common/util/util.js';
|
||||||
|
import {
|
||||||
kPerStageBindingLimits,
|
kPerStageBindingLimits,
|
||||||
kVertexFormatInfo,
|
kVertexFormatInfo,
|
||||||
kVertexFormats,
|
kVertexFormats,
|
||||||
@@ -58,6 +61,20 @@ function mapStateAttribs<V, A1, A2>(
|
|||||||
return buffers.map(b => mapBufferAttribs(b, f));
|
return buffers.map(b => mapBufferAttribs(b, f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeRgb10a2(rgba: Array<number>): number {
|
||||||
|
const [r, g, b, a] = rgba;
|
||||||
|
assert((r & 0x3ff) === r);
|
||||||
|
assert((g & 0x3ff) === g);
|
||||||
|
assert((b & 0x3ff) === b);
|
||||||
|
assert((a & 0x3) === a);
|
||||||
|
return r | (g << 10) | (b << 20) | (a << 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeRgb10a2(rgba: number, index: number): number {
|
||||||
|
const normalizationFactor = index % 4 === 3 ? 3 : 1023;
|
||||||
|
return rgba / normalizationFactor;
|
||||||
|
}
|
||||||
|
|
||||||
type TestData = {
|
type TestData = {
|
||||||
shaderBaseType: string;
|
shaderBaseType: string;
|
||||||
floatTolerance?: number;
|
floatTolerance?: number;
|
||||||
@@ -94,8 +111,11 @@ class VertexStateTest extends GPUTest {
|
|||||||
// than maxVertexAttributes = 16.
|
// than maxVertexAttributes = 16.
|
||||||
// However this might not work in the future for implementations that allow even more vertex
|
// However this might not work in the future for implementations that allow even more vertex
|
||||||
// attributes so there will need to be larger changes when that happens.
|
// attributes so there will need to be larger changes when that happens.
|
||||||
const maxUniformBuffers = kPerStageBindingLimits['uniformBuf'].max;
|
const maxUniformBuffers = this.getDefaultLimit(kPerStageBindingLimits['uniformBuf'].maxLimit);
|
||||||
assert(maxUniformBuffers + kPerStageBindingLimits['storageBuf'].max >= kMaxVertexAttributes);
|
assert(
|
||||||
|
maxUniformBuffers + this.getDefaultLimit(kPerStageBindingLimits['storageBuf'].maxLimit) >=
|
||||||
|
this.device.limits.maxVertexAttributes
|
||||||
|
);
|
||||||
|
|
||||||
let vsInputs = '';
|
let vsInputs = '';
|
||||||
let vsChecks = '';
|
let vsChecks = '';
|
||||||
@@ -302,7 +322,8 @@ struct VSOutputs {
|
|||||||
// test value in a test is still meaningful.
|
// test value in a test is still meaningful.
|
||||||
generateTestData(format: GPUVertexFormat): TestData {
|
generateTestData(format: GPUVertexFormat): TestData {
|
||||||
const formatInfo = kVertexFormatInfo[format];
|
const formatInfo = kVertexFormatInfo[format];
|
||||||
const bitSize = formatInfo.bytesPerComponent * 8;
|
const bitSize =
|
||||||
|
formatInfo.bytesPerComponent === 'packed' ? 0 : formatInfo.bytesPerComponent * 8;
|
||||||
|
|
||||||
switch (formatInfo.type) {
|
switch (formatInfo.type) {
|
||||||
case 'float': {
|
case 'float': {
|
||||||
@@ -405,6 +426,28 @@ struct VSOutputs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'unorm': {
|
case 'unorm': {
|
||||||
|
if (formatInfo.bytesPerComponent === 'packed') {
|
||||||
|
assert(format === 'unorm10-10-10-2'); // This is the only packed format for now.
|
||||||
|
assert(bitSize === 0);
|
||||||
|
|
||||||
|
/* prettier-ignore */
|
||||||
|
const data = [
|
||||||
|
[ 0, 0, 0, 0],
|
||||||
|
[1023, 1023, 1023, 3],
|
||||||
|
[ 243, 567, 765, 2],
|
||||||
|
];
|
||||||
|
const vertexData = new Uint32Array(data.map(makeRgb10a2)).buffer;
|
||||||
|
const expectedData = new Float32Array(data.flat().map(normalizeRgb10a2)).buffer;
|
||||||
|
|
||||||
|
return {
|
||||||
|
shaderBaseType: 'f32',
|
||||||
|
testComponentCount: data.flat().length,
|
||||||
|
expectedData,
|
||||||
|
vertexData,
|
||||||
|
floatTolerance: 0.1 / 1023,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
const data = [
|
const data = [
|
||||||
42,
|
42,
|
||||||
@@ -555,7 +598,7 @@ struct VSOutputs {
|
|||||||
this.interleaveVertexDataInto(vertexData, attrib.vertexData, {
|
this.interleaveVertexDataInto(vertexData, attrib.vertexData, {
|
||||||
targetStride: buffer.arrayStride,
|
targetStride: buffer.arrayStride,
|
||||||
offset: (buffer.vbOffset ?? 0) + attrib.offset,
|
offset: (buffer.vbOffset ?? 0) + attrib.offset,
|
||||||
size: formatInfo.componentCount * formatInfo.bytesPerComponent,
|
size: formatInfo.byteSize,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,11 +643,21 @@ g.test('vertex_format_to_shader_format_conversion')
|
|||||||
.combine('format', kVertexFormats)
|
.combine('format', kVertexFormats)
|
||||||
.combine('shaderComponentCount', [1, 2, 3, 4])
|
.combine('shaderComponentCount', [1, 2, 3, 4])
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.combine('slot', [0, 1, kMaxVertexBuffers - 1])
|
.combine('slotVariant', [
|
||||||
.combine('shaderLocation', [0, 1, kMaxVertexAttributes - 1])
|
{ mult: 0, add: 0 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: -1 },
|
||||||
|
])
|
||||||
|
.combine('shaderLocationVariant', [
|
||||||
|
{ mult: 0, add: 0 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: -1 },
|
||||||
|
])
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { format, shaderComponentCount, slot, shaderLocation } = t.params;
|
const { format, shaderComponentCount, slotVariant, shaderLocationVariant } = t.params;
|
||||||
|
const slot = t.makeLimitVariant('maxVertexBuffers', slotVariant);
|
||||||
|
const shaderLocation = t.makeLimitVariant('maxVertexAttributes', shaderLocationVariant);
|
||||||
t.runTest([
|
t.runTest([
|
||||||
{
|
{
|
||||||
slot,
|
slot,
|
||||||
@@ -637,7 +690,7 @@ g.test('setVertexBuffer_offset_and_attribute_offset')
|
|||||||
.combine('arrayStride', [128])
|
.combine('arrayStride', [128])
|
||||||
.expand('offset', p => {
|
.expand('offset', p => {
|
||||||
const formatInfo = kVertexFormatInfo[p.format];
|
const formatInfo = kVertexFormatInfo[p.format];
|
||||||
const formatSize = formatInfo.bytesPerComponent * formatInfo.componentCount;
|
const formatSize = formatInfo.byteSize;
|
||||||
return new Set([
|
return new Set([
|
||||||
0,
|
0,
|
||||||
4,
|
4,
|
||||||
@@ -683,30 +736,38 @@ g.test('non_zero_array_stride_and_attribute_offset')
|
|||||||
u //
|
u //
|
||||||
.combine('format', kVertexFormats)
|
.combine('format', kVertexFormats)
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.expand('arrayStride', p => {
|
.expand('arrayStrideVariant', p => {
|
||||||
const formatInfo = kVertexFormatInfo[p.format];
|
const formatInfo = kVertexFormatInfo[p.format];
|
||||||
const formatSize = formatInfo.bytesPerComponent * formatInfo.componentCount;
|
const formatSize = formatInfo.byteSize;
|
||||||
|
|
||||||
return [align(formatSize, 4), align(formatSize, 4) + 4, kMaxVertexBufferArrayStride];
|
return [
|
||||||
|
{ mult: 0, add: align(formatSize, 4) },
|
||||||
|
{ mult: 0, add: align(formatSize, 4) + 4 },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
];
|
||||||
})
|
})
|
||||||
.expand('offset', p => {
|
.expand('offsetVariant', function* (p) {
|
||||||
const formatInfo = kVertexFormatInfo[p.format];
|
const formatInfo = kVertexFormatInfo[p.format];
|
||||||
const formatSize = formatInfo.bytesPerComponent * formatInfo.componentCount;
|
const formatSize = formatInfo.byteSize;
|
||||||
return new Set(
|
yield { mult: 0, add: 0 };
|
||||||
[
|
yield { mult: 0, add: 4 };
|
||||||
0,
|
if (formatSize !== 4) yield { mult: 0, add: formatSize };
|
||||||
formatSize,
|
yield { mult: 0.5, add: 0 };
|
||||||
4,
|
yield { mult: 1, add: -formatSize * 2 };
|
||||||
p.arrayStride / 2,
|
if (formatSize !== 4) yield { mult: 1, add: -formatSize - 4 };
|
||||||
p.arrayStride - formatSize * 2,
|
yield { mult: 1, add: -formatSize };
|
||||||
p.arrayStride - formatSize - 4,
|
|
||||||
p.arrayStride - formatSize,
|
|
||||||
].map(offset => clamp(offset, { min: 0, max: p.arrayStride - formatSize }))
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { format, arrayStride, offset } = t.params;
|
const { format, arrayStrideVariant, offsetVariant } = t.params;
|
||||||
|
const arrayStride = t.makeLimitVariant('maxVertexBufferArrayStride', arrayStrideVariant);
|
||||||
|
const formatInfo = kVertexFormatInfo[format];
|
||||||
|
const formatSize = formatInfo.byteSize;
|
||||||
|
const offset = clamp(makeValueTestVariant(arrayStride, offsetVariant), {
|
||||||
|
min: 0,
|
||||||
|
max: arrayStride - formatSize,
|
||||||
|
});
|
||||||
|
|
||||||
t.runTest([
|
t.runTest([
|
||||||
{
|
{
|
||||||
slot: 0,
|
slot: 0,
|
||||||
@@ -764,15 +825,20 @@ g.test('vertex_buffer_used_multiple_times_overlapped')
|
|||||||
u //
|
u //
|
||||||
.combine('format', kVertexFormats)
|
.combine('format', kVertexFormats)
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.combine('vbCount', [2, 3, kMaxVertexBuffers])
|
.combine('vbCountVariant', [
|
||||||
|
{ mult: 0, add: 2 },
|
||||||
|
{ mult: 0, add: 3 },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
])
|
||||||
.combine('additionalVBOffset', [0, 4, 120])
|
.combine('additionalVBOffset', [0, 4, 120])
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { format, vbCount, additionalVBOffset } = t.params;
|
const { format, vbCountVariant, additionalVBOffset } = t.params;
|
||||||
|
const vbCount = t.makeLimitVariant('maxVertexBuffers', vbCountVariant);
|
||||||
const kVertexCount = 20;
|
const kVertexCount = 20;
|
||||||
const kInstanceCount = 1;
|
const kInstanceCount = 1;
|
||||||
const formatInfo = kVertexFormatInfo[format];
|
const formatInfo = kVertexFormatInfo[format];
|
||||||
const formatByteSize = formatInfo.bytesPerComponent * formatInfo.componentCount;
|
const formatByteSize = formatInfo.byteSize;
|
||||||
// We need to align so the offset for non-0 setVertexBuffer don't fail validation.
|
// We need to align so the offset for non-0 setVertexBuffer don't fail validation.
|
||||||
const alignedFormatByteSize = align(formatByteSize, 4);
|
const alignedFormatByteSize = align(formatByteSize, 4);
|
||||||
|
|
||||||
@@ -863,15 +929,20 @@ g.test('vertex_buffer_used_multiple_times_interleaved')
|
|||||||
u //
|
u //
|
||||||
.combine('format', kVertexFormats)
|
.combine('format', kVertexFormats)
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.combine('vbCount', [2, 3, kMaxVertexBuffers])
|
.combine('vbCountVariant', [
|
||||||
|
{ mult: 0, add: 2 },
|
||||||
|
{ mult: 0, add: 3 },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
])
|
||||||
.combine('additionalVBOffset', [0, 4, 120])
|
.combine('additionalVBOffset', [0, 4, 120])
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { format, vbCount, additionalVBOffset } = t.params;
|
const { format, vbCountVariant, additionalVBOffset } = t.params;
|
||||||
|
const vbCount = t.makeLimitVariant('maxVertexBuffers', vbCountVariant);
|
||||||
const kVertexCount = 20;
|
const kVertexCount = 20;
|
||||||
const kInstanceCount = 1;
|
const kInstanceCount = 1;
|
||||||
const formatInfo = kVertexFormatInfo[format];
|
const formatInfo = kVertexFormatInfo[format];
|
||||||
const formatByteSize = formatInfo.bytesPerComponent * formatInfo.componentCount;
|
const formatByteSize = formatInfo.byteSize;
|
||||||
// We need to align so the offset for non-0 setVertexBuffer don't fail validation.
|
// We need to align so the offset for non-0 setVertexBuffer don't fail validation.
|
||||||
const alignedFormatByteSize = align(formatByteSize, 4);
|
const alignedFormatByteSize = align(formatByteSize, 4);
|
||||||
|
|
||||||
@@ -942,12 +1013,14 @@ g.test('max_buffers_and_attribs')
|
|||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { format } = t.params;
|
const { format } = t.params;
|
||||||
// In compat mode, @builtin(vertex_index) and @builtin(instance_index) each take an attribute
|
// In compat mode, @builtin(vertex_index) and @builtin(instance_index) each take an attribute
|
||||||
const maxVertexAttributes = t.isCompatibility ? kMaxVertexAttributes - 2 : kMaxVertexAttributes;
|
const maxVertexBuffers = t.device.limits.maxVertexBuffers;
|
||||||
const attributesPerBuffer = Math.ceil(maxVertexAttributes / kMaxVertexBuffers);
|
const deviceMaxVertexAttributes = t.device.limits.maxVertexAttributes;
|
||||||
|
const maxVertexAttributes = deviceMaxVertexAttributes - (t.isCompatibility ? 2 : 0);
|
||||||
|
const attributesPerBuffer = Math.ceil(maxVertexAttributes / maxVertexBuffers);
|
||||||
let attributesEmitted = 0;
|
let attributesEmitted = 0;
|
||||||
|
|
||||||
const state: VertexLayoutState<{}, {}> = [];
|
const state: VertexLayoutState<{}, {}> = [];
|
||||||
for (let i = 0; i < kMaxVertexBuffers; i++) {
|
for (let i = 0; i < maxVertexBuffers; i++) {
|
||||||
const attributes: GPUVertexAttribute[] = [];
|
const attributes: GPUVertexAttribute[] = [];
|
||||||
for (let j = 0; j < attributesPerBuffer && attributesEmitted < maxVertexAttributes; j++) {
|
for (let j = 0; j < attributesPerBuffer && attributesEmitted < maxVertexAttributes; j++) {
|
||||||
attributes.push({ format, offset: 0, shaderLocation: attributesEmitted });
|
attributes.push({ format, offset: 0, shaderLocation: attributesEmitted });
|
||||||
@@ -974,25 +1047,26 @@ g.test('array_stride_zero')
|
|||||||
.combine('format', kVertexFormats)
|
.combine('format', kVertexFormats)
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.combine('stepMode', ['vertex', 'instance'] as const)
|
.combine('stepMode', ['vertex', 'instance'] as const)
|
||||||
.expand('offset', p => {
|
.expand('offsetVariant', p => {
|
||||||
const formatInfo = kVertexFormatInfo[p.format];
|
const formatInfo = kVertexFormatInfo[p.format];
|
||||||
const formatSize = formatInfo.bytesPerComponent * formatInfo.componentCount;
|
const formatSize = formatInfo.byteSize;
|
||||||
return new Set([
|
return filterUniqueValueTestVariants([
|
||||||
0,
|
{ mult: 0, add: 0 },
|
||||||
4,
|
{ mult: 0, add: 4 },
|
||||||
8,
|
{ mult: 0, add: 8 },
|
||||||
formatSize,
|
{ mult: 0, add: formatSize },
|
||||||
formatSize * 2,
|
{ mult: 0, add: formatSize * 2 },
|
||||||
kMaxVertexBufferArrayStride / 2,
|
{ mult: 0.5, add: 0 },
|
||||||
kMaxVertexBufferArrayStride - formatSize - 4,
|
{ mult: 1, add: -formatSize - 4 },
|
||||||
kMaxVertexBufferArrayStride - formatSize - 8,
|
{ mult: 1, add: -formatSize - 8 },
|
||||||
kMaxVertexBufferArrayStride - formatSize,
|
{ mult: 1, add: -formatSize },
|
||||||
kMaxVertexBufferArrayStride - formatSize * 2,
|
{ mult: 1, add: -formatSize * 2 },
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { format, stepMode, offset } = t.params;
|
const { format, stepMode, offsetVariant } = t.params;
|
||||||
|
const offset = t.makeLimitVariant('maxVertexBufferArrayStride', offsetVariant);
|
||||||
const kCount = 10;
|
const kCount = 10;
|
||||||
|
|
||||||
// Create the stride 0 part of the test, first by faking a single vertex being drawn and
|
// Create the stride 0 part of the test, first by faking a single vertex being drawn and
|
||||||
@@ -1055,7 +1129,7 @@ g.test('discontiguous_location_and_attribs')
|
|||||||
.fn(t => {
|
.fn(t => {
|
||||||
t.runTest([
|
t.runTest([
|
||||||
{
|
{
|
||||||
slot: kMaxVertexBuffers - 1,
|
slot: t.device.limits.maxVertexBuffers - 1,
|
||||||
arrayStride: 4,
|
arrayStride: 4,
|
||||||
stepMode: 'vertex',
|
stepMode: 'vertex',
|
||||||
attributes: [
|
attributes: [
|
||||||
@@ -1068,7 +1142,13 @@ g.test('discontiguous_location_and_attribs')
|
|||||||
arrayStride: 16,
|
arrayStride: 16,
|
||||||
stepMode: 'instance',
|
stepMode: 'instance',
|
||||||
vbOffset: 1000,
|
vbOffset: 1000,
|
||||||
attributes: [{ format: 'uint32x4', offset: 0, shaderLocation: kMaxVertexAttributes - 1 }],
|
attributes: [
|
||||||
|
{
|
||||||
|
format: 'uint32x4',
|
||||||
|
offset: 0,
|
||||||
|
shaderLocation: t.device.limits.maxVertexAttributes - 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -1083,7 +1163,7 @@ g.test('overlapping_attributes')
|
|||||||
const { format } = t.params;
|
const { format } = t.params;
|
||||||
|
|
||||||
// In compat mode, @builtin(vertex_index) and @builtin(instance_index) each take an attribute
|
// In compat mode, @builtin(vertex_index) and @builtin(instance_index) each take an attribute
|
||||||
const maxVertexAttributes = t.isCompatibility ? kMaxVertexAttributes - 2 : kMaxVertexAttributes;
|
const maxVertexAttributes = t.device.limits.maxVertexAttributes - (t.isCompatibility ? 2 : 0);
|
||||||
const attributes: GPUVertexAttribute[] = [];
|
const attributes: GPUVertexAttribute[] = [];
|
||||||
for (let i = 0; i < maxVertexAttributes; i++) {
|
for (let i = 0; i < maxVertexAttributes; i++) {
|
||||||
attributes.push({ format, offset: 0, shaderLocation: i });
|
attributes.push({ format, offset: 0, shaderLocation: i });
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type Raster8x4 = readonly [
|
|||||||
readonly [0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1],
|
readonly [0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1],
|
||||||
readonly [0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1],
|
readonly [0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1],
|
||||||
readonly [0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1],
|
readonly [0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1],
|
||||||
readonly [0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1]
|
readonly [0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1],
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Expected 4x4 rasterization of a bottom-left triangle. */
|
/** Expected 4x4 rasterization of a bottom-left triangle. */
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
kAllBufferUsageBits,
|
kAllBufferUsageBits,
|
||||||
kBufferSizeAlignment,
|
kBufferSizeAlignment,
|
||||||
kBufferUsages,
|
kBufferUsages,
|
||||||
kLimitInfo,
|
|
||||||
} from '../../../capability_info.js';
|
} from '../../../capability_info.js';
|
||||||
import { GPUConst } from '../../../constants.js';
|
import { GPUConst } from '../../../constants.js';
|
||||||
import { kMaxSafeMultipleOf8 } from '../../../util/math.js';
|
import { kMaxSafeMultipleOf8 } from '../../../util/math.js';
|
||||||
@@ -46,18 +45,11 @@ g.test('size')
|
|||||||
|
|
||||||
g.test('limit')
|
g.test('limit')
|
||||||
.desc('Test buffer size is validated against maxBufferSize.')
|
.desc('Test buffer size is validated against maxBufferSize.')
|
||||||
.params(u =>
|
.params(u => u.beginSubcases().combine('sizeAddition', [-1, 0, +1]))
|
||||||
u
|
|
||||||
.beginSubcases()
|
|
||||||
.combine('size', [
|
|
||||||
kLimitInfo.maxBufferSize.default - 1,
|
|
||||||
kLimitInfo.maxBufferSize.default,
|
|
||||||
kLimitInfo.maxBufferSize.default + 1,
|
|
||||||
])
|
|
||||||
)
|
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { size } = t.params;
|
const { sizeAddition } = t.params;
|
||||||
const isValid = size <= kLimitInfo.maxBufferSize.default;
|
const size = t.makeLimitVariant('maxBufferSize', { mult: 1, add: sizeAddition });
|
||||||
|
const isValid = size <= t.device.limits.maxBufferSize;
|
||||||
const usage = BufferUsage.COPY_SRC;
|
const usage = BufferUsage.COPY_SRC;
|
||||||
t.expectGPUError('validation', () => t.device.createBuffer({ size, usage }), !isValid);
|
t.expectGPUError('validation', () => t.device.createBuffer({ size, usage }), !isValid);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class F extends ValidationTest {
|
|||||||
assert(expectation.rejectName === null, 'mapAsync unexpectedly passed');
|
assert(expectation.rejectName === null, 'mapAsync unexpectedly passed');
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
assert(ex instanceof Error, 'mapAsync rejected with non-error');
|
assert(ex instanceof Error, 'mapAsync rejected with non-error');
|
||||||
|
assert(typeof ex.stack === 'string', 'mapAsync rejected without a stack');
|
||||||
assert(expectation.rejectName === ex.name, `mapAsync rejected unexpectedly with: ${ex}`);
|
assert(expectation.rejectName === ex.name, `mapAsync rejected unexpectedly with: ${ex}`);
|
||||||
assert(
|
assert(
|
||||||
expectation.earlyRejection === rejectedEarly,
|
expectation.earlyRejection === rejectedEarly,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ g.test('createQuerySet')
|
|||||||
'timestamp-query'.
|
'timestamp-query'.
|
||||||
- createQuerySet
|
- createQuerySet
|
||||||
- type {occlusion, timestamp}
|
- type {occlusion, timestamp}
|
||||||
- x= {pipeline statistics, timestamp} query {enable, disable}
|
- x= timestamp query {enable, disable}
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
.params(u =>
|
.params(u =>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ Tests for capability checking for features enabling optional texture formats.
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
|
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
|
||||||
|
import { getGPU } from '../../../../../common/util/navigator_gpu.js';
|
||||||
import { assert } from '../../../../../common/util/util.js';
|
import { assert } from '../../../../../common/util/util.js';
|
||||||
import { kAllTextureFormats, kTextureFormatInfo } from '../../../../format_info.js';
|
import { kAllTextureFormats, kTextureFormatInfo } from '../../../../format_info.js';
|
||||||
import { kAllCanvasTypes, createCanvas } from '../../../../util/create_elements.js';
|
import { kAllCanvasTypes, createCanvas } from '../../../../util/create_elements.js';
|
||||||
@@ -273,6 +274,7 @@ g.test('color_target_state')
|
|||||||
)
|
)
|
||||||
.params(u =>
|
.params(u =>
|
||||||
u
|
u
|
||||||
|
.combine('isAsync', [false, true])
|
||||||
.combine('format', kOptionalTextureFormats)
|
.combine('format', kOptionalTextureFormats)
|
||||||
.filter(t => !!kTextureFormatInfo[t.format].colorRender)
|
.filter(t => !!kTextureFormatInfo[t.format].colorRender)
|
||||||
.combine('enable_required_feature', [true, false])
|
.combine('enable_required_feature', [true, false])
|
||||||
@@ -286,10 +288,12 @@ g.test('color_target_state')
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { format, enable_required_feature } = t.params;
|
const { isAsync, format, enable_required_feature } = t.params;
|
||||||
|
|
||||||
t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
|
t.doCreateRenderPipelineTest(
|
||||||
t.device.createRenderPipeline({
|
isAsync,
|
||||||
|
enable_required_feature,
|
||||||
|
{
|
||||||
layout: 'auto',
|
layout: 'auto',
|
||||||
vertex: {
|
vertex: {
|
||||||
module: t.device.createShaderModule({
|
module: t.device.createShaderModule({
|
||||||
@@ -312,8 +316,9 @@ g.test('color_target_state')
|
|||||||
entryPoint: 'main',
|
entryPoint: 'main',
|
||||||
targets: [{ format }],
|
targets: [{ format }],
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
});
|
'TypeError'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
g.test('depth_stencil_state')
|
g.test('depth_stencil_state')
|
||||||
@@ -325,6 +330,7 @@ g.test('depth_stencil_state')
|
|||||||
)
|
)
|
||||||
.params(u =>
|
.params(u =>
|
||||||
u
|
u
|
||||||
|
.combine('isAsync', [false, true])
|
||||||
.combine('format', kOptionalTextureFormats)
|
.combine('format', kOptionalTextureFormats)
|
||||||
.filter(t => !!(kTextureFormatInfo[t.format].depth || kTextureFormatInfo[t.format].stencil))
|
.filter(t => !!(kTextureFormatInfo[t.format].depth || kTextureFormatInfo[t.format].stencil))
|
||||||
.combine('enable_required_feature', [true, false])
|
.combine('enable_required_feature', [true, false])
|
||||||
@@ -338,10 +344,12 @@ g.test('depth_stencil_state')
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { format, enable_required_feature } = t.params;
|
const { isAsync, format, enable_required_feature } = t.params;
|
||||||
|
|
||||||
t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
|
t.doCreateRenderPipelineTest(
|
||||||
t.device.createRenderPipeline({
|
isAsync,
|
||||||
|
enable_required_feature,
|
||||||
|
{
|
||||||
layout: 'auto',
|
layout: 'auto',
|
||||||
vertex: {
|
vertex: {
|
||||||
module: t.device.createShaderModule({
|
module: t.device.createShaderModule({
|
||||||
@@ -369,8 +377,9 @@ g.test('depth_stencil_state')
|
|||||||
entryPoint: 'main',
|
entryPoint: 'main',
|
||||||
targets: [{ format: 'rgba8unorm' }],
|
targets: [{ format: 'rgba8unorm' }],
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
});
|
'TypeError'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
g.test('render_bundle_encoder_descriptor_color_format')
|
g.test('render_bundle_encoder_descriptor_color_format')
|
||||||
@@ -437,3 +446,18 @@ g.test('render_bundle_encoder_descriptor_depth_stencil_format')
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
g.test('check_capability_guarantees')
|
||||||
|
.desc(
|
||||||
|
`check "texture-compression-bc" is supported or both "texture-compression-etc2" and "texture-compression-astc" are supported.`
|
||||||
|
)
|
||||||
|
.fn(async t => {
|
||||||
|
const adapter = await getGPU(t.rec).requestAdapter();
|
||||||
|
assert(adapter !== null);
|
||||||
|
|
||||||
|
const features = adapter.features;
|
||||||
|
t.expect(
|
||||||
|
features.has('texture-compression-bc') ||
|
||||||
|
(features.has('texture-compression-etc2') && features.has('texture-compression-astc'))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ import { kUnitCaseParamsBuilder } from '../../../../../common/framework/params_b
|
|||||||
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
|
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
|
||||||
import { getGPU } from '../../../../../common/util/navigator_gpu.js';
|
import { getGPU } from '../../../../../common/util/navigator_gpu.js';
|
||||||
import { assert, range, reorder, ReorderOrder } from '../../../../../common/util/util.js';
|
import { assert, range, reorder, ReorderOrder } from '../../../../../common/util/util.js';
|
||||||
import { kLimitInfo } from '../../../../capability_info.js';
|
import { getDefaultLimitsForAdapter } from '../../../../capability_info.js';
|
||||||
import { kTextureFormatInfo } from '../../../../format_info.js';
|
|
||||||
import { GPUTestBase } from '../../../../gpu_test.js';
|
import { GPUTestBase } from '../../../../gpu_test.js';
|
||||||
import { align } from '../../../../util/math.js';
|
|
||||||
|
|
||||||
type GPUSupportedLimit = keyof GPUSupportedLimits;
|
type GPUSupportedLimit = keyof GPUSupportedLimits;
|
||||||
|
|
||||||
@@ -14,16 +12,16 @@ export const kCreatePipelineTypes = [
|
|||||||
'createRenderPipelineWithFragmentStage',
|
'createRenderPipelineWithFragmentStage',
|
||||||
'createComputePipeline',
|
'createComputePipeline',
|
||||||
] as const;
|
] as const;
|
||||||
export type CreatePipelineType = typeof kCreatePipelineTypes[number];
|
export type CreatePipelineType = (typeof kCreatePipelineTypes)[number];
|
||||||
|
|
||||||
export const kRenderEncoderTypes = ['render', 'renderBundle'] as const;
|
export const kRenderEncoderTypes = ['render', 'renderBundle'] as const;
|
||||||
export type RenderEncoderType = typeof kRenderEncoderTypes[number];
|
export type RenderEncoderType = (typeof kRenderEncoderTypes)[number];
|
||||||
|
|
||||||
export const kEncoderTypes = ['compute', 'render', 'renderBundle'] as const;
|
export const kEncoderTypes = ['compute', 'render', 'renderBundle'] as const;
|
||||||
export type EncoderType = typeof kEncoderTypes[number];
|
export type EncoderType = (typeof kEncoderTypes)[number];
|
||||||
|
|
||||||
export const kBindGroupTests = ['sameGroup', 'differentGroups'] as const;
|
export const kBindGroupTests = ['sameGroup', 'differentGroups'] as const;
|
||||||
export type BindGroupTest = typeof kBindGroupTests[number];
|
export type BindGroupTest = (typeof kBindGroupTests)[number];
|
||||||
|
|
||||||
export const kBindingCombinations = [
|
export const kBindingCombinations = [
|
||||||
'vertex',
|
'vertex',
|
||||||
@@ -32,7 +30,7 @@ export const kBindingCombinations = [
|
|||||||
'vertexAndFragmentWithPossibleFragmentStageOverflow',
|
'vertexAndFragmentWithPossibleFragmentStageOverflow',
|
||||||
'compute',
|
'compute',
|
||||||
] as const;
|
] as const;
|
||||||
export type BindingCombination = typeof kBindingCombinations[number];
|
export type BindingCombination = (typeof kBindingCombinations)[number];
|
||||||
|
|
||||||
export function getPipelineTypeForBindingCombination(bindingCombination: BindingCombination) {
|
export function getPipelineTypeForBindingCombination(bindingCombination: BindingCombination) {
|
||||||
switch (bindingCombination) {
|
switch (bindingCombination) {
|
||||||
@@ -76,19 +74,6 @@ function getWGSLBindings(
|
|||||||
).join('\n ');
|
).join('\n ');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an array of GPUColorTargetState return the number of bytes per sample
|
|
||||||
*/
|
|
||||||
export function computeBytesPerSample(targets: GPUColorTargetState[]) {
|
|
||||||
let bytesPerSample = 0;
|
|
||||||
for (const { format } of targets) {
|
|
||||||
const info = kTextureFormatInfo[format];
|
|
||||||
const alignedBytesPerSample = align(bytesPerSample, info.colorRender!.alignment);
|
|
||||||
bytesPerSample = alignedBytesPerSample + info.colorRender!.byteCost;
|
|
||||||
}
|
|
||||||
return bytesPerSample;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPerStageWGSLForBindingCombinationImpl(
|
export function getPerStageWGSLForBindingCombinationImpl(
|
||||||
bindingCombination: BindingCombination,
|
bindingCombination: BindingCombination,
|
||||||
order: ReorderOrder,
|
order: ReorderOrder,
|
||||||
@@ -216,11 +201,11 @@ export function getPerStageWGSLForBindingCombinationStorageTextures(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const kLimitModes = ['defaultLimit', 'adapterLimit'] as const;
|
export const kLimitModes = ['defaultLimit', 'adapterLimit'] as const;
|
||||||
export type LimitMode = typeof kLimitModes[number];
|
export type LimitMode = (typeof kLimitModes)[number];
|
||||||
export type LimitsRequest = Record<string, LimitMode>;
|
export type LimitsRequest = Record<string, LimitMode>;
|
||||||
|
|
||||||
export const kMaximumTestValues = ['atLimit', 'overLimit'] as const;
|
export const kMaximumTestValues = ['atLimit', 'overLimit'] as const;
|
||||||
export type MaximumTestValue = typeof kMaximumTestValues[number];
|
export type MaximumTestValue = (typeof kMaximumTestValues)[number];
|
||||||
|
|
||||||
export function getMaximumTestValue(limit: number, testValue: MaximumTestValue) {
|
export function getMaximumTestValue(limit: number, testValue: MaximumTestValue) {
|
||||||
switch (testValue) {
|
switch (testValue) {
|
||||||
@@ -232,7 +217,7 @@ export function getMaximumTestValue(limit: number, testValue: MaximumTestValue)
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const kMinimumTestValues = ['atLimit', 'underLimit'] as const;
|
export const kMinimumTestValues = ['atLimit', 'underLimit'] as const;
|
||||||
export type MinimumTestValue = typeof kMinimumTestValues[number];
|
export type MinimumTestValue = (typeof kMinimumTestValues)[number];
|
||||||
|
|
||||||
export const kMaximumLimitValueTests = [
|
export const kMaximumLimitValueTests = [
|
||||||
'atDefault',
|
'atDefault',
|
||||||
@@ -241,7 +226,7 @@ export const kMaximumLimitValueTests = [
|
|||||||
'atMaximum',
|
'atMaximum',
|
||||||
'overMaximum',
|
'overMaximum',
|
||||||
] as const;
|
] as const;
|
||||||
export type MaximumLimitValueTest = typeof kMaximumLimitValueTests[number];
|
export type MaximumLimitValueTest = (typeof kMaximumLimitValueTests)[number];
|
||||||
|
|
||||||
export function getLimitValue(
|
export function getLimitValue(
|
||||||
defaultLimit: number,
|
defaultLimit: number,
|
||||||
@@ -270,10 +255,11 @@ export const kMinimumLimitValueTests = [
|
|||||||
'atMinimum',
|
'atMinimum',
|
||||||
'underMinimum',
|
'underMinimum',
|
||||||
] as const;
|
] as const;
|
||||||
export type MinimumLimitValueTest = typeof kMinimumLimitValueTests[number];
|
export type MinimumLimitValueTest = (typeof kMinimumLimitValueTests)[number];
|
||||||
|
|
||||||
export function getDefaultLimit(limit: GPUSupportedLimit): number {
|
export function getDefaultLimitForAdapter(adapter: GPUAdapter, limit: GPUSupportedLimit): number {
|
||||||
return (kLimitInfo as Record<string, { default: number }>)[limit].default;
|
const limitInfo = getDefaultLimitsForAdapter(adapter);
|
||||||
|
return limitInfo[limit as keyof typeof limitInfo].default;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeviceAndLimits = {
|
export type DeviceAndLimits = {
|
||||||
@@ -316,12 +302,12 @@ export class LimitTestsImpl extends GPUTestBase {
|
|||||||
defaultLimit = 0;
|
defaultLimit = 0;
|
||||||
adapterLimit = 0;
|
adapterLimit = 0;
|
||||||
|
|
||||||
async init() {
|
override async init() {
|
||||||
await super.init();
|
await super.init();
|
||||||
const gpu = getGPU(this.rec);
|
const gpu = getGPU(this.rec);
|
||||||
this._adapter = await gpu.requestAdapter();
|
this._adapter = await gpu.requestAdapter();
|
||||||
const limit = this.limit;
|
const limit = this.limit;
|
||||||
this.defaultLimit = getDefaultLimit(limit);
|
this.defaultLimit = getDefaultLimitForAdapter(this.adapter, limit);
|
||||||
this.adapterLimit = this.adapter.limits[limit] as number;
|
this.adapterLimit = this.adapter.limits[limit] as number;
|
||||||
assert(!Number.isNaN(this.defaultLimit));
|
assert(!Number.isNaN(this.defaultLimit));
|
||||||
assert(!Number.isNaN(this.adapterLimit));
|
assert(!Number.isNaN(this.adapterLimit));
|
||||||
@@ -332,7 +318,7 @@ export class LimitTestsImpl extends GPUTestBase {
|
|||||||
return this._adapter!;
|
return this._adapter!;
|
||||||
}
|
}
|
||||||
|
|
||||||
get device(): GPUDevice {
|
override get device(): GPUDevice {
|
||||||
assert(this._device !== undefined, 'device is only valid in _testThenDestroyDevice callback');
|
assert(this._device !== undefined, 'device is only valid in _testThenDestroyDevice callback');
|
||||||
return this._device;
|
return this._device;
|
||||||
}
|
}
|
||||||
@@ -344,7 +330,9 @@ export class LimitTestsImpl extends GPUTestBase {
|
|||||||
requiredFeatures?: GPUFeatureName[]
|
requiredFeatures?: GPUFeatureName[]
|
||||||
) {
|
) {
|
||||||
if (shouldReject) {
|
if (shouldReject) {
|
||||||
this.shouldReject('OperationError', adapter.requestDevice({ requiredLimits }));
|
this.shouldReject('OperationError', adapter.requestDevice({ requiredLimits }), {
|
||||||
|
allowMissingStack: true,
|
||||||
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
} else {
|
} else {
|
||||||
return await adapter.requestDevice({ requiredLimits, requiredFeatures });
|
return await adapter.requestDevice({ requiredLimits, requiredFeatures });
|
||||||
@@ -354,7 +342,7 @@ export class LimitTestsImpl extends GPUTestBase {
|
|||||||
getDefaultOrAdapterLimit(limit: GPUSupportedLimit, limitMode: LimitMode) {
|
getDefaultOrAdapterLimit(limit: GPUSupportedLimit, limitMode: LimitMode) {
|
||||||
switch (limitMode) {
|
switch (limitMode) {
|
||||||
case 'defaultLimit':
|
case 'defaultLimit':
|
||||||
return getDefaultLimit(limit);
|
return getDefaultLimitForAdapter(this.adapter, limit);
|
||||||
case 'adapterLimit':
|
case 'adapterLimit':
|
||||||
return this.adapter.limits[limit];
|
return this.adapter.limits[limit];
|
||||||
}
|
}
|
||||||
@@ -380,7 +368,7 @@ export class LimitTestsImpl extends GPUTestBase {
|
|||||||
const extraLimit = extraLimitStr as GPUSupportedLimit;
|
const extraLimit = extraLimitStr as GPUSupportedLimit;
|
||||||
requiredLimits[extraLimit] =
|
requiredLimits[extraLimit] =
|
||||||
limitMode === 'defaultLimit'
|
limitMode === 'defaultLimit'
|
||||||
? getDefaultLimit(extraLimit)
|
? getDefaultLimitForAdapter(adapter, extraLimit)
|
||||||
: (adapter.limits[extraLimit] as number);
|
: (adapter.limits[extraLimit] as number);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -576,12 +564,12 @@ export class LimitTestsImpl extends GPUTestBase {
|
|||||||
expectedName: string,
|
expectedName: string,
|
||||||
p: Promise<unknown>,
|
p: Promise<unknown>,
|
||||||
shouldReject: boolean,
|
shouldReject: boolean,
|
||||||
msg?: string
|
message?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (shouldReject) {
|
if (shouldReject) {
|
||||||
this.shouldReject(expectedName, p, msg);
|
this.shouldReject(expectedName, p, { message });
|
||||||
} else {
|
} else {
|
||||||
this.shouldResolve(p, msg);
|
this.shouldResolve(p, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to explicitly wait for the promise because the device may be
|
// We need to explicitly wait for the promise because the device may be
|
||||||
@@ -596,7 +584,11 @@ export class LimitTestsImpl extends GPUTestBase {
|
|||||||
/**
|
/**
|
||||||
* Calls a function that expects a validation error if shouldError is true
|
* Calls a function that expects a validation error if shouldError is true
|
||||||
*/
|
*/
|
||||||
async expectValidationError<R>(fn: () => R, shouldError: boolean = true, msg = ''): Promise<R> {
|
override async expectValidationError<R>(
|
||||||
|
fn: () => R,
|
||||||
|
shouldError: boolean = true,
|
||||||
|
msg = ''
|
||||||
|
): Promise<R> {
|
||||||
return this.expectGPUErrorAsync('validation', fn, shouldError, msg);
|
return this.expectGPUErrorAsync('validation', fn, shouldError, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1079,7 +1071,7 @@ export class LimitTestsImpl extends GPUTestBase {
|
|||||||
*/
|
*/
|
||||||
function makeLimitTestFixture(limit: GPUSupportedLimit): typeof LimitTestsImpl {
|
function makeLimitTestFixture(limit: GPUSupportedLimit): typeof LimitTestsImpl {
|
||||||
class LimitTests extends LimitTestsImpl {
|
class LimitTests extends LimitTestsImpl {
|
||||||
limit = limit;
|
override limit = limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
return LimitTests;
|
return LimitTests;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ g.test('createPipelineLayout,at_over')
|
|||||||
limitTest,
|
limitTest,
|
||||||
testValueName,
|
testValueName,
|
||||||
async ({ device, testValue, shouldError }) => {
|
async ({ device, testValue, shouldError }) => {
|
||||||
const bindGroupLayouts = range(testValue, (i: number) =>
|
const bindGroupLayouts = range(testValue, _i =>
|
||||||
device.createBindGroupLayout({
|
device.createBindGroupLayout({
|
||||||
entries: [
|
entries: [
|
||||||
{
|
{
|
||||||
@@ -85,3 +85,11 @@ g.test('setBindGroup,at_over')
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
g.test('validate,maxBindGroupsPlusVertexBuffers')
|
||||||
|
.desc(`Test that ${limit} <= maxBindGroupsPlusVertexBuffers`)
|
||||||
|
.fn(t => {
|
||||||
|
const { adapter, defaultLimit, adapterLimit } = t;
|
||||||
|
t.expect(defaultLimit <= t.getDefaultLimit('maxBindGroupsPlusVertexBuffers'));
|
||||||
|
t.expect(adapterLimit <= adapter.limits.maxBindGroupsPlusVertexBuffers);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { range } from '../../../../../common/util/util.js';
|
import { range } from '../../../../../common/util/util.js';
|
||||||
|
import { kMaxColorAttachmentsToTest } from '../../../../capability_info.js';
|
||||||
|
|
||||||
import { kMaximumLimitBaseParams, getDefaultLimit, makeLimitTestGroup } from './limit_utils.js';
|
import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
|
||||||
|
|
||||||
function getPipelineDescriptor(device: GPUDevice, testValue: number): GPURenderPipelineDescriptor {
|
function getPipelineDescriptor(device: GPUDevice, testValue: number): GPURenderPipelineDescriptor {
|
||||||
const code = `
|
const code = `
|
||||||
@@ -105,9 +106,19 @@ g.test('validate,maxColorAttachmentBytesPerSample')
|
|||||||
.desc(`Test ${limit} against maxColorAttachmentBytesPerSample`)
|
.desc(`Test ${limit} against maxColorAttachmentBytesPerSample`)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { adapter, defaultLimit, adapterLimit: maximumLimit } = t;
|
const { adapter, defaultLimit, adapterLimit: maximumLimit } = t;
|
||||||
const minColorAttachmentBytesPerSample = getDefaultLimit('maxColorAttachmentBytesPerSample');
|
const minColorAttachmentBytesPerSample = t.getDefaultLimit('maxColorAttachmentBytesPerSample');
|
||||||
// The smallest attachment is 1 byte
|
// The smallest attachment is 1 byte
|
||||||
// so make sure maxColorAttachments < maxColorAttachmentBytesPerSample
|
// so make sure maxColorAttachments < maxColorAttachmentBytesPerSample
|
||||||
t.expect(defaultLimit <= minColorAttachmentBytesPerSample);
|
t.expect(defaultLimit <= minColorAttachmentBytesPerSample);
|
||||||
t.expect(maximumLimit <= adapter.limits.maxColorAttachmentBytesPerSample);
|
t.expect(maximumLimit <= adapter.limits.maxColorAttachmentBytesPerSample);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
g.test('validate,kMaxColorAttachmentsToTest')
|
||||||
|
.desc(
|
||||||
|
`
|
||||||
|
Tests that kMaxColorAttachmentsToTest is large enough to test the limits of this device
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.fn(t => {
|
||||||
|
t.expect(t.adapter.limits.maxColorAttachments <= kMaxColorAttachmentsToTest);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { GPUTestBase } from '../../../../gpu_test.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
kMaximumLimitBaseParams,
|
kMaximumLimitBaseParams,
|
||||||
getDefaultLimit,
|
|
||||||
MaximumLimitValueTest,
|
MaximumLimitValueTest,
|
||||||
MaximumTestValue,
|
MaximumTestValue,
|
||||||
makeLimitTestGroup,
|
makeLimitTestGroup,
|
||||||
@@ -75,11 +76,15 @@ function getDeviceLimitToRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTestWorkgroupSize(testValueName: MaximumTestValue, requestedLimit: number) {
|
function getTestWorkgroupSize(
|
||||||
|
t: GPUTestBase,
|
||||||
|
testValueName: MaximumTestValue,
|
||||||
|
requestedLimit: number
|
||||||
|
) {
|
||||||
const maxDimensions = [
|
const maxDimensions = [
|
||||||
getDefaultLimit('maxComputeWorkgroupSizeX'),
|
t.getDefaultLimit('maxComputeWorkgroupSizeX'),
|
||||||
getDefaultLimit('maxComputeWorkgroupSizeY'),
|
t.getDefaultLimit('maxComputeWorkgroupSizeY'),
|
||||||
getDefaultLimit('maxComputeWorkgroupSizeZ'),
|
t.getDefaultLimit('maxComputeWorkgroupSizeZ'),
|
||||||
];
|
];
|
||||||
|
|
||||||
switch (testValueName) {
|
switch (testValueName) {
|
||||||
@@ -91,13 +96,14 @@ function getTestWorkgroupSize(testValueName: MaximumTestValue, requestedLimit: n
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDeviceLimitToRequestAndValueToTest(
|
function getDeviceLimitToRequestAndValueToTest(
|
||||||
|
t: GPUTestBase,
|
||||||
limitValueTest: MaximumLimitValueTest,
|
limitValueTest: MaximumLimitValueTest,
|
||||||
testValueName: MaximumTestValue,
|
testValueName: MaximumTestValue,
|
||||||
defaultLimit: number,
|
defaultLimit: number,
|
||||||
maximumLimit: number
|
maximumLimit: number
|
||||||
) {
|
) {
|
||||||
const requestedLimit = getDeviceLimitToRequest(limitValueTest, defaultLimit, maximumLimit);
|
const requestedLimit = getDeviceLimitToRequest(limitValueTest, defaultLimit, maximumLimit);
|
||||||
const workgroupSize = getTestWorkgroupSize(testValueName, requestedLimit);
|
const workgroupSize = getTestWorkgroupSize(t, testValueName, requestedLimit);
|
||||||
return {
|
return {
|
||||||
requestedLimit,
|
requestedLimit,
|
||||||
workgroupSize,
|
workgroupSize,
|
||||||
@@ -115,6 +121,7 @@ g.test('createComputePipeline,at_over')
|
|||||||
const { defaultLimit, adapterLimit: maximumLimit } = t;
|
const { defaultLimit, adapterLimit: maximumLimit } = t;
|
||||||
|
|
||||||
const { requestedLimit, workgroupSize } = getDeviceLimitToRequestAndValueToTest(
|
const { requestedLimit, workgroupSize } = getDeviceLimitToRequestAndValueToTest(
|
||||||
|
t,
|
||||||
limitTest,
|
limitTest,
|
||||||
testValueName,
|
testValueName,
|
||||||
defaultLimit,
|
defaultLimit,
|
||||||
|
|||||||
@@ -10,3 +10,11 @@ g.test('createComputePipeline,at_over')
|
|||||||
const { limitTest, testValueName, async } = t.params;
|
const { limitTest, testValueName, async } = t.params;
|
||||||
await t.testMaxComputeWorkgroupSize(limitTest, testValueName, async, 'X');
|
await t.testMaxComputeWorkgroupSize(limitTest, testValueName, async, 'X');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
g.test('validate,maxComputeInvocationsPerWorkgroup')
|
||||||
|
.desc(`Test that ${limit} <= maxComputeInvocationsPerWorkgroup`)
|
||||||
|
.fn(t => {
|
||||||
|
const { adapter, defaultLimit, adapterLimit } = t;
|
||||||
|
t.expect(defaultLimit <= t.getDefaultLimit('maxComputeInvocationsPerWorkgroup'));
|
||||||
|
t.expect(adapterLimit <= adapter.limits.maxComputeInvocationsPerWorkgroup);
|
||||||
|
});
|
||||||
|
|||||||
@@ -10,3 +10,11 @@ g.test('createComputePipeline,at_over')
|
|||||||
const { limitTest, testValueName, async } = t.params;
|
const { limitTest, testValueName, async } = t.params;
|
||||||
await t.testMaxComputeWorkgroupSize(limitTest, testValueName, async, 'Y');
|
await t.testMaxComputeWorkgroupSize(limitTest, testValueName, async, 'Y');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
g.test('validate,maxComputeInvocationsPerWorkgroup')
|
||||||
|
.desc(`Test that ${limit} <= maxComputeInvocationsPerWorkgroup`)
|
||||||
|
.fn(t => {
|
||||||
|
const { adapter, defaultLimit, adapterLimit } = t;
|
||||||
|
t.expect(defaultLimit <= t.getDefaultLimit('maxComputeInvocationsPerWorkgroup'));
|
||||||
|
t.expect(adapterLimit <= adapter.limits.maxComputeInvocationsPerWorkgroup);
|
||||||
|
});
|
||||||
|
|||||||
@@ -10,3 +10,11 @@ g.test('createComputePipeline,at_over')
|
|||||||
const { limitTest, testValueName, async } = t.params;
|
const { limitTest, testValueName, async } = t.params;
|
||||||
await t.testMaxComputeWorkgroupSize(limitTest, testValueName, async, 'Z');
|
await t.testMaxComputeWorkgroupSize(limitTest, testValueName, async, 'Z');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
g.test('validate,maxComputeInvocationsPerWorkgroup')
|
||||||
|
.desc(`Test that ${limit} <= maxComputeInvocationsPerWorkgroup`)
|
||||||
|
.fn(t => {
|
||||||
|
const { adapter, defaultLimit, adapterLimit } = t;
|
||||||
|
t.expect(defaultLimit <= t.getDefaultLimit('maxComputeInvocationsPerWorkgroup'));
|
||||||
|
t.expect(adapterLimit <= adapter.limits.maxComputeInvocationsPerWorkgroup);
|
||||||
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const kCreateComputePipelineTypes = [
|
|||||||
'createComputePipeline',
|
'createComputePipeline',
|
||||||
'createComputePipelineAsync',
|
'createComputePipelineAsync',
|
||||||
] as const;
|
] as const;
|
||||||
type CreateComputePipelineType = typeof kCreateComputePipelineTypes[number];
|
type CreateComputePipelineType = (typeof kCreateComputePipelineTypes)[number];
|
||||||
|
|
||||||
async function createComputePipeline(
|
async function createComputePipeline(
|
||||||
device: GPUDevice,
|
device: GPUDevice,
|
||||||
@@ -77,3 +77,21 @@ g.test('dispatchWorkgroups,at_over')
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
g.test('validate')
|
||||||
|
.desc(
|
||||||
|
`Test that ${limit} <= maxComputeWorkgroupSizeX x maxComputeWorkgroupSizeY x maxComputeWorkgroupSizeZ`
|
||||||
|
)
|
||||||
|
.fn(t => {
|
||||||
|
const { adapter, defaultLimit, adapterLimit } = t;
|
||||||
|
const defaultMaxComputeWorkgroupSizeProduct =
|
||||||
|
t.getDefaultLimit('maxComputeWorkgroupSizeX') *
|
||||||
|
t.getDefaultLimit('maxComputeWorkgroupSizeY') *
|
||||||
|
t.getDefaultLimit('maxComputeWorkgroupSizeZ');
|
||||||
|
const maxComputeWorkgroupSizeProduct =
|
||||||
|
adapter.limits.maxComputeWorkgroupSizeX *
|
||||||
|
adapter.limits.maxComputeWorkgroupSizeY *
|
||||||
|
adapter.limits.maxComputeWorkgroupSizeZ;
|
||||||
|
t.expect(defaultLimit <= defaultMaxComputeWorkgroupSizeProduct);
|
||||||
|
t.expect(adapterLimit <= maxComputeWorkgroupSizeProduct);
|
||||||
|
});
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ g.test('createBindGroupLayout,at_over')
|
|||||||
limitTest,
|
limitTest,
|
||||||
testValueName,
|
testValueName,
|
||||||
async ({ device, testValue, shouldError }) => {
|
async ({ device, testValue, shouldError }) => {
|
||||||
|
shouldError ||= testValue > t.device.limits.maxStorageBuffersPerShaderStage;
|
||||||
await t.expectValidationError(() => {
|
await t.expectValidationError(() => {
|
||||||
device.createBindGroupLayout({
|
device.createBindGroupLayout({
|
||||||
entries: range(testValue, i => ({
|
entries: range(testValue, i => ({
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ g.test('createBindGroupLayout,at_over')
|
|||||||
limitTest,
|
limitTest,
|
||||||
testValueName,
|
testValueName,
|
||||||
async ({ device, testValue, shouldError }) => {
|
async ({ device, testValue, shouldError }) => {
|
||||||
|
shouldError ||= testValue > t.device.limits.maxUniformBuffersPerShaderStage;
|
||||||
await t.expectValidationError(() => {
|
await t.expectValidationError(() => {
|
||||||
device.createBindGroupLayout({
|
device.createBindGroupLayout({
|
||||||
entries: range(testValue, i => ({
|
entries: range(testValue, i => ({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { assert, range } from '../../../../../common/util/util.js';
|
import { range } from '../../../../../common/util/util.js';
|
||||||
|
|
||||||
import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
|
import { kMaximumLimitBaseParams, LimitsRequest, makeLimitTestGroup } from './limit_utils.js';
|
||||||
|
|
||||||
function getTypeForNumComponents(numComponents: number) {
|
function getTypeForNumComponents(numComponents: number) {
|
||||||
return numComponents > 1 ? `vec${numComponents}f` : 'f32';
|
return numComponents > 1 ? `vec${numComponents}f` : 'f32';
|
||||||
@@ -21,7 +21,6 @@ function getPipelineDescriptor(
|
|||||||
|
|
||||||
const maxInterStageVariables = device.limits.maxInterStageShaderVariables;
|
const maxInterStageVariables = device.limits.maxInterStageShaderVariables;
|
||||||
const numComponents = Math.min(maxVertexShaderOutputComponents, maxFragmentShaderInputComponents);
|
const numComponents = Math.min(maxVertexShaderOutputComponents, maxFragmentShaderInputComponents);
|
||||||
assert(Math.ceil(numComponents / 4) <= maxInterStageVariables);
|
|
||||||
|
|
||||||
const num4ComponentVaryings = Math.floor(numComponents / 4);
|
const num4ComponentVaryings = Math.floor(numComponents / 4);
|
||||||
const lastVaryingNumComponents = numComponents % 4;
|
const lastVaryingNumComponents = numComponents % 4;
|
||||||
@@ -42,8 +41,8 @@ function getPipelineDescriptor(
|
|||||||
// maxInterStageShaderComponents : ${device.limits.maxInterStageShaderComponents}
|
// maxInterStageShaderComponents : ${device.limits.maxInterStageShaderComponents}
|
||||||
// num components in vertex shader : ${numComponents}${pointList ? ' + point-list' : ''}
|
// num components in vertex shader : ${numComponents}${pointList ? ' + point-list' : ''}
|
||||||
// num components in fragment shader : ${numComponents}${frontFacing ? ' + front-facing' : ''}${
|
// num components in fragment shader : ${numComponents}${frontFacing ? ' + front-facing' : ''}${
|
||||||
sampleIndex ? ' + sample_index' : ''
|
sampleIndex ? ' + sample_index' : ''
|
||||||
}${sampleMaskIn ? ' + sample_mask' : ''}
|
}${sampleMaskIn ? ' + sample_mask' : ''}
|
||||||
// maxVertexShaderOutputComponents : ${maxVertexShaderOutputComponents}
|
// maxVertexShaderOutputComponents : ${maxVertexShaderOutputComponents}
|
||||||
// maxFragmentShaderInputComponents : ${maxFragmentShaderInputComponents}
|
// maxFragmentShaderInputComponents : ${maxFragmentShaderInputComponents}
|
||||||
// maxInterStageVariables: : ${maxInterStageVariables}
|
// maxInterStageVariables: : ${maxInterStageVariables}
|
||||||
@@ -127,6 +126,10 @@ g.test('createRenderPipeline,at_over')
|
|||||||
sampleMaskIn,
|
sampleMaskIn,
|
||||||
sampleMaskOut,
|
sampleMaskOut,
|
||||||
} = t.params;
|
} = t.params;
|
||||||
|
// Request the largest value of maxInterStageShaderVariables to allow the test using as many
|
||||||
|
// inter-stage shader components as possible without being limited by
|
||||||
|
// maxInterStageShaderVariables.
|
||||||
|
const extraLimits: LimitsRequest = { maxInterStageShaderVariables: 'adapterLimit' };
|
||||||
await t.testDeviceWithRequestedMaximumLimits(
|
await t.testDeviceWithRequestedMaximumLimits(
|
||||||
limitTest,
|
limitTest,
|
||||||
testValueName,
|
testValueName,
|
||||||
@@ -142,6 +145,7 @@ g.test('createRenderPipeline,at_over')
|
|||||||
);
|
);
|
||||||
|
|
||||||
await t.testCreateRenderPipeline(pipelineDescriptor, async, shouldError, code);
|
await t.testCreateRenderPipeline(pipelineDescriptor, async, shouldError, code);
|
||||||
}
|
},
|
||||||
|
extraLimits
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ import {
|
|||||||
kMaximumLimitBaseParams,
|
kMaximumLimitBaseParams,
|
||||||
makeLimitTestGroup,
|
makeLimitTestGroup,
|
||||||
LimitMode,
|
LimitMode,
|
||||||
getDefaultLimit,
|
|
||||||
MaximumLimitValueTest,
|
MaximumLimitValueTest,
|
||||||
MaximumTestValue,
|
MaximumTestValue,
|
||||||
} from './limit_utils.js';
|
} from './limit_utils.js';
|
||||||
|
|
||||||
const kBufferParts = ['wholeBuffer', 'biggerBufferWithOffset'] as const;
|
const kBufferParts = ['wholeBuffer', 'biggerBufferWithOffset'] as const;
|
||||||
type BufferPart = typeof kBufferParts[number];
|
type BufferPart = (typeof kBufferParts)[number];
|
||||||
|
|
||||||
function getSizeAndOffsetForBufferPart(device: GPUDevice, bufferPart: BufferPart, size: number) {
|
function getSizeAndOffsetForBufferPart(device: GPUDevice, bufferPart: BufferPart, size: number) {
|
||||||
const align = device.limits.minUniformBufferOffsetAlignment;
|
const align = device.limits.minUniformBufferOffsetAlignment;
|
||||||
@@ -145,10 +144,18 @@ g.test('createBindGroup,at_over')
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
g.test('validate')
|
||||||
|
.desc(`Test that ${limit} is a multiple of 4 bytes`)
|
||||||
|
.fn(t => {
|
||||||
|
const { defaultLimit, adapterLimit } = t;
|
||||||
|
t.expect(defaultLimit % 4 === 0);
|
||||||
|
t.expect(adapterLimit % 4 === 0);
|
||||||
|
});
|
||||||
|
|
||||||
g.test('validate,maxBufferSize')
|
g.test('validate,maxBufferSize')
|
||||||
.desc(`Test that ${limit} <= maxBufferSize`)
|
.desc(`Test that ${limit} <= maxBufferSize`)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { adapter, defaultLimit, adapterLimit } = t;
|
const { adapter, defaultLimit, adapterLimit } = t;
|
||||||
t.expect(defaultLimit <= getDefaultLimit('maxBufferSize'));
|
t.expect(defaultLimit <= t.getDefaultLimit('maxBufferSize'));
|
||||||
t.expect(adapterLimit <= adapter.limits.maxBufferSize);
|
t.expect(adapterLimit <= adapter.limits.maxBufferSize);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import {
|
import { LimitMode, kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
|
||||||
LimitMode,
|
|
||||||
getDefaultLimit,
|
|
||||||
kMaximumLimitBaseParams,
|
|
||||||
makeLimitTestGroup,
|
|
||||||
} from './limit_utils.js';
|
|
||||||
|
|
||||||
const kBufferParts = ['wholeBuffer', 'biggerBufferWithOffset'] as const;
|
const kBufferParts = ['wholeBuffer', 'biggerBufferWithOffset'] as const;
|
||||||
type BufferPart = typeof kBufferParts[number];
|
type BufferPart = (typeof kBufferParts)[number];
|
||||||
|
|
||||||
function getSizeAndOffsetForBufferPart(device: GPUDevice, bufferPart: BufferPart, size: number) {
|
function getSizeAndOffsetForBufferPart(device: GPUDevice, bufferPart: BufferPart, size: number) {
|
||||||
const align = device.limits.minUniformBufferOffsetAlignment;
|
const align = device.limits.minUniformBufferOffsetAlignment;
|
||||||
@@ -90,6 +85,6 @@ g.test('validate,maxBufferSize')
|
|||||||
.desc(`Test that ${limit} <= maxBufferSize`)
|
.desc(`Test that ${limit} <= maxBufferSize`)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { adapter, defaultLimit, adapterLimit } = t;
|
const { adapter, defaultLimit, adapterLimit } = t;
|
||||||
t.expect(defaultLimit <= getDefaultLimit('maxBufferSize'));
|
t.expect(defaultLimit <= t.getDefaultLimit('maxBufferSize'));
|
||||||
t.expect(adapterLimit <= adapter.limits.maxBufferSize);
|
t.expect(adapterLimit <= adapter.limits.maxBufferSize);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -111,3 +111,11 @@ g.test('createRenderPipeline,at_over')
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
g.test('validate')
|
||||||
|
.desc(`Test that ${limit} is a multiple of 4 bytes`)
|
||||||
|
.fn(t => {
|
||||||
|
const { defaultLimit, adapterLimit } = t;
|
||||||
|
t.expect(defaultLimit % 4 === 0);
|
||||||
|
t.expect(adapterLimit % 4 === 0);
|
||||||
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { range } from '../../../../../common/util/util.js';
|
|||||||
import { kRenderEncoderTypes, kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
|
import { kRenderEncoderTypes, kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
|
||||||
|
|
||||||
const kPipelineTypes = ['withoutLocations', 'withLocations'] as const;
|
const kPipelineTypes = ['withoutLocations', 'withLocations'] as const;
|
||||||
type PipelineType = typeof kPipelineTypes[number];
|
type PipelineType = (typeof kPipelineTypes)[number];
|
||||||
|
|
||||||
function getPipelineDescriptor(
|
function getPipelineDescriptor(
|
||||||
device: GPUDevice,
|
device: GPUDevice,
|
||||||
@@ -90,3 +90,11 @@ g.test('setVertexBuffer,at_over')
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
g.test('validate,maxBindGroupsPlusVertexBuffers')
|
||||||
|
.desc(`Test that ${limit} <= maxBindGroupsPlusVertexBuffers`)
|
||||||
|
.fn(t => {
|
||||||
|
const { adapter, defaultLimit, adapterLimit } = t;
|
||||||
|
t.expect(defaultLimit <= t.getDefaultLimit('maxBindGroupsPlusVertexBuffers'));
|
||||||
|
t.expect(adapterLimit <= adapter.limits.maxBindGroupsPlusVertexBuffers);
|
||||||
|
});
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ Tests calling createComputePipeline(Async) validation for compute workgroup_size
|
|||||||
[1, 1, 63],
|
[1, 1, 63],
|
||||||
[1, 1, 64],
|
[1, 1, 64],
|
||||||
[1, 1, 65],
|
[1, 1, 65],
|
||||||
])
|
] as const)
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { isAsync, size } = t.params;
|
const { isAsync, size } = t.params;
|
||||||
@@ -251,13 +251,14 @@ Tests calling createComputePipeline(Async) validation for compute workgroup_size
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
size[1] = size[1] ?? 1;
|
const workgroupX = size[0];
|
||||||
size[2] = size[2] ?? 1;
|
const workgroupY = size[1] ?? 1;
|
||||||
|
const workgroupZ = size[2] ?? 1;
|
||||||
|
|
||||||
const _success =
|
const _success =
|
||||||
size[0] <= t.device.limits.maxComputeWorkgroupSizeX &&
|
workgroupX <= t.device.limits.maxComputeWorkgroupSizeX &&
|
||||||
size[1] <= t.device.limits.maxComputeWorkgroupSizeY &&
|
workgroupY <= t.device.limits.maxComputeWorkgroupSizeY &&
|
||||||
size[2] <= t.device.limits.maxComputeWorkgroupSizeZ;
|
workgroupZ <= t.device.limits.maxComputeWorkgroupSizeZ;
|
||||||
t.doCreateComputePipelineTest(isAsync, _success, descriptor);
|
t.doCreateComputePipelineTest(isAsync, _success, descriptor);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const description = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
import { makeTestGroup } from '../../../common/framework/test_group.js';
|
import { makeTestGroup } from '../../../common/framework/test_group.js';
|
||||||
import { assert, unreachable } from '../../../common/util/util.js';
|
import { assert, makeValueTestVariant, unreachable } from '../../../common/util/util.js';
|
||||||
import {
|
import {
|
||||||
allBindingEntries,
|
allBindingEntries,
|
||||||
bindingTypeInfo,
|
bindingTypeInfo,
|
||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
kBufferBindingTypes,
|
kBufferBindingTypes,
|
||||||
kBufferUsages,
|
kBufferUsages,
|
||||||
kCompareFunctions,
|
kCompareFunctions,
|
||||||
kLimitInfo,
|
|
||||||
kSamplerBindingTypes,
|
kSamplerBindingTypes,
|
||||||
kTextureUsages,
|
kTextureUsages,
|
||||||
kTextureViewDimensions,
|
kTextureViewDimensions,
|
||||||
@@ -467,19 +466,20 @@ g.test('minBindingSize')
|
|||||||
usage: GPUBufferUsage.STORAGE,
|
usage: GPUBufferUsage.STORAGE,
|
||||||
});
|
});
|
||||||
|
|
||||||
t.expectValidationError(() => {
|
t.expectValidationError(
|
||||||
t.device.createBindGroup({
|
() => {
|
||||||
layout: bindGroupLayout,
|
t.device.createBindGroup({
|
||||||
entries: [
|
layout: bindGroupLayout,
|
||||||
{
|
entries: [
|
||||||
binding: 0,
|
{
|
||||||
resource: {
|
binding: 0,
|
||||||
buffer: storageBuffer,
|
resource: { buffer: storageBuffer },
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
});
|
||||||
});
|
},
|
||||||
}, minBindingSize !== undefined && size < minBindingSize);
|
minBindingSize !== undefined && size < minBindingSize
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
g.test('buffer,resource_state')
|
g.test('buffer,resource_state')
|
||||||
@@ -882,24 +882,20 @@ g.test('buffer,resource_offset')
|
|||||||
u //
|
u //
|
||||||
.combine('type', kBufferBindingTypes)
|
.combine('type', kBufferBindingTypes)
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.expand('offset', ({ type }) =>
|
.combine('offsetAddMult', [
|
||||||
type === 'uniform'
|
{ add: 0, mult: 0 },
|
||||||
? [
|
{ add: 0, mult: 0.5 },
|
||||||
kLimitInfo.minUniformBufferOffsetAlignment.default,
|
{ add: 0, mult: 1.5 },
|
||||||
kLimitInfo.minUniformBufferOffsetAlignment.default * 0.5,
|
{ add: 2, mult: 0 },
|
||||||
kLimitInfo.minUniformBufferOffsetAlignment.default * 1.5,
|
])
|
||||||
kLimitInfo.minUniformBufferOffsetAlignment.default + 2,
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
kLimitInfo.minStorageBufferOffsetAlignment.default,
|
|
||||||
kLimitInfo.minStorageBufferOffsetAlignment.default * 0.5,
|
|
||||||
kLimitInfo.minStorageBufferOffsetAlignment.default * 1.5,
|
|
||||||
kLimitInfo.minStorageBufferOffsetAlignment.default + 2,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { type, offset } = t.params;
|
const { type, offsetAddMult } = t.params;
|
||||||
|
const minAlignment =
|
||||||
|
t.device.limits[
|
||||||
|
type === 'uniform' ? 'minUniformBufferOffsetAlignment' : 'minStorageBufferOffsetAlignment'
|
||||||
|
];
|
||||||
|
const offset = makeValueTestVariant(minAlignment, offsetAddMult);
|
||||||
|
|
||||||
const bindGroupLayout = t.device.createBindGroupLayout({
|
const bindGroupLayout = t.device.createBindGroupLayout({
|
||||||
entries: [
|
entries: [
|
||||||
@@ -911,14 +907,8 @@ g.test('buffer,resource_offset')
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
let usage, isValid;
|
const usage = type === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE;
|
||||||
if (type === 'uniform') {
|
const isValid = offset % minAlignment === 0;
|
||||||
usage = GPUBufferUsage.UNIFORM;
|
|
||||||
isValid = offset % kLimitInfo.minUniformBufferOffsetAlignment.default === 0;
|
|
||||||
} else {
|
|
||||||
usage = GPUBufferUsage.STORAGE;
|
|
||||||
isValid = offset % kLimitInfo.minStorageBufferOffsetAlignment.default === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buffer = t.device.createBuffer({
|
const buffer = t.device.createBuffer({
|
||||||
size: 1024,
|
size: 1024,
|
||||||
@@ -947,22 +937,23 @@ g.test('buffer,resource_binding_size')
|
|||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
// Test a size of 1 (for uniform buffer) or 4 (for storage and read-only storage buffer)
|
// Test a size of 1 (for uniform buffer) or 4 (for storage and read-only storage buffer)
|
||||||
// then values just within and just above the limit.
|
// then values just within and just above the limit.
|
||||||
.expand('bindingSize', ({ type }) =>
|
.combine('bindingSize', [
|
||||||
type === 'uniform'
|
{ base: 1, limit: 0 },
|
||||||
? [
|
{ base: 0, limit: 1 },
|
||||||
1,
|
{ base: 1, limit: 1 },
|
||||||
kLimitInfo.maxUniformBufferBindingSize.default,
|
])
|
||||||
kLimitInfo.maxUniformBufferBindingSize.default + 1,
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
4,
|
|
||||||
kLimitInfo.maxStorageBufferBindingSize.default,
|
|
||||||
kLimitInfo.maxStorageBufferBindingSize.default + 4,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { type, bindingSize } = t.params;
|
const {
|
||||||
|
type,
|
||||||
|
bindingSize: { base, limit },
|
||||||
|
} = t.params;
|
||||||
|
const mult = type === 'uniform' ? 1 : 4;
|
||||||
|
const maxBindingSize =
|
||||||
|
t.device.limits[
|
||||||
|
type === 'uniform' ? 'maxUniformBufferBindingSize' : 'maxStorageBufferBindingSize'
|
||||||
|
];
|
||||||
|
const bindingSize = base * mult + maxBindingSize * limit;
|
||||||
|
|
||||||
const bindGroupLayout = t.device.createBindGroupLayout({
|
const bindGroupLayout = t.device.createBindGroupLayout({
|
||||||
entries: [
|
entries: [
|
||||||
@@ -974,17 +965,12 @@ g.test('buffer,resource_binding_size')
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
let usage, isValid;
|
const usage = type === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE;
|
||||||
if (type === 'uniform') {
|
const isValid = bindingSize <= maxBindingSize;
|
||||||
usage = GPUBufferUsage.UNIFORM;
|
|
||||||
isValid = bindingSize <= kLimitInfo.maxUniformBufferBindingSize.default;
|
|
||||||
} else {
|
|
||||||
usage = GPUBufferUsage.STORAGE;
|
|
||||||
isValid = bindingSize <= kLimitInfo.maxStorageBufferBindingSize.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// MAINTENANCE_TODO: Allocating the max size seems likely to fail. Refactor test.
|
||||||
const buffer = t.device.createBuffer({
|
const buffer = t.device.createBuffer({
|
||||||
size: kLimitInfo.maxStorageBufferBindingSize.default,
|
size: maxBindingSize,
|
||||||
usage,
|
usage,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1007,26 +993,18 @@ g.test('buffer,effective_buffer_binding_size')
|
|||||||
u
|
u
|
||||||
.combine('type', kBufferBindingTypes)
|
.combine('type', kBufferBindingTypes)
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.expand('offset', ({ type }) =>
|
.combine('offsetMult', [0, 1])
|
||||||
type === 'uniform'
|
.combine('bufferSizeAddition', [8, 10])
|
||||||
? [0, kLimitInfo.minUniformBufferOffsetAlignment.default]
|
|
||||||
: [0, kLimitInfo.minStorageBufferOffsetAlignment.default]
|
|
||||||
)
|
|
||||||
.expand('bufferSize', ({ type }) =>
|
|
||||||
type === 'uniform'
|
|
||||||
? [
|
|
||||||
kLimitInfo.minUniformBufferOffsetAlignment.default + 8,
|
|
||||||
kLimitInfo.minUniformBufferOffsetAlignment.default + 10,
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
kLimitInfo.minStorageBufferOffsetAlignment.default + 8,
|
|
||||||
kLimitInfo.minStorageBufferOffsetAlignment.default + 10,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
.combine('bindingSize', [undefined, 2, 4, 6])
|
.combine('bindingSize', [undefined, 2, 4, 6])
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { type, offset, bufferSize, bindingSize } = t.params;
|
const { type, offsetMult, bufferSizeAddition, bindingSize } = t.params;
|
||||||
|
const minAlignment =
|
||||||
|
t.device.limits[
|
||||||
|
type === 'uniform' ? 'minUniformBufferOffsetAlignment' : 'minStorageBufferOffsetAlignment'
|
||||||
|
];
|
||||||
|
const offset = minAlignment * offsetMult;
|
||||||
|
const bufferSize = minAlignment + bufferSizeAddition;
|
||||||
|
|
||||||
const bindGroupLayout = t.device.createBindGroupLayout({
|
const bindGroupLayout = t.device.createBindGroupLayout({
|
||||||
entries: [
|
entries: [
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ TODO: make sure tests are complete.
|
|||||||
import { kUnitCaseParamsBuilder } from '../../../common/framework/params_builder.js';
|
import { kUnitCaseParamsBuilder } from '../../../common/framework/params_builder.js';
|
||||||
import { makeTestGroup } from '../../../common/framework/test_group.js';
|
import { makeTestGroup } from '../../../common/framework/test_group.js';
|
||||||
import {
|
import {
|
||||||
kLimitInfo,
|
|
||||||
kShaderStages,
|
kShaderStages,
|
||||||
kShaderStageCombinations,
|
kShaderStageCombinations,
|
||||||
kStorageTextureAccessValues,
|
kStorageTextureAccessValues,
|
||||||
@@ -63,27 +62,26 @@ g.test('maximum_binding_limit')
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
.paramsSubcasesOnly(u =>
|
.paramsSubcasesOnly(u =>
|
||||||
u //
|
u.combine('bindingVariant', [1, 4, 8, 256, 'default', 'default-minus-one'] as const)
|
||||||
.combine('binding', [
|
|
||||||
1,
|
|
||||||
4,
|
|
||||||
8,
|
|
||||||
256,
|
|
||||||
kLimitInfo.maxBindingsPerBindGroup.default - 1,
|
|
||||||
kLimitInfo.maxBindingsPerBindGroup.default,
|
|
||||||
])
|
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { binding } = t.params;
|
const { bindingVariant } = t.params;
|
||||||
const entries: Array<GPUBindGroupLayoutEntry> = [];
|
const entries: Array<GPUBindGroupLayoutEntry> = [];
|
||||||
|
|
||||||
|
const binding =
|
||||||
|
bindingVariant === 'default'
|
||||||
|
? t.device.limits.maxBindingsPerBindGroup
|
||||||
|
: bindingVariant === 'default-minus-one'
|
||||||
|
? t.device.limits.maxBindingsPerBindGroup - 1
|
||||||
|
: bindingVariant;
|
||||||
|
|
||||||
entries.push({
|
entries.push({
|
||||||
binding,
|
binding,
|
||||||
visibility: GPUShaderStage.COMPUTE,
|
visibility: GPUShaderStage.COMPUTE,
|
||||||
buffer: { type: 'storage' as const },
|
buffer: { type: 'storage' as const },
|
||||||
});
|
});
|
||||||
|
|
||||||
const success = binding < kLimitInfo.maxBindingsPerBindGroup.default;
|
const success = binding < t.device.limits.maxBindingsPerBindGroup;
|
||||||
|
|
||||||
t.expectValidationError(() => {
|
t.expectValidationError(() => {
|
||||||
t.device.createBindGroupLayout({
|
t.device.createBindGroupLayout({
|
||||||
@@ -234,7 +232,10 @@ g.test('max_dynamic_buffers')
|
|||||||
const { type, extraDynamicBuffers, staticBuffers } = t.params;
|
const { type, extraDynamicBuffers, staticBuffers } = t.params;
|
||||||
const info = bufferBindingTypeInfo({ type });
|
const info = bufferBindingTypeInfo({ type });
|
||||||
|
|
||||||
const dynamicBufferCount = info.perPipelineLimitClass.maxDynamic + extraDynamicBuffers;
|
const limitName = info.perPipelineLimitClass.maxDynamicLimit;
|
||||||
|
const bufferCount = limitName ? t.getDefaultLimit(limitName) : 0;
|
||||||
|
const dynamicBufferCount = bufferCount + extraDynamicBuffers;
|
||||||
|
const perStageLimit = t.getDefaultLimit(info.perStageLimitClass.maxLimit);
|
||||||
|
|
||||||
const entries = [];
|
const entries = [];
|
||||||
for (let i = 0; i < dynamicBufferCount; i++) {
|
for (let i = 0; i < dynamicBufferCount; i++) {
|
||||||
@@ -257,9 +258,12 @@ g.test('max_dynamic_buffers')
|
|||||||
entries,
|
entries,
|
||||||
};
|
};
|
||||||
|
|
||||||
t.expectValidationError(() => {
|
t.expectValidationError(
|
||||||
t.device.createBindGroupLayout(descriptor);
|
() => {
|
||||||
}, extraDynamicBuffers > 0);
|
t.device.createBindGroupLayout(descriptor);
|
||||||
|
},
|
||||||
|
extraDynamicBuffers > 0 || entries.length > perStageLimit
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -297,7 +301,7 @@ const kMaxResourcesCases = kUnitCaseParamsBuilder
|
|||||||
.combine('extraVisibility', kShaderStages)
|
.combine('extraVisibility', kShaderStages)
|
||||||
.filter(p => (bindingTypeInfo(p.extraEntry).validStages & p.extraVisibility) !== 0);
|
.filter(p => (bindingTypeInfo(p.extraEntry).validStages & p.extraVisibility) !== 0);
|
||||||
|
|
||||||
// Should never fail unless kLimitInfo.maxBindingsPerBindGroup.default is exceeded, because the validation for
|
// Should never fail unless limitInfo.maxBindingsPerBindGroup.default is exceeded, because the validation for
|
||||||
// resources-of-type-per-stage is in pipeline layout creation.
|
// resources-of-type-per-stage is in pipeline layout creation.
|
||||||
g.test('max_resources_per_stage,in_bind_group_layout')
|
g.test('max_resources_per_stage,in_bind_group_layout')
|
||||||
.desc(
|
.desc(
|
||||||
@@ -313,7 +317,7 @@ g.test('max_resources_per_stage,in_bind_group_layout')
|
|||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { maxedEntry, extraEntry, maxedVisibility, extraVisibility } = t.params;
|
const { maxedEntry, extraEntry, maxedVisibility, extraVisibility } = t.params;
|
||||||
const maxedTypeInfo = bindingTypeInfo(maxedEntry);
|
const maxedTypeInfo = bindingTypeInfo(maxedEntry);
|
||||||
const maxedCount = maxedTypeInfo.perStageLimitClass.max;
|
const maxedCount = t.getDefaultLimit(maxedTypeInfo.perStageLimitClass.maxLimit);
|
||||||
const extraTypeInfo = bindingTypeInfo(extraEntry);
|
const extraTypeInfo = bindingTypeInfo(extraEntry);
|
||||||
|
|
||||||
const maxResourceBindings: GPUBindGroupLayoutEntry[] = [];
|
const maxResourceBindings: GPUBindGroupLayoutEntry[] = [];
|
||||||
@@ -364,7 +368,7 @@ g.test('max_resources_per_stage,in_pipeline_layout')
|
|||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { maxedEntry, extraEntry, maxedVisibility, extraVisibility } = t.params;
|
const { maxedEntry, extraEntry, maxedVisibility, extraVisibility } = t.params;
|
||||||
const maxedTypeInfo = bindingTypeInfo(maxedEntry);
|
const maxedTypeInfo = bindingTypeInfo(maxedEntry);
|
||||||
const maxedCount = maxedTypeInfo.perStageLimitClass.max;
|
const maxedCount = t.getDefaultLimit(maxedTypeInfo.perStageLimitClass.maxLimit);
|
||||||
const extraTypeInfo = bindingTypeInfo(extraEntry);
|
const extraTypeInfo = bindingTypeInfo(extraEntry);
|
||||||
|
|
||||||
const maxResourceBindings: GPUBindGroupLayoutEntry[] = [];
|
const maxResourceBindings: GPUBindGroupLayoutEntry[] = [];
|
||||||
@@ -443,15 +447,18 @@ g.test('storage_texture,formats')
|
|||||||
const { format } = t.params;
|
const { format } = t.params;
|
||||||
const info = kTextureFormatInfo[format];
|
const info = kTextureFormatInfo[format];
|
||||||
|
|
||||||
t.expectValidationError(() => {
|
t.expectValidationError(
|
||||||
t.device.createBindGroupLayout({
|
() => {
|
||||||
entries: [
|
t.device.createBindGroupLayout({
|
||||||
{
|
entries: [
|
||||||
binding: 0,
|
{
|
||||||
visibility: GPUShaderStage.COMPUTE,
|
binding: 0,
|
||||||
storageTexture: { format },
|
visibility: GPUShaderStage.COMPUTE,
|
||||||
},
|
storageTexture: { format },
|
||||||
],
|
},
|
||||||
});
|
],
|
||||||
}, !info.color?.storage);
|
});
|
||||||
|
},
|
||||||
|
!info.color?.storage
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,7 +33,13 @@ g.test('number_of_dynamic_buffers_exceeds_the_maximum_value')
|
|||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { type, visibility } = t.params;
|
const { type, visibility } = t.params;
|
||||||
const { maxDynamic } = bufferBindingTypeInfo({ type }).perPipelineLimitClass;
|
const info = bufferBindingTypeInfo({ type });
|
||||||
|
const { maxDynamicLimit } = info.perPipelineLimitClass;
|
||||||
|
const perStageLimit = t.getDefaultLimit(info.perStageLimitClass.maxLimit);
|
||||||
|
const maxDynamic = Math.min(
|
||||||
|
maxDynamicLimit ? t.getDefaultLimit(maxDynamicLimit) : 0,
|
||||||
|
perStageLimit
|
||||||
|
);
|
||||||
|
|
||||||
const maxDynamicBufferBindings: GPUBindGroupLayoutEntry[] = [];
|
const maxDynamicBufferBindings: GPUBindGroupLayoutEntry[] = [];
|
||||||
for (let binding = 0; binding < maxDynamic; binding++) {
|
for (let binding = 0; binding < maxDynamic; binding++) {
|
||||||
@@ -52,15 +58,17 @@ g.test('number_of_dynamic_buffers_exceeds_the_maximum_value')
|
|||||||
entries: [{ binding: 0, visibility, buffer: { type, hasDynamicOffset: false } }],
|
entries: [{ binding: 0, visibility, buffer: { type, hasDynamicOffset: false } }],
|
||||||
};
|
};
|
||||||
|
|
||||||
const goodPipelineLayoutDescriptor = {
|
if (perStageLimit > maxDynamic) {
|
||||||
bindGroupLayouts: [
|
const goodPipelineLayoutDescriptor = {
|
||||||
maxDynamicBufferBindGroupLayout,
|
bindGroupLayouts: [
|
||||||
t.device.createBindGroupLayout(goodDescriptor),
|
maxDynamicBufferBindGroupLayout,
|
||||||
],
|
t.device.createBindGroupLayout(goodDescriptor),
|
||||||
};
|
],
|
||||||
|
};
|
||||||
|
|
||||||
// Control case
|
// Control case
|
||||||
t.device.createPipelineLayout(goodPipelineLayoutDescriptor);
|
t.device.createPipelineLayout(goodPipelineLayoutDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
// Check dynamic buffers exceed maximum in pipeline layout.
|
// Check dynamic buffers exceed maximum in pipeline layout.
|
||||||
const badDescriptor = clone(goodDescriptor);
|
const badDescriptor = clone(goodDescriptor);
|
||||||
|
|||||||
@@ -16,12 +16,16 @@ g.test('lodMinAndMaxClamp')
|
|||||||
.combine('lodMaxClamp', [-4e-30, -1, 0, 0.5, 1, 10, 4e30])
|
.combine('lodMaxClamp', [-4e-30, -1, 0, 0.5, 1, 10, 4e30])
|
||||||
)
|
)
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
|
const shouldError =
|
||||||
|
t.params.lodMinClamp > t.params.lodMaxClamp ||
|
||||||
|
t.params.lodMinClamp < 0 ||
|
||||||
|
t.params.lodMaxClamp < 0;
|
||||||
t.expectValidationError(() => {
|
t.expectValidationError(() => {
|
||||||
t.device.createSampler({
|
t.device.createSampler({
|
||||||
lodMinClamp: t.params.lodMinClamp,
|
lodMinClamp: t.params.lodMinClamp,
|
||||||
lodMaxClamp: t.params.lodMaxClamp,
|
lodMaxClamp: t.params.lodMaxClamp,
|
||||||
});
|
});
|
||||||
}, t.params.lodMinClamp > t.params.lodMaxClamp || t.params.lodMinClamp < 0 || t.params.lodMaxClamp < 0);
|
}, shouldError);
|
||||||
});
|
});
|
||||||
|
|
||||||
g.test('maxAnisotropy')
|
g.test('maxAnisotropy')
|
||||||
@@ -48,6 +52,11 @@ g.test('maxAnisotropy')
|
|||||||
magFilter?: GPUFilterMode;
|
magFilter?: GPUFilterMode;
|
||||||
mipmapFilter?: GPUFilterMode;
|
mipmapFilter?: GPUFilterMode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shouldError =
|
||||||
|
maxAnisotropy < 1 ||
|
||||||
|
(maxAnisotropy > 1 &&
|
||||||
|
!(minFilter === 'linear' && magFilter === 'linear' && mipmapFilter === 'linear'));
|
||||||
t.expectValidationError(() => {
|
t.expectValidationError(() => {
|
||||||
t.device.createSampler({
|
t.device.createSampler({
|
||||||
minFilter,
|
minFilter,
|
||||||
@@ -55,5 +64,5 @@ g.test('maxAnisotropy')
|
|||||||
mipmapFilter,
|
mipmapFilter,
|
||||||
maxAnisotropy,
|
maxAnisotropy,
|
||||||
});
|
});
|
||||||
}, maxAnisotropy < 1 || (maxAnisotropy > 1 && !(minFilter === 'linear' && magFilter === 'linear' && mipmapFilter === 'linear')));
|
}, shouldError);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ export const description = `createTexture validation tests.`;
|
|||||||
|
|
||||||
import { SkipTestCase } from '../../../common/framework/fixture.js';
|
import { SkipTestCase } from '../../../common/framework/fixture.js';
|
||||||
import { makeTestGroup } from '../../../common/framework/test_group.js';
|
import { makeTestGroup } from '../../../common/framework/test_group.js';
|
||||||
import { assert } from '../../../common/util/util.js';
|
import { assert, makeValueTestVariant } from '../../../common/util/util.js';
|
||||||
import { kTextureDimensions, kTextureUsages, kLimitInfo } from '../../capability_info.js';
|
import { kTextureDimensions, kTextureUsages } from '../../capability_info.js';
|
||||||
import { GPUConst } from '../../constants.js';
|
import { GPUConst } from '../../constants.js';
|
||||||
import {
|
import {
|
||||||
kTextureFormats,
|
kTextureFormats,
|
||||||
@@ -326,7 +326,7 @@ g.test('sampleCount,valid_sampleCount_with_other_parameter_varies')
|
|||||||
arrayLayerCount === 2 && dimension !== '2d' && dimension !== undefined
|
arrayLayerCount === 2 && dimension !== '2d' && dimension !== undefined
|
||||||
)
|
)
|
||||||
.combine('mipLevelCount', [1, 2])
|
.combine('mipLevelCount', [1, 2])
|
||||||
.expand('usage', p => {
|
.expand('usage', () => {
|
||||||
const usageSet = new Set<number>();
|
const usageSet = new Set<number>();
|
||||||
for (const usage0 of kTextureUsages) {
|
for (const usage0 of kTextureUsages) {
|
||||||
for (const usage1 of kTextureUsages) {
|
for (const usage1 of kTextureUsages) {
|
||||||
@@ -495,10 +495,10 @@ g.test('texture_size,1d_texture')
|
|||||||
// Compressed and depth-stencil textures are invalid for 1D.
|
// Compressed and depth-stencil textures are invalid for 1D.
|
||||||
.combine('format', kRegularTextureFormats)
|
.combine('format', kRegularTextureFormats)
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.combine('width', [
|
.combine('widthVariant', [
|
||||||
kLimitInfo.maxTextureDimension1D.default - 1,
|
{ mult: 1, add: -1 },
|
||||||
kLimitInfo.maxTextureDimension1D.default,
|
{ mult: 1, add: 0 },
|
||||||
kLimitInfo.maxTextureDimension1D.default + 1,
|
{ mult: 1, add: 1 },
|
||||||
])
|
])
|
||||||
.combine('height', [1, 2])
|
.combine('height', [1, 2])
|
||||||
.combine('depthOrArrayLayers', [1, 2])
|
.combine('depthOrArrayLayers', [1, 2])
|
||||||
@@ -510,7 +510,8 @@ g.test('texture_size,1d_texture')
|
|||||||
t.selectDeviceOrSkipTestCase(info.feature);
|
t.selectDeviceOrSkipTestCase(info.feature);
|
||||||
})
|
})
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { format, width, height, depthOrArrayLayers } = t.params;
|
const { format, widthVariant, height, depthOrArrayLayers } = t.params;
|
||||||
|
const width = t.makeLimitVariant('maxTextureDimension1D', widthVariant);
|
||||||
|
|
||||||
const descriptor: GPUTextureDescriptor = {
|
const descriptor: GPUTextureDescriptor = {
|
||||||
size: [width, height, depthOrArrayLayers],
|
size: [width, height, depthOrArrayLayers],
|
||||||
@@ -520,7 +521,7 @@ g.test('texture_size,1d_texture')
|
|||||||
};
|
};
|
||||||
|
|
||||||
const success =
|
const success =
|
||||||
width <= kLimitInfo.maxTextureDimension1D.default && height === 1 && depthOrArrayLayers === 1;
|
width <= t.device.limits.maxTextureDimension1D && height === 1 && depthOrArrayLayers === 1;
|
||||||
|
|
||||||
t.expectValidationError(() => {
|
t.expectValidationError(() => {
|
||||||
t.device.createTexture(descriptor);
|
t.device.createTexture(descriptor);
|
||||||
@@ -533,20 +534,23 @@ g.test('texture_size,2d_texture,uncompressed_format')
|
|||||||
u
|
u
|
||||||
.combine('dimension', [undefined, '2d'] as const)
|
.combine('dimension', [undefined, '2d'] as const)
|
||||||
.combine('format', kUncompressedTextureFormats)
|
.combine('format', kUncompressedTextureFormats)
|
||||||
.combine('size', [
|
.combine(
|
||||||
// Test the bound of width
|
'sizeVariant',
|
||||||
[kLimitInfo.maxTextureDimension2D.default - 1, 1, 1],
|
/* prettier-ignore */ [
|
||||||
[kLimitInfo.maxTextureDimension2D.default, 1, 1],
|
// Test the bound of width
|
||||||
[kLimitInfo.maxTextureDimension2D.default + 1, 1, 1],
|
[{ mult: 1, add: -1 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
|
||||||
// Test the bound of height
|
[{ mult: 1, add: 0 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
|
||||||
[1, kLimitInfo.maxTextureDimension2D.default - 1, 1],
|
[{ mult: 1, add: 1 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
|
||||||
[1, kLimitInfo.maxTextureDimension2D.default, 1],
|
// Test the bound of height
|
||||||
[1, kLimitInfo.maxTextureDimension2D.default + 1, 1],
|
[{ mult: 0, add: 1 }, { mult: 1, add: -1 }, { mult: 0, add: 1 }],
|
||||||
// Test the bound of array layers
|
[{ mult: 0, add: 1 }, { mult: 1, add: 0 }, { mult: 0, add: 1 }],
|
||||||
[1, 1, kLimitInfo.maxTextureArrayLayers.default - 1],
|
[{ mult: 0, add: 1 }, { mult: 1, add: 1 }, { mult: 0, add: 1 }],
|
||||||
[1, 1, kLimitInfo.maxTextureArrayLayers.default],
|
// Test the bound of array layers
|
||||||
[1, 1, kLimitInfo.maxTextureArrayLayers.default + 1],
|
[{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: -1 }],
|
||||||
])
|
[{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: 0 }],
|
||||||
|
[{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: 1 }],
|
||||||
|
]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.beforeAllSubcases(t => {
|
.beforeAllSubcases(t => {
|
||||||
const { format } = t.params;
|
const { format } = t.params;
|
||||||
@@ -555,7 +559,12 @@ g.test('texture_size,2d_texture,uncompressed_format')
|
|||||||
t.selectDeviceOrSkipTestCase(info.feature);
|
t.selectDeviceOrSkipTestCase(info.feature);
|
||||||
})
|
})
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { dimension, format, size } = t.params;
|
const { dimension, format, sizeVariant } = t.params;
|
||||||
|
const size = [
|
||||||
|
t.device.limits.maxTextureDimension2D,
|
||||||
|
t.device.limits.maxTextureDimension2D,
|
||||||
|
t.device.limits.maxTextureArrayLayers,
|
||||||
|
].map((limit, ndx) => makeValueTestVariant(limit, sizeVariant[ndx]));
|
||||||
|
|
||||||
const descriptor: GPUTextureDescriptor = {
|
const descriptor: GPUTextureDescriptor = {
|
||||||
size,
|
size,
|
||||||
@@ -565,9 +574,9 @@ g.test('texture_size,2d_texture,uncompressed_format')
|
|||||||
};
|
};
|
||||||
|
|
||||||
const success =
|
const success =
|
||||||
size[0] <= kLimitInfo.maxTextureDimension2D.default &&
|
size[0] <= t.device.limits.maxTextureDimension2D &&
|
||||||
size[1] <= kLimitInfo.maxTextureDimension2D.default &&
|
size[1] <= t.device.limits.maxTextureDimension2D &&
|
||||||
size[2] <= kLimitInfo.maxTextureArrayLayers.default;
|
size[2] <= t.device.limits.maxTextureArrayLayers;
|
||||||
|
|
||||||
t.expectValidationError(() => {
|
t.expectValidationError(() => {
|
||||||
t.device.createTexture(descriptor);
|
t.device.createTexture(descriptor);
|
||||||
@@ -580,40 +589,152 @@ g.test('texture_size,2d_texture,compressed_format')
|
|||||||
u
|
u
|
||||||
.combine('dimension', [undefined, '2d'] as const)
|
.combine('dimension', [undefined, '2d'] as const)
|
||||||
.combine('format', kCompressedTextureFormats)
|
.combine('format', kCompressedTextureFormats)
|
||||||
.expand('size', p => {
|
.expand('sizeVariant', p => {
|
||||||
const { blockWidth, blockHeight } = kTextureFormatInfo[p.format];
|
const { blockWidth, blockHeight } = kTextureFormatInfo[p.format];
|
||||||
return [
|
return [
|
||||||
// Test the bound of width
|
// Test the bound of width
|
||||||
[kLimitInfo.maxTextureDimension2D.default - 1, 1, 1],
|
[
|
||||||
[kLimitInfo.maxTextureDimension2D.default - blockWidth, 1, 1],
|
{ mult: 1, add: -1 },
|
||||||
[kLimitInfo.maxTextureDimension2D.default - blockWidth, blockHeight, 1],
|
{ mult: 0, add: 1 },
|
||||||
[kLimitInfo.maxTextureDimension2D.default, 1, 1],
|
{ mult: 0, add: 1 },
|
||||||
[kLimitInfo.maxTextureDimension2D.default, blockHeight, 1],
|
],
|
||||||
[kLimitInfo.maxTextureDimension2D.default + 1, 1, 1],
|
[
|
||||||
[kLimitInfo.maxTextureDimension2D.default + blockWidth, 1, 1],
|
{ mult: 1, add: -blockWidth },
|
||||||
[kLimitInfo.maxTextureDimension2D.default + blockWidth, blockHeight, 1],
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 1, add: -blockWidth },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 1, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 1, add: blockWidth },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 1, add: blockWidth },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
// Test the bound of height
|
// Test the bound of height
|
||||||
[1, kLimitInfo.maxTextureDimension2D.default - 1, 1],
|
[
|
||||||
[1, kLimitInfo.maxTextureDimension2D.default - blockHeight, 1],
|
{ mult: 0, add: 1 },
|
||||||
[blockWidth, kLimitInfo.maxTextureDimension2D.default - blockHeight, 1],
|
{ mult: 1, add: -1 },
|
||||||
[1, kLimitInfo.maxTextureDimension2D.default, 1],
|
{ mult: 0, add: 1 },
|
||||||
[blockWidth, kLimitInfo.maxTextureDimension2D.default, 1],
|
],
|
||||||
[1, kLimitInfo.maxTextureDimension2D.default + 1, 1],
|
[
|
||||||
[1, kLimitInfo.maxTextureDimension2D.default + blockWidth, 1],
|
{ mult: 0, add: 1 },
|
||||||
[blockWidth, kLimitInfo.maxTextureDimension2D.default + blockHeight, 1],
|
{ mult: 1, add: -blockHeight },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 1, add: -blockHeight },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: +1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: +blockWidth },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 1, add: +blockHeight },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
// Test the bound of array layers
|
// Test the bound of array layers
|
||||||
[1, 1, kLimitInfo.maxTextureArrayLayers.default - 1],
|
[
|
||||||
[blockWidth, 1, kLimitInfo.maxTextureArrayLayers.default - 1],
|
{ mult: 0, add: 1 },
|
||||||
[1, blockHeight, kLimitInfo.maxTextureArrayLayers.default - 1],
|
{ mult: 0, add: 1 },
|
||||||
[blockWidth, blockHeight, kLimitInfo.maxTextureArrayLayers.default - 1],
|
{ mult: 1, add: -1 },
|
||||||
[1, 1, kLimitInfo.maxTextureArrayLayers.default],
|
],
|
||||||
[blockWidth, 1, kLimitInfo.maxTextureArrayLayers.default],
|
[
|
||||||
[1, blockHeight, kLimitInfo.maxTextureArrayLayers.default],
|
{ mult: 0, add: blockWidth },
|
||||||
[blockWidth, blockHeight, kLimitInfo.maxTextureArrayLayers.default],
|
{ mult: 0, add: 1 },
|
||||||
[1, 1, kLimitInfo.maxTextureArrayLayers.default + 1],
|
{ mult: 1, add: -1 },
|
||||||
[blockWidth, 1, kLimitInfo.maxTextureArrayLayers.default + 1],
|
],
|
||||||
[1, blockHeight, kLimitInfo.maxTextureArrayLayers.default + 1],
|
[
|
||||||
[blockWidth, blockHeight, kLimitInfo.maxTextureArrayLayers.default + 1],
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 1, add: -1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 1, add: -1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: +1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: +1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 1, add: +1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 1, add: +1 },
|
||||||
|
],
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -623,8 +744,13 @@ g.test('texture_size,2d_texture,compressed_format')
|
|||||||
t.selectDeviceOrSkipTestCase(info.feature);
|
t.selectDeviceOrSkipTestCase(info.feature);
|
||||||
})
|
})
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { dimension, format, size } = t.params;
|
const { dimension, format, sizeVariant } = t.params;
|
||||||
const info = kTextureFormatInfo[format];
|
const info = kTextureFormatInfo[format];
|
||||||
|
const size = [
|
||||||
|
t.device.limits.maxTextureDimension2D,
|
||||||
|
t.device.limits.maxTextureDimension2D,
|
||||||
|
t.device.limits.maxTextureArrayLayers,
|
||||||
|
].map((limit, ndx) => makeValueTestVariant(limit, sizeVariant[ndx]));
|
||||||
|
|
||||||
const descriptor: GPUTextureDescriptor = {
|
const descriptor: GPUTextureDescriptor = {
|
||||||
size,
|
size,
|
||||||
@@ -636,9 +762,9 @@ g.test('texture_size,2d_texture,compressed_format')
|
|||||||
const success =
|
const success =
|
||||||
size[0] % info.blockWidth === 0 &&
|
size[0] % info.blockWidth === 0 &&
|
||||||
size[1] % info.blockHeight === 0 &&
|
size[1] % info.blockHeight === 0 &&
|
||||||
size[0] <= kLimitInfo.maxTextureDimension2D.default &&
|
size[0] <= t.device.limits.maxTextureDimension2D &&
|
||||||
size[1] <= kLimitInfo.maxTextureDimension2D.default &&
|
size[1] <= t.device.limits.maxTextureDimension2D &&
|
||||||
size[2] <= kLimitInfo.maxTextureArrayLayers.default;
|
size[2] <= t.device.limits.maxTextureArrayLayers;
|
||||||
|
|
||||||
t.expectValidationError(() => {
|
t.expectValidationError(() => {
|
||||||
t.device.createTexture(descriptor);
|
t.device.createTexture(descriptor);
|
||||||
@@ -653,20 +779,23 @@ g.test('texture_size,3d_texture,uncompressed_format')
|
|||||||
u //
|
u //
|
||||||
.combine('format', kRegularTextureFormats)
|
.combine('format', kRegularTextureFormats)
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.combine('size', [
|
.combine(
|
||||||
// Test the bound of width
|
'sizeVariant',
|
||||||
[kLimitInfo.maxTextureDimension3D.default - 1, 1, 1],
|
/* prettier-ignore */ [
|
||||||
[kLimitInfo.maxTextureDimension3D.default, 1, 1],
|
// Test the bound of width
|
||||||
[kLimitInfo.maxTextureDimension3D.default + 1, 1, 1],
|
[{ mult: 1, add: -1 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
|
||||||
// Test the bound of height
|
[{ mult: 1, add: 0 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
|
||||||
[1, kLimitInfo.maxTextureDimension3D.default - 1, 1],
|
[{ mult: 1, add: +1 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
|
||||||
[1, kLimitInfo.maxTextureDimension3D.default, 1],
|
// Test the bound of height
|
||||||
[1, kLimitInfo.maxTextureDimension3D.default + 1, 1],
|
[{ mult: 0, add: 1 }, { mult: 1, add: -1 }, { mult: 0, add: 1 }],
|
||||||
// Test the bound of depth
|
[{ mult: 0, add: 1 }, { mult: 1, add: 0 }, { mult: 0, add: 1 }],
|
||||||
[1, 1, kLimitInfo.maxTextureDimension3D.default - 1],
|
[{ mult: 0, add: 1 }, { mult: 1, add: +1 }, { mult: 0, add: 1 }],
|
||||||
[1, 1, kLimitInfo.maxTextureDimension3D.default],
|
// Test the bound of depth
|
||||||
[1, 1, kLimitInfo.maxTextureDimension3D.default + 1],
|
[{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: -1 }],
|
||||||
])
|
[{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: 0 }],
|
||||||
|
[{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: +1 }],
|
||||||
|
]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.beforeAllSubcases(t => {
|
.beforeAllSubcases(t => {
|
||||||
const { format } = t.params;
|
const { format } = t.params;
|
||||||
@@ -675,7 +804,9 @@ g.test('texture_size,3d_texture,uncompressed_format')
|
|||||||
t.selectDeviceOrSkipTestCase(info.feature);
|
t.selectDeviceOrSkipTestCase(info.feature);
|
||||||
})
|
})
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { format, size } = t.params;
|
const { format, sizeVariant } = t.params;
|
||||||
|
const maxTextureDimension3D = t.device.limits.maxTextureDimension3D;
|
||||||
|
const size = sizeVariant.map(variant => t.makeLimitVariant('maxTextureDimension3D', variant));
|
||||||
|
|
||||||
const descriptor: GPUTextureDescriptor = {
|
const descriptor: GPUTextureDescriptor = {
|
||||||
size,
|
size,
|
||||||
@@ -685,9 +816,9 @@ g.test('texture_size,3d_texture,uncompressed_format')
|
|||||||
};
|
};
|
||||||
|
|
||||||
const success =
|
const success =
|
||||||
size[0] <= kLimitInfo.maxTextureDimension3D.default &&
|
size[0] <= maxTextureDimension3D &&
|
||||||
size[1] <= kLimitInfo.maxTextureDimension3D.default &&
|
size[1] <= maxTextureDimension3D &&
|
||||||
size[2] <= kLimitInfo.maxTextureDimension3D.default;
|
size[2] <= maxTextureDimension3D;
|
||||||
|
|
||||||
t.expectValidationError(() => {
|
t.expectValidationError(() => {
|
||||||
t.device.createTexture(descriptor);
|
t.device.createTexture(descriptor);
|
||||||
@@ -700,40 +831,152 @@ g.test('texture_size,3d_texture,compressed_format')
|
|||||||
u //
|
u //
|
||||||
.combine('format', kCompressedTextureFormats)
|
.combine('format', kCompressedTextureFormats)
|
||||||
.beginSubcases()
|
.beginSubcases()
|
||||||
.expand('size', p => {
|
.expand('sizeVariant', p => {
|
||||||
const { blockWidth, blockHeight } = kTextureFormatInfo[p.format];
|
const { blockWidth, blockHeight } = kTextureFormatInfo[p.format];
|
||||||
return [
|
return [
|
||||||
// Test the bound of width
|
// Test the bound of width
|
||||||
[kLimitInfo.maxTextureDimension3D.default - 1, 1, 1],
|
[
|
||||||
[kLimitInfo.maxTextureDimension3D.default - blockWidth, 1, 1],
|
{ mult: 1, add: -1 },
|
||||||
[kLimitInfo.maxTextureDimension3D.default - blockWidth, blockHeight, 1],
|
{ mult: 0, add: 1 },
|
||||||
[kLimitInfo.maxTextureDimension3D.default, 1, 1],
|
{ mult: 0, add: 1 },
|
||||||
[kLimitInfo.maxTextureDimension3D.default, blockHeight, 1],
|
],
|
||||||
[kLimitInfo.maxTextureDimension3D.default + 1, 1, 1],
|
[
|
||||||
[kLimitInfo.maxTextureDimension3D.default + blockWidth, 1, 1],
|
{ mult: 1, add: -blockWidth },
|
||||||
[kLimitInfo.maxTextureDimension3D.default + blockWidth, blockHeight, 1],
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 1, add: -blockWidth },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 1, add: +1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 1, add: +blockWidth },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 1, add: +blockWidth },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
// Test the bound of height
|
// Test the bound of height
|
||||||
[1, kLimitInfo.maxTextureDimension3D.default - 1, 1],
|
[
|
||||||
[1, kLimitInfo.maxTextureDimension3D.default - blockHeight, 1],
|
{ mult: 0, add: 1 },
|
||||||
[blockWidth, kLimitInfo.maxTextureDimension3D.default - blockHeight, 1],
|
{ mult: 1, add: -1 },
|
||||||
[1, kLimitInfo.maxTextureDimension3D.default, 1],
|
{ mult: 0, add: 1 },
|
||||||
[blockWidth, kLimitInfo.maxTextureDimension3D.default, 1],
|
],
|
||||||
[1, kLimitInfo.maxTextureDimension3D.default + 1, 1],
|
[
|
||||||
[1, kLimitInfo.maxTextureDimension3D.default + blockWidth, 1],
|
{ mult: 0, add: 1 },
|
||||||
[blockWidth, kLimitInfo.maxTextureDimension3D.default + blockHeight, 1],
|
{ mult: 1, add: -blockHeight },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 1, add: -blockHeight },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: +1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: +blockWidth },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 1, add: +blockHeight },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
],
|
||||||
// Test the bound of depth
|
// Test the bound of depth
|
||||||
[1, 1, kLimitInfo.maxTextureDimension3D.default - 1],
|
[
|
||||||
[blockWidth, 1, kLimitInfo.maxTextureDimension3D.default - 1],
|
{ mult: 0, add: 1 },
|
||||||
[1, blockHeight, kLimitInfo.maxTextureDimension3D.default - 1],
|
{ mult: 0, add: 1 },
|
||||||
[blockWidth, blockHeight, kLimitInfo.maxTextureDimension3D.default - 1],
|
{ mult: 1, add: -1 },
|
||||||
[1, 1, kLimitInfo.maxTextureDimension3D.default],
|
],
|
||||||
[blockWidth, 1, kLimitInfo.maxTextureDimension3D.default],
|
[
|
||||||
[1, blockHeight, kLimitInfo.maxTextureDimension3D.default],
|
{ mult: 0, add: blockWidth },
|
||||||
[blockWidth, blockHeight, kLimitInfo.maxTextureDimension3D.default],
|
{ mult: 0, add: 1 },
|
||||||
[1, 1, kLimitInfo.maxTextureDimension3D.default + 1],
|
{ mult: 1, add: -1 },
|
||||||
[blockWidth, 1, kLimitInfo.maxTextureDimension3D.default + 1],
|
],
|
||||||
[1, blockHeight, kLimitInfo.maxTextureDimension3D.default + 1],
|
[
|
||||||
[blockWidth, blockHeight, kLimitInfo.maxTextureDimension3D.default + 1],
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 1, add: -1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 1, add: -1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 1, add: 0 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: +1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 1, add: +1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: 1 },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 1, add: +1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ mult: 0, add: blockWidth },
|
||||||
|
{ mult: 0, add: blockHeight },
|
||||||
|
{ mult: 1, add: +1 },
|
||||||
|
],
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -746,12 +989,15 @@ g.test('texture_size,3d_texture,compressed_format')
|
|||||||
t.selectDeviceOrSkipTestCase(info.feature);
|
t.selectDeviceOrSkipTestCase(info.feature);
|
||||||
})
|
})
|
||||||
.fn(t => {
|
.fn(t => {
|
||||||
const { format, size } = t.params;
|
const { format, sizeVariant } = t.params;
|
||||||
const info = kTextureFormatInfo[format];
|
const info = kTextureFormatInfo[format];
|
||||||
|
|
||||||
|
const maxTextureDimension3D = t.device.limits.maxTextureDimension3D;
|
||||||
|
const size = sizeVariant.map(variant => t.makeLimitVariant('maxTextureDimension3D', variant));
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
kLimitInfo.maxTextureDimension3D.default % info.blockWidth === 0 &&
|
maxTextureDimension3D % info.blockWidth === 0 &&
|
||||||
kLimitInfo.maxTextureDimension3D.default % info.blockHeight === 0
|
maxTextureDimension3D % info.blockHeight === 0
|
||||||
);
|
);
|
||||||
|
|
||||||
const descriptor: GPUTextureDescriptor = {
|
const descriptor: GPUTextureDescriptor = {
|
||||||
@@ -764,9 +1010,9 @@ g.test('texture_size,3d_texture,compressed_format')
|
|||||||
const success =
|
const success =
|
||||||
size[0] % info.blockWidth === 0 &&
|
size[0] % info.blockWidth === 0 &&
|
||||||
size[1] % info.blockHeight === 0 &&
|
size[1] % info.blockHeight === 0 &&
|
||||||
size[0] <= kLimitInfo.maxTextureDimension3D.default &&
|
size[0] <= maxTextureDimension3D &&
|
||||||
size[1] <= kLimitInfo.maxTextureDimension3D.default &&
|
size[1] <= maxTextureDimension3D &&
|
||||||
size[2] <= kLimitInfo.maxTextureDimension3D.default;
|
size[2] <= maxTextureDimension3D;
|
||||||
|
|
||||||
t.expectValidationError(() => {
|
t.expectValidationError(() => {
|
||||||
t.device.createTexture(descriptor);
|
t.device.createTexture(descriptor);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user