├── .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 [](https://bundlephobia.com/result?p=gl-bench) [](https://circleci.com/gh/munrocket/gl-bench) [](https://codecov.io/gh/munrocket/gl-bench)
2 |
3 | WebGL performance monitor that showing percentage of GPU/CPU load.
4 |
5 | ### Screenshots
6 | 
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
\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='';
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 \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 |
70 |
76 |
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 |
8 |
13 |
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 |
--------------------------------------------------------------------------------