Fix canvas memory leak.
Fixes: 430515056
Test: npm run test:unit:ci
Change-Id: Iaaff69a2433f79c1322e13cdf69ae250594b6aa5
diff --git a/tools/winscope/src/viewers/components/rects/canvas.ts b/tools/winscope/src/viewers/components/rects/canvas.ts
index 8f4f2d3..e9ea570 100644
--- a/tools/winscope/src/viewers/components/rects/canvas.ts
+++ b/tools/winscope/src/viewers/components/rects/canvas.ts
@@ -114,6 +114,21 @@
}
}
+ onDestroy() {
+ this.lastScene.rectIdToRectGraphics.forEach((graphics) => {
+ this.disposeMesh(graphics.mesh, graphics.rect.id);
+ });
+ this.lastScene.rectIdToLabelGraphics.forEach((graphics) => {
+ this.disposeObj(graphics.circle);
+ this.disposeObj(graphics.line);
+ this.disposeObj(graphics.text);
+ });
+ this.renderer.dispose();
+ this.renderer.info.programs?.forEach((program) => program.destroy());
+ this.renderer.renderLists.dispose();
+ this.renderer.forceContextLoss();
+ }
+
updateViewPosition(camera: Camera, bounds: Box3D, zDepth: number) {
// Must set 100% width and height so the HTML element expands to the parent's
// boundaries and the correct clientWidth and clientHeight values can be read
@@ -178,9 +193,13 @@
updateRects(rects: UiRect3D[]) {
for (const key of this.lastScene.rectIdToRectGraphics.keys()) {
if (!rects.some((rect) => rect.id === key)) {
+ const lastObj = assertDefined(
+ this.lastScene.rectIdToRectGraphics.get(key),
+ );
this.lastScene.rectIdToRectGraphics.delete(key);
- this.removeRays(key); // rays are added directly to the scene
this.scene.remove(assertDefined(this.scene.getObjectByName(key)));
+ this.removeRays(key); // rays are added directly to the scene
+ this.disposeMesh(lastObj.mesh, lastObj.rect.id);
}
}
rects.forEach((rect) => {
@@ -729,13 +748,13 @@
if (!newRect.fillRegion && existingRect.fillRegion) {
existingMesh.material = fillMaterial;
- existingMesh.remove(
- assertDefined(
- existingMesh.getObjectByName(
- existingRect.id + Canvas.GRAPHICS_NAMES.fillRegion,
- ),
+ const existingFillRegion = assertDefined(
+ existingMesh.getObjectByName(
+ existingRect.id + Canvas.GRAPHICS_NAMES.fillRegion,
),
);
+ existingMesh.remove(existingFillRegion);
+ this.disposeObj(existingFillRegion);
} else if (newRect.fillRegion && !existingRect.fillRegion) {
existingMesh.material = Canvas.TRANSPARENT_MATERIAL;
this.addFillRegionMesh(newRect, fillMaterial, existingMesh);
@@ -751,13 +770,13 @@
},
);
if (fillRegionChanged) {
- existingMesh.remove(
- assertDefined(
- existingMesh.getObjectByName(
- existingRect.id + Canvas.GRAPHICS_NAMES.fillRegion,
- ),
+ const existingFillRegion = assertDefined(
+ existingMesh.getObjectByName(
+ existingRect.id + Canvas.GRAPHICS_NAMES.fillRegion,
),
);
+ existingMesh.remove(existingFillRegion);
+ this.disposeObj(existingFillRegion);
this.addFillRegionMesh(newRect, fillMaterial, existingMesh);
}
}
@@ -787,6 +806,7 @@
newRect.cornerRadius !== existingRect.cornerRadius;
if (isGeometryChanged) {
+ existingMesh.geometry.dispose();
existingMesh.geometry = this.makeRoundedRectGeometry(newRect);
existingMesh.position.z = newRect.topLeft.z;
}
@@ -795,13 +815,13 @@
this.isDarkMode() !== this.lastScene.isDarkMode ||
newRect.isPinned !== existingRect.isPinned;
if (isGeometryChanged || isColorChanged) {
- existingMesh.remove(
- assertDefined(
- existingMesh.getObjectByName(
- existingRect.id + Canvas.GRAPHICS_NAMES.border,
- ),
+ const existingBorder = assertDefined(
+ existingMesh.getObjectByName(
+ existingRect.id + Canvas.GRAPHICS_NAMES.border,
),
);
+ existingMesh.remove(existingBorder);
+ this.disposeObj(existingBorder);
this.addRectBorders(newRect, existingMesh);
}
@@ -931,6 +951,7 @@
);
if (newLabel.circle.radius !== existingLabel.circle.radius) {
+ circle.geometry.dispose();
circle.geometry = new THREE.CircleGeometry(newLabel.circle.radius, 20);
}
if (!newLabel.circle.center.isEqual(existingLabel.circle.center)) {
@@ -946,7 +967,9 @@
this.isDarkMode() !== this.lastScene.isDarkMode
) {
const lineMaterial = this.makeLabelMaterial(newLabel);
+ this.disposeMaterial(circle);
circle.material = lineMaterial;
+ this.disposeMaterial(line);
line.material = lineMaterial;
text.element.style.color = newLabel.isHighlighted ? '' : 'gray';
}
@@ -956,6 +979,7 @@
(a as Point3D).isEqual(b as Point3D),
)
) {
+ line.geometry.dispose();
line.geometry = this.makeLabelLineGeometry(newLabel);
}
@@ -992,6 +1016,9 @@
this.scene.remove(graphics.line);
this.scene.remove(graphics.text);
this.lastScene.rectIdToLabelGraphics.delete(rectId);
+ this.disposeObj(graphics.circle);
+ this.disposeObj(graphics.line);
+ this.disposeObj(graphics.text);
}
}
}
@@ -1000,6 +1027,7 @@
let existingObj = root.getObjectByName(name);
while (existingObj) {
root.remove(existingObj);
+ this.disposeObj(existingObj);
existingObj = root.getObjectByName(name);
}
}
@@ -1028,6 +1056,29 @@
? Canvas.RECT_EDGE_COLOR_DARK_MODE
: Canvas.RECT_EDGE_COLOR_LIGHT_MODE;
}
+
+ private disposeMesh(obj: any, rectId: string) {
+ this.removeAllByName(obj, rectId + Canvas.GRAPHICS_NAMES.fillRegion);
+ this.removeAllByName(obj, rectId + Canvas.GRAPHICS_NAMES.pointerCircle);
+ this.removeAllByName(obj, rectId + Canvas.GRAPHICS_NAMES.pointerCrosshairs);
+ this.removeAllByName(obj, rectId + Canvas.GRAPHICS_NAMES.border);
+ this.disposeObj(obj);
+ }
+
+ private disposeObj(obj: any) {
+ if (obj.geometry) {
+ obj.geometry.dispose();
+ }
+ this.disposeMaterial(obj);
+ }
+
+ private disposeMaterial(obj: any) {
+ if (Array.isArray(obj.material)) {
+ obj.material.forEach((m: THREE.Material) => m.dispose());
+ } else if (obj.material) {
+ obj.material.dispose();
+ }
+ }
}
interface SceneState {
diff --git a/tools/winscope/src/viewers/components/rects/rects_component.ts b/tools/winscope/src/viewers/components/rects/rects_component.ts
index f458d2a..1199fef 100644
--- a/tools/winscope/src/viewers/components/rects/rects_component.ts
+++ b/tools/winscope/src/viewers/components/rects/rects_component.ts
@@ -488,12 +488,12 @@
);
this.resizeObserver.observe(canvasContainer);
- this.largeRectsCanvasElement = canvasContainer.querySelector(
- '.large-rects-canvas',
- )! as HTMLCanvasElement;
+ this.largeRectsCanvasElement = assertDefined(
+ canvasContainer.querySelector<HTMLCanvasElement>('.large-rects-canvas'),
+ );
this.largeRectsLabelsElement = assertDefined(
- canvasContainer.querySelector('.large-rects-labels'),
- ) as HTMLElement;
+ canvasContainer.querySelector<HTMLElement>('.large-rects-labels'),
+ );
this.largeRectsCanvas = new Canvas(
this.largeRectsCanvasElement,
this.largeRectsLabelsElement,
@@ -589,6 +589,10 @@
ngOnDestroy() {
this.resizeObserver?.disconnect();
+ this.largeRectsCanvas?.onDestroy();
+ this.miniRectsCanvas?.onDestroy();
+ (this.largeRectsCanvasElement?.getContext('2d') as any)?.reset();
+ (this.miniRectsCanvasElement?.getContext('2d') as any)?.reset();
}
onDisplaysChange(change: SimpleChange) {