import type { Worker } from '@playwright/test'; export async function installNativeGLTrace(worker: Worker, finishBeforeCompute = false) { await worker.evaluate(finishBeforeCompute => { const root = self as any, proto = WebGL2RenderingContext.prototype as any, events: any[] = []; const ids = new WeakMap(), current = new WeakMap(), uniforms = new WeakMap(); let next = 1, frozen = false; const id = (value: object | null) => { if(!value)return 0; if(!ids.has(value))ids.set(value, next++); return ids.get(value)!; }; root.__nativeGLTrace = events; if(finishBeforeCompute){const post = MessagePort.prototype.postMessage; let sequence = 0; MessagePort.prototype.postMessage = function(this: MessagePort, envelope: any, ...rest: any[]) { if(envelope?.request?.job && root.__wbv2LastGL){const current = ++sequence; console.info(`WBV2_GL_FINISH_BEGIN ${current}`); root.__wbv2LastGL.finish(); console.info(`WBV2_GL_FINISH_DONE ${current}`);} return Reflect.apply(post, this, [envelope, ...rest]); }; } const activeUniform = proto.getActiveUniform, uniformLocation = proto.getUniformLocation; const drawState = (gl: any) => { const program = gl.getParameter(gl.CURRENT_PROGRAM), result: { program: number; blocks: any[]; samplers: any[] } = { program: id(program), blocks: [], samplers: [] }; if(!program)return result; for(let i = 0; i < Math.min(16, gl.getProgramParameter(program, gl.ACTIVE_UNIFORM_BLOCKS)); i++){const binding = gl.getActiveUniformBlockParameter(program, i, gl.UNIFORM_BLOCK_BINDING); result.blocks.push({ name: gl.getActiveUniformBlockName(program, i), binding, required: gl.getActiveUniformBlockParameter(program, i, gl.UNIFORM_BLOCK_DATA_SIZE), buffer: id(gl.getIndexedParameter(gl.UNIFORM_BUFFER_BINDING, binding)), size: gl.getIndexedParameter(gl.UNIFORM_BUFFER_SIZE, binding) });} for(let i = 0; i < Math.min(64, gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS)); i++){const item = Reflect.apply(activeUniform, gl, [program, i]) as WebGLActiveInfo; if([gl.SAMPLER_2D,gl.SAMPLER_3D,gl.SAMPLER_CUBE,gl.SAMPLER_2D_ARRAY,gl.INT_SAMPLER_2D,gl.UNSIGNED_INT_SAMPLER_2D].includes(item.type)){const location = Reflect.apply(uniformLocation, gl, [program, item.name]); if(location){const value = gl.getUniform(program, location); result.samplers.push({ name: item.name, type: item.type, units: ArrayBuffer.isView(value) ? Array.from(value as any) : value });}}} return result; }; const methods = ['useProgram','getUniformLocation','getActiveUniform','uniform1i','uniform1iv','uniform2iv','uniform3iv','uniform4iv','uniform1ui','uniform1fv','uniform2fv','uniform3fv','uniform4fv','uniformMatrix3fv','uniformMatrix4fv','uniformBlockBinding','bindBufferBase','bindBufferRange','vertexAttribPointer','vertexAttribIPointer','drawArrays','drawElements','drawArraysInstanced','drawElementsInstanced','activeTexture','bindTexture','texStorage2D','texSubImage2D','readPixels','getError']; for(const name of methods){const original = proto[name]; if(typeof original !== 'function')continue; proto[name] = function(this: any, ...args: any[]) { root.__wbv2LastGL = this; if(!frozen && name.startsWith('draw'))root.__nativeGLDrawState = drawState(this); const result = Reflect.apply(original, this, args); if(name === 'getUniformLocation' && result)uniforms.set(result as object, { name: args[1], program: id(args[0]) }); if(!current.has(this) || name === 'useProgram')current.set(this, id(this.getParameter(this.CURRENT_PROGRAM))); if(!frozen){const brief = args.slice(0, 5).map(value => value == null || ['number','string','boolean'].includes(typeof value) ? value : ArrayBuffer.isView(value) ? { bytes: value.byteLength } : uniforms.get(value) ?? { kind: value.constructor?.name, id: id(value) }); events.push({ name, args: brief, program: current.get(this), result: name === 'getError' ? result : undefined }); if(events.length > 32)events.shift(); if(name === 'getError' && result)frozen = true;} return result; }; } }, finishBeforeCompute); }