@gnsx/three 0.185.11 → 0.185.13

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.
@@ -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
- const radiusToUse = this.radius;
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
- const DIRECTIONS = this.samples.lessThan( 30 ).select( 3, 5 ).toVar();
364
- const STEPS = add( this.samples, DIRECTIONS.sub( 1 ) ).div( DIRECTIONS ).toVar();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnsx/three",
3
- "version": "0.185.11",
3
+ "version": "0.185.13",
4
4
  "description": "JavaScript 3D library",
5
5
  "type": "module",
6
6
  "main": "./build/three.cjs",
@@ -10,6 +10,7 @@ import { warnOnce } from '../utils.js';
10
10
  * // play for a few seconds
11
11
  * __gnsx_profiler.report() // sorted console.table (aggregated stats)
12
12
  * __gnsx_profiler.downloadTrace() // download gnsx-trace.json for Speedscope / Perfetto
13
+ * __gnsx_profiler.downloadTrace('gnsx-trace.json', 0.05) // omit spans < 0.05 ms
13
14
  * __gnsx_profiler.reset() // clear samples and trace
14
15
  * __gnsx_profiler.disable()
15
16
  *
@@ -1216,11 +1217,16 @@ class ProfilerServiceClass {
1216
1217
  }
1217
1218
 
1218
1219
  /**
1220
+ * @param {number} [minDurationMs=0] Omit complete spans shorter than this duration (ms). Capture is unaffected.
1219
1221
  * @return {import('./ProfilerService.js').ChromeTrace}
1220
1222
  */
1221
- exportChromeTrace() {
1223
+ exportChromeTrace( minDurationMs = 0 ) {
1222
1224
 
1223
- const slices = [ ...this.traceEvents ];
1225
+ const thresholdMs = Number.isFinite( minDurationMs ) && minDurationMs > 0 ? minDurationMs : 0;
1226
+ const minDurUs = thresholdMs * 1000;
1227
+ const slices = minDurUs > 0
1228
+ ? this.traceEvents.filter( e => ( e.dur ?? 0 ) >= minDurUs )
1229
+ : [ ...this.traceEvents ];
1224
1230
  slices.sort( ( a, b ) => {
1225
1231
 
1226
1232
  if ( a.ts !== b.ts ) return a.ts - b.ts;
@@ -1298,8 +1304,9 @@ class ProfilerServiceClass {
1298
1304
 
1299
1305
  /**
1300
1306
  * @param {string} [filename='gnsx-trace.json']
1307
+ * @param {number} [minDurationMs=0] Omit complete spans shorter than this duration (ms).
1301
1308
  */
1302
- downloadTrace( filename = 'gnsx-trace.json' ) {
1309
+ downloadTrace( filename = 'gnsx-trace.json', minDurationMs = 0 ) {
1303
1310
 
1304
1311
  if ( typeof document === 'undefined' ) {
1305
1312
 
@@ -1322,7 +1329,10 @@ class ProfilerServiceClass {
1322
1329
 
1323
1330
  }
1324
1331
 
1325
- const json = JSON.stringify( this.exportChromeTrace() );
1332
+ const thresholdMs = Number.isFinite( minDurationMs ) && minDurationMs > 0 ? minDurationMs : 0;
1333
+ const trace = this.exportChromeTrace( thresholdMs );
1334
+ const eventCount = trace.traceEvents.filter( e => e.ph === 'X' ).length;
1335
+ const json = JSON.stringify( trace );
1326
1336
  const blob = new Blob( [ json ], { type: 'application/json' } );
1327
1337
  const url = URL.createObjectURL( blob );
1328
1338
  const a = document.createElement( 'a' );
@@ -1330,7 +1340,10 @@ class ProfilerServiceClass {
1330
1340
  a.download = filename;
1331
1341
  a.click();
1332
1342
  URL.revokeObjectURL( url );
1333
- console.log( `[ProfilerService] Trace downloaded: ${filename} (${this.traceEvents.length} events)` );
1343
+ const thresholdNote = thresholdMs > 0
1344
+ ? ` (filtered ${this.traceEvents.length - eventCount} spans < ${thresholdMs} ms)`
1345
+ : '';
1346
+ console.log( `[ProfilerService] Trace downloaded: ${filename} (${eventCount} events${thresholdNote})` );
1334
1347
 
1335
1348
  }
1336
1349
 
@@ -1378,20 +1378,26 @@ class Renderer {
1378
1378
  */
1379
1379
  render( scene, camera ) {
1380
1380
 
1381
- // WITH_GENESYS
1382
- ProfilerService.begin( 'Renderer.render' );
1383
- // !WITH_GENESYS
1384
-
1385
1381
  if ( this._initialized === false ) {
1386
1382
 
1387
1383
  throw new Error( 'THREE.Renderer: .render() called before the backend is initialized. Use "await renderer.init();" before rendering.' );
1388
1384
 
1389
1385
  }
1390
1386
 
1391
- this._renderScene( scene, camera );
1392
-
1393
1387
  // WITH_GENESYS
1394
- ProfilerService.end( 'Renderer.render' );
1388
+ const label = `Renderer.render (${scene.name || scene.type})`;
1389
+ ProfilerService.begin( label );
1390
+ const handle = ProfilerService.beginGpu( label, this );
1391
+ try {
1392
+
1393
+ this._renderScene( scene, camera );
1394
+
1395
+ } finally {
1396
+
1397
+ ProfilerService.endGpu( handle );
1398
+ ProfilerService.end( label );
1399
+
1400
+ }
1395
1401
  // !WITH_GENESYS
1396
1402
 
1397
1403
  }
@@ -3436,7 +3442,7 @@ class Renderer {
3436
3442
  _renderObjects( renderList, camera, scene, lightsNode, passId = null ) {
3437
3443
 
3438
3444
  // WITH_GENESYS
3439
- const label = `${this._currentRenderObjectFunction?.name || 'renderObject'} (${renderList.length})`;
3445
+ const label = `_renderObjects (${renderList.length})`;
3440
3446
  ProfilerService.begin( label );
3441
3447
  // !WITH_GENESYS
3442
3448
 
@@ -3671,6 +3677,11 @@ class Renderer {
3671
3677
  */
3672
3678
  renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext = null, passId = null ) {
3673
3679
 
3680
+ // WITH_GENESYS
3681
+ const label = `renderObject (${object.name || object.type} - ${material.name || material.type})`;
3682
+ ProfilerService.begin( label );
3683
+ // !WITH_GENESYS
3684
+
3674
3685
  let materialOverride = false;
3675
3686
  let materialColorNode;
3676
3687
  let materialDepthNode;
@@ -3782,6 +3793,10 @@ class Renderer {
3782
3793
 
3783
3794
  object.onAfterRender( this, scene, camera, geometry, material, group );
3784
3795
 
3796
+ // WITH_GENESYS
3797
+ ProfilerService.end( label );
3798
+ // !WITH_GENESYS
3799
+
3785
3800
  }
3786
3801
 
3787
3802
  /**