blob: 92e0119242ee4382ab67a999db1d4e92eaa3d425 [file] [log] [blame]
/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/ import { Fixture } from '../common/framework/fixture.js';
import { attemptGarbageCollection } from '../common/framework/util/collect_garbage.js';
import { assert } from '../common/framework/util/util.js';
import { DevicePool, TestOOMedShouldAttemptGC } from './util/device_pool.js';
import { align } from './util/math.js';
import { fillTextureDataWithTexelValue, getTextureCopyLayout } from './util/texture/layout.js';
import { kTexelRepresentationInfo } from './util/texture/texel_data.js';
const devicePool = new DevicePool();
export class GPUTest extends Fixture {
/** Must not be replaced once acquired. */
get device() {
assert(
this.provider !== undefined,
'No provider available right now; did you "await" selectDeviceOrSkipTestCase?'
);
if (!this.acquiredDevice) {
this.acquiredDevice = this.provider.acquire();
}
return this.acquiredDevice;
}
get queue() {
return this.device.defaultQueue;
}
async init() {
await super.init();
this.provider = await devicePool.reserve();
}
/**
* When a GPUTest test accesses `.device` for the first time, a "default" GPUDevice
* (descriptor = `undefined`) is provided by default.
* However, some tests or cases need particular extensions to be enabled. Call this function with
* a descriptor (or undefined) to select a GPUDevice matching that descriptor.
*
* If the request descriptor can't be supported, throws an exception to skip the entire test case.
*/
async selectDeviceOrSkipTestCase(descriptor) {
assert(this.provider !== undefined);
// Make sure the device isn't replaced after it's been retrieved once.
assert(
!this.acquiredDevice,
"Can't selectDeviceOrSkipTestCase() after the device has been used"
);
const oldProvider = this.provider;
this.provider = undefined;
await devicePool.release(oldProvider);
this.provider = await devicePool.reserve(descriptor);
this.acquiredDevice = this.provider.acquire();
}
// Note: finalize is called even if init was unsuccessful.
async finalize() {
await super.finalize();
if (this.provider) {
let threw;
{
const provider = this.provider;
this.provider = undefined;
try {
await devicePool.release(provider);
} catch (ex) {
threw = ex;
}
}
// The GPUDevice and GPUQueue should now have no outstanding references.
if (threw) {
if (threw instanceof TestOOMedShouldAttemptGC) {
// Try to clean up, in case there are stray GPU resources in need of collection.
await attemptGarbageCollection();
}
throw threw;
}
}
}
createCopyForMapRead(src, srcOffset, size) {
assert(srcOffset % 4 === 0);
assert(size % 4 === 0);
const dst = this.device.createBuffer({
size,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
const c = this.device.createCommandEncoder();
c.copyBufferToBuffer(src, srcOffset, dst, 0, size);
this.queue.submit([c.finish()]);
return dst;
}
// TODO: add an expectContents for textures, which logs data: uris on failure
// Offset and size passed to createCopyForMapRead must be divisible by 4. For that
// we might need to copy more bytes from the buffer than we want to map.
// begin and end values represent the part of the copied buffer that stores the contents
// we initially wanted to map.
// The copy will not cause an OOB error because the buffer size must be 4-aligned.
createAlignedCopyForMapRead(src, size, offset) {
const alignedOffset = Math.floor(offset / 4) * 4;
const offsetDifference = offset - alignedOffset;
const alignedSize = align(size + offsetDifference, 4);
const dst = this.createCopyForMapRead(src, alignedOffset, alignedSize);
return { dst, begin: offsetDifference, end: offsetDifference + size };
}
expectContents(src, expected, srcOffset = 0) {
const { dst, begin, end } = this.createAlignedCopyForMapRead(
src,
expected.byteLength,
srcOffset
);
this.eventualAsyncExpectation(async niceStack => {
const constructor = expected.constructor;
await dst.mapAsync(GPUMapMode.READ);
const actual = new constructor(dst.getMappedRange());
const check = this.checkBuffer(actual.subarray(begin, end), expected);
if (check !== undefined) {
niceStack.message = check;
this.rec.expectationFailed(niceStack);
}
dst.destroy();
});
}
// We can expand this function in order to support multiple valid values or two mixed vectors
// if needed. See the discussion at https://github.com/gpuweb/cts/pull/384#discussion_r533101429
expectContentsTwoValidValues(src, expected1, expected2, srcOffset = 0) {
assert(expected1.byteLength === expected2.byteLength);
const { dst, begin, end } = this.createAlignedCopyForMapRead(
src,
expected1.byteLength,
srcOffset
);
this.eventualAsyncExpectation(async niceStack => {
const constructor = expected1.constructor;
await dst.mapAsync(GPUMapMode.READ);
const actual = new constructor(dst.getMappedRange());
const check1 = this.checkBuffer(actual.subarray(begin, end), expected1);
const check2 = this.checkBuffer(actual.subarray(begin, end), expected2);
if (check1 !== undefined && check2 !== undefined) {
niceStack.message = `Expected one of the following two checks to succeed:
- ${check1}
- ${check2}`;
this.rec.expectationFailed(niceStack);
}
dst.destroy();
});
}
expectBuffer(actual, exp) {
const check = this.checkBuffer(actual, exp);
if (check !== undefined) {
this.rec.expectationFailed(new Error(check));
}
}
checkBuffer(actual, exp, tolerance = 0) {
assert(actual.constructor === exp.constructor);
const size = exp.byteLength;
if (actual.byteLength !== size) {
return 'size mismatch';
}
const failedByteIndices = [];
const failedByteExpectedValues = [];
const failedByteActualValues = [];
for (let i = 0; i < size; ++i) {
const tol = typeof tolerance === 'function' ? tolerance(i) : tolerance;
if (Math.abs(actual[i] - exp[i]) > tol) {
if (failedByteIndices.length >= 4) {
failedByteIndices.push('...');
failedByteExpectedValues.push('...');
failedByteActualValues.push('...');
break;
}
failedByteIndices.push(i.toString());
failedByteExpectedValues.push(exp[i].toString());
failedByteActualValues.push(actual[i].toString());
}
}
const summary = `at [${failedByteIndices.join(', ')}], \
expected [${failedByteExpectedValues.join(', ')}], \
got [${failedByteActualValues.join(', ')}]`;
const lines = [summary];
// TODO: Could make a more convenient message, which could look like e.g.:
//
// Starting at offset 48,
// got 22222222 ABCDABCD 99999999
// but expected 22222222 55555555 99999999
//
// or
//
// Starting at offset 0,
// got 00000000 00000000 00000000 00000000 (... more)
// but expected 00FF00FF 00FF00FF 00FF00FF 00FF00FF (... more)
//
// Or, maybe these diffs aren't actually very useful (given we have the prints just above here),
// and we should remove them. More important will be logging of texture data in a visual format.
if (size <= 256 && failedByteIndices.length > 0) {
const expHex = Array.from(new Uint8Array(exp.buffer, exp.byteOffset, exp.byteLength))
.map(x => x.toString(16).padStart(2, '0'))
.join('');
const actHex = Array.from(new Uint8Array(actual.buffer, actual.byteOffset, actual.byteLength))
.map(x => x.toString(16).padStart(2, '0'))
.join('');
lines.push('EXPECT:\t ' + exp.join(' '));
lines.push('\t0x' + expHex);
lines.push('ACTUAL:\t ' + actual.join(' '));
lines.push('\t0x' + actHex);
}
if (failedByteIndices.length) {
return lines.join('\n');
}
return undefined;
}
expectSingleColor(src, format, { size, exp, dimension = '2d', slice = 0, layout }) {
const { byteLength, bytesPerRow, rowsPerImage, mipSize } = getTextureCopyLayout(
format,
dimension,
size,
layout
);
const rep = kTexelRepresentationInfo[format];
const expectedTexelData = rep.pack(rep.encode(exp));
const buffer = this.device.createBuffer({
size: byteLength,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
const commandEncoder = this.device.createCommandEncoder();
commandEncoder.copyTextureToBuffer(
{
texture: src,
mipLevel: layout === null || layout === void 0 ? void 0 : layout.mipLevel,
origin: { x: 0, y: 0, z: slice },
},
{ buffer, bytesPerRow, rowsPerImage },
mipSize
);
this.queue.submit([commandEncoder.finish()]);
const arrayBuffer = new ArrayBuffer(byteLength);
fillTextureDataWithTexelValue(expectedTexelData, format, dimension, arrayBuffer, size, layout);
this.expectContents(buffer, new Uint8Array(arrayBuffer));
}
// TODO: Add check for values of depth/stencil, probably through sampling of shader
// TODO(natashalee): Can refactor this and expectSingleColor to use a similar base expect
expectSinglePixelIn2DTexture(src, format, { x, y }, { exp, slice = 0, layout }) {
const { byteLength, bytesPerRow, rowsPerImage, mipSize } = getTextureCopyLayout(
format,
'2d',
[1, 1, 1],
layout
);
const buffer = this.device.createBuffer({
size: byteLength,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
const commandEncoder = this.device.createCommandEncoder();
commandEncoder.copyTextureToBuffer(
{
texture: src,
mipLevel: layout === null || layout === void 0 ? void 0 : layout.mipLevel,
origin: { x, y, z: slice },
},
{ buffer, bytesPerRow, rowsPerImage },
mipSize
);
this.queue.submit([commandEncoder.finish()]);
this.expectContents(buffer, exp);
}
expectGPUError(filter, fn, shouldError = true) {
// If no error is expected, we let the scope surrounding the test catch it.
if (!shouldError) {
return fn();
}
this.device.pushErrorScope(filter);
const returnValue = fn();
const promise = this.device.popErrorScope();
this.eventualAsyncExpectation(async niceStack => {
const error = await promise;
let failed = false;
switch (filter) {
case 'out-of-memory':
failed = !(error instanceof GPUOutOfMemoryError);
break;
case 'validation':
failed = !(error instanceof GPUValidationError);
break;
}
if (failed) {
niceStack.message = `Expected ${filter} error`;
this.rec.expectationFailed(niceStack);
} else {
niceStack.message = `Captured ${filter} error`;
if (error instanceof GPUValidationError) {
niceStack.message += ` - ${error.message}`;
}
this.rec.debug(niceStack);
}
});
return returnValue;
}
makeBufferWithContents(dataArray, usage) {
const buffer = this.device.createBuffer({
mappedAtCreation: true,
size: dataArray.byteLength,
usage,
});
const mappedBuffer = buffer.getMappedRange();
const constructor = dataArray.constructor;
new constructor(mappedBuffer).set(dataArray);
buffer.unmap();
return buffer;
}
}