blob: 662a08be192a61a5ff87b12e51afabfb9af9d710 [file] [log] [blame]
export const description = `
Tests for the behavior of ArrayBuffers returned by getMappedRange.
TODO: Add tests that transfer to another thread instead of just using MessageChannel.
TODO: Add tests for any other Web APIs that can detach ArrayBuffers.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { timeout } from '../../../../common/util/timeout.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
import { checkElementsEqual } from '../../../util/check_contents.js';
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
g.test('postMessage')
.desc(
`Using postMessage to send a getMappedRange-returned ArrayBuffer throws a TypeError
if it was included in the transfer list. Otherwise, it makes a copy.
Test combinations of transfer={false, true}, mapMode={read,write}.`
)
.params(u =>
u //
.combine('transfer', [false, true])
.combine('mapMode', ['READ', 'WRITE'] as const)
)
.fn(async t => {
const { transfer, mapMode } = t.params;
const kSize = 1024;
// Populate initial data.
const initialData = new Uint32Array(new ArrayBuffer(kSize));
for (let i = 0; i < initialData.length; ++i) {
initialData[i] = i;
}
const buf = t.makeBufferWithContents(
initialData,
mapMode === 'WRITE' ? GPUBufferUsage.MAP_WRITE : GPUBufferUsage.MAP_READ
);
await buf.mapAsync(GPUMapMode[mapMode]);
const ab1 = buf.getMappedRange();
t.expect(ab1.byteLength === kSize, 'ab1 should have the size of the buffer');
const mc = new MessageChannel();
const ab2Promise = new Promise<ArrayBuffer>(resolve => {
mc.port2.onmessage = ev => {
if (transfer) {
t.fail(
`postMessage with ab1 in transfer list should not be received. Unexpected message: ${ev.data}`
);
} else {
resolve(ev.data);
}
};
});
if (transfer) {
t.shouldThrow('TypeError', () => mc.port1.postMessage(ab1, [ab1]));
// Wait to make sure the postMessage isn't received.
await new Promise(resolve => timeout(resolve, 100));
} else {
mc.port1.postMessage(ab1);
}
t.expect(ab1.byteLength === kSize, 'after postMessage, ab1 should not be detached');
if (!transfer) {
const ab2 = await ab2Promise;
t.expect(ab2.byteLength === kSize, 'ab2 should be the same size');
const ab2Data = new Uint32Array(ab2, 0, initialData.length);
// ab2 should have the same initial contents.
t.expectOK(checkElementsEqual(ab2Data, initialData));
// Mutations to ab2 should not be visible in ab1.
const ab1Data = new Uint32Array(ab1, 0, initialData.length);
const abs2NewData = initialData.slice().reverse();
for (let i = 0; i < ab2Data.length; ++i) {
ab2Data[i] = abs2NewData[i];
}
t.expectOK(checkElementsEqual(ab1Data, initialData));
t.expectOK(checkElementsEqual(ab2Data, abs2NewData));
}
buf.unmap();
t.expect(ab1.byteLength === 0, 'after unmap, ab1 should be detached');
// Transferring an already-detached ArrayBuffer is a DataCloneError.
t.shouldThrow('DataCloneError', () => mc.port1.postMessage(ab1, [ab1]));
});