rayzee 6.5.0 → 7.1.0

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.
Files changed (51) hide show
  1. package/README.md +24 -5
  2. package/dist/rayzee.es.js +7624 -7063
  3. package/dist/rayzee.es.js.map +1 -1
  4. package/dist/rayzee.umd.js +157 -236
  5. package/dist/rayzee.umd.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/EngineDefaults.js +26 -9
  8. package/src/PathTracerApp.js +118 -26
  9. package/src/Pipeline/PipelineContext.js +1 -2
  10. package/src/Pipeline/RenderPipeline.js +1 -1
  11. package/src/Pipeline/RenderStage.js +1 -1
  12. package/src/Processor/CameraOptimizer.js +0 -5
  13. package/src/Processor/GeometryExtractor.js +6 -0
  14. package/src/Processor/KernelManager.js +277 -0
  15. package/src/Processor/PackedRayBuffer.js +291 -0
  16. package/src/Processor/QueueManager.js +173 -0
  17. package/src/Processor/SceneProcessor.js +1 -0
  18. package/src/Processor/ShaderBuilder.js +11 -317
  19. package/src/Processor/StorageTexturePool.js +29 -15
  20. package/src/Processor/VRAMTracker.js +169 -0
  21. package/src/Processor/utils.js +11 -110
  22. package/src/RenderSettings.js +0 -3
  23. package/src/Stages/ASVGF.js +151 -78
  24. package/src/Stages/BilateralFilter.js +34 -10
  25. package/src/Stages/EdgeFilter.js +2 -3
  26. package/src/Stages/MotionVector.js +16 -9
  27. package/src/Stages/NormalDepth.js +17 -5
  28. package/src/Stages/PathTracer.js +671 -1456
  29. package/src/Stages/PathTracerStage.js +1451 -0
  30. package/src/Stages/SSRC.js +32 -15
  31. package/src/Stages/Variance.js +35 -12
  32. package/src/TSL/CompactKernel.js +110 -0
  33. package/src/TSL/DebugKernel.js +98 -0
  34. package/src/TSL/Environment.js +13 -11
  35. package/src/TSL/ExtendKernel.js +75 -0
  36. package/src/TSL/FinalWriteKernel.js +121 -0
  37. package/src/TSL/GenerateKernel.js +111 -0
  38. package/src/TSL/LightsSampling.js +2 -2
  39. package/src/TSL/PathTracerCore.js +43 -1039
  40. package/src/TSL/ShadeKernel.js +876 -0
  41. package/src/TSL/patches.js +81 -4
  42. package/src/index.js +3 -0
  43. package/src/managers/CameraManager.js +1 -1
  44. package/src/managers/DenoisingManager.js +40 -75
  45. package/src/managers/EnvironmentManager.js +30 -39
  46. package/src/managers/OverlayManager.js +7 -22
  47. package/src/managers/UniformManager.js +0 -3
  48. package/src/managers/helpers/TileHelper.js +2 -2
  49. package/src/Stages/AdaptiveSampling.js +0 -483
  50. package/src/TSL/PathTracer.js +0 -384
  51. package/src/managers/TileManager.js +0 -298
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rayzee",
3
- "version": "6.5.0",
3
+ "version": "7.1.0",
4
4
  "type": "module",
5
5
  "description": "Real-time WebGPU path tracing engine built on Three.js",
6
6
  "main": "dist/rayzee.umd.js",
