Skip to content

Commit 34bd217

Browse files
stephanheiglgithub-actions[bot]
authored andcommitted
Extended PerformanceUtils markup functionality for Chrome DevTools
(internal-7705) GitOrigin-RevId: e018b20517a2036c4d3891f0fb8b84c88af78e35
1 parent 0a15d90 commit 34bd217

File tree

7 files changed

+104
-14
lines changed

7 files changed

+104
-14
lines changed

3d-style/util/loaders.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {loadBuildingGen} from './building_gen';
1010
import assert from 'assert';
1111
import {DracoDecoderModule} from './draco_decoder_gltf';
1212
import {MeshoptDecoder} from './meshopt_decoder';
13+
import {PerformanceUtils} from '../../src/util/performance';
1314

1415
import type {vec3, mat4, quat} from 'gl-matrix';
1516
import type {BuildingGen} from './building_gen';
@@ -52,6 +53,7 @@ export function setDracoUrl(url: string) {
5253
function waitForDraco() {
5354
if (draco) return;
5455
if (dracoLoading != null) return dracoLoading;
56+
const startTime = PerformanceUtils.now();
5557

5658
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
5759
dracoLoading = DracoDecoderModule(fetch(getDracoUrl()));
@@ -60,6 +62,7 @@ function waitForDraco() {
6062
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
6163
draco = module;
6264
dracoLoading = undefined;
65+
PerformanceUtils.measureWithDetails("waitForDraco", "Models", startTime);
6366
});
6467
}
6568

