Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## main
### ✨ Features and improvements
- _...Add new stuff here..._
- Optimize matrix inversions and reduce GPU stalls ([#7367](https://github.com/maplibre/maplibre-gl-js/pull/7367)) (by [@xavierjs](https://github.com/xavierjs))
- Add example showing how to measure map performance using built-in events (`load`, `idle`, `render`) ([#7077](https://github.com/maplibre/maplibre-gl-js/pull/7077)) (by [@CommanderStorm](https://github.com/CommanderStorm))

### 🐞 Bug fixes
Expand Down
19 changes: 10 additions & 9 deletions src/geo/projection/mercator_transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {EXTENT} from '../../data/extent';
import {TransformHelper} from '../transform_helper';
import {MercatorCoveringTilesDetailsProvider} from './mercator_covering_tiles_details_provider';
import {Frustum} from '../../util/primitives/frustum';
import {fastInvertProjMat4} from '../../util/fast_maths';

import type {Terrain} from '../../render/terrain';
import type {IReadonlyTransform, ITransform, TransformConstrainFunction} from '../transform_interface';
Expand Down Expand Up @@ -202,20 +203,20 @@ export class MercatorTransform implements ITransform {
get renderWorldCopies(): boolean {
return this._helper.renderWorldCopies;
}
get cameraToCenterDistance(): number {
get cameraToCenterDistance(): number {
return this._helper.cameraToCenterDistance;
}
get constrainOverride(): TransformConstrainFunction {
return this._helper.constrainOverride;
}
public get nearZ(): number {
return this._helper.nearZ;
public get nearZ(): number {
return this._helper.nearZ;
}
public get farZ(): number {
return this._helper.farZ;
public get farZ(): number {
return this._helper.farZ;
}
public get autoCalculateNearFarZ(): boolean {
return this._helper.autoCalculateNearFarZ;
public get autoCalculateNearFarZ(): boolean {
return this._helper.autoCalculateNearFarZ;
}
setTransitionState(_value: number, _error: number): void {
// Do nothing
Expand Down Expand Up @@ -611,7 +612,7 @@ export class MercatorTransform implements ITransform {
m = new Float64Array(16) as any;
mat4.perspective(m, this.fovInRadians, this._helper._width / this._helper._height, this._helper._nearZ, this._helper._farZ);
this._invProjMatrix = new Float64Array(16) as any as mat4;
mat4.invert(this._invProjMatrix, m);
fastInvertProjMat4(this._invProjMatrix, m);

// Apply center of perspective offset
m[8] = -offset.x * 2 / this._helper._width;
Expand Down Expand Up @@ -730,7 +731,7 @@ export class MercatorTransform implements ITransform {
const {overscaledTileID, aligned, applyTerrainMatrix} = params;
const mercatorTileCoordinates = this._helper.getMercatorTileCoordinates(overscaledTileID);
const tilePosMatrix = overscaledTileID ? this.calculatePosMatrix(overscaledTileID, aligned, true) : null;

let mainMatrix: mat4;
if (overscaledTileID && overscaledTileID.terrainRttPosMatrix32f && applyTerrainMatrix) {
mainMatrix = overscaledTileID.terrainRttPosMatrix32f;
Expand Down
10 changes: 2 additions & 8 deletions src/gl/webgl2.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
const cache = new WeakMap();
export function isWebGL2(
gl: WebGLRenderingContext | WebGL2RenderingContext
): gl is WebGL2RenderingContext {
if (cache.has(gl)) {
return cache.get(gl);
} else {
const value = gl.getParameter(gl.VERSION)?.startsWith('WebGL 2.0');
cache.set(gl, value);
return value;
}
// this method is really faster than fetching a cache:
return typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext;
}
5 changes: 4 additions & 1 deletion src/render/draw_color_relief.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function drawColorRelief(painter: Painter, tileManager: TileManager, laye
}
}

let textureMaxSize = 0;
function renderColorRelief(
painter: Painter,
tileManager: TileManager,
Expand Down Expand Up @@ -64,7 +65,9 @@ function renderColorRelief(
const tile = tileManager.getTile(coord);
const dem = tile.dem;
if(firstTile) {
const maxLength = gl.getParameter(gl.MAX_TEXTURE_SIZE);
// we should avoid calling gl.getParameter at runtime (GPU stall risk)
textureMaxSize = textureMaxSize || gl.getParameter(gl.MAX_TEXTURE_SIZE);
const maxLength = textureMaxSize;
const {elevationTexture, colorTexture} = layer.getColorRampTextures(context, maxLength, dem.getUnpackVector());
context.activeTexture.set(gl.TEXTURE1);
elevationTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE);
Expand Down
8 changes: 5 additions & 3 deletions src/render/draw_symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {mat4} from 'gl-matrix';
import {StencilMode} from '../gl/stencil_mode';
import {DepthMode} from '../gl/depth_mode';
import {CullFaceMode} from '../gl/cull_face_mode';
import {fastInvertTransformMat4} from '../util/fast_maths';
import {addDynamicAttributes} from '../data/bucket/symbol_bucket';

import {getAnchorAlignment, WritingMode} from '../symbol/shaping';
Expand Down Expand Up @@ -304,7 +305,7 @@ function drawLayerSymbols(
pitchAlignment: SymbolLayerSpecification['layout']['text-pitch-alignment'],
keepUpright: boolean,
stencilMode: StencilMode,
colorMode: Readonly<ColorMode>,
colorMode: Readonly<ColorMode>,
isRenderingToTexture: boolean) {

const context = painter.context;
Expand Down Expand Up @@ -376,8 +377,6 @@ function drawLayerSymbols(
// See the comment at the beginning of src/symbol/projection.ts for an overview of the symbol projection process
const s = pixelsToTileUnits(tile, 1, painter.transform.zoom);
const pitchedLabelPlaneMatrix = getPitchedLabelPlaneMatrix(rotateWithMap, painter.transform, s);
const pitchedLabelPlaneMatrixInverse = mat4.create();
mat4.invert(pitchedLabelPlaneMatrixInverse, pitchedLabelPlaneMatrix);
const glCoordMatrixForShader = getGlCoordMatrix(pitchWithMap, rotateWithMap, painter.transform, s);

const translation = translatePosition(transform, tile, translate, translateAnchor);
Expand All @@ -389,6 +388,9 @@ function drawLayerSymbols(
bucket.hasIconData();

if (alongLine) {
const pitchedLabelPlaneMatrixInverse = mat4.create();
fastInvertTransformMat4(pitchedLabelPlaneMatrixInverse, pitchedLabelPlaneMatrix);

const getElevation = painter.style.map.terrain ? (x: number, y: number) => painter.style.map.terrain.getElevation(coord, x, y) : null;
const rotateToLine = layer.layout.get('text-rotation-alignment') === 'map';
updateLineLabels(bucket, painter, isText, pitchedLabelPlaneMatrix, pitchedLabelPlaneMatrixInverse, pitchWithMap, keepUpright, rotateToLine, coord.toUnwrapped(), transform.width, transform.height, translation, getElevation);
Expand Down
5 changes: 3 additions & 2 deletions src/symbol/projection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {WritingMode} from '../symbol/shaping';
import {findLineIntersection} from '../util/util';
import {type UnwrappedTileID} from '../tile/tile_id';
import {type StructArray} from '../util/struct_array';
import {fastInvertSkewMat4} from '../util/fast_maths';

/**
* The result of projecting a point to the screen, with some additional information about the projection.
Expand Down Expand Up @@ -930,9 +931,9 @@ export function xyTransformMat4(out: vec4, a: vec4, m: mat4) {
* Returns a new array of the projected points.
* Does not modify the input array.
*/
const inverseLabelPlaneMatrix = mat4.create();
export function projectPathSpecialProjection(projectedPath: Array<Point>, projectionContext: SymbolProjectionContext): Array<PointProjection> {
const inverseLabelPlaneMatrix = mat4.create();
mat4.invert(inverseLabelPlaneMatrix, projectionContext.pitchedLabelPlaneMatrix);
fastInvertSkewMat4(inverseLabelPlaneMatrix, projectionContext.pitchedLabelPlaneMatrix);
return projectedPath.map(p => {
const backProjected = projectWithMatrix(p.x, p.y, inverseLabelPlaneMatrix, projectionContext.getElevation);
const projected = projectionContext.transform.projectTileCoordinates(
Expand Down
43 changes: 43 additions & 0 deletions src/util/fast_maths.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {test, expect} from 'vitest';
import {mat4, vec3, quat} from 'gl-matrix';
import {fastInvertTransformMat4} from './fast_maths';

test('fast_maths', () => {
// forge a transform matrix:
const m = mat4.create();

// translation vector:
const v = vec3.create();
v[0] = 1;
v[1] = 2;
v[2] = -3;

// scale vector. sx shoule be = sy:
const s = vec3.create();
s[0] = 10;
s[1] = 10;
s[2] = 1;

// quaternion:
const q = quat.create();
const axis = vec3.create();
axis[0] = 5;
axis[1] = 6;
axis[2] = 7;
vec3.normalize(axis, axis);
quat.setAxisAngle(q, axis, Math.PI/4);

mat4.fromRotationTranslationScale(m, q, v, s);

// compute inv:
const mInv = mat4.create();
fastInvertTransformMat4(mInv, m);

// compute ref:
const mInvRef = mat4.create();
mat4.invert(mInvRef, m);

for (let i=0; i<16; ++i){
expect(mInv[i]).toBeCloseTo(mInvRef[i], 6);
}
});
111 changes: 111 additions & 0 deletions src/util/fast_maths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
(xbourry): this class implements fast mathematical operations
*/
import type {mat4} from 'gl-matrix';

/*
Invert a transform matrix
dst and src are in column-major flat format
*/
export function fastInvertTransformMat4(dst: mat4, src: mat4): mat4{
// extract scale. hyp: scale X = scale Y:
const sXYSqInv = 1.0 / (src[0] * src[0] + src[1] * src[1] + src[2] * src[2]);
const sZSqInv = 1.0 / (src[8] * src[8] + src[9] * src[9] + src[10] * src[10]);

// inv rotation and scaling part:
const s0 = src[0] * sXYSqInv;
const s4 = src[4] * sXYSqInv;
const s8 = src[8] * sZSqInv;
const s1 = src[1] * sXYSqInv;
const s5 = src[5] * sXYSqInv;
const s9 = src[9] * sZSqInv;
const s2 = src[2] * sXYSqInv;
const s6 = src[6] * sXYSqInv;
const s10 = src[10] * sZSqInv;

// rotation part:
dst[0] = s0;
dst[1] = s4;
dst[2] = s8;
dst[4] = s1;
dst[5] = s5;
dst[6] = s9;
dst[8] = s2;
dst[9] = s6;
dst[10] = s10;

// translation part:
const t0 = src[12];
const t1 = src[13];
const t2 = src[14];
dst[12] = -s0 * t0 - s1 * t1 - s2 * t2;
dst[13] = -s4 * t0 - s5 * t1 - s6 * t2;
dst[14] = -s8 * t0 - s9 * t1 - s10 * t2;

// constant part:
dst[3] = 0;
dst[7] = 0;
dst[11] = 0;
dst[15] = 1;

return dst;
}

/*
Invert a perspective projection matrix
dst and src are in column-major flat format
*/
export function fastInvertProjMat4(dst: mat4, src: mat4): mat4{
dst[0] = 1 / src[0];
dst[1] = 0;
dst[2] = 0;
dst[3] = 0;

dst[4] = 0;
dst[5] = 1 / src[5];
dst[6] = 0;
dst[7] = 0;

dst[8] = 0;
dst[9] = 0;
dst[10] = 0;
dst[11] = 1 / src[14];

dst[12] = 0;
dst[13] = 0;
dst[14] = -1;
dst[15] = src[10] / src[14];

return dst;
}

/*
Invert a skew matrix
with diag coefficients > 0 and XY counterdiag
dst and src are in column-major flat format
*/

export function fastInvertSkewMat4(dst: mat4, src: mat4): mat4{
const det = src[0]*src[5] - src[1]*src[4];
dst[0] = src[5] / det;
dst[1] = -src[4] / det;
dst[2] = 0;
dst[3] = 0;

dst[4] = -src[1] / det;
dst[5] = src[0] / det;
dst[6] = 0;
dst[7] = 0;

dst[8] = 0;
dst[9] = 0;
dst[10] = 1 / src[10];
dst[11] = 0;

dst[12] = 0;
dst[13] = 0;
dst[14] = 0;
dst[15] = 1 / src[15];

return dst;
}
Loading