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

WebGL matrix performance fix #5072

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
10 changes: 5 additions & 5 deletions src/geo/projection/globe_transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {mat2, mat4, vec3, vec4} from 'gl-matrix';
import {MAX_VALID_LATITUDE, TransformHelper} from '../transform_helper';
import {MercatorTransform} from './mercator_transform';
import {LngLat, LngLatLike, earthRadius} from '../lng_lat';
import {angleToRotateBetweenVectors2D, clamp, createIdentityMat4f64, createMat4f64, createVec3f64, createVec4f64, differenceOfAnglesDegrees, distanceOfAnglesRadians, easeCubicInOut, lerp, pointPlaneSignedDistance, warnOnce} from '../../util/util';
import {angleToRotateBetweenVectors2D, clamp, createIdentityMat4f32, createIdentityMat4f64, createMat4f32, createMat4f64, createVec3f64, createVec4f64, differenceOfAnglesDegrees, distanceOfAnglesRadians, easeCubicInOut, lerp, pointPlaneSignedDistance, warnOnce} from '../../util/util';
import {UnwrappedTileID, OverscaledTileID, CanonicalTileID} from '../../source/tile_id';
import Point from '@mapbox/point-geometry';
import {browser} from '../../util/browser';
Expand Down Expand Up @@ -240,7 +240,7 @@ export class GlobeTransform implements ITransform {
private _skipNextAnimation: boolean = true;

private _projectionMatrix: mat4 = createIdentityMat4f64();
private _globeViewProjMatrix: mat4 = createIdentityMat4f64();
private _globeViewProjMatrix32f: mat4 = createIdentityMat4f32(); // Must be 32 bit floats, otherwise WebGL calls in Chrome get very slow.
private _globeViewProjMatrixNoCorrection: mat4 = createIdentityMat4f64();
private _globeViewProjMatrixNoCorrectionInverted: mat4 = createIdentityMat4f64();
private _globeProjMatrixInverted: mat4 = createIdentityMat4f64();
Expand Down Expand Up @@ -459,7 +459,7 @@ export class GlobeTransform implements ITransform {

// Set 'projectionMatrix' to actual globe transform
if (this.isGlobeRendering) {
data.mainMatrix = this._globeViewProjMatrix;
data.mainMatrix = this._globeViewProjMatrix32f;
}

data.clippingPlane = this._cachedClippingPlane as [number, number, number, number];
Expand Down Expand Up @@ -661,7 +661,7 @@ export class GlobeTransform implements ITransform {
mat4.rotateX(globeMatrix, globeMatrix, this.center.lat * Math.PI / 180.0 - this._globeLatitudeErrorCorrectionRadians);
mat4.rotateY(globeMatrix, globeMatrix, -this.center.lng * Math.PI / 180.0);
mat4.scale(globeMatrix, globeMatrix, scaleVec); // Scale the unit sphere to a sphere with diameter of 1
this._globeViewProjMatrix = globeMatrix;
this._globeViewProjMatrix32f = new Float32Array(globeMatrix);

this._globeViewProjMatrixNoCorrectionInverted = createMat4f64();
mat4.invert(this._globeViewProjMatrixNoCorrectionInverted, globeMatrixUncorrected);
Expand Down Expand Up @@ -1194,7 +1194,7 @@ export class GlobeTransform implements ITransform {
// the fallback projection matrix by EXTENT.
// Note that the regular projection matrices do not need to be modified, since the rescaling happens by setting
// the `u_projection_tile_mercator_coords` uniform correctly.
const fallbackMatrixScaled = createMat4f64();
const fallbackMatrixScaled = createMat4f32();
mat4.scale(fallbackMatrixScaled, projectionData.fallbackMatrix, [EXTENT, EXTENT, 1]);

projectionData.fallbackMatrix = fallbackMatrixScaled;
Expand Down
48 changes: 28 additions & 20 deletions src/geo/projection/mercator_transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,11 @@ export class MercatorTransform implements ITransform {
private _pixelMatrixInverse: mat4;
private _fogMatrix: mat4;

private _posMatrixCache: {[_: string]: mat4};
private _fogMatrixCache: {[_: string]: mat4};
private _alignedPosMatrixCache: {[_: string]: mat4};
private _posMatrixCacheF32: Map<string, mat4> = new Map();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would advise to create an object/type that has both 32 and 64 matrices to avoid cache misalignment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean exactly. As in not allowing the maps to have different sets of key-value pairs by accident?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of two maps holding the same keys, have one map holding an object with two fields.

private _posMatrixCacheF64: Map<string, mat4> = new Map();
private _alignedPosMatrixCacheF32: Map<string, mat4> = new Map();
private _alignedPosMatrixCacheF64: Map<string, mat4> = new Map();
private _fogMatrixCacheF32: Map<string, mat4> = new Map();

private _nearZ;
private _farZ;
Expand All @@ -224,7 +226,6 @@ export class MercatorTransform implements ITransform {
calcMatrices: () => { this._calcMatrices(); },
getConstrained: (center, zoom) => { return this.getConstrained(center, zoom); }
}, minZoom, maxZoom, minPitch, maxPitch, renderWorldCopies);
this._clearMatrixCaches();
this._coveringTilesDetailsProvider = new MercatorCoveringTilesDetailsProvider();
}

Expand Down Expand Up @@ -405,33 +406,37 @@ export class MercatorTransform implements ITransform {
* This function is specific to the mercator projection.
* @param tileID - the tile ID
* @param aligned - whether to use a pixel-aligned matrix variant, intended for rendering raster tiles
* @param useFloat32 - when true, returns a float32 matrix instead of float64. Use float32 for matrices that are passed to shaders, use float64 for everything else.
*/
calculatePosMatrix(tileID: UnwrappedTileID | OverscaledTileID, aligned: boolean = false): mat4 {
calculatePosMatrix(tileID: UnwrappedTileID | OverscaledTileID, aligned: boolean = false, useFloat32?: boolean): mat4 {
const posMatrixKey = tileID.key ?? calculateTileKey(tileID.wrap, tileID.canonical.z, tileID.canonical.z, tileID.canonical.x, tileID.canonical.y);
const cache = aligned ? this._alignedPosMatrixCache : this._posMatrixCache;
if (cache[posMatrixKey]) {
return cache[posMatrixKey];
const cacheF32 = aligned ? this._alignedPosMatrixCacheF32 : this._posMatrixCacheF32;
const cacheF64 = aligned ? this._alignedPosMatrixCacheF64 : this._posMatrixCacheF64;
const cacheRequested = useFloat32 ? cacheF32 : cacheF64;
if (cacheRequested.has(posMatrixKey)) {
return cacheRequested.get(posMatrixKey);
}

const tileMatrix = calculateTileMatrix(tileID, this.worldSize);
mat4.multiply(tileMatrix, aligned ? this._alignedProjMatrix : this._viewProjMatrix, tileMatrix);

cache[posMatrixKey] = tileMatrix;
return cache[posMatrixKey];
cacheF64.set(posMatrixKey, tileMatrix);
cacheF32.set(posMatrixKey, new Float32Array(tileMatrix)); // Must be 32 bit floats, otherwise WebGL calls in Chrome get very slow.
// Make sure to return the correct precision
return cacheRequested.get(posMatrixKey);
}

calculateFogMatrix(unwrappedTileID: UnwrappedTileID): mat4 {
const posMatrixKey = unwrappedTileID.key;
const cache = this._fogMatrixCache;
if (cache[posMatrixKey]) {
return cache[posMatrixKey];
const cache = this._fogMatrixCacheF32;
if (cache.has(posMatrixKey)) {
return cache.get(posMatrixKey);
}

const fogMatrix = calculateTileMatrix(unwrappedTileID, this.worldSize);
mat4.multiply(fogMatrix, this._fogMatrix, fogMatrix);

cache[posMatrixKey] = fogMatrix;
return cache[posMatrixKey];
cache.set(posMatrixKey, new Float32Array(fogMatrix)); // Must be 32 bit floats, otherwise WebGL calls in Chrome get very slow.
return cache.get(posMatrixKey);
}

/**
Expand Down Expand Up @@ -713,9 +718,11 @@ export class MercatorTransform implements ITransform {
}

private _clearMatrixCaches(): void {
this._posMatrixCache = {};
this._alignedPosMatrixCache = {};
this._fogMatrixCache = {};
this._posMatrixCacheF32.clear();
this._posMatrixCacheF64.clear();
this._alignedPosMatrixCacheF32.clear();
this._alignedPosMatrixCacheF64.clear();
this._fogMatrixCacheF32.clear();
}

maxPitchScaleFactor(): number {
Expand Down Expand Up @@ -760,7 +767,7 @@ export class MercatorTransform implements ITransform {

getProjectionData(params: ProjectionDataParams): ProjectionData {
const {overscaledTileID, aligned, applyTerrainMatrix} = params;
const matrix = overscaledTileID ? this.calculatePosMatrix(overscaledTileID, aligned) : null;
const matrix = overscaledTileID ? this.calculatePosMatrix(overscaledTileID, aligned, true) : null;
return getBasicProjectionData(overscaledTileID, matrix, applyTerrainMatrix);
}

Expand Down Expand Up @@ -849,6 +856,7 @@ export class MercatorTransform implements ITransform {

const scale: vec3 = [EXTENT, EXTENT, this.worldSize / this._helper.pixelsPerMeter];

// We pass full-precision 64bit float matrices to custom layers to prevent precision loss in case the user wants to do further transformations.
const fallbackMatrixScaled = createMat4f64();
mat4.scale(fallbackMatrixScaled, tileMatrix, scale);

Expand Down
10 changes: 5 additions & 5 deletions src/geo/projection/mercator_utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {mat4} from 'gl-matrix';
import {EXTENT} from '../../data/extent';
import {OverscaledTileID} from '../../source/tile_id';
import {clamp, degreesToRadians} from '../../util/util';
import {clamp, createIdentityMat4f32, degreesToRadians} from '../../util/util';
import {MAX_VALID_LATITUDE, UnwrappedTileIDType, zoomScale} from '../transform_helper';
import {LngLat} from '../lng_lat';
import {MercatorCoordinate, mercatorXfromLng, mercatorYfromLat, mercatorZfromAltitude} from '../mercator_coordinate';
Expand Down Expand Up @@ -110,12 +110,12 @@ export function getBasicProjectionData(overscaledTileID: OverscaledTileID, tileP
}

let mainMatrix: mat4;
if (overscaledTileID && overscaledTileID.terrainRttPosMatrix && applyTerrainMatrix) {
mainMatrix = overscaledTileID.terrainRttPosMatrix;
if (overscaledTileID && overscaledTileID.terrainRttPosMatrix32f && applyTerrainMatrix) {
mainMatrix = overscaledTileID.terrainRttPosMatrix32f;
} else if (tilePosMatrix) {
mainMatrix = tilePosMatrix;
mainMatrix = tilePosMatrix; // This matrix should be float32
} else {
mainMatrix = mat4.create();
mainMatrix = createIdentityMat4f32();
}

const data: ProjectionData = {
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_fill.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ describe('drawFill', () => {

function constructMockTile(layer: FillStyleLayer): Tile {
const tileId = new OverscaledTileID(1, 0, 1, 0, 0);
tileId.terrainRttPosMatrix = mat4.create();
tileId.terrainRttPosMatrix32f = mat4.create();

const tile = new Tile(tileId, 256);
tile.tileID = tileId;
Expand Down
6 changes: 3 additions & 3 deletions src/render/draw_symbol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe('drawSymbol', () => {
layer.recalculate({zoom: 0, zoomHistory: {} as ZoomHistory} as EvaluationParameters, []);

const tileId = new OverscaledTileID(1, 0, 1, 0, 0);
tileId.terrainRttPosMatrix = mat4.create();
tileId.terrainRttPosMatrix32f = mat4.create();
const programMock = new Program(null, null, null, null, null, null, null, null);
(painterMock.useProgram as jest.Mock).mockReturnValue(programMock);
const bucketMock = new SymbolBucket(null);
Expand Down Expand Up @@ -147,7 +147,7 @@ describe('drawSymbol', () => {
layer.recalculate({zoom: 0, zoomHistory: {} as ZoomHistory} as EvaluationParameters, []);

const tileId = new OverscaledTileID(1, 0, 1, 0, 0);
tileId.terrainRttPosMatrix = mat4.create();
tileId.terrainRttPosMatrix32f = mat4.create();
const programMock = new Program(null, null, null, null, null, null, null, null);
(painterMock.useProgram as jest.Mock).mockReturnValue(programMock);
const bucketMock = new SymbolBucket(null);
Expand Down Expand Up @@ -214,7 +214,7 @@ describe('drawSymbol', () => {
layer.recalculate({zoom: 0, zoomHistory: {} as ZoomHistory} as EvaluationParameters, []);

const tileId = new OverscaledTileID(1, 0, 1, 0, 0);
tileId.terrainRttPosMatrix = mat4.create();
tileId.terrainRttPosMatrix32f = mat4.create();
const programMock = new Program(null, null, null, null, null, null, null, null);
(painterMock.useProgram as jest.Mock).mockReturnValue(programMock);
const bucketMock = new SymbolBucket(null);
Expand Down
2 changes: 1 addition & 1 deletion src/render/render_to_texture.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ describe('render to texture', () => {
'key': '923',
'overscaledZ': 3,
'wrap': 0,
'terrainRttPosMatrix': null,
'terrainRttPosMatrix32f': null,
}
]
}
Expand Down
32 changes: 15 additions & 17 deletions src/source/terrain_source_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {SourceCache} from '../source/source_cache';
import {Terrain} from '../render/terrain';
import {browser} from '../util/browser';
import {coveringTiles} from '../geo/projection/covering_tiles';
import {createMat4f64} from '../util/util';

/**
* @internal
Expand Down Expand Up @@ -98,8 +99,8 @@ export class TerrainSourceCache extends Evented {
keys[tileID.key] = true;
this._renderableTilesKeys.push(tileID.key);
if (!this._tiles[tileID.key]) {
tileID.terrainRttPosMatrix = new Float64Array(16) as any;
mat4.ortho(tileID.terrainRttPosMatrix, 0, EXTENT, EXTENT, 0, 0, 1);
tileID.terrainRttPosMatrix32f = new Float64Array(16) as any;
mat4.ortho(tileID.terrainRttPosMatrix32f, 0, EXTENT, EXTENT, 0, 0, 1);
this._tiles[tileID.key] = new Tile(tileID, this.tileSize);
this._lastTilesetChange = browser.now();
}
Expand Down Expand Up @@ -148,33 +149,30 @@ export class TerrainSourceCache extends Evented {
const coords = {};
for (const key of this._renderableTilesKeys) {
const _tileID = this._tiles[key].tileID;
const coord = tileID.clone();
const mat = createMat4f64();
if (_tileID.canonical.equals(tileID.canonical)) {
const coord = tileID.clone();
coord.terrainRttPosMatrix = new Float64Array(16) as any;
mat4.ortho(coord.terrainRttPosMatrix, 0, EXTENT, EXTENT, 0, 0, 1);
coords[key] = coord;
mat4.ortho(mat, 0, EXTENT, EXTENT, 0, 0, 1);
} else if (_tileID.canonical.isChildOf(tileID.canonical)) {
const coord = tileID.clone();
coord.terrainRttPosMatrix = new Float64Array(16) as any;
const dz = _tileID.canonical.z - tileID.canonical.z;
const dx = _tileID.canonical.x - (_tileID.canonical.x >> dz << dz);
const dy = _tileID.canonical.y - (_tileID.canonical.y >> dz << dz);
const size = EXTENT >> dz;
mat4.ortho(coord.terrainRttPosMatrix, 0, size, size, 0, 0, 1); // Note: we are using `size` instead of `EXTENT` here
mat4.translate(coord.terrainRttPosMatrix, coord.terrainRttPosMatrix, [-dx * size, -dy * size, 0]);
coords[key] = coord;
mat4.ortho(mat, 0, size, size, 0, 0, 1); // Note: we are using `size` instead of `EXTENT` here
mat4.translate(mat, mat, [-dx * size, -dy * size, 0]);
} else if (tileID.canonical.isChildOf(_tileID.canonical)) {
const coord = tileID.clone();
coord.terrainRttPosMatrix = new Float64Array(16) as any;
const dz = tileID.canonical.z - _tileID.canonical.z;
const dx = tileID.canonical.x - (tileID.canonical.x >> dz << dz);
const dy = tileID.canonical.y - (tileID.canonical.y >> dz << dz);
const size = EXTENT >> dz;
mat4.ortho(coord.terrainRttPosMatrix, 0, EXTENT, EXTENT, 0, 0, 1);
mat4.translate(coord.terrainRttPosMatrix, coord.terrainRttPosMatrix, [dx * size, dy * size, 0]);
mat4.scale(coord.terrainRttPosMatrix, coord.terrainRttPosMatrix, [1 / (2 ** dz), 1 / (2 ** dz), 0]);
coords[key] = coord;
mat4.ortho(mat, 0, EXTENT, EXTENT, 0, 0, 1);
mat4.translate(mat, mat, [dx * size, dy * size, 0]);
mat4.scale(mat, mat, [1 / (2 ** dz), 1 / (2 ** dz), 0]);
} else {
continue;
}
coord.terrainRttPosMatrix32f = new Float32Array(mat);
coords[key] = coord;
}
return coords;
}
Expand Down
5 changes: 3 additions & 2 deletions src/source/tile_id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ export class OverscaledTileID {
* This matrix is used during terrain's render-to-texture stage only.
* If the render-to-texture stage is active, this matrix will be present
* and should be used, otherwise this matrix will be null.
* The matrix should be float32 in order to avoid slow WebGL calls in Chrome.
*/
terrainRttPosMatrix: mat4 | null = null;
terrainRttPosMatrix32f: mat4 | null = null;

constructor(overscaledZ: number, wrap: number, z: number, x: number, y: number) {
if (overscaledZ < z) throw new Error(`overscaledZ should be >= z; overscaledZ = ${overscaledZ}; z = ${z}`);
Expand Down Expand Up @@ -223,4 +224,4 @@ function getQuadkey(z, x, y) {
}

register('CanonicalTileID', CanonicalTileID);
register('OverscaledTileID', OverscaledTileID, {omit: ['terrainRttPosMatrix']});
register('OverscaledTileID', OverscaledTileID, {omit: ['terrainRttPosMatrix32f']});
12 changes: 12 additions & 0 deletions src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export function createVec3f64(): vec3 { return new Float64Array(3) as any; }
* Returns a new 64 bit float mat4 of zeroes.
*/
export function createMat4f64(): mat4 { return new Float64Array(16) as any; }
/**
* Returns a new 32 bit float mat4 of zeroes.
*/
export function createMat4f32(): mat4 { return new Float32Array(16) as any; }
/**
* Returns a new 64 bit float mat4 set to identity.
*/
Expand All @@ -27,6 +31,14 @@ export function createIdentityMat4f64(): mat4 {
mat4.identity(m);
return m;
}
/**
* Returns a new 32 bit float mat4 set to identity.
*/
export function createIdentityMat4f32(): mat4 {
const m = new Float32Array(16) as any;
mat4.identity(m);
return m;
}

/**
* Returns a translation in tile units that correctly incorporates the view angle and the *-translate and *-translate-anchor properties.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.