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