mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Auto merge of #27549 - kunalmohan:update-cts, r=kvark
Update WebGPU CTS to main branch <!-- Please describe your changes on the following line: --> `main` branch in https://github.com/gpuweb/cts is closer to our implementation and it makes more sense to use that. r?@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 - [ ] These changes fix #___ (GitHub issue number if applicable) <!-- Either: --> - [ ] There are tests for these changes OR - [ ] These changes do not require tests because ___ <!-- 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
e41c8bc2b7
48 changed files with 1298 additions and 19763 deletions
|
@ -13,7 +13,7 @@ use crate::dom::gpubuffer::GPUBuffer;
|
|||
use dom_struct::dom_struct;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use webgpu::{WebGPU, WebGPUCommandBuffer};
|
||||
use webgpu::{WebGPU, WebGPUCommandBuffer, WebGPURequest};
|
||||
|
||||
impl Eq for DomRoot<GPUBuffer> {}
|
||||
impl Hash for DomRoot<GPUBuffer> {
|
||||
|
@ -67,6 +67,20 @@ impl GPUCommandBuffer {
|
|||
}
|
||||
}
|
||||
|
||||
impl Drop for GPUCommandBuffer {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = self.channel.0.send((
|
||||
None,
|
||||
WebGPURequest::FreeCommandBuffer(self.command_buffer.0),
|
||||
)) {
|
||||
warn!(
|
||||
"Failed to send FreeCommandBuffer({:?}) ({})",
|
||||
self.command_buffer.0, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GPUCommandBuffer {
|
||||
pub fn id(&self) -> WebGPUCommandBuffer {
|
||||
self.command_buffer
|
||||
|
|
|
@ -185,6 +185,7 @@ pub enum WebGPURequest {
|
|||
},
|
||||
DestroyTexture(id::TextureId),
|
||||
Exit(IpcSender<()>),
|
||||
FreeCommandBuffer(id::CommandBufferId),
|
||||
FreeDevice(id::DeviceId),
|
||||
RenderBundleEncoderFinish {
|
||||
render_bundle_encoder: RenderBundleEncoder,
|
||||
|
@ -856,6 +857,9 @@ impl<'a> WGPU<'a> {
|
|||
}
|
||||
return;
|
||||
},
|
||||
WebGPURequest::FreeCommandBuffer(command_buffer_id) => {
|
||||
self.error_command_buffers.remove(&command_buffer_id);
|
||||
},
|
||||
WebGPURequest::FreeDevice(device_id) => {
|
||||
let device = WebGPUDevice(device_id);
|
||||
let pipeline_id = self.devices.remove(&device).unwrap();
|
||||
|
|
|
@ -53,11 +53,7 @@
|
|||
[]
|
||||
],
|
||||
"fixture.js": [
|
||||
"191e2fc42d98ad2c2dce06adf7414e6d0c1e042d",
|
||||
[]
|
||||
],
|
||||
"glsl.js": [
|
||||
"7571416d886c2061245e262fcd06c45534472232",
|
||||
"fcff2a6bb9fac5fd35d4129f611e3ccb0a4864e3",
|
||||
[]
|
||||
],
|
||||
"gpu": {
|
||||
|
@ -89,7 +85,7 @@
|
|||
]
|
||||
},
|
||||
"params_builder.js": [
|
||||
"ab548de07844b48af7a54e08cd0dab676f2f5ba6",
|
||||
"f585e3a2184fd821e9a4ce4266c1d277b4c5965d",
|
||||
[]
|
||||
],
|
||||
"params_utils.js": [
|
||||
|
@ -102,11 +98,15 @@
|
|||
[]
|
||||
],
|
||||
"encode_selectively.js": [
|
||||
"c621808e284ae6315e921b96f75f3cae10d78eb0",
|
||||
"62cb55ee039b7cc04dfdf751ce2241ba5864cac6",
|
||||
[]
|
||||
],
|
||||
"json_param_value.js": [
|
||||
"1bb32d06dfdf95be6953faac150b767585198cf7",
|
||||
[]
|
||||
],
|
||||
"parseQuery.js": [
|
||||
"5a438c2bbb0beed622c030cc6748bd5c394ea2dd",
|
||||
"6c10baab0b2f97dc09e633201dae6618842fbd69",
|
||||
[]
|
||||
],
|
||||
"query.js": [
|
||||
|
@ -118,7 +118,7 @@
|
|||
[]
|
||||
],
|
||||
"stringify_params.js": [
|
||||
"931c5534f6172c801b39d9e24c05e4ef4e8d8938",
|
||||
"b9c9cde7067b8cfc2fe2ad59d9d3c7fde3d38733",
|
||||
[]
|
||||
],
|
||||
"validQueryPart.js": [
|
||||
|
@ -161,7 +161,7 @@
|
|||
]
|
||||
},
|
||||
"version.js": [
|
||||
"de50e581c26d8d5f2b6e91da39571cd8b21f1591",
|
||||
"4d5e72c0442fdbdd4d5ceeefa14dcbaafae4ffaa",
|
||||
[]
|
||||
]
|
||||
},
|
||||
|
@ -186,42 +186,24 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"third_party": {
|
||||
"glslang_js": {
|
||||
"lib": {
|
||||
"glslang.js": [
|
||||
"21bd32eafa285c95c87c14056e1e0f253549dedd",
|
||||
[]
|
||||
],
|
||||
"glslang.wasm": [
|
||||
"d951d65e063a930ad0d841848367f7b8cc6b2703",
|
||||
[]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"webgpu": {
|
||||
"api": {
|
||||
"operation": {
|
||||
"buffers": {
|
||||
"create_mapped.spec.js": [
|
||||
"0f950439b9d272d36b93bd76208af9461105f9fd",
|
||||
[]
|
||||
],
|
||||
"map.spec.js": [
|
||||
"19e1a2cc7c5c54a2e37c85dad35cc4fcfb8ac321",
|
||||
"f751b721d5f87013334e9e240ca517c6447a84f5",
|
||||
[]
|
||||
],
|
||||
"map_detach.spec.js": [
|
||||
"61f2d7aa9fbadaa6b6d2810314ec6b6c20f3f99d",
|
||||
"aca8dc46e5797b9c31ee682e7ce04d12a2644b79",
|
||||
[]
|
||||
],
|
||||
"map_oom.spec.js": [
|
||||
"f80fa767c08c5642c1add2612ac9895b16d1de19",
|
||||
"205f1147ed2b4304e21f8713126e69227ae48d3b",
|
||||
[]
|
||||
],
|
||||
"mapping_test.js": [
|
||||
"ad2be711d40a44f2b64a5f5c27ab5d32afee24a4",
|
||||
"45ce419a69d4accb2ac1b36f82a2065ce16a900e",
|
||||
[]
|
||||
]
|
||||
},
|
||||
|
@ -230,28 +212,14 @@
|
|||
"7f4dd29b24ff8280b7aaede344b42bb5edca4d19",
|
||||
[]
|
||||
],
|
||||
"compute": {
|
||||
"basic.spec.js": [
|
||||
"865601f869bac456d5de6678c75bc21fa8b3c722",
|
||||
[]
|
||||
]
|
||||
},
|
||||
"copies.spec.js": [
|
||||
"29d0bd58ee9c9cfdc8e244a7fbef0585c564b7f8",
|
||||
"a6ecc3c9c2973b1f46704f77d255d9f185cabc31",
|
||||
[]
|
||||
],
|
||||
"render": {
|
||||
"basic.spec.js": [
|
||||
"d3913ef5752b277c62e4f451923231c6d55ff947",
|
||||
[]
|
||||
],
|
||||
"rendering.spec.js": [
|
||||
"60e843a6973fa1b84e6eddf9c5a2308d1f848141",
|
||||
[]
|
||||
],
|
||||
"storeop.spec.js": [
|
||||
"cd0c9d6145e11e0046f8ea6bb001f9518c972601",
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -261,13 +229,7 @@
|
|||
],
|
||||
"render_pass": {
|
||||
"storeOp.spec.js": [
|
||||
"678a462925021a32db5d33baa33701259bcf7d7c",
|
||||
[]
|
||||
]
|
||||
},
|
||||
"render_pipeline": {
|
||||
"culling_tests.spec.js": [
|
||||
"a10a987520d089208b5aa49f3c329533188b379c",
|
||||
"c02e99db655933431cf8f310c21eac4febaf380d",
|
||||
[]
|
||||
]
|
||||
},
|
||||
|
@ -276,14 +238,6 @@
|
|||
"28fb5b368e317ef68ba6a3ec854729c3ca8cfb0e",
|
||||
[]
|
||||
],
|
||||
"depth_stencil_attachment_clear.spec.js": [
|
||||
"06004eadf3486eb59ccdc0588a7982ea3275d68c",
|
||||
[]
|
||||
],
|
||||
"sampled_texture_clear.spec.js": [
|
||||
"5ee102a37ffcda076fc001a4e15db176b07fe4d3",
|
||||
[]
|
||||
],
|
||||
"texture_zero_init_test.js": [
|
||||
"e3c21011bb288492bd137be9e2e43b5efd02aa36",
|
||||
[]
|
||||
|
@ -296,17 +250,13 @@
|
|||
[]
|
||||
],
|
||||
"createBindGroupLayout.spec.js": [
|
||||
"778ca6fb1b71e7f611722e0d90ce816bb062b90e",
|
||||
"9c0df538b278aaa38d11f3922b749799f72f4c3f",
|
||||
[]
|
||||
],
|
||||
"createPipelineLayout.spec.js": [
|
||||
"689ddaec883cd639f6cf94ab4d3005a4c4512db5",
|
||||
[]
|
||||
],
|
||||
"createRenderPipeline.spec.js": [
|
||||
"4a72faacd6c09555c3c1dac8424513f7b33a8c64",
|
||||
[]
|
||||
],
|
||||
"createTexture.spec.js": [
|
||||
"a9c7a8be01e03bcda1aa2f96374c30c0ce0bbe45",
|
||||
[]
|
||||
|
@ -315,40 +265,38 @@
|
|||
"d5b16563c13d9429bb1da58b9c5dd18279904f3d",
|
||||
[]
|
||||
],
|
||||
"encoding": {
|
||||
"cmds": {
|
||||
"index_access.spec.js": [
|
||||
"b0eb5c7bc6cfcdf4479a6365b8b54c362424a696",
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
"error_scope.spec.js": [
|
||||
"5523237daeba5d0c1c292dd3da8dead86a99f8e4",
|
||||
[]
|
||||
],
|
||||
"fences.spec.js": [
|
||||
"e2d4c7eb92220c98411c7e8f72be718c261a78b9",
|
||||
"cc04848410fed9f20b863aadbedf3482787b6de9",
|
||||
[]
|
||||
],
|
||||
"queue_submit.spec.js": [
|
||||
"2860a4f53188e1cc02774f0635c70962ecf9e52e",
|
||||
"4e77076e35089cbdd9ae1ffe04c8f13fd6663f1d",
|
||||
[]
|
||||
],
|
||||
"render_pass": {
|
||||
"resolve.spec.js": [
|
||||
"78965d9155e999ee19c3e97315502b11676ea838",
|
||||
[]
|
||||
],
|
||||
"storeOp.spec.js": [
|
||||
"c43c6547bc8f268af93031db7173185768f6d2b3",
|
||||
"e63540a9ca8afdc536200ebcfc8e955674b7a018",
|
||||
[]
|
||||
]
|
||||
},
|
||||
"render_pass.spec.js": [
|
||||
"b9db03bc86f19fd1ad5c847153cfcc6399f0450b",
|
||||
[]
|
||||
],
|
||||
"render_pass_descriptor.spec.js": [
|
||||
"6b2b8c54b784620b65d213b9cddf4b803fbeba3a",
|
||||
[]
|
||||
],
|
||||
"resource_usages": {
|
||||
"textureUsageInRender.spec.js": [
|
||||
"6322ec25ee537584030c322ab99ec3d69e02b30e",
|
||||
[]
|
||||
]
|
||||
},
|
||||
"setBindGroup.spec.js": [
|
||||
"fca85a83b0e5cf94d7950139c021ca6954e0e2ad",
|
||||
[]
|
||||
|
@ -365,10 +313,6 @@
|
|||
"f79a24044952c705004d254c81c31fd60967c54e",
|
||||
[]
|
||||
],
|
||||
"setVertexBuffer.spec.js": [
|
||||
"a7a173991eb3ff786af945af19e0e35ac56fc624",
|
||||
[]
|
||||
],
|
||||
"setViewport.spec.js": [
|
||||
"e2ef691b821825b9fa3c9fa5072dc734ec34d236",
|
||||
[]
|
||||
|
@ -376,23 +320,19 @@
|
|||
"validation_test.js": [
|
||||
"d816fd0594bd5097f4a7e3d2de91c3e0aa6e3f7b",
|
||||
[]
|
||||
],
|
||||
"vertex_state.spec.js": [
|
||||
"7a30e3a013aeada0469fa8d85c69c14c26921dbb",
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
"capability_info.js": [
|
||||
"df73575693bdf29f824ce6aeb144ec062aa2fb36",
|
||||
"2e4264fe229c11bb387d300cebce6a4e69780cd1",
|
||||
[]
|
||||
],
|
||||
"examples.spec.js": [
|
||||
"0d741fec3c2917808a885fa9a76a0300003c5382",
|
||||
"32825367009ab0c4427821a013e913480ba38863",
|
||||
[]
|
||||
],
|
||||
"gpu_test.js": [
|
||||
"a0163bb3ef38fe73f75b673f1507d92cc280b032",
|
||||
"92f0e41840bd8e74eda57bbeda60757af798e2f9",
|
||||
[]
|
||||
],
|
||||
"idl": {
|
||||
|
@ -408,21 +348,9 @@
|
|||
]
|
||||
},
|
||||
"listing.js": [
|
||||
"51bba64a493acad8c8f0bbc515fb621ef538b839",
|
||||
"8736de378a6a02235dfff5796f4fe4a963769001",
|
||||
[]
|
||||
],
|
||||
"shader": {
|
||||
"execution": {
|
||||
"robust_access.spec.js": [
|
||||
"0fe4a36ad10ee86a945a0546daac568882884fda",
|
||||
[]
|
||||
],
|
||||
"robust_access_vertex.spec.js": [
|
||||
"05b83fd049c98e4902096b467cf9f0aaa3fb4d51",
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
"util": {
|
||||
"conversion.js": [
|
||||
"bdba99cf7619ec4d66a5890f5566f68bbc6ed82d",
|
||||
|
@ -434,7 +362,7 @@
|
|||
],
|
||||
"texture": {
|
||||
"layout.js": [
|
||||
"cb9a883bd805e23b0c1769e78844fafd8717585f",
|
||||
"26d82a7be0aea4483b4b72d6faa2b9233a5f0039",
|
||||
[]
|
||||
],
|
||||
"subresource.js": [
|
||||
|
@ -455,7 +383,7 @@
|
|||
]
|
||||
},
|
||||
"copyImageBitmapToTexture.spec.js": [
|
||||
"b37e311d280f2d3cea3d85767075cc51f3f2a124",
|
||||
"1432ff05e1daa192d5898bf1c7f42e84008b23b4",
|
||||
[]
|
||||
],
|
||||
"reftests": {
|
||||
|
@ -464,7 +392,7 @@
|
|||
[]
|
||||
],
|
||||
"canvas_complex.js": [
|
||||
"e1aa3a25f88d26abdd381db249b03a6e84421fd5",
|
||||
"f2271d0debafde3681e6a4865ccd5c91817c94f9",
|
||||
[]
|
||||
],
|
||||
"gpu_ref_test.js": [
|
||||
|
@ -489,15 +417,7 @@
|
|||
"testharness": {
|
||||
"webgpu": {
|
||||
"cts.html": [
|
||||
"0e637156fa95e5039db18c62136f73595b92c087",
|
||||
[
|
||||
"webgpu/cts.html?q=webgpu:api,operation,buffers,create_mapped:*",
|
||||
{}
|
||||
],
|
||||
[
|
||||
"webgpu/cts.html?q=webgpu:api,operation,buffers,map:*",
|
||||
{}
|
||||
],
|
||||
"e87ceb79a409fc3e3ddcccf38e48b865740e0809",
|
||||
[
|
||||
"webgpu/cts.html?q=webgpu:api,operation,buffers,map_detach:*",
|
||||
{}
|
||||
|
@ -510,10 +430,6 @@
|
|||
"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:*",
|
||||
{}
|
||||
|
@ -522,14 +438,6 @@
|
|||
"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:*",
|
||||
{}
|
||||
|
@ -538,22 +446,6 @@
|
|||
"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:*",
|
||||
{}
|
||||
|
@ -566,10 +458,6 @@
|
|||
"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:*",
|
||||
{}
|
||||
|
@ -582,16 +470,20 @@
|
|||
"webgpu/cts.html?q=webgpu:api,validation,queue_submit:*",
|
||||
{}
|
||||
],
|
||||
[
|
||||
"webgpu/cts.html?q=webgpu:api,validation,render_pass,resolve:*",
|
||||
{}
|
||||
],
|
||||
[
|
||||
"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,render_pass_descriptor:*",
|
||||
"webgpu/cts.html?q=webgpu:api,validation,resource_usages,textureUsageInRender:*",
|
||||
{}
|
||||
],
|
||||
[
|
||||
|
@ -610,10 +502,6 @@
|
|||
"webgpu/cts.html?q=webgpu:api,validation,setStencilReference:*",
|
||||
{}
|
||||
],
|
||||
[
|
||||
"webgpu/cts.html?q=webgpu:api,validation,setVertexBuffer:*",
|
||||
{}
|
||||
],
|
||||
[
|
||||
"webgpu/cts.html?q=webgpu:api,validation,setViewport:*",
|
||||
{}
|
||||
|
@ -626,14 +514,6 @@
|
|||
"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:*",
|
||||
{}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -88,6 +88,19 @@ export class Fixture {
|
|||
}
|
||||
}
|
||||
|
||||
shouldResolve(p, msg) {
|
||||
this.eventualAsyncExpectation(async niceStack => {
|
||||
const m = msg ? ': ' + msg : '';
|
||||
try {
|
||||
await p;
|
||||
niceStack.message = 'resolved as expected' + m;
|
||||
} catch (ex) {
|
||||
niceStack.message = `REJECTED${m}\n${ex.message}`;
|
||||
this.rec.expectationFailed(niceStack);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
shouldReject(expectedName, p, msg) {
|
||||
this.eventualAsyncExpectation(async niceStack => {
|
||||
const m = msg ? ': ' + msg : '';
|
||||
|
@ -96,7 +109,7 @@ export class Fixture {
|
|||
niceStack.message = 'DID NOT REJECT' + m;
|
||||
this.rec.expectationFailed(niceStack);
|
||||
} catch (ex) {
|
||||
niceStack.message = m;
|
||||
niceStack.message = 'rejected as expected' + m;
|
||||
this.expectErrorValue(expectedName, ex, niceStack);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
|
@ -16,7 +16,30 @@ function _defineProperty(obj, key, value) {
|
|||
}
|
||||
import { publicParamsEquals } from './params_utils.js';
|
||||
import { assert } from './util/util.js';
|
||||
// https://stackoverflow.com/a/56375136
|
||||
/** Forces a type to resolve its type definitions, to make it readable/debuggable. */
|
||||
|
||||
function typeAssert() {}
|
||||
{
|
||||
{
|
||||
typeAssert();
|
||||
typeAssert();
|
||||
typeAssert();
|
||||
typeAssert();
|
||||
typeAssert();
|
||||
|
||||
typeAssert();
|
||||
|
||||
typeAssert();
|
||||
typeAssert();
|
||||
typeAssert();
|
||||
typeAssert();
|
||||
typeAssert();
|
||||
|
||||
// Unexpected test results - hopefully okay to ignore these
|
||||
typeAssert();
|
||||
typeAssert();
|
||||
}
|
||||
}
|
||||
|
||||
export function poptions(name, values) {
|
||||
const iter = makeReusableIterable(function* () {
|
||||
|
|
|
@ -17,5 +17,6 @@
|
|||
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
|
||||
ret = ret.replace(/%E2%9C%97/g, '✗'); // for jsUndefinedMagicValue
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
|
||||
**/ import { assert } from '../util/util.js';
|
||||
// JSON can't represent `undefined` and by default stores it as `null`.
|
||||
// Instead, store `undefined` as this magic string value in JSON.
|
||||
const jsUndefinedMagicValue = '✗undefined';
|
||||
|
||||
export function stringifyParamValue(value) {
|
||||
return JSON.stringify(value, (k, v) => {
|
||||
assert(v !== jsUndefinedMagicValue);
|
||||
|
||||
return v === undefined ? jsUndefinedMagicValue : v;
|
||||
});
|
||||
}
|
||||
|
||||
export function parseParamValue(s) {
|
||||
return JSON.parse(s, (k, v) => (v === jsUndefinedMagicValue ? undefined : v));
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
**/ import { badParamValueChars, paramKeyIsPublic } from '../params_utils.js';
|
||||
import { assert } from '../util/util.js';
|
||||
|
||||
import { parseParamValue } from './json_param_value.js';
|
||||
import {
|
||||
TestQueryMultiFile,
|
||||
TestQueryMultiTest,
|
||||
|
@ -124,5 +125,5 @@ function parseSingleParamValue(s) {
|
|||
`param value must not match ${badParamValueChars} - was ${s}`
|
||||
);
|
||||
|
||||
return s === 'undefined' ? undefined : JSON.parse(s);
|
||||
return parseParamValue(s);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
**/ import { badParamValueChars, paramKeyIsPublic } from '../params_utils.js';
|
||||
import { assert } from '../util/util.js';
|
||||
|
||||
import { stringifyParamValue } from './json_param_value.js';
|
||||
import { kParamKVSeparator } from './separators.js';
|
||||
|
||||
export function stringifyPublicParams(p) {
|
||||
|
@ -16,7 +17,7 @@ export function stringifySingleParam(k, v) {
|
|||
}
|
||||
|
||||
function stringifySingleParamValue(v) {
|
||||
const s = v === undefined ? 'undefined' : JSON.stringify(v);
|
||||
const s = stringifyParamValue(v);
|
||||
assert(
|
||||
!badParamValueChars.test(s),
|
||||
`JSON.stringified param value must not match ${badParamValueChars} - was ${s}`
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
// AUTO-GENERATED - DO NOT EDIT. See tools/gen_version.
|
||||
|
||||
export const version = '474ff8e569a0d206f8cb6f6146a8769fadf77da9-dirty';
|
||||
export const version = 'a4499c8ced40640242a40448c4d4258e3fb830d3';
|
||||
|
|
|
@ -27,45 +27,33 @@
|
|||
<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:*'>-->
|
||||
<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,operation,resource_init,copied_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,resolve:*'>
|
||||
<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,resource_usages,textureUsageInRender:*'>
|
||||
<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:*'>
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
|
||||
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;
|
||||
};
|
||||
})();
|
Binary file not shown.
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
});
|
|
@ -1,59 +1,110 @@
|
|||
/**
|
||||
* 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 { pbool, params } from '../../../../common/framework/params_builder.js';
|
||||
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
||||
import { assert } from '../../../../common/framework/util/util.js';
|
||||
|
||||
import { MappingTest } from './mapping_test.js';
|
||||
|
||||
export const g = makeTestGroup(MappingTest);
|
||||
|
||||
g.test('mapWriteAsync')
|
||||
.params(poptions('size', [12, 512 * 1024]))
|
||||
const kCases = [
|
||||
{ size: 0, range: [] },
|
||||
{ size: 0, range: [undefined] },
|
||||
{ size: 0, range: [undefined, undefined] },
|
||||
{ size: 0, range: [0] },
|
||||
{ size: 0, range: [0, undefined] },
|
||||
{ size: 0, range: [0, 0] },
|
||||
{ size: 12, range: [] },
|
||||
{ size: 12, range: [undefined] },
|
||||
{ size: 12, range: [undefined, undefined] },
|
||||
{ size: 12, range: [0] },
|
||||
{ size: 12, range: [0, undefined] },
|
||||
{ size: 12, range: [0, 12] },
|
||||
{ size: 12, range: [0, 0] },
|
||||
{ size: 12, range: [8] },
|
||||
{ size: 12, range: [8, undefined] },
|
||||
{ size: 12, range: [8, 4] },
|
||||
{ size: 28, range: [8, 8] },
|
||||
{ size: 28, range: [8, 12] },
|
||||
{ size: 512 * 1024, range: [] },
|
||||
];
|
||||
|
||||
function reifyMapRange(bufferSize, range) {
|
||||
var _range$, _range$2;
|
||||
const offset = (_range$ = range[0]) !== null && _range$ !== void 0 ? _range$ : 0;
|
||||
return [
|
||||
offset,
|
||||
(_range$2 = range[1]) !== null && _range$2 !== void 0 ? _range$2 : bufferSize - offset,
|
||||
];
|
||||
}
|
||||
|
||||
g.test('mapAsync,write')
|
||||
.params(kCases)
|
||||
.fn(async t => {
|
||||
const { size } = t.params;
|
||||
const { size, range } = t.params;
|
||||
const [rangeOffset, rangeSize] = reifyMapRange(size, range);
|
||||
|
||||
const buffer = t.device.createBuffer({
|
||||
size,
|
||||
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE,
|
||||
});
|
||||
|
||||
const arrayBuffer = await buffer.mapWriteAsync();
|
||||
t.checkMapWrite(buffer, arrayBuffer, size);
|
||||
await buffer.mapAsync(GPUMapMode.WRITE);
|
||||
const arrayBuffer = buffer.getMappedRange(...range);
|
||||
t.checkMapWrite(buffer, rangeOffset, arrayBuffer, rangeSize);
|
||||
});
|
||||
|
||||
g.test('mapReadAsync')
|
||||
.params(poptions('size', [12, 512 * 1024]))
|
||||
g.test('mapAsync,read')
|
||||
.params(kCases)
|
||||
.fn(async t => {
|
||||
const { size } = t.params;
|
||||
const { size, range } = t.params;
|
||||
const [, rangeSize] = reifyMapRange(size, range);
|
||||
|
||||
const [buffer, init] = t.device.createBufferMapped({
|
||||
const buffer = t.device.createBuffer({
|
||||
mappedAtCreation: true,
|
||||
size,
|
||||
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
|
||||
});
|
||||
|
||||
const expected = new Uint32Array(new ArrayBuffer(size));
|
||||
const init = buffer.getMappedRange(...range);
|
||||
|
||||
assert(init.byteLength === rangeSize);
|
||||
const expected = new Uint32Array(new ArrayBuffer(rangeSize));
|
||||
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());
|
||||
await buffer.mapAsync(GPUMapMode.READ);
|
||||
const actual = new Uint8Array(buffer.getMappedRange(...range));
|
||||
t.expectBuffer(actual, new Uint8Array(expected.buffer));
|
||||
});
|
||||
|
||||
g.test('createBufferMapped')
|
||||
g.test('mappedAtCreation')
|
||||
.params(
|
||||
params()
|
||||
.combine(poptions('size', [12, 512 * 1024]))
|
||||
.combine(kCases) //
|
||||
.combine(pbool('mappable'))
|
||||
)
|
||||
.fn(async t => {
|
||||
const { size, mappable } = t.params;
|
||||
const [buffer, arrayBuffer] = t.device.createBufferMapped({
|
||||
var _range$3;
|
||||
const { size, range, mappable } = t.params;
|
||||
const [, rangeSize] = reifyMapRange(size, range);
|
||||
|
||||
const buffer = t.device.createBuffer({
|
||||
mappedAtCreation: true,
|
||||
size,
|
||||
usage: GPUBufferUsage.COPY_SRC | (mappable ? GPUBufferUsage.MAP_WRITE : 0),
|
||||
});
|
||||
|
||||
t.checkMapWrite(buffer, arrayBuffer, size);
|
||||
const arrayBuffer = buffer.getMappedRange(...range);
|
||||
t.checkMapWrite(
|
||||
buffer,
|
||||
(_range$3 = range[0]) !== null && _range$3 !== void 0 ? _range$3 : 0,
|
||||
arrayBuffer,
|
||||
rangeSize
|
||||
);
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ class F extends GPUTest {
|
|||
|
||||
export const g = makeTestGroup(F);
|
||||
|
||||
g.test('mapWriteAsync')
|
||||
g.test('mapAsync,write')
|
||||
.params([
|
||||
{ unmap: true, destroy: false }, //
|
||||
{ unmap: false, destroy: true },
|
||||
|
@ -28,11 +28,12 @@ g.test('mapWriteAsync')
|
|||
])
|
||||
.fn(async t => {
|
||||
const buffer = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_WRITE });
|
||||
const arrayBuffer = await buffer.mapWriteAsync();
|
||||
await buffer.mapAsync(GPUMapMode.WRITE);
|
||||
const arrayBuffer = buffer.getMappedRange();
|
||||
t.checkDetach(buffer, arrayBuffer, t.params.unmap, t.params.destroy);
|
||||
});
|
||||
|
||||
g.test('mapReadAsync')
|
||||
g.test('mapAsync,read')
|
||||
.params([
|
||||
{ unmap: true, destroy: false }, //
|
||||
{ unmap: false, destroy: true },
|
||||
|
@ -40,7 +41,8 @@ g.test('mapReadAsync')
|
|||
])
|
||||
.fn(async t => {
|
||||
const buffer = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_READ });
|
||||
const arrayBuffer = await buffer.mapReadAsync();
|
||||
await buffer.mapAsync(GPUMapMode.READ);
|
||||
const arrayBuffer = buffer.getMappedRange();
|
||||
t.checkDetach(buffer, arrayBuffer, t.params.unmap, t.params.destroy);
|
||||
});
|
||||
|
||||
|
@ -52,11 +54,13 @@ g.test('create_mapped')
|
|||
])
|
||||
.fn(async t => {
|
||||
const desc = {
|
||||
mappedAtCreation: true,
|
||||
size: 4,
|
||||
usage: GPUBufferUsage.MAP_WRITE,
|
||||
};
|
||||
|
||||
const [buffer, arrayBuffer] = t.device.createBufferMapped(desc);
|
||||
const buffer = t.device.createBuffer(desc);
|
||||
const arrayBuffer = buffer.getMappedRange();
|
||||
|
||||
const view = new Uint8Array(arrayBuffer);
|
||||
t.expect(arrayBuffer.byteLength === 4);
|
||||
|
|
|
@ -1,34 +1,95 @@
|
|||
/**
|
||||
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
|
||||
**/ export const description = '';
|
||||
import { poptions, params, pbool } from '../../../../common/framework/params_builder.js';
|
||||
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
||||
import { kBufferUsages } from '../../../capability_info.js';
|
||||
import { GPUTest } from '../../../gpu_test.js';
|
||||
|
||||
function getBufferDesc(usage) {
|
||||
return {
|
||||
size: Number.MAX_SAFE_INTEGER,
|
||||
usage,
|
||||
};
|
||||
}
|
||||
// A multiple of 8 guaranteed to be way too large to allocate (just under 8 pebibytes).
|
||||
// (Note this is likely to exceed limitations other than just the system's
|
||||
// physical memory - so may test codepaths other than "true" OOM.)
|
||||
const MAX_ALIGNED_SAFE_INTEGER = Number.MAX_SAFE_INTEGER - 7;
|
||||
|
||||
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('mapAsync')
|
||||
.params(
|
||||
params()
|
||||
.combine(pbool('oom')) //
|
||||
.combine(pbool('write'))
|
||||
)
|
||||
.fn(async t => {
|
||||
const { oom, write } = t.params;
|
||||
const size = oom ? MAX_ALIGNED_SAFE_INTEGER : 16;
|
||||
|
||||
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());
|
||||
});
|
||||
const buffer = t.expectGPUError(
|
||||
'out-of-memory',
|
||||
() =>
|
||||
t.device.createBuffer({
|
||||
size,
|
||||
usage: write ? GPUBufferUsage.MAP_WRITE : GPUBufferUsage.MAP_READ,
|
||||
}),
|
||||
|
||||
g.test('createBufferMapped').fn(async t => {
|
||||
t.shouldThrow('RangeError', () => {
|
||||
t.device.createBufferMapped(getBufferDesc(GPUBufferUsage.COPY_SRC));
|
||||
oom
|
||||
);
|
||||
|
||||
const promise = t.expectGPUError(
|
||||
'validation', // Should be a validation error since the buffer is invalid.
|
||||
() => buffer.mapAsync(write ? GPUMapMode.WRITE : GPUMapMode.READ),
|
||||
oom
|
||||
);
|
||||
|
||||
if (oom) {
|
||||
// Should also reject in addition to the validation error.
|
||||
t.shouldReject('OperationError', promise);
|
||||
} else {
|
||||
await promise;
|
||||
const arraybuffer = buffer.getMappedRange();
|
||||
t.expect(arraybuffer.byteLength === size);
|
||||
buffer.unmap();
|
||||
t.expect(arraybuffer.byteLength === 0);
|
||||
}
|
||||
});
|
||||
|
||||
g.test('mappedAtCreation')
|
||||
.params(
|
||||
params()
|
||||
.combine(pbool('oom')) //
|
||||
.combine(poptions('usage', kBufferUsages))
|
||||
)
|
||||
.fn(async t => {
|
||||
const { oom, usage } = t.params;
|
||||
const size = oom ? MAX_ALIGNED_SAFE_INTEGER : 16;
|
||||
|
||||
const buffer = t.expectGPUError(
|
||||
'out-of-memory',
|
||||
() => t.device.createBuffer({ mappedAtCreation: true, size, usage }),
|
||||
oom
|
||||
);
|
||||
|
||||
const f = () => buffer.getMappedRange(0, size);
|
||||
|
||||
if (oom) {
|
||||
t.shouldThrow('RangeError', f);
|
||||
} else {
|
||||
f();
|
||||
}
|
||||
});
|
||||
|
||||
g.test('mappedAtCreation,smaller_getMappedRange')
|
||||
.params(poptions('usage', kBufferUsages))
|
||||
.fn(async t => {
|
||||
const { usage } = t.params;
|
||||
const size = MAX_ALIGNED_SAFE_INTEGER;
|
||||
|
||||
const buffer = t.expectGPUError('out-of-memory', () =>
|
||||
t.device.createBuffer({ mappedAtCreation: true, size, usage })
|
||||
);
|
||||
|
||||
// Smaller range inside a too-big mapping
|
||||
const mapping = buffer.getMappedRange(0, 16);
|
||||
t.expect(mapping.byteLength === 16);
|
||||
buffer.unmap();
|
||||
t.expect(mapping.byteLength === 0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
/**
|
||||
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
|
||||
**/ import { GPUTest } from '../../../gpu_test.js';
|
||||
**/ import { assert } from '../../../../common/framework/util/util.js';
|
||||
import { GPUTest } from '../../../gpu_test.js';
|
||||
export class MappingTest extends GPUTest {
|
||||
checkMapWrite(buffer, mappedContents, size) {
|
||||
checkMapWrite(buffer, offset, mappedContents, size) {
|
||||
this.checkMapWriteZeroed(mappedContents, size);
|
||||
|
||||
const mappedView = new Uint32Array(mappedContents);
|
||||
const expected = new Uint32Array(new ArrayBuffer(size));
|
||||
this.expect(mappedView.byteLength === size);
|
||||
assert(mappedView.byteLength === size);
|
||||
for (let i = 0; i < mappedView.length; ++i) {
|
||||
mappedView[i] = expected[i] = i + 1;
|
||||
}
|
||||
buffer.unmap();
|
||||
|
||||
this.expectContents(buffer, expected);
|
||||
this.expectContents(buffer, expected, offset);
|
||||
}
|
||||
|
||||
checkMapWriteZeroed(arrayBuffer, expectedSize) {
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
});
|
|
@ -11,12 +11,13 @@ export const g = makeTestGroup(GPUTest);
|
|||
g.test('b2b').fn(async t => {
|
||||
const data = new Uint32Array([0x01020304]);
|
||||
|
||||
const [src, map] = t.device.createBufferMapped({
|
||||
const src = t.device.createBuffer({
|
||||
mappedAtCreation: true,
|
||||
size: 4,
|
||||
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
new Uint32Array(map).set(data);
|
||||
new Uint32Array(src.getMappedRange()).set(data);
|
||||
src.unmap();
|
||||
|
||||
const dst = t.device.createBuffer({
|
||||
|
@ -34,12 +35,13 @@ g.test('b2b').fn(async t => {
|
|||
g.test('b2t2b').fn(async t => {
|
||||
const data = new Uint32Array([0x01020304]);
|
||||
|
||||
const [src, map] = t.device.createBufferMapped({
|
||||
const src = t.device.createBuffer({
|
||||
mappedAtCreation: true,
|
||||
size: 4,
|
||||
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
new Uint32Array(map).set(data);
|
||||
new Uint32Array(src.getMappedRange()).set(data);
|
||||
src.unmap();
|
||||
|
||||
const dst = t.device.createBuffer({
|
||||
|
@ -74,12 +76,13 @@ g.test('b2t2b').fn(async t => {
|
|||
g.test('b2t2t2b').fn(async t => {
|
||||
const data = new Uint32Array([0x01020304]);
|
||||
|
||||
const [src, map] = t.device.createBufferMapped({
|
||||
const src = t.device.createBuffer({
|
||||
mappedAtCreation: true,
|
||||
size: 4,
|
||||
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
new Uint32Array(map).set(data);
|
||||
new Uint32Array(src.getMappedRange()).set(data);
|
||||
src.unmap();
|
||||
|
||||
const dst = t.device.createBuffer({
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
/**
|
||||
* 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]));
|
||||
});
|
|
@ -1,78 +0,0 @@
|
|||
/**
|
||||
* 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 },
|
||||
});
|
||||
});
|
|
@ -2,23 +2,330 @@
|
|||
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
|
||||
**/ export const description = `API Operation Tests for RenderPass StoreOp.
|
||||
|
||||
Test Coverage Needed:
|
||||
Test Coverage:
|
||||
|
||||
- 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
|
||||
- Tests that color and depth-stencil store operations {'clear', 'store'} work correctly for a
|
||||
render pass with both a color attachment and depth-stencil attachment.
|
||||
TODO: use depth24plus-stencil8
|
||||
|
||||
- Tests that store operations {'clear', 'store'} work correctly for a render pass with multiple
|
||||
color attachments.
|
||||
TODO: test with more interesting loadOp values
|
||||
|
||||
- Tests that store operations {'clear', 'store'} work correctly for a render pass with a color
|
||||
attachment for:
|
||||
- All renderable color 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
|
||||
TODO: depth slice set to {'0', slice > '0'} for 3D textures
|
||||
|
||||
- Tests that store operations {'clear', 'store'} work correctly for a render pass with a
|
||||
depth-stencil attachment for:
|
||||
- All renderable 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`;
|
||||
TODO: test depth24plus and depth24plus-stencil8 formats
|
||||
TODO: test that depth and stencil aspects are set seperately
|
||||
TODO: depth slice set to {'0', slice > '0'} for 3D textures
|
||||
TODO: test with more interesting loadOp values`;
|
||||
import { params, poptions } from '../../../../common/framework/params_builder.js';
|
||||
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
||||
import { kTextureFormatInfo, kTextureFormats } from '../../../capability_info.js';
|
||||
import { GPUTest } from '../../../gpu_test.js';
|
||||
|
||||
// Test with a zero and non-zero mip.
|
||||
const kMipLevel = [0, 1];
|
||||
const kMipLevelCount = 2;
|
||||
|
||||
// Test with different numbers of color attachments.
|
||||
|
||||
const kNumColorAttachments = [1, 2, 3, 4];
|
||||
|
||||
// Test with a zero and non-zero array layer.
|
||||
const kArrayLayers = [0, 1];
|
||||
|
||||
const kStoreOps = ['clear', 'store'];
|
||||
|
||||
const kHeight = 2;
|
||||
const kWidth = 2;
|
||||
|
||||
export const g = makeTestGroup(GPUTest);
|
||||
|
||||
// Tests a render pass with both a color and depth stencil attachment to ensure store operations are
|
||||
// set independently.
|
||||
g.test('render_pass_store_op,color_attachment_with_depth_stencil_attachment')
|
||||
.params(
|
||||
params()
|
||||
.combine(poptions('colorStoreOperation', kStoreOps))
|
||||
.combine(poptions('depthStencilStoreOperation', kStoreOps))
|
||||
)
|
||||
.fn(t => {
|
||||
// Create a basic color attachment.
|
||||
const kColorFormat = 'rgba8unorm';
|
||||
const colorAttachment = t.device.createTexture({
|
||||
format: kColorFormat,
|
||||
size: { width: kWidth, height: kHeight, depth: 1 },
|
||||
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
|
||||
});
|
||||
|
||||
const colorAttachmentView = colorAttachment.createView();
|
||||
|
||||
// Create a basic depth/stencil attachment.
|
||||
const kDepthStencilFormat = 'depth32float';
|
||||
const depthStencilAttachment = t.device.createTexture({
|
||||
format: kDepthStencilFormat,
|
||||
size: { width: kWidth, height: kHeight, depth: 1 },
|
||||
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
|
||||
});
|
||||
|
||||
// Color load operation will clear to {1.0, 1.0, 1.0, 1.0}.
|
||||
// Depth & stencil load operations will clear to 1.0.
|
||||
// Store operations are determined by test the params.
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginRenderPass({
|
||||
colorAttachments: [
|
||||
{
|
||||
attachment: colorAttachmentView,
|
||||
loadValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
|
||||
storeOp: t.params.colorStoreOperation,
|
||||
},
|
||||
],
|
||||
|
||||
depthStencilAttachment: {
|
||||
attachment: depthStencilAttachment.createView(),
|
||||
depthLoadValue: 1.0,
|
||||
depthStoreOp: t.params.depthStencilStoreOperation,
|
||||
stencilLoadValue: 1.0,
|
||||
stencilStoreOp: t.params.depthStencilStoreOperation,
|
||||
},
|
||||
});
|
||||
|
||||
pass.endPass();
|
||||
|
||||
t.device.defaultQueue.submit([encoder.finish()]);
|
||||
|
||||
// Check that the correct store operation occurred.
|
||||
let expectedColorValue = {};
|
||||
if (t.params.colorStoreOperation === 'clear') {
|
||||
// If colorStoreOp was clear, the texture should now contain {0.0, 0.0, 0.0, 0.0}.
|
||||
expectedColorValue = { R: 0.0, G: 0.0, B: 0.0, A: 0.0 };
|
||||
} else if (t.params.colorStoreOperation === 'store') {
|
||||
// If colorStoreOP was store, the texture should still contain {1.0, 1.0, 1.0, 1.0}.
|
||||
expectedColorValue = { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
|
||||
}
|
||||
t.expectSingleColor(colorAttachment, kColorFormat, {
|
||||
size: [kHeight, kWidth, 1],
|
||||
exp: expectedColorValue,
|
||||
});
|
||||
|
||||
// Check that the correct store operation occurred.
|
||||
let expectedDepthValue = {};
|
||||
if (t.params.depthStencilStoreOperation === 'clear') {
|
||||
// If depthStencilStoreOperation was clear, the texture's depth component should be 0.0, and
|
||||
// the stencil component should be 0.0.
|
||||
expectedDepthValue = { Depth: 0.0 };
|
||||
} else if (t.params.depthStencilStoreOperation === 'store') {
|
||||
// If depthStencilStoreOperation was store, the texture's depth component should be 1.0, and
|
||||
// the stencil component should be 1.0.
|
||||
expectedDepthValue = { Depth: 1.0 };
|
||||
}
|
||||
t.expectSingleColor(depthStencilAttachment, kDepthStencilFormat, {
|
||||
size: [kHeight, kWidth, 1],
|
||||
exp: expectedDepthValue,
|
||||
});
|
||||
});
|
||||
|
||||
// Tests that render pass color attachment store operations work correctly for all renderable color
|
||||
// formats, mip levels and array layers.
|
||||
g.test('render_pass_store_op,color_attachment_only')
|
||||
.params(
|
||||
params()
|
||||
.combine(poptions('colorFormat', kTextureFormats))
|
||||
// Filter out any depth/stencil or non-renderable formats
|
||||
.filter(
|
||||
({ colorFormat }) =>
|
||||
kTextureFormatInfo[colorFormat].color && kTextureFormatInfo[colorFormat].renderable
|
||||
)
|
||||
.combine(poptions('storeOperation', kStoreOps))
|
||||
.combine(poptions('mipLevel', kMipLevel))
|
||||
.combine(poptions('arrayLayer', kArrayLayers))
|
||||
)
|
||||
.fn(t => {
|
||||
const kColorFormat = 'rgba8unorm';
|
||||
const colorAttachment = t.device.createTexture({
|
||||
format: kColorFormat,
|
||||
size: { width: kWidth, height: kHeight, depth: t.params.arrayLayer + 1 },
|
||||
mipLevelCount: kMipLevelCount,
|
||||
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
|
||||
});
|
||||
|
||||
const colorViewDesc = {
|
||||
baseArrayLayer: t.params.arrayLayer,
|
||||
baseMipLevel: t.params.mipLevel,
|
||||
mipLevelCount: 1,
|
||||
arrayLayerCount: 1,
|
||||
};
|
||||
|
||||
const colorAttachmentView = colorAttachment.createView(colorViewDesc);
|
||||
|
||||
// Color load operation will clear to {1.0, 0.0, 0.0, 1.0}.
|
||||
// Color store operation is determined by the test params.
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginRenderPass({
|
||||
colorAttachments: [
|
||||
{
|
||||
attachment: colorAttachmentView,
|
||||
loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
|
||||
storeOp: t.params.storeOperation,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
pass.endPass();
|
||||
t.device.defaultQueue.submit([encoder.finish()]);
|
||||
|
||||
// Check that the correct store operation occurred.
|
||||
let expectedValue = {};
|
||||
if (t.params.storeOperation === 'clear') {
|
||||
// If colorStoreOp was clear, the texture should now contain {0.0, 0.0, 0.0, 0.0}.
|
||||
expectedValue = { R: 0.0, G: 0.0, B: 0.0, A: 0.0 };
|
||||
} else if (t.params.storeOperation === 'store') {
|
||||
// If colorStoreOP was store, the texture should still contain {1.0, 0.0, 0.0, 1.0}.
|
||||
expectedValue = { R: 1.0, G: 0.0, B: 0.0, A: 1.0 };
|
||||
}
|
||||
|
||||
t.expectSingleColor(colorAttachment, kColorFormat, {
|
||||
size: [kHeight, kWidth, 1],
|
||||
slice: t.params.arrayLayer,
|
||||
exp: expectedValue,
|
||||
layout: { mipLevel: t.params.mipLevel },
|
||||
});
|
||||
});
|
||||
|
||||
// Test with multiple color attachments to ensure each attachment's storeOp is set independently.
|
||||
g.test('render_pass_store_op,multiple_color_attachments')
|
||||
.params(
|
||||
params()
|
||||
.combine(poptions('colorAttachments', kNumColorAttachments))
|
||||
.combine(poptions('storeOperation1', kStoreOps))
|
||||
.combine(poptions('storeOperation2', kStoreOps))
|
||||
)
|
||||
.fn(t => {
|
||||
const kColorFormat = 'rgba8unorm';
|
||||
const colorAttachments = [];
|
||||
|
||||
for (let i = 0; i < t.params.colorAttachments; i++) {
|
||||
colorAttachments.push(
|
||||
t.device.createTexture({
|
||||
format: kColorFormat,
|
||||
size: { width: kWidth, height: kHeight, depth: 1 },
|
||||
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Color load operation will clear to {1.0, 1.0, 1.0, 1.0}
|
||||
// Color store operation is determined by test params. Use storeOperation1 for even numbered
|
||||
// attachments and storeOperation2 for odd numbered attachments.
|
||||
const renderPassColorAttachmentDescriptors = [];
|
||||
for (let i = 0; i < t.params.colorAttachments; i++) {
|
||||
renderPassColorAttachmentDescriptors.push({
|
||||
attachment: colorAttachments[i].createView(),
|
||||
loadValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
|
||||
storeOp: i % 2 === 0 ? t.params.storeOperation1 : t.params.storeOperation2,
|
||||
});
|
||||
}
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginRenderPass({
|
||||
colorAttachments: renderPassColorAttachmentDescriptors,
|
||||
});
|
||||
|
||||
pass.endPass();
|
||||
t.device.defaultQueue.submit([encoder.finish()]);
|
||||
|
||||
// Check that the correct store operation occurred.
|
||||
let expectedValue = {};
|
||||
for (let i = 0; i < t.params.colorAttachments; i++) {
|
||||
if (renderPassColorAttachmentDescriptors[i].storeOp === 'clear') {
|
||||
// If colorStoreOp was clear, the texture should now contain {0.0, 0.0, 0.0, 0.0}.
|
||||
expectedValue = { R: 0.0, G: 0.0, B: 0.0, A: 0.0 };
|
||||
} else if (renderPassColorAttachmentDescriptors[i].storeOp === 'store') {
|
||||
// If colorStoreOP was store, the texture should still contain {1.0, 1.0, 1.0, 1.0}.
|
||||
expectedValue = { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
|
||||
}
|
||||
t.expectSingleColor(colorAttachments[i], kColorFormat, {
|
||||
size: [kHeight, kWidth, 1],
|
||||
exp: expectedValue,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Tests that render pass depth stencil store operations work correctly for all renderable color
|
||||
// formats, mip levels and array layers.
|
||||
g.test('render_pass_store_op,depth_stencil_attachment_only')
|
||||
.params(
|
||||
params()
|
||||
.combine(poptions('depthStencilFormat', kTextureFormats))
|
||||
// Filter out color and non-renderable formats.
|
||||
.filter(
|
||||
({ depthStencilFormat }) =>
|
||||
(kTextureFormatInfo[depthStencilFormat].depth ||
|
||||
kTextureFormatInfo[depthStencilFormat].stencil) &&
|
||||
kTextureFormatInfo[depthStencilFormat].renderable &&
|
||||
kTextureFormatInfo[depthStencilFormat].copyable
|
||||
)
|
||||
.combine(poptions('storeOperation', kStoreOps))
|
||||
.combine(poptions('mipLevel', kMipLevel))
|
||||
.combine(poptions('arrayLayer', kArrayLayers))
|
||||
)
|
||||
.fn(t => {
|
||||
const depthStencilAttachment = t.device.createTexture({
|
||||
format: t.params.depthStencilFormat,
|
||||
size: { width: kWidth, height: kHeight, depth: t.params.arrayLayer + 1 },
|
||||
mipLevelCount: kMipLevelCount,
|
||||
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
|
||||
});
|
||||
|
||||
const depthStencilViewDesc = {
|
||||
baseArrayLayer: t.params.arrayLayer,
|
||||
baseMipLevel: t.params.mipLevel,
|
||||
mipLevelCount: 1,
|
||||
arrayLayerCount: 1,
|
||||
};
|
||||
|
||||
const depthStencilAttachmentView = depthStencilAttachment.createView(depthStencilViewDesc);
|
||||
|
||||
// Depth-stencil load operation will clear to depth = 1.0, stencil = 1.0.
|
||||
// Depth-stencil store operate is determined by test params.
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginRenderPass({
|
||||
colorAttachments: [],
|
||||
depthStencilAttachment: {
|
||||
attachment: depthStencilAttachmentView,
|
||||
depthLoadValue: 1.0,
|
||||
depthStoreOp: t.params.storeOperation,
|
||||
stencilLoadValue: 1.0,
|
||||
stencilStoreOp: t.params.storeOperation,
|
||||
},
|
||||
});
|
||||
|
||||
pass.endPass();
|
||||
t.device.defaultQueue.submit([encoder.finish()]);
|
||||
|
||||
let expectedValue = {};
|
||||
if (t.params.storeOperation === 'clear') {
|
||||
// If depthStencilStoreOperation was clear, the texture's depth component should be 0.0,
|
||||
expectedValue = { Depth: 0.0 };
|
||||
} else if (t.params.storeOperation === 'store') {
|
||||
// If depthStencilStoreOperation was store, the texture's depth component should be 1.0,
|
||||
expectedValue = { Depth: 1.0 };
|
||||
}
|
||||
|
||||
t.expectSingleColor(depthStencilAttachment, t.params.depthStencilFormat, {
|
||||
size: [kHeight, kWidth, 1],
|
||||
slice: t.params.arrayLayer,
|
||||
exp: expectedValue,
|
||||
layout: { mipLevel: t.params.mipLevel },
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
/**
|
||||
* 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
|
||||
});
|
|
@ -1,213 +0,0 @@
|
|||
/**
|
||||
* 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());
|
|
@ -1,199 +0,0 @@
|
|||
/**
|
||||
* 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());
|
|
@ -14,7 +14,10 @@ import {
|
|||
kShaderStageCombinations,
|
||||
kTextureBindingTypeInfo,
|
||||
kTextureComponentTypes,
|
||||
kTextureFormats,
|
||||
kTextureFormatInfo,
|
||||
kTextureViewDimensions,
|
||||
kTextureViewDimensionInfo,
|
||||
} from '../../capability_info.js';
|
||||
|
||||
import { ValidationTest } from './validation_test.js';
|
||||
|
@ -45,125 +48,119 @@ g.test('some_binding_index_was_specified_more_than_once').fn(async t => {
|
|||
});
|
||||
});
|
||||
|
||||
g.test('visibility_and_dynamic_offsets')
|
||||
g.test('visibility')
|
||||
.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 { type, visibility } = t.params;
|
||||
|
||||
const supportsDynamicOffset = kBindingTypeInfo[type].perPipelineLimitClass.maxDynamic > 0;
|
||||
let success = true;
|
||||
if (!supportsDynamicOffset && hasDynamicOffset) success = false;
|
||||
if ((visibility & ~info.validStages) !== 0) success = false;
|
||||
const success = (visibility & ~kBindingTypeInfo[type].validStages) === 0;
|
||||
|
||||
// When hasDynamicOffset is false, it actually tests visibility.
|
||||
t.expectValidationError(() => {
|
||||
t.device.createBindGroupLayout({
|
||||
entries: [{ binding: 0, visibility, type, hasDynamicOffset }],
|
||||
entries: [{ binding: 0, visibility, type }],
|
||||
});
|
||||
}, !success);
|
||||
});
|
||||
|
||||
g.test('min_buffer_binding_size')
|
||||
g.test('bindingTypeSpecific_optional_members')
|
||||
.params(
|
||||
params()
|
||||
.combine(poptions('type', kBindingTypes))
|
||||
.combine(poptions('minBufferBindingSize', [undefined, 0, 4]))
|
||||
)
|
||||
.fn(async t => {
|
||||
const { type, minBufferBindingSize } = t.params;
|
||||
.combine([
|
||||
// Case with every member set to `undefined`.
|
||||
{
|
||||
// Workaround for TS inferring the type of [ {}, ...x ] overly conservatively, as {}[].
|
||||
_: 0,
|
||||
},
|
||||
|
||||
let success = false;
|
||||
if (
|
||||
minBufferBindingSize === undefined ||
|
||||
minBufferBindingSize === 0 ||
|
||||
type in kBufferBindingTypeInfo
|
||||
) {
|
||||
success = true;
|
||||
// Cases with one member set.
|
||||
...pbool('hasDynamicOffset'),
|
||||
...poptions('minBufferBindingSize', [0, 4]),
|
||||
...poptions('textureComponentType', kTextureComponentTypes),
|
||||
...pbool('multisampled'),
|
||||
...poptions('viewDimension', kTextureViewDimensions),
|
||||
...poptions('storageTextureFormat', kTextureFormats),
|
||||
])
|
||||
)
|
||||
.fn(t => {
|
||||
const {
|
||||
type,
|
||||
hasDynamicOffset,
|
||||
minBufferBindingSize,
|
||||
textureComponentType,
|
||||
multisampled,
|
||||
viewDimension,
|
||||
storageTextureFormat,
|
||||
} = t.params;
|
||||
|
||||
let success = true;
|
||||
if (!(type in kBufferBindingTypeInfo)) {
|
||||
if (hasDynamicOffset !== undefined) success = false;
|
||||
if (minBufferBindingSize !== undefined) success = false;
|
||||
}
|
||||
if (!(type in kTextureBindingTypeInfo)) {
|
||||
if (viewDimension !== undefined) success = false;
|
||||
}
|
||||
if (kBindingTypeInfo[type].resource !== 'sampledTex') {
|
||||
if (textureComponentType !== undefined) success = false;
|
||||
if (multisampled !== undefined) success = false;
|
||||
}
|
||||
if (kBindingTypeInfo[type].resource !== 'storageTex') {
|
||||
if (storageTextureFormat !== undefined) success = false;
|
||||
} else {
|
||||
if (viewDimension !== undefined && !kTextureViewDimensionInfo[viewDimension].storage) {
|
||||
success = false;
|
||||
}
|
||||
if (storageTextureFormat !== undefined && !kTextureFormatInfo[storageTextureFormat].storage) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
t.expectValidationError(() => {
|
||||
t.device.createBindGroupLayout({
|
||||
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type, minBufferBindingSize }],
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
type,
|
||||
hasDynamicOffset,
|
||||
minBufferBindingSize,
|
||||
textureComponentType,
|
||||
multisampled,
|
||||
viewDimension,
|
||||
storageTextureFormat,
|
||||
},
|
||||
],
|
||||
});
|
||||
}, !success);
|
||||
});
|
||||
|
||||
g.test('view_dimension')
|
||||
g.test('multisample_requires_2d_view_dimension')
|
||||
.params(
|
||||
params()
|
||||
.combine(poptions('type', kBindingTypes))
|
||||
.combine(poptions('multisampled', [undefined, false, true]))
|
||||
.combine(poptions('viewDimension', [undefined, ...kTextureViewDimensions]))
|
||||
)
|
||||
.fn(async t => {
|
||||
const { type, viewDimension } = t.params;
|
||||
const { multisampled, viewDimension } = t.params;
|
||||
|
||||
const success = viewDimension === undefined || type in kTextureBindingTypeInfo;
|
||||
const success = multisampled !== true || viewDimension === '2d' || viewDimension === undefined;
|
||||
|
||||
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 }],
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
type: 'sampled-texture',
|
||||
multisampled,
|
||||
viewDimension,
|
||||
},
|
||||
],
|
||||
});
|
||||
}, !success);
|
||||
});
|
||||
|
|
|
@ -1,234 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
||||
});
|
|
@ -1,135 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
||||
});
|
|
@ -57,32 +57,28 @@ g.test('increasing_fence_value_by_more_than_1_succeeds').fn(async t => {
|
|||
});
|
||||
|
||||
g.test('signal_a_fence_on_a_different_device_than_it_was_created_on_is_invalid').fn(async t => {
|
||||
const fence = t.queue.createFence();
|
||||
|
||||
const anotherDevice = await t.device.adapter.requestDevice();
|
||||
const fence = anotherDevice.defaultQueue.createFence();
|
||||
const anotherQueue = anotherDevice.defaultQueue;
|
||||
|
||||
t.expectValidationError(() => {
|
||||
t.queue.signal(fence, 2);
|
||||
anotherQueue.signal(fence, 2);
|
||||
});
|
||||
});
|
||||
|
||||
g.test('signal_a_fence_on_a_different_device_does_not_update_fence_signaled_value').fn(async t => {
|
||||
const fence = t.queue.createFence({ initialValue: 1 });
|
||||
|
||||
const anotherDevice = await t.device.adapter.requestDevice();
|
||||
const fence = anotherDevice.defaultQueue.createFence({ initialValue: 1 });
|
||||
const anotherQueue = anotherDevice.defaultQueue;
|
||||
|
||||
t.expectValidationError(() => {
|
||||
t.queue.signal(fence, 2);
|
||||
anotherQueue.signal(fence, 2);
|
||||
});
|
||||
|
||||
t.expect(fence.getCompletedValue() === 1);
|
||||
|
||||
anotherDevice.pushErrorScope('validation');
|
||||
|
||||
anotherDevice.defaultQueue.signal(fence, 2);
|
||||
t.queue.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}`);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -29,8 +29,8 @@ g.test('submitting_with_a_mapped_buffer_is_disallowed').fn(async t => {
|
|||
// 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();
|
||||
// Map the buffer, submitting when the buffer is mapped (even with no getMappedRange) should fail
|
||||
await buffer.mapAsync(GPUMapMode.WRITE);
|
||||
t.queue.submit([]);
|
||||
|
||||
t.expectValidationError(() => {
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
/**
|
||||
* 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,185 @@
|
|||
/**
|
||||
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
|
||||
**/ export const description = `API Validation Tests for RenderPass Resolve.
|
||||
|
||||
Test Coverage:
|
||||
- When resolveTarget is not null:
|
||||
- Test that the colorAttachment is multisampled:
|
||||
- A single sampled colorAttachment should generate an error.
|
||||
- Test that the resolveTarget is single sampled:
|
||||
- A multisampled resolveTarget should generate an error.
|
||||
- Test that the resolveTarget has usage OUTPUT_ATTACHMENT:
|
||||
- A resolveTarget without usage OUTPUT_ATTACHMENT should generate an error.
|
||||
- Test that the resolveTarget's texture view describes a single subresource:
|
||||
- A resolveTarget texture view with base mip {0, base mip > 0} and mip count of 1 should be
|
||||
valid.
|
||||
- An error should be generated when the resolve target view mip count is not 1 and base
|
||||
mip is {0, base mip > 0}.
|
||||
- A resolveTarget texture view with base array layer {0, base array layer > 0} and array
|
||||
layer count of 1 should be valid.
|
||||
- An error should be generated when the resolve target view array layer count is not 1 and
|
||||
base array layer is {0, base array layer > 0}.
|
||||
- Test that the resolveTarget's format is the same as the colorAttachment:
|
||||
- An error should be generated when the resolveTarget's format does not match the
|
||||
colorAttachment's format.
|
||||
- Test that the resolveTarget's size is the same the colorAttachment:
|
||||
- An error should be generated when the resolveTarget's height or width are not equal to
|
||||
the colorAttachment's height or width.`;
|
||||
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
||||
|
||||
import { ValidationTest } from './../validation_test.js';
|
||||
|
||||
const kNumColorAttachments = 4;
|
||||
|
||||
export const g = makeTestGroup(ValidationTest);
|
||||
|
||||
g.test('resolve_attachment')
|
||||
.params([
|
||||
// control case should be valid
|
||||
{ _valid: true },
|
||||
// a single sampled resolve source should cause a validation error.
|
||||
{ colorAttachmentSamples: 1, _valid: false },
|
||||
// a multisampled resolve target should cause a validation error.
|
||||
{ resolveTargetSamples: 4, _valid: false },
|
||||
// resolveTargetUsage without OUTPUT_ATTACHMENT usage should cause a validation error.
|
||||
{ resolveTargetUsage: GPUTextureUsage.COPY_SRC, _valid: false },
|
||||
// non-zero resolve target base mip level should be valid.
|
||||
{
|
||||
resolveTargetViewBaseMipLevel: 1,
|
||||
resolveTargetHeight: 4,
|
||||
resolveTargetWidth: 4,
|
||||
_valid: true,
|
||||
},
|
||||
|
||||
// a validation error should be created when mip count > 1
|
||||
{ resolveTargetViewMipCount: 2, _valid: false },
|
||||
{
|
||||
resolveTargetViewBaseMipLevel: 1,
|
||||
resolveTargetViewMipCount: 2,
|
||||
resolveTargetHeight: 4,
|
||||
resolveTargetWidth: 4,
|
||||
_valid: false,
|
||||
},
|
||||
|
||||
// non-zero resolve target base array layer should be valid.
|
||||
{ resolveTargetViewBaseArrayLayer: 1, _valid: true },
|
||||
// a validation error should be created when array layer count > 1
|
||||
{ resolveTargetViewArrayLayerCount: 2, _valid: false },
|
||||
{ resolveTargetViewBaseArrayLayer: 1, resolveTargetViewArrayLayerCount: 2, _valid: false },
|
||||
// other color attachments resolving with a different format should be valid.
|
||||
{ otherAttachmentFormat: 'bgra8unorm', _valid: true },
|
||||
// mismatched colorAttachment and resolveTarget formats should cause a validation error.
|
||||
{ colorAttachmentFormat: 'bgra8unorm', _valid: false },
|
||||
{ colorAttachmentFormat: 'rgba8unorm-srgb', _valid: false },
|
||||
{ resolveTargetFormat: 'bgra8unorm', _valid: false },
|
||||
{ resolveTargetFormat: 'rgba8unorm-srgb', _valid: false },
|
||||
// mismatched colorAttachment and resolveTarget sizes should cause a validation error.
|
||||
{ colorAttachmentHeight: 4, _valid: false },
|
||||
{ colorAttachmentWidth: 4, _valid: false },
|
||||
{ resolveTargetHeight: 4, _valid: false },
|
||||
{ resolveTargetWidth: 4, _valid: false },
|
||||
])
|
||||
.fn(async t => {
|
||||
const {
|
||||
colorAttachmentFormat = 'rgba8unorm',
|
||||
resolveTargetFormat = 'rgba8unorm',
|
||||
otherAttachmentFormat = 'rgba8unorm',
|
||||
colorAttachmentSamples = 4,
|
||||
resolveTargetSamples = 1,
|
||||
resolveTargetUsage = GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
|
||||
resolveTargetViewMipCount = 1,
|
||||
resolveTargetViewBaseMipLevel = 0,
|
||||
resolveTargetViewArrayLayerCount = 1,
|
||||
resolveTargetViewBaseArrayLayer = 0,
|
||||
colorAttachmentHeight = 2,
|
||||
colorAttachmentWidth = 2,
|
||||
resolveTargetHeight = 2,
|
||||
resolveTargetWidth = 2,
|
||||
_valid,
|
||||
} = t.params;
|
||||
|
||||
// Run the test in a nested loop such that the configured color attachment with resolve target
|
||||
// is tested while occupying each individual colorAttachment slot.
|
||||
for (let resolveSlot = 0; resolveSlot < kNumColorAttachments; resolveSlot++) {
|
||||
const renderPassColorAttachmentDescriptors = [];
|
||||
for (
|
||||
let colorAttachmentSlot = 0;
|
||||
colorAttachmentSlot < kNumColorAttachments;
|
||||
colorAttachmentSlot++
|
||||
) {
|
||||
// resolveSlot === colorAttachmentSlot denotes the color attachment slot that contains the color attachment with resolve
|
||||
// target.
|
||||
if (resolveSlot === colorAttachmentSlot) {
|
||||
// Create the color attachment with resolve target with the configurable parameters.
|
||||
const resolveSourceColorAttachment = t.device.createTexture({
|
||||
format: colorAttachmentFormat,
|
||||
size: { width: colorAttachmentWidth, height: colorAttachmentHeight, depth: 1 },
|
||||
sampleCount: colorAttachmentSamples,
|
||||
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
|
||||
});
|
||||
|
||||
const resolveTarget = t.device.createTexture({
|
||||
format: resolveTargetFormat,
|
||||
size: {
|
||||
width: resolveTargetWidth,
|
||||
height: resolveTargetHeight,
|
||||
depth: resolveTargetViewBaseArrayLayer + resolveTargetViewArrayLayerCount,
|
||||
},
|
||||
|
||||
sampleCount: resolveTargetSamples,
|
||||
mipLevelCount: resolveTargetViewBaseMipLevel + resolveTargetViewMipCount,
|
||||
usage: resolveTargetUsage,
|
||||
});
|
||||
|
||||
renderPassColorAttachmentDescriptors.push({
|
||||
attachment: resolveSourceColorAttachment.createView(),
|
||||
loadValue: 'load',
|
||||
resolveTarget: resolveTarget.createView({
|
||||
dimension: resolveTargetViewArrayLayerCount === 1 ? '2d' : '2d-array',
|
||||
mipLevelCount: resolveTargetViewMipCount,
|
||||
arrayLayerCount: resolveTargetViewArrayLayerCount,
|
||||
baseMipLevel: resolveTargetViewBaseMipLevel,
|
||||
baseArrayLayer: resolveTargetViewBaseArrayLayer,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
// Create a basic texture to fill other color attachment slots. This texture's dimensions
|
||||
// and sample count must match the resolve source color attachment to be valid.
|
||||
const colorAttachment = t.device.createTexture({
|
||||
format: otherAttachmentFormat,
|
||||
size: { width: colorAttachmentWidth, height: colorAttachmentHeight, depth: 1 },
|
||||
sampleCount: colorAttachmentSamples,
|
||||
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
|
||||
});
|
||||
|
||||
const resolveTarget = t.device.createTexture({
|
||||
format: otherAttachmentFormat,
|
||||
size: {
|
||||
width: colorAttachmentWidth,
|
||||
height: colorAttachmentHeight,
|
||||
depth: 1,
|
||||
},
|
||||
|
||||
sampleCount: 1,
|
||||
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
|
||||
});
|
||||
|
||||
renderPassColorAttachmentDescriptors.push({
|
||||
attachment: colorAttachment.createView(),
|
||||
loadValue: 'load',
|
||||
resolveTarget: resolveTarget.createView(),
|
||||
});
|
||||
}
|
||||
}
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginRenderPass({
|
||||
colorAttachments: renderPassColorAttachmentDescriptors,
|
||||
});
|
||||
|
||||
pass.endPass();
|
||||
|
||||
t.expectValidationError(() => {
|
||||
encoder.finish();
|
||||
}, !_valid);
|
||||
}
|
||||
});
|
|
@ -1,14 +1,76 @@
|
|||
/**
|
||||
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
|
||||
**/ export const description = `API Validation Tests for RenderPass StoreOp.
|
||||
**/ export const description = `
|
||||
API Validation Tests for RenderPass StoreOp.
|
||||
|
||||
Test Coverage Needed:
|
||||
Test Coverage:
|
||||
- Tests that when depthReadOnly is true, depthStoreOp must be 'store'.
|
||||
- When depthReadOnly is true and depthStoreOp is 'clear', an error should be generated.
|
||||
|
||||
- Test that when depthReadOnly is true, depthStoreOp must be 'store'
|
||||
- Tests that when stencilReadOnly is true, stencilStoreOp must be 'store'.
|
||||
- When stencilReadOnly is true and stencilStoreOp is 'clear', an error should be generated.
|
||||
|
||||
- Test that when stencilReadOnly is true, stencilStoreOp must be 'store'`;
|
||||
- Tests that the depthReadOnly value matches the stencilReadOnly value.
|
||||
- When depthReadOnly does not match stencilReadOnly, an error should be generated.
|
||||
|
||||
- Tests that depthReadOnly and stencilReadOnly default to false.`;
|
||||
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
||||
|
||||
import { ValidationTest } from './../validation_test.js';
|
||||
|
||||
export const g = makeTestGroup(ValidationTest);
|
||||
|
||||
g.test('store_op_and_read_only')
|
||||
.params([
|
||||
{ readonly: true, _valid: true },
|
||||
// Using depthReadOnly=true and depthStoreOp='clear' should cause a validation error.
|
||||
{ readonly: true, depthStoreOp: 'clear', _valid: false },
|
||||
// Using stencilReadOnly=true and stencilStoreOp='clear' should cause a validation error.
|
||||
{ readonly: true, stencilStoreOp: 'clear', _valid: false },
|
||||
// Mismatched depthReadOnly and stencilReadOnly values should cause a validation error.
|
||||
{ readonly: false, _valid: true },
|
||||
{ readonly: false, depthReadOnly: true, _valid: false },
|
||||
{ readonly: false, stencilReadOnly: true, _valid: false },
|
||||
// depthReadOnly and stencilReadOnly should default to false.
|
||||
{ readonly: undefined, _valid: true },
|
||||
{ readonly: undefined, depthReadOnly: true, _valid: false },
|
||||
{ readonly: undefined, stencilReadOnly: true, _valid: false },
|
||||
])
|
||||
.fn(async t => {
|
||||
const {
|
||||
readonly,
|
||||
depthStoreOp = 'store',
|
||||
depthReadOnly = readonly,
|
||||
stencilStoreOp = 'store',
|
||||
stencilReadOnly = readonly,
|
||||
_valid,
|
||||
} = t.params;
|
||||
|
||||
const depthAttachment = t.device.createTexture({
|
||||
format: 'depth24plus-stencil8',
|
||||
size: { width: 1, height: 1, depth: 1 },
|
||||
usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
|
||||
});
|
||||
|
||||
const depthAttachmentView = depthAttachment.createView();
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginRenderPass({
|
||||
colorAttachments: [],
|
||||
depthStencilAttachment: {
|
||||
attachment: depthAttachmentView,
|
||||
depthLoadValue: 'load',
|
||||
depthStoreOp,
|
||||
depthReadOnly,
|
||||
stencilLoadValue: 'load',
|
||||
stencilStoreOp,
|
||||
stencilReadOnly,
|
||||
},
|
||||
});
|
||||
|
||||
pass.endPass();
|
||||
|
||||
t.expectValidationError(() => {
|
||||
encoder.finish();
|
||||
}, !_valid);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
/**
|
||||
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
|
||||
**/ export const description = `
|
||||
Texture Usages Validation Tests in Render Pass.
|
||||
|
||||
Test Coverage:
|
||||
- Tests that read and write usages upon the same texture subresource, or different subresources
|
||||
of the same texture. Different subresources of the same texture includes different mip levels,
|
||||
different array layers, and different aspects.
|
||||
- When read and write usages are binding to the same texture subresource, an error should be
|
||||
generated. Otherwise, no error should be generated.
|
||||
`;
|
||||
import { poptions, params } from '../../../../common/framework/params_builder.js';
|
||||
import { makeTestGroup } from '../../../../common/framework/test_group.js';
|
||||
import { kTextureFormatInfo, kShaderStages } from '../../../capability_info.js';
|
||||
import { ValidationTest } from '../validation_test.js';
|
||||
|
||||
class TextureUsageTracking extends ValidationTest {
|
||||
createTexture(options = {}) {
|
||||
const {
|
||||
width = 32,
|
||||
height = 32,
|
||||
arrayLayerCount = 1,
|
||||
mipLevelCount = 1,
|
||||
sampleCount = 1,
|
||||
format = 'rgba8unorm',
|
||||
usage = GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.SAMPLED,
|
||||
} = options;
|
||||
|
||||
return this.device.createTexture({
|
||||
size: { width, height, depth: arrayLayerCount },
|
||||
mipLevelCount,
|
||||
sampleCount,
|
||||
dimension: '2d',
|
||||
format,
|
||||
usage,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const g = makeTestGroup(TextureUsageTracking);
|
||||
|
||||
const READ_BASE_LEVEL = 3;
|
||||
const READ_BASE_LAYER = 0;
|
||||
|
||||
g.test('readwrite_upon_subresources')
|
||||
.params([
|
||||
// read and write usages are binding to the same texture subresource.
|
||||
{
|
||||
writeBaseLevel: READ_BASE_LEVEL,
|
||||
writeBaseLayer: READ_BASE_LAYER,
|
||||
_success: false,
|
||||
},
|
||||
|
||||
// read and write usages are binding to different mip levels of the same texture.
|
||||
{
|
||||
writeBaseLevel: READ_BASE_LEVEL + 1,
|
||||
writeBaseLayer: READ_BASE_LAYER,
|
||||
_success: true,
|
||||
},
|
||||
|
||||
// read and write usages are binding to different array layers of the same texture.
|
||||
{
|
||||
writeBaseLevel: READ_BASE_LEVEL,
|
||||
writeBaseLayer: READ_BASE_LAYER + 1,
|
||||
_success: true,
|
||||
},
|
||||
])
|
||||
.fn(async t => {
|
||||
const { writeBaseLevel, writeBaseLayer, _success } = t.params;
|
||||
|
||||
const texture = t.createTexture({ arrayLayerCount: 2, mipLevelCount: 6 });
|
||||
|
||||
const sampleView = texture.createView({
|
||||
baseMipLevel: READ_BASE_LEVEL,
|
||||
mipLevelCount: 1,
|
||||
baseArrayLayer: READ_BASE_LAYER,
|
||||
arrayLayerCount: 1,
|
||||
});
|
||||
|
||||
const renderView = texture.createView({
|
||||
baseMipLevel: writeBaseLevel,
|
||||
mipLevelCount: 1,
|
||||
baseArrayLayer: writeBaseLayer,
|
||||
arrayLayerCount: 1,
|
||||
});
|
||||
|
||||
const bindGroupLayout = t.device.createBindGroupLayout({
|
||||
entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type: 'sampled-texture' }],
|
||||
});
|
||||
|
||||
const bindGroup = t.device.createBindGroup({
|
||||
entries: [{ binding: 0, resource: sampleView }],
|
||||
layout: bindGroupLayout,
|
||||
});
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginRenderPass({
|
||||
colorAttachments: [
|
||||
{
|
||||
attachment: renderView,
|
||||
loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
|
||||
storeOp: 'store',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
pass.setBindGroup(0, bindGroup);
|
||||
pass.endPass();
|
||||
|
||||
t.expectValidationError(() => {
|
||||
encoder.finish();
|
||||
}, !_success);
|
||||
});
|
||||
|
||||
g.test('readwrite_upon_aspects')
|
||||
.params(
|
||||
params()
|
||||
.combine(poptions('format', ['depth32float', 'depth24plus', 'depth24plus-stencil8']))
|
||||
.combine(poptions('readAspect', ['all', 'depth-only', 'stencil-only']))
|
||||
.combine(poptions('writeAspect', ['all', 'depth-only', 'stencil-only']))
|
||||
.unless(
|
||||
({ format, readAspect, writeAspect }) =>
|
||||
// TODO: Exclude depth-only aspect once WebGPU supports stencil-only texture format(s).
|
||||
(readAspect === 'stencil-only' && !kTextureFormatInfo[format].stencil) ||
|
||||
(writeAspect === 'stencil-only' && !kTextureFormatInfo[format].stencil)
|
||||
)
|
||||
)
|
||||
.fn(async t => {
|
||||
const { format, readAspect, writeAspect } = t.params;
|
||||
|
||||
const view = t.createTexture({ format }).createView();
|
||||
|
||||
const bindGroupLayout = t.device.createBindGroupLayout({
|
||||
entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type: 'sampled-texture' }],
|
||||
});
|
||||
|
||||
const bindGroup = t.device.createBindGroup({
|
||||
entries: [{ binding: 0, resource: view }],
|
||||
layout: bindGroupLayout,
|
||||
});
|
||||
|
||||
const success =
|
||||
(readAspect === 'depth-only' && writeAspect === 'stencil-only') ||
|
||||
(readAspect === 'stencil-only' && writeAspect === 'depth-only');
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginRenderPass({
|
||||
colorAttachments: [
|
||||
{
|
||||
attachment: t.createTexture().createView(),
|
||||
loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
|
||||
storeOp: 'store',
|
||||
},
|
||||
],
|
||||
|
||||
depthStencilAttachment: {
|
||||
attachment: view,
|
||||
depthStoreOp: 'clear',
|
||||
depthLoadValue: 'load',
|
||||
stencilStoreOp: 'clear',
|
||||
stencilLoadValue: 'load',
|
||||
},
|
||||
});
|
||||
|
||||
pass.setBindGroup(0, bindGroup);
|
||||
pass.endPass();
|
||||
|
||||
t.expectValidationError(() => {
|
||||
encoder.finish();
|
||||
}, !success);
|
||||
});
|
||||
|
||||
g.test('shader_stages_and_visibility')
|
||||
.params(
|
||||
params()
|
||||
.combine(poptions('readVisibility', [0, ...kShaderStages]))
|
||||
.combine(poptions('writeVisibility', [0, ...kShaderStages]))
|
||||
)
|
||||
.fn(async t => {
|
||||
const { readVisibility, writeVisibility } = t.params;
|
||||
|
||||
// writeonly-storage-texture binding type is not supported in vertex stage. So, this test
|
||||
// uses writeonly-storage-texture binding as writable binding upon the same subresource if
|
||||
// vertex stage is not included. Otherwise, it uses output attachment instead.
|
||||
const writeHasVertexStage = Boolean(writeVisibility & GPUShaderStage.VERTEX);
|
||||
const texUsage = writeHasVertexStage
|
||||
? GPUTextureUsage.SAMPLED | GPUTextureUsage.OUTPUT_ATTACHMENT
|
||||
: GPUTextureUsage.SAMPLED | GPUTextureUsage.STORAGE;
|
||||
|
||||
const texture = t.createTexture({ usage: texUsage });
|
||||
const view = texture.createView();
|
||||
const bglEntries = [{ binding: 0, visibility: readVisibility, type: 'sampled-texture' }];
|
||||
|
||||
const bgEntries = [{ binding: 0, resource: view }];
|
||||
if (!writeHasVertexStage) {
|
||||
bglEntries.push({
|
||||
binding: 1,
|
||||
visibility: writeVisibility,
|
||||
type: 'writeonly-storage-texture',
|
||||
storageTextureFormat: 'rgba8unorm',
|
||||
});
|
||||
|
||||
bgEntries.push({ binding: 1, resource: view });
|
||||
}
|
||||
const bindGroup = t.device.createBindGroup({
|
||||
entries: bgEntries,
|
||||
layout: t.device.createBindGroupLayout({ entries: bglEntries }),
|
||||
});
|
||||
|
||||
const encoder = t.device.createCommandEncoder();
|
||||
const pass = encoder.beginRenderPass({
|
||||
colorAttachments: [
|
||||
{
|
||||
attachment: writeHasVertexStage ? view : t.createTexture().createView(),
|
||||
loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
|
||||
storeOp: 'store',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
pass.setBindGroup(0, bindGroup);
|
||||
pass.endPass();
|
||||
|
||||
// Texture usages in bindings with invisible shader stages should be tracked. Invisible shader
|
||||
// stages include shader stage with visibility none and compute shader stage in render pass.
|
||||
t.expectValidationError(() => {
|
||||
encoder.finish();
|
||||
});
|
||||
});
|
|
@ -1,179 +0,0 @@
|
|||
/**
|
||||
* 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();
|
||||
});
|
||||
}
|
||||
});
|
|
@ -1,722 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
|
@ -10,6 +10,23 @@ function numericKeysOf(obj) {
|
|||
return Object.keys(obj).map(n => Number(n));
|
||||
}
|
||||
|
||||
// Buffers
|
||||
|
||||
export const kBufferUsageInfo = {
|
||||
[GPUBufferUsage.MAP_READ]: {},
|
||||
[GPUBufferUsage.MAP_WRITE]: {},
|
||||
[GPUBufferUsage.COPY_SRC]: {},
|
||||
[GPUBufferUsage.COPY_DST]: {},
|
||||
[GPUBufferUsage.INDEX]: {},
|
||||
[GPUBufferUsage.VERTEX]: {},
|
||||
[GPUBufferUsage.UNIFORM]: {},
|
||||
[GPUBufferUsage.STORAGE]: {},
|
||||
[GPUBufferUsage.INDIRECT]: {},
|
||||
[GPUBufferUsage.QUERY_RESOLVE]: {},
|
||||
};
|
||||
|
||||
export const kBufferUsages = numericKeysOf(kBufferUsageInfo);
|
||||
|
||||
// Textures
|
||||
|
||||
export const kTextureFormatInfo = {
|
||||
|
@ -476,12 +493,12 @@ export const kTextureComponentTypes = keysOf(kTextureComponentTypeInfo);
|
|||
// Texture View
|
||||
|
||||
export const kTextureViewDimensionInfo = {
|
||||
'1d': {},
|
||||
'2d': {},
|
||||
'2d-array': {},
|
||||
cube: {},
|
||||
'cube-array': {},
|
||||
'3d': {},
|
||||
'1d': { storage: true },
|
||||
'2d': { storage: true },
|
||||
'2d-array': { storage: true },
|
||||
cube: { storage: false },
|
||||
'cube-array': { storage: false },
|
||||
'3d': { storage: true },
|
||||
};
|
||||
|
||||
export const kTextureViewDimensions = keysOf(kTextureViewDimensionInfo);
|
||||
|
|
|
@ -89,12 +89,13 @@ g.test('gpu,async').fn(async t => {
|
|||
|
||||
g.test('gpu,buffers').fn(async t => {
|
||||
const data = new Uint32Array([0, 1234, 0]);
|
||||
const [src, map] = t.device.createBufferMapped({
|
||||
const src = t.device.createBuffer({
|
||||
mappedAtCreation: true,
|
||||
size: 12,
|
||||
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
new Uint32Array(map).set(data);
|
||||
new Uint32Array(src.getMappedRange()).set(data);
|
||||
src.unmap();
|
||||
|
||||
// Use the expectContents helper to check the actual contents of a GPUBuffer.
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
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';
|
||||
|
@ -43,7 +42,6 @@ export class GPUTest extends Fixture {
|
|||
|
||||
async init() {
|
||||
await super.init();
|
||||
await initGLSL();
|
||||
|
||||
const device = await devicePool.acquire();
|
||||
const queue = device.defaultQueue;
|
||||
|
@ -77,24 +75,14 @@ export class GPUTest extends Fixture {
|
|||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
createCopyForMapRead(src, srcOffset, 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);
|
||||
c.copyBufferToBuffer(src, srcOffset, dst, 0, size);
|
||||
|
||||
this.queue.submit([c.finish()]);
|
||||
|
||||
|
@ -103,16 +91,13 @@ export class GPUTest extends Fixture {
|
|||
|
||||
// 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);
|
||||
expectContents(src, expected, srcOffset = 0) {
|
||||
const dst = this.createCopyForMapRead(src, srcOffset, expected.buffer.byteLength);
|
||||
|
||||
this.eventualAsyncExpectation(async niceStack => {
|
||||
const constructor = expected.constructor;
|
||||
const actual = new constructor(await dst.mapReadAsync());
|
||||
await dst.mapAsync(GPUMapMode.READ);
|
||||
const actual = new constructor(dst.getMappedRange());
|
||||
const check = this.checkBuffer(actual, expected);
|
||||
if (check !== undefined) {
|
||||
niceStack.message = check;
|
||||
|
@ -223,38 +208,12 @@ got [${failedByteActualValues.join(', ')}]`;
|
|||
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, shouldError = true) {
|
||||
// If no error is expected, we let the scope surrounding the test catch it.
|
||||
if (!shouldError) {
|
||||
return fn();
|
||||
}
|
||||
|
||||
expectGPUError(filter, fn) {
|
||||
this.device.pushErrorScope(filter);
|
||||
const returnValue = fn();
|
||||
const promise = this.device.popErrorScope();
|
||||
|
|
|
@ -26,15 +26,6 @@ export const listing = [
|
|||
],
|
||||
"readme": "GPUBuffer tests."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
"operation",
|
||||
"buffers",
|
||||
"create_mapped"
|
||||
],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
|
@ -71,16 +62,6 @@ export const listing = [
|
|||
],
|
||||
"description": "Basic tests."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
"operation",
|
||||
"command_buffer",
|
||||
"compute",
|
||||
"basic"
|
||||
],
|
||||
"description": "Basic command buffer compute tests."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
|
@ -100,26 +81,6 @@ export const listing = [
|
|||
],
|
||||
"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",
|
||||
|
@ -135,16 +96,7 @@ export const listing = [
|
|||
"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)"
|
||||
"description": "API Operation Tests for RenderPass StoreOp.\n\n Test Coverage:\n\n - Tests that color and depth-stencil store operations {'clear', 'store'} work correctly for a\n render pass with both a color attachment and depth-stencil attachment.\n TODO: use depth24plus-stencil8\n\n - Tests that store operations {'clear', 'store'} work correctly for a render pass with multiple\n color attachments.\n TODO: test with more interesting loadOp values\n\n - Tests that store operations {'clear', 'store'} work correctly for a render pass with a color\n attachment for:\n - All renderable color formats\n - mip level set to {'0', mip > '0'}\n - array layer set to {'0', layer > '1'} for 2D textures\n TODO: depth slice set to {'0', slice > '0'} for 3D textures\n\n - Tests that store operations {'clear', 'store'} work correctly for a render pass with a\n depth-stencil attachment for:\n - All renderable depth-stencil formats\n - mip level set to {'0', mip > '0'}\n - array layer set to {'0', layer > '1'} for 2D textures\n TODO: test depth24plus and depth24plus-stencil8 formats\n TODO: test that depth and stencil aspects are set seperately\n TODO: depth slice set to {'0', slice > '0'} for 3D textures\n TODO: test with more interesting loadOp values"
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
|
@ -155,24 +107,6 @@ export const listing = [
|
|||
],
|
||||
"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",
|
||||
|
@ -211,14 +145,6 @@ export const listing = [
|
|||
],
|
||||
"description": "createPipelineLayout validation tests."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
"validation",
|
||||
"createRenderPipeline"
|
||||
],
|
||||
"description": "createRenderPipeline validation tests."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
|
@ -235,16 +161,6 @@ export const listing = [
|
|||
],
|
||||
"description": "createView validation tests."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
"validation",
|
||||
"encoding",
|
||||
"cmds",
|
||||
"index_access"
|
||||
],
|
||||
"description": "indexed draws validation tests."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
|
@ -273,9 +189,10 @@ export const listing = [
|
|||
"file": [
|
||||
"api",
|
||||
"validation",
|
||||
"render_pass"
|
||||
"render_pass",
|
||||
"resolve"
|
||||
],
|
||||
"description": "render pass validation tests."
|
||||
"description": "API Validation Tests for RenderPass Resolve.\n\n Test Coverage:\n - When resolveTarget is not null:\n - Test that the colorAttachment is multisampled:\n - A single sampled colorAttachment should generate an error.\n - Test that the resolveTarget is single sampled:\n - A multisampled resolveTarget should generate an error.\n - Test that the resolveTarget has usage OUTPUT_ATTACHMENT:\n - A resolveTarget without usage OUTPUT_ATTACHMENT should generate an error.\n - Test that the resolveTarget's texture view describes a single subresource:\n - A resolveTarget texture view with base mip {0, base mip > 0} and mip count of 1 should be\n valid.\n - An error should be generated when the resolve target view mip count is not 1 and base\n mip is {0, base mip > 0}.\n - A resolveTarget texture view with base array layer {0, base array layer > 0} and array\n layer count of 1 should be valid.\n - An error should be generated when the resolve target view array layer count is not 1 and\n base array layer is {0, base array layer > 0}.\n - Test that the resolveTarget's format is the same as the colorAttachment:\n - An error should be generated when the resolveTarget's format does not match the\n colorAttachment's format.\n - Test that the resolveTarget's size is the same the colorAttachment:\n - An error should be generated when the resolveTarget's height or width are not equal to\n the colorAttachment's height or width."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
|
@ -284,7 +201,7 @@ export const listing = [
|
|||
"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'"
|
||||
"description": "API Validation Tests for RenderPass StoreOp.\n\nTest Coverage:\n - Tests that when depthReadOnly is true, depthStoreOp must be 'store'.\n - When depthReadOnly is true and depthStoreOp is 'clear', an error should be generated.\n\n - Tests that when stencilReadOnly is true, stencilStoreOp must be 'store'.\n - When stencilReadOnly is true and stencilStoreOp is 'clear', an error should be generated.\n\n - Tests that the depthReadOnly value matches the stencilReadOnly value.\n - When depthReadOnly does not match stencilReadOnly, an error should be generated.\n\n - Tests that depthReadOnly and stencilReadOnly default to false."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
|
@ -294,6 +211,15 @@ export const listing = [
|
|||
],
|
||||
"description": "render pass descriptor validation tests."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
"validation",
|
||||
"resource_usages",
|
||||
"textureUsageInRender"
|
||||
],
|
||||
"description": "Texture Usages Validation Tests in Render Pass.\n\nTest Coverage:\n - Tests that read and write usages upon the same texture subresource, or different subresources\n of the same texture. Different subresources of the same texture includes different mip levels,\n different array layers, and different aspects.\n - When read and write usages are binding to the same texture subresource, an error should be\n generated. Otherwise, no error should be generated."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
|
@ -326,14 +252,6 @@ export const listing = [
|
|||
],
|
||||
"description": "setStencilReference validation tests."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
"validation",
|
||||
"setVertexBuffer"
|
||||
],
|
||||
"description": "setVertexBuffer validation tests."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
|
@ -342,14 +260,6 @@ export const listing = [
|
|||
],
|
||||
"description": "setViewport validation tests."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"api",
|
||||
"validation",
|
||||
"vertex_state"
|
||||
],
|
||||
"description": "vertexState validation tests."
|
||||
},
|
||||
{
|
||||
"file": [
|
||||
"examples"
|
||||
|
@ -383,22 +293,6 @@ export const listing = [
|
|||
],
|
||||
"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",
|
||||
|
|
|
@ -1,337 +0,0 @@
|
|||
/**
|
||||
* 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.
|
|
@ -1,451 +0,0 @@
|
|||
/**
|
||||
* 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 } }
|
||||
);
|
||||
});
|
|
@ -116,11 +116,14 @@ export function createTextureUploadBuffer(
|
|||
options
|
||||
);
|
||||
|
||||
const [buffer, mapping] = device.createBufferMapped({
|
||||
const buffer = device.createBuffer({
|
||||
mappedAtCreation: true,
|
||||
size: byteLength,
|
||||
usage: GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
|
||||
const mapping = buffer.getMappedRange();
|
||||
|
||||
assert(texelValue.byteLength === bytesPerBlock);
|
||||
fillTextureDataWithTexelValue(texelValue, format, dimension, mapping, size, options);
|
||||
buffer.unmap();
|
||||
|
|
|
@ -35,7 +35,8 @@ class F extends GPUTest {
|
|||
const dst = this.createCopyForMapRead(src, 0, rowPitch * height);
|
||||
|
||||
this.eventualAsyncExpectation(async niceStack => {
|
||||
const actual = new Uint8Array(await dst.mapReadAsync());
|
||||
await dst.mapAsync(GPUMapMode.READ);
|
||||
const actual = new Uint8Array(dst.getMappedRange());
|
||||
const check = this.checkBufferWithRowPitch(
|
||||
actual,
|
||||
exp,
|
||||
|
|
|
@ -25,11 +25,13 @@ export function run(format) {
|
|||
|
||||
const rows = 2;
|
||||
const bytesPerRow = 256;
|
||||
const [buffer, mapping] = t.device.createBufferMapped({
|
||||
const buffer = t.device.createBuffer({
|
||||
mappedAtCreation: true,
|
||||
size: rows * bytesPerRow,
|
||||
usage: GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
|
||||
const mapping = buffer.getMappedRange();
|
||||
switch (format) {
|
||||
case 'bgra8unorm':
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue