blob: aac5237debe69988e8a5130983d58e1287b83a5d [file] [log] [blame]
export const description = 'Operation tests for GPUQueue.writeBuffer()';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { memcpy, range } from '../../../../common/util/util.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
import { align } from '../../../util/math.js';
const kTypedArrays = [
'Uint8Array',
'Uint16Array',
'Uint32Array',
'Int8Array',
'Int16Array',
'Int32Array',
'Float32Array',
'Float64Array',
] as const;
type WriteBufferSignature = {
bufferOffset: number;
data: readonly number[];
arrayType: (typeof kTypedArrays)[number];
useArrayBuffer: boolean;
dataOffset?: number; // In elements when useArrayBuffer === false, bytes otherwise
dataSize?: number; // In elements when useArrayBuffer === false, bytes otherwise
};
class F extends AllFeaturesMaxLimitsGPUTest {
calculateRequiredBufferSize(writes: WriteBufferSignature[]): number {
let bufferSize = 0;
// Calculate size of final buffer
for (const { bufferOffset, data, arrayType, useArrayBuffer, dataOffset, dataSize } of writes) {
const TypedArrayConstructor = globalThis[arrayType];
// When passing data as an ArrayBuffer, dataOffset and dataSize use byte instead of number of
// elements. bytesPerElement is used to convert dataOffset and dataSize from elements to bytes
// when useArrayBuffer === false.
const bytesPerElement = useArrayBuffer ? 1 : TypedArrayConstructor.BYTES_PER_ELEMENT;
// Calculate the number of bytes written to the buffer. data is always an array of elements.
let bytesWritten =
data.length * TypedArrayConstructor.BYTES_PER_ELEMENT - (dataOffset || 0) * bytesPerElement;
if (dataSize) {
// When defined, dataSize clamps the number of bytes written
bytesWritten = Math.min(bytesWritten, dataSize * bytesPerElement);
}
// The minimum buffer size required for the write to succeed is the number of bytes written +
// the bufferOffset
const requiredBufferSize = bufferOffset + bytesWritten;
// Find the largest required size by all writes
bufferSize = Math.max(bufferSize, requiredBufferSize);
}
// writeBuffer requires buffers to be a multiple of 4
return align(bufferSize, 4);
}
testWriteBuffer(...writes: WriteBufferSignature[]) {
const bufferSize = this.calculateRequiredBufferSize(writes);
// Initialize buffer to non-zero data (0xff) for easier debug.
const expectedData = new Uint8Array(bufferSize).fill(0xff);
const buffer = this.makeBufferWithContents(
expectedData,
GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
);
for (const { bufferOffset, data, arrayType, useArrayBuffer, dataOffset, dataSize } of writes) {
const TypedArrayConstructor = globalThis[arrayType];
const writeData = new TypedArrayConstructor(data);
const writeSrc = useArrayBuffer ? writeData.buffer : writeData;
this.queue.writeBuffer(buffer, bufferOffset, writeSrc, dataOffset, dataSize);
memcpy(
{ src: writeSrc, start: dataOffset, length: dataSize },
{ dst: expectedData, start: bufferOffset }
);
}
this.debug(`expectedData: [${expectedData.join(', ')}]`);
this.expectGPUBufferValuesEqual(buffer, expectedData);
}
}
export const g = makeTestGroup(F);
const kTestData = range<number>(16, i => i);
g.test('array_types')
.desc('Tests that writeBuffer correctly handles different TypedArrays and ArrayBuffer.')
.params(u =>
u //
.combine('arrayType', kTypedArrays)
.combine('useArrayBuffer', [false, true])
)
.fn(t => {
const { arrayType, useArrayBuffer } = t.params;
const dataOffset = 1;
const dataSize = 8;
t.testWriteBuffer({
bufferOffset: 0,
arrayType,
data: kTestData,
dataOffset,
dataSize,
useArrayBuffer,
});
});
g.test('multiple_writes_at_different_offsets_and_sizes')
.desc(
`
Tests that writeBuffer currently handles different offsets and writes. This includes:
- Non-overlapping TypedArrays and ArrayLists
- Overlapping TypedArrays and ArrayLists
- Writing zero data
- Writing on zero sized buffers
- Unaligned source
- Multiple overlapping writes with decreasing sizes
`
)
.paramsSubcasesOnly([
{
// Concatenate 2 Uint32Arrays
writes: [
{
bufferOffset: 0,
data: kTestData,
arrayType: 'Uint32Array',
useArrayBuffer: false,
dataOffset: 2,
dataSize: 2,
}, // [2, 3]
{
bufferOffset: 2 * Uint32Array.BYTES_PER_ELEMENT,
data: kTestData,
arrayType: 'Uint32Array',
useArrayBuffer: false,
dataOffset: 0,
dataSize: 2,
}, // [0, 1]
], // Expected [2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
},
{
// Concatenate 2 Uint8Arrays
writes: [
{ bufferOffset: 0, data: [0, 1, 2, 3], arrayType: 'Uint8Array', useArrayBuffer: false },
{ bufferOffset: 4, data: [4, 5, 6, 7], arrayType: 'Uint8Array', useArrayBuffer: false },
], // Expected [0, 1, 2, 3, 4, 5, 6, 7]
},
{
// Overlap in the middle
writes: [
{ bufferOffset: 0, data: kTestData, arrayType: 'Uint8Array', useArrayBuffer: false },
{ bufferOffset: 4, data: [0], arrayType: 'Uint32Array', useArrayBuffer: false },
], // Expected [0, 1, 2, 3, 0, 0 ,0 ,0, 8, 9, 10, 11, 12, 13, 14, 15]
},
{
// Overlapping arrayLists
writes: [
{
bufferOffset: 0,
data: kTestData,
arrayType: 'Uint32Array',
useArrayBuffer: true,
dataOffset: 2,
dataSize: 4 * Uint32Array.BYTES_PER_ELEMENT,
},
{ bufferOffset: 4, data: [0x04030201], arrayType: 'Uint32Array', useArrayBuffer: true },
], // Expected [0, 0, 1, 0, 1, 2, 3, 4, 0, 0, 3, 0, 0, 0, 4, 0]
},
{
// Write over with empty buffer
writes: [
{ bufferOffset: 0, data: kTestData, arrayType: 'Uint8Array', useArrayBuffer: false },
{ bufferOffset: 0, data: [], arrayType: 'Uint8Array', useArrayBuffer: false },
], // Expected [0, 1, 2, 3, 4, 5 ,6 ,7, 8, 9, 10, 11, 12, 13, 14, 15]
},
{
// Zero buffer
writes: [{ bufferOffset: 0, data: [], arrayType: 'Uint8Array', useArrayBuffer: false }],
}, // Expected []
{
// Unaligned source
writes: [
{
bufferOffset: 0,
data: [0x77, ...kTestData],
arrayType: 'Uint8Array',
useArrayBuffer: false,
dataOffset: 1,
},
], // Expected [0, 1, 2, 3, 4, 5 ,6 ,7, 8, 9, 10, 11, 12, 13, 14, 15]
},
{
// Multiple overlapping writes
writes: [
{
bufferOffset: 0,
data: [0x05050505, 0x05050505, 0x05050505, 0x05050505, 0x05050505],
arrayType: 'Uint32Array',
useArrayBuffer: false,
},
{
bufferOffset: 0,
data: [0x04040404, 0x04040404, 0x04040404, 0x04040404],
arrayType: 'Uint32Array',
useArrayBuffer: false,
},
{
bufferOffset: 0,
data: [0x03030303, 0x03030303, 0x03030303],
arrayType: 'Uint32Array',
useArrayBuffer: false,
},
{
bufferOffset: 0,
data: [0x02020202, 0x02020202],
arrayType: 'Uint32Array',
useArrayBuffer: false,
},
{
bufferOffset: 0,
data: [0x01010101],
arrayType: 'Uint32Array',
useArrayBuffer: false,
},
], // Expected [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5]
},
] as const)
.fn(t => {
t.testWriteBuffer(...t.params.writes);
});