@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.cjs
CHANGED
|
@@ -59983,6 +59983,7 @@ class TextureUtils {
|
|
|
59983
59983
|
}
|
|
59984
59984
|
|
|
59985
59985
|
// WITH_GENESYS
|
|
59986
|
+
|
|
59986
59987
|
/**
|
|
59987
59988
|
* ProfilerService — per-label CPU timing with ring-buffer aggregation and DevTools integration.
|
|
59988
59989
|
*
|
|
@@ -60003,12 +60004,16 @@ const RING_SIZE = 120;
|
|
|
60003
60004
|
const FRAME_BUDGET_MS = 1000 / 60;
|
|
60004
60005
|
const TRACE_TID_MAIN = 1;
|
|
60005
60006
|
const TRACE_TID_ASYNC = 2;
|
|
60007
|
+
const TRACE_TID_GPU = 3;
|
|
60006
60008
|
const NOOP = () => {};
|
|
60007
60009
|
|
|
60008
60010
|
const DUMMY_SPAN = Object.freeze( { label: '', t0: 0, _seq: 0 } );
|
|
60011
|
+
const DUMMY_GPU_SPAN = Object.freeze( { label: '', renderer: null, t0: 0, _seq: 0, _generation: 0 } );
|
|
60009
60012
|
const NOOP_BEGIN_SPAN = () => DUMMY_SPAN;
|
|
60010
60013
|
const NOOP_END_SPAN = () => {};
|
|
60011
60014
|
|
|
60015
|
+
const NOOP_BEGIN_GPU_SPAN = () => DUMMY_GPU_SPAN;
|
|
60016
|
+
|
|
60012
60017
|
class ProfilerServiceClass {
|
|
60013
60018
|
|
|
60014
60019
|
constructor() {
|
|
@@ -60043,6 +60048,18 @@ class ProfilerServiceClass {
|
|
|
60043
60048
|
/** @type {Map<string, number>} Total (uncapped) invocation count since last reset, for calls-per-frame. */
|
|
60044
60049
|
this.invocations = new Map();
|
|
60045
60050
|
|
|
60051
|
+
/** @type {Map<string, Float64Array>} GPU time ring buffers (one per label). */
|
|
60052
|
+
this.gpuBuffers = new Map();
|
|
60053
|
+
/** @type {Map<string, number>} */
|
|
60054
|
+
this.gpuCursors = new Map();
|
|
60055
|
+
/** @type {Map<string, number>} */
|
|
60056
|
+
this.gpuCounts = new Map();
|
|
60057
|
+
/** @type {Map<string, number>} */
|
|
60058
|
+
this.gpuInvocations = new Map();
|
|
60059
|
+
/** @type {Map<Object, import('./ProfilerService.js').GpuRendererState>} */
|
|
60060
|
+
this.gpuRendererStates = new Map();
|
|
60061
|
+
this._gpuGeneration = 1;
|
|
60062
|
+
|
|
60046
60063
|
if ( this._enabled ) {
|
|
60047
60064
|
|
|
60048
60065
|
this.traceStartTime = performance.now();
|
|
@@ -60050,6 +60067,8 @@ class ProfilerServiceClass {
|
|
|
60050
60067
|
this.end = this._endImpl.bind( this );
|
|
60051
60068
|
this.beginSpan = this._beginSpanImpl.bind( this );
|
|
60052
60069
|
this.endSpan = this._endSpanImpl.bind( this );
|
|
60070
|
+
this.beginGpu = this._beginGpuImpl.bind( this );
|
|
60071
|
+
this.endGpu = this._endGpuImpl.bind( this );
|
|
60053
60072
|
this._exposeGlobal();
|
|
60054
60073
|
console.log( `[ProfilerService] auto-started from env (profile: ${this._profile})` );
|
|
60055
60074
|
|
|
@@ -60059,6 +60078,8 @@ class ProfilerServiceClass {
|
|
|
60059
60078
|
this.end = NOOP;
|
|
60060
60079
|
this.beginSpan = NOOP_BEGIN_SPAN;
|
|
60061
60080
|
this.endSpan = NOOP_END_SPAN;
|
|
60081
|
+
this.beginGpu = NOOP_BEGIN_GPU_SPAN;
|
|
60082
|
+
this.endGpu = NOOP_END_SPAN;
|
|
60062
60083
|
|
|
60063
60084
|
}
|
|
60064
60085
|
|
|
@@ -60092,6 +60113,8 @@ class ProfilerServiceClass {
|
|
|
60092
60113
|
this.end = this._endImpl.bind( this );
|
|
60093
60114
|
this.beginSpan = this._beginSpanImpl.bind( this );
|
|
60094
60115
|
this.endSpan = this._endSpanImpl.bind( this );
|
|
60116
|
+
this.beginGpu = this._beginGpuImpl.bind( this );
|
|
60117
|
+
this.endGpu = this._endGpuImpl.bind( this );
|
|
60095
60118
|
this._exposeGlobal();
|
|
60096
60119
|
console.log( `[ProfilerService] enabled (profile: ${this._profile}) — call __gnsx_profiler.report() or downloadTrace() from the console` );
|
|
60097
60120
|
|
|
@@ -60105,6 +60128,9 @@ class ProfilerServiceClass {
|
|
|
60105
60128
|
this.end = NOOP;
|
|
60106
60129
|
this.beginSpan = NOOP_BEGIN_SPAN;
|
|
60107
60130
|
this.endSpan = NOOP_END_SPAN;
|
|
60131
|
+
this.beginGpu = NOOP_BEGIN_GPU_SPAN;
|
|
60132
|
+
this.endGpu = NOOP_END_SPAN;
|
|
60133
|
+
this._detachGpuRenderers();
|
|
60108
60134
|
console.log( '[ProfilerService] disabled' );
|
|
60109
60135
|
|
|
60110
60136
|
}
|
|
@@ -60125,6 +60151,687 @@ class ProfilerServiceClass {
|
|
|
60125
60151
|
|
|
60126
60152
|
}
|
|
60127
60153
|
|
|
60154
|
+
/**
|
|
60155
|
+
* Enables GPU timestamp collection for a renderer.
|
|
60156
|
+
*
|
|
60157
|
+
* @param {Object} renderer
|
|
60158
|
+
* @return {Promise<boolean>} Whether GPU timestamps are available.
|
|
60159
|
+
*/
|
|
60160
|
+
async attachGpuRenderer( renderer ) {
|
|
60161
|
+
|
|
60162
|
+
if ( this._enabled === false || renderer === null || renderer === undefined ) return false;
|
|
60163
|
+
|
|
60164
|
+
const existingState = this.gpuRendererStates.get( renderer );
|
|
60165
|
+
if ( existingState !== undefined ) return existingState.available;
|
|
60166
|
+
|
|
60167
|
+
if ( renderer.isWebGLRenderer === true ) {
|
|
60168
|
+
|
|
60169
|
+
const gl = renderer.getContext();
|
|
60170
|
+
const extension = gl.getExtension( 'EXT_disjoint_timer_query_webgl2' );
|
|
60171
|
+
const state = {
|
|
60172
|
+
kind: 'legacy-webgl',
|
|
60173
|
+
available: extension !== null,
|
|
60174
|
+
renderer,
|
|
60175
|
+
gl,
|
|
60176
|
+
extension,
|
|
60177
|
+
activeSpan: null,
|
|
60178
|
+
pendingSpans: [],
|
|
60179
|
+
flushPromise: null,
|
|
60180
|
+
flushScheduled: false,
|
|
60181
|
+
};
|
|
60182
|
+
this.gpuRendererStates.set( renderer, state );
|
|
60183
|
+
|
|
60184
|
+
if ( extension === null ) {
|
|
60185
|
+
|
|
60186
|
+
warnOnce( 'ProfilerService: EXT_disjoint_timer_query_webgl2 is unavailable; GPU profiling is disabled for this WebGLRenderer.' );
|
|
60187
|
+
|
|
60188
|
+
}
|
|
60189
|
+
|
|
60190
|
+
return state.available;
|
|
60191
|
+
|
|
60192
|
+
}
|
|
60193
|
+
|
|
60194
|
+
if ( renderer.isRenderer === true && renderer.backend !== undefined ) {
|
|
60195
|
+
|
|
60196
|
+
await renderer.init();
|
|
60197
|
+
const previousTrackTimestamp = renderer.backend.trackTimestamp;
|
|
60198
|
+
renderer.backend.trackTimestamp = true;
|
|
60199
|
+
|
|
60200
|
+
const available = renderer.backend.hasTimestamp === true && renderer.hasFeature( 'timestamp-query' ) === true;
|
|
60201
|
+
const listener = ( type, uid, label ) => this._captureGpuTimestampQuery( renderer, type, uid, label );
|
|
60202
|
+
const state = {
|
|
60203
|
+
kind: 'common',
|
|
60204
|
+
available,
|
|
60205
|
+
renderer,
|
|
60206
|
+
listener,
|
|
60207
|
+
previousTrackTimestamp,
|
|
60208
|
+
activeSpans: [],
|
|
60209
|
+
pendingSpans: [],
|
|
60210
|
+
queryCpuTimes: new Map(),
|
|
60211
|
+
gpuTimestampOrigin: null,
|
|
60212
|
+
gpuTraceOrigin: null,
|
|
60213
|
+
flushPromise: null,
|
|
60214
|
+
flushScheduled: false,
|
|
60215
|
+
};
|
|
60216
|
+
this.gpuRendererStates.set( renderer, state );
|
|
60217
|
+
|
|
60218
|
+
if ( available ) {
|
|
60219
|
+
|
|
60220
|
+
renderer.backend.addTimestampQueryListener( listener );
|
|
60221
|
+
|
|
60222
|
+
} else {
|
|
60223
|
+
|
|
60224
|
+
warnOnce( 'ProfilerService: Timestamp queries are unavailable; GPU profiling is disabled for this renderer.' );
|
|
60225
|
+
|
|
60226
|
+
}
|
|
60227
|
+
|
|
60228
|
+
return available;
|
|
60229
|
+
|
|
60230
|
+
}
|
|
60231
|
+
|
|
60232
|
+
warnOnce( 'ProfilerService: Unsupported renderer; expected Renderer or WebGLRenderer.' );
|
|
60233
|
+
return false;
|
|
60234
|
+
|
|
60235
|
+
}
|
|
60236
|
+
|
|
60237
|
+
/**
|
|
60238
|
+
* @param {string} label
|
|
60239
|
+
* @param {Object} renderer
|
|
60240
|
+
* @return {import('./ProfilerService.js').GpuSpanHandle}
|
|
60241
|
+
*/
|
|
60242
|
+
_beginGpuImpl( label, renderer ) {
|
|
60243
|
+
|
|
60244
|
+
const state = this.gpuRendererStates.get( renderer );
|
|
60245
|
+
if ( state === undefined || state.available === false ) return DUMMY_GPU_SPAN;
|
|
60246
|
+
|
|
60247
|
+
const handle = {
|
|
60248
|
+
label,
|
|
60249
|
+
renderer,
|
|
60250
|
+
t0: performance.now(),
|
|
60251
|
+
_seq: ++ this._markId,
|
|
60252
|
+
_generation: this._gpuGeneration,
|
|
60253
|
+
};
|
|
60254
|
+
|
|
60255
|
+
if ( state.kind === 'common' ) {
|
|
60256
|
+
|
|
60257
|
+
handle._queries = {
|
|
60258
|
+
[ TimestampQuery.RENDER ]: new Set(),
|
|
60259
|
+
[ TimestampQuery.COMPUTE ]: new Set(),
|
|
60260
|
+
};
|
|
60261
|
+
state.activeSpans.push( handle );
|
|
60262
|
+
return handle;
|
|
60263
|
+
|
|
60264
|
+
}
|
|
60265
|
+
|
|
60266
|
+
if ( state.activeSpan !== null ) {
|
|
60267
|
+
|
|
60268
|
+
warnOnce( 'ProfilerService: Nested GPU spans are unsupported by legacy WebGLRenderer.' );
|
|
60269
|
+
return DUMMY_GPU_SPAN;
|
|
60270
|
+
|
|
60271
|
+
}
|
|
60272
|
+
|
|
60273
|
+
const query = state.gl.createQuery();
|
|
60274
|
+
if ( query === null ) return DUMMY_GPU_SPAN;
|
|
60275
|
+
|
|
60276
|
+
try {
|
|
60277
|
+
|
|
60278
|
+
state.gl.beginQuery( state.extension.TIME_ELAPSED_EXT, query );
|
|
60279
|
+
handle._query = query;
|
|
60280
|
+
state.activeSpan = handle;
|
|
60281
|
+
return handle;
|
|
60282
|
+
|
|
60283
|
+
} catch ( error ) {
|
|
60284
|
+
|
|
60285
|
+
state.gl.deleteQuery( query );
|
|
60286
|
+
warnOnce( `ProfilerService: Unable to begin a WebGL GPU span (${error.message}).` );
|
|
60287
|
+
return DUMMY_GPU_SPAN;
|
|
60288
|
+
|
|
60289
|
+
}
|
|
60290
|
+
|
|
60291
|
+
}
|
|
60292
|
+
|
|
60293
|
+
/**
|
|
60294
|
+
* @param {import('./ProfilerService.js').GpuSpanHandle} handle
|
|
60295
|
+
*/
|
|
60296
|
+
_endGpuImpl( handle ) {
|
|
60297
|
+
|
|
60298
|
+
if ( handle._seq === 0 || handle._generation !== this._gpuGeneration ) return;
|
|
60299
|
+
|
|
60300
|
+
const state = this.gpuRendererStates.get( handle.renderer );
|
|
60301
|
+
if ( state === undefined || state.available === false ) return;
|
|
60302
|
+
|
|
60303
|
+
handle._endTime = performance.now();
|
|
60304
|
+
|
|
60305
|
+
if ( state.kind === 'common' ) {
|
|
60306
|
+
|
|
60307
|
+
const index = state.activeSpans.indexOf( handle );
|
|
60308
|
+
if ( index === -1 ) return;
|
|
60309
|
+
|
|
60310
|
+
state.activeSpans.splice( index, 1 );
|
|
60311
|
+
if ( handle._queries.render.size > 0 || handle._queries.compute.size > 0 ) {
|
|
60312
|
+
|
|
60313
|
+
state.pendingSpans.push( handle );
|
|
60314
|
+
this._scheduleGpuFlush( state );
|
|
60315
|
+
|
|
60316
|
+
}
|
|
60317
|
+
|
|
60318
|
+
return;
|
|
60319
|
+
|
|
60320
|
+
}
|
|
60321
|
+
|
|
60322
|
+
if ( state.activeSpan !== handle ) return;
|
|
60323
|
+
|
|
60324
|
+
try {
|
|
60325
|
+
|
|
60326
|
+
state.gl.endQuery( state.extension.TIME_ELAPSED_EXT );
|
|
60327
|
+
state.activeSpan = null;
|
|
60328
|
+
state.pendingSpans.push( handle );
|
|
60329
|
+
this._scheduleGpuFlush( state );
|
|
60330
|
+
|
|
60331
|
+
} catch ( error ) {
|
|
60332
|
+
|
|
60333
|
+
state.activeSpan = null;
|
|
60334
|
+
state.gl.deleteQuery( handle._query );
|
|
60335
|
+
warnOnce( `ProfilerService: Unable to end a WebGL GPU span (${error.message}).` );
|
|
60336
|
+
|
|
60337
|
+
}
|
|
60338
|
+
|
|
60339
|
+
}
|
|
60340
|
+
|
|
60341
|
+
/**
|
|
60342
|
+
* Resolves pending GPU timestamp queries for a renderer.
|
|
60343
|
+
*
|
|
60344
|
+
* @param {Object} renderer
|
|
60345
|
+
* @return {Promise<void>}
|
|
60346
|
+
*/
|
|
60347
|
+
async flushGpu( renderer ) {
|
|
60348
|
+
|
|
60349
|
+
const state = this.gpuRendererStates.get( renderer );
|
|
60350
|
+
if ( state === undefined || state.available === false ) return;
|
|
60351
|
+
if ( state.flushPromise !== null ) return state.flushPromise;
|
|
60352
|
+
|
|
60353
|
+
state.flushPromise = state.kind === 'common'
|
|
60354
|
+
? this._flushCommonGpuState( state )
|
|
60355
|
+
: this._flushLegacyWebGLState( state );
|
|
60356
|
+
|
|
60357
|
+
try {
|
|
60358
|
+
|
|
60359
|
+
await state.flushPromise;
|
|
60360
|
+
|
|
60361
|
+
} finally {
|
|
60362
|
+
|
|
60363
|
+
state.flushPromise = null;
|
|
60364
|
+
if ( state.pendingSpans.length > 0 ) this._scheduleGpuFlush( state );
|
|
60365
|
+
|
|
60366
|
+
}
|
|
60367
|
+
|
|
60368
|
+
}
|
|
60369
|
+
|
|
60370
|
+
/**
|
|
60371
|
+
* @param {Object} renderer
|
|
60372
|
+
* @param {'render'|'compute'} type
|
|
60373
|
+
* @param {string} uid
|
|
60374
|
+
* @param {?string} [label=null]
|
|
60375
|
+
*/
|
|
60376
|
+
_captureGpuTimestampQuery( renderer, type, uid, label = null ) {
|
|
60377
|
+
|
|
60378
|
+
const state = this.gpuRendererStates.get( renderer );
|
|
60379
|
+
if ( state === undefined || state.kind !== 'common' ) return;
|
|
60380
|
+
|
|
60381
|
+
const cpuTime = performance.now();
|
|
60382
|
+
state.queryCpuTimes.set( uid, cpuTime );
|
|
60383
|
+
|
|
60384
|
+
for ( const span of state.activeSpans ) {
|
|
60385
|
+
|
|
60386
|
+
span._queries[ type ].add( uid );
|
|
60387
|
+
|
|
60388
|
+
}
|
|
60389
|
+
|
|
60390
|
+
if ( label !== null && label !== undefined && label !== '' ) {
|
|
60391
|
+
|
|
60392
|
+
// Queue labelled passes with the active beginGpu span(s). Flushing
|
|
60393
|
+
// immediately would commit them before their parent envelope exists and
|
|
60394
|
+
// can anchor gpuTraceOrigin mid-frame, which breaks Speedscope nesting.
|
|
60395
|
+
const passSpan = {
|
|
60396
|
+
label: `GPU pass: ${label}`,
|
|
60397
|
+
renderer,
|
|
60398
|
+
t0: cpuTime,
|
|
60399
|
+
_seq: ++ this._markId,
|
|
60400
|
+
_generation: this._gpuGeneration,
|
|
60401
|
+
_queries: {
|
|
60402
|
+
[ TimestampQuery.RENDER ]: new Set(),
|
|
60403
|
+
[ TimestampQuery.COMPUTE ]: new Set(),
|
|
60404
|
+
},
|
|
60405
|
+
};
|
|
60406
|
+
passSpan._queries[ type ].add( uid );
|
|
60407
|
+
state.pendingSpans.push( passSpan );
|
|
60408
|
+
// Only auto-flush when no beginGpu parent is open. Nested parents flush on endGpu
|
|
60409
|
+
// so pass slices share one origin and nest inside the parent envelope.
|
|
60410
|
+
if ( state.activeSpans.length === 0 ) {
|
|
60411
|
+
|
|
60412
|
+
this._scheduleGpuFlush( state );
|
|
60413
|
+
|
|
60414
|
+
}
|
|
60415
|
+
|
|
60416
|
+
}
|
|
60417
|
+
|
|
60418
|
+
}
|
|
60419
|
+
|
|
60420
|
+
/**
|
|
60421
|
+
* @param {import('./ProfilerService.js').GpuRendererState} state
|
|
60422
|
+
*/
|
|
60423
|
+
_scheduleGpuFlush( state ) {
|
|
60424
|
+
|
|
60425
|
+
if ( state.flushScheduled ) return;
|
|
60426
|
+
state.flushScheduled = true;
|
|
60427
|
+
|
|
60428
|
+
const callback = () => {
|
|
60429
|
+
|
|
60430
|
+
state.flushScheduled = false;
|
|
60431
|
+
if ( this.gpuRendererStates.get( state.renderer ) === state ) {
|
|
60432
|
+
|
|
60433
|
+
this.flushGpu( state.renderer ).catch( error => {
|
|
60434
|
+
|
|
60435
|
+
warnOnce( `ProfilerService: Unable to resolve GPU timestamps (${error.message}).` );
|
|
60436
|
+
|
|
60437
|
+
} );
|
|
60438
|
+
|
|
60439
|
+
}
|
|
60440
|
+
|
|
60441
|
+
};
|
|
60442
|
+
|
|
60443
|
+
if ( typeof requestAnimationFrame === 'function' ) {
|
|
60444
|
+
|
|
60445
|
+
requestAnimationFrame( callback );
|
|
60446
|
+
|
|
60447
|
+
} else {
|
|
60448
|
+
|
|
60449
|
+
setTimeout( callback, 0 );
|
|
60450
|
+
|
|
60451
|
+
}
|
|
60452
|
+
|
|
60453
|
+
}
|
|
60454
|
+
|
|
60455
|
+
/**
|
|
60456
|
+
* @param {import('./ProfilerService.js').CommonGpuRendererState} state
|
|
60457
|
+
*/
|
|
60458
|
+
async _flushCommonGpuState( state ) {
|
|
60459
|
+
|
|
60460
|
+
const spans = state.pendingSpans.splice( 0 );
|
|
60461
|
+
if ( spans.length === 0 ) return;
|
|
60462
|
+
|
|
60463
|
+
const hasRenderQueries = spans.some( span => span._queries.render.size > 0 );
|
|
60464
|
+
const hasComputeQueries = spans.some( span => span._queries.compute.size > 0 );
|
|
60465
|
+
const resolutions = [];
|
|
60466
|
+
|
|
60467
|
+
if ( hasRenderQueries ) resolutions.push( state.renderer.resolveTimestampsAsync( TimestampQuery.RENDER ) );
|
|
60468
|
+
if ( hasComputeQueries ) resolutions.push( state.renderer.resolveTimestampsAsync( TimestampQuery.COMPUTE ) );
|
|
60469
|
+
await Promise.all( resolutions );
|
|
60470
|
+
|
|
60471
|
+
if ( this._enabled === false ) return;
|
|
60472
|
+
|
|
60473
|
+
if ( state.gpuTimestampOrigin === null ) {
|
|
60474
|
+
|
|
60475
|
+
const origin = this._findEarliestGpuTimestampOrigin( state, spans );
|
|
60476
|
+
if ( origin !== null ) {
|
|
60477
|
+
|
|
60478
|
+
state.gpuTimestampOrigin = origin.gpuTimestampOrigin;
|
|
60479
|
+
state.gpuTraceOrigin = origin.gpuTraceOrigin;
|
|
60480
|
+
|
|
60481
|
+
}
|
|
60482
|
+
|
|
60483
|
+
}
|
|
60484
|
+
|
|
60485
|
+
const resolvedUids = new Set();
|
|
60486
|
+
/** @type {Array<{ label: string, startTime: number, durationMs: number, isPass: boolean }>} */
|
|
60487
|
+
const samples = [];
|
|
60488
|
+
/** @type {Set<string>} */
|
|
60489
|
+
const labelledQueryUids = new Set();
|
|
60490
|
+
|
|
60491
|
+
for ( const span of spans ) {
|
|
60492
|
+
|
|
60493
|
+
if ( span._generation !== this._gpuGeneration ) continue;
|
|
60494
|
+
|
|
60495
|
+
let duration = 0;
|
|
60496
|
+
let resolvedQueries = 0;
|
|
60497
|
+
let rangedQueries = 0;
|
|
60498
|
+
let gpuStart = null;
|
|
60499
|
+
let gpuEnd = null;
|
|
60500
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60501
|
+
|
|
60502
|
+
for ( const uid of span._queries[ type ] ) {
|
|
60503
|
+
|
|
60504
|
+
if ( state.renderer.backend.hasTimestampQuery( uid ) ) {
|
|
60505
|
+
|
|
60506
|
+
duration += state.renderer.backend.getTimestamp( uid );
|
|
60507
|
+
resolvedQueries ++;
|
|
60508
|
+
resolvedUids.add( uid );
|
|
60509
|
+
|
|
60510
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
60511
|
+
if ( range !== null ) {
|
|
60512
|
+
|
|
60513
|
+
rangedQueries ++;
|
|
60514
|
+
if ( gpuStart === null || range.start < gpuStart ) gpuStart = range.start;
|
|
60515
|
+
if ( gpuEnd === null || range.end > gpuEnd ) gpuEnd = range.end;
|
|
60516
|
+
|
|
60517
|
+
}
|
|
60518
|
+
|
|
60519
|
+
}
|
|
60520
|
+
|
|
60521
|
+
}
|
|
60522
|
+
|
|
60523
|
+
}
|
|
60524
|
+
|
|
60525
|
+
if ( resolvedQueries > 0 ) {
|
|
60526
|
+
|
|
60527
|
+
let startTime = span.t0;
|
|
60528
|
+
// Prefer the device envelope whenever any query has a range. Requiring
|
|
60529
|
+
// *all* queries to be ranged forced a CPU-t0 + summed-duration fallback
|
|
60530
|
+
// that often ended before later GPU pass slices, breaking flame nesting.
|
|
60531
|
+
if ( rangedQueries > 0 && gpuStart !== null && gpuEnd !== null && state.gpuTimestampOrigin !== null ) {
|
|
60532
|
+
|
|
60533
|
+
duration = Number( gpuEnd - gpuStart ) / 1e6;
|
|
60534
|
+
startTime = state.gpuTraceOrigin + Number( gpuStart - state.gpuTimestampOrigin ) / 1e6;
|
|
60535
|
+
|
|
60536
|
+
}
|
|
60537
|
+
|
|
60538
|
+
const isPass = span.label.startsWith( 'GPU pass:' );
|
|
60539
|
+
if ( isPass ) {
|
|
60540
|
+
|
|
60541
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60542
|
+
|
|
60543
|
+
for ( const uid of span._queries[ type ] ) labelledQueryUids.add( uid );
|
|
60544
|
+
|
|
60545
|
+
}
|
|
60546
|
+
|
|
60547
|
+
}
|
|
60548
|
+
|
|
60549
|
+
samples.push( {
|
|
60550
|
+
label: span.label,
|
|
60551
|
+
startTime,
|
|
60552
|
+
durationMs: duration,
|
|
60553
|
+
isPass,
|
|
60554
|
+
} );
|
|
60555
|
+
|
|
60556
|
+
}
|
|
60557
|
+
|
|
60558
|
+
}
|
|
60559
|
+
|
|
60560
|
+
// Parent beginGpu spans collect every timestamp query, including renders that
|
|
60561
|
+
// had no gpuProfilerLabel. Surface those as pass slices so they are not empty
|
|
60562
|
+
// holes under the parent envelope.
|
|
60563
|
+
for ( const span of spans ) {
|
|
60564
|
+
|
|
60565
|
+
if ( span._generation !== this._gpuGeneration ) continue;
|
|
60566
|
+
if ( span.label.startsWith( 'GPU pass:' ) ) continue;
|
|
60567
|
+
if ( state.gpuTimestampOrigin === null ) continue;
|
|
60568
|
+
|
|
60569
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60570
|
+
|
|
60571
|
+
for ( const uid of span._queries[ type ] ) {
|
|
60572
|
+
|
|
60573
|
+
if ( labelledQueryUids.has( uid ) ) continue;
|
|
60574
|
+
if ( state.renderer.backend.hasTimestampQuery( uid ) === false ) continue;
|
|
60575
|
+
|
|
60576
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
60577
|
+
if ( range === null ) continue;
|
|
60578
|
+
|
|
60579
|
+
const durationMs = Number( range.end - range.start ) / 1e6;
|
|
60580
|
+
if ( durationMs <= 0 ) continue;
|
|
60581
|
+
|
|
60582
|
+
samples.push( {
|
|
60583
|
+
label: 'GPU pass: (unlabeled)',
|
|
60584
|
+
startTime: state.gpuTraceOrigin + Number( range.start - state.gpuTimestampOrigin ) / 1e6,
|
|
60585
|
+
durationMs,
|
|
60586
|
+
isPass: true,
|
|
60587
|
+
} );
|
|
60588
|
+
labelledQueryUids.add( uid );
|
|
60589
|
+
|
|
60590
|
+
}
|
|
60591
|
+
|
|
60592
|
+
}
|
|
60593
|
+
|
|
60594
|
+
}
|
|
60595
|
+
|
|
60596
|
+
const passSamples = samples.filter( sample => sample.isPass );
|
|
60597
|
+
const otherSamples = samples.filter( sample => sample.isPass === false );
|
|
60598
|
+
this._snapHalfOpenGpuPassTraceIntervals( passSamples );
|
|
60599
|
+
this._expandGpuParentTraceToContainPasses( otherSamples, passSamples );
|
|
60600
|
+
|
|
60601
|
+
for ( const sample of otherSamples ) {
|
|
60602
|
+
|
|
60603
|
+
this._commitGpuDurationSample( sample.label, sample.startTime, sample.durationMs, {
|
|
60604
|
+
ts: sample.traceTs,
|
|
60605
|
+
dur: sample.traceDur,
|
|
60606
|
+
} );
|
|
60607
|
+
|
|
60608
|
+
}
|
|
60609
|
+
|
|
60610
|
+
for ( const sample of passSamples ) {
|
|
60611
|
+
|
|
60612
|
+
this._commitGpuDurationSample( sample.label, sample.startTime, sample.durationMs, {
|
|
60613
|
+
ts: sample.traceTs,
|
|
60614
|
+
dur: sample.traceDur,
|
|
60615
|
+
} );
|
|
60616
|
+
|
|
60617
|
+
}
|
|
60618
|
+
|
|
60619
|
+
// Keep CPU times for queries still referenced by active parent spans.
|
|
60620
|
+
for ( const uid of resolvedUids ) {
|
|
60621
|
+
|
|
60622
|
+
if ( this._isGpuQueryReferencedByActiveSpans( state, uid ) ) continue;
|
|
60623
|
+
state.queryCpuTimes.delete( uid );
|
|
60624
|
+
|
|
60625
|
+
}
|
|
60626
|
+
|
|
60627
|
+
}
|
|
60628
|
+
|
|
60629
|
+
/**
|
|
60630
|
+
* Convert labelled GPU pass samples to non-overlapping half-open µs intervals for
|
|
60631
|
+
* Chrome/Speedscope traces. Stats keep the raw measured durations.
|
|
60632
|
+
*
|
|
60633
|
+
* @param {Array<{ startTime: number, durationMs: number, traceTs?: number, traceDur?: number }>} samples
|
|
60634
|
+
*/
|
|
60635
|
+
_snapHalfOpenGpuPassTraceIntervals( samples ) {
|
|
60636
|
+
|
|
60637
|
+
if ( samples.length === 0 ) return;
|
|
60638
|
+
|
|
60639
|
+
for ( const sample of samples ) {
|
|
60640
|
+
|
|
60641
|
+
sample.traceTs = Math.round( this._getTraceTimestamp( sample.startTime ) );
|
|
60642
|
+
sample.traceDur = Math.max( 1, Math.round( sample.durationMs * 1000 ) );
|
|
60643
|
+
|
|
60644
|
+
}
|
|
60645
|
+
|
|
60646
|
+
// Longer first at the same timestamp so the substantial pass keeps its start.
|
|
60647
|
+
samples.sort( ( a, b ) => a.traceTs - b.traceTs || b.traceDur - a.traceDur );
|
|
60648
|
+
|
|
60649
|
+
let cursor = Number.NEGATIVE_INFINITY;
|
|
60650
|
+
for ( const sample of samples ) {
|
|
60651
|
+
|
|
60652
|
+
if ( sample.traceTs < cursor ) {
|
|
60653
|
+
|
|
60654
|
+
const end = Math.max( sample.traceTs + sample.traceDur, cursor + 1 );
|
|
60655
|
+
sample.traceTs = cursor;
|
|
60656
|
+
sample.traceDur = Math.max( 1, end - sample.traceTs );
|
|
60657
|
+
|
|
60658
|
+
}
|
|
60659
|
+
|
|
60660
|
+
cursor = sample.traceTs + sample.traceDur;
|
|
60661
|
+
|
|
60662
|
+
}
|
|
60663
|
+
|
|
60664
|
+
}
|
|
60665
|
+
|
|
60666
|
+
/**
|
|
60667
|
+
* Speedscope nests by time containment on a thread. If a snapped GPU pass
|
|
60668
|
+
* sticks out past its beginGpu parent (rounding / half-open adjust), the
|
|
60669
|
+
* parent fails containment and is pushed onto a lower lane under renderFrame.
|
|
60670
|
+
* Expand overlapping parents so they strictly cover their pass children.
|
|
60671
|
+
*
|
|
60672
|
+
* @param {Array<{ startTime: number, durationMs: number, traceTs?: number, traceDur?: number }>} parents
|
|
60673
|
+
* @param {Array<{ traceTs: number, traceDur: number }>} passes
|
|
60674
|
+
*/
|
|
60675
|
+
_expandGpuParentTraceToContainPasses( parents, passes ) {
|
|
60676
|
+
|
|
60677
|
+
if ( parents.length === 0 || passes.length === 0 ) return;
|
|
60678
|
+
|
|
60679
|
+
for ( const parent of parents ) {
|
|
60680
|
+
|
|
60681
|
+
parent.traceTs = Math.round( this._getTraceTimestamp( parent.startTime ) );
|
|
60682
|
+
parent.traceDur = Math.max( 1, Math.round( parent.durationMs * 1000 ) );
|
|
60683
|
+
|
|
60684
|
+
const parentEnd = parent.traceTs + parent.traceDur;
|
|
60685
|
+
let minTs = parent.traceTs;
|
|
60686
|
+
let maxEnd = parentEnd;
|
|
60687
|
+
let touched = false;
|
|
60688
|
+
|
|
60689
|
+
for ( const pass of passes ) {
|
|
60690
|
+
|
|
60691
|
+
const passEnd = pass.traceTs + pass.traceDur;
|
|
60692
|
+
if ( pass.traceTs >= parentEnd || passEnd <= parent.traceTs ) continue;
|
|
60693
|
+
|
|
60694
|
+
touched = true;
|
|
60695
|
+
if ( pass.traceTs < minTs ) minTs = pass.traceTs;
|
|
60696
|
+
if ( passEnd > maxEnd ) maxEnd = passEnd;
|
|
60697
|
+
|
|
60698
|
+
}
|
|
60699
|
+
|
|
60700
|
+
if ( touched === false ) continue;
|
|
60701
|
+
|
|
60702
|
+
// Start 1µs before the first child so equal-start pairs still nest
|
|
60703
|
+
// (Speedscope sorts same-ts by longer-first; a child that begins
|
|
60704
|
+
// strictly after the parent is unambiguous).
|
|
60705
|
+
parent.traceTs = minTs > 0 ? minTs - 1 : minTs;
|
|
60706
|
+
parent.traceDur = Math.max( 1, maxEnd - parent.traceTs );
|
|
60707
|
+
|
|
60708
|
+
}
|
|
60709
|
+
|
|
60710
|
+
}
|
|
60711
|
+
|
|
60712
|
+
/**
|
|
60713
|
+
* @param {import('./ProfilerService.js').CommonGpuRendererState} state
|
|
60714
|
+
* @param {import('./ProfilerService.js').GpuSpanHandle[]} spans
|
|
60715
|
+
* @return {?{ gpuTimestampOrigin: bigint, gpuTraceOrigin: number }}
|
|
60716
|
+
*/
|
|
60717
|
+
_findEarliestGpuTimestampOrigin( state, spans ) {
|
|
60718
|
+
|
|
60719
|
+
let earliestRange = null;
|
|
60720
|
+
let earliestUid = null;
|
|
60721
|
+
const candidates = spans.concat( state.activeSpans );
|
|
60722
|
+
|
|
60723
|
+
for ( const span of candidates ) {
|
|
60724
|
+
|
|
60725
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60726
|
+
|
|
60727
|
+
const queries = span._queries?.[ type ];
|
|
60728
|
+
if ( queries === undefined ) continue;
|
|
60729
|
+
|
|
60730
|
+
for ( const uid of queries ) {
|
|
60731
|
+
|
|
60732
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
60733
|
+
if ( range !== null && ( earliestRange === null || range.start < earliestRange.start ) ) {
|
|
60734
|
+
|
|
60735
|
+
earliestRange = range;
|
|
60736
|
+
earliestUid = uid;
|
|
60737
|
+
|
|
60738
|
+
}
|
|
60739
|
+
|
|
60740
|
+
}
|
|
60741
|
+
|
|
60742
|
+
}
|
|
60743
|
+
|
|
60744
|
+
}
|
|
60745
|
+
|
|
60746
|
+
if ( earliestRange === null ) return null;
|
|
60747
|
+
|
|
60748
|
+
return {
|
|
60749
|
+
gpuTimestampOrigin: earliestRange.start,
|
|
60750
|
+
gpuTraceOrigin: state.queryCpuTimes.get( earliestUid ) ?? spans[ 0 ].t0,
|
|
60751
|
+
};
|
|
60752
|
+
|
|
60753
|
+
}
|
|
60754
|
+
|
|
60755
|
+
/**
|
|
60756
|
+
* @param {import('./ProfilerService.js').CommonGpuRendererState} state
|
|
60757
|
+
* @param {string} uid
|
|
60758
|
+
* @return {boolean}
|
|
60759
|
+
*/
|
|
60760
|
+
_isGpuQueryReferencedByActiveSpans( state, uid ) {
|
|
60761
|
+
|
|
60762
|
+
for ( const span of state.activeSpans ) {
|
|
60763
|
+
|
|
60764
|
+
if ( span._queries.render.has( uid ) || span._queries.compute.has( uid ) ) return true;
|
|
60765
|
+
|
|
60766
|
+
}
|
|
60767
|
+
|
|
60768
|
+
return false;
|
|
60769
|
+
|
|
60770
|
+
}
|
|
60771
|
+
|
|
60772
|
+
/**
|
|
60773
|
+
* @param {import('./ProfilerService.js').LegacyWebGLGpuRendererState} state
|
|
60774
|
+
*/
|
|
60775
|
+
async _flushLegacyWebGLState( state ) {
|
|
60776
|
+
|
|
60777
|
+
const spans = state.pendingSpans.splice( 0 );
|
|
60778
|
+
|
|
60779
|
+
for ( const span of spans ) {
|
|
60780
|
+
|
|
60781
|
+
const duration = await this._resolveLegacyWebGLQuery( state, span._query );
|
|
60782
|
+
if ( duration !== null && span._generation === this._gpuGeneration && this._enabled ) {
|
|
60783
|
+
|
|
60784
|
+
this._commitGpuDurationSample( span.label, span.t0, duration );
|
|
60785
|
+
|
|
60786
|
+
}
|
|
60787
|
+
|
|
60788
|
+
}
|
|
60789
|
+
|
|
60790
|
+
}
|
|
60791
|
+
|
|
60792
|
+
/**
|
|
60793
|
+
* @param {import('./ProfilerService.js').LegacyWebGLGpuRendererState} state
|
|
60794
|
+
* @param {WebGLQuery} query
|
|
60795
|
+
* @return {Promise<?number>}
|
|
60796
|
+
*/
|
|
60797
|
+
_resolveLegacyWebGLQuery( state, query ) {
|
|
60798
|
+
|
|
60799
|
+
return new Promise( resolve => {
|
|
60800
|
+
|
|
60801
|
+
const poll = () => {
|
|
60802
|
+
|
|
60803
|
+
if ( state.gl.isContextLost() ) {
|
|
60804
|
+
|
|
60805
|
+
state.gl.deleteQuery( query );
|
|
60806
|
+
resolve( null );
|
|
60807
|
+
return;
|
|
60808
|
+
|
|
60809
|
+
}
|
|
60810
|
+
|
|
60811
|
+
const disjoint = state.gl.getParameter( state.extension.GPU_DISJOINT_EXT );
|
|
60812
|
+
const available = state.gl.getQueryParameter( query, state.gl.QUERY_RESULT_AVAILABLE );
|
|
60813
|
+
|
|
60814
|
+
if ( available === false ) {
|
|
60815
|
+
|
|
60816
|
+
setTimeout( poll, 1 );
|
|
60817
|
+
return;
|
|
60818
|
+
|
|
60819
|
+
}
|
|
60820
|
+
|
|
60821
|
+
const result = disjoint
|
|
60822
|
+
? null
|
|
60823
|
+
: Number( state.gl.getQueryParameter( query, state.gl.QUERY_RESULT ) ) / 1e6;
|
|
60824
|
+
state.gl.deleteQuery( query );
|
|
60825
|
+
resolve( result );
|
|
60826
|
+
|
|
60827
|
+
};
|
|
60828
|
+
|
|
60829
|
+
poll();
|
|
60830
|
+
|
|
60831
|
+
} );
|
|
60832
|
+
|
|
60833
|
+
}
|
|
60834
|
+
|
|
60128
60835
|
/**
|
|
60129
60836
|
* @param {string} label
|
|
60130
60837
|
*/
|
|
@@ -60282,6 +60989,46 @@ class ProfilerServiceClass {
|
|
|
60282
60989
|
|
|
60283
60990
|
}
|
|
60284
60991
|
|
|
60992
|
+
/**
|
|
60993
|
+
* @param {string} label
|
|
60994
|
+
* @param {number} startTime
|
|
60995
|
+
* @param {number} durationMs
|
|
60996
|
+
* @param {{ ts: number, dur: number }} [traceOverride]
|
|
60997
|
+
*/
|
|
60998
|
+
_commitGpuDurationSample( label, startTime, durationMs, traceOverride = null ) {
|
|
60999
|
+
|
|
61000
|
+
let buffer = this.gpuBuffers.get( label );
|
|
61001
|
+
if ( buffer === undefined ) {
|
|
61002
|
+
|
|
61003
|
+
buffer = new Float64Array( RING_SIZE );
|
|
61004
|
+
this.gpuBuffers.set( label, buffer );
|
|
61005
|
+
this.gpuCursors.set( label, 0 );
|
|
61006
|
+
this.gpuCounts.set( label, 0 );
|
|
61007
|
+
|
|
61008
|
+
}
|
|
61009
|
+
|
|
61010
|
+
const cursor = this.gpuCursors.get( label );
|
|
61011
|
+
buffer[ cursor ] = durationMs;
|
|
61012
|
+
this.gpuCursors.set( label, ( cursor + 1 ) % RING_SIZE );
|
|
61013
|
+
this.gpuCounts.set( label, Math.min( this.gpuCounts.get( label ) + 1, RING_SIZE ) );
|
|
61014
|
+
this.gpuInvocations.set( label, ( this.gpuInvocations.get( label ) ?? 0 ) + 1 );
|
|
61015
|
+
|
|
61016
|
+
if ( this._profile === 'full' ) {
|
|
61017
|
+
|
|
61018
|
+
this.traceEvents.push( {
|
|
61019
|
+
name: label,
|
|
61020
|
+
ph: 'X',
|
|
61021
|
+
ts: traceOverride?.ts ?? Math.round( this._getTraceTimestamp( startTime ) ),
|
|
61022
|
+
dur: traceOverride?.dur ?? Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
61023
|
+
pid: 1,
|
|
61024
|
+
tid: TRACE_TID_GPU,
|
|
61025
|
+
cat: 'gnsx-gpu',
|
|
61026
|
+
} );
|
|
61027
|
+
|
|
61028
|
+
}
|
|
61029
|
+
|
|
61030
|
+
}
|
|
61031
|
+
|
|
60285
61032
|
/**
|
|
60286
61033
|
* @param {string} label
|
|
60287
61034
|
* @return {number[]}
|
|
@@ -60297,6 +61044,21 @@ class ProfilerServiceClass {
|
|
|
60297
61044
|
|
|
60298
61045
|
}
|
|
60299
61046
|
|
|
61047
|
+
/**
|
|
61048
|
+
* @param {string} label
|
|
61049
|
+
* @return {number[]}
|
|
61050
|
+
*/
|
|
61051
|
+
getValidGpuSamples( label ) {
|
|
61052
|
+
|
|
61053
|
+
const buffer = this.gpuBuffers.get( label );
|
|
61054
|
+
const count = this.gpuCounts.get( label ) ?? 0;
|
|
61055
|
+
if ( buffer === undefined || count === 0 ) return [];
|
|
61056
|
+
return count < RING_SIZE
|
|
61057
|
+
? Array.from( buffer.subarray( 0, count ) )
|
|
61058
|
+
: Array.from( buffer );
|
|
61059
|
+
|
|
61060
|
+
}
|
|
61061
|
+
|
|
60300
61062
|
/**
|
|
60301
61063
|
* @param {string} label
|
|
60302
61064
|
* @return {import('./ProfilerService.js').ProfilerStats|null}
|
|
@@ -60344,6 +61106,31 @@ class ProfilerServiceClass {
|
|
|
60344
61106
|
|
|
60345
61107
|
}
|
|
60346
61108
|
|
|
61109
|
+
/**
|
|
61110
|
+
* @param {string} label
|
|
61111
|
+
* @return {import('./ProfilerService.js').GpuProfilerStats|null}
|
|
61112
|
+
*/
|
|
61113
|
+
getGpuStats( label ) {
|
|
61114
|
+
|
|
61115
|
+
const samples = this.getValidGpuSamples( label );
|
|
61116
|
+
if ( samples.length === 0 ) return null;
|
|
61117
|
+
|
|
61118
|
+
const sorted = [ ...samples ].sort( ( a, b ) => a - b );
|
|
61119
|
+
const avg = sorted.reduce( ( a, b ) => a + b, 0 ) / sorted.length;
|
|
61120
|
+
|
|
61121
|
+
return {
|
|
61122
|
+
label,
|
|
61123
|
+
samples: samples.length,
|
|
61124
|
+
avg,
|
|
61125
|
+
min: sorted[ 0 ],
|
|
61126
|
+
max: sorted[ sorted.length - 1 ],
|
|
61127
|
+
p95: sorted[ Math.floor( sorted.length * 0.95 ) ],
|
|
61128
|
+
frameBudget: ( avg / FRAME_BUDGET_MS ) * 100,
|
|
61129
|
+
totalInvocations: this.gpuInvocations.get( label ) ?? 0,
|
|
61130
|
+
};
|
|
61131
|
+
|
|
61132
|
+
}
|
|
61133
|
+
|
|
60347
61134
|
/**
|
|
60348
61135
|
* @return {import('./ProfilerService.js').ProfilerStats[]}
|
|
60349
61136
|
*/
|
|
@@ -60355,10 +61142,22 @@ class ProfilerServiceClass {
|
|
|
60355
61142
|
|
|
60356
61143
|
}
|
|
60357
61144
|
|
|
61145
|
+
/**
|
|
61146
|
+
* @return {import('./ProfilerService.js').GpuProfilerStats[]}
|
|
61147
|
+
*/
|
|
61148
|
+
getAllGpuStats() {
|
|
61149
|
+
|
|
61150
|
+
return [ ...this.gpuBuffers.keys() ]
|
|
61151
|
+
.map( label => this.getGpuStats( label ) )
|
|
61152
|
+
.filter( stats => stats !== null );
|
|
61153
|
+
|
|
61154
|
+
}
|
|
61155
|
+
|
|
60358
61156
|
report() {
|
|
60359
61157
|
|
|
60360
61158
|
const stats = this.getAllStats();
|
|
60361
|
-
|
|
61159
|
+
const gpuStats = this.getAllGpuStats();
|
|
61160
|
+
if ( stats.length === 0 && gpuStats.length === 0 ) {
|
|
60362
61161
|
|
|
60363
61162
|
console.log( '[ProfilerService] No data. Enable profiling first and wait a few frames.' );
|
|
60364
61163
|
return;
|
|
@@ -60378,6 +61177,24 @@ class ProfilerServiceClass {
|
|
|
60378
61177
|
} ) )
|
|
60379
61178
|
);
|
|
60380
61179
|
|
|
61180
|
+
if ( gpuStats.length > 0 ) {
|
|
61181
|
+
|
|
61182
|
+
gpuStats.sort( ( a, b ) => b.avg - a.avg );
|
|
61183
|
+
console.log( '[ProfilerService] GPU timings' );
|
|
61184
|
+
console.table(
|
|
61185
|
+
gpuStats.map( stats => ( {
|
|
61186
|
+
label: stats.label,
|
|
61187
|
+
'avg ms': stats.avg.toFixed( 3 ),
|
|
61188
|
+
'min ms': stats.min.toFixed( 3 ),
|
|
61189
|
+
'max ms': stats.max.toFixed( 3 ),
|
|
61190
|
+
'p95 ms': stats.p95.toFixed( 3 ),
|
|
61191
|
+
'budget %': stats.frameBudget.toFixed( 1 ) + '%',
|
|
61192
|
+
samples: stats.samples,
|
|
61193
|
+
} ) )
|
|
61194
|
+
);
|
|
61195
|
+
|
|
61196
|
+
}
|
|
61197
|
+
|
|
60381
61198
|
}
|
|
60382
61199
|
|
|
60383
61200
|
/**
|
|
@@ -60394,6 +61211,7 @@ class ProfilerServiceClass {
|
|
|
60394
61211
|
|
|
60395
61212
|
} );
|
|
60396
61213
|
const hasAsync = slices.some( e => e.tid === TRACE_TID_ASYNC );
|
|
61214
|
+
const hasGpu = slices.some( e => e.tid === TRACE_TID_GPU );
|
|
60397
61215
|
/** @type {import('./ProfilerService.js').ChromeTraceMetadataEvent[]} */
|
|
60398
61216
|
const prefix = [
|
|
60399
61217
|
{
|
|
@@ -60429,6 +61247,20 @@ class ProfilerServiceClass {
|
|
|
60429
61247
|
|
|
60430
61248
|
}
|
|
60431
61249
|
|
|
61250
|
+
if ( hasGpu ) {
|
|
61251
|
+
|
|
61252
|
+
prefix.push( {
|
|
61253
|
+
cat: '__metadata',
|
|
61254
|
+
name: 'thread_name',
|
|
61255
|
+
ph: 'M',
|
|
61256
|
+
pid: 1,
|
|
61257
|
+
tid: TRACE_TID_GPU,
|
|
61258
|
+
ts: 0,
|
|
61259
|
+
args: { name: 'GPU (device timestamps)' },
|
|
61260
|
+
} );
|
|
61261
|
+
|
|
61262
|
+
}
|
|
61263
|
+
|
|
60432
61264
|
return {
|
|
60433
61265
|
displayTimeUnit: 'ms',
|
|
60434
61266
|
traceEvents: [ ...prefix, ...slices ],
|
|
@@ -60493,8 +61325,24 @@ class ProfilerServiceClass {
|
|
|
60493
61325
|
|
|
60494
61326
|
}
|
|
60495
61327
|
|
|
61328
|
+
/**
|
|
61329
|
+
* @return {import('./ProfilerService.js').GpuProfilerStats[]}
|
|
61330
|
+
*/
|
|
61331
|
+
exportGpuJSON() {
|
|
61332
|
+
|
|
61333
|
+
return this.getAllGpuStats();
|
|
61334
|
+
|
|
61335
|
+
}
|
|
61336
|
+
|
|
60496
61337
|
_clearState() {
|
|
60497
61338
|
|
|
61339
|
+
this._gpuGeneration ++;
|
|
61340
|
+
for ( const state of this.gpuRendererStates.values() ) {
|
|
61341
|
+
|
|
61342
|
+
this._clearGpuRendererState( state );
|
|
61343
|
+
|
|
61344
|
+
}
|
|
61345
|
+
|
|
60498
61346
|
this.buffers.clear();
|
|
60499
61347
|
this.cursors.clear();
|
|
60500
61348
|
this.counts.clear();
|
|
@@ -60507,6 +61355,79 @@ class ProfilerServiceClass {
|
|
|
60507
61355
|
this.selfCounts.clear();
|
|
60508
61356
|
this.callStack.length = 0;
|
|
60509
61357
|
this.invocations.clear();
|
|
61358
|
+
this.gpuBuffers.clear();
|
|
61359
|
+
this.gpuCursors.clear();
|
|
61360
|
+
this.gpuCounts.clear();
|
|
61361
|
+
this.gpuInvocations.clear();
|
|
61362
|
+
|
|
61363
|
+
}
|
|
61364
|
+
|
|
61365
|
+
/**
|
|
61366
|
+
* @param {import('./ProfilerService.js').GpuRendererState} state
|
|
61367
|
+
*/
|
|
61368
|
+
_clearGpuRendererState( state ) {
|
|
61369
|
+
|
|
61370
|
+
if ( state.kind === 'common' ) {
|
|
61371
|
+
|
|
61372
|
+
state.activeSpans.length = 0;
|
|
61373
|
+
state.pendingSpans.length = 0;
|
|
61374
|
+
state.queryCpuTimes.clear();
|
|
61375
|
+
state.gpuTimestampOrigin = null;
|
|
61376
|
+
state.gpuTraceOrigin = null;
|
|
61377
|
+
return;
|
|
61378
|
+
|
|
61379
|
+
}
|
|
61380
|
+
|
|
61381
|
+
if ( state.activeSpan !== null ) {
|
|
61382
|
+
|
|
61383
|
+
try {
|
|
61384
|
+
|
|
61385
|
+
state.gl.endQuery( state.extension.TIME_ELAPSED_EXT );
|
|
61386
|
+
|
|
61387
|
+
} catch {
|
|
61388
|
+
|
|
61389
|
+
// The context may have been lost while the query was active.
|
|
61390
|
+
|
|
61391
|
+
}
|
|
61392
|
+
|
|
61393
|
+
state.gl.deleteQuery( state.activeSpan._query );
|
|
61394
|
+
state.activeSpan = null;
|
|
61395
|
+
|
|
61396
|
+
}
|
|
61397
|
+
|
|
61398
|
+
if ( state.flushPromise === null ) {
|
|
61399
|
+
|
|
61400
|
+
for ( const span of state.pendingSpans ) {
|
|
61401
|
+
|
|
61402
|
+
state.gl.deleteQuery( span._query );
|
|
61403
|
+
|
|
61404
|
+
}
|
|
61405
|
+
|
|
61406
|
+
}
|
|
61407
|
+
|
|
61408
|
+
state.pendingSpans.length = 0;
|
|
61409
|
+
|
|
61410
|
+
}
|
|
61411
|
+
|
|
61412
|
+
_detachGpuRenderers() {
|
|
61413
|
+
|
|
61414
|
+
for ( const state of this.gpuRendererStates.values() ) {
|
|
61415
|
+
|
|
61416
|
+
if ( state.kind === 'common' && state.available ) {
|
|
61417
|
+
|
|
61418
|
+
state.renderer.backend.removeTimestampQueryListener( state.listener );
|
|
61419
|
+
|
|
61420
|
+
}
|
|
61421
|
+
|
|
61422
|
+
if ( state.kind === 'common' ) {
|
|
61423
|
+
|
|
61424
|
+
state.renderer.backend.trackTimestamp = state.previousTrackTimestamp;
|
|
61425
|
+
|
|
61426
|
+
}
|
|
61427
|
+
|
|
61428
|
+
}
|
|
61429
|
+
|
|
61430
|
+
this.gpuRendererStates.clear();
|
|
60510
61431
|
|
|
60511
61432
|
}
|
|
60512
61433
|
|
|
@@ -60536,6 +61457,18 @@ class ProfilerServiceClass {
|
|
|
60536
61457
|
* @property {number|undefined} selfFrameBudget Exclusive time as percentage of a 60 fps frame budget.
|
|
60537
61458
|
*/
|
|
60538
61459
|
|
|
61460
|
+
/**
|
|
61461
|
+
* @typedef {Object} GpuProfilerStats
|
|
61462
|
+
* @property {string} label
|
|
61463
|
+
* @property {number} samples
|
|
61464
|
+
* @property {number} avg
|
|
61465
|
+
* @property {number} min
|
|
61466
|
+
* @property {number} max
|
|
61467
|
+
* @property {number} p95
|
|
61468
|
+
* @property {number} frameBudget Percentage of a 60 fps frame budget (16.67 ms)
|
|
61469
|
+
* @property {number} totalInvocations
|
|
61470
|
+
*/
|
|
61471
|
+
|
|
60539
61472
|
/**
|
|
60540
61473
|
* @typedef {Object} SpanHandle
|
|
60541
61474
|
* @property {string} label
|
|
@@ -60544,6 +61477,51 @@ class ProfilerServiceClass {
|
|
|
60544
61477
|
* @property {string} _startMark
|
|
60545
61478
|
*/
|
|
60546
61479
|
|
|
61480
|
+
/**
|
|
61481
|
+
* @typedef {Object} GpuSpanHandle
|
|
61482
|
+
* @property {string} label
|
|
61483
|
+
* @property {?Object} renderer
|
|
61484
|
+
* @property {number} t0
|
|
61485
|
+
* @property {number} _seq
|
|
61486
|
+
* @property {number} _generation
|
|
61487
|
+
* @property {{render: Set<string>, compute: Set<string>}} [_queries]
|
|
61488
|
+
* @property {WebGLQuery} [_query]
|
|
61489
|
+
* @property {number} [_endTime]
|
|
61490
|
+
*/
|
|
61491
|
+
|
|
61492
|
+
/**
|
|
61493
|
+
* @typedef {Object} CommonGpuRendererState
|
|
61494
|
+
* @property {'common'} kind
|
|
61495
|
+
* @property {boolean} available
|
|
61496
|
+
* @property {Object} renderer
|
|
61497
|
+
* @property {function('render'|'compute', string, ?string): void} listener
|
|
61498
|
+
* @property {boolean} previousTrackTimestamp
|
|
61499
|
+
* @property {GpuSpanHandle[]} activeSpans
|
|
61500
|
+
* @property {GpuSpanHandle[]} pendingSpans
|
|
61501
|
+
* @property {Map<string, number>} queryCpuTimes
|
|
61502
|
+
* @property {?bigint} gpuTimestampOrigin
|
|
61503
|
+
* @property {?number} gpuTraceOrigin
|
|
61504
|
+
* @property {?Promise<void>} flushPromise
|
|
61505
|
+
* @property {boolean} flushScheduled
|
|
61506
|
+
*/
|
|
61507
|
+
|
|
61508
|
+
/**
|
|
61509
|
+
* @typedef {Object} LegacyWebGLGpuRendererState
|
|
61510
|
+
* @property {'legacy-webgl'} kind
|
|
61511
|
+
* @property {boolean} available
|
|
61512
|
+
* @property {Object} renderer
|
|
61513
|
+
* @property {WebGL2RenderingContext} gl
|
|
61514
|
+
* @property {?EXT_disjoint_timer_query_webgl2} extension
|
|
61515
|
+
* @property {?GpuSpanHandle} activeSpan
|
|
61516
|
+
* @property {GpuSpanHandle[]} pendingSpans
|
|
61517
|
+
* @property {?Promise<void>} flushPromise
|
|
61518
|
+
* @property {boolean} flushScheduled
|
|
61519
|
+
*/
|
|
61520
|
+
|
|
61521
|
+
/**
|
|
61522
|
+
* @typedef {CommonGpuRendererState|LegacyWebGLGpuRendererState} GpuRendererState
|
|
61523
|
+
*/
|
|
61524
|
+
|
|
60547
61525
|
/**
|
|
60548
61526
|
* @typedef {Object} EndSpanOptions
|
|
60549
61527
|
* @property {boolean} [asyncTimeline] When true, trace slice uses `tid=2` (virtual async row).
|
|
@@ -60557,7 +61535,7 @@ class ProfilerServiceClass {
|
|
|
60557
61535
|
* @property {number} dur
|
|
60558
61536
|
* @property {1} pid
|
|
60559
61537
|
* @property {number} tid
|
|
60560
|
-
* @property {'gnsx'} cat
|
|
61538
|
+
* @property {'gnsx'|'gnsx-gpu'} cat
|
|
60561
61539
|
*/
|
|
60562
61540
|
|
|
60563
61541
|
/**
|