@gnsx/three 0.185.10 → 0.185.12
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 +253 -32
- package/build/three.core.js +253 -32
- package/build/three.core.min.js +1 -1
- package/build/three.webgpu.js +82 -64
- package/build/three.webgpu.min.js +1 -1
- package/build/three.webgpu.nodes.js +82 -64
- package/build/three.webgpu.nodes.min.js +1 -1
- package/examples/jsm/tsl/display/GTAONode.js +54 -4
- package/package.json +1 -1
- package/src/profiler/ProfilerService.js +253 -32
- package/src/renderers/common/Renderer.js +17 -2
- package/src/renderers/webgpu/WebGPUBackend.js +26 -8
- package/src/renderers/webgpu/utils/WebGPUTimestampQueryPool.js +40 -7
package/build/three.cjs
CHANGED
|
@@ -60389,6 +60389,9 @@ class ProfilerServiceClass {
|
|
|
60389
60389
|
|
|
60390
60390
|
if ( label !== null && label !== undefined && label !== '' ) {
|
|
60391
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.
|
|
60392
60395
|
const passSpan = {
|
|
60393
60396
|
label: `GPU pass: ${label}`,
|
|
60394
60397
|
renderer,
|
|
@@ -60402,7 +60405,13 @@ class ProfilerServiceClass {
|
|
|
60402
60405
|
};
|
|
60403
60406
|
passSpan._queries[ type ].add( uid );
|
|
60404
60407
|
state.pendingSpans.push( passSpan );
|
|
60405
|
-
|
|
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
|
+
}
|
|
60406
60415
|
|
|
60407
60416
|
}
|
|
60408
60417
|
|
|
@@ -60463,38 +60472,22 @@ class ProfilerServiceClass {
|
|
|
60463
60472
|
|
|
60464
60473
|
if ( state.gpuTimestampOrigin === null ) {
|
|
60465
60474
|
|
|
60466
|
-
|
|
60467
|
-
|
|
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 ) {
|
|
60475
|
+
const origin = this._findEarliestGpuTimestampOrigin( state, spans );
|
|
60476
|
+
if ( origin !== null ) {
|
|
60489
60477
|
|
|
60490
|
-
state.gpuTimestampOrigin =
|
|
60491
|
-
state.gpuTraceOrigin =
|
|
60478
|
+
state.gpuTimestampOrigin = origin.gpuTimestampOrigin;
|
|
60479
|
+
state.gpuTraceOrigin = origin.gpuTraceOrigin;
|
|
60492
60480
|
|
|
60493
60481
|
}
|
|
60494
60482
|
|
|
60495
60483
|
}
|
|
60496
60484
|
|
|
60497
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
|
+
|
|
60498
60491
|
for ( const span of spans ) {
|
|
60499
60492
|
|
|
60500
60493
|
if ( span._generation !== this._gpuGeneration ) continue;
|
|
@@ -60532,20 +60525,247 @@ class ProfilerServiceClass {
|
|
|
60532
60525
|
if ( resolvedQueries > 0 ) {
|
|
60533
60526
|
|
|
60534
60527
|
let startTime = span.t0;
|
|
60535
|
-
|
|
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 ) {
|
|
60536
60532
|
|
|
60537
60533
|
duration = Number( gpuEnd - gpuStart ) / 1e6;
|
|
60538
60534
|
startTime = state.gpuTraceOrigin + Number( gpuStart - state.gpuTimestampOrigin ) / 1e6;
|
|
60539
60535
|
|
|
60540
60536
|
}
|
|
60541
60537
|
|
|
60542
|
-
|
|
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 );
|
|
60543
60657
|
|
|
60544
60658
|
}
|
|
60545
60659
|
|
|
60660
|
+
cursor = sample.traceTs + sample.traceDur;
|
|
60661
|
+
|
|
60546
60662
|
}
|
|
60547
60663
|
|
|
60548
|
-
|
|
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;
|
|
60549
60769
|
|
|
60550
60770
|
}
|
|
60551
60771
|
|
|
@@ -60773,8 +60993,9 @@ class ProfilerServiceClass {
|
|
|
60773
60993
|
* @param {string} label
|
|
60774
60994
|
* @param {number} startTime
|
|
60775
60995
|
* @param {number} durationMs
|
|
60996
|
+
* @param {{ ts: number, dur: number }} [traceOverride]
|
|
60776
60997
|
*/
|
|
60777
|
-
_commitGpuDurationSample( label, startTime, durationMs ) {
|
|
60998
|
+
_commitGpuDurationSample( label, startTime, durationMs, traceOverride = null ) {
|
|
60778
60999
|
|
|
60779
61000
|
let buffer = this.gpuBuffers.get( label );
|
|
60780
61001
|
if ( buffer === undefined ) {
|
|
@@ -60797,8 +61018,8 @@ class ProfilerServiceClass {
|
|
|
60797
61018
|
this.traceEvents.push( {
|
|
60798
61019
|
name: label,
|
|
60799
61020
|
ph: 'X',
|
|
60800
|
-
ts: Math.round( this._getTraceTimestamp( startTime ) ),
|
|
60801
|
-
dur: Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
61021
|
+
ts: traceOverride?.ts ?? Math.round( this._getTraceTimestamp( startTime ) ),
|
|
61022
|
+
dur: traceOverride?.dur ?? Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
60802
61023
|
pid: 1,
|
|
60803
61024
|
tid: TRACE_TID_GPU,
|
|
60804
61025
|
cat: 'gnsx-gpu',
|
package/build/three.core.js
CHANGED
|
@@ -60409,6 +60409,9 @@ class ProfilerServiceClass {
|
|
|
60409
60409
|
|
|
60410
60410
|
if ( label !== null && label !== undefined && label !== '' ) {
|
|
60411
60411
|
|
|
60412
|
+
// Queue labelled passes with the active beginGpu span(s). Flushing
|
|
60413
|
+
// immediately would commit them before their parent envelope exists and
|
|
60414
|
+
// can anchor gpuTraceOrigin mid-frame, which breaks Speedscope nesting.
|
|
60412
60415
|
const passSpan = {
|
|
60413
60416
|
label: `GPU pass: ${label}`,
|
|
60414
60417
|
renderer,
|
|
@@ -60422,7 +60425,13 @@ class ProfilerServiceClass {
|
|
|
60422
60425
|
};
|
|
60423
60426
|
passSpan._queries[ type ].add( uid );
|
|
60424
60427
|
state.pendingSpans.push( passSpan );
|
|
60425
|
-
|
|
60428
|
+
// Only auto-flush when no beginGpu parent is open. Nested parents flush on endGpu
|
|
60429
|
+
// so pass slices share one origin and nest inside the parent envelope.
|
|
60430
|
+
if ( state.activeSpans.length === 0 ) {
|
|
60431
|
+
|
|
60432
|
+
this._scheduleGpuFlush( state );
|
|
60433
|
+
|
|
60434
|
+
}
|
|
60426
60435
|
|
|
60427
60436
|
}
|
|
60428
60437
|
|
|
@@ -60483,38 +60492,22 @@ class ProfilerServiceClass {
|
|
|
60483
60492
|
|
|
60484
60493
|
if ( state.gpuTimestampOrigin === null ) {
|
|
60485
60494
|
|
|
60486
|
-
|
|
60487
|
-
|
|
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 ) {
|
|
60495
|
+
const origin = this._findEarliestGpuTimestampOrigin( state, spans );
|
|
60496
|
+
if ( origin !== null ) {
|
|
60509
60497
|
|
|
60510
|
-
state.gpuTimestampOrigin =
|
|
60511
|
-
state.gpuTraceOrigin =
|
|
60498
|
+
state.gpuTimestampOrigin = origin.gpuTimestampOrigin;
|
|
60499
|
+
state.gpuTraceOrigin = origin.gpuTraceOrigin;
|
|
60512
60500
|
|
|
60513
60501
|
}
|
|
60514
60502
|
|
|
60515
60503
|
}
|
|
60516
60504
|
|
|
60517
60505
|
const resolvedUids = new Set();
|
|
60506
|
+
/** @type {Array<{ label: string, startTime: number, durationMs: number, isPass: boolean }>} */
|
|
60507
|
+
const samples = [];
|
|
60508
|
+
/** @type {Set<string>} */
|
|
60509
|
+
const labelledQueryUids = new Set();
|
|
60510
|
+
|
|
60518
60511
|
for ( const span of spans ) {
|
|
60519
60512
|
|
|
60520
60513
|
if ( span._generation !== this._gpuGeneration ) continue;
|
|
@@ -60552,20 +60545,247 @@ class ProfilerServiceClass {
|
|
|
60552
60545
|
if ( resolvedQueries > 0 ) {
|
|
60553
60546
|
|
|
60554
60547
|
let startTime = span.t0;
|
|
60555
|
-
|
|
60548
|
+
// Prefer the device envelope whenever any query has a range. Requiring
|
|
60549
|
+
// *all* queries to be ranged forced a CPU-t0 + summed-duration fallback
|
|
60550
|
+
// that often ended before later GPU pass slices, breaking flame nesting.
|
|
60551
|
+
if ( rangedQueries > 0 && gpuStart !== null && gpuEnd !== null && state.gpuTimestampOrigin !== null ) {
|
|
60556
60552
|
|
|
60557
60553
|
duration = Number( gpuEnd - gpuStart ) / 1e6;
|
|
60558
60554
|
startTime = state.gpuTraceOrigin + Number( gpuStart - state.gpuTimestampOrigin ) / 1e6;
|
|
60559
60555
|
|
|
60560
60556
|
}
|
|
60561
60557
|
|
|
60562
|
-
|
|
60558
|
+
const isPass = span.label.startsWith( 'GPU pass:' );
|
|
60559
|
+
if ( isPass ) {
|
|
60560
|
+
|
|
60561
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60562
|
+
|
|
60563
|
+
for ( const uid of span._queries[ type ] ) labelledQueryUids.add( uid );
|
|
60564
|
+
|
|
60565
|
+
}
|
|
60566
|
+
|
|
60567
|
+
}
|
|
60568
|
+
|
|
60569
|
+
samples.push( {
|
|
60570
|
+
label: span.label,
|
|
60571
|
+
startTime,
|
|
60572
|
+
durationMs: duration,
|
|
60573
|
+
isPass,
|
|
60574
|
+
} );
|
|
60575
|
+
|
|
60576
|
+
}
|
|
60577
|
+
|
|
60578
|
+
}
|
|
60579
|
+
|
|
60580
|
+
// Parent beginGpu spans collect every timestamp query, including renders that
|
|
60581
|
+
// had no gpuProfilerLabel. Surface those as pass slices so they are not empty
|
|
60582
|
+
// holes under the parent envelope.
|
|
60583
|
+
for ( const span of spans ) {
|
|
60584
|
+
|
|
60585
|
+
if ( span._generation !== this._gpuGeneration ) continue;
|
|
60586
|
+
if ( span.label.startsWith( 'GPU pass:' ) ) continue;
|
|
60587
|
+
if ( state.gpuTimestampOrigin === null ) continue;
|
|
60588
|
+
|
|
60589
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60590
|
+
|
|
60591
|
+
for ( const uid of span._queries[ type ] ) {
|
|
60592
|
+
|
|
60593
|
+
if ( labelledQueryUids.has( uid ) ) continue;
|
|
60594
|
+
if ( state.renderer.backend.hasTimestampQuery( uid ) === false ) continue;
|
|
60595
|
+
|
|
60596
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
60597
|
+
if ( range === null ) continue;
|
|
60598
|
+
|
|
60599
|
+
const durationMs = Number( range.end - range.start ) / 1e6;
|
|
60600
|
+
if ( durationMs <= 0 ) continue;
|
|
60601
|
+
|
|
60602
|
+
samples.push( {
|
|
60603
|
+
label: 'GPU pass: (unlabeled)',
|
|
60604
|
+
startTime: state.gpuTraceOrigin + Number( range.start - state.gpuTimestampOrigin ) / 1e6,
|
|
60605
|
+
durationMs,
|
|
60606
|
+
isPass: true,
|
|
60607
|
+
} );
|
|
60608
|
+
labelledQueryUids.add( uid );
|
|
60609
|
+
|
|
60610
|
+
}
|
|
60611
|
+
|
|
60612
|
+
}
|
|
60613
|
+
|
|
60614
|
+
}
|
|
60615
|
+
|
|
60616
|
+
const passSamples = samples.filter( sample => sample.isPass );
|
|
60617
|
+
const otherSamples = samples.filter( sample => sample.isPass === false );
|
|
60618
|
+
this._snapHalfOpenGpuPassTraceIntervals( passSamples );
|
|
60619
|
+
this._expandGpuParentTraceToContainPasses( otherSamples, passSamples );
|
|
60620
|
+
|
|
60621
|
+
for ( const sample of otherSamples ) {
|
|
60622
|
+
|
|
60623
|
+
this._commitGpuDurationSample( sample.label, sample.startTime, sample.durationMs, {
|
|
60624
|
+
ts: sample.traceTs,
|
|
60625
|
+
dur: sample.traceDur,
|
|
60626
|
+
} );
|
|
60627
|
+
|
|
60628
|
+
}
|
|
60629
|
+
|
|
60630
|
+
for ( const sample of passSamples ) {
|
|
60631
|
+
|
|
60632
|
+
this._commitGpuDurationSample( sample.label, sample.startTime, sample.durationMs, {
|
|
60633
|
+
ts: sample.traceTs,
|
|
60634
|
+
dur: sample.traceDur,
|
|
60635
|
+
} );
|
|
60636
|
+
|
|
60637
|
+
}
|
|
60638
|
+
|
|
60639
|
+
// Keep CPU times for queries still referenced by active parent spans.
|
|
60640
|
+
for ( const uid of resolvedUids ) {
|
|
60641
|
+
|
|
60642
|
+
if ( this._isGpuQueryReferencedByActiveSpans( state, uid ) ) continue;
|
|
60643
|
+
state.queryCpuTimes.delete( uid );
|
|
60644
|
+
|
|
60645
|
+
}
|
|
60646
|
+
|
|
60647
|
+
}
|
|
60648
|
+
|
|
60649
|
+
/**
|
|
60650
|
+
* Convert labelled GPU pass samples to non-overlapping half-open µs intervals for
|
|
60651
|
+
* Chrome/Speedscope traces. Stats keep the raw measured durations.
|
|
60652
|
+
*
|
|
60653
|
+
* @param {Array<{ startTime: number, durationMs: number, traceTs?: number, traceDur?: number }>} samples
|
|
60654
|
+
*/
|
|
60655
|
+
_snapHalfOpenGpuPassTraceIntervals( samples ) {
|
|
60656
|
+
|
|
60657
|
+
if ( samples.length === 0 ) return;
|
|
60658
|
+
|
|
60659
|
+
for ( const sample of samples ) {
|
|
60660
|
+
|
|
60661
|
+
sample.traceTs = Math.round( this._getTraceTimestamp( sample.startTime ) );
|
|
60662
|
+
sample.traceDur = Math.max( 1, Math.round( sample.durationMs * 1000 ) );
|
|
60663
|
+
|
|
60664
|
+
}
|
|
60665
|
+
|
|
60666
|
+
// Longer first at the same timestamp so the substantial pass keeps its start.
|
|
60667
|
+
samples.sort( ( a, b ) => a.traceTs - b.traceTs || b.traceDur - a.traceDur );
|
|
60668
|
+
|
|
60669
|
+
let cursor = Number.NEGATIVE_INFINITY;
|
|
60670
|
+
for ( const sample of samples ) {
|
|
60671
|
+
|
|
60672
|
+
if ( sample.traceTs < cursor ) {
|
|
60673
|
+
|
|
60674
|
+
const end = Math.max( sample.traceTs + sample.traceDur, cursor + 1 );
|
|
60675
|
+
sample.traceTs = cursor;
|
|
60676
|
+
sample.traceDur = Math.max( 1, end - sample.traceTs );
|
|
60563
60677
|
|
|
60564
60678
|
}
|
|
60565
60679
|
|
|
60680
|
+
cursor = sample.traceTs + sample.traceDur;
|
|
60681
|
+
|
|
60566
60682
|
}
|
|
60567
60683
|
|
|
60568
|
-
|
|
60684
|
+
}
|
|
60685
|
+
|
|
60686
|
+
/**
|
|
60687
|
+
* Speedscope nests by time containment on a thread. If a snapped GPU pass
|
|
60688
|
+
* sticks out past its beginGpu parent (rounding / half-open adjust), the
|
|
60689
|
+
* parent fails containment and is pushed onto a lower lane under renderFrame.
|
|
60690
|
+
* Expand overlapping parents so they strictly cover their pass children.
|
|
60691
|
+
*
|
|
60692
|
+
* @param {Array<{ startTime: number, durationMs: number, traceTs?: number, traceDur?: number }>} parents
|
|
60693
|
+
* @param {Array<{ traceTs: number, traceDur: number }>} passes
|
|
60694
|
+
*/
|
|
60695
|
+
_expandGpuParentTraceToContainPasses( parents, passes ) {
|
|
60696
|
+
|
|
60697
|
+
if ( parents.length === 0 || passes.length === 0 ) return;
|
|
60698
|
+
|
|
60699
|
+
for ( const parent of parents ) {
|
|
60700
|
+
|
|
60701
|
+
parent.traceTs = Math.round( this._getTraceTimestamp( parent.startTime ) );
|
|
60702
|
+
parent.traceDur = Math.max( 1, Math.round( parent.durationMs * 1000 ) );
|
|
60703
|
+
|
|
60704
|
+
const parentEnd = parent.traceTs + parent.traceDur;
|
|
60705
|
+
let minTs = parent.traceTs;
|
|
60706
|
+
let maxEnd = parentEnd;
|
|
60707
|
+
let touched = false;
|
|
60708
|
+
|
|
60709
|
+
for ( const pass of passes ) {
|
|
60710
|
+
|
|
60711
|
+
const passEnd = pass.traceTs + pass.traceDur;
|
|
60712
|
+
if ( pass.traceTs >= parentEnd || passEnd <= parent.traceTs ) continue;
|
|
60713
|
+
|
|
60714
|
+
touched = true;
|
|
60715
|
+
if ( pass.traceTs < minTs ) minTs = pass.traceTs;
|
|
60716
|
+
if ( passEnd > maxEnd ) maxEnd = passEnd;
|
|
60717
|
+
|
|
60718
|
+
}
|
|
60719
|
+
|
|
60720
|
+
if ( touched === false ) continue;
|
|
60721
|
+
|
|
60722
|
+
// Start 1µs before the first child so equal-start pairs still nest
|
|
60723
|
+
// (Speedscope sorts same-ts by longer-first; a child that begins
|
|
60724
|
+
// strictly after the parent is unambiguous).
|
|
60725
|
+
parent.traceTs = minTs > 0 ? minTs - 1 : minTs;
|
|
60726
|
+
parent.traceDur = Math.max( 1, maxEnd - parent.traceTs );
|
|
60727
|
+
|
|
60728
|
+
}
|
|
60729
|
+
|
|
60730
|
+
}
|
|
60731
|
+
|
|
60732
|
+
/**
|
|
60733
|
+
* @param {import('./ProfilerService.js').CommonGpuRendererState} state
|
|
60734
|
+
* @param {import('./ProfilerService.js').GpuSpanHandle[]} spans
|
|
60735
|
+
* @return {?{ gpuTimestampOrigin: bigint, gpuTraceOrigin: number }}
|
|
60736
|
+
*/
|
|
60737
|
+
_findEarliestGpuTimestampOrigin( state, spans ) {
|
|
60738
|
+
|
|
60739
|
+
let earliestRange = null;
|
|
60740
|
+
let earliestUid = null;
|
|
60741
|
+
const candidates = spans.concat( state.activeSpans );
|
|
60742
|
+
|
|
60743
|
+
for ( const span of candidates ) {
|
|
60744
|
+
|
|
60745
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
60746
|
+
|
|
60747
|
+
const queries = span._queries?.[ type ];
|
|
60748
|
+
if ( queries === undefined ) continue;
|
|
60749
|
+
|
|
60750
|
+
for ( const uid of queries ) {
|
|
60751
|
+
|
|
60752
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
60753
|
+
if ( range !== null && ( earliestRange === null || range.start < earliestRange.start ) ) {
|
|
60754
|
+
|
|
60755
|
+
earliestRange = range;
|
|
60756
|
+
earliestUid = uid;
|
|
60757
|
+
|
|
60758
|
+
}
|
|
60759
|
+
|
|
60760
|
+
}
|
|
60761
|
+
|
|
60762
|
+
}
|
|
60763
|
+
|
|
60764
|
+
}
|
|
60765
|
+
|
|
60766
|
+
if ( earliestRange === null ) return null;
|
|
60767
|
+
|
|
60768
|
+
return {
|
|
60769
|
+
gpuTimestampOrigin: earliestRange.start,
|
|
60770
|
+
gpuTraceOrigin: state.queryCpuTimes.get( earliestUid ) ?? spans[ 0 ].t0,
|
|
60771
|
+
};
|
|
60772
|
+
|
|
60773
|
+
}
|
|
60774
|
+
|
|
60775
|
+
/**
|
|
60776
|
+
* @param {import('./ProfilerService.js').CommonGpuRendererState} state
|
|
60777
|
+
* @param {string} uid
|
|
60778
|
+
* @return {boolean}
|
|
60779
|
+
*/
|
|
60780
|
+
_isGpuQueryReferencedByActiveSpans( state, uid ) {
|
|
60781
|
+
|
|
60782
|
+
for ( const span of state.activeSpans ) {
|
|
60783
|
+
|
|
60784
|
+
if ( span._queries.render.has( uid ) || span._queries.compute.has( uid ) ) return true;
|
|
60785
|
+
|
|
60786
|
+
}
|
|
60787
|
+
|
|
60788
|
+
return false;
|
|
60569
60789
|
|
|
60570
60790
|
}
|
|
60571
60791
|
|
|
@@ -60793,8 +61013,9 @@ class ProfilerServiceClass {
|
|
|
60793
61013
|
* @param {string} label
|
|
60794
61014
|
* @param {number} startTime
|
|
60795
61015
|
* @param {number} durationMs
|
|
61016
|
+
* @param {{ ts: number, dur: number }} [traceOverride]
|
|
60796
61017
|
*/
|
|
60797
|
-
_commitGpuDurationSample( label, startTime, durationMs ) {
|
|
61018
|
+
_commitGpuDurationSample( label, startTime, durationMs, traceOverride = null ) {
|
|
60798
61019
|
|
|
60799
61020
|
let buffer = this.gpuBuffers.get( label );
|
|
60800
61021
|
if ( buffer === undefined ) {
|
|
@@ -60817,8 +61038,8 @@ class ProfilerServiceClass {
|
|
|
60817
61038
|
this.traceEvents.push( {
|
|
60818
61039
|
name: label,
|
|
60819
61040
|
ph: 'X',
|
|
60820
|
-
ts: Math.round( this._getTraceTimestamp( startTime ) ),
|
|
60821
|
-
dur: Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
61041
|
+
ts: traceOverride?.ts ?? Math.round( this._getTraceTimestamp( startTime ) ),
|
|
61042
|
+
dur: traceOverride?.dur ?? Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
60822
61043
|
pid: 1,
|
|
60823
61044
|
tid: TRACE_TID_GPU,
|
|
60824
61045
|
cat: 'gnsx-gpu',
|