Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve texture memory usage, by unloading tiles, partly resolving #12906 #12924

Merged
merged 3 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions debug/pathological-flyto.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
<!--
This is useful for debugging memory usage.
-->
<!DOCTYPE html>
<script
src="https://greggman.github.io/webgl-memory/webgl-memory.js"
crossorigin
></script>
<html>

<head>
<title>Mapbox GL JS Pathological FlyTo</title>
<meta charset='utf-8'>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no"
>
<link
rel='stylesheet'
href='../dist/mapbox-gl.css'
/>
<style>
body {
margin: 0;
padding: 0;
}

#overlay {
position: absolute;
color: white;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
text-shadow: 0px 2px 2px black;
z-index: 100;
font-size: 150%;
background-color: rgba(0, 0, 0, 0.5);
padding: 10px;
}

html,
body,
#map {
height: 100%;
}
</style>
</head>

<body>
<div style="padding: 5px;">
<button id="start">Start Flying Around</button>
<button id="cancel">Stop Flying Around</button>
<button id="loadSatellite">Load Satellite Streets</button>
<button id="loadTerrain">Load Terrain</button>
<button id="loadGeo">Load Geojson</button>
</div>
<div id='map'>
<div id="overlay"></div>
</div>

<script src='../dist/mapbox-gl-dev.js'></script>
<script src='../debug/access_token_generated.js'></script>
<script>
let goat = 0;
let pleaseStop = null;
let totals = [];

document.getElementById("start").addEventListener("click", () => {
totals = [];
pleaseStop = setInterval(() => {
if ((goat % 2) === 0) {
map.flyTo({
center: [-122.41381885225483 + Math.random() * 20, 37.73787623085941],
zoom: 16,
duration: 7000,
preloadOnly: false
});
} else {
map.flyTo({
center: [148.44217489435937 - Math.random() * 10, -35.721640804238895],
zoom: 16,
duration: 7000,
preloadOnly: false
});
}

goat++;

}, 5000);
});

document.getElementById("cancel").addEventListener("click", () => {
window.pleaseStop = clearInterval(pleaseStop);
});

document.getElementById("loadGeo").addEventListener("click", () => {
totals = [];
map.addSource('earthquakes', {
type: 'geojson',
// Use a URL for the value for the `data` property.
data: 'https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson'
});

map.addLayer({
'id': 'earthquakes-layer',
'type': 'circle',
'source': 'earthquakes',
'paint': {
'circle-radius': 4,
'circle-stroke-width': 2,
'circle-color': 'red',
'circle-stroke-color': 'white'
}
});

});

var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 12.5,
center: [-122.4194, 37.7749],
hash: false,
style: "mapbox://styles/mapbox/streets-v11?optimize=true"

});

document.getElementById("loadTerrain").addEventListener("click", () => {
totals = [];
map.addSource('mapbox-dem', {
'type': 'raster-dem',
'url': 'mapbox://mapbox.mapbox-terrain-dem-v1',
'tileSize': 512,
'maxzoom': 14
});

map.setTerrain({'source': 'mapbox-dem', 'exaggeration': 1.5});
});

document.getElementById("loadSatellite").addEventListener("click", () => {
totals = [];
map.setStyle("mapbox://styles/mapbox/satellite-streets-v11?optimize=true");
});

const gl = map.getCanvas().getContext("webgl2");
const ext = gl.getExtension('GMAN_webgl_memory');
const gpu = ext.getMemoryInfo();
let prev = gpu;
let prevMax = 0;
let prevAvg = 0;
console.log("memory info", gpu);

// Limit moving average to 1000 items
const limit = (arr) => {
if (arr.length > 1000) {
arr.shift();
}
};

// Calculate a moving average for a number
const movingAverage = (arr) => {
const count = arr.length;
const sum = arr.reduce((a, b) => a + b, 0);
return Math.round(sum / count);
};

// Calculate the maximum value in an array
const max = (arr) => {
return arr.reduce((a, b) => Math.max(a, b));
};

const highlightDiff = (current, previous, count = 0) => {
if (current > previous) {
return `<span style="color: red">${count === 0 ? "" : count + " / "}${Math.round(current / 1024 / 1024)}<small>MB</small></span>`;
} else {
return `<span style="color: green">${count === 0 ? "" : count + " / "}${Math.round(current / 1024 / 1024)}<small>MB</small></span>`;
}
};

