Skip to content

Commit

Permalink
add getResourcesInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
greggman committed Aug 3, 2024
1 parent c769105 commit b0ebeb8
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 17 deletions.
107 changes: 103 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ Then in your code
const ext = gl.getExtension('GMAN_webgl_memory');
...
if (ext) {
// memory info
const info = ext.getMemoryInfo();
// every texture, it's size, a stack of where it was created and a stack of where it was last updated.
const textures = ext.getResourcesInfo(WebGLTexture);
// every buffer, it's size, a stack of where it was created and a stack of where it was last updated.
const buffers = ext.getResourcesInfo(WebGLBuffer);
}
```

Expand Down Expand Up @@ -60,6 +65,35 @@ The info returned is
}
```

The data for textures and buffers

```js
const ext = gl.getExtension('GMAN_webgl_memory');
...
if (ext) {
const tex = gl.createTexture(); // 1
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 4, 1); // 2

const buf = gl.createBuffer(); // 3
gl.bindBuffer(gl.ARRAY_BUFFER);
gl.bufferData(gl.ARRAY_BUFFER, 32, gl.STATIC_DRAW); // 4


const textures = ext.getResourcesInfo(WebGLTexture);
const buffers = ext.getResourcesInfo(WebGLBuffer);
```
```js
textures = [
{ size: 16, stackCreated: '...1...', stackUpdated: '...2...' }
]

buffers = [
{ size: 32, stackCreated: '...3'''., stackUpdated: '...4...' }
]
```
## Caveats
1. You must have WebGL error free code.
Expand All @@ -85,8 +119,73 @@ The info returned is
the issue by watching your resources counts climb.
Given that it seemed okay to skip this for now.

