@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.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,467 @@ 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
|
+
const passSpan = {
|
|
60393
|
+
label: `GPU pass: ${label}`,
|
|
60394
|
+
renderer,
|
|
60395
|
+
t0: cpuTime,
|
|
60396
|
+
_seq: ++ this._markId,
|
|
60397
|
+
_generation: this._gpuGeneration,
|
|
60398
|
+
_queries: {
|
|
60399
|
+
[ TimestampQuery.RENDER ]: new Set(),
|
|
60400
|
+
[ TimestampQuery.COMPUTE ]: new Set(),
|
|
60401
|
+
},
|
|
60402
|
+
};
|
|
60403
|
+
passSpan._queries[ type ].add( uid );
|
|
60404
|
+
state.pendingSpans.push( passSpan );
|
|
60405
|
+
this._scheduleGpuFlush( state );
|
|
60406
|
+
|
|
60407
|
+
}
|
|
60408
|
+
|
|
60409
|
+
}
|
|
60410
|
+
|
|
60411
|
+
/**
|
|
60412
|
+
* @param {import('./ProfilerService.js').GpuRendererState} state
|
|
60413
|
+
*/
|
|
60414
|
+
_scheduleGpuFlush( state ) {
|
|
60415
|
+
|
|
60416
|
+
if ( state.flushScheduled ) return;
|
|
60417
|
+
state.flushScheduled = true;
|
|
60418
|
+
|
|
60419
|
+
const callback = () => {
|
|
60420
|
+
|
|
60421
|
+
state.flushScheduled = false;
|
|
60422
|
+
if ( this.gpuRendererStates.get( state.renderer ) === state ) {
|
|
60423
|
+
|
|
60424
|
+
this.flushGpu( state.renderer ).catch( error => {
|
|
60425
|
+
|
|
60426
|
+
warnOnce( `ProfilerService: Unable to resolve GPU timestamps (${error.message}).` );
|
|
60427
|
+
|
|
60428
|
+
} );
|
|
60429
|
+
|
|
60430
|
+
}
|
|
60431
|
+
|
|
60432
|
+
};
|
|
60433
|
+
|
|
60434
|
+
if ( typeof requestAnimationFrame === 'function' ) {
|
|
60435
|
+
|
|
60436
|
+
requestAnimationFrame( callback );
|
|
60437
|
+
|
|
60438
|
+
} else {
|
|
60439
|
+
|
|
60440
|
+
setTimeout( callback, 0 );
|
|
60441
|
+
|
|
60442
|
+
}
|
|
60443
|
+
|
|
60444
|
+
}
|
|
60445
|
+
|
|
60446
|
+
/**
|
|
60447
|
+
* @param {import('./ProfilerService.js').CommonGpuRendererState} state
|
|
60448
|
+
*/
|
|
60449
|
+
async _flushCommonGpuState( state ) {
|
|
60450
|
+
|
|
60451
|
+
const spans = state.pendingSpans.splice( 0 );
|
|
60452
|
+
if ( spans.length === 0 ) return;
|
|
60453
|
+
|
|
60454
|
+
const hasRenderQueries = spans.some( span => span._queries.render.size > 0 );
|
|
60455
|
+
const hasComputeQueries = spans.some( span => span._queries.compute.size > 0 );
|
|
60456
|
+
const resolutions = [];
|
|
60457
|
+
|
|
60458
|
+
if ( hasRenderQueries ) resolutions.push( state.renderer.resolveTimestampsAsync( TimestampQuery.RENDER ) );
|
|
60459
|
+
if ( hasComputeQueries ) resolutions.push( state.renderer.resolveTimestampsAsync( TimestampQuery.COMPUTE ) );
|
|
60460
|
+
await Promise.all( resolutions );
|
|
60461
|
+
|
|
60462
|
+
if ( this._enabled === false ) return;
|
|
60463
|
+
|
|
60464
|
+
if ( state.gpuTimestampOrigin === null ) {
|
|
60465
|
+
|
|
60466
|
+
let earliestRange = null;
|
|
60467
|
+
let earliestUid = null;
|
|
60468
|
+
for ( const span of spans ) {
|
|
60469
|
+
|
|
60470
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60471
|
+
|
|
60472
|
+
for ( const uid of span._queries[ type ] ) {
|
|
60473
|
+
|
|
60474
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
60475
|
+
if ( range !== null && ( earliestRange === null || range.start < earliestRange.start ) ) {
|
|
60476
|
+
|
|
60477
|
+
earliestRange = range;
|
|
60478
|
+
earliestUid = uid;
|
|
60479
|
+
|
|
60480
|
+
}
|
|
60481
|
+
|
|
60482
|
+
}
|
|
60483
|
+
|
|
60484
|
+
}
|
|
60485
|
+
|
|
60486
|
+
}
|
|
60487
|
+
|
|
60488
|
+
if ( earliestRange !== null ) {
|
|
60489
|
+
|
|
60490
|
+
state.gpuTimestampOrigin = earliestRange.start;
|
|
60491
|
+
state.gpuTraceOrigin = state.queryCpuTimes.get( earliestUid ) ?? spans[ 0 ].t0;
|
|
60492
|
+
|
|
60493
|
+
}
|
|
60494
|
+
|
|
60495
|
+
}
|
|
60496
|
+
|
|
60497
|
+
const resolvedUids = new Set();
|
|
60498
|
+
for ( const span of spans ) {
|
|
60499
|
+
|
|
60500
|
+
if ( span._generation !== this._gpuGeneration ) continue;
|
|
60501
|
+
|
|
60502
|
+
let duration = 0;
|
|
60503
|
+
let resolvedQueries = 0;
|
|
60504
|
+
let rangedQueries = 0;
|
|
60505
|
+
let gpuStart = null;
|
|
60506
|
+
let gpuEnd = null;
|
|
60507
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60508
|
+
|
|
60509
|
+
for ( const uid of span._queries[ type ] ) {
|
|
60510
|
+
|
|
60511
|
+
if ( state.renderer.backend.hasTimestampQuery( uid ) ) {
|
|
60512
|
+
|
|
60513
|
+
duration += state.renderer.backend.getTimestamp( uid );
|
|
60514
|
+
resolvedQueries ++;
|
|
60515
|
+
resolvedUids.add( uid );
|
|
60516
|
+
|
|
60517
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
60518
|
+
if ( range !== null ) {
|
|
60519
|
+
|
|
60520
|
+
rangedQueries ++;
|
|
60521
|
+
if ( gpuStart === null || range.start < gpuStart ) gpuStart = range.start;
|
|
60522
|
+
if ( gpuEnd === null || range.end > gpuEnd ) gpuEnd = range.end;
|
|
60523
|
+
|
|
60524
|
+
}
|
|
60525
|
+
|
|
60526
|
+
}
|
|
60527
|
+
|
|
60528
|
+
}
|
|
60529
|
+
|
|
60530
|
+
}
|
|
60531
|
+
|
|
60532
|
+
if ( resolvedQueries > 0 ) {
|
|
60533
|
+
|
|
60534
|
+
let startTime = span.t0;
|
|
60535
|
+
if ( rangedQueries === resolvedQueries && gpuStart !== null && gpuEnd !== null && state.gpuTimestampOrigin !== null ) {
|
|
60536
|
+
|
|
60537
|
+
duration = Number( gpuEnd - gpuStart ) / 1e6;
|
|
60538
|
+
startTime = state.gpuTraceOrigin + Number( gpuStart - state.gpuTimestampOrigin ) / 1e6;
|
|
60539
|
+
|
|
60540
|
+
}
|
|
60541
|
+
|
|
60542
|
+
this._commitGpuDurationSample( span.label, startTime, duration );
|
|
60543
|
+
|
|
60544
|
+
}
|
|
60545
|
+
|
|
60546
|
+
}
|
|
60547
|
+
|
|
60548
|
+
for ( const uid of resolvedUids ) state.queryCpuTimes.delete( uid );
|
|
60549
|
+
|
|
60550
|
+
}
|
|
60551
|
+
|
|
60552
|
+
/**
|
|
60553
|
+
* @param {import('./ProfilerService.js').LegacyWebGLGpuRendererState} state
|
|
60554
|
+
*/
|
|
60555
|
+
async _flushLegacyWebGLState( state ) {
|
|
60556
|
+
|
|
60557
|
+
const spans = state.pendingSpans.splice( 0 );
|
|
60558
|
+
|
|
60559
|
+
for ( const span of spans ) {
|
|
60560
|
+
|
|
60561
|
+
const duration = await this._resolveLegacyWebGLQuery( state, span._query );
|
|
60562
|
+
if ( duration !== null && span._generation === this._gpuGeneration && this._enabled ) {
|
|
60563
|
+
|
|
60564
|
+
this._commitGpuDurationSample( span.label, span.t0, duration );
|
|
60565
|
+
|
|
60566
|
+
}
|
|
60567
|
+
|
|
60568
|
+
}
|
|
60569
|
+
|
|
60570
|
+
}
|
|
60571
|
+
|
|
60572
|
+
/**
|
|
60573
|
+
* @param {import('./ProfilerService.js').LegacyWebGLGpuRendererState} state
|
|
60574
|
+
* @param {WebGLQuery} query
|
|
60575
|
+
* @return {Promise<?number>}
|
|
60576
|
+
*/
|
|
60577
|
+
_resolveLegacyWebGLQuery( state, query ) {
|
|
60578
|
+
|
|
60579
|
+
return new Promise( resolve => {
|
|
60580
|
+
|
|
60581
|
+
const poll = () => {
|
|
60582
|
+
|
|
60583
|
+
if ( state.gl.isContextLost() ) {
|
|
60584
|
+
|
|
60585
|
+
state.gl.deleteQuery( query );
|
|
60586
|
+
resolve( null );
|
|
60587
|
+
return;
|
|
60588
|
+
|
|
60589
|
+
}
|
|
60590
|
+
|
|
60591
|
+
const disjoint = state.gl.getParameter( state.extension.GPU_DISJOINT_EXT );
|
|
60592
|
+
const available = state.gl.getQueryParameter( query, state.gl.QUERY_RESULT_AVAILABLE );
|
|
60593
|
+
|
|
60594
|
+
if ( available === false ) {
|
|
60595
|
+
|
|
60596
|
+
setTimeout( poll, 1 );
|
|
60597
|
+
return;
|
|
60598
|
+
|
|
60599
|
+
}
|
|
60600
|
+
|
|
60601
|
+
const result = disjoint
|
|
60602
|
+
? null
|
|
60603
|
+
: Number( state.gl.getQueryParameter( query, state.gl.QUERY_RESULT ) ) / 1e6;
|
|
60604
|
+
state.gl.deleteQuery( query );
|
|
60605
|
+
resolve( result );
|
|
60606
|
+
|
|
60607
|
+
};
|
|
60608
|
+
|
|
60609
|
+
poll();
|
|
60610
|
+
|
|
60611
|
+
} );
|
|
60612
|
+
|
|
60613
|
+
}
|
|
60614
|
+
|
|
60128
60615
|
/**
|
|
60129
60616
|
* @param {string} label
|
|
60130
60617
|
*/
|
|
@@ -60282,6 +60769,45 @@ class ProfilerServiceClass {
|
|
|
60282
60769
|
|
|
60283
60770
|
}
|
|
60284
60771
|
|
|
60772
|
+
/**
|
|
60773
|
+
* @param {string} label
|
|
60774
|
+
* @param {number} startTime
|
|
60775
|
+
* @param {number} durationMs
|
|
60776
|
+
*/
|
|
60777
|
+
_commitGpuDurationSample( label, startTime, durationMs ) {
|
|
60778
|
+
|
|
60779
|
+
let buffer = this.gpuBuffers.get( label );
|
|
60780
|
+
if ( buffer === undefined ) {
|
|
60781
|
+
|
|
60782
|
+
buffer = new Float64Array( RING_SIZE );
|
|
60783
|
+
this.gpuBuffers.set( label, buffer );
|
|
60784
|
+
this.gpuCursors.set( label, 0 );
|
|
60785
|
+
this.gpuCounts.set( label, 0 );
|
|
60786
|
+
|
|
60787
|
+
}
|
|
60788
|
+
|
|
60789
|
+
const cursor = this.gpuCursors.get( label );
|
|
60790
|
+
buffer[ cursor ] = durationMs;
|
|
60791
|
+
this.gpuCursors.set( label, ( cursor + 1 ) % RING_SIZE );
|
|
60792
|
+
this.gpuCounts.set( label, Math.min( this.gpuCounts.get( label ) + 1, RING_SIZE ) );
|
|
60793
|
+
this.gpuInvocations.set( label, ( this.gpuInvocations.get( label ) ?? 0 ) + 1 );
|
|
60794
|
+
|
|
60795
|
+
if ( this._profile === 'full' ) {
|
|
60796
|
+
|
|
60797
|
+
this.traceEvents.push( {
|
|
60798
|
+
name: label,
|
|
60799
|
+
ph: 'X',
|
|
60800
|
+
ts: Math.round( this._getTraceTimestamp( startTime ) ),
|
|
60801
|
+
dur: Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
60802
|
+
pid: 1,
|
|
60803
|
+
tid: TRACE_TID_GPU,
|
|
60804
|
+
cat: 'gnsx-gpu',
|
|
60805
|
+
} );
|
|
60806
|
+
|
|
60807
|
+
}
|
|
60808
|
+
|
|
60809
|
+
}
|
|
60810
|
+
|
|
60285
60811
|
/**
|
|
60286
60812
|
* @param {string} label
|
|
60287
60813
|
* @return {number[]}
|
|
@@ -60297,6 +60823,21 @@ class ProfilerServiceClass {
|
|
|
60297
60823
|
|
|
60298
60824
|
}
|
|
60299
60825
|
|
|
60826
|
+
/**
|
|
60827
|
+
* @param {string} label
|
|
60828
|
+
* @return {number[]}
|
|
60829
|
+
*/
|
|
60830
|
+
getValidGpuSamples( label ) {
|
|
60831
|
+
|
|
60832
|
+
const buffer = this.gpuBuffers.get( label );
|
|
60833
|
+
const count = this.gpuCounts.get( label ) ?? 0;
|
|
60834
|
+
if ( buffer === undefined || count === 0 ) return [];
|
|
60835
|
+
return count < RING_SIZE
|
|
60836
|
+
? Array.from( buffer.subarray( 0, count ) )
|
|
60837
|
+
: Array.from( buffer );
|
|
60838
|
+
|
|
60839
|
+
}
|
|
60840
|
+
|
|
60300
60841
|
/**
|
|
60301
60842
|
* @param {string} label
|
|
60302
60843
|
* @return {import('./ProfilerService.js').ProfilerStats|null}
|
|
@@ -60344,6 +60885,31 @@ class ProfilerServiceClass {
|
|
|
60344
60885
|
|
|
60345
60886
|
}
|
|
60346
60887
|
|
|
60888
|
+
/**
|
|
60889
|
+
* @param {string} label
|
|
60890
|
+
* @return {import('./ProfilerService.js').GpuProfilerStats|null}
|
|
60891
|
+
*/
|
|
60892
|
+
getGpuStats( label ) {
|
|
60893
|
+
|
|
60894
|
+
const samples = this.getValidGpuSamples( label );
|
|
60895
|
+
if ( samples.length === 0 ) return null;
|
|
60896
|
+
|
|
60897
|
+
const sorted = [ ...samples ].sort( ( a, b ) => a - b );
|
|
60898
|
+
const avg = sorted.reduce( ( a, b ) => a + b, 0 ) / sorted.length;
|
|
60899
|
+
|
|
60900
|
+
return {
|
|
60901
|
+
label,
|
|
60902
|
+
samples: samples.length,
|
|
60903
|
+
avg,
|
|
60904
|
+
min: sorted[ 0 ],
|
|
60905
|
+
max: sorted[ sorted.length - 1 ],
|
|
60906
|
+
p95: sorted[ Math.floor( sorted.length * 0.95 ) ],
|
|
60907
|
+
frameBudget: ( avg / FRAME_BUDGET_MS ) * 100,
|
|
60908
|
+
totalInvocations: this.gpuInvocations.get( label ) ?? 0,
|
|
60909
|
+
};
|
|
60910
|
+
|
|
60911
|
+
}
|
|
60912
|
+
|
|
60347
60913
|
/**
|
|
60348
60914
|
* @return {import('./ProfilerService.js').ProfilerStats[]}
|
|
60349
60915
|
*/
|
|
@@ -60355,10 +60921,22 @@ class ProfilerServiceClass {
|
|
|
60355
60921
|
|
|
60356
60922
|
}
|
|
60357
60923
|
|
|
60924
|
+
/**
|
|
60925
|
+
* @return {import('./ProfilerService.js').GpuProfilerStats[]}
|
|
60926
|
+
*/
|
|
60927
|
+
getAllGpuStats() {
|
|
60928
|
+
|
|
60929
|
+
return [ ...this.gpuBuffers.keys() ]
|
|
60930
|
+
.map( label => this.getGpuStats( label ) )
|
|
60931
|
+
.filter( stats => stats !== null );
|
|
60932
|
+
|
|
60933
|
+
}
|
|
60934
|
+
|
|
60358
60935
|
report() {
|
|
60359
60936
|
|
|
60360
60937
|
const stats = this.getAllStats();
|
|
60361
|
-
|
|
60938
|
+
const gpuStats = this.getAllGpuStats();
|
|
60939
|
+
if ( stats.length === 0 && gpuStats.length === 0 ) {
|
|
60362
60940
|
|
|
60363
60941
|
console.log( '[ProfilerService] No data. Enable profiling first and wait a few frames.' );
|
|
60364
60942
|
return;
|
|
@@ -60378,6 +60956,24 @@ class ProfilerServiceClass {
|
|
|
60378
60956
|
} ) )
|
|
60379
60957
|
);
|
|
60380
60958
|
|
|
60959
|
+
if ( gpuStats.length > 0 ) {
|
|
60960
|
+
|
|
60961
|
+
gpuStats.sort( ( a, b ) => b.avg - a.avg );
|
|
60962
|
+
console.log( '[ProfilerService] GPU timings' );
|
|
60963
|
+
console.table(
|
|
60964
|
+
gpuStats.map( stats => ( {
|
|
60965
|
+
label: stats.label,
|
|
60966
|
+
'avg ms': stats.avg.toFixed( 3 ),
|
|
60967
|
+
'min ms': stats.min.toFixed( 3 ),
|
|
60968
|
+
'max ms': stats.max.toFixed( 3 ),
|
|
60969
|
+
'p95 ms': stats.p95.toFixed( 3 ),
|
|
60970
|
+
'budget %': stats.frameBudget.toFixed( 1 ) + '%',
|
|
60971
|
+
samples: stats.samples,
|
|
60972
|
+
} ) )
|
|
60973
|
+
);
|
|
60974
|
+
|
|
60975
|
+
}
|
|
60976
|
+
|
|
60381
60977
|
}
|
|
60382
60978
|
|
|
60383
60979
|
/**
|
|
@@ -60394,6 +60990,7 @@ class ProfilerServiceClass {
|
|
|
60394
60990
|
|
|
60395
60991
|
} );
|
|
60396
60992
|
const hasAsync = slices.some( e => e.tid === TRACE_TID_ASYNC );
|
|
60993
|
+
const hasGpu = slices.some( e => e.tid === TRACE_TID_GPU );
|
|
60397
60994
|
/** @type {import('./ProfilerService.js').ChromeTraceMetadataEvent[]} */
|
|
60398
60995
|
const prefix = [
|
|
60399
60996
|
{
|
|
@@ -60429,6 +61026,20 @@ class ProfilerServiceClass {
|
|
|
60429
61026
|
|
|
60430
61027
|
}
|
|
60431
61028
|
|
|
61029
|
+
if ( hasGpu ) {
|
|
61030
|
+
|
|
61031
|
+
prefix.push( {
|
|
61032
|
+
cat: '__metadata',
|
|
61033
|
+
name: 'thread_name',
|
|
61034
|
+
ph: 'M',
|
|
61035
|
+
pid: 1,
|
|
61036
|
+
tid: TRACE_TID_GPU,
|
|
61037
|
+
ts: 0,
|
|
61038
|
+
args: { name: 'GPU (device timestamps)' },
|
|
61039
|
+
} );
|
|
61040
|
+
|
|
61041
|
+
}
|
|
61042
|
+
|
|
60432
61043
|
return {
|
|
60433
61044
|
displayTimeUnit: 'ms',
|
|
60434
61045
|
traceEvents: [ ...prefix, ...slices ],
|
|
@@ -60493,8 +61104,24 @@ class ProfilerServiceClass {
|
|
|
60493
61104
|
|
|
60494
61105
|
}
|
|
60495
61106
|
|
|
61107
|
+
/**
|
|
61108
|
+
* @return {import('./ProfilerService.js').GpuProfilerStats[]}
|
|
61109
|
+
*/
|
|
61110
|
+
exportGpuJSON() {
|
|
61111
|
+
|
|
61112
|
+
return this.getAllGpuStats();
|
|
61113
|
+
|
|
61114
|
+
}
|
|
61115
|
+
|
|
60496
61116
|
_clearState() {
|
|
60497
61117
|
|
|
61118
|
+
this._gpuGeneration ++;
|
|
61119
|
+
for ( const state of this.gpuRendererStates.values() ) {
|
|
61120
|
+
|
|
61121
|
+
this._clearGpuRendererState( state );
|
|
61122
|
+
|
|
61123
|
+
}
|
|
61124
|
+
|
|
60498
61125
|
this.buffers.clear();
|
|
60499
61126
|
this.cursors.clear();
|
|
60500
61127
|
this.counts.clear();
|
|
@@ -60507,6 +61134,79 @@ class ProfilerServiceClass {
|
|
|
60507
61134
|
this.selfCounts.clear();
|
|
60508
61135
|
this.callStack.length = 0;
|
|
60509
61136
|
this.invocations.clear();
|
|
61137
|
+
this.gpuBuffers.clear();
|
|
61138
|
+
this.gpuCursors.clear();
|
|
61139
|
+
this.gpuCounts.clear();
|
|
61140
|
+
this.gpuInvocations.clear();
|
|
61141
|
+
|
|
61142
|
+
}
|
|
61143
|
+
|
|
61144
|
+
/**
|
|
61145
|
+
* @param {import('./ProfilerService.js').GpuRendererState} state
|
|
61146
|
+
*/
|
|
61147
|
+
_clearGpuRendererState( state ) {
|
|
61148
|
+
|
|
61149
|
+
if ( state.kind === 'common' ) {
|
|
61150
|
+
|
|
61151
|
+
state.activeSpans.length = 0;
|
|
61152
|
+
state.pendingSpans.length = 0;
|
|
61153
|
+
state.queryCpuTimes.clear();
|
|
61154
|
+
state.gpuTimestampOrigin = null;
|
|
61155
|
+
state.gpuTraceOrigin = null;
|
|
61156
|
+
return;
|
|
61157
|
+
|
|
61158
|
+
}
|
|
61159
|
+
|
|
61160
|
+
if ( state.activeSpan !== null ) {
|
|
61161
|
+
|
|
61162
|
+
try {
|
|
61163
|
+
|
|
61164
|
+
state.gl.endQuery( state.extension.TIME_ELAPSED_EXT );
|
|
61165
|
+
|
|
61166
|
+
} catch {
|
|
61167
|
+
|
|
61168
|
+
// The context may have been lost while the query was active.
|
|
61169
|
+
|
|
61170
|
+
}
|
|
61171
|
+
|
|
61172
|
+
state.gl.deleteQuery( state.activeSpan._query );
|
|
61173
|
+
state.activeSpan = null;
|
|
61174
|
+
|
|
61175
|
+
}
|
|
61176
|
+
|
|
61177
|
+
if ( state.flushPromise === null ) {
|
|
61178
|
+
|
|
61179
|
+
for ( const span of state.pendingSpans ) {
|
|
61180
|
+
|
|
61181
|
+
state.gl.deleteQuery( span._query );
|
|
61182
|
+
|
|
61183
|
+
}
|
|
61184
|
+
|
|
61185
|
+
}
|
|
61186
|
+
|
|
61187
|
+
state.pendingSpans.length = 0;
|
|
61188
|
+
|
|
61189
|
+
}
|
|
61190
|
+
|
|
61191
|
+
_detachGpuRenderers() {
|
|
61192
|
+
|
|
61193
|
+
for ( const state of this.gpuRendererStates.values() ) {
|
|
61194
|
+
|
|
61195
|
+
if ( state.kind === 'common' && state.available ) {
|
|
61196
|
+
|
|
61197
|
+
state.renderer.backend.removeTimestampQueryListener( state.listener );
|
|
61198
|
+
|
|
61199
|
+
}
|
|
61200
|
+
|
|
61201
|
+
if ( state.kind === 'common' ) {
|
|
61202
|
+
|
|
61203
|
+
state.renderer.backend.trackTimestamp = state.previousTrackTimestamp;
|
|
61204
|
+
|
|
61205
|
+
}
|
|
61206
|
+
|
|
61207
|
+
}
|
|
61208
|
+
|
|
61209
|
+
this.gpuRendererStates.clear();
|
|
60510
61210
|
|
|
60511
61211
|
}
|
|
60512
61212
|
|
|
@@ -60536,6 +61236,18 @@ class ProfilerServiceClass {
|
|
|
60536
61236
|
* @property {number|undefined} selfFrameBudget Exclusive time as percentage of a 60 fps frame budget.
|
|
60537
61237
|
*/
|
|
60538
61238
|
|
|
61239
|
+
/**
|
|
61240
|
+
* @typedef {Object} GpuProfilerStats
|
|
61241
|
+
* @property {string} label
|
|
61242
|
+
* @property {number} samples
|
|
61243
|
+
* @property {number} avg
|
|
61244
|
+
* @property {number} min
|
|
61245
|
+
* @property {number} max
|
|
61246
|
+
* @property {number} p95
|
|
61247
|
+
* @property {number} frameBudget Percentage of a 60 fps frame budget (16.67 ms)
|
|
61248
|
+
* @property {number} totalInvocations
|
|
61249
|
+
*/
|
|
61250
|
+
|
|
60539
61251
|
/**
|
|
60540
61252
|
* @typedef {Object} SpanHandle
|
|
60541
61253
|
* @property {string} label
|
|
@@ -60544,6 +61256,51 @@ class ProfilerServiceClass {
|
|
|
60544
61256
|
* @property {string} _startMark
|
|
60545
61257
|
*/
|
|
60546
61258
|
|
|
61259
|
+
/**
|
|
61260
|
+
* @typedef {Object} GpuSpanHandle
|
|
61261
|
+
* @property {string} label
|
|
61262
|
+
* @property {?Object} renderer
|
|
61263
|
+
* @property {number} t0
|
|
61264
|
+
* @property {number} _seq
|
|
61265
|
+
* @property {number} _generation
|
|
61266
|
+
* @property {{render: Set<string>, compute: Set<string>}} [_queries]
|
|
61267
|
+
* @property {WebGLQuery} [_query]
|
|
61268
|
+
* @property {number} [_endTime]
|
|
61269
|
+
*/
|
|
61270
|
+
|
|
61271
|
+
/**
|
|
61272
|
+
* @typedef {Object} CommonGpuRendererState
|
|
61273
|
+
* @property {'common'} kind
|
|
61274
|
+
* @property {boolean} available
|
|
61275
|
+
* @property {Object} renderer
|
|
61276
|
+
* @property {function('render'|'compute', string, ?string): void} listener
|
|
61277
|
+
* @property {boolean} previousTrackTimestamp
|
|
61278
|
+
* @property {GpuSpanHandle[]} activeSpans
|
|
61279
|
+
* @property {GpuSpanHandle[]} pendingSpans
|
|
61280
|
+
* @property {Map<string, number>} queryCpuTimes
|
|
61281
|
+
* @property {?bigint} gpuTimestampOrigin
|
|
61282
|
+
* @property {?number} gpuTraceOrigin
|
|
61283
|
+
* @property {?Promise<void>} flushPromise
|
|
61284
|
+
* @property {boolean} flushScheduled
|
|
61285
|
+
*/
|
|
61286
|
+
|
|
61287
|
+
/**
|
|
61288
|
+
* @typedef {Object} LegacyWebGLGpuRendererState
|
|
61289
|
+
* @property {'legacy-webgl'} kind
|
|
61290
|
+
* @property {boolean} available
|
|
61291
|
+
* @property {Object} renderer
|
|
61292
|
+
* @property {WebGL2RenderingContext} gl
|
|
61293
|
+
* @property {?EXT_disjoint_timer_query_webgl2} extension
|
|
61294
|
+
* @property {?GpuSpanHandle} activeSpan
|
|
61295
|
+
* @property {GpuSpanHandle[]} pendingSpans
|
|
61296
|
+
* @property {?Promise<void>} flushPromise
|
|
61297
|
+
* @property {boolean} flushScheduled
|
|
61298
|
+
*/
|
|
61299
|
+
|
|
61300
|
+
/**
|
|
61301
|
+
* @typedef {CommonGpuRendererState|LegacyWebGLGpuRendererState} GpuRendererState
|
|
61302
|
+
*/
|
|
61303
|
+
|
|
60547
61304
|
/**
|
|
60548
61305
|
* @typedef {Object} EndSpanOptions
|
|
60549
61306
|
* @property {boolean} [asyncTimeline] When true, trace slice uses `tid=2` (virtual async row).
|
|
@@ -60557,7 +61314,7 @@ class ProfilerServiceClass {
|
|
|
60557
61314
|
* @property {number} dur
|
|
60558
61315
|
* @property {1} pid
|
|
60559
61316
|
* @property {number} tid
|
|
60560
|
-
* @property {'gnsx'} cat
|
|
61317
|
+
* @property {'gnsx'|'gnsx-gpu'} cat
|
|
60561
61318
|
*/
|
|
60562
61319
|
|
|
60563
61320
|
/**
|