mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
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:
commit
9fbdbddb0c
107 changed files with 28298 additions and 5 deletions
|
@ -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 \
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/,./
|
||||
/_mozilla/,../mozilla/tests
|
||||
/_webgl/,../webgl/tests
|
||||
/_webgpu/,../webgpu/tests
|
||||
|
|
|
@ -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/
|
||||
|
|
651
tests/wpt/webgpu/meta/MANIFEST.json
Normal file
651
tests/wpt/webgpu/meta/MANIFEST.json
Normal 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
|
||||
}
|
1
tests/wpt/webgpu/meta/__dir__.ini
Normal file
1
tests/wpt/webgpu/meta/__dir__.ini
Normal file
|
@ -0,0 +1 @@
|
|||
prefs: ["dom.webgpu.enabled:true"]
|
15799
tests/wpt/webgpu/meta/webgpu/cts.html.ini
Normal file
15799
tests/wpt/webgpu/meta/webgpu/cts.html.ini
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,2 @@
|
|||
[canvas_clear.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[canvas_complex_bgra8unorm.html]
|
||||
expected: TIMEOUT
|
26
tests/wpt/webgpu/tests/LICENSE.txt
Normal file
26
tests/wpt/webgpu/tests/LICENSE.txt
Normal 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.
|
10
tests/wpt/webgpu/tests/README.md
Normal file
10
tests/wpt/webgpu/tests/README.md
Normal 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.
|
|
@ -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}`);
|
||||
}
|
||||
}
|
124
tests/wpt/webgpu/tests/webgpu/common/framework/fixture.js
Normal file
124
tests/wpt/webgpu/tests/webgpu/common/framework/fixture.js
Normal 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;
|
||||
}
|
||||
}
|
33
tests/wpt/webgpu/tests/webgpu/common/framework/glsl.js
Normal file
33
tests/wpt/webgpu/tests/webgpu/common/framework/glsl.js
Normal 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);
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
/**
|
||||
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
|
||||
**/
|
|
@ -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++;
|
||||
}
|
||||
}
|
117
tests/wpt/webgpu/tests/webgpu/common/framework/params_builder.js
Normal file
117
tests/wpt/webgpu/tests/webgpu/common/framework/params_builder.js
Normal 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 };
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
127
tests/wpt/webgpu/tests/webgpu/common/framework/query/query.js
Normal file
127
tests/wpt/webgpu/tests/webgpu/common/framework/query/query.js
Normal 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),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -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 = '*';
|
|
@ -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;
|
||||
}
|
|
@ -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_]+$/;
|
162
tests/wpt/webgpu/tests/webgpu/common/framework/test_group.js
Normal file
162
tests/wpt/webgpu/tests/webgpu/common/framework/test_group.js
Normal 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
/**
|
||||
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
|
||||
**/
|
393
tests/wpt/webgpu/tests/webgpu/common/framework/tree.js
Normal file
393
tests/wpt/webgpu/tests/webgpu/common/framework/tree.js
Normal 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]);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
78
tests/wpt/webgpu/tests/webgpu/common/framework/util/stack.js
Normal file
78
tests/wpt/webgpu/tests/webgpu/common/framework/util/stack.js
Normal 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
|
|
@ -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;
|
72
tests/wpt/webgpu/tests/webgpu/common/framework/util/util.js
Normal file
72
tests/wpt/webgpu/tests/webgpu/common/framework/util/util.js
Normal 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));
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
// AUTO-GENERATED - DO NOT EDIT. See tools/gen_version.
|
||||
|
||||
export const version = '474ff8e569a0d206f8cb6f6146a8769fadf77da9-dirty';
|
|
@ -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';
|
||||
}
|
|
@ -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 });
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
58
tests/wpt/webgpu/tests/webgpu/common/runtime/wpt.js
Normal file
58
tests/wpt/webgpu/tests/webgpu/common/runtime/wpt.js
Normal 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);
|
||||
}
|
71
tests/wpt/webgpu/tests/webgpu/cts.html
Normal file
71
tests/wpt/webgpu/tests/webgpu/cts.html
Normal 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:*'>
|
78
tests/wpt/webgpu/tests/webgpu/third_party/glslang_js/lib/glslang.js
vendored
Normal file
78
tests/wpt/webgpu/tests/webgpu/third_party/glslang_js/lib/glslang.js
vendored
Normal 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;
|
||||
};
|
||||
})();
|
BIN
tests/wpt/webgpu/tests/webgpu/third_party/glslang_js/lib/glslang.wasm
vendored
Normal file
BIN
tests/wpt/webgpu/tests/webgpu/third_party/glslang_js/lib/glslang.wasm
vendored
Normal file
Binary file not shown.
|
@ -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);
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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');
|
||||
});
|
|
@ -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));
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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]));
|
||||
});
|
|
@ -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]));
|
||||
});
|
|
@ -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 },
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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);
|
|
@ -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
|
||||
});
|
|
@ -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();
|
||||
});
|
|
@ -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());
|
|
@ -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());
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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}`);
|
||||
}
|
||||
});
|
|
@ -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()]);
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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);
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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();
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
629
tests/wpt/webgpu/tests/webgpu/webgpu/capability_info.js
Normal file
629
tests/wpt/webgpu/tests/webgpu/webgpu/capability_info.js
Normal 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];
|
103
tests/wpt/webgpu/tests/webgpu/webgpu/examples.spec.js
Normal file
103
tests/wpt/webgpu/tests/webgpu/webgpu/examples.spec.js
Normal 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);
|
||||
});
|
292
tests/wpt/webgpu/tests/webgpu/webgpu/gpu_test.js
Normal file
292
tests/wpt/webgpu/tests/webgpu/webgpu/gpu_test.js
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
25
tests/wpt/webgpu/tests/webgpu/webgpu/idl/idl_test.js
Normal file
25
tests/wpt/webgpu/tests/webgpu/webgpu/idl/idl_test.js
Normal 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}`
|
||||
);
|
||||
}
|
||||
}
|
437
tests/wpt/webgpu/tests/webgpu/webgpu/listing.js
Normal file
437
tests/wpt/webgpu/tests/webgpu/webgpu/listing.js
Normal 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."
|
||||
}
|
||||
];
|
|
@ -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.
|
|
@ -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 } }
|
||||
);
|
||||
});
|
66
tests/wpt/webgpu/tests/webgpu/webgpu/util/conversion.js
Normal file
66
tests/wpt/webgpu/tests/webgpu/webgpu/util/conversion.js
Normal 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;
|
||||
}
|
9
tests/wpt/webgpu/tests/webgpu/webgpu/util/math.js
Normal file
9
tests/wpt/webgpu/tests/webgpu/webgpu/util/math.js
Normal 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);
|
||||
}
|
133
tests/wpt/webgpu/tests/webgpu/webgpu/util/texture/layout.js
Normal file
133
tests/wpt/webgpu/tests/webgpu/webgpu/util/texture/layout.js
Normal 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,
|
||||
};
|
||||
}
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
359
tests/wpt/webgpu/tests/webgpu/webgpu/util/texture/texelData.js
Normal file
359
tests/wpt/webgpu/tests/webgpu/webgpu/util/texture/texelData.js
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
});
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue