Skip to content

Commit 9cacf51

Browse files
committed
Fix Context Lost Support
1 parent 16b5149 commit 9cacf51

File tree

3 files changed

+217
-42
lines changed

3 files changed

+217
-42
lines changed

src/augment-api.js

Lines changed: 137 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ import {
6060
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
6161
*/
6262

63+
64+
const augmentedSet = new Set();
65+
6366
/**
6467
* Given a WebGL context replaces all the functions with wrapped functions
6568
* that call gl.getError after every command
@@ -68,11 +71,11 @@ import {
6871
* @param {string} nameOfClass (eg, webgl, webgl2, OES_texture_float)
6972
*/
7073
export function augmentAPI(ctx, nameOfClass, options = {}) {
71-
// Only augment this object once
72-
if (ctx.__webgl_memory_augmented) {
74+
75+
if (augmentedSet.has(ctx)) {
7376
return ctx;
7477
}
75-
ctx.__webgl_memory_augmented = true;
78+
augmentedSet.add(ctx);
7679

7780
const origGLErrorFn = options.origGLErrorFn || ctx.getError;
7881

@@ -81,20 +84,23 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
8184
const sharedState = {
8285
baseContext: ctx,
8386
config: options,
84-
customExtensions: {
87+
apis: {
88+
// custom extension
8589
gman_webgl_memory: {
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-
};
90+
ctx: {
91+
getMemoryInfo() {
92+
const drawingbuffer = computeDrawingbufferSize(ctx, drawingBufferInfo);
93+
return {
94+
memory: {
95+
...memory,
96+
drawingbuffer,
97+
total: drawingbuffer + memory.buffer + memory.texture + memory.renderbuffer,
98+
},
99+
resources: {
100+
...resources,
101+
}
102+
};
103+
},
98104
},
99105
},
100106
},
@@ -109,6 +115,12 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
109115
webglObjectToMemory: new Map(),
110116
};
111117

118+
const unRestorableAPIs = new Set([
119+
'webgl',
120+
'webgl2',
121+
'webgl_lose_context',
122+
]);
123+
112124
function resetSharedState() {
113125
sharedState.bindings.clear();
114126
sharedState.webglObjectToMemory.clear();
@@ -121,8 +133,46 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
121133
});
122134
}
123135

136+
function handleContextLost() {
137+
// Issues:
138+
// * all resources are lost.
139+
// Solution: handled by resetSharedState
140+
// * all functions are no-op
141+
// Solutions:
142+
// * swap all functions for noop
143+
// (not so easy because some functions return values)
144+
// * wrap all functions is a isContextLost check forwarder
145+
// (slow? and same as above)
146+
// * have each function manually check for context lost
147+
// (simple but repetitive)
148+
// * all extensions are lost
149+
// Solution: For these we go through and restore all the functions
150+
// on each extension
151+
resetSharedState();
152+
sharedState.isContextLost = true;
153+
154+
// restore all original functions for extensions since
155+
// user will have to get new extensions.
156+
for (const [name, {ctx, origFuncs}] of [...Object.entries(sharedState.apis)]) {
157+
if (!unRestorableAPIs.has(name) && origFuncs) {
158+
augmentedSet.delete(ctx);
159+
for (const [funcName, origFn] of Object.entries(origFuncs)) {
160+
ctx[funcName] = origFn
161+
}
162+
delete apis[name];
163+
}
164+
}
165+
}
166+
167+
function handleContextRestored() {
168+
sharedState.isContextLost = false;
169+
}
170+
124171
if (ctx.canvas) {
125-
ctx.canvas.addEventListener('webglcontextlost', resetSharedState);
172+
ctx.canvas.addEventListener('webglcontextlost', handleContextLost);
173+
ctx.canvas.addEventListener('contextlost', handleContextLost);
174+
ctx.canvas.addEventListener('webglcontextrestored', handleContextRestored);
175+
ctx.canvas.addEventListener('contextrestored', handleContextRestored);
126176
}
127177

128178
resetSharedState();
@@ -133,13 +183,17 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
133183
options.sharedState = sharedState;
134184

135185
const {
186+
apis,
187+
baseContext,
136188
bindings,
189+
config,
137190
memory,
138191
resources,
139192
webglObjectToMemory,
140-
customExtensions,
141193
} = sharedState;
142194

195+
const origFuncs = {};
196+
143197
function noop() {
144198
}
145199

@@ -150,6 +204,9 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
150204
}
151205
resources[typeName] = 0;
152206
return function(ctx, funcName, args, webglObj) {
207+
if (sharedState.isContextLost) {
208+
return null;
209+
}
153210
++resources[typeName];
154211
webglObjectToMemory.set(webglObj, {
155212
size: 0,
@@ -163,6 +220,9 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
163220
return;
164221
}
165222
return function(ctx, funcName, args) {
223+
if (sharedState.isContextLost) {
224+
return;
225+
}
166226
const [obj] = args;
167227
const info = webglObjectToMemory.get(obj);
168228
if (info) {
@@ -175,6 +235,9 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
175235
}
176236

177237
function updateRenderbuffer(target, samples, internalFormat, width, height) {
238+
if (sharedState.isContextLost) {
239+
return;
240+
}
178241
const obj = bindings.get(target);
179242
if (!obj) {
180243
throw new Error(`no renderbuffer bound to ${target}`);
@@ -275,11 +338,17 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
275338
}
276339

277340
function handleBindVertexArray(gl, funcName, args) {
341+
if (sharedState.isContextLost) {
342+
return;
343+
}
278344
const [va] = args;
279345
sharedState.currentVertexArray = va ? va : sharedState.defaultVertexArray;
280346
}
281347

282348
function handleBufferBinding(target, obj) {
349+
if (sharedState.isContextLost) {
350+
return;
351+
}
283352
switch (target) {
284353
case ELEMENT_ARRAY_BUFFER:
285354
const info = webglObjectToMemory.get(sharedState.currentVertexArray);
@@ -300,6 +369,9 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
300369
// void bufferData(GLenum target, [AllowShared] ArrayBufferView srcData, GLenum usage, GLuint srcOffset,
301370
// optional GLuint length = 0);
302371
bufferData(gl, funcName, args) {
372+
if (sharedState.isContextLost) {
373+
return;
374+
}
303375
const [target, src, /* usage */, srcOffset = 0, length = undefined] = args;
304376
let obj;
305377
switch (target) {
@@ -356,17 +428,26 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
356428
},
357429

358430
bindRenderbuffer(gl, funcName, args) {
431+
if (sharedState.isContextLost) {
432+
return;
433+
}
359434
const [target, obj] = args;
360435
bindings.set(target, obj);
361436
},
362437

363438
bindTexture(gl, funcName, args) {
439+
if (sharedState.isContextLost) {
440+
return;
441+
}
364442
const [target, obj] = args;
365443
bindings.set(target, obj);
366444
},
367445

368446
// void gl.copyTexImage2D(target, level, internalformat, x, y, width, height, border);
369447
copyTexImage2D(ctx, funcName, args) {
448+
if (sharedState.isContextLost) {
449+
return;
450+
}
370451
const [target, level, internalFormat, x, y, width, height, border] = args;
371452
const info = getTextureInfo(target);
372453
updateMipLevel(info, target, level, internalFormat, width, height, 1, UNSIGNED_BYTE);
@@ -393,6 +474,9 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
393474
// void gl.compressedTexImage2D(target, level, internalformat, width, height, border,
394475
// ArrayBufferView srcData, optional srcOffset, optional srcLengthOverride);
395476
compressedTexImage2D(ctx, funcName, args) {
477+
if (sharedState.isContextLost) {
478+
return;
479+
}
396480
const [target, level, internalFormat, width, height] = args;
397481
const info = getTextureInfo(target);
398482
updateMipLevel(info, target, level, internalFormat, width, height, 1, UNSIGNED_BYTE);
@@ -403,6 +487,9 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
403487
// void gl.compressedTexImage3D(target, level, internalformat, width, height, depth, border,
404488
// ArrayBufferView srcData, optional srcOffset, optional srcLengthOverride);
405489
compressedTexImage3D(ctx, funcName, args) {
490+
if (sharedState.isContextLost) {
491+
return;
492+
}
406493
const [target, level, internalFormat, width, height, depth] = args;
407494
const info = getTextureInfo(target);
408495
updateMipLevel(info, target, level, internalFormat, width, height, depth, UNSIGNED_BYTE);
@@ -428,6 +515,9 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
428515
deleteVertexArrayOES: makeDeleteWrapper('vertexArray', noop, 'deleteVertexArrayOES'),
429516

430517
fenceSync: function(ctx) {
518+
if (sharedState.isContextLost) {
519+
return;
520+
}
431521
if (!ctx.fenceSync) {
432522
return;
433523
}
@@ -442,6 +532,9 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
442532
}(ctx),
443533

444534
generateMipmap(ctx, funcName, args) {
535+
if (sharedState.isContextLost) {
536+
return;
537+
}
445538
const [target] = args;
446539
const info = getTextureInfo(target);
447540
const baseMipNdx = info.parameters ? info.parameters.get(TEXTURE_BASE_LEVEL) || 0 : 0;
@@ -463,6 +556,9 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
463556
},
464557

465558
getSupportedExtensions(ctx, funcName, args, result) {
559+
if (sharedState.isContextLost) {
560+
return;
561+
}
466562
result.push('GMAN_webgl_memory');
467563
},
468564

@@ -485,6 +581,9 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
485581
},
486582

487583
texImage2D(ctx, funcName, args) {
584+
if (sharedState.isContextLost) {
585+
return;
586+
}
488587
// WebGL1:
489588
// void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView? pixels);
490589
// void gl.texImage2D(target, level, internalformat, format, type, ImageData? pixels);
@@ -531,12 +630,18 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
531630
// void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ArrayBufferView srcData, srcOffset);
532631

533632
texImage3D(ctx, funcName, args) {
633+
if (sharedState.isContextLost) {
634+
return;
635+
}
534636
let [target, level, internalFormat, width, height, depth, border, format, type] = args;
535637
const info = getTextureInfo(target);
536638
updateMipLevel(info, target, level, internalFormat, width, height, depth, type);
537639
},
538640

539641
texParameteri(ctx, funcName, args) {
642+
if (sharedState.isContextLost) {
643+
return;
644+
}
540645
let [target, pname, value] = args;
541646
const info = getTextureInfo(target);
542647
info.parameters = info.parameters || new Map();
@@ -558,17 +663,21 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
558663

559664
const extraWrappers = {
560665
getExtension(ctx, propertyName) {
666+
if (sharedState.isContextLost) {
667+
return null;
668+
}
561669
const origFn = ctx[propertyName];
562670
ctx[propertyName] = function(...args) {
563671
const extensionName = args[0].toLowerCase();
564-
let ext = customExtensions[extensionName];
565-
if (!ext) {
566-
ext = origFn.call(ctx, ...args);
567-
if (ext) {
568-
augmentAPI(ext, extensionName, { ...options, origGLErrorFn });
569-
}
672+
const api = apis[extensionName];
673+
if (api) {
674+
return api.ctx;
570675
}
571-
return ext || null;
676+
const ext = origFn.call(ctx, ...args);
677+
if (ext) {
678+
augmentAPI(ext, extensionName, {...options, origGLErrorFn});
679+
}
680+
return ext;
572681
};
573682
},
574683
};
@@ -596,8 +705,11 @@ export function augmentAPI(ctx, nameOfClass, options = {}) {
596705
// Wrap each function
597706
for (const propertyName in ctx) {
598707
if (typeof ctx[propertyName] === 'function') {
708+
origFuncs[propertyName] = ctx[propertyName];
599709
makeErrorWrapper(ctx, propertyName);
600710
}
601711
}
712+
713+
apis[nameOfClass.toLowerCase()] = { ctx, origFuncs };
602714
}
603715

0 commit comments

Comments
 (0)