setInterval(() => {
const gpu = ext.getMemoryInfo();
const overlay = document.getElementById("overlay");

totals.push(gpu.memory.total);
const currAvg = movingAverage(totals);
const currMax = max(totals);
limit(totals);

let html = `GPU Memory <br />`;
html += `total: ${highlightDiff(gpu.memory.total, prev.memory.total)} (avg: ${highlightDiff(currAvg, prevAvg)}; max: ${highlightDiff(currMax, prevMax)}) <br />`;
html += `drawingbuffer: ${highlightDiff(gpu.memory.drawingbuffer, prev.memory.drawingbuffer)} <br />`;
html += `buffer: ${highlightDiff(gpu.memory.buffer, prev.memory.buffer, gpu.resources.buffer)} <br />`;
html += `texture: ${highlightDiff(gpu.memory.texture, prev.memory.texture, gpu.resources.texture)} <br />`;
overlay.innerHTML = html;
totals.push(gpu.memory.total);

prev = gpu;
prevMax = currMax;
prevAvg = currAvg;
}, 50);
</script>
</body>

</html>
4 changes: 3 additions & 1 deletion src/render/draw_fill.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ function drawFillTiles(painter: Painter, sourceCache: SourceCache, layer: FillSt

if (image) {
painter.context.activeTexture.set(gl.TEXTURE0);
tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
if (tile.imageAtlasTexture) {
tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
}
programConfiguration.updatePaintBuffers();
}

Expand Down
4 changes: 3 additions & 1 deletion src/render/draw_fill_extrusion.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,9 @@ function drawExtrusionTiles(painter: Painter, source: SourceCache, layer: FillEx

if (image) {
painter.context.activeTexture.set(gl.TEXTURE0);
tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
if (tile.imageAtlasTexture) {
tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
}
programConfiguration.updatePaintBuffers();
}
const constantPattern = patternProperty.constantOr(null);
Expand Down
8 changes: 6 additions & 2 deletions src/render/draw_line.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,16 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay
}
if (dasharray) {
context.activeTexture.set(gl.TEXTURE0);
tile.lineAtlasTexture.bind(gl.LINEAR, gl.REPEAT);
if (tile.lineAtlasTexture) {
tile.lineAtlasTexture.bind(gl.LINEAR, gl.REPEAT);
}
programConfiguration.updatePaintBuffers();
}
if (image) {
context.activeTexture.set(gl.TEXTURE0);
tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
if (tile.imageAtlasTexture) {
tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
}
programConfiguration.updatePaintBuffers();
}

Expand Down
12 changes: 8 additions & 4 deletions src/render/draw_raster.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,22 +149,26 @@ function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterSty
let parentScaleBy, parentTL;

context.activeTexture.set(gl.TEXTURE0);
tile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE);
if (tile.texture) {
tile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE);
}

context.activeTexture.set(gl.TEXTURE1);

if (parentTile) {
parentTile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE);
if (parentTile.texture) {
parentTile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE);
}
parentScaleBy = Math.pow(2, parentTile.tileID.overscaledZ - tile.tileID.overscaledZ);
parentTL = [tile.tileID.canonical.x * parentScaleBy % 1, tile.tileID.canonical.y * parentScaleBy % 1];

} else {
} else if (tile.texture) {
tile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE);
}

