Skip to content

Commit 275b3ac

Browse files
authored
Merge pull request #1476 from OpenGeoscience/webrender-setup-call
fix contextrestored webgl renderer call
2 parents d3d8660 + a301b84 commit 275b3ac

10 files changed

Lines changed: 250 additions & 20 deletions

File tree

src/vgl/mapper.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@ vgl.mapper = function (arg) {
3838
*/
3939
this.deleteVertexBufferObjects = function (renderState) {
4040
var i;
41+
if (renderState && renderState.m_contextChanged) {
42+
// The old context is already gone, so deleting its buffers is invalid.
43+
return;
44+
}
4145
var context = m_context;
42-
if (renderState) {
46+
if (renderState && !m_context) {
4347
context = renderState.m_context;
4448
}
4549
if (context) {

src/vgl/renderWindow.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,20 +126,30 @@ vgl.renderWindow = function (canvas) {
126126
* Create the window.
127127
*
128128
* @param {vgl.renderState} renderState
129+
* @param {boolean} forceContextChanged
130+
* If true, force a context change even if the context is the same as the old context.
129131
* @returns {boolean}
130132
*/
131-
this._setup = function (renderState) {
133+
this._setup = function (renderState, forceContextChanged) {
134+
var oldContext = m_context;
132135
m_context = null;
133136

134137
try {
135138
// Try to grab the standard context. If it fails, fallback to
136139
// experimental.
137140
m_context = m_canvas.getContext('webgl') ||
138141
m_canvas.getContext('experimental-webgl');
142+
var didContextChange = !!oldContext && oldContext !== m_context;
143+
if (forceContextChanged) {
144+
didContextChange = true;
145+
}
139146

140147
// Set width and height of renderers if not set already
141148
var i;
142149
for (i = 0; i < m_renderers.length; i += 1) {
150+
if (didContextChange && m_renderers[i]._contextChanged) {
151+
m_renderers[i]._contextChanged();
152+
}
143153
if ((m_renderers[i].width() > m_width) ||
144154
m_renderers[i].width() === 0 ||
145155
(m_renderers[i].height() > m_height) ||

src/vgl/renderer.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ vgl.renderer = function (arg) {
128128
return m_this.m_camera;
129129
};
130130

131+
/**
132+
* Mark that the GL context changed and cached GL resources need to be
133+
* recreated on the next render pass.
134+
*/
135+
this._contextChanged = function () {
136+
m_this.m_contextChanged = true;
137+
m_this.modified();
138+
};
139+
131140
/**
132141
* Render the scene.
133142
*/

src/vgl/shader.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,14 @@ vgl.shader = function (type) {
9595
*/
9696
this.compile = function (renderState) {
9797
var entry = this._getContextEntry(renderState);
98-
if (this.getMTime() < entry.compileTimestamp.getMTime()) {
98+
if (!renderState.m_contextChanged &&
99+
this.getMTime() < entry.compileTimestamp.getMTime()) {
99100
return entry.shaderHandle;
100101
}
101102

102-
renderState.m_context.deleteShader(entry.shaderHandle);
103+
if (!renderState.m_contextChanged) {
104+
renderState.m_context.deleteShader(entry.shaderHandle);
105+
}
103106
entry.shaderHandle = renderState.m_context.createShader(m_shaderType);
104107
renderState.m_context.shaderSource(entry.shaderHandle, m_shaderSource);
105108
renderState.m_context.compileShader(entry.shaderHandle);

src/vgl/shaderProgram.js

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ vgl.shaderProgram = function () {
2020

2121
var m_this = this,
2222
m_programHandle = 0,
23+
m_programContext = null,
2324
m_compileTimestamp = timestamp(),
2425
m_bindTimestamp = timestamp(),
2526
m_shaders = [],
@@ -28,6 +29,22 @@ vgl.shaderProgram = function () {
2829
m_uniformNameToLocation = {},
2930
m_vertexAttributeNameToLocation = {};
3031

32+
function hasContextChanged(renderState) {
33+
return m_programContext !== renderState.m_context ||
34+
renderState.m_contextChanged;
35+
}
36+
37+
function clearLocationCaches() {
38+
m_uniformNameToLocation = {};
39+
m_vertexAttributeNameToLocation = {};
40+
}
41+
42+
function resetProgramState() {
43+
m_programHandle = 0;
44+
m_programContext = null;
45+
clearLocationCaches();
46+
}
47+
3148
/**
3249
* Query uniform location in the program.
3350
*
@@ -184,7 +201,7 @@ vgl.shaderProgram = function () {
184201
* @param {vgl.renderState} renderState
185202
*/
186203
this.use = function (renderState) {
187-
renderState.m_context.useProgram(m_programHandle);
204+
renderState.m_context.useProgram(m_programHandle || null);
188205
};
189206

190207
/**
@@ -193,8 +210,13 @@ vgl.shaderProgram = function () {
193210
* @param {vgl.renderState} renderState
194211
*/
195212
this._setup = function (renderState) {
213+
if (m_programHandle && hasContextChanged(renderState)) {
214+
// A restored/replaced context invalidates all cached program state.
215+
resetProgramState();
216+
}
196217
if (m_programHandle === 0) {
197218
m_programHandle = renderState.m_context.createProgram();
219+
m_programContext = renderState.m_context;
198220
}
199221
};
200222

@@ -215,10 +237,11 @@ vgl.shaderProgram = function () {
215237
* @param {vgl.renderState} renderState
216238
*/
217239
this.deleteProgram = function (renderState) {
218-
if (m_programHandle) {
240+
if (m_programHandle && renderState &&
241+
m_programContext === renderState.m_context) {
219242
renderState.m_context.deleteProgram(m_programHandle);
220243
}
221-
m_programHandle = 0;
244+
resetProgramState();
222245
};
223246

224247
/**
@@ -229,8 +252,14 @@ vgl.shaderProgram = function () {
229252
this.deleteVertexAndFragment = function (renderState) {
230253
var i;
231254
for (i = 0; i < m_shaders.length; i += 1) {
255+
if (renderState && renderState.m_contextChanged) {
256+
// After context loss there is nothing to detach/delete in GL.
257+
m_shaders[i].removeContext(renderState);
258+
continue;
259+
}
232260
if (m_shaders[i].shaderHandle(renderState)) {
233-
renderState.m_context.detachShader(m_programHandle, m_shaders[i].shaderHandle(renderState));
261+
renderState.m_context.detachShader(
262+
m_programHandle, m_shaders[i].shaderHandle(renderState));
234263
}
235264
renderState.m_context.deleteShader(m_shaders[i].shaderHandle(renderState));
236265
m_shaders[i].removeContext(renderState);
@@ -241,15 +270,20 @@ vgl.shaderProgram = function () {
241270
* Compile and link a shader.
242271
*
243272
* @param {vgl.renderState} renderState
273+
* @returns {boolean} True if the program was compiled and linked successfully, false otherwise.
244274
*/
245275
this.compileAndLink = function (renderState) {
246276
var i;
277+
// Rebuild if timestamps are stale or we switched GL contexts.
278+
var contextChanged = hasContextChanged(renderState) || !m_programHandle;
279+
280+
m_this._setup(renderState);
247281

248-
if (m_compileTimestamp.getMTime() >= this.getMTime()) {
249-
return;
282+
if (!contextChanged && m_compileTimestamp.getMTime() >= this.getMTime()) {
283+
return !!m_programHandle;
250284
}
251285

252-
m_this._setup(renderState);
286+
clearLocationCaches();
253287

254288
// Compile shaders
255289
for (i = 0; i < m_shaders.length; i += 1) {
@@ -263,9 +297,11 @@ vgl.shaderProgram = function () {
263297
if (!m_this.link(renderState)) {
264298
console.log('[ERROR] Failed to link Program'); // eslint-disable-line no-console
265299
m_this._cleanup(renderState);
300+
return false;
266301
}
267302

268303
m_compileTimestamp.modified();
304+
return true;
269305
};
270306

271307
/**
@@ -275,16 +311,24 @@ vgl.shaderProgram = function () {
275311
*/
276312
this.bind = function (renderState) {
277313
var i = 0;
314+
var needBind = renderState.m_contextChanged ||
315+
m_bindTimestamp.getMTime() < m_this.getMTime() ||
316+
m_programContext !== renderState.m_context;
278317

279-
if (m_bindTimestamp.getMTime() < m_this.getMTime()) {
318+
if (needBind) {
280319

281320
// Compile shaders
282-
m_this.compileAndLink(renderState);
321+
if (!m_this.compileAndLink(renderState)) {
322+
return;
323+
}
283324

284325
m_this.use(renderState);
285326
m_this.bindUniforms(renderState);
286327
m_bindTimestamp.modified();
287328
} else {
329+
if (!m_programHandle) {
330+
return;
331+
}
288332
m_this.use(renderState);
289333
}
290334

src/vgl/texture.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ vgl.texture = function () {
3333
this.m_texture = null;
3434

3535
var m_setupTimestamp = timestamp(),
36+
m_setupContext = null,
3637
m_that = this;
3738

3839
function activateTextureUnit(renderState) {
@@ -52,8 +53,11 @@ vgl.texture = function () {
5253
// Activate the texture unit first
5354
activateTextureUnit(renderState);
5455

55-
renderState.m_context.deleteTexture(this.m_textureHandle);
56+
if (this.m_textureHandle && m_setupContext === renderState.m_context) {
57+
renderState.m_context.deleteTexture(this.m_textureHandle);
58+
}
5659
this.m_textureHandle = renderState.m_context.createTexture();
60+
m_setupContext = renderState.m_context;
5761
renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, this.m_textureHandle);
5862
renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
5963
vgl.GL.TEXTURE_MIN_FILTER,
@@ -101,7 +105,9 @@ vgl.texture = function () {
101105
*/
102106
this.bind = function (renderState) {
103107
// TODO Call setup via material setup
104-
if (this.getMTime() > m_setupTimestamp.getMTime()) {
108+
if (renderState.m_contextChanged ||
109+
m_setupContext !== renderState.m_context ||
110+
this.getMTime() > m_setupTimestamp.getMTime()) {
105111
this.setup(renderState);
106112
}
107113

@@ -289,6 +295,16 @@ vgl.texture = function () {
289295
return this.m_textureHandle;
290296
};
291297

298+
/**
299+
* Return true if the texture handle belongs to the specified context.
300+
*
301+
* @param {WebGLRenderingContext} context A candidate GL context.
302+
* @returns {boolean} True if the texture has been setup for the context.
303+
*/
304+
this.hasContext = function (context) {
305+
return !!this.m_textureHandle && m_setupContext === context;
306+
};
307+
292308
return this;
293309
};
294310

src/webgl/quadFeature.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,12 @@ var webgl_quadFeature = function (arg) {
5555
newbuf = false;
5656

5757
if (m_quads.imgQuads.length) {
58+
if (renderState.m_contextChanged) {
59+
m_glBuffers.imgQuadsPosition = null;
60+
}
5861
if (!m_imgposbuf || m_imgposbuf.length < m_quads.imgQuads.length * 12 ||
5962
!m_glBuffers.imgQuadsPosition) {
60-
if (m_glBuffers.imgQuadsPosition) {
63+
if (m_glBuffers.imgQuadsPosition && !renderState.m_contextChanged) {
6164
context.deleteBuffer(m_glBuffers.imgQuadsPosition);
6265
}
6366
m_glBuffers.imgQuadsPosition = context.createBuffer();
@@ -93,9 +96,12 @@ var webgl_quadFeature = function (arg) {
9396
newbuf = false;
9497

9598
if (m_quads.clrQuads.length) {
99+
if (renderState.m_contextChanged) {
100+
m_glBuffers.clrQuadsPosition = null;
101+
}
96102
if (!m_clrposbuf || m_clrposbuf.length < m_quads.clrQuads.length * 12 ||
97103
!m_glBuffers.clrQuadsPosition) {
98-
if (m_glBuffers.clrQuadsPosition) {
104+
if (m_glBuffers.clrQuadsPosition && !renderState.m_contextChanged) {
99105
context.deleteBuffer(m_glBuffers.clrQuadsPosition);
100106
}
101107
m_glBuffers.clrQuadsPosition = context.createBuffer();
@@ -393,7 +399,10 @@ var webgl_quadFeature = function (arg) {
393399
nearestPixel = curZoom >= nearestPixel;
394400
}
395401
m_quads.imgQuads.forEach((quad) => {
396-
if ((quad.image || quad.imageTexture) && quad.texture && quad.texture.nearestPixel() !== nearestPixel && quad.texture.textureHandle()) {
402+
if ((quad.image || quad.imageTexture) && quad.texture &&
403+
quad.texture.nearestPixel() !== nearestPixel &&
404+
quad.texture.textureHandle() &&
405+
quad.texture.hasContext(renderState.m_context)) {
397406
/* This could just be
398407
* quad.texture.setNearestPixel(nearestPixel);
399408
* but that needlessly redecodes the image. Instead, just change the

src/webgl/webglRenderer.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ var webglRenderer = function (arg) {
3434
m_contextRenderer = null,
3535
m_viewer = null,
3636
m_lastZoom,
37+
m_contextLost = false,
3738
m_updateCamera = false,
3839
s_init = this._init,
3940
s_exit = this._exit;
@@ -92,8 +93,22 @@ var webglRenderer = function (arg) {
9293
m_viewer.init();
9394
m_contextRenderer = m_viewer.renderWindow().activeRenderer();
9495
m_contextRenderer.setResetScene(false);
95-
canvas.get(0).addEventListener('webglcontextlost', (evt) => evt.preventDefault(), false);
96-
canvas.get(0).addEventListener('webglcontextrestored', () => m_viewer.renderWindow()._init(), false);
96+
canvas.get(0).addEventListener('webglcontextlost', function (evt) {
97+
m_contextLost = true;
98+
evt.preventDefault();
99+
}, false);
100+
canvas.get(0).addEventListener('webglcontextrestored', function () {
101+
if (!m_viewer) {
102+
return;
103+
}
104+
m_contextLost = false;
105+
// Reinitialize GL objects, then force a camera sync before redraw.
106+
// While the context is lost and restored it could be the same context,
107+
// so we need to force a context change for gpu resources to be re-created.
108+
m_viewer.renderWindow()._setup(undefined, true);
109+
m_updateCamera = true;
110+
m_this._render();
111+
}, false);
97112

98113
if (m_viewer.renderWindow().renderers().length > 0) {
99114
m_contextRenderer.setLayer(m_viewer.renderWindow().renderers().length);
@@ -154,6 +169,9 @@ var webglRenderer = function (arg) {
154169
*/
155170
this._renderFrame = function () {
156171
if (m_viewer) {
172+
if (m_contextLost) {
173+
return;
174+
}
157175
if (m_updateCamera) {
158176
m_updateCamera = false;
159177
m_this._updateRendererCamera();

tests/gl-cases/osmLayer.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,26 @@ describe('osmLayer', function () {
2222
imageTest.imageTest('osmLayerVgl', null, 0.0015, done, myMap.onIdle, 0, 2);
2323
});
2424

25+
it('lose context and recover', function (done) {
26+
myMap = common.createOsmMap({center: {x: -78, y: 21}}, {
27+
renderer: 'webgl',
28+
attribution: '&copy; <a href="http://some-unvisited-domain.org">OpenStreetMap</a> contributors'
29+
});
30+
myMap.draw();
31+
myMap.center({x: 60, y: 20}).zoom(3);
32+
const gl = myMap.node().find('canvas')[0].getContext('webgl');
33+
const ext = gl.getExtension('WEBGL_lose_context');
34+
// we must wait until the context is available and then until it is lost
35+
window.setTimeout(() => {
36+
ext.loseContext();
37+
window.setTimeout(() => {
38+
ext.restoreContext();
39+
myMap.center({x: 0, y: 0}).zoom(2.5);
40+
imageTest.imageTest('osmLayerVgl', null, 0.0015, done, myMap.onIdle, 0, 2);
41+
}, 10);
42+
}, 10);
43+
});
44+
2545
it('canvas renderer', function (done) {
2646
myMap = common.createOsmMap({}, {
2747
renderer: 'canvas',

0 commit comments

Comments
 (0)