Bug 1595203 [wpt PR 20186] - Update naming for vertex state, a=testonly
authorKai Ninomiya <kainino@chromium.org>
Mon, 25 Nov 2019 16:59:41 +0000
changeset 504462 9839ef573c437b6f4f2550b8514fd36964f525fe
parent 504461 61f7826a9e88fbe609a64276cb3d60f0e49343b5
child 504463 887952235fbc7643c58689c79fd67819976d7be2
push id101897
push userwptsync@mozilla.com
push dateFri, 29 Nov 2019 11:10:32 +0000
treeherderautoland@47be1b3fdda6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1595203, 20186, 1900807, 713682
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1595203 [wpt PR 20186] - Update naming for vertex state, a=testonly Automatic update from web-platform-tests Update naming for vertex state Specification change: https://github.com/gpuweb/gpuweb/pull/469 Dawn roll: https://dawn.googlesource.com/dawn/+log/c3284fa40ec6b12731ed66c2f2a9256ae3fa692e..ae1f25fee85ebf2773b24a0a4f39c160839b1dbb CTS roll: https://github.com/gpuweb/cts/compare/3dc37c83a70667e9a92df773f34d154ec600d203...e114192747a54f34157eb65754e037701fbdf98b TBR: haraken@chromium.org Bug: dawn:22 Change-Id: Ib427760b9fba4b2cc4290de046c4a31d77e0b67a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1900807 Reviewed-by: Austin Eng <enga@chromium.org> Reviewed-by: Yunchao He <yunchao.he@intel.com> Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Kai Ninomiya <kainino@chromium.org> Cr-Commit-Position: refs/heads/master@{#713682} -- wpt-commits: 4831b37a33f002e67ecc5f9f5b41145721e0ab60 wpt-pr: 20186
testing/web-platform/tests/webgpu/cts.html
testing/web-platform/tests/webgpu/framework/fixture.js
testing/web-platform/tests/webgpu/framework/logger.js
testing/web-platform/tests/webgpu/framework/version.js
testing/web-platform/tests/webgpu/runtime/wpt.js
testing/web-platform/tests/webgpu/suites/cts/command_buffer/render/rendering.spec.js
testing/web-platform/tests/webgpu/suites/cts/gpu_test.js
testing/web-platform/tests/webgpu/suites/cts/index.js
testing/web-platform/tests/webgpu/suites/cts/validation/setVertexBuffer.spec.js
testing/web-platform/tests/webgpu/suites/cts/validation/validation_test.js
testing/web-platform/tests/webgpu/suites/cts/validation/vertex_input.spec.js
testing/web-platform/tests/webgpu/suites/cts/validation/vertex_state.spec.js
--- a/testing/web-platform/tests/webgpu/cts.html
+++ b/testing/web-platform/tests/webgpu/cts.html
@@ -34,16 +34,27 @@
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <style>
 #results {
     font-family: monospace;
     width: 100%;
     height: 15em;
 }
+
+/* Test Name column */
+#results > tbody > tr > td:nth-child(2) {
+    word-break: break-word;
+}
+
+/* Message column */
+#results > tbody > tr > td:nth-child(3) {
+    white-space: pre-wrap;
+    word-break: break-word;
+}
 </style>
 
 <textarea id=results></textarea>
 <script type=module src=/webgpu/runtime/wpt.js></script>
 
 <meta name=variant content='?q=cts:buffers/create_mapped:'>
 <meta name=variant content='?q=cts:buffers/map:'>
 <meta name=variant content='?q=cts:buffers/map_detach:'>
@@ -69,9 +80,9 @@
 <meta name=variant content='?q=cts:validation/render_pass:'>
 <meta name=variant content='?q=cts:validation/render_pass_descriptor:'>
 <meta name=variant content='?q=cts:validation/setBindGroup:'>
 <meta name=variant content='?q=cts:validation/setBlendColor:'>
 <meta name=variant content='?q=cts:validation/setScissorRect:'>
 <meta name=variant content='?q=cts:validation/setStencilReference:'>
 <meta name=variant content='?q=cts:validation/setVertexBuffer:'>
 <meta name=variant content='?q=cts:validation/setViewport:'>
