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