3. `texImage2D/3D` vs `texStorage2D/3D`
3. Deletion by Garbage Collection (GC) is not supported
In JavaScript and WebGL, it's possible to let things get auto deleted by GC.
```js
{
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, 1024 * 1024 * 256, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
```
Given the code above, buffer will, at some point in the future, get automatically
deleted. The problem is you have no idea when. JavaScript does know now the size of
VRAM nor does it have any concept of the size of the WebGL buffer (256meg in this case).
All JavaScript has is a tiny object that holds an ID for the actual OpenGL buffer
and maybe a little metadata.
That means there's absolutely no pressure to delete the buffer above in a timely
manner nor either is there any way for JavaScript to know that releasing that
object would free up VRAM.
In other words. Let's say you had a total of 384meg of ram. You'd expect this to
work.
```js
{
const a = new Uint32Array(256 * 1024 * 1024)
}
{
const b = new Uint32Array(256 * 1024 * 1024)
}
```
The code above allocates 512meg. Given we were pretending the system only has 384meg,
JavaScript will likely free `a` to make room for `b`
Now, Let's do the WebGL case and assume 384meg of VRAM
```js
{
const a = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, 1024 * 1024 * 256, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
{
const b = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, 1024 * 1024 * 256, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
```
In this case, JavaScript only sees `a` as taking a few bytes (the object that tracks
the OpenGL resource) so it has no idea that it needs to free `a` to make room for `b`.
This could would fail, ideally with `gl.OUT_OF_MEMORY`.
That was the long way of saying, you should never count on GC for WebGL!
Free your resources explicitly!
That's also part of the reason why we don't support this case because
counting on GC is not a useful solution.
4. `texImage2D/3D` vs `texStorage2D/3D`
Be aware that `texImage2D/3D` *may* require double the memory of
`texStorage2D/3D`.
Expand All @@ -109,7 +208,7 @@ The info returned is
you can just upload the new image to the existing texture. With `texStorage`
you'd be required to create a new texture.
4. `ELEMENT_ARRAY_BUFFER`
5. `ELEMENT_ARRAY_BUFFER`
Buffers used with `ELEMENT_ARRAY_BUFFER` may need a second copy in ram.
This is because WebGL requires no out of bounds memory access (eg,
Expand Down Expand Up @@ -209,6 +308,6 @@ vs just some library you call like `webglMemoryTracker.init(someWebGLRenderingCo
I structured it this way just because I used [webgl-lint](https://greggman.github.io/webgl-lint) as
the basis to get this working.

## Licence
## License

[MIT](https://github.com/greggman/webgl-memory/blob/main/LICENCE.md)
7 changes: 5 additions & 2 deletions src/augment-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
ctx: {
getMemoryInfo() {
const drawingbuffer = computeDrawingbufferSize(ctx, drawingBufferInfo);
const textures = collectObjects(sharedState, 'WebGLTexture');
return {
memory: {
...memory,
Expand All @@ -99,9 +98,11 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
resources: {
...resources,
},
textures,
};
},
getResourcesInfo(type) {
return collectObjects(sharedState, type);
},
},
},
},
Expand Down Expand Up @@ -250,6 +251,7 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {

memory.renderbuffer -= info.size;
info.size = newSize;
info.stackUpdated = getStackTrace();
memory.renderbuffer += newSize;
}

Expand Down Expand Up @@ -407,6 +409,7 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {

memory.buffer -= info.size;
info.size = newSize;
info.stackUpdated = getStackTrace();
memory.buffer += newSize;
},

Expand Down
2 changes: 1 addition & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export function isNumber(v) {

export function collectObjects(state, type) {
const list = [...state.webglObjectToMemory.keys()]
.filter((obj) => obj.constructor.name === type)
.filter(obj => obj instanceof type)
.map((obj) => state.webglObjectToMemory.get(obj));

return list;
Expand Down
8 changes: 6 additions & 2 deletions test/tests/info-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('info tests', () => {
assertEqual(drawingbufferSize, canvasSize);

const info = ext.getMemoryInfo();
const {memory, resources, textures} = info;
const {memory, resources} = info;

assertEqual(memory.buffer, 0);
assertEqual(memory.texture, 0);
Expand All @@ -40,6 +40,8 @@ describe('info tests', () => {
assertEqual(resources.texture, 0);
assertEqual(resources.transformFeedback, undefined);
assertEqual(resources.vertexArray, undefined);

const textures = ext.getResourcesInfo(WebGLTexture);
assertEqual(textures.length, 0);
});

Expand All @@ -48,7 +50,7 @@ describe('info tests', () => {
assertTruthy(ext, 'got extension');

const info = ext.getMemoryInfo();
const {memory, resources, textures} = info;
const {memory, resources} = info;

assertEqual(memory.buffer, 0);
assertEqual(memory.texture, 0);
Expand All @@ -66,6 +68,8 @@ describe('info tests', () => {
assertEqual(resources.texture, 0);
assertEqual(resources.transformFeedback, 0);
assertEqual(resources.vertexArray, 0);

const textures = ext.getResourcesInfo(WebGLTexture);
assertEqual(textures.length, 0);
});

Expand Down
71 changes: 63 additions & 8 deletions test/tests/stack-tests.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,79 @@
import {describe, it} from '../mocha-support.js';
import {assertEqual, assertTruthy} from '../assert.js';
import {assertEqual, assertFalsy, assertTruthy} from '../assert.js';
import {createContext} from '../webgl.js';

describe('stack tests', () => {

it('test stack capture', () => {
it('test texture stack capture', () => {
const {gl, ext} = createContext();

const tex1 = gl.createTexture();

gl.bindTexture(gl.TEXTURE_2D, tex1);
gl.texImage2D(gl.TEXTURE_2D, 1, gl.RGBA, 16, 8, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);

const info = ext.getMemoryInfo();
const {textures} = info;

assertEqual(textures.length, 1);
assertTruthy(textures[0].stackCreated);
assertTruthy(textures[0].stackUpdated);
{
const textures = ext.getResourcesInfo(WebGLTexture);
assertEqual(textures.length, 1);
assertTruthy(textures[0].stackCreated);
assertTruthy(textures[0].stackUpdated);
}

gl.deleteTexture(tex1);

{
const textures = ext.getResourcesInfo(WebGLTexture);
assertEqual(textures.length, 0);
}
});

it('test buffers stack capture', () => {
const {gl, ext} = createContext();

const buf = gl.createBuffer();

{
const buffers = ext.getResourcesInfo(WebGLBuffer);
assertEqual(buffers.length, 1);
assertTruthy(buffers[0].stackCreated);
assertFalsy(buffers[0].stackUpdated);
}

gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, 16, gl.STATIC_DRAW);

{
const buffers = ext.getResourcesInfo(WebGLBuffer);
assertEqual(buffers.length, 1);
assertTruthy(buffers[0].stackCreated);
assertTruthy(buffers[0].stackUpdated);
}

gl.deleteBuffer(buf);

{
const buffers = ext.getResourcesInfo(WebGLBuffer);
assertEqual(buffers.length, 0);
}
});

it('test program stack capture', () => {
const {gl, ext} = createContext();

const program = gl.createProgram();

{
const programs = ext.getResourcesInfo(WebGLProgram);
assertEqual(programs.length, 1);
assertTruthy(programs[0].stackCreated);
}

gl.deleteProgram(program);

{
const programs = ext.getResourcesInfo(WebGLProgram);
assertEqual(programs.length, 0);
}
});

});

0 comments on commit b0ebeb8

Please sign in to comment.