-<meta name=variant content='?q=cts:validation/vertex_input:'>
+<meta name=variant content='?q=cts:validation/vertex_state:'>
--- a/testing/web-platform/tests/webgpu/framework/fixture.js
+++ b/testing/web-platform/tests/webgpu/framework/fixture.js
@@ -22,100 +22,101 @@ export class Fixture {
     this.params = params;
   } // This has to be a member function instead of an async `createFixture` function, because
   // we need to be able to ergonomically override it in subclasses.
 
 
   async init() {}
 
   debug(msg) {
-    this.rec.debug(msg);
-  }
-
-  log(msg) {
-    this.rec.log(msg);
+    this.rec.debug(new Error(msg));
   }
 
   skip(msg) {
     throw new SkipTestCase(msg);
   }
 
   async finalize() {
     if (this.numOutstandingAsyncExpectations !== 0) {
       throw new Error('there were outstanding asynchronous expectations (e.g. shouldReject) at the end of the test');
     }
 
     await Promise.all(this.eventualExpectations);
   }
 
   warn(msg) {
-    this.rec.warn(msg);
+    this.rec.warn(new Error(msg));
   }
 
   fail(msg) {
-    this.rec.fail(msg);
+    this.rec.fail(new Error(msg));
   }
 
   async immediateAsyncExpectation(fn) {
     this.numOutstandingAsyncExpectations++;
     const ret = await fn();
     this.numOutstandingAsyncExpectations--;
     return ret;
   }
 
   eventualAsyncExpectation(fn) {
-    const promise = fn();
+    const promise = fn(new Error());
     this.eventualExpectations.push(promise);
     return promise;
   }
 
