├── .eslintignore
├── .eslintrc.cjs
├── .github
└── workflows
│ ├── build-and-publish.yml
│ └── test.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── _config.yml
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── augment-api.js
├── texture-utils.js
├── utils.js
├── webgl-memory.js
└── wrap.js
├── test
├── assert.js
├── index.html
├── index.js
├── js
│ └── twgl-full.module.js
├── mocha-support.js
├── mocha.css
├── mocha.js
├── puppeteer.js
├── tests
│ ├── buffer-tests.js
│ ├── contextloss-tests.js
│ ├── info-tests.js
│ ├── program-tests.js
│ ├── renderbuffer-tests.js
│ ├── sampler-tests.js
│ ├── shader-tests.js
│ ├── stack-tests.js
│ ├── sync-tests.js
│ ├── test-utils.js
│ ├── texture-tests.js
│ ├── transformfeedback-tests.js
│ └── vertexarray-tests.js
└── webgl.js
├── webgl-memory.js
└── webgl-memory.png
/.eslintignore:
--------------------------------------------------------------------------------
1 | test/mocha.js
2 | test/js/twgl-full.module.js
3 | webgl-memory.js
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* global module */
2 | module.exports = {
3 | "env": {
4 | "browser": true,
5 | "es6": true,
6 | },
7 | "globals": {
8 | "chroma": true,
9 | "importScripts": true,
10 | "SharedArrayBuffer": true,
11 | "twgl": true,
12 | },
13 | "parserOptions": {
14 | "sourceType": "module",
15 | "ecmaVersion": 2020,
16 | },
17 | "plugins": [
18 | "eslint-plugin-html",
19 | "eslint-plugin-optional-comma-spacing",
20 | "eslint-plugin-one-variable-per-var",
21 | "eslint-plugin-require-trailing-comma",
22 | ],
23 | "extends": "eslint:recommended",
24 | "rules": {
25 | "no-alert": 2,
26 | "no-array-constructor": 2,
27 | "no-caller": 2,
28 | "no-catch-shadow": 2,
29 | "no-const-assign": 2,
30 | "no-eval": 2,
31 | "no-extend-native": 2,
32 | "no-extra-bind": 2,
33 | "no-implied-eval": 2,
34 | "no-iterator": 2,
35 | "no-label-var": 2,
36 | "no-labels": 2,
37 | "no-lone-blocks": 2,
38 | "no-loop-func": 2,
39 | "no-multi-str": 2,
40 | "no-native-reassign": 2,
41 | "no-new": 2,
42 | "no-new-func": 2,
43 | "no-new-object": 2,
44 | "no-new-wrappers": 2,
45 | "no-octal-escape": 2,
46 | "no-process-exit": 2,
47 | "no-proto": 2,
48 | "no-return-assign": 2,
49 | "no-script-url": 2,
50 | "no-sequences": 2,
51 | "no-shadow-restricted-names": 2,
52 | "no-spaced-func": 2,
53 | "no-trailing-spaces": 2,
54 | "no-undef-init": 2,
55 | "no-underscore-dangle": 2,
56 | "no-unused-expressions": 2,
57 | "no-use-before-define": 0,
58 | "no-with": 2,
59 | "consistent-return": 2,
60 | "curly": [2, "all"],
61 | "no-extra-parens": [2, "functions"],
62 | "eqeqeq": 2,
63 | "new-cap": 2,
64 | "new-parens": 2,
65 | "semi-spacing": [2, {"before": false, "after": true}],
66 | "space-infix-ops": 2,
67 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
68 | "strict": [2, "function"],
69 | "yoda": [2, "never"],
70 | "prefer-const": 2,
71 | "no-var": 2,
72 |
73 | "brace-style": [2, "1tbs", { "allowSingleLine": false }],
74 | "camelcase": [0],
75 | "comma-spacing": 0,
76 | "comma-dangle": 0,
77 | "comma-style": [2, "last"],
78 | "dot-notation": 0,
79 | "eol-last": [0],
80 | "global-strict": [0],
81 | "key-spacing": [0],
82 | "no-comma-dangle": [0],
83 | "no-irregular-whitespace": 2,
84 | "no-multi-spaces": [0],
85 | "no-obj-calls": 2,
86 | "no-shadow": [0],
87 | "no-undef": 2,
88 | "no-unreachable": 2,
89 | "one-variable-per-var/one-variable-per-var": [2],
90 | "optional-comma-spacing/optional-comma-spacing": [2, {"after": true}],
91 | "quotes": [0, "single"],
92 | "require-trailing-comma/require-trailing-comma": [2],
93 | "semi": [2, "always"],
94 | "space-before-function-paren": [2, "never"],
95 | "keyword-spacing": [1, {"before": true, "after": true, "overrides": {}} ],
96 | },
97 | };
98 |
--------------------------------------------------------------------------------
/.github/workflows/build-and-publish.yml:
--------------------------------------------------------------------------------
1 | name: Build and Publish
2 | on:
3 | push:
4 | tags:
5 | - v*
6 | jobs:
7 | test:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout 🍔🍟🥤
11 | uses: actions/checkout@v4
12 | with:
13 | persist-credentials: false
14 |
15 | - name: Use Node.js 😂
16 | uses: actions/setup-node@v4
17 | with:
18 | node-version: 18
19 |
20 | - name: Test 🧪
21 | run: |
22 | npm ci
23 | npm run check-ci
24 |
25 | - name: Publish to NPM
26 | uses: JS-DevTools/npm-publish@v3
27 | with:
28 | token: ${{ secrets.NPM_TOKEN }}
29 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | jobs:
8 | test:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout 🍔🍟🥤
12 | uses: actions/checkout@v4
13 | with:
14 | persist-credentials: false
15 |
16 | - name: Use Node.js 😂
17 | uses: actions/setup-node@v4
18 | with:
19 | node-version: 18
20 |
21 | - name: Test 🧪
22 | run: |
23 | npm ci
24 | npm run check-ci
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .DS_Store
3 | node_modules
4 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021 Gregg Tavares
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebGL-Memory
2 |
3 |
4 |
5 | This is a WebGL-Memory tracker. You add the script to your page
6 | before you initialize WebGL and then for a given context
7 | you can ask how much WebGL memory you're using.
8 |
9 | Note: This is only a guess as various GPUs have different
10 | internal requirements. For example a GPU might require that
11 | RGB be expanded internally to RGBA. Similarly a GPU might
12 | have alignment requirements. Still, this is likely to give
13 | a reasonable approximation.
14 |
15 | ## Usage
16 |
17 | ```html
18 |
19 | ```
20 |
21 | or
22 |
23 | ```js
24 | import 'https://greggman.github.io/webgl-memory/webgl-memory.js';
25 | ```
26 |
27 | Then in your code
28 |
29 | ```js
30 | const ext = gl.getExtension('GMAN_webgl_memory');
31 | ...
32 | if (ext) {
33 | // memory info
34 | const info = ext.getMemoryInfo();
35 | // every texture, it's size, a stack of where it was created and a stack of where it was last updated.
36 | const textures = ext.getResourcesInfo(WebGLTexture);
37 | // every buffer, it's size, a stack of where it was created and a stack of where it was last updated.
38 | const buffers = ext.getResourcesInfo(WebGLBuffer);
39 | }
40 | ```
41 |
42 | The info returned is
43 |
44 | ```js
45 | {
46 | memory: {
47 | buffer:
48 | texture:
49 | renderbuffer:
50 | drawingbuffer:
51 | total:
52 | },
53 | resources: {
54 | buffer: ,
55 | renderbuffer:
56 | program:
57 | query:
58 | sampler:
59 | shader:
60 | sync:
61 | texture:
62 | transformFeedback:
63 | vertexArray:
64 | }
65 | }
66 | ```
67 |
68 | The data for textures and buffers
69 |
70 | ```js
71 | const ext = gl.getExtension('GMAN_webgl_memory');
72 | ...
73 | if (ext) {
74 | const tex = gl.createTexture(); // 1
75 | gl.bindTexture(gl.TEXTURE_2D, tex);
76 | gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 4, 1); // 2
77 |
78 | const buf = gl.createBuffer(); // 3
79 | gl.bindBuffer(gl.ARRAY_BUFFER);
80 | gl.bufferData(gl.ARRAY_BUFFER, 32, gl.STATIC_DRAW); // 4
81 |
82 |
83 | const textures = ext.getResourcesInfo(WebGLTexture);
84 | const buffers = ext.getResourcesInfo(WebGLBuffer);
85 | ```
86 |
87 | ```js
88 | textures = [
89 | { size: 16, stackCreated: '...1...', stackUpdated: '...2...' }
90 | ]
91 |
92 | buffers = [
93 | { size: 32, stackCreated: '...3'''., stackUpdated: '...4...' }
94 | ]
95 | ```
96 |
97 | ## Caveats
98 |
99 | 1. You must have WebGL error free code.
100 |
101 | If your code is generating WebGL errors you must fix those first
102 | before using this library. Consider using [webgl-lint](https://greggman.github.io/webgl-lint) to help find your errors.
103 |
104 | 2. Resource reference counting is not supported.
105 |
106 | In WebGL if you delete a WebGLObject (a buffer, a texture, etc..),
107 | then, if that object is still attached to something else (a buffer
108 | attached to a vertex array, a texture attached to a framebuffer,
109 | a shader attached to a program), the object is not actually deleted
110 | until it's detached or the thing it's attached to is itself deleted
111 | unless the thing it's attached to is currently bound. It's complicated 😭
112 |
113 | Tracking all of that in JavaScript is more work than I was willing
114 | to put in ATM. My belief is that the stuff that is still attached
115 | is usually not a problem because either (a) you'll delete the objects
116 | that are holding the attachments (b) you'll detach the attachments
117 | by binding new ones (c) you have a leak where you're creating more and
118 | more of these objects that hold attachments in which case you can find
119 | the issue by watching your resources counts climb.
120 |
121 | Given that it seemed okay to skip this for now.
122 |
123 | 3. Deletion by Garbage Collection (GC) is not supported
124 |
125 | In JavaScript and WebGL, it's possible to let things get auto deleted by GC.
126 |
127 | ```js
128 | {
129 | const buf = gl.createBuffer();
130 | gl.bindBuffer(gl.ARRAY_BUFFER, buf);
131 | gl.bufferData(gl.ARRAY_BUFFER, 1024 * 1024 * 256, gl.STATIC_DRAW);
132 | gl.bindBuffer(gl.ARRAY_BUFFER, null);
133 | }
134 | ```
135 |
136 | Given the code above, buffer will, at some point in the future, get automatically
137 | deleted. The problem is you have no idea when. JavaScript does know now the size of
138 | VRAM nor does it have any concept of the size of the WebGL buffer (256meg in this case).
139 | All JavaScript has is a tiny object that holds an ID for the actual OpenGL buffer
140 | and maybe a little metadata.
141 |
142 | That means there's absolutely no pressure to delete the buffer above in a timely
143 | manner nor either is there any way for JavaScript to know that releasing that
144 | object would free up VRAM.
145 |
146 | In other words. Let's say you had a total of 384meg of ram. You'd expect this to
147 | work.
148 |
149 | ```js
150 | {
151 | const a = new Uint32Array(256 * 1024 * 1024)
152 | }
153 | {
154 | const b = new Uint32Array(256 * 1024 * 1024)
155 | }
156 | ```
157 |
158 | The code above allocates 512meg. Given we were pretending the system only has 384meg,
159 | JavaScript will likely free `a` to make room for `b`
160 |
161 | Now, Let's do the WebGL case and assume 384meg of VRAM
162 |
163 | ```js
164 | {
165 | const a = gl.createBuffer();
166 | gl.bindBuffer(gl.ARRAY_BUFFER, buf);
167 | gl.bufferData(gl.ARRAY_BUFFER, 1024 * 1024 * 256, gl.STATIC_DRAW);
168 | gl.bindBuffer(gl.ARRAY_BUFFER, null);
169 | }
170 | {
171 | const b = gl.createBuffer();
172 | gl.bindBuffer(gl.ARRAY_BUFFER, buf);
173 | gl.bufferData(gl.ARRAY_BUFFER, 1024 * 1024 * 256, gl.STATIC_DRAW);
174 | gl.bindBuffer(gl.ARRAY_BUFFER, null);
175 | }
176 | ```
177 |
178 | In this case, JavaScript only sees `a` as taking a few bytes (the object that tracks
179 | the OpenGL resource) so it has no idea that it needs to free `a` to make room for `b`.
180 | This could would fail, ideally with `gl.OUT_OF_MEMORY`.
181 |
182 | That was the long way of saying, you should never count on GC for WebGL!
183 | Free your resources explicitly!
184 |
185 | That's also part of the reason why we don't support this case because
186 | counting on GC is not a useful solution.
187 |
188 | 4. `texImage2D/3D` vs `texStorage2D/3D`
189 |
190 | Be aware that `texImage2D/3D` *may* require double the memory of
191 | `texStorage2D/3D`.
192 |
193 | Based on the design of `texImage2D/3D`, every mip level can have a
194 | different size/format and so until it's time to draw, there is
195 | no way to know if those levels will be updated by the app to be
196 | matching. Further, in WebGL2, there's no way to know before draw time
197 | if the app will set `TEXTURE_BASE_LEVEL` and `TEXTURE_MAX_LEVEL` to
198 | be a *texture complete* subset of mip levels.
199 |
200 | WebGL-memory does not report this difference
201 | because it's up to the implementation what really happens behind the scenes.
202 | In general though, `texStorage2D/3D` has a much higher probability
203 | of using less memory overall.
204 |
205 | The tradeoff for using `texStorage` is that the texture's size is immutable.
206 | So, for example, if you wanted to wrap a user's image to a cube, and then
207 | change that image when the user selects a different sized image, with `texImage`
208 | you can just upload the new image to the existing texture. With `texStorage`
209 | you'd be required to create a new texture.
210 |
211 | 5. `ELEMENT_ARRAY_BUFFER`
212 |
213 | Buffers used with `ELEMENT_ARRAY_BUFFER` may need a second copy in ram.
214 | This is because WebGL requires no out of bounds memory access (eg,
215 | you have a buffer with 10 vertices but you have an index greater >= 10).
216 | This can be handled in 2 ways (1) if the driver advertizes "robustness"
217 | then rely on the driver (2) keep a copy of that data in ram and check
218 | before draw time that no indices are out of range.
219 |
220 | WebGL-memory does not report this difference because it's up to the
221 | implementation. Further, unlike the texture issue above there is
222 | nothing an app can do. Fortunately such buffers are usually
223 | a small percent of the data used by most WebGL apps.
224 |
225 | ## Example:
226 |
227 | [Click here for an Example](https://jsgist.org/?src=57dafa41cb1d2d5bc1520832db49f946)
228 | [Unity example here](https://greggman.github.io/webgl-memory-unity-example/)
229 |
230 | ## Development
231 |
232 | ```bash
233 | git clone https://github.com/greggman/webgl-memory.git
234 | cd webgl-memory
235 | npm install
236 | ```
237 |
238 | now serve the folder
239 |
240 | ```
241 | npx servez
242 | ```
243 |
244 | and go to [`http://localhost:8080/test?src=true`](http://localhost:8080/test?src=true)
245 |
246 | `src=true` tells the test harness to use the unrolled source from the `src` folder
247 | where as without it uses `webgl-memory.js` in the root folder which is built using
248 | `npm run build`.
249 |
250 | `grep=` will limit the tests as in `...?src=true&grep=renderbuffer` only
251 | runs the tests with `renderbuffer` in their description.
252 |
253 | ## Live Tests
254 |
255 | [built version](https://greggman.github.io/webgl-memory/test/)
256 | [source version](https://greggman.github.io/webgl-memory/test/?src=true)
257 |
258 | ## Thoughts
259 |
260 | I'm not totally convinced this is the right way to do this. If I was making a
261 | webgl app and I wanted to know this stuff I think I'd track it myself by wrapping
262 | my own creation functions.
263 |
264 | In other words, lets say I wanted to know how many times I call
265 | `fetch`.
266 |
267 | ```js
268 | const req = await fetch(url);
269 | const text = await req.text();
270 | ```
271 |
272 | I'd just refactor that
273 |
274 | ```js
275 | let fetchCount = 0;
276 | function doFetch(url) {
277 | fetchCount++;
278 | return fetch(url);
279 | }
280 |
281 | ...
282 | const req = await doFetch(url);
283 | const text = await req.text();
284 | ```
285 |
286 | No need for some fancy library. Simple.
287 |
288 | I could do similar things for WebGL functions.
289 |
290 | ```js
291 | let textureCount = 0;
292 | function makeTexture(gl) {
293 | textureCount++;
294 | return gl.createTexture(gl);
295 | }
296 | function freeTexture(gl, tex) {
297 | --textureCount;
298 | gl.deleteTexture(tex);
299 | }
300 |
301 | const tex = makeTexture(gl);
302 | ...
303 | freeTexture(gl, tex);
304 | ```
305 |
306 | Also, even if webgl-memory is an okay way to do it I'm not sure making it an extension was the best way
307 | vs just some library you call like `webglMemoryTracker.init(someWebGLRenderingContext)`.
308 | I structured it this way just because I used [webgl-lint](https://greggman.github.io/webgl-lint) as
309 | the basis to get this working.
310 |
311 | ## License
312 |
313 | [MIT](https://github.com/greggman/webgl-memory/blob/main/LICENCE.md)
314 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-hacker
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webgl-memory",
3 | "version": "1.1.2",
4 | "description": "A WebGL memory tracker",
5 | "main": "webgl-memory.js",
6 | "module": "webgl-memory.js",
7 | "type": "module",
8 | "scripts": {
9 | "build": "rollup -c",
10 | "check-ci": "npm run eslint && npm run build && npm run test",
11 | "eslint": "eslint \"**/*.js\"",
12 | "pre-push": "npm run eslint && npm run test",
13 | "test": "npm run test-browser",
14 | "test-browser": "node test/puppeteer.js"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/greggman/webgl-memory.git"
19 | },
20 | "keywords": [
21 | "webgl"
22 | ],
23 | "author": "Gregg Tavares",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/greggman/webgl-memory/issues"
27 | },
28 | "files": [
29 | "webgl-memory.js"
30 | ],
31 | "homepage": "https://github.com/greggman/webgl-memory#readme",
32 | "devDependencies": {
33 | "@rollup/plugin-node-resolve": "^15.2.3",
34 | "eslint": "^8.57.0",
35 | "eslint-plugin-html": "^8.1.1",
36 | "eslint-plugin-one-variable-per-var": "^0.0.3",
37 | "eslint-plugin-optional-comma-spacing": "^0.0.4",
38 | "eslint-plugin-require-trailing-comma": "^0.0.1",
39 | "express": "^4.21.2",
40 | "puppeteer": "^23.11.1",
41 | "rollup": "^4.29.1"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from '@rollup/plugin-node-resolve';
2 | import fs from 'fs';
3 |
4 | const pkg = JSON.parse(fs.readFileSync('package.json', {encoding: 'utf8'}));
5 | const banner = `/* webgl-memory@${pkg.version}, license MIT */`;
6 |
7 | export default [
8 | {
9 | input: 'src/webgl-memory.js',
10 | plugins: [
11 | resolve({
12 | modulesOnly: true,
13 | }),
14 | ],
15 | output: [
16 | {
17 | format: 'umd',
18 | file: 'webgl-memory.js',
19 | indent: ' ',
20 | banner,
21 | },
22 | ],
23 | },
24 | ];
25 |
--------------------------------------------------------------------------------
/src/augment-api.js:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2021 Gregg Tavares
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 |
24 | import {
25 | getBytesForMip,
26 | } from './texture-utils.js';
27 | import {
28 | computeDrawingbufferSize,
29 | getDrawingbufferInfo,
30 | isBufferSource,
31 | isNumber,
32 | getStackTrace,
33 | collectObjects,
34 | } from './utils.js';
35 |
36 | //------------ [ from https://github.com/KhronosGroup/WebGLDeveloperTools ]
37 |
38 | /*
39 | ** Copyright (c) 2012 The Khronos Group Inc.
40 | **
41 | ** Permission is hereby granted, free of charge, to any person obtaining a
42 | ** copy of this software and/or associated documentation files (the
43 | ** "Materials"), to deal in the Materials without restriction, including
44 | ** without limitation the rights to use, copy, modify, merge, publish,
45 | ** distribute, sublicense, and/or sell copies of the Materials, and to
46 | ** permit persons to whom the Materials are furnished to do so, subject to
47 | ** the following conditions:
48 | **
49 | ** The above copyright notice and this permission notice shall be included
50 | ** in all copies or substantial portions of the Materials.
51 | **
52 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
53 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
54 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
55 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
56 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
57 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
58 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
59 | */
60 |
61 |
62 | const augmentedSet = new Set();
63 |
64 | /**
65 | * Given a WebGL context replaces all the functions with wrapped functions
66 | * that call gl.getError after every command
67 | *
68 | * @param {WebGLRenderingContext|Extension} ctx The webgl context to wrap.
69 | * @param {string} nameOfClass (eg, webgl, webgl2, OES_texture_float)
70 | */
71 | // eslint-disable-next-line consistent-return
72 | export function augmentAPI(ctx, nameOfClass, options = {}) {
73 |
74 | if (augmentedSet.has(ctx)) {
75 | return ctx;
76 | }
77 | augmentedSet.add(ctx);
78 |
79 | const origGLErrorFn = options.origGLErrorFn || ctx.getError;
80 |
81 | function createSharedState(ctx) {
82 | const drawingBufferInfo = getDrawingbufferInfo(ctx);
83 | const sharedState = {
84 | baseContext: ctx,
85 | config: options,
86 | apis: {
87 | // custom extension
88 | gman_webgl_memory: {
89 | ctx: {
90 | getMemoryInfo() {
91 | const drawingbuffer = computeDrawingbufferSize(ctx, drawingBufferInfo);
92 | return {
93 | memory: {
94 | ...memory,
95 | drawingbuffer,
96 | total: drawingbuffer + memory.buffer + memory.texture + memory.renderbuffer,
97 | },
98 | resources: {
99 | ...resources,
100 | },
101 | };
102 | },
103 | getResourcesInfo(type) {
104 | return collectObjects(sharedState, type);
105 | },
106 | },
107 | },
108 | },
109 | resources: {},
110 | memory: {
111 | texture: 0,
112 | buffer: 0,
113 | renderbuffer: 0,
114 | },
115 | bindings: new Map(),
116 | defaultVertexArray: {},
117 | webglObjectToMemory: new Map(),
118 | };
119 |
120 | const unRestorableAPIs = new Set([
121 | 'webgl',
122 | 'webgl2',
123 | 'webgl_lose_context',
124 | ]);
125 |
126 | function resetSharedState() {
127 | sharedState.bindings.clear();
128 | sharedState.webglObjectToMemory.clear();
129 | sharedState.webglObjectToMemory.set(sharedState.defaultVertexArray, {});
130 | sharedState.currentVertexArray = sharedState.defaultVertexArray;
131 | [sharedState.resources, sharedState.memory].forEach(function(obj) {
132 | for (const prop in obj) {
133 | obj[prop] = 0;
134 | }
135 | });
136 | }
137 |
138 | function handleContextLost() {
139 | // Issues:
140 | // * all resources are lost.
141 | // Solution: handled by resetSharedState
142 | // * all functions are no-op
143 | // Solutions:
144 | // * swap all functions for noop
145 | // (not so easy because some functions return values)
146 | // * wrap all functions is a isContextLost check forwarder
147 | // (slow? and same as above)
148 | // * have each function manually check for context lost
149 | // (simple but repetitive)
150 | // * all extensions are lost
151 | // Solution: For these we go through and restore all the functions
152 | // on each extension
153 | resetSharedState();
154 | sharedState.isContextLost = true;
155 |
156 | // restore all original functions for extensions since
157 | // user will have to get new extensions.
158 | for (const [name, {ctx, origFuncs}] of [...Object.entries(sharedState.apis)]) {
159 | if (!unRestorableAPIs.has(name) && origFuncs) {
160 | augmentedSet.delete(ctx);
161 | for (const [funcName, origFn] of Object.entries(origFuncs)) {
162 | ctx[funcName] = origFn;
163 | }
164 | delete apis[name];
165 | }
166 | }
167 | }
168 |
169 | function handleContextRestored() {
170 | sharedState.isContextLost = false;
171 | }
172 |
173 | if (ctx.canvas) {
174 | ctx.canvas.addEventListener('webglcontextlost', handleContextLost);
175 | ctx.canvas.addEventListener('webglcontextrestored', handleContextRestored);
176 | }
177 |
178 | resetSharedState();
179 | return sharedState;
180 | }
181 |
182 | const sharedState = options.sharedState || createSharedState(ctx);
183 | options.sharedState = sharedState;
184 |
185 | const {
186 | apis,
187 | bindings,
188 | memory,
189 | resources,
190 | webglObjectToMemory,
191 | } = sharedState;
192 |
193 | const origFuncs = {};
194 |
195 | function noop() {
196 | }
197 |
198 | function makeCreateWrapper(ctx, typeName, _funcName) {
199 | const funcName = _funcName || `create${typeName[0].toUpperCase()}${typeName.substr(1)}`;
200 | if (!ctx[funcName]) {
201 | return null;
202 | }
203 | resources[typeName] = 0;
204 | return function(ctx, funcName, args, webglObj) {
205 | if (sharedState.isContextLost) {
206 | return;
207 | }
208 | ++resources[typeName];
209 | webglObjectToMemory.set(webglObj, {
210 | size: 0,
211 | stackCreated: getStackTrace(),
212 | });
213 | };
214 | }
215 |
216 | function makeDeleteWrapper(typeName, fn = noop, _funcName) {
217 | const funcName = _funcName || `delete${typeName[0].toUpperCase()}${typeName.substr(1)}`;
218 | if (!ctx[funcName]) {
219 | return null;
220 | }
221 | return function(ctx, funcName, args) {
222 | if (sharedState.isContextLost) {
223 | return;
224 | }
225 | const [obj] = args;
226 | const info = webglObjectToMemory.get(obj);
227 | if (info) {
228 | --resources[typeName];
229 | fn(obj, info);
230 | // TODO: handle resource counts
231 | webglObjectToMemory.delete(obj);
232 | }
233 | };
234 | }
235 |
236 | function updateRenderbuffer(target, samples, internalFormat, width, height) {
237 | if (sharedState.isContextLost) {
238 | return;
239 | }
240 | const obj = bindings.get(target);
241 | if (!obj) {
242 | throw new Error(`no renderbuffer bound to ${target}`);
243 | }
244 | const info = webglObjectToMemory.get(obj);
245 | if (!info) {
246 | throw new Error(`unknown renderbuffer ${obj}`);
247 | }
248 |
249 | const bytesForMip = getBytesForMip(internalFormat, width, height, 1);
250 | const newSize = bytesForMip * samples;
251 |
252 | memory.renderbuffer -= info.size;
253 | info.size = newSize;
254 | info.stackUpdated = getStackTrace();
255 | memory.renderbuffer += newSize;
256 | }
257 |
258 | const ELEMENT_ARRAY_BUFFER = 0x8893;
259 |
260 | const UNSIGNED_BYTE = 0x1401;
261 | const TEXTURE_CUBE_MAP = 0x8513;
262 | const TEXTURE_2D_ARRAY = 0x8C1A;
263 | const TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515;
264 | const TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516;
265 | const TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517;
266 | const TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518;
267 | const TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519;
268 | const TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A;
269 |
270 | const TEXTURE_BASE_LEVEL = 0x813C;
271 | const TEXTURE_MAX_LEVEL = 0x813D;
272 |
273 | const cubemapTargets = new Set([
274 | TEXTURE_CUBE_MAP_POSITIVE_X,
275 | TEXTURE_CUBE_MAP_NEGATIVE_X,
276 | TEXTURE_CUBE_MAP_POSITIVE_Y,
277 | TEXTURE_CUBE_MAP_NEGATIVE_Y,
278 | TEXTURE_CUBE_MAP_POSITIVE_Z,
279 | TEXTURE_CUBE_MAP_NEGATIVE_Z,
280 | ]);
281 |
282 | function isCubemapFace(target) {
283 | return cubemapTargets.has(target);
284 | }
285 |
286 | function getTextureInfo(target) {
287 | target = isCubemapFace(target) ? TEXTURE_CUBE_MAP : target;
288 | const obj = bindings.get(target);
289 | if (!obj) {
290 | throw new Error(`no texture bound to ${target}`);
291 | }
292 | const info = webglObjectToMemory.get(obj);
293 | if (!info) {
294 | throw new Error(`unknown texture ${obj}`);
295 | }
296 | return info;
297 | }
298 |
299 | function updateMipLevel(info, target, level, internalFormat, width, height, depth, type) {
300 | const oldSize = info.size;
301 | const newMipSize = getBytesForMip(internalFormat, width, height, depth, type);
302 |
303 | const faceNdx = isCubemapFace(target)
304 | ? target - TEXTURE_CUBE_MAP_POSITIVE_X
305 | : 0;
306 |
307 | info.mips = info.mips || [];
308 | info.mips[level] = info.mips[level] || [];
309 | const mipFaceInfo = info.mips[level][faceNdx] || {};
310 | info.size -= mipFaceInfo.size || 0;
311 |
312 | mipFaceInfo.size = newMipSize;
313 | mipFaceInfo.internalFormat = internalFormat;
314 | mipFaceInfo.type = type;
315 | mipFaceInfo.width = width;
316 | mipFaceInfo.height = height;
317 | mipFaceInfo.depth = depth;
318 |
319 | info.mips[level][faceNdx] = mipFaceInfo;
320 | info.size += newMipSize;
321 |
322 | memory.texture -= oldSize;
323 | memory.texture += info.size;
324 |
325 | info.stackUpdated = getStackTrace();
326 | }
327 |
328 | function updateTexStorage(target, levels, internalFormat, width, height, depth) {
329 | const info = getTextureInfo(target);
330 | const numFaces = target === TEXTURE_CUBE_MAP ? 6 : 1;
331 | const baseFaceTarget = target === TEXTURE_CUBE_MAP ? TEXTURE_CUBE_MAP_POSITIVE_X : target;
332 | for (let level = 0; level < levels; ++level) {
333 | for (let face = 0; face < numFaces; ++face) {
334 | updateMipLevel(info, baseFaceTarget + face, level, internalFormat, width, height, depth);
335 | }
336 | width = Math.ceil(Math.max(width / 2, 1));
337 | height = Math.ceil(Math.max(height / 2, 1));
338 | depth = target === TEXTURE_2D_ARRAY ? depth : Math.ceil(Math.max(depth / 2, 1));
339 | }
340 | }
341 |
342 | function handleBindVertexArray(gl, funcName, args) {
343 | if (sharedState.isContextLost) {
344 | return;
345 | }
346 | const [va] = args;
347 | sharedState.currentVertexArray = va ? va : sharedState.defaultVertexArray;
348 | }
349 |
350 | function handleBufferBinding(target, obj) {
351 | if (sharedState.isContextLost) {
352 | return;
353 | }
354 | switch (target) {
355 | case ELEMENT_ARRAY_BUFFER: {
356 | const info = webglObjectToMemory.get(sharedState.currentVertexArray);
357 | info.elementArrayBuffer = obj;
358 | break;
359 | }
360 | default:
361 | bindings.set(target, obj);
362 | break;
363 | }
364 | }
365 |
366 | const preChecks = {};
367 | const postChecks = {
368 | // WebGL1
369 | // void bufferData(GLenum target, GLsizeiptr size, GLenum usage);
370 | // void bufferData(GLenum target, [AllowShared] BufferSource? srcData, GLenum usage);
371 | // WebGL2:
372 | // void bufferData(GLenum target, [AllowShared] ArrayBufferView srcData, GLenum usage, GLuint srcOffset,
373 | // optional GLuint length = 0);
374 | bufferData(gl, funcName, args) {
375 | if (sharedState.isContextLost) {
376 | return;
377 | }
378 | const [target, src, /* usage */, /*srcOffset = 0*/, length = undefined] = args;
379 | let obj;
380 | switch (target) {
381 | case ELEMENT_ARRAY_BUFFER:
382 | {
383 | const info = webglObjectToMemory.get(sharedState.currentVertexArray);
384 | obj = info.elementArrayBuffer;
385 | }
386 | break;
387 | default:
388 | obj = bindings.get(target);
389 | break;
390 | }
391 | if (!obj) {
392 | throw new Error(`no buffer bound to ${target}`);
393 | }
394 | let newSize = 0;
395 | if (length !== undefined) {
396 | newSize = length * src.BYTES_PER_ELEMENT;
397 | } else if (isBufferSource(src)) {
398 | newSize = src.byteLength;
399 | } else if (isNumber(src)) {
400 | newSize = src;
401 | } else {
402 | throw new Error(`unsupported bufferData src type ${src}`);
403 | }
404 |
405 | const info = webglObjectToMemory.get(obj);
406 | if (!info) {
407 | throw new Error(`unknown buffer ${obj}`);
408 | }
409 |
410 | memory.buffer -= info.size;
411 | info.size = newSize;
412 | info.stackUpdated = getStackTrace();
413 | memory.buffer += newSize;
414 | },
415 |
416 | bindVertexArray: handleBindVertexArray,
417 | bindVertexArrayOES: handleBindVertexArray,
418 |
419 | bindBuffer(gl, funcName, args) {
420 | const [target, obj] = args;
421 | handleBufferBinding(target, obj);
422 | },
423 |
424 | bindBufferBase(gl, funcName, args) {
425 | const [target, /*ndx*/, obj] = args;
426 | handleBufferBinding(target, obj);
427 | },
428 |
429 | bindBufferRange(gl, funcName, args) {
430 | const [target, /*ndx*/, obj, /*offset*/, /*size*/] = args;
431 | handleBufferBinding(target, obj);
432 | },
433 |
434 | bindRenderbuffer(gl, funcName, args) {
435 | if (sharedState.isContextLost) {
436 | return;
437 | }
438 | const [target, obj] = args;
439 | bindings.set(target, obj);
440 | },
441 |
442 | bindTexture(gl, funcName, args) {
443 | if (sharedState.isContextLost) {
444 | return;
445 | }
446 | const [target, obj] = args;
447 | bindings.set(target, obj);
448 | },
449 |
450 | // void gl.copyTexImage2D(target, level, internalformat, x, y, width, height, border);
451 | copyTexImage2D(ctx, funcName, args) {
452 | if (sharedState.isContextLost) {
453 | return;
454 | }
455 | const [target, level, internalFormat, /*x*/, /*y*/, width, height, /*border*/] = args;
456 | const info = getTextureInfo(target);
457 | updateMipLevel(info, target, level, internalFormat, width, height, 1, UNSIGNED_BYTE);
458 | },
459 |
460 | createBuffer: makeCreateWrapper(ctx, 'buffer'),
461 | createFramebuffer: makeCreateWrapper(ctx, 'framebuffer'),
462 | createRenderbuffer: makeCreateWrapper(ctx, 'renderbuffer'),
463 | createProgram: makeCreateWrapper(ctx, 'program'),
464 | createQuery: makeCreateWrapper(ctx, 'query'),
465 | createShader: makeCreateWrapper(ctx, 'shader'),
466 | createSampler: makeCreateWrapper(ctx, 'sampler'),
467 | createTexture: makeCreateWrapper(ctx, 'texture'),
468 | createTransformFeedback: makeCreateWrapper(ctx, 'transformFeedback'),
469 | createVertexArray: makeCreateWrapper(ctx, 'vertexArray'),
470 | createVertexArrayOES: makeCreateWrapper(ctx, 'vertexArray', 'createVertexArrayOES'),
471 |
472 | // WebGL 1:
473 | // void gl.compressedTexImage2D(target, level, internalformat, width, height, border, ArrayBufferView? pixels);
474 | //
475 | // Additionally available in WebGL 2:
476 | // read from buffer bound to gl.PIXEL_UNPACK_BUFFER
477 | // void gl.compressedTexImage2D(target, level, internalformat, width, height, border, GLsizei imageSize, GLintptr offset);
478 | // void gl.compressedTexImage2D(target, level, internalformat, width, height, border,
479 | // ArrayBufferView srcData, optional srcOffset, optional srcLengthOverride);
480 | compressedTexImage2D(ctx, funcName, args) {
481 | if (sharedState.isContextLost) {
482 | return;
483 | }
484 | const [target, level, internalFormat, width, height] = args;
485 | const info = getTextureInfo(target);
486 | updateMipLevel(info, target, level, internalFormat, width, height, 1, UNSIGNED_BYTE);
487 | },
488 |
489 | // read from buffer bound to gl.PIXEL_UNPACK_BUFFER
490 | // void gl.compressedTexImage3D(target, level, internalformat, width, height, depth, border, GLsizei imageSize, GLintptr offset);
491 | // void gl.compressedTexImage3D(target, level, internalformat, width, height, depth, border,
492 | // ArrayBufferView srcData, optional srcOffset, optional srcLengthOverride);
493 | compressedTexImage3D(ctx, funcName, args) {
494 | if (sharedState.isContextLost) {
495 | return;
496 | }
497 | const [target, level, internalFormat, width, height, depth] = args;
498 | const info = getTextureInfo(target);
499 | updateMipLevel(info, target, level, internalFormat, width, height, depth, UNSIGNED_BYTE);
500 | },
501 |
502 | deleteBuffer: makeDeleteWrapper('buffer', function(obj, info) {
503 | memory.buffer -= info.size;
504 | }),
505 | deleteFramebuffer: makeDeleteWrapper('framebuffer'),
506 | deleteProgram: makeDeleteWrapper('program'),
507 | deleteQuery: makeDeleteWrapper('query'),
508 | deleteRenderbuffer: makeDeleteWrapper('renderbuffer', function(obj, info) {
509 | memory.renderbuffer -= info.size;
510 | }),
511 | deleteSampler: makeDeleteWrapper('sampler'),
512 | deleteShader: makeDeleteWrapper('shader'),
513 | deleteSync: makeDeleteWrapper('sync'),
514 | deleteTexture: makeDeleteWrapper('texture', function(obj, info) {
515 | memory.texture -= info.size;
516 | }),
517 | deleteTransformFeedback: makeDeleteWrapper('transformFeedback'),
518 | deleteVertexArray: makeDeleteWrapper('vertexArray'),
519 | deleteVertexArrayOES: makeDeleteWrapper('vertexArray', noop, 'deleteVertexArrayOES'),
520 |
521 | fenceSync: function(ctx) {
522 | if (sharedState.isContextLost) {
523 | return undefined;
524 | }
525 | if (!ctx.fenceSync) {
526 | return undefined;
527 | }
528 | resources.sync = 0;
529 | return function(ctx, funcName, args, webglObj) {
530 | ++resources.sync;
531 |
532 | webglObjectToMemory.set(webglObj, {
533 | size: 0,
534 | });
535 | };
536 | }(ctx),
537 |
538 | generateMipmap(ctx, funcName, args) {
539 | if (sharedState.isContextLost) {
540 | return;
541 | }
542 | const [target] = args;
543 | const info = getTextureInfo(target);
544 | const baseMipNdx = info.parameters ? info.parameters.get(TEXTURE_BASE_LEVEL) || 0 : 0;
545 | const maxMipNdx = info.parameters ? info.parameters.get(TEXTURE_MAX_LEVEL) || 1024 : 1024;
546 | const mipInfo = info.mips[baseMipNdx][0];
547 | let {width, height, depth} = mipInfo;
548 | const {internalFormat, type} = mipInfo;
549 | let level = baseMipNdx + 1;
550 |
551 | const numFaces = target === TEXTURE_CUBE_MAP ? 6 : 1;
552 | const baseFaceTarget = target === TEXTURE_CUBE_MAP ? TEXTURE_CUBE_MAP_POSITIVE_X : target;
553 | while (level <= maxMipNdx && !(width === 1 && height === 1 && (depth === 1 || target === TEXTURE_2D_ARRAY))) {
554 | width = Math.ceil(Math.max(width / 2, 1));
555 | height = Math.ceil(Math.max(height / 2, 1));
556 | depth = target === TEXTURE_2D_ARRAY ? depth : Math.ceil(Math.max(depth / 2, 1));
557 | for (let face = 0; face < numFaces; ++face) {
558 | updateMipLevel(info, baseFaceTarget + face, level, internalFormat, width, height, depth, type);
559 | }
560 | ++level;
561 | }
562 | },
563 |
564 | getSupportedExtensions(ctx, funcName, args, result) {
565 | if (sharedState.isContextLost) {
566 | return;
567 | }
568 | result.push('GMAN_webgl_memory');
569 | },
570 |
571 | // void gl.renderbufferStorage(target, internalFormat, width, height);
572 | // gl.RGBA4: 4 red bits, 4 green bits, 4 blue bits 4 alpha bits.
573 | // gl.RGB565: 5 red bits, 6 green bits, 5 blue bits.
574 | // gl.RGB5_A1: 5 red bits, 5 green bits, 5 blue bits, 1 alpha bit.
575 | // gl.DEPTH_COMPONENT16: 16 depth bits.
576 | // gl.STENCIL_INDEX8: 8 stencil bits.
577 | // gl.DEPTH_STENCIL
578 | renderbufferStorage(ctx, funcName, args) {
579 | const [target, internalFormat, width, height] = args;
580 | updateRenderbuffer(target, 1, internalFormat, width, height);
581 | },
582 |
583 | // void gl.renderbufferStorageMultisample(target, samples, internalFormat, width, height);
584 | renderbufferStorageMultisample(ctx, funcName, args) {
585 | const [target, samples, internalFormat, width, height] = args;
586 | updateRenderbuffer(target, samples, internalFormat, width, height);
587 | },
588 |
589 | texImage2D(ctx, funcName, args) {
590 | if (sharedState.isContextLost) {
591 | return;
592 | }
593 | // WebGL1:
594 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView? pixels);
595 | // void gl.texImage2D(target, level, internalformat, format, type, ImageData? pixels);
596 | // void gl.texImage2D(target, level, internalformat, format, type, HTMLImageElement? pixels);
597 | // void gl.texImage2D(target, level, internalformat, format, type, HTMLCanvasElement? pixels);
598 | // void gl.texImage2D(target, level, internalformat, format, type, HTMLVideoElement? pixels);
599 | // void gl.texImage2D(target, level, internalformat, format, type, ImageBitmap? pixels// );
600 |
601 | // WebGL2:
602 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, GLintptr offset);
603 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLCanvasElement source);
604 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLImageElement source);
605 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLVideoElement source);
606 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ImageBitmap source);
607 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ImageData source);
608 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView srcData, srcOffset);
609 | const [target, level, internalFormat] = args;
610 | let width;
611 | let height;
612 | let type;
613 | if (args.length === 6) {
614 | const src = args[5];
615 | width = src.width;
616 | height = src.height;
617 | type = args[4];
618 | } else {
619 | width = args[3];
620 | height = args[4];
621 | type = args[7];
622 | }
623 |
624 | const info = getTextureInfo(target);
625 | updateMipLevel(info, target, level, internalFormat, width, height, 1, type);
626 | },
627 |
628 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, GLintptr offset);
629 | //
630 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, HTMLCanvasElement source);
631 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, HTMLImageElement source);
632 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, HTMLVideoElement source);
633 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ImageBitmap source);
634 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ImageData source);
635 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ArrayBufferView? srcData);
636 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ArrayBufferView srcData, srcOffset);
637 |
638 | texImage3D(ctx, funcName, args) {
639 | if (sharedState.isContextLost) {
640 | return;
641 | }
642 | const [target, level, internalFormat, width, height, depth, /*border*/, /*format*/, type] = args;
643 | const info = getTextureInfo(target);
644 | updateMipLevel(info, target, level, internalFormat, width, height, depth, type);
645 | },
646 |
647 | texParameteri(ctx, funcName, args) {
648 | if (sharedState.isContextLost) {
649 | return;
650 | }
651 | const [target, pname, value] = args;
652 | const info = getTextureInfo(target);
653 | info.parameters = info.parameters || new Map();
654 | info.parameters.set(pname, value);
655 | },
656 |
657 | // void gl.texStorage2D(target, levels, internalformat, width, height);
658 | texStorage2D(ctx, funcName, args) {
659 | const [target, levels, internalFormat, width, height] = args;
660 | updateTexStorage(target, levels, internalFormat, width, height, 1);
661 | },
662 |
663 | // void gl.texStorage3D(target, levels, internalformat, width, height, depth);
664 | texStorage3D(ctx, funcName, args) {
665 | const [target, levels, internalFormat, width, height, depth] = args;
666 | updateTexStorage(target, levels, internalFormat, width, height, depth);
667 | },
668 | };
669 |
670 | const extraWrappers = {
671 | getExtension(ctx, propertyName) {
672 | if (sharedState.isContextLost) {
673 | return;
674 | }
675 | const origFn = ctx[propertyName];
676 | ctx[propertyName] = function(...args) {
677 | const extensionName = args[0].toLowerCase();
678 | const api = apis[extensionName];
679 | if (api) {
680 | return api.ctx;
681 | }
682 | const ext = origFn.call(ctx, ...args);
683 | if (ext) {
684 | augmentAPI(ext, extensionName, {...options, origGLErrorFn});
685 | }
686 | return ext;
687 | };
688 | },
689 | };
690 |
691 | // Makes a function that calls a WebGL function and then calls getError.
692 | function makeErrorWrapper(ctx, funcName) {
693 | const origFn = ctx[funcName];
694 | const preCheck = preChecks[funcName] || noop;
695 | const postCheck = postChecks[funcName] || noop;
696 | if (preCheck === noop && postChecks === noop) {
697 | return;
698 | }
699 | ctx[funcName] = function(...args) {
700 | preCheck(ctx, funcName, args);
701 | const result = origFn.call(ctx, ...args);
702 | postCheck(ctx, funcName, args, result);
703 | return result;
704 | };
705 | const extraWrapperFn = extraWrappers[funcName];
706 | if (extraWrapperFn) {
707 | extraWrapperFn(ctx, funcName, origGLErrorFn);
708 | }
709 | }
710 |
711 | // Wrap each function
712 | for (const propertyName in ctx) {
713 | if (typeof ctx[propertyName] === 'function') {
714 | origFuncs[propertyName] = ctx[propertyName];
715 | makeErrorWrapper(ctx, propertyName);
716 | }
717 | }
718 |
719 | apis[nameOfClass.toLowerCase()] = { ctx, origFuncs };
720 | }
721 |
722 |
--------------------------------------------------------------------------------
/src/texture-utils.js:
--------------------------------------------------------------------------------
1 |
2 | /* PixelFormat */
3 | const ALPHA = 0x1906;
4 | const RGB = 0x1907;
5 | const RGBA = 0x1908;
6 | const LUMINANCE = 0x1909;
7 | const LUMINANCE_ALPHA = 0x190A;
8 | const DEPTH_COMPONENT = 0x1902;
9 | const DEPTH_STENCIL = 0x84F9;
10 |
11 | const R8 = 0x8229;
12 | const R8_SNORM = 0x8F94;
13 | const R16F = 0x822D;
14 | const R32F = 0x822E;
15 | const R8UI = 0x8232;
16 | const R8I = 0x8231;
17 | const RG16UI = 0x823A;
18 | const RG16I = 0x8239;
19 | const RG32UI = 0x823C;
20 | const RG32I = 0x823B;
21 | const RG8 = 0x822B;
22 | const RG8_SNORM = 0x8F95;
23 | const RG16F = 0x822F;
24 | const RG32F = 0x8230;
25 | const RG8UI = 0x8238;
26 | const RG8I = 0x8237;
27 | const R16UI = 0x8234;
28 | const R16I = 0x8233;
29 | const R32UI = 0x8236;
30 | const R32I = 0x8235;
31 | const RGB8 = 0x8051;
32 | const SRGB8 = 0x8C41;
33 | const RGB565 = 0x8D62;
34 | const RGB8_SNORM = 0x8F96;
35 | const R11F_G11F_B10F = 0x8C3A;
36 | const RGB9_E5 = 0x8C3D;
37 | const RGB16F = 0x881B;
38 | const RGB32F = 0x8815;
39 | const RGB8UI = 0x8D7D;
40 | const RGB8I = 0x8D8F;
41 | const RGB16UI = 0x8D77;
42 | const RGB16I = 0x8D89;
43 | const RGB32UI = 0x8D71;
44 | const RGB32I = 0x8D83;
45 | const RGBA8 = 0x8058;
46 | const SRGB8_ALPHA8 = 0x8C43;
47 | const RGBA8_SNORM = 0x8F97;
48 | const RGB5_A1 = 0x8057;
49 | const RGBA4 = 0x8056;
50 | const RGB10_A2 = 0x8059;
51 | const RGBA16F = 0x881A;
52 | const RGBA32F = 0x8814;
53 | const RGBA8UI = 0x8D7C;
54 | const RGBA8I = 0x8D8E;
55 | const RGB10_A2UI = 0x906F;
56 | const RGBA16UI = 0x8D76;
57 | const RGBA16I = 0x8D88;
58 | const RGBA32I = 0x8D82;
59 | const RGBA32UI = 0x8D70;
60 |
61 | const DEPTH_COMPONENT16 = 0x81A5;
62 | const DEPTH_COMPONENT24 = 0x81A6;
63 | const DEPTH_COMPONENT32F = 0x8CAC;
64 | const DEPTH32F_STENCIL8 = 0x8CAD;
65 | const DEPTH24_STENCIL8 = 0x88F0;
66 | const STENCIL_INDEX8 = 0x8d48;
67 |
68 | /* DataType */
69 | // const BYTE = 0x1400;
70 | const UNSIGNED_BYTE = 0x1401;
71 | // const SHORT = 0x1402;
72 | const UNSIGNED_SHORT = 0x1403;
73 | // const INT = 0x1404;
74 | const UNSIGNED_INT = 0x1405;
75 | const FLOAT = 0x1406;
76 | const UNSIGNED_SHORT_4_4_4_4 = 0x8033;
77 | const UNSIGNED_SHORT_5_5_5_1 = 0x8034;
78 | const UNSIGNED_SHORT_5_6_5 = 0x8363;
79 | const HALF_FLOAT = 0x140B;
80 | const HALF_FLOAT_OES = 0x8D61; // Thanks Khronos for making this different >:(
81 | // const UNSIGNED_INT_2_10_10_10_REV = 0x8368;
82 | // const UNSIGNED_INT_10F_11F_11F_REV = 0x8C3B;
83 | // const UNSIGNED_INT_5_9_9_9_REV = 0x8C3E;
84 | // const FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8DAD;
85 | // const UNSIGNED_INT_24_8 = 0x84FA;
86 |
87 | const RG = 0x8227;
88 | const RG_INTEGER = 0x8228;
89 | const RED = 0x1903;
90 | const RED_INTEGER = 0x8D94;
91 | const RGB_INTEGER = 0x8D98;
92 | const RGBA_INTEGER = 0x8D99;
93 |
94 | const SRGB_ALPHA_EXT = 0x8C42;
95 |
96 |
97 | const formatInfo = {};
98 | {
99 | // NOTE: this is named `numColorComponents` vs `numComponents` so we can let Uglify mangle
100 | // the name.
101 | const f = formatInfo;
102 | f[ALPHA] = { numColorComponents: 1, };
103 | f[LUMINANCE] = { numColorComponents: 1, };
104 | f[LUMINANCE_ALPHA] = { numColorComponents: 2, };
105 | f[RGB] = { numColorComponents: 3, };
106 | f[RGBA] = { numColorComponents: 4, };
107 | f[RED] = { numColorComponents: 1, };
108 | f[RED_INTEGER] = { numColorComponents: 1, };
109 | f[RG] = { numColorComponents: 2, };
110 | f[RG_INTEGER] = { numColorComponents: 2, };
111 | f[RGB] = { numColorComponents: 3, };
112 | f[RGB_INTEGER] = { numColorComponents: 3, };
113 | f[RGBA] = { numColorComponents: 4, };
114 | f[RGBA_INTEGER] = { numColorComponents: 4, };
115 | f[DEPTH_COMPONENT] = { numColorComponents: 1, };
116 | f[DEPTH_STENCIL] = { numColorComponents: 2, };
117 | }
118 |
119 | /**
120 | * @typedef {Object} TextureFormatDetails
121 | * @property {number} textureFormat format to pass texImage2D and similar functions.
122 | * @property {boolean} colorRenderable true if you can render to this format of texture.
123 | * @property {boolean} textureFilterable true if you can filter the texture, false if you can ony use `NEAREST`.
124 | * @property {number[]} type Array of possible types you can pass to texImage2D and similar function
125 | * @property {Object.} bytesPerElementMap A map of types to bytes per element
126 | * @private
127 | */
128 |
129 | let s_textureInternalFormatInfo;
130 | function getTextureInternalFormatInfo(internalFormat) {
131 | if (!s_textureInternalFormatInfo) {
132 | // NOTE: these properties need unique names so we can let Uglify mangle the name.
133 | const t = {};
134 | // unsized formats
135 | t[ALPHA] = { bytesPerElement: [1, 2, 2, 4], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT], };
136 | t[LUMINANCE] = { bytesPerElement: [1, 2, 2, 4], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT], };
137 | t[LUMINANCE_ALPHA] = { bytesPerElement: [2, 4, 4, 8], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT], };
138 | t[RGB] = { bytesPerElement: [3, 6, 6, 12, 2], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT, UNSIGNED_SHORT_5_6_5], };
139 | t[RGBA] = { bytesPerElement: [4, 8, 8, 16, 2, 2], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT, UNSIGNED_SHORT_4_4_4_4, UNSIGNED_SHORT_5_5_5_1], };
140 | t[SRGB_ALPHA_EXT] = { bytesPerElement: [4, 8, 8, 16, 2, 2], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT, UNSIGNED_SHORT_4_4_4_4, UNSIGNED_SHORT_5_5_5_1], };
141 | t[DEPTH_COMPONENT] = { bytesPerElement: [2, 4], type: [UNSIGNED_INT, UNSIGNED_SHORT], };
142 | t[DEPTH_STENCIL] = { bytesPerElement: [4], };
143 |
144 | // sized formats
145 | t[R8] = { bytesPerElement: [1], };
146 | t[R8_SNORM] = { bytesPerElement: [1], };
147 | t[R16F] = { bytesPerElement: [2], };
148 | t[R32F] = { bytesPerElement: [4], };
149 | t[R8UI] = { bytesPerElement: [1], };
150 | t[R8I] = { bytesPerElement: [1], };
151 | t[R16UI] = { bytesPerElement: [2], };
152 | t[R16I] = { bytesPerElement: [2], };
153 | t[R32UI] = { bytesPerElement: [4], };
154 | t[R32I] = { bytesPerElement: [4], };
155 | t[RG8] = { bytesPerElement: [2], };
156 | t[RG8_SNORM] = { bytesPerElement: [2], };
157 | t[RG16F] = { bytesPerElement: [4], };
158 | t[RG32F] = { bytesPerElement: [8], };
159 | t[RG8UI] = { bytesPerElement: [2], };
160 | t[RG8I] = { bytesPerElement: [2], };
161 | t[RG16UI] = { bytesPerElement: [4], };
162 | t[RG16I] = { bytesPerElement: [4], };
163 | t[RG32UI] = { bytesPerElement: [8], };
164 | t[RG32I] = { bytesPerElement: [8], };
165 | t[RGB8] = { bytesPerElement: [3], };
166 | t[SRGB8] = { bytesPerElement: [3], };
167 | t[RGB565] = { bytesPerElement: [2], };
168 | t[RGB8_SNORM] = { bytesPerElement: [3], };
169 | t[R11F_G11F_B10F] = { bytesPerElement: [4], };
170 | t[RGB9_E5] = { bytesPerElement: [4], };
171 | t[RGB16F] = { bytesPerElement: [6], };
172 | t[RGB32F] = { bytesPerElement: [12], };
173 | t[RGB8UI] = { bytesPerElement: [3], };
174 | t[RGB8I] = { bytesPerElement: [3], };
175 | t[RGB16UI] = { bytesPerElement: [6], };
176 | t[RGB16I] = { bytesPerElement: [6], };
177 | t[RGB32UI] = { bytesPerElement: [12], };
178 | t[RGB32I] = { bytesPerElement: [12], };
179 | t[RGBA8] = { bytesPerElement: [4], };
180 | t[SRGB8_ALPHA8] = { bytesPerElement: [4], };
181 | t[RGBA8_SNORM] = { bytesPerElement: [4], };
182 | t[RGB5_A1] = { bytesPerElement: [2], };
183 | t[RGBA4] = { bytesPerElement: [2], };
184 | t[RGB10_A2] = { bytesPerElement: [4], };
185 | t[RGBA16F] = { bytesPerElement: [8], };
186 | t[RGBA32F] = { bytesPerElement: [16], };
187 | t[RGBA8UI] = { bytesPerElement: [4], };
188 | t[RGBA8I] = { bytesPerElement: [4], };
189 | t[RGB10_A2UI] = { bytesPerElement: [4], };
190 | t[RGBA16UI] = { bytesPerElement: [8], };
191 | t[RGBA16I] = { bytesPerElement: [8], };
192 | t[RGBA32I] = { bytesPerElement: [16], };
193 | t[RGBA32UI] = { bytesPerElement: [16], };
194 | // Sized Internal
195 | t[DEPTH_COMPONENT16] = { bytesPerElement: [2], };
196 | t[DEPTH_COMPONENT24] = { bytesPerElement: [4], };
197 | t[DEPTH_COMPONENT32F] = { bytesPerElement: [4], };
198 | t[DEPTH24_STENCIL8] = { bytesPerElement: [4], };
199 | t[DEPTH32F_STENCIL8] = { bytesPerElement: [4], };
200 | t[STENCIL_INDEX8] = { bytesPerElement: [1], };
201 |
202 | s_textureInternalFormatInfo = t;
203 | }
204 | return s_textureInternalFormatInfo[internalFormat];
205 | }
206 |
207 | function makeComputeBlockRectSizeFunction(blockWidth, blockHeight, bytesPerBlock) {
208 | return function(width, height, depth) {
209 | const blocksAcross = (width + blockWidth - 1) / blockWidth | 0;
210 | const blocksDown = (height + blockHeight - 1) / blockHeight | 0;
211 | return blocksAcross * blocksDown * bytesPerBlock * depth;
212 | };
213 | }
214 |
215 | function makeComputePaddedRectSizeFunction(minWidth, minHeight, divisor) {
216 | return function(width, height, depth) {
217 | return (Math.max(width, minWidth) * Math.max(height, minHeight) / divisor | 0) * depth;
218 | };
219 | }
220 |
221 | // WEBGL_compressed_texture_s3tc
222 | const COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
223 | const COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
224 | const COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
225 | const COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
226 | // WEBGL_compressed_texture_etc1
227 | const COMPRESSED_RGB_ETC1_WEBGL = 0x8D64;
228 | // WEBGL_compressed_texture_pvrtc
229 | const COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00;
230 | const COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01;
231 | const COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02;
232 | const COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03;
233 | // WEBGL_compressed_texture_etc
234 | const COMPRESSED_R11_EAC = 0x9270;
235 | const COMPRESSED_SIGNED_R11_EAC = 0x9271;
236 | const COMPRESSED_RG11_EAC = 0x9272;
237 | const COMPRESSED_SIGNED_RG11_EAC = 0x9273;
238 | const COMPRESSED_RGB8_ETC2 = 0x9274;
239 | const COMPRESSED_SRGB8_ETC2 = 0x9275;
240 | const COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276;
241 | const COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277;
242 | const COMPRESSED_RGBA8_ETC2_EAC = 0x9278;
243 | const COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279;
244 | // WEBGL_compressed_texture_astc
245 | const COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;
246 | const COMPRESSED_RGBA_ASTC_5x4_KHR = 0x93B1;
247 | const COMPRESSED_RGBA_ASTC_5x5_KHR = 0x93B2;
248 | const COMPRESSED_RGBA_ASTC_6x5_KHR = 0x93B3;
249 | const COMPRESSED_RGBA_ASTC_6x6_KHR = 0x93B4;
250 | const COMPRESSED_RGBA_ASTC_8x5_KHR = 0x93B5;
251 | const COMPRESSED_RGBA_ASTC_8x6_KHR = 0x93B6;
252 | const COMPRESSED_RGBA_ASTC_8x8_KHR = 0x93B7;
253 | const COMPRESSED_RGBA_ASTC_10x5_KHR = 0x93B8;
254 | const COMPRESSED_RGBA_ASTC_10x6_KHR = 0x93B9;
255 | const COMPRESSED_RGBA_ASTC_10x8_KHR = 0x93BA;
256 | const COMPRESSED_RGBA_ASTC_10x10_KHR = 0x93BB;
257 | const COMPRESSED_RGBA_ASTC_12x10_KHR = 0x93BC;
258 | const COMPRESSED_RGBA_ASTC_12x12_KHR = 0x93BD;
259 | const COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR = 0x93D0;
260 | const COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR = 0x93D1;
261 | const COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR = 0x93D2;
262 | const COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR = 0x93D3;
263 | const COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR = 0x93D4;
264 | const COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR = 0x93D5;
265 | const COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR = 0x93D6;
266 | const COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR = 0x93D7;
267 | const COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR = 0x93D8;
268 | const COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR = 0x93D9;
269 | const COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR = 0x93DA;
270 | const COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR = 0x93DB;
271 | const COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR = 0x93DC;
272 | const COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR = 0x93DD;
273 | // WEBGL_compressed_texture_s3tc_srgb
274 | const COMPRESSED_SRGB_S3TC_DXT1_EXT = 0x8C4C;
275 | const COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT = 0x8C4D;
276 | const COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT = 0x8C4E;
277 | const COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = 0x8C4F;
278 | // EXT_texture_compression_bptc
279 | const COMPRESSED_RGBA_BPTC_UNORM_EXT = 0x8E8C;
280 | const COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT = 0x8E8D;
281 | const COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT = 0x8E8E;
282 | const COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT = 0x8E8F;
283 | // EXT_texture_compression_rgtc
284 | const COMPRESSED_RED_RGTC1_EXT = 0x8DBB;
285 | const COMPRESSED_SIGNED_RED_RGTC1_EXT = 0x8DBC;
286 | const COMPRESSED_RED_GREEN_RGTC2_EXT = 0x8DBD;
287 | const COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT = 0x8DBE;
288 |
289 | const compressedTextureFunctions = new Map([
290 | [ COMPRESSED_RGB_S3TC_DXT1_EXT, makeComputeBlockRectSizeFunction(4, 4, 8) ],
291 | [ COMPRESSED_RGBA_S3TC_DXT1_EXT, makeComputeBlockRectSizeFunction(4, 4, 8) ],
292 | [ COMPRESSED_RGBA_S3TC_DXT3_EXT, makeComputeBlockRectSizeFunction(4, 4, 16) ],
293 | [ COMPRESSED_RGBA_S3TC_DXT5_EXT, makeComputeBlockRectSizeFunction(4, 4, 16) ],
294 |
295 | [ COMPRESSED_RGB_ETC1_WEBGL, makeComputeBlockRectSizeFunction(4, 4, 8) ],
296 |
297 | [ COMPRESSED_RGB_PVRTC_4BPPV1_IMG, makeComputePaddedRectSizeFunction(8, 8, 2) ],
298 | [ COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, makeComputePaddedRectSizeFunction(8, 8, 2) ],
299 | [ COMPRESSED_RGB_PVRTC_2BPPV1_IMG, makeComputePaddedRectSizeFunction(16, 8, 4) ],
300 | [ COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, makeComputePaddedRectSizeFunction(16, 8, 4) ],
301 |
302 | [ COMPRESSED_R11_EAC, makeComputeBlockRectSizeFunction(4, 4, 8) ],
303 | [ COMPRESSED_SIGNED_R11_EAC, makeComputeBlockRectSizeFunction(4, 4, 8) ],
304 | [ COMPRESSED_RGB8_ETC2, makeComputeBlockRectSizeFunction(4, 4, 8) ],
305 | [ COMPRESSED_SRGB8_ETC2, makeComputeBlockRectSizeFunction(4, 4, 8) ],
306 | [ COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, makeComputeBlockRectSizeFunction(4, 4, 8) ],
307 | [ COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, makeComputeBlockRectSizeFunction(4, 4, 8) ],
308 |
309 | [ COMPRESSED_RG11_EAC, makeComputeBlockRectSizeFunction(4, 4, 16) ],
310 | [ COMPRESSED_SIGNED_RG11_EAC, makeComputeBlockRectSizeFunction(4, 4, 16) ],
311 | [ COMPRESSED_RGBA8_ETC2_EAC, makeComputeBlockRectSizeFunction(4, 4, 16) ],
312 | [ COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, makeComputeBlockRectSizeFunction(4, 4, 16) ],
313 |
314 | [ COMPRESSED_RGBA_ASTC_4x4_KHR, makeComputeBlockRectSizeFunction(4, 4, 16) ],
315 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR, makeComputeBlockRectSizeFunction(4, 4, 16) ],
316 | [ COMPRESSED_RGBA_ASTC_5x4_KHR, makeComputeBlockRectSizeFunction(5, 4, 16) ],
317 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR, makeComputeBlockRectSizeFunction(5, 4, 16) ],
318 | [ COMPRESSED_RGBA_ASTC_5x5_KHR, makeComputeBlockRectSizeFunction(5, 5, 16) ],
319 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR, makeComputeBlockRectSizeFunction(5, 5, 16) ],
320 | [ COMPRESSED_RGBA_ASTC_6x5_KHR, makeComputeBlockRectSizeFunction(6, 5, 16) ],
321 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR, makeComputeBlockRectSizeFunction(6, 5, 16) ],
322 | [ COMPRESSED_RGBA_ASTC_6x6_KHR, makeComputeBlockRectSizeFunction(6, 6, 16) ],
323 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR, makeComputeBlockRectSizeFunction(6, 6, 16) ],
324 | [ COMPRESSED_RGBA_ASTC_8x5_KHR, makeComputeBlockRectSizeFunction(8, 5, 16) ],
325 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR, makeComputeBlockRectSizeFunction(8, 5, 16) ],
326 | [ COMPRESSED_RGBA_ASTC_8x6_KHR, makeComputeBlockRectSizeFunction(8, 6, 16) ],
327 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR, makeComputeBlockRectSizeFunction(8, 6, 16) ],
328 | [ COMPRESSED_RGBA_ASTC_8x8_KHR, makeComputeBlockRectSizeFunction(8, 8, 16) ],
329 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR, makeComputeBlockRectSizeFunction(8, 8, 16) ],
330 | [ COMPRESSED_RGBA_ASTC_10x5_KHR, makeComputeBlockRectSizeFunction(10, 5, 16) ],
331 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR, makeComputeBlockRectSizeFunction(10, 5, 16) ],
332 | [ COMPRESSED_RGBA_ASTC_10x6_KHR, makeComputeBlockRectSizeFunction(10, 6, 16) ],
333 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR, makeComputeBlockRectSizeFunction(10, 6, 16) ],
334 | [ COMPRESSED_RGBA_ASTC_10x8_KHR, makeComputeBlockRectSizeFunction(10, 8, 16) ],
335 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR, makeComputeBlockRectSizeFunction(10, 8, 16) ],
336 | [ COMPRESSED_RGBA_ASTC_10x10_KHR, makeComputeBlockRectSizeFunction(10, 10, 16) ],
337 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR, makeComputeBlockRectSizeFunction(10, 10, 16) ],
338 | [ COMPRESSED_RGBA_ASTC_12x10_KHR, makeComputeBlockRectSizeFunction(12, 10, 16) ],
339 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR, makeComputeBlockRectSizeFunction(12, 10, 16) ],
340 | [ COMPRESSED_RGBA_ASTC_12x12_KHR, makeComputeBlockRectSizeFunction(12, 12, 16) ],
341 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR, makeComputeBlockRectSizeFunction(12, 12, 16) ],
342 |
343 | [ COMPRESSED_SRGB_S3TC_DXT1_EXT, makeComputeBlockRectSizeFunction(4, 4, 8) ],
344 | [ COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, makeComputeBlockRectSizeFunction(4, 4, 8) ],
345 | [ COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, makeComputeBlockRectSizeFunction(4, 4, 16) ],
346 | [ COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, makeComputeBlockRectSizeFunction(4, 4, 16) ],
347 |
348 | [ COMPRESSED_RGBA_BPTC_UNORM_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ],
349 | [ COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ],
350 | [ COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ],
351 | [ COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ],
352 |
353 | [ COMPRESSED_RED_RGTC1_EXT, makeComputeBlockRectSizeFunction( 4, 4, 8 ) ],
354 | [ COMPRESSED_SIGNED_RED_RGTC1_EXT, makeComputeBlockRectSizeFunction( 4, 4, 8 ) ],
355 | [ COMPRESSED_RED_GREEN_RGTC2_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ],
356 | [ COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ],
357 | ]);
358 |
359 | /**
360 | * Gets the number of bytes per element for a given internalFormat / type
361 | * @param {number} internalFormat The internalFormat parameter from texImage2D etc..
362 | * @param {number} type The type parameter for texImage2D etc..
363 | * @return {number} the number of bytes per element for the given internalFormat, type combo
364 | * @memberOf module:twgl/textures
365 | */
366 | export function getBytesPerElementForInternalFormat(internalFormat, type) {
367 | const info = getTextureInternalFormatInfo(internalFormat);
368 | if (!info) {
369 | throw "unknown internal format";
370 | }
371 | if (info.type) {
372 | const ndx = info.type.indexOf(type);
373 | if (ndx < 0) {
374 | throw new Error(`unsupported type ${type} for internalformat ${internalFormat}`);
375 | }
376 | return info.bytesPerElement[ndx];
377 | }
378 | return info.bytesPerElement[0];
379 | }
380 |
381 | function getBytesForMipUncompressed(internalFormat, width, height, depth, type) {
382 | const bytesPerElement = getBytesPerElementForInternalFormat(internalFormat, type);
383 | return width * height * depth * bytesPerElement;
384 | }
385 |
386 | export function getBytesForMip(internalFormat, width, height, depth, type) {
387 | const fn = compressedTextureFunctions.get(internalFormat);
388 | return fn ? fn(width, height, depth) : getBytesForMipUncompressed(internalFormat, width, height, depth, type);
389 | }
390 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | export function isWebGL2(gl) {
2 | // a proxy for if this is webgl
3 | return !!gl.texImage3D;
4 | }
5 |
6 | export function isTypedArray(v) {
7 | return v && v.buffer && v.buffer instanceof ArrayBuffer;
8 | }
9 |
10 | export function isBufferSource(v) {
11 | return isTypedArray(v) || v instanceof ArrayBuffer;
12 | }
13 |
14 | // ---------------------------------
15 | const FLOAT = 0x1406;
16 | const INT = 0x1404;
17 | const BOOL = 0x8B56;
18 | const UNSIGNED_INT = 0x1405;
19 |
20 | const TEXTURE_BINDING_2D = 0x8069;
21 | const TEXTURE_BINDING_CUBE_MAP = 0x8514;
22 | const TEXTURE_BINDING_3D = 0x806A;
23 | const TEXTURE_BINDING_2D_ARRAY = 0x8C1D;
24 |
25 |
26 | const ARRAY_BUFFER = 0x8892;
27 | const ELEMENT_ARRAY_BUFFER = 0x8893;
28 | const ARRAY_BUFFER_BINDING = 0x8894;
29 | const ELEMENT_ARRAY_BUFFER_BINDING = 0x8895;
30 | const TEXTURE_2D = 0x0DE1;
31 | const TEXTURE_3D = 0x806F;
32 | const TEXTURE_2D_ARRAY = 0x8C1A;
33 | const TEXTURE_CUBE_MAP = 0x8513;
34 | const FRAMEBUFFER = 0x8D40;
35 | const RENDERBUFFER = 0x8D41;
36 | const FRAMEBUFFER_BINDING = 0x8CA6;
37 | const RENDERBUFFER_BINDING = 0x8CA7;
38 | const TRANSFORM_FEEDBACK_BUFFER = 0x8C8E;
39 | const TRANSFORM_FEEDBACK_BUFFER_BINDING = 0x8C8F;
40 | const DRAW_FRAMEBUFFER = 0x8CA9;
41 | const READ_FRAMEBUFFER = 0x8CA8;
42 | const READ_FRAMEBUFFER_BINDING = 0x8CAA;
43 | const UNIFORM_BUFFER = 0x8A11;
44 | const UNIFORM_BUFFER_BINDING = 0x8A28;
45 | const TRANSFORM_FEEDBACK = 0x8E22;
46 | const TRANSFORM_FEEDBACK_BINDING = 0x8E25;
47 |
48 | const bindPointMap = new Map([
49 | [ARRAY_BUFFER, ARRAY_BUFFER_BINDING],
50 | [ELEMENT_ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER_BINDING],
51 | [TEXTURE_2D, TEXTURE_BINDING_2D],
52 | [TEXTURE_CUBE_MAP, TEXTURE_BINDING_CUBE_MAP],
53 | [TEXTURE_3D, TEXTURE_BINDING_3D],
54 | [TEXTURE_2D_ARRAY, TEXTURE_BINDING_2D_ARRAY],
55 | [RENDERBUFFER, RENDERBUFFER_BINDING],
56 | [FRAMEBUFFER, FRAMEBUFFER_BINDING],
57 | [DRAW_FRAMEBUFFER, FRAMEBUFFER_BINDING],
58 | [READ_FRAMEBUFFER, READ_FRAMEBUFFER_BINDING],
59 | [UNIFORM_BUFFER, UNIFORM_BUFFER_BINDING],
60 | [TRANSFORM_FEEDBACK_BUFFER, TRANSFORM_FEEDBACK_BUFFER_BINDING],
61 | [TRANSFORM_FEEDBACK, TRANSFORM_FEEDBACK_BINDING],
62 | ]);
63 |
64 | export function getBindingQueryEnumForBindPoint(bindPoint) {
65 | return bindPointMap.get(bindPoint);
66 | }
67 |
68 | const BYTE = 0x1400;
69 | const SHORT = 0x1402;
70 | const UNSIGNED_BYTE = 0x1401;
71 | const UNSIGNED_SHORT = 0x1403;
72 |
73 | const glTypeToSizeMap = new Map([
74 | [BOOL , 1],
75 | [BYTE , 1],
76 | [UNSIGNED_BYTE , 1],
77 | [SHORT , 2],
78 | [UNSIGNED_SHORT , 2],
79 | [INT , 4],
80 | [UNSIGNED_INT , 4],
81 | [FLOAT , 4],
82 | ]);
83 |
84 | export function getBytesPerValueForGLType(type) {
85 | return glTypeToSizeMap.get(type) || 0;
86 | }
87 |
88 | const glTypeToTypedArrayMap = new Map([
89 | [UNSIGNED_BYTE, Uint8Array],
90 | [UNSIGNED_SHORT, Uint16Array],
91 | [UNSIGNED_INT, Uint32Array],
92 | ]);
93 |
94 | export function glTypeToTypedArray(type) {
95 | return glTypeToTypedArrayMap.get(type);
96 | }
97 |
98 | export function getDrawingbufferInfo(gl) {
99 | return {
100 | samples: gl.getParameter(gl.SAMPLES) || 1,
101 | depthBits: gl.getParameter(gl.DEPTH_BITS),
102 | stencilBits: gl.getParameter(gl.STENCIL_BITS),
103 | contextAttributes: gl.getContextAttributes(),
104 | };
105 | }
106 |
107 | function computeDepthStencilSize(drawingBufferInfo) {
108 | const {depthBits, stencilBits} = drawingBufferInfo;
109 | const depthSize = (depthBits + stencilBits + 7) / 8 | 0;
110 | return depthSize === 3 ? 4 : depthSize;
111 | }
112 |
113 | export function computeDrawingbufferSize(gl, drawingBufferInfo) {
114 | if (gl.isContextLost()) {
115 | return 0;
116 | }
117 | const {samples} = drawingBufferInfo;
118 | // this will need to change for hi-color support
119 | const colorSize = 4;
120 | const size = gl.drawingBufferWidth * gl.drawingBufferHeight;
121 | const depthStencilSize = computeDepthStencilSize(drawingBufferInfo);
122 | return size * colorSize + size * samples * colorSize + size * depthStencilSize;
123 | }
124 |
125 | // I know this is not a full check
126 | export function isNumber(v) {
127 | return typeof v === 'number';
128 | }
129 |
130 | export function collectObjects(state, type) {
131 | const list = [...state.webglObjectToMemory.keys()]
132 | .filter(obj => obj instanceof type)
133 | .map((obj) => state.webglObjectToMemory.get(obj));
134 |
135 | return list;
136 | }
137 |
138 | export function getStackTrace() {
139 | const stack = (new Error()).stack;
140 | const lines = stack.split('\n');
141 | // Remove the first two entries, the error message and this function itself, or the webgl-memory itself.
142 | const userLines = lines.slice(2).filter((l) => !l.includes('webgl-memory.js'));
143 | return userLines.join('\n');
144 | }
145 |
--------------------------------------------------------------------------------
/src/webgl-memory.js:
--------------------------------------------------------------------------------
1 | import './wrap.js';
2 |
--------------------------------------------------------------------------------
/src/wrap.js:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2021 Gregg Tavares
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 |
24 | import {augmentAPI} from './augment-api.js';
25 |
26 | function wrapGetContext(Ctor) {
27 | const oldFn = Ctor.prototype.getContext;
28 | Ctor.prototype.getContext = function(type, ...args) {
29 | const ctx = oldFn.call(this, type, ...args);
30 | // Using bindTexture to see if it's WebGL. Could check for instanceof WebGLRenderingContext
31 | // but that might fail if wrapped by debugging extension
32 | if (ctx && ctx.bindTexture) {
33 | const config = {};
34 | augmentAPI(ctx, type, config);
35 | ctx.getExtension('GMAN_webgl_memory');
36 | }
37 | return ctx;
38 | };
39 | }
40 |
41 | if (typeof HTMLCanvasElement !== 'undefined') {
42 | wrapGetContext(HTMLCanvasElement);
43 | }
44 | if (typeof OffscreenCanvas !== 'undefined') {
45 | wrapGetContext(OffscreenCanvas);
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/test/assert.js:
--------------------------------------------------------------------------------
1 | export const config = {};
2 |
3 | export function setConfig(options) {
4 | Object.assign(config, options);
5 | }
6 |
7 | function formatMsg(msg) {
8 | return `${msg}${msg ? ': ' : ''}`;
9 | }
10 |
11 | export function assertTruthy(actual, msg = '') {
12 | if (!config.noLint && !actual) {
13 | throw new Error(`${formatMsg(msg)}expected: truthy, actual: ${actual}`);
14 | }
15 | }
16 |
17 | export function assertFalsy(actual, msg = '') {
18 | if (!config.noLint && actual) {
19 | throw new Error(`${formatMsg(msg)}expected: falsy, actual: ${actual}`);
20 | }
21 | }
22 |
23 | export function assertStringMatchesRegEx(actual, regex, msg = '') {
24 | if (!config.noLint && !regex.test(actual)) {
25 | throw new Error(`${formatMsg(msg)}expected: ${regex}, actual: ${actual}`);
26 | }
27 | }
28 |
29 | export function assertEqual(actual, expected, msg = '') {
30 | if (!config.noLint && actual !== expected) {
31 | throw new Error(`${formatMsg(msg)}expected: ${expected} to equal actual: ${actual}`);
32 | }
33 | }
34 |
35 | export function assertNotEqual(actual, expected, msg = '') {
36 | if (!config.noLint && actual === expected) {
37 | throw new Error(`${formatMsg(msg)}expected: ${expected} to not equal actual: ${actual}`);
38 | }
39 | }
40 |
41 | export function assertThrowsWith(func, expectations, msg = '') {
42 | let error = '';
43 | if (config.throwOnError === false) {
44 | const origFn = console.error;
45 | const errors = [];
46 | console.error = function(...args) {
47 | errors.push(args.join(' '));
48 | };
49 | func();
50 | console.error = origFn;
51 | if (errors.length) {
52 | error = errors.join('\n');
53 | console.error(error);
54 | }
55 | } else {
56 | try {
57 | func();
58 | } catch (e) {
59 | console.error(e); // eslint-disable-line
60 | error = e;
61 | }
62 |
63 | }
64 |
65 | if (config.noLint) {
66 | return;
67 | }
68 |
69 | assertStringMatchesREs(error.toString().replace(/\n/g, ' '), expectations, msg);
70 | }
71 |
72 | // check if it throws it throws with x
73 | export function assertIfThrowsItThrowsWith(func, expectations, msg = '') {
74 | let error = '';
75 | let threw = false;
76 | if (config.throwOnError === false) {
77 | const origFn = console.error;
78 | const errors = [];
79 | console.error = function(...args) {
80 | errors.push(args.join(' '));
81 | };
82 | func();
83 | console.error = origFn;
84 | if (errors.length) {
85 | error = errors.join('\n');
86 | console.error(error);
87 | }
88 | } else {
89 | try {
90 | func();
91 | } catch (e) {
92 | console.error(e); // eslint-disable-line
93 | error = e;
94 | threw = true;
95 | }
96 |
97 | }
98 |
99 | if (config.noLint) {
100 | return;
101 | }
102 |
103 | if (!threw) {
104 | return;
105 | }
106 |
107 | assertStringMatchesREs(error.toString().replace(/\n/g, ' '), expectations, msg);
108 | }
109 |
110 | function assertStringMatchesREs(actual, expectations, msg) {
111 | for (const expectation of expectations) {
112 | if (expectation instanceof RegExp) {
113 | if (!expectation.test(actual)) {
114 | throw new Error(`${formatMsg(msg)}expected: ${expectation}, actual: ${actual}`);
115 | }
116 | }
117 | }
118 |
119 | }
120 | export function assertWarnsWith(func, expectations, msg = '') {
121 | const warnings = [];
122 | const origWarnFn = console.warn;
123 | console.warn = function(...args) {
124 | origWarnFn.call(this, ...args);
125 | warnings.push(args.join(' '));
126 | };
127 |
128 | let error;
129 | try {
130 | func();
131 | } catch (e) {
132 | error = e;
133 | }
134 |
135 | console.warn = origWarnFn;
136 |
137 | if (error) {
138 | throw error;
139 | }
140 |
141 | if (config.noLint) {
142 | return;
143 | }
144 |
145 | assertStringMatchesREs(warnings.join(' '), expectations, msg);
146 | }
147 |
148 | export default {
149 | false: assertFalsy,
150 | equal: assertEqual,
151 | matchesRegEx: assertStringMatchesRegEx,
152 | notEqual: assertNotEqual,
153 | throwsWith: assertThrowsWith,
154 | true: assertTruthy,
155 | };
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WebGL Memory Tests
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
33 |
34 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | /* global mocha */
2 |
3 | import './tests/buffer-tests.js';
4 | import './tests/contextloss-tests.js';
5 | import './tests/info-tests.js';
6 | import './tests/program-tests.js';
7 | import './tests/renderbuffer-tests.js';
8 | import './tests/sampler-tests.js';
9 | import './tests/shader-tests.js';
10 | import './tests/sync-tests.js';
11 | import './tests/texture-tests.js';
12 | import './tests/transformfeedback-tests.js';
13 | import './tests/vertexarray-tests.js';
14 | import './tests/stack-tests.js';
15 |
16 | const settings = Object.fromEntries(new URLSearchParams(window.location.search).entries());
17 | if (settings.reporter) {
18 | mocha.reporter(settings.reporter);
19 | }
20 | mocha.run((failures) => {
21 | window.testsPromiseInfo.resolve(failures);
22 | });
23 |
--------------------------------------------------------------------------------
/test/mocha-support.js:
--------------------------------------------------------------------------------
1 | export const describe = window.describe;
2 | export const it = window.it;
3 | export const before = window.before;
4 | export const after = window.after;
5 | export const beforeEach = window.beforeEach;
6 | export const afterEach = window.afterEach;
7 |
8 |
--------------------------------------------------------------------------------
/test/mocha.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | :root {
4 | --mocha-color: #000;
5 | --mocha-bg-color: #fff;
6 | --mocha-pass-icon-color: #00d6b2;
7 | --mocha-pass-color: #fff;
8 | --mocha-pass-shadow-color: rgba(0,0,0,.2);
9 | --mocha-pass-mediump-color: #c09853;
10 | --mocha-pass-slow-color: #b94a48;
11 | --mocha-test-pending-color: #0b97c4;
12 | --mocha-test-pending-icon-color: #0b97c4;
13 | --mocha-test-fail-color: #c00;
14 | --mocha-test-fail-icon-color: #c00;
15 | --mocha-test-fail-pre-color: #000;
16 | --mocha-test-fail-pre-error-color: #c00;
17 | --mocha-test-html-error-color: #000;
18 | --mocha-box-shadow-color: #eee;
19 | --mocha-box-bottom-color: #ddd;
20 | --mocha-test-replay-color: #888;
21 | --mocha-test-replay-bg-color: #eee;
22 | --mocha-stats-color: #888;
23 | --mocha-stats-em-color: #000;
24 | --mocha-stats-hover-color: #eee;
25 | --mocha-error-color: #c00;
26 |
27 | --mocha-code-comment: #ddd;
28 | --mocha-code-init: #2f6fad;
29 | --mocha-code-string: #5890ad;
30 | --mocha-code-keyword: #8a6343;
31 | --mocha-code-number: #2f6fad;
32 | }
33 |
34 | @media (prefers-color-scheme: dark) {
35 | :root {
36 | --mocha-color: #fff;
37 | --mocha-bg-color: #222;
38 | --mocha-pass-icon-color: #00d6b2;
39 | --mocha-pass-color: #222;
40 | --mocha-pass-shadow-color: rgba(255,255,255,.2);
41 | --mocha-pass-mediump-color: #f1be67;
42 | --mocha-pass-slow-color: #f49896;
43 | --mocha-test-pending-color: #0b97c4;
44 | --mocha-test-pending-icon-color: #0b97c4;
45 | --mocha-test-fail-color: #f44;
46 | --mocha-test-fail-icon-color: #f44;
47 | --mocha-test-fail-pre-color: #fff;
48 | --mocha-test-fail-pre-error-color: #f44;
49 | --mocha-test-html-error-color: #fff;
50 | --mocha-box-shadow-color: #444;
51 | --mocha-box-bottom-color: #555;
52 | --mocha-test-replay-color: #888;
53 | --mocha-test-replay-bg-color: #444;
54 | --mocha-stats-color: #aaa;
55 | --mocha-stats-em-color: #fff;
56 | --mocha-stats-hover-color: #444;
57 | --mocha-error-color: #f44;
58 |
59 | --mocha-code-comment: #ddd;
60 | --mocha-code-init: #9cc7f1;
61 | --mocha-code-string: #80d4ff;
62 | --mocha-code-keyword: #e3a470;
63 | --mocha-code-number: #4ca7ff;
64 | }
65 | }
66 |
67 | body {
68 | margin:0;
69 | background-color: var(--mocha-bg-color);
70 | color: var(--mocha-color);
71 | }
72 |
73 | #mocha {
74 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
75 | margin: 60px 50px;
76 | }
77 |
78 | #mocha ul,
79 | #mocha li {
80 | margin: 0;
81 | padding: 0;
82 | }
83 |
84 | #mocha ul {
85 | list-style: none;
86 | }
87 |
88 | #mocha h1,
89 | #mocha h2 {
90 | margin: 0;
91 | }
92 |
93 | #mocha h1 {
94 | margin-top: 15px;
95 | font-size: 1em;
96 | font-weight: 200;
97 | }
98 |
99 | #mocha h1 a {
100 | text-decoration: none;
101 | color: inherit;
102 | }
103 |
104 | #mocha h1 a:hover {
105 | text-decoration: underline;
106 | }
107 |
108 | #mocha .suite .suite h1 {
109 | margin-top: 0;
110 | font-size: .8em;
111 | }
112 |
113 | #mocha .hidden {
114 | display: none;
115 | }
116 |
117 | #mocha h2 {
118 | font-size: 12px;
119 | font-weight: normal;
120 | cursor: pointer;
121 | }
122 |
123 | #mocha .suite {
124 | margin-left: 15px;
125 | }
126 |
127 | #mocha .test {
128 | margin-left: 15px;
129 | overflow: hidden;
130 | }
131 |
132 | #mocha .test.pending:hover h2::after {
133 | content: '(pending)';
134 | font-family: arial, sans-serif;
135 | }
136 |
137 | #mocha .test.pass.medium .duration {
138 | background: var(--mocha-pass-mediump-color);
139 | }
140 |
141 | #mocha .test.pass.slow .duration {
142 | background: var(--mocha-pass-slow-color);
143 | }
144 |
145 | #mocha .test.pass::before {
146 | content: '✓';
147 | font-size: 12px;
148 | display: block;
149 | float: left;
150 | margin-right: 5px;
151 | color: var(--mocha-pass-icon-color);
152 | }
153 |
154 | #mocha .test.pass .duration {
155 | font-size: 9px;
156 | margin-left: 5px;
157 | padding: 2px 5px;
158 | color: var(--mocha-pass-color);
159 | -webkit-box-shadow: inset 0 1px 1px var(--mocha-pass-shadow-color);
160 | -moz-box-shadow: inset 0 1px 1px var(--mocha-pass-shadow-color);
161 | box-shadow: inset 0 1px 1px var(--mocha-pass-shadow-color);
162 | -webkit-border-radius: 5px;
163 | -moz-border-radius: 5px;
164 | -ms-border-radius: 5px;
165 | -o-border-radius: 5px;
166 | border-radius: 5px;
167 | }
168 |
169 | #mocha .test.pass.fast .duration {
170 | display: none;
171 | }
172 |
173 | #mocha .test.pending {
174 | color: var(--mocha-test-pending-color);
175 | }
176 |
177 | #mocha .test.pending::before {
178 | content: '◦';
179 | color: var(--mocha-test-pending-icon-color);
180 | }
181 |
182 | #mocha .test.fail {
183 | color: var(--mocha-test-fail-color);
184 | }
185 |
186 | #mocha .test.fail pre {
187 | color: var(--mocha-test-fail-pre-color);
188 | }
189 |
190 | #mocha .test.fail::before {
191 | content: '✖';
192 | font-size: 12px;
193 | display: block;
194 | float: left;
195 | margin-right: 5px;
196 | color: var(--mocha-pass-icon-color);
197 | }
198 |
199 | #mocha .test pre.error {
200 | color: var(--mocha-test-fail-pre-error-color);
201 | max-height: 300px;
202 | overflow: auto;
203 | }
204 |
205 | #mocha .test .html-error {
206 | overflow: auto;
207 | color: var(--mocha-test-html-error-color);
208 | display: block;
209 | float: left;
210 | clear: left;
211 | font: 12px/1.5 monaco, monospace;
212 | margin: 5px;
213 | padding: 15px;
214 | border: 1px solid var(--mocha-box-shadow-color);
215 | max-width: 85%; /*(1)*/
216 | max-width: -webkit-calc(100% - 42px);
217 | max-width: -moz-calc(100% - 42px);
218 | max-width: calc(100% - 42px); /*(2)*/
219 | max-height: 300px;
220 | word-wrap: break-word;
221 | border-bottom-color: var(--mocha-box-bottom-color);
222 | -webkit-box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
223 | -moz-box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
224 | box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
225 | -webkit-border-radius: 3px;
226 | -moz-border-radius: 3px;
227 | border-radius: 3px;
228 | }
229 |
230 | #mocha .test .html-error pre.error {
231 | border: none;
232 | -webkit-border-radius: 0;
233 | -moz-border-radius: 0;
234 | border-radius: 0;
235 | -webkit-box-shadow: 0;
236 | -moz-box-shadow: 0;
237 | box-shadow: 0;
238 | padding: 0;
239 | margin: 0;
240 | margin-top: 18px;
241 | max-height: none;
242 | }
243 |
244 | /**
245 | * (1): approximate for browsers not supporting calc
246 | * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
247 | * ^^ seriously
248 | */
249 | #mocha .test pre {
250 | display: block;
251 | float: left;
252 | clear: left;
253 | font: 12px/1.5 monaco, monospace;
254 | margin: 5px;
255 | padding: 15px;
256 | border: 1px solid var(--mocha-box-shadow-color);
257 | max-width: 85%; /*(1)*/
258 | max-width: -webkit-calc(100% - 42px);
259 | max-width: -moz-calc(100% - 42px);
260 | max-width: calc(100% - 42px); /*(2)*/
261 | word-wrap: break-word;
262 | border-bottom-color: var(--mocha-box-bottom-color);
263 | -webkit-box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
264 | -moz-box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
265 | box-shadow: 0 1px 3px var(--mocha-box-shadow-color);
266 | -webkit-border-radius: 3px;
267 | -moz-border-radius: 3px;
268 | border-radius: 3px;
269 | }
270 |
271 | #mocha .test h2 {
272 | position: relative;
273 | }
274 |
275 | #mocha .test a.replay {
276 | position: absolute;
277 | top: 3px;
278 | right: 0;
279 | text-decoration: none;
280 | vertical-align: middle;
281 | display: block;
282 | width: 15px;
283 | height: 15px;
284 | line-height: 15px;
285 | text-align: center;
286 | background: var(--mocha-test-replay-bg-color);
287 | font-size: 15px;
288 | -webkit-border-radius: 15px;
289 | -moz-border-radius: 15px;
290 | border-radius: 15px;
291 | -webkit-transition:opacity 200ms;
292 | -moz-transition:opacity 200ms;
293 | -o-transition:opacity 200ms;
294 | transition: opacity 200ms;
295 | opacity: 0.3;
296 | color: var(--mocha-test-replay-color);
297 | }
298 |
299 | #mocha .test:hover a.replay {
300 | opacity: 1;
301 | }
302 |
303 | #mocha-report.pass .test.fail {
304 | display: none;
305 | }
306 |
307 | #mocha-report.fail .test.pass {
308 | display: none;
309 | }
310 |
311 | #mocha-report.pending .test.pass,
312 | #mocha-report.pending .test.fail {
313 | display: none;
314 | }
315 | #mocha-report.pending .test.pass.pending {
316 | display: block;
317 | }
318 |
319 | #mocha-error {
320 | color: var(--mocha-error-color);
321 | font-size: 1.5em;
322 | font-weight: 100;
323 | letter-spacing: 1px;
324 | }
325 |
326 | #mocha-stats {
327 | position: fixed;
328 | top: 15px;
329 | right: 10px;
330 | font-size: 12px;
331 | margin: 0;
332 | color: var(--mocha-stats-color);
333 | z-index: 1;
334 | }
335 |
336 | #mocha-stats .progress {
337 | float: right;
338 | padding-top: 0;
339 |
340 | /**
341 | * Set safe initial values, so mochas .progress does not inherit these
342 | * properties from Bootstrap .progress (which causes .progress height to
343 | * equal line height set in Bootstrap).
344 | */
345 | height: auto;
346 | -webkit-box-shadow: none;
347 | -moz-box-shadow: none;
348 | box-shadow: none;
349 | background-color: initial;
350 | }
351 |
352 | #mocha-stats em {
353 | color: var(--mocha-stats-em-color);
354 | }
355 |
356 | #mocha-stats a {
357 | text-decoration: none;
358 | color: inherit;
359 | }
360 |
361 | #mocha-stats a:hover {
362 | border-bottom: 1px solid var(--mocha-stats-hover-color);
363 | }
364 |
365 | #mocha-stats li {
366 | display: inline-block;
367 | margin: 0 5px;
368 | list-style: none;
369 | padding-top: 11px;
370 | }
371 |
372 | #mocha-stats canvas {
373 | width: 40px;
374 | height: 40px;
375 | }
376 |
377 | #mocha code .comment { color: var(--mocha-code-comment); }
378 | #mocha code .init { color: var(--mocha-code-init); }
379 | #mocha code .string { color: var(--mocha-code-string); }
380 | #mocha code .keyword { color: var(--mocha-code-keyword); }
381 | #mocha code .number { color: var(--mocha-code-number); }
382 |
383 | @media screen and (max-device-width: 480px) {
384 | #mocha {
385 | margin: 60px 0px;
386 | }
387 |
388 | #mocha #stats {
389 | position: absolute;
390 | }
391 | }
392 |
--------------------------------------------------------------------------------
/test/puppeteer.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import puppeteer from 'puppeteer';
4 | import path from 'path';
5 | import url from 'url';
6 | import express from 'express';
7 | const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); // eslint-disable-line
8 | const app = express();
9 | const port = 3000;
10 |
11 | app.use(express.static(path.dirname(__dirname)));
12 | const server = app.listen(port, () => {
13 | console.log(`Example app listening on port ${port}!`);
14 | test(port);
15 | });
16 |
17 | function makePromiseInfo() {
18 | const info = {};
19 | const promise = new Promise((resolve, reject) => {
20 | Object.assign(info, {resolve, reject});
21 | });
22 | info.promise = promise;
23 | return info;
24 | }
25 |
26 | async function test(port) {
27 | const browser = await puppeteer.launch({
28 | args: [
29 | '--no-sandbox',
30 | '--disable-setuid-sandbox',
31 | ],
32 | });
33 | const page = await browser.newPage();
34 |
35 | page.on('console', async e => {
36 | const args = await Promise.all(e.args().map(a => a.jsonValue()));
37 | console.log(...args);
38 | });
39 |
40 | let totalFailures = 0;
41 | let waitingPromiseInfo;
42 |
43 | page.on('domcontentloaded', async() => {
44 | const failures = await page.evaluate(() => {
45 | return window.testsPromiseInfo.promise;
46 | });
47 |
48 | totalFailures += failures;
49 | if (failures) {
50 | console.error('FAILED');
51 | }
52 |
53 | waitingPromiseInfo.resolve();
54 | });
55 |
56 | const testPages = [
57 | {url: `http://localhost:${port}/test/index.html?reporter=spec` },
58 | ];
59 |
60 | for (const {url, js} of testPages) {
61 | waitingPromiseInfo = makePromiseInfo();
62 | console.log(`===== [ ${url} ] =====`);
63 | if (js) {
64 | await page.evaluateOnNewDocument(js);
65 | }
66 | await page.goto(url);
67 | await page.waitForNetworkIdle();
68 | if (js) {
69 | await page.evaluate(() => {
70 | setTimeout(() => {
71 | window.testsPromiseInfo.resolve(0);
72 | }, 10);
73 | });
74 | }
75 | await waitingPromiseInfo.promise;
76 | }
77 |
78 | await browser.close();
79 | server.close();
80 |
81 | process.exit(totalFailures ? 1 : 0); // eslint-disable-line
82 | }
83 |
--------------------------------------------------------------------------------
/test/tests/buffer-tests.js:
--------------------------------------------------------------------------------
1 | import {describe, it} from '../mocha-support.js';
2 | import {createContext, createContext2} from '../webgl.js';
3 | import {MemInfoTracker} from './test-utils.js';
4 |
5 | describe('buffer tests', () => {
6 | it('test bufferData with typedarray', () => {
7 | const {gl} = createContext();
8 | const tracker = new MemInfoTracker(gl, 'buffer');
9 |
10 | const buf1 = gl.createBuffer();
11 | tracker.addObjects(1);
12 |
13 | gl.bindBuffer(gl.ARRAY_BUFFER, buf1);
14 | const data1 = new Float32Array(25);
15 | gl.bufferData(gl.ARRAY_BUFFER, data1, gl.STATIC_DRAW);
16 | tracker.addMemory(data1.byteLength);
17 |
18 | const data1a = new Uint16Array(37);
19 | gl.bufferData(gl.ARRAY_BUFFER, data1a, gl.STATIC_DRAW);
20 | tracker.addMemory(data1a.byteLength - data1.byteLength);
21 |
22 | const buf2 = gl.createBuffer();
23 | tracker.addObjects(1);
24 |
25 | gl.bindBuffer(gl.ARRAY_BUFFER, buf2);
26 | const data2 = new Float32Array(55);
27 | gl.bufferData(gl.ARRAY_BUFFER, data2, gl.STATIC_DRAW);
28 | tracker.addMemory(data2.byteLength);
29 |
30 | gl.deleteBuffer(buf1);
31 | tracker.deleteObjectAndMemory(data1a.byteLength);
32 |
33 | gl.deleteBuffer(buf2);
34 | tracker.deleteObjectAndMemory(data2.byteLength);
35 | });
36 |
37 | it('test bufferData with ArrayBufferView', () => {
38 | const {gl} = createContext2();
39 | if (!gl) {
40 | return;
41 | }
42 | const tracker = new MemInfoTracker(gl, 'buffer');
43 |
44 | const buf1 = gl.createBuffer();
45 | tracker.addObjects(1);
46 |
47 | gl.bindBuffer(gl.ARRAY_BUFFER, buf1);
48 | const data1 = new Float32Array(25);
49 | const length1 = 15;
50 | const size1 = length1 * data1.BYTES_PER_ELEMENT;
51 | gl.bufferData(gl.ARRAY_BUFFER, data1, gl.STATIC_DRAW, 0, length1);
52 | tracker.addMemory(size1);
53 |
54 | const data1a = new Uint16Array(37);
55 | const length1a = 30;
56 | const size1a = length1a * data1a.BYTES_PER_ELEMENT;
57 | gl.bufferData(gl.ARRAY_BUFFER, data1a, gl.STATIC_DRAW, 0, length1a);
58 | tracker.addMemory(size1a - size1);
59 |
60 | const buf2 = gl.createBuffer();
61 | tracker.addObjects(1);
62 |
63 | gl.bindBuffer(gl.ARRAY_BUFFER, buf2);
64 | const data2 = new Float32Array(55);
65 | const length2 = 41;
66 | const size2 = length2 * data2.BYTES_PER_ELEMENT;
67 | gl.bufferData(gl.ARRAY_BUFFER, data2, gl.STATIC_DRAW, 0, length2);
68 | tracker.addMemory(size2);
69 |
70 | gl.deleteBuffer(buf1);
71 | tracker.deleteObjectAndMemory(size1a);
72 |
73 | gl.deleteBuffer(buf2);
74 | tracker.deleteObjectAndMemory(size2);
75 | });
76 |
77 | it('test bufferData with ArrayBuffer', () => {
78 | const {gl} = createContext2();
79 | if (!gl) {
80 | return;
81 | }
82 | const tracker = new MemInfoTracker(gl, 'buffer');
83 |
84 | const buf1 = gl.createBuffer();
85 | tracker.addObjects(1);
86 |
87 | gl.bindBuffer(gl.ARRAY_BUFFER, buf1);
88 | const data1 = new Float32Array(25);
89 | gl.bufferData(gl.ARRAY_BUFFER, data1.buffer, gl.STATIC_DRAW);
90 | tracker.addMemory(data1.byteLength);
91 |
92 | const data1a = new Uint16Array(37);
93 | gl.bufferData(gl.ARRAY_BUFFER, data1a.buffer, gl.STATIC_DRAW);
94 | tracker.addMemory(data1a.byteLength - data1.byteLength);
95 |
96 | const buf2 = gl.createBuffer();
97 | tracker.addObjects(1);
98 |
99 | gl.bindBuffer(gl.ARRAY_BUFFER, buf2);
100 | const data2 = new Float32Array(55);
101 | gl.bufferData(gl.ARRAY_BUFFER, data2.buffer, gl.STATIC_DRAW);
102 | tracker.addMemory(data2.byteLength);
103 |
104 | gl.deleteBuffer(buf1);
105 | tracker.deleteObjectAndMemory(data1a.byteLength);
106 |
107 | gl.deleteBuffer(buf2);
108 | tracker.deleteObjectAndMemory(data2.byteLength);
109 | });
110 |
111 | it('test bufferData with size', () => {
112 | const {gl} = createContext();
113 | const tracker = new MemInfoTracker(gl, 'buffer');
114 |
115 | const buf1 = gl.createBuffer();
116 | tracker.addObjects(1);
117 |
118 | gl.bindBuffer(gl.ARRAY_BUFFER, buf1);
119 | const size1 = 26;
120 | gl.bufferData(gl.ARRAY_BUFFER, size1, gl.STATIC_DRAW);
121 | tracker.addMemory(size1);
122 |
123 | const size1a = 38;
124 | gl.bufferData(gl.ARRAY_BUFFER, size1a, gl.STATIC_DRAW);
125 | tracker.addMemory(size1a - size1);
126 |
127 | const buf2 = gl.createBuffer();
128 | tracker.addObjects(1);
129 |
130 | gl.bindBuffer(gl.ARRAY_BUFFER, buf2);
131 | const size2 = 55;
132 | gl.bufferData(gl.ARRAY_BUFFER, size2, gl.STATIC_DRAW);
133 | tracker.addMemory(size2);
134 |
135 | gl.deleteBuffer(buf1);
136 | tracker.deleteObjectAndMemory(size1a);
137 |
138 | gl.deleteBuffer(buf2);
139 | tracker.deleteObjectAndMemory(size2);
140 | });
141 |
142 | it('test ELEMENT_ARRAY_BUFFER', () => {
143 | const {gl} = createContext2();
144 | if (!gl) {
145 | return;
146 | }
147 | const tracker = new MemInfoTracker(gl, 'buffer');
148 |
149 | const va1 = gl.createVertexArray();
150 | gl.bindVertexArray(va1);
151 |
152 | const buf1 = gl.createBuffer();
153 | tracker.addObjects(1);
154 |
155 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buf1);
156 | const size1 = 26;
157 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, size1, gl.STATIC_DRAW);
158 | tracker.addMemory(size1);
159 |
160 | const va2 = gl.createVertexArray();
161 | gl.bindVertexArray(va2);
162 |
163 | const buf2 = gl.createBuffer();
164 | tracker.addObjects(1);
165 |
166 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buf2);
167 | const size2 = 55;
168 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, size2, gl.STATIC_DRAW);
169 | tracker.addMemory(size2);
170 |
171 | gl.bindVertexArray(va1);
172 | const size1a = 5;
173 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, size1a, gl.STATIC_DRAW);
174 | tracker.addMemory(size1a - size1);
175 |
176 | gl.deleteBuffer(buf1);
177 | tracker.deleteObjectAndMemory(size1a);
178 |
179 | gl.deleteBuffer(buf2);
180 | tracker.deleteObjectAndMemory(size2);
181 | });
182 |
183 | it('test bindBufferBase/bindBufferRange', () => {
184 | const {gl} = createContext2();
185 | if (!gl) {
186 | return;
187 | }
188 | const tracker = new MemInfoTracker(gl, 'buffer');
189 |
190 | const buf1 = gl.createBuffer();
191 | tracker.addObjects(1);
192 |
193 | gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, buf1);
194 | const data1 = new Float32Array(25);
195 | gl.bufferData(gl.UNIFORM_BUFFER, data1, gl.STATIC_DRAW);
196 | tracker.addMemory(data1.byteLength);
197 |
198 | const data1a = new Uint16Array(37);
199 | gl.bufferData(gl.UNIFORM_BUFFER, data1a, gl.STATIC_DRAW);
200 | tracker.addMemory(data1a.byteLength - data1.byteLength);
201 |
202 | const buf2 = gl.createBuffer();
203 | tracker.addObjects(1);
204 |
205 | const data2 = new Float32Array(55);
206 | gl.bindBufferRange(gl.TRANSFORM_FEEDBACK_BUFFER, 0, buf2, 0, data2.byteLength);
207 | gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, data2, gl.STATIC_DRAW);
208 | tracker.addMemory(data2.byteLength);
209 |
210 | gl.deleteBuffer(buf1);
211 | tracker.deleteObjectAndMemory(data1a.byteLength);
212 |
213 | gl.deleteBuffer(buf2);
214 | tracker.deleteObjectAndMemory(data2.byteLength);
215 |
216 | });
217 |
218 | });
--------------------------------------------------------------------------------
/test/tests/contextloss-tests.js:
--------------------------------------------------------------------------------
1 | import { assertEqual, assertFalsy } from '../assert.js';
2 | import {describe, it} from '../mocha-support.js';
3 | import {createContext} from '../webgl.js';
4 | import {MemInfoTracker} from './test-utils.js';
5 |
6 | function createExposedPromise() {
7 | const p = {};
8 | p.promise = new Promise((resolve, reject) => {
9 | p.resolve = resolve;
10 | p.reject = reject;
11 | });
12 | return p;
13 | }
14 |
15 | const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
16 |
17 | describe('webgl context lost tests', () => {
18 |
19 | async function testContextLost(gl) {
20 | const contextLostExposedPromise = createExposedPromise();
21 | const contextRestoredExposedPromise = createExposedPromise();
22 |
23 | const handleContextLost = e => {
24 | // To enable context restoration we must preventDefault on the context loss event:
25 | e.preventDefault();
26 | contextLostExposedPromise.resolve();
27 | };
28 | const handleContextRestored = () => {
29 | contextRestoredExposedPromise.resolve();
30 | };
31 |
32 | gl.canvas.addEventListener('webglcontextlost', handleContextLost);
33 | gl.canvas.addEventListener('contextlost', handleContextLost);
34 |
35 | gl.canvas.addEventListener('webglcontextrestored', handleContextRestored);
36 | gl.canvas.addEventListener('contextrestored', handleContextRestored);
37 |
38 | const loseContextExt = gl.getExtension('WEBGL_lose_context');
39 | const oesVertexArrayExt = gl.getExtension('OES_vertex_array_object');
40 | if (!loseContextExt && !oesVertexArrayExt) {
41 | return;
42 | }
43 | const memExt = gl.getExtension('GMAN_webgl_memory');
44 |
45 | const tracker = new MemInfoTracker(gl, 'texture');
46 |
47 | // Add a texture
48 | const tex1 = gl.createTexture();
49 | tracker.addObjects(1);
50 | gl.bindTexture(gl.TEXTURE_2D, tex1);
51 | const texSize = 32 * 16 * 4;
52 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 32, 16, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
53 | tracker.addMemory(texSize);
54 |
55 | const va = oesVertexArrayExt.createVertexArrayOES();
56 | oesVertexArrayExt.bindVertexArrayOES(va);
57 | assertEqual(memExt.getMemoryInfo().resources.vertexArray, 1);
58 |
59 | // Force context loss and wait for event loop to complete
60 | loseContextExt.loseContext();
61 | await contextLostExposedPromise.promise;
62 | await wait();
63 |
64 | // Verify memory tracking was zeroed out
65 | tracker.deleteObjectAndMemory(texSize, 1);
66 | assertEqual(memExt.getMemoryInfo().resources.vertexArray, 0);
67 |
68 | // Check that calling functions while context is lost does not add memory
69 | gl.bindTexture(gl.TEXTURE_2D, tex1);
70 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 32, 16, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
71 | tracker.addMemory(0);
72 |
73 | oesVertexArrayExt.createVertexArrayOES();
74 | assertEqual(memExt.getMemoryInfo().resources.vertexArray, 0);
75 |
76 | // Check that getting an extension while context lost returns null?
77 | // Actually not entirely sure what's supposed to happen here.
78 | const ext = gl.getExtension('OES_vertex_array_object');
79 | assertFalsy(ext);
80 |
81 | // Force context restoration and wait for event loop to complete
82 | loseContextExt.restoreContext();
83 |
84 | await contextRestoredExposedPromise.promise;
85 | await wait();
86 |
87 | // Verify you can still add new things
88 | const tex2 = gl.createTexture();
89 | tracker.addObjects(1);
90 | gl.bindTexture(gl.TEXTURE_2D, tex2);
91 | const texSize2 = 64 * 24 * 4;
92 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 64, 24, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
93 | tracker.addMemory(texSize2);
94 |
95 | // Check that old extensions do not add new info
96 | oesVertexArrayExt.createVertexArrayOES();
97 | assertEqual(memExt.getMemoryInfo().resources.vertexArray, 0);
98 |
99 | // Check that new extensions do add new info
100 | const newOESVertexArrayExt = gl.getExtension('OES_vertex_array_object');
101 | if (!newOESVertexArrayExt) {
102 | return;
103 | }
104 |
105 | newOESVertexArrayExt.createVertexArrayOES();
106 | assertEqual(memExt.getMemoryInfo().resources.vertexArray, 1);
107 | }
108 |
109 | it('test context loss', async() => {
110 | const {gl} = createContext();
111 | await testContextLost(gl);
112 | });
113 |
114 | it('test context loss OffscreenCanvas', async() => {
115 | if (typeof OffscreenCanvas === 'undefined') {
116 | return;
117 | }
118 |
119 | const gl = new OffscreenCanvas(300, 150).getContext('webgl');
120 | await testContextLost(gl);
121 | });
122 |
123 | });
124 |
--------------------------------------------------------------------------------
/test/tests/info-tests.js:
--------------------------------------------------------------------------------
1 | import { computeDrawingbufferSize, getDrawingbufferInfo } from '../../src/utils.js';
2 | import {assertEqual, assertNotEqual, assertTruthy} from '../assert.js';
3 | import {describe, it} from '../mocha-support.js';
4 | import {createContext, createContext2} from '../webgl.js';
5 |
6 | describe('info tests', () => {
7 | it('test base state webgl1', () => {
8 | const {gl, ext, drawingbufferSize} = createContext();
9 | assertTruthy(ext, 'got extension');
10 |
11 | // compute this size ourselves so we can compare
12 | const size = gl.drawingBufferWidth * gl.drawingBufferHeight;
13 | const colorSize = 4;
14 | const samples = gl.getParameter(gl.SAMPLES) || 1;
15 | const depthStencilBytes = (gl.getParameter(gl.DEPTH_BITS) + gl.getParameter(gl.STENCIL_BITS) + 7) / 8 | 0;
16 | const depthStencilSize = depthStencilBytes === 3 ? 4 : depthStencilBytes;
17 | const canvasSize =
18 | size * colorSize + // display buffer
19 | size * colorSize * samples + // drawing buffer
20 | size * depthStencilSize; // depth + stencil buffer
21 |
22 | assertEqual(drawingbufferSize, canvasSize);
23 |
24 | const info = ext.getMemoryInfo();
25 | const {memory, resources} = info;
26 |
27 | assertEqual(memory.buffer, 0);
28 | assertEqual(memory.texture, 0);
29 | assertEqual(memory.renderbuffer, 0);
30 | assertEqual(memory.drawingbuffer, drawingbufferSize);
31 | assertEqual(memory.total, drawingbufferSize);
32 | assertEqual(resources.buffer, 0);
33 | assertEqual(resources.framebuffer, 0);
34 | assertEqual(resources.renderbuffer, 0);
35 | assertEqual(resources.program, 0);
36 | assertEqual(resources.query, undefined);
37 | assertEqual(resources.sampler, undefined);
38 | assertEqual(resources.shader, 0);
39 | assertEqual(resources.sync, undefined);
40 | assertEqual(resources.texture, 0);
41 | assertEqual(resources.transformFeedback, undefined);
42 | assertEqual(resources.vertexArray, undefined);
43 |
44 | const textures = ext.getResourcesInfo(WebGLTexture);
45 | assertEqual(textures.length, 0);
46 | });
47 |
48 | it('test base state webgl2', () => {
49 | const {ext, drawingbufferSize} = createContext2();
50 | assertTruthy(ext, 'got extension');
51 |
52 | const info = ext.getMemoryInfo();
53 | const {memory, resources} = info;
54 |
55 | assertEqual(memory.buffer, 0);
56 | assertEqual(memory.texture, 0);
57 | assertEqual(memory.renderbuffer, 0);
58 | assertEqual(memory.drawingbuffer, drawingbufferSize);
59 | assertEqual(memory.total, drawingbufferSize);
60 | assertEqual(resources.buffer, 0);
61 | assertEqual(resources.framebuffer, 0);
62 | assertEqual(resources.renderbuffer, 0);
63 | assertEqual(resources.program, 0);
64 | assertEqual(resources.query, 0);
65 | assertEqual(resources.sampler, 0);
66 | assertEqual(resources.shader, 0);
67 | assertEqual(resources.sync, 0);
68 | assertEqual(resources.texture, 0);
69 | assertEqual(resources.transformFeedback, 0);
70 | assertEqual(resources.vertexArray, 0);
71 |
72 | const textures = ext.getResourcesInfo(WebGLTexture);
73 | assertEqual(textures.length, 0);
74 | });
75 |
76 | it('test canvas resize', () => {
77 | const {gl, ext, drawingbufferSize} = createContext();
78 | assertTruthy(ext, 'got extension');
79 |
80 | {
81 | const info = ext.getMemoryInfo();
82 | const {memory} = info;
83 | assertEqual(memory.drawingbuffer, drawingbufferSize);
84 | assertEqual(memory.total, drawingbufferSize);
85 | }
86 |
87 | gl.canvas.width = 150;
88 | gl.canvas.height = 75;
89 |
90 | {
91 | const newDrawingbufferSize = computeDrawingbufferSize(gl, getDrawingbufferInfo(gl));
92 | const info = ext.getMemoryInfo();
93 | const {memory} = info;
94 | assertEqual(memory.drawingbuffer, newDrawingbufferSize);
95 | assertEqual(memory.total, newDrawingbufferSize);
96 | assertNotEqual(drawingbufferSize, newDrawingbufferSize);
97 | }
98 |
99 | });
100 |
101 | });
102 |
--------------------------------------------------------------------------------
/test/tests/program-tests.js:
--------------------------------------------------------------------------------
1 | import {describe, it} from '../mocha-support.js';
2 | import {createContext} from '../webgl.js';
3 | import {MemInfoTracker} from './test-utils.js';
4 |
5 | describe('program tests', () => {
6 |
7 | it('test program', () => {
8 | const {gl} = createContext();
9 | if (!gl) {
10 | return;
11 | }
12 | const tracker = new MemInfoTracker(gl, 'program');
13 | const prg = gl.createProgram();
14 | tracker.addObjects(1);
15 | gl.deleteProgram(prg);
16 | tracker.deleteObjectAndMemory(0);
17 | });
18 |
19 | });
--------------------------------------------------------------------------------
/test/tests/renderbuffer-tests.js:
--------------------------------------------------------------------------------
1 | import {describe, it} from '../mocha-support.js';
2 | import {createContext, createContext2} from '../webgl.js';
3 | import {MemInfoTracker} from './test-utils.js';
4 |
5 | describe('renderbuffer tests', () => {
6 |
7 | it('test renderbufferStorage', () => {
8 | const {gl} = createContext();
9 | const tracker = new MemInfoTracker(gl, 'renderbuffer');
10 |
11 | const rb1 = gl.createRenderbuffer();
12 | tracker.addObjects(1);
13 |
14 | gl.bindRenderbuffer(gl.RENDERBUFFER, rb1);
15 | let size1a;
16 | {
17 | const width = 17;
18 | const height = 49;
19 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGB565, width, height);
20 | size1a = width * height * 2;
21 | tracker.addMemory(size1a);
22 | }
23 |
24 | let size1b;
25 | {
26 | const width = 37;
27 | const height = 9;
28 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
29 | size1b = width * height * 2;
30 | tracker.addMemory(size1b - size1a);
31 | }
32 |
33 | const rb2 = gl.createRenderbuffer();
34 | tracker.addObjects(1);
35 |
36 | gl.bindRenderbuffer(gl.RENDERBUFFER, rb2);
37 | let size2;
38 | {
39 | const width = 71;
40 | const height = 94;
41 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, width, height);
42 | size2 = width * height * 2;
43 | tracker.addMemory(size2);
44 | }
45 |
46 | gl.deleteRenderbuffer(rb1);
47 | tracker.deleteObjectAndMemory(size1b);
48 | gl.deleteRenderbuffer(rb2);
49 | tracker.deleteObjectAndMemory(size2);
50 | });
51 |
52 | it('test renderbufferStorage STENCIL_INDEX8', () => {
53 | const {gl} = createContext();
54 | const tracker = new MemInfoTracker(gl, 'renderbuffer');
55 |
56 | const rb1 = gl.createRenderbuffer();
57 | tracker.addObjects(1);
58 |
59 | gl.bindRenderbuffer(gl.RENDERBUFFER, rb1);
60 | let size1a;
61 | {
62 | const width = 17;
63 | const height = 49;
64 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, width, height);
65 | size1a = width * height;
66 | tracker.addMemory(size1a);
67 | }
68 |
69 | gl.deleteRenderbuffer(rb1);
70 | tracker.deleteObjectAndMemory(size1a);
71 | });
72 |
73 |
74 | it('test renderbufferStorageMultisample', () => {
75 | const {gl} = createContext2();
76 | if (!gl) {
77 | return;
78 | }
79 | const tracker = new MemInfoTracker(gl, 'renderbuffer');
80 |
81 | const rb1 = gl.createRenderbuffer();
82 | tracker.addObjects(1);
83 |
84 | gl.bindRenderbuffer(gl.RENDERBUFFER, rb1);
85 | let size1a;
86 | {
87 | const width = 17;
88 | const height = 49;
89 | gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 2, gl.RGB565, width, height);
90 | size1a = width * height * 2 * 2;
91 | tracker.addMemory(size1a);
92 | }
93 |
94 | let size1b;
95 | {
96 | const width = 37;
97 | const height = 9;
98 | gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.DEPTH_COMPONENT16, width, height);
99 | size1b = width * height * 2 * 4;
100 | tracker.addMemory(size1b - size1a);
101 | }
102 |
103 | const rb2 = gl.createRenderbuffer();
104 | tracker.addObjects(1);
105 |
106 | gl.bindRenderbuffer(gl.RENDERBUFFER, rb2);
107 | let size2;
108 | {
109 | const width = 71;
110 | const height = 94;
111 | gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 2, gl.RGBA4, width, height);
112 | size2 = width * height * 2 * 2;
113 | tracker.addMemory(size2);
114 | }
115 |
116 | gl.deleteRenderbuffer(rb1);
117 | tracker.deleteObjectAndMemory(size1b);
118 | gl.deleteRenderbuffer(rb2);
119 | tracker.deleteObjectAndMemory(size2);
120 | });
121 | });
--------------------------------------------------------------------------------
/test/tests/sampler-tests.js:
--------------------------------------------------------------------------------
1 | import {describe, it} from '../mocha-support.js';
2 | import {createContext2} from '../webgl.js';
3 | import {MemInfoTracker} from './test-utils.js';
4 |
5 | describe('sampler tests', () => {
6 |
7 | it('test sampler', () => {
8 | const {gl} = createContext2();
9 | if (!gl) {
10 | return;
11 | }
12 | const tracker = new MemInfoTracker(gl, 'sampler');
13 | const s = gl.createSampler();
14 | tracker.addObjects(1);
15 | gl.deleteSampler(s);
16 | tracker.deleteObjectAndMemory(0);
17 | });
18 |
19 | });
--------------------------------------------------------------------------------
/test/tests/shader-tests.js:
--------------------------------------------------------------------------------
1 | import {describe, it} from '../mocha-support.js';
2 | import {createContext} from '../webgl.js';
3 | import {MemInfoTracker} from './test-utils.js';
4 |
5 | describe('shader tests', () => {
6 |
7 | it('test shader', () => {
8 | const {gl} = createContext();
9 | if (!gl) {
10 | return;
11 | }
12 | const tracker = new MemInfoTracker(gl, 'shader');
13 | const sh = gl.createShader(gl.VERTEX_SHADER);
14 | tracker.addObjects(1);
15 | gl.deleteShader(sh);
16 | tracker.deleteObjectAndMemory(0);
17 | });
18 |
19 | });
--------------------------------------------------------------------------------
/test/tests/stack-tests.js:
--------------------------------------------------------------------------------
1 | import {describe, it} from '../mocha-support.js';
2 | import {assertEqual, assertFalsy, assertTruthy} from '../assert.js';
3 | import {createContext} from '../webgl.js';
4 |
5 | describe('stack tests', () => {
6 |
7 | it('test texture stack capture', () => {
8 | const {gl, ext} = createContext();
9 |
10 | const tex1 = gl.createTexture();
11 |
12 | gl.bindTexture(gl.TEXTURE_2D, tex1);
13 | gl.texImage2D(gl.TEXTURE_2D, 1, gl.RGBA, 16, 8, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
14 |
15 | {
16 | const textures = ext.getResourcesInfo(WebGLTexture);
17 | assertEqual(textures.length, 1);
18 | assertTruthy(textures[0].stackCreated);
19 | assertTruthy(textures[0].stackUpdated);
20 | }
21 |
22 | gl.deleteTexture(tex1);
23 |
24 | {
25 | const textures = ext.getResourcesInfo(WebGLTexture);
26 | assertEqual(textures.length, 0);
27 | }
28 | });
29 |
30 | it('test buffers stack capture', () => {
31 | const {gl, ext} = createContext();
32 |
33 | const buf = gl.createBuffer();
34 |
35 | {
36 | const buffers = ext.getResourcesInfo(WebGLBuffer);
37 | assertEqual(buffers.length, 1);
38 | assertTruthy(buffers[0].stackCreated);
39 | assertFalsy(buffers[0].stackUpdated);
40 | }
41 |
42 | gl.bindBuffer(gl.ARRAY_BUFFER, buf);
43 | gl.bufferData(gl.ARRAY_BUFFER, 16, gl.STATIC_DRAW);
44 |
45 | {
46 | const buffers = ext.getResourcesInfo(WebGLBuffer);
47 | assertEqual(buffers.length, 1);
48 | assertTruthy(buffers[0].stackCreated);
49 | assertTruthy(buffers[0].stackUpdated);
50 | }
51 |
52 | gl.deleteBuffer(buf);
53 |
54 | {
55 | const buffers = ext.getResourcesInfo(WebGLBuffer);
56 | assertEqual(buffers.length, 0);
57 | }
58 | });
59 |
60 | it('test program stack capture', () => {
61 | const {gl, ext} = createContext();
62 |
63 | const program = gl.createProgram();
64 |
65 | {
66 | const programs = ext.getResourcesInfo(WebGLProgram);
67 | assertEqual(programs.length, 1);
68 | assertTruthy(programs[0].stackCreated);
69 | }
70 |
71 | gl.deleteProgram(program);
72 |
73 | {
74 | const programs = ext.getResourcesInfo(WebGLProgram);
75 | assertEqual(programs.length, 0);
76 | }
77 | });
78 |
79 | });
80 |
--------------------------------------------------------------------------------
/test/tests/sync-tests.js:
--------------------------------------------------------------------------------
1 | import {describe, it} from '../mocha-support.js';
2 | import {createContext2} from '../webgl.js';
3 | import {MemInfoTracker} from './test-utils.js';
4 |
5 | describe('sync tests', () => {
6 |
7 | it('test sync', () => {
8 | const {gl} = createContext2();
9 | if (!gl) {
10 | return;
11 | }
12 | const tracker = new MemInfoTracker(gl, 'sync');
13 | const s = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
14 | tracker.addObjects(1);
15 | gl.deleteSync(s);
16 | tracker.deleteObjectAndMemory(0);
17 | });
18 |
19 | });
--------------------------------------------------------------------------------
/test/tests/test-utils.js:
--------------------------------------------------------------------------------
1 | import {assertEqual, assertTruthy} from '../assert.js';
2 | import {computeDrawingbufferSize, getDrawingbufferInfo} from '../../src/utils.js';
3 |
4 | export class MemInfoTracker {
5 | constructor(gl, type) {
6 | assertTruthy(gl);
7 | this.ext = gl.getExtension('GMAN_webgl_memory');
8 | assertTruthy(this.ext);
9 | this.gl = gl;
10 | this.numObjects = 0;
11 | this.memSize = 0;
12 | this.type = type;
13 | }
14 | check() {
15 | const {gl} = this;
16 | const drawingbufferSize = computeDrawingbufferSize(gl, getDrawingbufferInfo(gl));
17 | const {ext, type, memSize, numObjects} = this;
18 | const {memory, resources} = ext.getMemoryInfo();
19 | if (memory[type] !== undefined) {
20 | assertEqual(memory[type], memSize, `memory.${type}`);
21 | }
22 | assertEqual(resources[type], numObjects, `resources.${type}`);
23 | assertEqual(memory.total, drawingbufferSize + memSize, `total`);
24 | }
25 | addObjects(deltaObjects = 1) {
26 | this.numObjects += deltaObjects;
27 | this.check();
28 | }
29 | addMemory(deltaMemory = 0) {
30 | this.memSize += deltaMemory;
31 | this.check();
32 | }
33 | deleteObjectAndMemory(deltaMemory, deltaObjects = 1) {
34 | this.memSize -= deltaMemory;
35 | this.numObjects -= deltaObjects;
36 | this.check();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test/tests/texture-tests.js:
--------------------------------------------------------------------------------
1 | import {describe, it} from '../mocha-support.js';
2 | import {createContext, createContext2} from '../webgl.js';
3 | import {MemInfoTracker} from './test-utils.js';
4 |
5 | describe('tex-image tests', () => {
6 |
7 | it('test texImage2D', () => {
8 | const {gl} = createContext();
9 | const tracker = new MemInfoTracker(gl, 'texture');
10 |
11 | const tex1 = gl.createTexture();
12 | tracker.addObjects(1);
13 |
14 | gl.bindTexture(gl.TEXTURE_2D, tex1);
15 | gl.texImage2D(gl.TEXTURE_2D, 1, gl.RGBA, 16, 8, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
16 | const mip1Size = 16 * 8 * 4;
17 | tracker.addMemory(mip1Size);
18 |
19 | const mip0Size = 32 * 16 * 4;
20 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 32, 16, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
21 | tracker.addMemory(mip0Size);
22 |
23 | gl.generateMipmap(gl.TEXTURE_2D);
24 | const texSize = 4 * (
25 | 32 * 16 + // level0
26 | 16 * 8 + // level1
27 | 8 * 4 + // level2
28 | 4 * 2 + // level3
29 | 2 * 1 + // level4
30 | 1 * 1); // level5
31 | tracker.addMemory(texSize - mip0Size - mip1Size);
32 |
33 | gl.deleteTexture(tex1);
34 | tracker.deleteObjectAndMemory(texSize);
35 | });
36 |
37 | it('test texImage2D cube map', () => {
38 | const {gl} = createContext();
39 | const tracker = new MemInfoTracker(gl, 'texture');
40 |
41 | const tex1 = gl.createTexture();
42 | tracker.addObjects(1);
43 |
44 | gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex1);
45 |
46 | const faces = [
47 | gl.TEXTURE_CUBE_MAP_POSITIVE_X,
48 | gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
49 | gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
50 | gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
51 | gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
52 | gl.TEXTURE_CUBE_MAP_NEGATIVE_Z,
53 | ];
54 |
55 | const mip0Size = 32 * 32 * 4;
56 | for (const face of faces) {
57 | gl.texImage2D(face, 0, gl.RGBA, 32, 32, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
58 | tracker.addMemory(mip0Size);
59 | }
60 | const level0Size = mip0Size * 6;
61 |
62 | gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
63 | const faceSize = 4 * (
64 | 32 * 32 + // level0
65 | 16 * 16 + // level1
66 | 8 * 8 + // level2
67 | 4 * 4 + // level3
68 | 2 * 2 + // level4
69 | 1 * 1); // level5
70 | tracker.addMemory(faceSize * 6 - level0Size);
71 |
72 | gl.deleteTexture(tex1);
73 | tracker.deleteObjectAndMemory(faceSize * 6);
74 | });
75 |
76 | it('test compressedTexImage2D', () => {
77 | const {gl} = createContext();
78 | const ext = gl.getExtension('WEBGL_compressed_texture_s3tc');
79 | if (!ext) {
80 | return;
81 | }
82 | const tracker = new MemInfoTracker(gl, 'texture');
83 |
84 | const tex1 = gl.createTexture();
85 | tracker.addObjects(1);
86 |
87 | gl.bindTexture(gl.TEXTURE_2D, tex1);
88 | const mip0Size = (12 / 4) * (12 / 4) * 8;
89 | gl.compressedTexImage2D(gl.TEXTURE_2D, 0, ext.COMPRESSED_RGB_S3TC_DXT1_EXT, 12, 12, 0, new Uint8Array(mip0Size));
90 | tracker.addMemory(mip0Size);
91 |
92 | gl.deleteTexture(tex1);
93 | tracker.deleteObjectAndMemory(mip0Size);
94 | });
95 |
96 | /*
97 | it('test compressedTexImage3D', () => {
98 | const {gl} = createContext2();
99 | const ext = gl.getExtension('WEBGL_compressed_texture_s3tc');
100 | if (!ext) {
101 | return;
102 | }
103 | const tracker = new MemInfoTracker(gl, 'texture');
104 |
105 | const tex1 = gl.createTexture();
106 | tracker.addObjects(1);
107 |
108 | gl.bindTexture(gl.TEXTURE_3D, tex1);
109 | const mip0Size = (12 / 4) * (12 / 4) * 8 * 6;
110 | gl.compressedTexImage3D(gl.TEXTURE_3D, 0, ext.COMPRESSED_RGB_S3TC_DXT1_EXT, 12, 12, 6, 0, new Uint8Array(mip0Size));
111 | tracker.addMemory(mip0Size);
112 |
113 | gl.deleteTexture(tex1);
114 | tracker.deleteObjectAndMemory(mip0Size);
115 | });
116 | */
117 |
118 | it('test OES_texture_float', () => {
119 | const {gl} = createContext();
120 | const ext = gl.getExtension('OES_texture_float');
121 | if (!ext) {
122 | return;
123 | }
124 | const tracker = new MemInfoTracker(gl, 'texture');
125 |
126 | const tex1 = gl.createTexture();
127 | tracker.addObjects(1);
128 |
129 | gl.bindTexture(gl.TEXTURE_2D, tex1);
130 | gl.texImage2D(gl.TEXTURE_2D, 1, gl.RGBA, 16, 8, 0, gl.RGBA, gl.FLOAT, null);
131 | const mip1Size = 16 * 8 * 16;
132 | tracker.addMemory(mip1Size);
133 |
134 | const mip0Size = 32 * 16 * 16;
135 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 32, 16, 0, gl.RGBA, gl.FLOAT, null);
136 | tracker.addMemory(mip0Size);
137 |
138 | gl.deleteTexture(tex1);
139 | tracker.deleteObjectAndMemory(mip1Size + mip0Size);
140 | });
141 |
142 | it('test texImage3D', () => {
143 | const {gl} = createContext2();
144 | if (!gl) {
145 | return;
146 | }
147 | const tracker = new MemInfoTracker(gl, 'texture');
148 |
149 | const tex1 = gl.createTexture();
150 | tracker.addObjects(1);
151 |
152 | gl.bindTexture(gl.TEXTURE_3D, tex1);
153 | gl.texImage3D(gl.TEXTURE_3D, 1, gl.RGBA, 10, 20, 5, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
154 | const mip1Size = 10 * 20 * 5 * 4;
155 | tracker.addMemory(mip1Size);
156 |
157 | const mip0Size = 30 * 15 * 6 * 4;
158 | gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA, 30, 15, 6, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
159 | tracker.addMemory(mip0Size);
160 |
161 | gl.generateMipmap(gl.TEXTURE_3D);
162 | const texSize = 4 * (
163 | 30 * 15 * 6 + // level0
164 | 15 * 8 * 3 + // level1
165 | 8 * 4 * 2 + // level2
166 | 4 * 2 * 1 + // level3
167 | 2 * 1 * 1 + // level4
168 | 1 * 1 * 1); // level5
169 | tracker.addMemory(texSize - mip0Size - mip1Size);
170 |
171 | gl.deleteTexture(tex1);
172 | tracker.deleteObjectAndMemory(texSize);
173 | });
174 |
175 | it('test texStorage2D', () => {
176 | const {gl} = createContext2();
177 | if (!gl) {
178 | return;
179 | }
180 | const tracker = new MemInfoTracker(gl, 'texture');
181 |
182 | const tex1 = gl.createTexture();
183 | tracker.addObjects(1);
184 |
185 | gl.bindTexture(gl.TEXTURE_2D, tex1);
186 | gl.texStorage2D(gl.TEXTURE_2D, 4, gl.RGB16I, 10, 20);
187 | const size = 6 * (
188 | 10 * 20 + // level0
189 | 5 * 10 + // level1
190 | 3 * 5 + // level2
191 | 2 * 3 // level3
192 | );
193 | tracker.addMemory(size);
194 |
195 | gl.deleteTexture(tex1);
196 | tracker.deleteObjectAndMemory(size);
197 | });
198 |
199 | it('test texStorage3D', () => {
200 | const {gl} = createContext2();
201 | if (!gl) {
202 | return;
203 | }
204 | const tracker = new MemInfoTracker(gl, 'texture');
205 |
206 | const tex1 = gl.createTexture();
207 | tracker.addObjects(1);
208 |
209 | gl.bindTexture(gl.TEXTURE_2D_ARRAY, tex1);
210 | gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 4, gl.RGB16I, 10, 20, 6);
211 | const size = 6 * (
212 | 10 * 20 * 6 + // level0
213 | 5 * 10 * 6 + // level1
214 | 3 * 5 * 6 + // level2
215 | 2 * 3 * 6 // level3
216 | );
217 | tracker.addMemory(size);
218 |
219 | gl.deleteTexture(tex1);
220 | tracker.deleteObjectAndMemory(size);
221 | });
222 |
223 | it('test copyTexImage2D', () => {
224 | const {gl} = createContext();
225 | const tracker = new MemInfoTracker(gl, 'texture');
226 |
227 | const tex1 = gl.createTexture();
228 | tracker.addObjects(1);
229 |
230 | gl.bindTexture(gl.TEXTURE_2D, tex1);
231 | gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, 15, 25, 0);
232 | const mip0Size = 15 * 25 * 4;
233 | tracker.addMemory(mip0Size);
234 |
235 | gl.deleteTexture(tex1);
236 | tracker.deleteObjectAndMemory(mip0Size);
237 | });
238 |
239 | it('test TEXTURE_BASE_LEVEL, TEXTURE_MAX_LEVEL', () => {
240 | const {gl} = createContext2();
241 | if (!gl) {
242 | return;
243 | }
244 | const tracker = new MemInfoTracker(gl, 'texture');
245 |
246 | const tex1 = gl.createTexture();
247 | tracker.addObjects(1);
248 |
249 | gl.bindTexture(gl.TEXTURE_2D, tex1);
250 | gl.texImage2D(gl.TEXTURE_2D, 1, gl.RGBA, 16, 8, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
251 | const mip1Size = 16 * 8 * 4;
252 | tracker.addMemory(mip1Size);
253 |
254 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, 1);
255 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 3);
256 | gl.generateMipmap(gl.TEXTURE_2D);
257 |
258 | const texSize = 4 * (
259 | 16 * 8 + // level1
260 | 8 * 4 + // level2
261 | 4 * 2); // level3
262 | tracker.addMemory(texSize - mip1Size);
263 |
264 | gl.deleteTexture(tex1);
265 | tracker.deleteObjectAndMemory(texSize);
266 | });
267 |
268 | });
--------------------------------------------------------------------------------
/test/tests/transformfeedback-tests.js:
--------------------------------------------------------------------------------
1 | import {describe, it} from '../mocha-support.js';
2 | import {createContext2} from '../webgl.js';
3 | import {MemInfoTracker} from './test-utils.js';
4 |
5 | describe('transformfeedback tests', () => {
6 |
7 | it('test transformfeedback', () => {
8 | const {gl} = createContext2();
9 | if (!gl) {
10 | return;
11 | }
12 | const tracker = new MemInfoTracker(gl, 'transformFeedback');
13 | const tf = gl.createTransformFeedback();
14 | tracker.addObjects(1);
15 | gl.deleteTransformFeedback(tf);
16 | tracker.deleteObjectAndMemory(0);
17 | });
18 |
19 | });
--------------------------------------------------------------------------------
/test/tests/vertexarray-tests.js:
--------------------------------------------------------------------------------
1 | import {describe, it} from '../mocha-support.js';
2 | import {createContext, createContext2} from '../webgl.js';
3 | import {MemInfoTracker} from './test-utils.js';
4 |
5 | describe('vertex-array tests', () => {
6 |
7 | it('test vertex-array WebGL1', () => {
8 | const {gl} = createContext();
9 | const ext = gl.getExtension('OES_vertex_array_object');
10 | if (!ext) {
11 | return;
12 | }
13 | const tracker = new MemInfoTracker(gl, 'vertexArray');
14 | const va = ext.createVertexArrayOES();
15 | tracker.addObjects(1);
16 | ext.deleteVertexArrayOES(va);
17 | tracker.deleteObjectAndMemory(0);
18 | });
19 |
20 | it('test vertex-array WebGL2', () => {
21 | const {gl} = createContext2();
22 | if (!gl) {
23 | return;
24 | }
25 | const tracker = new MemInfoTracker(gl, 'vertexArray');
26 | const va = gl.createVertexArray();
27 | tracker.addObjects(1);
28 | gl.deleteVertexArray(va);
29 | tracker.deleteObjectAndMemory(0);
30 | });
31 |
32 | });
--------------------------------------------------------------------------------
/test/webgl.js:
--------------------------------------------------------------------------------
1 | import {
2 | computeDrawingbufferSize,
3 | getDrawingbufferInfo,
4 | } from '../src/utils.js';
5 |
6 | export function createContext() {
7 | const gl = document.createElement('canvas').getContext('webgl');
8 | const ext = gl.getExtension('GMAN_webgl_memory');
9 | return { gl, ext, drawingbufferSize: computeDrawingbufferSize(gl, getDrawingbufferInfo(gl)) };
10 | }
11 |
12 | export function createContext2() {
13 | const gl = document.createElement('canvas').getContext('webgl2');
14 | const ext = gl ? gl.getExtension('GMAN_webgl_memory') : null;
15 | return { gl, ext, drawingbufferSize: computeDrawingbufferSize(gl, getDrawingbufferInfo(gl)) };
16 | }
17 |
18 | function resetContext(gl) {
19 | gl.bindBuffer(gl.ARRAY_BUFFER, null);
20 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
21 | gl.bindRenderbuffer(gl.RENDERBUFFER, null);
22 | gl.bindFramebuffer(gl.FRAMEBUFFER, null);
23 | gl.activeTexture(gl.TEXTURE0);
24 | gl.bindTexture(gl.TEXTURE_2D, null);
25 | gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
26 | gl.useProgram(null);
27 | }
28 |
29 | export function resetContexts(context) {
30 | const { gl, gl2, vaoExt } = context;
31 | if (vaoExt) {
32 | vaoExt.bindVertexArrayOES(null);
33 | }
34 | resetContext(gl);
35 |
36 | if (gl2) {
37 | gl2.bindVertexArray(null);
38 | resetContext(gl2);
39 | }
40 | }
41 |
42 | export function escapeRE(str) {
43 | return str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
44 | }
45 |
46 | export function not(str) {
47 | return new RegExp(`^((?!${escapeRE(str)}).)*$`);
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/webgl-memory.js:
--------------------------------------------------------------------------------
1 | /* webgl-memory@1.1.2, license MIT */
2 | (function (factory) {
3 | typeof define === 'function' && define.amd ? define(factory) :
4 | factory();
5 | })((function () { 'use strict';
6 |
7 | /* PixelFormat */
8 | const ALPHA = 0x1906;
9 | const RGB = 0x1907;
10 | const RGBA = 0x1908;
11 | const LUMINANCE = 0x1909;
12 | const LUMINANCE_ALPHA = 0x190A;
13 | const DEPTH_COMPONENT = 0x1902;
14 | const DEPTH_STENCIL = 0x84F9;
15 |
16 | const R8 = 0x8229;
17 | const R8_SNORM = 0x8F94;
18 | const R16F = 0x822D;
19 | const R32F = 0x822E;
20 | const R8UI = 0x8232;
21 | const R8I = 0x8231;
22 | const RG16UI = 0x823A;
23 | const RG16I = 0x8239;
24 | const RG32UI = 0x823C;
25 | const RG32I = 0x823B;
26 | const RG8 = 0x822B;
27 | const RG8_SNORM = 0x8F95;
28 | const RG16F = 0x822F;
29 | const RG32F = 0x8230;
30 | const RG8UI = 0x8238;
31 | const RG8I = 0x8237;
32 | const R16UI = 0x8234;
33 | const R16I = 0x8233;
34 | const R32UI = 0x8236;
35 | const R32I = 0x8235;
36 | const RGB8 = 0x8051;
37 | const SRGB8 = 0x8C41;
38 | const RGB565 = 0x8D62;
39 | const RGB8_SNORM = 0x8F96;
40 | const R11F_G11F_B10F = 0x8C3A;
41 | const RGB9_E5 = 0x8C3D;
42 | const RGB16F = 0x881B;
43 | const RGB32F = 0x8815;
44 | const RGB8UI = 0x8D7D;
45 | const RGB8I = 0x8D8F;
46 | const RGB16UI = 0x8D77;
47 | const RGB16I = 0x8D89;
48 | const RGB32UI = 0x8D71;
49 | const RGB32I = 0x8D83;
50 | const RGBA8 = 0x8058;
51 | const SRGB8_ALPHA8 = 0x8C43;
52 | const RGBA8_SNORM = 0x8F97;
53 | const RGB5_A1 = 0x8057;
54 | const RGBA4 = 0x8056;
55 | const RGB10_A2 = 0x8059;
56 | const RGBA16F = 0x881A;
57 | const RGBA32F = 0x8814;
58 | const RGBA8UI = 0x8D7C;
59 | const RGBA8I = 0x8D8E;
60 | const RGB10_A2UI = 0x906F;
61 | const RGBA16UI = 0x8D76;
62 | const RGBA16I = 0x8D88;
63 | const RGBA32I = 0x8D82;
64 | const RGBA32UI = 0x8D70;
65 |
66 | const DEPTH_COMPONENT16 = 0x81A5;
67 | const DEPTH_COMPONENT24 = 0x81A6;
68 | const DEPTH_COMPONENT32F = 0x8CAC;
69 | const DEPTH32F_STENCIL8 = 0x8CAD;
70 | const DEPTH24_STENCIL8 = 0x88F0;
71 | const STENCIL_INDEX8 = 0x8d48;
72 |
73 | /* DataType */
74 | // const BYTE = 0x1400;
75 | const UNSIGNED_BYTE = 0x1401;
76 | // const SHORT = 0x1402;
77 | const UNSIGNED_SHORT = 0x1403;
78 | // const INT = 0x1404;
79 | const UNSIGNED_INT = 0x1405;
80 | const FLOAT = 0x1406;
81 | const UNSIGNED_SHORT_4_4_4_4 = 0x8033;
82 | const UNSIGNED_SHORT_5_5_5_1 = 0x8034;
83 | const UNSIGNED_SHORT_5_6_5 = 0x8363;
84 | const HALF_FLOAT = 0x140B;
85 | const HALF_FLOAT_OES = 0x8D61; // Thanks Khronos for making this different >:(
86 |
87 | const SRGB_ALPHA_EXT = 0x8C42;
88 |
89 | /**
90 | * @typedef {Object} TextureFormatDetails
91 | * @property {number} textureFormat format to pass texImage2D and similar functions.
92 | * @property {boolean} colorRenderable true if you can render to this format of texture.
93 | * @property {boolean} textureFilterable true if you can filter the texture, false if you can ony use `NEAREST`.
94 | * @property {number[]} type Array of possible types you can pass to texImage2D and similar function
95 | * @property {Object.} bytesPerElementMap A map of types to bytes per element
96 | * @private
97 | */
98 |
99 | let s_textureInternalFormatInfo;
100 | function getTextureInternalFormatInfo(internalFormat) {
101 | if (!s_textureInternalFormatInfo) {
102 | // NOTE: these properties need unique names so we can let Uglify mangle the name.
103 | const t = {};
104 | // unsized formats
105 | t[ALPHA] = { bytesPerElement: [1, 2, 2, 4], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT], };
106 | t[LUMINANCE] = { bytesPerElement: [1, 2, 2, 4], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT], };
107 | t[LUMINANCE_ALPHA] = { bytesPerElement: [2, 4, 4, 8], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT], };
108 | t[RGB] = { bytesPerElement: [3, 6, 6, 12, 2], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT, UNSIGNED_SHORT_5_6_5], };
109 | t[RGBA] = { bytesPerElement: [4, 8, 8, 16, 2, 2], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT, UNSIGNED_SHORT_4_4_4_4, UNSIGNED_SHORT_5_5_5_1], };
110 | t[SRGB_ALPHA_EXT] = { bytesPerElement: [4, 8, 8, 16, 2, 2], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT, UNSIGNED_SHORT_4_4_4_4, UNSIGNED_SHORT_5_5_5_1], };
111 | t[DEPTH_COMPONENT] = { bytesPerElement: [2, 4], type: [UNSIGNED_INT, UNSIGNED_SHORT], };
112 | t[DEPTH_STENCIL] = { bytesPerElement: [4], };
113 |
114 | // sized formats
115 | t[R8] = { bytesPerElement: [1], };
116 | t[R8_SNORM] = { bytesPerElement: [1], };
117 | t[R16F] = { bytesPerElement: [2], };
118 | t[R32F] = { bytesPerElement: [4], };
119 | t[R8UI] = { bytesPerElement: [1], };
120 | t[R8I] = { bytesPerElement: [1], };
121 | t[R16UI] = { bytesPerElement: [2], };
122 | t[R16I] = { bytesPerElement: [2], };
123 | t[R32UI] = { bytesPerElement: [4], };
124 | t[R32I] = { bytesPerElement: [4], };
125 | t[RG8] = { bytesPerElement: [2], };
126 | t[RG8_SNORM] = { bytesPerElement: [2], };
127 | t[RG16F] = { bytesPerElement: [4], };
128 | t[RG32F] = { bytesPerElement: [8], };
129 | t[RG8UI] = { bytesPerElement: [2], };
130 | t[RG8I] = { bytesPerElement: [2], };
131 | t[RG16UI] = { bytesPerElement: [4], };
132 | t[RG16I] = { bytesPerElement: [4], };
133 | t[RG32UI] = { bytesPerElement: [8], };
134 | t[RG32I] = { bytesPerElement: [8], };
135 | t[RGB8] = { bytesPerElement: [3], };
136 | t[SRGB8] = { bytesPerElement: [3], };
137 | t[RGB565] = { bytesPerElement: [2], };
138 | t[RGB8_SNORM] = { bytesPerElement: [3], };
139 | t[R11F_G11F_B10F] = { bytesPerElement: [4], };
140 | t[RGB9_E5] = { bytesPerElement: [4], };
141 | t[RGB16F] = { bytesPerElement: [6], };
142 | t[RGB32F] = { bytesPerElement: [12], };
143 | t[RGB8UI] = { bytesPerElement: [3], };
144 | t[RGB8I] = { bytesPerElement: [3], };
145 | t[RGB16UI] = { bytesPerElement: [6], };
146 | t[RGB16I] = { bytesPerElement: [6], };
147 | t[RGB32UI] = { bytesPerElement: [12], };
148 | t[RGB32I] = { bytesPerElement: [12], };
149 | t[RGBA8] = { bytesPerElement: [4], };
150 | t[SRGB8_ALPHA8] = { bytesPerElement: [4], };
151 | t[RGBA8_SNORM] = { bytesPerElement: [4], };
152 | t[RGB5_A1] = { bytesPerElement: [2], };
153 | t[RGBA4] = { bytesPerElement: [2], };
154 | t[RGB10_A2] = { bytesPerElement: [4], };
155 | t[RGBA16F] = { bytesPerElement: [8], };
156 | t[RGBA32F] = { bytesPerElement: [16], };
157 | t[RGBA8UI] = { bytesPerElement: [4], };
158 | t[RGBA8I] = { bytesPerElement: [4], };
159 | t[RGB10_A2UI] = { bytesPerElement: [4], };
160 | t[RGBA16UI] = { bytesPerElement: [8], };
161 | t[RGBA16I] = { bytesPerElement: [8], };
162 | t[RGBA32I] = { bytesPerElement: [16], };
163 | t[RGBA32UI] = { bytesPerElement: [16], };
164 | // Sized Internal
165 | t[DEPTH_COMPONENT16] = { bytesPerElement: [2], };
166 | t[DEPTH_COMPONENT24] = { bytesPerElement: [4], };
167 | t[DEPTH_COMPONENT32F] = { bytesPerElement: [4], };
168 | t[DEPTH24_STENCIL8] = { bytesPerElement: [4], };
169 | t[DEPTH32F_STENCIL8] = { bytesPerElement: [4], };
170 | t[STENCIL_INDEX8] = { bytesPerElement: [1], };
171 |
172 | s_textureInternalFormatInfo = t;
173 | }
174 | return s_textureInternalFormatInfo[internalFormat];
175 | }
176 |
177 | function makeComputeBlockRectSizeFunction(blockWidth, blockHeight, bytesPerBlock) {
178 | return function(width, height, depth) {
179 | const blocksAcross = (width + blockWidth - 1) / blockWidth | 0;
180 | const blocksDown = (height + blockHeight - 1) / blockHeight | 0;
181 | return blocksAcross * blocksDown * bytesPerBlock * depth;
182 | };
183 | }
184 |
185 | function makeComputePaddedRectSizeFunction(minWidth, minHeight, divisor) {
186 | return function(width, height, depth) {
187 | return (Math.max(width, minWidth) * Math.max(height, minHeight) / divisor | 0) * depth;
188 | };
189 | }
190 |
191 | // WEBGL_compressed_texture_s3tc
192 | const COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
193 | const COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
194 | const COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
195 | const COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
196 | // WEBGL_compressed_texture_etc1
197 | const COMPRESSED_RGB_ETC1_WEBGL = 0x8D64;
198 | // WEBGL_compressed_texture_pvrtc
199 | const COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00;
200 | const COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01;
201 | const COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02;
202 | const COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03;
203 | // WEBGL_compressed_texture_etc
204 | const COMPRESSED_R11_EAC = 0x9270;
205 | const COMPRESSED_SIGNED_R11_EAC = 0x9271;
206 | const COMPRESSED_RG11_EAC = 0x9272;
207 | const COMPRESSED_SIGNED_RG11_EAC = 0x9273;
208 | const COMPRESSED_RGB8_ETC2 = 0x9274;
209 | const COMPRESSED_SRGB8_ETC2 = 0x9275;
210 | const COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276;
211 | const COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277;
212 | const COMPRESSED_RGBA8_ETC2_EAC = 0x9278;
213 | const COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279;
214 | // WEBGL_compressed_texture_astc
215 | const COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;
216 | const COMPRESSED_RGBA_ASTC_5x4_KHR = 0x93B1;
217 | const COMPRESSED_RGBA_ASTC_5x5_KHR = 0x93B2;
218 | const COMPRESSED_RGBA_ASTC_6x5_KHR = 0x93B3;
219 | const COMPRESSED_RGBA_ASTC_6x6_KHR = 0x93B4;
220 | const COMPRESSED_RGBA_ASTC_8x5_KHR = 0x93B5;
221 | const COMPRESSED_RGBA_ASTC_8x6_KHR = 0x93B6;
222 | const COMPRESSED_RGBA_ASTC_8x8_KHR = 0x93B7;
223 | const COMPRESSED_RGBA_ASTC_10x5_KHR = 0x93B8;
224 | const COMPRESSED_RGBA_ASTC_10x6_KHR = 0x93B9;
225 | const COMPRESSED_RGBA_ASTC_10x8_KHR = 0x93BA;
226 | const COMPRESSED_RGBA_ASTC_10x10_KHR = 0x93BB;
227 | const COMPRESSED_RGBA_ASTC_12x10_KHR = 0x93BC;
228 | const COMPRESSED_RGBA_ASTC_12x12_KHR = 0x93BD;
229 | const COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR = 0x93D0;
230 | const COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR = 0x93D1;
231 | const COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR = 0x93D2;
232 | const COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR = 0x93D3;
233 | const COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR = 0x93D4;
234 | const COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR = 0x93D5;
235 | const COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR = 0x93D6;
236 | const COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR = 0x93D7;
237 | const COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR = 0x93D8;
238 | const COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR = 0x93D9;
239 | const COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR = 0x93DA;
240 | const COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR = 0x93DB;
241 | const COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR = 0x93DC;
242 | const COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR = 0x93DD;
243 | // WEBGL_compressed_texture_s3tc_srgb
244 | const COMPRESSED_SRGB_S3TC_DXT1_EXT = 0x8C4C;
245 | const COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT = 0x8C4D;
246 | const COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT = 0x8C4E;
247 | const COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = 0x8C4F;
248 | // EXT_texture_compression_bptc
249 | const COMPRESSED_RGBA_BPTC_UNORM_EXT = 0x8E8C;
250 | const COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT = 0x8E8D;
251 | const COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT = 0x8E8E;
252 | const COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT = 0x8E8F;
253 | // EXT_texture_compression_rgtc
254 | const COMPRESSED_RED_RGTC1_EXT = 0x8DBB;
255 | const COMPRESSED_SIGNED_RED_RGTC1_EXT = 0x8DBC;
256 | const COMPRESSED_RED_GREEN_RGTC2_EXT = 0x8DBD;
257 | const COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT = 0x8DBE;
258 |
259 | const compressedTextureFunctions = new Map([
260 | [ COMPRESSED_RGB_S3TC_DXT1_EXT, makeComputeBlockRectSizeFunction(4, 4, 8) ],
261 | [ COMPRESSED_RGBA_S3TC_DXT1_EXT, makeComputeBlockRectSizeFunction(4, 4, 8) ],
262 | [ COMPRESSED_RGBA_S3TC_DXT3_EXT, makeComputeBlockRectSizeFunction(4, 4, 16) ],
263 | [ COMPRESSED_RGBA_S3TC_DXT5_EXT, makeComputeBlockRectSizeFunction(4, 4, 16) ],
264 |
265 | [ COMPRESSED_RGB_ETC1_WEBGL, makeComputeBlockRectSizeFunction(4, 4, 8) ],
266 |
267 | [ COMPRESSED_RGB_PVRTC_4BPPV1_IMG, makeComputePaddedRectSizeFunction(8, 8, 2) ],
268 | [ COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, makeComputePaddedRectSizeFunction(8, 8, 2) ],
269 | [ COMPRESSED_RGB_PVRTC_2BPPV1_IMG, makeComputePaddedRectSizeFunction(16, 8, 4) ],
270 | [ COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, makeComputePaddedRectSizeFunction(16, 8, 4) ],
271 |
272 | [ COMPRESSED_R11_EAC, makeComputeBlockRectSizeFunction(4, 4, 8) ],
273 | [ COMPRESSED_SIGNED_R11_EAC, makeComputeBlockRectSizeFunction(4, 4, 8) ],
274 | [ COMPRESSED_RGB8_ETC2, makeComputeBlockRectSizeFunction(4, 4, 8) ],
275 | [ COMPRESSED_SRGB8_ETC2, makeComputeBlockRectSizeFunction(4, 4, 8) ],
276 | [ COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, makeComputeBlockRectSizeFunction(4, 4, 8) ],
277 | [ COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, makeComputeBlockRectSizeFunction(4, 4, 8) ],
278 |
279 | [ COMPRESSED_RG11_EAC, makeComputeBlockRectSizeFunction(4, 4, 16) ],
280 | [ COMPRESSED_SIGNED_RG11_EAC, makeComputeBlockRectSizeFunction(4, 4, 16) ],
281 | [ COMPRESSED_RGBA8_ETC2_EAC, makeComputeBlockRectSizeFunction(4, 4, 16) ],
282 | [ COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, makeComputeBlockRectSizeFunction(4, 4, 16) ],
283 |
284 | [ COMPRESSED_RGBA_ASTC_4x4_KHR, makeComputeBlockRectSizeFunction(4, 4, 16) ],
285 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR, makeComputeBlockRectSizeFunction(4, 4, 16) ],
286 | [ COMPRESSED_RGBA_ASTC_5x4_KHR, makeComputeBlockRectSizeFunction(5, 4, 16) ],
287 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR, makeComputeBlockRectSizeFunction(5, 4, 16) ],
288 | [ COMPRESSED_RGBA_ASTC_5x5_KHR, makeComputeBlockRectSizeFunction(5, 5, 16) ],
289 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR, makeComputeBlockRectSizeFunction(5, 5, 16) ],
290 | [ COMPRESSED_RGBA_ASTC_6x5_KHR, makeComputeBlockRectSizeFunction(6, 5, 16) ],
291 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR, makeComputeBlockRectSizeFunction(6, 5, 16) ],
292 | [ COMPRESSED_RGBA_ASTC_6x6_KHR, makeComputeBlockRectSizeFunction(6, 6, 16) ],
293 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR, makeComputeBlockRectSizeFunction(6, 6, 16) ],
294 | [ COMPRESSED_RGBA_ASTC_8x5_KHR, makeComputeBlockRectSizeFunction(8, 5, 16) ],
295 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR, makeComputeBlockRectSizeFunction(8, 5, 16) ],
296 | [ COMPRESSED_RGBA_ASTC_8x6_KHR, makeComputeBlockRectSizeFunction(8, 6, 16) ],
297 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR, makeComputeBlockRectSizeFunction(8, 6, 16) ],
298 | [ COMPRESSED_RGBA_ASTC_8x8_KHR, makeComputeBlockRectSizeFunction(8, 8, 16) ],
299 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR, makeComputeBlockRectSizeFunction(8, 8, 16) ],
300 | [ COMPRESSED_RGBA_ASTC_10x5_KHR, makeComputeBlockRectSizeFunction(10, 5, 16) ],
301 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR, makeComputeBlockRectSizeFunction(10, 5, 16) ],
302 | [ COMPRESSED_RGBA_ASTC_10x6_KHR, makeComputeBlockRectSizeFunction(10, 6, 16) ],
303 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR, makeComputeBlockRectSizeFunction(10, 6, 16) ],
304 | [ COMPRESSED_RGBA_ASTC_10x8_KHR, makeComputeBlockRectSizeFunction(10, 8, 16) ],
305 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR, makeComputeBlockRectSizeFunction(10, 8, 16) ],
306 | [ COMPRESSED_RGBA_ASTC_10x10_KHR, makeComputeBlockRectSizeFunction(10, 10, 16) ],
307 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR, makeComputeBlockRectSizeFunction(10, 10, 16) ],
308 | [ COMPRESSED_RGBA_ASTC_12x10_KHR, makeComputeBlockRectSizeFunction(12, 10, 16) ],
309 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR, makeComputeBlockRectSizeFunction(12, 10, 16) ],
310 | [ COMPRESSED_RGBA_ASTC_12x12_KHR, makeComputeBlockRectSizeFunction(12, 12, 16) ],
311 | [ COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR, makeComputeBlockRectSizeFunction(12, 12, 16) ],
312 |
313 | [ COMPRESSED_SRGB_S3TC_DXT1_EXT, makeComputeBlockRectSizeFunction(4, 4, 8) ],
314 | [ COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, makeComputeBlockRectSizeFunction(4, 4, 8) ],
315 | [ COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, makeComputeBlockRectSizeFunction(4, 4, 16) ],
316 | [ COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, makeComputeBlockRectSizeFunction(4, 4, 16) ],
317 |
318 | [ COMPRESSED_RGBA_BPTC_UNORM_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ],
319 | [ COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ],
320 | [ COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ],
321 | [ COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ],
322 |
323 | [ COMPRESSED_RED_RGTC1_EXT, makeComputeBlockRectSizeFunction( 4, 4, 8 ) ],
324 | [ COMPRESSED_SIGNED_RED_RGTC1_EXT, makeComputeBlockRectSizeFunction( 4, 4, 8 ) ],
325 | [ COMPRESSED_RED_GREEN_RGTC2_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ],
326 | [ COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ],
327 | ]);
328 |
329 | /**
330 | * Gets the number of bytes per element for a given internalFormat / type
331 | * @param {number} internalFormat The internalFormat parameter from texImage2D etc..
332 | * @param {number} type The type parameter for texImage2D etc..
333 | * @return {number} the number of bytes per element for the given internalFormat, type combo
334 | * @memberOf module:twgl/textures
335 | */
336 | function getBytesPerElementForInternalFormat(internalFormat, type) {
337 | const info = getTextureInternalFormatInfo(internalFormat);
338 | if (!info) {
339 | throw "unknown internal format";
340 | }
341 | if (info.type) {
342 | const ndx = info.type.indexOf(type);
343 | if (ndx < 0) {
344 | throw new Error(`unsupported type ${type} for internalformat ${internalFormat}`);
345 | }
346 | return info.bytesPerElement[ndx];
347 | }
348 | return info.bytesPerElement[0];
349 | }
350 |
351 | function getBytesForMipUncompressed(internalFormat, width, height, depth, type) {
352 | const bytesPerElement = getBytesPerElementForInternalFormat(internalFormat, type);
353 | return width * height * depth * bytesPerElement;
354 | }
355 |
356 | function getBytesForMip(internalFormat, width, height, depth, type) {
357 | const fn = compressedTextureFunctions.get(internalFormat);
358 | return fn ? fn(width, height, depth) : getBytesForMipUncompressed(internalFormat, width, height, depth, type);
359 | }
360 |
361 | function isTypedArray(v) {
362 | return v && v.buffer && v.buffer instanceof ArrayBuffer;
363 | }
364 |
365 | function isBufferSource(v) {
366 | return isTypedArray(v) || v instanceof ArrayBuffer;
367 | }
368 |
369 | function getDrawingbufferInfo(gl) {
370 | return {
371 | samples: gl.getParameter(gl.SAMPLES) || 1,
372 | depthBits: gl.getParameter(gl.DEPTH_BITS),
373 | stencilBits: gl.getParameter(gl.STENCIL_BITS),
374 | contextAttributes: gl.getContextAttributes(),
375 | };
376 | }
377 |
378 | function computeDepthStencilSize(drawingBufferInfo) {
379 | const {depthBits, stencilBits} = drawingBufferInfo;
380 | const depthSize = (depthBits + stencilBits + 7) / 8 | 0;
381 | return depthSize === 3 ? 4 : depthSize;
382 | }
383 |
384 | function computeDrawingbufferSize(gl, drawingBufferInfo) {
385 | if (gl.isContextLost()) {
386 | return 0;
387 | }
388 | const {samples} = drawingBufferInfo;
389 | // this will need to change for hi-color support
390 | const colorSize = 4;
391 | const size = gl.drawingBufferWidth * gl.drawingBufferHeight;
392 | const depthStencilSize = computeDepthStencilSize(drawingBufferInfo);
393 | return size * colorSize + size * samples * colorSize + size * depthStencilSize;
394 | }
395 |
396 | // I know this is not a full check
397 | function isNumber(v) {
398 | return typeof v === 'number';
399 | }
400 |
401 | function collectObjects(state, type) {
402 | const list = [...state.webglObjectToMemory.keys()]
403 | .filter(obj => obj instanceof type)
404 | .map((obj) => state.webglObjectToMemory.get(obj));
405 |
406 | return list;
407 | }
408 |
409 | function getStackTrace() {
410 | const stack = (new Error()).stack;
411 | const lines = stack.split('\n');
412 | // Remove the first two entries, the error message and this function itself, or the webgl-memory itself.
413 | const userLines = lines.slice(2).filter((l) => !l.includes('webgl-memory.js'));
414 | return userLines.join('\n');
415 | }
416 |
417 | /*
418 | The MIT License (MIT)
419 |
420 | Copyright (c) 2021 Gregg Tavares
421 |
422 | Permission is hereby granted, free of charge, to any person obtaining a copy of
423 | this software and associated documentation files (the "Software"), to deal in
424 | the Software without restriction, including without limitation the rights to
425 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
426 | the Software, and to permit persons to whom the Software is furnished to do so,
427 | subject to the following conditions:
428 |
429 | The above copyright notice and this permission notice shall be included in all
430 | copies or substantial portions of the Software.
431 |
432 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
433 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
434 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
435 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
436 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
437 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
438 | */
439 |
440 | //------------ [ from https://github.com/KhronosGroup/WebGLDeveloperTools ]
441 |
442 | /*
443 | ** Copyright (c) 2012 The Khronos Group Inc.
444 | **
445 | ** Permission is hereby granted, free of charge, to any person obtaining a
446 | ** copy of this software and/or associated documentation files (the
447 | ** "Materials"), to deal in the Materials without restriction, including
448 | ** without limitation the rights to use, copy, modify, merge, publish,
449 | ** distribute, sublicense, and/or sell copies of the Materials, and to
450 | ** permit persons to whom the Materials are furnished to do so, subject to
451 | ** the following conditions:
452 | **
453 | ** The above copyright notice and this permission notice shall be included
454 | ** in all copies or substantial portions of the Materials.
455 | **
456 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
457 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
458 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
459 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
460 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
461 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
462 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
463 | */
464 |
465 |
466 | const augmentedSet = new Set();
467 |
468 | /**
469 | * Given a WebGL context replaces all the functions with wrapped functions
470 | * that call gl.getError after every command
471 | *
472 | * @param {WebGLRenderingContext|Extension} ctx The webgl context to wrap.
473 | * @param {string} nameOfClass (eg, webgl, webgl2, OES_texture_float)
474 | */
475 | // eslint-disable-next-line consistent-return
476 | function augmentAPI(ctx, nameOfClass, options = {}) {
477 |
478 | if (augmentedSet.has(ctx)) {
479 | return ctx;
480 | }
481 | augmentedSet.add(ctx);
482 |
483 | const origGLErrorFn = options.origGLErrorFn || ctx.getError;
484 |
485 | function createSharedState(ctx) {
486 | const drawingBufferInfo = getDrawingbufferInfo(ctx);
487 | const sharedState = {
488 | baseContext: ctx,
489 | config: options,
490 | apis: {
491 | // custom extension
492 | gman_webgl_memory: {
493 | ctx: {
494 | getMemoryInfo() {
495 | const drawingbuffer = computeDrawingbufferSize(ctx, drawingBufferInfo);
496 | return {
497 | memory: {
498 | ...memory,
499 | drawingbuffer,
500 | total: drawingbuffer + memory.buffer + memory.texture + memory.renderbuffer,
501 | },
502 | resources: {
503 | ...resources,
504 | },
505 | };
506 | },
507 | getResourcesInfo(type) {
508 | return collectObjects(sharedState, type);
509 | },
510 | },
511 | },
512 | },
513 | resources: {},
514 | memory: {
515 | texture: 0,
516 | buffer: 0,
517 | renderbuffer: 0,
518 | },
519 | bindings: new Map(),
520 | defaultVertexArray: {},
521 | webglObjectToMemory: new Map(),
522 | };
523 |
524 | const unRestorableAPIs = new Set([
525 | 'webgl',
526 | 'webgl2',
527 | 'webgl_lose_context',
528 | ]);
529 |
530 | function resetSharedState() {
531 | sharedState.bindings.clear();
532 | sharedState.webglObjectToMemory.clear();
533 | sharedState.webglObjectToMemory.set(sharedState.defaultVertexArray, {});
534 | sharedState.currentVertexArray = sharedState.defaultVertexArray;
535 | [sharedState.resources, sharedState.memory].forEach(function(obj) {
536 | for (const prop in obj) {
537 | obj[prop] = 0;
538 | }
539 | });
540 | }
541 |
542 | function handleContextLost() {
543 | // Issues:
544 | // * all resources are lost.
545 | // Solution: handled by resetSharedState
546 | // * all functions are no-op
547 | // Solutions:
548 | // * swap all functions for noop
549 | // (not so easy because some functions return values)
550 | // * wrap all functions is a isContextLost check forwarder
551 | // (slow? and same as above)
552 | // * have each function manually check for context lost
553 | // (simple but repetitive)
554 | // * all extensions are lost
555 | // Solution: For these we go through and restore all the functions
556 | // on each extension
557 | resetSharedState();
558 | sharedState.isContextLost = true;
559 |
560 | // restore all original functions for extensions since
561 | // user will have to get new extensions.
562 | for (const [name, {ctx, origFuncs}] of [...Object.entries(sharedState.apis)]) {
563 | if (!unRestorableAPIs.has(name) && origFuncs) {
564 | augmentedSet.delete(ctx);
565 | for (const [funcName, origFn] of Object.entries(origFuncs)) {
566 | ctx[funcName] = origFn;
567 | }
568 | delete apis[name];
569 | }
570 | }
571 | }
572 |
573 | function handleContextRestored() {
574 | sharedState.isContextLost = false;
575 | }
576 |
577 | if (ctx.canvas) {
578 | ctx.canvas.addEventListener('webglcontextlost', handleContextLost);
579 | ctx.canvas.addEventListener('webglcontextrestored', handleContextRestored);
580 | }
581 |
582 | resetSharedState();
583 | return sharedState;
584 | }
585 |
586 | const sharedState = options.sharedState || createSharedState(ctx);
587 | options.sharedState = sharedState;
588 |
589 | const {
590 | apis,
591 | bindings,
592 | memory,
593 | resources,
594 | webglObjectToMemory,
595 | } = sharedState;
596 |
597 | const origFuncs = {};
598 |
599 | function noop() {
600 | }
601 |
602 | function makeCreateWrapper(ctx, typeName, _funcName) {
603 | const funcName = _funcName || `create${typeName[0].toUpperCase()}${typeName.substr(1)}`;
604 | if (!ctx[funcName]) {
605 | return null;
606 | }
607 | resources[typeName] = 0;
608 | return function(ctx, funcName, args, webglObj) {
609 | if (sharedState.isContextLost) {
610 | return;
611 | }
612 | ++resources[typeName];
613 | webglObjectToMemory.set(webglObj, {
614 | size: 0,
615 | stackCreated: getStackTrace(),
616 | });
617 | };
618 | }
619 |
620 | function makeDeleteWrapper(typeName, fn = noop, _funcName) {
621 | const funcName = _funcName || `delete${typeName[0].toUpperCase()}${typeName.substr(1)}`;
622 | if (!ctx[funcName]) {
623 | return null;
624 | }
625 | return function(ctx, funcName, args) {
626 | if (sharedState.isContextLost) {
627 | return;
628 | }
629 | const [obj] = args;
630 | const info = webglObjectToMemory.get(obj);
631 | if (info) {
632 | --resources[typeName];
633 | fn(obj, info);
634 | // TODO: handle resource counts
635 | webglObjectToMemory.delete(obj);
636 | }
637 | };
638 | }
639 |
640 | function updateRenderbuffer(target, samples, internalFormat, width, height) {
641 | if (sharedState.isContextLost) {
642 | return;
643 | }
644 | const obj = bindings.get(target);
645 | if (!obj) {
646 | throw new Error(`no renderbuffer bound to ${target}`);
647 | }
648 | const info = webglObjectToMemory.get(obj);
649 | if (!info) {
650 | throw new Error(`unknown renderbuffer ${obj}`);
651 | }
652 |
653 | const bytesForMip = getBytesForMip(internalFormat, width, height, 1);
654 | const newSize = bytesForMip * samples;
655 |
656 | memory.renderbuffer -= info.size;
657 | info.size = newSize;
658 | info.stackUpdated = getStackTrace();
659 | memory.renderbuffer += newSize;
660 | }
661 |
662 | const ELEMENT_ARRAY_BUFFER = 0x8893;
663 |
664 | const UNSIGNED_BYTE = 0x1401;
665 | const TEXTURE_CUBE_MAP = 0x8513;
666 | const TEXTURE_2D_ARRAY = 0x8C1A;
667 | const TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515;
668 | const TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516;
669 | const TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517;
670 | const TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518;
671 | const TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519;
672 | const TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A;
673 |
674 | const TEXTURE_BASE_LEVEL = 0x813C;
675 | const TEXTURE_MAX_LEVEL = 0x813D;
676 |
677 | const cubemapTargets = new Set([
678 | TEXTURE_CUBE_MAP_POSITIVE_X,
679 | TEXTURE_CUBE_MAP_NEGATIVE_X,
680 | TEXTURE_CUBE_MAP_POSITIVE_Y,
681 | TEXTURE_CUBE_MAP_NEGATIVE_Y,
682 | TEXTURE_CUBE_MAP_POSITIVE_Z,
683 | TEXTURE_CUBE_MAP_NEGATIVE_Z,
684 | ]);
685 |
686 | function isCubemapFace(target) {
687 | return cubemapTargets.has(target);
688 | }
689 |
690 | function getTextureInfo(target) {
691 | target = isCubemapFace(target) ? TEXTURE_CUBE_MAP : target;
692 | const obj = bindings.get(target);
693 | if (!obj) {
694 | throw new Error(`no texture bound to ${target}`);
695 | }
696 | const info = webglObjectToMemory.get(obj);
697 | if (!info) {
698 | throw new Error(`unknown texture ${obj}`);
699 | }
700 | return info;
701 | }
702 |
703 | function updateMipLevel(info, target, level, internalFormat, width, height, depth, type) {
704 | const oldSize = info.size;
705 | const newMipSize = getBytesForMip(internalFormat, width, height, depth, type);
706 |
707 | const faceNdx = isCubemapFace(target)
708 | ? target - TEXTURE_CUBE_MAP_POSITIVE_X
709 | : 0;
710 |
711 | info.mips = info.mips || [];
712 | info.mips[level] = info.mips[level] || [];
713 | const mipFaceInfo = info.mips[level][faceNdx] || {};
714 | info.size -= mipFaceInfo.size || 0;
715 |
716 | mipFaceInfo.size = newMipSize;
717 | mipFaceInfo.internalFormat = internalFormat;
718 | mipFaceInfo.type = type;
719 | mipFaceInfo.width = width;
720 | mipFaceInfo.height = height;
721 | mipFaceInfo.depth = depth;
722 |
723 | info.mips[level][faceNdx] = mipFaceInfo;
724 | info.size += newMipSize;
725 |
726 | memory.texture -= oldSize;
727 | memory.texture += info.size;
728 |
729 | info.stackUpdated = getStackTrace();
730 | }
731 |
732 | function updateTexStorage(target, levels, internalFormat, width, height, depth) {
733 | const info = getTextureInfo(target);
734 | const numFaces = target === TEXTURE_CUBE_MAP ? 6 : 1;
735 | const baseFaceTarget = target === TEXTURE_CUBE_MAP ? TEXTURE_CUBE_MAP_POSITIVE_X : target;
736 | for (let level = 0; level < levels; ++level) {
737 | for (let face = 0; face < numFaces; ++face) {
738 | updateMipLevel(info, baseFaceTarget + face, level, internalFormat, width, height, depth);
739 | }
740 | width = Math.ceil(Math.max(width / 2, 1));
741 | height = Math.ceil(Math.max(height / 2, 1));
742 | depth = target === TEXTURE_2D_ARRAY ? depth : Math.ceil(Math.max(depth / 2, 1));
743 | }
744 | }
745 |
746 | function handleBindVertexArray(gl, funcName, args) {
747 | if (sharedState.isContextLost) {
748 | return;
749 | }
750 | const [va] = args;
751 | sharedState.currentVertexArray = va ? va : sharedState.defaultVertexArray;
752 | }
753 |
754 | function handleBufferBinding(target, obj) {
755 | if (sharedState.isContextLost) {
756 | return;
757 | }
758 | switch (target) {
759 | case ELEMENT_ARRAY_BUFFER: {
760 | const info = webglObjectToMemory.get(sharedState.currentVertexArray);
761 | info.elementArrayBuffer = obj;
762 | break;
763 | }
764 | default:
765 | bindings.set(target, obj);
766 | break;
767 | }
768 | }
769 |
770 | const preChecks = {};
771 | const postChecks = {
772 | // WebGL1
773 | // void bufferData(GLenum target, GLsizeiptr size, GLenum usage);
774 | // void bufferData(GLenum target, [AllowShared] BufferSource? srcData, GLenum usage);
775 | // WebGL2:
776 | // void bufferData(GLenum target, [AllowShared] ArrayBufferView srcData, GLenum usage, GLuint srcOffset,
777 | // optional GLuint length = 0);
778 | bufferData(gl, funcName, args) {
779 | if (sharedState.isContextLost) {
780 | return;
781 | }
782 | const [target, src, /* usage */, /*srcOffset = 0*/, length = undefined] = args;
783 | let obj;
784 | switch (target) {
785 | case ELEMENT_ARRAY_BUFFER:
786 | {
787 | const info = webglObjectToMemory.get(sharedState.currentVertexArray);
788 | obj = info.elementArrayBuffer;
789 | }
790 | break;
791 | default:
792 | obj = bindings.get(target);
793 | break;
794 | }
795 | if (!obj) {
796 | throw new Error(`no buffer bound to ${target}`);
797 | }
798 | let newSize = 0;
799 | if (length !== undefined) {
800 | newSize = length * src.BYTES_PER_ELEMENT;
801 | } else if (isBufferSource(src)) {
802 | newSize = src.byteLength;
803 | } else if (isNumber(src)) {
804 | newSize = src;
805 | } else {
806 | throw new Error(`unsupported bufferData src type ${src}`);
807 | }
808 |
809 | const info = webglObjectToMemory.get(obj);
810 | if (!info) {
811 | throw new Error(`unknown buffer ${obj}`);
812 | }
813 |
814 | memory.buffer -= info.size;
815 | info.size = newSize;
816 | info.stackUpdated = getStackTrace();
817 | memory.buffer += newSize;
818 | },
819 |
820 | bindVertexArray: handleBindVertexArray,
821 | bindVertexArrayOES: handleBindVertexArray,
822 |
823 | bindBuffer(gl, funcName, args) {
824 | const [target, obj] = args;
825 | handleBufferBinding(target, obj);
826 | },
827 |
828 | bindBufferBase(gl, funcName, args) {
829 | const [target, /*ndx*/, obj] = args;
830 | handleBufferBinding(target, obj);
831 | },
832 |
833 | bindBufferRange(gl, funcName, args) {
834 | const [target, /*ndx*/, obj, /*offset*/, /*size*/] = args;
835 | handleBufferBinding(target, obj);
836 | },
837 |
838 | bindRenderbuffer(gl, funcName, args) {
839 | if (sharedState.isContextLost) {
840 | return;
841 | }
842 | const [target, obj] = args;
843 | bindings.set(target, obj);
844 | },
845 |
846 | bindTexture(gl, funcName, args) {
847 | if (sharedState.isContextLost) {
848 | return;
849 | }
850 | const [target, obj] = args;
851 | bindings.set(target, obj);
852 | },
853 |
854 | // void gl.copyTexImage2D(target, level, internalformat, x, y, width, height, border);
855 | copyTexImage2D(ctx, funcName, args) {
856 | if (sharedState.isContextLost) {
857 | return;
858 | }
859 | const [target, level, internalFormat, /*x*/, /*y*/, width, height, /*border*/] = args;
860 | const info = getTextureInfo(target);
861 | updateMipLevel(info, target, level, internalFormat, width, height, 1, UNSIGNED_BYTE);
862 | },
863 |
864 | createBuffer: makeCreateWrapper(ctx, 'buffer'),
865 | createFramebuffer: makeCreateWrapper(ctx, 'framebuffer'),
866 | createRenderbuffer: makeCreateWrapper(ctx, 'renderbuffer'),
867 | createProgram: makeCreateWrapper(ctx, 'program'),
868 | createQuery: makeCreateWrapper(ctx, 'query'),
869 | createShader: makeCreateWrapper(ctx, 'shader'),
870 | createSampler: makeCreateWrapper(ctx, 'sampler'),
871 | createTexture: makeCreateWrapper(ctx, 'texture'),
872 | createTransformFeedback: makeCreateWrapper(ctx, 'transformFeedback'),
873 | createVertexArray: makeCreateWrapper(ctx, 'vertexArray'),
874 | createVertexArrayOES: makeCreateWrapper(ctx, 'vertexArray', 'createVertexArrayOES'),
875 |
876 | // WebGL 1:
877 | // void gl.compressedTexImage2D(target, level, internalformat, width, height, border, ArrayBufferView? pixels);
878 | //
879 | // Additionally available in WebGL 2:
880 | // read from buffer bound to gl.PIXEL_UNPACK_BUFFER
881 | // void gl.compressedTexImage2D(target, level, internalformat, width, height, border, GLsizei imageSize, GLintptr offset);
882 | // void gl.compressedTexImage2D(target, level, internalformat, width, height, border,
883 | // ArrayBufferView srcData, optional srcOffset, optional srcLengthOverride);
884 | compressedTexImage2D(ctx, funcName, args) {
885 | if (sharedState.isContextLost) {
886 | return;
887 | }
888 | const [target, level, internalFormat, width, height] = args;
889 | const info = getTextureInfo(target);
890 | updateMipLevel(info, target, level, internalFormat, width, height, 1, UNSIGNED_BYTE);
891 | },
892 |
893 | // read from buffer bound to gl.PIXEL_UNPACK_BUFFER
894 | // void gl.compressedTexImage3D(target, level, internalformat, width, height, depth, border, GLsizei imageSize, GLintptr offset);
895 | // void gl.compressedTexImage3D(target, level, internalformat, width, height, depth, border,
896 | // ArrayBufferView srcData, optional srcOffset, optional srcLengthOverride);
897 | compressedTexImage3D(ctx, funcName, args) {
898 | if (sharedState.isContextLost) {
899 | return;
900 | }
901 | const [target, level, internalFormat, width, height, depth] = args;
902 | const info = getTextureInfo(target);
903 | updateMipLevel(info, target, level, internalFormat, width, height, depth, UNSIGNED_BYTE);
904 | },
905 |
906 | deleteBuffer: makeDeleteWrapper('buffer', function(obj, info) {
907 | memory.buffer -= info.size;
908 | }),
909 | deleteFramebuffer: makeDeleteWrapper('framebuffer'),
910 | deleteProgram: makeDeleteWrapper('program'),
911 | deleteQuery: makeDeleteWrapper('query'),
912 | deleteRenderbuffer: makeDeleteWrapper('renderbuffer', function(obj, info) {
913 | memory.renderbuffer -= info.size;
914 | }),
915 | deleteSampler: makeDeleteWrapper('sampler'),
916 | deleteShader: makeDeleteWrapper('shader'),
917 | deleteSync: makeDeleteWrapper('sync'),
918 | deleteTexture: makeDeleteWrapper('texture', function(obj, info) {
919 | memory.texture -= info.size;
920 | }),
921 | deleteTransformFeedback: makeDeleteWrapper('transformFeedback'),
922 | deleteVertexArray: makeDeleteWrapper('vertexArray'),
923 | deleteVertexArrayOES: makeDeleteWrapper('vertexArray', noop, 'deleteVertexArrayOES'),
924 |
925 | fenceSync: function(ctx) {
926 | if (sharedState.isContextLost) {
927 | return undefined;
928 | }
929 | if (!ctx.fenceSync) {
930 | return undefined;
931 | }
932 | resources.sync = 0;
933 | return function(ctx, funcName, args, webglObj) {
934 | ++resources.sync;
935 |
936 | webglObjectToMemory.set(webglObj, {
937 | size: 0,
938 | });
939 | };
940 | }(ctx),
941 |
942 | generateMipmap(ctx, funcName, args) {
943 | if (sharedState.isContextLost) {
944 | return;
945 | }
946 | const [target] = args;
947 | const info = getTextureInfo(target);
948 | const baseMipNdx = info.parameters ? info.parameters.get(TEXTURE_BASE_LEVEL) || 0 : 0;
949 | const maxMipNdx = info.parameters ? info.parameters.get(TEXTURE_MAX_LEVEL) || 1024 : 1024;
950 | const mipInfo = info.mips[baseMipNdx][0];
951 | let {width, height, depth} = mipInfo;
952 | const {internalFormat, type} = mipInfo;
953 | let level = baseMipNdx + 1;
954 |
955 | const numFaces = target === TEXTURE_CUBE_MAP ? 6 : 1;
956 | const baseFaceTarget = target === TEXTURE_CUBE_MAP ? TEXTURE_CUBE_MAP_POSITIVE_X : target;
957 | while (level <= maxMipNdx && !(width === 1 && height === 1 && (depth === 1 || target === TEXTURE_2D_ARRAY))) {
958 | width = Math.ceil(Math.max(width / 2, 1));
959 | height = Math.ceil(Math.max(height / 2, 1));
960 | depth = target === TEXTURE_2D_ARRAY ? depth : Math.ceil(Math.max(depth / 2, 1));
961 | for (let face = 0; face < numFaces; ++face) {
962 | updateMipLevel(info, baseFaceTarget + face, level, internalFormat, width, height, depth, type);
963 | }
964 | ++level;
965 | }
966 | },
967 |
968 | getSupportedExtensions(ctx, funcName, args, result) {
969 | if (sharedState.isContextLost) {
970 | return;
971 | }
972 | result.push('GMAN_webgl_memory');
973 | },
974 |
975 | // void gl.renderbufferStorage(target, internalFormat, width, height);
976 | // gl.RGBA4: 4 red bits, 4 green bits, 4 blue bits 4 alpha bits.
977 | // gl.RGB565: 5 red bits, 6 green bits, 5 blue bits.
978 | // gl.RGB5_A1: 5 red bits, 5 green bits, 5 blue bits, 1 alpha bit.
979 | // gl.DEPTH_COMPONENT16: 16 depth bits.
980 | // gl.STENCIL_INDEX8: 8 stencil bits.
981 | // gl.DEPTH_STENCIL
982 | renderbufferStorage(ctx, funcName, args) {
983 | const [target, internalFormat, width, height] = args;
984 | updateRenderbuffer(target, 1, internalFormat, width, height);
985 | },
986 |
987 | // void gl.renderbufferStorageMultisample(target, samples, internalFormat, width, height);
988 | renderbufferStorageMultisample(ctx, funcName, args) {
989 | const [target, samples, internalFormat, width, height] = args;
990 | updateRenderbuffer(target, samples, internalFormat, width, height);
991 | },
992 |
993 | texImage2D(ctx, funcName, args) {
994 | if (sharedState.isContextLost) {
995 | return;
996 | }
997 | // WebGL1:
998 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView? pixels);
999 | // void gl.texImage2D(target, level, internalformat, format, type, ImageData? pixels);
1000 | // void gl.texImage2D(target, level, internalformat, format, type, HTMLImageElement? pixels);
1001 | // void gl.texImage2D(target, level, internalformat, format, type, HTMLCanvasElement? pixels);
1002 | // void gl.texImage2D(target, level, internalformat, format, type, HTMLVideoElement? pixels);
1003 | // void gl.texImage2D(target, level, internalformat, format, type, ImageBitmap? pixels// );
1004 |
1005 | // WebGL2:
1006 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, GLintptr offset);
1007 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLCanvasElement source);
1008 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLImageElement source);
1009 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLVideoElement source);
1010 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ImageBitmap source);
1011 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ImageData source);
1012 | // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView srcData, srcOffset);
1013 | const [target, level, internalFormat] = args;
1014 | let width;
1015 | let height;
1016 | let type;
1017 | if (args.length === 6) {
1018 | const src = args[5];
1019 | width = src.width;
1020 | height = src.height;
1021 | type = args[4];
1022 | } else {
1023 | width = args[3];
1024 | height = args[4];
1025 | type = args[7];
1026 | }
1027 |
1028 | const info = getTextureInfo(target);
1029 | updateMipLevel(info, target, level, internalFormat, width, height, 1, type);
1030 | },
1031 |
1032 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, GLintptr offset);
1033 | //
1034 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, HTMLCanvasElement source);
1035 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, HTMLImageElement source);
1036 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, HTMLVideoElement source);
1037 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ImageBitmap source);
1038 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ImageData source);
1039 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ArrayBufferView? srcData);
1040 | // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ArrayBufferView srcData, srcOffset);
1041 |
1042 | texImage3D(ctx, funcName, args) {
1043 | if (sharedState.isContextLost) {
1044 | return;
1045 | }
1046 | const [target, level, internalFormat, width, height, depth, /*border*/, /*format*/, type] = args;
1047 | const info = getTextureInfo(target);
1048 | updateMipLevel(info, target, level, internalFormat, width, height, depth, type);
1049 | },
1050 |
1051 | texParameteri(ctx, funcName, args) {
1052 | if (sharedState.isContextLost) {
1053 | return;
1054 | }
1055 | const [target, pname, value] = args;
1056 | const info = getTextureInfo(target);
1057 | info.parameters = info.parameters || new Map();
1058 | info.parameters.set(pname, value);
1059 | },
1060 |
1061 | // void gl.texStorage2D(target, levels, internalformat, width, height);
1062 | texStorage2D(ctx, funcName, args) {
1063 | const [target, levels, internalFormat, width, height] = args;
1064 | updateTexStorage(target, levels, internalFormat, width, height, 1);
1065 | },
1066 |
1067 | // void gl.texStorage3D(target, levels, internalformat, width, height, depth);
1068 | texStorage3D(ctx, funcName, args) {
1069 | const [target, levels, internalFormat, width, height, depth] = args;
1070 | updateTexStorage(target, levels, internalFormat, width, height, depth);
1071 | },
1072 | };
1073 |
1074 | const extraWrappers = {
1075 | getExtension(ctx, propertyName) {
1076 | if (sharedState.isContextLost) {
1077 | return;
1078 | }
1079 | const origFn = ctx[propertyName];
1080 | ctx[propertyName] = function(...args) {
1081 | const extensionName = args[0].toLowerCase();
1082 | const api = apis[extensionName];
1083 | if (api) {
1084 | return api.ctx;
1085 | }
1086 | const ext = origFn.call(ctx, ...args);
1087 | if (ext) {
1088 | augmentAPI(ext, extensionName, {...options, origGLErrorFn});
1089 | }
1090 | return ext;
1091 | };
1092 | },
1093 | };
1094 |
1095 | // Makes a function that calls a WebGL function and then calls getError.
1096 | function makeErrorWrapper(ctx, funcName) {
1097 | const origFn = ctx[funcName];
1098 | const preCheck = preChecks[funcName] || noop;
1099 | const postCheck = postChecks[funcName] || noop;
1100 | if (preCheck === noop && postChecks === noop) {
1101 | return;
1102 | }
1103 | ctx[funcName] = function(...args) {
1104 | preCheck(ctx, funcName, args);
1105 | const result = origFn.call(ctx, ...args);
1106 | postCheck(ctx, funcName, args, result);
1107 | return result;
1108 | };
1109 | const extraWrapperFn = extraWrappers[funcName];
1110 | if (extraWrapperFn) {
1111 | extraWrapperFn(ctx, funcName, origGLErrorFn);
1112 | }
1113 | }
1114 |
1115 | // Wrap each function
1116 | for (const propertyName in ctx) {
1117 | if (typeof ctx[propertyName] === 'function') {
1118 | origFuncs[propertyName] = ctx[propertyName];
1119 | makeErrorWrapper(ctx, propertyName);
1120 | }
1121 | }
1122 |
1123 | apis[nameOfClass.toLowerCase()] = { ctx, origFuncs };
1124 | }
1125 |
1126 | /*
1127 | The MIT License (MIT)
1128 |
1129 | Copyright (c) 2021 Gregg Tavares
1130 |
1131 | Permission is hereby granted, free of charge, to any person obtaining a copy of
1132 | this software and associated documentation files (the "Software"), to deal in
1133 | the Software without restriction, including without limitation the rights to
1134 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
1135 | the Software, and to permit persons to whom the Software is furnished to do so,
1136 | subject to the following conditions:
1137 |
1138 | The above copyright notice and this permission notice shall be included in all
1139 | copies or substantial portions of the Software.
1140 |
1141 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1142 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
1143 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
1144 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
1145 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1146 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1147 | */
1148 |
1149 | function wrapGetContext(Ctor) {
1150 | const oldFn = Ctor.prototype.getContext;
1151 | Ctor.prototype.getContext = function(type, ...args) {
1152 | const ctx = oldFn.call(this, type, ...args);
1153 | // Using bindTexture to see if it's WebGL. Could check for instanceof WebGLRenderingContext
1154 | // but that might fail if wrapped by debugging extension
1155 | if (ctx && ctx.bindTexture) {
1156 | const config = {};
1157 | augmentAPI(ctx, type, config);
1158 | ctx.getExtension('GMAN_webgl_memory');
1159 | }
1160 | return ctx;
1161 | };
1162 | }
1163 |
1164 | if (typeof HTMLCanvasElement !== 'undefined') {
1165 | wrapGetContext(HTMLCanvasElement);
1166 | }
1167 | if (typeof OffscreenCanvas !== 'undefined') {
1168 | wrapGetContext(OffscreenCanvas);
1169 | }
1170 |
1171 | }));
1172 |
--------------------------------------------------------------------------------
/webgl-memory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/greggman/webgl-memory/ce1db62ed4b639591a20952e52b86ff1d1f6f9fa/webgl-memory.png
--------------------------------------------------------------------------------