rayzee 7.23.2 → 7.24.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.
package/README.md CHANGED
@@ -31,7 +31,9 @@ A real-time WebGPU path tracing engine built on Three.js. Framework-agnostic —
31
31
  - [engine.interactionManager](#engineinteractionmanager)
32
32
  - [engine.transformManager](#enginetransformmanager)
33
33
  - [Output Methods](#output-methods)
34
+ - [Render Resolution Reserve](#render-resolution-reserve)
34
35
  - [Memory Monitoring](#memory-monitoring)
36
+ - [Logging](#logging)
35
37
  - [Deterministic & Headless Rendering](#deterministic--headless-rendering)
36
38
  - [Events](#events)
37
39
  - [Advanced: Custom Pipeline Stages](#advanced-custom-pipeline-stages)
@@ -135,7 +137,7 @@ A single HTML file — no Node.js, no build step. Uses [ES module import maps](h
135
137
  "three/tsl": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.tsl.js",
136
138
  "three/webgpu": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.webgpu.js",
137
139
  "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/",
138
- "oidn-web": "https://cdn.jsdelivr.net/npm/oidn-web@0.3.5/dist/oidn.js",
140
+ "oidn-web": "https://cdn.jsdelivr.net/npm/oidn-web@0.4.0/dist/oidn.js",
139
141
  "rayzee": "https://cdn.jsdelivr.net/npm/rayzee/dist/rayzee.es.js"
140
142
  }
141
143
  }
@@ -594,6 +596,36 @@ URL.revokeObjectURL(url);
594
596
 
595
597
  ---
596
598
 
599
+ ### Render Resolution Reserve
600
+
601
+ Every compute `StorageTexture` and aux buffer is pre-allocated at one square dimension — the *reserve* — and `setCanvasSize()` refuses anything larger. The default is 2048, so 4K output needs the reserve raised first.
602
+
603
+ ```js
604
+ engine.setReservedRenderResolution(4096) // raise to 4K (longest edge)
605
+ engine.setReservedRenderResolution(2048, { allowLower: true }) // lower, paying a rebuild, to reclaim VRAM
606
+ engine.getReservedRenderResolution() // the reserve actually in force
607
+ ```
608
+
609
+ The request is **device-capped**: a 4096 reserve pins roughly 1.5 GB of MRT textures, so it is only granted on GPUs reporting ≥ 8 GB and a ≥ 1 GB `maxStorageBufferBindingSize`; weaker devices clamp to 2048. `MAX_RESERVABLE_RENDER_SIZE` (4096) is the ceiling on any request.
610
+
611
+ Raises are monotonic unless you pass `allowLower` — UI-driven callers ask for whatever the current view needs, and honouring every decrease made the reserve oscillate across preview↔render switches, paying a full kernel rebuild each time.
612
+
613
+ Callable at any point in the lifecycle:
614
+
615
+ - **Before `init()`** — recorded and applied during `init()`, after the device exists but before the stages are constructed, so they allocate at the raised size directly. The device gate cannot run without a device, so the return value here is the *request*, not the verdict.
616
+ - **After `init()`** — applied immediately, re-initialising the reserved GPU storage in place.
617
+
618
+ Either way the verdict arrives as a `reserved_render_size_changed` event (a plain string type, not an `EngineEvents` constant):
619
+
620
+ ```js
621
+ engine.addEventListener('reserved_render_size_changed', e => console.log('reserve:', e.size));
622
+ engine.setReservedRenderResolution(4096);
623
+ await engine.init();
624
+ console.log(engine.getReservedRenderResolution()); // 4096, or 2048 if the device declined
625
+ ```
626
+
627
+ ---
628
+
597
629
  ### Memory Monitoring
598
630
 
599
631
  Track GPU (VRAM) usage across the whole pipeline. Sizes are measured from live GPU resources (buffer `byteLength` + texture dimensions × format), so they are exact, not estimated.
@@ -612,6 +644,27 @@ The React app surfaces this as a `Memory: … | Peak: …` readout in the on-can
612
644
 
613
645
  ---
614
646
 
647
+ ### Logging
648
+
649
+ Leveled, namespaced console output, shared with the engine's Web Workers. The default level is `info`, which hides per-mesh and per-texture detail; drop to `debug` to see it.
650
+
651
+ ```js
652
+ import { Logger, createLogger, fmt, LOG_LEVELS } from 'rayzee';
653
+
654
+ Logger.setLevel('debug'); // 'silent' | 'error' | 'warn' | 'info' | 'debug'
655
+ Logger.getLevel();
656
+ Logger.isEnabled('debug'); // gate expensive message construction
657
+ Logger.only('bvh', 'gpu'); // restrict debug to these namespaces (implies setLevel('debug'))
658
+ Logger.only(); // clear the namespace filter
659
+ Logger.refresh(); // re-read the level from globals/localStorage
660
+ ```
661
+
662
+ The chosen level persists in `localStorage` under `rayzeeLogLevel` (namespace filter: `rayzeeLogNamespaces`), so it survives a reload. The engine does not install a global itself — expose one from your host if you want console access without an import; the demo app does `globalThis.rayzee = { log: Logger, ... }`, which is what makes `rayzee.log.setLevel('debug')` work there.
663
+
664
+ `createLogger(namespace)` returns a channel with `error` / `warn` / `info` / `debug` plus `summary(headline, details)`, which prints one `info` line with the detail lines folded into a collapsed group. `fmt` holds the formatting helpers those summaries use — `n`, `ms`, `mb`, `px`, `count`, `list`. `LOG_LEVELS` is the name→severity map.
665
+
666
+ ---
667
+
615
668
  ### Deterministic & Headless Rendering
616
669
 
617
670
  For offline rendering, regression testing, and benchmarking — drive accumulation yourself instead of the rAF loop, and get bit-reproducible output.
@@ -645,6 +698,19 @@ WebGPU timestamp queries are the only true GPU metric here — `pipeline.getStat
645
698
 
646
699
  `getKernelGPUTimings()` attributes each compute pass of the last resolved frame back to a wavefront kernel name. Durations are **summed per kernel across the frame**, so `extend` reports its whole per-frame cost over every bounce iteration, not one bounce. `unattributed` collects passes belonging to no registered kernel (other stages, denoisers), so `sum(kernels) + unattributed` reconciles with `total`.
647
700
 
701
+ Neither method can see the OIDN denoise — `oidn-web` submits on its own command encoders, outside
702
+ the stages three.js times. It carries its own profiler instead:
703
+
704
+ ```js
705
+ engine.profileNextDenoise(); // arms one capture; per-denoise, not sticky
706
+ const { profile, runtime } = await engine.getDenoiseProfile();
707
+ ```
708
+
709
+ `profile` is the per-layer GPU timing of that denoise; `runtime` reports the selected engine and
710
+ precision, the model, kernel capabilities, tile state and resource counts — which is the way to
711
+ confirm FP16 actually engaged on a given GPU rather than inferring it from the device's feature
712
+ list. Both need `timestamp-query`, and `getDenoiseProfile()` returns `null` when OIDN is not set up.
713
+
648
714
  ---
649
715
 
650
716
  ### Events
@@ -724,8 +790,12 @@ import {
724
790
  MEMORY_CONSTANTS,
725
791
  PRODUCTION_RENDER_CONFIG,
726
792
  INTERACTIVE_RENDER_CONFIG,
793
+ MAX_RESERVABLE_RENDER_SIZE,
727
794
  } from 'rayzee';
728
795
 
796
+ // Leveled/namespaced logging, shared with the workers
797
+ import { Logger, createLogger, fmt, LOG_LEVELS } from 'rayzee';
798
+
729
799
  // Asset URL / cache namespace overrides
730
800
  import { configureAssets, getAssetConfig } from 'rayzee';
731
801
 
@@ -767,7 +837,7 @@ import { setBindingAudit, getBindingAuditFindings, clearBindingAuditFindings } f
767
837
 
768
838
  | Package | Purpose | Install needed? |
769
839
  |---|---|---|
770
- | `oidn-web` | Intel Open Image Denoise for high-quality final renders | Yes — `npm install oidn-web` |
840
+ | `oidn-web` | Intel Open Image Denoise for high-quality final renders | Yes — `npm install oidn-web` (**>=0.4.0**) |
771
841
  | `onnxruntime-web` | AI-powered upscaling | No — loaded from CDN at runtime |
772
842
 
773
843
  > **Note:** `onnxruntime-web` is also listed in `package.json` under `optionalDependencies` for bundler compatibility, but the engine's own runtime path always fetches it from a CDN (see `ortRuntimeUrl` / `ortWasmPaths` in [Configuring Assets](#configuring-assets-cdn-urls--cache-namespace)) rather than importing the installed package — installing it locally has no effect unless you also override those URLs to point at your own copy.
@@ -787,7 +857,7 @@ OIDN provides high-quality AI denoising for final renders. It runs automatically
787
857
  ```js
788
858
  // After engine.init() completes
789
859
  engine.denoisingManager.setOIDNEnabled(true);
790
- engine.denoisingManager.setOIDNQuality('balance'); // 'fast' | 'balance' | 'high'
860
+ engine.denoisingManager.setOIDNQuality('balance'); // 'fast' | 'fast-clean' | 'balance' | 'high'
791
861
  ```
792
862
 
793
863
  3. **Listen for progress** (optional)
@@ -801,11 +871,27 @@ OIDN provides high-quality AI denoising for final renders. It runs automatically
801
871
  });
802
872
  ```
803
873
 
804
- | Quality | Model size | Speed | Best for |
874
+ | Quality | Weights | Aux guide | Best for |
805
875
  |---|---|---|---|
806
- | `'fast'` | ~20 MB | Fastest | Quick previews |
807
- | `'balance'` | ~50 MB | Moderate | General use (default) |
808
- | `'high'` | ~100 MB | Slowest | Final quality renders |
876
+ | `'fast'` | 0.6 MB | point-sampled | Low sample counts — the default |
877
+ | `'fast-clean'` | 0.6 MB | accumulated | Converged frames, at `'fast'`'s cost |
878
+ | `'balance'` | 1.8 MB | accumulated | General use |
879
+ | `'high'` | 7.3 MB | accumulated | Final renders — used by `configureForMode('production')` |
880
+
881
+ `'fast'` and `'fast-clean'` are the same network size and cost the same to run; they differ only in
882
+ which auxiliary guide their weights expect. That makes the ordering **not** a simple quality ladder:
883
+ at 1 spp the accumulated guide has one sample, so `'fast-clean'` is fed something it was not trained
884
+ for and measures materially worse than `'fast'` (on a transmission-heavy scene, more than double the
885
+ RMSE). Once the guide converges it wins by a few percent — but `'high'` beats it there anyway. Pick
886
+ `'fast'` for previews and `'high'` for output; `'fast-clean'` is for the narrow case of denoising a
887
+ converged frame on a budget.
888
+
889
+ Denoise cost scales with frame area, and the tile tracks the frame so that a frame fitting inside one
890
+ tile pays no overlap padding — at 1024x1024 that is roughly 1.8x faster than tiling it. A cap
891
+ (default 1024) bounds the one-time activation allocation, so larger frames tile and stay
892
+ memory-bounded. Raise or lower it with
893
+ `engine.denoisingManager.denoiser.updateConfiguration({ tileSize: 2048 })`; the effective tile is
894
+ `min( max( width, height ), tileSize )`.
809
895
 
810
896
  > **Note:** The neural network model is downloaded on first use. Subsequent runs use the browser cache. OIDN also works with `configureForMode('production')`, which enables it automatically alongside high-quality render settings.
811
897
 
@@ -851,21 +937,14 @@ Then load it via a script tag or import map instead:
851
937
  <script type="importmap">
852
938
  {
853
939
  "imports": {
854
- "oidn-web": "https://cdn.jsdelivr.net/npm/oidn-web@0.3.5/dist/oidn.js"
940
+ "oidn-web": "https://cdn.jsdelivr.net/npm/oidn-web@0.4.0/dist/oidn.js"
855
941
  }
856
942
  }
857
943
  </script>
858
944
  ```
859
945
 
860
- **OIDN: `TypeError: t.alea is not a function` or `Argument 'x' passed to 'conv2d' must be a Tensor ... got 'L'`**
861
- You're loading `oidn-web` via `jsdelivr.net/npm/oidn-web/+esm` or `esm.sh/oidn-web`. Don't — use the **self-bundled** `/dist/oidn.js` path instead:
862
-
863
- ```diff
864
- - "oidn-web": "https://cdn.jsdelivr.net/npm/oidn-web@0.3.5/+esm"
865
- + "oidn-web": "https://cdn.jsdelivr.net/npm/oidn-web@0.3.5/dist/oidn.js"
866
- ```
867
-
868
- Why: `oidn-web` transitively depends on `@tensorflow/tfjs-core`, which does `import * as t from "seedrandom"` and calls `t.alea(...)`. jsDelivr's `/+esm` CJS→ESM shim of `seedrandom` emits only `export default` (no named exports), so `t.alea` is undefined. Swapping to `esm.sh` trades that for a different issue: deep-path imports produce multiple tfjs-core instances, so a Tensor made in one module fails `instanceof` checks in another (`got 'L'`). The `/dist/oidn.js` file in the npm package is a single pre-bundled ESM with all of tfjs inlined — no external imports, one tfjs instance, same exports. Use it.
946
+ **OIDN from a CDN**
947
+ Load the self-bundled `/dist/oidn.js` path rather than `/+esm` or `esm.sh` — it is a single pre-bundled ESM with no external imports, and it is the path this engine is tested against.
869
948
 
870
949
  **Black screen / "WebGPU not supported"**
871
950
  Your browser may not support WebGPU. Use Chrome 113+, Edge 113+, Safari 18+, or Firefox 141+. Ensure you're on HTTPS or localhost.