export const description = ` copyBufferToBuffer tests. Test Plan: * Buffer is valid/invalid - the source buffer is invalid - the destination buffer is invalid * Buffer usages - the source buffer is created without GPUBufferUsage::COPY_SRC - the destination buffer is created without GPUBufferUsage::COPY_DEST * CopySize - copySize is not a multiple of 4 - copySize is 0 * copy offsets - sourceOffset is not a multiple of 4 - destinationOffset is not a multiple of 4 * Arithmetic overflow - (sourceOffset + copySize) is overflow - (destinationOffset + copySize) is overflow * Out of bounds - (sourceOffset + copySize) > size of source buffer - (destinationOffset + copySize) > size of destination buffer * Source buffer and destination buffer are the same buffer `; import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { kBufferUsages } from '../../../../capability_info.js'; import { kResourceStates, AllFeaturesMaxLimitsGPUTest } from '../../../../gpu_test.js'; import { kMaxSafeMultipleOf8 } from '../../../../util/math.js'; import * as vtu from '../../validation_test_utils.js'; class F extends AllFeaturesMaxLimitsGPUTest { testCopyBufferToBuffer(options: { srcBuffer: GPUBuffer; srcOffset: number; dstBuffer: GPUBuffer; dstOffset: number; copySize: number; expectation: 'Success' | 'FinishError' | 'SubmitError'; }): void { const { srcBuffer, srcOffset, dstBuffer, dstOffset, copySize, expectation } = options; const commandEncoder = this.device.createCommandEncoder(); commandEncoder.copyBufferToBuffer(srcBuffer, srcOffset, dstBuffer, dstOffset, copySize); if (expectation === 'FinishError') { this.expectValidationError(() => { commandEncoder.finish(); }); } else { const cmd = commandEncoder.finish(); this.expectValidationError(() => { this.device.queue.submit([cmd]); }, expectation === 'SubmitError'); } } } export const g = makeTestGroup(F); g.test('buffer_state') .params(u => u // .combine('srcBufferState', kResourceStates) .combine('dstBufferState', kResourceStates) ) .fn(t => { const { srcBufferState, dstBufferState } = t.params; const srcBuffer = vtu.createBufferWithState(t, srcBufferState, { size: 16, usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, }); const dstBuffer = vtu.createBufferWithState(t, dstBufferState, { size: 16, usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, }); const shouldFinishError = srcBufferState === 'invalid' || dstBufferState === 'invalid'; const shouldSubmitSuccess = srcBufferState === 'valid' && dstBufferState === 'valid'; const expectation = shouldSubmitSuccess ? 'Success' : shouldFinishError ? 'FinishError' : 'SubmitError'; t.testCopyBufferToBuffer({ srcBuffer, srcOffset: 0, dstBuffer, dstOffset: 0, copySize: 8, expectation, }); }); g.test('buffer,device_mismatch') .desc( 'Tests copyBufferToBuffer cannot be called with src buffer or dst buffer created from another device' ) .paramsSubcasesOnly([ { srcMismatched: false, dstMismatched: false }, // control case { srcMismatched: true, dstMismatched: false }, { srcMismatched: false, dstMismatched: true }, ] as const) .beforeAllSubcases(t => t.usesMismatchedDevice()) .fn(t => { const { srcMismatched, dstMismatched } = t.params; const srcBufferDevice = srcMismatched ? t.mismatchedDevice : t.device; const srcBuffer = t.trackForCleanup( srcBufferDevice.createBuffer({ size: 16, usage: GPUBufferUsage.COPY_SRC, }) ); const dstBufferDevice = dstMismatched ? t.mismatchedDevice : t.device; const dstBuffer = t.trackForCleanup( dstBufferDevice.createBuffer({ size: 16, usage: GPUBufferUsage.COPY_DST, }) ); t.testCopyBufferToBuffer({ srcBuffer, srcOffset: 0, dstBuffer, dstOffset: 0, copySize: 8, expectation: srcMismatched || dstMismatched ? 'FinishError' : 'Success', }); }); g.test('buffer_usage') .paramsSubcasesOnly(u => u // .combine('srcUsage', kBufferUsages) .combine('dstUsage', kBufferUsages) ) .fn(t => { const { srcUsage, dstUsage } = t.params; const srcBuffer = t.createBufferTracked({ size: 16, usage: srcUsage, }); const dstBuffer = t.createBufferTracked({ size: 16, usage: dstUsage, }); const isSuccess = srcUsage === GPUBufferUsage.COPY_SRC && dstUsage === GPUBufferUsage.COPY_DST; const expectation = isSuccess ? 'Success' : 'FinishError'; t.testCopyBufferToBuffer({ srcBuffer, srcOffset: 0, dstBuffer, dstOffset: 0, copySize: 8, expectation, }); }); g.test('copy_size_alignment') .paramsSubcasesOnly([ { copySize: 0, _isSuccess: true }, { copySize: 2, _isSuccess: false }, { copySize: 4, _isSuccess: true }, { copySize: 5, _isSuccess: false }, { copySize: 8, _isSuccess: true }, ] as const) .fn(t => { const { copySize, _isSuccess: isSuccess } = t.params; const srcBuffer = t.createBufferTracked({ size: 16, usage: GPUBufferUsage.COPY_SRC, }); const dstBuffer = t.createBufferTracked({ size: 16, usage: GPUBufferUsage.COPY_DST, }); t.testCopyBufferToBuffer({ srcBuffer, srcOffset: 0, dstBuffer, dstOffset: 0, copySize, expectation: isSuccess ? 'Success' : 'FinishError', }); }); g.test('copy_offset_alignment') .paramsSubcasesOnly([ { srcOffset: 0, dstOffset: 0, _isSuccess: true }, { srcOffset: 2, dstOffset: 0, _isSuccess: false }, { srcOffset: 4, dstOffset: 0, _isSuccess: true }, { srcOffset: 5, dstOffset: 0, _isSuccess: false }, { srcOffset: 8, dstOffset: 0, _isSuccess: true }, { srcOffset: 0, dstOffset: 2, _isSuccess: false }, { srcOffset: 0, dstOffset: 4, _isSuccess: true }, { srcOffset: 0, dstOffset: 5, _isSuccess: false }, { srcOffset: 0, dstOffset: 8, _isSuccess: true }, { srcOffset: 4, dstOffset: 4, _isSuccess: true }, ] as const) .fn(t => { const { srcOffset, dstOffset, _isSuccess: isSuccess } = t.params; const srcBuffer = t.createBufferTracked({ size: 16, usage: GPUBufferUsage.COPY_SRC, }); const dstBuffer = t.createBufferTracked({ size: 16, usage: GPUBufferUsage.COPY_DST, }); t.testCopyBufferToBuffer({ srcBuffer, srcOffset, dstBuffer, dstOffset, copySize: 8, expectation: isSuccess ? 'Success' : 'FinishError', }); }); g.test('copy_overflow') .paramsSubcasesOnly([ { srcOffset: 0, dstOffset: 0, copySize: kMaxSafeMultipleOf8 }, { srcOffset: 16, dstOffset: 0, copySize: kMaxSafeMultipleOf8 }, { srcOffset: 0, dstOffset: 16, copySize: kMaxSafeMultipleOf8 }, { srcOffset: kMaxSafeMultipleOf8, dstOffset: 0, copySize: 16 }, { srcOffset: 0, dstOffset: kMaxSafeMultipleOf8, copySize: 16 }, { srcOffset: kMaxSafeMultipleOf8, dstOffset: 0, copySize: kMaxSafeMultipleOf8 }, { srcOffset: 0, dstOffset: kMaxSafeMultipleOf8, copySize: kMaxSafeMultipleOf8 }, { srcOffset: kMaxSafeMultipleOf8, dstOffset: kMaxSafeMultipleOf8, copySize: kMaxSafeMultipleOf8, }, ] as const) .fn(t => { const { srcOffset, dstOffset, copySize } = t.params; const srcBuffer = t.createBufferTracked({ size: 16, usage: GPUBufferUsage.COPY_SRC, }); const dstBuffer = t.createBufferTracked({ size: 16, usage: GPUBufferUsage.COPY_DST, }); t.testCopyBufferToBuffer({ srcBuffer, srcOffset, dstBuffer, dstOffset, copySize, expectation: 'FinishError', }); }); g.test('copy_out_of_bounds') .paramsSubcasesOnly([ { srcOffset: 0, dstOffset: 0, copySize: 32, _isSuccess: true }, { srcOffset: 0, dstOffset: 0, copySize: 36 }, { srcOffset: 36, dstOffset: 0, copySize: 4 }, { srcOffset: 0, dstOffset: 36, copySize: 4 }, { srcOffset: 36, dstOffset: 0, copySize: 0 }, { srcOffset: 0, dstOffset: 36, copySize: 0 }, { srcOffset: 20, dstOffset: 0, copySize: 16 }, { srcOffset: 20, dstOffset: 0, copySize: 12, _isSuccess: true }, { srcOffset: 0, dstOffset: 20, copySize: 16 }, { srcOffset: 0, dstOffset: 20, copySize: 12, _isSuccess: true }, ] as const) .fn(t => { const { srcOffset, dstOffset, copySize, _isSuccess = false } = t.params; const srcBuffer = t.createBufferTracked({ size: 32, usage: GPUBufferUsage.COPY_SRC, }); const dstBuffer = t.createBufferTracked({ size: 32, usage: GPUBufferUsage.COPY_DST, }); t.testCopyBufferToBuffer({ srcBuffer, srcOffset, dstBuffer, dstOffset, copySize, expectation: _isSuccess ? 'Success' : 'FinishError', }); }); g.test('copy_within_same_buffer') .paramsSubcasesOnly([ { srcOffset: 0, dstOffset: 8, copySize: 4 }, { srcOffset: 8, dstOffset: 0, copySize: 4 }, { srcOffset: 0, dstOffset: 4, copySize: 8 }, { srcOffset: 4, dstOffset: 0, copySize: 8 }, ] as const) .fn(t => { const { srcOffset, dstOffset, copySize } = t.params; const buffer = t.createBufferTracked({ size: 16, usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, }); t.testCopyBufferToBuffer({ srcBuffer: buffer, srcOffset, dstBuffer: buffer, dstOffset, copySize, expectation: 'FinishError', }); });