blob: 951c9ead3cfc53569bbcfa322252edef3b672609 [file] [log] [blame]
/**
* 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.makeShaderModule('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.makeShaderModule('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