diff --git a/assets/gl-bench.js b/assets/gl-bench.js index 0fb6bc4f..e7cb26bd 100644 --- a/assets/gl-bench.js +++ b/assets/gl-bench.js @@ -1,48 +1,40 @@ -// downloaded from https://github.com/munrocket/gl-bench -// this file is https://github.com/munrocket/gl-bench/blob/master/dist/gl-bench.module.js - -/* -var UISVG = "
\n \n 00 FPS\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
"; - -var UICSS = "#gl-bench {\n position:absolute;\n left:0;\n top:0;\n z-index:1000;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n}\n\n#gl-bench div {\n position: relative;\n display: block;\n margin: 4px;\n padding: 0 7px 0 10px;\n background: #6c6;\n border-radius: 15px;\n cursor: pointer;\n opacity: 0.9;\n}\n\n#gl-bench svg {\n height: 60px;\n margin: 0 -1px;\n}\n\n#gl-bench text {\n font-size: 12px;\n font-family: Helvetica,Arial,sans-serif;\n font-weight: 700;\n dominant-baseline: middle;\n text-anchor: middle;\n}\n\n#gl-bench .gl-mem {\n font-size: 9px;\n}\n\n#gl-bench line {\n stroke-width: 5;\n stroke: #112211;\n stroke-linecap: round;\n}\n\n#gl-bench polyline {\n fill: none;\n stroke: #112211;\n stroke-linecap: round;\n stroke-linejoin: round;\n stroke-width: 3.5;\n}\n\n#gl-bench rect {\n fill: #448844;\n}\n\n#gl-bench .opacity {\n stroke: #448844;\n}\n"; -*/ - -const UISVG = ` -
- - 00 FPS - - - - - - - - - - - - - - -
- `; +// modified based on: https://github.com/munrocket/gl-bench const UICSS = ` - #gl-bench { position: absolute; right:0; bottom:0; z-index:1000; -webkit-user-select: none; -moz-user-select: none; user-select: none; } + #gl-bench { position: absolute; right: 1rem; bottom: 1rem; z-index:1000; -webkit-user-select: none; -moz-user-select: none; user-select: none; } #gl-bench div { position: relative; display: block; margin: 4px; padding: 0 7px 0 10px; background: darkslategray; border-radius: 0.2rem; cursor: pointer; opacity: 0.9; } - #gl-bench svg { height: 60px; margin: 0 -1px; } - #gl-bench text { font-size: 12px; font-family: Helvetica,Arial,sans-serif; font-weight: 700; dominant-baseline: middle; text-anchor: middle; } - #gl-bench .gl-mem { font-size: 9px; fill: white; } - #gl-bench .gl-fps { font-size: 10px; fill: white; } + #gl-bench svg { height: 60px; margin: 0 4px 0px 4px; } + #gl-bench text { font-size: 16px; font-family: 'Lato', 'Segoe UI'; dominant-baseline: middle; text-anchor: middle; } + #gl-bench .gl-mem { font-size: 12px; fill: white; } + #gl-bench .gl-fps { font-size: 13px; fill: white; } #gl-bench line { stroke-width: 5; stroke: white; stroke-linecap: round; } #gl-bench polyline { fill: none; stroke: white; stroke-linecap: round; stroke-linejoin: round; stroke-width: 3.5; } #gl-bench rect { fill: black; } #gl-bench .opacity { stroke: black; } `; -class GLBench { +const UISVG = ` +
+ + 00 FPS + + + + + + + + + + + + + + +
+ `; +class GLBench { /** GLBench constructor * @param { WebGLRenderingContext | WebGL2RenderingContext } gl context * @param { Object | undefined } settings additional settings @@ -57,14 +49,12 @@ class GLBench { this.names = []; this.cpuAccums = []; - this.gpuAccums = []; + this.gpuAccums = []; this.activeAccums = []; this.chart = new Array(this.chartLen); - this.now = () => (performance && performance.now) ? performance.now() : Date.now(); + this.now = () => ((performance && performance.now) ? performance.now() : Date.now()); this.updateUI = () => { - [].forEach.call(this.nodes['gl-gpu-svg'], node => { - node.style.display = this.trackGPU ? 'inline' : 'none'; - }); + [].forEach.call(this.nodes['gl-gpu-svg'], (node) => node.style.display = this.trackGPU ? 'inline' : 'none'); }; Object.assign(this, settings); @@ -74,8 +64,9 @@ class GLBench { this.frameId = 0; // 120hz device detection - let rafId, n = 0, t0; - let loop = (t) => { + let rafId; let n = 0; let + t0; + const loop = (t) => { if (++n < 20) { rafId = requestAnimationFrame(loop); } else { @@ -88,31 +79,27 @@ class GLBench { // attach gpu profilers if (gl) { - const glFinish = async (t, activeAccums) => - Promise.resolve(setTimeout(() => { - gl.getError(); - const dt = this.now() - t; - activeAccums.forEach((active, i) => { - if (active) this.gpuAccums[i] += dt; - }); - }, 0)); + const glFinish = async (t, activeAccums) => Promise.resolve(setTimeout(() => { + gl.getError(); + const dt = this.now() - t; + activeAccums.forEach((active, i) => { + if (active) this.gpuAccums[i] += dt; + }); + }, 0)); - const addProfiler = (fn, self, target) => function() { + const addProfiler = (fn, self, target) => function () { const t = self.now(); + // eslint-disable-next-line prefer-rest-params fn.apply(target, arguments); if (self.trackGPU) self.finished.push(glFinish(t, self.activeAccums.slice(0))); }; - ['drawArrays', 'drawElements', 'drawArraysInstanced', - 'drawBuffers', 'drawElementsInstanced', 'drawRangeElements'] - .forEach(fn => { if (gl[fn]) gl[fn] = addProfiler(gl[fn], this, gl); }); + ['drawArrays', 'drawElements', 'drawArraysInstanced', 'drawBuffers', 'drawElementsInstanced', 'drawRangeElements'].forEach((fn) => { if (gl[fn]) gl[fn] = addProfiler(gl[fn], this, gl); }); - gl.getExtension = ((fn, self) => function() { - let ext = fn.apply(gl, arguments); - if (ext) { - ['drawElementsInstancedANGLE', 'drawBuffersWEBGL'] - .forEach(fn => { if (ext[fn]) ext[fn] = addProfiler(ext[fn], self, ext); }); - } + gl.getExtension = ((fn, self) => function () { + // eslint-disable-next-line prefer-rest-params + const ext = fn.apply(gl, arguments); + if (ext) ['drawElementsInstancedANGLE', 'drawBuffersWEBGL'].forEach((fn2) => { if (ext[fn2]) ext[fn2] = addProfiler(ext[fn2], self, ext); }); return ext; })(gl.getExtension, this); } @@ -120,7 +107,7 @@ class GLBench { // init ui and ui loggers if (!this.withoutUI) { if (!this.dom) this.dom = document.body; - let elm = document.createElement('div'); + const elm = document.createElement('div'); elm.id = 'gl-bench'; this.dom.appendChild(elm); this.dom.insertAdjacentHTML('afterbegin', ''); @@ -132,43 +119,41 @@ class GLBench { this.paramLogger = ((logger, dom, names) => { const classes = ['gl-cpu', 'gl-gpu', 'gl-mem', 'gl-fps', 'gl-gpu-svg', 'gl-chart']; - const nodes = Object.assign({}, classes); - classes.forEach(c => nodes[c] = dom.getElementsByClassName(c)); + const nodes = { ...classes }; + classes.forEach((c) => nodes[c] = dom.getElementsByClassName(c)); this.nodes = nodes; return (i, cpu, gpu, mem, fps, totalTime, frameId) => { nodes['gl-cpu'][i].style.strokeDasharray = (cpu * 0.27).toFixed(0) + ' 100'; nodes['gl-gpu'][i].style.strokeDasharray = (gpu * 0.27).toFixed(0) + ' 100'; + // eslint-disable-next-line no-nested-ternary nodes['gl-mem'][i].innerHTML = names[i] ? names[i] : (mem ? 'mem: ' + mem.toFixed(0) + 'mb' : ''); nodes['gl-fps'][i].innerHTML = fps.toFixed(0) + ' FPS'; logger(names[i], cpu, gpu, mem, fps, totalTime, frameId); - } + }; })(this.paramLogger, this.dom, this.names); this.chartLogger = ((logger, dom) => { - let nodes = { 'gl-chart': dom.getElementsByClassName('gl-chart') }; + const nodes = { 'gl-chart': dom.getElementsByClassName('gl-chart') }; return (i, chart, circularId) => { let points = ''; - let len = chart.length; - for (let i = 0; i < len; i++) { - let id = (circularId + i + 1) % len; - if (chart[id] != undefined) { - points = points + ' ' + (55 * i / (len - 1)).toFixed(1) + ',' - + (45 - chart[id] * 22 / 60 / this.detected).toFixed(1); - } + const len = chart.length; + for (let j = 0; j < len; j++) { + const id = (circularId + j + 1) % len; + if (chart[id] !== undefined) points = points + ' ' + (55 * j / (len - 1)).toFixed(1) + ',' + (45 - chart[id] * 22 / 60 / this.detected).toFixed(1); } nodes['gl-chart'][i].setAttribute('points', points); logger(this.names[i], chart, circularId); - } + }; })(this.chartLogger, this.dom); } } /** * Explicit UI add - * @param { string | undefined } name + * @param { string | undefined } name */ addUI(name) { - if (this.names.indexOf(name) == -1) { + if (this.names.indexOf(name) === -1) { this.names.push(name); if (this.dom) { this.dom.insertAdjacentHTML('beforeend', this.svg); @@ -186,21 +171,21 @@ class GLBench { */ nextFrame(now) { this.frameId++; - const t = now ? now : this.now(); + const t = now || this.now(); // params if (this.frameId <= 1) { this.paramFrame = this.frameId; this.paramTime = t; } else { - let duration = t - this.paramTime; + const duration = t - this.paramTime; if (duration >= 1e3) { const frameCount = this.frameId - this.paramFrame; const fps = frameCount / duration * 1e3; for (let i = 0; i < this.names.length; i++) { - const cpu = this.cpuAccums[i] / duration * 100, - gpu = this.gpuAccums[i] / duration * 100, - mem = (performance && performance.memory) ? performance.memory.usedJSHeapSize / (1 << 20) : 0; + const cpu = this.cpuAccums[i] / duration * 100; + const gpu = this.gpuAccums[i] / duration * 100; + const mem = (performance && performance.memory) ? performance.memory.usedJSHeapSize / (1 << 20) : 0; this.paramLogger(i, cpu, gpu, mem, fps, duration, frameCount); this.cpuAccums[i] = 0; Promise.all(this.finished).then(() => { @@ -219,15 +204,13 @@ class GLBench { this.chartTime = t; this.circularId = 0; } else { - let timespan = t - this.chartTime; + const timespan = t - this.chartTime; let hz = this.chartHz * timespan / 1e3; while (--hz > 0 && this.detected) { const frameCount = this.frameId - this.chartFrame; const fps = frameCount / timespan * 1e3; this.chart[this.circularId % this.chartLen] = fps; - for (let i = 0; i < this.names.length; i++) { - this.chartLogger(i, this.chart, this.circularId); - } + for (let i = 0; i < this.names.length; i++) this.chartLogger(i, this.chart, this.circularId); this.circularId++; this.chartFrame = this.frameId; this.chartTime = t; @@ -253,7 +236,7 @@ class GLBench { updateAccums(name) { let nameId = this.names.indexOf(name); - if (nameId == -1) { + if (nameId === -1) { nameId = this.names.length; this.addUI(name); } @@ -261,13 +244,11 @@ class GLBench { const t = this.now(); const dt = t - this.t0; for (let i = 0; i < nameId + 1; i++) { - if (this.activeAccums[i]) { - this.cpuAccums[i] += dt; - } - } this.activeAccums[nameId] = !this.activeAccums[nameId]; + if (this.activeAccums[i]) this.cpuAccums[i] += dt; + } + this.activeAccums[nameId] = !this.activeAccums[nameId]; this.t0 = t; } - } export default GLBench; diff --git a/demo/browser.js b/demo/browser.js index 5170f69e..fbe8924c 100644 --- a/demo/browser.js +++ b/demo/browser.js @@ -35,9 +35,10 @@ const ui = { menuWidth: 0, menuHeight: 0, camera: {}, - fps: [], + detectFPS: [], + drawFPS: [], buffered: false, - bufferedFPSTarget: 24, + bufferedFPSTarget: 0, drawThread: null, detectThread: null, framesDraw: 0, @@ -86,20 +87,21 @@ async function calcSimmilariry(faces) { } // draws processed results and starts processing of a next frame +let lastDraw = performance.now(); async function drawResults(input) { const result = lastDetectedResult; const canvas = document.getElementById('canvas'); - // update fps data - // const elapsed = performance.now() - timeStamp; - if (result.performance && result.performance.total) ui.fps.push(1000 / result.performance.total); - if (ui.fps.length > ui.maxFPSframes) ui.fps.shift(); + // update draw fps data + ui.drawFPS.push(1000 / (performance.now() - lastDraw)); + if (ui.drawFPS.length > ui.maxFPSframes) ui.drawFPS.shift(); + lastDraw = performance.now(); // enable for continous performance monitoring // console.log(result.performance); // draw fps chart - await menu.updateChart('FPS', ui.fps); + await menu.updateChart('FPS', ui.detectFPS); // get updated canvas if (ui.buffered || !result.canvas) result.canvas = await human.image(input, userConfig); @@ -128,21 +130,26 @@ async function drawResults(input) { const gpu = engine.backendInstance ? `gpu: ${(engine.backendInstance.numBytesInGPU ? engine.backendInstance.numBytesInGPU : 0).toLocaleString()} bytes` : ''; const memory = `system: ${engine.state.numBytes.toLocaleString()} bytes ${gpu} | tensors: ${engine.state.numTensors.toLocaleString()}`; const processing = result.canvas ? `processing: ${result.canvas.width} x ${result.canvas.height}` : ''; - const avg = Math.trunc(10 * ui.fps.reduce((a, b) => a + b, 0) / ui.fps.length) / 10; - const warning = (ui.fps.length > 5) && (avg < 5) ? 'warning: your performance is low: try switching to higher performance backend, lowering resolution or disabling some models' : ''; + const avgDetect = Math.trunc(10 * ui.detectFPS.reduce((a, b) => a + b, 0) / ui.detectFPS.length) / 10; + const avgDraw = Math.trunc(10 * ui.drawFPS.reduce((a, b) => a + b, 0) / ui.drawFPS.length) / 10; + const warning = (ui.detectFPS.length > 5) && (avgDetect < 5) ? 'warning: your performance is low: try switching to higher performance backend, lowering resolution or disabling some models' : ''; document.getElementById('log').innerHTML = ` video: ${ui.camera.name} | facing: ${ui.camera.facing} | resolution: ${ui.camera.width} x ${ui.camera.height} ${processing}
backend: ${human.tf.getBackend()} | ${memory}
- performance: ${str(result.performance)} FPS:${avg}
+ performance: ${str(result.performance)} FPS process:${avgDetect} refresh:${avgDraw}
${warning} `; ui.framesDraw++; ui.lastFrame = performance.now(); // if buffered, immediate loop but limit frame rate although it's going to run slower as JS is singlethreaded - if (ui.buffered && !ui.drawThread) ui.drawThread = setInterval(() => drawResults(input, canvas), 1000 / ui.bufferedFPSTarget); - // stop buffering - if (!ui.buffered && ui.drawThread) { + if ((ui.bufferedFPSTarget === 0) && ui.buffered) { + ui.drawThread = requestAnimationFrame(() => drawResults(input, canvas)); + } else if ((ui.bufferedFPSTarget === 0) && ui.buffered && !ui.drawThread) { + log('starting buffered refresh'); + if (ui.bufferedFPSTarget > 0) ui.drawThread = setInterval(() => drawResults(input, canvas), 1000 / ui.bufferedFPSTarget); + } else if (!ui.buffered && ui.drawThread) { + log('stopping buffered refresh'); clearTimeout(ui.drawThread); ui.drawThread = null; } @@ -156,7 +163,6 @@ async function setupCamera() { const canvas = document.getElementById('canvas'); const output = document.getElementById('log'); const live = video.srcObject ? ((video.srcObject.getVideoTracks()[0].readyState === 'live') && (video.readyState > 2) && (!video.paused)) : false; - console.log('camera live', live); let msg = ''; status('setting up camera'); // setup webcam. note that navigator.mediaDevices requires that page is accessed via https @@ -208,7 +214,6 @@ async function setupCamera() { // silly font resizing for paint-on-canvas since viewport can be zoomed const size = 14 + (6 * canvas.width / window.innerWidth); ui.baseFont = ui.baseFontProto.replace(/{size}/, `${size}px`); - console.log('camera continue', live); if (live) video.play(); // eslint-disable-next-line no-use-before-define if (live && !ui.detectThread) runHumanDetect(video, canvas); @@ -229,6 +234,8 @@ function webWorker(input, image, canvas, timestamp) { worker = new Worker(ui.worker, { type: 'module' }); // after receiving message from webworker, parse&draw results and send new frame for processing worker.addEventListener('message', (msg) => { + if (msg.data.result.performance && msg.data.result.performance.total) ui.detectFPS.push(1000 / msg.data.result.performance.total); + if (ui.detectFPS.length > ui.maxFPSframes) ui.detectFPS.shift(); if (ui.bench) bench.end(); if (ui.bench) bench.nextFrame(timestamp); lastDetectedResult = msg.data.result; @@ -274,6 +281,8 @@ function runHumanDetect(input, canvas, timestamp) { } else { if (ui.bench) bench.begin(); human.detect(input, userConfig).then((result) => { + if (result.performance && result.performance.total) ui.detectFPS.push(1000 / result.performance.total); + if (ui.detectFPS.length > ui.maxFPSframes) ui.detectFPS.shift(); if (ui.bench) bench.end(); if (ui.bench) bench.nextFrame(timestamp); if (result.error) log(result.error); @@ -446,7 +455,7 @@ async function setupMonitor() { bench = new GLBench(gl, { trackGPU: true, chartHz: 20, - chartLen: 50, + chartLen: 20, }); } /*