Resolve memory leak when deleting a webgl layer

Various references were kept, preventing the layer and underlying
renderer and webgl context to be garbage collected.

Also, the Helper was simplified because it turns out deleting manually
all Webgl objects is useless: these objects will be released when
the context is garbage collected anyway.

Note: this touches the Layer and BaseLayer classes, as the following were
preventing the layer from being garbage collected:
* layer reference in the `state_` object in BaseLayer
* dangling listener for source change in Layer
This commit is contained in:
jahow
2019-10-30 21:59:57 +01:00
committed by Olivier Guyot
parent e5e03d46a0
commit f7b0f6750b
7 changed files with 43 additions and 35 deletions

View File

@@ -320,6 +320,17 @@ class BaseLayer extends BaseObject {
setZIndex(zindex) {
this.set(LayerProperty.Z_INDEX, zindex);
}
/**
* @inheritDoc
*/
disposeInternal() {
if (this.state_) {
this.state_.layer = null;
this.state_ = null;
}
super.disposeInternal();
}
}

View File

@@ -287,6 +287,13 @@ class Layer extends BaseLayer {
return null;
}
/**
* @inheritDoc
*/
disposeInternal() {
this.setSource(null);
super.disposeInternal();
}
}

View File

@@ -57,6 +57,9 @@ import Layer from './Layer.js';
* }
* ```
*
* **Important: a `WebGLPoints` layer must be manually disposed when removed, otherwise the underlying WebGL context
* will not be garbage collected.**
*
* Note that any property set in the options is set as a {@link module:ol/Object~BaseObject}
* property on the layer object; for example, setting `title: 'My Title'` in the
* options means that `title` is observable, and has get/set accessors.
@@ -100,6 +103,15 @@ class WebGLPointsLayer extends Layer {
attributes: this.parseResult_.attributes
});
}
/**
*
* @inheritDoc
*/
disposeInternal() {
this.renderer_.dispose();
super.disposeInternal();
}
}
export default WebGLPointsLayer;

View File

@@ -71,7 +71,7 @@ class WebGLLayerRenderer extends LayerRenderer {
* @inheritDoc
*/
disposeInternal() {
this.helper.disposeInternal();
this.helper.dispose();
super.disposeInternal();
}

View File

@@ -19,7 +19,7 @@ import {getUid} from '../../util.js';
import WebGLRenderTarget from '../../webgl/RenderTarget.js';
import {assert} from '../../asserts.js';
import BaseVector from '../../layer/BaseVector.js';
import {listen} from '../../events.js';
import {listen, unlistenByKey} from '../../events.js';
import VectorEventType from '../../source/VectorEventType.js';
/**
@@ -288,9 +288,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
this.featureCache_ = {};
const source = this.getLayer().getSource();
listen(source, VectorEventType.ADDFEATURE, this.handleSourceFeatureChanged_, this);
listen(source, VectorEventType.CHANGEFEATURE, this.handleSourceFeatureChanged_, this);
listen(source, VectorEventType.REMOVEFEATURE, this.handleSourceFeatureDelete_, this);
this.sourceListenKeys_ = [
listen(source, VectorEventType.ADDFEATURE, this.handleSourceFeatureChanged_, this),
listen(source, VectorEventType.CHANGEFEATURE, this.handleSourceFeatureChanged_, this),
listen(source, VectorEventType.REMOVEFEATURE, this.handleSourceFeatureDelete_, this)
];
source.getFeatures().forEach(function(feature) {
const uid = getUid(feature);
this.featureCache_[uid] = {
@@ -548,6 +550,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
*/
disposeInternal() {
this.worker_.terminate();
this.layer_ = null;
this.sourceListenKeys_.forEach(function(key) {
unlistenByKey(key);
});
this.sourceListenKeys_ = null;
super.disposeInternal();
}
}

View File

@@ -266,18 +266,6 @@ class WebGLHelper extends Disposable {
*/
this.bufferCache_ = {};
/**
* @private
* @type {!Array<WebGLShader>}
*/
this.shaderCache_ = [];
/**
* @private
* @type {!Array<WebGLProgram>}
*/
this.programCache_ = [];
/**
* @private
* @type {WebGLProgram}
@@ -419,18 +407,6 @@ class WebGLHelper extends Disposable {
disposeInternal() {
this.canvas_.removeEventListener(ContextEventType.LOST, this.boundHandleWebGLContextLost_);
this.canvas_.removeEventListener(ContextEventType.RESTORED, this.boundHandleWebGLContextRestored_);
const gl = this.getGL();
if (!gl.isContextLost()) {
for (const key in this.bufferCache_) {
gl.deleteBuffer(this.bufferCache_[key].buffer);
}
for (const key in this.programCache_) {
gl.deleteProgram(this.programCache_[key]);
}
for (const key in this.shaderCache_) {
gl.deleteShader(this.shaderCache_[key]);
}
}
}
/**
@@ -652,7 +628,6 @@ class WebGLHelper extends Disposable {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
this.shaderCache_.push(shader);
return shader;
}
@@ -684,7 +659,6 @@ class WebGLHelper extends Disposable {
gl.attachShader(program, fragmentShader);
gl.attachShader(program, vertexShader);
gl.linkProgram(program);
this.programCache_.push(program);
return program;
}
@@ -811,8 +785,6 @@ class WebGLHelper extends Disposable {
*/
handleWebGLContextLost() {
clear(this.bufferCache_);
clear(this.shaderCache_);
clear(this.programCache_);
this.currentProgram_ = null;
}
@@ -823,8 +795,6 @@ class WebGLHelper extends Disposable {
handleWebGLContextRestored() {
}
// TODO: shutdown program
/**
* Will create or reuse a given webgl texture and apply the given size. If no image data
* specified, the texture will be empty, otherwise image data will be used and the `size`