@@ -92,20 +95,23 @@ export function setMeshoptUrl(url: string) {
9295

9396
function waitForMeshopt() {
9497
if (meshopt) return;
98+
const startTime = PerformanceUtils.now();
9599
const decoder = MeshoptDecoder(fetch(getMeshoptUrl()));
96100
return decoder.ready.then(() => {
101+
PerformanceUtils.measureWithDetails("waitForMeshopt", "Models", startTime);
97102
meshopt = decoder;
98103
});
99104
}
100105

101106
export function waitForBuildingGen(): Promise<unknown> {
102107
if (buildingGen != null || buildingGenError != null) return null;
103108
if (buildingGenLoading != null) return buildingGenLoading;
104-
109+
const m = PerformanceUtils.now();
105110
const wasmData = fetch(config.BUILDING_GEN_URL);
106111
buildingGenLoading = loadBuildingGen(wasmData).then((instance) => {
107112
buildingGenLoading = null;
108113
buildingGen = instance;
114+
PerformanceUtils.measureWithDetails("waitForBuildingGen", "BuildingBucket", m);
109115
return buildingGen;
110116
}).catch((error) => {
111117
warnOnce('Could not load building-gen');
@@ -385,6 +391,8 @@ function loadImage(img: {uri?: string; bufferView?: number; mimeType: string}, g
385391
}
386392

387393
export function decodeGLTF(arrayBuffer: ArrayBuffer, byteOffset: number = 0, baseUrl?: string): Promise<GLTF | void> {
394+
const startTime = PerformanceUtils.now();
395+
388396
const gltf: GLTF = {json: null, images: [], buffers: []};
389397

390398
if (new Uint32Array(arrayBuffer, byteOffset, 1)[0] === MAGIC_GLTF) {
@@ -464,6 +472,8 @@ export function decodeGLTF(arrayBuffer: ArrayBuffer, byteOffset: number = 0, bas
464472
}
465473
}
466474

475+
PerformanceUtils.measureWithDetails("decodeGLTF", "Models", startTime);
476+
467477
return gltf;
468478
});
469479
});

src/render/painter.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import Framebuffer from '../gl/framebuffer';
5454
import {OcclusionParams} from './occlusion_params';
5555
import {Rain} from '../precipitation/draw_rain';
5656
import {Snow} from '../precipitation/draw_snow';
57+
import {PerformanceUtils} from '../util/performance';
5758

5859
import type ImageManager from './image_manager';
5960
import type IndexBuffer from '../gl/index_buffer';
@@ -758,6 +759,8 @@ class Painter {
758759
this._dt = curTime - this._timeStamp;
759760
this._timeStamp = curTime;
760761

762+
const renderStartTime = PerformanceUtils.now();
763+
761764
Debug.run(() => { this.updateAverageFPS(); });
762765

763766
// Update debug cache, i.e. clear all unused buffers
@@ -816,17 +819,21 @@ class Painter {
816819
let conflationSourcesInStyle = 0;
817820
let conflationActiveThisFrame = false;
818821

822+
const prepareStartTime = PerformanceUtils.now();
819823
for (const id in sourceCaches) {
820824
const sourceCache = sourceCaches[id];
821825
if (sourceCache.used) {
826+
const sourceCachePrepareStartTime = PerformanceUtils.now();
822827
sourceCache.prepare(this.context);
828+
PerformanceUtils.measureLowOverhead(`prepare: ${sourceCache.id.toString()}`, sourceCachePrepareStartTime, undefined);
823829

824830
// @ts-expect-error - TS2339 - Property 'usedInConflation' does not exist on type 'Source'.
825831
if (sourceCache.getSource().usedInConflation) {
826832
++conflationSourcesInStyle;
827833
}
828834
}
829835
}
836+
PerformanceUtils.measureLowOverhead('sourceCaches: prepare', prepareStartTime, undefined);
830837

831838
let clippingActiveThisFrame = false;
832839
for (const layer of orderedLayers) {
@@ -1098,8 +1105,10 @@ class Painter {
10981105

10991106
// Shadow pass ==================================================
11001107
if (this._shadowRenderer) {
1108+
const shadowPassStartTime = PerformanceUtils.now();
11011109
this.renderPass = 'shadow';
11021110
this._shadowRenderer.drawShadowPass(this.style, coordsShadowCasters);
1111+
PerformanceUtils.measureLowOverhead('Shadow Pass', shadowPassStartTime);
11031112
}
11041113

11051114
// Rebind the main framebuffer now that all offscreen layers have been rendered:
@@ -1145,7 +1154,7 @@ class Painter {
11451154
// Opaque pass ===============================================
11461155
// Draw opaque layers top-to-bottom first.
11471156
this.renderPass = 'opaque';
1148-
1157+
const opaquePassStartTime = PerformanceUtils.now();
11491158
if (this.style.fog && this.transform.projection.supportsFog && this._atmosphere && !this._showOverdrawInspector && shouldRenderAtmosphere) {
11501159
this._atmosphere.drawStars(this, this.style.fog);
11511160
}
@@ -1164,6 +1173,7 @@ class Painter {
11641173
if (this.style.fog && this.transform.projection.supportsFog && this._atmosphere && !this._showOverdrawInspector && shouldRenderAtmosphere) {
11651174
this._atmosphere.drawAtmosphereGlow(this, this.style.fog);
11661175
}
1176+
PerformanceUtils.measureLowOverhead('Opaque Pass', opaquePassStartTime);
11671177

11681178
// Sky pass ======================================================
11691179
// Draw all sky layers bottom to top.
@@ -1185,7 +1195,7 @@ class Painter {
11851195
// Translucent pass ===============================================
11861196
// Draw all other layers bottom-to-top.
11871197
this.renderPass = 'translucent';
1188-
1198+
const translucentPassStartTime = PerformanceUtils.now();
11891199
function coordsForTranslucentLayer(layer: TypedStyleLayer, sourceCache?: SourceCache) {
11901200
// For symbol layers in the translucent pass, we add extra tiles to the renderable set
11911201
// for cross-tile symbol fading. Symbol layers don't use tile clipping, so no need to render
@@ -1382,6 +1392,8 @@ class Painter {
13821392
if (this._rain) {
13831393
this._rain.draw(this);
13841394
}
1395+
PerformanceUtils.measureLowOverhead('Translucent Pass', translucentPassStartTime);
1396+
13851397
if (this.options.showTileBoundaries || this.options.showQueryGeometry || this.options.showTileAABBs) {
13861398
// Use source with highest maxzoom
13871399
let selectedSource = null;
@@ -1435,6 +1447,8 @@ class Painter {
14351447
if (!conflationActiveThisFrame) {
14361448
this.conflationActive = false;
14371449
}
1450+
1451+
PerformanceUtils.measureLowOverhead('Painter.render', renderStartTime);
14381452
}
14391453

14401454
prepareLayer(layer: TypedStyleLayer) {
@@ -1459,12 +1473,15 @@ class Painter {
14591473

14601474
this.id = layer.id;
14611475

1476+
const startTime = PerformanceUtils.now();
14621477
this.gpuTimingStart(layer);
14631478
if ((!painter.transform.projection.unsupportedLayers || !painter.transform.projection.unsupportedLayers.includes(layer.type) ||
14641479
(painter.terrain && layer.type === 'custom')) && layer.type !== 'clip') {
14651480
draw[layer.type](painter, sourceCache, layer, coords, this.style.placement.variableOffsets, this.options.isInitialLoad);
14661481
}
14671482
this.gpuTimingEnd();
1483+
// PerformanceUtils.measureLowOverhead(`${layer.type.toString()}:${layer.id.toString()}`, startTime, undefined);
1484+
PerformanceUtils.measureLowOverhead(`renderLayer: ${layer.type.toString()}`, startTime, undefined);
14681485
}
14691486

14701487
gpuTimingStart(layer: TypedStyleLayer) {

src/source/worker_tile.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ class WorkerTile {
301301
iconMap: null,
302302
glyphPositions: null
303303
});
304-
PerformanceUtils.endMeasure(m);
304+
PerformanceUtils.endMeasure(m, [["tileID", this.tileID.toString()], ["source", this.source]]);
305305
} else if (glyphMap && iconMap && patternMap) {
306306
const m = PerformanceUtils.beginMeasure('parseTile2');
307307
const glyphAtlas = new GlyphAtlas(glyphMap);
@@ -372,7 +372,7 @@ class WorkerTile {
372372
imageAtlas,
373373
brightness: options.brightness
374374
});
375-
PerformanceUtils.endMeasure(m);
375+
PerformanceUtils.endMeasure(m, [["tileID", this.tileID.toString()], ["source", this.source]]);
376376
};
377377

378378
if (!this.extraShadowCaster) {
@@ -455,7 +455,7 @@ class WorkerTile {
455455
}
456456
}
457457

458-
PerformanceUtils.endMeasure(m);
458+
PerformanceUtils.endMeasure(m, [["tileID", this.tileID.toString()], ["source", this.source]]);
459459

460460
maybePrepare();
461461
};

src/util/performance.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,66 @@ type PerformanceMarkOptions = {
5555
let fullLoadFinished = false;
5656
let placementTime = 0;
5757

58+
export type PerformanceMeasureDevToolsColor =
59+
"primary" | "primary-light" | "primary-dark"
60+
| "secondary" | "secondary-light" | "secondary-dark"
61+
| "tertiary" | "tertiary-light" | "tertiary-dark"
62+
| "error";
63+
64+
// To ensure there is not overlap in zones, use worker name or 'Main' as track and actual track name as trackgroup
65+
function trackNameOrDefault() {
66+
return (isWorker(self) && self.name) ? `${self.name}` : "Main";
67+
}
68+
5869
export const PerformanceUtils = {
70+
now() {
71+
return performance.now();
72+
},
73+
5974
mark(marker: PerformanceMarker, markOptions?: PerformanceMarkOptions) {
6075
performance.mark(marker, markOptions);
6176

6277
if (marker === LivePerformanceMarkers.fullLoad) {
6378
fullLoadFinished = true;
6479
}
6580
},
81+
82+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
83+
measureWithDetails(name: string, track: string, startTime: number, properties?: any[][], color?: PerformanceMeasureDevToolsColor) {
84+
performance.measure(name, {start: startTime, detail: {
85+
devtools: {
86+
trackGroup: track,
87+
track: trackNameOrDefault(),
88+
properties,
89+
color
90+
}
91+
}});
92+
},
93+
94+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
95+
markWithDetails(name: string, properties?: any[][], color?: PerformanceMeasureDevToolsColor) {
96+
performance.mark(name, {
97+
detail: {
98+
devtools: {
99+
dataType: "marker",
100+
color,
101+
properties,
102+
}
103+
}
104+
});
105+
},
106+
107+
// Based on console.timeStamp()
108+
// Records timing measures to DevTools performance panel only
109+
// Low overhead, but not recorded on Chrome timeline.
110+
measureLowOverhead(label: string,
111+
start?: string | number,
112+
end?: string | number,
113+
trackName?: string) {
114+
// @ts-expect-error: TS2554 Chrome extension of console.timeStamp
115+
console.timeStamp(label, start, end !== undefined ? end : performance.now(), trackNameOrDefault());
116+
},
117+
66118
measure(name: string, begin?: string, end?: string) {
67119
performance.measure(name, begin, end);
68120
},
@@ -74,8 +126,17 @@ export const PerformanceUtils = {
74126
name
75127
};
76128
},
77-
endMeasure(m: PerformanceMark) {
78-
performance.measure(m.name, m.mark);
129+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
130+
endMeasure(m: PerformanceMark, properties?: any[][]) {
131+
performance.measure(m.name, {
132+
start: m.mark,
133+
detail: {
134+
devtools: {
135+
track: trackNameOrDefault(),
136+
properties
137+
}
138+
}
139+
});
79140
},
80141
recordPlacementTime(time: number) {
81142
// Ignore placementTimes during loading

src/util/web_worker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import WorkerClass from './worker_class';
22

3-
export function createWorker(): Worker {
3+
export function createWorker(name?: string): Worker {
44
// eslint-disable-next-line new-cap
5-
return WorkerClass.workerClass != null ? new WorkerClass.workerClass() : new self.Worker(WorkerClass.workerUrl, WorkerClass.workerParams);
5+
return WorkerClass.workerClass != null ? new WorkerClass.workerClass() : new self.Worker(WorkerClass.workerUrl, Object.assign({name}, WorkerClass.workerParams));
66
}

src/util/worker_pool.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ export default class WorkerPool {
1111

1212
active: Partial<Record<number | string, boolean>>;
1313
workers: Array<Worker>;
14-
15-
constructor() {
14+
name?: string;
15+
constructor(name?: string) {
1616
this.active = {};
17+
this.name = name;
1718
}
1819

1920
acquire(mapId: number | string, count = WorkerPool.workerCount): Array<Worker> {
@@ -22,7 +23,8 @@ export default class WorkerPool {
2223
// client code has had a chance to set it.
2324
this.workers = [];
2425
while (this.workers.length < count) {
25-
this.workers.push(createWorker());
26+
const w = createWorker(`${this.name || ''}WorkerPool: ${mapId}-${this.workers.length}`);
27+
this.workers.push(w);
2628
}
2729
}
2830

src/util/worker_pool_factory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function getGlobalWorkerPool(): WorkerPool {
2020
// - To unblock tiles worker pool when image rasterization is in progress
2121
export function getImageRasterizerWorkerPool(): WorkerPool {
2222
if (!imageRasterizerWorkerPool) {
23-
imageRasterizerWorkerPool = new WorkerPool();
23+
imageRasterizerWorkerPool = new WorkerPool('ImageRasterizer');
2424
}
2525

2626
return imageRasterizerWorkerPool;

0 commit comments

Comments
 (0)