@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DataTexture, RenderTarget, RepeatWrapping, Vector2, Vector3, TempNode, QuadMesh, NodeMaterial, RendererUtils, RedFormat } from '@gnsx/three/webgpu';
|
|
2
|
-
import { reference, logarithmicDepthToViewZ, viewZToPerspectiveDepth, getNormalFromDepth, getScreenPosition, getViewPosition, nodeObject, Fn, float, NodeUpdateType, uv, uniform, Loop, vec2, vec3, vec4, int, dot, max, pow, abs, If, textureSize, sin, cos, PI, texture, passTexture, mat3, add, normalize, mul, cross, div, mix, acos, clamp } from '@gnsx/three/tsl';
|
|
2
|
+
import { reference, logarithmicDepthToViewZ, viewZToPerspectiveDepth, getNormalFromDepth, getScreenPosition, getViewPosition, nodeObject, Fn, float, NodeUpdateType, uv, uniform, Loop, vec2, vec3, vec4, int, dot, max, min, pow, abs, If, textureSize, sin, cos, PI, texture, passTexture, mat3, add, normalize, mul, cross, div, mix, acos, clamp } from '@gnsx/three/tsl';
|
|
3
3
|
|
|
4
4
|
const _quadMesh = /*@__PURE__*/ new QuadMesh();
|
|
5
5
|
const _size = /*@__PURE__*/ new Vector2();
|
|
@@ -70,6 +70,18 @@ class GTAONode extends TempNode {
|
|
|
70
70
|
*/
|
|
71
71
|
this.normalNode = normalNode;
|
|
72
72
|
|
|
73
|
+
// WITH_GENESYS
|
|
74
|
+
/**
|
|
75
|
+
* Optional node that marks pixels where GTAO should run. This is sampled only
|
|
76
|
+
* for the center pixel, allowing neighbor depth taps to use the raw depth
|
|
77
|
+
* texture without repeating an auxiliary validity texture fetch.
|
|
78
|
+
*
|
|
79
|
+
* @type {?Node<float>}
|
|
80
|
+
* @default null
|
|
81
|
+
*/
|
|
82
|
+
this.validityNode = null;
|
|
83
|
+
// !WITH_GENESYS
|
|
84
|
+
|
|
73
85
|
/**
|
|
74
86
|
* The resolution scale. By default the effect is rendered in full resolution
|
|
75
87
|
* for best quality but a value of `0.5` should be sufficient for most scenes.
|
|
@@ -106,6 +118,18 @@ class GTAONode extends TempNode {
|
|
|
106
118
|
*/
|
|
107
119
|
this.radius = uniform( 0.25 );
|
|
108
120
|
|
|
121
|
+
// WITH_GENESYS
|
|
122
|
+
/**
|
|
123
|
+
* Maximum screen-space radius in pixels. Clamps the projected world-space
|
|
124
|
+
* radius when the camera is close to surfaces so sample UVs stay local and
|
|
125
|
+
* avoid texture-cache thrashing (Activision GTAO guidance).
|
|
126
|
+
*
|
|
127
|
+
* @type {UniformNode<float>}
|
|
128
|
+
* @default 64
|
|
129
|
+
*/
|
|
130
|
+
this.maxScreenRadius = uniform( 64 );
|
|
131
|
+
// !WITH_GENESYS
|
|
132
|
+
|
|
109
133
|
/**
|
|
110
134
|
* The resolution of the effect. Can be scaled via
|
|
111
135
|
* `resolutionScale`.
|
|
@@ -343,12 +367,30 @@ class GTAONode extends TempNode {
|
|
|
343
367
|
|
|
344
368
|
const depth = sampleDepth( uvNode ).toVar();
|
|
345
369
|
|
|
370
|
+
// WITH_GENESYS
|
|
371
|
+
if ( this.validityNode !== null ) {
|
|
372
|
+
|
|
373
|
+
this.validityNode.sample( uvNode ).lessThan( 0.5 ).discard();
|
|
374
|
+
|
|
375
|
+
}
|
|
376
|
+
// !WITH_GENESYS
|
|
377
|
+
|
|
346
378
|
depth.greaterThanEqual( 1.0 ).discard();
|
|
347
379
|
|
|
348
380
|
const viewPosition = getViewPosition( uvNode, depth, this._cameraProjectionMatrixInverse ).toVar();
|
|
349
381
|
const viewNormal = sampleNormal( uvNode ).toVar();
|
|
350
382
|
|
|
351
|
-
|
|
383
|
+
// WITH_GENESYS
|
|
384
|
+
// Clamp world radius so its screen footprint never exceeds maxScreenRadius
|
|
385
|
+
// pixels. Without this, near-camera surfaces project a huge UV stride and
|
|
386
|
+
// the depth/normal taps thrash the texture cache.
|
|
387
|
+
const probeScreen = getScreenPosition( viewPosition.add( vec3( this.radius, 0.0, 0.0 ) ), this._cameraProjectionMatrix );
|
|
388
|
+
const centerScreen = getScreenPosition( viewPosition, this._cameraProjectionMatrix );
|
|
389
|
+
const screenRadiusPx = abs( probeScreen.x.sub( centerScreen.x ) ).mul( this.resolution.x );
|
|
390
|
+
const radiusScale = min( float( 1.0 ), this.maxScreenRadius.div( max( screenRadiusPx, float( 1e-4 ) ) ) );
|
|
391
|
+
const radiusToUse = this.radius.mul( radiusScale );
|
|
392
|
+
// !WITH_GENESYS
|
|
393
|
+
// const radiusToUse = this.radius;
|
|
352
394
|
|
|
353
395
|
const noiseResolution = textureSize( this._noiseNode, 0 );
|
|
354
396
|
let noiseUv = vec2( uvNode.x, uvNode.y.oneMinus() );
|
|
@@ -360,8 +402,16 @@ class GTAONode extends TempNode {
|
|
|
360
402
|
const bitangent = vec3( tangent.y.mul( - 1.0 ), tangent.x, 0.0 );
|
|
361
403
|
const kernelMatrix = mat3( tangent, bitangent, vec3( 0.0, 0.0, 1.0 ) );
|
|
362
404
|
|
|
363
|
-
|
|
364
|
-
|
|
405
|
+
// WITH_GENESYS
|
|
406
|
+
// A clamped radius covers less screen space, so proportionally reduce the
|
|
407
|
+
// sample budget as well. Keeping all samples only changes tap locality and
|
|
408
|
+
// does not address the near-camera shader cost.
|
|
409
|
+
const samplesToUse = min( this.samples, max( float( 6 ), this.samples.mul( radiusScale ) ) ).toVar();
|
|
410
|
+
const DIRECTIONS = samplesToUse.lessThan( 30 ).select( 3, 5 ).toVar();
|
|
411
|
+
const STEPS = add( samplesToUse, DIRECTIONS.sub( 1 ) ).div( DIRECTIONS ).toVar();
|
|
412
|
+
// !WITH_GENESYS
|
|
413
|
+
// const DIRECTIONS = this.samples.lessThan( 30 ).select( 3, 5 ).toVar();
|
|
414
|
+
// const STEPS = add( this.samples, DIRECTIONS.sub( 1 ) ).div( DIRECTIONS ).toVar();
|
|
365
415
|
|
|
366
416
|
const ao = float( 0 ).toVar();
|
|
367
417
|
|
package/package.json
CHANGED
|
@@ -407,6 +407,9 @@ class ProfilerServiceClass {
|
|
|
407
407
|
|
|
408
408
|
if ( label !== null && label !== undefined && label !== '' ) {
|
|
409
409
|
|
|
410
|
+
// Queue labelled passes with the active beginGpu span(s). Flushing
|
|
411
|
+
// immediately would commit them before their parent envelope exists and
|
|
412
|
+
// can anchor gpuTraceOrigin mid-frame, which breaks Speedscope nesting.
|
|
410
413
|
const passSpan = {
|
|
411
414
|
label: `GPU pass: ${label}`,
|
|
412
415
|
renderer,
|
|
@@ -420,7 +423,13 @@ class ProfilerServiceClass {
|
|
|
420
423
|
};
|
|
421
424
|
passSpan._queries[ type ].add( uid );
|
|
422
425
|
state.pendingSpans.push( passSpan );
|
|
423
|
-
|
|
426
|
+
// Only auto-flush when no beginGpu parent is open. Nested parents flush on endGpu
|
|
427
|
+
// so pass slices share one origin and nest inside the parent envelope.
|
|
428
|
+
if ( state.activeSpans.length === 0 ) {
|
|
429
|
+
|
|
430
|
+
this._scheduleGpuFlush( state );
|
|
431
|
+
|
|
432
|
+
}
|
|
424
433
|
|
|
425
434
|
}
|
|
426
435
|
|
|
@@ -481,38 +490,22 @@ class ProfilerServiceClass {
|
|
|
481
490
|
|
|
482
491
|
if ( state.gpuTimestampOrigin === null ) {
|
|
483
492
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
for ( const span of spans ) {
|
|
487
|
-
|
|
488
|
-
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
489
|
-
|
|
490
|
-
for ( const uid of span._queries[ type ] ) {
|
|
491
|
-
|
|
492
|
-
const range = state.renderer.backend.getTimestampRange( uid );
|
|
493
|
-
if ( range !== null && ( earliestRange === null || range.start < earliestRange.start ) ) {
|
|
494
|
-
|
|
495
|
-
earliestRange = range;
|
|
496
|
-
earliestUid = uid;
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if ( earliestRange !== null ) {
|
|
493
|
+
const origin = this._findEarliestGpuTimestampOrigin( state, spans );
|
|
494
|
+
if ( origin !== null ) {
|
|
507
495
|
|
|
508
|
-
state.gpuTimestampOrigin =
|
|
509
|
-
state.gpuTraceOrigin =
|
|
496
|
+
state.gpuTimestampOrigin = origin.gpuTimestampOrigin;
|
|
497
|
+
state.gpuTraceOrigin = origin.gpuTraceOrigin;
|
|
510
498
|
|
|
511
499
|
}
|
|
512
500
|
|
|
513
501
|
}
|
|
514
502
|
|
|
515
503
|
const resolvedUids = new Set();
|
|
504
|
+
/** @type {Array<{ label: string, startTime: number, durationMs: number, isPass: boolean }>} */
|
|
505
|
+
const samples = [];
|
|
506
|
+
/** @type {Set<string>} */
|
|
507
|
+
const labelledQueryUids = new Set();
|
|
508
|
+
|
|
516
509
|
for ( const span of spans ) {
|
|
517
510
|
|
|
518
511
|
if ( span._generation !== this._gpuGeneration ) continue;
|
|
@@ -550,20 +543,247 @@ class ProfilerServiceClass {
|
|
|
550
543
|
if ( resolvedQueries > 0 ) {
|
|
551
544
|
|
|
552
545
|
let startTime = span.t0;
|
|
553
|
-
|
|
546
|
+
// Prefer the device envelope whenever any query has a range. Requiring
|
|
547
|
+
// *all* queries to be ranged forced a CPU-t0 + summed-duration fallback
|
|
548
|
+
// that often ended before later GPU pass slices, breaking flame nesting.
|
|
549
|
+
if ( rangedQueries > 0 && gpuStart !== null && gpuEnd !== null && state.gpuTimestampOrigin !== null ) {
|
|
554
550
|
|
|
555
551
|
duration = Number( gpuEnd - gpuStart ) / 1e6;
|
|
556
552
|
startTime = state.gpuTraceOrigin + Number( gpuStart - state.gpuTimestampOrigin ) / 1e6;
|
|
557
553
|
|
|
558
554
|
}
|
|
559
555
|
|
|
560
|
-
|
|
556
|
+
const isPass = span.label.startsWith( 'GPU pass:' );
|
|
557
|
+
if ( isPass ) {
|
|
558
|
+
|
|
559
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
560
|
+
|
|
561
|
+
for ( const uid of span._queries[ type ] ) labelledQueryUids.add( uid );
|
|
562
|
+
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
samples.push( {
|
|
568
|
+
label: span.label,
|
|
569
|
+
startTime,
|
|
570
|
+
durationMs: duration,
|
|
571
|
+
isPass,
|
|
572
|
+
} );
|
|
561
573
|
|
|
562
574
|
}
|
|
563
575
|
|
|
564
576
|
}
|
|
565
577
|
|
|
566
|
-
|
|
578
|
+
// Parent beginGpu spans collect every timestamp query, including renders that
|
|
579
|
+
// had no gpuProfilerLabel. Surface those as pass slices so they are not empty
|
|
580
|
+
// holes under the parent envelope.
|
|
581
|
+
for ( const span of spans ) {
|
|
582
|
+
|
|
583
|
+
if ( span._generation !== this._gpuGeneration ) continue;
|
|
584
|
+
if ( span.label.startsWith( 'GPU pass:' ) ) continue;
|
|
585
|
+
if ( state.gpuTimestampOrigin === null ) continue;
|
|
586
|
+
|
|
587
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
588
|
+
|
|
589
|
+
for ( const uid of span._queries[ type ] ) {
|
|
590
|
+
|
|
591
|
+
if ( labelledQueryUids.has( uid ) ) continue;
|
|
592
|
+
if ( state.renderer.backend.hasTimestampQuery( uid ) === false ) continue;
|
|
593
|
+
|
|
594
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
595
|
+
if ( range === null ) continue;
|
|
596
|
+
|
|
597
|
+
const durationMs = Number( range.end - range.start ) / 1e6;
|
|
598
|
+
if ( durationMs <= 0 ) continue;
|
|
599
|
+
|
|
600
|
+
samples.push( {
|
|
601
|
+
label: 'GPU pass: (unlabeled)',
|
|
602
|
+
startTime: state.gpuTraceOrigin + Number( range.start - state.gpuTimestampOrigin ) / 1e6,
|
|
603
|
+
durationMs,
|
|
604
|
+
isPass: true,
|
|
605
|
+
} );
|
|
606
|
+
labelledQueryUids.add( uid );
|
|
607
|
+
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const passSamples = samples.filter( sample => sample.isPass );
|
|
615
|
+
const otherSamples = samples.filter( sample => sample.isPass === false );
|
|
616
|
+
this._snapHalfOpenGpuPassTraceIntervals( passSamples );
|
|
617
|
+
this._expandGpuParentTraceToContainPasses( otherSamples, passSamples );
|
|
618
|
+
|
|
619
|
+
for ( const sample of otherSamples ) {
|
|
620
|
+
|
|
621
|
+
this._commitGpuDurationSample( sample.label, sample.startTime, sample.durationMs, {
|
|
622
|
+
ts: sample.traceTs,
|
|
623
|
+
dur: sample.traceDur,
|
|
624
|
+
} );
|
|
625
|
+
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
for ( const sample of passSamples ) {
|
|
629
|
+
|
|
630
|
+
this._commitGpuDurationSample( sample.label, sample.startTime, sample.durationMs, {
|
|
631
|
+
ts: sample.traceTs,
|
|
632
|
+
dur: sample.traceDur,
|
|
633
|
+
} );
|
|
634
|
+
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Keep CPU times for queries still referenced by active parent spans.
|
|
638
|
+
for ( const uid of resolvedUids ) {
|
|
639
|
+
|
|
640
|
+
if ( this._isGpuQueryReferencedByActiveSpans( state, uid ) ) continue;
|
|
641
|
+
state.queryCpuTimes.delete( uid );
|
|
642
|
+
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Convert labelled GPU pass samples to non-overlapping half-open µs intervals for
|
|
649
|
+
* Chrome/Speedscope traces. Stats keep the raw measured durations.
|
|
650
|
+
*
|
|
651
|
+
* @param {Array<{ startTime: number, durationMs: number, traceTs?: number, traceDur?: number }>} samples
|
|
652
|
+
*/
|
|
653
|
+
_snapHalfOpenGpuPassTraceIntervals( samples ) {
|
|
654
|
+
|
|
655
|
+
if ( samples.length === 0 ) return;
|
|
656
|
+
|
|
657
|
+
for ( const sample of samples ) {
|
|
658
|
+
|
|
659
|
+
sample.traceTs = Math.round( this._getTraceTimestamp( sample.startTime ) );
|
|
660
|
+
sample.traceDur = Math.max( 1, Math.round( sample.durationMs * 1000 ) );
|
|
661
|
+
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Longer first at the same timestamp so the substantial pass keeps its start.
|
|
665
|
+
samples.sort( ( a, b ) => a.traceTs - b.traceTs || b.traceDur - a.traceDur );
|
|
666
|
+
|
|
667
|
+
let cursor = Number.NEGATIVE_INFINITY;
|
|
668
|
+
for ( const sample of samples ) {
|
|
669
|
+
|
|
670
|
+
if ( sample.traceTs < cursor ) {
|
|
671
|
+
|
|
672
|
+
const end = Math.max( sample.traceTs + sample.traceDur, cursor + 1 );
|
|
673
|
+
sample.traceTs = cursor;
|
|
674
|
+
sample.traceDur = Math.max( 1, end - sample.traceTs );
|
|
675
|
+
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
cursor = sample.traceTs + sample.traceDur;
|
|
679
|
+
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Speedscope nests by time containment on a thread. If a snapped GPU pass
|
|
686
|
+
* sticks out past its beginGpu parent (rounding / half-open adjust), the
|
|
687
|
+
* parent fails containment and is pushed onto a lower lane under renderFrame.
|
|
688
|
+
* Expand overlapping parents so they strictly cover their pass children.
|
|
689
|
+
*
|
|
690
|
+
* @param {Array<{ startTime: number, durationMs: number, traceTs?: number, traceDur?: number }>} parents
|
|
691
|
+
* @param {Array<{ traceTs: number, traceDur: number }>} passes
|
|
692
|
+
*/
|
|
693
|
+
_expandGpuParentTraceToContainPasses( parents, passes ) {
|
|
694
|
+
|
|
695
|
+
if ( parents.length === 0 || passes.length === 0 ) return;
|
|
696
|
+
|
|
697
|
+
for ( const parent of parents ) {
|
|
698
|
+
|
|
699
|
+
parent.traceTs = Math.round( this._getTraceTimestamp( parent.startTime ) );
|
|
700
|
+
parent.traceDur = Math.max( 1, Math.round( parent.durationMs * 1000 ) );
|
|
701
|
+
|
|
702
|
+
const parentEnd = parent.traceTs + parent.traceDur;
|
|
703
|
+
let minTs = parent.traceTs;
|
|
704
|
+
let maxEnd = parentEnd;
|
|
705
|
+
let touched = false;
|
|
706
|
+
|
|
707
|
+
for ( const pass of passes ) {
|
|
708
|
+
|
|
709
|
+
const passEnd = pass.traceTs + pass.traceDur;
|
|
710
|
+
if ( pass.traceTs >= parentEnd || passEnd <= parent.traceTs ) continue;
|
|
711
|
+
|
|
712
|
+
touched = true;
|
|
713
|
+
if ( pass.traceTs < minTs ) minTs = pass.traceTs;
|
|
714
|
+
if ( passEnd > maxEnd ) maxEnd = passEnd;
|
|
715
|
+
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if ( touched === false ) continue;
|
|
719
|
+
|
|
720
|
+
// Start 1µs before the first child so equal-start pairs still nest
|
|
721
|
+
// (Speedscope sorts same-ts by longer-first; a child that begins
|
|
722
|
+
// strictly after the parent is unambiguous).
|
|
723
|
+
parent.traceTs = minTs > 0 ? minTs - 1 : minTs;
|
|
724
|
+
parent.traceDur = Math.max( 1, maxEnd - parent.traceTs );
|
|
725
|
+
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* @param {import('./ProfilerService.js').CommonGpuRendererState} state
|
|
732
|
+
* @param {import('./ProfilerService.js').GpuSpanHandle[]} spans
|
|
733
|
+
* @return {?{ gpuTimestampOrigin: bigint, gpuTraceOrigin: number }}
|
|
734
|
+
*/
|
|
735
|
+
_findEarliestGpuTimestampOrigin( state, spans ) {
|
|
736
|
+
|
|
737
|
+
let earliestRange = null;
|
|
738
|
+
let earliestUid = null;
|
|
739
|
+
const candidates = spans.concat( state.activeSpans );
|
|
740
|
+
|
|
741
|
+
for ( const span of candidates ) {
|
|
742
|
+
|
|
743
|
+
for ( const type of [ TimestampQuery.RENDER, TimestampQuery.COMPUTE ] ) {
|
|
744
|
+
|
|
745
|
+
const queries = span._queries?.[ type ];
|
|
746
|
+
if ( queries === undefined ) continue;
|
|
747
|
+
|
|
748
|
+
for ( const uid of queries ) {
|
|
749
|
+
|
|
750
|
+
const range = state.renderer.backend.getTimestampRange( uid );
|
|
751
|
+
if ( range !== null && ( earliestRange === null || range.start < earliestRange.start ) ) {
|
|
752
|
+
|
|
753
|
+
earliestRange = range;
|
|
754
|
+
earliestUid = uid;
|
|
755
|
+
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if ( earliestRange === null ) return null;
|
|
765
|
+
|
|
766
|
+
return {
|
|
767
|
+
gpuTimestampOrigin: earliestRange.start,
|
|
768
|
+
gpuTraceOrigin: state.queryCpuTimes.get( earliestUid ) ?? spans[ 0 ].t0,
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* @param {import('./ProfilerService.js').CommonGpuRendererState} state
|
|
775
|
+
* @param {string} uid
|
|
776
|
+
* @return {boolean}
|
|
777
|
+
*/
|
|
778
|
+
_isGpuQueryReferencedByActiveSpans( state, uid ) {
|
|
779
|
+
|
|
780
|
+
for ( const span of state.activeSpans ) {
|
|
781
|
+
|
|
782
|
+
if ( span._queries.render.has( uid ) || span._queries.compute.has( uid ) ) return true;
|
|
783
|
+
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return false;
|
|
567
787
|
|
|
568
788
|
}
|
|
569
789
|
|
|
@@ -791,8 +1011,9 @@ class ProfilerServiceClass {
|
|
|
791
1011
|
* @param {string} label
|
|
792
1012
|
* @param {number} startTime
|
|
793
1013
|
* @param {number} durationMs
|
|
1014
|
+
* @param {{ ts: number, dur: number }} [traceOverride]
|
|
794
1015
|
*/
|
|
795
|
-
_commitGpuDurationSample( label, startTime, durationMs ) {
|
|
1016
|
+
_commitGpuDurationSample( label, startTime, durationMs, traceOverride = null ) {
|
|
796
1017
|
|
|
797
1018
|
let buffer = this.gpuBuffers.get( label );
|
|
798
1019
|
if ( buffer === undefined ) {
|
|
@@ -815,8 +1036,8 @@ class ProfilerServiceClass {
|
|
|
815
1036
|
this.traceEvents.push( {
|
|
816
1037
|
name: label,
|
|
817
1038
|
ph: 'X',
|
|
818
|
-
ts: Math.round( this._getTraceTimestamp( startTime ) ),
|
|
819
|
-
dur: Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
1039
|
+
ts: traceOverride?.ts ?? Math.round( this._getTraceTimestamp( startTime ) ),
|
|
1040
|
+
dur: traceOverride?.dur ?? Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
820
1041
|
pid: 1,
|
|
821
1042
|
tid: TRACE_TID_GPU,
|
|
822
1043
|
cat: 'gnsx-gpu',
|
|
@@ -1618,8 +1618,23 @@ class Renderer {
|
|
|
1618
1618
|
const renderContext = this._renderContexts.get( renderTarget, this._mrt, this._callDepth );
|
|
1619
1619
|
|
|
1620
1620
|
// WITH_GENESYS
|
|
1621
|
-
|
|
1621
|
+
// Prefer an explicit scene/pass name; otherwise fall back to the render
|
|
1622
|
+
// target texture name so depth/shadow/RTT work is not an invisible hole.
|
|
1623
|
+
let gpuProfilerLabel = scene.name !== '' ? scene.name : null;
|
|
1624
|
+
if ( gpuProfilerLabel === null && renderTarget !== null ) {
|
|
1625
|
+
|
|
1626
|
+
const textureName = renderTarget.texture?.name;
|
|
1627
|
+
if ( textureName !== undefined && textureName !== null && textureName !== '' ) {
|
|
1628
|
+
|
|
1629
|
+
gpuProfilerLabel = textureName;
|
|
1630
|
+
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
renderContext.gpuProfilerLabel = gpuProfilerLabel;
|
|
1622
1636
|
// !WITH_GENESYS
|
|
1637
|
+
// renderContext.gpuProfilerLabel = scene.name !== '' ? scene.name : null;
|
|
1623
1638
|
|
|
1624
1639
|
this._currentRenderContext = renderContext;
|
|
1625
1640
|
this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject;
|
|
@@ -2620,7 +2635,7 @@ class Renderer {
|
|
|
2620
2635
|
|
|
2621
2636
|
// WITH_GENESYS
|
|
2622
2637
|
// Drop the strong reference to the active `RenderContext` so module-level
|
|
2623
|
-
// `WeakMap<RenderContext,
|
|
2638
|
+
// `WeakMap<RenderContext, ...>` caches can release entries once
|
|
2624
2639
|
// `_renderContexts.dispose()` has cleared its dictionary.
|
|
2625
2640
|
this._currentRenderContext = null;
|
|
2626
2641
|
// !WITH_GENESYS
|
|
@@ -25,7 +25,7 @@ import GPUShaderModuleDescriptor from './descriptors/GPUShaderModuleDescriptor.j
|
|
|
25
25
|
import GPURenderPassColorAttachment from './descriptors/GPURenderPassColorAttachment.js';
|
|
26
26
|
import GPURenderPassDepthStencilAttachment from './descriptors/GPURenderPassDepthStencilAttachment.js';
|
|
27
27
|
import GPURenderPassDescriptor from './descriptors/GPURenderPassDescriptor.js';
|
|
28
|
-
import GPURenderPassTimestampWrites from './descriptors/GPURenderPassTimestampWrites.js';
|
|
28
|
+
// import GPURenderPassTimestampWrites from './descriptors/GPURenderPassTimestampWrites.js';
|
|
29
29
|
import GPUTexelCopyTextureInfo from './descriptors/GPUTexelCopyTextureInfo.js';
|
|
30
30
|
import GPUTextureViewDescriptor from './descriptors/GPUTextureViewDescriptor.js';
|
|
31
31
|
import GPUExtent3D from './descriptors/GPUExtent3D.js';
|
|
@@ -36,7 +36,7 @@ const _commandEncoderDescriptor = new GPUCommandEncoderDescriptor();
|
|
|
36
36
|
const _computePassDescriptor = new GPUComputePassDescriptor();
|
|
37
37
|
const _querySetDescriptor = new GPUQuerySetDescriptor();
|
|
38
38
|
const _shaderModuleDescriptor = new GPUShaderModuleDescriptor();
|
|
39
|
-
const _renderPassTimestampWrites = new GPURenderPassTimestampWrites();
|
|
39
|
+
// const _renderPassTimestampWrites = new GPURenderPassTimestampWrites();
|
|
40
40
|
const _texelCopyTextureInfoSrc = new GPUTexelCopyTextureInfo();
|
|
41
41
|
const _texelCopyTextureInfoDst = new GPUTexelCopyTextureInfo();
|
|
42
42
|
const _viewDescriptor = new GPUTextureViewDescriptor();
|
|
@@ -2279,15 +2279,22 @@ class WebGPUBackend extends Backend {
|
|
|
2279
2279
|
if ( baseOffset === null || timestampQueryPool.querySet === null ) return;
|
|
2280
2280
|
// !WITH_GENESYS
|
|
2281
2281
|
|
|
2282
|
-
_renderPassTimestampWrites.querySet = timestampQueryPool.querySet;
|
|
2283
|
-
_renderPassTimestampWrites.beginningOfPassWriteIndex = baseOffset;
|
|
2284
|
-
_renderPassTimestampWrites.endOfPassWriteIndex = baseOffset + 1;
|
|
2285
|
-
|
|
2286
|
-
descriptor.timestampWrites = _renderPassTimestampWrites;
|
|
2287
|
-
|
|
2288
2282
|
// WITH_GENESYS
|
|
2283
|
+
// Per-pass object: a shared descriptor is mutated by nested beginRender /
|
|
2284
|
+
// copyFramebuffer restarts and can make unrelated passes write the same
|
|
2285
|
+
// query indices (shadow maps collapsing to ~1µs is the common symptom).
|
|
2286
|
+
descriptor.timestampWrites = {
|
|
2287
|
+
querySet: timestampQueryPool.querySet,
|
|
2288
|
+
beginningOfPassWriteIndex: baseOffset,
|
|
2289
|
+
endOfPassWriteIndex: baseOffset + 1,
|
|
2290
|
+
};
|
|
2291
|
+
|
|
2289
2292
|
this.notifyTimestampQuery( type, uid, label );
|
|
2290
2293
|
// !WITH_GENESYS
|
|
2294
|
+
// _renderPassTimestampWrites.querySet = timestampQueryPool.querySet;
|
|
2295
|
+
// _renderPassTimestampWrites.beginningOfPassWriteIndex = baseOffset;
|
|
2296
|
+
// _renderPassTimestampWrites.endOfPassWriteIndex = baseOffset + 1;
|
|
2297
|
+
// descriptor.timestampWrites = _renderPassTimestampWrites;
|
|
2291
2298
|
|
|
2292
2299
|
}
|
|
2293
2300
|
|
|
@@ -2848,6 +2855,17 @@ class WebGPUBackend extends Backend {
|
|
|
2848
2855
|
if ( renderContext.depth ) descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
|
|
2849
2856
|
if ( renderContext.stencil ) descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
|
|
2850
2857
|
|
|
2858
|
+
// WITH_GENESYS
|
|
2859
|
+
// Restarted passes need fresh timestamp slots; reusing the previous
|
|
2860
|
+
// timestampWrites overwrites the first segment's query results.
|
|
2861
|
+
this.initTimestampQuery(
|
|
2862
|
+
TimestampQuery.RENDER,
|
|
2863
|
+
this.getTimestampUID( renderContext ),
|
|
2864
|
+
descriptor,
|
|
2865
|
+
renderContext.gpuProfilerLabel
|
|
2866
|
+
);
|
|
2867
|
+
// !WITH_GENESYS
|
|
2868
|
+
|
|
2851
2869
|
renderContextData.currentPass = encoder.beginRenderPass( descriptor );
|
|
2852
2870
|
renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null };
|
|
2853
2871
|
|
|
@@ -78,7 +78,20 @@ class WebGPUTimestampQueryPool extends TimestampQueryPool {
|
|
|
78
78
|
const baseOffset = this.currentQueryIndex;
|
|
79
79
|
this.currentQueryIndex += 2;
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
// WITH_GENESYS
|
|
82
|
+
// One logical uid can own multiple GPU passes (nested interrupt /
|
|
83
|
+
// copyFramebuffer restart). Keep every offset; resolve sums them.
|
|
84
|
+
let offsets = this.queryOffsets.get( uid );
|
|
85
|
+
if ( offsets === undefined ) {
|
|
86
|
+
|
|
87
|
+
offsets = [];
|
|
88
|
+
this.queryOffsets.set( uid, offsets );
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
offsets.push( baseOffset );
|
|
93
|
+
// !WITH_GENESYS
|
|
94
|
+
// this.queryOffsets.set( uid, baseOffset );
|
|
82
95
|
|
|
83
96
|
return baseOffset;
|
|
84
97
|
|
|
@@ -200,7 +213,7 @@ class WebGPUTimestampQueryPool extends TimestampQueryPool {
|
|
|
200
213
|
|
|
201
214
|
const frames = [];
|
|
202
215
|
|
|
203
|
-
for ( const [ uid,
|
|
216
|
+
for ( const [ uid, offsetOrOffsets ] of currentOffsets ) {
|
|
204
217
|
|
|
205
218
|
const match = uid.match( /^(.*):f(\d+)$/ );
|
|
206
219
|
const frame = parseInt( match[ 2 ] );
|
|
@@ -213,14 +226,34 @@ class WebGPUTimestampQueryPool extends TimestampQueryPool {
|
|
|
213
226
|
|
|
214
227
|
if ( framesDuration[ frame ] === undefined ) framesDuration[ frame ] = 0;
|
|
215
228
|
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
229
|
+
// WITH_GENESYS
|
|
230
|
+
const offsets = Array.isArray( offsetOrOffsets ) ? offsetOrOffsets : [ offsetOrOffsets ];
|
|
231
|
+
let duration = 0;
|
|
232
|
+
let rangeStart = null;
|
|
233
|
+
let rangeEnd = null;
|
|
234
|
+
|
|
235
|
+
for ( const baseOffset of offsets ) {
|
|
236
|
+
|
|
237
|
+
const startTime = times[ baseOffset ];
|
|
238
|
+
const endTime = times[ baseOffset + 1 ];
|
|
239
|
+
duration += Number( endTime - startTime ) / 1e6;
|
|
240
|
+
|
|
241
|
+
if ( rangeStart === null || startTime < rangeStart ) rangeStart = startTime;
|
|
242
|
+
if ( rangeEnd === null || endTime > rangeEnd ) rangeEnd = endTime;
|
|
243
|
+
|
|
244
|
+
}
|
|
219
245
|
|
|
220
246
|
this.timestamps.set( uid, duration );
|
|
221
|
-
|
|
222
|
-
|
|
247
|
+
if ( rangeStart !== null && rangeEnd !== null ) {
|
|
248
|
+
|
|
249
|
+
this.timestampRanges.set( uid, { start: rangeStart, end: rangeEnd } );
|
|
250
|
+
|
|
251
|
+
}
|
|
223
252
|
// !WITH_GENESYS
|
|
253
|
+
// const startTime = times[ baseOffset ];
|
|
254
|
+
// const endTime = times[ baseOffset + 1 ];
|
|
255
|
+
// const duration = Number( endTime - startTime ) / 1e6;
|
|
256
|
+
// this.timestamps.set( uid, duration );
|
|
224
257
|
|
|
225
258
|
framesDuration[ frame ] += duration;
|
|
226
259
|
|