Auto merge of #27402 - kunalmohan:webgpu-cts, r=jdm

Add a copy of WebGPU CTS to wpt.

<!-- Please describe your changes on the following line: -->
The test suite is generated from `glsl-dependent` branch in https://github.com/gpuweb/cts
We already have 6/44 tests passing and a number of subtests in other tests also pass.

r?@jdm
cc @kvark

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #27395  (GitHub issue number if applicable)

<!-- Either: -->
- [X] These changes do not require tests because we don't have tests for tests.

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
bors-servo 2020-08-04 14:12:45 -04:00 committed by GitHub
commit 9fbdbddb0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
107 changed files with 28298 additions and 5 deletions

View file

@ -549,6 +549,7 @@ def macos_wpt():
repo_dir="repo",
total_chunks=20,
processes=8,
run_webgpu=True,
)
@ -569,7 +570,7 @@ def linux_wpt_common(total_chunks, layout_2020):
def wpt_chunks(platform, make_chunk_task, build_task, total_chunks, processes,
repo_dir, chunks="all", layout_2020=False):
repo_dir, chunks="all", layout_2020=False, run_webgpu=False):
if layout_2020:
start = 1 # Skip the "extra" WPT testing, a.k.a. chunk 0
name_prefix = "Layout 2020 "
@ -618,6 +619,22 @@ def wpt_chunks(platform, make_chunk_task, build_task, total_chunks, processes,
# and wptrunner does not use "interactive mode" formatting:
# https://github.com/servo/servo/issues/22438
if this_chunk == 0:
if run_webgpu:
webgpu_script = """
time ./mach test-wpt _webgpu --release --processes $PROCESSES \
--headless --log-raw test-webgpu.log \
--log-errorsummary webgpu-errorsummary.log \
| cat
./mach filter-intermittents \
webgpu-errorsummary.log \
--log-intermittents webgpu-intermittents.log \
--log-filteredsummary filtered-webgpu-errorsummary.log \
--tracker-api default \
--reporter-api default
"""
else:
webgpu_script = ""
task.with_script("""
time python ./mach test-wpt --release --binary-arg=--multiprocess \
--processes $PROCESSES \
@ -654,7 +671,8 @@ def wpt_chunks(platform, make_chunk_task, build_task, total_chunks, processes,
--log-filteredsummary filtered-wdspec-errorsummary.log \
--tracker-api default \
--reporter-api default
""")
""" + webgpu_script
)
else:
task.with_script("""
./mach test-wpt \

View file

@ -552,6 +552,8 @@ class MachCommands(CommandBase):
elif tracker_api.endswith('/'):
tracker_api = tracker_api[0:-1]
if 'test' not in failure:
continue
query = urllib.parse.quote(failure['test'], safe='')
request = urllib.request.Request("%s/query.py?name=%s" % (tracker_api, query))
search = urllib.request.urlopen(request)
@ -569,9 +571,17 @@ class MachCommands(CommandBase):
is_intermittent = data['total_count'] > 0
if is_intermittent:
intermittents.append(failure["output"])
if 'output' in failure:
intermittents.append(failure["output"])
else:
intermittents.append("%s [expected %s] %s \n"
% (failure["status"], failure["expected"], failure['test']))
else:
actual_failures.append(failure["output"])
if 'output' in failure:
actual_failures.append(failure["output"])
else:
actual_failures.append("%s [expected %s] %s \n"
% (failure["status"], failure["expected"], failure['test']))
def format(outputs, description, file=sys.stdout):
formatted = "%s %s:\n%s" % (len(outputs), description, "\n".join(outputs))

View file

@ -516,7 +516,7 @@ def check_manifest_dirs(config_file, print_text=True):
p = parser.parse(lines)
paths = rec_parse(wpt_path("web-platform-tests"), p)
for idx, path in enumerate(paths):
if '_mozilla' in path or '_webgl' in path:
if '_mozilla' in path or '_webgl' in path or '_webgpu' in path:
continue
if not os.path.isdir(path):
yield(config_file, idx + 1, "Path in manifest was not found: {}".format(path))

View file

@ -1,3 +1,4 @@
/,./
/_mozilla/,../mozilla/tests
/_webgl/,../webgl/tests
/_webgpu/,../webgpu/tests

View file

@ -25,3 +25,8 @@ url_base = /_mozilla/
tests = webgl/tests
metadata = webgl/meta
url_base = /_webgl/
[manifest:webgpu]
tests = webgpu/tests
metadata = webgpu/meta
url_base = /_webgpu/

View file

@ -0,0 +1,651 @@
{
"items": {
"reftest": {
"webgpu": {
"webgpu": {
"web-platform": {
"reftests": {
"canvas_clear.html": [
"86a3da939dbe94efbeec04e5d366a40736ca4560",
[
null,
[
[
"/_webgpu/webgpu/webgpu/web-platform/reftests/ref/canvas_clear-ref.html",
"=="
]
],
{}
]
],
"canvas_complex_bgra8unorm.html": [
"1310543648e4fd640b0deb227000731af7b88b00",
[
null,
[
[
"/_webgpu/webgpu/webgpu/web-platform/reftests/ref/canvas_complex-ref.html",
"=="
]
],
{}
]
]
}
}
}
}
},
"support": {
"LICENSE.txt": [
"c7a75d7d221561df02c42470ad5e751324de645d",
[]
],
"README.md": [
"ef0dbfee60ce3e787131ca40e5be64952e578e03",
[]
],
"webgpu": {
"common": {
"framework": {
"file_loader.js": [
"b6f3d50859515c47f8a9bdf2f2a9c6158fe104e0",
[]
],
"fixture.js": [
"191e2fc42d98ad2c2dce06adf7414e6d0c1e042d",
[]
],
"glsl.js": [
"7571416d886c2061245e262fcd06c45534472232",
[]
],
"gpu": {
"device_pool.js": [
"c6be73f9918be979816bf02978e8429ce8b4d63d",
[]
],
"implementation.js": [
"ab118a45444d120e521fbbd3e79d326b0a43beff",
[]
]
},
"logging": {
"log_message.js": [
"8c1007fa822ddfc2a894540ad9ed041db298b972",
[]
],
"logger.js": [
"c4e65c746f04e3dfcb8ef2ed4053bef10250f958",
[]
],
"result.js": [
"d387dc3e340863c31f6f92d15083544021f7e4c7",
[]
],
"test_case_recorder.js": [
"37acb53143c22e9859eb94e1d059916b52274941",
[]
]
},
"params_builder.js": [
"ab548de07844b48af7a54e08cd0dab676f2f5ba6",
[]
],
"params_utils.js": [
"a95d01b9c8058076af5cf49d4ab82b5c74367a5b",
[]
],
"query": {
"compare.js": [
"782088752f9cf68d4b7528cd42c72bc084380be3",
[]
],
"encode_selectively.js": [
"c621808e284ae6315e921b96f75f3cae10d78eb0",
[]
],
"parseQuery.js": [
"5a438c2bbb0beed622c030cc6748bd5c394ea2dd",
[]
],
"query.js": [
"0a9742055eb29f987958c1cc4d66a1cf4910f74b",
[]
],
"separators.js": [
"8fbaebdf945aaf95301b4d4e213659c152e1b6bc",
[]
],
"stringify_params.js": [
"931c5534f6172c801b39d9e24c05e4ef4e8d8938",
[]
],
"validQueryPart.js": [
"ca21627975fc498f2bff550c9a4e9d064c23af38",
[]
]
},
"test_group.js": [
"4515bca19b70a701b2c32f2894b61b330d8e95ba",
[]
],
"test_suite_listing.js": [
"d387dc3e340863c31f6f92d15083544021f7e4c7",
[]
],
"tree.js": [
"87ff0d227e169c75255e0738af90946716f20134",
[]
],
"util": {
"async_mutex.js": [
"da461048fc495b118def36bff846e02be4268346",
[]
],
"collect_garbage.js": [
"d18982a2758e46634995ce7ac692918a0620a800",
[]
],
"stack.js": [
"00d08642d1534ea678f101c6d1627c5265df76bc",
[]
],
"timeout.js": [
"db1e5cfa1ae71e8c7fded6655b388441f4f851da",
[]
],
"util.js": [
"e7479046bee2e30f1116c1ab9f7d1e19ea6d82b4",
[]
]
},
"version.js": [
"de50e581c26d8d5f2b6e91da39571cd8b21f1591",
[]
]
},
"runtime": {
"helper": {
"options.js": [
"e70671a56319af57068da4c201c4868822da4564",
[]
],
"test_worker-worker.js": [
"2402c6c3ab7b978e1e093cc20be9c8cef75b9ca8",
[]
],
"test_worker.js": [
"f844920b50dc80340430cf0f42e54369d557a6ca",
[]
]
},
"wpt.js": [
"5749159538dc7c08dd98f6b966054e2695d77437",
[]
]
}
},
"third_party": {
"glslang_js": {
"lib": {
"glslang.js": [
"21bd32eafa285c95c87c14056e1e0f253549dedd",
[]
],
"glslang.wasm": [
"d951d65e063a930ad0d841848367f7b8cc6b2703",
[]
]
}
}
},
"webgpu": {
"api": {
"operation": {
"buffers": {
"create_mapped.spec.js": [
"0f950439b9d272d36b93bd76208af9461105f9fd",
[]
],
"map.spec.js": [
"19e1a2cc7c5c54a2e37c85dad35cc4fcfb8ac321",
[]
],
"map_detach.spec.js": [
"61f2d7aa9fbadaa6b6d2810314ec6b6c20f3f99d",
[]
],
"map_oom.spec.js": [
"f80fa767c08c5642c1add2612ac9895b16d1de19",
[]
],
"mapping_test.js": [
"ad2be711d40a44f2b64a5f5c27ab5d32afee24a4",
[]
]
},
"command_buffer": {
"basic.spec.js": [
"7f4dd29b24ff8280b7aaede344b42bb5edca4d19",
[]
],
"compute": {
"basic.spec.js": [
"865601f869bac456d5de6678c75bc21fa8b3c722",
[]
]
},
"copies.spec.js": [
"29d0bd58ee9c9cfdc8e244a7fbef0585c564b7f8",
[]
],
"render": {
"basic.spec.js": [
"d3913ef5752b277c62e4f451923231c6d55ff947",
[]
],
"rendering.spec.js": [
"60e843a6973fa1b84e6eddf9c5a2308d1f848141",
[]
],
"storeop.spec.js": [
"cd0c9d6145e11e0046f8ea6bb001f9518c972601",
[]
]
}
},
"fences.spec.js": [
"98f913008b8af33e1dc866f5714388d4ec9e050d",
[]
],
"render_pass": {
"storeOp.spec.js": [
"678a462925021a32db5d33baa33701259bcf7d7c",
[]
]
},
"render_pipeline": {
"culling_tests.spec.js": [
"a10a987520d089208b5aa49f3c329533188b379c",
[]
]
},
"resource_init": {
"copied_texture_clear.spec.js": [
"28fb5b368e317ef68ba6a3ec854729c3ca8cfb0e",
[]
],
"depth_stencil_attachment_clear.spec.js": [
"06004eadf3486eb59ccdc0588a7982ea3275d68c",
[]
],
"sampled_texture_clear.spec.js": [
"5ee102a37ffcda076fc001a4e15db176b07fe4d3",
[]
],
"texture_zero_init_test.js": [
"e3c21011bb288492bd137be9e2e43b5efd02aa36",
[]
]
}
},
"validation": {
"createBindGroup.spec.js": [
"fa585038e39ff6208787ee89bc177bdb13cdc385",
[]
],
"createBindGroupLayout.spec.js": [
"778ca6fb1b71e7f611722e0d90ce816bb062b90e",
[]
],
"createPipelineLayout.spec.js": [
"689ddaec883cd639f6cf94ab4d3005a4c4512db5",
[]
],
"createRenderPipeline.spec.js": [
"4a72faacd6c09555c3c1dac8424513f7b33a8c64",
[]
],
"createTexture.spec.js": [
"a9c7a8be01e03bcda1aa2f96374c30c0ce0bbe45",
[]
],
"createView.spec.js": [
"d5b16563c13d9429bb1da58b9c5dd18279904f3d",
[]
],
"encoding": {
"cmds": {
"index_access.spec.js": [
"b0eb5c7bc6cfcdf4479a6365b8b54c362424a696",
[]
]
}
},
"error_scope.spec.js": [
"5523237daeba5d0c1c292dd3da8dead86a99f8e4",
[]
],
"fences.spec.js": [
"e2d4c7eb92220c98411c7e8f72be718c261a78b9",
[]
],
"queue_submit.spec.js": [
"2860a4f53188e1cc02774f0635c70962ecf9e52e",
[]
],
"render_pass": {
"storeOp.spec.js": [
"c43c6547bc8f268af93031db7173185768f6d2b3",
[]
]
},
"render_pass.spec.js": [
"b9db03bc86f19fd1ad5c847153cfcc6399f0450b",
[]
],
"render_pass_descriptor.spec.js": [
"6b2b8c54b784620b65d213b9cddf4b803fbeba3a",
[]
],
"setBindGroup.spec.js": [
"fca85a83b0e5cf94d7950139c021ca6954e0e2ad",
[]
],
"setBlendColor.spec.js": [
"8a302fffd9807b5bf8ae1b6540f9af1b7c4425b8",
[]
],
"setScissorRect.spec.js": [
"7934c007286c8c4e9702eaaadb14b288a78e8fb1",
[]
],
"setStencilReference.spec.js": [
"f79a24044952c705004d254c81c31fd60967c54e",
[]
],
"setVertexBuffer.spec.js": [
"a7a173991eb3ff786af945af19e0e35ac56fc624",
[]
],
"setViewport.spec.js": [
"e2ef691b821825b9fa3c9fa5072dc734ec34d236",
[]
],
"validation_test.js": [
"d816fd0594bd5097f4a7e3d2de91c3e0aa6e3f7b",
[]
],
"vertex_state.spec.js": [
"7a30e3a013aeada0469fa8d85c69c14c26921dbb",
[]
]
}
},
"capability_info.js": [
"df73575693bdf29f824ce6aeb144ec062aa2fb36",
[]
],
"examples.spec.js": [
"0d741fec3c2917808a885fa9a76a0300003c5382",
[]
],
"gpu_test.js": [
"a0163bb3ef38fe73f75b673f1507d92cc280b032",
[]
],
"idl": {
"constants": {
"flags.spec.js": [
"1de75c84e5daeaafd3ac47b31b6011223b03e360",
[]
]
},
"idl_test.js": [
"231fc0945f87fe6aede03ec7a03c1ecaf7fe04db",
[]
]
},
"listing.js": [
"51bba64a493acad8c8f0bbc515fb621ef538b839",
[]
],
"shader": {
"execution": {
"robust_access.spec.js": [
"0fe4a36ad10ee86a945a0546daac568882884fda",
[]
],
"robust_access_vertex.spec.js": [
"05b83fd049c98e4902096b467cf9f0aaa3fb4d51",
[]
]
}
},
"util": {
"conversion.js": [
"bdba99cf7619ec4d66a5890f5566f68bbc6ed82d",
[]
],
"math.js": [
"a76f1a00158ab50df82c74187de859dfbef41d60",
[]
],
"texture": {
"layout.js": [
"cb9a883bd805e23b0c1769e78844fafd8717585f",
[]
],
"subresource.js": [
"889fab16e61f6b811d7475a4b5eb7c56353a8a91",
[]
],
"texelData.js": [
"8a827c47d21e1c1a89e081b2e70d1759c9208b0e",
[]
]
}
},
"web-platform": {
"canvas": {
"context_creation.spec.js": [
"26dc5d8ab6787c628c820f42e43e33603e4cebff",
[]
]
},
"copyImageBitmapToTexture.spec.js": [
"b37e311d280f2d3cea3d85767075cc51f3f2a124",
[]
],
"reftests": {
"canvas_clear.js": [
"a9149f039424546a9822d090b979fef921fe79c1",
[]
],
"canvas_complex.js": [
"e1aa3a25f88d26abdd381db249b03a6e84421fd5",
[]
],
"gpu_ref_test.js": [
"dd58b543b01ec86c1a820d9d661a5c3f9b68b323",
[]
],
"ref": {
"canvas_clear-ref.html": [
"2e0781186273ac49d6c70b5e5a9c68103aa5f173",
[]
],
"canvas_complex-ref.html": [
"3d5b3b3376d23cfdcda57f0c53fc3192e8a77bb6",
[]
]
}
}
}
}
}
},
"testharness": {
"webgpu": {
"cts.html": [
"0e637156fa95e5039db18c62136f73595b92c087",
[
"webgpu/cts.html?q=webgpu:api,operation,buffers,create_mapped:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,buffers,map:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,buffers,map_detach:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,buffers,map_oom:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,command_buffer,basic:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,command_buffer,compute,basic:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,command_buffer,copies:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,command_buffer,render,basic:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,command_buffer,render,rendering:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,command_buffer,render,storeop:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,fences:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,render_pass,storeOp:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,render_pipeline,culling_tests:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,resource_init,copied_texture_clear:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,resource_init,depth_stencil_attachment_clear:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,operation,resource_init,sampled_texture_clear:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,createBindGroup:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,createPipelineLayout:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,createTexture:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,encoding,cmds,index_access:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,error_scope:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,fences:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,queue_submit:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,render_pass,storeOp:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,render_pass:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,render_pass_descriptor:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,setBindGroup:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,setBlendColor:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,setScissorRect:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,setStencilReference:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,setVertexBuffer:*",
{}
],
[
"webgpu/cts.html?q=webgpu:api,validation,setViewport:*",
{}
],
[
"webgpu/cts.html?q=webgpu:examples:*",
{}
],
[
"webgpu/cts.html?q=webgpu:idl,constants,flags:*",
{}
],
[
"webgpu/cts.html?q=webgpu:shader,execution,robust_access:*",
{}
],
[
"webgpu/cts.html?q=webgpu:shader,execution,robust_access_vertex:*",
{}
],
[
"webgpu/cts.html?q=webgpu:web-platform,canvas,context_creation:*",
{}
],
[
"webgpu/cts.html?q=webgpu:web-platform,copyImageBitmapToTexture:*",
{}
]
]
}
}
},
"url_base": "/_webgpu/",
"version": 8
}

View file

@ -0,0 +1 @@
prefs: ["dom.webgpu.enabled:true"]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
[canvas_clear.html]
expected: FAIL

View file

@ -0,0 +1,2 @@
[canvas_complex_bgra8unorm.html]
expected: TIMEOUT

View file

@ -0,0 +1,26 @@
Copyright 2019 WebGPU CTS Contributors
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,10 @@
# WebGPU Conformance Test Suite
The WebGPU CTS requires support for the WebGPU API. This requires both browser
support and hardware support, so this API cannot run on most automated testing
infrastructure. Tests inside this directory should always be skipped if
appropriate GPU hardware is not available.
The contents of this directory are automatically generated from TypeScript
sources which live upstream in the [WebGPU CTS](https://github.com/gpuweb/cts).
They are periodically built and pushed to WPT.

View file

@ -0,0 +1,39 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { parseQuery } from './query/parseQuery.js';
import { loadTreeForQuery } from './tree.js';
// A listing file, e.g. either of:
// - `src/webgpu/listing.ts` (which is dynamically computed, has a Promise<TestSuiteListing>)
// - `out/webgpu/listing.js` (which is pre-baked, has a TestSuiteListing)
// Base class for DefaultTestFileLoader and FakeTestFileLoader.
export class TestFileLoader {
importSpecFile(suite, path) {
return this.import(`${suite}/${path.join('/')}.spec.js`);
}
async loadTree(query, subqueriesToExpand = []) {
return loadTreeForQuery(
this,
query,
subqueriesToExpand.map(q => parseQuery(q))
);
}
async loadCases(query) {
const tree = await this.loadTree(query);
return tree.iterateLeaves();
}
}
export class DefaultTestFileLoader extends TestFileLoader {
async listing(suite) {
return (await import(`../../${suite}/listing.js`)).listing;
}
import(path) {
return import(`../../${path}`);
}
}

View file

@ -0,0 +1,124 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { assert } from './util/util.js';
export class SkipTestCase extends Error {}
// A Fixture is a class used to instantiate each test case at run time.
// A new instance of the Fixture is created for every single test case
// (i.e. every time the test function is run).
export class Fixture {
constructor(rec, params) {
_defineProperty(this, 'params', void 0);
_defineProperty(this, 'rec', void 0);
_defineProperty(this, 'eventualExpectations', []);
_defineProperty(this, 'numOutstandingAsyncExpectations', 0);
this.rec = rec;
this.params = params;
}
// This has to be a member function instead of an async `createFixture` function, because
// we need to be able to ergonomically override it in subclasses.
async init() {}
debug(msg) {
this.rec.debug(new Error(msg));
}
skip(msg) {
throw new SkipTestCase(msg);
}
async finalize() {
assert(
this.numOutstandingAsyncExpectations === 0,
'there were outstanding asynchronous expectations (e.g. shouldReject) at the end of the test'
);
await Promise.all(this.eventualExpectations);
}
warn(msg) {
this.rec.warn(new Error(msg));
}
fail(msg) {
this.rec.expectationFailed(new Error(msg));
}
async immediateAsyncExpectation(fn) {
this.numOutstandingAsyncExpectations++;
const ret = await fn();
this.numOutstandingAsyncExpectations--;
return ret;
}
eventualAsyncExpectation(fn) {
const promise = fn(new Error());
this.eventualExpectations.push(promise);
return promise;
}
expectErrorValue(expectedName, ex, niceStack) {
if (!(ex instanceof Error)) {
niceStack.message = `THREW non-error value, of type ${typeof ex}: ${ex}`;
this.rec.expectationFailed(niceStack);
return;
}
const actualName = ex.name;
if (actualName !== expectedName) {
niceStack.message = `THREW ${actualName}, instead of ${expectedName}: ${ex}`;
this.rec.expectationFailed(niceStack);
} else {
niceStack.message = `OK: threw ${actualName}${ex.message}`;
this.rec.debug(niceStack);
}
}
shouldReject(expectedName, p, msg) {
this.eventualAsyncExpectation(async niceStack => {
const m = msg ? ': ' + msg : '';
try {
await p;
niceStack.message = 'DID NOT REJECT' + m;
this.rec.expectationFailed(niceStack);
} catch (ex) {
niceStack.message = m;
this.expectErrorValue(expectedName, ex, niceStack);
}
});
}
shouldThrow(expectedName, fn, msg) {
const m = msg ? ': ' + msg : '';
try {
fn();
this.rec.expectationFailed(new Error('DID NOT THROW' + m));
} catch (ex) {
this.expectErrorValue(expectedName, ex, new Error(m));
}
}
expect(cond, msg) {
if (cond) {
const m = msg ? ': ' + msg : '';
this.rec.debug(new Error('expect OK' + m));
} else {
this.rec.expectationFailed(new Error(msg));
}
return cond;
}
}

View file

@ -0,0 +1,33 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { assert, unreachable } from './util/util.js';
let glslangAttempted = false;
let glslangInstance;
export async function initGLSL() {
if (glslangAttempted) {
assert(glslangInstance !== undefined, 'glslang is not available');
} else {
glslangAttempted = true;
const glslangPath = '../../third_party/glslang_js/lib/glslang.js';
let glslangModule;
try {
glslangModule = (await import(glslangPath)).default;
} catch (ex) {
unreachable('glslang is not available');
}
const glslang = await glslangModule();
glslangInstance = glslang;
}
}
export function compileGLSL(glsl, shaderType, genDebug, spirvVersion) {
assert(
glslangInstance !== undefined,
'GLSL compiler is not instantiated. Run `await initGLSL()` first'
);
return glslangInstance.compileGLSL(glsl.trimLeft(), shaderType, genDebug, spirvVersion);
}

View file

@ -0,0 +1,152 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { assert, raceWithRejectOnTimeout, unreachable, assertReject } from '../util/util.js';
import { getGPU } from './implementation.js';
class TestFailedButDeviceReusable extends Error {}
export class TestOOMedShouldAttemptGC extends Error {}
const kPopErrorScopeTimeoutMS = 5000;
export class DevicePool {
constructor() {
_defineProperty(this, 'failed', false);
_defineProperty(this, 'holder', undefined);
} // undefined if "uninitialized" (not yet initialized, or lost)
async acquire() {
assert(!this.failed, 'WebGPU device previously failed to initialize; not retrying');
if (this.holder === undefined) {
try {
this.holder = await DevicePool.makeHolder();
} catch (ex) {
this.failed = true;
throw ex;
}
}
assert(!this.holder.acquired, 'Device was in use on DevicePool.acquire');
this.holder.acquired = true;
this.beginErrorScopes();
return this.holder.device;
}
// When a test is done using a device, it's released back into the pool.
// This waits for error scopes, checks their results, and checks for various error conditions.
async release(device) {
const holder = this.holder;
assert(holder !== undefined, 'trying to release a device while pool is uninitialized');
assert(holder.acquired, 'trying to release a device while already released');
assert(device === holder.device, 'Released device was the wrong device');
try {
// Time out if popErrorScope never completes. This could happen due to a browser bug - e.g.,
// as of this writing, on Chrome GPU process crash, popErrorScope just hangs.
await raceWithRejectOnTimeout(
this.endErrorScopes(),
kPopErrorScopeTimeoutMS,
'finalization popErrorScope timed out'
);
// (Hopefully if the device was lost, it has been reported by the time endErrorScopes()
// has finished (or timed out). If not, it could cause a finite number of extra test
// failures following this one (but should recover eventually).)
const lostReason = holder.lostReason;
if (lostReason !== undefined) {
// Fail the current test.
unreachable(`Device was lost: ${lostReason}`);
}
} catch (ex) {
// Any error that isn't explicitly TestFailedButDeviceReusable forces a new device to be
// created for the next test.
if (!(ex instanceof TestFailedButDeviceReusable)) {
this.holder = undefined;
}
throw ex;
} finally {
// TODO: device.destroy()
// Mark the holder as free. (This only has an effect if the pool still has the holder.)
// This could be done at the top but is done here to guard against async-races during release.
holder.acquired = false;
}
}
// Gets a device and creates a DeviceHolder.
// If the device is lost, DeviceHolder.lostReason gets set.
static async makeHolder() {
const gpu = getGPU();
const adapter = await gpu.requestAdapter();
const holder = {
acquired: false,
device: await adapter.requestDevice(),
lostReason: undefined,
};
holder.device.lost.then(ev => {
holder.lostReason = ev.message;
});
return holder;
}
// Create error scopes that wrap the entire test.
beginErrorScopes() {
assert(this.holder !== undefined);
this.holder.device.pushErrorScope('out-of-memory');
this.holder.device.pushErrorScope('validation');
}
// End the whole-test error scopes. Check that there are no extra error scopes, and that no
// otherwise-uncaptured errors occurred during the test.
async endErrorScopes() {
assert(this.holder !== undefined);
let gpuValidationError;
let gpuOutOfMemoryError;
try {
// May reject if the device was lost.
gpuValidationError = await this.holder.device.popErrorScope();
gpuOutOfMemoryError = await this.holder.device.popErrorScope();
} catch (ex) {
assert(
this.holder.lostReason !== undefined,
"popErrorScope failed, but device.lost hasn't fired (yet)"
);
throw ex;
}
await assertReject(
this.holder.device.popErrorScope(),
'There was an extra error scope on the stack after a test'
);
if (gpuValidationError !== null) {
assert(gpuValidationError instanceof GPUValidationError);
// Allow the device to be reused.
throw new TestFailedButDeviceReusable(
`Unexpected validation error occurred: ${gpuValidationError.message}`
);
}
if (gpuOutOfMemoryError !== null) {
assert(gpuOutOfMemoryError instanceof GPUOutOfMemoryError);
// Don't allow the device to be reused; unexpected OOM could break the device.
throw new TestOOMedShouldAttemptGC('Unexpected out-of-memory error occurred');
}
}
}

View file

@ -0,0 +1,19 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ /// <reference types="@webgpu/types" />
import { assert } from '../util/util.js';
let impl = undefined;
export function getGPU() {
if (impl) {
return impl;
}
assert(
typeof navigator !== 'undefined' && navigator.gpu !== undefined,
'No WebGPU implementation found'
);
impl = navigator.gpu;
return impl;
}

View file

@ -0,0 +1,48 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { extractImportantStackTrace } from '../util/stack.js';
export class LogMessageWithStack extends Error {
constructor(name, ex) {
super(ex.message);
_defineProperty(this, 'stackHidden', false);
_defineProperty(this, 'timesSeen', 1);
this.name = name;
this.stack = ex.stack;
}
/** Set a flag so the stack is not printed in toJSON(). */
setStackHidden() {
this.stackHidden = true;
}
/** Increment the "seen x times" counter. */
incrementTimesSeen() {
this.timesSeen++;
}
toJSON() {
let m = this.name;
if (this.message) m += ': ' + this.message;
if (!this.stackHidden && this.stack) {
m += '\n' + extractImportantStackTrace(this);
}
if (this.timesSeen > 1) {
m += `\n(seen ${this.timesSeen} times with identical stack)`;
}
return m;
}
}

View file

@ -0,0 +1,35 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { version } from '../version.js';
import { TestCaseRecorder } from './test_case_recorder.js';
export class Logger {
constructor(debug) {
_defineProperty(this, 'debug', void 0);
_defineProperty(this, 'results', new Map());
this.debug = debug;
}
record(name) {
const result = { status: 'running', timems: -1 };
this.results.set(name, result);
return [new TestCaseRecorder(result, this.debug), result];
}
asJSON(space) {
return JSON.stringify({ version, results: Array.from(this.results) }, undefined, space);
}
}

View file

@ -0,0 +1,3 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/

View file

@ -0,0 +1,139 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { SkipTestCase } from '../fixture.js';
import { now, assert } from '../util/util.js';
import { LogMessageWithStack } from './log_message.js';
var LogSeverity;
(function (LogSeverity) {
LogSeverity[(LogSeverity['Pass'] = 0)] = 'Pass';
LogSeverity[(LogSeverity['Skip'] = 1)] = 'Skip';
LogSeverity[(LogSeverity['Warn'] = 2)] = 'Warn';
LogSeverity[(LogSeverity['ExpectFailed'] = 3)] = 'ExpectFailed';
LogSeverity[(LogSeverity['ValidationFailed'] = 4)] = 'ValidationFailed';
LogSeverity[(LogSeverity['ThrewException'] = 5)] = 'ThrewException';
})(LogSeverity || (LogSeverity = {}));
const kMaxLogStacks = 2;
/** Holds onto a LiveTestCaseResult owned by the Logger, and writes the results into it. */
export class TestCaseRecorder {
/** Used to dedup log messages which have identical stacks. */
constructor(result, debugging) {
_defineProperty(this, 'result', void 0);
_defineProperty(this, 'maxLogSeverity', LogSeverity.Pass);
_defineProperty(this, 'startTime', -1);
_defineProperty(this, 'logs', []);
_defineProperty(this, 'logLinesAtCurrentSeverity', 0);
_defineProperty(this, 'debugging', false);
_defineProperty(this, 'messagesForPreviouslySeenStacks', new Map());
this.result = result;
this.debugging = debugging;
}
start() {
assert(this.startTime < 0, 'TestCaseRecorder cannot be reused');
this.startTime = now();
}
finish() {
assert(this.startTime >= 0, 'finish() before start()');
const timeMilliseconds = now() - this.startTime;
// Round to next microsecond to avoid storing useless .xxxx00000000000002 in results.
this.result.timems = Math.ceil(timeMilliseconds * 1000) / 1000;
// Convert numeric enum back to string (but expose 'exception' as 'fail')
this.result.status =
this.maxLogSeverity === LogSeverity.Pass
? 'pass'
: this.maxLogSeverity === LogSeverity.Skip
? 'skip'
: this.maxLogSeverity === LogSeverity.Warn
? 'warn'
: 'fail'; // Everything else is an error
this.result.logs = this.logs;
}
injectResult(injectedResult) {
Object.assign(this.result, injectedResult);
}
debug(ex) {
if (!this.debugging) {
return;
}
const logMessage = new LogMessageWithStack('DEBUG', ex);
logMessage.setStackHidden();
this.logImpl(LogSeverity.Pass, logMessage);
}
skipped(ex) {
this.logImpl(LogSeverity.Skip, new LogMessageWithStack('SKIP', ex));
}
warn(ex) {
this.logImpl(LogSeverity.Warn, new LogMessageWithStack('WARN', ex));
}
expectationFailed(ex) {
this.logImpl(LogSeverity.ExpectFailed, new LogMessageWithStack('EXPECTATION FAILED', ex));
}
validationFailed(ex) {
this.logImpl(LogSeverity.ValidationFailed, new LogMessageWithStack('VALIDATION FAILED', ex));
}
threw(ex) {
if (ex instanceof SkipTestCase) {
this.skipped(ex);
return;
}
this.logImpl(LogSeverity.ThrewException, new LogMessageWithStack('EXCEPTION', ex));
}
logImpl(level, logMessage) {
// Deduplicate errors with the exact same stack
if (logMessage.stack) {
const seen = this.messagesForPreviouslySeenStacks.get(logMessage.stack);
if (seen) {
seen.incrementTimesSeen();
return;
}
this.messagesForPreviouslySeenStacks.set(logMessage.stack, logMessage);
}
// Mark printStack=false for all logs except 2 at the highest severity
if (level > this.maxLogSeverity) {
this.logLinesAtCurrentSeverity = 0;
this.maxLogSeverity = level;
if (!this.debugging) {
// Go back and turn off printStack for everything of a lower log level
for (const log of this.logs) {
log.setStackHidden();
}
}
}
if (level < this.maxLogSeverity || this.logLinesAtCurrentSeverity >= kMaxLogStacks) {
if (!this.debugging) {
logMessage.setStackHidden();
}
}
this.logs.push(logMessage);
this.logLinesAtCurrentSeverity++;
}
}

View file

@ -0,0 +1,117 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ let _Symbol$iterator;
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { publicParamsEquals } from './params_utils.js';
import { assert } from './util/util.js';
// https://stackoverflow.com/a/56375136
export function poptions(name, values) {
const iter = makeReusableIterable(function* () {
for (const value of values) {
yield { [name]: value };
}
});
return iter;
}
export function pbool(name) {
return poptions(name, [false, true]);
}
export function params() {
return new ParamsBuilder();
}
_Symbol$iterator = Symbol.iterator;
export class ParamsBuilder {
constructor() {
_defineProperty(this, 'paramSpecs', [{}]);
}
[_Symbol$iterator]() {
const iter = this.paramSpecs[Symbol.iterator]();
return iter;
}
combine(newParams) {
const paramSpecs = this.paramSpecs;
this.paramSpecs = makeReusableIterable(function* () {
for (const a of paramSpecs) {
for (const b of newParams) {
yield mergeParams(a, b);
}
}
});
return this;
}
expand(expander) {
const paramSpecs = this.paramSpecs;
this.paramSpecs = makeReusableIterable(function* () {
for (const a of paramSpecs) {
for (const b of expander(a)) {
yield mergeParams(a, b);
}
}
});
return this;
}
filter(pred) {
const paramSpecs = this.paramSpecs;
this.paramSpecs = makeReusableIterable(function* () {
for (const p of paramSpecs) {
if (pred(p)) {
yield p;
}
}
});
return this;
}
unless(pred) {
return this.filter(x => !pred(x));
}
exclude(exclude) {
const excludeArray = Array.from(exclude);
const paramSpecs = this.paramSpecs;
this.paramSpecs = makeReusableIterable(function* () {
for (const p of paramSpecs) {
if (excludeArray.every(e => !publicParamsEquals(p, e))) {
yield p;
}
}
});
return this;
}
}
// If you create an Iterable by calling a generator function (e.g. in IIFE), it is exhausted after
// one use. This just wraps a generator function in an object so it be iterated multiple times.
function makeReusableIterable(generatorFn) {
return { [Symbol.iterator]: generatorFn };
}
// (keyof A & keyof B) is not empty, so they overlapped
function mergeParams(a, b) {
for (const key of Object.keys(a)) {
assert(!(key in b), 'Duplicate key: ' + key);
}
return { ...a, ...b };
}

View file

@ -0,0 +1,27 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { comparePublicParamsPaths, Ordering } from './query/compare.js';
import { kWildcard, kParamSeparator, kParamKVSeparator } from './query/separators.js';
// Consider adding more types here if needed
export function paramKeyIsPublic(key) {
return !key.startsWith('_');
}
export function extractPublicParams(params) {
const publicParams = {};
for (const k of Object.keys(params)) {
if (paramKeyIsPublic(k)) {
publicParams[k] = params[k];
}
}
return publicParams;
}
export const badParamValueChars = new RegExp(
'[' + kParamKVSeparator + kParamSeparator + kWildcard + ']'
);
export function publicParamsEquals(x, y) {
return comparePublicParamsPaths(x, y) === Ordering.Equal;
}

View file

@ -0,0 +1,93 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { paramKeyIsPublic } from '../params_utils.js';
import { assert, objectEquals } from '../util/util.js';
export let Ordering;
/**
* Compares two queries for their ordering (which is used to build the tree).
*
* See src/unittests/query_compare.spec.ts for examples.
*/ (function (Ordering) {
Ordering[(Ordering['Unordered'] = 0)] = 'Unordered';
Ordering[(Ordering['StrictSuperset'] = 1)] = 'StrictSuperset';
Ordering[(Ordering['Equal'] = 2)] = 'Equal';
Ordering[(Ordering['StrictSubset'] = 3)] = 'StrictSubset';
})(Ordering || (Ordering = {}));
export function compareQueries(a, b) {
if (a.suite !== b.suite) {
return Ordering.Unordered;
}
const filePathOrdering = comparePaths(a.filePathParts, b.filePathParts);
if (filePathOrdering !== Ordering.Equal || a.isMultiFile || b.isMultiFile) {
return compareOneLevel(filePathOrdering, a.isMultiFile, b.isMultiFile);
}
assert('testPathParts' in a && 'testPathParts' in b);
const testPathOrdering = comparePaths(a.testPathParts, b.testPathParts);
if (testPathOrdering !== Ordering.Equal || a.isMultiTest || b.isMultiTest) {
return compareOneLevel(testPathOrdering, a.isMultiTest, b.isMultiTest);
}
assert('params' in a && 'params' in b);
const paramsPathOrdering = comparePublicParamsPaths(a.params, b.params);
if (paramsPathOrdering !== Ordering.Equal || a.isMultiCase || b.isMultiCase) {
return compareOneLevel(paramsPathOrdering, a.isMultiCase, b.isMultiCase);
}
return Ordering.Equal;
}
/**
* Compares a single level of a query.
*
* "IsBig" means the query is big relative to the level, e.g. for test-level:
* - Anything >= `suite:a,*` is big
* - Anything <= `suite:a:*` is small
*/
function compareOneLevel(ordering, aIsBig, bIsBig) {
assert(ordering !== Ordering.Equal || aIsBig || bIsBig);
if (ordering === Ordering.Unordered) return Ordering.Unordered;
if (aIsBig && bIsBig) return ordering;
if (!aIsBig && !bIsBig) return Ordering.Unordered; // Equal case is already handled
// Exactly one of (a, b) is big.
if (aIsBig && ordering !== Ordering.StrictSubset) return Ordering.StrictSuperset;
if (bIsBig && ordering !== Ordering.StrictSuperset) return Ordering.StrictSubset;
return Ordering.Unordered;
}
function comparePaths(a, b) {
const shorter = Math.min(a.length, b.length);
for (let i = 0; i < shorter; ++i) {
if (a[i] !== b[i]) {
return Ordering.Unordered;
}
}
if (a.length === b.length) {
return Ordering.Equal;
} else if (a.length < b.length) {
return Ordering.StrictSuperset;
} else {
return Ordering.StrictSubset;
}
}
export function comparePublicParamsPaths(a, b) {
const aKeys = Object.keys(a).filter(k => paramKeyIsPublic(k));
const commonKeys = new Set(aKeys.filter(k => k in b));
for (const k of commonKeys) {
if (!objectEquals(a[k], b[k])) {
return Ordering.Unordered;
}
}
const bKeys = Object.keys(b).filter(k => paramKeyIsPublic(k));
const aRemainingKeys = aKeys.length - commonKeys.size;
const bRemainingKeys = bKeys.length - commonKeys.size;
if (aRemainingKeys === 0 && bRemainingKeys === 0) return Ordering.Equal;
if (aRemainingKeys === 0) return Ordering.StrictSuperset;
if (bRemainingKeys === 0) return Ordering.StrictSubset;
return Ordering.Unordered;
}

View file

@ -0,0 +1,21 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ /**
* Encodes a stringified TestQuery so that it can be placed in a `?q=` parameter in a URL.
*
* `encodeURIComponent` encodes in accordance with `application/x-www-form-urlencoded`,
* but URLs don't actually have to be as strict as HTML form encoding
* (we interpret this purely from JavaScript).
* So we encode the component, then selectively convert some %-encoded escape codes
* back to their original form for readability/copyability.
*/ export function encodeURIComponentSelectively(s) {
let ret = encodeURIComponent(s);
ret = ret.replace(/%22/g, '"'); // for JSON strings
ret = ret.replace(/%2C/g, ','); // for path separator, and JSON arrays
ret = ret.replace(/%3A/g, ':'); // for big separator
ret = ret.replace(/%3B/g, ';'); // for param separator
ret = ret.replace(/%3D/g, '='); // for params (k=v)
ret = ret.replace(/%5B/g, '['); // for JSON arrays
ret = ret.replace(/%5D/g, ']'); // for JSON arrays
return ret;
}

View file

@ -0,0 +1,128 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { badParamValueChars, paramKeyIsPublic } from '../params_utils.js';
import { assert } from '../util/util.js';
import {
TestQueryMultiFile,
TestQueryMultiTest,
TestQueryMultiCase,
TestQuerySingleCase,
} from './query.js';
import { kBigSeparator, kWildcard, kPathSeparator, kParamSeparator } from './separators.js';
import { validQueryPart } from './validQueryPart.js';
export function parseQuery(s) {
try {
return parseQueryImpl(s);
} catch (ex) {
ex.message += '\n on: ' + s;
throw ex;
}
}
function parseQueryImpl(s) {
// Undo encodeURIComponentSelectively
s = decodeURIComponent(s);
// bigParts are: suite, group, test, params (note kBigSeparator could appear in params)
const [suite, fileString, testString, paramsString] = s.split(kBigSeparator, 4);
assert(fileString !== undefined, `filter string must have at least one ${kBigSeparator}`);
const { parts: file, wildcard: filePathHasWildcard } = parseBigPart(fileString, kPathSeparator);
if (testString === undefined) {
// Query is file-level
assert(
filePathHasWildcard,
`File-level query without wildcard ${kWildcard}. Did you want a file-level query \
(append ${kPathSeparator}${kWildcard}) or test-level query (append ${kBigSeparator}${kWildcard})?`
);
return new TestQueryMultiFile(suite, file);
}
assert(!filePathHasWildcard, `Wildcard ${kWildcard} must be at the end of the query string`);
const { parts: test, wildcard: testPathHasWildcard } = parseBigPart(testString, kPathSeparator);
if (paramsString === undefined) {
// Query is test-level
assert(
testPathHasWildcard,
`Test-level query without wildcard ${kWildcard}; did you want a test-level query \
(append ${kPathSeparator}${kWildcard}) or case-level query (append ${kBigSeparator}${kWildcard})?`
);
assert(file.length > 0, 'File part of test-level query was empty (::)');
return new TestQueryMultiTest(suite, file, test);
}
// Query is case-level
assert(!testPathHasWildcard, `Wildcard ${kWildcard} must be at the end of the query string`);
const { parts: paramsParts, wildcard: paramsHasWildcard } = parseBigPart(
paramsString,
kParamSeparator
);
assert(test.length > 0, 'Test part of case-level query was empty (::)');
const params = {};
for (const paramPart of paramsParts) {
const [k, v] = parseSingleParam(paramPart);
assert(validQueryPart.test(k), 'param key names must match ' + validQueryPart);
params[k] = v;
}
if (paramsHasWildcard) {
return new TestQueryMultiCase(suite, file, test, params);
} else {
return new TestQuerySingleCase(suite, file, test, params);
}
}
// webgpu:a,b,* or webgpu:a,b,c:*
const kExampleQueries = `\
webgpu${kBigSeparator}a${kPathSeparator}b${kPathSeparator}${kWildcard} or \
webgpu${kBigSeparator}a${kPathSeparator}b${kPathSeparator}c${kBigSeparator}${kWildcard}`;
function parseBigPart(s, separator) {
if (s === '') {
return { parts: [], wildcard: false };
}
const parts = s.split(separator);
let endsWithWildcard = false;
for (const [i, part] of parts.entries()) {
if (i === parts.length - 1) {
endsWithWildcard = part === kWildcard;
}
assert(
part.indexOf(kWildcard) === -1 || endsWithWildcard,
`Wildcard ${kWildcard} must be complete last part of a path (e.g. ${kExampleQueries})`
);
}
if (endsWithWildcard) {
// Remove the last element of the array (which is just the wildcard).
parts.length = parts.length - 1;
}
return { parts, wildcard: endsWithWildcard };
}
function parseSingleParam(paramSubstring) {
assert(paramSubstring !== '', 'Param in a query must not be blank (is there a trailing comma?)');
const i = paramSubstring.indexOf('=');
assert(i !== -1, 'Param in a query must be of form key=value');
const k = paramSubstring.substring(0, i);
assert(paramKeyIsPublic(k), 'Param in a query must not be private (start with _)');
const v = paramSubstring.substring(i + 1);
return [k, parseSingleParamValue(v)];
}
function parseSingleParamValue(s) {
assert(
!badParamValueChars.test(s),
`param value must not match ${badParamValueChars} - was ${s}`
);
return s === 'undefined' ? undefined : JSON.parse(s);
}

View file

@ -0,0 +1,127 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { assert } from '../util/util.js';
import { encodeURIComponentSelectively } from './encode_selectively.js';
import { kBigSeparator, kPathSeparator, kWildcard, kParamSeparator } from './separators.js';
import { stringifyPublicParams } from './stringify_params.js';
/**
* Represents a test query of some level.
*
* TestQuery types are immutable.
*/
// SingleCase
/**
* A multi-file test query, like `s:*` or `s:a,b,*`.
*
* Immutable (makes copies of constructor args).
*/
export class TestQueryMultiFile {
constructor(suite, file) {
_defineProperty(this, 'level', 1);
_defineProperty(this, 'isMultiFile', true);
_defineProperty(this, 'suite', void 0);
_defineProperty(this, 'filePathParts', void 0);
this.suite = suite;
this.filePathParts = [...file];
}
toString() {
return encodeURIComponentSelectively(this.toStringHelper().join(kBigSeparator));
}
toStringHelper() {
return [this.suite, [...this.filePathParts, kWildcard].join(kPathSeparator)];
}
}
/**
* A multi-test test query, like `s:f:*` or `s:f:a,b,*`.
*
* Immutable (makes copies of constructor args).
*/
export class TestQueryMultiTest extends TestQueryMultiFile {
constructor(suite, file, test) {
super(suite, file);
_defineProperty(this, 'level', 2);
_defineProperty(this, 'isMultiFile', false);
_defineProperty(this, 'isMultiTest', true);
_defineProperty(this, 'testPathParts', void 0);
assert(file.length > 0, 'multi-test (or finer) query must have file-path');
this.testPathParts = [...test];
}
toStringHelper() {
return [
this.suite,
this.filePathParts.join(kPathSeparator),
[...this.testPathParts, kWildcard].join(kPathSeparator),
];
}
}
/**
* A multi-case test query, like `s:f:t:*` or `s:f:t:a,b,*`.
*
* Immutable (makes copies of constructor args), except for param values
* (which aren't normally supposed to change; they're marked readonly in CaseParams).
*/
export class TestQueryMultiCase extends TestQueryMultiTest {
constructor(suite, file, test, params) {
super(suite, file, test);
_defineProperty(this, 'level', 3);
_defineProperty(this, 'isMultiTest', false);
_defineProperty(this, 'isMultiCase', true);
_defineProperty(this, 'params', void 0);
assert(test.length > 0, 'multi-case (or finer) query must have test-path');
this.params = { ...params };
}
toStringHelper() {
const paramsParts = stringifyPublicParams(this.params);
return [
this.suite,
this.filePathParts.join(kPathSeparator),
this.testPathParts.join(kPathSeparator),
[...paramsParts, kWildcard].join(kParamSeparator),
];
}
}
/**
* A multi-case test query, like `s:f:t:` or `s:f:t:a=1,b=1`.
*
* Immutable (makes copies of constructor args).
*/
export class TestQuerySingleCase extends TestQueryMultiCase {
constructor(...args) {
super(...args);
_defineProperty(this, 'level', 4);
_defineProperty(this, 'isMultiCase', false);
}
toStringHelper() {
const paramsParts = stringifyPublicParams(this.params);
return [
this.suite,
this.filePathParts.join(kPathSeparator),
this.testPathParts.join(kPathSeparator),
paramsParts.join(kParamSeparator),
];
}
}

View file

@ -0,0 +1,14 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ /** Separator between big parts: suite:file:test:case */ export const kBigSeparator = ':';
/** Separator between path,to,file or path,to,test */
export const kPathSeparator = ',';
/** Separator between k=v;k=v */
export const kParamSeparator = ';';
/** Separator between key and value in k=v */
export const kParamKVSeparator = '=';
/** Final wildcard, if query is not single-case */
export const kWildcard = '*';

View file

@ -0,0 +1,26 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { badParamValueChars, paramKeyIsPublic } from '../params_utils.js';
import { assert } from '../util/util.js';
import { kParamKVSeparator } from './separators.js';
export function stringifyPublicParams(p) {
return Object.keys(p)
.filter(k => paramKeyIsPublic(k))
.map(k => stringifySingleParam(k, p[k]));
}
export function stringifySingleParam(k, v) {
return `${k}${kParamKVSeparator}${stringifySingleParamValue(v)}`;
}
function stringifySingleParamValue(v) {
const s = v === undefined ? 'undefined' : JSON.stringify(v);
assert(
!badParamValueChars.test(s),
`JSON.stringified param value must not match ${badParamValueChars} - was ${s}`
);
return s;
}

View file

@ -0,0 +1,3 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ /** Applies to group parts, test parts, params keys. */ export const validQueryPart = /^[a-zA-Z0-9_]+$/;

View file

@ -0,0 +1,162 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { extractPublicParams, publicParamsEquals } from './params_utils.js';
import { kPathSeparator } from './query/separators.js';
import { stringifyPublicParams } from './query/stringify_params.js';
import { validQueryPart } from './query/validQueryPart.js';
import { assert } from './util/util.js';
export function makeTestGroup(fixture) {
return new TestGroup(fixture);
}
// Interface for running tests
export function makeTestGroupForUnitTesting(fixture) {
return new TestGroup(fixture);
}
class TestGroup {
constructor(fixture) {
_defineProperty(this, 'fixture', void 0);
_defineProperty(this, 'seen', new Set());
_defineProperty(this, 'tests', []);
this.fixture = fixture;
}
*iterate() {
for (const test of this.tests) {
yield* test.iterate();
}
}
checkName(name) {
assert(
// Shouldn't happen due to the rule above. Just makes sure that treated
// unencoded strings as encoded strings is OK.
name === decodeURIComponent(name),
`Not decodeURIComponent-idempotent: ${name} !== ${decodeURIComponent(name)}`
);
assert(!this.seen.has(name), `Duplicate test name: ${name}`);
this.seen.add(name);
}
// TODO: This could take a fixture, too, to override the one for the group.
test(name) {
this.checkName(name);
const parts = name.split(kPathSeparator);
for (const p of parts) {
assert(validQueryPart.test(p), `Invalid test name part ${p}; must match ${validQueryPart}`);
}
const test = new TestBuilder(parts, this.fixture);
this.tests.push(test);
return test;
}
checkCaseNamesAndDuplicates() {
for (const test of this.tests) {
test.checkCaseNamesAndDuplicates();
}
}
}
class TestBuilder {
constructor(testPath, fixture) {
_defineProperty(this, 'testPath', void 0);
_defineProperty(this, 'fixture', void 0);
_defineProperty(this, 'testFn', void 0);
_defineProperty(this, 'cases', undefined);
this.testPath = testPath;
this.fixture = fixture;
}
fn(fn) {
this.testFn = fn;
}
checkCaseNamesAndDuplicates() {
if (this.cases === undefined) {
return;
}
// This is n^2.
const seen = [];
for (const testcase of this.cases) {
// stringifyPublicParams also checks for invalid params values
const testcaseString = stringifyPublicParams(testcase);
assert(
!seen.some(x => publicParamsEquals(x, testcase)),
`Duplicate public test case params: ${testcaseString}`
);
seen.push(testcase);
}
}
params(casesIterable) {
assert(this.cases === undefined, 'test case is already parameterized');
this.cases = Array.from(casesIterable);
return this;
}
*iterate() {
assert(this.testFn !== undefined, 'No test function (.fn()) for test');
for (const params of this.cases || [{}]) {
yield new RunCaseSpecific(this.testPath, params, this.fixture, this.testFn);
}
}
}
class RunCaseSpecific {
constructor(testPath, params, fixture, fn) {
_defineProperty(this, 'id', void 0);
_defineProperty(this, 'params', void 0);
_defineProperty(this, 'fixture', void 0);
_defineProperty(this, 'fn', void 0);
this.id = { test: testPath, params: extractPublicParams(params) };
this.params = params;
this.fixture = fixture;
this.fn = fn;
}
async run(rec) {
rec.start();
try {
const inst = new this.fixture(rec, this.params || {});
try {
await inst.init();
await this.fn(inst);
} finally {
// Runs as long as constructor succeeded, even if initialization or the test failed.
await inst.finalize();
}
} catch (ex) {
// There was an exception from constructor, init, test, or finalize.
// An error from init or test may have been a SkipTestCase.
// An error from finalize may have been an eventualAsyncExpectation failure
// or unexpected validation/OOM error from the GPUDevice.
rec.threw(ex);
}
rec.finish();
}
}

View file

@ -0,0 +1,3 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/

View file

@ -0,0 +1,393 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { compareQueries, Ordering } from './query/compare.js';
import {
TestQueryMultiCase,
TestQuerySingleCase,
TestQueryMultiFile,
TestQueryMultiTest,
} from './query/query.js';
import { kBigSeparator, kWildcard, kPathSeparator, kParamSeparator } from './query/separators.js';
import { stringifySingleParam } from './query/stringify_params.js';
import { assert } from './util/util.js';
// `loadTreeForQuery()` loads a TestTree for a given queryToLoad.
// The resulting tree is a linked-list all the way from `suite:*` to queryToLoad,
// and under queryToLoad is a tree containing every case matched by queryToLoad.
//
// `subqueriesToExpand` influences the `collapsible` flag on nodes in the resulting tree.
// A node is considered "collapsible" if none of the subqueriesToExpand is a StrictSubset
// of that node.
//
// In WebKit/Blink-style web_tests, an expectation file marks individual cts.html "variants" as
// "Failure", "Crash", etc.
// By passing in the list of expectations as the subqueriesToExpand, we can programmatically
// subdivide the cts.html "variants" list to be able to implement arbitrarily-fine suppressions
// (instead of having to suppress entire test files, which would lose a lot of coverage).
//
// `iterateCollapsedQueries()` produces the list of queries for the variants list.
//
// Though somewhat complicated, this system has important benefits:
// - Avoids having to suppress entire test files, which would cause large test coverage loss.
// - Minimizes the number of page loads needed for fine-grained suppressions.
// (In the naive case, we could do one page load per test case - but the test suite would
// take impossibly long to run.)
// - Enables developers to put any number of tests in one file as appropriate, without worrying
// about expectation granularity.
export class TestTree {
constructor(root) {
_defineProperty(this, 'root', void 0);
this.root = root;
}
iterateCollapsedQueries() {
return TestTree.iterateSubtreeCollapsedQueries(this.root);
}
iterateLeaves() {
return TestTree.iterateSubtreeLeaves(this.root);
}
/**
* If a parent and its child are at different levels, then
* generally the parent has only one child, i.e.:
* a,* { a,b,* { a,b:* { ... } } }
* Collapse that down into:
* a,* { a,b:* { ... } }
* which is less needlessly verbose when displaying the tree in the standalone runner.
*/
dissolveLevelBoundaries() {
const newRoot = dissolveLevelBoundaries(this.root);
assert(newRoot === this.root);
}
toString() {
return TestTree.subtreeToString('(root)', this.root, '');
}
static *iterateSubtreeCollapsedQueries(subtree) {
for (const [, child] of subtree.children) {
if ('children' in child && !child.collapsible) {
yield* TestTree.iterateSubtreeCollapsedQueries(child);
} else {
yield child.query;
}
}
}
static *iterateSubtreeLeaves(subtree) {
for (const [, child] of subtree.children) {
if ('children' in child) {
yield* TestTree.iterateSubtreeLeaves(child);
} else {
yield child;
}
}
}
static subtreeToString(name, tree, indent) {
const collapsible = 'run' in tree ? '>' : tree.collapsible ? '+' : '-';
let s = indent + `${collapsible} ${JSON.stringify(name)} => ${tree.query}`;
if ('children' in tree) {
if (tree.description !== undefined) {
s += `\n${indent} | ${JSON.stringify(tree.description)}`;
}
for (const [name, child] of tree.children) {
s += '\n' + TestTree.subtreeToString(name, child, indent + ' ');
}
}
return s;
}
}
// TODO: Consider having subqueriesToExpand actually impact the depth-order of params in the tree.
export async function loadTreeForQuery(loader, queryToLoad, subqueriesToExpand) {
const suite = queryToLoad.suite;
const specs = await loader.listing(suite);
const subqueriesToExpandEntries = Array.from(subqueriesToExpand.entries());
const seenSubqueriesToExpand = new Array(subqueriesToExpand.length);
seenSubqueriesToExpand.fill(false);
const isCollapsible = subquery =>
subqueriesToExpandEntries.every(([i, toExpand]) => {
const ordering = compareQueries(toExpand, subquery);
// If toExpand == subquery, no expansion is needed (but it's still "seen").
if (ordering === Ordering.Equal) seenSubqueriesToExpand[i] = true;
return ordering !== Ordering.StrictSubset;
});
// L0 = suite-level, e.g. suite:*
// L1 = file-level, e.g. suite:a,b:*
// L2 = test-level, e.g. suite:a,b:c,d:*
// L3 = case-level, e.g. suite:a,b:c,d:
let foundCase = false;
// L0 is suite:*
const subtreeL0 = makeTreeForSuite(suite);
isCollapsible(subtreeL0.query); // mark seenSubqueriesToExpand
for (const entry of specs) {
if (entry.file.length === 0 && 'readme' in entry) {
// Suite-level readme.
assert(subtreeL0.description === undefined);
subtreeL0.description = entry.readme.trim();
continue;
}
{
const queryL1 = new TestQueryMultiFile(suite, entry.file);
const orderingL1 = compareQueries(queryL1, queryToLoad);
if (orderingL1 === Ordering.Unordered) {
// File path is not matched by this query.
continue;
}
}
if ('readme' in entry) {
// Entry is a README that is an ancestor or descendant of the query.
// (It's included for display in the standalone runner.)
// readmeSubtree is suite:a,b,*
// (This is always going to dedup with a file path, if there are any test spec files under
// the directory that has the README).
const readmeSubtree = addSubtreeForDirPath(subtreeL0, entry.file);
assert(readmeSubtree.description === undefined);
readmeSubtree.description = entry.readme.trim();
continue;
}
// Entry is a spec file.
const spec = await loader.importSpecFile(queryToLoad.suite, entry.file);
const description = spec.description.trim();
// subtreeL1 is suite:a,b:*
const subtreeL1 = addSubtreeForFilePath(subtreeL0, entry.file, description, isCollapsible);
// TODO: If tree generation gets too slow, avoid actually iterating the cases in a file
// if there's no need to (based on the subqueriesToExpand).
for (const t of spec.g.iterate()) {
{
const queryL3 = new TestQuerySingleCase(suite, entry.file, t.id.test, t.id.params);
const orderingL3 = compareQueries(queryL3, queryToLoad);
if (orderingL3 === Ordering.Unordered || orderingL3 === Ordering.StrictSuperset) {
// Case is not matched by this query.
continue;
}
}
// subtreeL2 is suite:a,b:c,d:*
const subtreeL2 = addSubtreeForTestPath(subtreeL1, t.id.test, isCollapsible);
// Leaf for case is suite:a,b:c,d:x=1;y=2
addLeafForCase(subtreeL2, t, isCollapsible);
foundCase = true;
}
}
for (const [i, sq] of subqueriesToExpandEntries) {
const seen = seenSubqueriesToExpand[i];
assert(
seen,
`subqueriesToExpand entry did not match anything \
(can happen due to overlap with another subquery): ${sq.toString()}`
);
}
assert(foundCase, 'Query does not match any cases');
return new TestTree(subtreeL0);
}
function makeTreeForSuite(suite) {
return {
readableRelativeName: suite + kBigSeparator,
query: new TestQueryMultiFile(suite, []),
children: new Map(),
collapsible: false,
};
}
function addSubtreeForDirPath(tree, file) {
const subqueryFile = [];
// To start, tree is suite:*
// This loop goes from that -> suite:a,* -> suite:a,b,*
for (const part of file) {
subqueryFile.push(part);
tree = getOrInsertSubtree(part, tree, () => {
const query = new TestQueryMultiFile(tree.query.suite, subqueryFile);
return { readableRelativeName: part + kPathSeparator + kWildcard, query, collapsible: false };
});
}
return tree;
}
function addSubtreeForFilePath(tree, file, description, checkCollapsible) {
// To start, tree is suite:*
// This goes from that -> suite:a,* -> suite:a,b,*
tree = addSubtreeForDirPath(tree, file);
// This goes from that -> suite:a,b:*
const subtree = getOrInsertSubtree('', tree, () => {
const query = new TestQueryMultiTest(tree.query.suite, tree.query.filePathParts, []);
assert(file.length > 0, 'file path is empty');
return {
readableRelativeName: file[file.length - 1] + kBigSeparator + kWildcard,
query,
description,
collapsible: checkCollapsible(query),
};
});
return subtree;
}
function addSubtreeForTestPath(tree, test, isCollapsible) {
const subqueryTest = [];
// To start, tree is suite:a,b:*
// This loop goes from that -> suite:a,b:c,* -> suite:a,b:c,d,*
for (const part of test) {
subqueryTest.push(part);
tree = getOrInsertSubtree(part, tree, () => {
const query = new TestQueryMultiTest(
tree.query.suite,
tree.query.filePathParts,
subqueryTest
);
return {
readableRelativeName: part + kPathSeparator + kWildcard,
query,
collapsible: isCollapsible(query),
};
});
}
// This goes from that -> suite:a,b:c,d:*
return getOrInsertSubtree('', tree, () => {
const query = new TestQueryMultiCase(
tree.query.suite,
tree.query.filePathParts,
subqueryTest,
{}
);
assert(subqueryTest.length > 0, 'subqueryTest is empty');
return {
readableRelativeName: subqueryTest[subqueryTest.length - 1] + kBigSeparator + kWildcard,
kWildcard,
query,
collapsible: isCollapsible(query),
};
});
}
function addLeafForCase(tree, t, checkCollapsible) {
const query = tree.query;
let name = '';
const subqueryParams = {};
// To start, tree is suite:a,b:c,d:*
// This loop goes from that -> suite:a,b:c,d:x=1;* -> suite:a,b:c,d:x=1;y=2;*
for (const [k, v] of Object.entries(t.id.params)) {
name = stringifySingleParam(k, v);
subqueryParams[k] = v;
tree = getOrInsertSubtree(name, tree, () => {
const subquery = new TestQueryMultiCase(
query.suite,
query.filePathParts,
query.testPathParts,
subqueryParams
);
return {
readableRelativeName: name + kParamSeparator + kWildcard,
query: subquery,
collapsible: checkCollapsible(subquery),
};
});
}
// This goes from that -> suite:a,b:c,d:x=1;y=2
const subquery = new TestQuerySingleCase(
query.suite,
query.filePathParts,
query.testPathParts,
subqueryParams
);
checkCollapsible(subquery); // mark seenSubqueriesToExpand
insertLeaf(tree, subquery, t);
}
function getOrInsertSubtree(key, parent, createSubtree) {
let v;
const child = parent.children.get(key);
if (child !== undefined) {
assert('children' in child); // Make sure cached subtree is not actually a leaf
v = child;
} else {
v = { ...createSubtree(), children: new Map() };
parent.children.set(key, v);
}
return v;
}
function insertLeaf(parent, query, t) {
const key = '';
const leaf = {
readableRelativeName: readableNameForCase(query),
query,
run: rec => t.run(rec),
};
assert(!parent.children.has(key));
parent.children.set(key, leaf);
}
function dissolveLevelBoundaries(tree) {
if ('children' in tree) {
if (tree.children.size === 1 && tree.description === undefined) {
// Loops exactly once
for (const [, child] of tree.children) {
if (child.query.level > tree.query.level) {
const newtree = dissolveLevelBoundaries(child);
return newtree;
}
}
}
for (const [k, child] of tree.children) {
const newChild = dissolveLevelBoundaries(child);
if (newChild !== child) {
tree.children.set(k, newChild);
}
}
}
return tree;
}
/** Generate a readable relative name for a case (used in standalone). */
function readableNameForCase(query) {
const paramsKeys = Object.keys(query.params);
if (paramsKeys.length === 0) {
return query.testPathParts[query.testPathParts.length - 1] + kBigSeparator;
} else {
const lastKey = paramsKeys[paramsKeys.length - 1];
return stringifySingleParam(lastKey, query.params[lastKey]);
}
}

View file

@ -0,0 +1,38 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
export class AsyncMutex {
constructor() {
_defineProperty(this, 'newestQueueItem', void 0);
}
// Run an async function with a lock on this mutex.
// Waits until the mutex is available, locks it, runs the function, then releases it.
async with(fn) {
const p = (async () => {
// If the mutex is locked, wait for the last thing in the queue before running.
// (Everything in the queue runs in order, so this is after everything currently enqueued.)
if (this.newestQueueItem) {
await this.newestQueueItem;
}
return fn();
})();
// Push the newly-created Promise onto the queue by replacing the old "newest" item.
this.newestQueueItem = p;
// And return so the caller can wait on the result.
return p;
}
}

View file

@ -0,0 +1,47 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { resolveOnTimeout } from './util.js';
export async function attemptGarbageCollection() {
const w = self;
if (w.GCController) {
w.GCController.collect();
return;
}
if (w.opera && w.opera.collect) {
w.opera.collect();
return;
}
try {
w.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.garbageCollect();
return;
} catch (e) {}
if (w.gc) {
w.gc();
return;
}
if (w.CollectGarbage) {
w.CollectGarbage();
return;
}
let i;
function gcRec(n) {
if (n < 1) return;
let temp = { i: 'ab' + i + i / 100000 };
temp = temp + 'foo';
temp; // dummy use of unused variable
gcRec(n - 1);
}
for (i = 0; i < 1000; i++) {
gcRec(10);
}
return resolveOnTimeout(35); // Let the event loop run a few frames in case it helps.
}

View file

@ -0,0 +1,78 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ // Returns the stack trace of an Error, but without the extra boilerplate at the bottom
// (e.g. RunCaseSpecific, processTicksAndRejections, etc.), for logging.
export function extractImportantStackTrace(e) {
if (!e.stack) {
return '';
}
const lines = e.stack.split('\n');
for (let i = lines.length - 1; i >= 0; --i) {
const line = lines[i];
if (line.indexOf('.spec.') !== -1) {
return lines.slice(0, i + 1).join('\n');
}
}
return e.stack;
}
// *** Examples ***
//
// Node fail()
// > Error:
// > at CaseRecorder.fail (/Users/kainino/src/cts/src/common/framework/logger.ts:99:30)
// > at RunCaseSpecific.exports.g.test.t [as fn] (/Users/kainino/src/cts/src/unittests/logger.spec.ts:80:7)
// x at RunCaseSpecific.run (/Users/kainino/src/cts/src/common/framework/test_group.ts:121:18)
// x at processTicksAndRejections (internal/process/task_queues.js:86:5)
//
// Node throw
// > Error: hello
// > at RunCaseSpecific.g.test.t [as fn] (/Users/kainino/src/cts/src/unittests/test_group.spec.ts:51:11)
// x at RunCaseSpecific.run (/Users/kainino/src/cts/src/common/framework/test_group.ts:121:18)
// x at processTicksAndRejections (internal/process/task_queues.js:86:5)
//
// Firefox fail()
// > fail@http://localhost:8080/out/framework/logger.js:104:30
// > expect@http://localhost:8080/out/framework/default_fixture.js:59:16
// > @http://localhost:8080/out/unittests/util.spec.js:35:5
// x run@http://localhost:8080/out/framework/test_group.js:119:18
//
// Firefox throw
// > @http://localhost:8080/out/unittests/test_group.spec.js:48:11
// x run@http://localhost:8080/out/framework/test_group.js:119:18
//
// Safari fail()
// > fail@http://localhost:8080/out/framework/logger.js:104:39
// > expect@http://localhost:8080/out/framework/default_fixture.js:59:20
// > http://localhost:8080/out/unittests/util.spec.js:35:11
// x http://localhost:8080/out/framework/test_group.js:119:20
// x asyncFunctionResume@[native code]
// x [native code]
// x promiseReactionJob@[native code]
//
// Safari throw
// > http://localhost:8080/out/unittests/test_group.spec.js:48:20
// x http://localhost:8080/out/framework/test_group.js:119:20
// x asyncFunctionResume@[native code]
// x [native code]
// x promiseReactionJob@[native code]
//
// Chrome fail()
// x Error
// x at CaseRecorder.fail (http://localhost:8080/out/framework/logger.js:104:30)
// x at DefaultFixture.expect (http://localhost:8080/out/framework/default_fixture.js:59:16)
// > at RunCaseSpecific.fn (http://localhost:8080/out/unittests/util.spec.js:35:5)
// x at RunCaseSpecific.run (http://localhost:8080/out/framework/test_group.js:119:18)
// x at async runCase (http://localhost:8080/out/runtime/standalone.js:37:17)
// x at async http://localhost:8080/out/runtime/standalone.js:102:7
//
// Chrome throw
// x Error: hello
// > at RunCaseSpecific.fn (http://localhost:8080/out/unittests/test_group.spec.js:48:11)
// x at RunCaseSpecific.run (http://localhost:8080/out/framework/test_group.js:119:18)"
// x at async Promise.all (index 0)
// x at async TestGroupTest.run (http://localhost:8080/out/unittests/test_group_test.js:6:5)
// x at async RunCaseSpecific.fn (http://localhost:8080/out/unittests/test_group.spec.js:53:15)
// x at async RunCaseSpecific.run (http://localhost:8080/out/framework/test_group.js:119:7)
// x at async runCase (http://localhost:8080/out/runtime/standalone.js:37:17)
// x at async http://localhost:8080/out/runtime/standalone.js:102:7

View file

@ -0,0 +1,3 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const timeout = typeof step_timeout !== 'undefined' ? step_timeout : setTimeout;

View file

@ -0,0 +1,72 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { timeout } from './timeout.js';
export function assert(condition, msg) {
if (!condition) {
throw new Error(msg && (typeof msg === 'string' ? msg : msg()));
}
}
export async function assertReject(p, msg) {
try {
await p;
unreachable(msg);
} catch (ex) {
// Assertion OK
}
}
export function unreachable(msg) {
throw new Error(msg);
}
// performance.now() is available in all browsers, but not in scope by default in Node.
const perf = typeof performance !== 'undefined' ? performance : require('perf_hooks').performance;
export function now() {
return perf.now();
}
export function resolveOnTimeout(ms) {
return new Promise(resolve => {
timeout(() => {
resolve();
}, ms);
});
}
export class PromiseTimeoutError extends Error {}
export function rejectOnTimeout(ms, msg) {
return new Promise((_resolve, reject) => {
timeout(() => {
reject(new PromiseTimeoutError(msg));
}, ms);
});
}
export function raceWithRejectOnTimeout(p, ms, msg) {
return Promise.race([p, rejectOnTimeout(ms, msg)]);
}
export function objectEquals(x, y) {
if (typeof x !== 'object' || typeof y !== 'object') return x === y;
if (x === null || y === null) return x === y;
if (x.constructor !== y.constructor) return false;
if (x instanceof Function) return x === y;
if (x instanceof RegExp) return x === y;
if (x === y || x.valueOf() === y.valueOf()) return true;
if (Array.isArray(x) && Array.isArray(y) && x.length !== y.length) return false;
if (x instanceof Date) return false;
if (!(x instanceof Object)) return false;
if (!(y instanceof Object)) return false;
const x1 = x;
const y1 = y;
const p = Object.keys(x);
return Object.keys(y).every(i => p.indexOf(i) !== -1) && p.every(i => objectEquals(x1[i], y1[i]));
}
export function range(n, fn) {
return [...new Array(n)].map((_, i) => fn(i));
}

View file

@ -0,0 +1,3 @@
// AUTO-GENERATED - DO NOT EDIT. See tools/gen_version.
export const version = '474ff8e569a0d206f8cb6f6146a8769fadf77da9-dirty';

View file

@ -0,0 +1,7 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ const url = new URL(window.location.toString());
export function optionEnabled(opt) {
const val = url.searchParams.get(opt);
return val !== null && val !== '0';
}

View file

@ -0,0 +1,26 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { DefaultTestFileLoader } from '../../framework/file_loader.js';
import { Logger } from '../../framework/logging/logger.js';
import { parseQuery } from '../../framework/query/parseQuery.js';
import { assert } from '../../framework/util/util.js';
// should be DedicatedWorkerGlobalScope
const loader = new DefaultTestFileLoader();
self.onmessage = async ev => {
const query = ev.data.query;
const debug = ev.data.debug;
const log = new Logger(debug);
const testcases = Array.from(await loader.loadCases(parseQuery(query)));
assert(testcases.length === 1, 'worker query resulted in != 1 cases');
const testcase = testcases[0];
const [rec, result] = log.record(testcase.query.toString());
await testcase.run(rec);
self.postMessage({ query, result });
};

View file

@ -0,0 +1,51 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { LogMessageWithStack } from '../../framework/logging/log_message.js';
export class TestWorker {
constructor(debug) {
_defineProperty(this, 'debug', void 0);
_defineProperty(this, 'worker', void 0);
_defineProperty(this, 'resolvers', new Map());
this.debug = debug;
const selfPath = import.meta.url;
const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
const workerPath = selfPathDir + '/test_worker-worker.js';
this.worker = new Worker(workerPath, { type: 'module' });
this.worker.onmessage = ev => {
const query = ev.data.query;
const result = ev.data.result;
if (result.logs) {
for (const l of result.logs) {
Object.setPrototypeOf(l, LogMessageWithStack.prototype);
}
}
this.resolvers.get(query)(result);
// TODO(kainino0x): update the Logger with this result (or don't have a logger and update the
// entire results JSON somehow at some point).
};
}
async run(rec, query) {
this.worker.postMessage({ query, debug: this.debug });
const workerResult = await new Promise(resolve => {
this.resolvers.set(query, resolve);
});
rec.injectResult(workerResult);
}
}

View file

@ -0,0 +1,58 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { DefaultTestFileLoader } from '../framework/file_loader.js';
import { Logger } from '../framework/logging/logger.js';
import { parseQuery } from '../framework/query/parseQuery.js';
import { AsyncMutex } from '../framework/util/async_mutex.js';
import { assert } from '../framework/util/util.js';
import { optionEnabled } from './helper/options.js';
import { TestWorker } from './helper/test_worker.js';
(async () => {
const loader = new DefaultTestFileLoader();
const qs = new URLSearchParams(window.location.search).getAll('q');
assert(qs.length === 1, 'currently, there must be exactly one ?q=');
const testcases = await loader.loadCases(parseQuery(qs[0]));
await addWPTTests(testcases);
})();
// Note: `async_test`s must ALL be added within the same task. This function *must not* be async.
function addWPTTests(testcases) {
const worker = optionEnabled('worker') ? new TestWorker(false) : undefined;
const log = new Logger(false);
const mutex = new AsyncMutex();
const running = [];
for (const testcase of testcases) {
const name = testcase.query.toString();
const wpt_fn = function () {
const p = mutex.with(async () => {
const [rec, res] = log.record(name);
if (worker) {
await worker.run(rec, name);
} else {
await testcase.run(rec);
}
this.step(() => {
// Unfortunately, it seems not possible to surface any logs for warn/skip.
if (res.status === 'fail') {
throw (res.logs || []).map(s => s.toJSON()).join('\n\n');
}
});
this.done();
});
running.push(p);
return p;
};
async_test(wpt_fn, name);
}
return Promise.all(running).then(() => log);
}

View file

@ -0,0 +1,71 @@
<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
<!--
This test suite is built from the TypeScript sources at:
https://github.com/gpuweb/cts
If you are debugging WebGPU conformance tests, it's highly recommended that
you use the standalone interactive runner in that repository, which
provides tools for easier debugging and editing (source maps, debug
logging, warn/skip functionality, etc.)
NOTE:
The WPT version of this file is generated with *one variant per test spec
file*. If your harness needs more fine-grained suppressions, you'll need to
generate your own variants list from your suppression list.
See `tools/gen_wpt_cts_html` to do this.
When run under browser CI, the original cts.html should be skipped, and
this alternate version should be run instead, under a non-exported WPT test
directory (e.g. Chromium's wpt_internal).
-->
<!doctype html>
<title>WebGPU CTS</title>
<meta charset=utf-8>
<link rel=help href='https://gpuweb.github.io/gpuweb/'>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script type=module src=../webgpu/common/runtime/wpt.js></script>
<meta name=variant content='?q=webgpu:api,operation,buffers,create_mapped:*'>
<meta name=variant content='?q=webgpu:api,operation,buffers,map:*'>
<meta name=variant content='?q=webgpu:api,operation,buffers,map_detach:*'>
<meta name=variant content='?q=webgpu:api,operation,buffers,map_oom:*'>
<meta name=variant content='?q=webgpu:api,operation,command_buffer,basic:*'>
<meta name=variant content='?q=webgpu:api,operation,command_buffer,compute,basic:*'>
<meta name=variant content='?q=webgpu:api,operation,command_buffer,copies:*'>
<meta name=variant content='?q=webgpu:api,operation,command_buffer,render,basic:*'>
<meta name=variant content='?q=webgpu:api,operation,command_buffer,render,rendering:*'>
<meta name=variant content='?q=webgpu:api,operation,command_buffer,render,storeop:*'>
<meta name=variant content='?q=webgpu:api,operation,fences:*'>
<meta name=variant content='?q=webgpu:api,operation,render_pass,storeOp:*'>
<meta name=variant content='?q=webgpu:api,operation,render_pipeline,culling_tests:*'>
<meta name=variant content='?q=webgpu:api,operation,resource_init,copied_texture_clear:*'>
<meta name=variant content='?q=webgpu:api,operation,resource_init,depth_stencil_attachment_clear:*'>
<meta name=variant content='?q=webgpu:api,operation,resource_init,sampled_texture_clear:*'>
<meta name=variant content='?q=webgpu:api,validation,createBindGroup:*'>
<!--<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:*'>-->
<meta name=variant content='?q=webgpu:api,validation,createPipelineLayout:*'>
<!--<meta name=variant content='?q=webgpu:api,validation,createRenderPipeline:*'>-->
<meta name=variant content='?q=webgpu:api,validation,createTexture:*'>
<!--<meta name=variant content='?q=webgpu:api,validation,createView:*'>-->
<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,index_access:*'>
<meta name=variant content='?q=webgpu:api,validation,error_scope:*'>
<meta name=variant content='?q=webgpu:api,validation,fences:*'>
<meta name=variant content='?q=webgpu:api,validation,queue_submit:*'>
<meta name=variant content='?q=webgpu:api,validation,render_pass:*'>
<meta name=variant content='?q=webgpu:api,validation,render_pass,storeOp:*'>
<meta name=variant content='?q=webgpu:api,validation,render_pass_descriptor:*'>
<meta name=variant content='?q=webgpu:api,validation,setBindGroup:*'>
<meta name=variant content='?q=webgpu:api,validation,setBlendColor:*'>
<meta name=variant content='?q=webgpu:api,validation,setScissorRect:*'>
<meta name=variant content='?q=webgpu:api,validation,setStencilReference:*'>
<meta name=variant content='?q=webgpu:api,validation,setVertexBuffer:*'>
<meta name=variant content='?q=webgpu:api,validation,setViewport:*'>
<!--<meta name=variant content='?q=webgpu:api,validation,vertex_state:*'>-->
<meta name=variant content='?q=webgpu:examples:*'>
<meta name=variant content='?q=webgpu:idl,constants,flags:*'>
<meta name=variant content='?q=webgpu:shader,execution,robust_access:*'>
<meta name=variant content='?q=webgpu:shader,execution,robust_access_vertex:*'>
<meta name=variant content='?q=webgpu:web-platform,canvas,context_creation:*'>
<meta name=variant content='?q=webgpu:web-platform,copyImageBitmapToTexture:*'>

View file

@ -0,0 +1,78 @@
var Module = (function() {
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
return (
function(Module) {
Module = Module || {};
var c;c||(c=typeof Module !== 'undefined' ? Module : {});
c.compileGLSLZeroCopy=function(a,b,d,e){d=!!d;switch(b){case "vertex":var g=0;break;case "fragment":g=4;break;case "compute":g=5;break;default:throw Error("shader_stage must be 'vertex', 'fragment', or 'compute'.");}switch(e||"1.0"){case "1.0":var f=65536;break;case "1.1":f=65792;break;case "1.2":f=66048;break;case "1.3":f=66304;break;case "1.4":f=66560;break;case "1.5":f=66816;break;default:throw Error("spirv_version must be '1.0' ~ '1.5'.");}e=c._malloc(4);b=c._malloc(4);var h=aa([a,g,d,f,e,b]);
d=k(e);a=k(b);c._free(e);c._free(b);if(0===h)throw Error("GLSL compilation failed");e={};d/=4;e.data=c.HEAPU32.subarray(d,d+a);e.free=function(){c._destroy_output_buffer(h)};return e};c.compileGLSL=function(a,b,d,e){a=c.compileGLSLZeroCopy(a,b,d,e);b=a.data.slice();a.free();return b};var p={},q;for(q in c)c.hasOwnProperty(q)&&(p[q]=c[q]);var r="./this.program",t=!1,u=!1;t="object"===typeof window;u="function"===typeof importScripts;var v="",w;
if(t||u)u?v=self.location.href:document.currentScript&&(v=document.currentScript.src),_scriptDir&&(v=_scriptDir),0!==v.indexOf("blob:")?v=v.substr(0,v.lastIndexOf("/")+1):v="",u&&(w=function(a){var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)});var x=c.print||console.log.bind(console),y=c.printErr||console.warn.bind(console);for(q in p)p.hasOwnProperty(q)&&(c[q]=p[q]);p=null;c.thisProgram&&(r=c.thisProgram);var A;
c.wasmBinary&&(A=c.wasmBinary);"object"!==typeof WebAssembly&&y("no native wasm support detected");function k(a){var b="i32";"*"===b.charAt(b.length-1)&&(b="i32");switch(b){case "i1":return B[a>>0];case "i8":return B[a>>0];case "i16":return ba[a>>1];case "i32":return C[a>>2];case "i64":return C[a>>2];case "float":return ca[a>>2];case "double":return da[a>>3];default:D("invalid type for getValue: "+b)}return null}var E,ea=new WebAssembly.Table({initial:859,maximum:859,element:"anyfunc"}),fa=!1;
function ha(){var a=c._convert_glsl_to_spirv;a||D("Assertion failed: Cannot call unknown function convert_glsl_to_spirv, make sure it is exported");return a}
function aa(a){var b="string number boolean number number number".split(" "),d={string:function(a){var b=0;if(null!==a&&void 0!==a&&0!==a){var d=(a.length<<2)+1;b=G(d);ia(a,H,b,d)}return b},array:function(a){var b=G(a.length);B.set(a,b);return b}},e=ha(),g=[],f=0;if(a)for(var h=0;h<a.length;h++){var n=d[b[h]];n?(0===f&&(f=ja()),g[h]=n(a[h])):g[h]=a[h]}a=e.apply(null,g);0!==f&&ka(f);return a}var la="undefined"!==typeof TextDecoder?new TextDecoder("utf8"):void 0;
function I(a,b,d){var e=b+d;for(d=b;a[d]&&!(d>=e);)++d;if(16<d-b&&a.subarray&&la)return la.decode(a.subarray(b,d));for(e="";b<d;){var g=a[b++];if(g&128){var f=a[b++]&63;if(192==(g&224))e+=String.fromCharCode((g&31)<<6|f);else{var h=a[b++]&63;g=224==(g&240)?(g&15)<<12|f<<6|h:(g&7)<<18|f<<12|h<<6|a[b++]&63;65536>g?e+=String.fromCharCode(g):(g-=65536,e+=String.fromCharCode(55296|g>>10,56320|g&1023))}}else e+=String.fromCharCode(g)}return e}
function ia(a,b,d,e){if(0<e){e=d+e-1;for(var g=0;g<a.length;++g){var f=a.charCodeAt(g);if(55296<=f&&57343>=f){var h=a.charCodeAt(++g);f=65536+((f&1023)<<10)|h&1023}if(127>=f){if(d>=e)break;b[d++]=f}else{if(2047>=f){if(d+1>=e)break;b[d++]=192|f>>6}else{if(65535>=f){if(d+2>=e)break;b[d++]=224|f>>12}else{if(d+3>=e)break;b[d++]=240|f>>18;b[d++]=128|f>>12&63}b[d++]=128|f>>6&63}b[d++]=128|f&63}}b[d]=0}}"undefined"!==typeof TextDecoder&&new TextDecoder("utf-16le");var J,B,H,ba,C,ca,da;
function ma(a){J=a;c.HEAP8=B=new Int8Array(a);c.HEAP16=ba=new Int16Array(a);c.HEAP32=C=new Int32Array(a);c.HEAPU8=H=new Uint8Array(a);c.HEAPU16=new Uint16Array(a);c.HEAPU32=new Uint32Array(a);c.HEAPF32=ca=new Float32Array(a);c.HEAPF64=da=new Float64Array(a)}var na=c.TOTAL_MEMORY||16777216;c.wasmMemory?E=c.wasmMemory:E=new WebAssembly.Memory({initial:na/65536});E&&(J=E.buffer);na=J.byteLength;ma(J);C[84916]=5582704;
function K(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b();else{var d=b.J;"number"===typeof d?void 0===b.H?c.dynCall_v(d):c.dynCall_vi(d,b.H):d(void 0===b.H?null:b.H)}}}var oa=[],pa=[],qa=[],ra=[];function sa(){var a=c.preRun.shift();oa.unshift(a)}var L=0,M=null,N=null;c.preloadedImages={};c.preloadedAudios={};function D(a){if(c.onAbort)c.onAbort(a);x(a);y(a);fa=!0;throw new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");}
function ta(){var a=O;return String.prototype.startsWith?a.startsWith("data:application/octet-stream;base64,"):0===a.indexOf("data:application/octet-stream;base64,")}var O="glslang.wasm";if(!ta()){var ua=O;O=c.locateFile?c.locateFile(ua,v):v+ua}function wa(){try{if(A)return new Uint8Array(A);if(w)return w(O);throw"both async and sync fetching of the wasm failed";}catch(a){D(a)}}
function xa(){return A||!t&&!u||"function"!==typeof fetch?new Promise(function(a){a(wa())}):fetch(O,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+O+"'";return a.arrayBuffer()}).catch(function(){return wa()})}pa.push({J:function(){ya()}});var za=[null,[],[]],P=0;function Aa(){P+=4;return C[P-4>>2]}var Q={},Ba={};
function Ca(){if(!R){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"===typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:r},b;for(b in Ba)a[b]=Ba[b];var d=[];for(b in a)d.push(b+"="+a[b]);R=d}return R}var R;function S(a){return 0===a%4&&(0!==a%100||0===a%400)}function T(a,b){for(var d=0,e=0;e<=b;d+=a[e++]);return d}var U=[31,29,31,30,31,30,31,31,30,31,30,31],W=[31,28,31,30,31,30,31,31,30,31,30,31];
function X(a,b){for(a=new Date(a.getTime());0<b;){var d=a.getMonth(),e=(S(a.getFullYear())?U:W)[d];if(b>e-a.getDate())b-=e-a.getDate()+1,a.setDate(1),11>d?a.setMonth(d+1):(a.setMonth(0),a.setFullYear(a.getFullYear()+1));else{a.setDate(a.getDate()+b);break}}return a}
function Da(a,b,d,e){function g(a,b,d){for(a="number"===typeof a?a.toString():a||"";a.length<b;)a=d[0]+a;return a}function f(a,b){return g(a,b,"0")}function h(a,b){function V(a){return 0>a?-1:0<a?1:0}var d;0===(d=V(a.getFullYear()-b.getFullYear()))&&0===(d=V(a.getMonth()-b.getMonth()))&&(d=V(a.getDate()-b.getDate()));return d}function n(a){switch(a.getDay()){case 0:return new Date(a.getFullYear()-1,11,29);case 1:return a;case 2:return new Date(a.getFullYear(),0,3);case 3:return new Date(a.getFullYear(),
0,2);case 4:return new Date(a.getFullYear(),0,1);case 5:return new Date(a.getFullYear()-1,11,31);case 6:return new Date(a.getFullYear()-1,11,30)}}function z(a){a=X(new Date(a.A+1900,0,1),a.G);var b=n(new Date(a.getFullYear()+1,0,4));return 0>=h(n(new Date(a.getFullYear(),0,4)),a)?0>=h(b,a)?a.getFullYear()+1:a.getFullYear():a.getFullYear()-1}var m=C[e+40>>2];e={N:C[e>>2],M:C[e+4>>2],D:C[e+8>>2],C:C[e+12>>2],B:C[e+16>>2],A:C[e+20>>2],F:C[e+24>>2],G:C[e+28>>2],X:C[e+32>>2],L:C[e+36>>2],O:m?m?I(H,m,void 0):
"":""};d=d?I(H,d,void 0):"";m={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var l in m)d=d.replace(new RegExp(l,"g"),m[l]);var F="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),
va="January February March April May June July August September October November December".split(" ");m={"%a":function(a){return F[a.F].substring(0,3)},"%A":function(a){return F[a.F]},"%b":function(a){return va[a.B].substring(0,3)},"%B":function(a){return va[a.B]},"%C":function(a){return f((a.A+1900)/100|0,2)},"%d":function(a){return f(a.C,2)},"%e":function(a){return g(a.C,2," ")},"%g":function(a){return z(a).toString().substring(2)},"%G":function(a){return z(a)},"%H":function(a){return f(a.D,2)},
"%I":function(a){a=a.D;0==a?a=12:12<a&&(a-=12);return f(a,2)},"%j":function(a){return f(a.C+T(S(a.A+1900)?U:W,a.B-1),3)},"%m":function(a){return f(a.B+1,2)},"%M":function(a){return f(a.M,2)},"%n":function(){return"\n"},"%p":function(a){return 0<=a.D&&12>a.D?"AM":"PM"},"%S":function(a){return f(a.N,2)},"%t":function(){return"\t"},"%u":function(a){return a.F||7},"%U":function(a){var b=new Date(a.A+1900,0,1),d=0===b.getDay()?b:X(b,7-b.getDay());a=new Date(a.A+1900,a.B,a.C);return 0>h(d,a)?f(Math.ceil((31-
d.getDate()+(T(S(a.getFullYear())?U:W,a.getMonth()-1)-31)+a.getDate())/7),2):0===h(d,b)?"01":"00"},"%V":function(a){var b=n(new Date(a.A+1900,0,4)),d=n(new Date(a.A+1901,0,4)),e=X(new Date(a.A+1900,0,1),a.G);return 0>h(e,b)?"53":0>=h(d,e)?"01":f(Math.ceil((b.getFullYear()<a.A+1900?a.G+32-b.getDate():a.G+1-b.getDate())/7),2)},"%w":function(a){return a.F},"%W":function(a){var b=new Date(a.A,0,1),d=1===b.getDay()?b:X(b,0===b.getDay()?1:7-b.getDay()+1);a=new Date(a.A+1900,a.B,a.C);return 0>h(d,a)?f(Math.ceil((31-
d.getDate()+(T(S(a.getFullYear())?U:W,a.getMonth()-1)-31)+a.getDate())/7),2):0===h(d,b)?"01":"00"},"%y":function(a){return(a.A+1900).toString().substring(2)},"%Y":function(a){return a.A+1900},"%z":function(a){a=a.L;var b=0<=a;a=Math.abs(a)/60;return(b?"+":"-")+String("0000"+(a/60*100+a%60)).slice(-4)},"%Z":function(a){return a.O},"%%":function(){return"%"}};for(l in m)0<=d.indexOf(l)&&(d=d.replace(new RegExp(l,"g"),m[l](e)));l=Ea(d);if(l.length>b)return 0;B.set(l,a);return l.length-1}
function Ea(a){for(var b=0,d=0;d<a.length;++d){var e=a.charCodeAt(d);55296<=e&&57343>=e&&(e=65536+((e&1023)<<10)|a.charCodeAt(++d)&1023);127>=e?++b:b=2047>=e?b+2:65535>=e?b+3:b+4}b=Array(b+1);ia(a,b,0,b.length);return b}
var Ga={f:function(){},c:function(){c.___errno_location&&(C[c.___errno_location()>>2]=63);return-1},n:function(a,b){P=b;try{var d=Aa();var e=Aa();if(-1===d||0===e)var g=-28;else{var f=Q.K[d];if(f&&e===f.U){var h=(void 0).T(f.S);Q.R(d,h,e,f.flags,f.offset);(void 0).W(h);Q.K[d]=null;f.P&&Fa(f.V)}g=0}return g}catch(n){return D(n),-n.I}},a:function(){},b:function(){D()},k:function(a,b,d){H.set(H.subarray(b,b+d),a)},l:function(a){var b=B.length;if(2147418112<a)return!1;for(var d=1;4>=d;d*=2){var e=b*(1+
.2/d);e=Math.min(e,a+100663296);e=Math.max(16777216,a,e);0<e%65536&&(e+=65536-e%65536);a:{try{E.grow(Math.min(2147418112,e)-J.byteLength+65535>>16);ma(E.buffer);var g=1;break a}catch(f){}g=void 0}if(g)return!0}return!1},d:function(a,b){var d=0;Ca().forEach(function(e,g){var f=b+d;g=C[a+4*g>>2]=f;for(f=0;f<e.length;++f)B[g++>>0]=e.charCodeAt(f);B[g>>0]=0;d+=e.length+1});return 0},e:function(a,b){var d=Ca();C[a>>2]=d.length;var e=0;d.forEach(function(a){e+=a.length+1});C[b>>2]=e;return 0},h:function(){return 0},
j:function(){return 0},g:function(a,b,d,e){try{for(var g=0,f=0;f<d;f++){for(var h=C[b+8*f>>2],n=C[b+(8*f+4)>>2],z=0;z<n;z++){var m=H[h+z],l=za[a];0===m||10===m?((1===a?x:y)(I(l,0)),l.length=0):l.push(m)}g+=n}C[e>>2]=g;return 0}catch(F){return D(F),F.I}},memory:E,o:function(){},i:function(){},m:function(a,b,d,e){return Da(a,b,d,e)},table:ea},Ha=function(){function a(a){c.asm=a.exports;L--;c.monitorRunDependencies&&c.monitorRunDependencies(L);0==L&&(null!==M&&(clearInterval(M),M=null),N&&(a=N,N=null,
a()))}function b(b){a(b.instance)}function d(a){return xa().then(function(a){return WebAssembly.instantiate(a,e)}).then(a,function(a){y("failed to asynchronously prepare wasm: "+a);D(a)})}var e={env:Ga,wasi_snapshot_preview1:Ga};L++;c.monitorRunDependencies&&c.monitorRunDependencies(L);if(c.instantiateWasm)try{return c.instantiateWasm(e,a)}catch(g){return y("Module.instantiateWasm callback failed with error: "+g),!1}(function(){if(A||"function"!==typeof WebAssembly.instantiateStreaming||ta()||"function"!==
typeof fetch)return d(b);fetch(O,{credentials:"same-origin"}).then(function(a){return WebAssembly.instantiateStreaming(a,e).then(b,function(a){y("wasm streaming compile failed: "+a);y("falling back to ArrayBuffer instantiation");d(b)})})})();return{}}();c.asm=Ha;var ya=c.___wasm_call_ctors=function(){return(ya=c.___wasm_call_ctors=c.asm.p).apply(null,arguments)};c._convert_glsl_to_spirv=function(){return(c._convert_glsl_to_spirv=c.asm.q).apply(null,arguments)};
c._destroy_output_buffer=function(){return(c._destroy_output_buffer=c.asm.r).apply(null,arguments)};c._malloc=function(){return(c._malloc=c.asm.s).apply(null,arguments)};var Fa=c._free=function(){return(Fa=c._free=c.asm.t).apply(null,arguments)},ja=c.stackSave=function(){return(ja=c.stackSave=c.asm.u).apply(null,arguments)},G=c.stackAlloc=function(){return(G=c.stackAlloc=c.asm.v).apply(null,arguments)},ka=c.stackRestore=function(){return(ka=c.stackRestore=c.asm.w).apply(null,arguments)};
c.dynCall_vi=function(){return(c.dynCall_vi=c.asm.x).apply(null,arguments)};c.dynCall_v=function(){return(c.dynCall_v=c.asm.y).apply(null,arguments)};c.asm=Ha;var Y;c.then=function(a){if(Y)a(c);else{var b=c.onRuntimeInitialized;c.onRuntimeInitialized=function(){b&&b();a(c)}}return c};N=function Ia(){Y||Z();Y||(N=Ia)};
function Z(){function a(){if(!Y&&(Y=!0,!fa)){K(pa);K(qa);if(c.onRuntimeInitialized)c.onRuntimeInitialized();if(c.postRun)for("function"==typeof c.postRun&&(c.postRun=[c.postRun]);c.postRun.length;){var a=c.postRun.shift();ra.unshift(a)}K(ra)}}if(!(0<L)){if(c.preRun)for("function"==typeof c.preRun&&(c.preRun=[c.preRun]);c.preRun.length;)sa();K(oa);0<L||(c.setStatus?(c.setStatus("Running..."),setTimeout(function(){setTimeout(function(){c.setStatus("")},1);a()},1)):a())}}c.run=Z;
if(c.preInit)for("function"==typeof c.preInit&&(c.preInit=[c.preInit]);0<c.preInit.length;)c.preInit.pop()();Z();
return Module
}
);
})();
if (typeof exports === 'object' && typeof module === 'object')
module.exports = Module;
else if (typeof define === 'function' && define['amd'])
define([], function() { return Module; });
else if (typeof exports === 'object')
exports["Module"] = Module;
export default (() => {
const initialize = () => {
return new Promise(resolve => {
Module({
locateFile() {
const i = import.meta.url.lastIndexOf('/')
return import.meta.url.substring(0, i) + '/glslang.wasm';
},
onRuntimeInitialized() {
resolve({
compileGLSLZeroCopy: this.compileGLSLZeroCopy,
compileGLSL: this.compileGLSL,
});
},
});
});
};
let instance;
return () => {
if (!instance) {
instance = initialize();
}
return instance;
};
})();

View file

@ -0,0 +1,25 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = '';
import { params, pbool, poptions } from '../../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { MappingTest } from './mapping_test.js';
export const g = makeTestGroup(MappingTest);
g.test('createBufferMapped')
.params(
params()
.combine(poptions('size', [12, 512 * 1024]))
.combine(pbool('mappable'))
)
.fn(t => {
const { size, mappable } = t.params;
const [buffer, arrayBuffer] = t.device.createBufferMapped({
size,
usage: GPUBufferUsage.COPY_SRC | (mappable ? GPUBufferUsage.MAP_WRITE : 0),
});
t.checkMapWrite(buffer, arrayBuffer, size);
});

View file

@ -0,0 +1,59 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = '';
import { pbool, poptions, params } from '../../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { MappingTest } from './mapping_test.js';
export const g = makeTestGroup(MappingTest);
g.test('mapWriteAsync')
.params(poptions('size', [12, 512 * 1024]))
.fn(async t => {
const { size } = t.params;
const buffer = t.device.createBuffer({
size,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE,
});
const arrayBuffer = await buffer.mapWriteAsync();
t.checkMapWrite(buffer, arrayBuffer, size);
});
g.test('mapReadAsync')
.params(poptions('size', [12, 512 * 1024]))
.fn(async t => {
const { size } = t.params;
const [buffer, init] = t.device.createBufferMapped({
size,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
});
const expected = new Uint32Array(new ArrayBuffer(size));
const data = new Uint32Array(init);
for (let i = 0; i < data.length; ++i) {
data[i] = expected[i] = i + 1;
}
buffer.unmap();
const actual = new Uint8Array(await buffer.mapReadAsync());
t.expectBuffer(actual, new Uint8Array(expected.buffer));
});
g.test('createBufferMapped')
.params(
params()
.combine(poptions('size', [12, 512 * 1024]))
.combine(pbool('mappable'))
)
.fn(async t => {
const { size, mappable } = t.params;
const [buffer, arrayBuffer] = t.device.createBufferMapped({
size,
usage: GPUBufferUsage.COPY_SRC | (mappable ? GPUBufferUsage.MAP_WRITE : 0),
});
t.checkMapWrite(buffer, arrayBuffer, size);
});

View file

@ -0,0 +1,69 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = '';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { GPUTest } from '../../../gpu_test.js';
class F extends GPUTest {
checkDetach(buffer, arrayBuffer, unmap, destroy) {
const view = new Uint8Array(arrayBuffer);
this.expect(arrayBuffer.byteLength === 4);
this.expect(view.length === 4);
if (unmap) buffer.unmap();
if (destroy) buffer.destroy();
this.expect(arrayBuffer.byteLength === 0, 'ArrayBuffer should be detached');
this.expect(view.byteLength === 0, 'ArrayBufferView should be detached');
}
}
export const g = makeTestGroup(F);
g.test('mapWriteAsync')
.params([
{ unmap: true, destroy: false }, //
{ unmap: false, destroy: true },
{ unmap: true, destroy: true },
])
.fn(async t => {
const buffer = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_WRITE });
const arrayBuffer = await buffer.mapWriteAsync();
t.checkDetach(buffer, arrayBuffer, t.params.unmap, t.params.destroy);
});
g.test('mapReadAsync')
.params([
{ unmap: true, destroy: false }, //
{ unmap: false, destroy: true },
{ unmap: true, destroy: true },
])
.fn(async t => {
const buffer = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_READ });
const arrayBuffer = await buffer.mapReadAsync();
t.checkDetach(buffer, arrayBuffer, t.params.unmap, t.params.destroy);
});
g.test('create_mapped')
.params([
{ unmap: true, destroy: false },
{ unmap: false, destroy: true },
{ unmap: true, destroy: true },
])
.fn(async t => {
const desc = {
size: 4,
usage: GPUBufferUsage.MAP_WRITE,
};
const [buffer, arrayBuffer] = t.device.createBufferMapped(desc);
const view = new Uint8Array(arrayBuffer);
t.expect(arrayBuffer.byteLength === 4);
t.expect(view.length === 4);
if (t.params.unmap) buffer.unmap();
if (t.params.destroy) buffer.destroy();
t.expect(arrayBuffer.byteLength === 0, 'ArrayBuffer should be detached');
t.expect(view.byteLength === 0, 'ArrayBufferView should be detached');
});

View file

@ -0,0 +1,34 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = '';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { GPUTest } from '../../../gpu_test.js';
function getBufferDesc(usage) {
return {
size: Number.MAX_SAFE_INTEGER,
usage,
};
}
export const g = makeTestGroup(GPUTest);
g.test('mapWriteAsync').fn(async t => {
const buffer = t.expectGPUError('out-of-memory', () => {
return t.device.createBuffer(getBufferDesc(GPUBufferUsage.MAP_WRITE));
});
t.shouldReject('OperationError', buffer.mapWriteAsync());
});
g.test('mapReadAsync').fn(async t => {
const buffer = t.expectGPUError('out-of-memory', () => {
return t.device.createBuffer(getBufferDesc(GPUBufferUsage.MAP_READ));
});
t.shouldReject('OperationError', buffer.mapReadAsync());
});
g.test('createBufferMapped').fn(async t => {
t.shouldThrow('RangeError', () => {
t.device.createBufferMapped(getBufferDesc(GPUBufferUsage.COPY_SRC));
});
});

View file

@ -0,0 +1,34 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { GPUTest } from '../../../gpu_test.js';
export class MappingTest extends GPUTest {
checkMapWrite(buffer, mappedContents, size) {
this.checkMapWriteZeroed(mappedContents, size);
const mappedView = new Uint32Array(mappedContents);
const expected = new Uint32Array(new ArrayBuffer(size));
this.expect(mappedView.byteLength === size);
for (let i = 0; i < mappedView.length; ++i) {
mappedView[i] = expected[i] = i + 1;
}
buffer.unmap();
this.expectContents(buffer, expected);
}
checkMapWriteZeroed(arrayBuffer, expectedSize) {
this.expect(arrayBuffer.byteLength === expectedSize);
const view = new Uint8Array(arrayBuffer);
this.expectZero(view);
}
expectZero(actual) {
const size = actual.byteLength;
for (let i = 0; i < size; ++i) {
if (actual[i] !== 0) {
this.fail(`at [${i}], expected zero, got ${actual[i]}`);
break;
}
}
}
}

View file

@ -0,0 +1,15 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
Basic tests.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { GPUTest } from '../../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
g.test('empty').fn(async t => {
const encoder = t.device.createCommandEncoder();
const cmd = encoder.finish();
t.device.defaultQueue.submit([cmd]);
});

View file

@ -0,0 +1,74 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
Basic command buffer compute tests.
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
g.test('memcpy').fn(async t => {
const data = new Uint32Array([0x01020304]);
const [src, srcData] = t.device.createBufferMapped({
size: 4,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
});
new Uint32Array(srcData).set(data);
src.unmap();
const dst = t.device.createBuffer({
size: 4,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
});
const bgl = t.device.createBindGroupLayout({
entries: [
{ binding: 0, visibility: 4, type: 'storage-buffer' },
{ binding: 1, visibility: 4, type: 'storage-buffer' },
],
});
const bg = t.device.createBindGroup({
entries: [
{ binding: 0, resource: { buffer: src, offset: 0, size: 4 } },
{ binding: 1, resource: { buffer: dst, offset: 0, size: 4 } },
],
layout: bgl,
});
const module = t.makeShaderModule('compute', {
glsl: `
#version 310 es
layout(std140, set = 0, binding = 0) buffer Src {
int value;
} src;
layout(std140, set = 0, binding = 1) buffer Dst {
int value;
} dst;
void main() {
dst.value = src.value;
}
`,
});
const pl = t.device.createPipelineLayout({ bindGroupLayouts: [bgl] });
const pipeline = t.device.createComputePipeline({
computeStage: { module, entryPoint: 'main' },
layout: pl,
});
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bg);
pass.dispatch(1, 1, 1);
pass.endPass();
t.device.defaultQueue.submit([encoder.finish()]);
t.expectContents(dst, data);
});

View file

@ -0,0 +1,121 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
copy{Buffer,Texture}To{Buffer,Texture} tests.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { GPUTest } from '../../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
g.test('b2b').fn(async t => {
const data = new Uint32Array([0x01020304]);
const [src, map] = t.device.createBufferMapped({
size: 4,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
new Uint32Array(map).set(data);
src.unmap();
const dst = t.device.createBuffer({
size: 4,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
const encoder = t.device.createCommandEncoder();
encoder.copyBufferToBuffer(src, 0, dst, 0, 4);
t.device.defaultQueue.submit([encoder.finish()]);
t.expectContents(dst, data);
});
g.test('b2t2b').fn(async t => {
const data = new Uint32Array([0x01020304]);
const [src, map] = t.device.createBufferMapped({
size: 4,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
new Uint32Array(map).set(data);
src.unmap();
const dst = t.device.createBuffer({
size: 4,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
const mid = t.device.createTexture({
size: { width: 1, height: 1, depth: 1 },
format: 'rgba8uint',
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
});
const encoder = t.device.createCommandEncoder();
encoder.copyBufferToTexture(
{ buffer: src, bytesPerRow: 256 },
{ texture: mid, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
{ width: 1, height: 1, depth: 1 }
);
encoder.copyTextureToBuffer(
{ texture: mid, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
{ buffer: dst, bytesPerRow: 256 },
{ width: 1, height: 1, depth: 1 }
);
t.device.defaultQueue.submit([encoder.finish()]);
t.expectContents(dst, data);
});
g.test('b2t2t2b').fn(async t => {
const data = new Uint32Array([0x01020304]);
const [src, map] = t.device.createBufferMapped({
size: 4,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
new Uint32Array(map).set(data);
src.unmap();
const dst = t.device.createBuffer({
size: 4,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
const midDesc = {
size: { width: 1, height: 1, depth: 1 },
format: 'rgba8uint',
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
};
const mid1 = t.device.createTexture(midDesc);
const mid2 = t.device.createTexture(midDesc);
const encoder = t.device.createCommandEncoder();
encoder.copyBufferToTexture(
{ buffer: src, bytesPerRow: 256 },
{ texture: mid1, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
{ width: 1, height: 1, depth: 1 }
);
encoder.copyTextureToTexture(
{ texture: mid1, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
{ texture: mid2, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
{ width: 1, height: 1, depth: 1 }
);
encoder.copyTextureToBuffer(
{ texture: mid2, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
{ buffer: dst, bytesPerRow: 256 },
{ width: 1, height: 1, depth: 1 }
);
t.device.defaultQueue.submit([encoder.finish()]);
t.expectContents(dst, data);
});

View file

@ -0,0 +1,46 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
Basic command buffer rendering tests.
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
g.test('clear').fn(async t => {
const dst = t.device.createBuffer({
size: 4,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
const colorAttachment = t.device.createTexture({
format: 'rgba8unorm',
size: { width: 1, height: 1, depth: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
});
const colorAttachmentView = colorAttachment.createView();
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
attachment: colorAttachmentView,
loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
storeOp: 'store',
},
],
});
pass.endPass();
encoder.copyTextureToBuffer(
{ texture: colorAttachment, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
{ buffer: dst, bytesPerRow: 256 },
{ width: 1, height: 1, depth: 1 }
);
t.device.defaultQueue.submit([encoder.finish()]);
t.expectContents(dst, new Uint8Array([0x00, 0xff, 0x00, 0xff]));
});

View file

@ -0,0 +1,85 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = '';
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
g.test('fullscreen_quad').fn(async t => {
const dst = t.device.createBuffer({
size: 4,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
const colorAttachment = t.device.createTexture({
format: 'rgba8unorm',
size: { width: 1, height: 1, depth: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
});
const colorAttachmentView = colorAttachment.createView();
const vertexModule = t.makeShaderModule('vertex', {
glsl: `
#version 310 es
void main() {
const vec2 pos[3] = vec2[3](
vec2(-1.f, -3.f), vec2(3.f, 1.f), vec2(-1.f, 1.f));
gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
}
`,
});
const fragmentModule = t.makeShaderModule('fragment', {
glsl: `
#version 310 es
precision mediump float;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
`,
});
const pl = t.device.createPipelineLayout({ bindGroupLayouts: [] });
const pipeline = t.device.createRenderPipeline({
vertexStage: { module: vertexModule, entryPoint: 'main' },
fragmentStage: { module: fragmentModule, entryPoint: 'main' },
layout: pl,
primitiveTopology: 'triangle-list',
rasterizationState: {
frontFace: 'ccw',
},
colorStates: [{ format: 'rgba8unorm', alphaBlend: {}, colorBlend: {} }],
vertexState: {
indexFormat: 'uint16',
vertexBuffers: [],
},
});
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
attachment: colorAttachmentView,
storeOp: 'store',
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
});
pass.setPipeline(pipeline);
pass.draw(3, 1, 0, 0);
pass.endPass();
encoder.copyTextureToBuffer(
{ texture: colorAttachment, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
{ buffer: dst, bytesPerRow: 256 },
{ width: 1, height: 1, depth: 1 }
);
t.device.defaultQueue.submit([encoder.finish()]);
t.expectContents(dst, new Uint8Array([0x00, 0xff, 0x00, 0xff]));
});

View file

@ -0,0 +1,78 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
renderPass store op test that drawn quad is either stored or cleared based on storeop`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
g.test('storeOp_controls_whether_1x1_drawn_quad_is_stored')
.params([
{ storeOp: 'store', _expected: 1 }, //
{ storeOp: 'clear', _expected: 0 },
])
.fn(async t => {
const renderTexture = t.device.createTexture({
size: { width: 1, height: 1, depth: 1 },
format: 'r8unorm',
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
});
// create render pipeline
const vertexModule = t.makeShaderModule('vertex', {
glsl: `
#version 450
const vec2 pos[3] = vec2[3](
vec2( 1.0f, -1.0f),
vec2( 1.0f, 1.0f),
vec2(-1.0f, 1.0f)
);
void main() {
gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
}
`,
});
const fragmentModule = t.makeShaderModule('fragment', {
glsl: `
#version 450
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`,
});
const renderPipeline = t.device.createRenderPipeline({
vertexStage: { module: vertexModule, entryPoint: 'main' },
fragmentStage: { module: fragmentModule, entryPoint: 'main' },
layout: t.device.createPipelineLayout({ bindGroupLayouts: [] }),
primitiveTopology: 'triangle-list',
colorStates: [{ format: 'r8unorm' }],
});
// encode pass and submit
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
attachment: renderTexture.createView(),
storeOp: t.params.storeOp,
loadValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
},
],
});
pass.setPipeline(renderPipeline);
pass.draw(3, 1, 0, 0);
pass.endPass();
t.device.defaultQueue.submit([encoder.finish()]);
// expect the buffer to be clear
t.expectSingleColor(renderTexture, 'r8unorm', {
size: [1, 1, 1],
exp: { R: t.params._expected },
});
});

View file

@ -0,0 +1,145 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = '';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { attemptGarbageCollection } from '../../../common/framework/util/collect_garbage.js';
import { raceWithRejectOnTimeout } from '../../../common/framework/util/util.js';
import { GPUTest } from '../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
g.test('initial,no_descriptor').fn(t => {
const fence = t.queue.createFence();
t.expect(fence.getCompletedValue() === 0);
});
g.test('initial,empty_descriptor').fn(t => {
const fence = t.queue.createFence({});
t.expect(fence.getCompletedValue() === 0);
});
g.test('initial,descriptor_with_initialValue').fn(t => {
const fence = t.queue.createFence({ initialValue: 2 });
t.expect(fence.getCompletedValue() === 2);
});
// Promise resolves when onCompletion value is less than signal value.
g.test('wait,less_than_signaled').fn(async t => {
const fence = t.queue.createFence();
t.queue.signal(fence, 2);
await fence.onCompletion(1);
t.expect(fence.getCompletedValue() === 2);
});
// Promise resolves when onCompletion value is equal to signal value.
g.test('wait,equal_to_signaled').fn(async t => {
const fence = t.queue.createFence();
t.queue.signal(fence, 2);
await fence.onCompletion(2);
t.expect(fence.getCompletedValue() === 2);
});
// All promises resolve when signal is called once.
g.test('wait,signaled_once').fn(async t => {
const fence = t.queue.createFence();
t.queue.signal(fence, 20);
const promises = [];
for (let i = 0; i <= 20; ++i) {
promises.push(
fence.onCompletion(i).then(() => {
t.expect(fence.getCompletedValue() >= i);
})
);
}
await Promise.all(promises);
});
// Promise resolves when signal is called multiple times.
g.test('wait,signaled_multiple_times').fn(async t => {
const fence = t.queue.createFence();
t.queue.signal(fence, 1);
t.queue.signal(fence, 2);
await fence.onCompletion(2);
t.expect(fence.getCompletedValue() === 2);
});
// Promise resolves if fence has already completed.
g.test('wait,already_completed').fn(async t => {
const fence = t.queue.createFence();
t.queue.signal(fence, 2);
// Wait for value to update.
while (fence.getCompletedValue() < 2) {
await new Promise(resolve => {
requestAnimationFrame(resolve);
});
}
t.expect(fence.getCompletedValue() === 2);
await fence.onCompletion(2);
t.expect(fence.getCompletedValue() === 2);
});
// Test many calls to signal and wait on fence values one at a time.
g.test('wait,many,serially').fn(async t => {
const fence = t.queue.createFence();
for (let i = 1; i <= 20; ++i) {
t.queue.signal(fence, i);
await fence.onCompletion(i);
t.expect(fence.getCompletedValue() === i);
}
});
// Test many calls to signal and wait on all fence values.
g.test('wait,many,parallel').fn(async t => {
const fence = t.queue.createFence();
const promises = [];
for (let i = 1; i <= 20; ++i) {
t.queue.signal(fence, i);
promises.push(
fence.onCompletion(i).then(() => {
t.expect(fence.getCompletedValue() >= i);
})
);
}
await Promise.all(promises);
t.expect(fence.getCompletedValue() === 20);
});
// Test onCompletion promise resolves within a time limit.
g.test('wait,resolves_within_timeout').fn(t => {
const fence = t.queue.createFence();
t.queue.signal(fence, 2);
return raceWithRejectOnTimeout(
(async () => {
await fence.onCompletion(2);
t.expect(fence.getCompletedValue() === 2);
})(),
100,
'The fence has not been resolved within time limit.'
);
});
// Test dropping references to the fence and onCompletion promise does not crash.
g.test('drop,fence_and_promise').fn(async t => {
{
const fence = t.queue.createFence();
t.queue.signal(fence, 2);
fence.onCompletion(2);
}
await attemptGarbageCollection();
});
// Test dropping references to the fence and holding the promise does not crash.
g.test('drop,promise').fn(async t => {
let promise;
{
const fence = t.queue.createFence();
t.queue.signal(fence, 2);
promise = fence.onCompletion(2);
}
await attemptGarbageCollection();
await promise;
});

View file

@ -0,0 +1,24 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `API Operation Tests for RenderPass StoreOp.
Test Coverage Needed:
- Test that a render pass has correct output for combinations of:
- All color attachments from '0' to 'MAX_COLOR_ATTACHMENTS' with combinations of:
- storeOp set to {'clear', 'store', 'undefined}
- All color renderable formats
- mip level set to {'0', mip > '0'}
- array layer set to {'0', layer > '1'} for 2D textures
- depth slice set to {'0', slice > '0'} for 3D textures
- With and without a depthStencilAttachment that has the combinations of:
- depthStoreOp set to {'clear', 'store', 'undefined'}
- stencilStoreOp set to {'clear', 'store', 'undefined'}
- All depth/stencil formats
- mip level set to {'0', mip > '0'}
- array layer set to {'0', layer > '1'} for 2D textures
- depth slice set to {'0', slice > '0'} for 3D textures`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { GPUTest } from '../../../gpu_test.js';
export const g = makeTestGroup(GPUTest);

View file

@ -0,0 +1,165 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `Test culling and rasterizaion state.
Test coverage:
Test all culling combinations of GPUFrontFace and GPUCullMode show the correct output.
Use 2 triangles with different winding orders:
- Test that the counter-clock wise triangle has correct output for:
- All FrontFaces (ccw, cw)
- All CullModes (none, front, back)
- All depth stencil attachment types (none, depth24plus, depth32float, depth24plus-stencil8)
- Some primitive topologies (triangle-list, TODO: triangle-strip)
- Test that the clock wise triangle has correct output for:
- All FrontFaces (ccw, cw)
- All CullModes (none, front, back)
- All depth stencil attachment types (none, depth24plus, depth32float, depth24plus-stencil8)
- Some primitive topologies (triangle-list, TODO: triangle-strip)
`;
import { poptions, params } from '../../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { GPUTest } from '../../../gpu_test.js';
function faceIsCulled(face, frontFace, cullMode) {
return cullMode !== 'none' && (frontFace === face) === (cullMode === 'front');
}
function faceColor(face, frontFace, cullMode) {
// front facing color is green, non front facing is red, background is blue
const isCulled = faceIsCulled(face, frontFace, cullMode);
if (!isCulled && face === frontFace) {
return new Uint8Array([0x00, 0xff, 0x00, 0xff]);
} else if (isCulled) {
return new Uint8Array([0x00, 0x00, 0xff, 0xff]);
} else {
return new Uint8Array([0xff, 0x00, 0x00, 0xff]);
}
}
export const g = makeTestGroup(GPUTest);
g.test('culling')
.params(
params()
.combine(poptions('frontFace', ['ccw', 'cw']))
.combine(poptions('cullMode', ['none', 'front', 'back']))
.combine(
poptions('depthStencilFormat', [
null,
'depth24plus',
'depth32float',
'depth24plus-stencil8',
])
)
// TODO: test triangle-strip as well
.combine(poptions('primitiveTopology', ['triangle-list']))
)
.fn(t => {
const size = 4;
const format = 'rgba8unorm';
const texture = t.device.createTexture({
size: { width: size, height: size, depth: 1 },
format,
usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.COPY_SRC,
});
const depthTexture = t.params.depthStencilFormat
? t.device.createTexture({
size: { width: size, height: size, depth: 1 },
format: t.params.depthStencilFormat,
usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
})
: null;
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
attachment: texture.createView(),
loadValue: { r: 0.0, g: 0.0, b: 1.0, a: 1.0 },
},
],
depthStencilAttachment: depthTexture
? {
attachment: depthTexture.createView(),
depthLoadValue: 1.0,
depthStoreOp: 'store',
stencilLoadValue: 0,
stencilStoreOp: 'store',
}
: undefined,
});
// Draw two triangles with different winding orders:
// 1. The top-left one is counterclockwise (CCW)
// 2. The bottom-right one is clockwise (CW)
const vertexModule = t.makeShaderModule('vertex', {
glsl: `#version 450
const vec2 pos[6] = vec2[6](vec2(-1.0f, 1.0f),
vec2(-1.0f, 0.0f),
vec2( 0.0f, 1.0f),
vec2( 0.0f, -1.0f),
vec2( 1.0f, 0.0f),
vec2( 1.0f, -1.0f));
void main() {
gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
}`,
});
const fragmentModule = t.makeShaderModule('fragment', {
glsl: `#version 450
layout(location = 0) out vec4 fragColor;
void main() {
if (gl_FrontFacing) {
fragColor = vec4(0.0, 1.0, 0.0, 1.0);
} else {
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
}`,
});
pass.setPipeline(
t.device.createRenderPipeline({
vertexStage: { module: vertexModule, entryPoint: 'main' },
fragmentStage: { module: fragmentModule, entryPoint: 'main' },
primitiveTopology: t.params.primitiveTopology,
rasterizationState: {
frontFace: t.params.frontFace,
cullMode: t.params.cullMode,
},
colorStates: [{ format }],
depthStencilState: depthTexture ? { format: t.params.depthStencilFormat } : undefined,
})
);
pass.draw(6, 1, 0, 0);
pass.endPass();
t.device.defaultQueue.submit([encoder.finish()]);
// front facing color is green, non front facing is red, background is blue
const kCCWTriangleTopLeftColor = faceColor('ccw', t.params.frontFace, t.params.cullMode);
t.expectSinglePixelIn2DTexture(
texture,
format,
{ x: 0, y: 0 },
{ exp: kCCWTriangleTopLeftColor }
);
const kCWTriangleBottomRightColor = faceColor('cw', t.params.frontFace, t.params.cullMode);
t.expectSinglePixelIn2DTexture(
texture,
format,
{ x: size - 1, y: size - 1 },
{ exp: kCWTriangleBottomRightColor }
);
// TODO: check the contents of the depth and stencil outputs
});

View file

@ -0,0 +1,75 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = 'Test uninitialized textures are initialized to zero when copied.';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { assert, unreachable } from '../../../../common/framework/util/util.js';
import { ReadMethod, TextureZeroInitTest } from './texture_zero_init_test.js';
class CopiedTextureClearTest extends TextureZeroInitTest {
checkContentsByBufferCopy(texture, state, subresourceRange) {
for (const { level: mipLevel, slice } of subresourceRange.each()) {
assert(this.params.dimension === '2d');
this.expectSingleColor(texture, this.params.format, {
size: [this.textureWidth, this.textureHeight, 1],
dimension: this.params.dimension,
slice,
layout: { mipLevel },
exp: this.stateToTexelComponents[state],
});
}
}
checkContentsByTextureCopy(texture, state, subresourceRange) {
for (const { level, slice } of subresourceRange.each()) {
assert(this.params.dimension === '2d');
const width = this.textureWidth >> level;
const height = this.textureHeight >> level;
const dst = this.device.createTexture({
size: [width, height, 1],
format: this.params.format,
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC,
});
const commandEncoder = this.device.createCommandEncoder();
commandEncoder.copyTextureToTexture(
{ texture, mipLevel: level, origin: { x: 0, y: 0, z: slice } },
{ texture: dst, mipLevel: 0 },
{ width, height, depth: 1 }
);
this.queue.submit([commandEncoder.finish()]);
this.expectSingleColor(dst, this.params.format, {
size: [width, height, 1],
exp: this.stateToTexelComponents[state],
});
}
}
checkContents(texture, state, subresourceRange) {
switch (this.params.readMethod) {
case ReadMethod.CopyToBuffer:
this.checkContentsByBufferCopy(texture, state, subresourceRange);
break;
case ReadMethod.CopyToTexture:
this.checkContentsByTextureCopy(texture, state, subresourceRange);
break;
default:
unreachable();
}
}
}
export const g = makeTestGroup(CopiedTextureClearTest);
g.test('uninitialized_texture_is_zero')
.params(TextureZeroInitTest.generateParams([ReadMethod.CopyToBuffer, ReadMethod.CopyToTexture]))
.fn(t => {
t.run();
});

View file

@ -0,0 +1,213 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description =
'Test uninitialized textures are initialized to zero when used as a depth/stencil attachment.';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { unreachable } from '../../../../common/framework/util/util.js';
import {
ReadMethod,
TextureZeroInitTest,
initializedStateAsDepth,
initializedStateAsStencil,
} from './texture_zero_init_test.js';
class DepthStencilAttachmentClearTest extends TextureZeroInitTest {
// Construct a pipeline which will render a single triangle with depth
// equal to |initializeStateAsDepth(state)|. The depth compare function
// is set to "equal" so the fragment shader will only write 1.0 to the
// R8Unorm output if the depth buffer contains exactly the expected value.
getDepthTestReadbackPipeline(state, format, sampleCount) {
return this.device.createRenderPipeline({
vertexStage: {
entryPoint: 'main',
module: this.makeShaderModule('vertex', {
glsl: `#version 310 es
void main() {
const vec2 pos[3] = vec2[3](
vec2(-1.f, -3.f), vec2(3.f, 1.f), vec2(-1.f, 1.f));
gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
}
`,
}),
},
fragmentStage: {
entryPoint: 'main',
module: this.makeShaderModule('fragment', {
glsl: `#version 310 es
precision highp float;
layout(location = 0) out float outSuccess;
void main() {
gl_FragDepth = float(${initializedStateAsDepth(state)});
outSuccess = 1.0;
}
`,
}),
},
colorStates: [
{
format: 'r8unorm',
},
],
depthStencilState: {
format,
depthCompare: 'equal',
},
primitiveTopology: 'triangle-list',
sampleCount,
});
}
// Construct a pipeline which will render a single triangle.
// The stencil compare function is set to "equal" so the fragment shader
// will only write 1.0 to the R8Unorm output if the stencil buffer contains
// exactly the stencil reference value.
getStencilTestReadbackPipeline(format, sampleCount) {
return this.device.createRenderPipeline({
vertexStage: {
entryPoint: 'main',
module: this.makeShaderModule('vertex', {
glsl: `#version 310 es
void main() {
const vec2 pos[3] = vec2[3](
vec2(-1.f, -3.f), vec2(3.f, 1.f), vec2(-1.f, 1.f));
gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
}
`,
}),
},
fragmentStage: {
entryPoint: 'main',
module: this.makeShaderModule('fragment', {
glsl: `#version 310 es
precision highp float;
layout(location = 0) out float outSuccess;
void main() {
outSuccess = 1.0;
}
`,
}),
},
colorStates: [
{
format: 'r8unorm',
},
],
depthStencilState: {
format,
stencilFront: {
compare: 'equal',
},
stencilBack: {
compare: 'equal',
},
},
primitiveTopology: 'triangle-list',
sampleCount,
});
}
// Check the contents by running either a depth or stencil test. The test will
// render 1.0 to an R8Unorm texture if the depth/stencil buffer is equal to the expected
// value. This is done by using a depth compare function and explicitly setting the depth
// value with gl_FragDepth in the shader, or by using a stencil compare function and
// setting the stencil reference value in the render pass.
checkContents(texture, state, subresourceRange) {
for (const viewDescriptor of this.generateTextureViewDescriptorsForRendering(
this.params.aspect,
subresourceRange
)) {
const width = this.textureWidth >> viewDescriptor.baseMipLevel;
const height = this.textureHeight >> viewDescriptor.baseMipLevel;
const renderTexture = this.device.createTexture({
size: [width, height, 1],
format: 'r8unorm',
usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.COPY_SRC,
sampleCount: this.params.sampleCount,
});
let resolveTexture = undefined;
let resolveTarget = undefined;
if (this.params.sampleCount > 1) {
resolveTexture = this.device.createTexture({
size: [width, height, 1],
format: 'r8unorm',
usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.COPY_SRC,
});
resolveTarget = resolveTexture.createView();
}
const commandEncoder = this.device.createCommandEncoder();
const pass = commandEncoder.beginRenderPass({
colorAttachments: [
{
attachment: renderTexture.createView(),
resolveTarget,
loadValue: [0, 0, 0, 0],
storeOp: 'store',
},
],
depthStencilAttachment: {
attachment: texture.createView(viewDescriptor),
depthStoreOp: 'store',
depthLoadValue: 'load',
stencilStoreOp: 'store',
stencilLoadValue: 'load',
},
});
switch (this.params.readMethod) {
case ReadMethod.DepthTest:
pass.setPipeline(
this.getDepthTestReadbackPipeline(state, this.params.format, this.params.sampleCount)
);
break;
case ReadMethod.StencilTest:
pass.setPipeline(
this.getStencilTestReadbackPipeline(this.params.format, this.params.sampleCount)
);
// Set the stencil reference. The rendering pipeline uses stencil compare function "equal"
// so this pass will write 1.0 to the output only if the stencil buffer is equal to this
// reference value.
pass.setStencilReference(initializedStateAsStencil(state));
break;
default:
unreachable();
}
pass.draw(3, 1, 0, 0);
pass.endPass();
this.queue.submit([commandEncoder.finish()]);
this.expectSingleColor(resolveTexture || renderTexture, 'r8unorm', {
size: [width, height, 1],
exp: { R: 1 },
});
}
}
}
export const g = makeTestGroup(DepthStencilAttachmentClearTest);
g.test('uninitialized_texture_is_zero')
.params(TextureZeroInitTest.generateParams([ReadMethod.DepthTest, ReadMethod.StencilTest]))
.fn(t => t.run());

View file

@ -0,0 +1,199 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = 'Test uninitialized textures are initialized to zero when sampled.';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { assert } from '../../../../common/framework/util/util.js';
import { getTexelDataRepresentation } from '../../../util/texture/texelData.js';
import {
ReadMethod,
TextureZeroInitTest,
initializedStateAsFloat,
initializedStateAsSint,
initializedStateAsUint,
} from './texture_zero_init_test.js';
class SampledTextureClearTest extends TextureZeroInitTest {
getSamplingReadbackPipeline(prefix, sampleCount, dimension) {
const componentOrder = getTexelDataRepresentation(this.params.format).componentOrder;
const MS = sampleCount > 1 ? 'MS' : '';
const XD = dimension.toUpperCase();
const componentCount = componentOrder.length;
const indexExpression =
componentCount === 1
? componentOrder[0].toLowerCase()
: componentOrder.map(c => c.toLowerCase()).join('') + '[i]';
const glsl = `#version 310 es
precision highp float;
precision highp ${prefix}texture${XD}${MS};
precision highp sampler;
layout(set = 0, binding = 0, std140) uniform Constants {
int level;
};
layout(set = 0, binding = 1) uniform ${prefix}texture${XD}${MS} myTexture;
layout(set = 0, binding = 2) uniform sampler mySampler;
layout(set = 0, binding = 3, std430) writeonly buffer Result {
uint result[];
};
void writeResult(uint flatIndex, uvec4 texel) {
for (uint i = flatIndex; i < flatIndex + ${componentCount}u; ++i) {
result[i] = texel.${indexExpression};
}
}
void writeResult(uint flatIndex, ivec4 texel) {
for (uint i = flatIndex; i < flatIndex + ${componentCount}u; ++i) {
result[i] = uint(texel.${indexExpression});
}
}
void writeResult(uint flatIndex, vec4 texel) {
for (uint i = flatIndex; i < flatIndex + ${componentCount}u; ++i) {
result[i] = floatBitsToUint(texel.${indexExpression});
}
}
void main() {
uint flatIndex = gl_NumWorkGroups.x * gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;
flatIndex = flatIndex * ${componentCount}u;
writeResult(flatIndex, texelFetch(
${prefix}sampler${XD}${MS}(myTexture, mySampler),
ivec2(gl_GlobalInvocationID.xy), level));
}
`;
return this.device.createComputePipeline({
layout: undefined,
computeStage: {
entryPoint: 'main',
module: this.makeShaderModule('compute', { glsl }),
},
});
}
checkContents(texture, state, subresourceRange) {
assert(this.params.dimension === '2d');
const sampler = this.device.createSampler();
for (const { level, slices } of subresourceRange.mipLevels()) {
const width = this.textureWidth >> level;
const height = this.textureHeight >> level;
let readbackTypedArray = Float32Array;
let prefix = '';
let expectedShaderValue = initializedStateAsFloat(state);
if (this.params.format.indexOf('sint') !== -1) {
prefix = 'i';
expectedShaderValue = initializedStateAsSint(state);
readbackTypedArray = Int32Array;
} else if (this.params.format.indexOf('uint') !== -1) {
prefix = 'u';
expectedShaderValue = initializedStateAsUint(state);
readbackTypedArray = Uint32Array;
}
const computePipeline = this.getSamplingReadbackPipeline(
prefix,
this.params.sampleCount,
this.params.dimension
);
for (const slice of slices) {
const [ubo, uboMapping] = this.device.createBufferMapped({
size: 4,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
new Int32Array(uboMapping, 0, 1)[0] = level;
ubo.unmap();
const byteLength =
width *
height *
Uint32Array.BYTES_PER_ELEMENT *
getTexelDataRepresentation(this.params.format).componentOrder.length;
const resultBuffer = this.device.createBuffer({
size: byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
});
const bindGroup = this.device.createBindGroup({
layout: computePipeline.getBindGroupLayout(0),
entries: [
{
binding: 0,
resource: { buffer: ubo },
},
{
binding: 1,
resource: texture.createView({
baseMipLevel: 0,
mipLevelCount: this.params.mipLevelCount,
baseArrayLayer: slice,
arrayLayerCount: 1,
}),
},
{ binding: 2, resource: sampler },
{
binding: 3,
resource: {
buffer: resultBuffer,
},
},
],
});
const commandEncoder = this.device.createCommandEncoder();
const pass = commandEncoder.beginComputePass();
pass.setPipeline(computePipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatch(width, height);
pass.endPass();
this.queue.submit([commandEncoder.finish()]);
ubo.destroy();
const mappedResultBuffer = this.createCopyForMapRead(resultBuffer, 0, byteLength);
resultBuffer.destroy();
this.eventualAsyncExpectation(async niceStack => {
const actual = await mappedResultBuffer.mapReadAsync();
const expected = new readbackTypedArray(new ArrayBuffer(actual.byteLength));
expected.fill(expectedShaderValue);
// TODO: Have a better way to determine approximately equal, maybe in ULPs.
let tolerance;
if (this.params.format === 'rgb10a2unorm') {
tolerance = i => {
// The alpha component is only two bits. Use a generous tolerance.
return i % 4 === 3 ? 0.18 : 0.01;
};
} else {
tolerance = 0.01;
}
const check = this.checkBuffer(new readbackTypedArray(actual), expected, tolerance);
if (check !== undefined) {
niceStack.message = check;
this.rec.expectationFailed(niceStack);
}
mappedResultBuffer.destroy();
});
}
}
}
}
export const g = makeTestGroup(SampledTextureClearTest);
g.test('uninitialized_texture_is_zero')
.params(TextureZeroInitTest.generateParams([ReadMethod.Sample]))
.fn(t => t.run());

View file

@ -0,0 +1,552 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { params, poptions, pbool } from '../../../../common/framework/params_builder.js';
import { assert, unreachable } from '../../../../common/framework/util/util.js';
import { kTextureAspects, kTextureFormatInfo, kTextureFormats } from '../../../capability_info.js';
import { GPUTest } from '../../../gpu_test.js';
import { createTextureUploadBuffer } from '../../../util/texture/layout.js';
import { SubresourceRange } from '../../../util/texture/subresource.js';
import { getTexelDataRepresentation } from '../../../util/texture/texelData.js';
var UninitializeMethod;
(function (UninitializeMethod) {
UninitializeMethod['Creation'] = 'Creation';
UninitializeMethod['StoreOpClear'] = 'StoreOpClear';
})(UninitializeMethod || (UninitializeMethod = {}));
const kUninitializeMethods = Object.keys(UninitializeMethod);
export let ReadMethod;
(function (ReadMethod) {
ReadMethod['Sample'] = 'Sample';
ReadMethod['CopyToBuffer'] = 'CopyToBuffer';
ReadMethod['CopyToTexture'] = 'CopyToTexture';
ReadMethod['DepthTest'] = 'DepthTest';
ReadMethod['StencilTest'] = 'StencilTest';
ReadMethod['ColorBlending'] = 'ColorBlending';
ReadMethod['Storage'] = 'Storage';
})(ReadMethod || (ReadMethod = {}));
const kMipLevelCounts = [1, 5];
// For each mip level count, define the mip ranges to leave uninitialized.
const kUninitializedMipRangesToTest = {
1: [{ begin: 0, end: 1 }], // Test the only mip
5: [
{ begin: 0, end: 2 },
{ begin: 3, end: 4 },
],
// Test a range and a single mip
};
// Test with these sample counts.
const kSampleCounts = [1, 4];
// Test with these slice counts. This means the depth of a 3d texture or the number
// or layers in a 2D or a 1D texture array.
// For each slice count, define the slices to leave uninitialized.
const kUninitializedSliceRangesToTest = {
1: [{ begin: 0, end: 1 }], // Test the only slice
7: [
{ begin: 2, end: 4 },
{ begin: 6, end: 7 },
],
// Test a range and a single slice
};
// Test with these combinations of texture dimension and sliceCount.
const kCreationSizes = [
// { dimension: '1d', sliceCount: 7 }, // TODO: 1d textures
{ dimension: '2d', sliceCount: 1 }, // 2d textures
{ dimension: '2d', sliceCount: 7 }, // 2d array textures
// { dimension: '3d', sliceCount: 7 }, // TODO: 3d textures
];
// Enums to abstract over color / depth / stencil values in textures. Depending on the texture format,
// the data for each value may have a different representation. These enums are converted to a
// representation such that their values can be compared. ex.) An integer is needed to upload to an
// unsigned normalized format, but its value is read as a float in the shader.
export let InitializedState;
(function (InitializedState) {
InitializedState[(InitializedState['Canary'] = 0)] = 'Canary';
InitializedState[(InitializedState['Zero'] = 1)] = 'Zero';
})(InitializedState || (InitializedState = {}));
export function initializedStateAsFloat(state) {
switch (state) {
case InitializedState.Zero:
return 0;
case InitializedState.Canary:
return 1;
default:
unreachable();
}
}
export function initializedStateAsUint(state) {
switch (state) {
case InitializedState.Zero:
return 0;
case InitializedState.Canary:
return 255;
default:
unreachable();
}
}
export function initializedStateAsSint(state) {
switch (state) {
case InitializedState.Zero:
return 0;
case InitializedState.Canary:
return -1;
default:
unreachable();
}
}
export function initializedStateAsColor(state, format) {
let value;
if (format.indexOf('uint') !== -1) {
value = initializedStateAsUint(state);
} else if (format.indexOf('sint') !== -1) {
value = initializedStateAsSint(state);
} else {
value = initializedStateAsFloat(state);
}
return [value, value, value, value];
}
export function initializedStateAsDepth(state) {
switch (state) {
case InitializedState.Zero:
return 0;
case InitializedState.Canary:
return 1;
default:
unreachable();
}
}
export function initializedStateAsStencil(state) {
switch (state) {
case InitializedState.Zero:
return 0;
case InitializedState.Canary:
return 42;
default:
unreachable();
}
}
function getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMethod) {
let usage = GPUTextureUsage.COPY_DST;
switch (uninitializeMethod) {
case UninitializeMethod.Creation:
break;
case UninitializeMethod.StoreOpClear:
usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;
break;
default:
unreachable();
}
switch (readMethod) {
case ReadMethod.CopyToBuffer:
case ReadMethod.CopyToTexture:
usage |= GPUTextureUsage.COPY_SRC;
break;
case ReadMethod.Sample:
usage |= GPUTextureUsage.SAMPLED;
break;
case ReadMethod.Storage:
usage |= GPUTextureUsage.STORAGE;
break;
case ReadMethod.DepthTest:
case ReadMethod.StencilTest:
case ReadMethod.ColorBlending:
usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;
break;
default:
unreachable();
}
if (sampleCount > 1) {
// Copies to multisampled textures are not allowed. We need OutputAttachment to initialize
// canary data in multisampled textures.
usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;
}
if (!kTextureFormatInfo[format].copyable) {
// Copies are not possible. We need OutputAttachment to initialize
// canary data.
assert(kTextureFormatInfo[format].renderable);
usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;
}
return usage;
}
export class TextureZeroInitTest extends GPUTest {
constructor(rec, params) {
super(rec, params);
_defineProperty(this, 'stateToTexelComponents', void 0);
const stateToTexelComponents = state => {
const [R, G, B, A] = initializedStateAsColor(state, this.params.format);
return {
R,
G,
B,
A,
Depth: initializedStateAsDepth(state),
Stencil: initializedStateAsStencil(state),
};
};
this.stateToTexelComponents = {
[InitializedState.Zero]: stateToTexelComponents(InitializedState.Zero),
[InitializedState.Canary]: stateToTexelComponents(InitializedState.Canary),
};
}
get params() {
return super.params;
}
get textureWidth() {
let width = 1 << this.params.mipLevelCount;
if (this.params.nonPowerOfTwo) {
width = 2 * width - 1;
}
return width;
}
get textureHeight() {
let height = 1 << this.params.mipLevelCount;
if (this.params.nonPowerOfTwo) {
height = 2 * height - 1;
}
return height;
}
// Used to iterate subresources and check that their uninitialized contents are zero when accessed
*iterateUninitializedSubresources() {
for (const mipRange of kUninitializedMipRangesToTest[this.params.mipLevelCount]) {
for (const sliceRange of kUninitializedSliceRangesToTest[this.params.sliceCount]) {
yield new SubresourceRange({ mipRange, sliceRange });
}
}
}
// Used to iterate and initialize other subresources not checked for zero-initialization.
// Zero-initialization of uninitialized subresources should not have side effects on already
// initialized subresources.
*iterateInitializedSubresources() {
const uninitialized = new Array(this.params.mipLevelCount);
for (let level = 0; level < uninitialized.length; ++level) {
uninitialized[level] = new Array(this.params.sliceCount);
}
for (const subresources of this.iterateUninitializedSubresources()) {
for (const { level, slice } of subresources.each()) {
uninitialized[level][slice] = true;
}
}
for (let level = 0; level < uninitialized.length; ++level) {
for (let slice = 0; slice < uninitialized[level].length; ++slice) {
if (!uninitialized[level][slice]) {
yield new SubresourceRange({
mipRange: { begin: level, count: 1 },
sliceRange: { begin: slice, count: 1 },
});
}
}
}
}
*generateTextureViewDescriptorsForRendering(aspect, subresourceRange) {
const viewDescriptor = {
dimension: '2d',
aspect,
};
if (subresourceRange === undefined) {
return viewDescriptor;
}
for (const { level, slice } of subresourceRange.each()) {
yield {
...viewDescriptor,
baseMipLevel: level,
mipLevelCount: 1,
baseArrayLayer: slice,
arrayLayerCount: 1,
};
}
}
initializeWithStoreOp(state, texture, subresourceRange) {
const commandEncoder = this.device.createCommandEncoder();
for (const viewDescriptor of this.generateTextureViewDescriptorsForRendering(
this.params.aspect,
subresourceRange
)) {
if (kTextureFormatInfo[this.params.format].color) {
commandEncoder
.beginRenderPass({
colorAttachments: [
{
attachment: texture.createView(viewDescriptor),
storeOp: 'store',
loadValue: initializedStateAsColor(state, this.params.format),
},
],
})
.endPass();
} else {
commandEncoder
.beginRenderPass({
colorAttachments: [],
depthStencilAttachment: {
attachment: texture.createView(viewDescriptor),
depthStoreOp: 'store',
depthLoadValue: initializedStateAsDepth(state),
stencilStoreOp: 'store',
stencilLoadValue: initializedStateAsStencil(state),
},
})
.endPass();
}
}
this.queue.submit([commandEncoder.finish()]);
}
initializeWithCopy(texture, state, subresourceRange) {
if (this.params.dimension === '1d' || this.params.dimension === '3d') {
// TODO: https://github.com/gpuweb/gpuweb/issues/69
// Copies with 1D and 3D textures are not yet specified
unreachable();
}
const firstSubresource = subresourceRange.each().next().value;
assert(typeof firstSubresource !== 'undefined');
const largestWidth = this.textureWidth >> firstSubresource.level;
const largestHeight = this.textureHeight >> firstSubresource.level;
const texelData = new Uint8Array(
getTexelDataRepresentation(this.params.format).getBytes(this.stateToTexelComponents[state])
);
const { buffer, bytesPerRow, rowsPerImage } = createTextureUploadBuffer(
texelData,
this.device,
this.params.format,
this.params.dimension,
[largestWidth, largestHeight, 1]
);
const commandEncoder = this.device.createCommandEncoder();
for (const { level, slice } of subresourceRange.each()) {
const width = this.textureWidth >> level;
const height = this.textureHeight >> level;
commandEncoder.copyBufferToTexture(
{
buffer,
bytesPerRow,
rowsPerImage,
},
{ texture, mipLevel: level, origin: { x: 0, y: 0, z: slice } },
{ width, height, depth: 1 }
);
}
this.queue.submit([commandEncoder.finish()]);
buffer.destroy();
}
initializeTexture(texture, state, subresourceRange) {
if (this.params.sampleCount > 1 || !kTextureFormatInfo[this.params.format].copyable) {
// Copies to multisampled textures not yet specified.
// Use a storeOp for now.
assert(kTextureFormatInfo[this.params.format].renderable);
this.initializeWithStoreOp(state, texture, subresourceRange);
} else {
this.initializeWithCopy(texture, state, subresourceRange);
}
}
discardTexture(texture, subresourceRange) {
const commandEncoder = this.device.createCommandEncoder();
for (const desc of this.generateTextureViewDescriptorsForRendering(
this.params.aspect,
subresourceRange
)) {
if (kTextureFormatInfo[this.params.format].color) {
commandEncoder
.beginRenderPass({
colorAttachments: [
{
attachment: texture.createView(desc),
storeOp: 'clear',
loadValue: 'load',
},
],
})
.endPass();
} else {
commandEncoder
.beginRenderPass({
colorAttachments: [],
depthStencilAttachment: {
attachment: texture.createView(desc),
depthStoreOp: 'clear',
depthLoadValue: 'load',
stencilStoreOp: 'clear',
stencilLoadValue: 'load',
},
})
.endPass();
}
}
this.queue.submit([commandEncoder.finish()]);
}
static generateParams(readMethods) {
return (
// TODO: Consider making a list of "valid" texture descriptors in capability_info.
params()
.combine(poptions('format', kTextureFormats))
.combine(poptions('aspect', kTextureAspects))
.unless(
({ format, aspect }) =>
(aspect === 'depth-only' && !kTextureFormatInfo[format].depth) ||
(aspect === 'stencil-only' && !kTextureFormatInfo[format].stencil)
)
.combine(poptions('mipLevelCount', kMipLevelCounts))
.combine(poptions('sampleCount', kSampleCounts))
// Multisampled textures may only have one mip
.unless(({ sampleCount, mipLevelCount }) => sampleCount > 1 && mipLevelCount > 1)
.combine(poptions('uninitializeMethod', kUninitializeMethods))
.combine(poptions('readMethod', readMethods))
.unless(
({ readMethod, format }) =>
// It doesn't make sense to copy from a packed depth format.
// This is not specified yet, but it will probably be disallowed as the bits may
// be vendor-specific.
// TODO: Test copying out of the stencil aspect.
(readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture) &&
(format === 'depth24plus' || format === 'depth24plus-stencil8')
)
.unless(
({ readMethod, format }) =>
(readMethod === ReadMethod.DepthTest && !kTextureFormatInfo[format].depth) ||
(readMethod === ReadMethod.StencilTest && !kTextureFormatInfo[format].stencil) ||
(readMethod === ReadMethod.ColorBlending && !kTextureFormatInfo[format].color) ||
// TODO: Test with depth sampling
(readMethod === ReadMethod.Sample && kTextureFormatInfo[format].depth)
)
.unless(
({ readMethod, sampleCount }) =>
// We can only read from multisampled textures by sampling.
sampleCount > 1 &&
(readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture)
)
.combine(kCreationSizes)
// Multisampled 3D / 2D array textures not supported.
.unless(({ sampleCount, sliceCount }) => sampleCount > 1 && sliceCount > 1)
.filter(({ format, sampleCount, uninitializeMethod, readMethod }) => {
const usage = getRequiredTextureUsage(
format,
sampleCount,
uninitializeMethod,
readMethod
);
if (usage & GPUTextureUsage.OUTPUT_ATTACHMENT && !kTextureFormatInfo[format].renderable) {
return false;
}
if (usage & GPUTextureUsage.STORAGE && !kTextureFormatInfo[format].storage) {
return false;
}
return true;
})
.combine(pbool('nonPowerOfTwo'))
);
}
run() {
const {
format,
dimension,
mipLevelCount,
sliceCount,
sampleCount,
uninitializeMethod,
readMethod,
} = this.params;
const usage = getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMethod);
const texture = this.device.createTexture({
size: [this.textureWidth, this.textureHeight, sliceCount],
format,
dimension,
usage,
mipLevelCount,
sampleCount,
});
// Initialize some subresources with canary values
for (const subresourceRange of this.iterateInitializedSubresources()) {
this.initializeTexture(texture, InitializedState.Canary, subresourceRange);
}
switch (uninitializeMethod) {
case UninitializeMethod.Creation:
break;
case UninitializeMethod.StoreOpClear:
// Initialize the rest of the resources.
for (const subresourceRange of this.iterateUninitializedSubresources()) {
this.initializeTexture(texture, InitializedState.Canary, subresourceRange);
}
// Then use a store op to discard their contents.
for (const subresourceRange of this.iterateUninitializedSubresources()) {
this.discardTexture(texture, subresourceRange);
}
break;
default:
unreachable();
}
// Check that all uninitialized resources are zero.
for (const subresourceRange of this.iterateUninitializedSubresources()) {
this.checkContents(texture, InitializedState.Zero, subresourceRange);
}
// Check the all other resources are unchanged.
for (const subresourceRange of this.iterateInitializedSubresources()) {
this.checkContents(texture, InitializedState.Canary, subresourceRange);
}
}
}

View file

@ -0,0 +1,303 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
createBindGroup validation tests.
`;
import { poptions, params } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { unreachable } from '../../../common/framework/util/util.js';
import {
kBindingTypes,
kBindingTypeInfo,
kBindableResources,
kTextureUsages,
kTextureBindingTypes,
kTextureBindingTypeInfo,
} from '../../capability_info.js';
import { ValidationTest } from './validation_test.js';
function clone(descriptor) {
return JSON.parse(JSON.stringify(descriptor));
}
export const g = makeTestGroup(ValidationTest);
g.test('binding_count_mismatch').fn(async t => {
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }],
});
const goodDescriptor = {
entries: [{ binding: 0, resource: { buffer: t.getStorageBuffer() } }],
layout: bindGroupLayout,
};
// Control case
t.device.createBindGroup(goodDescriptor);
// Another binding is not expected.
const badDescriptor = {
entries: [
{ binding: 0, resource: { buffer: t.getStorageBuffer() } },
// Another binding is added.
{ binding: 1, resource: { buffer: t.getStorageBuffer() } },
],
layout: bindGroupLayout,
};
t.expectValidationError(() => {
t.device.createBindGroup(badDescriptor);
});
});
g.test('binding_must_be_present_in_layout').fn(async t => {
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }],
});
const goodDescriptor = {
entries: [{ binding: 0, resource: { buffer: t.getStorageBuffer() } }],
layout: bindGroupLayout,
};
// Control case
t.device.createBindGroup(goodDescriptor);
// Binding index 0 must be present.
const badDescriptor = {
entries: [{ binding: 1, resource: { buffer: t.getStorageBuffer() } }],
layout: bindGroupLayout,
};
t.expectValidationError(() => {
t.device.createBindGroup(badDescriptor);
});
});
g.test('buffer_binding_must_contain_exactly_one_buffer_of_its_type')
.params(
params()
.combine(poptions('bindingType', kBindingTypes))
.combine(poptions('resourceType', kBindableResources))
)
.fn(t => {
const { bindingType, resourceType } = t.params;
const info = kBindingTypeInfo[bindingType];
const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined;
const layout = t.device.createBindGroupLayout({
entries: [
{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: bindingType, storageTextureFormat },
],
});
const resource = t.getBindingResource(resourceType);
const resourceBindingMatches = info.resource === resourceType;
t.expectValidationError(() => {
t.device.createBindGroup({ layout, entries: [{ binding: 0, resource }] });
}, !resourceBindingMatches);
});
g.test('texture_binding_must_have_correct_usage')
.params(
params()
.combine(poptions('type', kTextureBindingTypes))
.combine(poptions('usage', kTextureUsages))
)
.fn(async t => {
const { type, usage } = t.params;
const info = kTextureBindingTypeInfo[type];
const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined;
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type, storageTextureFormat }],
});
const descriptor = {
size: { width: 16, height: 16, depth: 1 },
format: 'rgba8unorm',
usage,
};
const shouldError = usage !== info.usage;
t.expectValidationError(() => {
t.device.createBindGroup({
entries: [{ binding: 0, resource: t.device.createTexture(descriptor).createView() }],
layout: bindGroupLayout,
});
}, shouldError);
});
g.test('texture_must_have_correct_component_type')
.params(poptions('textureComponentType', ['float', 'sint', 'uint']))
.fn(async t => {
const { textureComponentType } = t.params;
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
type: 'sampled-texture',
textureComponentType,
},
],
});
// TODO: Test more texture component types.
let format;
if (textureComponentType === 'float') {
format = 'r8unorm';
} else if (textureComponentType === 'sint') {
format = 'r8sint';
} else if (textureComponentType === 'uint') {
format = 'r8uint';
} else {
unreachable('Unexpected texture component type');
}
const goodDescriptor = {
size: { width: 16, height: 16, depth: 1 },
format,
usage: GPUTextureUsage.SAMPLED,
};
// Control case
t.device.createBindGroup({
entries: [
{
binding: 0,
resource: t.device.createTexture(goodDescriptor).createView(),
},
],
layout: bindGroupLayout,
});
function* mismatchedTextureFormats() {
if (textureComponentType !== 'float') {
yield 'r8unorm';
}
if (textureComponentType !== 'sint') {
yield 'r8sint';
}
if (textureComponentType !== 'uint') {
yield 'r8uint';
}
}
// Mismatched texture binding formats are not valid.
for (const mismatchedTextureFormat of mismatchedTextureFormats()) {
const badDescriptor = clone(goodDescriptor);
badDescriptor.format = mismatchedTextureFormat;
t.expectValidationError(() => {
t.device.createBindGroup({
entries: [{ binding: 0, resource: t.device.createTexture(badDescriptor).createView() }],
layout: bindGroupLayout,
});
});
}
});
// TODO: Write test for all dimensions.
g.test('texture_must_have_correct_dimension').fn(async t => {
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
type: 'sampled-texture',
viewDimension: '2d',
},
],
});
const goodDescriptor = {
size: { width: 16, height: 16, depth: 1 },
format: 'rgba8unorm',
usage: GPUTextureUsage.SAMPLED,
};
// Control case
t.device.createBindGroup({
entries: [{ binding: 0, resource: t.device.createTexture(goodDescriptor).createView() }],
layout: bindGroupLayout,
});
// Mismatched texture binding formats are not valid.
const badDescriptor = clone(goodDescriptor);
badDescriptor.size.depth = 2;
t.expectValidationError(() => {
t.device.createBindGroup({
entries: [{ binding: 0, resource: t.device.createTexture(badDescriptor).createView() }],
layout: bindGroupLayout,
});
});
});
g.test('buffer_offset_and_size_for_bind_groups_match')
.params([
{ offset: 0, size: 512, _success: true }, // offset 0 is valid
{ offset: 256, size: 256, _success: true }, // offset 256 (aligned) is valid
// Touching the end of the buffer
{ offset: 0, size: 1024, _success: true },
{ offset: 0, size: undefined, _success: true },
{ offset: 256 * 3, size: 256, _success: true },
{ offset: 256 * 3, size: undefined, _success: true },
// Zero-sized bindings
{ offset: 0, size: 0, _success: true },
{ offset: 256, size: 0, _success: true },
{ offset: 1024, size: 0, _success: true },
{ offset: 1024, size: undefined, _success: true },
// Unaligned buffer offset is invalid
{ offset: 1, size: 256, _success: false },
{ offset: 1, size: undefined, _success: false },
{ offset: 128, size: 256, _success: false },
{ offset: 255, size: 256, _success: false },
// Out-of-bounds
{ offset: 256 * 5, size: 0, _success: false }, // offset is OOB
{ offset: 0, size: 256 * 5, _success: false }, // size is OOB
{ offset: 1024, size: 1, _success: false }, // offset+size is OOB
])
.fn(async t => {
const { offset, size, _success } = t.params;
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }],
});
const buffer = t.device.createBuffer({
size: 1024,
usage: GPUBufferUsage.STORAGE,
});
const descriptor = {
entries: [
{
binding: 0,
resource: { buffer, offset, size },
},
],
layout: bindGroupLayout,
};
if (_success) {
// Control case
t.device.createBindGroup(descriptor);
} else {
// Buffer offset and/or size don't match in bind groups.
t.expectValidationError(() => {
t.device.createBindGroup(descriptor);
});
}
});

View file

@ -0,0 +1,335 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
createBindGroupLayout validation tests.
`;
import { pbool, poptions, params } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import {
kBindingTypeInfo,
kBindingTypes,
kBufferBindingTypeInfo,
kMaxBindingsPerBindGroup,
kShaderStages,
kShaderStageCombinations,
kTextureBindingTypeInfo,
kTextureComponentTypes,
kTextureViewDimensions,
} from '../../capability_info.js';
import { ValidationTest } from './validation_test.js';
function clone(descriptor) {
return JSON.parse(JSON.stringify(descriptor));
}
export const g = makeTestGroup(ValidationTest);
g.test('some_binding_index_was_specified_more_than_once').fn(async t => {
const goodDescriptor = {
entries: [
{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' },
{ binding: 1, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' },
],
};
// Control case
t.device.createBindGroupLayout(goodDescriptor);
const badDescriptor = clone(goodDescriptor);
badDescriptor.entries[1].binding = 0;
// Binding index 0 can't be specified twice.
t.expectValidationError(() => {
t.device.createBindGroupLayout(badDescriptor);
});
});
g.test('visibility_and_dynamic_offsets')
.params(
params()
.combine(poptions('type', kBindingTypes))
.combine(pbool('hasDynamicOffset'))
.combine(poptions('visibility', kShaderStageCombinations))
)
.fn(async t => {
const { type, hasDynamicOffset, visibility } = t.params;
const info = kBindingTypeInfo[type];
const supportsDynamicOffset = kBindingTypeInfo[type].perPipelineLimitClass.maxDynamic > 0;
let success = true;
if (!supportsDynamicOffset && hasDynamicOffset) success = false;
if ((visibility & ~info.validStages) !== 0) success = false;
// When hasDynamicOffset is false, it actually tests visibility.
t.expectValidationError(() => {
t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility, type, hasDynamicOffset }],
});
}, !success);
});
g.test('min_buffer_binding_size')
.params(
params()
.combine(poptions('type', kBindingTypes))
.combine(poptions('minBufferBindingSize', [undefined, 0, 4]))
)
.fn(async t => {
const { type, minBufferBindingSize } = t.params;
let success = false;
if (
minBufferBindingSize === undefined ||
minBufferBindingSize === 0 ||
type in kBufferBindingTypeInfo
) {
success = true;
}
t.expectValidationError(() => {
t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type, minBufferBindingSize }],
});
}, !success);
});
g.test('view_dimension')
.params(
params()
.combine(poptions('type', kBindingTypes))
.combine(poptions('viewDimension', [undefined, ...kTextureViewDimensions]))
)
.fn(async t => {
const { type, viewDimension } = t.params;
const success = viewDimension === undefined || type in kTextureBindingTypeInfo;
t.expectValidationError(() => {
t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type, viewDimension }],
});
}, !success);
});
g.test('texture_component_type')
.params(
params()
.combine(poptions('type', kBindingTypes))
.combine(poptions('textureComponentType', [undefined, ...kTextureComponentTypes]))
)
.fn(async t => {
const { type, textureComponentType } = t.params;
const success =
textureComponentType === undefined || kBindingTypeInfo[type].resource === 'sampledTex';
t.expectValidationError(() => {
t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type, textureComponentType }],
});
}, !success);
});
g.test('multisampled')
.params(
params()
.combine(poptions('type', kBindingTypes))
.combine(poptions('multisampled', [undefined, false, true]))
)
.fn(async t => {
const { type, multisampled } = t.params;
const success = multisampled === false || kBindingTypeInfo[type].resource === 'sampledTex';
t.expectValidationError(() => {
t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type, multisampled }],
});
}, !success);
});
g.test('storage_texture_format')
.params(
params()
.combine(poptions('type', kBindingTypes))
.combine(poptions('storageTextureFormat', [undefined, 'rgba8unorm']))
)
.fn(async t => {
const { type, storageTextureFormat } = t.params;
const success =
storageTextureFormat === undefined || kBindingTypeInfo[type].resource === 'storageTex';
t.expectValidationError(() => {
t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type, storageTextureFormat }],
});
}, !success);
});
g.test('number_of_dynamic_buffers_exceeds_the_maximum_value')
.params([
{ type: 'storage-buffer', maxDynamicBufferCount: 4 },
{ type: 'uniform-buffer', maxDynamicBufferCount: 8 },
])
.fn(async t => {
const { type, maxDynamicBufferCount } = t.params;
const maxDynamicBufferBindings = [];
for (let i = 0; i < maxDynamicBufferCount; i++) {
maxDynamicBufferBindings.push({
binding: i,
visibility: GPUShaderStage.COMPUTE,
type,
hasDynamicOffset: true,
});
}
const goodDescriptor = {
entries: [
...maxDynamicBufferBindings,
{
binding: maxDynamicBufferBindings.length,
visibility: GPUShaderStage.COMPUTE,
type,
hasDynamicOffset: false,
},
],
};
// Control case
t.device.createBindGroupLayout(goodDescriptor);
// Dynamic buffers exceed maximum in a bind group layout.
const badDescriptor = clone(goodDescriptor);
badDescriptor.entries[maxDynamicBufferCount].hasDynamicOffset = true;
t.expectValidationError(() => {
t.device.createBindGroupLayout(badDescriptor);
});
});
// One bind group layout will be filled with kPerStageBindingLimit[...] of the type |type|.
// For each item in the array returned here, a case will be generated which tests a pipeline
// layout with one extra bind group layout with one extra binding. That extra binding will have:
//
// - If extraTypeSame, any of the binding types which counts toward the same limit as |type|.
// (i.e. 'storage-buffer' <-> 'readonly-storage-buffer').
// - Otherwise, an arbitrary other type.
function* pickExtraBindingTypes(bindingType, extraTypeSame) {
const info = kBindingTypeInfo[bindingType];
if (extraTypeSame) {
for (const extraBindingType of kBindingTypes) {
if (
info.perStageLimitClass.class ===
kBindingTypeInfo[extraBindingType].perStageLimitClass.class
) {
yield extraBindingType;
}
}
} else {
yield info.perStageLimitClass.class === 'sampler' ? 'sampled-texture' : 'sampler';
}
}
const kCasesForMaxResourcesPerStageTests = params()
.combine(poptions('maxedType', kBindingTypes))
.combine(poptions('maxedVisibility', kShaderStages))
.filter(p => (kBindingTypeInfo[p.maxedType].validStages & p.maxedVisibility) !== 0)
.expand(function* (p) {
for (const extraTypeSame of [true, false]) {
yield* poptions('extraType', pickExtraBindingTypes(p.maxedType, extraTypeSame));
}
})
.combine(poptions('extraVisibility', kShaderStages))
.filter(p => (kBindingTypeInfo[p.extraType].validStages & p.extraVisibility) !== 0);
// Should never fail unless kMaxBindingsPerBindGroup is exceeded, because the validation for
// resources-of-type-per-stage is in pipeline layout creation.
g.test('max_resources_per_stage,in_bind_group_layout')
.params(kCasesForMaxResourcesPerStageTests)
.fn(async t => {
const { maxedType, extraType, maxedVisibility, extraVisibility } = t.params;
const maxedTypeInfo = kBindingTypeInfo[maxedType];
const maxedCount = maxedTypeInfo.perStageLimitClass.max;
const extraTypeInfo = kBindingTypeInfo[extraType];
const maxResourceBindings = [];
for (let i = 0; i < maxedCount; i++) {
maxResourceBindings.push({
binding: i,
visibility: maxedVisibility,
type: maxedType,
storageTextureFormat: maxedTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined,
});
}
const goodDescriptor = { entries: maxResourceBindings };
// Control
t.device.createBindGroupLayout(goodDescriptor);
const newDescriptor = clone(goodDescriptor);
newDescriptor.entries.push({
binding: maxedCount,
visibility: extraVisibility,
type: extraType,
storageTextureFormat: extraTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined,
});
const shouldError = maxedCount >= kMaxBindingsPerBindGroup;
t.expectValidationError(() => {
t.device.createBindGroupLayout(newDescriptor);
}, shouldError);
});
// One pipeline layout can have a maximum number of each type of binding *per stage* (which is
// different for each type). Test that the max works, then add one more binding of same-or-different
// type and same-or-different visibility.
g.test('max_resources_per_stage,in_pipeline_layout')
.params(kCasesForMaxResourcesPerStageTests)
.fn(async t => {
const { maxedType, extraType, maxedVisibility, extraVisibility } = t.params;
const maxedTypeInfo = kBindingTypeInfo[maxedType];
const maxedCount = maxedTypeInfo.perStageLimitClass.max;
const extraTypeInfo = kBindingTypeInfo[extraType];
const maxResourceBindings = [];
for (let i = 0; i < maxedCount; i++) {
maxResourceBindings.push({
binding: i,
visibility: maxedVisibility,
type: maxedType,
storageTextureFormat: maxedTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined,
});
}
const goodLayout = t.device.createBindGroupLayout({ entries: maxResourceBindings });
// Control
t.device.createPipelineLayout({ bindGroupLayouts: [goodLayout] });
const extraLayout = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: extraVisibility,
type: extraType,
storageTextureFormat: extraTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined,
},
],
});
// Some binding types use the same limit, e.g. 'storage-buffer' and 'readonly-storage-buffer'.
const newBindingCountsTowardSamePerStageLimit =
(maxedVisibility & extraVisibility) !== 0 &&
kBindingTypeInfo[maxedType].perStageLimitClass.class ===
kBindingTypeInfo[extraType].perStageLimitClass.class;
const layoutExceedsPerStageLimit = newBindingCountsTowardSamePerStageLimit;
t.expectValidationError(() => {
t.device.createPipelineLayout({ bindGroupLayouts: [goodLayout, extraLayout] });
}, layoutExceedsPerStageLimit);
});

View file

@ -0,0 +1,95 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
createPipelineLayout validation tests.
`;
import { poptions, params } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { kBindingTypeInfo } from '../../capability_info.js';
import { ValidationTest } from './validation_test.js';
function clone(descriptor) {
return JSON.parse(JSON.stringify(descriptor));
}
export const g = makeTestGroup(ValidationTest);
g.test('number_of_dynamic_buffers_exceeds_the_maximum_value')
.params(
params()
.combine(poptions('visibility', [0, 2, 4, 6]))
.combine(poptions('type', ['uniform-buffer', 'storage-buffer', 'readonly-storage-buffer']))
)
.fn(async t => {
const { type, visibility } = t.params;
const { maxDynamic } = kBindingTypeInfo[type].perPipelineLimitClass;
const maxDynamicBufferBindings = [];
for (let binding = 0; binding < maxDynamic; binding++) {
maxDynamicBufferBindings.push({ binding, visibility, type, hasDynamicOffset: true });
}
const maxDynamicBufferBindGroupLayout = t.device.createBindGroupLayout({
entries: maxDynamicBufferBindings,
});
const goodDescriptor = {
entries: [{ binding: 0, visibility, type, hasDynamicOffset: false }],
};
const goodPipelineLayoutDescriptor = {
bindGroupLayouts: [
maxDynamicBufferBindGroupLayout,
t.device.createBindGroupLayout(goodDescriptor),
],
};
// Control case
t.device.createPipelineLayout(goodPipelineLayoutDescriptor);
// Check dynamic buffers exceed maximum in pipeline layout.
const badDescriptor = clone(goodDescriptor);
badDescriptor.entries[0].hasDynamicOffset = true;
const badPipelineLayoutDescriptor = {
bindGroupLayouts: [
maxDynamicBufferBindGroupLayout,
t.device.createBindGroupLayout(badDescriptor),
],
};
t.expectValidationError(() => {
t.device.createPipelineLayout(badPipelineLayoutDescriptor);
});
});
g.test('number_of_bind_group_layouts_exceeds_the_maximum_value').fn(async t => {
const bindGroupLayoutDescriptor = {
entries: [],
};
// 4 is the maximum number of bind group layouts.
const maxBindGroupLayouts = [1, 2, 3, 4].map(() =>
t.device.createBindGroupLayout(bindGroupLayoutDescriptor)
);
const goodPipelineLayoutDescriptor = {
bindGroupLayouts: maxBindGroupLayouts,
};
// Control case
t.device.createPipelineLayout(goodPipelineLayoutDescriptor);
// Check bind group layouts exceed maximum in pipeline layout.
const badPipelineLayoutDescriptor = {
bindGroupLayouts: [
...maxBindGroupLayouts,
t.device.createBindGroupLayout(bindGroupLayoutDescriptor),
],
};
t.expectValidationError(() => {
t.device.createPipelineLayout(badPipelineLayoutDescriptor);
});
});

View file

@ -0,0 +1,234 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
createRenderPipeline validation tests.
`;
import { poptions } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { kTextureFormatInfo, kTextureFormats } from '../../capability_info.js';
import { ValidationTest } from './validation_test.js';
class F extends ValidationTest {
getDescriptor(options = {}) {
const defaultColorStates = [{ format: 'rgba8unorm' }];
const {
primitiveTopology = 'triangle-list',
colorStates = defaultColorStates,
sampleCount = 1,
depthStencilState,
} = options;
const format = colorStates.length ? colorStates[0].format : 'rgba8unorm';
return {
vertexStage: this.getVertexStage(),
fragmentStage: this.getFragmentStage(format),
layout: this.getPipelineLayout(),
primitiveTopology,
colorStates,
sampleCount,
depthStencilState,
};
}
getVertexStage() {
return {
module: this.makeShaderModule('vertex', {
glsl: `
#version 450
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
`,
}),
entryPoint: 'main',
};
}
getFragmentStage(format) {
let fragColorType;
if (format.endsWith('sint')) {
fragColorType = 'ivec4';
} else if (format.endsWith('uint')) {
fragColorType = 'uvec4';
} else {
fragColorType = 'vec4';
}
const glsl = `
#version 450
layout(location = 0) out ${fragColorType} fragColor;
void main() {
fragColor = ${fragColorType}(0.0, 1.0, 0.0, 1.0);
}
`;
return {
module: this.makeShaderModule('fragment', { glsl }),
entryPoint: 'main',
};
}
getPipelineLayout() {
return this.device.createPipelineLayout({ bindGroupLayouts: [] });
}
createTexture(params) {
const { format, sampleCount } = params;
return this.device.createTexture({
size: { width: 4, height: 4, depth: 1 },
usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
format,
sampleCount,
});
}
}
export const g = makeTestGroup(F);
g.test('basic_use_of_createRenderPipeline').fn(t => {
const descriptor = t.getDescriptor();
t.device.createRenderPipeline(descriptor);
});
g.test('at_least_one_color_state_is_required').fn(async t => {
const goodDescriptor = t.getDescriptor({
colorStates: [{ format: 'rgba8unorm' }],
});
// Control case
t.device.createRenderPipeline(goodDescriptor);
// Fail because lack of color states
const badDescriptor = t.getDescriptor({
colorStates: [],
});
t.expectValidationError(() => {
t.device.createRenderPipeline(badDescriptor);
});
});
g.test('color_formats_must_be_renderable')
.params(poptions('format', kTextureFormats))
.fn(async t => {
const format = t.params.format;
const info = kTextureFormatInfo[format];
const descriptor = t.getDescriptor({ colorStates: [{ format }] });
if (info.renderable && info.color) {
// Succeeds when color format is renderable
t.device.createRenderPipeline(descriptor);
} else {
// Fails because when format is non-renderable
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
});
g.test('sample_count_must_be_valid')
.params([
{ sampleCount: 0, _success: false },
{ sampleCount: 1, _success: true },
{ sampleCount: 2, _success: false },
{ sampleCount: 3, _success: false },
{ sampleCount: 4, _success: true },
{ sampleCount: 8, _success: false },
{ sampleCount: 16, _success: false },
])
.fn(async t => {
const { sampleCount, _success } = t.params;
const descriptor = t.getDescriptor({ sampleCount });
if (_success) {
// Succeeds when sample count is valid
t.device.createRenderPipeline(descriptor);
} else {
// Fails when sample count is not 4 or 1
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
});
g.test('sample_count_must_be_equal_to_the_one_of_every_attachment_in_the_render_pass')
.params([
{ attachmentSamples: 4, pipelineSamples: 4, _success: true }, // It is allowed to use multisampled render pass and multisampled render pipeline.
{ attachmentSamples: 4, pipelineSamples: 1, _success: false }, // It is not allowed to use multisampled render pass and non-multisampled render pipeline.
{ attachmentSamples: 1, pipelineSamples: 4, _success: false }, // It is not allowed to use non-multisampled render pass and multisampled render pipeline.
])
.fn(async t => {
const { attachmentSamples, pipelineSamples, _success } = t.params;
const colorTexture = t.createTexture({
format: 'rgba8unorm',
sampleCount: attachmentSamples,
});
const depthStencilTexture = t.createTexture({
format: 'depth24plus-stencil8',
sampleCount: attachmentSamples,
});
const renderPassDescriptorWithoutDepthStencil = {
colorAttachments: [
{
attachment: colorTexture.createView(),
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
};
const renderPassDescriptorWithDepthStencilOnly = {
colorAttachments: [],
depthStencilAttachment: {
attachment: depthStencilTexture.createView(),
depthLoadValue: 1.0,
depthStoreOp: 'store',
stencilLoadValue: 0,
stencilStoreOp: 'store',
},
};
const pipelineWithoutDepthStencil = t.device.createRenderPipeline(
t.getDescriptor({
sampleCount: pipelineSamples,
})
);
const pipelineWithDepthStencilOnly = t.device.createRenderPipeline(
t.getDescriptor({
colorStates: [],
depthStencilState: { format: 'depth24plus-stencil8' },
sampleCount: pipelineSamples,
})
);
for (const { renderPassDescriptor, pipeline } of [
{
renderPassDescriptor: renderPassDescriptorWithoutDepthStencil,
pipeline: pipelineWithoutDepthStencil,
},
{
renderPassDescriptor: renderPassDescriptorWithDepthStencilOnly,
pipeline: pipelineWithDepthStencilOnly,
},
]) {
const commandEncoder = t.device.createCommandEncoder();
const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor);
renderPass.setPipeline(pipeline);
renderPass.endPass();
t.expectValidationError(() => {
commandEncoder.finish();
}, !_success);
}
});

View file

@ -0,0 +1,145 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
createTexture validation tests.
`;
import { poptions } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { kTextureFormatInfo, kTextureFormats } from '../../capability_info.js';
import { ValidationTest } from './validation_test.js';
class F extends ValidationTest {
getDescriptor(options = {}) {
const {
width = 32,
height = 32,
arrayLayerCount = 1,
mipLevelCount = 1,
sampleCount = 1,
format = 'rgba8unorm',
} = options;
return {
size: { width, height, depth: arrayLayerCount },
mipLevelCount,
sampleCount,
dimension: '2d',
format,
usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.SAMPLED,
};
}
}
export const g = makeTestGroup(F);
g.test('validation_of_sampleCount')
.params([
// TODO: Consider making a list of "valid"+"invalid" texture descriptors in capability_info.
{ sampleCount: 0, _success: false }, // sampleCount of 0 is not allowed
{ sampleCount: 1, _success: true }, // sampleCount of 1 is allowed
{ sampleCount: 2, _success: false }, // sampleCount of 2 is not allowed
{ sampleCount: 3, _success: false }, // sampleCount of 3 is not allowed
{ sampleCount: 4, _success: true }, // sampleCount of 4 is allowed
{ sampleCount: 8, _success: false }, // sampleCount of 8 is not allowed
{ sampleCount: 16, _success: false }, // sampleCount of 16 is not allowed
{ sampleCount: 4, mipLevelCount: 2, _success: false }, // multisampled multi-level not allowed
{ sampleCount: 4, arrayLayerCount: 2, _success: false }, // multisampled multi-layer is not allowed
])
.fn(async t => {
const { sampleCount, mipLevelCount, arrayLayerCount, _success } = t.params;
const descriptor = t.getDescriptor({ sampleCount, mipLevelCount, arrayLayerCount });
t.expectValidationError(() => {
t.device.createTexture(descriptor);
}, !_success);
});
g.test('validation_of_mipLevelCount')
.params([
{ width: 32, height: 32, mipLevelCount: 1, _success: true }, // mipLevelCount of 1 is allowed
{ width: 32, height: 32, mipLevelCount: 0, _success: false }, // mipLevelCount of 0 is not allowed
{ width: 32, height: 32, mipLevelCount: 6, _success: true }, // full mip chains are allowed (Mip level sizes: 32, 16, 8, 4, 2, 1)
{ width: 31, height: 32, mipLevelCount: 6, _success: true }, // full mip chains are allowed (Mip level sizes: 31x32, 15x16, 7x8, 3x4, 1x2, 1x1)
{ width: 32, height: 31, mipLevelCount: 6, _success: true }, // full mip chains are allowed (Mip level sizes: 32x31, 16x15, 8x7, 4x3, 2x1, 1x1)
{ width: 31, height: 32, mipLevelCount: 7, _success: false }, // too big mip chains on width are disallowed (Mip level sizes: 31x32, 15x16, 7x8, 3x4, 1x2, 1x1, ?x?)
{ width: 32, height: 31, mipLevelCount: 7, _success: false }, // too big mip chains on height are disallowed (Mip level sizes: 32x31, 16x15, 8x7, 4x3, 2x1, 1x1, ?x?)
{ width: 32, height: 32, mipLevelCount: 100, _success: false }, // undefined shift check if miplevel is bigger than the integer bit width
{ width: 32, height: 8, mipLevelCount: 6, _success: true }, // non square mip map halves the resolution until a 1x1 dimension. (Mip maps: 32 * 8, 16 * 4, 8 * 2, 4 * 1, 2 * 1, 1 * 1)
])
.fn(async t => {
const { width, height, mipLevelCount, _success } = t.params;
const descriptor = t.getDescriptor({ width, height, mipLevelCount });
t.expectValidationError(() => {
t.device.createTexture(descriptor);
}, !_success);
});
g.test('it_is_valid_to_destroy_a_texture').fn(t => {
const descriptor = t.getDescriptor();
const texture = t.device.createTexture(descriptor);
texture.destroy();
});
g.test('it_is_valid_to_destroy_a_destroyed_texture').fn(t => {
const descriptor = t.getDescriptor();
const texture = t.device.createTexture(descriptor);
texture.destroy();
texture.destroy();
});
g.test('it_is_invalid_to_submit_a_destroyed_texture_before_and_after_encode')
.params([
{ destroyBeforeEncode: false, destroyAfterEncode: false, _success: true },
{ destroyBeforeEncode: true, destroyAfterEncode: false, _success: false },
{ destroyBeforeEncode: false, destroyAfterEncode: true, _success: false },
])
.fn(async t => {
const { destroyBeforeEncode, destroyAfterEncode, _success } = t.params;
const descriptor = t.getDescriptor();
const texture = t.device.createTexture(descriptor);
const textureView = texture.createView();
if (destroyBeforeEncode) {
texture.destroy();
}
const commandEncoder = t.device.createCommandEncoder();
const renderPass = commandEncoder.beginRenderPass({
colorAttachments: [
{
attachment: textureView,
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
});
renderPass.endPass();
const commandBuffer = commandEncoder.finish();
if (destroyAfterEncode) {
texture.destroy();
}
t.expectValidationError(() => {
t.queue.submit([commandBuffer]);
}, !_success);
});
g.test('it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format')
.params(poptions('format', kTextureFormats))
.fn(async t => {
const format = t.params.format;
const info = kTextureFormatInfo[format];
const descriptor = t.getDescriptor({ width: 1, height: 1, format });
t.expectValidationError(() => {
t.device.createTexture(descriptor);
}, !info.renderable);
});
// TODO: Add tests for compressed texture formats

View file

@ -0,0 +1,267 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
createView validation tests.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { ValidationTest } from './validation_test.js';
const ARRAY_LAYER_COUNT_2D = 6;
const MIP_LEVEL_COUNT = 6;
const FORMAT = 'rgba8unorm';
class F extends ValidationTest {
createTexture(options = {}) {
const {
width = 32,
height = 32,
arrayLayerCount = 1,
mipLevelCount = MIP_LEVEL_COUNT,
sampleCount = 1,
} = options;
return this.device.createTexture({
size: { width, height, depth: arrayLayerCount },
mipLevelCount,
sampleCount,
dimension: '2d',
format: FORMAT,
usage: GPUTextureUsage.SAMPLED,
});
}
getDescriptor(options = {}) {
const {
format = FORMAT,
dimension = '2d',
baseMipLevel = 0,
mipLevelCount = MIP_LEVEL_COUNT,
baseArrayLayer = 0,
arrayLayerCount = 1,
} = options;
return {
format,
dimension,
baseMipLevel,
mipLevelCount,
baseArrayLayer,
arrayLayerCount,
};
}
}
export const g = makeTestGroup(F);
g.test('creating_texture_view_on_a_2D_non_array_texture')
.params([
{ _success: true }, // default view works
{ arrayLayerCount: 1, _success: true }, // it is OK to create a 2D texture view on a 2D texture
{ arrayLayerCount: 2, _success: false }, // it is an error to view a layer past the end of the texture
{ dimension: '2d-array', arrayLayerCount: 1, _success: true }, // it is OK to create a 1-layer 2D array texture view on a 2D texture
// mip level is in range
{ mipLevelCount: 1, baseMipLevel: MIP_LEVEL_COUNT - 1, _success: true },
{ mipLevelCount: 2, baseMipLevel: MIP_LEVEL_COUNT - 2, _success: true },
// baseMipLevel == k && mipLevelCount == 0 means to use levels k..end
{ mipLevelCount: 0, baseMipLevel: 0, _success: true },
{ mipLevelCount: 0, baseMipLevel: 1, _success: true },
{ mipLevelCount: 0, baseMipLevel: MIP_LEVEL_COUNT - 1, _success: true },
{ mipLevelCount: 0, baseMipLevel: MIP_LEVEL_COUNT, _success: false },
// it is an error to make the mip level out of range
{ mipLevelCount: MIP_LEVEL_COUNT + 1, baseMipLevel: 0, _success: false },
{ mipLevelCount: MIP_LEVEL_COUNT, baseMipLevel: 1, _success: false },
{ mipLevelCount: 2, baseMipLevel: MIP_LEVEL_COUNT - 1, _success: false },
{ mipLevelCount: 1, baseMipLevel: MIP_LEVEL_COUNT, _success: false },
])
.fn(async t => {
const { dimension = '2d', arrayLayerCount, mipLevelCount, baseMipLevel, _success } = t.params;
const texture = t.createTexture({ arrayLayerCount: 1 });
const descriptor = t.getDescriptor({
dimension,
arrayLayerCount,
mipLevelCount,
baseMipLevel,
});
t.expectValidationError(() => {
texture.createView(descriptor);
}, !_success);
});
g.test('creating_texture_view_on_a_2D_array_texture')
.params([
{ _success: true }, // default view works
{ dimension: '2d', arrayLayerCount: 1, _success: true }, // it is OK to create a 2D texture view on a 2D array texture
{ arrayLayerCount: ARRAY_LAYER_COUNT_2D, _success: true }, // it is OK to create a 2D array texture view on a 2D array texture
// baseArrayLayer == k && arrayLayerCount == 0 means to use layers k..end.
{ arrayLayerCount: 0, baseArrayLayer: 0, _success: true },
{ arrayLayerCount: 0, baseArrayLayer: 1, _success: true },
{ arrayLayerCount: 0, baseArrayLayer: ARRAY_LAYER_COUNT_2D - 1, _success: true },
{ arrayLayerCount: 0, baseArrayLayer: ARRAY_LAYER_COUNT_2D, _success: false },
// It is an error for the array layer range of the view to exceed that of the texture
{ arrayLayerCount: ARRAY_LAYER_COUNT_2D + 1, baseArrayLayer: 0, _success: false },
{ arrayLayerCount: ARRAY_LAYER_COUNT_2D, baseArrayLayer: 1, _success: false },
{ arrayLayerCount: 2, baseArrayLayer: ARRAY_LAYER_COUNT_2D - 1, _success: false },
{ arrayLayerCount: 1, baseArrayLayer: ARRAY_LAYER_COUNT_2D, _success: false },
])
.fn(async t => {
const { dimension = '2d-array', arrayLayerCount, baseArrayLayer, _success } = t.params;
const texture = t.createTexture({ arrayLayerCount: ARRAY_LAYER_COUNT_2D });
const descriptor = t.getDescriptor({
dimension,
arrayLayerCount,
baseArrayLayer,
});
t.expectValidationError(() => {
texture.createView(descriptor);
}, !_success);
});
g.test('Using_defaults_validates_the_same_as_setting_values_for_more_than_1_array_layer')
.params([
{ _success: true },
{ format: 'rgba8unorm', _success: true },
{ format: 'r8unorm', _success: false },
{ dimension: '2d-array', _success: true },
{ dimension: '2d', _success: false },
{ arrayLayerCount: ARRAY_LAYER_COUNT_2D, _success: false }, // setting array layers to non-0 means the dimensionality will default to 2D so by itself it causes an error.
{ arrayLayerCount: ARRAY_LAYER_COUNT_2D, dimension: '2d-array', _success: true },
{
arrayLayerCount: ARRAY_LAYER_COUNT_2D,
dimension: '2d-array',
mipLevelCount: MIP_LEVEL_COUNT,
_success: true,
},
])
.fn(async t => {
const { format, dimension, arrayLayerCount, mipLevelCount, _success } = t.params;
const texture = t.createTexture({ arrayLayerCount: ARRAY_LAYER_COUNT_2D });
const descriptor = { format, dimension, arrayLayerCount, mipLevelCount };
t.expectValidationError(() => {
texture.createView(descriptor);
}, !_success);
});
g.test('Using_defaults_validates_the_same_as_setting_values_for_only_1_array_layer')
.params([
{ _success: true },
{ format: 'rgba8unorm', _success: true },
{ format: 'r8unorm', _success: false },
{ dimension: '2d-array', _success: true },
{ dimension: '2d', _success: true },
{ arrayLayerCount: 0, _success: true },
{ arrayLayerCount: 1, _success: true },
{ arrayLayerCount: 2, _success: false },
{ mipLevelCount: MIP_LEVEL_COUNT, _success: true },
{ mipLevelCount: 1, _success: true },
])
.fn(async t => {
const { format, dimension, arrayLayerCount, mipLevelCount, _success } = t.params;
const texture = t.createTexture({ arrayLayerCount: 1 });
const descriptor = { format, dimension, arrayLayerCount, mipLevelCount };
t.expectValidationError(() => {
texture.createView(descriptor);
}, !_success);
});
g.test('creating_cube_map_texture_view')
.params([
{ dimension: 'cube', arrayLayerCount: 6, _success: true }, // it is OK to create a cube map texture view with arrayLayerCount == 6
// it is an error to create a cube map texture view with arrayLayerCount != 6
{ dimension: 'cube', arrayLayerCount: 3, _success: false },
{ dimension: 'cube', arrayLayerCount: 7, _success: false },
{ dimension: 'cube', arrayLayerCount: 12, _success: false },
{ dimension: 'cube', _success: false },
{ dimension: 'cube-array', arrayLayerCount: 12, _success: true }, // it is OK to create a cube map array texture view with arrayLayerCount % 6 == 0
// it is an error to create a cube map array texture view with arrayLayerCount % 6 != 0
{ dimension: 'cube-array', arrayLayerCount: 11, _success: false },
{ dimension: 'cube-array', arrayLayerCount: 13, _success: false },
])
.fn(async t => {
const { dimension = '2d-array', arrayLayerCount, _success } = t.params;
const texture = t.createTexture({ arrayLayerCount: 16 });
const descriptor = t.getDescriptor({
dimension,
arrayLayerCount,
});
t.expectValidationError(() => {
texture.createView(descriptor);
}, !_success);
});
g.test('creating_cube_map_texture_view_with_a_non_square_texture')
.params([
{ dimension: 'cube', arrayLayerCount: 6 }, // it is an error to create a cube map texture view with width != height.
{ dimension: 'cube-array', arrayLayerCount: 12 }, // it is an error to create a cube map array texture view with width != height.
])
.fn(async t => {
const { dimension, arrayLayerCount } = t.params;
const nonSquareTexture = t.createTexture({
arrayLayerCount: 18,
width: 32,
height: 16,
mipLevelCount: 5,
});
const descriptor = t.getDescriptor({
dimension,
arrayLayerCount,
});
t.expectValidationError(() => {
nonSquareTexture.createView(descriptor);
});
});
// TODO: add more tests when rules are fully implemented.
g.test('test_the_format_compatibility_rules_when_creating_a_texture_view').fn(async t => {
const texture = t.createTexture({ arrayLayerCount: 1 });
const descriptor = t.getDescriptor({
format: 'depth24plus-stencil8',
});
// it is invalid to create a view in depth-stencil format on a RGBA texture
t.expectValidationError(() => {
texture.createView(descriptor);
});
});
g.test('it_is_invalid_to_use_a_texture_view_created_from_a_destroyed_texture').fn(async t => {
const texture = t.createTexture({ arrayLayerCount: 1 });
const commandEncoder = t.device.createCommandEncoder();
const renderPass = commandEncoder.beginRenderPass({
colorAttachments: [
{
attachment: texture.createView(),
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
});
renderPass.endPass();
texture.destroy();
t.expectValidationError(() => {
commandEncoder.finish();
});
});
// TODO: Add tests for TextureAspect

View file

@ -0,0 +1,135 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
indexed draws validation tests.
`;
import { params, poptions, pbool } from '../../../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { ValidationTest } from './../../validation_test.js';
class F extends ValidationTest {
createIndexBuffer() {
const indexArray = new Uint32Array([0, 1, 2, 3, 1, 2]);
const [indexBuffer, indexMapping] = this.device.createBufferMapped({
size: indexArray.byteLength,
usage: GPUBufferUsage.INDEX,
});
new Uint32Array(indexMapping).set(indexArray);
indexBuffer.unmap();
return indexBuffer;
}
createRenderPipeline() {
const vertexModule = this.makeShaderModule('vertex', {
glsl: `
#version 450
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
`,
});
const fragmentModule = this.makeShaderModule('fragment', {
glsl: `
#version 450
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
`,
});
return this.device.createRenderPipeline({
layout: this.device.createPipelineLayout({ bindGroupLayouts: [] }),
vertexStage: { module: vertexModule, entryPoint: 'main' },
fragmentStage: { module: fragmentModule, entryPoint: 'main' },
primitiveTopology: 'triangle-strip',
colorStates: [{ format: 'rgba8unorm' }],
});
}
beginRenderPass(encoder) {
const colorAttachment = this.device.createTexture({
format: 'rgba8unorm',
size: { width: 1, height: 1, depth: 1 },
usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
});
return encoder.beginRenderPass({
colorAttachments: [
{
attachment: colorAttachment.createView(),
loadValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
storeOp: 'store',
},
],
});
}
drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance) {
const indexBuffer = this.createIndexBuffer();
const pipeline = this.createRenderPipeline();
const encoder = this.device.createCommandEncoder();
const pass = this.beginRenderPass(encoder);
pass.setPipeline(pipeline);
pass.setIndexBuffer(indexBuffer);
pass.drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance);
pass.endPass();
this.device.defaultQueue.submit([encoder.finish()]);
}
drawIndexedIndirect(bufferArray, indirectOffset) {
const [indirectBuffer, indirectMapping] = this.device.createBufferMapped({
size: bufferArray.byteLength,
usage: GPUBufferUsage.INDIRECT,
});
new Uint32Array(indirectMapping).set(bufferArray);
indirectBuffer.unmap();
const indexBuffer = this.createIndexBuffer();
const pipeline = this.createRenderPipeline();
const encoder = this.device.createCommandEncoder();
const pass = this.beginRenderPass(encoder);
pass.setPipeline(pipeline);
pass.setIndexBuffer(indexBuffer, 0);
pass.drawIndexedIndirect(indirectBuffer, indirectOffset);
pass.endPass();
this.device.defaultQueue.submit([encoder.finish()]);
}
}
export const g = makeTestGroup(F);
g.test('out_of_bounds')
.params(
params()
.combine(pbool('indirect')) // indirect drawIndexed
.combine([
{ indexCount: 6, firstIndex: 1 }, // indexCount + firstIndex out of bound
{ indexCount: 6, firstIndex: 6 }, // only firstIndex out of bound
{ indexCount: 6, firstIndex: 10000 }, // firstIndex much larger than the bound
{ indexCount: 7, firstIndex: 0 }, // only indexCount out of bound
{ indexCount: 10000, firstIndex: 0 }, // indexCount much larger than the bound
])
.combine(poptions('instanceCount', [1, 10000])) // normal and large instanceCount
)
.fn(t => {
const { indirect, indexCount, firstIndex, instanceCount } = t.params;
if (indirect) {
t.drawIndexedIndirect(new Uint32Array([indexCount, instanceCount, firstIndex, 0, 0]), 0);
} else {
t.drawIndexed(indexCount, instanceCount, firstIndex, 0, 0);
}
});

View file

@ -0,0 +1,173 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
export const description = `
error scope validation tests.
`;
import { Fixture } from '../../../common/framework/fixture.js';
import { getGPU } from '../../../common/framework/gpu/implementation.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { assert, raceWithRejectOnTimeout } from '../../../common/framework/util/util.js';
class F extends Fixture {
constructor(...args) {
super(...args);
_defineProperty(this, '_device', undefined);
}
get device() {
assert(this._device !== undefined);
return this._device;
}
async init() {
super.init();
const gpu = getGPU();
const adapter = await gpu.requestAdapter();
this._device = await adapter.requestDevice();
}
createErrorBuffer() {
this.device.createBuffer({
size: 1024,
usage: 0xffff, // Invalid GPUBufferUsage
});
// TODO: Remove when chrome does it automatically.
this.device.defaultQueue.submit([]);
}
// Expect an uncapturederror event to occur. Note: this MUST be awaited, because
// otherwise it could erroneously pass by capturing an error from later in the test.
async expectUncapturedError(fn) {
return this.immediateAsyncExpectation(() => {
// TODO: Make arbitrary timeout value a test runner variable
const TIMEOUT_IN_MS = 1000;
const promise = new Promise(resolve => {
const eventListener = event => {
this.debug(`Got uncaptured error event with ${event.error}`);
resolve(event);
};
this.device.addEventListener('uncapturederror', eventListener, { once: true });
});
fn();
return raceWithRejectOnTimeout(
promise,
TIMEOUT_IN_MS,
'Timeout occurred waiting for uncaptured error'
);
});
}
}
export const g = makeTestGroup(F);
g.test('simple_case_where_the_error_scope_catches_an_error').fn(async t => {
t.device.pushErrorScope('validation');
t.createErrorBuffer();
const error = await t.device.popErrorScope();
t.expect(error instanceof GPUValidationError);
});
g.test('errors_bubble_to_the_parent_scope_if_not_handled_by_the_current_scope').fn(async t => {
t.device.pushErrorScope('validation');
t.device.pushErrorScope('out-of-memory');
t.createErrorBuffer();
{
const error = await t.device.popErrorScope();
t.expect(error === null);
}
{
const error = await t.device.popErrorScope();
t.expect(error instanceof GPUValidationError);
}
});
g.test('if_an_error_scope_matches_an_error_it_does_not_bubble_to_the_parent_scope').fn(async t => {
t.device.pushErrorScope('validation');
t.device.pushErrorScope('validation');
t.createErrorBuffer();
{
const error = await t.device.popErrorScope();
t.expect(error instanceof GPUValidationError);
}
{
const error = await t.device.popErrorScope();
t.expect(error === null);
}
});
g.test('if_no_error_scope_handles_an_error_it_fires_an_uncapturederror_event').fn(async t => {
t.device.pushErrorScope('out-of-memory');
const uncapturedErrorEvent = await t.expectUncapturedError(() => {
t.createErrorBuffer();
});
t.expect(uncapturedErrorEvent.error instanceof GPUValidationError);
const error = await t.device.popErrorScope();
t.expect(error === null);
});
g.test('push,popping_sibling_error_scopes_must_be_balanced').fn(async t => {
{
const promise = t.device.popErrorScope();
t.shouldReject('OperationError', promise);
}
const promises = [];
for (let i = 0; i < 1000; i++) {
t.device.pushErrorScope('validation');
promises.push(t.device.popErrorScope());
}
const errors = await Promise.all(promises);
t.expect(errors.every(e => e === null));
{
const promise = t.device.popErrorScope();
t.shouldReject('OperationError', promise);
}
});
g.test('push,popping_nested_error_scopes_must_be_balanced').fn(async t => {
{
const promise = t.device.popErrorScope();
t.shouldReject('OperationError', promise);
}
const promises = [];
for (let i = 0; i < 1000; i++) {
t.device.pushErrorScope('validation');
}
for (let i = 0; i < 1000; i++) {
promises.push(t.device.popErrorScope());
}
const errors = await Promise.all(promises);
t.expect(errors.every(e => e === null));
{
const promise = t.device.popErrorScope();
t.shouldReject('OperationError', promise);
}
});

View file

@ -0,0 +1,88 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
fences validation tests.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { ValidationTest } from './validation_test.js';
export const g = makeTestGroup(ValidationTest);
// TODO: Remove if https://github.com/gpuweb/gpuweb/issues/377 is decided
g.test('wait_on_a_fence_without_signaling_the_value_is_invalid').fn(async t => {
const fence = t.queue.createFence();
t.expectValidationError(() => {
const promise = fence.onCompletion(2);
t.shouldReject('OperationError', promise);
});
});
// TODO: Remove if https://github.com/gpuweb/gpuweb/issues/377 is decided
g.test('wait_on_a_fence_with_a_value_greater_than_signaled_value_is_invalid').fn(async t => {
const fence = t.queue.createFence();
t.queue.signal(fence, 2);
t.expectValidationError(() => {
const promise = fence.onCompletion(3);
t.shouldReject('OperationError', promise);
});
});
g.test('signal_a_value_lower_than_signaled_value_is_invalid').fn(async t => {
const fence = t.queue.createFence({ initialValue: 1 });
t.expectValidationError(() => {
t.queue.signal(fence, 0);
});
});
g.test('signal_a_value_equal_to_signaled_value_is_invalid').fn(async t => {
const fence = t.queue.createFence({ initialValue: 1 });
t.expectValidationError(() => {
t.queue.signal(fence, 1);
});
});
g.test('increasing_fence_value_by_more_than_1_succeeds').fn(async t => {
const fence = t.queue.createFence();
t.queue.signal(fence, 2);
await fence.onCompletion(2);
t.queue.signal(fence, 6);
await fence.onCompletion(6);
});
g.test('signal_a_fence_on_a_different_device_than_it_was_created_on_is_invalid').fn(async t => {
const anotherDevice = await t.device.adapter.requestDevice();
const fence = anotherDevice.defaultQueue.createFence();
t.expectValidationError(() => {
t.queue.signal(fence, 2);
});
});
g.test('signal_a_fence_on_a_different_device_does_not_update_fence_signaled_value').fn(async t => {
const anotherDevice = await t.device.adapter.requestDevice();
const fence = anotherDevice.defaultQueue.createFence({ initialValue: 1 });
t.expectValidationError(() => {
t.queue.signal(fence, 2);
});
t.expect(fence.getCompletedValue() === 1);
anotherDevice.pushErrorScope('validation');
anotherDevice.defaultQueue.signal(fence, 2);
await fence.onCompletion(2);
t.expect(fence.getCompletedValue() === 2);
const gpuValidationError = await anotherDevice.popErrorScope();
if (gpuValidationError instanceof GPUValidationError) {
t.fail(`Captured validation error - ${gpuValidationError.message}`);
}
});

View file

@ -0,0 +1,43 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
queue submit validation tests.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { ValidationTest } from './validation_test.js';
export const g = makeTestGroup(ValidationTest);
g.test('submitting_with_a_mapped_buffer_is_disallowed').fn(async t => {
const buffer = t.device.createBuffer({
size: 4,
usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC,
});
const targetBuffer = t.device.createBuffer({
size: 4,
usage: GPUBufferUsage.COPY_DST,
});
const getCommandBuffer = () => {
const commandEncoder = t.device.createCommandEncoder();
commandEncoder.copyBufferToBuffer(buffer, 0, targetBuffer, 0, 4);
return commandEncoder.finish();
};
// Submitting when the buffer has never been mapped should succeed
t.queue.submit([getCommandBuffer()]);
// Map the buffer, submitting when the buffer is mapped should fail
await buffer.mapWriteAsync();
t.queue.submit([]);
t.expectValidationError(() => {
t.queue.submit([getCommandBuffer()]);
});
// Unmap the buffer, queue submit should succeed
buffer.unmap();
t.queue.submit([getCommandBuffer()]);
});

View file

@ -0,0 +1,152 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
render pass validation tests.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { ValidationTest } from './validation_test.js';
class F extends ValidationTest {
getUniformBuffer() {
return this.device.createBuffer({
size: 8 * Float32Array.BYTES_PER_ELEMENT,
usage: GPUBufferUsage.UNIFORM,
});
}
createRenderPipeline(pipelineLayout) {
const vertexModule = this.makeShaderModule('vertex', {
glsl: `#version 450
layout (set = 0, binding = 0) uniform vertexUniformBuffer {
mat2 transform;
};
void main() {
const vec2 pos[3] = vec2[3](vec2(-1.f, -1.f), vec2(1.f, -1.f), vec2(-1.f, 1.f));
gl_Position = vec4(transform * pos[gl_VertexIndex], 0.f, 1.f);
}
`,
});
const fragmentModule = this.makeShaderModule('fragment', {
glsl: `
#version 450
layout (set = 1, binding = 0) uniform fragmentUniformBuffer {
vec4 color;
};
layout(location = 0) out vec4 fragColor;
void main() {
}
`,
});
const pipeline = this.device.createRenderPipeline({
vertexStage: { module: vertexModule, entryPoint: 'main' },
fragmentStage: { module: fragmentModule, entryPoint: 'main' },
layout: pipelineLayout,
primitiveTopology: 'triangle-list',
colorStates: [{ format: 'rgba8unorm' }],
});
return pipeline;
}
beginRenderPass(commandEncoder) {
const attachmentTexture = this.device.createTexture({
format: 'rgba8unorm',
size: { width: 16, height: 16, depth: 1 },
usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
});
return commandEncoder.beginRenderPass({
colorAttachments: [
{
attachment: attachmentTexture.createView(),
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
});
}
}
export const g = makeTestGroup(F);
g.test('it_is_invalid_to_draw_in_a_render_pass_with_missing_bind_groups')
.params([
{ setBindGroup1: true, setBindGroup2: true, _success: true },
{ setBindGroup1: true, setBindGroup2: false, _success: false },
{ setBindGroup1: false, setBindGroup2: true, _success: false },
{ setBindGroup1: false, setBindGroup2: false, _success: false },
])
.fn(async t => {
const { setBindGroup1, setBindGroup2, _success } = t.params;
const uniformBuffer = t.getUniformBuffer();
const bindGroupLayout1 = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.VERTEX,
type: 'uniform-buffer',
},
],
});
const bindGroup1 = t.device.createBindGroup({
entries: [
{
binding: 0,
resource: {
buffer: uniformBuffer,
},
},
],
layout: bindGroupLayout1,
});
const bindGroupLayout2 = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
type: 'uniform-buffer',
},
],
});
const bindGroup2 = t.device.createBindGroup({
entries: [
{
binding: 0,
resource: {
buffer: uniformBuffer,
},
},
],
layout: bindGroupLayout2,
});
const pipelineLayout = t.device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayout1, bindGroupLayout2],
});
const pipeline = t.createRenderPipeline(pipelineLayout);
const commandEncoder = t.device.createCommandEncoder();
const renderPass = t.beginRenderPass(commandEncoder);
renderPass.setPipeline(pipeline);
if (setBindGroup1) {
renderPass.setBindGroup(0, bindGroup1);
}
if (setBindGroup2) {
renderPass.setBindGroup(1, bindGroup2);
}
renderPass.draw(3, 1, 0, 0);
renderPass.endPass();
t.expectValidationError(() => {
commandEncoder.finish();
}, !_success);
});

View file

@ -0,0 +1,14 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `API Validation Tests for RenderPass StoreOp.
Test Coverage Needed:
- Test that when depthReadOnly is true, depthStoreOp must be 'store'
- Test that when stencilReadOnly is true, stencilStoreOp must be 'store'`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { ValidationTest } from './../validation_test.js';
export const g = makeTestGroup(ValidationTest);

View file

@ -0,0 +1,568 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
render pass descriptor validation tests.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { ValidationTest } from './validation_test.js';
class F extends ValidationTest {
createTexture(options = {}) {
const {
format = 'rgba8unorm',
width = 16,
height = 16,
arrayLayerCount = 1,
mipLevelCount = 1,
sampleCount = 1,
usage = GPUTextureUsage.OUTPUT_ATTACHMENT,
} = options;
return this.device.createTexture({
size: { width, height, depth: arrayLayerCount },
format,
mipLevelCount,
sampleCount,
usage,
});
}
getColorAttachment(texture, textureViewDescriptor) {
const attachment = texture.createView(textureViewDescriptor);
return {
attachment,
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
};
}
getDepthStencilAttachment(texture, textureViewDescriptor) {
const attachment = texture.createView(textureViewDescriptor);
return {
attachment,
depthLoadValue: 1.0,
depthStoreOp: 'store',
stencilLoadValue: 0,
stencilStoreOp: 'store',
};
}
async tryRenderPass(success, descriptor) {
const commandEncoder = this.device.createCommandEncoder();
const renderPass = commandEncoder.beginRenderPass(descriptor);
renderPass.endPass();
this.expectValidationError(() => {
commandEncoder.finish();
}, !success);
}
}
export const g = makeTestGroup(F);
g.test('a_render_pass_with_only_one_color_is_ok').fn(t => {
const colorTexture = t.createTexture({ format: 'rgba8unorm' });
const descriptor = {
colorAttachments: [t.getColorAttachment(colorTexture)],
};
t.tryRenderPass(true, descriptor);
});
g.test('a_render_pass_with_only_one_depth_attachment_is_ok').fn(t => {
const depthStencilTexture = t.createTexture({ format: 'depth24plus-stencil8' });
const descriptor = {
colorAttachments: [],
depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture),
};
t.tryRenderPass(true, descriptor);
});
g.test('OOB_color_attachment_indices_are_handled')
.params([
{ colorAttachmentsCount: 4, _success: true }, // Control case
{ colorAttachmentsCount: 5, _success: false }, // Out of bounds
])
.fn(async t => {
const { colorAttachmentsCount, _success } = t.params;
const colorAttachments = [];
for (let i = 0; i < colorAttachmentsCount; i++) {
const colorTexture = t.createTexture();
colorAttachments.push(t.getColorAttachment(colorTexture));
}
await t.tryRenderPass(_success, { colorAttachments });
});
g.test('attachments_must_have_the_same_size').fn(async t => {
const colorTexture1x1A = t.createTexture({ width: 1, height: 1, format: 'rgba8unorm' });
const colorTexture1x1B = t.createTexture({ width: 1, height: 1, format: 'rgba8unorm' });
const colorTexture2x2 = t.createTexture({ width: 2, height: 2, format: 'rgba8unorm' });
const depthStencilTexture1x1 = t.createTexture({
width: 1,
height: 1,
format: 'depth24plus-stencil8',
});
const depthStencilTexture2x2 = t.createTexture({
width: 2,
height: 2,
format: 'depth24plus-stencil8',
});
{
// Control case: all the same size (1x1)
const descriptor = {
colorAttachments: [
t.getColorAttachment(colorTexture1x1A),
t.getColorAttachment(colorTexture1x1B),
],
depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture1x1),
};
t.tryRenderPass(true, descriptor);
}
{
// One of the color attachments has a different size
const descriptor = {
colorAttachments: [
t.getColorAttachment(colorTexture1x1A),
t.getColorAttachment(colorTexture2x2),
],
};
await t.tryRenderPass(false, descriptor);
}
{
// The depth stencil attachment has a different size
const descriptor = {
colorAttachments: [
t.getColorAttachment(colorTexture1x1A),
t.getColorAttachment(colorTexture1x1B),
],
depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture2x2),
};
await t.tryRenderPass(false, descriptor);
}
});
g.test('attachments_must_match_whether_they_are_used_for_color_or_depth_stencil').fn(async t => {
const colorTexture = t.createTexture({ format: 'rgba8unorm' });
const depthStencilTexture = t.createTexture({ format: 'depth24plus-stencil8' });
{
// Using depth-stencil for color
const descriptor = {
colorAttachments: [t.getColorAttachment(depthStencilTexture)],
};
await t.tryRenderPass(false, descriptor);
}
{
// Using color for depth-stencil
const descriptor = {
colorAttachments: [],
depthStencilAttachment: t.getDepthStencilAttachment(colorTexture),
};
await t.tryRenderPass(false, descriptor);
}
});
g.test('check_layer_count_for_color_or_depth_stencil')
.params([
{ arrayLayerCount: 5, baseArrayLayer: 0, _success: false }, // using 2D array texture view with arrayLayerCount > 1 is not allowed
{ arrayLayerCount: 1, baseArrayLayer: 0, _success: true }, // using 2D array texture view that covers the first layer of the texture is OK
{ arrayLayerCount: 1, baseArrayLayer: 9, _success: true }, // using 2D array texture view that covers the last layer is OK for depth stencil
])
.fn(async t => {
const { arrayLayerCount, baseArrayLayer, _success } = t.params;
const ARRAY_LAYER_COUNT = 10;
const MIP_LEVEL_COUNT = 1;
const COLOR_FORMAT = 'rgba8unorm';
const DEPTH_STENCIL_FORMAT = 'depth24plus-stencil8';
const colorTexture = t.createTexture({
format: COLOR_FORMAT,
width: 32,
height: 32,
mipLevelCount: MIP_LEVEL_COUNT,
arrayLayerCount: ARRAY_LAYER_COUNT,
});
const depthStencilTexture = t.createTexture({
format: DEPTH_STENCIL_FORMAT,
width: 32,
height: 32,
mipLevelCount: MIP_LEVEL_COUNT,
arrayLayerCount: ARRAY_LAYER_COUNT,
});
const baseTextureViewDescriptor = {
dimension: '2d-array',
baseArrayLayer,
arrayLayerCount,
baseMipLevel: 0,
mipLevelCount: MIP_LEVEL_COUNT,
};
{
// Check 2D array texture view for color
const textureViewDescriptor = {
...baseTextureViewDescriptor,
format: COLOR_FORMAT,
};
const descriptor = {
colorAttachments: [t.getColorAttachment(colorTexture, textureViewDescriptor)],
};
await t.tryRenderPass(_success, descriptor);
}
{
// Check 2D array texture view for depth stencil
const textureViewDescriptor = {
...baseTextureViewDescriptor,
format: DEPTH_STENCIL_FORMAT,
};
const descriptor = {
colorAttachments: [],
depthStencilAttachment: t.getDepthStencilAttachment(
depthStencilTexture,
textureViewDescriptor
),
};
await t.tryRenderPass(_success, descriptor);
}
});
g.test('check_mip_level_count_for_color_or_depth_stencil')
.params([
{ mipLevelCount: 2, baseMipLevel: 0, _success: false }, // using 2D texture view with mipLevelCount > 1 is not allowed
{ mipLevelCount: 1, baseMipLevel: 0, _success: true }, // using 2D texture view that covers the first level of the texture is OK
{ mipLevelCount: 1, baseMipLevel: 3, _success: true }, // using 2D texture view that covers the last level of the texture is OK
])
.fn(async t => {
const { mipLevelCount, baseMipLevel, _success } = t.params;
const ARRAY_LAYER_COUNT = 1;
const MIP_LEVEL_COUNT = 4;
const COLOR_FORMAT = 'rgba8unorm';
const DEPTH_STENCIL_FORMAT = 'depth24plus-stencil8';
const colorTexture = t.createTexture({
format: COLOR_FORMAT,
width: 32,
height: 32,
mipLevelCount: MIP_LEVEL_COUNT,
arrayLayerCount: ARRAY_LAYER_COUNT,
});
const depthStencilTexture = t.createTexture({
format: DEPTH_STENCIL_FORMAT,
width: 32,
height: 32,
mipLevelCount: MIP_LEVEL_COUNT,
arrayLayerCount: ARRAY_LAYER_COUNT,
});
const baseTextureViewDescriptor = {
dimension: '2d',
baseArrayLayer: 0,
arrayLayerCount: ARRAY_LAYER_COUNT,
baseMipLevel,
mipLevelCount,
};
{
// Check 2D texture view for color
const textureViewDescriptor = {
...baseTextureViewDescriptor,
format: COLOR_FORMAT,
};
const descriptor = {
colorAttachments: [t.getColorAttachment(colorTexture, textureViewDescriptor)],
};
await t.tryRenderPass(_success, descriptor);
}
{
// Check 2D texture view for depth stencil
const textureViewDescriptor = {
...baseTextureViewDescriptor,
format: DEPTH_STENCIL_FORMAT,
};
const descriptor = {
colorAttachments: [],
depthStencilAttachment: t.getDepthStencilAttachment(
depthStencilTexture,
textureViewDescriptor
),
};
await t.tryRenderPass(_success, descriptor);
}
});
g.test('it_is_invalid_to_set_resolve_target_if_color_attachment_is_non_multisampled').fn(
async t => {
const colorTexture = t.createTexture({ sampleCount: 1 });
const resolveTargetTexture = t.createTexture({ sampleCount: 1 });
const descriptor = {
colorAttachments: [
{
attachment: colorTexture.createView(),
resolveTarget: resolveTargetTexture.createView(),
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
};
await t.tryRenderPass(false, descriptor);
}
);
g.test('check_the_use_of_multisampled_textures_as_color_attachments').fn(async t => {
const colorTexture = t.createTexture({ sampleCount: 1 });
const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
{
// It is allowed to use a multisampled color attachment without setting resolve target
const descriptor = {
colorAttachments: [t.getColorAttachment(multisampledColorTexture)],
};
t.tryRenderPass(true, descriptor);
}
{
// It is not allowed to use multiple color attachments with different sample counts
const descriptor = {
colorAttachments: [
t.getColorAttachment(colorTexture),
t.getColorAttachment(multisampledColorTexture),
],
};
await t.tryRenderPass(false, descriptor);
}
});
g.test('it_is_invalid_to_use_a_multisampled_resolve_target').fn(async t => {
const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
const multisampledResolveTargetTexture = t.createTexture({ sampleCount: 4 });
const colorAttachment = t.getColorAttachment(multisampledColorTexture);
colorAttachment.resolveTarget = multisampledResolveTargetTexture.createView();
const descriptor = {
colorAttachments: [colorAttachment],
};
await t.tryRenderPass(false, descriptor);
});
g.test('it_is_invalid_to_use_a_resolve_target_with_array_layer_count_greater_than_1').fn(
async t => {
const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
const resolveTargetTexture = t.createTexture({ arrayLayerCount: 2 });
const colorAttachment = t.getColorAttachment(multisampledColorTexture);
colorAttachment.resolveTarget = resolveTargetTexture.createView();
const descriptor = {
colorAttachments: [colorAttachment],
};
await t.tryRenderPass(false, descriptor);
}
);
g.test('it_is_invalid_to_use_a_resolve_target_with_mipmap_level_count_greater_than_1').fn(
async t => {
const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
const resolveTargetTexture = t.createTexture({ mipLevelCount: 2 });
const colorAttachment = t.getColorAttachment(multisampledColorTexture);
colorAttachment.resolveTarget = resolveTargetTexture.createView();
const descriptor = {
colorAttachments: [colorAttachment],
};
await t.tryRenderPass(false, descriptor);
}
);
g.test('it_is_invalid_to_use_a_resolve_target_whose_usage_is_not_output_attachment').fn(async t => {
const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
const resolveTargetTexture = t.createTexture({
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
});
const colorAttachment = t.getColorAttachment(multisampledColorTexture);
colorAttachment.resolveTarget = resolveTargetTexture.createView();
const descriptor = {
colorAttachments: [colorAttachment],
};
await t.tryRenderPass(false, descriptor);
});
g.test('it_is_invalid_to_use_a_resolve_target_in_error_state').fn(async t => {
const ARRAY_LAYER_COUNT = 1;
const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
const resolveTargetTexture = t.createTexture({ arrayLayerCount: ARRAY_LAYER_COUNT });
const colorAttachment = t.getColorAttachment(multisampledColorTexture);
t.expectValidationError(() => {
colorAttachment.resolveTarget = resolveTargetTexture.createView({
dimension: '2d',
format: 'rgba8unorm',
baseArrayLayer: ARRAY_LAYER_COUNT + 1,
});
});
const descriptor = {
colorAttachments: [colorAttachment],
};
await t.tryRenderPass(false, descriptor);
});
g.test('use_of_multisampled_attachment_and_non_multisampled_resolve_target_is_allowed').fn(
async t => {
const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
const resolveTargetTexture = t.createTexture({ sampleCount: 1 });
const colorAttachment = t.getColorAttachment(multisampledColorTexture);
colorAttachment.resolveTarget = resolveTargetTexture.createView();
const descriptor = {
colorAttachments: [colorAttachment],
};
t.tryRenderPass(true, descriptor);
}
);
g.test('use_a_resolve_target_in_a_format_different_than_the_attachment_is_not_allowed').fn(
async t => {
const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
const resolveTargetTexture = t.createTexture({ format: 'bgra8unorm' });
const colorAttachment = t.getColorAttachment(multisampledColorTexture);
colorAttachment.resolveTarget = resolveTargetTexture.createView();
const descriptor = {
colorAttachments: [colorAttachment],
};
await t.tryRenderPass(false, descriptor);
}
);
g.test('size_of_the_resolve_target_must_be_the_same_as_the_color_attachment').fn(async t => {
const size = 16;
const multisampledColorTexture = t.createTexture({ width: size, height: size, sampleCount: 4 });
const resolveTargetTexture = t.createTexture({
width: size * 2,
height: size * 2,
mipLevelCount: 2,
});
{
const resolveTargetTextureView = resolveTargetTexture.createView({
baseMipLevel: 0,
mipLevelCount: 1,
});
const colorAttachment = t.getColorAttachment(multisampledColorTexture);
colorAttachment.resolveTarget = resolveTargetTextureView;
const descriptor = {
colorAttachments: [colorAttachment],
};
await t.tryRenderPass(false, descriptor);
}
{
const resolveTargetTextureView = resolveTargetTexture.createView({ baseMipLevel: 1 });
const colorAttachment = t.getColorAttachment(multisampledColorTexture);
colorAttachment.resolveTarget = resolveTargetTextureView;
const descriptor = {
colorAttachments: [colorAttachment],
};
t.tryRenderPass(true, descriptor);
}
});
g.test('check_depth_stencil_attachment_sample_counts_mismatch').fn(async t => {
const multisampledDepthStencilTexture = t.createTexture({
sampleCount: 4,
format: 'depth24plus-stencil8',
});
{
// It is not allowed to use a depth stencil attachment whose sample count is different from the
// one of the color attachment
const depthStencilTexture = t.createTexture({
sampleCount: 1,
format: 'depth24plus-stencil8',
});
const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
const descriptor = {
colorAttachments: [t.getColorAttachment(multisampledColorTexture)],
depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture),
};
await t.tryRenderPass(false, descriptor);
}
{
const colorTexture = t.createTexture({ sampleCount: 1 });
const descriptor = {
colorAttachments: [t.getColorAttachment(colorTexture)],
depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture),
};
await t.tryRenderPass(false, descriptor);
}
{
// It is allowed to use a multisampled depth stencil attachment whose sample count is equal to
// the one of the color attachment.
const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
const descriptor = {
colorAttachments: [t.getColorAttachment(multisampledColorTexture)],
depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture),
};
t.tryRenderPass(true, descriptor);
}
{
// It is allowed to use a multisampled depth stencil attachment with no color attachment
const descriptor = {
colorAttachments: [],
depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture),
};
t.tryRenderPass(true, descriptor);
}
});

View file

@ -0,0 +1,193 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
setBindGroup validation tests.
`;
import { poptions, params } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { ValidationTest } from './validation_test.js';
class F extends ValidationTest {
makeAttachmentTexture() {
return this.device.createTexture({
format: 'rgba8unorm',
size: { width: 16, height: 16, depth: 1 },
usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
});
}
testComputePass(bindGroup, dynamicOffsets) {
const encoder = this.device.createCommandEncoder();
const computePass = encoder.beginComputePass();
computePass.setBindGroup(0, bindGroup, dynamicOffsets);
computePass.endPass();
encoder.finish();
}
testRenderPass(bindGroup, dynamicOffsets) {
const encoder = this.device.createCommandEncoder();
const renderPass = encoder.beginRenderPass({
colorAttachments: [
{
attachment: this.makeAttachmentTexture().createView(),
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
});
renderPass.setBindGroup(0, bindGroup, dynamicOffsets);
renderPass.endPass();
encoder.finish();
}
testRenderBundle(bindGroup, dynamicOffsets) {
const encoder = this.device.createRenderBundleEncoder({
colorFormats: ['rgba8unorm'],
});
encoder.setBindGroup(0, bindGroup, dynamicOffsets);
encoder.finish();
}
}
export const g = makeTestGroup(F);
g.test('dynamic_offsets_passed_but_not_expected,compute_pass')
.params(poptions('type', ['compute', 'renderpass', 'renderbundle']))
.fn(async t => {
const bindGroupLayout = t.device.createBindGroupLayout({ entries: [] });
const bindGroup = t.device.createBindGroup({ layout: bindGroupLayout, entries: [] });
const { type } = t.params;
const dynamicOffsets = [0];
t.expectValidationError(() => {
if (type === 'compute') {
const encoder = t.device.createCommandEncoder();
const computePass = encoder.beginComputePass();
computePass.setBindGroup(0, bindGroup, dynamicOffsets);
computePass.endPass();
encoder.finish();
} else if (type === 'renderpass') {
const encoder = t.device.createCommandEncoder();
const renderPass = encoder.beginRenderPass({
colorAttachments: [
{
attachment: t.makeAttachmentTexture().createView(),
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
});
renderPass.setBindGroup(0, bindGroup, dynamicOffsets);
renderPass.endPass();
encoder.finish();
} else if (type === 'renderbundle') {
const encoder = t.device.createRenderBundleEncoder({
colorFormats: ['rgba8unorm'],
});
encoder.setBindGroup(0, bindGroup, dynamicOffsets);
encoder.finish();
} else {
t.fail();
}
});
});
g.test('dynamic_offsets_match_expectations_in_pass_encoder')
.params(
params()
.combine(poptions('type', ['compute', 'renderpass', 'renderbundle']))
.combine([
{ dynamicOffsets: [256, 0], _success: true }, // Dynamic offsets aligned
{ dynamicOffsets: [1, 2], _success: false }, // Dynamic offsets not aligned
// Wrong number of dynamic offsets
{ dynamicOffsets: [256, 0, 0], _success: false },
{ dynamicOffsets: [256], _success: false },
{ dynamicOffsets: [], _success: false },
// Dynamic uniform buffer out of bounds because of binding size
{ dynamicOffsets: [512, 0], _success: false },
{ dynamicOffsets: [1024, 0], _success: false },
{ dynamicOffsets: [0xffffffff, 0], _success: false },
// Dynamic storage buffer out of bounds because of binding size
{ dynamicOffsets: [0, 512], _success: false },
{ dynamicOffsets: [0, 1024], _success: false },
{ dynamicOffsets: [0, 0xffffffff], _success: false },
])
)
.fn(async t => {
// Dynamic buffer offsets require offset to be divisible by 256
const MIN_DYNAMIC_BUFFER_OFFSET_ALIGNMENT = 256;
const BINDING_SIZE = 9;
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,
type: 'uniform-buffer',
hasDynamicOffset: true,
},
{
binding: 1,
visibility: GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,
type: 'storage-buffer',
hasDynamicOffset: true,
},
],
});
const uniformBuffer = t.device.createBuffer({
size: 2 * MIN_DYNAMIC_BUFFER_OFFSET_ALIGNMENT + 8,
usage: GPUBufferUsage.UNIFORM,
});
const storageBuffer = t.device.createBuffer({
size: 2 * MIN_DYNAMIC_BUFFER_OFFSET_ALIGNMENT + 8,
usage: GPUBufferUsage.STORAGE,
});
const bindGroup = t.device.createBindGroup({
layout: bindGroupLayout,
entries: [
{
binding: 0,
resource: {
buffer: uniformBuffer,
size: BINDING_SIZE,
},
},
{
binding: 1,
resource: {
buffer: storageBuffer,
size: BINDING_SIZE,
},
},
],
});
const { type, dynamicOffsets, _success } = t.params;
t.expectValidationError(() => {
if (type === 'compute') {
t.testComputePass(bindGroup, dynamicOffsets);
} else if (type === 'renderpass') {
t.testRenderPass(bindGroup, dynamicOffsets);
} else if (type === 'renderbundle') {
t.testRenderBundle(bindGroup, dynamicOffsets);
} else {
t.fail();
}
t.testComputePass(bindGroup, dynamicOffsets);
}, !_success);
});
// TODO: test error bind group

View file

@ -0,0 +1,49 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
setBlendColor validation tests.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { ValidationTest } from './validation_test.js';
// TODO: Move beginRenderPass to a Fixture class.
class F extends ValidationTest {
beginRenderPass(commandEncoder) {
const attachmentTexture = this.device.createTexture({
format: 'rgba8unorm',
size: { width: 16, height: 16, depth: 1 },
usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
});
return commandEncoder.beginRenderPass({
colorAttachments: [
{
attachment: attachmentTexture.createView(),
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
});
}
}
export const g = makeTestGroup(F);
g.test('basic_use_of_setBlendColor').fn(t => {
const commandEncoder = t.device.createCommandEncoder();
const renderPass = t.beginRenderPass(commandEncoder);
renderPass.setBlendColor({ r: 0, g: 0, b: 0, a: 0 });
renderPass.endPass();
commandEncoder.finish();
});
g.test('setBlendColor_allows_any_number_value').fn(t => {
const values = [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];
for (const value of values) {
const commandEncoder = t.device.createCommandEncoder();
const renderPass = t.beginRenderPass(commandEncoder);
renderPass.setBlendColor({ r: value, g: value, b: value, a: value });
renderPass.endPass();
commandEncoder.finish();
}
});

View file

@ -0,0 +1,54 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
setScissorRect validation tests.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { ValidationTest } from './validation_test.js';
const TEXTURE_WIDTH = 16;
const TEXTURE_HEIGHT = 16;
// TODO: Move this fixture class to a common file.
class F extends ValidationTest {
beginRenderPass(commandEncoder) {
const attachmentTexture = this.device.createTexture({
format: 'rgba8unorm',
size: { width: TEXTURE_WIDTH, height: TEXTURE_HEIGHT, depth: 1 },
usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
});
return commandEncoder.beginRenderPass({
colorAttachments: [
{
attachment: attachmentTexture.createView(),
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
});
}
}
export const g = makeTestGroup(F);
g.test('use_of_setScissorRect')
.params([
{ x: 0, y: 0, width: 1, height: 1, _success: true }, // Basic use
{ x: 0, y: 0, width: 0, height: 1, _success: false }, // Width of zero is not allowed
{ x: 0, y: 0, width: 1, height: 0, _success: false }, // Height of zero is not allowed
{ x: 0, y: 0, width: 0, height: 0, _success: false }, // Both width and height of zero are not allowed
{ x: 0, y: 0, width: TEXTURE_WIDTH + 1, height: TEXTURE_HEIGHT + 1, _success: true }, // Scissor larger than the framebuffer is allowed
])
.fn(async t => {
const { x, y, width, height, _success } = t.params;
const commandEncoder = t.device.createCommandEncoder();
const renderPass = t.beginRenderPass(commandEncoder);
renderPass.setScissorRect(x, y, width, height);
renderPass.endPass();
t.expectValidationError(() => {
commandEncoder.finish();
}, !_success);
});

View file

@ -0,0 +1,43 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
setStencilReference validation tests.
`;
import { poptions } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { ValidationTest } from './validation_test.js';
// TODO: Move this fixture class to a common file.
class F extends ValidationTest {
beginRenderPass(commandEncoder) {
const attachmentTexture = this.device.createTexture({
format: 'rgba8unorm',
size: { width: 16, height: 16, depth: 1 },
usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
});
return commandEncoder.beginRenderPass({
colorAttachments: [
{
attachment: attachmentTexture.createView(),
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
});
}
}
export const g = makeTestGroup(F);
g.test('use_of_setStencilReference')
.params(poptions('reference', [0, 0xffffffff]))
.fn(t => {
const { reference } = t.params;
const commandEncoder = t.device.createCommandEncoder();
const renderPass = t.beginRenderPass(commandEncoder);
renderPass.setStencilReference(reference);
renderPass.endPass();
commandEncoder.finish();
});

View file

@ -0,0 +1,179 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
setVertexBuffer validation tests.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { range } from '../../../common/framework/util/util.js';
import { ValidationTest } from './validation_test.js';
class F extends ValidationTest {
getVertexBuffer() {
return this.device.createBuffer({
size: 256,
usage: GPUBufferUsage.VERTEX,
});
}
createRenderPipeline(bufferCount) {
const descriptor = {
vertexStage: this.getVertexStage(bufferCount),
fragmentStage: this.getFragmentStage(),
layout: this.getPipelineLayout(),
primitiveTopology: 'triangle-list',
colorStates: [{ format: 'rgba8unorm' }],
vertexState: {
vertexBuffers: [
{
arrayStride: 3 * 4,
attributes: range(bufferCount, i => ({
format: 'float3',
offset: 0,
shaderLocation: i,
})),
},
],
},
};
return this.device.createRenderPipeline(descriptor);
}
getVertexStage(bufferCount) {
const glsl = `
#version 450
${range(bufferCount, i => `\nlayout(location = ${i}) in vec3 a_position${i};`).join('')}
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
return {
module: this.makeShaderModule('vertex', { glsl }),
entryPoint: 'main',
};
}
getFragmentStage() {
const glsl = `
#version 450
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
`;
return {
module: this.makeShaderModule('fragment', { glsl }),
entryPoint: 'main',
};
}
getPipelineLayout() {
return this.device.createPipelineLayout({ bindGroupLayouts: [] });
}
beginRenderPass(commandEncoder) {
const attachmentTexture = this.device.createTexture({
format: 'rgba8unorm',
size: { width: 16, height: 16, depth: 1 },
usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
});
return commandEncoder.beginRenderPass({
colorAttachments: [
{
attachment: attachmentTexture.createView(),
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
});
}
}
export const g = makeTestGroup(F);
g.test('vertex_buffers_inherit_from_previous_pipeline').fn(async t => {
const pipeline1 = t.createRenderPipeline(1);
const pipeline2 = t.createRenderPipeline(2);
const vertexBuffer1 = t.getVertexBuffer();
const vertexBuffer2 = t.getVertexBuffer();
{
// Check failure when vertex buffer is not set
const commandEncoder = t.device.createCommandEncoder();
const renderPass = t.beginRenderPass(commandEncoder);
renderPass.setPipeline(pipeline1);
renderPass.draw(3, 1, 0, 0);
renderPass.endPass();
t.expectValidationError(() => {
commandEncoder.finish();
});
}
{
// Check success when vertex buffer is inherited from previous pipeline
const commandEncoder = t.device.createCommandEncoder();
const renderPass = t.beginRenderPass(commandEncoder);
renderPass.setPipeline(pipeline2);
renderPass.setVertexBuffer(0, vertexBuffer1);
renderPass.setVertexBuffer(1, vertexBuffer2);
renderPass.draw(3, 1, 0, 0);
renderPass.setPipeline(pipeline1);
renderPass.draw(3, 1, 0, 0);
renderPass.endPass();
commandEncoder.finish();
}
});
g.test('vertex_buffers_do_not_inherit_between_render_passes').fn(async t => {
const pipeline1 = t.createRenderPipeline(1);
const pipeline2 = t.createRenderPipeline(2);
const vertexBuffer1 = t.getVertexBuffer();
const vertexBuffer2 = t.getVertexBuffer();
{
// Check success when vertex buffer is set for each render pass
const commandEncoder = t.device.createCommandEncoder();
{
const renderPass = t.beginRenderPass(commandEncoder);
renderPass.setPipeline(pipeline2);
renderPass.setVertexBuffer(0, vertexBuffer1);
renderPass.setVertexBuffer(1, vertexBuffer2);
renderPass.draw(3, 1, 0, 0);
renderPass.endPass();
}
{
const renderPass = t.beginRenderPass(commandEncoder);
renderPass.setPipeline(pipeline1);
renderPass.setVertexBuffer(0, vertexBuffer1);
renderPass.draw(3, 1, 0, 0);
renderPass.endPass();
}
commandEncoder.finish();
}
{
// Check failure because vertex buffer is not inherited in second subpass
const commandEncoder = t.device.createCommandEncoder();
{
const renderPass = t.beginRenderPass(commandEncoder);
renderPass.setPipeline(pipeline2);
renderPass.setVertexBuffer(0, vertexBuffer1);
renderPass.setVertexBuffer(1, vertexBuffer2);
renderPass.draw(3, 1, 0, 0);
renderPass.endPass();
}
{
const renderPass = t.beginRenderPass(commandEncoder);
renderPass.setPipeline(pipeline1);
renderPass.draw(3, 1, 0, 0);
renderPass.endPass();
}
t.expectValidationError(() => {
commandEncoder.finish();
});
}
});

View file

@ -0,0 +1,73 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
setViewport validation tests.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { ValidationTest } from './validation_test.js';
const TEXTURE_WIDTH = 16;
const TEXTURE_HEIGHT = 16;
// TODO: Move this fixture class to a common file.
class F extends ValidationTest {
beginRenderPass(commandEncoder) {
const attachmentTexture = this.device.createTexture({
format: 'rgba8unorm',
size: { width: TEXTURE_WIDTH, height: TEXTURE_HEIGHT, depth: 1 },
usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
});
return commandEncoder.beginRenderPass({
colorAttachments: [
{
attachment: attachmentTexture.createView(),
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
},
],
});
}
}
export const g = makeTestGroup(F);
g.test('use_of_setViewport')
.params([
{ x: 0, y: 0, width: 1, height: 1, minDepth: 0, maxDepth: 1, _success: true }, // Basic use
{ x: 0, y: 0, width: 0, height: 1, minDepth: 0, maxDepth: 1, _success: false }, // Width of zero is not allowed
{ x: 0, y: 0, width: 1, height: 0, minDepth: 0, maxDepth: 1, _success: false }, // Height of zero is not allowed
{ x: 0, y: 0, width: 0, height: 0, minDepth: 0, maxDepth: 1, _success: false }, // Both width and height of zero are not allowed
{ x: -1, y: 0, width: 1, height: 1, minDepth: 0, maxDepth: 1, _success: true }, // Negative x is allowed
{ x: 0, y: -1, width: 1, height: 1, minDepth: 0, maxDepth: 1, _success: true }, // Negative y is allowed
{ x: 0, y: 0, width: -1, height: 1, minDepth: 0, maxDepth: 1, _success: false }, // Negative width is not allowed
{ x: 0, y: 0, width: 1, height: -1, minDepth: 0, maxDepth: 1, _success: false }, // Negative height is not allowed
{ x: 0, y: 0, width: 1, height: 1, minDepth: -1, maxDepth: 1, _success: false }, // Negative minDepth is not allowed
{ x: 0, y: 0, width: 1, height: 1, minDepth: 0, maxDepth: -1, _success: false }, // Negative maxDepth is not allowed
{ x: 0, y: 0, width: 1, height: 1, minDepth: 10, maxDepth: 1, _success: false }, // minDepth greater than 1 is not allowed
{ x: 0, y: 0, width: 1, height: 1, minDepth: 0, maxDepth: 10, _success: false }, // maxDepth greater than 1 is not allowed
{ x: 0, y: 0, width: 1, height: 1, minDepth: 0.5, maxDepth: 0.5, _success: true }, // minDepth equal to maxDepth is allowed
{ x: 0, y: 0, width: 1, height: 1, minDepth: 0.8, maxDepth: 0.5, _success: true }, // minDepth greater than maxDepth is allowed
{
x: 0,
y: 0,
width: TEXTURE_WIDTH + 1,
height: TEXTURE_HEIGHT + 1,
minDepth: 0,
maxDepth: 1,
_success: true,
},
// Viewport larger than the framebuffer is allowed
])
.fn(async t => {
const { x, y, width, height, minDepth, maxDepth, _success } = t.params;
const commandEncoder = t.device.createCommandEncoder();
const renderPass = t.beginRenderPass(commandEncoder);
renderPass.setViewport(x, y, width, height, minDepth, maxDepth);
renderPass.endPass();
t.expectValidationError(() => {
commandEncoder.finish();
}, !_success);
});

View file

@ -0,0 +1,116 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { unreachable } from '../../../common/framework/util/util.js';
import { GPUTest } from '../../gpu_test.js';
export class ValidationTest extends GPUTest {
getStorageBuffer() {
return this.device.createBuffer({ size: 1024, usage: GPUBufferUsage.STORAGE });
}
getUniformBuffer() {
return this.device.createBuffer({ size: 1024, usage: GPUBufferUsage.UNIFORM });
}
getErrorBuffer() {
this.device.pushErrorScope('validation');
const errorBuffer = this.device.createBuffer({
size: 1024,
usage: 0xffff, // Invalid GPUBufferUsage
});
this.device.popErrorScope();
return errorBuffer;
}
getSampler() {
return this.device.createSampler();
}
getComparisonSampler() {
return this.device.createSampler({ compare: 'never' });
}
getErrorSampler() {
this.device.pushErrorScope('validation');
const sampler = this.device.createSampler({ lodMinClamp: -1 });
this.device.popErrorScope();
return sampler;
}
getSampledTexture() {
return this.device.createTexture({
size: { width: 16, height: 16, depth: 1 },
format: 'rgba8unorm',
usage: GPUTextureUsage.SAMPLED,
});
}
getStorageTexture() {
return this.device.createTexture({
size: { width: 16, height: 16, depth: 1 },
format: 'rgba8unorm',
usage: GPUTextureUsage.STORAGE,
});
}
getErrorTextureView() {
this.device.pushErrorScope('validation');
const view = this.device
.createTexture({
size: { width: 0, height: 0, depth: 0 },
format: 'rgba8unorm',
usage: GPUTextureUsage.SAMPLED,
})
.createView();
this.device.popErrorScope();
return view;
}
getBindingResource(bindingType) {
switch (bindingType) {
case 'errorBuf':
return { buffer: this.getErrorBuffer() };
case 'errorSamp':
return this.getErrorSampler();
case 'errorTex':
return this.getErrorTextureView();
case 'uniformBuf':
return { buffer: this.getUniformBuffer() };
case 'storageBuf':
return { buffer: this.getStorageBuffer() };
case 'plainSamp':
return this.getSampler();
case 'compareSamp':
return this.getComparisonSampler();
case 'sampledTex':
return this.getSampledTexture().createView();
case 'storageTex':
return this.getStorageTexture().createView();
default:
unreachable('unknown binding resource type');
}
}
expectValidationError(fn, shouldError = true) {
// If no error is expected, we let the scope surrounding the test catch it.
if (shouldError === false) {
fn();
return;
}
this.device.pushErrorScope('validation');
fn();
const promise = this.device.popErrorScope();
this.eventualAsyncExpectation(async niceStack => {
const gpuValidationError = await promise;
if (!gpuValidationError) {
niceStack.message = 'Validation error was expected.';
this.rec.validationFailed(niceStack);
} else if (gpuValidationError instanceof GPUValidationError) {
niceStack.message = `Captured validation error - ${gpuValidationError.message}`;
this.rec.debug(niceStack);
}
});
}
}

View file

@ -0,0 +1,722 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
vertexState validation tests.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { ValidationTest } from './validation_test.js';
const MAX_VERTEX_ATTRIBUTES = 16;
const MAX_VERTEX_BUFFER_END = 2048;
const MAX_VERTEX_BUFFER_ARRAY_STRIDE = 2048;
const MAX_VERTEX_BUFFERS = 16;
const SIZEOF_FLOAT = Float32Array.BYTES_PER_ELEMENT;
const VERTEX_SHADER_CODE_WITH_NO_INPUT = `
#version 450
void main() {
gl_Position = vec4(0.0);
}
`;
const FRAGMENT_SHADER_CODE = `
#version 450
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
`;
function clone(descriptor) {
return JSON.parse(JSON.stringify(descriptor));
}
class F extends ValidationTest {
getDescriptor(vertexState, vertexShaderCode) {
const descriptor = {
vertexStage: {
module: this.makeShaderModule('vertex', { glsl: vertexShaderCode }),
entryPoint: 'main',
},
fragmentStage: {
module: this.makeShaderModule('fragment', { glsl: FRAGMENT_SHADER_CODE }),
entryPoint: 'main',
},
layout: this.device.createPipelineLayout({ bindGroupLayouts: [] }),
primitiveTopology: 'triangle-list',
colorStates: [{ format: 'rgba8unorm' }],
vertexState,
};
return descriptor;
}
}
export const g = makeTestGroup(F);
g.test('an_empty_vertex_input_is_valid').fn(t => {
const vertexState = {};
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
});
g.test('a_null_buffer_is_valid').fn(t => {
{
// One null buffer is OK
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: [],
},
],
};
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// One null buffer followed by a buffer is OK
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: [],
},
{
arrayStride: 0,
attributes: [
{
format: 'float',
offset: 0,
shaderLocation: 0,
},
],
},
],
};
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// One null buffer sitting between buffers is OK
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: [
{
format: 'float',
offset: 0,
shaderLocation: 0,
},
],
},
{
arrayStride: 0,
attributes: [],
},
{
arrayStride: 0,
attributes: [
{
format: 'float',
offset: 0,
shaderLocation: 1,
},
],
},
],
};
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
});
g.test('pipeline_vertex_buffers_are_backed_by_attributes_in_vertex_input').fn(async t => {
const vertexState = {
vertexBuffers: [
{
arrayStride: 2 * SIZEOF_FLOAT,
attributes: [
{
format: 'float',
offset: 0,
shaderLocation: 0,
},
{
format: 'float',
offset: 0,
shaderLocation: 1,
},
],
},
],
};
{
// Control case: pipeline with one input per attribute
const code = `
#version 450
layout(location = 0) in vec4 a;
layout(location = 1) in vec4 b;
void main() {
gl_Position = vec4(0.0);
}
`;
const descriptor = t.getDescriptor(vertexState, code);
t.device.createRenderPipeline(descriptor);
}
{
// Check it is valid for the pipeline to use a subset of the VertexState
const code = `
#version 450
layout(location = 0) in vec4 a;
void main() {
gl_Position = vec4(0.0);
}
`;
const descriptor = t.getDescriptor(vertexState, code);
t.device.createRenderPipeline(descriptor);
}
{
// Check for an error when the pipeline uses an attribute not in the vertex input
const code = `
#version 450
layout(location = 2) in vec4 a;
void main() {
gl_Position = vec4(0.0);
}
`;
const descriptor = t.getDescriptor(vertexState, code);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
});
g.test('an_arrayStride_of_0_is_valid').fn(t => {
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: [
{
format: 'float',
offset: 0,
shaderLocation: 0,
},
],
},
],
};
{
// Works ok without attributes
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Works ok with attributes at a large-ish offset
vertexState.vertexBuffers[0].attributes[0].offset = 128;
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
});
g.test('offset_should_be_within_vertex_buffer_arrayStride_if_arrayStride_is_not_zero').fn(
async t => {
const vertexState = {
vertexBuffers: [
{
arrayStride: 2 * SIZEOF_FLOAT,
attributes: [
{
format: 'float',
offset: 0,
shaderLocation: 0,
},
{
format: 'float',
offset: SIZEOF_FLOAT,
shaderLocation: 1,
},
],
},
],
};
{
// Control case, setting correct arrayStride and offset
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Test vertex attribute offset exceed vertex buffer arrayStride range
const badVertexState = clone(vertexState);
badVertexState.vertexBuffers[0].attributes[1].format = 'float2';
const descriptor = t.getDescriptor(badVertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
{
// Test vertex attribute offset exceed vertex buffer arrayStride range
const badVertexState = clone(vertexState);
badVertexState.vertexBuffers[0].arrayStride = SIZEOF_FLOAT;
const descriptor = t.getDescriptor(badVertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
{
// It's OK if arrayStride is zero
const goodVertexState = clone(vertexState);
goodVertexState.vertexBuffers[0].arrayStride = 0;
const descriptor = t.getDescriptor(goodVertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
}
);
// TODO: This should be made into an operation test.
g.test('check_two_attributes_overlapping').fn(async t => {
const vertexState = {
vertexBuffers: [
{
arrayStride: 2 * SIZEOF_FLOAT,
attributes: [
{
format: 'float',
offset: 0,
shaderLocation: 0,
},
{
format: 'float',
offset: SIZEOF_FLOAT,
shaderLocation: 1,
},
],
},
],
};
{
// Control case, setting correct arrayStride and offset
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Test two attributes overlapping
const overlappingVertexState = clone(vertexState);
overlappingVertexState.vertexBuffers[0].attributes[0].format = 'int2';
const descriptor = t.getDescriptor(overlappingVertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
});
g.test('check_out_of_bounds_condition_on_total_number_of_vertex_buffers').fn(async t => {
const vertexBuffers = [];
for (let i = 0; i < MAX_VERTEX_BUFFERS; i++) {
vertexBuffers.push({
arrayStride: 0,
attributes: [
{
format: 'float',
offset: 0,
shaderLocation: i,
},
],
});
}
{
// Control case, setting max vertex buffer number
const vertexState = { vertexBuffers };
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Test vertex buffer number exceed the limit
const vertexState = {
vertexBuffers: [
...vertexBuffers,
{
arrayStride: 0,
attributes: [
{
format: 'float',
offset: 0,
shaderLocation: MAX_VERTEX_BUFFERS,
},
],
},
],
};
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
});
g.test('check_out_of_bounds_on_number_of_vertex_attributes_on_a_single_vertex_buffer').fn(
async t => {
const vertexAttributes = [];
for (let i = 0; i < MAX_VERTEX_ATTRIBUTES; i++) {
vertexAttributes.push({
format: 'float',
offset: 0,
shaderLocation: i,
});
}
{
// Control case, setting max vertex buffer number
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: vertexAttributes,
},
],
};
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Test vertex attribute number exceed the limit
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: [
...vertexAttributes,
{
format: 'float',
offset: 0,
shaderLocation: MAX_VERTEX_ATTRIBUTES,
},
],
},
],
};
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
}
);
g.test('check_out_of_bounds_on_number_of_vertex_attributes_across_vertex_buffers').fn(async t => {
const vertexBuffers = [];
for (let i = 0; i < MAX_VERTEX_ATTRIBUTES; i++) {
vertexBuffers.push({
arrayStride: 0,
attributes: [{ format: 'float', offset: 0, shaderLocation: i }],
});
}
{
// Control case, setting max vertex buffer number
const vertexState = { vertexBuffers };
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Test vertex attribute number exceed the limit
vertexBuffers[MAX_VERTEX_ATTRIBUTES - 1].attributes.push({
format: 'float',
offset: 0,
shaderLocation: MAX_VERTEX_ATTRIBUTES,
});
const vertexState = { vertexBuffers };
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
});
g.test('check_out_of_bounds_condition_on_input_strides').fn(async t => {
const vertexState = {
vertexBuffers: [{ arrayStride: MAX_VERTEX_BUFFER_ARRAY_STRIDE, attributes: [] }],
};
{
// Control case, setting max input arrayStride
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Test input arrayStride OOB
vertexState.vertexBuffers[0].arrayStride = MAX_VERTEX_BUFFER_ARRAY_STRIDE + 4;
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
});
g.test('check_multiple_of_4_bytes_constraint_on_input_arrayStride').fn(async t => {
const vertexState = {
vertexBuffers: [
{
arrayStride: 4,
attributes: [{ format: 'uchar2', offset: 0, shaderLocation: 0 }],
},
],
};
{
// Control case, setting input arrayStride 4 bytes
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Test input arrayStride not multiple of 4 bytes
vertexState.vertexBuffers[0].arrayStride = 2;
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
});
g.test('identical_duplicate_attributes_are_invalid').fn(async t => {
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: [{ format: 'float', offset: 0, shaderLocation: 0 }],
},
],
};
{
// Control case, setting attribute 0
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Oh no, attribute 0 is set twice
vertexState.vertexBuffers[0].attributes.push({
format: 'float',
offset: 0,
shaderLocation: 0,
});
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
});
g.test('we_cannot_set_same_shader_location').fn(async t => {
{
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: [
{ format: 'float', offset: 0, shaderLocation: 0 },
{ format: 'float', offset: SIZEOF_FLOAT, shaderLocation: 1 },
],
},
],
};
{
// Control case, setting different shader locations in two attributes
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Test same shader location in two attributes in the same buffer
vertexState.vertexBuffers[0].attributes[1].shaderLocation = 0;
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
}
{
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: [
{
format: 'float',
offset: 0,
shaderLocation: 0,
},
],
},
{
arrayStride: 0,
attributes: [
{
format: 'float',
offset: 0,
shaderLocation: 0,
},
],
},
],
};
// Test same shader location in two attributes in different buffers
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
});
g.test('check_out_of_bounds_condition_on_attribute_shader_location').fn(async t => {
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: [{ format: 'float', offset: 0, shaderLocation: MAX_VERTEX_ATTRIBUTES - 1 }],
},
],
};
{
// Control case, setting last attribute shader location
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Test attribute location OOB
vertexState.vertexBuffers[0].attributes[0].shaderLocation = MAX_VERTEX_ATTRIBUTES;
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
});
g.test('check_attribute_offset_out_of_bounds').fn(async t => {
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: [
{
format: 'float2',
offset: MAX_VERTEX_BUFFER_END - 2 * SIZEOF_FLOAT,
shaderLocation: 0,
},
],
},
],
};
{
// Control case, setting max attribute offset to MAX_VERTEX_BUFFER_END - 8
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Control case, setting attribute offset to 8
vertexState.vertexBuffers[0].attributes[0].offset = 8;
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Test attribute offset out of bounds
vertexState.vertexBuffers[0].attributes[0].offset = MAX_VERTEX_BUFFER_END - 4;
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
});
g.test('check_multiple_of_4_bytes_constraint_on_offset').fn(async t => {
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: [{ format: 'float', offset: SIZEOF_FLOAT, shaderLocation: 0 }],
},
],
};
{
// Control case, setting offset 4 bytes
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.device.createRenderPipeline(descriptor);
}
{
// Test offset of 2 bytes with uchar2 format
vertexState.vertexBuffers[0].attributes[0].offset = 2;
vertexState.vertexBuffers[0].attributes[0].format = 'uchar2';
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
{
// Test offset of 2 bytes with float format
vertexState.vertexBuffers[0].attributes[0].offset = 2;
vertexState.vertexBuffers[0].attributes[0].format = 'float';
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
}
});
g.test('check_attribute_offset_overflow').fn(async t => {
const vertexState = {
vertexBuffers: [
{
arrayStride: 0,
attributes: [{ format: 'float', offset: Number.MAX_SAFE_INTEGER, shaderLocation: 0 }],
},
],
};
const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
t.expectValidationError(() => {
t.device.createRenderPipeline(descriptor);
});
});

View file

@ -0,0 +1,629 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/
function keysOf(obj) {
return Object.keys(obj);
}
function numericKeysOf(obj) {
return Object.keys(obj).map(n => Number(n));
}
// Textures
export const kTextureFormatInfo = {
// Try to keep these manually-formatted in a readable grid.
// (Note: this list should always match the one in the spec.)
// 8-bit formats
r8unorm: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 1,
blockWidth: 1,
blockHeight: 1,
},
r8snorm: {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 1,
blockWidth: 1,
blockHeight: 1,
},
r8uint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 1,
blockWidth: 1,
blockHeight: 1,
},
r8sint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 1,
blockWidth: 1,
blockHeight: 1,
},
// 16-bit formats
r16uint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
},
r16sint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
},
r16float: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
},
rg8unorm: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
},
rg8snorm: {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
},
rg8uint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
},
rg8sint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 2,
blockWidth: 1,
blockHeight: 1,
},
// 32-bit formats
r32uint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
r32sint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
r32float: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
rg16uint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
rg16sint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
rg16float: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
rgba8unorm: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
'rgba8unorm-srgb': {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
rgba8snorm: {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
rgba8uint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
rgba8sint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
bgra8unorm: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
'bgra8unorm-srgb': {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
// Packed 32-bit formats
rgb10a2unorm: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
rg11b10float: {
renderable: false,
color: true,
depth: false,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
// 64-bit formats
rg32uint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 8,
blockWidth: 1,
blockHeight: 1,
},
rg32sint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 8,
blockWidth: 1,
blockHeight: 1,
},
rg32float: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 8,
blockWidth: 1,
blockHeight: 1,
},
rgba16uint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 8,
blockWidth: 1,
blockHeight: 1,
},
rgba16sint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 8,
blockWidth: 1,
blockHeight: 1,
},
rgba16float: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 8,
blockWidth: 1,
blockHeight: 1,
},
// 128-bit formats
rgba32uint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 16,
blockWidth: 1,
blockHeight: 1,
},
rgba32sint: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 16,
blockWidth: 1,
blockHeight: 1,
},
rgba32float: {
renderable: true,
color: true,
depth: false,
stencil: false,
storage: true,
copyable: true,
bytesPerBlock: 16,
blockWidth: 1,
blockHeight: 1,
},
// Depth/stencil formats
depth32float: {
renderable: true,
color: false,
depth: true,
stencil: false,
storage: false,
copyable: true,
bytesPerBlock: 4,
blockWidth: 1,
blockHeight: 1,
},
depth24plus: {
renderable: true,
color: false,
depth: true,
stencil: false,
storage: false,
copyable: false,
},
'depth24plus-stencil8': {
renderable: true,
color: false,
depth: true,
stencil: true,
storage: false,
copyable: false,
},
};
export const kTextureFormats = keysOf(kTextureFormatInfo);
export const kTextureDimensionInfo = {
'1d': {},
'2d': {},
'3d': {},
};
export const kTextureDimensions = keysOf(kTextureDimensionInfo);
export const kTextureAspectInfo = {
all: {},
'depth-only': {},
'stencil-only': {},
};
export const kTextureAspects = keysOf(kTextureAspectInfo);
export const kTextureUsageInfo = {
[GPUTextureUsage.COPY_SRC]: {},
[GPUTextureUsage.COPY_DST]: {},
[GPUTextureUsage.SAMPLED]: {},
[GPUTextureUsage.STORAGE]: {},
[GPUTextureUsage.OUTPUT_ATTACHMENT]: {},
};
export const kTextureUsages = numericKeysOf(kTextureUsageInfo);
export const kTextureComponentTypeInfo = {
float: {},
sint: {},
uint: {},
};
export const kTextureComponentTypes = keysOf(kTextureComponentTypeInfo);
// Texture View
export const kTextureViewDimensionInfo = {
'1d': {},
'2d': {},
'2d-array': {},
cube: {},
'cube-array': {},
'3d': {},
};
export const kTextureViewDimensions = keysOf(kTextureViewDimensionInfo);
// Typedefs for bindings
// Bindings
export const kMaxBindingsPerBindGroup = 16;
export const kPerStageBindingLimits = {
uniformBuf: { class: 'uniformBuf', max: 12 },
storageBuf: { class: 'storageBuf', max: 4 },
sampler: { class: 'sampler', max: 16 },
sampledTex: { class: 'sampledTex', max: 16 },
storageTex: { class: 'storageTex', max: 4 },
};
export const kPerPipelineBindingLimits = {
uniformBuf: { class: 'uniformBuf', maxDynamic: 8 },
storageBuf: { class: 'storageBuf', maxDynamic: 4 },
sampler: { class: 'sampler', maxDynamic: 0 },
sampledTex: { class: 'sampledTex', maxDynamic: 0 },
storageTex: { class: 'storageTex', maxDynamic: 0 },
};
const kBindableResource = {
uniformBuf: {},
storageBuf: {},
plainSamp: {},
compareSamp: {},
sampledTex: {},
storageTex: {},
errorBuf: {},
errorSamp: {},
errorTex: {},
};
export const kBindableResources = keysOf(kBindableResource);
const kBindingKind = {
uniformBuf: {
resource: 'uniformBuf',
perStageLimitClass: kPerStageBindingLimits.uniformBuf,
perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf,
},
storageBuf: {
resource: 'storageBuf',
perStageLimitClass: kPerStageBindingLimits.storageBuf,
perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf,
},
plainSamp: {
resource: 'plainSamp',
perStageLimitClass: kPerStageBindingLimits.sampler,
perPipelineLimitClass: kPerPipelineBindingLimits.sampler,
},
compareSamp: {
resource: 'compareSamp',
perStageLimitClass: kPerStageBindingLimits.sampler,
perPipelineLimitClass: kPerPipelineBindingLimits.sampler,
},
sampledTex: {
resource: 'sampledTex',
perStageLimitClass: kPerStageBindingLimits.sampledTex,
perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex,
},
storageTex: {
resource: 'storageTex',
perStageLimitClass: kPerStageBindingLimits.storageTex,
perPipelineLimitClass: kPerPipelineBindingLimits.storageTex,
},
};
// Binding type info
const kValidStagesAll = {
validStages: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE,
};
const kValidStagesStorageWrite = { validStages: GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE };
export const kBufferBindingTypeInfo = {
'uniform-buffer': {
usage: GPUBufferUsage.UNIFORM,
...kBindingKind.uniformBuf,
...kValidStagesAll,
},
'storage-buffer': {
usage: GPUBufferUsage.STORAGE,
...kBindingKind.storageBuf,
...kValidStagesStorageWrite,
},
'readonly-storage-buffer': {
usage: GPUBufferUsage.STORAGE,
...kBindingKind.storageBuf,
...kValidStagesAll,
},
};
export const kBufferBindingTypes = keysOf(kBufferBindingTypeInfo);
export const kSamplerBindingTypeInfo = {
sampler: { ...kBindingKind.plainSamp, ...kValidStagesAll },
'comparison-sampler': { ...kBindingKind.compareSamp, ...kValidStagesAll },
};
export const kSamplerBindingTypes = keysOf(kSamplerBindingTypeInfo);
export const kTextureBindingTypeInfo = {
'sampled-texture': {
usage: GPUTextureUsage.SAMPLED,
...kBindingKind.sampledTex,
...kValidStagesAll,
},
'writeonly-storage-texture': {
usage: GPUTextureUsage.STORAGE,
...kBindingKind.storageTex,
...kValidStagesStorageWrite,
},
'readonly-storage-texture': {
usage: GPUTextureUsage.STORAGE,
...kBindingKind.storageTex,
...kValidStagesAll,
},
};
export const kTextureBindingTypes = keysOf(kTextureBindingTypeInfo);
// All binding types (merged from above)
export const kBindingTypeInfo = {
...kBufferBindingTypeInfo,
...kSamplerBindingTypeInfo,
...kTextureBindingTypeInfo,
};
export const kBindingTypes = keysOf(kBindingTypeInfo);
export const kShaderStages = [
GPUShaderStage.VERTEX,
GPUShaderStage.FRAGMENT,
GPUShaderStage.COMPUTE,
];
export const kShaderStageCombinations = [0, 1, 2, 3, 4, 5, 6, 7];

View file

@ -0,0 +1,103 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
Examples of writing CTS tests with various features.
Start here when looking for examples of basic framework usage.
`;
import { makeTestGroup } from '../common/framework/test_group.js';
import { GPUTest } from './gpu_test.js';
// To run these tests in the standalone runner, run `grunt build` or `grunt pre` then open:
// - http://localhost:8080/?runnow=1&q=webgpu:examples:
// To run in WPT, copy/symlink the out-wpt/ directory as the webgpu/ directory in WPT, then open:
// - (wpt server url)/webgpu/cts.html?q=webgpu:examples:
//
// Tests here can be run individually or in groups:
// - ?q=webgpu:examples:basic/async=
// - ?q=webgpu:examples:basic/
// - ?q=webgpu:examples:
export const g = makeTestGroup(GPUTest);
// Note: spaces in test names are replaced with underscores: webgpu:examples:test_name=
g.test('test_name').fn(t => {});
g.test('basic').fn(t => {
t.expect(true);
t.expect(true, 'true should be true');
t.shouldThrow(
// The expected '.name' of the thrown error.
'TypeError',
// This function is run inline inside shouldThrow, and is expected to throw.
() => {
throw new TypeError();
},
// Log message.
'function should throw Error'
);
});
g.test('basic,async').fn(async t => {
// shouldReject must be awaited to ensure it can wait for the promise before the test ends.
t.shouldReject(
// The expected '.name' of the thrown error.
'TypeError',
// Promise expected to reject.
Promise.reject(new TypeError()),
// Log message.
'Promise.reject should reject'
);
// Promise can also be an IIFE.
t.shouldReject(
'TypeError',
(async () => {
throw new TypeError();
})(),
'Promise.reject should reject'
);
});
// A test can be parameterized with a simple array of objects.
//
// Parameters can be public (x, y) which means they're part of the case name.
// They can also be private by starting with an underscore (_result), which passes
// them into the test but does not make them part of the case name:
//
// - webgpu:examples:basic/params={"x":2,"y":4} runs with t.params = {x: 2, y: 5, _result: 6}.
// - webgpu:examples:basic/params={"x":-10,"y":18} runs with t.params = {x: -10, y: 18, _result: 8}.
g.test('basic,params')
.params([
{ x: 2, y: 4, _result: 6 }, //
{ x: -10, y: 18, _result: 8 },
])
.fn(t => {
t.expect(t.params.x + t.params.y === t.params._result);
});
// (note the blank comment above to enforce newlines on autoformat)
g.test('gpu,async').fn(async t => {
const fence = t.queue.createFence();
t.queue.signal(fence, 2);
await fence.onCompletion(1);
t.expect(fence.getCompletedValue() === 2);
});
g.test('gpu,buffers').fn(async t => {
const data = new Uint32Array([0, 1234, 0]);
const [src, map] = t.device.createBufferMapped({
size: 12,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
new Uint32Array(map).set(data);
src.unmap();
// Use the expectContents helper to check the actual contents of a GPUBuffer.
// Like shouldReject, it must be awaited.
t.expectContents(src, data);
});

View file

@ -0,0 +1,292 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { Fixture } from '../common/framework/fixture.js';
import { compileGLSL, initGLSL } from '../common/framework/glsl.js';
import { DevicePool, TestOOMedShouldAttemptGC } from '../common/framework/gpu/device_pool.js';
import { attemptGarbageCollection } from '../common/framework/util/collect_garbage.js';
import { assert } from '../common/framework/util/util.js';
import { fillTextureDataWithTexelValue, getTextureCopyLayout } from './util/texture/layout.js';
import { getTexelDataRepresentation } from './util/texture/texelData.js';
const devicePool = new DevicePool();
export class GPUTest extends Fixture {
constructor(...args) {
super(...args);
_defineProperty(this, 'objects', undefined);
_defineProperty(this, 'initialized', false);
}
get device() {
assert(this.objects !== undefined);
return this.objects.device;
}
get queue() {
assert(this.objects !== undefined);
return this.objects.queue;
}
async init() {
await super.init();
await initGLSL();
const device = await devicePool.acquire();
const queue = device.defaultQueue;
this.objects = { device, queue };
}
// Note: finalize is called even if init was unsuccessful.
async finalize() {
await super.finalize();
if (this.objects) {
let threw;
{
const objects = this.objects;
this.objects = undefined;
try {
await devicePool.release(objects.device);
} catch (ex) {
threw = ex;
}
}
// The GPUDevice and GPUQueue should now have no outstanding references.
if (threw) {
if (threw instanceof TestOOMedShouldAttemptGC) {
// Try to clean up, in case there are stray GPU resources in need of collection.
await attemptGarbageCollection();
}
throw threw;
}
}
}
makeShaderModule(stage, code) {
// If both are provided, always choose WGSL. (Can change this if needed.)
if ('wgsl' in code) {
return this.device.createShaderModule({ code: code.wgsl });
} else {
const spirv = compileGLSL(code.glsl, stage, false);
return this.device.createShaderModule({ code: spirv });
}
}
createCopyForMapRead(src, start, size) {
const dst = this.device.createBuffer({
size,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
const c = this.device.createCommandEncoder();
c.copyBufferToBuffer(src, start, dst, 0, size);
this.queue.submit([c.finish()]);
return dst;
}
// TODO: add an expectContents for textures, which logs data: uris on failure
expectContents(src, expected) {
this.expectSubContents(src, 0, expected);
}
expectSubContents(src, start, expected) {
const dst = this.createCopyForMapRead(src, start, expected.buffer.byteLength);
this.eventualAsyncExpectation(async niceStack => {
const constructor = expected.constructor;
const actual = new constructor(await dst.mapReadAsync());
const check = this.checkBuffer(actual, expected);
if (check !== undefined) {
niceStack.message = check;
this.rec.expectationFailed(niceStack);
}
dst.destroy();
});
}
expectBuffer(actual, exp) {
const check = this.checkBuffer(actual, exp);
if (check !== undefined) {
this.rec.expectationFailed(new Error(check));
}
}
checkBuffer(actual, exp, tolerance = 0) {
assert(actual.constructor === exp.constructor);
const size = exp.byteLength;
if (actual.byteLength !== size) {
return 'size mismatch';
}
const failedByteIndices = [];
const failedByteExpectedValues = [];
const failedByteActualValues = [];
for (let i = 0; i < size; ++i) {
const tol = typeof tolerance === 'function' ? tolerance(i) : tolerance;
if (Math.abs(actual[i] - exp[i]) > tol) {
if (failedByteIndices.length >= 4) {
failedByteIndices.push('...');
failedByteExpectedValues.push('...');
failedByteActualValues.push('...');
break;
}
failedByteIndices.push(i.toString());
failedByteExpectedValues.push(exp[i].toString());
failedByteActualValues.push(actual[i].toString());
}
}
const summary = `at [${failedByteIndices.join(', ')}], \
expected [${failedByteExpectedValues.join(', ')}], \
got [${failedByteActualValues.join(', ')}]`;
const lines = [summary];
// TODO: Could make a more convenient message, which could look like e.g.:
//
// Starting at offset 48,
// got 22222222 ABCDABCD 99999999
// but expected 22222222 55555555 99999999
//
// or
//
// Starting at offset 0,
// got 00000000 00000000 00000000 00000000 (... more)
// but expected 00FF00FF 00FF00FF 00FF00FF 00FF00FF (... more)
//
// Or, maybe these diffs aren't actually very useful (given we have the prints just above here),
// and we should remove them. More important will be logging of texture data in a visual format.
if (size <= 256 && failedByteIndices.length > 0) {
const expHex = Array.from(new Uint8Array(exp.buffer, exp.byteOffset, exp.byteLength))
.map(x => x.toString(16).padStart(2, '0'))
.join('');
const actHex = Array.from(new Uint8Array(actual.buffer, actual.byteOffset, actual.byteLength))
.map(x => x.toString(16).padStart(2, '0'))
.join('');
lines.push('EXPECT:\t ' + exp.join(' '));
lines.push('\t0x' + expHex);
lines.push('ACTUAL:\t ' + actual.join(' '));
lines.push('\t0x' + actHex);
}
if (failedByteIndices.length) {
return lines.join('\n');
}
return undefined;
}
expectSingleColor(src, format, { size, exp, dimension = '2d', slice = 0, layout }) {
const { byteLength, bytesPerRow, rowsPerImage, mipSize } = getTextureCopyLayout(
format,
dimension,
size,
layout
);
const expectedTexelData = getTexelDataRepresentation(format).getBytes(exp);
const buffer = this.device.createBuffer({
size: byteLength,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
const commandEncoder = this.device.createCommandEncoder();
commandEncoder.copyTextureToBuffer(
{
texture: src,
mipLevel: layout === null || layout === void 0 ? void 0 : layout.mipLevel,
origin: { x: 0, y: 0, z: slice },
},
{ buffer, bytesPerRow, rowsPerImage },
mipSize
);
this.queue.submit([commandEncoder.finish()]);
const arrayBuffer = new ArrayBuffer(byteLength);
fillTextureDataWithTexelValue(expectedTexelData, format, dimension, arrayBuffer, size, layout);
this.expectContents(buffer, new Uint8Array(arrayBuffer));
}
// TODO: Add check for values of depth/stencil, probably through sampling of shader
// TODO(natashalee): Can refactor this and expectSingleColor to use a similar base expect
expectSinglePixelIn2DTexture(src, format, { x, y }, { exp, slice = 0, layout }) {
const { byteLength, bytesPerRow, rowsPerImage, mipSize } = getTextureCopyLayout(
format,
'2d',
[1, 1, 1],
layout
);
const buffer = this.device.createBuffer({
size: byteLength,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
const commandEncoder = this.device.createCommandEncoder();
commandEncoder.copyTextureToBuffer(
{
texture: src,
mipLevel: layout === null || layout === void 0 ? void 0 : layout.mipLevel,
origin: { x, y, z: slice },
},
{ buffer, bytesPerRow, rowsPerImage },
mipSize
);
this.queue.submit([commandEncoder.finish()]);
this.expectContents(buffer, exp);
}
expectGPUError(filter, fn) {
this.device.pushErrorScope(filter);
const returnValue = fn();
const promise = this.device.popErrorScope();
this.eventualAsyncExpectation(async niceStack => {
const error = await promise;
let failed = false;
switch (filter) {
case 'none':
failed = error !== null;
break;
case 'out-of-memory':
failed = !(error instanceof GPUOutOfMemoryError);
break;
case 'validation':
failed = !(error instanceof GPUValidationError);
break;
}
if (failed) {
niceStack.message = `Expected ${filter} error`;
this.rec.expectationFailed(niceStack);
} else {
niceStack.message = `Captured ${filter} error`;
if (error instanceof GPUValidationError) {
niceStack.message += ` - ${error.message}`;
}
this.rec.debug(niceStack);
}
});
return returnValue;
}
}

View file

@ -0,0 +1,59 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
Test the values of flags interfaces (e.g. GPUTextureUsage).
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { IDLTest } from '../idl_test.js';
export const g = makeTestGroup(IDLTest);
g.test('BufferUsage').fn(t => {
const expected = {
MAP_READ: 0x0001,
MAP_WRITE: 0x0002,
COPY_SRC: 0x0004,
COPY_DST: 0x0008,
INDEX: 0x0010,
VERTEX: 0x0020,
UNIFORM: 0x0040,
STORAGE: 0x0080,
INDIRECT: 0x0100,
};
t.assertMembers(GPUBufferUsage, expected);
});
g.test('TextureUsage').fn(t => {
const expected = {
COPY_SRC: 0x01,
COPY_DST: 0x02,
SAMPLED: 0x04,
STORAGE: 0x08,
OUTPUT_ATTACHMENT: 0x10,
};
t.assertMembers(GPUTextureUsage, expected);
});
g.test('ColorWrite').fn(t => {
const expected = {
RED: 0x1,
GREEN: 0x2,
BLUE: 0x4,
ALPHA: 0x8,
ALL: 0xf,
};
t.assertMembers(GPUColorWrite, expected);
});
g.test('ShaderStage').fn(t => {
const expected = {
VERTEX: 0x1,
FRAGMENT: 0x2,
COMPUTE: 0x4,
};
t.assertMembers(GPUShaderStage, expected);
});

View file

@ -0,0 +1,25 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { Fixture } from '../../common/framework/fixture.js';
import { assert } from '../../common/framework/util/util.js';
export class IDLTest extends Fixture {
/**
* Asserts that an IDL interface has the expected members.
*/
// TODO: exp should allow sentinel markers for unnameable values, such as methods and attributes
// TODO: handle extensions
// TODO: check prototype chains (maybe as separate method)
assertMembers(act, exp) {
const expKeys = Object.keys(exp);
for (const k of expKeys) {
assert(k in act, () => `Expected key ${k} missing`);
assert(act[k] === exp[k], () => `Value of [${k}] was ${act[k]}, expected ${exp[k]}`);
}
const actKeys = Object.keys(act);
assert(
actKeys.length === expKeys.length,
() => `Had ${actKeys.length} keys, expected ${expKeys.length}`
);
}
}

View file

@ -0,0 +1,437 @@
// AUTO-GENERATED - DO NOT EDIT. See src/common/tools/gen_listings.ts.
export const listing = [
{
"file": [],
"readme": "WebGPU conformance test suite."
},
{
"file": [
"api"
],
"readme": "Tests for full coverage of the Javascript API surface of WebGPU."
},
{
"file": [
"api",
"operation"
],
"readme": "Tests that check the result of performing valid WebGPU operations, taking advantage of\nparameterization to exercise interactions between features."
},
{
"file": [
"api",
"operation",
"buffers"
],
"readme": "GPUBuffer tests."
},
{
"file": [
"api",
"operation",
"buffers",
"create_mapped"
],
"description": ""
},
{
"file": [
"api",
"operation",
"buffers",
"map"
],
"description": ""
},
{
"file": [
"api",
"operation",
"buffers",
"map_detach"
],
"description": ""
},
{
"file": [
"api",
"operation",
"buffers",
"map_oom"
],
"description": ""
},
{
"file": [
"api",
"operation",
"command_buffer",
"basic"
],
"description": "Basic tests."
},
{
"file": [
"api",
"operation",
"command_buffer",
"compute",
"basic"
],
"description": "Basic command buffer compute tests."
},
{
"file": [
"api",
"operation",
"command_buffer",
"copies"
],
"description": "copy{Buffer,Texture}To{Buffer,Texture} tests."
},
{
"file": [
"api",
"operation",
"command_buffer",
"render",
"basic"
],
"description": "Basic command buffer rendering tests."
},
{
"file": [
"api",
"operation",
"command_buffer",
"render",
"rendering"
],
"description": ""
},
{
"file": [
"api",
"operation",
"command_buffer",
"render",
"storeop"
],
"description": "renderPass store op test that drawn quad is either stored or cleared based on storeop"
},
{
"file": [
"api",
"operation",
"fences"
],
"description": ""
},
{
"file": [
"api",
"operation",
"render_pass",
"storeOp"
],
"description": "API Operation Tests for RenderPass StoreOp.\n\n Test Coverage Needed:\n\n - Test that a render pass has correct output for combinations of:\n - All color attachments from '0' to 'MAX_COLOR_ATTACHMENTS' with combinations of:\n - storeOp set to {'clear', 'store', 'undefined}\n - All color renderable formats\n - mip level set to {'0', mip > '0'}\n - array layer set to {'0', layer > '1'} for 2D textures\n - depth slice set to {'0', slice > '0'} for 3D textures\n - With and without a depthStencilAttachment that has the combinations of:\n - depthStoreOp set to {'clear', 'store', 'undefined'}\n - stencilStoreOp set to {'clear', 'store', 'undefined'}\n - All depth/stencil formats\n - mip level set to {'0', mip > '0'}\n - array layer set to {'0', layer > '1'} for 2D textures\n - depth slice set to {'0', slice > '0'} for 3D textures"
},
{
"file": [
"api",
"operation",
"render_pipeline",
"culling_tests"
],
"description": "Test culling and rasterizaion state.\n\nTest coverage:\nTest all culling combinations of GPUFrontFace and GPUCullMode show the correct output.\n\nUse 2 triangles with different winding orders:\n\n- Test that the counter-clock wise triangle has correct output for:\n - All FrontFaces (ccw, cw)\n - All CullModes (none, front, back)\n - All depth stencil attachment types (none, depth24plus, depth32float, depth24plus-stencil8)\n - Some primitive topologies (triangle-list, TODO: triangle-strip)\n\n- Test that the clock wise triangle has correct output for:\n - All FrontFaces (ccw, cw)\n - All CullModes (none, front, back)\n - All depth stencil attachment types (none, depth24plus, depth32float, depth24plus-stencil8)\n - Some primitive topologies (triangle-list, TODO: triangle-strip)"
},
{
"file": [
"api",
"operation",
"resource_init",
"copied_texture_clear"
],
"description": "Test uninitialized textures are initialized to zero when copied."
},
{
"file": [
"api",
"operation",
"resource_init",
"depth_stencil_attachment_clear"
],
"description": "Test uninitialized textures are initialized to zero when used as a depth/stencil attachment."
},
{
"file": [
"api",
"operation",
"resource_init",
"sampled_texture_clear"
],
"description": "Test uninitialized textures are initialized to zero when sampled."
},
{
"file": [
"api",
"regression"
],
"readme": "One-off tests that reproduce API bugs found in implementations to prevent the bugs from\nappearing again."
},
{
"file": [
"api",
"validation"
],
"readme": "Positive and negative tests for all the validation rules of the API."
},
{
"file": [
"api",
"validation",
"createBindGroup"
],
"description": "createBindGroup validation tests."
},
{
"file": [
"api",
"validation",
"createBindGroupLayout"
],
"description": "createBindGroupLayout validation tests."
},
{
"file": [
"api",
"validation",
"createPipelineLayout"
],
"description": "createPipelineLayout validation tests."
},
{
"file": [
"api",
"validation",
"createRenderPipeline"
],
"description": "createRenderPipeline validation tests."
},
{
"file": [
"api",
"validation",
"createTexture"
],
"description": "createTexture validation tests."
},
{
"file": [
"api",
"validation",
"createView"
],
"description": "createView validation tests."
},
{
"file": [
"api",
"validation",
"encoding",
"cmds",
"index_access"
],
"description": "indexed draws validation tests."
},
{
"file": [
"api",
"validation",
"error_scope"
],
"description": "error scope validation tests."
},
{
"file": [
"api",
"validation",
"fences"
],
"description": "fences validation tests."
},
{
"file": [
"api",
"validation",
"queue_submit"
],
"description": "queue submit validation tests."
},
{
"file": [
"api",
"validation",
"render_pass"
],
"description": "render pass validation tests."
},
{
"file": [
"api",
"validation",
"render_pass",
"storeOp"
],
"description": "API Validation Tests for RenderPass StoreOp.\n\n Test Coverage Needed:\n\n - Test that when depthReadOnly is true, depthStoreOp must be 'store'\n\n - Test that when stencilReadOnly is true, stencilStoreOp must be 'store'"
},
{
"file": [
"api",
"validation",
"render_pass_descriptor"
],
"description": "render pass descriptor validation tests."
},
{
"file": [
"api",
"validation",
"setBindGroup"
],
"description": "setBindGroup validation tests."
},
{
"file": [
"api",
"validation",
"setBlendColor"
],
"description": "setBlendColor validation tests."
},
{
"file": [
"api",
"validation",
"setScissorRect"
],
"description": "setScissorRect validation tests."
},
{
"file": [
"api",
"validation",
"setStencilReference"
],
"description": "setStencilReference validation tests."
},
{
"file": [
"api",
"validation",
"setVertexBuffer"
],
"description": "setVertexBuffer validation tests."
},
{
"file": [
"api",
"validation",
"setViewport"
],
"description": "setViewport validation tests."
},
{
"file": [
"api",
"validation",
"vertex_state"
],
"description": "vertexState validation tests."
},
{
"file": [
"examples"
],
"description": "Examples of writing CTS tests with various features.\n\nStart here when looking for examples of basic framework usage."
},
{
"file": [
"idl"
],
"readme": "Tests to check that the WebGPU IDL is correctly implemented, for examples that objects exposed\nexactly the correct members, and that methods throw when passed incomplete dictionaries."
},
{
"file": [
"idl",
"constants",
"flags"
],
"description": "Test the values of flags interfaces (e.g. GPUTextureUsage)."
},
{
"file": [
"shader"
],
"readme": "Tests for full coverage of the shaders that can be passed to WebGPU."
},
{
"file": [
"shader",
"execution"
],
"readme": "Tests that check the result of valid shader execution."
},
{
"file": [
"shader",
"execution",
"robust_access"
],
"description": "Tests to check array clamping in shaders is correctly implemented including vector / matrix indexing"
},
{
"file": [
"shader",
"execution",
"robust_access_vertex"
],
"description": "Test vertex attributes behave correctly (no crash / data leak) when accessed out of bounds\n\nTest coverage:\n\nThe following will be parameterized (all combinations tested):\n\n1) Draw call indexed? (false / true)\n - Run the draw call using an index buffer\n\n2) Draw call indirect? (false / true)\n - Run the draw call using an indirect buffer\n\n3) Draw call parameter (vertexCount, firstVertex, indexCount, firstIndex, baseVertex, instanceCount,\n firstInstance)\n - The parameter which will go out of bounds. Filtered depending on if the draw call is indexed.\n\n4) Attribute type (float, vec2, vec3, vec4)\n - The input attribute type in the vertex shader\n\n5) Error scale (1, 4, 10^2, 10^4, 10^6)\n - Offset to add to the correct draw call parameter\n\n6) Additional vertex buffers (0, +4)\n - Tests that no OOB occurs if more vertex buffers are used\n\nThe tests will also have another vertex buffer bound for an instanced attribute, to make sure\ninstanceCount / firstInstance are tested.\n\nThe tests will include multiple attributes per vertex buffer.\n\nThe vertex buffers will be filled by repeating a few chosen values until the end of the buffer.\n\nThe test will run a render pipeline which verifies the following:\n1) All vertex attribute values occur in the buffer or are zero\n2) All gl_VertexIndex values are within the index buffer or 0\n\nTODO:\n\nA suppression may be needed for d3d12 on tests that have non-zero baseVertex, since d3d12 counts\nfrom 0 instead of from baseVertex (will fail check for gl_VertexIndex).\n\nVertex buffer contents could be randomized to prevent the case where a previous test creates\na similar buffer to ours and the OOB-read seems valid. This should be deterministic, which adds\nmore complexity that we may not need."
},
{
"file": [
"shader",
"regression"
],
"readme": "One-off tests that reproduce shader bugs found in implementations to prevent the bugs from\nappearing again."
},
{
"file": [
"shader",
"validation"
],
"readme": "Positive and negative tests for all the validation rules of the shading language."
},
{
"file": [
"web-platform"
],
"readme": "Tests for Web platform-specific interactions like GPUSwapChain and canvas, WebXR,\nImageBitmaps, and video APIs."
},
{
"file": [
"web-platform",
"canvas",
"context_creation"
],
"description": ""
},
{
"file": [
"web-platform",
"copyImageBitmapToTexture"
],
"description": "copy imageBitmap To texture tests."
}
];

View file

@ -0,0 +1,337 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
Tests to check array clamping in shaders is correctly implemented including vector / matrix indexing
`;
import { params, poptions } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { assert } from '../../../common/framework/util/util.js';
import { GPUTest } from '../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
// Utilities that should probably live in some shared place.
function copyArrayBuffer(src) {
const dst = new ArrayBuffer(src.byteLength);
new Uint8Array(dst).set(new Uint8Array(src));
return dst;
}
const kUintMax = 4294967295;
const kIntMax = 2147483647;
// A small utility to test shaders:
// - it wraps the source into a small harness that checks the runTest() function returns 0.
// - it runs the shader with the testBindings set as bindgroup 0.
//
// The shader also has access to a uniform value that's equal to 1u to avoid constant propagation
// in the shader compiler.
function runShaderTest(t, stage, testSource, testBindings) {
assert(stage === GPUShaderStage.COMPUTE, 'Only know how to deal with compute for now');
const [constantsBuffer, constantsInit] = t.device.createBufferMapped({
size: 4,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM,
});
const constantsData = new Uint32Array(constantsInit);
constantsData[0] = 1;
constantsBuffer.unmap();
const resultBuffer = t.device.createBuffer({
size: 4,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
});
const source = `#version 450
layout(std140, set = 1, binding = 0) uniform Constants {
uint one;
};
layout(std430, set = 1, binding = 1) buffer Result {
uint result;
};
${testSource}
void main() {
result = runTest();
}`;
const pipeline = t.device.createComputePipeline({
computeStage: {
entryPoint: 'main',
module: t.makeShaderModule('compute', { glsl: source }),
},
});
const group = t.device.createBindGroup({
layout: pipeline.getBindGroupLayout(1),
entries: [
{ binding: 0, resource: { buffer: constantsBuffer } },
{ binding: 1, resource: { buffer: resultBuffer } },
],
});
const testGroup = t.device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: testBindings,
});
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, testGroup);
pass.setBindGroup(1, group);
pass.dispatch(1);
pass.endPass();
t.queue.submit([encoder.finish()]);
t.expectContents(resultBuffer, new Uint32Array([0]));
}
// The definition of base types for aggregate types, for example float, uint, etc.
const baseTypes = {
// TODO bools
uint: {
name: 'uint',
byteSize: 4,
glslPrefix: 'u',
glslZero: '0u',
fillBuffer(data, zeroStart, size) {
const typedData = new Uint32Array(data);
typedData.fill(42);
for (let i = 0; i < size / 4; i++) {
typedData[zeroStart / 4 + i] = 0;
}
},
},
int: {
name: 'int',
byteSize: 4,
glslPrefix: 'i',
glslZero: '0',
fillBuffer(data, zeroStart, size) {
const typedData = new Int32Array(data);
typedData.fill(42);
for (let i = 0; i < size / 4; i++) {
typedData[zeroStart / 4 + i] = 0;
}
},
},
float: {
name: 'float',
byteSize: 4,
glslPrefix: '',
glslZero: '0.0f',
fillBuffer(data, zeroStart, size) {
const typedData = new Float32Array(data);
typedData.fill(42);
for (let i = 0; i < size / 4; i++) {
typedData[zeroStart / 4 + i] = 0;
}
},
},
bool: {
name: 'bool',
byteSize: 4,
glslPrefix: 'b',
glslZero: 'false',
fillBuffer(data, zeroStart, size) {
const typedData = new Uint32Array(data);
typedData.fill(42);
for (let i = 0; i < size / 4; i++) {
typedData[zeroStart / 4 + i] = 0;
}
},
},
};
// The definition of aggregate types.
const typeParams = (() => {
const types = {};
for (const baseTypeName of Object.keys(baseTypes)) {
const baseType = baseTypes[baseTypeName];
// Arrays
types[`${baseTypeName}_sizedArray`] = {
declaration: `${baseTypeName} data[3]`,
length: 3,
std140Length: 2 * 4 + 1,
std430Length: 3,
zero: baseType.glslZero,
baseType,
};
types[`${baseTypeName}_unsizedArray`] = {
declaration: `${baseTypeName} data[]`,
length: 3,
std140Length: 0, // Unused
std430Length: 3,
zero: baseType.glslZero,
baseType,
isUnsizedArray: true,
};
// Vectors
for (let dimension = 2; dimension <= 4; dimension++) {
types[`${baseTypeName}_vector${dimension}`] = {
declaration: `${baseType.glslPrefix}vec${dimension} data`,
length: dimension,
std140Length: dimension,
std430Length: dimension,
zero: baseType.glslZero,
baseType,
};
}
}
// Matrices, there are only float matrics in GLSL.
for (const transposed of [false, true]) {
for (let numColumns = 2; numColumns <= 4; numColumns++) {
for (let numRows = 2; numRows <= 4; numRows++) {
const majorDim = transposed ? numRows : numColumns;
const minorDim = transposed ? numColumns : numRows;
const std140SizePerMinorDim = 4;
const std430SizePerMinorDim = minorDim === 3 ? 4 : minorDim;
let typeName = `mat${numColumns}`;
if (numColumns !== numRows) {
typeName += `x${numRows}`;
}
types[(transposed ? 'transposed_' : '') + typeName] = {
declaration: (transposed ? 'layout(row_major) ' : '') + `${typeName} data`,
length: numColumns,
std140Length: std140SizePerMinorDim * (majorDim - 1) + minorDim,
std430Length: std430SizePerMinorDim * (majorDim - 1) + minorDim,
zero: `vec${numRows}(0.0f)`,
baseType: baseTypes['float'],
};
}
}
}
return types;
})();
g.test('bufferMemory')
.params(
params()
.combine(poptions('type', Object.keys(typeParams)))
.combine([
{ memory: 'storage', access: 'read' },
{ memory: 'storage', access: 'write' },
{ memory: 'storage', access: 'atomic' },
{ memory: 'uniform', access: 'read' },
])
// Unsized arrays are only supported with SSBOs
.unless(p => typeParams[p.type].isUnsizedArray === true && p.memory !== 'storage')
// Atomics are only supported with integers
.unless(p => p.access === 'atomic' && !(typeParams[p.type].baseType.name in ['uint', 'int']))
)
.fn(async t => {
const type = typeParams[t.params.type];
const baseType = type.baseType;
const indicesToTest = [
// Write to the inside of the type so we can check the size computations were correct.
'0',
`${type.length} - 1`,
// Check exact bounds
'-1',
`${type.length}`,
// Check large offset
'-1000000',
'1000000',
// Check with max uint
`${kUintMax}`,
`-1 * ${kUintMax}`,
// Check with max int
`${kIntMax}`,
`-1 * ${kIntMax}`,
];
let testSource = '';
let byteSize = 0;
// Declare the data that will be accessed to check robust access.
if (t.params.memory === 'uniform') {
testSource += `
layout(std140, set = 0, binding = 0) uniform TestData {
${type.declaration};
};`;
byteSize = baseType.byteSize * type.std140Length;
} else {
testSource += `
layout(std430, set = 0, binding = 0) buffer TestData {
${type.declaration};
};`;
byteSize = baseType.byteSize * type.std430Length;
}
// Build the test function that will do the tests.
testSource += `
uint runTest() {
`;
for (const indexToTest of indicesToTest) {
// TODO check with constants too.
const index = `(${indexToTest}) * one`;
if (t.params.access === 'read') {
testSource += `
if(data[${index}] != ${type.zero}) {
return __LINE__;
}`;
} else if (t.params.access === 'write') {
testSource += `data[${index}] = ${type.zero};`;
} else {
testSource += `atomicAdd(data[${index}], 1);`;
}
}
testSource += `
return 0;
}`;
// Create a buffer that contains zeroes in the allowed access area, and 42s everywhere else.
const [testBuffer, testInit] = t.device.createBufferMapped({
size: 512,
usage:
GPUBufferUsage.COPY_SRC |
GPUBufferUsage.UNIFORM |
GPUBufferUsage.STORAGE |
GPUBufferUsage.COPY_DST,
});
baseType.fillBuffer(testInit, 256, byteSize);
const testInitCopy = copyArrayBuffer(testInit);
testBuffer.unmap();
// Run the shader, accessing the buffer.
runShaderTest(t, GPUShaderStage.COMPUTE, testSource, [
{ binding: 0, resource: { buffer: testBuffer, offset: 256, size: byteSize } },
]);
// Check that content of the buffer outside of the allowed area didn't change.
t.expectSubContents(testBuffer, 0, new Uint8Array(testInitCopy.slice(0, 256)));
const dataEnd = 256 + byteSize;
t.expectSubContents(testBuffer, dataEnd, new Uint8Array(testInitCopy.slice(dataEnd, 512)));
});
// TODO: also check other shader stages.
// TODO: also check global, function local, and shared variables.
// TODO: also check interface variables.
// TODO: also check storage texture access.

View file

@ -0,0 +1,451 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
export const description = `
Test vertex attributes behave correctly (no crash / data leak) when accessed out of bounds
Test coverage:
The following will be parameterized (all combinations tested):
1) Draw call indexed? (false / true)
- Run the draw call using an index buffer
2) Draw call indirect? (false / true)
- Run the draw call using an indirect buffer
3) Draw call parameter (vertexCount, firstVertex, indexCount, firstIndex, baseVertex, instanceCount,
firstInstance)
- The parameter which will go out of bounds. Filtered depending on if the draw call is indexed.
4) Attribute type (float, vec2, vec3, vec4)
- The input attribute type in the vertex shader
5) Error scale (1, 4, 10^2, 10^4, 10^6)
- Offset to add to the correct draw call parameter
6) Additional vertex buffers (0, +4)
- Tests that no OOB occurs if more vertex buffers are used
The tests will also have another vertex buffer bound for an instanced attribute, to make sure
instanceCount / firstInstance are tested.
The tests will include multiple attributes per vertex buffer.
The vertex buffers will be filled by repeating a few chosen values until the end of the buffer.
The test will run a render pipeline which verifies the following:
1) All vertex attribute values occur in the buffer or are zero
2) All gl_VertexIndex values are within the index buffer or 0
TODO:
A suppression may be needed for d3d12 on tests that have non-zero baseVertex, since d3d12 counts
from 0 instead of from baseVertex (will fail check for gl_VertexIndex).
Vertex buffer contents could be randomized to prevent the case where a previous test creates
a similar buffer to ours and the OOB-read seems valid. This should be deterministic, which adds
more complexity that we may not need.`;
import { params, pbool, poptions } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { GPUTest } from '../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
// Encapsulates a draw call (either indexed or non-indexed)
class DrawCall {
// Add a float offset when binding vertex buffer
// Draw
// DrawIndexed
// Both Draw and DrawIndexed
constructor(device, vertexArrays, vertexCount, partialLastNumber, offsetVertexBuffer) {
_defineProperty(this, 'device', void 0);
_defineProperty(this, 'vertexBuffers', void 0);
_defineProperty(this, 'indexBuffer', void 0);
_defineProperty(this, 'offsetVertexBuffer', void 0);
_defineProperty(this, 'vertexCount', void 0);
_defineProperty(this, 'firstVertex', void 0);
_defineProperty(this, 'indexCount', void 0);
_defineProperty(this, 'firstIndex', void 0);
_defineProperty(this, 'baseVertex', void 0);
_defineProperty(this, 'instanceCount', void 0);
_defineProperty(this, 'firstInstance', void 0);
this.device = device;
this.vertexBuffers = vertexArrays.map(v => this.generateVertexBuffer(v, partialLastNumber));
const indexArray = new Uint16Array(vertexCount).fill(0).map((_, i) => i);
this.indexBuffer = this.generateIndexBuffer(indexArray);
// Default arguments (valid call)
this.vertexCount = vertexCount;
this.firstVertex = 0;
this.indexCount = vertexCount;
this.firstIndex = 0;
this.baseVertex = 0;
this.instanceCount = vertexCount;
this.firstInstance = 0;
this.offsetVertexBuffer = offsetVertexBuffer;
}
// Insert a draw call into |pass| with specified type
insertInto(pass, indexed, indirect) {
if (indexed) {
if (indirect) {
this.drawIndexedIndirect(pass);
} else {
this.drawIndexed(pass);
}
} else {
if (indirect) {
this.drawIndirect(pass);
} else {
this.draw(pass);
}
}
}
// Insert a draw call into |pass|
draw(pass) {
this.bindVertexBuffers(pass);
pass.draw(this.vertexCount, this.instanceCount, this.firstVertex, this.firstInstance);
}
// Insert an indexed draw call into |pass|
drawIndexed(pass) {
this.bindVertexBuffers(pass);
pass.setIndexBuffer(this.indexBuffer);
pass.drawIndexed(
this.indexCount,
this.instanceCount,
this.firstIndex,
this.baseVertex,
this.firstInstance
);
}
// Insert an indirect draw call into |pass|
drawIndirect(pass) {
this.bindVertexBuffers(pass);
pass.drawIndirect(this.generateIndirectBuffer(), 0);
}
// Insert an indexed indirect draw call into |pass|
drawIndexedIndirect(pass) {
this.bindVertexBuffers(pass);
pass.setIndexBuffer(this.indexBuffer);
pass.drawIndexedIndirect(this.generateIndexedIndirectBuffer(), 0);
}
// Bind all vertex buffers generated
bindVertexBuffers(pass) {
let currSlot = 0;
for (let i = 0; i < this.vertexBuffers.length; i++) {
pass.setVertexBuffer(currSlot++, this.vertexBuffers[i], this.offsetVertexBuffer ? 4 : 0);
}
}
// Create a vertex buffer from |vertexArray|
// If |partialLastNumber| is true, delete one byte off the end
generateVertexBuffer(vertexArray, partialLastNumber) {
let size = vertexArray.byteLength;
if (partialLastNumber) {
size -= 1;
}
const [vertexBuffer, vertexMapping] = this.device.createBufferMapped({
size,
usage: GPUBufferUsage.VERTEX,
});
if (!partialLastNumber) {
new Float32Array(vertexMapping).set(vertexArray);
} else {
new Uint8Array(vertexMapping).set(new Uint8Array(vertexArray.buffer).slice(0, size));
}
vertexBuffer.unmap();
return vertexBuffer;
}
// Create an index buffer from |indexArray|
generateIndexBuffer(indexArray) {
const [indexBuffer, indexMapping] = this.device.createBufferMapped({
size: indexArray.byteLength,
usage: GPUBufferUsage.INDEX,
});
new Uint16Array(indexMapping).set(indexArray);
indexBuffer.unmap();
return indexBuffer;
}
// Create an indirect buffer containing draw call values
generateIndirectBuffer() {
const indirectArray = new Int32Array([
this.vertexCount,
this.instanceCount,
this.firstVertex,
this.firstInstance,
]);
const [indirectBuffer, indirectMapping] = this.device.createBufferMapped({
size: indirectArray.byteLength,
usage: GPUBufferUsage.INDIRECT,
});
new Int32Array(indirectMapping).set(indirectArray);
indirectBuffer.unmap();
return indirectBuffer;
}
// Create an indirect buffer containing indexed draw call values
generateIndexedIndirectBuffer() {
const indirectArray = new Int32Array([
this.indexCount,
this.instanceCount,
this.firstVertex,
this.baseVertex,
this.firstInstance,
]);
const [indirectBuffer, indirectMapping] = this.device.createBufferMapped({
size: indirectArray.byteLength,
usage: GPUBufferUsage.INDIRECT,
});
new Int32Array(indirectMapping).set(indirectArray);
indirectBuffer.unmap();
return indirectBuffer;
}
}
// Parameterize different sized types
const typeInfoMap = {
float: {
format: 'float',
size: 4,
validationFunc: 'return valid(v);',
},
vec2: {
format: 'float2',
size: 8,
validationFunc: 'return valid(v.x) && valid(v.y);',
},
vec3: {
format: 'float3',
size: 12,
validationFunc: 'return valid(v.x) && valid(v.y) && valid(v.z);',
},
vec4: {
format: 'float4',
size: 16,
validationFunc: `return valid(v.x) && valid(v.y) && valid(v.z) && valid(v.w) ||
v.x == 0 && v.y == 0 && v.z == 0 && (v.w == 0.0 || v.w == 1.0);`,
},
};
g.test('vertexAccess')
.params(
params()
.combine(pbool('indexed'))
.combine(pbool('indirect'))
.expand(p =>
poptions(
'drawCallTestParameter',
p.indexed
? ['indexCount', 'instanceCount', 'firstIndex', 'baseVertex', 'firstInstance']
: ['vertexCount', 'instanceCount', 'firstVertex', 'firstInstance']
)
)
.combine(poptions('type', Object.keys(typeInfoMap)))
.combine(poptions('additionalBuffers', [0, 4]))
.combine(pbool('partialLastNumber'))
.combine(pbool('offsetVertexBuffer'))
.combine(poptions('errorScale', [1, 4, 10 ** 2, 10 ** 4, 10 ** 6]))
)
.fn(async t => {
const p = t.params;
const typeInfo = typeInfoMap[p.type];
// Number of vertices to draw
const numVertices = 3;
// Each buffer will be bound to this many attributes (2 would mean 2 attributes per buffer)
const attributesPerBuffer = 2;
// Make an array big enough for the vertices, attributes, and size of each element
const vertexArray = new Float32Array(numVertices * attributesPerBuffer * (typeInfo.size / 4));
// Sufficiently unusual values to fill our buffer with to avoid collisions with other tests
const arbitraryValues = [759, 329, 908];
for (let i = 0; i < vertexArray.length; ++i) {
vertexArray[i] = arbitraryValues[i % arbitraryValues.length];
}
// A valid value is 0 or one in the buffer
const validValues = [0, ...arbitraryValues];
// Instance step mode buffer, vertex step mode buffer
const bufferContents = [vertexArray, vertexArray];
// Additional buffers (vertex step mode)
for (let i = 0; i < p.additionalBuffers; i++) {
bufferContents.push(vertexArray);
}
// Mutable draw call
const draw = new DrawCall(
t.device,
bufferContents,
numVertices,
p.partialLastNumber,
p.offsetVertexBuffer
);
// Create attributes listing
let layoutStr = '';
const attributeNames = [];
{
let currAttribute = 0;
for (let i = 0; i < bufferContents.length; i++) {
for (let j = 0; j < attributesPerBuffer; j++) {
layoutStr += `layout(location=${currAttribute}) in ${p.type} a_${currAttribute};\n`;
attributeNames.push(`a_${currAttribute}`);
currAttribute++;
}
}
}
// Vertex buffer descriptors
const vertexBuffers = [];
{
let currAttribute = 0;
for (let i = 0; i < bufferContents.length; i++) {
vertexBuffers.push({
arrayStride: attributesPerBuffer * typeInfo.size,
stepMode: i === 0 ? 'instance' : 'vertex',
attributes: Array(attributesPerBuffer)
.fill(0)
.map((_, i) => ({
shaderLocation: currAttribute++,
offset: i * typeInfo.size,
format: typeInfo.format,
})),
});
}
}
// Offset the range checks for gl_VertexIndex in the shader if we use BaseVertex
let vertexIndexOffset = 0;
if (p.drawCallTestParameter === 'baseVertex') {
vertexIndexOffset += p.errorScale;
}
// Construct pipeline that outputs a red fragment, only if we notice any invalid values
const vertexModule = t.makeShaderModule('vertex', {
glsl: `
#version 450
${layoutStr}
bool valid(float f) {
return ${validValues.map(v => `f == ${v}`).join(' || ')};
}
bool validationFunc(${p.type} v) {
${typeInfo.validationFunc}
}
void main() {
bool attributesInBounds = ${attributeNames.map(a => `validationFunc(${a})`).join(' && ')};
bool indexInBounds = gl_VertexIndex == 0 || (gl_VertexIndex >= ${vertexIndexOffset} &&
gl_VertexIndex < ${vertexIndexOffset + numVertices});
if (attributesInBounds && (${!p.indexed} || indexInBounds)) {
// Success case, move the vertex out of the viewport
gl_Position = vec4(-1.0, 0.0, 0.0, 1.0);
} else {
// Failure case, move the vertex inside the viewport
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
}
`,
});
const fragmentModule = t.makeShaderModule('fragment', {
glsl: `
#version 450
precision mediump float;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`,
});
// Pipeline setup, texture setup
const colorAttachment = t.device.createTexture({
format: 'rgba8unorm',
size: { width: 1, height: 1, depth: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
});
const colorAttachmentView = colorAttachment.createView();
const pipeline = t.device.createRenderPipeline({
vertexStage: { module: vertexModule, entryPoint: 'main' },
fragmentStage: { module: fragmentModule, entryPoint: 'main' },
primitiveTopology: 'point-list',
colorStates: [{ format: 'rgba8unorm', alphaBlend: {}, colorBlend: {} }],
vertexState: {
indexFormat: 'uint16',
vertexBuffers,
},
});
// Offset the draw call parameter we are testing by |errorScale|
draw[p.drawCallTestParameter] += p.errorScale;
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
attachment: colorAttachmentView,
storeOp: 'store',
loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
},
],
});
pass.setPipeline(pipeline);
// Run the draw variant
draw.insertInto(pass, p.indexed, p.indirect);
pass.endPass();
t.device.defaultQueue.submit([encoder.finish()]);
// Validate we see green instead of red, meaning no fragment ended up on-screen
t.expectSinglePixelIn2DTexture(
colorAttachment,
'rgba8unorm',
{ x: 0, y: 0 },
{ exp: new Uint8Array([0x00, 0xff, 0x00, 0xff]), layout: { mipLevel: 0 } }
);
});

View file

@ -0,0 +1,66 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { assert } from '../../common/framework/util/util.js';
export function floatAsNormalizedInteger(float, bits, signed) {
if (signed) {
assert(float >= -1 && float <= 1);
const max = Math.pow(2, bits - 1) - 1;
return Math.round(float * max);
} else {
assert(float >= 0 && float <= 1);
const max = Math.pow(2, bits) - 1;
return Math.round(float * max);
}
}
// Does not handle clamping, underflow, overflow, denormalized numbers
export function float32ToFloatBits(n, signBits, exponentBits, fractionBits, bias) {
assert(exponentBits <= 8);
assert(fractionBits <= 23);
assert(Number.isFinite(n));
if (n === 0) {
return 0;
}
if (signBits === 0) {
assert(n >= 0);
}
const buf = new DataView(new ArrayBuffer(Float32Array.BYTES_PER_ELEMENT));
buf.setFloat32(0, n, true);
const bits = buf.getUint32(0, true);
// bits (32): seeeeeeeefffffffffffffffffffffff
const fractionBitsToDiscard = 23 - fractionBits;
// 0 or 1
const sign = (bits >> 31) & signBits;
// >> to remove fraction, & to remove sign, - 127 to remove bias.
const exp = ((bits >> 23) & 0xff) - 127;
// Convert to the new biased exponent.
const newBiasedExp = bias + exp;
assert(newBiasedExp >= 0 && newBiasedExp < 1 << exponentBits);
// Mask only the fraction, and discard the lower bits.
const newFraction = (bits & 0x7fffff) >> fractionBitsToDiscard;
return (sign << (exponentBits + fractionBits)) | (newBiasedExp << fractionBits) | newFraction;
}
export function assertInIntegerRange(n, bits, signed) {
if (signed) {
const min = -Math.pow(2, bits - 1);
const max = Math.pow(2, bits - 1) - 1;
assert(n >= min && n <= max);
} else {
const max = Math.pow(2, bits) - 1;
assert(n >= 0 && n <= max);
}
}
export function gammaCompress(n) {
n = n <= 0.0031308 ? 12.92 * n : 1.055 * Math.pow(n, 1 / 2.4) - 0.055;
return n < 0 ? 0 : n > 1 ? 1 : n;
}

View file

@ -0,0 +1,9 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export function align(n, alignment) {
return Math.ceil(n / alignment) * alignment;
}
export function isAligned(n, alignment) {
return n === align(n, alignment);
}

View file

@ -0,0 +1,133 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { assert, unreachable } from '../../../common/framework/util/util.js';
import { kTextureFormatInfo } from '../../capability_info.js';
import { align, isAligned } from '../math.js';
export const kBytesPerRowAlignment = 256;
export const kBufferCopyAlignment = 4;
const kDefaultLayoutOptions = { mipLevel: 0, bytesPerRow: undefined, rowsPerImage: undefined };
export function getMipSizePassthroughLayers(dimension, size, mipLevel) {
const shiftMinOne = n => Math.max(1, n >> mipLevel);
switch (dimension) {
case '1d':
assert(size[2] === 1);
return [shiftMinOne(size[0]), size[1], size[2]];
case '2d':
return [shiftMinOne(size[0]), shiftMinOne(size[1]), size[2]];
case '3d':
return [shiftMinOne(size[0]), shiftMinOne(size[1]), shiftMinOne(size[2])];
default:
unreachable();
}
}
export function getTextureCopyLayout(format, dimension, size, options = kDefaultLayoutOptions) {
const { mipLevel } = options;
let { bytesPerRow, rowsPerImage } = options;
const mipSize = getMipSizePassthroughLayers(dimension, size, mipLevel);
const { blockWidth, blockHeight, bytesPerBlock } = kTextureFormatInfo[format];
assert(!!bytesPerBlock && !!blockWidth && !!blockHeight);
assert(isAligned(mipSize[0], blockWidth));
const minBytesPerRow = (mipSize[0] / blockWidth) * bytesPerBlock;
const alignedMinBytesPerRow = align(minBytesPerRow, kBytesPerRowAlignment);
if (bytesPerRow !== undefined) {
assert(bytesPerRow >= alignedMinBytesPerRow);
assert(isAligned(bytesPerRow, kBytesPerRowAlignment));
} else {
bytesPerRow = alignedMinBytesPerRow;
}
if (rowsPerImage !== undefined) {
assert(rowsPerImage >= mipSize[1]);
} else {
rowsPerImage = mipSize[1];
}
assert(isAligned(rowsPerImage, blockHeight));
const bytesPerSlice = bytesPerRow * (rowsPerImage / blockHeight);
const sliceSize =
bytesPerRow * (mipSize[1] / blockHeight - 1) + bytesPerBlock * (mipSize[0] / blockWidth);
const byteLength = bytesPerSlice * (mipSize[2] - 1) + sliceSize;
return {
bytesPerBlock,
byteLength: align(byteLength, kBufferCopyAlignment),
minBytesPerRow,
bytesPerRow,
rowsPerImage,
mipSize,
};
}
export function fillTextureDataWithTexelValue(
texelValue,
format,
dimension,
outputBuffer,
size,
options = kDefaultLayoutOptions
) {
const { blockWidth, blockHeight, bytesPerBlock } = kTextureFormatInfo[format];
assert(!!bytesPerBlock && !!blockWidth && !!blockHeight);
assert(bytesPerBlock === texelValue.byteLength);
const { byteLength, rowsPerImage, bytesPerRow } = getTextureCopyLayout(
format,
dimension,
size,
options
);
assert(byteLength <= outputBuffer.byteLength);
const mipSize = getMipSizePassthroughLayers(dimension, size, options.mipLevel);
const texelValueBytes = new Uint8Array(texelValue);
const outputTexelValueBytes = new Uint8Array(outputBuffer);
for (let slice = 0; slice < mipSize[2]; ++slice) {
for (let row = 0; row < mipSize[1]; row += blockHeight) {
for (let col = 0; col < mipSize[0]; col += blockWidth) {
const byteOffset =
slice * rowsPerImage * bytesPerRow + row * bytesPerRow + col * texelValue.byteLength;
outputTexelValueBytes.set(texelValueBytes, byteOffset);
}
}
}
}
export function createTextureUploadBuffer(
texelValue,
device,
format,
dimension,
size,
options = kDefaultLayoutOptions
) {
const { byteLength, bytesPerRow, rowsPerImage, bytesPerBlock } = getTextureCopyLayout(
format,
dimension,
size,
options
);
const [buffer, mapping] = device.createBufferMapped({
size: byteLength,
usage: GPUBufferUsage.COPY_SRC,
});
assert(texelValue.byteLength === bytesPerBlock);
fillTextureDataWithTexelValue(texelValue, format, dimension, mapping, size, options);
buffer.unmap();
return {
buffer,
bytesPerRow,
rowsPerImage,
};
}

View file

@ -0,0 +1,58 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
function endOfRange(r) {
return 'count' in r ? r.begin + r.count : r.end;
}
function* rangeAsIterator(r) {
for (let i = r.begin; i < endOfRange(r); ++i) {
yield i;
}
}
export class SubresourceRange {
constructor(subresources) {
_defineProperty(this, 'mipRange', void 0);
_defineProperty(this, 'sliceRange', void 0);
this.mipRange = {
begin: subresources.mipRange.begin,
end: endOfRange(subresources.mipRange),
};
this.sliceRange = {
begin: subresources.sliceRange.begin,
end: endOfRange(subresources.sliceRange),
};
}
*each() {
for (let level = this.mipRange.begin; level < this.mipRange.end; ++level) {
for (let slice = this.sliceRange.begin; slice < this.sliceRange.end; ++slice) {
yield { level, slice };
}
}
}
*mipLevels() {
for (let level = this.mipRange.begin; level < this.mipRange.end; ++level) {
yield {
level,
slices: rangeAsIterator(this.sliceRange),
};
}
}
}

View file

@ -0,0 +1,359 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
import { assert, unreachable } from '../../../common/framework/util/util.js';
import { kTextureFormatInfo } from '../../capability_info.js';
import {
assertInIntegerRange,
float32ToFloatBits,
floatAsNormalizedInteger,
gammaCompress,
} from '../conversion.js';
export let TexelComponent;
(function (TexelComponent) {
TexelComponent['R'] = 'R';
TexelComponent['G'] = 'G';
TexelComponent['B'] = 'B';
TexelComponent['A'] = 'A';
TexelComponent['Depth'] = 'Depth';
TexelComponent['Stencil'] = 'Stencil';
})(TexelComponent || (TexelComponent = {}));
var TexelWriteType;
// Function to convert a value into a texel value. It returns the converted value
// and the type of the converted value. For example, conversion may convert:
// - floats to unsigned normalized integers
// - floats to half floats, interpreted as uint16 bits
(function (TexelWriteType) {
TexelWriteType[(TexelWriteType['Sint'] = 0)] = 'Sint';
TexelWriteType[(TexelWriteType['Uint'] = 1)] = 'Uint';
TexelWriteType[(TexelWriteType['Float'] = 2)] = 'Float';
})(TexelWriteType || (TexelWriteType = {}));
const kR = [TexelComponent.R];
const kRG = [TexelComponent.R, TexelComponent.G];
const kRGB = [TexelComponent.R, TexelComponent.G, TexelComponent.B];
const kRGBA = [TexelComponent.R, TexelComponent.G, TexelComponent.B, TexelComponent.A];
const kBGRA = [TexelComponent.B, TexelComponent.G, TexelComponent.R, TexelComponent.A];
const unorm = bitLength => n => ({
value: floatAsNormalizedInteger(n, bitLength, false),
type: TexelWriteType.Uint,
});
const snorm = bitLength => n => ({
value: floatAsNormalizedInteger(n, bitLength, true),
type: TexelWriteType.Sint,
});
const uint = bitLength => n => ({
value: (assertInIntegerRange(n, bitLength, false), n),
type: TexelWriteType.Uint,
});
const sint = bitLength => n => ({
value: (assertInIntegerRange(n, bitLength, true), n),
type: TexelWriteType.Sint,
});
const unorm2 = { write: unorm(2), bitLength: 2 };
const unorm8 = { write: unorm(8), bitLength: 8 };
const unorm10 = { write: unorm(10), bitLength: 10 };
const snorm8 = { write: snorm(8), bitLength: 8 };
const uint8 = { write: uint(8), bitLength: 8 };
const uint16 = { write: uint(16), bitLength: 16 };
const uint32 = { write: uint(32), bitLength: 32 };
const sint8 = { write: sint(8), bitLength: 8 };
const sint16 = { write: sint(16), bitLength: 16 };
const sint32 = { write: sint(32), bitLength: 32 };
const float10 = {
write: n => ({
value: float32ToFloatBits(n, 0, 5, 5, 15),
type: TexelWriteType.Uint,
}),
bitLength: 10,
};
const float11 = {
write: n => ({
value: float32ToFloatBits(n, 0, 5, 6, 15),
type: TexelWriteType.Uint,
}),
bitLength: 11,
};
const float16 = {
write: n => ({
value: float32ToFloatBits(n, 1, 5, 10, 15),
type: TexelWriteType.Uint,
}),
bitLength: 16,
};
const float32 = {
write: n => ({
value: Math.fround(n),
type: TexelWriteType.Float,
}),
bitLength: 32,
};
const repeatComponents = (componentOrder, perComponentInfo) => {
const componentInfo = componentOrder.reduce((acc, curr) => {
return Object.assign(acc, {
[curr]: perComponentInfo,
});
}, {});
return {
componentOrder,
componentInfo,
};
};
const kRepresentationInfo = {
r8unorm: { ...repeatComponents(kR, unorm8), sRGB: false },
r8snorm: { ...repeatComponents(kR, snorm8), sRGB: false },
r8uint: { ...repeatComponents(kR, uint8), sRGB: false },
r8sint: { ...repeatComponents(kR, sint8), sRGB: false },
r16uint: { ...repeatComponents(kR, uint16), sRGB: false },
r16sint: { ...repeatComponents(kR, sint16), sRGB: false },
r16float: { ...repeatComponents(kR, float16), sRGB: false },
rg8unorm: { ...repeatComponents(kRG, unorm8), sRGB: false },
rg8snorm: { ...repeatComponents(kRG, snorm8), sRGB: false },
rg8uint: { ...repeatComponents(kRG, uint8), sRGB: false },
rg8sint: { ...repeatComponents(kRG, sint8), sRGB: false },
r32uint: { ...repeatComponents(kR, uint32), sRGB: false },
r32sint: { ...repeatComponents(kR, sint32), sRGB: false },
r32float: { ...repeatComponents(kR, float32), sRGB: false },
rg16uint: { ...repeatComponents(kRG, uint16), sRGB: false },
rg16sint: { ...repeatComponents(kRG, sint16), sRGB: false },
rg16float: { ...repeatComponents(kRG, float16), sRGB: false },
rgba8unorm: { ...repeatComponents(kRGBA, unorm8), sRGB: false },
'rgba8unorm-srgb': { ...repeatComponents(kRGBA, unorm8), sRGB: true },
rgba8snorm: { ...repeatComponents(kRGBA, snorm8), sRGB: false },
rgba8uint: { ...repeatComponents(kRGBA, uint8), sRGB: false },
rgba8sint: { ...repeatComponents(kRGBA, sint8), sRGB: false },
bgra8unorm: { ...repeatComponents(kBGRA, unorm8), sRGB: false },
'bgra8unorm-srgb': { ...repeatComponents(kBGRA, unorm8), sRGB: true },
rg32uint: { ...repeatComponents(kRG, uint32), sRGB: false },
rg32sint: { ...repeatComponents(kRG, sint32), sRGB: false },
rg32float: { ...repeatComponents(kRG, float32), sRGB: false },
rgba16uint: { ...repeatComponents(kRGBA, uint16), sRGB: false },
rgba16sint: { ...repeatComponents(kRGBA, sint16), sRGB: false },
rgba16float: { ...repeatComponents(kRGBA, float16), sRGB: false },
rgba32uint: { ...repeatComponents(kRGBA, uint32), sRGB: false },
rgba32sint: { ...repeatComponents(kRGBA, sint32), sRGB: false },
rgba32float: { ...repeatComponents(kRGBA, float32), sRGB: false },
rgb10a2unorm: {
componentOrder: kRGBA,
componentInfo: { R: unorm10, G: unorm10, B: unorm10, A: unorm2 },
sRGB: false,
},
rg11b10float: {
componentOrder: kRGB,
componentInfo: { R: float11, G: float11, B: float10 },
sRGB: false,
},
depth32float: {
componentOrder: [TexelComponent.Depth],
componentInfo: { Depth: float32 },
sRGB: false,
},
depth24plus: {
componentOrder: [TexelComponent.Depth],
componentInfo: { Depth: null },
sRGB: false,
},
'depth24plus-stencil8': {
componentOrder: [TexelComponent.Depth, TexelComponent.Stencil],
componentInfo: { Depth: null, Stencil: null },
sRGB: false,
},
};
class TexelDataRepresentationImpl {
// TODO: Determine endianness of the GPU data?
constructor(format, componentOrder, componentInfo, sRGB) {
this.format = format;
this.componentOrder = componentOrder;
this.componentInfo = componentInfo;
this.sRGB = sRGB;
_defineProperty(this, 'isGPULittleEndian', true);
}
totalBitLength() {
return this.componentOrder.reduce((acc, curr) => {
return acc + this.componentInfo[curr].bitLength;
}, 0);
}
setComponent(data, component, n) {
const componentIndex = this.componentOrder.indexOf(component);
assert(componentIndex !== -1);
const bitOffset = this.componentOrder.slice(0, componentIndex).reduce((acc, curr) => {
const componentInfo = this.componentInfo[curr];
assert(!!componentInfo);
return acc + componentInfo.bitLength;
}, 0);
const componentInfo = this.componentInfo[component];
assert(!!componentInfo);
const { write, bitLength } = componentInfo;
const { value, type } = write(n);
switch (type) {
case TexelWriteType.Float: {
const byteOffset = Math.floor(bitOffset / 8);
const byteLength = Math.ceil(bitLength / 8);
assert(byteOffset === bitOffset / 8 && byteLength === bitLength / 8);
switch (byteLength) {
case 4:
new DataView(data, byteOffset, byteLength).setFloat32(0, value, this.isGPULittleEndian);
break;
default:
unreachable();
}
break;
}
case TexelWriteType.Sint: {
const byteOffset = Math.floor(bitOffset / 8);
const byteLength = Math.ceil(bitLength / 8);
assert(byteOffset === bitOffset / 8 && byteLength === bitLength / 8);
switch (byteLength) {
case 1:
new DataView(data, byteOffset, byteLength).setInt8(0, value);
break;
case 2:
new DataView(data, byteOffset, byteLength).setInt16(0, value, this.isGPULittleEndian);
break;
case 4:
new DataView(data, byteOffset, byteLength).setInt32(0, value, this.isGPULittleEndian);
break;
default:
unreachable();
}
break;
}
case TexelWriteType.Uint: {
const byteOffset = Math.floor(bitOffset / 8);
const byteLength = Math.ceil(bitLength / 8);
if (byteOffset === bitOffset / 8 && byteLength === bitLength / 8) {
switch (byteLength) {
case 1:
new DataView(data, byteOffset, byteLength).setUint8(0, value);
break;
case 2:
new DataView(data, byteOffset, byteLength).setUint16(
0,
value,
this.isGPULittleEndian
);
break;
case 4:
new DataView(data, byteOffset, byteLength).setUint32(
0,
value,
this.isGPULittleEndian
);
break;
default:
unreachable();
}
} else {
// Packed representations are all 32-bit and use Uint as the data type.
// ex.) rg10b11float, rgb10a2unorm
switch (this.totalBitLength()) {
case 32: {
const view = new DataView(data);
const currentValue = view.getUint32(0, this.isGPULittleEndian);
let mask = 0xffffffff;
const bitsToClearRight = bitOffset;
const bitsToClearLeft = 32 - (bitLength + bitOffset);
mask = (mask >>> bitsToClearRight) << bitsToClearRight;
mask = (mask << bitsToClearLeft) >>> bitsToClearLeft;
const newValue = (currentValue & ~mask) | (value << bitOffset);
view.setUint32(0, newValue, this.isGPULittleEndian);
break;
}
default:
unreachable();
}
}
break;
}
default:
unreachable();
}
}
getBytes(components) {
if (this.sRGB) {
components = Object.assign({}, components);
assert(components.R !== undefined);
assert(components.G !== undefined);
assert(components.B !== undefined);
[components.R, components.G, components.B] = [
gammaCompress(components.R),
gammaCompress(components.G),
gammaCompress(components.B),
];
}
const bytesPerBlock = kTextureFormatInfo[this.format].bytesPerBlock;
assert(!!bytesPerBlock);
const data = new ArrayBuffer(bytesPerBlock);
for (const c of this.componentOrder) {
const componentValue = components[c];
assert(componentValue !== undefined);
this.setComponent(data, c, componentValue);
}
return data;
}
}
const kRepresentationCache = new Map();
export function getTexelDataRepresentation(format) {
if (!kRepresentationCache.has(format)) {
const { componentOrder, componentInfo, sRGB } = kRepresentationInfo[format];
kRepresentationCache.set(
format,
new TexelDataRepresentationImpl(format, componentOrder, componentInfo, sRGB)
);
}
return kRepresentationCache.get(format);
}

View file

@ -0,0 +1,24 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = '';
import { Fixture } from '../../../common/framework/fixture.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
export const g = makeTestGroup(Fixture);
g.test('canvas_element_getContext_returns_GPUCanvasContext').fn(async t => {
if (typeof document === 'undefined') {
// Skip if there is no document (Workers, Node)
t.skip('DOM is not available to create canvas element');
}
const canvas = document.createElement('canvas');
canvas.width = 10;
canvas.height = 10;
// TODO: fix types so these aren't necessary
const ctx = canvas.getContext('gpupresent');
t.expect(ctx instanceof window.GPUCanvasContext);
});

View file

@ -0,0 +1,372 @@
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ export const description = `
copy imageBitmap To texture tests.
`;
import { poptions, params } from '../../common/framework/params_builder.js';
import { makeTestGroup } from '../../common/framework/test_group.js';
import { unreachable } from '../../common/framework/util/util.js';
import { kTextureFormatInfo } from '../capability_info.js';
import { GPUTest } from '../gpu_test.js';
import { getTexelDataRepresentation } from '../util/texture/texelData.js';
function calculateRowPitch(width, bytesPerPixel) {
const bytesPerRow = width * bytesPerPixel;
// Rounds up to a multiple of 256 according to WebGPU requirements.
return (((bytesPerRow - 1) >> 8) + 1) << 8;
}
var Color;
// Cache for generated pixels.
(function (Color) {
Color[(Color['Red'] = 0)] = 'Red';
Color[(Color['Green'] = 1)] = 'Green';
Color[(Color['Blue'] = 2)] = 'Blue';
Color[(Color['White'] = 3)] = 'White';
Color[(Color['OpaqueBlack'] = 4)] = 'OpaqueBlack';
Color[(Color['TransparentBlack'] = 5)] = 'TransparentBlack';
})(Color || (Color = {}));
const generatedPixelCache = new Map();
class F extends GPUTest {
checkCopyImageBitmapResult(src, expected, width, height, bytesPerPixel) {
const exp = new Uint8Array(expected.buffer, expected.byteOffset, expected.byteLength);
const rowPitch = calculateRowPitch(width, bytesPerPixel);
const dst = this.createCopyForMapRead(src, 0, rowPitch * height);
this.eventualAsyncExpectation(async niceStack => {
const actual = new Uint8Array(await dst.mapReadAsync());
const check = this.checkBufferWithRowPitch(
actual,
exp,
width,
height,
rowPitch,
bytesPerPixel
);
if (check !== undefined) {
niceStack.message = check;
this.rec.expectationFailed(niceStack);
}
dst.destroy();
});
}
checkBufferWithRowPitch(actual, exp, width, height, rowPitch, bytesPerPixel) {
const failedByteIndices = [];
const failedByteExpectedValues = [];
const failedByteActualValues = [];
iLoop: for (let i = 0; i < height; ++i) {
const bytesPerRow = width * bytesPerPixel;
for (let j = 0; j < bytesPerRow; ++j) {
const indexExp = j + i * bytesPerRow;
const indexActual = j + rowPitch * i;
if (actual[indexActual] !== exp[indexExp]) {
if (failedByteIndices.length >= 4) {
failedByteIndices.push('...');
failedByteExpectedValues.push('...');
failedByteActualValues.push('...');
break iLoop;
}
failedByteIndices.push(`(${i},${j})`);
failedByteExpectedValues.push(exp[indexExp].toString());
failedByteActualValues.push(actual[indexActual].toString());
}
}
}
if (failedByteIndices.length > 0) {
return `at [${failedByteIndices.join(', ')}], \
expected [${failedByteExpectedValues.join(', ')}], \
got [${failedByteActualValues.join(', ')}]`;
}
return undefined;
}
doTestAndCheckResult(
imageBitmapCopyView,
dstTextureCopyView,
copySize,
bytesPerPixel,
expectedData
) {
this.device.defaultQueue.copyImageBitmapToTexture(
imageBitmapCopyView,
dstTextureCopyView,
copySize
);
const imageBitmap = imageBitmapCopyView.imageBitmap;
const dstTexture = dstTextureCopyView.texture;
const bytesPerRow = calculateRowPitch(imageBitmap.width, bytesPerPixel);
const testBuffer = this.device.createBuffer({
size: bytesPerRow * imageBitmap.height,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
const encoder = this.device.createCommandEncoder();
encoder.copyTextureToBuffer(
{ texture: dstTexture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
{ buffer: testBuffer, bytesPerRow },
{ width: imageBitmap.width, height: imageBitmap.height, depth: 1 }
);
this.device.defaultQueue.submit([encoder.finish()]);
this.checkCopyImageBitmapResult(
testBuffer,
expectedData,
imageBitmap.width,
imageBitmap.height,
bytesPerPixel
);
}
generatePixel(color, format) {
var _generatedPixelCache$, _generatedPixelCache$3;
if (!generatedPixelCache.get(format)) {
generatedPixelCache.set(format, new Map());
}
// None of the dst texture format is 'uint' or 'sint', so we can always use float value.
if (
!((_generatedPixelCache$ = generatedPixelCache.get(format)) === null ||
_generatedPixelCache$ === void 0
? void 0
: _generatedPixelCache$.has(color))
) {
var _generatedPixelCache$2;
let pixels;
switch (color) {
case Color.Red:
pixels = new Uint8Array(
getTexelDataRepresentation(format).getBytes({ R: 1.0, G: 0, B: 0, A: 1.0 })
);
break;
case Color.Green:
pixels = new Uint8Array(
getTexelDataRepresentation(format).getBytes({ R: 0, G: 1.0, B: 0, A: 1.0 })
);
break;
case Color.Blue:
pixels = new Uint8Array(
getTexelDataRepresentation(format).getBytes({ R: 0, G: 0, B: 1.0, A: 1.0 })
);
break;
case Color.White:
pixels = new Uint8Array(
getTexelDataRepresentation(format).getBytes({ R: 0, G: 0, B: 0, A: 1.0 })
);
break;
case Color.OpaqueBlack:
pixels = new Uint8Array(
getTexelDataRepresentation(format).getBytes({ R: 1.0, G: 1.0, B: 1.0, A: 1.0 })
);
break;
case Color.TransparentBlack:
pixels = new Uint8Array(
getTexelDataRepresentation(format).getBytes({ R: 1.0, G: 1.0, B: 1.0, A: 0 })
);
break;
default:
unreachable();
}
(_generatedPixelCache$2 = generatedPixelCache.get(format)) === null ||
_generatedPixelCache$2 === void 0
? void 0
: _generatedPixelCache$2.set(color, pixels);
}
return (_generatedPixelCache$3 = generatedPixelCache.get(format)) === null ||
_generatedPixelCache$3 === void 0
? void 0
: _generatedPixelCache$3.get(color);
}
}
export const g = makeTestGroup(F);
g.test('from_ImageData')
.params(
params()
.combine(poptions('width', [1, 2, 4, 15, 255, 256]))
.combine(poptions('height', [1, 2, 4, 15, 255, 256]))
.combine(poptions('alpha', ['none', 'premultiply']))
.combine(poptions('orientation', ['none', 'flipY']))
.combine(
poptions('dstColorFormat', [
'rgba8unorm',
'bgra8unorm',
'rgba8unorm-srgb',
'bgra8unorm-srgb',
'rgb10a2unorm',
'rgba16float',
'rgba32float',
'rg8unorm',
'rg16float',
])
)
)
.fn(async t => {
const { width, height, alpha, orientation, dstColorFormat } = t.params;
const srcBytesPerPixel = kTextureFormatInfo['rgba8unorm'].bytesPerBlock;
// Generate input contents by iterating 'Color' enum
const imagePixels = new Uint8ClampedArray(srcBytesPerPixel * width * height);
const startPixel = Color.Red;
for (let i = 0, currentPixel = startPixel; i < width * height; ++i) {
const pixels = t.generatePixel(currentPixel, 'rgba8unorm');
if (currentPixel === Color.TransparentBlack) {
currentPixel = Color.Red;
} else {
++currentPixel;
}
for (let j = 0; j < srcBytesPerPixel; ++j) {
imagePixels[i * srcBytesPerPixel + j] = pixels[j];
}
}
// Generate correct expected values
const imageData = new ImageData(imagePixels, width, height);
const imageBitmap = await createImageBitmap(imageData, {
premultiplyAlpha: alpha,
imageOrientation: orientation,
});
const dst = t.device.createTexture({
size: {
width: imageBitmap.width,
height: imageBitmap.height,
depth: 1,
},
format: dstColorFormat,
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC,
});
// Construct expected value for different dst color format
const dstBytesPerPixel = kTextureFormatInfo[dstColorFormat].bytesPerBlock;
const dstPixels = new Uint8ClampedArray(dstBytesPerPixel * width * height);
let expectedPixels = new Uint8ClampedArray(dstBytesPerPixel * width * height);
for (let i = 0, currentPixel = startPixel; i < width * height; ++i) {
const pixels = t.generatePixel(currentPixel, dstColorFormat);
for (let j = 0; j < dstBytesPerPixel; ++j) {
// All pixels are 0 due to premultiply alpha
if (alpha === 'premultiply' && currentPixel === Color.TransparentBlack) {
dstPixels[i * dstBytesPerPixel + j] = 0;
} else {
dstPixels[i * dstBytesPerPixel + j] = pixels[j];
}
}
if (currentPixel === Color.TransparentBlack) {
currentPixel = Color.Red;
} else {
++currentPixel;
}
}
if (orientation === 'flipY') {
for (let i = 0; i < height; ++i) {
for (let j = 0; j < width * dstBytesPerPixel; ++j) {
const posImagePixel = (height - i - 1) * width * dstBytesPerPixel + j;
const posExpectedValue = i * width * dstBytesPerPixel + j;
expectedPixels[posExpectedValue] = dstPixels[posImagePixel];
}
}
} else {
expectedPixels = dstPixels;
}
t.doTestAndCheckResult(
{ imageBitmap, origin: { x: 0, y: 0 } },
{ texture: dst },
{ width: imageBitmap.width, height: imageBitmap.height, depth: 1 },
dstBytesPerPixel,
expectedPixels
);
});
g.test('from_canvas')
.params(
params()
.combine(poptions('width', [1, 2, 4, 15, 255, 256]))
.combine(poptions('height', [1, 2, 4, 15, 255, 256]))
)
.fn(async t => {
const { width, height } = t.params;
// CTS sometimes runs on worker threads, where document is not available.
// In this case, OffscreenCanvas can be used instead of <canvas>.
// But some browsers don't support OffscreenCanvas, and some don't
// support '2d' contexts on OffscreenCanvas.
// In this situation, the case will be skipped.
let imageCanvas;
if (typeof document !== 'undefined') {
imageCanvas = document.createElement('canvas');
imageCanvas.width = width;
imageCanvas.height = height;
} else if (typeof OffscreenCanvas === 'undefined') {
t.skip('OffscreenCanvas is not supported');
return;
} else {
imageCanvas = new OffscreenCanvas(width, height);
}
const imageCanvasContext = imageCanvas.getContext('2d');
if (imageCanvasContext === null) {
t.skip('OffscreenCanvas "2d" context not available');
return;
}
// The texture format is rgba8unorm, so the bytes per pixel is 4.
const bytesPerPixel = 4;
// Generate original data.
const imagePixels = new Uint8ClampedArray(bytesPerPixel * width * height);
for (let i = 0; i < width * height * bytesPerPixel; ++i) {
imagePixels[i] = i % 4 === 3 ? 255 : i % 256;
}
const imageData = new ImageData(imagePixels, width, height);
imageCanvasContext.putImageData(imageData, 0, 0);
const imageBitmap = await createImageBitmap(imageCanvas);
const dst = t.device.createTexture({
size: {
width: imageBitmap.width,
height: imageBitmap.height,
depth: 1,
},
format: 'rgba8unorm',
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC,
});
// This will get origin data and even it has premultiplied-alpha
const expectedData = imageCanvasContext.getImageData(
0,
0,
imageBitmap.width,
imageBitmap.height
).data;
t.doTestAndCheckResult(
{ imageBitmap, origin: { x: 0, y: 0 } },
{ texture: dst },
{ width: imageBitmap.width, height: imageBitmap.height, depth: 1 },
bytesPerPixel,
expectedData
);
});

Some files were not shown because too many files have changed in this diff Show more