@gnsx/three 0.185.9 → 0.185.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/three.cjs +980 -2
- package/build/three.core.js +980 -2
- package/build/three.core.min.js +1 -1
- package/build/three.webgpu.js +215 -68
- package/build/three.webgpu.min.js +1 -1
- package/build/three.webgpu.nodes.js +215 -68
- package/build/three.webgpu.nodes.min.js +1 -1
- package/package.json +1 -1
- package/src/profiler/ProfilerService.js +982 -2
- package/src/renderers/common/Backend.js +60 -0
- package/src/renderers/common/RenderContext.js +10 -0
- package/src/renderers/common/Renderer.js +20 -1
- package/src/renderers/common/TimestampQueryPool.js +23 -0
- package/src/renderers/webgl-fallback/WebGLBackend.js +14 -3
- package/src/renderers/webgl-fallback/utils/WebGLTimestampQueryPool.js +8 -4
- package/src/renderers/webgpu/WebGPUBackend.js +37 -8
- package/src/renderers/webgpu/utils/WebGPUTimestampQueryPool.js +44 -5
package/build/three.core.js
CHANGED
|
@@ -60003,6 +60003,7 @@ class TextureUtils {
|
|
|
60003
60003
|
}
|
|
60004
60004
|
|
|
60005
60005
|
// WITH_GENESYS
|
|
60006
|
+
|
|
60006
60007
|
/**
|
|
60007
60008
|
* ProfilerService — per-label CPU timing with ring-buffer aggregation and DevTools integration.
|
|
60008
60009
|
*
|
|
@@ -60023,12 +60024,16 @@ const RING_SIZE = 120;
|
|
|
60023
60024
|
const FRAME_BUDGET_MS = 1000 / 60;
|
|
60024
60025
|
const TRACE_TID_MAIN = 1;
|
|
60025
60026
|
const TRACE_TID_ASYNC = 2;
|
|
60027
|
+
const TRACE_TID_GPU = 3;
|
|
60026
60028
|
const NOOP = () => {};
|
|
60027
60029
|
|
|
60028
60030
|
const DUMMY_SPAN = Object.freeze( { label: '', t0: 0, _seq: 0 } );
|
|
60031
|
+
const DUMMY_GPU_SPAN = Object.freeze( { label: '', renderer: null, t0: 0, _seq: 0, _generation: 0 } );
|
|
60029
60032
|
const NOOP_BEGIN_SPAN = () => DUMMY_SPAN;
|
|
60030
60033
|
const NOOP_END_SPAN = () => {};
|
|
60031
60034
|
|
|
60035
|
+
const NOOP_BEGIN_GPU_SPAN = () => DUMMY_GPU_SPAN;
|
|
60036
|
+
|
|
60032
60037
|
class ProfilerServiceClass {
|
|
60033
60038
|
|
|
60034
60039
|
constructor() {
|
|
@@ -60063,6 +60068,18 @@ class ProfilerServiceClass {
|
|
|
60063
60068
|
/** @type {Map<string, number>} Total (uncapped) invocation count since last reset, for calls-per-frame. */
|
|
60064
60069
|
this.invocations = new Map();
|
|
60065
60070
|
|
|
60071
|
+
/** @type {Map<string, Float64Array>} GPU time ring buffers (one per label). */
|
|
60072
|
+
this.gpuBuffers = new Map();
|
|
60073
|
+
/** @type {Map<string, number>} */
|
|
60074
|
+
this.gpuCursors = new Map();
|
|
60075
|
+
/** @type {Map<string, number>} */
|
|
60076
|
+
this.gpuCounts = new Map();
|
|
60077
|
+
/** @type {Map<string, number>} */
|
|
60078
|
+
this.gpuInvocations = new Map();
|
|
60079
|
+
/** @type {Map<Object, import('./ProfilerService.js').GpuRendererState>} */
|
|
60080
|
+
this.gpuRendererStates = new Map();
|
|
60081
|
+
this._gpuGeneration = 1;
|
|
60082
|
+
|
|
60066
60083
|
if ( this._enabled ) {
|
|
60067
60084
|
|
|
60068
60085
|
this.traceStartTime = performance.now();
|
|
@@ -60070,6 +60087,8 @@ class ProfilerServiceClass {
|
|
|
60070
60087
|
this.end = this._endImpl.bind( this );
|
|
60071
60088
|
this.beginSpan = this._beginSpanImpl.bind( this );
|
|
60072
60089
|
this.endSpan = this._endSpanImpl.bind( this );
|
|
60090
|
+
this.beginGpu = this._beginGpuImpl.bind( this );
|
|
60091
|
+
this.endGpu = this._endGpuImpl.bind( this );
|
|
60073
60092
|
this._exposeGlobal();
|
|
60074
60093
|
console.log( `[ProfilerService] auto-started from env (profile: ${this._profile})` );
|
|
60075
60094
|
|
|
@@ -60079,6 +60098,8 @@ class ProfilerServiceClass {
|
|
|
60079
60098
|
this.end = NOOP;
|
|
60080
60099
|
this.beginSpan = NOOP_BEGIN_SPAN;
|
|
60081
60100
|
this.endSpan = NOOP_END_SPAN;
|
|
60101
|
+
this.beginGpu = NOOP_BEGIN_GPU_SPAN;
|
|
60102
|
+
this.endGpu = NOOP_END_SPAN;
|
|
60082
60103
|
|
|
60083
60104
|
}
|
|
60084
60105
|
|
|
@@ -60112,6 +60133,8 @@ class ProfilerServiceClass {
|
|
|
60112
60133
|
this.end = this._endImpl.bind( this );
|
|
60113
60134
|
this.beginSpan = this._beginSpanImpl.bind( this );
|
|
60114
60135
|
this.endSpan = this._endSpanImpl.bind( this );
|
|
60136
|
+
this.beginGpu = this._beginGpuImpl.bind( this );
|
|
60137
|
+
this.endGpu = this._endGpuImpl.bind( this );
|
|
60115
60138
|
this._exposeGlobal();
|
|
60116
60139
|
console.log( `[ProfilerService] enabled (profile: ${this._profile}) — call __gnsx_profiler.report() or downloadTrace() from the console` );
|
|
60117
60140
|
|
|
@@ -60125,6 +60148,9 @@ class ProfilerServiceClass {
|
|
|
60125
60148
|
this.end = NOOP;
|
|
60126
60149
|
this.beginSpan = NOOP_BEGIN_SPAN;
|
|
60127
60150
|
this.endSpan = NOOP_END_SPAN;
|
|
60151
|
+
this.beginGpu = NOOP_BEGIN_GPU_SPAN;
|
|
60152
|
+
this.endGpu = NOOP_END_SPAN;
|
|
60153
|
+
this._detachGpuRenderers();
|
|
60128
60154
|
console.log( '[ProfilerService] disabled' );
|
|
60129
60155
|
|
|
60130
60156
|
}
|
|
@@ -60145,6 +60171,687 @@ class ProfilerServiceClass {
|
|
|
60145
60171
|
|
|
60146
60172
|
}
|
|
60147
60173
|
|
|
60174
|
+
/**
|
|
60175
|
+
* Enables GPU timestamp collection for a renderer.
|
|
60176
|
+
*
|
|
60177
|
+
* @param {Object} renderer
|
|
60178
|
+
* @return {Promise<boolean>} Whether GPU timestamps are available.
|
|
60179
|
+
*/
|
|
60180
|
+
async attachGpuRenderer( renderer ) {
|
|
60181
|
+
|
|
60182
|
+
if ( this._enabled === false || renderer === null || renderer === undefined ) return false;
|
|
60183
|
+
|
|
60184
|
+
const existingState = this.gpuRendererStates.get( renderer );
|
|
60185
|
+
if ( existingState !== undefined ) return existingState.available;
|
|
60186
|
+
|
|
60187
|
+
if ( renderer.isWebGLRenderer === true ) {
|
|
60188
|
+
|
|
60189
|
+
const gl = renderer.getContext();
|
|
60190
|
+
const extension = gl.getExtension( 'EXT_disjoint_timer_query_webgl2' );
|
|
60191
|
+
const state = {
|
|
60192
|
+
kind: 'legacy-webgl',
|
|
60193
|
+
available: extension !== null,
|
|
60194
|
+
renderer,
|
|
60195
|
+
gl,
|
|
60196
|
+
extension,
|
|
60197
|
+
activeSpan: null,
|
|
60198
|
+
pendingSpans: [],
|
|
60199
|
+
flushPromise: null,
|
|
60200
|
+
flushScheduled: false,
|
|
60201
|
+
};
|
|
60202
|
+
this.gpuRendererStates.set( renderer, state );
|
|
60203
|
+
|
|
60204
|
+
if ( extension === null ) {
|
|
60205
|
+
|
|
60206
|
+
warnOnce( 'ProfilerService: EXT_disjoint_timer_query_webgl2 is unavailable; GPU profiling is disabled for this WebGLRenderer.' );
|
|
60207
|
+
|
|
60208
|
+
}
|
|
60209
|
+
|
|
60210
|
+
return state.available;
|
|
60211
|
+
|
|
60212
|
+
}
|
|
60213
|
+
|
|
60214
|
+
if ( renderer.isRenderer === true && renderer.backend !== undefined ) {
|
|
60215
|
+
|
|
60216
|
+
await renderer.init();
|
|
60217
|
+
const previousTrackTimestamp = renderer.backend.trackTimestamp;
|
|
60218
|
+
renderer.backend.trackTimestamp = true;
|
|
60219
|
+
|
|
60220
|
+
const available = renderer.backend.hasTimestamp === true && renderer.hasFeature( 'timestamp-query' ) === true;
|
|
60221
|
+
const listener = ( type, uid, label ) => this._captureGpuTimestampQuery( renderer, type, uid, label );
|
|
60222
|
+
const state = {
|
|
60223
|
+
kind: 'common',
|
|
60224
|
+
available,
|
|
60225
|
+
renderer,
|
|
60226
|
+
listener,
|
|
60227
|
+
previousTrackTimestamp,
|
|
60228
|
+
activeSpans: [],
|
|
60229
|
+
pendingSpans: [],
|
|
60230
|
+
queryCpuTimes: new Map(),
|
|
60231
|
+
gpuTimestampOrigin: null,
|
|
60232
|
+
gpuTraceOrigin: null,
|
|
60233
|
+
flushPromise: null,
|
|
60234
|
+
flushScheduled: false,
|
|
60235
|
+
};
|
|
60236
|
+
this.gpuRendererStates.set( renderer, state );
|
|
60237
|
+
|
|
60238
|
+
if ( available ) {
|
|
60239
|
+
|
|
60240
|
+
renderer.backend.addTimestampQueryListener( listener );
|
|
60241
|
+
|
|
60242
|
+
} else {
|
|
60243
|
+
|
|
60244
|
+
warnOnce( 'ProfilerService: Timestamp queries are unavailable; GPU profiling is disabled for this renderer.' );
|
|
60245
|
+
|
|
60246
|
+
}
|
|
60247
|
+
|
|
60248
|
+
return available;
|
|
60249
|
+
|
|
60250
|
+
}
|
|
60251
|
+
|
|
60252
|
+
warnOnce( 'ProfilerService: Unsupported renderer; expected Renderer or WebGLRenderer.' );
|
|
60253
|
+
return false;
|
|
60254
|
+
|
|
60255
|
+
}
|
|
60256
|
+
|
|
60257
|
+
/**
|
|
60258
|
+
* @param {string} label
|
|
60259
|
+
* @param {Object} renderer
|
|
60260
|
+
* @return {import('./ProfilerService.js').GpuSpanHandle}
|
|
60261
|
+
*/
|
|
60262
|
+
_beginGpuImpl( label, renderer ) {
|
|
60263
|
+
|
|
60264
|
+
const state = this.gpuRendererStates.get( renderer );
|
|
60265
|
+
if ( state === undefined || state.available === false ) return DUMMY_GPU_SPAN;
|
|
60266
|
+
|
|
60267
|
+
const handle = {
|
|
60268
|
+
label,
|
|
60269
|
+
renderer,
|
|
60270
|
+
t0: performance.now(),
|
|
60271
|
+
_seq: ++ this._markId,
|
|
60272
|
+
_generation: this._gpuGeneration,
|
|
60273
|
+
};
|
|
60274
|
+
|
|
60275
|
+
if ( state.kind === 'common' ) {
|
|
60276
|
+
|
|
60277
|
+
handle._queries = {
|
|
60278
|
+
[ TimestampQuery.RENDER ]: new Set(),
|
|
60279
|
+
[ TimestampQuery.COMPUTE ]: new Set(),
|
|
60280
|
+
};
|
|
60281
|
+
state.activeSpans.push( handle );
|
|
60282
|
+
return handle;
|
|
60283
|
+
|
|
60284
|
+
}
|
|
60285
|
+
|
|
60286
|
+
if ( state.activeSpan !== null ) {
|
|
60287
|
+
|
|
60288
|
+
warnOnce( 'ProfilerService: Nested GPU spans are unsupported by legacy WebGLRenderer.' );
|
|
60289
|
+
return DUMMY_GPU_SPAN;
|
|
60290
|
+
|
|
60291
|
+
}
|
|
60292
|
+
|
|
60293
|
+
const query = state.gl.createQuery();
|
|
60294
|
+
if ( query === null ) return DUMMY_GPU_SPAN;
|
|
60295
|
+
|
|
60296
|
+
try {
|
|
60297
|
+
|
|
60298
|
+
state.gl.beginQuery( state.extension.TIME_ELAPSED_EXT, query );
|
|
60299
|
+
handle._query = query;
|
|
60300
|
+
state.activeSpan = handle;
|
|
60301
|
+
return handle;
|
|
60302
|
+
|
|
60303
|
+
} catch ( error ) {
|
|
60304
|
+
|
|
60305
|
+
state.gl.deleteQuery( query );
|
|
60306
|
+
warnOnce( `ProfilerService: Unable to begin a WebGL GPU span (${error.message}).` );
|
|
60307
|
+
return DUMMY_GPU_SPAN;
|
|
60308
|
+
|
|
60309
|
+
}
|
|
60310
|
+
|
|
60311
|
+
}
|
|
60312
|
+
|
|
60313
|
+
/**
|
|
60314
|
+
* @param {import('./ProfilerService.js').GpuSpanHandle} handle
|
|
60315
|
+
*/
|
|
60316
|
+
_endGpuImpl( handle ) {
|
|
60317
|
+
|
|
60318
|
+
if ( handle._seq === 0 || handle._generation !== this._gpuGeneration ) return;
|
|
60319
|
+
|
|
60320
|
+
const state = this.gpuRendererStates.get( handle.renderer );
|
|
60321
|
+
if ( state === undefined || state.available === false ) return;
|
|
60322
|
+
|
|
60323
|
+
handle._endTime = performance.now();
|
|
60324
|
+
|
|
60325
|
+
if ( state.kind === 'common' ) {
|
|
60326
|
+
|
|
60327
|
+
const index = state.activeSpans.indexOf( handle );
|
|
60328
|
+
if ( index === -1 ) return;
|
|
60329
|
+
|
|
60330
|
+
state.activeSpans.splice( index, 1 );
|
|
60331
|
+
if ( handle._queries.render.size > 0 || handle._queries.compute.size > 0 ) {
|
|
60332
|
+
|
|
60333
|
+
state.pendingSpans.push( handle );
|
|
60334
|
+
this._scheduleGpuFlush( state );
|
|
60335
|
+
|
|
60336
|
+
}
|
|
60337
|
+
|
|
60338
|
+
return;
|
|
60339
|
+
|
|
60340
|
+
}
|
|
60341
|
+
|
|
60342
|
+
if ( state.activeSpan !== handle ) return;
|
|
60343
|
+
|
|
60344
|
+
try {
|
|
60345
|
+
|
|
60346
|
+
state.gl.endQuery( state.extension.TIME_ELAPSED_EXT );
|
|
60347
|
+
state.activeSpan = null;
|
|
60348
|
+
state.pendingSpans.push( handle );
|
|
60349
|
+
this._scheduleGpuFlush( state );
|
|
60350
|
+
|
|
60351
|
+
} catch ( error ) {
|
|
60352
|
+
|
|
60353
|
+
state.activeSpan = null;
|
|
60354
|
+
state.gl.deleteQuery( handle._query );
|
|
60355
|
+
warnOnce( `ProfilerService: Unable to end a WebGL GPU span (${error.message}).` );
|
|
60356
|
+
|
|
60357
|
+
}
|
|
60358
|
+
|
|
60359
|
+
}
|
|
60360
|
+
|
|
60361
|
+
/**
|
|
60362
|
+
* Resolves pending GPU timestamp queries for a renderer.
|
|
60363
|
+
*
|
|
60364
|
+
* @param {Object} renderer
|
|
60365
|
+
* @return {Promise<void>}
|
|
60366
|
+
*/
|
|
60367
|
+
async flushGpu( renderer ) {
|
|
60368
|
+
|
|
60369
|
+
const state = this.gpuRendererStates.get( renderer );
|
|
60370
|
+
if ( state === undefined || state.available === false ) return;
|
|
60371
|
+
if ( state.flushPromise !== null ) return state.flushPromise;
|
|
60372
|
+
|
|
60373
|
+
state.flushPromise = state.kind === 'common'
|
|
60374
|
+
? this._flushCommonGpuState( state )
|
|
60375
|
+
: this._flushLegacyWebGLState( state );
|
|
60376
|
+
|
|
60377
|
+
try {
|
|
60378
|
+
|
|
60379
|
+
await state.flushPromise;
|
|
60380
|
+
|
|
60381
|
+
} finally {
|
|
60382
|
+
|
|
60383
|
+
state.flushPromise = null;
|
|
60384
|
+
if ( state.pendingSpans.length > 0 ) this._scheduleGpuFlush( state );
|
|
60385
|
+
|
|
60386
|
+
}
|
|
60387
|
+
|
|
60388
|
+
}
|
|
60389
|
+
|
|
60390
|
+
/**
|
|
60391
|
+
* @param {Object} renderer
|
|
60392
|
+
* @param {'render'|'compute'} type
|
|
60393
|
+
* @param {string} uid
|
|
60394
|
+
* @param {?string} [label=null]
|
|
60395
|
+
*/
|
|
60396
|
+
_captureGpuTimestampQuery( renderer, type, uid, label = null ) {
|
|
60397
|
+
|
|
60398
|
+
const state = this.gpuRendererStates.get( renderer );
|
|
60399
|
+
if ( state === undefined || state.kind !== 'common' ) return;
|
|
60400
|
+
|
|
60401
|
+
const cpuTime = performance.now();
|
|
60402
|
+
state.queryCpuTimes.set( uid, cpuTime );
|
|
60403
|
+
|
|
60404
|
+
for ( const span of state.activeSpans ) {
|
|
60405
|
+
|
|
60406
|
+
span._queries[ type ].add( uid );
|
|
60407
|
+
|
|
60408
|
+
}
|
|
60409
|
+
|
|
60410
|
+
if ( label !== null && label !== undefined && label !== '' ) {
|
|
60411
|
+
|
|
60412
|
+
// Queue labelled passes with the active beginGpu span(s). Flushing
|
|
60413
|
+
// immediately would commit them before their parent envelope exists and
|
|
60414
|
+
// can anchor gpuTraceOrigin mid-frame, which breaks Speedscope nesting.
|
|
60415
|
+
const passSpan = {
|
|
60416
|
+
label: `GPU pass: ${label}`,
|
|
60417
|
+
renderer,
|
|
60418
|
+
t0: cpuTime,
|
|
60419
|
+
_seq: ++ this._markId,
|
|
60420
|
+
_generation: this._gpuGeneration,
|
|
60421
|
+
_queries: {
|
|
60422
|
+
[ TimestampQuery.RENDER ]: new Set(),
|
|
60423
|
+
[ TimestampQuery.COMPUTE ]: new Set(),
|
|
60424
|
+
},
|
|
60425
|
+
};
|
|
60426
|
+
passSpan._queries[ type ].add( uid );
|
|
60427
|
+
state.pendingSpans.push( passSpan );
|
|
60428
|
+
// Only auto-flush when no beginGpu parent is open. Nested parents flush on endGpu
|
|
60429
|
+
// so pass slices share one origin and nest inside the parent envelope.
|
|
60430
|
+
if ( state.activeSpans.length === 0 ) {
|
|
60431
|
+
|
|
60432
|
+
this._scheduleGpuFlush( state );
|
|
60433
|
+
|
|
60434
|
+
}
|
|
60435
|
+
|
|
60436
|
+
}
|
|
60437
|
+
|
|
60438
|
+
}
|
|
60439
|
+
|
|
60440
|
+
/**
|
|
60441
|
+
* @param {import('./ProfilerService.js').GpuRendererState} state
|
|
60442
|
+
*/
|
|
60443
|
+
_scheduleGpuFlush( state ) {
|
|
60444
|
+
|
|
60445
|
+
if ( state.flushScheduled ) return;
|
|
60446
|
+
state.flushScheduled = true;
|
|
60447
|
+
|
|
60448
|
+
const callback = () => {
|
|
60449
|
+
|
|
60450
|
+
state.flushScheduled = false;
|
|
60451
|
+
if ( this.gpuRendererStates.get( state.renderer ) === state ) {
|
|
60452
|
+
|
|
60453
|
+
this.flushGpu( state.renderer ).catch( error => {
|
|
60454
|
+
|
|
60455
|
+
warnOnce( `ProfilerService: Unable to resolve GPU timestamps (${error.message}).` );
|
|
60456
|
+
|
|
60457
|
+
} );
|
|
60458
|
+
|
|
60459
|
+
}
|
|
60460
|
+
|
|
60461
|
+
};
|
|
60462
|
+
|
|
60463
|
+
if ( typeof requestAnimationFrame === 'function' ) {
|
|
60464
|
+
|
|
60465
|
+
requestAnimationFrame( callback );
|
|
60466
|
+
|
|
60467
|
+
} else {
|
|
60468
|
+
|
|
60469
|
+
setTimeout( callback, 0 );
|
|
60470
|
+
|
|
60471
|
+
}
|
|
60472
|
+
|
|
60473
|
+
}
|
|
60474
|
+
|
|
60475
|
+
/**
|
|
60476
|
+
* @param {import('./ProfilerService.js').CommonGpuRendererState} state
|
|
60477
|
+
*/
|
|
60478
|
+
async _flushCommonGpuState( state ) {
|
|
60479
|
+
|
|
60480
|
+
const spans = state.pendingSpans.splice( 0 );
|
|
60481
|
+
if ( spans.length === 0 ) return;
|
|
60482
|
+
|
|
60483
|
+
const hasRenderQueries = spans.some( span => span._queries.render.size > 0 );
|
|
60484
|
+
const hasComputeQueries = spans.some( span => span._queries.compute.size > 0 );
|
|
60485
|
+
const resolutions = [];
|
|
60486
|
+
|
|
60487
|
+
if ( hasRenderQueries ) resolutions.push( state.renderer.resolveTimestampsAsync( TimestampQuery.RENDER ) );
|
|
60488
|
+
if ( hasComputeQueries ) resolutions.push( state.renderer.resolveTimestampsAsync( TimestampQuery.COMPUTE ) );
|
|
60489
|
+
await Promise.all( resolutions );
|
|
60490
|
+
|
|
60491
|
+
if ( this._enabled === false ) return;
|
|
60492
|
+
|
|
60493
|
+
if ( state.gpuTimestampOrigin === null ) {
|
|
60494
|
+
|
|
60495
|
+
const origin = this._findEarliestGpuTimestampOrigin( state, spans );
|
|
60496
|
+
if ( origin !== null ) {
|
|
60497
|
+
|
|
60498
|
+
state.gpuTimestampOrigin = origin.gpuTimestampOrigin;
|
|
60499
|
+
state.gpuTraceOrigin = origin.gpuTraceOrigin;
|
|
60500
|
+
|
|
60501
|
+
}
|
|
60502
|
+
|
|
60503
|
+
}
|
|
60504
|
+
|
|
60505
|
+
const resolvedUids = new Set();
|
|
60506
|
+
/** @type {Array<{ label: string, startTime: number, durationMs: number, isPass: boolean }>} */
|
|
60507
|
+
const samples = [];
|
|
60508
|
+
/** @type {Set<string>} */
|
|
60509
|
+
const labelledQueryUids = new Set();
|
|
60510
|
+
|
|
60511
|
+
for ( const span of spans ) {
|
|
60512
|
+
|
|
60513
|
+
if ( span._generation !== this._gpuGeneration ) continue;
|
|
60514
|
+
|
|
60515
|
+
let duration = 0;
|
|
60516
|
+
let resolvedQueries = 0;
|
|
60517
|
+
let rangedQueries = 0;
|
|
60518
|
+
let gpuStart = null;
|
|
60519
|
+
let gpuEnd = null;
|
|
60520
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60521
|
+
|
|
60522
|
+
for ( const uid of span._queries[ type ] ) {
|
|
60523
|
+
|
|
60524
|
+
if ( state.renderer.backend.hasTimestampQuery( uid ) ) {
|
|
60525
|
+
|
|
60526
|
+
duration += state.renderer.backend.getTimestamp( uid );
|
|
60527
|
+
resolvedQueries ++;
|
|
60528
|
+
resolvedUids.add( uid );
|
|
60529
|
+
|
|
60530
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
60531
|
+
if ( range !== null ) {
|
|
60532
|
+
|
|
60533
|
+
rangedQueries ++;
|
|
60534
|
+
if ( gpuStart === null || range.start < gpuStart ) gpuStart = range.start;
|
|
60535
|
+
if ( gpuEnd === null || range.end > gpuEnd ) gpuEnd = range.end;
|
|
60536
|
+
|
|
60537
|
+
}
|
|
60538
|
+
|
|
60539
|
+
}
|
|
60540
|
+
|
|
60541
|
+
}
|
|
60542
|
+
|
|
60543
|
+
}
|
|
60544
|
+
|
|
60545
|
+
if ( resolvedQueries > 0 ) {
|
|
60546
|
+
|
|
60547
|
+
let startTime = span.t0;
|
|
60548
|
+
// Prefer the device envelope whenever any query has a range. Requiring
|
|
60549
|
+
// *all* queries to be ranged forced a CPU-t0 + summed-duration fallback
|
|
60550
|
+
// that often ended before later GPU pass slices, breaking flame nesting.
|
|
60551
|
+
if ( rangedQueries > 0 && gpuStart !== null && gpuEnd !== null && state.gpuTimestampOrigin !== null ) {
|
|
60552
|
+
|
|
60553
|
+
duration = Number( gpuEnd - gpuStart ) / 1e6;
|
|
60554
|
+
startTime = state.gpuTraceOrigin + Number( gpuStart - state.gpuTimestampOrigin ) / 1e6;
|
|
60555
|
+
|
|
60556
|
+
}
|
|
60557
|
+
|
|
60558
|
+
const isPass = span.label.startsWith( 'GPU pass:' );
|
|
60559
|
+
if ( isPass ) {
|
|
60560
|
+
|
|
60561
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60562
|
+
|
|
60563
|
+
for ( const uid of span._queries[ type ] ) labelledQueryUids.add( uid );
|
|
60564
|
+
|
|
60565
|
+
}
|
|
60566
|
+
|
|
60567
|
+
}
|
|
60568
|
+
|
|
60569
|
+
samples.push( {
|
|
60570
|
+
label: span.label,
|
|
60571
|
+
startTime,
|
|
60572
|
+
durationMs: duration,
|
|
60573
|
+
isPass,
|
|
60574
|
+
} );
|
|
60575
|
+
|
|
60576
|
+
}
|
|
60577
|
+
|
|
60578
|
+
}
|
|
60579
|
+
|
|
60580
|
+
// Parent beginGpu spans collect every timestamp query, including renders that
|
|
60581
|
+
// had no gpuProfilerLabel. Surface those as pass slices so they are not empty
|
|
60582
|
+
// holes under the parent envelope.
|
|
60583
|
+
for ( const span of spans ) {
|
|
60584
|
+
|
|
60585
|
+
if ( span._generation !== this._gpuGeneration ) continue;
|
|
60586
|
+
if ( span.label.startsWith( 'GPU pass:' ) ) continue;
|
|
60587
|
+
if ( state.gpuTimestampOrigin === null ) continue;
|
|
60588
|
+
|
|
60589
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60590
|
+
|
|
60591
|
+
for ( const uid of span._queries[ type ] ) {
|
|
60592
|
+
|
|
60593
|
+
if ( labelledQueryUids.has( uid ) ) continue;
|
|
60594
|
+
if ( state.renderer.backend.hasTimestampQuery( uid ) === false ) continue;
|
|
60595
|
+
|
|
60596
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
60597
|
+
if ( range === null ) continue;
|
|
60598
|
+
|
|
60599
|
+
const durationMs = Number( range.end - range.start ) / 1e6;
|
|
60600
|
+
if ( durationMs <= 0 ) continue;
|
|
60601
|
+
|
|
60602
|
+
samples.push( {
|
|
60603
|
+
label: 'GPU pass: (unlabeled)',
|
|
60604
|
+
startTime: state.gpuTraceOrigin + Number( range.start - state.gpuTimestampOrigin ) / 1e6,
|
|
60605
|
+
durationMs,
|
|
60606
|
+
isPass: true,
|
|
60607
|
+
} );
|
|
60608
|
+
labelledQueryUids.add( uid );
|
|
60609
|
+
|
|
60610
|
+
}
|
|
60611
|
+
|
|
60612
|
+
}
|
|
60613
|
+
|
|
60614
|
+
}
|
|
60615
|
+
|
|
60616
|
+
const passSamples = samples.filter( sample => sample.isPass );
|
|
60617
|
+
const otherSamples = samples.filter( sample => sample.isPass === false );
|
|
60618
|
+
this._snapHalfOpenGpuPassTraceIntervals( passSamples );
|
|
60619
|
+
this._expandGpuParentTraceToContainPasses( otherSamples, passSamples );
|
|
60620
|
+
|
|
60621
|
+
for ( const sample of otherSamples ) {
|
|
60622
|
+
|
|
60623
|
+
this._commitGpuDurationSample( sample.label, sample.startTime, sample.durationMs, {
|
|
60624
|
+
ts: sample.traceTs,
|
|
60625
|
+
dur: sample.traceDur,
|
|
60626
|
+
} );
|
|
60627
|
+
|
|
60628
|
+
}
|
|
60629
|
+
|
|
60630
|
+
for ( const sample of passSamples ) {
|
|
60631
|
+
|
|
60632
|
+
this._commitGpuDurationSample( sample.label, sample.startTime, sample.durationMs, {
|
|
60633
|
+
ts: sample.traceTs,
|
|
60634
|
+
dur: sample.traceDur,
|
|
60635
|
+
} );
|
|
60636
|
+
|
|
60637
|
+
}
|
|
60638
|
+
|
|
60639
|
+
// Keep CPU times for queries still referenced by active parent spans.
|
|
60640
|
+
for ( const uid of resolvedUids ) {
|
|
60641
|
+
|
|
60642
|
+
if ( this._isGpuQueryReferencedByActiveSpans( state, uid ) ) continue;
|
|
60643
|
+
state.queryCpuTimes.delete( uid );
|
|
60644
|
+
|
|
60645
|
+
}
|
|
60646
|
+
|
|
60647
|
+
}
|
|
60648
|
+
|
|
60649
|
+
/**
|
|
60650
|
+
* Convert labelled GPU pass samples to non-overlapping half-open µs intervals for
|
|
60651
|
+
* Chrome/Speedscope traces. Stats keep the raw measured durations.
|
|
60652
|
+
*
|
|
60653
|
+
* @param {Array<{ startTime: number, durationMs: number, traceTs?: number, traceDur?: number }>} samples
|
|
60654
|
+
*/
|
|
60655
|
+
_snapHalfOpenGpuPassTraceIntervals( samples ) {
|
|
60656
|
+
|
|
60657
|
+
if ( samples.length === 0 ) return;
|
|
60658
|
+
|
|
60659
|
+
for ( const sample of samples ) {
|
|
60660
|
+
|
|
60661
|
+
sample.traceTs = Math.round( this._getTraceTimestamp( sample.startTime ) );
|
|
60662
|
+
sample.traceDur = Math.max( 1, Math.round( sample.durationMs * 1000 ) );
|
|
60663
|
+
|
|
60664
|
+
}
|
|
60665
|
+
|
|
60666
|
+
// Longer first at the same timestamp so the substantial pass keeps its start.
|
|
60667
|
+
samples.sort( ( a, b ) => a.traceTs - b.traceTs || b.traceDur - a.traceDur );
|
|
60668
|
+
|
|
60669
|
+
let cursor = Number.NEGATIVE_INFINITY;
|
|
60670
|
+
for ( const sample of samples ) {
|
|
60671
|
+
|
|
60672
|
+
if ( sample.traceTs < cursor ) {
|
|
60673
|
+
|
|
60674
|
+
const end = Math.max( sample.traceTs + sample.traceDur, cursor + 1 );
|
|
60675
|
+
sample.traceTs = cursor;
|
|
60676
|
+
sample.traceDur = Math.max( 1, end - sample.traceTs );
|
|
60677
|
+
|
|
60678
|
+
}
|
|
60679
|
+
|
|
60680
|
+
cursor = sample.traceTs + sample.traceDur;
|
|
60681
|
+
|
|
60682
|
+
}
|
|
60683
|
+
|
|
60684
|
+
}
|
|
60685
|
+
|
|
60686
|
+
/**
|
|
60687
|
+
* Speedscope nests by time containment on a thread. If a snapped GPU pass
|
|
60688
|
+
* sticks out past its beginGpu parent (rounding / half-open adjust), the
|
|
60689
|
+
* parent fails containment and is pushed onto a lower lane under renderFrame.
|
|
60690
|
+
* Expand overlapping parents so they strictly cover their pass children.
|
|
60691
|
+
*
|
|
60692
|
+
* @param {Array<{ startTime: number, durationMs: number, traceTs?: number, traceDur?: number }>} parents
|
|
60693
|
+
* @param {Array<{ traceTs: number, traceDur: number }>} passes
|
|
60694
|
+
*/
|
|
60695
|
+
_expandGpuParentTraceToContainPasses( parents, passes ) {
|
|
60696
|
+
|
|
60697
|
+
if ( parents.length === 0 || passes.length === 0 ) return;
|
|
60698
|
+
|
|
60699
|
+
for ( const parent of parents ) {
|
|
60700
|
+
|
|
60701
|
+
parent.traceTs = Math.round( this._getTraceTimestamp( parent.startTime ) );
|
|
60702
|
+
parent.traceDur = Math.max( 1, Math.round( parent.durationMs * 1000 ) );
|
|
60703
|
+
|
|
60704
|
+
const parentEnd = parent.traceTs + parent.traceDur;
|
|
60705
|
+
let minTs = parent.traceTs;
|
|
60706
|
+
let maxEnd = parentEnd;
|
|
60707
|
+
let touched = false;
|
|
60708
|
+
|
|
60709
|
+
for ( const pass of passes ) {
|
|
60710
|
+
|
|
60711
|
+
const passEnd = pass.traceTs + pass.traceDur;
|
|
60712
|
+
if ( pass.traceTs >= parentEnd || passEnd <= parent.traceTs ) continue;
|
|
60713
|
+
|
|
60714
|
+
touched = true;
|
|
60715
|
+
if ( pass.traceTs < minTs ) minTs = pass.traceTs;
|
|
60716
|
+
if ( passEnd > maxEnd ) maxEnd = passEnd;
|
|
60717
|
+
|
|
60718
|
+
}
|
|
60719
|
+
|
|
60720
|
+
if ( touched === false ) continue;
|
|
60721
|
+
|
|
60722
|
+
// Start 1µs before the first child so equal-start pairs still nest
|
|
60723
|
+
// (Speedscope sorts same-ts by longer-first; a child that begins
|
|
60724
|
+
// strictly after the parent is unambiguous).
|
|
60725
|
+
parent.traceTs = minTs > 0 ? minTs - 1 : minTs;
|
|
60726
|
+
parent.traceDur = Math.max( 1, maxEnd - parent.traceTs );
|
|
60727
|
+
|
|
60728
|
+
}
|
|
60729
|
+
|
|
60730
|
+
}
|
|
60731
|
+
|
|
60732
|
+
/**
|
|
60733
|
+
* @param {import('./ProfilerService.js').CommonGpuRendererState} state
|
|
60734
|
+
* @param {import('./ProfilerService.js').GpuSpanHandle[]} spans
|
|
60735
|
+
* @return {?{ gpuTimestampOrigin: bigint, gpuTraceOrigin: number }}
|
|
60736
|
+
*/
|
|
60737
|
+
_findEarliestGpuTimestampOrigin( state, spans ) {
|
|
60738
|
+
|
|
60739
|
+
let earliestRange = null;
|
|
60740
|
+
let earliestUid = null;
|
|
60741
|
+
const candidates = spans.concat( state.activeSpans );
|
|
60742
|
+
|
|
60743
|
+
for ( const span of candidates ) {
|
|
60744
|
+
|
|
60745
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60746
|
+
|
|
60747
|
+
const queries = span._queries?.[ type ];
|
|
60748
|
+
if ( queries === undefined ) continue;
|
|
60749
|
+
|
|
60750
|
+
for ( const uid of queries ) {
|
|
60751
|
+
|
|
60752
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
60753
|
+
if ( range !== null && ( earliestRange === null || range.start < earliestRange.start ) ) {
|
|
60754
|
+
|
|
60755
|
+
earliestRange = range;
|
|
60756
|
+
earliestUid = uid;
|
|
60757
|
+
|
|
60758
|
+
}
|
|
60759
|
+
|
|
60760
|
+
}
|
|
60761
|
+
|
|
60762
|
+
}
|
|
60763
|
+
|
|
60764
|
+
}
|
|
60765
|
+
|
|
60766
|
+
if ( earliestRange === null ) return null;
|
|
60767
|
+
|
|
60768
|
+
return {
|
|
60769
|
+
gpuTimestampOrigin: earliestRange.start,
|
|
60770
|
+
gpuTraceOrigin: state.queryCpuTimes.get( earliestUid ) ?? spans[ 0 ].t0,
|
|
60771
|
+
};
|
|
60772
|
+
|
|
60773
|
+
}
|
|
60774
|
+
|
|
60775
|
+
/**
|
|
60776
|
+
* @param {import('./ProfilerService.js').CommonGpuRendererState} state
|
|
60777
|
+
* @param {string} uid
|
|
60778
|
+
* @return {boolean}
|
|
60779
|
+
*/
|
|
60780
|
+
_isGpuQueryReferencedByActiveSpans( state, uid ) {
|
|
60781
|
+
|
|
60782
|
+
for ( const span of state.activeSpans ) {
|
|
60783
|
+
|
|
60784
|
+
if ( span._queries.render.has( uid ) || span._queries.compute.has( uid ) ) return true;
|
|
60785
|
+
|
|
60786
|
+
}
|
|
60787
|
+
|
|
60788
|
+
return false;
|
|
60789
|
+
|
|
60790
|
+
}
|
|
60791
|
+
|
|
60792
|
+
/**
|
|
60793
|
+
* @param {import('./ProfilerService.js').LegacyWebGLGpuRendererState} state
|
|
60794
|
+
*/
|
|
60795
|
+
async _flushLegacyWebGLState( state ) {
|
|
60796
|
+
|
|
60797
|
+
const spans = state.pendingSpans.splice( 0 );
|
|
60798
|
+
|
|
60799
|
+
for ( const span of spans ) {
|
|
60800
|
+
|
|
60801
|
+
const duration = await this._resolveLegacyWebGLQuery( state, span._query );
|
|
60802
|
+
if ( duration !== null && span._generation === this._gpuGeneration && this._enabled ) {
|
|
60803
|
+
|
|
60804
|
+
this._commitGpuDurationSample( span.label, span.t0, duration );
|
|
60805
|
+
|
|
60806
|
+
}
|
|
60807
|
+
|
|
60808
|
+
}
|
|
60809
|
+
|
|
60810
|
+
}
|
|
60811
|
+
|
|
60812
|
+
/**
|
|
60813
|
+
* @param {import('./ProfilerService.js').LegacyWebGLGpuRendererState} state
|
|
60814
|
+
* @param {WebGLQuery} query
|
|
60815
|
+
* @return {Promise<?number>}
|
|
60816
|
+
*/
|
|
60817
|
+
_resolveLegacyWebGLQuery( state, query ) {
|
|
60818
|
+
|
|
60819
|
+
return new Promise( resolve => {
|
|
60820
|
+
|
|
60821
|
+
const poll = () => {
|
|
60822
|
+
|
|
60823
|
+
if ( state.gl.isContextLost() ) {
|
|
60824
|
+
|
|
60825
|
+
state.gl.deleteQuery( query );
|
|
60826
|
+
resolve( null );
|
|
60827
|
+
return;
|
|
60828
|
+
|
|
60829
|
+
}
|
|
60830
|
+
|
|
60831
|
+
const disjoint = state.gl.getParameter( state.extension.GPU_DISJOINT_EXT );
|
|
60832
|
+
const available = state.gl.getQueryParameter( query, state.gl.QUERY_RESULT_AVAILABLE );
|
|
60833
|
+
|
|
60834
|
+
if ( available === false ) {
|
|
60835
|
+
|
|
60836
|
+
setTimeout( poll, 1 );
|
|
60837
|
+
return;
|
|
60838
|
+
|
|
60839
|
+
}
|
|
60840
|
+
|
|
60841
|
+
const result = disjoint
|
|
60842
|
+
? null
|
|
60843
|
+
: Number( state.gl.getQueryParameter( query, state.gl.QUERY_RESULT ) ) / 1e6;
|
|
60844
|
+
state.gl.deleteQuery( query );
|
|
60845
|
+
resolve( result );
|
|
60846
|
+
|
|
60847
|
+
};
|
|
60848
|
+
|
|
60849
|
+
poll();
|
|
60850
|
+
|
|
60851
|
+
} );
|
|
60852
|
+
|
|
60853
|
+
}
|
|
60854
|
+
|
|
60148
60855
|
/**
|
|
60149
60856
|
* @param {string} label
|
|
60150
60857
|
*/
|
|
@@ -60302,6 +61009,46 @@ class ProfilerServiceClass {
|
|
|
60302
61009
|
|
|
60303
61010
|
}
|
|
60304
61011
|
|
|
61012
|
+
/**
|
|
61013
|
+
* @param {string} label
|
|
61014
|
+
* @param {number} startTime
|
|
61015
|
+
* @param {number} durationMs
|
|
61016
|
+
* @param {{ ts: number, dur: number }} [traceOverride]
|
|
61017
|
+
*/
|
|
61018
|
+
_commitGpuDurationSample( label, startTime, durationMs, traceOverride = null ) {
|
|
61019
|
+
|
|
61020
|
+
let buffer = this.gpuBuffers.get( label );
|
|
61021
|
+
if ( buffer === undefined ) {
|
|
61022
|
+
|
|
61023
|
+
buffer = new Float64Array( RING_SIZE );
|
|
61024
|
+
this.gpuBuffers.set( label, buffer );
|
|
61025
|
+
this.gpuCursors.set( label, 0 );
|
|
61026
|
+
this.gpuCounts.set( label, 0 );
|
|
61027
|
+
|
|
61028
|
+
}
|
|
61029
|
+
|
|
61030
|
+
const cursor = this.gpuCursors.get( label );
|
|
61031
|
+
buffer[ cursor ] = durationMs;
|
|
61032
|
+
this.gpuCursors.set( label, ( cursor + 1 ) % RING_SIZE );
|
|
61033
|
+
this.gpuCounts.set( label, Math.min( this.gpuCounts.get( label ) + 1, RING_SIZE ) );
|
|
61034
|
+
this.gpuInvocations.set( label, ( this.gpuInvocations.get( label ) ?? 0 ) + 1 );
|
|
61035
|
+
|
|
61036
|
+
if ( this._profile === 'full' ) {
|
|
61037
|
+
|
|
61038
|
+
this.traceEvents.push( {
|
|
61039
|
+
name: label,
|
|
61040
|
+
ph: 'X',
|
|
61041
|
+
ts: traceOverride?.ts ?? Math.round( this._getTraceTimestamp( startTime ) ),
|
|
61042
|
+
dur: traceOverride?.dur ?? Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
61043
|
+
pid: 1,
|
|
61044
|
+
tid: TRACE_TID_GPU,
|
|
61045
|
+
cat: 'gnsx-gpu',
|
|
61046
|
+
} );
|
|
61047
|
+
|
|
61048
|
+
}
|
|
61049
|
+
|
|
61050
|
+
}
|
|
61051
|
+
|
|
60305
61052
|
/**
|
|
60306
61053
|
* @param {string} label
|
|
60307
61054
|
* @return {number[]}
|
|
@@ -60317,6 +61064,21 @@ class ProfilerServiceClass {
|
|
|
60317
61064
|
|
|
60318
61065
|
}
|
|
60319
61066
|
|
|
61067
|
+
/**
|
|
61068
|
+
* @param {string} label
|
|
61069
|
+
* @return {number[]}
|
|
61070
|
+
*/
|
|
61071
|
+
getValidGpuSamples( label ) {
|
|
61072
|
+
|
|
61073
|
+
const buffer = this.gpuBuffers.get( label );
|
|
61074
|
+
const count = this.gpuCounts.get( label ) ?? 0;
|
|
61075
|
+
if ( buffer === undefined || count === 0 ) return [];
|
|
61076
|
+
return count < RING_SIZE
|
|
61077
|
+
? Array.from( buffer.subarray( 0, count ) )
|
|
61078
|
+
: Array.from( buffer );
|
|
61079
|
+
|
|
61080
|
+
}
|
|
61081
|
+
|
|
60320
61082
|
/**
|
|
60321
61083
|
* @param {string} label
|
|
60322
61084
|
* @return {import('./ProfilerService.js').ProfilerStats|null}
|
|
@@ -60364,6 +61126,31 @@ class ProfilerServiceClass {
|
|
|
60364
61126
|
|
|
60365
61127
|
}
|
|
60366
61128
|
|
|
61129
|
+
/**
|
|
61130
|
+
* @param {string} label
|
|
61131
|
+
* @return {import('./ProfilerService.js').GpuProfilerStats|null}
|
|
61132
|
+
*/
|
|
61133
|
+
getGpuStats( label ) {
|
|
61134
|
+
|
|
61135
|
+
const samples = this.getValidGpuSamples( label );
|
|
61136
|
+
if ( samples.length === 0 ) return null;
|
|
61137
|
+
|
|
61138
|
+
const sorted = [ ...samples ].sort( ( a, b ) => a - b );
|
|
61139
|
+
const avg = sorted.reduce( ( a, b ) => a + b, 0 ) / sorted.length;
|
|
61140
|
+
|
|
61141
|
+
return {
|
|
61142
|
+
label,
|
|
61143
|
+
samples: samples.length,
|
|
61144
|
+
avg,
|
|
61145
|
+
min: sorted[ 0 ],
|
|
61146
|
+
max: sorted[ sorted.length - 1 ],
|
|
61147
|
+
p95: sorted[ Math.floor( sorted.length * 0.95 ) ],
|
|
61148
|
+
frameBudget: ( avg / FRAME_BUDGET_MS ) * 100,
|
|
61149
|
+
totalInvocations: this.gpuInvocations.get( label ) ?? 0,
|
|
61150
|
+
};
|
|
61151
|
+
|
|
61152
|
+
}
|
|
61153
|
+
|
|
60367
61154
|
/**
|
|
60368
61155
|
* @return {import('./ProfilerService.js').ProfilerStats[]}
|
|
60369
61156
|
*/
|
|
@@ -60375,10 +61162,22 @@ class ProfilerServiceClass {
|
|
|
60375
61162
|
|
|
60376
61163
|
}
|
|
60377
61164
|
|
|
61165
|
+
/**
|
|
61166
|
+
* @return {import('./ProfilerService.js').GpuProfilerStats[]}
|
|
61167
|
+
*/
|
|
61168
|
+
getAllGpuStats() {
|
|
61169
|
+
|
|
61170
|
+
return [ ...this.gpuBuffers.keys() ]
|
|
61171
|
+
.map( label => this.getGpuStats( label ) )
|
|
61172
|
+
.filter( stats => stats !== null );
|
|
61173
|
+
|
|
61174
|
+
}
|
|
61175
|
+
|
|
60378
61176
|
report() {
|
|
60379
61177
|
|
|
60380
61178
|
const stats = this.getAllStats();
|
|
60381
|
-
|
|
61179
|
+
const gpuStats = this.getAllGpuStats();
|
|
61180
|
+
if ( stats.length === 0 && gpuStats.length === 0 ) {
|
|
60382
61181
|
|
|
60383
61182
|
console.log( '[ProfilerService] No data. Enable profiling first and wait a few frames.' );
|
|
60384
61183
|
return;
|
|
@@ -60398,6 +61197,24 @@ class ProfilerServiceClass {
|
|
|
60398
61197
|
} ) )
|
|
60399
61198
|
);
|
|
60400
61199
|
|
|
61200
|
+
if ( gpuStats.length > 0 ) {
|
|
61201
|
+
|
|
61202
|
+
gpuStats.sort( ( a, b ) => b.avg - a.avg );
|
|
61203
|
+
console.log( '[ProfilerService] GPU timings' );
|
|
61204
|
+
console.table(
|
|
61205
|
+
gpuStats.map( stats => ( {
|
|
61206
|
+
label: stats.label,
|
|
61207
|
+
'avg ms': stats.avg.toFixed( 3 ),
|
|
61208
|
+
'min ms': stats.min.toFixed( 3 ),
|
|
61209
|
+
'max ms': stats.max.toFixed( 3 ),
|
|
61210
|
+
'p95 ms': stats.p95.toFixed( 3 ),
|
|
61211
|
+
'budget %': stats.frameBudget.toFixed( 1 ) + '%',
|
|
61212
|
+
samples: stats.samples,
|
|
61213
|
+
} ) )
|
|
61214
|
+
);
|
|
61215
|
+
|
|
61216
|
+
}
|
|
61217
|
+
|
|
60401
61218
|
}
|
|
60402
61219
|
|
|
60403
61220
|
/**
|
|
@@ -60414,6 +61231,7 @@ class ProfilerServiceClass {
|
|
|
60414
61231
|
|
|
60415
61232
|
} );
|
|
60416
61233
|
const hasAsync = slices.some( e => e.tid === TRACE_TID_ASYNC );
|
|
61234
|
+
const hasGpu = slices.some( e => e.tid === TRACE_TID_GPU );
|
|
60417
61235
|
/** @type {import('./ProfilerService.js').ChromeTraceMetadataEvent[]} */
|
|
60418
61236
|
const prefix = [
|
|
60419
61237
|
{
|
|
@@ -60449,6 +61267,20 @@ class ProfilerServiceClass {
|
|
|
60449
61267
|
|
|
60450
61268
|
}
|
|
60451
61269
|
|
|
61270
|
+
if ( hasGpu ) {
|
|
61271
|
+
|
|
61272
|
+
prefix.push( {
|
|
61273
|
+
cat: '__metadata',
|
|
61274
|
+
name: 'thread_name',
|
|
61275
|
+
ph: 'M',
|
|
61276
|
+
pid: 1,
|
|
61277
|
+
tid: TRACE_TID_GPU,
|
|
61278
|
+
ts: 0,
|
|
61279
|
+
args: { name: 'GPU (device timestamps)' },
|
|
61280
|
+
} );
|
|
61281
|
+
|
|
61282
|
+
}
|
|
61283
|
+
|
|
60452
61284
|
return {
|
|
60453
61285
|
displayTimeUnit: 'ms',
|
|
60454
61286
|
traceEvents: [ ...prefix, ...slices ],
|
|
@@ -60513,8 +61345,24 @@ class ProfilerServiceClass {
|
|
|
60513
61345
|
|
|
60514
61346
|
}
|
|
60515
61347
|
|
|
61348
|
+
/**
|
|
61349
|
+
* @return {import('./ProfilerService.js').GpuProfilerStats[]}
|
|
61350
|
+
*/
|
|
61351
|
+
exportGpuJSON() {
|
|
61352
|
+
|
|
61353
|
+
return this.getAllGpuStats();
|
|
61354
|
+
|
|
61355
|
+
}
|
|
61356
|
+
|
|
60516
61357
|
_clearState() {
|
|
60517
61358
|
|
|
61359
|
+
this._gpuGeneration ++;
|
|
61360
|
+
for ( const state of this.gpuRendererStates.values() ) {
|
|
61361
|
+
|
|
61362
|
+
this._clearGpuRendererState( state );
|
|
61363
|
+
|
|
61364
|
+
}
|
|
61365
|
+
|
|
60518
61366
|
this.buffers.clear();
|
|
60519
61367
|
this.cursors.clear();
|
|
60520
61368
|
this.counts.clear();
|
|
@@ -60527,6 +61375,79 @@ class ProfilerServiceClass {
|
|
|
60527
61375
|
this.selfCounts.clear();
|
|
60528
61376
|
this.callStack.length = 0;
|
|
60529
61377
|
this.invocations.clear();
|
|
61378
|
+
this.gpuBuffers.clear();
|
|
61379
|
+
this.gpuCursors.clear();
|
|
61380
|
+
this.gpuCounts.clear();
|
|
61381
|
+
this.gpuInvocations.clear();
|
|
61382
|
+
|
|
61383
|
+
}
|
|
61384
|
+
|
|
61385
|
+
/**
|
|
61386
|
+
* @param {import('./ProfilerService.js').GpuRendererState} state
|
|
61387
|
+
*/
|
|
61388
|
+
_clearGpuRendererState( state ) {
|
|
61389
|
+
|
|
61390
|
+
if ( state.kind === 'common' ) {
|
|
61391
|
+
|
|
61392
|
+
state.activeSpans.length = 0;
|
|
61393
|
+
state.pendingSpans.length = 0;
|
|
61394
|
+
state.queryCpuTimes.clear();
|
|
61395
|
+
state.gpuTimestampOrigin = null;
|
|
61396
|
+
state.gpuTraceOrigin = null;
|
|
61397
|
+
return;
|
|
61398
|
+
|
|
61399
|
+
}
|
|
61400
|
+
|
|
61401
|
+
if ( state.activeSpan !== null ) {
|
|
61402
|
+
|
|
61403
|
+
try {
|
|
61404
|
+
|
|
61405
|
+
state.gl.endQuery( state.extension.TIME_ELAPSED_EXT );
|
|
61406
|
+
|
|
61407
|
+
} catch {
|
|
61408
|
+
|
|
61409
|
+
// The context may have been lost while the query was active.
|
|
61410
|
+
|
|
61411
|
+
}
|
|
61412
|
+
|
|
61413
|
+
state.gl.deleteQuery( state.activeSpan._query );
|
|
61414
|
+
state.activeSpan = null;
|
|
61415
|
+
|
|
61416
|
+
}
|
|
61417
|
+
|
|
61418
|
+
if ( state.flushPromise === null ) {
|
|
61419
|
+
|
|
61420
|
+
for ( const span of state.pendingSpans ) {
|
|
61421
|
+
|
|
61422
|
+
state.gl.deleteQuery( span._query );
|
|
61423
|
+
|
|
61424
|
+
}
|
|
61425
|
+
|
|
61426
|
+
}
|
|
61427
|
+
|
|
61428
|
+
state.pendingSpans.length = 0;
|
|
61429
|
+
|
|
61430
|
+
}
|
|
61431
|
+
|
|
61432
|
+
_detachGpuRenderers() {
|
|
61433
|
+
|
|
61434
|
+
for ( const state of this.gpuRendererStates.values() ) {
|
|
61435
|
+
|
|
61436
|
+
if ( state.kind === 'common' && state.available ) {
|
|
61437
|
+
|
|
61438
|
+
state.renderer.backend.removeTimestampQueryListener( state.listener );
|
|
61439
|
+
|
|
61440
|
+
}
|
|
61441
|
+
|
|
61442
|
+
if ( state.kind === 'common' ) {
|
|
61443
|
+
|
|
61444
|
+
state.renderer.backend.trackTimestamp = state.previousTrackTimestamp;
|
|
61445
|
+
|
|
61446
|
+
}
|
|
61447
|
+
|
|
61448
|
+
}
|
|
61449
|
+
|
|
61450
|
+
this.gpuRendererStates.clear();
|
|
60530
61451
|
|
|
60531
61452
|
}
|
|
60532
61453
|
|
|
@@ -60556,6 +61477,18 @@ class ProfilerServiceClass {
|
|
|
60556
61477
|
* @property {number|undefined} selfFrameBudget Exclusive time as percentage of a 60 fps frame budget.
|
|
60557
61478
|
*/
|
|
60558
61479
|
|
|
61480
|
+
/**
|
|
61481
|
+
* @typedef {Object} GpuProfilerStats
|
|
61482
|
+
* @property {string} label
|
|
61483
|
+
* @property {number} samples
|
|
61484
|
+
* @property {number} avg
|
|
61485
|
+
* @property {number} min
|
|
61486
|
+
* @property {number} max
|
|
61487
|
+
* @property {number} p95
|
|
61488
|
+
* @property {number} frameBudget Percentage of a 60 fps frame budget (16.67 ms)
|
|
61489
|
+
* @property {number} totalInvocations
|
|
61490
|
+
*/
|
|
61491
|
+
|
|
60559
61492
|
/**
|
|
60560
61493
|
* @typedef {Object} SpanHandle
|
|
60561
61494
|
* @property {string} label
|
|
@@ -60564,6 +61497,51 @@ class ProfilerServiceClass {
|
|
|
60564
61497
|
* @property {string} _startMark
|
|
60565
61498
|
*/
|
|
60566
61499
|
|
|
61500
|
+
/**
|
|
61501
|
+
* @typedef {Object} GpuSpanHandle
|
|
61502
|
+
* @property {string} label
|
|
61503
|
+
* @property {?Object} renderer
|
|
61504
|
+
* @property {number} t0
|
|
61505
|
+
* @property {number} _seq
|
|
61506
|
+
* @property {number} _generation
|
|
61507
|
+
* @property {{render: Set<string>, compute: Set<string>}} [_queries]
|
|
61508
|
+
* @property {WebGLQuery} [_query]
|
|
61509
|
+
* @property {number} [_endTime]
|
|
61510
|
+
*/
|
|
61511
|
+
|
|
61512
|
+
/**
|
|
61513
|
+
* @typedef {Object} CommonGpuRendererState
|
|
61514
|
+
* @property {'common'} kind
|
|
61515
|
+
* @property {boolean} available
|
|
61516
|
+
* @property {Object} renderer
|
|
61517
|
+
* @property {function('render'|'compute', string, ?string): void} listener
|
|
61518
|
+
* @property {boolean} previousTrackTimestamp
|
|
61519
|
+
* @property {GpuSpanHandle[]} activeSpans
|
|
61520
|
+
* @property {GpuSpanHandle[]} pendingSpans
|
|
61521
|
+
* @property {Map<string, number>} queryCpuTimes
|
|
61522
|
+
* @property {?bigint} gpuTimestampOrigin
|
|
61523
|
+
* @property {?number} gpuTraceOrigin
|
|
61524
|
+
* @property {?Promise<void>} flushPromise
|
|
61525
|
+
* @property {boolean} flushScheduled
|
|
61526
|
+
*/
|
|
61527
|
+
|
|
61528
|
+
/**
|
|
61529
|
+
* @typedef {Object} LegacyWebGLGpuRendererState
|
|
61530
|
+
* @property {'legacy-webgl'} kind
|
|
61531
|
+
* @property {boolean} available
|
|
61532
|
+
* @property {Object} renderer
|
|
61533
|
+
* @property {WebGL2RenderingContext} gl
|
|
61534
|
+
* @property {?EXT_disjoint_timer_query_webgl2} extension
|
|
61535
|
+
* @property {?GpuSpanHandle} activeSpan
|
|
61536
|
+
* @property {GpuSpanHandle[]} pendingSpans
|
|
61537
|
+
* @property {?Promise<void>} flushPromise
|
|
61538
|
+
* @property {boolean} flushScheduled
|
|
61539
|
+
*/
|
|
61540
|
+
|
|
61541
|
+
/**
|
|
61542
|
+
* @typedef {CommonGpuRendererState|LegacyWebGLGpuRendererState} GpuRendererState
|
|
61543
|
+
*/
|
|
61544
|
+
|
|
60567
61545
|
/**
|
|
60568
61546
|
* @typedef {Object} EndSpanOptions
|
|
60569
61547
|
* @property {boolean} [asyncTimeline] When true, trace slice uses `tid=2` (virtual async row).
|
|
@@ -60577,7 +61555,7 @@ class ProfilerServiceClass {
|
|
|
60577
61555
|
* @property {number} dur
|
|
60578
61556
|
* @property {1} pid
|
|
60579
61557
|
* @property {number} tid
|
|
60580
|
-
* @property {'gnsx'} cat
|
|
61558
|
+
* @property {'gnsx'|'gnsx-gpu'} cat
|
|
60581
61559
|
*/
|
|
60582
61560
|
|
|
60583
61561
|
/**
|