-  expectErrorValue(expectedName, ex, m) {
+  expectErrorValue(expectedName, ex, niceStack) {
     if (!(ex instanceof Error)) {
-      this.fail('THREW non-error value, of type ' + typeof ex);
+      niceStack.message = 'THREW non-error value, of type ' + typeof ex + niceStack.message;
+      this.rec.fail(niceStack);
       return;
     }
 
     const actualName = ex.name;
 
     if (actualName !== expectedName) {
-      this.fail(`THREW ${actualName}, instead of ${expectedName}${m}`);
+      niceStack.message = `THREW ${actualName}, instead of ${expectedName}` + niceStack.message;
+      this.rec.fail(niceStack);
     } else {
-      this.debug(`OK: threw ${actualName}${m}`);
+      niceStack.message = 'OK: threw ' + actualName + niceStack.message;
+      this.rec.debug(niceStack);
     }
   }
 
   shouldReject(expectedName, p, msg) {
-    this.eventualAsyncExpectation(async () => {
+    this.eventualAsyncExpectation(async niceStack => {
       const m = msg ? ': ' + msg : '';
 
       try {
         await p;
-        this.fail('DID NOT THROW' + m);
+        niceStack.message = 'DID NOT THROW' + m;
+        this.rec.fail(niceStack);
       } catch (ex) {
-        this.expectErrorValue(expectedName, ex, m);
+        niceStack.message = m;
+        this.expectErrorValue(expectedName, ex, niceStack);
       }
     });
   }
 
   shouldThrow(expectedName, fn, msg) {
     const m = msg ? ': ' + msg : '';
 
     try {
       fn();
-      this.fail('DID NOT THROW' + m);
+      this.rec.fail(new Error('DID NOT THROW' + m));
     } catch (ex) {
-      this.expectErrorValue(expectedName, ex, m);
+      this.expectErrorValue(expectedName, ex, new Error(m));
     }
   }
 
   expect(cond, msg) {
     if (cond) {
       const m = msg ? ': ' + msg : '';
-      this.debug('expect OK' + m);
+      this.rec.debug(new Error('expect OK' + m));
     } else {
-      this.rec.fail(msg);
+      this.rec.fail(new Error(msg));
     }
 
     return cond;
   }
 
 }
 //# sourceMappingURL=fixture.js.map
\ No newline at end of file
--- a/testing/web-platform/tests/webgpu/framework/logger.js
+++ b/testing/web-platform/tests/webgpu/framework/logger.js
@@ -4,16 +4,44 @@
 
 function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
 
 import { SkipTestCase } from './fixture.js';
 import { makeQueryString } from './url_query.js';
 import { extractPublicParams } from './url_query.js';
 import { getStackTrace, now } from './util/index.js';
 import { version } from './version.js';
+
+class LogMessageWithStack extends Error {
+  constructor(name, ex) {
+    super(ex.message);
+    this.name = name;
+    this.stack = ex.stack;
+  }
+
+  toJSON() {
+    let m = this.name;
+
+    if (this.message) {
+      m += ': ' + this.message;
+    }
+
+    m += '\n' + getStackTrace(this);
+    return m;
+  }
+
+}
+
+class LogMessageWithoutStack extends LogMessageWithStack {
+  toJSON() {
+    return this.message;
+  }
+
+}
+
 export class Logger {
   constructor() {
     _defineProperty(this, "results", []);
   }
 
   record(spec) {
     const result = {
       spec: makeQueryString(spec),
@@ -89,66 +117,47 @@ export class TestCaseRecorder {
     const endTime = now(); // Round to next microsecond to avoid storing useless .xxxx00000000000002 in results.
 
     this.result.timems = Math.ceil((endTime - this.startTime) * 1000) / 1000;
     this.result.status = PassState[this.state];
     this.result.logs = this.logs;
     this.debugging = false;
   }
 
-  debug(msg) {
+  debug(ex) {
     if (!this.debugging) {
       return;
     }
 
-    this.log('DEBUG: ' + msg);
-  }
-
-  log(msg) {
-    this.logs.push(msg);
+    this.logs.push(new LogMessageWithoutStack('DEBUG', ex));
   }
 
-  warn(msg) {
+  warn(ex) {
     this.setState(PassState.warn);
-    let m = 'WARN';
-
-    if (msg) {
-      m += ': ' + msg;
-    }
-
-    m += ' ' + getStackTrace(new Error());
-    this.log(m);
+    this.logs.push(new LogMessageWithStack('WARN', ex));
   }
 
-  fail(msg) {
+  fail(ex) {
     this.setState(PassState.fail);
-    let m = 'FAIL';
-
-    if (msg) {
-      m += ': ' + msg;
-    }
-
-    m += '\n' + getStackTrace(new Error());
-    this.log(m);
+    this.logs.push(new LogMessageWithStack('FAIL', ex));
   }
 
   skipped(ex) {
     this.setState(PassState.skip);
-    const m = 'SKIPPED: ' + getStackTrace(ex);
-    this.log(m);
+    this.logs.push(new LogMessageWithStack('SKIP', ex));
   }
 
   threw(ex) {
     if (ex instanceof SkipTestCase) {
       this.skipped(ex);
       return;
     }
 
     this.setState(PassState.fail);
-    this.log('EXCEPTION: ' + ex.name + ':\n' + ex.message + '\n' + getStackTrace(ex));
+    this.logs.push(new LogMessageWithStack('EXCEPTION', ex));
   }
 
   setState(state) {
     this.state = Math.max(this.state, state);
   }
 
 }
 //# sourceMappingURL=logger.js.map
\ No newline at end of file
--- a/testing/web-platform/tests/webgpu/framework/version.js
+++ b/testing/web-platform/tests/webgpu/framework/version.js
@@ -1,3 +1,3 @@
 // AUTO-GENERATED - DO NOT EDIT. See tools/gen_version.
 
-export const version = '3dc37c83a70667e9a92df773f34d154ec600d203';
+export const version = 'e114192747a54f34157eb65754e037701fbdf98b';
--- a/testing/web-platform/tests/webgpu/runtime/wpt.js
+++ b/testing/web-platform/tests/webgpu/runtime/wpt.js
@@ -36,17 +36,17 @@ import { TestWorker } from './helper/tes
             t.injectResult(r);
           } else {
             r = await t.run();
           }
 
           this.step(() => {
             // Unfortunately, it seems not possible to surface any logs for warn/skip.
             if (r.status === 'fail') {
-              throw (r.logs || []).join('\n');
+              throw (r.logs || []).map(s => s.toJSON()).join('\n\n');
             }
           });
           this.done();
         });
         running.push(p);
         return p;
       }, name);
     }
--- a/testing/web-platform/tests/webgpu/suites/cts/command_buffer/render/rendering.spec.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/command_buffer/render/rendering.spec.js
@@ -68,17 +68,17 @@ g.test('fullscreen quad', async t => {
     rasterizationState: {
       frontFace: 'ccw'
     },
     colorStates: [{
       format: 'rgba8unorm',
       alphaBlend: {},
       colorBlend: {}
     }],
-    vertexInput: {
+    vertexState: {
       indexFormat: 'uint16',
       vertexBuffers: []
     }
   });
   const encoder = t.device.createCommandEncoder();
   const pass = encoder.beginRenderPass({
     colorAttachments: [{
       attachment: colorAttachmentView,
--- a/testing/web-platform/tests/webgpu/suites/cts/gpu_test.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/gpu_test.js
@@ -107,47 +107,67 @@ export class GPUTest extends Fixture {
     const size = expected.buffer.byteLength;
     const dst = this.device.createBuffer({
       size: expected.buffer.byteLength,
       usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
     });
     const c = this.device.createCommandEncoder();
     c.copyBufferToBuffer(src, 0, dst, 0, size);
     this.queue.submit([c.finish()]);
-    this.eventualAsyncExpectation(async () => {
+    this.eventualAsyncExpectation(async niceStack => {
       const actual = new Uint8Array((await dst.mapReadAsync()));
-      this.expectBuffer(actual, exp);
+      const check = this.checkBuffer(actual, exp);
+
+      if (check !== undefined) {
+        niceStack.message = check;
+        this.rec.fail(niceStack);
+      }
+
       dst.destroy();
     });
   }
 
   expectBuffer(actual, exp) {
+    const check = this.checkBuffer(actual, exp);
+
+    if (check !== undefined) {
+      this.rec.fail(new Error(check));
+    }
+  }
+
+  checkBuffer(actual, exp) {
     const size = exp.byteLength;
 
     if (actual.byteLength !== size) {
-      this.rec.fail('size mismatch');
-      return;
+      return 'size mismatch';
     }
 
+    const lines = [];
     let failedPixels = 0;
 
     for (let i = 0; i < size; ++i) {
       if (actual[i] !== exp[i]) {
         if (failedPixels > 4) {
-          this.rec.fail('... and more');
+          lines.push('... and more');
           break;
         }
 
         failedPixels++;
-        this.rec.fail(`at [${i}], expected ${exp[i]}, got ${actual[i]}`);
+        lines.push(`at [${i}], expected ${exp[i]}, got ${actual[i]}`);
       }
     }
 
     if (size <= 256 && failedPixels > 0) {
       const expHex = Array.from(exp).map(x => x.toString(16).padStart(2, '0')).join('');
       const actHex = Array.from(actual).map(x => x.toString(16).padStart(2, '0')).join('');
-      this.rec.log('EXPECT: ' + expHex);
-      this.rec.log('ACTUAL: ' + actHex);
+      lines.push('EXPECT: ' + expHex);
+      lines.push('ACTUAL: ' + actHex);
     }
+
+    if (failedPixels) {
+      return lines.join('\n');
+    }
+
+    return undefined;
   }
 
 }
 //# sourceMappingURL=gpu_test.js.map
\ No newline at end of file
--- a/testing/web-platform/tests/webgpu/suites/cts/index.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/index.js
@@ -125,12 +125,12 @@ export const listing = [
     "path": "validation/setVertexBuffer",
     "description": "setVertexBuffer validation tests."
   },
   {
     "path": "validation/setViewport",
     "description": "setViewport validation tests."
   },
   {
-    "path": "validation/vertex_input",
-    "description": "vertexInput validation tests."
+    "path": "validation/vertex_state",
+    "description": "vertexState validation tests."
   }
 ];
--- a/testing/web-platform/tests/webgpu/suites/cts/validation/setVertexBuffer.spec.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/validation/setVertexBuffer.spec.js
@@ -24,21 +24,22 @@ class F extends ValidationTest {
     const descriptor = {
       vertexStage: this.getVertexStage(bufferCount),
       fragmentStage: this.getFragmentStage(),
       layout: this.getPipelineLayout(),
       primitiveTopology: 'triangle-list',
       colorStates: [{
         format: 'rgba8unorm'
       }],
-      vertexInput: {
+      vertexState: {
         vertexBuffers: [{
-          stride: 3 * 4,
-          attributeSet: range(bufferCount, i => ({
+          arrayStride: 3 * 4,
+          attributes: range(bufferCount, i => ({
             format: 'float3',
+            offset: 0,
             shaderLocation: i
           }))
         }]
       }
     };
     return this.device.createRenderPipeline(descriptor);
   }
 
--- a/testing/web-platform/tests/webgpu/suites/cts/validation/validation_test.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/validation/validation_test.js
@@ -20,21 +20,23 @@ export class ValidationTest extends GPUT
     if (shouldError === false) {
       fn();
       return;
     }
 
     this.device.pushErrorScope('validation');
     fn();
     const promise = this.device.popErrorScope();
-    this.eventualAsyncExpectation(async () => {
+    this.eventualAsyncExpectation(async niceStack => {
       const gpuValidationError = await promise;
 
       if (!gpuValidationError) {
-        this.fail('Validation error was expected.');
+        niceStack.message = 'Validation error was expected.';
+        this.rec.fail(niceStack);
       } else if (gpuValidationError instanceof GPUValidationError) {
-        this.debug(`Captured validation error - ${gpuValidationError.message}`);
+        niceStack.message = `Captured validation error - ${gpuValidationError.message}`;
+        this.rec.debug(niceStack);
       }
     });
   }
 
 }
 //# sourceMappingURL=validation_test.js.map
\ No newline at end of file
deleted file mode 100644
--- a/testing/web-platform/tests/webgpu/suites/cts/validation/vertex_input.spec.js
+++ /dev/null
@@ -1,623 +0,0 @@
-/**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
-vertexInput validation tests.
-`;
-import { TestGroup } from '../../../framework/index.js';
-import { ValidationTest } from './validation_test.js';
-const MAX_VERTEX_ATTRIBUTES = 16;
-const MAX_VERTEX_BUFFER_END = 2048;
-const MAX_VERTEX_BUFFER_STRIDE = 2048;
-const MAX_VERTEX_BUFFERS = 16;
-const VERTEX_SHADER_CODE_WITH_NO_INPUT = `
-  #version 450
-  void main() {
-    gl_Position = vec4(0.0);
-  }
-`;
-
-function clone(descriptor) {
-  return JSON.parse(JSON.stringify(descriptor));
-}
-
-class F extends ValidationTest {
-  async init() {
-    await Promise.all([super.init(), this.initGLSL()]);
-  }
-
-  getDescriptor(vertexInput, vertexShaderCode) {
-    const descriptor = {
-      vertexStage: this.getVertexStage(vertexShaderCode),
-      fragmentStage: this.getFragmentStage(),
-      layout: this.getPipelineLayout(),
-      primitiveTopology: 'triangle-list',
-      colorStates: [{
-        format: 'rgba8unorm'
-      }],
-      vertexInput
-    };
-    return descriptor;
-  }
-
-  getVertexStage(code) {
-    return {
-      module: this.makeShaderModuleFromGLSL('vertex', code),
-      entryPoint: 'main'
-    };
-  }
-
-  getFragmentStage() {
-    const code = `
-      #version 450
-      layout(location = 0) out vec4 fragColor;
-      void main() {
-        fragColor = vec4(0.0, 1.0, 0.0, 1.0);
-      }
-    `;
-    return {
-      module: this.makeShaderModuleFromGLSL('fragment', code),
-      entryPoint: 'main'
-    };
-  }
-
-  getPipelineLayout() {
-    return this.device.createPipelineLayout({
-      bindGroupLayouts: []
-    });
-  }
-
-}
-
-export const g = new TestGroup(F);
-g.test('an empty vertex input is valid', t => {
-  const vertexInput = {};
-  const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-  t.device.createRenderPipeline(descriptor);
-});
-g.test('a null buffer is valid', t => {
-  {
-    // One null buffer is OK
-    const vertexInput = {
-      vertexBuffers: [{
-        stride: 0,
-        attributeSet: []
-      }]
-    };
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    //  One null buffer followed by a buffer is OK
-    const vertexInput = {
-      vertexBuffers: [{
-        stride: 0,
-        attributeSet: []
-      }, {
-        stride: 0,
-        attributeSet: [{
-          shaderLocation: 0,
-          format: 'float'
-        }]
-      }]
-    };
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    //  One null buffer sitting between buffers is OK
-    const vertexInput = {
-      vertexBuffers: [{
-        stride: 0,
-        attributeSet: [{
-          shaderLocation: 0,
-          format: 'float'
-        }]
-      }, {
-        stride: 0,
-        attributeSet: []
-      }, {
-        stride: 0,
-        attributeSet: [{
-          shaderLocation: 1,
-          format: 'float'
-        }]
-      }]
-    };
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-});
-g.test('pipeline vertex buffers are backed by attributes in vertex input', async t => {
-  const vertexInput = {
-    vertexBuffers: [{
-      stride: 2 * Float32Array.BYTES_PER_ELEMENT,
-      attributeSet: [{
-        shaderLocation: 0,
-        format: 'float'
-      }, {
-        shaderLocation: 1,
-        format: 'float'
-      }]
-    }]
-  };
-  {
-    // 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(vertexInput, code);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Check it is valid for the pipeline to use a subset of the VertexInput
-    const code = `
-      #version 450
-      layout(location = 0) in vec4 a;
-      void main() {
-          gl_Position = vec4(0.0);
-      }
-    `;
-    const descriptor = t.getDescriptor(vertexInput, 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(vertexInput, code);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-});
-g.test('a stride of 0 is valid', t => {
-  const vertexInput = {
-    vertexBuffers: [{
-      stride: 0,
-      attributeSet: [{
-        shaderLocation: 0,
-        format: 'float'
-      }]
-    }]
-  };
-  {
-    // Works ok without attributes
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Works ok with attributes at a large-ish offset
-    vertexInput.vertexBuffers[0].attributeSet[0].offset = 128;
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-});
-g.test('offset should be within vertex buffer stride if stride is not zero', async t => {
-  const vertexInput = {
-    vertexBuffers: [{
-      stride: 2 * Float32Array.BYTES_PER_ELEMENT,
-      attributeSet: [{
-        shaderLocation: 0,
-        format: 'float'
-      }, {
-        offset: Float32Array.BYTES_PER_ELEMENT,
-        shaderLocation: 1,
-        format: 'float'
-      }]
-    }]
-  };
-  {
-    // Control case, setting correct stride and offset
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Test vertex attribute offset exceed vertex buffer stride range
-    const badVertexInput = clone(vertexInput);
-    badVertexInput.vertexBuffers[0].attributeSet[1].format = 'float2';
-    const descriptor = t.getDescriptor(badVertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-  {
-    // Test vertex attribute offset exceed vertex buffer stride range
-    const badVertexInput = clone(vertexInput);
-    badVertexInput.vertexBuffers[0].stride = Float32Array.BYTES_PER_ELEMENT;
-    const descriptor = t.getDescriptor(badVertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-  {
-    // It's OK if stride is zero
-    const goodVertexInput = clone(vertexInput);
-    goodVertexInput.vertexBuffers[0].stride = 0;
-    const descriptor = t.getDescriptor(goodVertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-});
-g.test('check two attributes overlapping', async t => {
-  const vertexInput = {
-    vertexBuffers: [{
-      stride: 2 * Float32Array.BYTES_PER_ELEMENT,
-      attributeSet: [{
-        shaderLocation: 0,
-        format: 'float'
-      }, {
-        offset: Float32Array.BYTES_PER_ELEMENT,
-        shaderLocation: 1,
-        format: 'float'
-      }]
-    }]
-  };
-  {
-    // Control case, setting correct stride and offset
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Test two attributes overlapping
-    const badVertexInput = clone(vertexInput);
-    badVertexInput.vertexBuffers[0].attributeSet[0].format = 'int2';
-    const descriptor = t.getDescriptor(badVertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-});
-g.test('check out of bounds condition on total number of vertex buffers', async t => {
-  const vertexBuffers = [];
-
-  for (let i = 0; i < MAX_VERTEX_BUFFERS; i++) {
-    vertexBuffers.push({
-      stride: 0,
-      attributeSet: [{
-        shaderLocation: i,
-        format: 'float'
-      }]
-    });
-  }
-
-  {
-    // Control case, setting max vertex buffer number
-    const vertexInput = {
-      vertexBuffers
-    };
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Test vertex buffer number exceed the limit
-    const vertexInput = {
-      vertexBuffers: [...vertexBuffers, {
-        stride: 0,
-        attributeSet: [{
-          shaderLocation: MAX_VERTEX_BUFFERS,
-          format: 'float'
-        }]
-      }]
-    };
-    const descriptor = t.getDescriptor(vertexInput, 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', async t => {
-  const vertexAttributes = [];
-
-  for (let i = 0; i < MAX_VERTEX_ATTRIBUTES; i++) {
-    vertexAttributes.push({
-      shaderLocation: i,
-      format: 'float'
-    });
-  }
-
-  {
-    // Control case, setting max vertex buffer number
-    const vertexInput = {
-      vertexBuffers: [{
-        stride: 0,
-        attributeSet: vertexAttributes
-      }]
-    };
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Test vertex attribute number exceed the limit
-    const vertexInput = {
-      vertexBuffers: [{
-        stride: 0,
-        attributeSet: [...vertexAttributes, {
-          shaderLocation: MAX_VERTEX_ATTRIBUTES,
-          format: 'float'
-        }]
-      }]
-    };
-    const descriptor = t.getDescriptor(vertexInput, 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', async t => {
-  const vertexBuffers = [];
-
-  for (let i = 0; i < MAX_VERTEX_ATTRIBUTES; i++) {
-    vertexBuffers.push({
-      stride: 0,
-      attributeSet: [{
-        shaderLocation: i,
-        format: 'float'
-      }]
-    });
-  }
-
-  {
-    // Control case, setting max vertex buffer number
-    const vertexInput = {
-      vertexBuffers
-    };
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Test vertex attribute number exceed the limit
-    vertexBuffers[MAX_VERTEX_ATTRIBUTES - 1].attributeSet.push({
-      shaderLocation: MAX_VERTEX_ATTRIBUTES,
-      format: 'float'
-    });
-    const vertexInput = {
-      vertexBuffers
-    };
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-});
-g.test('check out of bounds condition on input strides', async t => {
-  const vertexInput = {
-    vertexBuffers: [{
-      stride: MAX_VERTEX_BUFFER_STRIDE,
-      attributeSet: []
-    }]
-  };
-  {
-    // Control case, setting max input stride
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Test input stride OOB
-    vertexInput.vertexBuffers[0].stride = MAX_VERTEX_BUFFER_STRIDE + 4;
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-});
-g.test('check multiple of 4 bytes constraint on input stride', async t => {
-  const vertexInput = {
-    vertexBuffers: [{
-      stride: 4,
-      attributeSet: [{
-        shaderLocation: 0,
-        format: 'uchar2'
-      }]
-    }]
-  };
-  {
-    // Control case, setting input stride 4 bytes
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Test input stride not multiple of 4 bytes
-    vertexInput.vertexBuffers[0].stride = 2;
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-});
-g.test('identical duplicate attributes are invalid', async t => {
-  const vertexInput = {
-    vertexBuffers: [{
-      stride: 0,
-      attributeSet: [{
-        shaderLocation: 0,
-        format: 'float'
-      }]
-    }]
-  };
-  {
-    // Control case, setting attribute 0
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Oh no, attribute 0 is set twice
-    vertexInput.vertexBuffers[0].attributeSet.push({
-      shaderLocation: 0,
-      format: 'float'
-    });
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-});
-g.test('we cannot set same shader location', async t => {
-  {
-    const vertexInput = {
-      vertexBuffers: [{
-        stride: 0,
-        attributeSet: [{
-          shaderLocation: 0,
-          format: 'float'
-        }, {
-          offset: Float32Array.BYTES_PER_ELEMENT,
-          shaderLocation: 1,
-          format: 'float'
-        }]
-      }]
-    };
-    {
-      // Control case, setting different shader locations in two attributes
-      const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-      t.device.createRenderPipeline(descriptor);
-    }
-    {
-      // Test same shader location in two attributes in the same buffer
-      vertexInput.vertexBuffers[0].attributeSet[1].shaderLocation = 0;
-      const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-      t.expectValidationError(() => {
-        t.device.createRenderPipeline(descriptor);
-      });
-    }
-  }
-  {
-    const vertexInput = {
-      vertexBuffers: [{
-        stride: 0,
-        attributeSet: [{
-          shaderLocation: 0,
-          format: 'float'
-        }]
-      }, {
-        stride: 0,
-        attributeSet: [{
-          shaderLocation: 0,
-          format: 'float'
-        }]
-      }]
-    }; // Test same shader location in two attributes in different buffers
-
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-});
-g.test('check out of bounds condition on attribute shader location', async t => {
-  const vertexInput = {
-    vertexBuffers: [{
-      stride: 0,
-      attributeSet: [{
-        shaderLocation: MAX_VERTEX_ATTRIBUTES - 1,
-        format: 'float'
-      }]
-    }]
-  };
-  {
-    // Control case, setting last attribute shader location
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Test attribute location OOB
-    vertexInput.vertexBuffers[0].attributeSet[0].shaderLocation = MAX_VERTEX_ATTRIBUTES;
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-});
-g.test('check attribute offset out of bounds', async t => {
-  const vertexInput = {
-    vertexBuffers: [{
-      stride: 0,
-      attributeSet: [{
-        offset: MAX_VERTEX_BUFFER_END - 2 * Float32Array.BYTES_PER_ELEMENT,
-        shaderLocation: 0,
-        format: 'float2'
-      }]
-    }]
-  };
-  {
-    // Control case, setting max attribute offset to MAX_VERTEX_BUFFER_END - 8
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Control case, setting attribute offset to 8
-    vertexInput.vertexBuffers[0].attributeSet[0].offset = 8;
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Test attribute offset out of bounds
-    vertexInput.vertexBuffers[0].attributeSet[0].offset = MAX_VERTEX_BUFFER_END - 4;
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-});
-g.test('check multiple of 4 bytes constraint on offset', async t => {
-  const vertexInput = {
-    vertexBuffers: [{
-      stride: 0,
-      attributeSet: [{
-        offset: Float32Array.BYTES_PER_ELEMENT,
-        shaderLocation: 0,
-        format: 'float'
-      }]
-    }]
-  };
-  {
-    // Control case, setting offset 4 bytes
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Test offset of 2 bytes with uchar2 format
-    vertexInput.vertexBuffers[0].attributeSet[0].offset = 2;
-    vertexInput.vertexBuffers[0].attributeSet[0].format = 'uchar2';
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-  {
-    // Test offset of 2 bytes with float format
-    vertexInput.vertexBuffers[0].attributeSet[0].offset = 2;
-    vertexInput.vertexBuffers[0].attributeSet[0].format = 'float';
-    const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-});
-g.test('check attribute offset overflow', async t => {
-  const vertexInput = {
-    vertexBuffers: [{
-      stride: 0,
-      attributeSet: [{
-        offset: Number.MAX_SAFE_INTEGER,
-        shaderLocation: 0,
-        format: 'float'
-      }]
-    }]
-  };
-  const descriptor = t.getDescriptor(vertexInput, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-  t.expectValidationError(() => {
-    t.device.createRenderPipeline(descriptor);
-  });
-});
-//# sourceMappingURL=vertex_input.spec.js.map
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webgpu/suites/cts/validation/vertex_state.spec.js
@@ -0,0 +1,644 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/
+
+export const description = `
+vertexState validation tests.
+`;
+import { TestGroup } from '../../../framework/index.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 VERTEX_SHADER_CODE_WITH_NO_INPUT = `
+  #version 450
+  void main() {
+    gl_Position = vec4(0.0);
+  }
+`;
+
+function clone(descriptor) {
+  return JSON.parse(JSON.stringify(descriptor));
+}
+
+class F extends ValidationTest {
+  async init() {
+    await Promise.all([super.init(), this.initGLSL()]);
+  }
+
+  getDescriptor(vertexState, vertexShaderCode) {
+    const descriptor = {
+      vertexStage: this.getVertexStage(vertexShaderCode),
+      fragmentStage: this.getFragmentStage(),
+      layout: this.getPipelineLayout(),
+      primitiveTopology: 'triangle-list',
+      colorStates: [{
+        format: 'rgba8unorm'
+      }],
+      vertexState
+    };
+    return descriptor;
+  }
+
+  getVertexStage(code) {
+    return {
+      module: this.makeShaderModuleFromGLSL('vertex', code),
+      entryPoint: 'main'
+    };
+  }
+
+  getFragmentStage() {
+    const code = `
+      #version 450
+      layout(location = 0) out vec4 fragColor;
+      void main() {
+        fragColor = vec4(0.0, 1.0, 0.0, 1.0);
+      }
+    `;
+    return {
+      module: this.makeShaderModuleFromGLSL('fragment', code),
+      entryPoint: 'main'
+    };
+  }
+
+  getPipelineLayout() {
+    return this.device.createPipelineLayout({
+      bindGroupLayouts: []
+    });
+  }
+
+}
+
+export const g = new TestGroup(F);
+g.test('an empty vertex input is valid', 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', 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', async t => {
+  const vertexState = {
+    vertexBuffers: [{
+      arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT,
+      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', 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', async t => {
+  const vertexState = {
+    vertexBuffers: [{
+      arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT,
+      attributes: [{
+        format: 'float',
+        offset: 0,
+        shaderLocation: 0
+      }, {
+        format: 'float',
+        offset: Float32Array.BYTES_PER_ELEMENT,
+        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 = Float32Array.BYTES_PER_ELEMENT;
+    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);
+  }
+});
+g.test('check two attributes overlapping', async t => {
+  const vertexState = {
+    vertexBuffers: [{
+      arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT,
+      attributes: [{
+        format: 'float',
+        offset: 0,
+        shaderLocation: 0
+      }, {
+        format: 'float',
+        offset: Float32Array.BYTES_PER_ELEMENT,
+        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 badVertexState = clone(vertexState);
+    badVertexState.vertexBuffers[0].attributes[0].format = 'int2';
+    const descriptor = t.getDescriptor(badVertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+    t.expectValidationError(() => {
+      t.device.createRenderPipeline(descriptor);
+    });
+  }
+});
+g.test('check out of bounds condition on total number of vertex buffers', 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', 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', 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', 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', 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', 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', async t => {
+  {
+    const vertexState = {
+      vertexBuffers: [{
+        arrayStride: 0,
+        attributes: [{
+          format: 'float',
+          offset: 0,
+          shaderLocation: 0
+        }, {
+          format: 'float',
+          offset: Float32Array.BYTES_PER_ELEMENT,
+          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', 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', async t => {
+  const vertexState = {
+    vertexBuffers: [{
+      arrayStride: 0,
+      attributes: [{
+        format: 'float2',
+        offset: MAX_VERTEX_BUFFER_END - 2 * Float32Array.BYTES_PER_ELEMENT,
+        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', async t => {
+  const vertexState = {
+    vertexBuffers: [{
+      arrayStride: 0,
+      attributes: [{
+        format: 'float',
+        offset: Float32Array.BYTES_PER_ELEMENT,
+        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', 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);
+  });
+});
+//# sourceMappingURL=vertex_state.spec.js.map
\ No newline at end of file