020a945843
Probes UNMASKED_RENDERER_WEBGL once per page via a throwaway WebGL context; defaults the lava subsystem off on a positive software-renderer match or total WebGL failure; releases the throwaway context via WEBGL_lose_context after reading the renderer string to avoid exhausting the browser's per-page context limit.
138 lines
5.6 KiB
TypeScript
138 lines
5.6 KiB
TypeScript
/**
|
|
* hwAccel classifier tests — the pure software-renderer signature matching and the
|
|
* uncertainty/failure policy that drives the lava default-off decision.
|
|
*
|
|
* These cover the code-PROVABLE half of the feature: given a renderer string (or its absence, or a
|
|
* total WebGL failure), is the browser classified "accelerated" (lava on) or not (lava off)? The
|
|
* impure probe (detectHardwareAcceleration → real getContext) is browser-confirmed, not unit-tested.
|
|
*
|
|
* Same harness convention as decodePressure.test.ts — no test runner in this repo; Node 22+ strips TS
|
|
* types natively. Run a copy from the COMPILED output so the `./hwAccel.js` import specifier resolves:
|
|
*
|
|
* dotnet build DeepDrftPublic/DeepDrftPublic.csproj
|
|
* cp DeepDrftPublic/Interop/visualizer/hwAccel.test.ts DeepDrftPublic/wwwroot/js/visualizer/
|
|
* node DeepDrftPublic/wwwroot/js/visualizer/hwAccel.test.ts
|
|
*
|
|
* A thrown error / non-zero exit signals failure; "ALL <n> TESTS PASSED" signals success.
|
|
* Excluded from the production tsc build via tsconfig `exclude: Interop/ ** /*.test.ts`.
|
|
*/
|
|
|
|
import {
|
|
classifyHardwareAcceleration,
|
|
isSoftwareRenderer,
|
|
SOFTWARE_RENDERER_SIGNATURES,
|
|
} from './hwAccel.js';
|
|
|
|
// --- tiny inline harness (no dependencies) ---------------------------------------------------
|
|
let passed = 0;
|
|
const failures: string[] = [];
|
|
function test(name: string, fn: () => void): void {
|
|
try {
|
|
fn();
|
|
passed++;
|
|
} catch (e) {
|
|
failures.push(`FAIL: ${name}\n ${(e as Error).message}`);
|
|
}
|
|
}
|
|
function assertTrue(actual: boolean, msg?: string): void {
|
|
if (actual !== true) throw new Error(`${msg ?? 'assertTrue'}: expected true, got ${String(actual)}`);
|
|
}
|
|
function assertFalse(actual: boolean, msg?: string): void {
|
|
if (actual !== false) throw new Error(`${msg ?? 'assertFalse'}: expected false, got ${String(actual)}`);
|
|
}
|
|
|
|
// --- isSoftwareRenderer: positive matches -----------------------------------------------------
|
|
|
|
// Real-world software renderer strings, as reported by UNMASKED_RENDERER_WEBGL on accel-off configs.
|
|
const SOFTWARE_STRINGS = [
|
|
'Google SwiftShader',
|
|
'ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (LLVM 10.0.0) (0x0000C0DE)), SwiftShader driver)',
|
|
'llvmpipe (LLVM 12.0.0, 256 bits)',
|
|
'Gallium 0.4 on llvmpipe (LLVM 17.0.6, 256 bits)',
|
|
'softpipe',
|
|
'Microsoft Basic Render Driver',
|
|
'Mesa OffScreen',
|
|
'Software Rasterizer',
|
|
];
|
|
|
|
for (const s of SOFTWARE_STRINGS) {
|
|
test(`isSoftwareRenderer matches software string: "${s}"`, () => {
|
|
assertTrue(isSoftwareRenderer(s), `"${s}" should match a software signature`);
|
|
});
|
|
}
|
|
|
|
// --- isSoftwareRenderer: hardware (GPU) strings must NOT match ---------------------------------
|
|
|
|
const HARDWARE_STRINGS = [
|
|
'ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
|
'ANGLE (Intel, Intel(R) Iris(R) Xe Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
|
'ANGLE (AMD, AMD Radeon RX 6800 XT Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
|
'Apple GPU',
|
|
'Mali-G78',
|
|
'Adreno (TM) 650',
|
|
];
|
|
|
|
for (const s of HARDWARE_STRINGS) {
|
|
test(`isSoftwareRenderer rejects hardware string: "${s}"`, () => {
|
|
assertFalse(isSoftwareRenderer(s), `"${s}" should NOT match any software signature`);
|
|
});
|
|
}
|
|
|
|
// --- case-insensitivity -----------------------------------------------------------------------
|
|
|
|
test('isSoftwareRenderer is case-insensitive', () => {
|
|
assertTrue(isSoftwareRenderer('SWIFTSHADER'), 'upper-case must still match');
|
|
assertTrue(isSoftwareRenderer('LlVmPiPe'), 'mixed-case must still match');
|
|
});
|
|
|
|
test('every declared signature self-matches (sanity on the list)', () => {
|
|
for (const sig of SOFTWARE_RENDERER_SIGNATURES) {
|
|
assertTrue(isSoftwareRenderer(sig), `signature "${sig}" must match itself`);
|
|
}
|
|
});
|
|
|
|
// --- classifyHardwareAcceleration: the full policy --------------------------------------------
|
|
|
|
test('positive software match → NOT accelerated (lava off)', () => {
|
|
assertFalse(
|
|
classifyHardwareAcceleration(true, 'Google SwiftShader'),
|
|
'a working context with a software renderer must classify as not accelerated',
|
|
);
|
|
});
|
|
|
|
test('real GPU renderer → accelerated (lava on)', () => {
|
|
assertTrue(
|
|
classifyHardwareAcceleration(true, 'ANGLE (NVIDIA GeForce RTX 3080, D3D11)'),
|
|
'a working context with a GPU renderer must classify as accelerated',
|
|
);
|
|
});
|
|
|
|
// Uncertainty / default-on case: context works but the renderer string is masked or absent.
|
|
test('masked renderer (null) with a working context → accelerated (default on)', () => {
|
|
assertTrue(
|
|
classifyHardwareAcceleration(true, null),
|
|
'an unknown renderer must favor the HW-accel majority',
|
|
);
|
|
});
|
|
|
|
test('empty/whitespace renderer with a working context → accelerated (default on)', () => {
|
|
assertTrue(classifyHardwareAcceleration(true, ''), 'empty string is unknown, not software');
|
|
assertTrue(classifyHardwareAcceleration(true, ' '), 'whitespace is unknown, not software');
|
|
});
|
|
|
|
// Total-WebGL-failure case: no context at all → lava can't run → not accelerated.
|
|
test('no WebGL context at all → NOT accelerated (lava off), regardless of renderer arg', () => {
|
|
assertFalse(classifyHardwareAcceleration(false, null), 'no context → lava off');
|
|
assertFalse(
|
|
classifyHardwareAcceleration(false, 'ANGLE (NVIDIA GeForce RTX 3080, D3D11)'),
|
|
'no context dominates even a GPU-looking string',
|
|
);
|
|
});
|
|
|
|
// --- report ----------------------------------------------------------------------------------
|
|
if (failures.length > 0) {
|
|
console.error(failures.join('\n'));
|
|
throw new Error(`${failures.length} test(s) failed, ${passed} passed`);
|
|
}
|
|
console.log(`ALL ${passed} TESTS PASSED`);
|