'use strict';
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs'), path = require('node:path'), zlib = require('node:zlib');
const { chromium } = require('playwright');
const root = path.resolve(__dirname, '..');
function packagedSources() {
const files = ['utils/native-face.js', 'utils/native-face-cpu.js', 'utils/face-luma.js', 'vendor/native-face/face-api.js', 'vendor/native-face/weights.js'];
for (const name of fs.readdirSync(path.join(root, 'utils'))) if (/^native-face.*(?:webgl|gpu).*\.js$/.test(name)) files.push('utils/' + name);
return Object.fromEntries(files.map(file => [file, fs.readFileSync(path.join(root, file), 'utf8')]));
}
function localFixture() {
const value = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures/native-face-c3.json'), 'utf8'));
return { width: value.width, height: value.height, rgbaBase64: zlib.gunzipSync(Buffer.from(value.gzipBase64, 'base64')).toString('base64') };
}
async function isolatedWebGL(run) {
const browser = await chromium.launch({ headless: true,
args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
const context = await browser.newContext(), network = [];
await context.route('**/*', route => { network.push(route.request().url()); return route.abort(); });
try {
const page = await context.newPage();
await page.setContent('
Offline model WebGL integration');
await page.evaluate(({ sources, fixture }) => {
const cache = {}, calls = { draws: 0, reads: 0 }, objects = {}, createdContexts = [];
function fail(message) { throw Error(message); }
function equal(actual, expected, message) { if (JSON.stringify(actual) !== JSON.stringify(expected)) fail(message + ': ' + JSON.stringify({ actual, expected })); }
function resolve(from, request) {
if (!request.startsWith('.')) fail('No runtime package/network imports: ' + request);
const parts = from.split('/').slice(0, -1).concat(request.split('/')), clean = [];
for (const part of parts) { if (part === '..') clean.pop(); else if (part && part !== '.') clean.push(part); }
return clean.join('/').replace(/(?:\.js)?$/, '.js');
}
function load(file) {
if (cache[file]) return cache[file].exports;
if (!Object.hasOwn(sources, file)) fail('Unapproved runtime module: ' + file);
const module = { exports: {} }; cache[file] = module;
// Model and app modules must not get a DOM or browser environment.
// The Canvas provider below is the only explicit graphics capability.
const hidden = 'var window; var document; var self; var WorkerGlobalScope; var OffscreenCanvas; var fetch; var XMLHttpRequest; var Image; var HTMLCanvasElement; var HTMLImageElement; var ImageData; var Video; var process; var Buffer;\n';
new Function('exports', 'require', 'module', 'my', hidden + sources[file])(
module.exports, request => load(resolve(file, request)), module, platform);
return module.exports;
}
function makeCanvas(options = {}) {
const canvas = document.createElement('canvas'); canvas.width = 32; canvas.height = 32;
const gl = canvas.getContext('webgl', { alpha: false, antialias: false, depth: false, stencil: false,
failIfMajorPerformanceCaveat: false });
if (!gl) fail('Headless WebGL1 is required for this regression');
const resources = {};
for (const type of ['Buffer', 'Texture', 'Framebuffer', 'Shader', 'Program', 'Renderbuffer']) {
resources[type] = new Set();
const create = gl['create' + type].bind(gl), remove = gl['delete' + type].bind(gl);
gl['create' + type] = function () { const value = create.apply(null, arguments); if (value) resources[type].add(value); return value; };
gl['delete' + type] = value => { resources[type].delete(value); return remove(value); };
}
let flipAfterRead = options.emulateNativeFlip === true;
const requestedContexts = [];
canvas.getContext = (kind, attributes) => {
requestedContexts.push({ kind, attributes });
if (options.emulateNativeFlip) flipAfterRead = !(attributes && attributes.enable_flip_y_after_read_pixels === false);
return kind === 'webgl' ? gl : null;
};
const draw = gl.drawElements.bind(gl), drawArrays = gl.drawArrays.bind(gl), read = gl.readPixels.bind(gl), extension = gl.getExtension.bind(gl);
const rawViewport = gl.viewport.bind(gl), rawScissor = gl.scissor.bind(gl), bufferUploads = [];
const upload = gl.texImage2D.bind(gl), bufferUpload = gl.bufferData.bind(gl), shaderStatus = gl.getShaderParameter.bind(gl), programStatus = gl.getProgramParameter.bind(gl);
gl.drawElements = function () { calls.draws++; if (options.drawElementsNoop) return; return draw.apply(null, arguments); };
gl.drawArrays = function () { calls.draws++; if (options.drawArraysNoop) return; return drawArrays.apply(null, arguments); };
gl.viewport = (x, y, width, height) => rawViewport(x, options.nativeViewportFlip ? canvas.height - y - height : y, width, height);
gl.scissor = (x, y, width, height) => rawScissor(x, options.nativeViewportFlip ? canvas.height - y - height : y, width, height);
gl.readPixels = function () {
calls.reads++;
const args = Array.from(arguments), preflight = args[2] === 2 && args[3] === 2;
if (preflight && options.noReadMutation) return;
if (preflight && options.returnOnly) {
args[6] = new Uint8Array(args[6].length); read.apply(null, args);
return options.returnOnly === 'arraybuffer' ? args[6].buffer : options.returnOnly === 'typedarray' ? args[6] : Promise.resolve(args[6]);
}
// A host-side Y conversion based on the canvas height can read
// outside a smaller offscreen framebuffer and produce zeros with
// NO_ERROR. The documented opt-out must reach getContext.
if (flipAfterRead) args[1] = canvas.height - args[1] - args[3];
const result = read.apply(null, args);
// Existing compatibility failures target TF selftests, not the new
// independent 2x2 native preflight.
if (!preflight && (options.corruptReadback || options.corruptFloatReadback && args[5] === gl.FLOAT) && ArrayBuffer.isView(args[6])) args[6].fill(0);
return result;
};
gl.texImage2D = function () {
const args = Array.from(arguments);
if (args.length === 9 && args[3] === 2 && args[4] === 2 && ArrayBuffer.isView(args[8]) &&
(options.byteTextureFailure && args[7] === gl.UNSIGNED_BYTE || options.floatTextureFailure && args[7] === gl.FLOAT)) {
args[8] = new args[8].constructor(args[8].length);
}
return upload.apply(null, args);
};
gl.getShaderParameter = (shader, key) => options.shaderCompileZero && key === gl.COMPILE_STATUS ? 0 : shaderStatus(shader, key);
gl.getProgramParameter = (program, key) => options.programLinkZero && key === gl.LINK_STATUS ? 0 : programStatus(program, key);
gl.bufferData = function (target, data, usage) {
const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : null;
bufferUploads.push({ target, kind: data instanceof ArrayBuffer ? 'arraybuffer' : ArrayBuffer.isView(data) ? 'typedview' : 'other', bytes: bytes ? Array.from(bytes) : [] });
if (options.typedViewUploadFailure && ArrayBuffer.isView(data)) data = new ArrayBuffer(data.byteLength);
if (options.zeroVertexBuffer && target === gl.ARRAY_BUFFER && (data instanceof ArrayBuffer || ArrayBuffer.isView(data))) data = new ArrayBuffer(data.byteLength);
return bufferUpload(target, data, usage);
};
// Exercise the minimal proven GL1 extensions. An advertised extension
// alone cannot replace the module's actual framebuffer/readback test.
gl.getExtension = name => options.missingFloat && name === 'OES_texture_float' ? null
: ['OES_texture_float', 'WEBGL_color_buffer_float', 'WEBGL_lose_context'].includes(name) ? extension(name) : null;
if (options.inheritedCullBlend) {
gl.enable(gl.CULL_FACE); gl.cullFace(gl.FRONT_AND_BACK);
gl.enable(gl.BLEND); gl.blendFunc(gl.ZERO, gl.ZERO);
gl.enable(gl.SCISSOR_TEST); gl.scissor(0, 0, 0, 0);
gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.NEVER);
gl.colorMask(false, false, false, false);
}
const row = { canvas, gl, resources, requestedContexts, bufferUploads, counts: () => Object.fromEntries(Object.entries(resources).map(([type, items]) => [type, items.size])) };
createdContexts.push(row); objects.lastContext = row;
return row;
}
const platform = { createOffscreenCanvas(width, height) {
const options = typeof objects.canvasOptions === 'function' ? objects.canvasOptions(createdContexts.length) : objects.canvasOptions;
const row = makeCanvas(options || {}); row.canvas.width = width; row.canvas.height = height;
return row.canvas;
} };
const bytes = Uint8Array.from(atob(fixture.rgbaBase64), value => value.charCodeAt(0));
window.__nativeFaceGLTest = { load, platform, makeCanvas, calls, objects, createdContexts, fail, equal,
frame: () => ({ width: fixture.width, height: fixture.height, pixelFormat: 'RGBA', data: new Uint8Array(bytes) }) };
}, { sources: packagedSources(), fixture: localFixture() });
const result = await run(page);
assert.deepEqual(network, [], 'all inference and numerical tests stay offline');
return result;
} finally { await context.close(); await browser.close(); }
}
test('packaged WebGL adapter executes numerical shaders without DOM or feature overrides and releases resources before rebuilding', { timeout: 30000 }, async () => {
const result = await isolatedWebGL(page => page.evaluate(async () => {
const t = window.__nativeFaceGLTest, face = t.load('vendor/native-face/face-api.js');
const create = t.load('utils/native-face-webgl.js');
const summaries = [];
t.equal(!!face.tf.findBackendFactory('webgl'), false, 'DOM-free module must not implicitly create a browser GPU backend');
for (let cycle = 0; cycle < 2; cycle++) {
const adapter = create(face, t.platform);
try {
await adapter.initialize(); adapter.check();
const summary = adapter.summary(); summaries.push(summary);
if (summary.selftest !== 'passed' || !summary.matmulDraws || !summary.convDraws || !summary.pixelReads) t.fail('Real shader and typed-array readback verification required');
t.equal(face.tf.getBackend(), 'webgl', 'selected backend');
t.equal(face.tf.env().get('IS_BROWSER'), false, 'model runtime stays DOM-free');
t.equal(face.tf.env().get('IS_NODE'), false, 'model runtime stays non-Node');
t.equal(face.tf.memory().numTensors, 0, 'numerical selftest tensors released');
} finally { adapter.dispose(); }
t.equal(Object.values(t.objects.lastContext.counts()).some(Boolean), false, 'all adapter-owned GL resources explicitly deleted');
t.equal(!!face.tf.findBackendFactory('webgl'), false, 'disposed backend registration removed');
}
return { summaries, draws: t.calls.draws, reads: t.calls.reads };
}));
assert.equal(result.summaries.length, 2); assert(result.draws > 0); assert(result.reads > 0);
});
test('packaged WebGL adapter rejects missing float textures and lost contexts without leaving a backend or resources', { timeout: 30000 }, async () => {
const result = await isolatedWebGL(page => page.evaluate(async () => {
const t = window.__nativeFaceGLTest, face = t.load('vendor/native-face/face-api.js'), create = t.load('utils/native-face-webgl.js');
t.objects.canvasOptions = { missingFloat: true };
const failed = create(face, t.platform);
let code;
try { await failed.initialize(); t.fail('Missing float textures must fail initialization'); }
catch (error) { code = error.code; if (!/^NATIVE_FACE_WEBGL_/.test(code || '')) throw error; }
finally { failed.dispose(); }
t.equal(Object.values(t.objects.lastContext.counts()).some(Boolean), false, 'partial initialization resources released');
t.equal(!!face.tf.findBackendFactory('webgl'), false, 'failed backend removed');
t.objects.canvasOptions = { corruptReadback: true };
const corrupted = create(face, t.platform);
let corruptCode;
try { await corrupted.initialize(); t.fail('Corrupt GPU readback must fail initialization'); }
catch (error) { corruptCode = error.code; if (!/^NATIVE_FACE_WEBGL_/.test(corruptCode || '')) throw error; }
finally { corrupted.dispose(); }
t.equal(Object.values(t.objects.lastContext.counts()).some(Boolean), false, 'corrupt readback resources released');
t.equal(!!face.tf.findBackendFactory('webgl'), false, 'corrupt backend removed');
t.objects.canvasOptions = {};
const adapter = create(face, t.platform);
try {
await adapter.initialize();
const row = t.objects.lastContext;
// Dispatch the standard loss event and verify fail-closed invalidation.
// Keep the software context alive so exact resource deletion can still
// be counted independently of browser context-loss reclamation.
row.canvas.dispatchEvent(new Event('webglcontextlost'));
let lost;
try { adapter.check(); } catch (error) { lost = error.code; }
t.equal(lost, 'NATIVE_FACE_WEBGL_CONTEXT_LOST', 'loss event blocks further inference');
} finally { adapter.dispose(); }
t.equal(Object.values(t.objects.lastContext.counts()).some(Boolean), false, 'context loss cleanup releases resources');
return { missingFloatCode: code, corruptReadbackCode: corruptCode };
}));
assert.match(result.missingFloatCode, /^NATIVE_FACE_WEBGL_/);
assert.match(result.corruptReadbackCode, /^NATIVE_FACE_WEBGL_/);
});
test('real packaged WebGL models share a backend, reject CPU switching and defer GPU deletion until pending inference settles', { timeout: 45000 }, async () => {
const result = await isolatedWebGL(page => page.evaluate(async () => {
const t = window.__nativeFaceGLTest, api = t.load('utils/native-face.js'), face = t.load('vendor/native-face/face-api.js');
const options = { backend: 'webgl', platform: t.platform, inputSize: 160, maxSide: 320 };
const tiny = api.create({ ...options, landmarkModel: 'tiny' }), full = api.create({ ...options, landmarkModel: 'full' });
const info = await tiny.init(); await full.init();
t.equal(info.backend, 'webgl', 'engine must report actual requested backend');
t.equal(info.gpu.selftest, 'passed', 'initialization requires shader numerical tests');
t.equal(info.gpu.stage, 'ready', 'initialization requires model warmup');
t.equal(t.createdContexts.length, 1, 'two model variants share one owned GPU context');
const cpu = api.create({ backend: 'cpu' });
let conflict;
try { await cpu.init(); } catch (error) { conflict = error.code; }
t.equal(conflict, 'NATIVE_FACE_BACKEND_CONFLICT', 'a live GPU session cannot silently switch to CPU'); cpu.dispose();
const baseline = await tiny.detect(t.frame());
if (!baseline.detections.length || baseline.detections[0].landmarks.positions.length !== 68) t.fail('Real Tiny68 model must detect the local fixture');
const originalData = face.tf.Tensor.prototype.data;
let releaseRead, reachedRead;
const enteredRead = new Promise(resolve => { reachedRead = resolve; });
face.tf.Tensor.prototype.data = function () {
face.tf.Tensor.prototype.data = originalData;
const read = originalData.apply(this, arguments); reachedRead();
return new Promise((resolve, reject) => { releaseRead = () => Promise.resolve(read).then(resolve, reject); });
};
const pending = tiny.detect(t.frame());
// Register rejection handlers before releasing a pending shader readback.
const completed = pending.then(() => 'unexpected_success', error => error.code);
await enteredRead;
let busy;
try { await full.detect(t.frame()); } catch (error) { busy = error.code; }
t.equal(busy, 'NATIVE_FACE_BUSY', 'all engines serialize inference');
tiny.dispose(); full.dispose();
if (!Object.values(t.objects.lastContext.counts()).some(Boolean)) t.fail('Pending GPU tensors must not be destroyed early');
const originalDispose = face.tf.Tensor.prototype.dispose, originalNetDispose = face.nets.faceLandmark68Net.dispose;
let throwTensorDispose = true, throwNetDispose = true;
face.tf.Tensor.prototype.dispose = function () {
const value = originalDispose.apply(this, arguments);
// Inject into the engine-owned original RGB input's cleanup, rather than
// unrelated third-party Tensor.data() internals that are still running.
if (throwTensorDispose && this.dtype === 'int32' && JSON.stringify(this.shape) === JSON.stringify([240, 320, 3])) {
throwTensorDispose = false; throw Error('injected post-dispose input tensor failure');
}
return value;
};
face.nets.faceLandmark68Net.dispose = function () {
const value = originalNetDispose.apply(this, arguments);
if (throwNetDispose) { throwNetDispose = false; throw Error('injected post-dispose net failure'); }
return value;
};
releaseRead();
const completionCode = await Promise.race([completed, new Promise((resolve, reject) => setTimeout(() => reject(Error('pending inference cleanup did not settle: ' + JSON.stringify(t.objects.lastContext.counts()))), 10000))]);
t.equal(completionCode, 'NATIVE_FACE_DISPOSED', 'retired inference cannot return a usable result');
face.tf.Tensor.prototype.dispose = originalDispose; face.nets.faceLandmark68Net.dispose = originalNetDispose;
t.equal(throwTensorDispose, false, 'tensor cleanup failure was exercised');
t.equal(throwNetDispose, false, 'model cleanup failure was exercised');
t.equal(face.tf.memory().numTensors, 0, 'model tensors released after pending inference');
t.equal(Object.values(t.objects.lastContext.counts()).some(Boolean), false, 'last owner releases all GL resources');
const next = api.create({ ...options, landmarkModel: 'tiny' });
await next.init();
const detected = await next.detect(t.frame());
t.equal(detected.backend, 'webgl', 'new session rebuilds the GPU backend');
if (!detected.detections.length || detected.detections[0].landmarks.positions.length !== 68) t.fail('Rebuilt backend must run the real model');
next.dispose();
t.equal(face.tf.memory().numTensors, 0, 'rebuilt models released');
for (const context of t.createdContexts) t.equal(Object.values(context.counts()).some(Boolean), false, 'all sessions release GL objects');
const cpuAfter = api.create({ backend: 'cpu', inputSize: 128 }); await cpuAfter.init();
t.equal(cpuAfter.stats().backend, 'cpu', 'CPU can be selected only after all GPU owners have retired'); cpuAfter.dispose();
return { backend: detected.backend, faceCount: detected.faceCount, landmarkCount: detected.detections[0].landmarks.positions.length,
gpuContexts: t.createdContexts.length, shaderDraws: t.calls.draws, pixelReads: t.calls.reads, remainingTensors: face.tf.memory().numTensors };
}));
assert.equal(result.backend, 'webgl'); assert(result.faceCount > 0); assert.equal(result.landmarkCount, 68);
assert.equal(result.gpuContexts, 2); assert.equal(result.remainingTensors, 0);
});
test('byte readback compatibility retries preserve real-model geometry and release each failed context before replacement', { timeout: 45000 }, async () => {
const result = await isolatedWebGL(page => page.evaluate(async () => {
const t = window.__nativeFaceGLTest, api = t.load('utils/native-face.js'), face = t.load('vendor/native-face/face-api.js');
const options = { inputSize: 160, maxSide: 320, landmarkModel: 'tiny' };
const cpu = api.create({ ...options, backend: 'cpu' });
let reference;
try { await cpu.init(); reference = await cpu.detect(t.frame()); } finally { cpu.dispose(); }
t.equal(reference.faceCount, 1, 'reference must exercise an actual detected face');
t.equal(face.tf.memory().numTensors, 0, 'reference backend released before GPU selection');
const results = [];
for (const mode of ['float_only_failure', 'two_failed_contexts']) {
const firstContext = t.createdContexts.length;
t.objects.canvasOptions = index => {
if (index > firstContext) t.equal(Object.values(t.objects.lastContext.counts()).some(Boolean), false, 'failed attempt is fully deleted before next canvas creation');
return mode === 'float_only_failure' ? { corruptFloatReadback: true } : { corruptReadback: index - firstContext < 2 };
};
const engine = api.create({ ...options, backend: 'webgl', platform: t.platform });
let detected, summary;
try {
await engine.init(); summary = engine.stats().gpu;
t.equal(summary.profile, mode === 'float_only_failure' ? 'packed_byte' : 'unpacked_byte', 'first numerically valid profile selected');
t.equal(summary.attemptCount, mode === 'float_only_failure' ? 2 : 3, 'retry count is bounded and accurate');
t.equal(summary.selftest, 'passed', 'original strict matrix and convolution tests passed');
t.equal(summary.floatReads, 0, 'byte path never calls FLOAT readback');
if (!summary.byteReads || !summary.matmulDraws || !summary.convDraws) t.fail('compatible path still executes and reads actual GPU shaders');
for (const operation of ['matmul', 'conv']) {
t.equal(summary[operation].mismatchCount, 0, 'no synthetic numerical mismatches');
t.equal(summary[operation].nonFiniteCount, 0, 'finite synthetic numerical output');
if (!(summary[operation].maxAbsError >= 0 && summary[operation].maxAbsError <= 0.0001)) t.fail('original numerical tolerance must be met');
}
t.equal(face.tf.env().get('WEBGL_DOWNLOAD_FLOAT_ENABLED'), false, 'only download strategy is disabled');
t.equal(face.tf.env().get('WEBGL_PACK'), mode === 'float_only_failure', 'packing preserved until final profile');
detected = await engine.detect(t.frame());
t.equal(detected.backend, 'webgl', 'actual real model uses requested GPU backend');
t.equal(detected.faceCount, reference.faceCount, 'face count matches CPU reference');
const actual = detected.detections[0], expected = reference.detections[0];
t.equal(actual.landmarks.positions.length, 68, 'full landmark geometry returned');
const distances = actual.landmarks.positions.map((point, index) => Math.hypot(point.x - expected.landmarks.positions[index].x, point.y - expected.landmarks.positions[index].y));
const meanError = distances.reduce((sum, value) => sum + value, 0) / 68 / expected.detection.box.width;
const maxError = Math.max(...distances) / expected.detection.box.width;
if (meanError >= 0.005 || maxError >= 0.01) t.fail('GPU landmark geometry diverged from CPU: ' + JSON.stringify({ meanError, maxError }));
for (const key of ['x', 'y', 'width', 'height']) if (Math.abs(actual.detection.box[key] - expected.detection.box[key]) / expected.detection.box.width >= 0.005) t.fail('GPU face box diverged: ' + key);
if (Math.abs(actual.detection.score - expected.detection.score) >= 0.005) t.fail('GPU detector confidence diverged');
results.push({ profile: summary.profile, attemptCount: summary.attemptCount, meanError, maxError });
} finally { engine.dispose(); }
t.equal(face.tf.memory().numTensors, 0, 'all real-model and selftest tensors released');
for (const context of t.createdContexts) t.equal(Object.values(context.counts()).some(Boolean), false, 'every failed and successful context releases all GL object types');
}
return results;
}));
assert.deepEqual(result.map(value => value.profile), ['packed_byte', 'unpacked_byte']);
assert.deepEqual(result.map(value => value.attemptCount), [2, 3]);
});
test('three corrupt compatibility paths stop with fixed-input diagnostics and no model tensors or graphics objects retained', { timeout: 30000 }, async () => {
const result = await isolatedWebGL(page => page.evaluate(async () => {
const t = window.__nativeFaceGLTest, face = t.load('vendor/native-face/face-api.js'), create = t.load('utils/native-face-webgl.js');
t.objects.canvasOptions = index => {
if (index) t.equal(Object.values(t.objects.lastContext.counts()).some(Boolean), false, 'previous failed profile cleaned before retry');
return { corruptReadback: true };
};
const adapter = create(face, t.platform);
let failure;
try { await adapter.initialize(); t.fail('all corrupt profiles must stop initialization'); }
catch (error) { failure = { code: error.code, gpu: error.gpu }; }
finally { adapter.dispose(); }
t.equal(failure.code, 'NATIVE_FACE_WEBGL_MATMUL_FAILED', 'strict numerical failure survives retries');
t.equal(failure.gpu.attemptCount, 3, 'exactly three profiles attempted');
t.equal(failure.gpu.profile, 'unpacked_byte', 'last attempted profile reported');
t.equal(failure.gpu.matmul.actualLength, 256, 'fixed matrix output length retained');
t.equal(failure.gpu.matmul.nonFiniteCount, 0, 'zero output is finite but incorrect');
t.equal(failure.gpu.matmul.actualChecksum, 0, 'corrupted output is observed');
if (!(failure.gpu.matmul.mismatchCount > 0 && failure.gpu.matmul.maxAbsError > 0 && failure.gpu.matmul.expectedChecksum > 0 && failure.gpu.pixelReads > 0 && failure.gpu.byteReads > 0)) t.fail('numeric failure diagnostics must distinguish bad values from no read');
t.equal(face.tf.memory().numTensors, 0, 'selftest tensors released on all failures');
t.equal(!!face.tf.findBackendFactory('webgl'), false, 'failed backend registration removed');
for (const context of t.createdContexts) t.equal(Object.values(context.counts()).some(Boolean), false, 'failed profiles leave no GL resources');
return { code: failure.code, attempts: failure.gpu.attemptCount };
}));
assert.equal(result.code, 'NATIVE_FACE_WEBGL_MATMUL_FAILED'); assert.equal(result.attempts, 3);
});
test('native readback flip is explicitly disabled for small framebuffers before strict preflight and real face inference', { timeout: 30000 }, async () => {
const result = await isolatedWebGL(page => page.evaluate(async () => {
const t = window.__nativeFaceGLTest, api = t.load('utils/native-face.js'), face = t.load('vendor/native-face/face-api.js');
const sample = t.makeCanvas({ emulateNativeFlip: true }), canvas = sample.canvas;
const gl = canvas.getContext('webgl'), texture = gl.createTexture(), framebuffer = gl.createFramebuffer();
const expected = new Uint8Array([17, 31, 63, 255, 71, 89, 101, 255, 127, 149, 173, 255, 191, 211, 229, 255]);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, expected);
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
const wrong = new Uint8Array(16), correct = new Uint8Array(16);
gl.readPixels(0, 0, 2, 2, gl.RGBA, gl.UNSIGNED_BYTE, wrong);
t.equal(Array.from(wrong), Array(16).fill(0), 'native-style canvas-height conversion can silently read zeros outside a 2x2 FBO');
t.equal(gl.getError(), gl.NO_ERROR, 'out-of-bounds zero readback is not diagnosed by getError alone');
canvas.getContext('webgl', { enable_flip_y_after_read_pixels: false });
gl.readPixels(0, 0, 2, 2, gl.RGBA, gl.UNSIGNED_BYTE, correct);
t.equal(Array.from(correct), Array.from(expected), 'documented opt-out retains exact asymmetric row and channel order');
gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.deleteFramebuffer(framebuffer); gl.deleteTexture(texture);
t.equal(Object.values(sample.counts()).some(Boolean), false, 'independent fixture releases its GL objects');
t.objects.canvasOptions = { emulateNativeFlip: true };
const engine = api.create({ backend: 'webgl', platform: t.platform, inputSize: 160, maxSide: 320, landmarkModel: 'tiny' });
let detected, probe;
try {
await engine.init(); probe = engine.stats().gpu.nativeProbe;
t.equal(t.objects.lastContext.requestedContexts[0].attributes.enable_flip_y_after_read_pixels, false, 'adapter passes native opt-out at context creation');
t.equal(probe.stage, 'passed', 'native upload, draw and readback checks passed');
t.equal(probe.code, 'NONE', 'preflight reports actual numerical success');
for (const operation of ['clear', 'constantArrays', 'constantIndexed', 'shader', 'byteTexture', 'floatTexture']) t.equal(probe[operation], 'passed', operation + ' is verified');
detected = await engine.detect(t.frame());
t.equal(detected.backend, 'webgl', 'real model runs after native preflight');
t.equal(detected.faceCount, 1, 'reference face still detected');
t.equal(detected.detections[0].landmarks.positions.length, 68, 'real landmark network runs under native readback emulation');
} finally { engine.dispose(); }
t.equal(face.tf.memory().numTensors, 0, 'real model and preflight leave no tensors');
for (const context of t.createdContexts) t.equal(Object.values(context.counts()).some(Boolean), false, 'all manual and adapter-owned objects released');
return { stage: probe.stage, faceCount: detected.faceCount };
}));
assert.deepEqual(result, { stage: 'passed', faceCount: 1 });
});
test('native preflight failures identify unchanged output, shader/link status zero and byte/float upload faults before backend registration', { timeout: 30000 }, async () => {
const result = await isolatedWebGL(page => page.evaluate(async () => {
const t = window.__nativeFaceGLTest, api = t.load('utils/native-face.js'), face = t.load('vendor/native-face/face-api.js');
const register = face.tf.registerBackend; let registrations = 0;
face.tf.registerBackend = function (name) { if (name === 'webgl') registrations++; return register.apply(this, arguments); };
const cases = [
{ options: { noReadMutation: true }, stage: 'clear', code: 'READ_UNCHANGED' },
{ options: { returnOnly: 'arraybuffer' }, stage: 'clear', code: 'READ_UNCHANGED', readReturn: 'arraybuffer' },
{ options: { returnOnly: 'typedarray' }, stage: 'clear', code: 'READ_UNCHANGED', readReturn: 'typedarray' },
{ options: { returnOnly: 'promise' }, stage: 'clear', code: 'READ_UNCHANGED', readReturn: 'promise' },
{ options: { shaderCompileZero: true }, stage: 'constant_arrays', code: 'SHADER_COMPILE_FAILED' },
{ options: { programLinkZero: true }, stage: 'constant_arrays', code: 'PROGRAM_LINK_FAILED' },
{ options: { zeroVertexBuffer: true }, stage: 'constant_arrays', code: 'VALUE_MISMATCH' },
{ options: { drawElementsNoop: true, drawArraysNoop: true }, stage: 'constant_arrays', code: 'VALUE_MISMATCH' },
{ options: { byteTextureFailure: true }, stage: 'byte_texture', code: 'VALUE_MISMATCH' },
{ options: { floatTextureFailure: true }, stage: 'float_texture', code: 'VALUE_MISMATCH' }
];
const reports = [];
try {
for (const item of cases) {
t.objects.canvasOptions = item.options;
const before = t.createdContexts.length;
const engine = api.create({ backend: 'webgl', platform: t.platform, inputSize: 160, maxSide: 320, landmarkModel: 'tiny' });
let failure, failedStats;
try { await engine.init(); t.fail('broken native preflight must stop initialization'); }
catch (error) { failure = error; failedStats = engine.stats(); }
finally { engine.dispose(); }
t.equal(failure.code, 'NATIVE_FACE_WEBGL_PREFLIGHT_FAILED', 'preflight failure stays distinct from matrix mismatch');
t.equal(failure.gpu.nativeInitialStage, item.stage, 'first failed native operation remains visible after all compatibility strategies');
t.equal(failure.gpu.nativeInitialCode, item.code, 'first native failure category is retained');
t.equal(failure.gpu.nativeProbe.code, item.code, 'safe failure category');
if (item.readReturn) t.equal(failure.gpu.nativeProbe.readReturn, item.readReturn, 'returned data cannot substitute for synchronous TypedArray mutation');
t.equal(failure.gpu.attemptCount, 1, 'native contract failures do not trigger TF profile retries');
t.equal(failure.gpu.nativeAttempts, item.code === 'VALUE_MISMATCH' ? 4 : 1, 'only numerical mismatches permit bounded native compatibility strategies');
t.equal(t.createdContexts.length - before, 1, 'one native canvas is enough to diagnose incompatible contract');
t.equal(registrations, 0, 'TF GPU backend not registered before native checks pass');
t.equal(failedStats.modelsLoaded, false, 'no model weights loaded at failed native preflight before owner disposal');
t.equal(face.tf.memory().numTensors, 0, 'native preflight creates no leaked tensors');
t.equal(Object.values(t.objects.lastContext.counts()).some(Boolean), false, 'failure releases every native probe GL object');
reports.push({ stage: failure.gpu.nativeInitialStage, code: failure.gpu.nativeProbe.code });
}
} finally { face.tf.registerBackend = register; }
return reports;
}));
assert.equal(result.length, 10);
assert.deepEqual(result.map(value => value.stage), ['clear', 'clear', 'clear', 'clear', 'constant_arrays', 'constant_arrays', 'constant_arrays', 'constant_arrays', 'byte_texture', 'float_texture']);
});
test('native strategy selection repairs typed-view uploads and framebuffer viewport/scissor coordinates only after strict tests, with CPU-equivalent real models', { timeout: 60000 }, async () => {
const result = await isolatedWebGL(page => page.evaluate(async () => {
const t = window.__nativeFaceGLTest, api = t.load('utils/native-face.js'), face = t.load('vendor/native-face/face-api.js');
const options = { inputSize: 160, maxSide: 320, landmarkModel: 'tiny' };
const cpu = api.create({ ...options, backend: 'cpu' }); let reference;
try { await cpu.init(); reference = await cpu.detect(t.frame()); } finally { cpu.dispose(); }
t.equal(reference.faceCount, 1, 'CPU reference uses the real fixture');
const cases = [
{ options: { inheritedCullBlend: true }, strategy: 'standard', attempts: 1 },
{ options: { typedViewUploadFailure: true }, strategy: 'buffer_copy', attempts: 2 },
{ options: { nativeViewportFlip: true }, strategy: 'framebuffer_y', attempts: 3 },
{ options: { typedViewUploadFailure: true, nativeViewportFlip: true, inheritedCullBlend: true }, strategy: 'buffer_copy_framebuffer_y', attempts: 4 }
];
const records = [];
for (const item of cases) {
t.objects.canvasOptions = item.options;
const before = t.createdContexts.length;
const engine = api.create({ ...options, backend: 'webgl', platform: t.platform });
try {
await engine.init(); const gpu = engine.stats().gpu;
t.equal(gpu.nativeStrategy, item.strategy, 'the first numerically valid native strategy is selected');
t.equal(gpu.nativeAttempts, item.attempts, 'native retries stop at the first proven strategy');
t.equal(gpu.nativeInitialStage, item.attempts === 1 ? 'not_run' : 'constant_arrays', 'initial drawing failure stays diagnosable after a successful repair');
t.equal(gpu.nativeInitialCode, item.attempts === 1 ? 'NONE' : 'VALUE_MISMATCH', 'success does not overwrite the original native failure');
t.equal(t.createdContexts.length - before, 1, 'native strategy retries reuse one owned GL context');
t.equal(gpu.selftest, 'passed', 'repair must also pass original matrix and convolution tests');
t.equal(gpu.stage, 'ready', 'repair must also complete real model warmup');
for (const operation of ['clear', 'constantArrays', 'constantIndexed', 'shader', 'byteTexture', 'floatTexture']) t.equal(gpu.nativeProbe[operation], 'passed', operation + ' passed under the selected strategy');
const detected = await engine.detect(t.frame());
t.equal(detected.backend, 'webgl', 'repair never falls back to CPU');
t.equal(detected.faceCount, reference.faceCount, 'real model retains CPU face count');
const actual = detected.detections[0], expected = reference.detections[0];
t.equal(actual.landmarks.positions.length, 68, 'real network retains all semantic landmarks');
const distances = actual.landmarks.positions.map((point, index) => Math.hypot(point.x - expected.landmarks.positions[index].x, point.y - expected.landmarks.positions[index].y));
const meanError = distances.reduce((sum, value) => sum + value, 0) / 68 / expected.detection.box.width;
const maxError = Math.max(...distances) / expected.detection.box.width;
if (meanError >= 0.005 || maxError >= 0.01) t.fail('native strategy changed landmark geometry: ' + JSON.stringify({ strategy: item.strategy, meanError, maxError }));
for (const key of ['x', 'y', 'width', 'height']) if (Math.abs(actual.detection.box[key] - expected.detection.box[key]) / expected.detection.box.width >= 0.005) t.fail('native strategy changed face box: ' + key);
if (Math.abs(actual.detection.score - expected.detection.score) >= 0.005) t.fail('native strategy changed detector confidence');
if (item.strategy.indexOf('buffer_copy') >= 0) {
const row = t.objects.lastContext, gl = row.gl, buffer = gl.createBuffer();
try {
const storage = new Uint8Array([199, 198, 1, 2, 3, 4, 5, 6, 197, 196]), view = storage.subarray(2, 8);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, view, gl.STATIC_DRAW);
const payload = row.bufferUploads[row.bufferUploads.length - 1];
t.equal(payload.kind, 'arraybuffer', 'selected upload compatibility uses a tight ArrayBuffer');
t.equal(payload.bytes, [1, 2, 3, 4, 5, 6], 'typed-view byte offset and byte length exclude adjacent backing bytes');
t.equal(gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE), view.byteLength, 'native upload owns exactly the intended bytes');
} finally { gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.deleteBuffer(buffer); }
}
records.push({ strategy: gpu.nativeStrategy, attempts: gpu.nativeAttempts, faceCount: detected.faceCount });
} finally { engine.dispose(); }
t.equal(face.tf.memory().numTensors, 0, 'models and synthetic workloads release all tensors');
for (const context of t.createdContexts) t.equal(Object.values(context.counts()).some(Boolean), false, 'all successful and failed strategy objects are released');
}
return records;
}));
assert.deepEqual(result.map(item => item.strategy), ['standard', 'buffer_copy', 'framebuffer_y', 'buffer_copy_framebuffer_y']);
assert.deepEqual(result.map(item => item.attempts), [1, 2, 3, 4]);
});
test('a failed native indexed quad uses verified strip drawing through strict tensor tests and CPU-equivalent face inference', { timeout: 45000 }, async () => {
const result = await isolatedWebGL(page => page.evaluate(async () => {
const t = window.__nativeFaceGLTest, api = t.load('utils/native-face.js'), face = t.load('vendor/native-face/face-api.js');
const options = { inputSize: 160, maxSide: 320, landmarkModel: 'tiny' };
const cpu = api.create({ ...options, backend: 'cpu' }); let reference;
try { await cpu.init(); reference = await cpu.detect(t.frame()); } finally { cpu.dispose(); }
t.equal(reference.faceCount, 1, 'real CPU reference detected');
t.objects.canvasOptions = { drawElementsNoop: true };
const engine = api.create({ ...options, backend: 'webgl', platform: t.platform });
let summary, detected;
try {
await engine.init(); summary = engine.stats().gpu;
t.equal(summary.nativeStrategy, 'standard', 'working native coordinates and typed uploads retained');
t.equal(summary.nativeAttempts, 2, 'indexed failure is followed directly by its strip alternative');
t.equal(summary.nativeInitialStage, 'constant_indexed', 'original indexed failure is not lost');
t.equal(summary.nativeInitialCode, 'VALUE_MISMATCH', 'original numerical failure retained');
t.equal(summary.drawStrategy, 'quad_strip', 'physical drawing alternative is explicit');
t.equal(summary.nativeProbe.constantIndexed, 'not_run', 'strip success cannot claim raw native indices passed');
t.equal(summary.nativeProbe.constantQuad, 'passed', 'alternative fixed quad actually rendered');
for (const operation of ['clear', 'constantArrays', 'shader', 'byteTexture', 'floatTexture']) t.equal(summary.nativeProbe[operation], 'passed', operation + ' still checked');
for (const operation of ['matmul', 'conv']) {
t.equal(summary[operation].mismatchCount, 0, 'original strict tensor check passed');
t.equal(summary[operation].nonFiniteCount, 0, 'original tensor results finite');
}
if (!summary.matmulDraws || !summary.convDraws || !summary.quadStripDraws) t.fail('actual alternative draws must be observed');
detected = await engine.detect(t.frame());
t.equal(detected.backend, 'webgl', 'no CPU fallback'); t.equal(detected.faceCount, 1, 'actual model detects face');
const actual = detected.detections[0], expected = reference.detections[0];
t.equal(actual.landmarks.positions.length, 68, 'actual model returns all landmarks');
const distances = actual.landmarks.positions.map((point, index) => Math.hypot(point.x - expected.landmarks.positions[index].x, point.y - expected.landmarks.positions[index].y));
const mean = distances.reduce((sum, value) => sum + value, 0) / 68 / expected.detection.box.width;
if (mean >= 0.005 || Math.max(...distances) / expected.detection.box.width >= 0.01) t.fail('strip landmarks differ from CPU reference');
for (const key of ['x', 'y', 'width', 'height']) if (Math.abs(actual.detection.box[key] - expected.detection.box[key]) / expected.detection.box.width >= 0.005) t.fail('strip box differs from CPU reference');
if (Math.abs(actual.detection.score - expected.detection.score) >= 0.005) t.fail('strip confidence differs from CPU reference');
} finally { engine.dispose(); }
t.equal(face.tf.memory().numTensors, 0, 'all models and tensor tests released');
for (const context of t.createdContexts) t.equal(Object.values(context.counts()).some(Boolean), false, 'all indexed/strip attempts release graphics objects');
return { strategy: summary.drawStrategy, faceCount: detected.faceCount };
}));
assert.deepEqual(result, { strategy: 'quad_strip', faceCount: 1 });
});
test('strip conversion rejects unknown signatures and mutated index buffers while respecting exact typed-view bytes', { timeout: 30000 }, async () => {
const result = await isolatedWebGL(page => page.evaluate(async () => {
const t = window.__nativeFaceGLTest, face = t.load('vendor/native-face/face-api.js'), create = t.load('utils/native-face-webgl.js');
t.objects.canvasOptions = { drawElementsNoop: true };
const adapter = create(face, t.platform); await adapter.initialize();
t.equal(adapter.summary().drawStrategy, 'quad_strip', 'guard only enabled on measured fallback');
const gl = t.objects.lastContext.gl, drawArrays = gl.drawArrays, issued = [];
// Tensor/model numerical equivalence is verified separately. This cell
// isolates admission before any native drawing side effect is issued.
gl.drawArrays = function () { issued.push(Array.from(arguments)); };
let rejected = 0;
function rejectDraw(args) {
const count = issued.length; let code;
try { gl.drawElements.apply(gl, args); } catch (error) { code = error.code; }
t.equal(code, 'NATIVE_FACE_WEBGL_DRAW_UNSUPPORTED', 'unverified geometry is never converted');
t.equal(issued.length, count, 'rejected signature does not issue a strip'); rejected++;
}
const buffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
const backing = new Uint16Array([65535, 0, 1, 2, 2, 1, 3, 65535]);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, backing.subarray(1, 7), gl.STATIC_DRAW);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
t.equal(issued[0], [gl.TRIANGLE_STRIP, 0, 4], 'only the tightly bounded known quad is translated');
for (const args of [[gl.LINES, 6, gl.UNSIGNED_SHORT, 0], [gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0],
[gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0], [gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 2]]) rejectDraw(args);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW);
rejectDraw([gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0]);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 12, gl.STATIC_DRAW); rejectDraw([gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0]);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, backing.subarray(1, 7), gl.STATIC_DRAW);
gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, new Uint16Array([0])); rejectDraw([gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0]);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, backing.subarray(1, 7), gl.STATIC_DRAW);
const unknown = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, unknown); rejectDraw([gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0]);
gl.bindBuffer(gl.ARRAY_BUFFER, unknown); gl.bufferData(gl.ARRAY_BUFFER, backing.subarray(1, 7), gl.STATIC_DRAW);
rejectDraw([gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0]);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer); gl.deleteBuffer(buffer); rejectDraw([gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0]);
gl.deleteBuffer(unknown); gl.drawArrays = drawArrays; adapter.dispose();
t.equal(face.tf.memory().numTensors, 0, 'guard checks retain no tensors');
t.equal(Object.values(t.objects.lastContext.counts()).some(Boolean), false, 'guard bookkeeping releases native resources');
return { rejected, issued: issued.length };
}));
assert.deepEqual(result, { rejected: 10, issued: 1 });
});
test('engine GPU initialization failures and cancellation release ownership before a different backend can initialize', { timeout: 30000 }, async () => {
const result = await isolatedWebGL(page => page.evaluate(async () => {
const t = window.__nativeFaceGLTest, api = t.load('utils/native-face.js'), face = t.load('vendor/native-face/face-api.js');
t.objects.canvasOptions = { missingFloat: true };
const failed = api.create({ backend: 'webgl', platform: t.platform });
let failedCode;
try { await failed.init(); } catch (error) { failedCode = error.code; }
if (!/^NATIVE_FACE_WEBGL_/.test(failedCode || '')) t.fail('Unsupported GPU must fail explicitly instead of falling back to CPU');
t.equal(failed.stats().backend, '', 'failed GPU session never reports CPU success');
failed.dispose();
t.equal(face.tf.memory().numTensors, 0, 'failed initialization releases model tensors');
t.equal(Object.values(t.objects.lastContext.counts()).some(Boolean), false, 'failed initialization releases GL objects');
const cpu = api.create({ backend: 'cpu' });
await cpu.init(); t.equal(cpu.stats().backend, 'cpu', 'retired failed GPU session does not retain ownership'); cpu.dispose();
t.objects.canvasOptions = {};
const canceled = api.create({ backend: 'webgl', platform: t.platform });
const pending = canceled.init(); canceled.dispose();
let canceledCode; try { await pending; } catch (error) { canceledCode = error.code; }
t.equal(canceledCode, 'NATIVE_FACE_DISPOSED', 'cancelled queued init settles with disposal');
const after = api.create({ backend: 'cpu' }); await after.init(); after.dispose();
t.equal(face.tf.memory().numTensors, 0, 'backend ownership and tensors reset after cancelled init');
return { failedCode, canceledCode };
}));
assert.match(result.failedCode, /^NATIVE_FACE_WEBGL_/); assert.equal(result.canceledCode, 'NATIVE_FACE_DISPOSED');
});