Skip to content

Commit 16b5149

Browse files
lojjicgreggman
authored andcommitted
Add handling for webgl context loss
- Tracking state is reset upon context loss - Extensions objects are no longer cached because new ones will be returned after context loss/restore - Ensured each context/extension is only augmented once - Removed unused origFuncs and an unused local const
1 parent 79c0f0d commit 16b5149

File tree

3 files changed

+94
-37
lines changed

3 files changed

+94
-37
lines changed

src/augment-api.js

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -68,30 +68,33 @@ import {
6868
* @param {string} nameOfClass (eg, webgl, webgl2, OES_texture_float)
6969
*/
7070
export function augmentAPI(ctx, nameOfClass, options = {}) {
71+
// Only augment this object once
72+
if (ctx.__webgl_memory_augmented) {
73+
return ctx;
74+
}
75+
ctx.__webgl_memory_augmented = true;
76+
7177
const origGLErrorFn = options.origGLErrorFn || ctx.getError;
7278

7379
function createSharedState(ctx) {
7480
const drawingBufferInfo = getDrawingbufferInfo(ctx);
7581
const sharedState = {
7682
baseContext: ctx,
7783
config: options,
78-
apis: {
79-
// custom extension
84+
customExtensions: {
8085
gman_webgl_memory: {
81-
ctx: {
82-
getMemoryInfo() {
83-
const drawingbuffer = computeDrawingbufferSize(ctx, drawingBufferInfo);
84-
return {
85-
memory: {
86-
...memory,
87-
drawingbuffer,
88-
total: drawingbuffer + memory.buffer + memory.texture + memory.renderbuffer,
89-
},
90-
resources: {
91-
...resources,
92-
}
93-
};
94-
},
86+
getMemoryInfo() {
87+
const drawingbuffer = computeDrawingbufferSize(ctx, drawingBufferInfo);
88+
return {
89+
memory: {
90+
...memory,
91+
drawingbuffer,
92+
total: drawingbuffer + memory.buffer + memory.texture + memory.renderbuffer,
93+
},
94+
resources: {
95+
...resources,
96+
},
97+
};
9598
},
9699
},
97100
},
@@ -105,26 +108,38 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
105108
defaultVertexArray: {},
106109
webglObjectToMemory: new Map(),
107110
};
108-
sharedState.webglObjectToMemory.set(sharedState.defaultVertexArray, {});
109-
sharedState.currentVertexArray = sharedState.defaultVertexArray;
111+
112+
function resetSharedState() {
113+
sharedState.bindings.clear();
114+
sharedState.webglObjectToMemory.clear();
115+
sharedState.webglObjectToMemory.set(sharedState.defaultVertexArray, {});
116+
sharedState.currentVertexArray = sharedState.defaultVertexArray;
117+
[sharedState.resources, sharedState.memory].forEach(function(obj) {
118+
for (let prop in obj) {
119+
obj[prop] = 0;
120+
}
121+
});
122+
}
123+
124+
if (ctx.canvas) {
125+
ctx.canvas.addEventListener('webglcontextlost', resetSharedState);
126+
}
127+
128+
resetSharedState();
110129
return sharedState;
111130
}
112131

113132
const sharedState = options.sharedState || createSharedState(ctx);
114133
options.sharedState = sharedState;
115134

116135
const {
117-
apis,
118-
baseContext,
119136
bindings,
120-
config,
121137
memory,
122138
resources,
123139
webglObjectToMemory,
140+
customExtensions,
124141
} = sharedState;
125142

126-
const origFuncs = {};
127-
128143
function noop() {
129144
}
130145

@@ -230,14 +245,14 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
230245
info.mips[level] = info.mips[level] || [];
231246
const mipFaceInfo = info.mips[level][faceNdx] || {}
232247
info.size -= mipFaceInfo.size || 0;
233-
248+
234249
mipFaceInfo.size = newMipSize;
235250
mipFaceInfo.internalFormat = internalFormat;
236251
mipFaceInfo.type = type;
237252
mipFaceInfo.width = width;
238253
mipFaceInfo.height = height;
239254
mipFaceInfo.depth = depth;
240-
255+
241256
info.mips[level][faceNdx] = mipFaceInfo;
242257
info.size += newMipSize;
243258

@@ -546,15 +561,14 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
546561
const origFn = ctx[propertyName];
547562
ctx[propertyName] = function(...args) {
548563
const extensionName = args[0].toLowerCase();
549-
const api = apis[extensionName];
550-
if (api) {
551-
return api.ctx;
552-
}
553-
const ext = origFn.call(ctx, ...args);
554-
if (ext) {
555-
augmentAPI(ext, extensionName, {...options, origGLErrorFn});
564+
let ext = customExtensions[extensionName];
565+
if (!ext) {
566+
ext = origFn.call(ctx, ...args);
567+
if (ext) {
568+
augmentAPI(ext, extensionName, { ...options, origGLErrorFn });
569+
}
556570
}
557-
return ext;
571+
return ext || null;
558572
};
559573
},
560574
};
@@ -570,7 +584,6 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
570584
ctx[funcName] = function(...args) {
571585
preCheck(ctx, funcName, args);
572586
const result = origFn.call(ctx, ...args);
573-
const gl = baseContext;
574587
postCheck(ctx, funcName, args, result);
575588
return result;
576589
};
@@ -583,11 +596,8 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
583596
// Wrap each function
584597
for (const propertyName in ctx) {
585598
if (typeof ctx[propertyName] === 'function') {
586-
origFuncs[propertyName] = ctx[propertyName];
587599
makeErrorWrapper(ctx, propertyName);
588600
}
589601
}
590-
591-
apis[nameOfClass.toLowerCase()] = { ctx, origFuncs };
592602
}
593603

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/* global window */
44

55
import './tests/buffer-tests.js';
6+
import './tests/contextloss-tests.js';
67
import './tests/info-tests.js';
78
import './tests/program-tests.js';
89
import './tests/renderbuffer-tests.js';

test/tests/contextloss-tests.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, it } from '../mocha-support.js';
2+
import { createContext } from '../webgl.js';
3+
import { MemInfoTracker } from './test-utils.js';
4+
5+
describe('webgl context loss tests', () => {
6+
it('test context loss', async () => {
7+
const { gl } = createContext();
8+
// To enable context restoration we must preventDefault on the context loss event:
9+
gl.canvas.addEventListener('webglcontextlost', e => e.preventDefault());
10+
const ext = gl.getExtension('WEBGL_lose_context');
11+
const tracker = new MemInfoTracker(gl, 'texture');
12+
13+
// Add a texture
14+
const tex1 = gl.createTexture();
15+
tracker.addObjects(1);
16+
gl.bindTexture(gl.TEXTURE_2D, tex1);
17+
const texSize = 32 * 16 * 4;
18+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 32, 16, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
19+
tracker.addMemory(texSize);
20+
21+
// Force context loss and wait for event loop to complete
22+
ext.loseContext();
23+
await delay(1);
24+
25+
// Verify memory tracking was zeroed out
26+
tracker.deleteObjectAndMemory(texSize, 1);
27+
28+
// Force context restoration and wait for event loop to complete
29+
ext.restoreContext();
30+
await delay(1);
31+
32+
// Verify you can still add new things
33+
const tex2 = gl.createTexture();
34+
tracker.addObjects(1);
35+
gl.bindTexture(gl.TEXTURE_2D, tex2);
36+
const texSize2 = 64 * 24 * 4;
37+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 64, 24, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
38+
tracker.addMemory(texSize2);
39+
});
40+
});
41+
42+
async function delay(ms) {
43+
return new Promise(resolve => {
44+
setTimeout(resolve, ms);
45+
});
46+
}

0 commit comments

Comments
 (0)