From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../cts/checkout/src/stress/memory/oom.spec.ts | 178 +++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 dom/webgpu/tests/cts/checkout/src/stress/memory/oom.spec.ts (limited to 'dom/webgpu/tests/cts/checkout/src/stress/memory/oom.spec.ts') diff --git a/dom/webgpu/tests/cts/checkout/src/stress/memory/oom.spec.ts b/dom/webgpu/tests/cts/checkout/src/stress/memory/oom.spec.ts new file mode 100644 index 0000000000..d14486ecee --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/stress/memory/oom.spec.ts @@ -0,0 +1,178 @@ +export const description = ` +Stress tests covering robustness when available VRAM is exhausted. +`; + +import { makeTestGroup } from '../../common/framework/test_group.js'; +import { unreachable } from '../../common/util/util.js'; +import { GPUConst } from '../../webgpu/constants.js'; +import { GPUTest } from '../../webgpu/gpu_test.js'; +import { exhaustVramUntilUnder64MB } from '../../webgpu/util/memory.js'; + +export const g = makeTestGroup(GPUTest); + +function createBufferWithMapState( + device: GPUDevice, + size: number, + mapState: GPUBufferMapState, + mode: GPUMapModeFlags, + mappedAtCreation: boolean +) { + const mappable = mapState === 'unmapped'; + if (!mappable && !mappedAtCreation) { + return device.createBuffer({ + size, + usage: GPUBufferUsage.UNIFORM, + mappedAtCreation, + }); + } + let buffer: GPUBuffer; + switch (mode) { + case GPUMapMode.READ: + buffer = device.createBuffer({ + size, + usage: GPUBufferUsage.MAP_READ, + mappedAtCreation, + }); + break; + case GPUMapMode.WRITE: + buffer = device.createBuffer({ + size, + usage: GPUBufferUsage.MAP_WRITE, + mappedAtCreation, + }); + break; + default: + unreachable(); + } + // If we want the buffer to be mappable and also mappedAtCreation, we call unmap on it now. + if (mappable && mappedAtCreation) { + buffer.unmap(); + } + return buffer; +} + +g.test('vram_oom') + .desc(`Tests that we can allocate buffers until we run out of VRAM.`) + .fn(async t => { + await exhaustVramUntilUnder64MB(t.device); + }); + +g.test('map_after_vram_oom') + .desc( + `Allocates tons of buffers and textures with varying mapping states (unmappable, +mappable, mapAtCreation, mapAtCreation-then-unmapped) until OOM; then attempts +to mapAsync all the mappable objects. The last buffer should be an error buffer so +mapAsync on it should reject and produce a validation error. ` + ) + .params(u => + u + .combine('mapState', ['mapped', 'unmapped'] as GPUBufferMapState[]) + .combine('mode', [GPUConst.MapMode.READ, GPUConst.MapMode.WRITE]) + .combine('mappedAtCreation', [true, false]) + .combine('unmapBeforeResolve', [true, false]) + ) + .fn(async t => { + // Use a relatively large size to quickly hit OOM. + const kSize = 512 * 1024 * 1024; + + const { mapState, mode, mappedAtCreation, unmapBeforeResolve } = t.params; + const mappable = mapState === 'unmapped'; + const buffers: GPUBuffer[] = []; + // Closure to call map and verify results on all of the buffers. + const finish = async () => { + if (mappable) { + await Promise.all(buffers.map(value => value.mapAsync(mode))); + } else { + buffers.forEach(value => { + t.expectValidationError(() => { + void value.mapAsync(mode); + }); + }); + } + // Finally, destroy all the buffers to free the resources. + buffers.forEach(buffer => buffer.destroy()); + }; + + let errorBuffer: GPUBuffer; + for (;;) { + if (mappedAtCreation) { + // When mappedAtCreation is true, OOM can happen on the client which throws a RangeError. In + // this case, we don't do any validations on the OOM buffer. + try { + t.device.pushErrorScope('out-of-memory'); + const buffer = t.trackForCleanup( + createBufferWithMapState(t.device, kSize, mapState, mode, mappedAtCreation) + ); + if (await t.device.popErrorScope()) { + errorBuffer = buffer; + break; + } + buffers.push(buffer); + } catch (ex) { + t.expect(ex instanceof RangeError); + await finish(); + return; + } + } else { + t.device.pushErrorScope('out-of-memory'); + const buffer = t.trackForCleanup( + createBufferWithMapState(t.device, kSize, mapState, mode, mappedAtCreation) + ); + if (await t.device.popErrorScope()) { + errorBuffer = buffer; + break; + } + buffers.push(buffer); + } + } + + // Do some validation on the OOM buffer. + let promise: Promise; + t.expectValidationError(() => { + promise = errorBuffer.mapAsync(mode); + }); + if (unmapBeforeResolve) { + // Should reject with abort error because buffer will be unmapped + // before validation check finishes. + t.shouldReject('AbortError', promise!); + } else { + // Should also reject in addition to the validation error. + t.shouldReject('OperationError', promise!); + + // Wait for validation error before unmap to ensure validation check + // ends before unmap. + try { + await promise!; + throw new Error('The promise should be rejected.'); + } catch { + // Should cause an exception because the promise should be rejected. + } + } + + // Should throw an OperationError because the buffer is not mapped. + // Note: not a RangeError because the state of the buffer is checked first. + t.shouldThrow('OperationError', () => { + errorBuffer.getMappedRange(); + }); + + // Should't be a validation error even if the buffer failed to be mapped. + errorBuffer.unmap(); + errorBuffer.destroy(); + + // Finish the rest of the test w.r.t the mappable buffers. + await finish(); + }); + +g.test('validation_vs_oom') + .desc( + `Tests that calls affected by both OOM and validation errors expose the +validation error with precedence.` + ) + .unimplemented(); + +g.test('recovery') + .desc( + `Tests that after going VRAM-OOM, destroying allocated resources eventually +allows new resources to be allocated.` + ) + .unimplemented(); -- cgit v1.2.3