Skip to content
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
## 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))
- Add `touchZoomRotate.setZoomRate()` and `touchZoomRotate.setZoomThreshold()` to customize touch zoom speed and pinch sensitivity ([#7271](https://github.com/maplibre/maplibre-gl-js/issues/7271))

### 🐞 Bug fixes
- Fix `Popup` not updating its position when switching between terrain/globe projections ([#7468](https://github.com/maplibre/maplibre-gl-js/pull/7468)) (by [@CommanderStorm](https://github.com/CommanderStorm))
Expand Down
3 changes: 2 additions & 1 deletion 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 @@ -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
10 changes: 8 additions & 2 deletions src/symbol/projection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ 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';

/**
* Pre-allocate objects to avoid online allocation
*/
const tmpMat4 = mat4.create();
Comment thread
xavierjs marked this conversation as resolved.

/**
* The result of projecting a point to the screen, with some additional information about the projection.
Expand Down Expand Up @@ -931,8 +937,8 @@ export function xyTransformMat4(out: vec4, a: vec4, m: mat4) {
* Does not modify the input array.
*/
export function projectPathSpecialProjection(projectedPath: Point[], projectionContext: SymbolProjectionContext): PointProjection[] {
const inverseLabelPlaneMatrix = mat4.create();
mat4.invert(inverseLabelPlaneMatrix, projectionContext.pitchedLabelPlaneMatrix);
const inverseLabelPlaneMatrix = tmpMat4;
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
177 changes: 177 additions & 0 deletions src/util/fast_maths.test.ts
Comment thread
xavierjs marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import {describe, test, expect} from 'vitest';
import {mat4, vec3, quat} from 'gl-matrix';
import {fastInvertTransformMat4, fastInvertProjMat4, fastInvertSkewMat4} from './fast_maths';

function compare_matrix(mat: mat4, matRef: mat4){
Comment thread
xavierjs marked this conversation as resolved.
for (let i=0; i<16; ++i){
expect(mat[i]).toBeCloseTo(matRef[i], 6);
}
}

describe('fast_maths', () => {
test('invert a transform matrix', () => {
const testParams = [{
translate: [1, 2, -3],
scaleXY: 10,
rotAxis: [5, 6, 7],
rotAngle: Math.PI / 4
},{
translate: [0, 0, 0],
scaleXY: 1,
rotAxis: [0, 0, 1],
rotAngle: 0
},{
translate: [-50, 100, 0.5],
scaleXY: 0.01,
rotAxis: [1, 0, 0],
rotAngle: Math.PI
},{
translate: [1000, -2000, 500],
scaleXY: 200,
rotAxis: [0, 1, 0],
rotAngle: -Math.PI / 6
},{
translate: [-0.1, 0.2, -0.3],
scaleXY: 0.5,
rotAxis: [1, 1, 0],
rotAngle: Math.PI / 2
},{
translate: [0, 0, -9999],
scaleXY: 50,
rotAxis: [0.3, 0.4, 0.5],
rotAngle: -0.01
},{
translate: [42, -7, 13],
scaleXY: 4,
rotAxis: [1, -2, 3],
rotAngle: 2.1
}];
const m = mat4.create();
const mInv = mat4.create();
const mInvRef = mat4.create();
for (const {translate, scaleXY, rotAxis, rotAngle} of testParams) {
// translation vector:
const v = vec3.create();
vec3.copy(v, translate);

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

// quaternion:
const q = quat.create();
const axis = vec3.create();
vec3.copy(axis, rotAxis);
vec3.normalize(axis, axis);
quat.setAxisAngle(q, axis, rotAngle);
mat4.fromRotationTranslationScale(m, q, v, s);

// compute inv:
fastInvertTransformMat4(mInv, m);

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

compare_matrix(mInv, mInvRef);
};
});

test('invert a projection matrix', () => {
const testParams = [
{
fov: Math.PI/4,
aspect: 16/9,
zNear: 1,
zFar: 1000
},
{
fov: Math.PI/3,
aspect: 4/3,
zNear: 10,
zFar: 10000
},
{
fov: Math.PI/8,
aspect: 1,
zNear: 1,
zFar: 10
},
{
fov: Math.PI/4,
aspect: 2,
zNear: 1,
zFar: 1000
}
];
const m = mat4.create();
const mInv = mat4.create();
const mInvRef = mat4.create();
for (const {fov, aspect, zNear, zFar} of testParams) {
mat4.perspective(m, fov, aspect, zNear, zFar);
// compute inv:
fastInvertProjMat4(mInv, m);
// compute ref:
mat4.invert(mInvRef, m);
compare_matrix(mInv, mInvRef);
};
});

test('invert a skew matrix', () => {
const testParams = [{
diag: [1, 1, 1, 1],
counterDiagXY: [0, 0]
},{
diag: [2, 3, 4, 5],
counterDiagXY: [0.5, -0.5]
},{
diag: [0.1, 0.2, 0.3, 0.4],
counterDiagXY: [0.01, -0.01]
},{
diag: [10, 10, 10, 1],
counterDiagXY: [3, -3]
},{
diag: [1, 1, 1, 1],
counterDiagXY: [0.9, -0.9]
},{
diag: [100, 200, 50, 1],
counterDiagXY: [10, 20]
},{
diag: [0.5, 0.5, 0.5, 2],
counterDiagXY: [0, 0]
},{
diag: [7, 3, 5, 1],
counterDiagXY: [-2, 1]
},{
diag: [1, 1, 0.01, 1],
counterDiagXY: [0.5, 0.3]
},{
diag: [4, 8, 2, 1],
counterDiagXY: [-5, 3]
},{
diag: [0.3, 0.7, 1.5, 1],
counterDiagXY: [0.1, -0.2]
}];
const m = mat4.create();
const mInv = mat4.create();
const mInvRef = mat4.create();
for(const {diag, counterDiagXY} of testParams) {
// forge a skew matrix:
m[0] = diag[0];
m[5] = diag[1];
m[10] = diag[2];
m[15] = diag[3];
m[4] = counterDiagXY[0];
m[1] = counterDiagXY[1];

// compute inv:
fastInvertSkewMat4(mInv, m);

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

compare_matrix(mInv, mInvRef);
};
});
});
111 changes: 111 additions & 0 deletions src/util/fast_maths.ts
Comment thread
xavierjs marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
This class implements fast mathematical operations to optimize or complement gl-matrix
*/
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 invDetXY = 1.0 / (src[0]*src[5] - src[1]*src[4]);
dst[0] = src[5] * invDetXY;
dst[1] = -src[1] * invDetXY;
dst[2] = 0;
dst[3] = 0;

dst[4] = -src[4] * invDetXY;
dst[5] = src[0] * invDetXY;
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;
}
5 changes: 4 additions & 1 deletion src/webgl/draw/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
9 changes: 5 additions & 4 deletions src/webgl/draw/draw_symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {StencilMode} from '../stencil_mode';
import {DepthMode} from '../depth_mode';
import {CullFaceMode} from '../cull_face_mode';
import {addDynamicAttributes} from '../../data/bucket/symbol_bucket';

import {fastInvertTransformMat4} from '../../util/fast_maths';
import {getAnchorAlignment, WritingMode} from '../../symbol/shaping';
import ONE_EM from '../../symbol/one_em';

Expand Down Expand Up @@ -304,7 +304,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 +376,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 +387,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
Loading
Loading