Files
juzi/taobao/utils/native-face-webgl-probe.js
T
xsl 774376fa16 feat: 完善淘宝小程序拍照、报告与登录流程
接入本地实时识别和自动三连拍,调整报告裁切、商品跳转、分享标题及居中页眉;完善游客会话、后台渠道隔离、部署脚本与回归测试。
2026-11-04 15:07:51 +08:00

157 lines
10 KiB
JavaScript

// Fixed synthetic WebGL1 workloads only. No model, camera frame or face data.
// A smaller FBO than the canvas also verifies native readback coordinates.
module.exports = function nativeWebGLProbe(gl, options) {
var strip = options && options.drawStrategy === 'quad_strip';
var status = { stage: 'not_run', code: 'NONE', clear: 'not_run', constantArrays: 'not_run', constantIndexed: 'not_run',
constantQuad: 'not_run', shader: 'not_run', byteTexture: 'not_run', floatTexture: 'not_run', vertexBufferBytes: 0, indexBufferBytes: 0,
changedBytes: 0, mismatchCount: 0, expectedChecksum: 0, actualChecksum: 0, pixel0: 0, pixel1: 0, pixel2: 0, pixel3: 0,
glError: 0, framebufferStatus: 0, readReturn: 'not_run' };
var resources = [], attributes = [];
function fail(code) { status.code = code; throw new Error('Native WebGL synthetic probe failed'); }
function integer(value) { return typeof value === 'number' && isFinite(value) && value >= 0 && Math.floor(value) === value ? value : 0; }
function own(kind, argument) { var value = kind === 'Shader' ? gl.createShader(argument) : gl['create' + kind]();
if (!value) fail('API_FAILED'); resources.push({ kind: kind, value: value }); return value; }
function inspect() {
var error = gl.getError(), framebuffer = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
status.glError = integer(error); status.framebufferStatus = integer(framebuffer);
if (error !== gl.NO_ERROR) fail('GL_ERROR');
if (framebuffer !== gl.FRAMEBUFFER_COMPLETE) fail('FRAMEBUFFER_INCOMPLETE');
}
function texture(data, type) {
var value = own('Texture'); gl.bindTexture(gl.TEXTURE_2D, value);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, type, data);
return value;
}
function compiled(type, source) {
var shader = own('Shader', type); gl.shaderSource(shader, source); gl.compileShader(shader);
var state = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
// Some native bindings expose GLboolean as 0/1 rather than JS boolean.
if (state !== true && state !== 1) fail('SHADER_COMPILE_FAILED');
return shader;
}
function program(fragment) {
var vertex = compiled(gl.VERTEX_SHADER, 'attribute vec2 position; varying vec2 uv; void main(){uv=(position+1.0)*0.5;gl_Position=vec4(position,0.0,1.0);}');
var shader = compiled(gl.FRAGMENT_SHADER, fragment), value = own('Program');
gl.attachShader(value, vertex); gl.attachShader(value, shader); gl.linkProgram(value);
var state = gl.getProgramParameter(value, gl.LINK_STATUS);
if (state !== true && state !== 1) fail('PROGRAM_LINK_FAILED');
gl.useProgram(value);
var attribute = gl.getAttribLocation(value, 'position');
if (typeof attribute !== 'number' || attribute < 0) fail('API_FAILED');
if (attributes.indexOf(attribute) < 0) attributes.push(attribute);
gl.enableVertexAttribArray(attribute); gl.vertexAttribPointer(attribute, 2, gl.FLOAT, false, 0, 0);
return value;
}
function clearForDraw() { gl.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); }
function drawQuad() {
if (strip) gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
else {
// Rebind immediately before every indexed draw: drawArrays and other
// native host work must not leave this probe relying on stale binding.
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
}
}
function bufferSize(target) {
if (typeof gl.getBufferParameter !== 'function' || typeof gl.BUFFER_SIZE !== 'number') return 0;
// Preserve upload errors, but an unsupported optional metadata query must
// not become a new numerical admission requirement.
var pending = gl.getError();
if (pending !== gl.NO_ERROR) { status.glError = integer(pending); fail('GL_ERROR'); }
try {
var value = gl.getBufferParameter(target, gl.BUFFER_SIZE);
return gl.getError() === gl.NO_ERROR ? integer(value) : 0;
} catch (_) { try { gl.getError(); } catch (_) {} return 0; }
}
function read(expected) {
var pixels = new Uint8Array(16); pixels.fill(165);
var returned = gl.readPixels(0, 0, 2, 2, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
status.readReturn = returned === undefined ? 'undefined' : returned instanceof ArrayBuffer ? 'arraybuffer'
: ArrayBuffer.isView(returned) ? 'typedarray' : returned instanceof Promise ? 'promise' : 'other';
if (returned instanceof Promise) returned.catch(function () {});
status.changedBytes = 0; status.mismatchCount = 0; status.expectedChecksum = 0; status.actualChecksum = 0;
for (var index = 0; index < 16; index++) {
if (pixels[index] !== 165) status.changedBytes++;
if (Math.abs(pixels[index] - expected[index]) > 1) status.mismatchCount++;
status.expectedChecksum += expected[index]; status.actualChecksum += pixels[index];
}
// Big-endian RGBA uint32 values from fixed synthetic input only.
for (var pixel = 0; pixel < 4; pixel++) {
var offset = pixel * 4;
status['pixel' + pixel] = ((pixels[offset] * 256 + pixels[offset + 1]) * 256 + pixels[offset + 2]) * 256 + pixels[offset + 3];
}
// Capture GL state even when all returned values are wrong.
inspect();
if (!status.changedBytes) fail('READ_UNCHANGED');
if (status.mismatchCount) fail('VALUE_MISMATCH');
}
try {
status.stage = 'clear';
var methods = ['createTexture', 'deleteTexture', 'bindTexture', 'texParameteri', 'texImage2D', 'createFramebuffer', 'deleteFramebuffer',
'bindFramebuffer', 'framebufferTexture2D', 'checkFramebufferStatus', 'getError', 'viewport', 'clearColor', 'clear',
'createBuffer', 'deleteBuffer', 'bindBuffer', 'bufferData', 'createShader', 'deleteShader', 'shaderSource', 'compileShader', 'getShaderParameter',
'createProgram', 'deleteProgram', 'attachShader', 'linkProgram', 'getProgramParameter', 'useProgram', 'getAttribLocation',
'enableVertexAttribArray', 'disableVertexAttribArray', 'vertexAttribPointer', 'drawElements', 'drawArrays', 'disable', 'colorMask',
'activeTexture', 'getUniformLocation', 'uniform1i', 'readPixels', 'getExtension'];
for (var index = 0; index < methods.length; index++) if (!gl || typeof gl[methods[index]] !== 'function') fail('API_MISSING');
['BLEND', 'CULL_FACE', 'DEPTH_TEST', 'STENCIL_TEST', 'SCISSOR_TEST', 'DITHER', 'POLYGON_OFFSET_FILL', 'SAMPLE_COVERAGE'].forEach(function (name) {
if (typeof gl[name] === 'number') gl.disable(gl[name]);
});
gl.colorMask(true, true, true, true);
gl.activeTexture(gl.TEXTURE0);
var target = texture(null, gl.UNSIGNED_BYTE), framebuffer = own('Framebuffer');
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target, 0);
gl.bindTexture(gl.TEXTURE_2D, null); gl.viewport(0, 0, 2, 2); inspect();
gl.clearColor(17 / 255, 85 / 255, 153 / 255, 1); gl.clear(gl.COLOR_BUFFER_BIT);
read([17, 85, 153, 255, 17, 85, 153, 255, 17, 85, 153, 255, 17, 85, 153, 255]); status.clear = 'passed';
status.stage = 'constant_arrays';
var vertices = own('Buffer'); gl.bindBuffer(gl.ARRAY_BUFFER, vertices);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
status.vertexBufferBytes = bufferSize(gl.ARRAY_BUFFER);
var indices = own('Buffer'); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 2, 1, 3]), gl.STATIC_DRAW);
status.indexBufferBytes = bufferSize(gl.ELEMENT_ARRAY_BUFFER);
var constant = [31, 127, 223, 255, 31, 127, 223, 255, 31, 127, 223, 255, 31, 127, 223, 255];
program('precision highp float;void main(){gl_FragColor=vec4(31.0/255.0,127.0/255.0,223.0/255.0,1.0);}');
clearForDraw(); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
read(constant); status.constantArrays = 'passed';
status.stage = strip ? 'constant_quad' : 'constant_indexed';
clearForDraw(); drawQuad(); read(constant);
if (strip) status.constantQuad = 'passed';
else status.constantIndexed = 'passed';
status.stage = 'shader';
program('precision highp float;void main(){float n=step(1.0,gl_FragCoord.x)+2.0*step(1.0,gl_FragCoord.y);gl_FragColor=vec4((17.0+34.0*n)/255.0,85.0/255.0,153.0/255.0,1.0);}');
clearForDraw(); drawQuad();
read([17, 85, 153, 255, 51, 85, 153, 255, 85, 85, 153, 255, 119, 85, 153, 255]); status.shader = 'passed';
var expected = [31, 63, 95, 255, 127, 159, 191, 255, 223, 47, 79, 255, 111, 143, 175, 255];
var sampling = 'precision highp float;varying vec2 uv;uniform sampler2D inputTexture;void main(){gl_FragColor=texture2D(inputTexture,uv);}';
status.stage = 'byte_texture';
var sampler = program(sampling); texture(new Uint8Array(expected), gl.UNSIGNED_BYTE);
var location = gl.getUniformLocation(sampler, 'inputTexture'); if (location === null || location === undefined) fail('API_FAILED');
gl.uniform1i(location, 0); clearForDraw(); drawQuad();
read(expected); status.byteTexture = 'passed';
status.stage = 'float_texture';
if (!gl.getExtension('OES_texture_float')) fail('API_MISSING');
var floats = new Float32Array(expected.length); for (var index = 0; index < expected.length; index++) floats[index] = expected[index] / 255;
texture(floats, gl.FLOAT); clearForDraw(); drawQuad();
read(expected); status.floatTexture = 'passed'; status.stage = 'passed';
} catch (_) { if (status.code === 'NONE') status.code = 'API_FAILED'; }
finally {
attributes.forEach(function (attribute) { try { gl.disableVertexAttribArray(attribute); } catch (_) {} });
try { gl.useProgram(null); gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); gl.clearColor(0, 0, 0, 0); } catch (_) {}
for (var index = resources.length - 1; index >= 0; index--) { try { gl['delete' + resources[index].kind](resources[index].value); } catch (_) {} }
}
return status;
};