├── .circleci ├── config.yml └── setup_puppeteer.sh ├── .eslintrc ├── .gitignore ├── .npmignore ├── .renovaterc ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── gl-bench.d.ts ├── gl-bench.js ├── gl-bench.min.js ├── gl-bench.module.d.ts └── gl-bench.module.js ├── examples ├── common │ ├── maingui.js │ ├── style.css │ └── worker.js ├── depth-textures.html ├── float-textures.html ├── framebuffer.html ├── instanced-arrays.html ├── named-measuring.html ├── new-loggers.html ├── themes │ ├── behance.js │ └── oscilloscope.js ├── web-workers.html ├── webgl.html └── webgl2.html ├── index.html ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── index.js ├── ui.css └── ui.svg └── test ├── puppeteer.js ├── stress-test.html ├── test.js └── unit-test.html /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:12.11 6 | working_directory: ~/repo 7 | 8 | steps: 9 | - checkout 10 | 11 | - restore_cache: 12 | keys: 13 | - v1-dependencies-{{ checksum "package.json" }} 14 | - v1-dependencies- 15 | 16 | - run: 17 | name: Workaround for GoogleChrome/puppeteer#290 18 | command: 'sh .circleci/setup_puppeteer.sh' 19 | 20 | - run: npm install 21 | 22 | - save_cache: 23 | paths: 24 | - node_modules 25 | key: v1-dependencies-{{ checksum "package.json" }} 26 | 27 | - run: npm run ci && npx codecov 28 | -------------------------------------------------------------------------------- /.circleci/setup_puppeteer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo apt-get update 4 | sudo apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \ 5 | libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \ 6 | libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \ 7 | libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ 8 | ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget --fix-missing -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "es6": true 7 | }, 8 | "parserOptions": { 9 | "ecmaVersion": 6, 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "no-console": "warn", 14 | "strict": ["error", "global"], 15 | "curly": "warn" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | coverage 4 | .nyc_output -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .circleci 2 | .nyc_output 3 | coverage 4 | node_modules 5 | test 6 | .gitignore 7 | .nycrc 8 | .renovaterc 9 | .travis.yml 10 | LICENSE 11 | package-lock.json -------------------------------------------------------------------------------- /.renovaterc: -------------------------------------------------------------------------------- 1 | { 2 | "automerge": true, 3 | "automergeType": "branch", 4 | "bumpVersion": "patch", 5 | "schedule": ["on sunday"], 6 | "ignorePaths": [".circleci"] 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: node_js 3 | services: xvfb 4 | node_js: 10 5 | 6 | jobs: 7 | include: 8 | - name: test 9 | script: npm run build && npm run ci 10 | 11 | - name: publish 12 | script: npm run build 13 | deploy: 14 | provider: npm 15 | edge: true 16 | email: $NPM_EMAIL 17 | api_key: $NPM_TOKEN 18 | on: 19 | branch: master 20 | 21 | allow_failures: 22 | - name: publish -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 munrocket 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gl-bench [![Bundlephobia](https://badgen.net/bundlephobia/minzip/gl-bench)](https://bundlephobia.com/result?p=gl-bench) [![CircleCI](https://badgen.net/github/status/munrocket/gl-bench)](https://circleci.com/gh/munrocket/gl-bench) [![Codecov](https://img.shields.io/codecov/c/github/munrocket/gl-bench.svg)](https://codecov.io/gh/munrocket/gl-bench) 2 | 3 | WebGL performance monitor that showing percentage of GPU/CPU load. 4 | 5 | ### Screenshots 6 | ![](https://habrastorage.org/webt/vb/ys/pz/vbyspz0emcxkslj0c-u0toxbom0.png) 7 | 8 | ### Examples / e2e tests 9 | - [webgl](https://munrocket.github.io/gl-bench/examples/webgl.html) 10 | - [webgl2](https://munrocket.github.io/gl-bench/examples/webgl2.html) 11 | - [new-loggers](https://munrocket.github.io/gl-bench/examples/new-loggers.html) 12 | - [named-measuring](https://munrocket.github.io/gl-bench/examples/named-measuring.html) 13 | - [instanced-arrays](https://munrocket.github.io/gl-bench/examples/instanced-arrays.html) 14 | - [float-textures](https://munrocket.github.io/gl-bench/examples/float-textures.html) 15 | - [web-workers](https://munrocket.github.io/gl-bench/examples/web-workers.html) 16 | 17 | ### Pros and cons 18 | | Pros | Cons | 19 | |------------------------------------------------------------------|------------------------------------------| 20 | | CPU/GPU percentage load | Shipped only with ES6 classes | 21 | | Cool themes and loggers | Size not so tiny | 22 | | Chart show inactive page or significant performance drop | Not too easy to add on a page | 23 | | Two and more measuring in one loop | | 24 | | Support for devices with 120+ FPS | | 25 | | Web workers support | | 26 | | Typescript support | | 27 | | It is 2x faster than Stats.js in Chrome accorging to stress test | | 28 | 29 | ### How it works 30 | For GPU/CPU synchronization I am use gl.getError(), it is better than gl.readPixels() at least for me. Code is asynchronous and not stall rendering pipeline by hitting CPU limit due to waiting GPU answer, so you can calculate your heavy physics on CPU with this monitor. If you want turn off GPU tracking just press on it with one click. Check online examples/e2e tests to find out how it works. Version 1 used the EXT_disjoint_timer_query extension, but it [not supported](https://caniuse.com/#search=disjoint_timer_query) on some pc anymore. 31 | 32 | ### Usage with Three.js 33 | Add script on page from [npm](https://www.npmjs.com/package/gl-bench) or [jsdelivr](https://cdn.jsdelivr.net/npm/gl-bench/dist/gl-bench.min.js)/[unpkg](https://unpkg.com/gl-bench/dist/gl-bench.min.js) and wrap monitored code with begin/end marks 34 | ```javascript 35 | import GLBench from 'gl-bench/dist/gl-bench'; 36 | let bench = new GLBench(renderer.getContext()); 37 | 38 | function draw(now) { 39 | bench.begin(); 40 | // monitored code 41 | bench.end(); 42 | bench.nextFrame(now); 43 | } 44 | 45 | renderer.setAnimationLoop((now) => draw(now)); 46 | ``` 47 | 48 | ### Using TypeScript 49 | Replace ``import GLBench from 'gl-bench/dist/gl-bench';`` into ``import GLBench from 'gl-bench/dist/gl-bench.module';`` 50 | 51 | ### Profiling with another WebGL frameworks 52 | ```javascript 53 | let gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); 54 | let bench = new GLBench(gl); 55 | 56 | // engine initialization with instanced_arrays/draw_buffers webgl1 extensions goes after! 57 | 58 | function draw(now) { 59 | bench.begin('first measure'); 60 | // some bottleneck 61 | bench.end('first measure'); 62 | 63 | bench.begin('second measure'); 64 | // some bottleneck 65 | bench.end('second measure'); 66 | 67 | bench.nextFrame(now); 68 | requestAnimationFrame(draw); 69 | } 70 | requestAnimationFrame(draw); 71 | ``` 72 | 73 | ### Custom settings 74 | ```javascript 75 | let bench = new GLBench(gl, { 76 | css: 'newStyleString', 77 | svg: 'newSvgString', 78 | dom: newDomContainer, 79 | withoutUI: false, 80 | trackGPU: false, // don't track GPU load by default 81 | chartHz: 20, // chart update speed 82 | chartLen: 20, 83 | paramLogger: (i, cpu, gpu, mem, fps, totalTime, frameId) => { console.log(cpu, gpu) }, 84 | chartLogger: (i, chart, circularId) => { console.log('chart circular buffer=', chart) }, 85 | }; 86 | ``` 87 | 88 | ### Contributing 89 | Fork this repository and install the dependencies, after that you can start dev server with `npm run dev` 90 | and open examples in browser `localhost:1234`. 91 | 92 | [//]: # (posible optimizations: delete array clone, get rid of self) 93 | -------------------------------------------------------------------------------- /dist/gl-bench.d.ts: -------------------------------------------------------------------------------- 1 | export default class GLBench { 2 | constructor(gl: WebGLRenderingContext | WebGL2RenderingContext | null, settings?: object); 3 | 4 | addUI(name?: string): void; 5 | 6 | nextFrame(now?: number): void; 7 | begin(name?: string): void; 8 | end(name?: string): void; 9 | } -------------------------------------------------------------------------------- /dist/gl-bench.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global = global || self, global.GLBench = factory()); 5 | }(this, function () { 'use strict'; 6 | 7 | var UISVG = "
\n \n 00 FPS\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
"; 8 | 9 | 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"; 10 | 11 | class GLBench { 12 | 13 | /** GLBench constructor 14 | * @param { WebGLRenderingContext | WebGL2RenderingContext } gl context 15 | * @param { Object | undefined } settings additional settings 16 | */ 17 | constructor(gl, settings = {}) { 18 | this.css = UICSS; 19 | this.svg = UISVG; 20 | this.paramLogger = () => {}; 21 | this.chartLogger = () => {}; 22 | this.chartLen = 20; 23 | this.chartHz = 20; 24 | 25 | this.names = []; 26 | this.cpuAccums = []; 27 | this.gpuAccums = []; 28 | this.activeAccums = []; 29 | this.chart = new Array(this.chartLen); 30 | this.now = () => (performance && performance.now) ? performance.now() : Date.now(); 31 | this.updateUI = () => { 32 | [].forEach.call(this.nodes['gl-gpu-svg'], node => { 33 | node.style.display = this.trackGPU ? 'inline' : 'none'; 34 | }); 35 | }; 36 | 37 | Object.assign(this, settings); 38 | this.detected = 0; 39 | this.finished = []; 40 | this.isFramebuffer = 0; 41 | this.frameId = 0; 42 | 43 | // 120hz device detection 44 | let rafId, n = 0, t0; 45 | let loop = (t) => { 46 | if (++n < 20) { 47 | rafId = requestAnimationFrame(loop); 48 | } else { 49 | this.detected = Math.ceil(1e3 * n / (t - t0) / 70); 50 | cancelAnimationFrame(rafId); 51 | } 52 | if (!t0) t0 = t; 53 | }; 54 | requestAnimationFrame(loop); 55 | 56 | // attach gpu profilers 57 | if (gl) { 58 | const glFinish = async (t, activeAccums) => 59 | Promise.resolve(setTimeout(() => { 60 | gl.getError(); 61 | const dt = this.now() - t; 62 | activeAccums.forEach((active, i) => { 63 | if (active) this.gpuAccums[i] += dt; 64 | }); 65 | }, 0)); 66 | 67 | const addProfiler = (fn, self, target) => function() { 68 | const t = self.now(); 69 | fn.apply(target, arguments); 70 | if (self.trackGPU) self.finished.push(glFinish(t, self.activeAccums.slice(0))); 71 | }; 72 | 73 | ['drawArrays', 'drawElements', 'drawArraysInstanced', 74 | 'drawBuffers', 'drawElementsInstanced', 'drawRangeElements'] 75 | .forEach(fn => { if (gl[fn]) gl[fn] = addProfiler(gl[fn], this, gl); }); 76 | 77 | gl.getExtension = ((fn, self) => function() { 78 | let ext = fn.apply(gl, arguments); 79 | if (ext) { 80 | ['drawElementsInstancedANGLE', 'drawBuffersWEBGL'] 81 | .forEach(fn => { if (ext[fn]) ext[fn] = addProfiler(ext[fn], self, ext); }); 82 | } 83 | return ext; 84 | })(gl.getExtension, this); 85 | } 86 | 87 | // init ui and ui loggers 88 | if (!this.withoutUI) { 89 | if (!this.dom) this.dom = document.body; 90 | let elm = document.createElement('div'); 91 | elm.id = 'gl-bench'; 92 | this.dom.appendChild(elm); 93 | this.dom.insertAdjacentHTML('afterbegin', ''); 94 | this.dom = elm; 95 | this.dom.addEventListener('click', () => { 96 | this.trackGPU = !this.trackGPU; 97 | this.updateUI(); 98 | }); 99 | 100 | this.paramLogger = ((logger, dom, names) => { 101 | const classes = ['gl-cpu', 'gl-gpu', 'gl-mem', 'gl-fps', 'gl-gpu-svg', 'gl-chart']; 102 | const nodes = Object.assign({}, classes); 103 | classes.forEach(c => nodes[c] = dom.getElementsByClassName(c)); 104 | this.nodes = nodes; 105 | return (i, cpu, gpu, mem, fps, totalTime, frameId) => { 106 | nodes['gl-cpu'][i].style.strokeDasharray = (cpu * 0.27).toFixed(0) + ' 100'; 107 | nodes['gl-gpu'][i].style.strokeDasharray = (gpu * 0.27).toFixed(0) + ' 100'; 108 | nodes['gl-mem'][i].innerHTML = names[i] ? names[i] : (mem ? 'mem: ' + mem.toFixed(0) + 'mb' : ''); 109 | nodes['gl-fps'][i].innerHTML = fps.toFixed(0) + ' FPS'; 110 | logger(names[i], cpu, gpu, mem, fps, totalTime, frameId); 111 | } 112 | })(this.paramLogger, this.dom, this.names); 113 | 114 | this.chartLogger = ((logger, dom) => { 115 | let nodes = { 'gl-chart': dom.getElementsByClassName('gl-chart') }; 116 | return (i, chart, circularId) => { 117 | let points = ''; 118 | let len = chart.length; 119 | for (let i = 0; i < len; i++) { 120 | let id = (circularId + i + 1) % len; 121 | if (chart[id] != undefined) { 122 | points = points + ' ' + (55 * i / (len - 1)).toFixed(1) + ',' 123 | + (45 - chart[id] * 22 / 60 / this.detected).toFixed(1); 124 | } 125 | } 126 | nodes['gl-chart'][i].setAttribute('points', points); 127 | logger(this.names[i], chart, circularId); 128 | } 129 | })(this.chartLogger, this.dom); 130 | } 131 | } 132 | 133 | /** 134 | * Explicit UI add 135 | * @param { string | undefined } name 136 | */ 137 | addUI(name) { 138 | if (this.names.indexOf(name) == -1) { 139 | this.names.push(name); 140 | if (this.dom) { 141 | this.dom.insertAdjacentHTML('beforeend', this.svg); 142 | this.updateUI(); 143 | } 144 | this.cpuAccums.push(0); 145 | this.gpuAccums.push(0); 146 | this.activeAccums.push(false); 147 | } 148 | } 149 | 150 | /** 151 | * Increase frameID 152 | * @param { number | undefined } now 153 | */ 154 | nextFrame(now) { 155 | this.frameId++; 156 | const t = now ? now : this.now(); 157 | 158 | // params 159 | if (this.frameId <= 1) { 160 | this.paramFrame = this.frameId; 161 | this.paramTime = t; 162 | } else { 163 | let duration = t - this.paramTime; 164 | if (duration >= 1e3) { 165 | const frameCount = this.frameId - this.paramFrame; 166 | const fps = frameCount / duration * 1e3; 167 | for (let i = 0; i < this.names.length; i++) { 168 | const cpu = this.cpuAccums[i] / duration * 100, 169 | gpu = this.gpuAccums[i] / duration * 100, 170 | mem = (performance && performance.memory) ? performance.memory.usedJSHeapSize / (1 << 20) : 0; 171 | this.paramLogger(i, cpu, gpu, mem, fps, duration, frameCount); 172 | this.cpuAccums[i] = 0; 173 | Promise.all(this.finished).then(() => { 174 | this.gpuAccums[i] = 0; 175 | this.finished = []; 176 | }); 177 | } 178 | this.paramFrame = this.frameId; 179 | this.paramTime = t; 180 | } 181 | } 182 | 183 | // chart 184 | if (!this.detected || !this.chartFrame) { 185 | this.chartFrame = this.frameId; 186 | this.chartTime = t; 187 | this.circularId = 0; 188 | } else { 189 | let timespan = t - this.chartTime; 190 | let hz = this.chartHz * timespan / 1e3; 191 | while (--hz > 0 && this.detected) { 192 | const frameCount = this.frameId - this.chartFrame; 193 | const fps = frameCount / timespan * 1e3; 194 | this.chart[this.circularId % this.chartLen] = fps; 195 | for (let i = 0; i < this.names.length; i++) { 196 | this.chartLogger(i, this.chart, this.circularId); 197 | } 198 | this.circularId++; 199 | this.chartFrame = this.frameId; 200 | this.chartTime = t; 201 | } 202 | } 203 | } 204 | 205 | /** 206 | * Begin named measurement 207 | * @param { string | undefined } name 208 | */ 209 | begin(name) { 210 | this.updateAccums(name); 211 | } 212 | 213 | /** 214 | * End named measure 215 | * @param { string | undefined } name 216 | */ 217 | end(name) { 218 | this.updateAccums(name); 219 | } 220 | 221 | updateAccums(name) { 222 | let nameId = this.names.indexOf(name); 223 | if (nameId == -1) { 224 | nameId = this.names.length; 225 | this.addUI(name); 226 | } 227 | 228 | const t = this.now(); 229 | const dt = t - this.t0; 230 | for (let i = 0; i < nameId + 1; i++) { 231 | if (this.activeAccums[i]) { 232 | this.cpuAccums[i] += dt; 233 | } 234 | } this.activeAccums[nameId] = !this.activeAccums[nameId]; 235 | this.t0 = t; 236 | } 237 | 238 | } 239 | 240 | return GLBench; 241 | 242 | })); 243 | -------------------------------------------------------------------------------- /dist/gl-bench.min.js: -------------------------------------------------------------------------------- 1 | 'use strict';var GLBench=function(){class l{constructor(a,b={}){this.css="#gl-bench { position:absolute; left:0; top:0; 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: #6c6; border-radius: 15px; 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; } #gl-bench line { stroke-width: 5; stroke: #112211; stroke-linecap: round; } #gl-bench polyline { fill: none; stroke: #112211; stroke-linecap: round; stroke-linejoin: round; stroke-width: 3.5; } #gl-bench rect { fill: #448844; } #gl-bench .opacity { stroke: #448844; } "; 2 | this.svg='
00 FPS
'; 3 | this.paramLogger=()=>{};this.chartLogger=()=>{};this.chartHz=this.chartLen=20;this.names=[];this.cpuAccums=[];this.gpuAccums=[];this.activeAccums=[];this.chart=Array(this.chartLen);this.now=()=>performance&&performance.now?performance.now():Date.now();this.updateUI=()=>{[].forEach.call(this.nodes["gl-gpu-svg"],a=>{a.style.display=this.trackGPU?"inline":"none"})};Object.assign(this,b);this.detected=0;this.finished=[];this.frameId=this.isFramebuffer=0;let e,c=0,f,g=a=>{20>++c?e=requestAnimationFrame(g): 4 | (this.detected=Math.ceil(1E3*c/(a-f)/70),cancelAnimationFrame(e));f||(f=a)};requestAnimationFrame(g);if(a){let b=async(b,c)=>Promise.resolve(setTimeout(()=>{a.getError();const d=this.now()-b;c.forEach((a,b)=>{a&&(this.gpuAccums[b]+=d)})},0)),c=(a,c,d)=>function(){const n=c.now();a.apply(d,arguments);c.trackGPU&&c.finished.push(b(n,c.activeAccums.slice(0)))};"drawArrays drawElements drawArraysInstanced drawBuffers drawElementsInstanced drawRangeElements".split(" ").forEach(b=>{a[b]&&(a[b]=c(a[b],this, 5 | a))});a.getExtension=((b,f)=>function(){let d=b.apply(a,arguments);d&&["drawElementsInstancedANGLE","drawBuffersWEBGL"].forEach(a=>{d[a]&&(d[a]=c(d[a],f,d))});return d})(a.getExtension,this)}this.withoutUI||(this.dom||(this.dom=document.body),b=document.createElement("div"),b.id="gl-bench",this.dom.appendChild(b),this.dom.insertAdjacentHTML("afterbegin",'"),this.dom=b,this.dom.addEventListener("click",()=>{this.trackGPU=!this.trackGPU;this.updateUI()}), 6 | this.paramLogger=((a,b,c)=>{let f="gl-cpu gl-gpu gl-mem gl-fps gl-gpu-svg gl-chart".split(" "),d=Object.assign({},f);f.forEach(a=>d[a]=b.getElementsByClassName(a));this.nodes=d;return(b,f,e,h,k,g,m)=>{d["gl-cpu"][b].style.strokeDasharray=(.27*f).toFixed(0)+" 100";d["gl-gpu"][b].style.strokeDasharray=(.27*e).toFixed(0)+" 100";d["gl-mem"][b].innerHTML=c[b]?c[b]:h?"mem: "+h.toFixed(0)+"mb":"";d["gl-fps"][b].innerHTML=k.toFixed(0)+" FPS";a(c[b],f,e,h,k,g,m)}})(this.paramLogger,this.dom,this.names),this.chartLogger= 7 | ((a,b)=>{let c={"gl-chart":b.getElementsByClassName("gl-chart")};return(b,d,f)=>{let e="",g=d.length;for(let a=0;a=this.frameId)this.paramFrame=this.frameId,this.paramTime=a;else{var b=a-this.paramTime;if(1E3<=b){var e=this.frameId-this.paramFrame,c=e/b*1E3;for(let a=0;a{this.gpuAccums[a]=0;this.finished=[]}); 9 | this.paramFrame=this.frameId;this.paramTime=a}}if(this.detected&&this.chartFrame)for(b=a-this.chartTime,e=this.chartHz*b/1E3;0<--e&&this.detected;){this.chart[this.circularId%this.chartLen]=(this.frameId-this.chartFrame)/b*1E3;for(c=0;c\n \n 00 FPS\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n"; 2 | 3 | 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"; 4 | 5 | class GLBench { 6 | 7 | /** GLBench constructor 8 | * @param { WebGLRenderingContext | WebGL2RenderingContext } gl context 9 | * @param { Object | undefined } settings additional settings 10 | */ 11 | constructor(gl, settings = {}) { 12 | this.css = UICSS; 13 | this.svg = UISVG; 14 | this.paramLogger = () => {}; 15 | this.chartLogger = () => {}; 16 | this.chartLen = 20; 17 | this.chartHz = 20; 18 | 19 | this.names = []; 20 | this.cpuAccums = []; 21 | this.gpuAccums = []; 22 | this.activeAccums = []; 23 | this.chart = new Array(this.chartLen); 24 | this.now = () => (performance && performance.now) ? performance.now() : Date.now(); 25 | this.updateUI = () => { 26 | [].forEach.call(this.nodes['gl-gpu-svg'], node => { 27 | node.style.display = this.trackGPU ? 'inline' : 'none'; 28 | }); 29 | }; 30 | 31 | Object.assign(this, settings); 32 | this.detected = 0; 33 | this.finished = []; 34 | this.isFramebuffer = 0; 35 | this.frameId = 0; 36 | 37 | // 120hz device detection 38 | let rafId, n = 0, t0; 39 | let loop = (t) => { 40 | if (++n < 20) { 41 | rafId = requestAnimationFrame(loop); 42 | } else { 43 | this.detected = Math.ceil(1e3 * n / (t - t0) / 70); 44 | cancelAnimationFrame(rafId); 45 | } 46 | if (!t0) t0 = t; 47 | }; 48 | requestAnimationFrame(loop); 49 | 50 | // attach gpu profilers 51 | if (gl) { 52 | const glFinish = async (t, activeAccums) => 53 | Promise.resolve(setTimeout(() => { 54 | gl.getError(); 55 | const dt = this.now() - t; 56 | activeAccums.forEach((active, i) => { 57 | if (active) this.gpuAccums[i] += dt; 58 | }); 59 | }, 0)); 60 | 61 | const addProfiler = (fn, self, target) => function() { 62 | const t = self.now(); 63 | fn.apply(target, arguments); 64 | if (self.trackGPU) self.finished.push(glFinish(t, self.activeAccums.slice(0))); 65 | }; 66 | 67 | ['drawArrays', 'drawElements', 'drawArraysInstanced', 68 | 'drawBuffers', 'drawElementsInstanced', 'drawRangeElements'] 69 | .forEach(fn => { if (gl[fn]) gl[fn] = addProfiler(gl[fn], this, gl); }); 70 | 71 | gl.getExtension = ((fn, self) => function() { 72 | let ext = fn.apply(gl, arguments); 73 | if (ext) { 74 | ['drawElementsInstancedANGLE', 'drawBuffersWEBGL'] 75 | .forEach(fn => { if (ext[fn]) ext[fn] = addProfiler(ext[fn], self, ext); }); 76 | } 77 | return ext; 78 | })(gl.getExtension, this); 79 | } 80 | 81 | // init ui and ui loggers 82 | if (!this.withoutUI) { 83 | if (!this.dom) this.dom = document.body; 84 | let elm = document.createElement('div'); 85 | elm.id = 'gl-bench'; 86 | this.dom.appendChild(elm); 87 | this.dom.insertAdjacentHTML('afterbegin', ''); 88 | this.dom = elm; 89 | this.dom.addEventListener('click', () => { 90 | this.trackGPU = !this.trackGPU; 91 | this.updateUI(); 92 | }); 93 | 94 | this.paramLogger = ((logger, dom, names) => { 95 | const classes = ['gl-cpu', 'gl-gpu', 'gl-mem', 'gl-fps', 'gl-gpu-svg', 'gl-chart']; 96 | const nodes = Object.assign({}, classes); 97 | classes.forEach(c => nodes[c] = dom.getElementsByClassName(c)); 98 | this.nodes = nodes; 99 | return (i, cpu, gpu, mem, fps, totalTime, frameId) => { 100 | nodes['gl-cpu'][i].style.strokeDasharray = (cpu * 0.27).toFixed(0) + ' 100'; 101 | nodes['gl-gpu'][i].style.strokeDasharray = (gpu * 0.27).toFixed(0) + ' 100'; 102 | nodes['gl-mem'][i].innerHTML = names[i] ? names[i] : (mem ? 'mem: ' + mem.toFixed(0) + 'mb' : ''); 103 | nodes['gl-fps'][i].innerHTML = fps.toFixed(0) + ' FPS'; 104 | logger(names[i], cpu, gpu, mem, fps, totalTime, frameId); 105 | } 106 | })(this.paramLogger, this.dom, this.names); 107 | 108 | this.chartLogger = ((logger, dom) => { 109 | let nodes = { 'gl-chart': dom.getElementsByClassName('gl-chart') }; 110 | return (i, chart, circularId) => { 111 | let points = ''; 112 | let len = chart.length; 113 | for (let i = 0; i < len; i++) { 114 | let id = (circularId + i + 1) % len; 115 | if (chart[id] != undefined) { 116 | points = points + ' ' + (55 * i / (len - 1)).toFixed(1) + ',' 117 | + (45 - chart[id] * 22 / 60 / this.detected).toFixed(1); 118 | } 119 | } 120 | nodes['gl-chart'][i].setAttribute('points', points); 121 | logger(this.names[i], chart, circularId); 122 | } 123 | })(this.chartLogger, this.dom); 124 | } 125 | } 126 | 127 | /** 128 | * Explicit UI add 129 | * @param { string | undefined } name 130 | */ 131 | addUI(name) { 132 | if (this.names.indexOf(name) == -1) { 133 | this.names.push(name); 134 | if (this.dom) { 135 | this.dom.insertAdjacentHTML('beforeend', this.svg); 136 | this.updateUI(); 137 | } 138 | this.cpuAccums.push(0); 139 | this.gpuAccums.push(0); 140 | this.activeAccums.push(false); 141 | } 142 | } 143 | 144 | /** 145 | * Increase frameID 146 | * @param { number | undefined } now 147 | */ 148 | nextFrame(now) { 149 | this.frameId++; 150 | const t = now ? now : this.now(); 151 | 152 | // params 153 | if (this.frameId <= 1) { 154 | this.paramFrame = this.frameId; 155 | this.paramTime = t; 156 | } else { 157 | let duration = t - this.paramTime; 158 | if (duration >= 1e3) { 159 | const frameCount = this.frameId - this.paramFrame; 160 | const fps = frameCount / duration * 1e3; 161 | for (let i = 0; i < this.names.length; i++) { 162 | const cpu = this.cpuAccums[i] / duration * 100, 163 | gpu = this.gpuAccums[i] / duration * 100, 164 | mem = (performance && performance.memory) ? performance.memory.usedJSHeapSize / (1 << 20) : 0; 165 | this.paramLogger(i, cpu, gpu, mem, fps, duration, frameCount); 166 | this.cpuAccums[i] = 0; 167 | Promise.all(this.finished).then(() => { 168 | this.gpuAccums[i] = 0; 169 | this.finished = []; 170 | }); 171 | } 172 | this.paramFrame = this.frameId; 173 | this.paramTime = t; 174 | } 175 | } 176 | 177 | // chart 178 | if (!this.detected || !this.chartFrame) { 179 | this.chartFrame = this.frameId; 180 | this.chartTime = t; 181 | this.circularId = 0; 182 | } else { 183 | let timespan = t - this.chartTime; 184 | let hz = this.chartHz * timespan / 1e3; 185 | while (--hz > 0 && this.detected) { 186 | const frameCount = this.frameId - this.chartFrame; 187 | const fps = frameCount / timespan * 1e3; 188 | this.chart[this.circularId % this.chartLen] = fps; 189 | for (let i = 0; i < this.names.length; i++) { 190 | this.chartLogger(i, this.chart, this.circularId); 191 | } 192 | this.circularId++; 193 | this.chartFrame = this.frameId; 194 | this.chartTime = t; 195 | } 196 | } 197 | } 198 | 199 | /** 200 | * Begin named measurement 201 | * @param { string | undefined } name 202 | */ 203 | begin(name) { 204 | this.updateAccums(name); 205 | } 206 | 207 | /** 208 | * End named measure 209 | * @param { string | undefined } name 210 | */ 211 | end(name) { 212 | this.updateAccums(name); 213 | } 214 | 215 | updateAccums(name) { 216 | let nameId = this.names.indexOf(name); 217 | if (nameId == -1) { 218 | nameId = this.names.length; 219 | this.addUI(name); 220 | } 221 | 222 | const t = this.now(); 223 | const dt = t - this.t0; 224 | for (let i = 0; i < nameId + 1; i++) { 225 | if (this.activeAccums[i]) { 226 | this.cpuAccums[i] += dt; 227 | } 228 | } this.activeAccums[nameId] = !this.activeAccums[nameId]; 229 | this.t0 = t; 230 | } 231 | 232 | } 233 | 234 | export default GLBench; 235 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2wtYmVuY2gubW9kdWxlLmpzIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXguanMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFVJU1ZHIGZyb20gJy4vdWkuc3ZnJztcbmltcG9ydCBVSUNTUyBmcm9tICcuL3VpLmNzcyc7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEdMQmVuY2gge1xuXG4gIC8qKiBHTEJlbmNoIGNvbnN0cnVjdG9yXG4gICAqIEBwYXJhbSB7IFdlYkdMUmVuZGVyaW5nQ29udGV4dCB8IFdlYkdMMlJlbmRlcmluZ0NvbnRleHQgfSBnbCBjb250ZXh0XG4gICAqIEBwYXJhbSB7IE9iamVjdCB8IHVuZGVmaW5lZCB9IHNldHRpbmdzIGFkZGl0aW9uYWwgc2V0dGluZ3NcbiAgICovXG4gIGNvbnN0cnVjdG9yKGdsLCBzZXR0aW5ncyA9IHt9KSB7XG4gICAgdGhpcy5jc3MgPSBVSUNTUztcbiAgICB0aGlzLnN2ZyA9IFVJU1ZHO1xuICAgIHRoaXMucGFyYW1Mb2dnZXIgPSAoKSA9PiB7fTtcbiAgICB0aGlzLmNoYXJ0TG9nZ2VyID0gKCkgPT4ge307XG4gICAgdGhpcy5jaGFydExlbiA9IDIwO1xuICAgIHRoaXMuY2hhcnRIeiA9IDIwO1xuXG4gICAgdGhpcy5uYW1lcyA9IFtdO1xuICAgIHRoaXMuY3B1QWNjdW1zID0gW107XG4gICAgdGhpcy5ncHVBY2N1bXMgPSBbXTsgIFxuICAgIHRoaXMuYWN0aXZlQWNjdW1zID0gW107XG4gICAgdGhpcy5jaGFydCA9IG5ldyBBcnJheSh0aGlzLmNoYXJ0TGVuKTtcbiAgICB0aGlzLm5vdyA9ICgpID0+IChwZXJmb3JtYW5jZSAmJiBwZXJmb3JtYW5jZS5ub3cpID8gcGVyZm9ybWFuY2Uubm93KCkgOiBEYXRlLm5vdygpO1xuICAgIHRoaXMudXBkYXRlVUkgPSAoKSA9PiB7XG4gICAgICBbXS5mb3JFYWNoLmNhbGwodGhpcy5ub2Rlc1snZ2wtZ3B1LXN2ZyddLCBub2RlID0+IHtcbiAgICAgICAgbm9kZS5zdHlsZS5kaXNwbGF5ID0gdGhpcy50cmFja0dQVSA/ICdpbmxpbmUnIDogJ25vbmUnO1xuICAgICAgfSlcbiAgICB9XG5cbiAgICBPYmplY3QuYXNzaWduKHRoaXMsIHNldHRpbmdzKTtcbiAgICB0aGlzLmRldGVjdGVkID0gMDtcbiAgICB0aGlzLmZpbmlzaGVkID0gW107XG4gICAgdGhpcy5pc0ZyYW1lYnVmZmVyID0gMDtcbiAgICB0aGlzLmZyYW1lSWQgPSAwO1xuXG4gICAgLy8gMTIwaHogZGV2aWNlIGRldGVjdGlvblxuICAgIGxldCByYWZJZCwgbiA9IDAsIHQwO1xuICAgIGxldCBsb29wID0gKHQpID0+IHtcbiAgICAgIGlmICgrK24gPCAyMCkge1xuICAgICAgICByYWZJZCA9IHJlcXVlc3RBbmltYXRpb25GcmFtZShsb29wKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMuZGV0ZWN0ZWQgPSBNYXRoLmNlaWwoMWUzICogbiAvICh0IC0gdDApIC8gNzApO1xuICAgICAgICBjYW5jZWxBbmltYXRpb25GcmFtZShyYWZJZCk7XG4gICAgICB9XG4gICAgICBpZiAoIXQwKSB0MCA9IHQ7XG4gICAgfVxuICAgIHJlcXVlc3RBbmltYXRpb25GcmFtZShsb29wKTtcblxuICAgIC8vIGF0dGFjaCBncHUgcHJvZmlsZXJzXG4gICAgaWYgKGdsKSB7XG4gICAgICBjb25zdCBnbEZpbmlzaCA9IGFzeW5jICh0LCBhY3RpdmVBY2N1bXMpID0+XG4gICAgICAgIFByb21pc2UucmVzb2x2ZShzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICBnbC5nZXRFcnJvcigpO1xuICAgICAgICAgIGNvbnN0IGR0ID0gdGhpcy5ub3coKSAtIHQ7XG4gICAgICAgICAgYWN0aXZlQWNjdW1zLmZvckVhY2goKGFjdGl2ZSwgaSkgPT4ge1xuICAgICAgICAgICAgaWYgKGFjdGl2ZSkgdGhpcy5ncHVBY2N1bXNbaV0gKz0gZHQ7XG4gICAgICAgICAgfSk7XG4gICAgICAgIH0sIDApKTtcblxuICAgICAgY29uc3QgYWRkUHJvZmlsZXIgPSAoZm4sIHNlbGYsIHRhcmdldCkgPT4gZnVuY3Rpb24oKSB7XG4gICAgICAgIGNvbnN0IHQgPSBzZWxmLm5vdygpO1xuICAgICAgICBmbi5hcHBseSh0YXJnZXQsIGFyZ3VtZW50cyk7XG4gICAgICAgIGlmIChzZWxmLnRyYWNrR1BVKSBzZWxmLmZpbmlzaGVkLnB1c2goZ2xGaW5pc2godCwgc2VsZi5hY3RpdmVBY2N1bXMuc2xpY2UoMCkpKTtcbiAgICAgIH07XG5cbiAgICAgIFsnZHJhd0FycmF5cycsICdkcmF3RWxlbWVudHMnLCAnZHJhd0FycmF5c0luc3RhbmNlZCcsXG4gICAgICAgICdkcmF3QnVmZmVycycsICdkcmF3RWxlbWVudHNJbnN0YW5jZWQnLCAnZHJhd1JhbmdlRWxlbWVudHMnXVxuICAgICAgICAuZm9yRWFjaChmbiA9PiB7IGlmIChnbFtmbl0pIGdsW2ZuXSA9IGFkZFByb2ZpbGVyKGdsW2ZuXSwgdGhpcywgZ2wpIH0pO1xuXG4gICAgICBnbC5nZXRFeHRlbnNpb24gPSAoKGZuLCBzZWxmKSA9PiBmdW5jdGlvbigpIHtcbiAgICAgICAgbGV0IGV4dCA9IGZuLmFwcGx5KGdsLCBhcmd1bWVudHMpO1xuICAgICAgICBpZiAoZXh0KSB7XG4gICAgICAgICAgWydkcmF3RWxlbWVudHNJbnN0YW5jZWRBTkdMRScsICdkcmF3QnVmZmVyc1dFQkdMJ11cbiAgICAgICAgICAgIC5mb3JFYWNoKGZuID0+IHsgaWYgKGV4dFtmbl0pIGV4dFtmbl0gPSBhZGRQcm9maWxlcihleHRbZm5dLCBzZWxmLCBleHQpIH0pO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBleHQ7XG4gICAgICB9KShnbC5nZXRFeHRlbnNpb24sIHRoaXMpO1xuICAgIH1cblxuICAgIC8vIGluaXQgdWkgYW5kIHVpIGxvZ2dlcnNcbiAgICBpZiAoIXRoaXMud2l0aG91dFVJKSB7XG4gICAgICBpZiAoIXRoaXMuZG9tKSB0aGlzLmRvbSA9IGRvY3VtZW50LmJvZHk7XG4gICAgICBsZXQgZWxtID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XG4gICAgICBlbG0uaWQgPSAnZ2wtYmVuY2gnO1xuICAgICAgdGhpcy5kb20uYXBwZW5kQ2hpbGQoZWxtKTtcbiAgICAgIHRoaXMuZG9tLmluc2VydEFkamFjZW50SFRNTCgnYWZ0ZXJiZWdpbicsICc8c3R5bGUgaWQ9XCJnbC1iZW5jaC1zdHlsZVwiPicgKyB0aGlzLmNzcyArICc8L3N0eWxlPicpO1xuICAgICAgdGhpcy5kb20gPSBlbG07XG4gICAgICB0aGlzLmRvbS5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IHtcbiAgICAgICAgdGhpcy50cmFja0dQVSA9ICF0aGlzLnRyYWNrR1BVO1xuICAgICAgICB0aGlzLnVwZGF0ZVVJKCk7XG4gICAgICB9KTtcblxuICAgICAgdGhpcy5wYXJhbUxvZ2dlciA9ICgobG9nZ2VyLCBkb20sIG5hbWVzKSA9PiB7XG4gICAgICAgIGNvbnN0IGNsYXNzZXMgPSBbJ2dsLWNwdScsICdnbC1ncHUnLCAnZ2wtbWVtJywgJ2dsLWZwcycsICdnbC1ncHUtc3ZnJywgJ2dsLWNoYXJ0J107XG4gICAgICAgIGNvbnN0IG5vZGVzID0gT2JqZWN0LmFzc2lnbih7fSwgY2xhc3Nlcyk7XG4gICAgICAgIGNsYXNzZXMuZm9yRWFjaChjID0+IG5vZGVzW2NdID0gZG9tLmdldEVsZW1lbnRzQnlDbGFzc05hbWUoYykpO1xuICAgICAgICB0aGlzLm5vZGVzID0gbm9kZXM7XG4gICAgICAgIHJldHVybiAoaSwgY3B1LCBncHUsIG1lbSwgZnBzLCB0b3RhbFRpbWUsIGZyYW1lSWQpID0+IHtcbiAgICAgICAgICBub2Rlc1snZ2wtY3B1J11baV0uc3R5bGUuc3Ryb2tlRGFzaGFycmF5ID0gKGNwdSAqIDAuMjcpLnRvRml4ZWQoMCkgKyAnIDEwMCc7XG4gICAgICAgICAgbm9kZXNbJ2dsLWdwdSddW2ldLnN0eWxlLnN0cm9rZURhc2hhcnJheSA9IChncHUgKiAwLjI3KS50b0ZpeGVkKDApICsgJyAxMDAnO1xuICAgICAgICAgIG5vZGVzWydnbC1tZW0nXVtpXS5pbm5lckhUTUwgPSBuYW1lc1tpXSA/IG5hbWVzW2ldIDogKG1lbSA/ICdtZW06ICcgKyBtZW0udG9GaXhlZCgwKSArICdtYicgOiAnJyk7XG4gICAgICAgICAgbm9kZXNbJ2dsLWZwcyddW2ldLmlubmVySFRNTCA9IGZwcy50b0ZpeGVkKDApICsgJyBGUFMnO1xuICAgICAgICAgIGxvZ2dlcihuYW1lc1tpXSwgY3B1LCBncHUsIG1lbSwgZnBzLCB0b3RhbFRpbWUsIGZyYW1lSWQpO1xuICAgICAgICB9XG4gICAgICB9KSh0aGlzLnBhcmFtTG9nZ2VyLCB0aGlzLmRvbSwgdGhpcy5uYW1lcyk7XG5cbiAgICAgIHRoaXMuY2hhcnRMb2dnZXIgPSAoKGxvZ2dlciwgZG9tKSA9PiB7XG4gICAgICAgIGxldCBub2RlcyA9IHsgJ2dsLWNoYXJ0JzogZG9tLmdldEVsZW1lbnRzQnlDbGFzc05hbWUoJ2dsLWNoYXJ0JykgfTtcbiAgICAgICAgcmV0dXJuIChpLCBjaGFydCwgY2lyY3VsYXJJZCkgPT4ge1xuICAgICAgICAgIGxldCBwb2ludHMgPSAnJztcbiAgICAgICAgICBsZXQgbGVuID0gY2hhcnQubGVuZ3RoO1xuICAgICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgIGxldCBpZCA9IChjaXJjdWxhcklkICsgaSArIDEpICUgbGVuO1xuICAgICAgICAgICAgaWYgKGNoYXJ0W2lkXSAhPSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgcG9pbnRzID0gcG9pbnRzICsgJyAnICsgKDU1ICogaSAvIChsZW4gLSAxKSkudG9GaXhlZCgxKSArICcsJ1xuICAgICAgICAgICAgICAgICsgKDQ1IC0gY2hhcnRbaWRdICogMjIgLyA2MCAvIHRoaXMuZGV0ZWN0ZWQpLnRvRml4ZWQoMSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIG5vZGVzWydnbC1jaGFydCddW2ldLnNldEF0dHJpYnV0ZSgncG9pbnRzJywgcG9pbnRzKTtcbiAgICAgICAgICBsb2dnZXIodGhpcy5uYW1lc1tpXSwgY2hhcnQsIGNpcmN1bGFySWQpO1xuICAgICAgICB9XG4gICAgICB9KSh0aGlzLmNoYXJ0TG9nZ2VyLCB0aGlzLmRvbSk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEV4cGxpY2l0IFVJIGFkZFxuICAgKiBAcGFyYW0geyBzdHJpbmcgfCB1bmRlZmluZWQgfSBuYW1lIFxuICAgKi9cbiAgYWRkVUkobmFtZSkge1xuICAgIGlmICh0aGlzLm5hbWVzLmluZGV4T2YobmFtZSkgPT0gLTEpIHtcbiAgICAgIHRoaXMubmFtZXMucHVzaChuYW1lKTtcbiAgICAgIGlmICh0aGlzLmRvbSkge1xuICAgICAgICB0aGlzLmRvbS5pbnNlcnRBZGphY2VudEhUTUwoJ2JlZm9yZWVuZCcsIHRoaXMuc3ZnKTtcbiAgICAgICAgdGhpcy51cGRhdGVVSSgpO1xuICAgICAgfVxuICAgICAgdGhpcy5jcHVBY2N1bXMucHVzaCgwKTtcbiAgICAgIHRoaXMuZ3B1QWNjdW1zLnB1c2goMCk7XG4gICAgICB0aGlzLmFjdGl2ZUFjY3Vtcy5wdXNoKGZhbHNlKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogSW5jcmVhc2UgZnJhbWVJRFxuICAgKiBAcGFyYW0geyBudW1iZXIgfCB1bmRlZmluZWQgfSBub3dcbiAgICovXG4gIG5leHRGcmFtZShub3cpIHtcbiAgICB0aGlzLmZyYW1lSWQrKztcbiAgICBjb25zdCB0ID0gbm93ID8gbm93IDogdGhpcy5ub3coKTtcblxuICAgIC8vIHBhcmFtc1xuICAgIGlmICh0aGlzLmZyYW1lSWQgPD0gMSkge1xuICAgICAgdGhpcy5wYXJhbUZyYW1lID0gdGhpcy5mcmFtZUlkO1xuICAgICAgdGhpcy5wYXJhbVRpbWUgPSB0O1xuICAgIH0gZWxzZSB7XG4gICAgICBsZXQgZHVyYXRpb24gPSB0IC0gdGhpcy5wYXJhbVRpbWU7XG4gICAgICBpZiAoZHVyYXRpb24gPj0gMWUzKSB7XG4gICAgICAgIGNvbnN0IGZyYW1lQ291bnQgPSB0aGlzLmZyYW1lSWQgLSB0aGlzLnBhcmFtRnJhbWU7XG4gICAgICAgIGNvbnN0IGZwcyA9IGZyYW1lQ291bnQgLyBkdXJhdGlvbiAqIDFlMztcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0aGlzLm5hbWVzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgY29uc3QgY3B1ID0gdGhpcy5jcHVBY2N1bXNbaV0gLyBkdXJhdGlvbiAqIDEwMCxcbiAgICAgICAgICAgIGdwdSA9IHRoaXMuZ3B1QWNjdW1zW2ldIC8gZHVyYXRpb24gKiAxMDAsXG4gICAgICAgICAgICBtZW0gPSAocGVyZm9ybWFuY2UgJiYgcGVyZm9ybWFuY2UubWVtb3J5KSA/IHBlcmZvcm1hbmNlLm1lbW9yeS51c2VkSlNIZWFwU2l6ZSAvICgxIDw8IDIwKSA6IDA7XG4gICAgICAgICAgdGhpcy5wYXJhbUxvZ2dlcihpLCBjcHUsIGdwdSwgbWVtLCBmcHMsIGR1cmF0aW9uLCBmcmFtZUNvdW50KTtcbiAgICAgICAgICB0aGlzLmNwdUFjY3Vtc1tpXSA9IDA7XG4gICAgICAgICAgUHJvbWlzZS5hbGwodGhpcy5maW5pc2hlZCkudGhlbigoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLmdwdUFjY3Vtc1tpXSA9IDA7XG4gICAgICAgICAgICB0aGlzLmZpbmlzaGVkID0gW107XG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5wYXJhbUZyYW1lID0gdGhpcy5mcmFtZUlkO1xuICAgICAgICB0aGlzLnBhcmFtVGltZSA9IHQ7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gY2hhcnRcbiAgICBpZiAoIXRoaXMuZGV0ZWN0ZWQgfHwgIXRoaXMuY2hhcnRGcmFtZSkge1xuICAgICAgdGhpcy5jaGFydEZyYW1lID0gdGhpcy5mcmFtZUlkO1xuICAgICAgdGhpcy5jaGFydFRpbWUgPSB0O1xuICAgICAgdGhpcy5jaXJjdWxhcklkID0gMDtcbiAgICB9IGVsc2Uge1xuICAgICAgbGV0IHRpbWVzcGFuID0gdCAtIHRoaXMuY2hhcnRUaW1lO1xuICAgICAgbGV0IGh6ID0gdGhpcy5jaGFydEh6ICogdGltZXNwYW4gLyAxZTM7XG4gICAgICB3aGlsZSAoLS1oeiA+IDAgJiYgdGhpcy5kZXRlY3RlZCkge1xuICAgICAgICBjb25zdCBmcmFtZUNvdW50ID0gdGhpcy5mcmFtZUlkIC0gdGhpcy5jaGFydEZyYW1lO1xuICAgICAgICBjb25zdCBmcHMgPSBmcmFtZUNvdW50IC8gdGltZXNwYW4gKiAxZTM7XG4gICAgICAgIHRoaXMuY2hhcnRbdGhpcy5jaXJjdWxhcklkICUgdGhpcy5jaGFydExlbl0gPSBmcHM7XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgdGhpcy5uYW1lcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgIHRoaXMuY2hhcnRMb2dnZXIoaSwgdGhpcy5jaGFydCwgdGhpcy5jaXJjdWxhcklkKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLmNpcmN1bGFySWQrKztcbiAgICAgICAgdGhpcy5jaGFydEZyYW1lID0gdGhpcy5mcmFtZUlkO1xuICAgICAgICB0aGlzLmNoYXJ0VGltZSA9IHQ7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEJlZ2luIG5hbWVkIG1lYXN1cmVtZW50XG4gICAqIEBwYXJhbSB7IHN0cmluZyB8IHVuZGVmaW5lZCB9IG5hbWVcbiAgICovXG4gIGJlZ2luKG5hbWUpIHtcbiAgICB0aGlzLnVwZGF0ZUFjY3VtcyhuYW1lKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBFbmQgbmFtZWQgbWVhc3VyZVxuICAgKiBAcGFyYW0geyBzdHJpbmcgfCB1bmRlZmluZWQgfSBuYW1lXG4gICAqL1xuICBlbmQobmFtZSkge1xuICAgIHRoaXMudXBkYXRlQWNjdW1zKG5hbWUpO1xuICB9XG5cbiAgdXBkYXRlQWNjdW1zKG5hbWUpIHtcbiAgICBsZXQgbmFtZUlkID0gdGhpcy5uYW1lcy5pbmRleE9mKG5hbWUpO1xuICAgIGlmIChuYW1lSWQgPT0gLTEpIHtcbiAgICAgIG5hbWVJZCA9IHRoaXMubmFtZXMubGVuZ3RoO1xuICAgICAgdGhpcy5hZGRVSShuYW1lKTtcbiAgICB9XG5cbiAgICBjb25zdCB0ID0gdGhpcy5ub3coKTtcbiAgICBjb25zdCBkdCA9IHQgLSB0aGlzLnQwO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbmFtZUlkICsgMTsgaSsrKSB7XG4gICAgICBpZiAodGhpcy5hY3RpdmVBY2N1bXNbaV0pIHtcbiAgICAgICAgdGhpcy5jcHVBY2N1bXNbaV0gKz0gZHQ7XG4gICAgICB9XG4gICAgfTtcbiAgICB0aGlzLmFjdGl2ZUFjY3Vtc1tuYW1lSWRdID0gIXRoaXMuYWN0aXZlQWNjdW1zW25hbWVJZF07XG4gICAgdGhpcy50MCA9IHQ7XG4gIH1cblxufVxuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7QUFHZSxNQUFNLE9BQU8sQ0FBQzs7Ozs7O0VBTTNCLFdBQVcsQ0FBQyxFQUFFLEVBQUUsUUFBUSxHQUFHLEVBQUUsRUFBRTtJQUM3QixJQUFJLENBQUMsR0FBRyxHQUFHLEtBQUssQ0FBQztJQUNqQixJQUFJLENBQUMsR0FBRyxHQUFHLEtBQUssQ0FBQztJQUNqQixJQUFJLENBQUMsV0FBVyxHQUFHLE1BQU0sRUFBRSxDQUFDO0lBQzVCLElBQUksQ0FBQyxXQUFXLEdBQUcsTUFBTSxFQUFFLENBQUM7SUFDNUIsSUFBSSxDQUFDLFFBQVEsR0FBRyxFQUFFLENBQUM7SUFDbkIsSUFBSSxDQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7O0lBRWxCLElBQUksQ0FBQyxLQUFLLEdBQUcsRUFBRSxDQUFDO0lBQ2hCLElBQUksQ0FBQyxTQUFTLEdBQUcsRUFBRSxDQUFDO0lBQ3BCLElBQUksQ0FBQyxTQUFTLEdBQUcsRUFBRSxDQUFDO0lBQ3BCLElBQUksQ0FBQyxZQUFZLEdBQUcsRUFBRSxDQUFDO0lBQ3ZCLElBQUksQ0FBQyxLQUFLLEdBQUcsSUFBSSxLQUFLLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3RDLElBQUksQ0FBQyxH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LElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3RCLElBQUksQ0FBQyxRQUFRLEdBQUcsRUFBRSxDQUFDO1dBQ3BCLENBQUMsQ0FBQztTQUNKO1FBQ0QsSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDO1FBQy9CLElBQUksQ0FBQyxTQUFTLEdBQUcsQ0FBQyxDQUFDO09BQ3BCO0tBQ0Y7OztJQUdELElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRTtNQUN0QyxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUM7TUFDL0IsSUFBSSxDQUFDLFNBQVMsR0FBRyxDQUFDLENBQUM7TUFDbkIsSUFBSSxDQUFDLFVBQVUsR0FBRyxDQUFDLENBQUM7S0FDckIsTUFBTTtNQUNMLElBQUksUUFBUSxHQUFHLENBQUMsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDO01BQ2xDLElBQUksRUFBRSxHQUFHLElBQUksQ0FBQyxPQUFPLEdBQUcsUUFBUSxHQUFHLEdBQUcsQ0FBQztNQUN2QyxPQUFPLEVBQUUsRUFBRSxHQUFHLENBQUMsSUFBSSxJQUFJLENBQUMsUUFBUSxFQUFFO1FBQ2hDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQztRQUNsRCxNQUFNLEdBQUcsR0FBRyxVQUFVLEdBQUcsUUFBUSxHQUFHLEdBQUcsQ0FBQztRQUN4QyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxVQUFVLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLEdBQUcsQ0FBQztRQUNsRCxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUU7VUFDMUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDLEVBQUUsSUFBSSxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7U0FDbEQ7UUFDRCxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDbEIsSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDO1FBQy9CLElBQUksQ0FBQyxTQUFTLEdBQUcsQ0FBQyxDQUFDO09BQ3BCO0tBQ0Y7R0FDRjs7Ozs7O0VBTUQsS0FBSyxDQUFDLElBQUksRUFBRTtJQUNWLElBQUksQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUM7R0FDekI7Ozs7OztFQU1ELEdBQUcsQ0FBQyxJQUFJLEVBQUU7SUFDUixJQUFJLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDO0dBQ3pCOztFQUVELFlBQVksQ0FBQyxJQUFJLEVBQUU7SUFDakIsSUFBSSxNQUFNLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDdEMsSUFBSSxNQUFNLElBQUksQ0FBQyxDQUFDLEVBQUU7TUFDaEIsTUFBTSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDO01BQzNCLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7S0FDbEI7O0lBRUQsTUFBTSxDQUFDLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO0lBQ3JCLE1BQU0sRUFBRSxHQUFHLENBQUMsR0FBRyxJQUFJLENBQUMsRUFBRSxDQUFDO0lBQ3ZCLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFO01BQ25DLElBQUksSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsRUFBRTtRQUN4QixJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztPQUN6QjtLQUNGLEFBQ0wsSUFBSSxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUN2RCxJQUFJLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQztHQUNiOztDQUVGOzs7OyJ9 236 | -------------------------------------------------------------------------------- /examples/common/maingui.js: -------------------------------------------------------------------------------- 1 | let settings = { 2 | count: 100000, 3 | width: window.innerWidth, 4 | height: window.innerHeight, 5 | 'unit testing': () => window.location.replace("../test/unit-test.html"), 6 | 'stress testing': () => window.location.replace("../test/stress-test.html"), 7 | 'webgl': () => window.location.replace("../examples/webgl.html"), 8 | 'webgl2': () => window.location.replace("../examples/webgl2.html"), 9 | 'new loggers': () => window.location.replace("../examples/new-loggers.html"), 10 | 'named measuring': () => window.location.replace("../examples/named-measuring.html"), 11 | 'webgl instancing': () => window.location.replace("../examples/instanced-arrays.html"), 12 | 'webgl framebuffer': () => window.location.replace("../examples/framebuffer.html"), 13 | 'webgl float tex': () => window.location.replace("../examples/float-textures.html"), 14 | 'webgl depth tex': () => window.location.replace("../examples/depth-textures.html"), 15 | 'web workers': () => window.location.replace("../examples/web-workers.html"), 16 | }; 17 | let gui = new dat.GUI(); 18 | let unitStresTest = gui.addFolder('Unit / Stress'); 19 | unitStresTest.add(settings, 'unit testing'); 20 | unitStresTest.add(settings, 'stress testing'); 21 | unitStresTest.open(); 22 | let e2eTest = gui.addFolder('E2E testing'); 23 | e2eTest.add(settings, 'webgl'); 24 | e2eTest.add(settings, 'webgl2'); 25 | e2eTest.add(settings, 'new loggers'); 26 | e2eTest.add(settings, 'named measuring'); 27 | e2eTest.add(settings, 'webgl instancing'); 28 | e2eTest.add(settings, 'webgl framebuffer'); 29 | e2eTest.add(settings, 'webgl float tex'); 30 | e2eTest.add(settings, 'webgl depth tex'); 31 | e2eTest.add(settings, 'web workers'); 32 | e2eTest.open(); 33 | let countControl = gui.add(settings, 'count', 10000, 30000000); 34 | let count = settings.count, 35 | width = settings.innerWidth, 36 | height = settings.innerHeight; -------------------------------------------------------------------------------- /examples/common/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | background-color: black; 4 | position: relative; 5 | } 6 | 7 | canvas { 8 | display: block; 9 | position: absolute; 10 | z-index: -1; 11 | } 12 | 13 | h1 { 14 | position: absolute; 15 | color: white; 16 | top: 50%; 17 | left: 50%; 18 | transform: translate(-50%, 0%); 19 | z-index: 10; 20 | } 21 | 22 | p { 23 | position: absolute; 24 | margin-top: 65px; 25 | color: white; 26 | top: 50%; 27 | left: 50%; 28 | transform: translate(-50%, 0%); 29 | z-index: 10; 30 | } 31 | 32 | #report { 33 | position: absolute; 34 | } 35 | 36 | .star { 37 | position: fixed; 38 | bottom: 0; 39 | right: 0; 40 | transform: scale(0.8); 41 | } -------------------------------------------------------------------------------- /examples/common/worker.js: -------------------------------------------------------------------------------- 1 | importScripts('../../dist/gl-bench.js') 2 | importScripts('https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js'); 3 | 4 | let canvas; 5 | let context; 6 | let settings; 7 | let bench; 8 | let scene; 9 | let camera; 10 | let renderer; 11 | 12 | self.onmessage = function(e) { 13 | if (e.data.msg == 'init') { 14 | canvas = e.data.canvas; 15 | settings = e.data.settings; 16 | setup(); 17 | self.requestAnimationFrame(draw); 18 | } else if (e.data.msg == 'settings') { 19 | settings = e.data.settings; 20 | } else if (e.data.msg == 'withoutGPU') { 21 | bench.withoutGPU != bench.withoutGPU; 22 | } 23 | } 24 | 25 | function setup() { 26 | setupScene(); 27 | setupRenderer(); 28 | setupParticles(); 29 | } 30 | 31 | function setupScene() { 32 | scene = new THREE.Scene(); 33 | let ratio = settings.width / settings.height; 34 | camera = new THREE.PerspectiveCamera(70, ratio, 1, 10000); 35 | } 36 | 37 | function setupRenderer() { 38 | context = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); 39 | renderer = new THREE.WebGLRenderer( { canvas: canvas, context: context } ); 40 | 41 | let maxI = -1; 42 | function name(i) { 43 | if (maxI < i) { 44 | self.postMessage({ msg: 'initUI', name: bench.names[i] }); 45 | maxI = i; 46 | } 47 | return bench.names[i]; 48 | } 49 | 50 | //GLBench initialization 51 | bench = new GLBench(renderer.getContext(), { 52 | withoutUI: true, 53 | trackGPU: true, 54 | paramLogger: (i, cpu, gpu, mem, fps) => { 55 | self.postMessage({ msg: 'paramLogger', name: name(i), i, cpu, gpu, mem, fps}) 56 | }, 57 | chartLogger: (i, chart, circularId) => { 58 | self.postMessage({ msg: 'chartLogger', name: name(i), i, chart, circularId }); 59 | } 60 | }); 61 | } 62 | 63 | function setupParticles() { 64 | let geometry = new THREE.Geometry(); 65 | addLorenzVertices(geometry); 66 | let color = new THREE.Color(0xbb00ff); 67 | for (let i = 0; i < settings.count; i++) { 68 | geometry.colors.push(color); 69 | } 70 | let material = new THREE.PointsMaterial({ size: 1, vertexColors: THREE.VertexColors, depthTest: false, opacity: 0.3, sizeAttenuation: false, transparent: true }); 71 | let mesh = new THREE.Points(geometry, material); 72 | scene.add(mesh); 73 | } 74 | 75 | function addLorenzVertices(geometry) { 76 | let x0 = 0.1; 77 | let y0 = 0; 78 | let z0 = 0; 79 | let x1, y1, z1; 80 | let h = 0.001; 81 | let a = 10.0; 82 | let b = 28.0; 83 | let c = 8.0 / 3.0; 84 | 85 | for (let i = 0; i < settings.count; i++) { 86 | x1 = x0 + h * a * (y0 - x0); 87 | y1 = y0 + h * (x0 * (b - z0) - y0); 88 | z1 = z0 + h * (x0 * y0 - c * z0); 89 | x0 = x1; 90 | y0 = y1; 91 | z0 = z1; 92 | let vertex = new THREE.Vector3( 93 | x0, 94 | y0, 95 | z0-24); 96 | geometry.vertices.push(vertex); 97 | } 98 | } 99 | 100 | let width, height; 101 | function updateRenderer() { 102 | if (width != settings.width || height != settings.height) { 103 | width = settings.width; 104 | height = settings.height; 105 | camera.aspect = width / height; 106 | camera.updateProjectionMatrix(); 107 | renderer.setSize( width, height, false ); 108 | } 109 | } 110 | 111 | function movePosition(position, phase) { 112 | let r = 40; 113 | let θ = new Date() * 0.0005 + 10; 114 | let φ = new Date() * 0.0005; 115 | let x = r * Math.sin(φ + phase) * Math.cos(θ + phase); 116 | let y = r * Math.sin(φ + phase) * Math.sin(θ + phase); 117 | let z = r * Math.cos(φ + phase); 118 | position.set(x, y, z); 119 | } 120 | 121 | let count; 122 | function heavyCpuUpdate() { 123 | if (count != settings.count) { 124 | scene.remove(scene.children[0]); 125 | setupParticles(); 126 | count = settings.count; 127 | } 128 | } 129 | 130 | function draw(now) { 131 | bench.begin('in worker'); 132 | movePosition(camera.position, 0); 133 | camera.lookAt(scene.position); 134 | heavyCpuUpdate(); 135 | updateRenderer(); 136 | renderer.render(scene, camera); 137 | bench.end('in worker'); 138 | 139 | bench.nextFrame(now); 140 | self.requestAnimationFrame(draw); 141 | } -------------------------------------------------------------------------------- /examples/depth-textures.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | E2E Testing - Depth texture support 7 | 8 | 9 | 10 | 11 | 12 |

Depth texture support

13 |

Three.js by @mattdesl

14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 50 | 51 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /examples/float-textures.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | E2E Testing - Float texture support 7 | 8 | 9 | 10 | 11 | 12 |

Float texture support

13 |

Three.js by @nraynaud

14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 43 | 44 | 51 | 52 | 149 | 150 | -------------------------------------------------------------------------------- /examples/framebuffer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | E2E Testing - With ramebuffer 7 | 8 | 9 | 10 | 11 | 12 |

With framebuffer

13 |

Three.js by @Mugen87

14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /examples/instanced-arrays.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | E2E Testing - Instanced arrays 7 | 8 | 9 | 10 | 11 | 12 |

Instanced arrays

13 |

Three.js example by @mrdoob

14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /examples/named-measuring.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | E2E Testing - Named measuring 7 | 8 | 9 | 10 | 11 | 12 |

Named measuring

13 |

Lorenz Attractor by @DonKarlssonSan

14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 150 | 151 | -------------------------------------------------------------------------------- /examples/new-loggers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | E2E Testing - New loggers 7 | 8 | 9 | 10 | 11 | 12 |

New loggers

13 |

Lorenz Attractor by @DonKarlssonSan

14 |
15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 164 | 165 | -------------------------------------------------------------------------------- /examples/themes/behance.js: -------------------------------------------------------------------------------- 1 | let CSS = ` 2 | #gl-bench { 3 | position:absolute; 4 | left:0; 5 | top:0; 6 | z-index:1000; 7 | -webkit-user-select: none; 8 | -moz-user-select: none; 9 | user-select: none; 10 | } 11 | 12 | #gl-bench div { 13 | position: relative; 14 | display: block; 15 | margin: 5px 0 0 5px; 16 | padding: 0 6px; 17 | background: #1010f0; 18 | border-radius: 19px; 19 | cursor: pointer; 20 | opacity: 0.9; 21 | } 22 | 23 | #gl-bench svg { 24 | height: 60px; 25 | fill: #dff; 26 | } 27 | 28 | #gl-bench rect { 29 | fill: #5460F5; 30 | } 31 | 32 | #gl-bench text { 33 | font-size: 12px; 34 | font-family: Helvetica,Arial,sans-serif; 35 | font-weight: 700; 36 | dominant-baseline: middle; 37 | text-anchor: middle; 38 | fill: #eff; 39 | } 40 | 41 | #gl-bench .gl-mem { 42 | font-size: 9px; 43 | } 44 | 45 | #gl-bench line { 46 | stroke-width: 5; 47 | stroke: #eff; 48 | stroke-linecap: round; 49 | } 50 | 51 | #gl-bench .opacity { 52 | stroke: #5460F5; 53 | } 54 | 55 | #gl-bench polyline { 56 | fill: none; 57 | stroke: #dff; 58 | stroke-linecap: round; 59 | stroke-linejoin: round; 60 | stroke-width: 2.5; 61 | }`; 62 | 63 | let SVG = ` 64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 00 FPS 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
`; -------------------------------------------------------------------------------- /examples/themes/oscilloscope.js: -------------------------------------------------------------------------------- 1 | let CSS = ` 2 | #gl-bench { 3 | position:absolute; 4 | left:0; 5 | top:0; 6 | z-index:1000; 7 | -webkit-user-select: none; 8 | -moz-user-select: none; 9 | user-select: none; 10 | } 11 | 12 | #gl-bench div { 13 | position: relative; 14 | display: block; 15 | margin: 5px; 16 | padding: 0 7px 0 10px; 17 | background: #bdbdbd; 18 | border-radius: 15px; 19 | cursor: pointer; 20 | opacity: 0.9; 21 | } 22 | 23 | #gl-bench svg { 24 | height: 60px; 25 | margin: 0 -1px; 26 | } 27 | 28 | #gl-bench text { 29 | font-size: 12px; 30 | font-family: Helvetica,Arial,sans-serif; 31 | font-weight: 700; 32 | dominant-baseline: middle; 33 | text-anchor: middle; 34 | } 35 | 36 | #gl-bench .gl-mem { 37 | font-size: 9px; 38 | } 39 | 40 | #gl-bench line { 41 | stroke-width: 5; 42 | stroke: #222; 43 | stroke-linecap: round; 44 | } 45 | 46 | #gl-bench polyline { 47 | fill: none; 48 | stroke: #8db; 49 | stroke-linecap: round; 50 | stroke-linejoin: round; 51 | stroke-width: 2; 52 | } 53 | 54 | #gl-bench rect { 55 | fill: #1e5e6c; 56 | } 57 | 58 | #gl-bench .opacity { 59 | stroke: #888; 60 | } 61 | `; 62 | -------------------------------------------------------------------------------- /examples/web-workers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | E2E Testing - Web Worker support 7 | 8 | 9 | 10 | 11 | 12 |

Web Worker support

13 |

Lorenz Attractor by @DonKarlssonSan

14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 68 | 69 | -------------------------------------------------------------------------------- /examples/webgl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | E2E Testing - WebGL support 7 | 8 | 9 | 10 | 11 | 12 |

WebGL support

13 |

Lorenz Attractor by @DonKarlssonSan

14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 141 | 142 | -------------------------------------------------------------------------------- /examples/webgl2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | E2E Testing - WebGL2 support 7 | 8 | 9 | 10 | 11 | 12 |

WebGL2 support

13 |

Lorenz Attractor by @DonKarlssonSan

14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 138 | 139 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Unit testing 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gl-bench", 3 | "version": "1.0.47", 4 | "description": "WebGL performance monitor that showing percentage of GPU/CPU load", 5 | "main": "./dist/gl-bench.js", 6 | "browser": "./dist/gl-bench.min.js", 7 | "module": "./dist/gl-bench.module.js", 8 | "files": [ 9 | "dist" 10 | ], 11 | "scripts": { 12 | "build": "rollup -c", 13 | "dev": "npm run build && concurrently \"rollup -cw\" \"npm:http\"", 14 | "test": "node -r esm ./test/puppeteer.js", 15 | "http": "servez -p 1234", 16 | "cov": "sed -i 's/.nyc_output\\/js/dist/g' .nyc_output/out.json && nyc report --reporter=lcov", 17 | "ci": "CI=true npm run http & npm run test && npm run cov; pkill http" 18 | }, 19 | "devDependencies": { 20 | "@ampproject/rollup-plugin-closure-compiler": "0.27.0", 21 | "codecov": "3.8.3", 22 | "concurrently": "9.1.0", 23 | "esm": "^3.2.25", 24 | "servez": "^1.12.0", 25 | "nyc": "^15.0.0", 26 | "puppeteer": "23.10.4", 27 | "puppeteer-to-istanbul": "1.4.0", 28 | "rollup": "2.79.2", 29 | "rollup-plugin-modify": "3.0.0", 30 | "rollup-plugin-string": "3.0.0", 31 | "zora": "5.2.0" 32 | }, 33 | "author": "munrocket", 34 | "license": "MIT", 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/munrocket/gl-bench.git" 38 | }, 39 | "nyc": { 40 | "include": "**/gl-bench**" 41 | }, 42 | "keywords": [ 43 | "monitor", 44 | "benchmark", 45 | "profiling", 46 | "performance", 47 | "webgl", 48 | "webgl2", 49 | "stats.js", 50 | "pixi", 51 | "three", 52 | "babylon", 53 | "regl", 54 | "cesium" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import closure from '@ampproject/rollup-plugin-closure-compiler'; 2 | import pkg from './package.json'; 3 | import { string } from 'rollup-plugin-string'; 4 | import modify from 'rollup-plugin-modify'; 5 | 6 | export default [ 7 | { 8 | input: 'src/index', 9 | output: [ 10 | { file: pkg.main, name: 'GLBench', format: 'umd' }, 11 | { file: pkg.module, name: 'GLBench', format: 'module', sourcemap: 'inline' }, 12 | ], 13 | plugins: [ string({ include: ['**/*.svg', '**/*.css'] }) ] 14 | }, 15 | { 16 | input: 'src/index', 17 | output: { file: pkg.browser, name: 'GLBench', format: 'iife' }, 18 | plugins: [ 19 | string({ include: ['**/*.svg', '**/*.css'] }), 20 | modify({ find: /\\n+\s*/g, replace: ' '}), 21 | closure() 22 | ] 23 | } 24 | ] -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import UISVG from './ui.svg'; 2 | import UICSS from './ui.css'; 3 | 4 | export default class GLBench { 5 | 6 | /** GLBench constructor 7 | * @param { WebGLRenderingContext | WebGL2RenderingContext } gl context 8 | * @param { Object | undefined } settings additional settings 9 | */ 10 | constructor(gl, settings = {}) { 11 | this.css = UICSS; 12 | this.svg = UISVG; 13 | this.paramLogger = () => {}; 14 | this.chartLogger = () => {}; 15 | this.chartLen = 20; 16 | this.chartHz = 20; 17 | 18 | this.names = []; 19 | this.cpuAccums = []; 20 | this.gpuAccums = []; 21 | this.activeAccums = []; 22 | this.chart = new Array(this.chartLen); 23 | this.now = () => (performance && performance.now) ? performance.now() : Date.now(); 24 | this.updateUI = () => { 25 | [].forEach.call(this.nodes['gl-gpu-svg'], node => { 26 | node.style.display = this.trackGPU ? 'inline' : 'none'; 27 | }) 28 | } 29 | 30 | Object.assign(this, settings); 31 | this.detected = 0; 32 | this.finished = []; 33 | this.isFramebuffer = 0; 34 | this.frameId = 0; 35 | 36 | // 120hz device detection 37 | let rafId, n = 0, t0; 38 | let loop = (t) => { 39 | if (++n < 20) { 40 | rafId = requestAnimationFrame(loop); 41 | } else { 42 | this.detected = Math.ceil(1e3 * n / (t - t0) / 70); 43 | cancelAnimationFrame(rafId); 44 | } 45 | if (!t0) t0 = t; 46 | } 47 | requestAnimationFrame(loop); 48 | 49 | // attach gpu profilers 50 | if (gl) { 51 | const glFinish = async (t, activeAccums) => 52 | Promise.resolve(setTimeout(() => { 53 | gl.getError(); 54 | const dt = this.now() - t; 55 | activeAccums.forEach((active, i) => { 56 | if (active) this.gpuAccums[i] += dt; 57 | }); 58 | }, 0)); 59 | 60 | const addProfiler = (fn, self, target) => function() { 61 | const t = self.now(); 62 | fn.apply(target, arguments); 63 | if (self.trackGPU) self.finished.push(glFinish(t, self.activeAccums.slice(0))); 64 | }; 65 | 66 | ['drawArrays', 'drawElements', 'drawArraysInstanced', 67 | 'drawBuffers', 'drawElementsInstanced', 'drawRangeElements'] 68 | .forEach(fn => { if (gl[fn]) gl[fn] = addProfiler(gl[fn], this, gl) }); 69 | 70 | gl.getExtension = ((fn, self) => function() { 71 | let ext = fn.apply(gl, arguments); 72 | if (ext) { 73 | ['drawElementsInstancedANGLE', 'drawBuffersWEBGL'] 74 | .forEach(fn => { if (ext[fn]) ext[fn] = addProfiler(ext[fn], self, ext) }); 75 | } 76 | return ext; 77 | })(gl.getExtension, this); 78 | } 79 | 80 | // init ui and ui loggers 81 | if (!this.withoutUI) { 82 | if (!this.dom) this.dom = document.body; 83 | let elm = document.createElement('div'); 84 | elm.id = 'gl-bench'; 85 | this.dom.appendChild(elm); 86 | this.dom.insertAdjacentHTML('afterbegin', ''); 87 | this.dom = elm; 88 | this.dom.addEventListener('click', () => { 89 | this.trackGPU = !this.trackGPU; 90 | this.updateUI(); 91 | }); 92 | 93 | this.paramLogger = ((logger, dom, names) => { 94 | const classes = ['gl-cpu', 'gl-gpu', 'gl-mem', 'gl-fps', 'gl-gpu-svg', 'gl-chart']; 95 | const nodes = Object.assign({}, classes); 96 | classes.forEach(c => nodes[c] = dom.getElementsByClassName(c)); 97 | this.nodes = nodes; 98 | return (i, cpu, gpu, mem, fps, totalTime, frameId) => { 99 | nodes['gl-cpu'][i].style.strokeDasharray = (cpu * 0.27).toFixed(0) + ' 100'; 100 | nodes['gl-gpu'][i].style.strokeDasharray = (gpu * 0.27).toFixed(0) + ' 100'; 101 | nodes['gl-mem'][i].innerHTML = names[i] ? names[i] : (mem ? 'mem: ' + mem.toFixed(0) + 'mb' : ''); 102 | nodes['gl-fps'][i].innerHTML = fps.toFixed(0) + ' FPS'; 103 | logger(names[i], cpu, gpu, mem, fps, totalTime, frameId); 104 | } 105 | })(this.paramLogger, this.dom, this.names); 106 | 107 | this.chartLogger = ((logger, dom) => { 108 | let nodes = { 'gl-chart': dom.getElementsByClassName('gl-chart') }; 109 | return (i, chart, circularId) => { 110 | let points = ''; 111 | let len = chart.length; 112 | for (let i = 0; i < len; i++) { 113 | let id = (circularId + i + 1) % len; 114 | if (chart[id] != undefined) { 115 | points = points + ' ' + (55 * i / (len - 1)).toFixed(1) + ',' 116 | + (45 - chart[id] * 22 / 60 / this.detected).toFixed(1); 117 | } 118 | } 119 | nodes['gl-chart'][i].setAttribute('points', points); 120 | logger(this.names[i], chart, circularId); 121 | } 122 | })(this.chartLogger, this.dom); 123 | } 124 | } 125 | 126 | /** 127 | * Explicit UI add 128 | * @param { string | undefined } name 129 | */ 130 | addUI(name) { 131 | if (this.names.indexOf(name) == -1) { 132 | this.names.push(name); 133 | if (this.dom) { 134 | this.dom.insertAdjacentHTML('beforeend', this.svg); 135 | this.updateUI(); 136 | } 137 | this.cpuAccums.push(0); 138 | this.gpuAccums.push(0); 139 | this.activeAccums.push(false); 140 | } 141 | } 142 | 143 | /** 144 | * Increase frameID 145 | * @param { number | undefined } now 146 | */ 147 | nextFrame(now) { 148 | this.frameId++; 149 | const t = now ? now : this.now(); 150 | 151 | // params 152 | if (this.frameId <= 1) { 153 | this.paramFrame = this.frameId; 154 | this.paramTime = t; 155 | } else { 156 | let duration = t - this.paramTime; 157 | if (duration >= 1e3) { 158 | const frameCount = this.frameId - this.paramFrame; 159 | const fps = frameCount / duration * 1e3; 160 | for (let i = 0; i < this.names.length; i++) { 161 | const cpu = this.cpuAccums[i] / duration * 100, 162 | gpu = this.gpuAccums[i] / duration * 100, 163 | mem = (performance && performance.memory) ? performance.memory.usedJSHeapSize / (1 << 20) : 0; 164 | this.paramLogger(i, cpu, gpu, mem, fps, duration, frameCount); 165 | this.cpuAccums[i] = 0; 166 | Promise.all(this.finished).then(() => { 167 | this.gpuAccums[i] = 0; 168 | this.finished = []; 169 | }); 170 | } 171 | this.paramFrame = this.frameId; 172 | this.paramTime = t; 173 | } 174 | } 175 | 176 | // chart 177 | if (!this.detected || !this.chartFrame) { 178 | this.chartFrame = this.frameId; 179 | this.chartTime = t; 180 | this.circularId = 0; 181 | } else { 182 | let timespan = t - this.chartTime; 183 | let hz = this.chartHz * timespan / 1e3; 184 | while (--hz > 0 && this.detected) { 185 | const frameCount = this.frameId - this.chartFrame; 186 | const fps = frameCount / timespan * 1e3; 187 | this.chart[this.circularId % this.chartLen] = fps; 188 | for (let i = 0; i < this.names.length; i++) { 189 | this.chartLogger(i, this.chart, this.circularId); 190 | } 191 | this.circularId++; 192 | this.chartFrame = this.frameId; 193 | this.chartTime = t; 194 | } 195 | } 196 | } 197 | 198 | /** 199 | * Begin named measurement 200 | * @param { string | undefined } name 201 | */ 202 | begin(name) { 203 | this.updateAccums(name); 204 | } 205 | 206 | /** 207 | * End named measure 208 | * @param { string | undefined } name 209 | */ 210 | end(name) { 211 | this.updateAccums(name); 212 | } 213 | 214 | updateAccums(name) { 215 | let nameId = this.names.indexOf(name); 216 | if (nameId == -1) { 217 | nameId = this.names.length; 218 | this.addUI(name); 219 | } 220 | 221 | const t = this.now(); 222 | const dt = t - this.t0; 223 | for (let i = 0; i < nameId + 1; i++) { 224 | if (this.activeAccums[i]) { 225 | this.cpuAccums[i] += dt; 226 | } 227 | }; 228 | this.activeAccums[nameId] = !this.activeAccums[nameId]; 229 | this.t0 = t; 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /src/ui.css: -------------------------------------------------------------------------------- 1 | #gl-bench { 2 | position:absolute; 3 | left:0; 4 | top:0; 5 | z-index:1000; 6 | -webkit-user-select: none; 7 | -moz-user-select: none; 8 | user-select: none; 9 | } 10 | 11 | #gl-bench div { 12 | position: relative; 13 | display: block; 14 | margin: 4px; 15 | padding: 0 7px 0 10px; 16 | background: #6c6; 17 | border-radius: 15px; 18 | cursor: pointer; 19 | opacity: 0.9; 20 | } 21 | 22 | #gl-bench svg { 23 | height: 60px; 24 | margin: 0 -1px; 25 | } 26 | 27 | #gl-bench text { 28 | font-size: 12px; 29 | font-family: Helvetica,Arial,sans-serif; 30 | font-weight: 700; 31 | dominant-baseline: middle; 32 | text-anchor: middle; 33 | } 34 | 35 | #gl-bench .gl-mem { 36 | font-size: 9px; 37 | } 38 | 39 | #gl-bench line { 40 | stroke-width: 5; 41 | stroke: #112211; 42 | stroke-linecap: round; 43 | } 44 | 45 | #gl-bench polyline { 46 | fill: none; 47 | stroke: #112211; 48 | stroke-linecap: round; 49 | stroke-linejoin: round; 50 | stroke-width: 3.5; 51 | } 52 | 53 | #gl-bench rect { 54 | fill: #448844; 55 | } 56 | 57 | #gl-bench .opacity { 58 | stroke: #448844; 59 | } 60 | -------------------------------------------------------------------------------- /src/ui.svg: -------------------------------------------------------------------------------- 1 |
2 | 3 | 00 FPS 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
-------------------------------------------------------------------------------- /test/puppeteer.js: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import pup2ist from 'puppeteer-to-istanbul'; 3 | 4 | (async () => { 5 | const browser = await puppeteer.launch({ 6 | headless: !process.env.CI, 7 | args: ['--use-gl=egl'] 8 | }); 9 | const page = (await browser.pages())[0]; 10 | 11 | // enable coverage 12 | await page.coverage.startJSCoverage(); 13 | 14 | // navigate to unit test page 15 | page.on('console', msg => { 16 | for (let i = 0; i < msg.args().length; ++i) { 17 | const str = msg.args()[i].toString().slice(9); 18 | if (str.slice(0, 3) == 'TAP') { 19 | console.log('\x1b[34m'); 20 | } 21 | console.log(str); 22 | if (str.trim() == '# ok') { 23 | console.log('\x1b[32m' + 'SUCCESS!'); 24 | setTimeout(() => process.exit(0), 100); 25 | } 26 | } 27 | }); 28 | 29 | // navigate to unit test page 30 | await page.goto('http://127.0.0.1:1234/'); 31 | 32 | // disable coverage 33 | const jsCoverage = await page.coverage.stopJSCoverage(); 34 | pup2ist.write([...jsCoverage]); 35 | 36 | await new Promise(resolve => setTimeout(resolve, 6000)); 37 | await browser.close(); 38 | })(); -------------------------------------------------------------------------------- /test/stress-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Stress Testing 8 | 9 | 15 | 16 | 17 |
18 |
19 |

Stress testing (500 full entities)

20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 58 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import GLBench from '../dist/gl-bench.module.js'; 2 | import { test } from 'https://cdn.jsdelivr.net/npm/zora@3.0.3/dist/bundle/module.js'; 3 | 4 | function drawTriangle(canvas, gl) { 5 | gl.viewport(0,0,canvas.width,canvas.height); 6 | const vertShader = gl.createShader(gl.VERTEX_SHADER); 7 | gl.shaderSource(vertShader, 'attribute vec3 c;void main(void){gl_Position=vec4(c, 1.0);}'); 8 | gl.compileShader(vertShader); 9 | const fragShader = gl.createShader(gl.FRAGMENT_SHADER); 10 | gl.shaderSource(fragShader, 'void main(void){gl_FragColor=vec4(0,1,1,1);}'); 11 | gl.compileShader(fragShader); 12 | const prog = gl.createProgram(); 13 | gl.attachShader(prog, vertShader); 14 | gl.attachShader(prog, fragShader); 15 | gl.linkProgram(prog); 16 | gl.useProgram(prog); 17 | gl.clearColor(1, 0, 1, 1); 18 | gl.clear(gl.COLOR_BUFFER_BIT); 19 | const vertexBuf = gl.createBuffer(); 20 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuf); 21 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -0.5,0.5,0.0, -0.5,-0.5,0.0, 0.5,-0.5,0.0 ]), gl.STATIC_DRAW); 22 | const coord = gl.getAttribLocation(prog, "c"); 23 | gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0); 24 | gl.enableVertexAttribArray(coord); 25 | gl.drawArrays(gl.TRIANGLES, 0, 3); 26 | } 27 | 28 | test('CPU', async (t) => { 29 | let tfps = null, tcpu = null; 30 | const bench = new GLBench(null, { 31 | withoutUI: true, 32 | paramLogger: (i, cpu, gpu, mem, fps) => { 33 | tfps = fps; 34 | tcpu = cpu; 35 | } 36 | }); 37 | for(let frameId = 0; frameId < 25; frameId++) { 38 | bench.nextFrame(); 39 | bench.begin(); 40 | await new Promise(resolve => setTimeout(resolve, 100)); 41 | bench.end(); 42 | } 43 | t.ok(tfps != null, 'fps = ' + (tfps != null ? tfps.toFixed(1) : 'null')); 44 | t.ok(tcpu != null, 'cpu = ' + (tcpu != null ? tcpu.toFixed(1) : 'null')); 45 | }); 46 | 47 | test('Memory', async (t) => { 48 | let tmem = null; 49 | const bench = new GLBench(null, { 50 | withoutUI: true, 51 | paramLogger: (i, cpu, gpu, mem, fps) => { 52 | tmem = mem; 53 | } 54 | }); 55 | for(let frameId = 0; frameId < 25; frameId++) { 56 | bench.nextFrame(); 57 | bench.begin(); 58 | await new Promise(resolve => setTimeout(resolve, 100)); 59 | bench.end(); 60 | } 61 | t.ok(tmem != null, 'mem = ' + (tmem != null ? tmem.toFixed(1) : 'null')); 62 | }); 63 | 64 | test('WebGL1', async (t) => { 65 | let tfps = null, tcpu = null, tgpu = null; 66 | const canvas = document.querySelector('canvas'); 67 | const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); 68 | const bench = new GLBench(gl, { 69 | trackGPU: true, 70 | withoutUI: true, 71 | paramLogger: (i, cpu, gpu, mem, fps) => { 72 | tfps = fps; 73 | tcpu = cpu; 74 | tgpu = gpu; 75 | } 76 | }); 77 | for(let frameId = 0; frameId < 100; frameId++) { 78 | bench.nextFrame(); 79 | bench.begin(); 80 | drawTriangle(canvas, gl); 81 | await new Promise(resolve => setTimeout(resolve, 30)); 82 | bench.end(); 83 | } 84 | t.ok(tfps != null, 'fps = ' + (tfps != null ? tfps.toFixed(1) : 'null')); 85 | t.ok(tcpu != null, 'cpu = ' + (tcpu != null ? tcpu.toFixed(1) : 'null')); 86 | t.ok(tgpu != null, 'gpu = ' + (tgpu != null ? tgpu.toFixed(1) : 'null')); 87 | }); 88 | 89 | test('WebGL2', async (t) => { 90 | let tfps = null, tcpu = null, tgpu = null; 91 | const canvas = document.querySelectorAll('canvas')[1]; 92 | const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); 93 | const bench = new GLBench(gl, { 94 | trackGPU: true, 95 | withoutUI: true, 96 | paramLogger: (i, cpu, gpu, mem, fps) => { 97 | tfps = fps; 98 | tcpu = cpu; 99 | tgpu = gpu; 100 | } 101 | }); 102 | for(let frameId = 0; frameId < 100; frameId++) { 103 | bench.nextFrame(); 104 | bench.begin(); 105 | drawTriangle(canvas, gl); 106 | await new Promise(resolve => setTimeout(resolve, 30)); 107 | bench.end(); 108 | } 109 | t.ok(tfps != null, 'fps = ' + (tfps != null ? tfps.toFixed(1) : 'null')); 110 | t.ok(tcpu != null, 'cpu = ' + (tcpu != null ? tcpu.toFixed(1) : 'null')); 111 | t.ok(tgpu != null, 'gpu = ' + (tgpu != null ? tgpu.toFixed(1) : 'null')); 112 | }); 113 | 114 | test('UI', async (t) => { 115 | let tfps = null, tcpu = null, tgpu = null, tmem = null; 116 | const canvas = document.querySelectorAll('canvas')[2]; 117 | const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); 118 | const bench = new GLBench(gl, { 119 | trackGPU: true, 120 | paramLogger: (i, cpu, gpu, mem, fps) => { 121 | tfps = fps; 122 | tcpu = cpu; 123 | tgpu = gpu; 124 | tmem = mem; 125 | } 126 | }); 127 | for(let frameId = 0; frameId < 100; frameId++) { 128 | bench.nextFrame(); 129 | bench.begin(); 130 | drawTriangle(canvas, gl); 131 | await new Promise(resolve => setTimeout(resolve, 30)); 132 | bench.end(); 133 | } 134 | let fpsUI = null, cpuUI = null, gpuUI = null, memUI = null; 135 | const benchNode = document.querySelector('#gl-bench'); 136 | if (benchNode) fpsUI = benchNode.querySelector('.gl-fps'); 137 | if (benchNode) cpuUI = benchNode.querySelector('.gl-cpu'); 138 | if (benchNode) gpuUI = benchNode.querySelector('.gl-gpu'); 139 | if (benchNode) memUI = benchNode.querySelector('.gl-mem'); 140 | if (fpsUI) fpsUI = fpsUI.innerHTML; 141 | if (cpuUI) cpuUI = cpuUI.style.strokeDasharray; 142 | if (gpuUI) gpuUI = gpuUI.style.strokeDasharray; 143 | if (memUI) memUI = memUI.innerHTML; 144 | t.ok(fpsUI != null, 'fps ui = ' + fpsUI); 145 | t.ok(cpuUI != null, 'cpu ui = ' + cpuUI); 146 | t.ok(gpuUI != null, 'gpu ui = ' + gpuUI); 147 | t.ok(memUI != null, 'mem ui = ' + memUI); 148 | t.ok(tfps != null, 'fps = ' + (tfps != null ? tfps.toFixed(1) : 'null')); 149 | t.ok(tcpu != null, 'cpu = ' + (tcpu != null ? tcpu.toFixed(1) : 'null')); 150 | t.ok(tgpu != null, 'gpu = ' + (tgpu != null ? tgpu.toFixed(1) : 'null')); 151 | t.ok(tmem != null, 'mem = ' + (tmem != null ? tmem.toFixed(1) : 'null')); 152 | }); 153 | -------------------------------------------------------------------------------- /test/unit-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Unit testing 10 | 11 | 12 | 13 |

UNIT TESTING

14 | 15 |
16 | 17 |
18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 48 | 49 | 50 | --------------------------------------------------------------------------------