├── example ├── crate.gif ├── UV_Grid_Sm.jpg └── index.html ├── rollup.config.js ├── package.json ├── LICENSE ├── .gitignore ├── README.md ├── index.js └── dist ├── webgl-stats.module.js ├── webgl-stats.cjs.js └── webgl-stats.js /example/crate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernandojsg/webgl-stats/HEAD/example/crate.gif -------------------------------------------------------------------------------- /example/UV_Grid_Sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernandojsg/webgl-stats/HEAD/example/UV_Grid_Sm.jpg -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | 3 | export default { 4 | input: 'index.js', 5 | plugins: [resolve({ 6 | customResolveOptions: 'node_modules' 7 | })], 8 | // sourceMap: true, 9 | output: [ 10 | { 11 | format: 'umd', 12 | name: 'WEBGLSTATS', 13 | file: 'dist/webgl-stats.js', 14 | indent: '\t' 15 | }, 16 | { 17 | format: 'es', 18 | file: 'dist/webgl-stats.module.js', 19 | indent: '\t' 20 | }, 21 | { 22 | format: 'cjs', 23 | file: 'dist/webgl-stats.cjs.js', 24 | indent: '\t' 25 | } 26 | ] 27 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgl-stats", 3 | "version": "0.2.1", 4 | "description": "WebGL stats", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rollup -c", 8 | "start": "http-server", 9 | "dev": "concurrently --names \"ROLLUP,SERVER\" -c \"bgGreen.bold,bgBlue.bold\" \"rollup -c -w -m inline\" \"npm run start\"" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/fernandojsg/webgl-stats.git" 14 | }, 15 | "author": "Fernando Serrano (http://fernandojsg.com)", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/fernandojsg/webgl-stats/issues" 19 | }, 20 | "homepage": "https://github.com/fernandojsg/webgl-stats#readme", 21 | "devDependencies": { 22 | "concurrently": "^4.0.1", 23 | "http-server": "^0.11.1", 24 | "rollup": "^0.67.1", 25 | "rollup-plugin-node-resolve": "^3.4.0" 26 | }, 27 | "dependencies": { 28 | "incremental-stats-lite": "^0.1.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Fernando Serrano 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webgl-stats 2 | 3 | *If you are looking for a UI representation of this stats you should take a look at: https://github.com/fernandojsg/webg-stats-ui* 4 | 5 | Keep tracks of the number of WebGL calls on your application by hooking the WebGL1 and WebGL2 APIs: 6 | * DrawCalls: 7 | * DrawArrays 8 | * DrawArraysInstanced 9 | * DrawElements 10 | * DrawElementsInstanced 11 | * numFaces 12 | * numVertices 13 | * numPoints 14 | * useProgram 15 | * bindTextures 16 | 17 | ## Installation 18 | 19 | ### NPM 20 | 21 | Install via NPM: 22 | ``` 23 | npm install --save webgl-stats 24 | ``` 25 | 26 | Then import to use it: 27 | ```javascript 28 | import WEBGLSTATS from 'webgl-stats'; 29 | ``` 30 | 31 | or 32 | 33 | ```javascript 34 | WEBGLSTATS = require('webgl-stats'); 35 | ``` 36 | 37 | ### Browser 38 | 39 | ```html 40 | 41 | ``` 42 | 43 | ## Usage 44 | 45 | `webgl-stats` will hook the WebGL1 and WebGL2 automatically, but you should provide your context once you have created it in your app, and before you start using it: 46 | 47 | ```javascript 48 | var canvas = document.getElementById('canvas'); 49 | var ctx = canvas.getContext('2d'); 50 | 51 | WEBGLSTATS.setupExtensions(ctx); 52 | ``` 53 | 54 | On every frame you should call `frameStart` before your render call and `frameEnd` after that. 55 | 56 | ```javascript 57 | function animate() { 58 | requestAnimationFrame( animate ); 59 | 60 | WEBGLSTATS.frameStart(); 61 | render(); 62 | WEBGLSTATS.frameEnd(); 63 | 64 | showStats(); 65 | } 66 | ``` 67 | 68 | After that you could query the current frame data or the accumulated stats: 69 | 70 | * `getCurrentData()`: 71 | ```json 72 | { 73 | "numDrawCalls": 0, 74 | 75 | "numDrawArraysCalls": 0, 76 | "numDrawArraysInstancedCalls": 0, 77 | "numDrawElementsCalls": 0, 78 | "numDrawElementsInstancedCalls": 0, 79 | 80 | "numUseProgramCalls": 0, 81 | "numFaces": 0, 82 | "numVertices": 0, 83 | "numPoints": 0, 84 | "numBindTextures": 0 85 | } 86 | ``` 87 | 88 | * `getSummary()`: 89 | ```json 90 | { 91 | "drawCalls": { 92 | "min": 0, 93 | "max": 0, 94 | "avg": 0, 95 | "standard_deviation": 0 96 | }, 97 | "useProgramCalls": { 98 | "min": 0, 99 | "max": 0, 100 | "avg": 0, 101 | "standard_deviation": 0 102 | }, 103 | "faces": { 104 | "min": 0, 105 | "max": 0, 106 | "avg": 0, 107 | "standard_deviation": 0 108 | }, 109 | "vertices": { 110 | "min": 0, 111 | "max": 0, 112 | "avg": 0, 113 | "standard_deviation": 0 114 | }, 115 | "bindTextures": { 116 | "min": 0, 117 | "max": 0, 118 | "avg": 0, 119 | "standard_deviation": 0 120 | } 121 | } 122 | ``` 123 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | three.js webgl - geometries 5 | 6 | 7 | 37 | 38 | 39 |
 40 |       

CURRENT

41 |
42 | 43 |
44 |

SUMMARY

45 |
46 | 47 |
48 |
49 | 50 | 51 | 52 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import Stats from 'incremental-stats-lite'; 2 | 3 | 'use strict'; 4 | 5 | function WebGLStats () { 6 | 7 | var data = { 8 | numDrawCalls: 0, 9 | 10 | numDrawArraysCalls:0, 11 | numDrawArraysInstancedCalls:0, 12 | numDrawElementsCalls:0, 13 | numDrawElementsInstancedCalls: 0, 14 | 15 | numUseProgramCalls:0, 16 | numFaces:0, 17 | numVertices:0, 18 | numPoints:0, 19 | numBindTextures:0 20 | } 21 | 22 | var stats = { 23 | drawCalls: new Stats(), 24 | useProgramCalls: new Stats(), 25 | faces: new Stats(), 26 | vertices: new Stats(), 27 | bindTextures: new Stats() 28 | }; 29 | 30 | function frameEnd() { 31 | for (let stat in stats) { 32 | var counterName = 'num' + stat.charAt(0).toUpperCase() + stat.slice(1); 33 | stats[stat].update(data[counterName]); 34 | } 35 | } 36 | 37 | function _h ( f, c ) { 38 | return function () { 39 | c.apply( this, arguments ); 40 | f.apply( this, arguments ); 41 | }; 42 | } 43 | 44 | if ('WebGL2RenderingContext' in window) { 45 | WebGL2RenderingContext.prototype.drawArraysInstanced = _h( WebGL2RenderingContext.prototype.drawArraysInstanced, function () { 46 | data.numDrawArraysInstancedCalls++; 47 | data.numDrawCalls++; 48 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 49 | else data.numVertices += arguments[ 2 ]; 50 | }); 51 | 52 | WebGL2RenderingContext.prototype.drawElementsInstanced = _h( WebGL2RenderingContext.prototype.drawElementsInstanced, function () { 53 | data.numDrawElementsInstancedCalls++; 54 | data.numDrawCalls++; 55 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 56 | else data.numVertices += arguments[ 2 ]; 57 | }); 58 | 59 | WebGL2RenderingContext.prototype.drawArrays = _h( WebGL2RenderingContext.prototype.drawArrays, function () { 60 | data.numDrawArraysCalls++; 61 | data.numDrawCalls++; 62 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 63 | else data.numVertices += arguments[ 2 ]; 64 | } ); 65 | 66 | WebGL2RenderingContext.prototype.drawElements = _h( WebGL2RenderingContext.prototype.drawElements, function () { 67 | data.numDrawElementsCalls++; 68 | data.numDrawCalls++; 69 | data.numFaces += arguments[ 1 ] / 3; 70 | data.numVertices += arguments[ 1 ]; 71 | } ); 72 | 73 | WebGL2RenderingContext.prototype.useProgram = _h( WebGL2RenderingContext.prototype.useProgram, function () { 74 | data.numUseProgramCalls++; 75 | } ); 76 | 77 | WebGL2RenderingContext.prototype.bindTexture = _h( WebGL2RenderingContext.prototype.bindTexture, function () { 78 | data.numBindTextures++; 79 | } ); 80 | 81 | } 82 | 83 | 84 | WebGLRenderingContext.prototype.drawArrays = _h( WebGLRenderingContext.prototype.drawArrays, function () { 85 | data.numDrawArraysCalls++; 86 | data.numDrawCalls++; 87 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 88 | else data.numVertices += arguments[ 2 ]; 89 | } ); 90 | 91 | WebGLRenderingContext.prototype.drawElements = _h( WebGLRenderingContext.prototype.drawElements, function () { 92 | data.numDrawElementsCalls++; 93 | data.numDrawCalls++; 94 | data.numFaces += arguments[ 1 ] / 3; 95 | data.numVertices += arguments[ 1 ]; 96 | } ); 97 | 98 | WebGLRenderingContext.prototype.useProgram = _h( WebGLRenderingContext.prototype.useProgram, function () { 99 | data.numUseProgramCalls++; 100 | } ); 101 | 102 | WebGLRenderingContext.prototype.bindTexture = _h( WebGLRenderingContext.prototype.bindTexture, function () { 103 | data.numBindTextures++; 104 | } ); 105 | 106 | function frameStart () { 107 | data.numDrawCalls = 0; 108 | data.numDrawArraysCalls = 0; 109 | data.numDrawArraysInstancedCalls = 0; 110 | data.numDrawElementsCalls = 0; 111 | data.numDrawElementsInstancedCalls = 0; 112 | data.numUseProgramCalls = 0; 113 | data.numFaces = 0; 114 | data.numVertices = 0; 115 | data.numPoints = 0; 116 | data.numBindTextures = 0; 117 | } 118 | 119 | function isWebGLContext(ctx) { 120 | return (ctx instanceof WebGLRenderingContext) || (window.WebGL2RenderingContext && (ctx instanceof WebGL2RenderingContext)); 121 | } 122 | 123 | function setupExtensions(context) { 124 | if (!isWebGLContext(context)) { 125 | console.warn("WEBGL-STATS: Provided context is not WebGL"); 126 | return; 127 | } 128 | 129 | var ext = context.getExtension('ANGLE_instanced_arrays'); 130 | if (!ext) { 131 | return; 132 | } 133 | ext.drawArraysInstancedANGLE = _h( ext.drawArraysInstancedANGLE, function () { 134 | data.numDrawArraysInstancedCalls++; 135 | data.numDrawCalls++; 136 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 137 | else data.numVertices += arguments[ 2 ]; 138 | }); 139 | 140 | ext.drawElementsInstancedANGLE = _h( ext.drawElementsInstancedANGLE, function () { 141 | data.numDrawElementsInstancedCalls++; 142 | data.numDrawCalls++; 143 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 144 | else data.numVertices += arguments[ 2 ]; 145 | }); 146 | } 147 | 148 | function getSummary() { 149 | var result = {}; 150 | Object.keys(stats).forEach(key => { 151 | result[key] = { 152 | min: stats[key].min, 153 | max: stats[key].max, 154 | avg: stats[key].mean, 155 | standard_deviation: stats[key].standard_deviation 156 | }; 157 | }); 158 | return result; 159 | } 160 | 161 | return { 162 | getCurrentData: () => {return data;}, 163 | setupExtensions: setupExtensions, 164 | getSummary: getSummary, 165 | frameStart: frameStart, 166 | frameEnd: frameEnd 167 | 168 | //enable: enable, 169 | //disable: disable 170 | } 171 | } 172 | 173 | export default WebGLStats(); -------------------------------------------------------------------------------- /dist/webgl-stats.module.js: -------------------------------------------------------------------------------- 1 | class PerfStats { 2 | constructor() { 3 | this.n = 0; 4 | this.min = Number.MAX_VALUE; 5 | this.max = -Number.MAX_VALUE; 6 | this.sum = 0; 7 | this.mean = 0; 8 | this.q = 0; 9 | } 10 | 11 | get variance() { 12 | return this.q / this.n; 13 | } 14 | 15 | get standard_deviation() { 16 | return Math.sqrt(this.q / this.n); 17 | } 18 | 19 | update(value) { 20 | var num = parseFloat(value); 21 | if (isNaN(num)) { 22 | // Sorry, no NaNs 23 | return; 24 | } 25 | this.n++; 26 | this.min = Math.min(this.min, num); 27 | this.max = Math.max(this.max, num); 28 | this.sum += num; 29 | const prevMean = this.mean; 30 | this.mean = this.mean + (num - this.mean) / this.n; 31 | this.q = this.q + (num - prevMean) * (num - this.mean); 32 | } 33 | 34 | getAll() { 35 | return { 36 | n: this.n, 37 | min: this.min, 38 | max: this.max, 39 | sum: this.sum, 40 | mean: this.mean, 41 | variance: this.variance, 42 | standard_deviation: this.standard_deviation 43 | }; 44 | } 45 | } 46 | 47 | function WebGLStats () { 48 | 49 | var data = { 50 | numDrawCalls: 0, 51 | 52 | numDrawArraysCalls:0, 53 | numDrawArraysInstancedCalls:0, 54 | numDrawElementsCalls:0, 55 | numDrawElementsInstancedCalls: 0, 56 | 57 | numUseProgramCalls:0, 58 | numFaces:0, 59 | numVertices:0, 60 | numPoints:0, 61 | numBindTextures:0 62 | }; 63 | 64 | var stats = { 65 | drawCalls: new PerfStats(), 66 | useProgramCalls: new PerfStats(), 67 | faces: new PerfStats(), 68 | vertices: new PerfStats(), 69 | bindTextures: new PerfStats() 70 | }; 71 | 72 | function frameEnd() { 73 | for (let stat in stats) { 74 | var counterName = 'num' + stat.charAt(0).toUpperCase() + stat.slice(1); 75 | stats[stat].update(data[counterName]); 76 | } 77 | } 78 | 79 | function _h ( f, c ) { 80 | return function () { 81 | c.apply( this, arguments ); 82 | f.apply( this, arguments ); 83 | }; 84 | } 85 | 86 | if ('WebGL2RenderingContext' in window) { 87 | WebGL2RenderingContext.prototype.drawArraysInstanced = _h( WebGL2RenderingContext.prototype.drawArraysInstanced, function () { 88 | data.numDrawArraysInstancedCalls++; 89 | data.numDrawCalls++; 90 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 91 | else data.numVertices += arguments[ 2 ]; 92 | }); 93 | 94 | WebGL2RenderingContext.prototype.drawElementsInstanced = _h( WebGL2RenderingContext.prototype.drawElementsInstanced, function () { 95 | data.numDrawElementsInstancedCalls++; 96 | data.numDrawCalls++; 97 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 98 | else data.numVertices += arguments[ 2 ]; 99 | }); 100 | 101 | WebGL2RenderingContext.prototype.drawArrays = _h( WebGL2RenderingContext.prototype.drawArrays, function () { 102 | data.numDrawArraysCalls++; 103 | data.numDrawCalls++; 104 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 105 | else data.numVertices += arguments[ 2 ]; 106 | } ); 107 | 108 | WebGL2RenderingContext.prototype.drawElements = _h( WebGL2RenderingContext.prototype.drawElements, function () { 109 | data.numDrawElementsCalls++; 110 | data.numDrawCalls++; 111 | data.numFaces += arguments[ 1 ] / 3; 112 | data.numVertices += arguments[ 1 ]; 113 | } ); 114 | 115 | WebGL2RenderingContext.prototype.useProgram = _h( WebGL2RenderingContext.prototype.useProgram, function () { 116 | data.numUseProgramCalls++; 117 | } ); 118 | 119 | WebGL2RenderingContext.prototype.bindTexture = _h( WebGL2RenderingContext.prototype.bindTexture, function () { 120 | data.numBindTextures++; 121 | } ); 122 | 123 | } 124 | 125 | 126 | WebGLRenderingContext.prototype.drawArrays = _h( WebGLRenderingContext.prototype.drawArrays, function () { 127 | data.numDrawArraysCalls++; 128 | data.numDrawCalls++; 129 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 130 | else data.numVertices += arguments[ 2 ]; 131 | } ); 132 | 133 | WebGLRenderingContext.prototype.drawElements = _h( WebGLRenderingContext.prototype.drawElements, function () { 134 | data.numDrawElementsCalls++; 135 | data.numDrawCalls++; 136 | data.numFaces += arguments[ 1 ] / 3; 137 | data.numVertices += arguments[ 1 ]; 138 | } ); 139 | 140 | WebGLRenderingContext.prototype.useProgram = _h( WebGLRenderingContext.prototype.useProgram, function () { 141 | data.numUseProgramCalls++; 142 | } ); 143 | 144 | WebGLRenderingContext.prototype.bindTexture = _h( WebGLRenderingContext.prototype.bindTexture, function () { 145 | data.numBindTextures++; 146 | } ); 147 | 148 | function frameStart () { 149 | data.numDrawCalls = 0; 150 | data.numDrawArraysCalls = 0; 151 | data.numDrawArraysInstancedCalls = 0; 152 | data.numDrawElementsCalls = 0; 153 | data.numDrawElementsInstancedCalls = 0; 154 | data.numUseProgramCalls = 0; 155 | data.numFaces = 0; 156 | data.numVertices = 0; 157 | data.numPoints = 0; 158 | data.numBindTextures = 0; 159 | } 160 | 161 | function setupExtensions(context) { 162 | var ext = context.getExtension('ANGLE_instanced_arrays'); 163 | if (!ext) { 164 | return; 165 | } 166 | ext.drawArraysInstancedANGLE = _h( ext.drawArraysInstancedANGLE, function () { 167 | data.numDrawArraysInstancedCalls++; 168 | data.numDrawCalls++; 169 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 170 | else data.numVertices += arguments[ 2 ]; 171 | }); 172 | 173 | ext.drawElementsInstancedANGLE = _h( ext.drawElementsInstancedANGLE, function () { 174 | data.numDrawElementsInstancedCalls++; 175 | data.numDrawCalls++; 176 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 177 | else data.numVertices += arguments[ 2 ]; 178 | }); 179 | } 180 | 181 | function getSummary() { 182 | var result = {}; 183 | Object.keys(stats).forEach(key => { 184 | result[key] = { 185 | min: stats[key].min, 186 | max: stats[key].max, 187 | avg: stats[key].mean, 188 | standard_deviation: stats[key].standard_deviation 189 | }; 190 | }); 191 | return result; 192 | } 193 | 194 | return { 195 | getCurrentData: () => {return data;}, 196 | setupExtensions: setupExtensions, 197 | getSummary: getSummary, 198 | frameStart: frameStart, 199 | frameEnd: frameEnd 200 | 201 | //enable: enable, 202 | //disable: disable 203 | } 204 | } 205 | 206 | var index = WebGLStats(); 207 | 208 | export default index; 209 | -------------------------------------------------------------------------------- /dist/webgl-stats.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class PerfStats { 4 | constructor() { 5 | this.n = 0; 6 | this.min = Number.MAX_VALUE; 7 | this.max = -Number.MAX_VALUE; 8 | this.sum = 0; 9 | this.mean = 0; 10 | this.q = 0; 11 | } 12 | 13 | get variance() { 14 | return this.q / this.n; 15 | } 16 | 17 | get standard_deviation() { 18 | return Math.sqrt(this.q / this.n); 19 | } 20 | 21 | update(value) { 22 | var num = parseFloat(value); 23 | if (isNaN(num)) { 24 | // Sorry, no NaNs 25 | return; 26 | } 27 | this.n++; 28 | this.min = Math.min(this.min, num); 29 | this.max = Math.max(this.max, num); 30 | this.sum += num; 31 | const prevMean = this.mean; 32 | this.mean = this.mean + (num - this.mean) / this.n; 33 | this.q = this.q + (num - prevMean) * (num - this.mean); 34 | } 35 | 36 | getAll() { 37 | return { 38 | n: this.n, 39 | min: this.min, 40 | max: this.max, 41 | sum: this.sum, 42 | mean: this.mean, 43 | variance: this.variance, 44 | standard_deviation: this.standard_deviation 45 | }; 46 | } 47 | } 48 | 49 | function WebGLStats () { 50 | 51 | var data = { 52 | numDrawCalls: 0, 53 | 54 | numDrawArraysCalls:0, 55 | numDrawArraysInstancedCalls:0, 56 | numDrawElementsCalls:0, 57 | numDrawElementsInstancedCalls: 0, 58 | 59 | numUseProgramCalls:0, 60 | numFaces:0, 61 | numVertices:0, 62 | numPoints:0, 63 | numBindTextures:0 64 | }; 65 | 66 | var stats = { 67 | drawCalls: new PerfStats(), 68 | useProgramCalls: new PerfStats(), 69 | faces: new PerfStats(), 70 | vertices: new PerfStats(), 71 | bindTextures: new PerfStats() 72 | }; 73 | 74 | function frameEnd() { 75 | for (let stat in stats) { 76 | var counterName = 'num' + stat.charAt(0).toUpperCase() + stat.slice(1); 77 | stats[stat].update(data[counterName]); 78 | } 79 | } 80 | 81 | function _h ( f, c ) { 82 | return function () { 83 | c.apply( this, arguments ); 84 | f.apply( this, arguments ); 85 | }; 86 | } 87 | 88 | if ('WebGL2RenderingContext' in window) { 89 | WebGL2RenderingContext.prototype.drawArraysInstanced = _h( WebGL2RenderingContext.prototype.drawArraysInstanced, function () { 90 | data.numDrawArraysInstancedCalls++; 91 | data.numDrawCalls++; 92 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 93 | else data.numVertices += arguments[ 2 ]; 94 | }); 95 | 96 | WebGL2RenderingContext.prototype.drawElementsInstanced = _h( WebGL2RenderingContext.prototype.drawElementsInstanced, function () { 97 | data.numDrawElementsInstancedCalls++; 98 | data.numDrawCalls++; 99 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 100 | else data.numVertices += arguments[ 2 ]; 101 | }); 102 | 103 | WebGL2RenderingContext.prototype.drawArrays = _h( WebGL2RenderingContext.prototype.drawArrays, function () { 104 | data.numDrawArraysCalls++; 105 | data.numDrawCalls++; 106 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 107 | else data.numVertices += arguments[ 2 ]; 108 | } ); 109 | 110 | WebGL2RenderingContext.prototype.drawElements = _h( WebGL2RenderingContext.prototype.drawElements, function () { 111 | data.numDrawElementsCalls++; 112 | data.numDrawCalls++; 113 | data.numFaces += arguments[ 1 ] / 3; 114 | data.numVertices += arguments[ 1 ]; 115 | } ); 116 | 117 | WebGL2RenderingContext.prototype.useProgram = _h( WebGL2RenderingContext.prototype.useProgram, function () { 118 | data.numUseProgramCalls++; 119 | } ); 120 | 121 | WebGL2RenderingContext.prototype.bindTexture = _h( WebGL2RenderingContext.prototype.bindTexture, function () { 122 | data.numBindTextures++; 123 | } ); 124 | 125 | } 126 | 127 | 128 | WebGLRenderingContext.prototype.drawArrays = _h( WebGLRenderingContext.prototype.drawArrays, function () { 129 | data.numDrawArraysCalls++; 130 | data.numDrawCalls++; 131 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 132 | else data.numVertices += arguments[ 2 ]; 133 | } ); 134 | 135 | WebGLRenderingContext.prototype.drawElements = _h( WebGLRenderingContext.prototype.drawElements, function () { 136 | data.numDrawElementsCalls++; 137 | data.numDrawCalls++; 138 | data.numFaces += arguments[ 1 ] / 3; 139 | data.numVertices += arguments[ 1 ]; 140 | } ); 141 | 142 | WebGLRenderingContext.prototype.useProgram = _h( WebGLRenderingContext.prototype.useProgram, function () { 143 | data.numUseProgramCalls++; 144 | } ); 145 | 146 | WebGLRenderingContext.prototype.bindTexture = _h( WebGLRenderingContext.prototype.bindTexture, function () { 147 | data.numBindTextures++; 148 | } ); 149 | 150 | function frameStart () { 151 | data.numDrawCalls = 0; 152 | data.numDrawArraysCalls = 0; 153 | data.numDrawArraysInstancedCalls = 0; 154 | data.numDrawElementsCalls = 0; 155 | data.numDrawElementsInstancedCalls = 0; 156 | data.numUseProgramCalls = 0; 157 | data.numFaces = 0; 158 | data.numVertices = 0; 159 | data.numPoints = 0; 160 | data.numBindTextures = 0; 161 | } 162 | 163 | function setupExtensions(context) { 164 | var ext = context.getExtension('ANGLE_instanced_arrays'); 165 | if (!ext) { 166 | return; 167 | } 168 | ext.drawArraysInstancedANGLE = _h( ext.drawArraysInstancedANGLE, function () { 169 | data.numDrawArraysInstancedCalls++; 170 | data.numDrawCalls++; 171 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 172 | else data.numVertices += arguments[ 2 ]; 173 | }); 174 | 175 | ext.drawElementsInstancedANGLE = _h( ext.drawElementsInstancedANGLE, function () { 176 | data.numDrawElementsInstancedCalls++; 177 | data.numDrawCalls++; 178 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 179 | else data.numVertices += arguments[ 2 ]; 180 | }); 181 | } 182 | 183 | function getSummary() { 184 | var result = {}; 185 | Object.keys(stats).forEach(key => { 186 | result[key] = { 187 | min: stats[key].min, 188 | max: stats[key].max, 189 | avg: stats[key].mean, 190 | standard_deviation: stats[key].standard_deviation 191 | }; 192 | }); 193 | return result; 194 | } 195 | 196 | return { 197 | getCurrentData: () => {return data;}, 198 | setupExtensions: setupExtensions, 199 | getSummary: getSummary, 200 | frameStart: frameStart, 201 | frameEnd: frameEnd 202 | 203 | //enable: enable, 204 | //disable: disable 205 | } 206 | } 207 | 208 | var index = WebGLStats(); 209 | 210 | module.exports = index; 211 | -------------------------------------------------------------------------------- /dist/webgl-stats.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.WEBGLSTATS = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | class PerfStats { 8 | constructor() { 9 | this.n = 0; 10 | this.min = Number.MAX_VALUE; 11 | this.max = -Number.MAX_VALUE; 12 | this.sum = 0; 13 | this.mean = 0; 14 | this.q = 0; 15 | } 16 | 17 | get variance() { 18 | return this.q / this.n; 19 | } 20 | 21 | get standard_deviation() { 22 | return Math.sqrt(this.q / this.n); 23 | } 24 | 25 | update(value) { 26 | var num = parseFloat(value); 27 | if (isNaN(num)) { 28 | // Sorry, no NaNs 29 | return; 30 | } 31 | this.n++; 32 | this.min = Math.min(this.min, num); 33 | this.max = Math.max(this.max, num); 34 | this.sum += num; 35 | const prevMean = this.mean; 36 | this.mean = this.mean + (num - this.mean) / this.n; 37 | this.q = this.q + (num - prevMean) * (num - this.mean); 38 | } 39 | 40 | getAll() { 41 | return { 42 | n: this.n, 43 | min: this.min, 44 | max: this.max, 45 | sum: this.sum, 46 | mean: this.mean, 47 | variance: this.variance, 48 | standard_deviation: this.standard_deviation 49 | }; 50 | } 51 | } 52 | 53 | function WebGLStats () { 54 | 55 | var data = { 56 | numDrawCalls: 0, 57 | 58 | numDrawArraysCalls:0, 59 | numDrawArraysInstancedCalls:0, 60 | numDrawElementsCalls:0, 61 | numDrawElementsInstancedCalls: 0, 62 | 63 | numUseProgramCalls:0, 64 | numFaces:0, 65 | numVertices:0, 66 | numPoints:0, 67 | numBindTextures:0 68 | }; 69 | 70 | var stats = { 71 | drawCalls: new PerfStats(), 72 | useProgramCalls: new PerfStats(), 73 | faces: new PerfStats(), 74 | vertices: new PerfStats(), 75 | bindTextures: new PerfStats() 76 | }; 77 | 78 | function frameEnd() { 79 | for (let stat in stats) { 80 | var counterName = 'num' + stat.charAt(0).toUpperCase() + stat.slice(1); 81 | stats[stat].update(data[counterName]); 82 | } 83 | } 84 | 85 | function _h ( f, c ) { 86 | return function () { 87 | c.apply( this, arguments ); 88 | f.apply( this, arguments ); 89 | }; 90 | } 91 | 92 | if ('WebGL2RenderingContext' in window) { 93 | WebGL2RenderingContext.prototype.drawArraysInstanced = _h( WebGL2RenderingContext.prototype.drawArraysInstanced, function () { 94 | data.numDrawArraysInstancedCalls++; 95 | data.numDrawCalls++; 96 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 97 | else data.numVertices += arguments[ 2 ]; 98 | }); 99 | 100 | WebGL2RenderingContext.prototype.drawElementsInstanced = _h( WebGL2RenderingContext.prototype.drawElementsInstanced, function () { 101 | data.numDrawElementsInstancedCalls++; 102 | data.numDrawCalls++; 103 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 104 | else data.numVertices += arguments[ 2 ]; 105 | }); 106 | 107 | WebGL2RenderingContext.prototype.drawArrays = _h( WebGL2RenderingContext.prototype.drawArrays, function () { 108 | data.numDrawArraysCalls++; 109 | data.numDrawCalls++; 110 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 111 | else data.numVertices += arguments[ 2 ]; 112 | } ); 113 | 114 | WebGL2RenderingContext.prototype.drawElements = _h( WebGL2RenderingContext.prototype.drawElements, function () { 115 | data.numDrawElementsCalls++; 116 | data.numDrawCalls++; 117 | data.numFaces += arguments[ 1 ] / 3; 118 | data.numVertices += arguments[ 1 ]; 119 | } ); 120 | 121 | WebGL2RenderingContext.prototype.useProgram = _h( WebGL2RenderingContext.prototype.useProgram, function () { 122 | data.numUseProgramCalls++; 123 | } ); 124 | 125 | WebGL2RenderingContext.prototype.bindTexture = _h( WebGL2RenderingContext.prototype.bindTexture, function () { 126 | data.numBindTextures++; 127 | } ); 128 | 129 | } 130 | 131 | 132 | WebGLRenderingContext.prototype.drawArrays = _h( WebGLRenderingContext.prototype.drawArrays, function () { 133 | data.numDrawArraysCalls++; 134 | data.numDrawCalls++; 135 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 136 | else data.numVertices += arguments[ 2 ]; 137 | } ); 138 | 139 | WebGLRenderingContext.prototype.drawElements = _h( WebGLRenderingContext.prototype.drawElements, function () { 140 | data.numDrawElementsCalls++; 141 | data.numDrawCalls++; 142 | data.numFaces += arguments[ 1 ] / 3; 143 | data.numVertices += arguments[ 1 ]; 144 | } ); 145 | 146 | WebGLRenderingContext.prototype.useProgram = _h( WebGLRenderingContext.prototype.useProgram, function () { 147 | data.numUseProgramCalls++; 148 | } ); 149 | 150 | WebGLRenderingContext.prototype.bindTexture = _h( WebGLRenderingContext.prototype.bindTexture, function () { 151 | data.numBindTextures++; 152 | } ); 153 | 154 | function frameStart () { 155 | data.numDrawCalls = 0; 156 | data.numDrawArraysCalls = 0; 157 | data.numDrawArraysInstancedCalls = 0; 158 | data.numDrawElementsCalls = 0; 159 | data.numDrawElementsInstancedCalls = 0; 160 | data.numUseProgramCalls = 0; 161 | data.numFaces = 0; 162 | data.numVertices = 0; 163 | data.numPoints = 0; 164 | data.numBindTextures = 0; 165 | } 166 | 167 | function setupExtensions(context) { 168 | var ext = context.getExtension('ANGLE_instanced_arrays'); 169 | if (!ext) { 170 | return; 171 | } 172 | ext.drawArraysInstancedANGLE = _h( ext.drawArraysInstancedANGLE, function () { 173 | data.numDrawArraysInstancedCalls++; 174 | data.numDrawCalls++; 175 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 176 | else data.numVertices += arguments[ 2 ]; 177 | }); 178 | 179 | ext.drawElementsInstancedANGLE = _h( ext.drawElementsInstancedANGLE, function () { 180 | data.numDrawElementsInstancedCalls++; 181 | data.numDrawCalls++; 182 | if ( arguments[ 0 ] == this.POINTS ) data.numPoints += arguments[ 2 ]; 183 | else data.numVertices += arguments[ 2 ]; 184 | }); 185 | } 186 | 187 | function getSummary() { 188 | var result = {}; 189 | Object.keys(stats).forEach(key => { 190 | result[key] = { 191 | min: stats[key].min, 192 | max: stats[key].max, 193 | avg: stats[key].mean, 194 | standard_deviation: stats[key].standard_deviation 195 | }; 196 | }); 197 | return result; 198 | } 199 | 200 | return { 201 | getCurrentData: () => {return data;}, 202 | setupExtensions: setupExtensions, 203 | getSummary: getSummary, 204 | frameStart: frameStart, 205 | frameEnd: frameEnd 206 | 207 | //enable: enable, 208 | //disable: disable 209 | } 210 | } 211 | 212 | var index = WebGLStats(); 213 | 214 | return index; 215 | 216 | }))); 217 | --------------------------------------------------------------------------------