'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'); });