Skip to content

Commit 7016bc9

Browse files
committed
fix(webgl): context dirty management and restoration
1 parent bf54180 commit 7016bc9

6 files changed

Lines changed: 85 additions & 13 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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,22 @@ vgl.renderWindow = function (canvas) {
129129
* @returns {boolean}
130130
*/
131131
this._setup = function (renderState) {
132+
var oldContext = m_context;
132133
m_context = null;
133134

134135
try {
135136
// Try to grab the standard context. If it fails, fallback to
136137
// experimental.
137138
m_context = m_canvas.getContext('webgl') ||
138139
m_canvas.getContext('experimental-webgl');
140+
var didContextChange = !!oldContext && oldContext !== m_context;
139141

140142
// Set width and height of renderers if not set already
141143
var i;
142144
for (i = 0; i < m_renderers.length; i += 1) {
145+
if (didContextChange && m_renderers[i]._contextChanged) {
146+
m_renderers[i]._contextChanged();
147+
}
143148
if ((m_renderers[i].width() > m_width) ||
144149
m_renderers[i].width() === 0 ||
145150
(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: 52 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);
@@ -244,12 +273,16 @@ vgl.shaderProgram = function () {
244273
*/
245274
this.compileAndLink = function (renderState) {
246275
var i;
276+
// Rebuild if timestamps are stale or we switched GL contexts.
277+
var contextChanged = hasContextChanged(renderState) || !m_programHandle;
278+
279+
m_this._setup(renderState);
247280

248-
if (m_compileTimestamp.getMTime() >= this.getMTime()) {
249-
return;
281+
if (!contextChanged && m_compileTimestamp.getMTime() >= this.getMTime()) {
282+
return !!m_programHandle;
250283
}
251284

252-
m_this._setup(renderState);
285+
clearLocationCaches();
253286

254287
// Compile shaders
255288
for (i = 0; i < m_shaders.length; i += 1) {
@@ -263,9 +296,11 @@ vgl.shaderProgram = function () {
263296
if (!m_this.link(renderState)) {
264297
console.log('[ERROR] Failed to link Program'); // eslint-disable-line no-console
265298
m_this._cleanup(renderState);
299+
return false;
266300
}
267301

268302
m_compileTimestamp.modified();
303+
return true;
269304
};
270305

271306
/**
@@ -275,16 +310,24 @@ vgl.shaderProgram = function () {
275310
*/
276311
this.bind = function (renderState) {
277312
var i = 0;
313+
var needBind = renderState.m_contextChanged ||
314+
m_bindTimestamp.getMTime() < m_this.getMTime() ||
315+
m_programContext !== renderState.m_context;
278316

279-
if (m_bindTimestamp.getMTime() < m_this.getMTime()) {
317+
if (needBind) {
280318

281319
// Compile shaders
282-
m_this.compileAndLink(renderState);
320+
if (!m_this.compileAndLink(renderState)) {
321+
return;
322+
}
283323

284324
m_this.use(renderState);
285325
m_this.bindUniforms(renderState);
286326
m_bindTimestamp.modified();
287327
} else {
328+
if (!m_programHandle) {
329+
return;
330+
}
288331
m_this.use(renderState);
289332
}
290333

src/webgl/webglRenderer.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,15 @@ var webglRenderer = function (arg) {
9393
m_contextRenderer = m_viewer.renderWindow().activeRenderer();
9494
m_contextRenderer.setResetScene(false);
9595
canvas.get(0).addEventListener('webglcontextlost', (evt) => evt.preventDefault(), false);
96-
canvas.get(0).addEventListener('webglcontextrestored', () => m_viewer.renderWindow()._setup(), false);
96+
canvas.get(0).addEventListener('webglcontextrestored', function () {
97+
if (!m_viewer) {
98+
return;
99+
}
100+
// Reinitialize GL objects, then force a camera sync before redraw.
101+
m_viewer.renderWindow()._setup();
102+
m_updateCamera = true;
103+
m_this._render();
104+
}, false);
97105

98106
if (m_viewer.renderWindow().renderers().length > 0) {
99107
m_contextRenderer.setLayer(m_viewer.renderWindow().renderers().length);

0 commit comments

Comments
 (0)