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(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); });