@@ -58,6 +58,10 @@ export const ENGINE_DEFAULTS = {
58
58
  afScreenPoint: { x: 0.5, y: 0.5 },
59
59
  afSmoothingFactor: 0.15,
60
60
 
61
+ // Multi-sample pool: S=samplesPerPixel rays/pixel/frame, FinalWrite averages them; interactive-only (renderMode 0, ≤ cap), else S=1.
62
+ // Pixel cap (768²) bounds pool memory; covers the 512² default, excludes ≥768².
63
+ wavefrontMultiSampleMaxPixels: 589824,
64
+
61
65
  enablePathTracer: true,
62
66
  enableAccumulation: true,
63
67
  pauseRendering: false,
@@ -70,14 +74,9 @@ export const ENGINE_DEFAULTS = {
70
74
  enableEmissiveTriangleSampling: false,
71
75
  emissiveBoost: 1.0,
72
76
 
73
- adaptiveSampling: false,
74
- adaptiveSamplingMin: 1,
75
- adaptiveSamplingMax: 8,
76
- adaptiveSamplingVarianceThreshold: 0.1,
77
77
  temporalVarianceWeight: 0.6,
78
78
  enableEarlyTermination: true,
79
79
  earlyTerminationThreshold: 0.002,
80
- showAdaptiveSamplingHelper: false,
81
80
  performanceModeAdaptive: 'medium',
82
81
 
83
82
  fireflyThreshold: 3.0,
@@ -85,8 +84,7 @@ export const ENGINE_DEFAULTS = {
85
84
  renderTimeLimit: 30,
86
85
  renderMode: 0,
87
86
  enableAlphaShadows: false,
88
- tiles: 3,
89
- tilesHelper: false,
87
+ tilesHelper: true, // show OIDN denoise / AI upscale tile progress overlay
90
88
  showLightHelper: false,
91
89
 
92
90
  directionalLightIntensity: 0,
@@ -129,6 +127,9 @@ export const ENGINE_DEFAULTS = {
129
127
  asvgfPhiDepth: 1.0,
130
128
  asvgfVarianceBoost: 1.0,
131
129
  asvgfMaxAccumFrames: 32,
130
+ asvgfGradientStrength: 0.0,
131
+ asvgfGradientSigmaScale: 2.0,
132
+ asvgfGradientNoiseFloor: 0.0,
132
133
  asvgfDebugMode: 0,
133
134
  asvgfQualityPreset: 'medium',
134
135
  showAsvgfHeatmap: false,
@@ -152,6 +153,12 @@ export const ENGINE_DEFAULTS = {
152
153
  // only round-trip exactly when both sides agree.
153
154
  export const ALBEDO_EPS = 0.01;
154
155
 
156
+ // Per-resolution compute StorageTextures are pre-allocated at this size and never
157
+ // resized (works around three.js r184 StorageTexture-resize bugs — see TSL/patches
158
+ // history). Render resolution must not exceed this on either axis; the engine warns
159
+ // and ignores larger requests.
160
+ export const MAX_STORAGE_TEXTURE_SIZE = 2048;
161
+
155
162
  export const ASVGF_QUALITY_PRESETS = {
156
163
  // phiColor / phiDepth are RELATIVE tolerances (fractions). Bigger = more
157
164
  // permissive. gradientStrength = 0 keeps the adaptive-α boost off; the
@@ -191,6 +198,17 @@ export const ASVGF_QUALITY_PRESETS = {
191
198
  }
192
199
  };
193
200
 
201
+ // Adaptive variants — same SVGF quality as the base preset plus the temporal-
202
+ // gradient anti-lag enabled (gradientStrength > 0). The gradient measures real
203
+ // change in units of noise σ (gradientSigmaScale), so a static scene reads ~0
204
+ // and convergence is unaffected; only moving lights/anim/disocclusion drop
205
+ // history. Mutating the exported object is fine (const binding, mutable object);
206
+ // spreading lets each inherit its base. See ASVGF._buildGradientCompute.
207
+ const ADAPTIVE_GRADIENT = { gradientSigmaScale: 2.5, gradientNoiseFloor: 0.05 };
208
+ ASVGF_QUALITY_PRESETS.low_adaptive = { ...ASVGF_QUALITY_PRESETS.low, gradientStrength: 0.8, ...ADAPTIVE_GRADIENT };
209
+ ASVGF_QUALITY_PRESETS.medium_adaptive = { ...ASVGF_QUALITY_PRESETS.medium, gradientStrength: 1.0, ...ADAPTIVE_GRADIENT };
210
+ ASVGF_QUALITY_PRESETS.high_adaptive = { ...ASVGF_QUALITY_PRESETS.high, gradientStrength: 1.0, ...ADAPTIVE_GRADIENT };
211
+
194
212
  export const CAMERA_RANGES = {
195
213
  fov: {
196
214
  min: 10,
@@ -467,7 +485,7 @@ export const DEFAULT_TEXTURE_MATRIX = [ 0, 0, 1, 1, 0, 0, 0, 1 ];
467
485
  // 'production' — high-sample, deep bounces, OIDN enabled, controls disabled.
468
486
  export const PRODUCTION_RENDER_CONFIG = {
469
487
  maxSamples: 30, bounces: 20, transmissiveBounces: 8, maxSubsurfaceSteps: 64, samplesPerPixel: 1,
470
- renderMode: 1, enableAlphaShadows: true, tiles: 3, tilesHelper: true,
488
+ renderMode: 1, enableAlphaShadows: true,
471
489
  enableOIDN: true, oidnQuality: 'balance',
472
490
  interactionModeEnabled: false,
473
491
  };
@@ -477,7 +495,6 @@ export const INTERACTIVE_RENDER_CONFIG = {
477
495
  samplesPerPixel: ENGINE_DEFAULTS.samplesPerPixel, renderMode: ENGINE_DEFAULTS.renderMode, enableAlphaShadows: ENGINE_DEFAULTS.enableAlphaShadows,
478
496
  transmissiveBounces: ENGINE_DEFAULTS.transmissiveBounces,
479
497
  maxSubsurfaceSteps: ENGINE_DEFAULTS.maxSubsurfaceSteps,
480
- tiles: ENGINE_DEFAULTS.tiles, tilesHelper: ENGINE_DEFAULTS.tilesHelper,
481
498
  enableOIDN: false, oidnQuality: 'fast',
482
499
  interactionModeEnabled: true,
483
500
  };
@@ -11,14 +11,13 @@ import { MotionVector } from './Stages/MotionVector.js';
11
11
  import { ASVGF } from './Stages/ASVGF.js';
12
12
  import { Variance } from './Stages/Variance.js';
13
13
  import { BilateralFilter } from './Stages/BilateralFilter.js';
14
- import { AdaptiveSampling } from './Stages/AdaptiveSampling.js';
15
14
  import { EdgeFilter } from './Stages/EdgeFilter.js';
16
15
  import { AutoExposure } from './Stages/AutoExposure.js';
17
16
  import { SSRC } from './Stages/SSRC.js';
18
17
  import { Compositor } from './Stages/Compositor.js';
19
18
  import { RenderPipeline } from './Pipeline/RenderPipeline.js';
20
19
  import { CompletionTracker } from './Pipeline/CompletionTracker.js';
21
- import { ENGINE_DEFAULTS as DEFAULT_STATE, PRODUCTION_RENDER_CONFIG, INTERACTIVE_RENDER_CONFIG } from './EngineDefaults.js';
20
+ import { ENGINE_DEFAULTS as DEFAULT_STATE, PRODUCTION_RENDER_CONFIG, INTERACTIVE_RENDER_CONFIG, MAX_STORAGE_TEXTURE_SIZE } from './EngineDefaults.js';
22
21
  import { updateStats, updateLoading, resetLoading, setStatusCallback, getDisplaySamples, disposeObjectFromMemory } from './Processor/utils.js';
23
22
  import { BuildTimer } from './Processor/BuildTimer.js';
24
23
  import { InteractionManager } from './managers/InteractionManager.js';
@@ -310,7 +309,14 @@ export class PathTracerApp extends EventDispatcher {
310
309
 
311
310
  }
312
311
 
313
- updateStats( { timeElapsed: this.completion.timeElapsed, samples: getDisplaySamples( this.stages.pathTracer ) } );
312
+ this._ensureVRAMWiring();
313
+ const mem = this.stages.pathTracer?.vramTracker?.measure();
314
+ updateStats( {
315
+ timeElapsed: this.completion.timeElapsed,
316
+ samples: getDisplaySamples( this.stages.pathTracer ),
317
+ memoryUsed: mem?.current ?? 0,
318
+ memoryPeak: mem?.peak ?? 0,
319
+ } );
314
320
 
315
321
  // Check time limit
316
322
  if ( this.completion.isTimeLimitReached( this.settings.get( 'renderLimitMode' ), this.settings.get( 'renderTimeLimit' ) ) ) {
@@ -740,12 +746,9 @@ export class PathTracerApp extends EventDispatcher {
740
746
  this.stages.pathTracer.setupMaterial();
741
747
  timer.end( 'Material setup (TSL compile)' );
742
748
 
743
- // Front-load GPU pipeline creation so the first animate frame is snappy:
744
- // - compute: Three.js has no async compute compile one dispatch at
745
- // build time moves the stall to this loading moment.
746
- // - raster fallback: compileAsync yields to main thread (r184+).
749
+ // Front-load raster pipeline creation (compileAsync yields to main thread, r184+) so the first
750
+ // animate frame is snappy. Wavefront compute kernels compile lazily on their first dispatch.
747
751
  timer.start( 'Pipeline precompile' );
748
- this.stages.pathTracer.shaderBuilder.forceCompile( this.renderer );
749
752
  try {
750
753
 
751
754
  await this.renderer.compileAsync( this.meshScene, this.cameraManager.camera );
@@ -845,11 +848,31 @@ export class PathTracerApp extends EventDispatcher {
845
848
  // Resize
846
849
  // ═══════════════════════════════════════════════════════════════
847
850
 
851
+ /**
852
+ * Guard against render resolutions the compute pipeline can't support.
853
+ * Per-resolution StorageTextures are pre-allocated at MAX_STORAGE_TEXTURE_SIZE
854
+ * and never resized, so a larger request would overflow them. Warn and skip.
855
+ * @returns {boolean} true if the size is renderable
856
+ */
857
+ _isRenderSizeSupported( width, height ) {
858
+
859
+ if ( width > MAX_STORAGE_TEXTURE_SIZE || height > MAX_STORAGE_TEXTURE_SIZE ) {
860
+
861
+ console.warn( `[Rayzee] Render resolution ${width}×${height} exceeds the ${MAX_STORAGE_TEXTURE_SIZE}px limit (compute storage textures are pre-allocated at ${MAX_STORAGE_TEXTURE_SIZE}px). Ignoring resize — use a resolution ≤ ${MAX_STORAGE_TEXTURE_SIZE}.` );
862
+ return false;
863
+
864
+ }
865
+
866
+ return true;
867
+
868
+ }
869
+
848
870
  onResize() {
849
871
 
850
872
  const width = this.canvas.clientWidth;
851
873
  const height = this.canvas.clientHeight;
852
874
  if ( width === 0 || height === 0 ) return;
875
+ if ( ! this._isRenderSizeSupported( width, height ) ) return;
853
876
 
854
877
  this.renderer.setPixelRatio( 1.0 );
855
878
  this.renderer.setSize( width, height, false );
@@ -878,6 +901,8 @@ export class PathTracerApp extends EventDispatcher {
878
901
 
879
902
  _applyRenderResize( renderWidth, renderHeight ) {
880
903
 
904
+ if ( ! this._isRenderSizeSupported( renderWidth, renderHeight ) ) return;
905
+
881
906
  this.pipeline?.setSize( renderWidth, renderHeight );
882
907
  this.denoisingManager?.setRenderSize( renderWidth, renderHeight );
883
908
  this.needsReset = true;
@@ -889,6 +914,7 @@ export class PathTracerApp extends EventDispatcher {
889
914
  setCanvasSize( width, height ) {
890
915
 
891
916
  if ( width === 0 || height === 0 ) return;
917
+ if ( ! this._isRenderSizeSupported( width, height ) ) return;
892
918
 
893
919
  this.renderer.setPixelRatio( 1.0 );
894
920
  this.renderer.setSize( width, height, false );
@@ -927,15 +953,6 @@ export class PathTracerApp extends EventDispatcher {
927
953
 
928
954
  this.stages.pathTracer?.setUniform( 'renderMode', parseInt( config.renderMode ) );
929
955
  this.stages.pathTracer?.setUniform( 'enableAlphaShadows', config.enableAlphaShadows ?? false );
930
- this.stages.pathTracer?.tileManager?.setTileCount( config.tiles );
931
-
932
- const tileHelper = this.overlayManager?.getHelper( 'tiles' );
933
- if ( tileHelper ) {
934
-
935
- tileHelper.enabled = config.tilesHelper;
936
- if ( ! config.tilesHelper ) tileHelper.hide();
937
-
938
- }
939
956
 
940
957
  this.stages.pathTracer?.updateCompletionThreshold?.();
941
958
 
@@ -958,6 +975,20 @@ export class PathTracerApp extends EventDispatcher {
958
975
 
959
976
  this.needsReset = false;
960
977
  this.pauseRendering = false;
978
+
979
+ // Entering a final render starts a fresh peak window (Blender per-render semantics).
980
+ if ( isProduction ) {
981
+
982
+ const tracker = this.stages.pathTracer?.vramTracker;
983
+ if ( tracker ) {
984
+
985
+ tracker.measure();
986
+ tracker.resetPeak();
987
+
988
+ }
989
+
990
+ }
991
+
961
992
  this.reset();
962
993
 
963
994
  }
@@ -1067,6 +1098,69 @@ export class PathTracerApp extends EventDispatcher {
1067
1098
 
1068
1099
  }
1069
1100
 
1101
+ /** The path tracer's VRAM tracker, or null before stages are built. */
1102
+ get vram() {
1103
+
1104
+ return this.stages.pathTracer?.vramTracker ?? null;
1105
+
1106
+ }
1107
+
1108
+ /**
1109
+ * On-demand current/peak GPU memory snapshot.
1110
+ * @returns {{ current: number, peak: number, byCategory: Object }} bytes
1111
+ */
1112
+ getMemoryInfo() {
1113
+
1114
+ return this.stages.pathTracer?.vramTracker?.measure() ?? { current: 0, peak: 0, byCategory: {} };
1115
+
1116
+ }
1117
+
1118
+ // Idempotent: registers the cross-stage texture provider and re-measures on
1119
+ // allocation events (scene/env load, resize) so peak is caught even while idle.
1120
+ _ensureVRAMWiring() {
1121
+
1122
+ if ( this._vramWired ) return;
1123
+ const tracker = this.stages.pathTracer?.vramTracker;
1124
+ if ( ! tracker ) return; // stages not ready yet
1125
+
1126
+ tracker.register( 'stages', () => this._collectStageTextures() );
1127
+
1128
+ const remeasure = () => tracker.measure();
1129
+ this._addTrackedListener( this, 'SceneRebuild', remeasure );
1130
+ this._addTrackedListener( this, 'EnvironmentLoaded', remeasure );
1131
+ this._addTrackedListener( this, 'resolution_changed', remeasure );
1132
+
1133
+ this._vramWired = true;
1134
+
1135
+ }
1136
+
1137
+ // Direct StorageTexture/RenderTarget properties of every non-pathTracer stage
1138
+ // (denoiser/G-buffer/filter targets). The pathTracer's own buffers/textures are
1139
+ // registered explicitly; measure() dedupes by identity so overlaps don't double-count.
1140
+ _collectStageTextures() {
1141
+
1142
+ const out = [];
1143
+ const stages = this.stages || {};
1144
+ const pt = stages.pathTracer;
1145
+
1146
+ for ( const key in stages ) {
1147
+
1148
+ const stage = stages[ key ];
1149
+ if ( ! stage || stage === pt || typeof stage !== 'object' ) continue;
1150
+
1151
+ for ( const prop in stage ) {
1152
+
1153
+ const v = stage[ prop ];
1154
+ if ( v && ( v.isTexture || v.isRenderTarget ) ) out.push( v );
1155
+
1156
+ }
1157
+
1158
+ }
1159
+
1160
+ return out;
1161
+
1162
+ }
1163
+
1070
1164
  // ═══════════════════════════════════════════════════════════════
1071
1165
  // Materials (absorbed from MaterialsAPI)
1072
1166
  // ═══════════════════════════════════════════════════════════════
@@ -1207,6 +1301,7 @@ export class PathTracerApp extends EventDispatcher {
1207
1301
  maxBufferSize: adapterLimits.maxBufferSize,
1208
1302
  maxStorageBufferBindingSize: adapterLimits.maxStorageBufferBindingSize,
1209
1303
  maxColorAttachmentBytesPerSample: 128,
1304
+ maxStorageBuffersPerShaderStage: Math.min( adapterLimits.maxStorageBuffersPerShaderStage, 10 ),
1210
1305
  }
1211
1306
  } );
1212
1307
 
@@ -1265,7 +1360,6 @@ export class PathTracerApp extends EventDispatcher {
1265
1360
  this.pipeline.addStage( this.stages.asvgf );
1266
1361
  this.pipeline.addStage( this.stages.variance );
1267
1362
  this.pipeline.addStage( this.stages.bilateralFilter );
1268
- this.pipeline.addStage( this.stages.adaptiveSampling );
1269
1363
  this.pipeline.addStage( this.stages.edgeFilter );
1270
1364
  this.pipeline.addStage( this.stages.autoExposure );
1271
1365
  this.pipeline.addStage( this.stages.compositor );
@@ -1469,9 +1563,6 @@ export class PathTracerApp extends EventDispatcher {
1469
1563
 
1470
1564
  _createStages() {
1471
1565
 
1472
- const adaptiveSamplingMax = this.settings.get( 'adaptiveSamplingMax' );
1473
- const useAdaptiveSampling = this.settings.get( 'useAdaptiveSampling' );
1474
-
1475
1566
  this.stages.pathTracer = new PathTracer( this.renderer, this.scene, this.cameraManager.camera );
1476
1567
  this.stages.normalDepth = new NormalDepth( this.renderer, {
1477
1568
  pathTracer: this.stages.pathTracer
@@ -1483,10 +1574,6 @@ export class PathTracerApp extends EventDispatcher {
1483
1574
  this.stages.asvgf = new ASVGF( this.renderer, { enabled: false } );
1484
1575
  this.stages.variance = new Variance( this.renderer, { enabled: false } );
1485
1576
  this.stages.bilateralFilter = new BilateralFilter( this.renderer, { enabled: false } );
1486
- this.stages.adaptiveSampling = new AdaptiveSampling( this.renderer, {
1487
- adaptiveSamplingMax,
1488
- enabled: useAdaptiveSampling,
1489
- } );
1490
1577
  this.stages.edgeFilter = new EdgeFilter( this.renderer, { enabled: false } );
1491
1578
  this.stages.autoExposure = new AutoExposure( this.renderer, { enabled: DEFAULT_STATE.autoExposure ?? false } );
1492
1579
 
@@ -1505,10 +1592,11 @@ export class PathTracerApp extends EventDispatcher {
1505
1592
  camera: this.cameraManager.camera,
1506
1593
  stages: {
1507
1594
  pathTracer: this.stages.pathTracer,
1595
+ normalDepth: this.stages.normalDepth,
1596
+ motionVector: this.stages.motionVector,
1508
1597
  asvgf: this.stages.asvgf,
1509
1598
  variance: this.stages.variance,
1510
1599
  bilateralFilter: this.stages.bilateralFilter,
1511
- adaptiveSampling: this.stages.adaptiveSampling,
1512
1600
  edgeFilter: this.stages.edgeFilter,
1513
1601
  ssrc: this.stages.ssrc,
1514
1602
  autoExposure: this.stages.autoExposure,
@@ -1523,6 +1611,10 @@ export class PathTracerApp extends EventDispatcher {
1523
1611
  this.denoisingManager.setupDenoiser();
1524
1612
  this.denoisingManager.setupUpscaler();
1525
1613
 
1614
+ // Seed G-buffer gating: NormalDepth/MotionVector start enabled (stage default)
1615
+ // but are only needed by real-time denoisers — idle them until one is active.
1616
+ this.denoisingManager._syncGBufferStages();
1617
+
1526
1618
  // Set initial render resolution
1527
1619
  const initW = this.canvas.clientWidth || 1;
1528
1620
  const initH = this.canvas.clientHeight || 1;
@@ -42,7 +42,7 @@ export class PipelineContext {
42
42
  accumulatedFrames: 0,
43
43
 
44
44
  // Render modes
45
- renderMode: 0, // 0 = progressive, 1 = tiled
45
+ renderMode: 0, // 0 = interactive, 1 = production (full-frame in both)
46
46
  interactionMode: false,
47
47
  isComplete: false,
48
48
 
@@ -65,7 +65,6 @@ export class PipelineContext {
65
65
 
66
66
  // Feature flags
67
67
  enableASVGF: false,
68
- enableAdaptiveSampling: false,
69
68
  enableEdgeFiltering: false,
70
69
  // Can be extended by stages as needed
71
70
  };
@@ -22,7 +22,7 @@ import { EventDispatcher } from './EventDispatcher.js';
22
22
  * // Add stages in execution order
23
23
  * pipeline.addStage(new PathTracer(...));
24
24
  * pipeline.addStage(new ASVGF(...));
25
- * pipeline.addStage(new AdaptiveSampling(...));
25
+ * pipeline.addStage(new EdgeFilter(...));
26
26
  *
27
27
  * // Render all stages
28
28
  * pipeline.render(writeBuffer);
@@ -28,7 +28,7 @@ export const StageExecutionMode = {
28
28
 
29
29
  /**
30
30
  * CONDITIONAL - Stage decides execution via shouldExecute() method
31
- * Use for: Stages with complex execution logic (AdaptiveSampling)
31
+ * Use for: Stages with complex execution logic
32
32
  */
33
33
  CONDITIONAL: 'conditional'
34
34
  };
@@ -25,7 +25,6 @@ export class CameraOptimizer {
25
25
  this.interactionQualitySettings = {
26
26
  maxBounceCount: 1,
27
27
  numRaysPerPixel: 1,
28
- useAdaptiveSampling: false,
29
28
  useEnvMapIS: false,
30
29
  // pixelRatio: 0.25,
31
30
  enableAccumulation: false,
@@ -306,7 +305,6 @@ export class CameraOptimizer {
306
305
  'ultra-low': {
307
306
  maxBounceCount: 1,
308
307
  numRaysPerPixel: 1,
309
- useAdaptiveSampling: false,
310
308
  useEnvMapIS: false,
311
309
  pixelRatio: 0.125,
312
310
  enableAccumulation: false
@@ -314,7 +312,6 @@ export class CameraOptimizer {
314
312
  'low': {
315
313
  maxBounceCount: 1,
316
314
  numRaysPerPixel: 1,
317
- useAdaptiveSampling: false,
318
315
  useEnvMapIS: false,
319
316
  pixelRatio: 0.25,
320
317
  enableAccumulation: false
@@ -322,7 +319,6 @@ export class CameraOptimizer {
322
319
  'medium': {
323
320
  maxBounceCount: 2,
324
321
  numRaysPerPixel: 1,
325
- useAdaptiveSampling: false,
326
322
  useEnvMapIS: true,
327
323
  pixelRatio: 0.5,
328
324
  enableAccumulation: false
@@ -330,7 +326,6 @@ export class CameraOptimizer {
330
326
  'high': {
331
327
  maxBounceCount: 3,
332
328
  numRaysPerPixel: 1,
333
- useAdaptiveSampling: true,
334
329
  useEnvMapIS: true,
335
330
  pixelRatio: 0.75,
336
331
  enableAccumulation: true
@@ -536,6 +536,10 @@ export class GeometryExtractor {
536
536
  // triangle extraction that stores directly in texture format
537
537
  extractTrianglesInBatch( positions, normals, uvs, indices, triangleCount, materialIndex, meshIndex ) {
538
538
 
539
+ // Track per-material triangle count for sort-bin remap (item 41)
540
+ while ( this.materialTriangleCounts.length <= materialIndex ) this.materialTriangleCounts.push( 0 );
541
+ this.materialTriangleCounts[ materialIndex ] += triangleCount;
542
+
539
543
  // Pre-allocate objects for positions, normals, and UVs
540
544
  const posA = this._getVec3( 0 );
541
545
  const posB = this._getVec3( 1 );
@@ -779,6 +783,7 @@ export class GeometryExtractor {
779
783
 
780
784
  // Reset other arrays
781
785
  this.materials = [];
786
+ this.materialTriangleCounts = []; // Per-material triangle count (for sort-bin remap, item 41)
782
787
  this.meshes = [];
783
788
  this.meshTriangleRanges = []; // Per-mesh { start, count } for TLAS/BLAS
784
789
  this.maps = [];
@@ -816,6 +821,7 @@ export class GeometryExtractor {
816
821
  triangleData: this.getTriangleData(), // Texture-ready Float32Array format
817
822
  triangleCount: this.getTriangleCount(),
818
823
  materials: this.materials,
824
+ materialTriangleCounts: this.materialTriangleCounts,
819
825
  meshes: this.meshes,
820
826
  meshTriangleRanges: this.meshTriangleRanges, // Per-mesh { start, count } for TLAS/BLAS
821
827
  maps: this.maps,