@gnsx/three 0.185.8 → 0.185.10
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 +759 -2
- package/build/three.core.js +759 -2
- package/build/three.core.min.js +1 -1
- package/build/three.tsl.js +1 -1
- package/build/three.tsl.min.js +1 -1
- package/build/three.webgpu.js +138 -9
- package/build/three.webgpu.min.js +1 -1
- package/build/three.webgpu.nodes.js +138 -9
- package/build/three.webgpu.nodes.min.js +1 -1
- package/package.json +1 -1
- package/src/profiler/ProfilerService.js +761 -2
- package/src/renderers/common/Backend.js +60 -0
- package/src/renderers/common/RenderContext.js +10 -0
- package/src/renderers/common/Renderer.js +5 -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 +13 -2
- package/src/renderers/webgpu/utils/WebGPUTimestampQueryPool.js +6 -0
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,467 @@ 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
|
+
const passSpan = {
|
|
60413
|
+
label: `GPU pass: ${label}`,
|
|
60414
|
+
renderer,
|
|
60415
|
+
t0: cpuTime,
|
|
60416
|
+
_seq: ++ this._markId,
|
|
60417
|
+
_generation: this._gpuGeneration,
|
|
60418
|
+
_queries: {
|
|
60419
|
+
[ TimestampQuery.RENDER ]: new Set(),
|
|
60420
|
+
[ TimestampQuery.COMPUTE ]: new Set(),
|
|
60421
|
+
},
|
|
60422
|
+
};
|
|
60423
|
+
passSpan._queries[ type ].add( uid );
|
|
60424
|
+
state.pendingSpans.push( passSpan );
|
|
60425
|
+
this._scheduleGpuFlush( state );
|
|
60426
|
+
|
|
60427
|
+
}
|
|
60428
|
+
|
|
60429
|
+
}
|
|
60430
|
+
|
|
60431
|
+
/**
|
|
60432
|
+
* @param {import('./ProfilerService.js').GpuRendererState} state
|
|
60433
|
+
*/
|
|
60434
|
+
_scheduleGpuFlush( state ) {
|
|
60435
|
+
|
|
60436
|
+
if ( state.flushScheduled ) return;
|
|
60437
|
+
state.flushScheduled = true;
|
|
60438
|
+
|
|
60439
|
+
const callback = () => {
|
|
60440
|
+
|
|
60441
|
+
state.flushScheduled = false;
|
|
60442
|
+
if ( this.gpuRendererStates.get( state.renderer ) === state ) {
|
|
60443
|
+
|
|
60444
|
+
this.flushGpu( state.renderer ).catch( error => {
|
|
60445
|
+
|
|
60446
|
+
warnOnce( `ProfilerService: Unable to resolve GPU timestamps (${error.message}).` );
|
|
60447
|
+
|
|
60448
|
+
} );
|
|
60449
|
+
|
|
60450
|
+
}
|
|
60451
|
+
|
|
60452
|
+
};
|
|
60453
|
+
|
|
60454
|
+
if ( typeof requestAnimationFrame === 'function' ) {
|
|
60455
|
+
|
|
60456
|
+
requestAnimationFrame( callback );
|
|
60457
|
+
|
|
60458
|
+
} else {
|
|
60459
|
+
|
|
60460
|
+
setTimeout( callback, 0 );
|
|
60461
|
+
|
|
60462
|
+
}
|
|
60463
|
+
|
|
60464
|
+
}
|
|
60465
|
+
|
|
60466
|
+
/**
|
|
60467
|
+
* @param {import('./ProfilerService.js').CommonGpuRendererState} state
|
|
60468
|
+
*/
|
|
60469
|
+
async _flushCommonGpuState( state ) {
|
|
60470
|
+
|
|
60471
|
+
const spans = state.pendingSpans.splice( 0 );
|
|
60472
|
+
if ( spans.length === 0 ) return;
|
|
60473
|
+
|
|
60474
|
+
const hasRenderQueries = spans.some( span => span._queries.render.size > 0 );
|
|
60475
|
+
const hasComputeQueries = spans.some( span => span._queries.compute.size > 0 );
|
|
60476
|
+
const resolutions = [];
|
|
60477
|
+
|
|
60478
|
+
if ( hasRenderQueries ) resolutions.push( state.renderer.resolveTimestampsAsync( TimestampQuery.RENDER ) );
|
|
60479
|
+
if ( hasComputeQueries ) resolutions.push( state.renderer.resolveTimestampsAsync( TimestampQuery.COMPUTE ) );
|
|
60480
|
+
await Promise.all( resolutions );
|
|
60481
|
+
|
|
60482
|
+
if ( this._enabled === false ) return;
|
|
60483
|
+
|
|
60484
|
+
if ( state.gpuTimestampOrigin === null ) {
|
|
60485
|
+
|
|
60486
|
+
let earliestRange = null;
|
|
60487
|
+
let earliestUid = null;
|
|
60488
|
+
for ( const span of spans ) {
|
|
60489
|
+
|
|
60490
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60491
|
+
|
|
60492
|
+
for ( const uid of span._queries[ type ] ) {
|
|
60493
|
+
|
|
60494
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
60495
|
+
if ( range !== null && ( earliestRange === null || range.start < earliestRange.start ) ) {
|
|
60496
|
+
|
|
60497
|
+
earliestRange = range;
|
|
60498
|
+
earliestUid = uid;
|
|
60499
|
+
|
|
60500
|
+
}
|
|
60501
|
+
|
|
60502
|
+
}
|
|
60503
|
+
|
|
60504
|
+
}
|
|
60505
|
+
|
|
60506
|
+
}
|
|
60507
|
+
|
|
60508
|
+
if ( earliestRange !== null ) {
|
|
60509
|
+
|
|
60510
|
+
state.gpuTimestampOrigin = earliestRange.start;
|
|
60511
|
+
state.gpuTraceOrigin = state.queryCpuTimes.get( earliestUid ) ?? spans[ 0 ].t0;
|
|
60512
|
+
|
|
60513
|
+
}
|
|
60514
|
+
|
|
60515
|
+
}
|
|
60516
|
+
|
|
60517
|
+
const resolvedUids = new Set();
|
|
60518
|
+
for ( const span of spans ) {
|
|
60519
|
+
|
|
60520
|
+
if ( span._generation !== this._gpuGeneration ) continue;
|
|
60521
|
+
|
|
60522
|
+
let duration = 0;
|
|
60523
|
+
let resolvedQueries = 0;
|
|
60524
|
+
let rangedQueries = 0;
|
|
60525
|
+
let gpuStart = null;
|
|
60526
|
+
let gpuEnd = null;
|
|
60527
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60528
|
+
|
|
60529
|
+
for ( const uid of span._queries[ type ] ) {
|
|
60530
|
+
|
|
60531
|
+
if ( state.renderer.backend.hasTimestampQuery( uid ) ) {
|
|
60532
|
+
|
|
60533
|
+
duration += state.renderer.backend.getTimestamp( uid );
|
|
60534
|
+
resolvedQueries ++;
|
|
60535
|
+
resolvedUids.add( uid );
|
|
60536
|
+
|
|
60537
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
60538
|
+
if ( range !== null ) {
|
|
60539
|
+
|
|
60540
|
+
rangedQueries ++;
|
|
60541
|
+
if ( gpuStart === null || range.start < gpuStart ) gpuStart = range.start;
|
|
60542
|
+
if ( gpuEnd === null || range.end > gpuEnd ) gpuEnd = range.end;
|
|
60543
|
+
|
|
60544
|
+
}
|
|
60545
|
+
|
|
60546
|
+
}
|
|
60547
|
+
|
|
60548
|
+
}
|
|
60549
|
+
|
|
60550
|
+
}
|
|
60551
|
+
|
|
60552
|
+
if ( resolvedQueries > 0 ) {
|
|
60553
|
+
|
|
60554
|
+
let startTime = span.t0;
|
|
60555
|
+
if ( rangedQueries === resolvedQueries && gpuStart !== null && gpuEnd !== null && state.gpuTimestampOrigin !== null ) {
|
|
60556
|
+
|
|
60557
|
+
duration = Number( gpuEnd - gpuStart ) / 1e6;
|
|
60558
|
+
startTime = state.gpuTraceOrigin + Number( gpuStart - state.gpuTimestampOrigin ) / 1e6;
|
|
60559
|
+
|
|
60560
|
+
}
|
|
60561
|
+
|
|
60562
|
+
this._commitGpuDurationSample( span.label, startTime, duration );
|
|
60563
|
+
|
|
60564
|
+
}
|
|
60565
|
+
|
|
60566
|
+
}
|
|
60567
|
+
|
|
60568
|
+
for ( const uid of resolvedUids ) state.queryCpuTimes.delete( uid );
|
|
60569
|
+
|
|
60570
|
+
}
|
|
60571
|
+
|
|
60572
|
+
/**
|
|
60573
|
+
* @param {import('./ProfilerService.js').LegacyWebGLGpuRendererState} state
|
|
60574
|
+
*/
|
|
60575
|
+
async _flushLegacyWebGLState( state ) {
|
|
60576
|
+
|
|
60577
|
+
const spans = state.pendingSpans.splice( 0 );
|
|
60578
|
+
|
|
60579
|
+
for ( const span of spans ) {
|
|
60580
|
+
|
|
60581
|
+
const duration = await this._resolveLegacyWebGLQuery( state, span._query );
|
|
60582
|
+
if ( duration !== null && span._generation === this._gpuGeneration && this._enabled ) {
|
|
60583
|
+
|
|
60584
|
+
this._commitGpuDurationSample( span.label, span.t0, duration );
|
|
60585
|
+
|
|
60586
|
+
}
|
|
60587
|
+
|
|
60588
|
+
}
|
|
60589
|
+
|
|
60590
|
+
}
|
|
60591
|
+
|
|
60592
|
+
/**
|
|
60593
|
+
* @param {import('./ProfilerService.js').LegacyWebGLGpuRendererState} state
|
|
60594
|
+
* @param {WebGLQuery} query
|
|
60595
|
+
* @return {Promise<?number>}
|
|
60596
|
+
*/
|
|
60597
|
+
_resolveLegacyWebGLQuery( state, query ) {
|
|
60598
|
+
|
|
60599
|
+
return new Promise( resolve => {
|
|
60600
|
+
|
|
60601
|
+
const poll = () => {
|
|
60602
|
+
|
|
60603
|
+
if ( state.gl.isContextLost() ) {
|
|
60604
|
+
|
|
60605
|
+
state.gl.deleteQuery( query );
|
|
60606
|
+
resolve( null );
|
|
60607
|
+
return;
|
|
60608
|
+
|
|
60609
|
+
}
|
|
60610
|
+
|
|
60611
|
+
const disjoint = state.gl.getParameter( state.extension.GPU_DISJOINT_EXT );
|
|
60612
|
+
const available = state.gl.getQueryParameter( query, state.gl.QUERY_RESULT_AVAILABLE );
|
|
60613
|
+
|
|
60614
|
+
if ( available === false ) {
|
|
60615
|
+
|
|
60616
|
+
setTimeout( poll, 1 );
|
|
60617
|
+
return;
|
|
60618
|
+
|
|
60619
|
+
}
|
|
60620
|
+
|
|
60621
|
+
const result = disjoint
|
|
60622
|
+
? null
|
|
60623
|
+
: Number( state.gl.getQueryParameter( query, state.gl.QUERY_RESULT ) ) / 1e6;
|
|
60624
|
+
state.gl.deleteQuery( query );
|
|
60625
|
+
resolve( result );
|
|
60626
|
+
|
|
60627
|
+
};
|
|
60628
|
+
|
|
60629
|
+
poll();
|
|
60630
|
+
|
|
60631
|
+
} );
|
|
60632
|
+
|
|
60633
|
+
}
|
|
60634
|
+
|
|
60148
60635
|
/**
|
|
60149
60636
|
* @param {string} label
|
|
60150
60637
|
*/
|
|
@@ -60302,6 +60789,45 @@ class ProfilerServiceClass {
|
|
|
60302
60789
|
|
|
60303
60790
|
}
|
|
60304
60791
|
|
|
60792
|
+
/**
|
|
60793
|
+
* @param {string} label
|
|
60794
|
+
* @param {number} startTime
|
|
60795
|
+
* @param {number} durationMs
|
|
60796
|
+
*/
|
|
60797
|
+
_commitGpuDurationSample( label, startTime, durationMs ) {
|
|
60798
|
+
|
|
60799
|
+
let buffer = this.gpuBuffers.get( label );
|
|
60800
|
+
if ( buffer === undefined ) {
|
|
60801
|
+
|
|
60802
|
+
buffer = new Float64Array( RING_SIZE );
|
|
60803
|
+
this.gpuBuffers.set( label, buffer );
|
|
60804
|
+
this.gpuCursors.set( label, 0 );
|
|
60805
|
+
this.gpuCounts.set( label, 0 );
|
|
60806
|
+
|
|
60807
|
+
}
|
|
60808
|
+
|
|
60809
|
+
const cursor = this.gpuCursors.get( label );
|
|
60810
|
+
buffer[ cursor ] = durationMs;
|
|
60811
|
+
this.gpuCursors.set( label, ( cursor + 1 ) % RING_SIZE );
|
|
60812
|
+
this.gpuCounts.set( label, Math.min( this.gpuCounts.get( label ) + 1, RING_SIZE ) );
|
|
60813
|
+
this.gpuInvocations.set( label, ( this.gpuInvocations.get( label ) ?? 0 ) + 1 );
|
|
60814
|
+
|
|
60815
|
+
if ( this._profile === 'full' ) {
|
|
60816
|
+
|
|
60817
|
+
this.traceEvents.push( {
|
|
60818
|
+
name: label,
|
|
60819
|
+
ph: 'X',
|
|
60820
|
+
ts: Math.round( this._getTraceTimestamp( startTime ) ),
|
|
60821
|
+
dur: Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
60822
|
+
pid: 1,
|
|
60823
|
+
tid: TRACE_TID_GPU,
|
|
60824
|
+
cat: 'gnsx-gpu',
|
|
60825
|
+
} );
|
|
60826
|
+
|
|
60827
|
+
}
|
|
60828
|
+
|
|
60829
|
+
}
|
|
60830
|
+
|
|
60305
60831
|
/**
|
|
60306
60832
|
* @param {string} label
|
|
60307
60833
|
* @return {number[]}
|
|
@@ -60317,6 +60843,21 @@ class ProfilerServiceClass {
|
|
|
60317
60843
|
|
|
60318
60844
|
}
|
|
60319
60845
|
|
|
60846
|
+
/**
|
|
60847
|
+
* @param {string} label
|
|
60848
|
+
* @return {number[]}
|
|
60849
|
+
*/
|
|
60850
|
+
getValidGpuSamples( label ) {
|
|
60851
|
+
|
|
60852
|
+
const buffer = this.gpuBuffers.get( label );
|
|
60853
|
+
const count = this.gpuCounts.get( label ) ?? 0;
|
|
60854
|
+
if ( buffer === undefined || count === 0 ) return [];
|
|
60855
|
+
return count < RING_SIZE
|
|
60856
|
+
? Array.from( buffer.subarray( 0, count ) )
|
|
60857
|
+
: Array.from( buffer );
|
|
60858
|
+
|
|
60859
|
+
}
|
|
60860
|
+
|
|
60320
60861
|
/**
|
|
60321
60862
|
* @param {string} label
|
|
60322
60863
|
* @return {import('./ProfilerService.js').ProfilerStats|null}
|
|
@@ -60364,6 +60905,31 @@ class ProfilerServiceClass {
|
|
|
60364
60905
|
|
|
60365
60906
|
}
|
|
60366
60907
|
|
|
60908
|
+
/**
|
|
60909
|
+
* @param {string} label
|
|
60910
|
+
* @return {import('./ProfilerService.js').GpuProfilerStats|null}
|
|
60911
|
+
*/
|
|
60912
|
+
getGpuStats( label ) {
|
|
60913
|
+
|
|
60914
|
+
const samples = this.getValidGpuSamples( label );
|
|
60915
|
+
if ( samples.length === 0 ) return null;
|
|
60916
|
+
|
|
60917
|
+
const sorted = [ ...samples ].sort( ( a, b ) => a - b );
|
|
60918
|
+
const avg = sorted.reduce( ( a, b ) => a + b, 0 ) / sorted.length;
|
|
60919
|
+
|
|
60920
|
+
return {
|
|
60921
|
+
label,
|
|
60922
|
+
samples: samples.length,
|
|
60923
|
+
avg,
|
|
60924
|
+
min: sorted[ 0 ],
|
|
60925
|
+
max: sorted[ sorted.length - 1 ],
|
|
60926
|
+
p95: sorted[ Math.floor( sorted.length * 0.95 ) ],
|
|
60927
|
+
frameBudget: ( avg / FRAME_BUDGET_MS ) * 100,
|
|
60928
|
+
totalInvocations: this.gpuInvocations.get( label ) ?? 0,
|
|
60929
|
+
};
|
|
60930
|
+
|
|
60931
|
+
}
|
|
60932
|
+
|
|
60367
60933
|
/**
|
|
60368
60934
|
* @return {import('./ProfilerService.js').ProfilerStats[]}
|
|
60369
60935
|
*/
|
|
@@ -60375,10 +60941,22 @@ class ProfilerServiceClass {
|
|
|
60375
60941
|
|
|
60376
60942
|
}
|
|
60377
60943
|
|
|
60944
|
+
/**
|
|
60945
|
+
* @return {import('./ProfilerService.js').GpuProfilerStats[]}
|
|
60946
|
+
*/
|
|
60947
|
+
getAllGpuStats() {
|
|
60948
|
+
|
|
60949
|
+
return [ ...this.gpuBuffers.keys() ]
|
|
60950
|
+
.map( label => this.getGpuStats( label ) )
|
|
60951
|
+
.filter( stats => stats !== null );
|
|
60952
|
+
|
|
60953
|
+
}
|
|
60954
|
+
|
|
60378
60955
|
report() {
|
|
60379
60956
|
|
|
60380
60957
|
const stats = this.getAllStats();
|
|
60381
|
-
|
|
60958
|
+
const gpuStats = this.getAllGpuStats();
|
|
60959
|
+
if ( stats.length === 0 && gpuStats.length === 0 ) {
|
|
60382
60960
|
|
|
60383
60961
|
console.log( '[ProfilerService] No data. Enable profiling first and wait a few frames.' );
|
|
60384
60962
|
return;
|
|
@@ -60398,6 +60976,24 @@ class ProfilerServiceClass {
|
|
|
60398
60976
|
} ) )
|
|
60399
60977
|
);
|
|
60400
60978
|
|
|
60979
|
+
if ( gpuStats.length > 0 ) {
|
|
60980
|
+
|
|
60981
|
+
gpuStats.sort( ( a, b ) => b.avg - a.avg );
|
|
60982
|
+
console.log( '[ProfilerService] GPU timings' );
|
|
60983
|
+
console.table(
|
|
60984
|
+
gpuStats.map( stats => ( {
|
|
60985
|
+
label: stats.label,
|
|
60986
|
+
'avg ms': stats.avg.toFixed( 3 ),
|
|
60987
|
+
'min ms': stats.min.toFixed( 3 ),
|
|
60988
|
+
'max ms': stats.max.toFixed( 3 ),
|
|
60989
|
+
'p95 ms': stats.p95.toFixed( 3 ),
|
|
60990
|
+
'budget %': stats.frameBudget.toFixed( 1 ) + '%',
|
|
60991
|
+
samples: stats.samples,
|
|
60992
|
+
} ) )
|
|
60993
|
+
);
|
|
60994
|
+
|
|
60995
|
+
}
|
|
60996
|
+
|
|
60401
60997
|
}
|
|
60402
60998
|
|
|
60403
60999
|
/**
|
|
@@ -60414,6 +61010,7 @@ class ProfilerServiceClass {
|
|
|
60414
61010
|
|
|
60415
61011
|
} );
|
|
60416
61012
|
const hasAsync = slices.some( e => e.tid === TRACE_TID_ASYNC );
|
|
61013
|
+
const hasGpu = slices.some( e => e.tid === TRACE_TID_GPU );
|
|
60417
61014
|
/** @type {import('./ProfilerService.js').ChromeTraceMetadataEvent[]} */
|
|
60418
61015
|
const prefix = [
|
|
60419
61016
|
{
|
|
@@ -60449,6 +61046,20 @@ class ProfilerServiceClass {
|
|
|
60449
61046
|
|
|
60450
61047
|
}
|
|
60451
61048
|
|
|
61049
|
+
if ( hasGpu ) {
|
|
61050
|
+
|
|
61051
|
+
prefix.push( {
|
|
61052
|
+
cat: '__metadata',
|
|
61053
|
+
name: 'thread_name',
|
|
61054
|
+
ph: 'M',
|
|
61055
|
+
pid: 1,
|
|
61056
|
+
tid: TRACE_TID_GPU,
|
|
61057
|
+
ts: 0,
|
|
61058
|
+
args: { name: 'GPU (device timestamps)' },
|
|
61059
|
+
} );
|
|
61060
|
+
|
|
61061
|
+
}
|
|
61062
|
+
|
|
60452
61063
|
return {
|
|
60453
61064
|
displayTimeUnit: 'ms',
|
|
60454
61065
|
traceEvents: [ ...prefix, ...slices ],
|
|
@@ -60513,8 +61124,24 @@ class ProfilerServiceClass {
|
|
|
60513
61124
|
|
|
60514
61125
|
}
|
|
60515
61126
|
|
|
61127
|
+
/**
|
|
61128
|
+
* @return {import('./ProfilerService.js').GpuProfilerStats[]}
|
|
61129
|
+
*/
|
|
61130
|
+
exportGpuJSON() {
|
|
61131
|
+
|
|
61132
|
+
return this.getAllGpuStats();
|
|
61133
|
+
|
|
61134
|
+
}
|
|
61135
|
+
|
|
60516
61136
|
_clearState() {
|
|
60517
61137
|
|
|
61138
|
+
this._gpuGeneration ++;
|
|
61139
|
+
for ( const state of this.gpuRendererStates.values() ) {
|
|
61140
|
+
|
|
61141
|
+
this._clearGpuRendererState( state );
|
|
61142
|
+
|
|
61143
|
+
}
|
|
61144
|
+
|
|
60518
61145
|
this.buffers.clear();
|
|
60519
61146
|
this.cursors.clear();
|
|
60520
61147
|
this.counts.clear();
|
|
@@ -60527,6 +61154,79 @@ class ProfilerServiceClass {
|
|
|
60527
61154
|
this.selfCounts.clear();
|
|
60528
61155
|
this.callStack.length = 0;
|
|
60529
61156
|
this.invocations.clear();
|
|
61157
|
+
this.gpuBuffers.clear();
|
|
61158
|
+
this.gpuCursors.clear();
|
|
61159
|
+
this.gpuCounts.clear();
|
|
61160
|
+
this.gpuInvocations.clear();
|
|
61161
|
+
|
|
61162
|
+
}
|
|
61163
|
+
|
|
61164
|
+
/**
|
|
61165
|
+
* @param {import('./ProfilerService.js').GpuRendererState} state
|
|
61166
|
+
*/
|
|
61167
|
+
_clearGpuRendererState( state ) {
|
|
61168
|
+
|
|
61169
|
+
if ( state.kind === 'common' ) {
|
|
61170
|
+
|
|
61171
|
+
state.activeSpans.length = 0;
|
|
61172
|
+
state.pendingSpans.length = 0;
|
|
61173
|
+
state.queryCpuTimes.clear();
|
|
61174
|
+
state.gpuTimestampOrigin = null;
|
|
61175
|
+
state.gpuTraceOrigin = null;
|
|
61176
|
+
return;
|
|
61177
|
+
|
|
61178
|
+
}
|
|
61179
|
+
|
|
61180
|
+
if ( state.activeSpan !== null ) {
|
|
61181
|
+
|
|
61182
|
+
try {
|
|
61183
|
+
|
|
61184
|
+
state.gl.endQuery( state.extension.TIME_ELAPSED_EXT );
|
|
61185
|
+
|
|
61186
|
+
} catch {
|
|
61187
|
+
|
|
61188
|
+
// The context may have been lost while the query was active.
|
|
61189
|
+
|
|
61190
|
+
}
|
|
61191
|
+
|
|
61192
|
+
state.gl.deleteQuery( state.activeSpan._query );
|
|
61193
|
+
state.activeSpan = null;
|
|
61194
|
+
|
|
61195
|
+
}
|
|
61196
|
+
|
|
61197
|
+
if ( state.flushPromise === null ) {
|
|
61198
|
+
|
|
61199
|
+
for ( const span of state.pendingSpans ) {
|
|
61200
|
+
|
|
61201
|
+
state.gl.deleteQuery( span._query );
|
|
61202
|
+
|
|
61203
|
+
}
|
|
61204
|
+
|
|
61205
|
+
}
|
|
61206
|
+
|
|
61207
|
+
state.pendingSpans.length = 0;
|
|
61208
|
+
|
|
61209
|
+
}
|
|
61210
|
+
|
|
61211
|
+
_detachGpuRenderers() {
|
|
61212
|
+
|
|
61213
|
+
for ( const state of this.gpuRendererStates.values() ) {
|
|
61214
|
+
|
|
61215
|
+
if ( state.kind === 'common' && state.available ) {
|
|
61216
|
+
|
|
61217
|
+
state.renderer.backend.removeTimestampQueryListener( state.listener );
|
|
61218
|
+
|
|
61219
|
+
}
|
|
61220
|
+
|
|
61221
|
+
if ( state.kind === 'common' ) {
|
|
61222
|
+
|
|
61223
|
+
state.renderer.backend.trackTimestamp = state.previousTrackTimestamp;
|
|
61224
|
+
|
|
61225
|
+
}
|
|
61226
|
+
|
|
61227
|
+
}
|
|
61228
|
+
|
|
61229
|
+
this.gpuRendererStates.clear();
|
|
60530
61230
|
|
|
60531
61231
|
}
|
|
60532
61232
|
|
|
@@ -60556,6 +61256,18 @@ class ProfilerServiceClass {
|
|
|
60556
61256
|
* @property {number|undefined} selfFrameBudget Exclusive time as percentage of a 60 fps frame budget.
|
|
60557
61257
|
*/
|
|
60558
61258
|
|
|
61259
|
+
/**
|
|
61260
|
+
* @typedef {Object} GpuProfilerStats
|
|
61261
|
+
* @property {string} label
|
|
61262
|
+
* @property {number} samples
|
|
61263
|
+
* @property {number} avg
|
|
61264
|
+
* @property {number} min
|
|
61265
|
+
* @property {number} max
|
|
61266
|
+
* @property {number} p95
|
|
61267
|
+
* @property {number} frameBudget Percentage of a 60 fps frame budget (16.67 ms)
|
|
61268
|
+
* @property {number} totalInvocations
|
|
61269
|
+
*/
|
|
61270
|
+
|
|
60559
61271
|
/**
|
|
60560
61272
|
* @typedef {Object} SpanHandle
|
|
60561
61273
|
* @property {string} label
|
|
@@ -60564,6 +61276,51 @@ class ProfilerServiceClass {
|
|
|
60564
61276
|
* @property {string} _startMark
|
|
60565
61277
|
*/
|
|
60566
61278
|
|
|
61279
|
+
/**
|
|
61280
|
+
* @typedef {Object} GpuSpanHandle
|
|
61281
|
+
* @property {string} label
|
|
61282
|
+
* @property {?Object} renderer
|
|
61283
|
+
* @property {number} t0
|
|
61284
|
+
* @property {number} _seq
|
|
61285
|
+
* @property {number} _generation
|
|
61286
|
+
* @property {{render: Set<string>, compute: Set<string>}} [_queries]
|
|
61287
|
+
* @property {WebGLQuery} [_query]
|
|
61288
|
+
* @property {number} [_endTime]
|
|
61289
|
+
*/
|
|
61290
|
+
|
|
61291
|
+
/**
|
|
61292
|
+
* @typedef {Object} CommonGpuRendererState
|
|
61293
|
+
* @property {'common'} kind
|
|
61294
|
+
* @property {boolean} available
|
|
61295
|
+
* @property {Object} renderer
|
|
61296
|
+
* @property {function('render'|'compute', string, ?string): void} listener
|
|
61297
|
+
* @property {boolean} previousTrackTimestamp
|
|
61298
|
+
* @property {GpuSpanHandle[]} activeSpans
|
|
61299
|
+
* @property {GpuSpanHandle[]} pendingSpans
|
|
61300
|
+
* @property {Map<string, number>} queryCpuTimes
|
|
61301
|
+
* @property {?bigint} gpuTimestampOrigin
|
|
61302
|
+
* @property {?number} gpuTraceOrigin
|
|
61303
|
+
* @property {?Promise<void>} flushPromise
|
|
61304
|
+
* @property {boolean} flushScheduled
|
|
61305
|
+
*/
|
|
61306
|
+
|
|
61307
|
+
/**
|
|
61308
|
+
* @typedef {Object} LegacyWebGLGpuRendererState
|
|
61309
|
+
* @property {'legacy-webgl'} kind
|
|
61310
|
+
* @property {boolean} available
|
|
61311
|
+
* @property {Object} renderer
|
|
61312
|
+
* @property {WebGL2RenderingContext} gl
|
|
61313
|
+
* @property {?EXT_disjoint_timer_query_webgl2} extension
|
|
61314
|
+
* @property {?GpuSpanHandle} activeSpan
|
|
61315
|
+
* @property {GpuSpanHandle[]} pendingSpans
|
|
61316
|
+
* @property {?Promise<void>} flushPromise
|
|
61317
|
+
* @property {boolean} flushScheduled
|
|
61318
|
+
*/
|
|
61319
|
+
|
|
61320
|
+
/**
|
|
61321
|
+
* @typedef {CommonGpuRendererState|LegacyWebGLGpuRendererState} GpuRendererState
|
|
61322
|
+
*/
|
|
61323
|
+
|
|
60567
61324
|
/**
|
|
60568
61325
|
* @typedef {Object} EndSpanOptions
|
|
60569
61326
|
* @property {boolean} [asyncTimeline] When true, trace slice uses `tid=2` (virtual async row).
|
|
@@ -60577,7 +61334,7 @@ class ProfilerServiceClass {
|
|
|
60577
61334
|
* @property {number} dur
|
|
60578
61335
|
* @property {1} pid
|
|
60579
61336
|
* @property {number} tid
|
|
60580
|
-
* @property {'gnsx'} cat
|
|
61337
|
+
* @property {'gnsx'|'gnsx-gpu'} cat
|
|
60581
61338
|
*/
|
|
60582
61339
|
|
|
60583
61340
|
/**
|