From d6b752b7621ebd00c72d93429480adba4032cb8b Mon Sep 17 00:00:00 2001 From: wangra Date: Mon, 17 Aug 2026 17:21:11 -0400 Subject: [PATCH 1/3] This PR adds CTS test coverage for the `snorm10-10-10-2` vertex format. * Adds `snorm10-10-10-2` to `kVertexFormatInfo` in `capability_info.ts` (with an explicit cast until `@webgpu/types` is updated). * Adds signed 10-10-10-2 bit-packing (`makeRgb10a2Signed`) and normalization (`normalizeRgb10a2Signed`) test helpers. * Adds packed format test data in `correctness.spec.ts` to verify conversion from raw vertex data to `vec4` in the vertex shader. Issue: https://github.com/gpuweb/gpuweb/issues/6292
**Requirements for PR author:** - [x] All missing test coverage is tracked with "TODO" or `.unimplemented()`. - [x] New helpers are `/** documented */` and new helper files are found in `helper_index.txt`. - [x] Test behaves as expected in a WebGPU implementation. (If not passing, explain above.) - [x] Test have be tested with compatibility mode validation enabled and behave as expected. (If not passing, explain above.) **Requirements for [reviewer sign-off](https://github.com/gpuweb/cts/blob/main/docs/reviews.md):** - [ ] Tests are properly located. - [ ] [Test descriptions](https://github.com/gpuweb/cts/blob/main/docs/intro/plans.md) are accurate and complete. - [ ] Tests provide complete coverage (including validation control cases). **Missing coverage MUST be covered by TODOs.** - [ ] Tests avoid [over-parameterization](https://github.com/gpuweb/cts/blob/main/docs/organization.md#parameterization) (see case count report). When landing this PR, be sure to make any necessary issue status updates. --- .../vertex_state/correctness.spec.ts | 45 +++++++++++++++++++ src/webgpu/capability_info.ts | 4 +- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/webgpu/api/operation/vertex_state/correctness.spec.ts b/src/webgpu/api/operation/vertex_state/correctness.spec.ts index 8ddeaa4e5c60..55348c01c7d4 100644 --- a/src/webgpu/api/operation/vertex_state/correctness.spec.ts +++ b/src/webgpu/api/operation/vertex_state/correctness.spec.ts @@ -71,6 +71,25 @@ function normalizeRgb10a2(rgba: number, index: number): number { return rgba / normalizationFactor; } +function makeRgb10a2Signed(rgba: Array): number { + const [r, g, b, a] = rgba; + assert(r >= -512 && r <= 511); + assert(g >= -512 && g <= 511); + assert(b >= -512 && b <= 511); + assert(a >= -2 && a <= 1); + const r_bits = (r < 0 ? r + 1024 : r) & 0x3ff; + const g_bits = (g < 0 ? g + 1024 : g) & 0x3ff; + const b_bits = (b < 0 ? b + 1024 : b) & 0x3ff; + const a_bits = (a < 0 ? a + 4 : a) & 0x3; + return r_bits | (g_bits << 10) | (b_bits << 20) | (a_bits << 30); +} + +function normalizeRgb10a2Signed(val: number, index: number): number { + const isAlpha = index % 4 === 3; + const maxVal = isAlpha ? 1 : 511; + return Math.max(val / maxVal, -1.0); +} + type TestData = { shaderBaseType: string; floatTolerance?: number; @@ -379,6 +398,32 @@ struct VSOutputs { } case 'snorm': { + if (formatInfo.bytesPerComponent === 'packed') { + assert(bitSize === 0); + switch (format as string) { + case 'snorm10-10-10-2': { + /* prettier-ignore */ + const data = [ + [ 0, 0, 0, 0], + [ 511, 511, 511, 1], + [-512, -512, -512, -2], + [ 243, -123, 342, -1], + ]; + const vertexData = new Uint32Array(data.map(makeRgb10a2Signed)).buffer; + const expectedData = new Float32Array(data.flat().map(normalizeRgb10a2Signed)).buffer; + + return { + shaderBaseType: 'f32', + testComponentCount: data.flat().length, + expectedData, + vertexData, + floatTolerance: 0.1 / 511, + }; + } + default: + unreachable(); + } + } /* prettier-ignore */ const data = [ 42, diff --git a/src/webgpu/capability_info.ts b/src/webgpu/capability_info.ts index b13e310bcf9c..c68bdd3cf531 100644 --- a/src/webgpu/capability_info.ts +++ b/src/webgpu/capability_info.ts @@ -371,7 +371,9 @@ export const kVertexFormatInfo: { // 32 bit packed 'unorm10-10-10-2': [ 'packed', 'unorm', 4, 4, 'vec4'], 'unorm8x4-bgra': [ 'packed', 'unorm', 4, 4, 'vec4'], -} as const); + 'snorm10-10-10-2': [ 'packed', 'snorm', 4, 4, 'vec4'], + // eslint-disable-next-line @typescript-eslint/no-explicit-any +} as const) as any; /** List of all GPUVertexFormat values. */ export const kVertexFormats = keysOf(kVertexFormatInfo); From 278adacdf0fe980416c1764ce6b7bbb406924cda Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Tue, 18 Aug 2026 19:05:09 -0700 Subject: [PATCH 2/3] Roll @webgpu/types to 0.1.72 --- package-lock.json | 45 +++++++++-------------------------- package.json | 2 +- src/webgpu/capability_info.ts | 3 +-- 3 files changed, 13 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb651203d1e5..d61298adb78f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@types/w3c-image-capture": "^1.0.10", "@typescript-eslint/eslint-plugin": "^6.9.1", "@typescript-eslint/parser": "^6.9.1", - "@webgpu/types": "^0.1.71", + "@webgpu/types": "^0.1.72", "ansi-colors": "4.1.3", "babel-plugin-add-header-comment": "^1.0.3", "babel-plugin-const-enum": "^1.2.0", @@ -148,7 +148,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dev": true, - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -1042,7 +1041,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", "dev": true, - "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -1218,7 +1216,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", "dev": true, - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.9.1", "@typescript-eslint/types": "6.9.1", @@ -1542,11 +1539,10 @@ "dev": true }, "node_modules/@webgpu/types": { - "version": "0.1.71", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.71.tgz", - "integrity": "sha512-mMy8/ODcKhab808co15eW+yN+HgXoQxRQHTiBV9Mrvl1r0ufnid7YOcI+gi4eUWSWl9ezD6TW2KXccrL8HCh2A==", - "dev": true, - "license": "BSD-3-Clause" + "version": "0.1.72", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.72.tgz", + "integrity": "sha512-0cF7RFM2edNoiIS1ODJp0/Gzv4/xSXhwoR0YCza+OWpJWtn4wmo9DvK91aLlH9+uUnwIriP7ZiC3WitmyhuzBw==", + "dev": true }, "node_modules/abbrev": { "version": "1.1.1", @@ -1572,7 +1568,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2042,7 +2037,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001541", "electron-to-chromium": "^1.4.535", @@ -2903,7 +2897,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4356,7 +4349,6 @@ "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz", "integrity": "sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA==", "dev": true, - "peer": true, "dependencies": { "dateformat": "~4.6.2", "eventemitter2": "~0.4.13", @@ -4834,7 +4826,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -5042,7 +5033,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -7220,7 +7210,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", "dev": true, - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -8645,7 +8634,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9054,7 +9042,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dev": true, - "peer": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -9760,7 +9747,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", "dev": true, - "peer": true, "requires": { "undici-types": "~5.26.4" } @@ -9901,7 +9887,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", "dev": true, - "peer": true, "requires": { "@typescript-eslint/scope-manager": "6.9.1", "@typescript-eslint/types": "6.9.1", @@ -10091,9 +10076,9 @@ "dev": true }, "@webgpu/types": { - "version": "0.1.71", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.71.tgz", - "integrity": "sha512-mMy8/ODcKhab808co15eW+yN+HgXoQxRQHTiBV9Mrvl1r0ufnid7YOcI+gi4eUWSWl9ezD6TW2KXccrL8HCh2A==", + "version": "0.1.72", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.72.tgz", + "integrity": "sha512-0cF7RFM2edNoiIS1ODJp0/Gzv4/xSXhwoR0YCza+OWpJWtn4wmo9DvK91aLlH9+uUnwIriP7ZiC3WitmyhuzBw==", "dev": true }, "abbrev": { @@ -10116,8 +10101,7 @@ "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "dev": true, - "peer": true + "dev": true }, "acorn-jsx": { "version": "5.3.2", @@ -10463,7 +10447,6 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, - "peer": true, "requires": { "caniuse-lite": "^1.0.30001541", "electron-to-chromium": "^1.4.535", @@ -11092,7 +11075,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", "dev": true, - "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -12160,7 +12142,6 @@ "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz", "integrity": "sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA==", "dev": true, - "peer": true, "requires": { "dateformat": "~4.6.2", "eventemitter2": "~0.4.13", @@ -12513,7 +12494,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, - "peer": true, "requires": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -12641,7 +12621,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", "dev": true, - "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -14230,8 +14209,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", - "dev": true, - "peer": true + "dev": true }, "prettier-linter-helpers": { "version": "1.0.0", @@ -15307,8 +15285,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "peer": true + "dev": true }, "unbox-primitive": { "version": "1.0.2", diff --git a/package.json b/package.json index 7493b849d253..ec9e302bf79f 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@types/w3c-image-capture": "^1.0.10", "@typescript-eslint/eslint-plugin": "^6.9.1", "@typescript-eslint/parser": "^6.9.1", - "@webgpu/types": "^0.1.71", + "@webgpu/types": "^0.1.72", "ansi-colors": "4.1.3", "babel-plugin-add-header-comment": "^1.0.3", "babel-plugin-const-enum": "^1.2.0", diff --git a/src/webgpu/capability_info.ts b/src/webgpu/capability_info.ts index c68bdd3cf531..017227cec6f5 100644 --- a/src/webgpu/capability_info.ts +++ b/src/webgpu/capability_info.ts @@ -372,8 +372,7 @@ export const kVertexFormatInfo: { 'unorm10-10-10-2': [ 'packed', 'unorm', 4, 4, 'vec4'], 'unorm8x4-bgra': [ 'packed', 'unorm', 4, 4, 'vec4'], 'snorm10-10-10-2': [ 'packed', 'snorm', 4, 4, 'vec4'], - // eslint-disable-next-line @typescript-eslint/no-explicit-any -} as const) as any; +} as const); /** List of all GPUVertexFormat values. */ export const kVertexFormats = keysOf(kVertexFormatInfo); From 2b8d1a846b87f15ab22e861d4a774dbcb637e141 Mon Sep 17 00:00:00 2001 From: wangra Date: Wed, 19 Aug 2026 13:38:11 -0400 Subject: [PATCH 3/3] Address review comments on makeRgb10a2Signed --- .../operation/vertex_state/correctness.spec.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/webgpu/api/operation/vertex_state/correctness.spec.ts b/src/webgpu/api/operation/vertex_state/correctness.spec.ts index 55348c01c7d4..44baebd22926 100644 --- a/src/webgpu/api/operation/vertex_state/correctness.spec.ts +++ b/src/webgpu/api/operation/vertex_state/correctness.spec.ts @@ -73,14 +73,15 @@ function normalizeRgb10a2(rgba: number, index: number): number { function makeRgb10a2Signed(rgba: Array): number { const [r, g, b, a] = rgba; - assert(r >= -512 && r <= 511); - assert(g >= -512 && g <= 511); - assert(b >= -512 && b <= 511); - assert(a >= -2 && a <= 1); - const r_bits = (r < 0 ? r + 1024 : r) & 0x3ff; - const g_bits = (g < 0 ? g + 1024 : g) & 0x3ff; - const b_bits = (b < 0 ? b + 1024 : b) & 0x3ff; - const a_bits = (a < 0 ? a + 4 : a) & 0x3; + // Check the input fits in i32, then check it's in range for i10 or i2. + assert((r | 0) === r && r >= -512 && r <= 511); + assert((g | 0) === g && g >= -512 && g <= 511); + assert((b | 0) === b && b >= -512 && b <= 511); + assert((a | 0) === a && a >= -2 && a <= 1); + const r_bits = r & 0x3ff; + const g_bits = g & 0x3ff; + const b_bits = b & 0x3ff; + const a_bits = a & 0x3; return r_bits | (g_bits << 10) | (b_bits << 20) | (a_bits << 30); }