export const description = `
Tests writeBuffer validation.
Note: buffer map state is tested in ./buffer_mapped.spec.ts.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import {
kTypedArrayBufferViewConstructors,
TypedArrayBufferView,
TypedArrayBufferViewConstructor,
} from '../../../../common/util/util.js';
import { Float16Array } from '../../../../external/petamoriken/float16/float16.js';
import { GPUConst } from '../../../constants.js';
import { kResourceStates, AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
import * as vtu from '../validation_test_utils.js';
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
g.test('buffer_state')
.desc(
`
Test that the buffer used for GPUQueue.writeBuffer() must be valid. Tests calling writeBuffer
with {valid, invalid, destroyed} buffer.
`
)
.params(u => u.combine('bufferState', kResourceStates))
.fn(t => {
const { bufferState } = t.params;
const buffer = vtu.createBufferWithState(t, bufferState, {
size: 16,
usage: GPUBufferUsage.COPY_DST,
});
const data = new Uint8Array(16);
const _valid = bufferState === 'valid';
t.expectValidationError(() => {
t.device.queue.writeBuffer(buffer, 0, data, 0, data.length);
}, !_valid);
});
g.test('ranges')
.desc(
`
Tests that the data ranges given to GPUQueue.writeBuffer() are properly validated. Tests calling
writeBuffer with both TypedArrays and ArrayBuffers and checks that the data offset and size is
interpreted correctly for both.
- When passing a TypedArray the data offset and size is given in elements.
- When passing an ArrayBuffer the data offset and size is given in bytes.
Also verifies that the specified data range:
- Describes a valid range of the destination buffer and source buffer.
- Fits fully within the destination buffer.
- Has a byte size which is a multiple of 4.
`
)
.fn(t => {
const queue = t.device.queue;
function runTest(ArrayType: TypedArrayBufferViewConstructor, testBuffer: boolean) {
const elementSize = ArrayType.BYTES_PER_ELEMENT;
const bufferSize = 16 * elementSize;
const buffer = t.createBufferTracked({
size: bufferSize,
usage: GPUBufferUsage.COPY_DST,
});
const arraySm: TypedArrayBufferView | ArrayBuffer = testBuffer
? new ArrayType(8).buffer
: new ArrayType(8);
const arrayMd: TypedArrayBufferView | ArrayBuffer = testBuffer
? new ArrayType(16).buffer
: new ArrayType(16);
const arrayLg: TypedArrayBufferView | ArrayBuffer = testBuffer
? new ArrayType(32).buffer
: new ArrayType(32);
if (elementSize < 4) {
const array15: TypedArrayBufferView | ArrayBuffer = testBuffer
? new ArrayType(15).buffer
: new ArrayType(15);
t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, array15));
queue.writeBuffer(buffer, 0, array15, 3);
t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arrayMd, 3));
t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arraySm, 0, 7));
}
queue.writeBuffer(buffer, 0, arraySm);
queue.writeBuffer(buffer, 0, arrayMd);
t.expectValidationError(() => queue.writeBuffer(buffer, 0, arrayLg));
queue.writeBuffer(buffer, 8, arraySm);
t.expectValidationError(() => queue.writeBuffer(buffer, 8, arrayMd));
t.expectValidationError(() => queue.writeBuffer(buffer, 3, arraySm));
queue.writeBuffer(buffer, 0, arraySm, 4);
queue.writeBuffer(buffer, 0, arrayMd, 4);
t.expectValidationError(() => queue.writeBuffer(buffer, 0, arrayLg, 4));
queue.writeBuffer(buffer, 0, arrayLg, 16);
queue.writeBuffer(buffer, 0, arraySm, 4, 4);
t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arraySm, 0, 16));
t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arraySm, 4, 8));
queue.writeBuffer(buffer, 0, arraySm, 3, 4);
queue.writeBuffer(buffer, bufferSize, arraySm, 0, 0);
t.expectValidationError(() => queue.writeBuffer(buffer, bufferSize + 4, arraySm, 0, 0));
queue.writeBuffer(buffer, 0, arraySm, 8, 0);
t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arraySm, 9, 0));
t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arraySm, 9, undefined));
queue.writeBuffer(buffer, 0, arraySm, undefined, 8);
t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arraySm, undefined, 12));
}
runTest(Uint8Array, true);
for (const arrayType of kTypedArrayBufferViewConstructors) {
if (arrayType === Float16Array) {
continue;
}
runTest(arrayType, false);
}
});
g.test('usages')
.desc(
`
Tests calling writeBuffer with the buffer missed COPY_DST usage.
- buffer {with, without} COPY DST usage
`
)
.paramsSubcasesOnly([
{ usage: GPUConst.BufferUsage.COPY_DST, _valid: true },
{ usage: GPUConst.BufferUsage.STORAGE, _valid: false },
{ usage: GPUConst.BufferUsage.STORAGE | GPUConst.BufferUsage.COPY_SRC, _valid: false },
{ usage: GPUConst.BufferUsage.STORAGE | GPUConst.BufferUsage.COPY_DST, _valid: true },
])
.fn(t => {
const { usage, _valid } = t.params;
const buffer = t.createBufferTracked({ size: 16, usage });
const data = new Uint8Array(16);
t.expectValidationError(() => {
t.device.queue.writeBuffer(buffer, 0, data, 0, data.length);
}, !_valid);
});
g.test('buffer,device_mismatch')
.desc('Tests writeBuffer cannot be called with a buffer created from another device.')
.paramsSubcasesOnly(u => u.combine('mismatched', [true, false]))
.beforeAllSubcases(t => t.usesMismatchedDevice())
.fn(t => {
const { mismatched } = t.params;
const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
const buffer = t.trackForCleanup(
sourceDevice.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_DST,
})
);
const data = new Uint8Array(16);
t.expectValidationError(() => {
t.device.queue.writeBuffer(buffer, 0, data, 0, data.length);
}, mismatched);
});