// Enable trilinear filtering on tiles only beyond 20 degrees pitch,
// to prevent it from compromising image crispness on flat or low tilted maps.
if (tile.texture.useMipmap && context.extTextureFilterAnisotropic && painter.transform.pitch > 20) {
if (tile.texture && tile.texture.useMipmap && context.extTextureFilterAnisotropic && painter.transform.pitch > 20) {
gl.texParameterf(gl.TEXTURE_2D, context.extTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, context.extTextureFilterAnisotropicMax);
}

Expand Down
24 changes: 13 additions & 11 deletions src/render/draw_symbol.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import type Painter from './painter.js';
import type SourceCache from '../source/source_cache.js';
import type SymbolStyleLayer from '../style/style_layer/symbol_style_layer.js';
import type SymbolBucket, {SymbolBuffers} from '../data/bucket/symbol_bucket.js';
import type Texture from '../render/texture.js';
import Texture from '../render/texture.js';
import type ColorMode from '../gl/color_mode.js';
import {OverscaledTileID} from '../source/tile_id.js';
import type {UniformValues} from './uniform_binding.js';
Expand All @@ -49,7 +49,7 @@ type SymbolTileRenderState = {
program: any,
buffers: SymbolBuffers,
uniformValues: any,
atlasTexture: Texture,
atlasTexture: Texture | null,
atlasTextureIcon: Texture | null,
atlasInterpolation: any,
atlasInterpolationIcon: any,
Expand Down Expand Up @@ -327,27 +327,27 @@ function drawLayerSymbols(painter: Painter, sourceCache: SourceCache, layer: Sym

let texSize: [number, number];
let texSizeIcon: [number, number] = [0, 0];
let atlasTexture;
let atlasTexture: Texture | null;
let atlasInterpolation;
let atlasTextureIcon = null;
let atlasTextureIcon: Texture | null = null;
let atlasInterpolationIcon;
if (isText) {
atlasTexture = tile.glyphAtlasTexture;
atlasTexture = tile.glyphAtlasTexture ? tile.glyphAtlasTexture : null;
atlasInterpolation = gl.LINEAR;
texSize = tile.glyphAtlasTexture.size;
texSize = tile.glyphAtlasTexture ? tile.glyphAtlasTexture.size : [0, 0];
if (bucket.iconsInText) {
texSizeIcon = tile.imageAtlasTexture.size;
atlasTextureIcon = tile.imageAtlasTexture;
texSizeIcon = tile.imageAtlasTexture ? tile.imageAtlasTexture.size : [0, 0];
atlasTextureIcon = tile.imageAtlasTexture ? tile.imageAtlasTexture : null;
const zoomDependentSize = sizeData.kind === 'composite' || sizeData.kind === 'camera';
atlasInterpolationIcon = transformed || painter.options.rotating || painter.options.zooming || zoomDependentSize ? gl.LINEAR : gl.NEAREST;
}
} else {
const iconScaled = layer.layout.get('icon-size').constantOr(0) !== 1 || bucket.iconsNeedLinear;
atlasTexture = tile.imageAtlasTexture;
atlasTexture = tile.imageAtlasTexture ? tile.imageAtlasTexture : null;
atlasInterpolation = isSDF || painter.options.rotating || painter.options.zooming || iconScaled || transformed ?
gl.LINEAR :
gl.NEAREST;
texSize = tile.imageAtlasTexture.size;
texSize = tile.imageAtlasTexture ? tile.imageAtlasTexture.size : [0, 0];
}

const bucketIsGlobeProjection = bucket.projection.name === 'globe';
Expand Down Expand Up @@ -463,7 +463,9 @@ function drawLayerSymbols(painter: Painter, sourceCache: SourceCache, layer: Sym
painter.terrain.setupElevationDraw(state.tile, state.program, options);
}
context.activeTexture.set(gl.TEXTURE0);
state.atlasTexture.bind(state.atlasInterpolation, gl.CLAMP_TO_EDGE);
if (state.atlasTexture) {
state.atlasTexture.bind(state.atlasInterpolation, gl.CLAMP_TO_EDGE);
}
if (state.atlasTextureIcon) {
context.activeTexture.set(gl.TEXTURE1);
if (state.atlasTextureIcon) {
Expand Down
11 changes: 9 additions & 2 deletions src/render/painter.js
Original file line number Diff line number Diff line change
Expand Up @@ -1114,10 +1114,17 @@ class Painter {
return translatedMatrix;
}

/**
* Saves the tile texture for re-use when another tile is loaded.
*
* @returns true if the tile was cached, false if the tile was not cached and should be destroyed.
* @private
*/
saveTileTexture(texture: Texture) {
const textures = this._tileTextures[texture.size[0]];
const tileSize = texture.size[0];
const textures = this._tileTextures[tileSize];
if (!textures) {
this._tileTextures[texture.size[0]] = [texture];
this._tileTextures[tileSize] = [texture];
} else {
textures.push(texture);
}
Expand Down
4 changes: 2 additions & 2 deletions src/render/program/line_program.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const lineUniformValues = (
'u_dash_image': 0,
'u_gradient_image': 1,
'u_image_height': imageHeight,
'u_texsize': hasDash(layer) ? tile.lineAtlasTexture.size : [0, 0],
'u_texsize': hasDash(layer) && tile.lineAtlasTexture ? tile.lineAtlasTexture.size : [0, 0],
'u_tile_units_to_pixels': calculateTileRatio(tile, painter.transform),
'u_alpha_discard_threshold': 0.0,
'u_trim_offset': trimOffset,
Expand All @@ -111,7 +111,7 @@ const linePatternUniformValues = (
const transform = painter.transform;
return {
'u_matrix': calculateMatrix(painter, tile, layer, matrix),
'u_texsize': tile.imageAtlasTexture.size,
'u_texsize': tile.imageAtlasTexture ? tile.imageAtlasTexture.size : [0, 0],
// camera zoom ratio
'u_pixels_to_tile_units': transform.calculatePixelsToTileUnitsMatrix(tile),
'u_device_pixel_ratio': pixelRatio,
Expand Down
2 changes: 1 addition & 1 deletion src/render/program/pattern.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function patternUniformValues(painter: Painter, tile: Tile): UniformValues<Patte

return {
'u_image': 0,
'u_texsize': tile.imageAtlasTexture.size,
'u_texsize': tile.imageAtlasTexture ? tile.imageAtlasTexture.size : [0, 0],
'u_tile_units_to_pixels': 1 / pixelsToTileUnits(tile, 1, painter.transform.tileZoom),
// split the pixel coord into two pairs of 16 bit numbers. The glsl spec only guarantees 16 bits of precision.
'u_pixel_coord_upper': [pixelX >> 16, pixelY >> 16],
Expand Down
2 changes: 2 additions & 0 deletions src/source/custom_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,8 @@ class CustomSource<T> extends Evented implements Source {
this._implementation.unloadTile({x, y, z});
}

tile.destroy();

callback();
}

Expand Down
Loading