rayzee 8.5.0 → 8.6.1
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 +60 -56
- package/dist/{DLSSNeural-BNSJ8V3z.js → NeuralRetouch-CYOJ21Wn.js} +33 -33
- package/dist/{DLSSSuperRes-BoEW89Cl.js → NeuralSuperRes-Dc6jRYke.js} +25 -25
- package/dist/ToneMapGPU-BVE852iN.js +533 -0
- package/dist/rayzee.es.js +27235 -9074
- package/dist/rayzee.umd.js +255 -187
- package/package.json +1 -1
- package/src/AssetConfig.js +21 -7
- package/src/Color/BakedViews.js +134 -0
- package/src/Color/BuiltinViews.js +261 -0
- package/src/Color/ColorManagement.js +1130 -0
- package/src/Color/ColorSpaces.js +432 -0
- package/src/Color/Displays.js +19 -0
- package/src/Color/InputColorSpaces.js +243 -0
- package/src/Color/LutBake.js +371 -0
- package/src/Color/OcioRuntime.js +338 -0
- package/src/Color/OcioViews.js +419 -0
- package/src/Color/ViewTransforms.js +323 -0
- package/src/Color/WorkingMatrix.js +86 -0
- package/src/EngineDefaults.js +55 -3
- package/src/EngineIssues.js +3 -0
- package/src/Passes/AIUpscaler.js +16 -7
- package/src/Passes/OIDNDenoiser.js +13 -5
- package/src/PathTracerApp.js +227 -7
- package/src/Processor/AssetLoader.js +1 -2
- package/src/Processor/EmissiveTriangleBuilder.js +34 -2
- package/src/Processor/GeometryExtractor.js +151 -138
- package/src/Processor/LightSerializer.js +8 -3
- package/src/Processor/MaterialPacking.js +168 -0
- package/src/Processor/PackedRayBuffer.js +6 -2
- package/src/Processor/ProceduralSky.js +4 -0
- package/src/Processor/SceneProcessor.js +25 -1
- package/src/Processor/SimpleSky.js +8 -0
- package/src/Processor/TextureCreator.js +137 -91
- package/src/Processor/TextureReadback.js +92 -0
- package/src/Processor/ToneMapCPU.js +80 -165
- package/src/Processor/ToneMapGPU.js +122 -151
- package/src/RenderSettings.js +1 -0
- package/src/Stages/Compositor.js +11 -2
- package/src/Stages/PathTracer.js +2 -0
- package/src/Stages/PathTracerStage.js +1 -1
- package/src/TSL/Common.js +15 -0
- package/src/TSL/Debugger.js +2 -3
- package/src/TSL/EmissiveSampling.js +6 -8
- package/src/TSL/ExtendKernel.js +9 -0
- package/src/TSL/Fresnel.js +23 -1
- package/src/TSL/HitFacet.js +102 -0
- package/src/TSL/LightsDirect.js +12 -33
- package/src/TSL/LightsSampling.js +15 -7
- package/src/TSL/MaterialEvaluation.js +26 -19
- package/src/TSL/MaterialProperties.js +74 -37
- package/src/TSL/MaterialTransmission.js +2 -3
- package/src/TSL/ShadeKernel.js +34 -21
- package/src/TSL/ShadowTerminator.js +125 -0
- package/src/TSL/Struct.js +9 -0
- package/src/TSL/Subsurface.js +2 -3
- package/src/index.js +32 -4
- package/src/managers/DenoisingManager.js +60 -44
- package/src/managers/EnvironmentManager.js +20 -0
- package/src/managers/MaterialDataManager.js +118 -208
- package/src/managers/UniformManager.js +1 -0
- package/src/{dlss/DLSSNeural.js → neural/NeuralRetouch.js} +42 -41
- package/src/{dlss/DLSSSuperRes.js → neural/NeuralSuperRes.js} +27 -26
- package/dist/ToneMapGPU-DzqSyzTx.js +0 -341
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ A real-time WebGPU path tracing engine built on Three.js. Framework-agnostic —
|
|
|
26
26
|
- [engine.lightManager](#enginelightmanager)
|
|
27
27
|
- [engine.animationManager](#engineanimationmanager)
|
|
28
28
|
- [Materials](#materials)
|
|
29
|
+
- [Colour Management](#colour-management)
|
|
29
30
|
- [engine.environmentManager](#engineenvironmentmanager)
|
|
30
31
|
- [engine.denoisingManager](#enginedenoisingmanager)
|
|
31
32
|
- [engine.interactionManager](#engineinteractionmanager)
|
|
@@ -287,10 +288,10 @@ configureAssets({
|
|
|
287
288
|
oidnWeightsBaseUrl: '/oidn-tzas/',
|
|
288
289
|
upscalerModelBaseUrl: '/upscaler-onnx/',
|
|
289
290
|
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
|
|
293
|
-
|
|
291
|
+
// OpenColorIO runtime (~6 MB of WebAssembly) — the engine never names the package, so a host
|
|
292
|
+
// that loads colour configs supplies it, bundled or served. Unset, colour management stays inert.
|
|
293
|
+
ocioRuntimeFactory: () => import('@bb-studio/ocio'), // or: ocioRuntimeUrl: '/vendor/ocio/index.js'
|
|
294
|
+
ocioWasmUrl: '/vendor/ocio/ocio-wasm.wasm', // optional override for the .wasm
|
|
294
295
|
|
|
295
296
|
// Prefix for engine-managed IndexedDB stores. Set to a unique value if multiple
|
|
296
297
|
// apps embed the engine on the same origin to avoid cache collisions.
|
|
@@ -414,13 +415,14 @@ Key settings:
|
|
|
414
415
|
| `maxBounces` | `number` | 3 | Max ray bounce depth |
|
|
415
416
|
| `maxSamples` | `number` | 60 | Max accumulated samples before stopping |
|
|
416
417
|
| `exposure` | `number` | 1.0 | Exposure value |
|
|
417
|
-
| `saturation` | `number` | 1.
|
|
418
|
+
| `saturation` | `number` | 1.0 | Color saturation (1 = no grade) |
|
|
418
419
|
| `enableEnvironment` | `boolean` | true | Use environment lighting |
|
|
419
420
|
| `environmentIntensity` | `number` | 1.0 | Environment light strength |
|
|
420
|
-
| `environmentRotation` | `number` |
|
|
421
|
+
| `environmentRotation` | `number` | 0 | Environment Y-rotation (degrees); 0 shows the HDRI as authored, as Blender's unmapped world does |
|
|
421
422
|
| `showBackground` | `boolean` | true | Show the environment as a visible backdrop for camera-miss rays (vs. a solid/transparent background) |
|
|
422
423
|
| `samplingTechnique` | `number` | 2 | Sampler: `0` PCG, `1` scrambled Halton, `2` Owen-scrambled Sobol |
|
|
423
424
|
| `fireflyThreshold` | `number` | 3.0 | Firefly clamping threshold |
|
|
425
|
+
| `shadowTerminatorOffset` | `number` | 0.1 | Cycles' Shadow Terminator → Geometry Offset: near the light's terminator on a smooth-shaded low-poly mesh, light and environment shadow rays start on the smooth surface the vertex normals describe, not the flat facet. Blender's default; `0` disables |
|
|
424
426
|
| `transmissiveBounces` | `number` | 5 | Max bounces for transmissive materials |
|
|
425
427
|
| `maxSubsurfaceSteps` | `number` | 8 | Max random-walk steps for subsurface scattering (raised to 64 by `configureForMode('production')`) |
|
|
426
428
|
| `enableAlphaShadows` | `boolean` | false | Alpha-tested shadow rays (enabled by `configureForMode('production')`) |
|
|
@@ -429,7 +431,8 @@ Key settings:
|
|
|
429
431
|
| `aperture` | `number` | 5.6 | DOF aperture (f-stop) |
|
|
430
432
|
| `focalLength` | `number` | 50 | DOF focal length (mm) |
|
|
431
433
|
| `transparentBackground` | `boolean` | false | Transparent canvas background |
|
|
432
|
-
| `interactionModeEnabled` | `boolean` | true |
|
|
434
|
+
| `interactionModeEnabled` | `boolean` | true | Render at lower resolution while the camera moves, keeping the full bounce budget ("Fast Navigation" in the app) |
|
|
435
|
+
| `interactionRenderScale` | `number` | 0.5 | Per-axis render scale while the camera moves (0.5 = a quarter of the pixels); `1` turns the drop off. Ignored while OIDN is the live denoiser |
|
|
433
436
|
| `renderMode` | `number` | 0 | Internal preview(0)/production(1) flag driving accumulation & ASVGF behavior — normally set via `configureForMode()`, not written directly |
|
|
434
437
|
| `visMode` | `number` | 0 | Debug visualization mode (0 = off) |
|
|
435
438
|
| `environmentMode` | `string` | 'hdri' | Sky mode: `'hdri'` \| `'procedural'` \| `'gradient'` \| `'color'` — not routed through `engine.settings`; use `engine.environmentManager.setMode()` instead |
|
|
@@ -445,7 +448,7 @@ Key settings:
|
|
|
445
448
|
| `pixelFreezeThreshold` | `number` | 0.02 | Relative-error threshold for a pixel to become a freeze candidate |
|
|
446
449
|
| `pixelFreezeStability` | `number` | 8 | Consecutive candidate frames required before a pixel freezes |
|
|
447
450
|
|
|
448
|
-
See `ENGINE_DEFAULTS` for the full list with default values.
|
|
451
|
+
See `ENGINE_DEFAULTS` for the full list with default values. The default look is AgX (`toneMapping: 6`) at neutral saturation; tone mapping is chosen through [Colour Management](#colour-management) (`engine.color.setActiveView( id )`), not `settings`.
|
|
449
452
|
|
|
450
453
|
#### Rendering Modes
|
|
451
454
|
|
|
@@ -552,8 +555,43 @@ engine.updateAllMeshVisibility() // re-sync after manual object
|
|
|
552
555
|
|
|
553
556
|
// Read access to the active scene (returns the mesh-bearing scene)
|
|
554
557
|
engine.getScene()
|
|
558
|
+
|
|
559
|
+
// Where a packed value came from: 'material' | 'mapped' | 'default' | 'host'
|
|
560
|
+
engine.getMaterialPropertySource(index, 'ior')
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
A property a three.js material lacks falls back to `MATERIAL_DEFAULTS` (exported) — MeshPhysicalMaterial's own values, so a glTF metallic material gets IOR 1.5, not a guess derived from its metalness. Weights and roughnesses (metalness, roughness, transmission, opacity, clearcoat, sheen, iridescence, …) are clamped to [0, 1] on upload and on `setMaterialProperty`.
|
|
564
|
+
|
|
565
|
+
### Colour Management
|
|
566
|
+
|
|
567
|
+
`engine.color` is an OpenColorIO pipeline: what textures and lights mean, what the render happens in, and what it is shown and saved as. **It is inert until a config is loaded** — the render stays linear Rec.709 and the view transforms are three.js's own seven, so a host that never loads one sees no change. The host supplies the runtime (`ocioRuntimeFactory` or `ocioRuntimeUrl` in [`configureAssets`](#configuring-assets-cdn-urls--cache-namespace)).
|
|
568
|
+
|
|
569
|
+
```js
|
|
570
|
+
configureAssets({ ocioRuntimeFactory: () => import('@bb-studio/ocio') });
|
|
571
|
+
|
|
572
|
+
await engine.loadColorConfig({ builtin: 'ocio://cg-config-v4.0.0_aces-v2.0_ocio-v2.5' }); // a runtime built-in
|
|
573
|
+
await engine.loadColorConfig({ files, configPath: 'config.ocio', id: 'studio' }); // or a folder: [{ relativePath, data }]
|
|
574
|
+
|
|
575
|
+
engine.color.setView({ display: 'sRGB - Display', view: 'ACES 2.0 - SDR 100 nits (Rec.709)', look: null });
|
|
576
|
+
engine.color.setLook(look); // a look from status().config.looks, on the active view
|
|
577
|
+
engine.color.setActiveView(id); // any registered view, OCIO or built-in (three.js constant)
|
|
578
|
+
engine.color.setContext({ SHOT: '010' }); // $SHOT in the config resolves to this
|
|
579
|
+
engine.color.status(); // config, working space, active view, registered views
|
|
580
|
+
|
|
581
|
+
engine.color.setWorkingSpace('ACEScg'); // render in the config's space…
|
|
582
|
+
await engine.applyColorWorkingSpace(); // …which rebuilds textures, materials and the environment
|
|
583
|
+
|
|
584
|
+
await engine.setTextureColorSpace(texture, 'srgb'); // null (auto), 'srgb', 'linear' or a config space
|
|
585
|
+
const { data } = await engine.renderToBuffer({ colorSpace: 'ACES2065-1', source: 'display' }); // float delivery buffer
|
|
586
|
+
await engine.unloadColorConfig();
|
|
555
587
|
```
|
|
556
588
|
|
|
589
|
+
- **Load and unload through `engine.loadColorConfig()` / `unloadColorConfig()`** once a scene exists: they undo an adopted working space while the config that converted the environment is still loaded.
|
|
590
|
+
- A view is baked to a log2 shaper and a 65³ table, interpolated tetrahedrally; the same table drives the canvas, the GPU and CPU readbacks and the menu (`listViewTransforms()`, `onRegistryChange()`). An OCIO view returns display-encoded colour, so the engine switches the output pass to linear while one is active.
|
|
591
|
+
- **Baked views.** `await engine.color.saveBakedView(id)` writes a view's table to a file (157 KB gzip for 65³), and `await engine.color.loadBakedView(bytes, { expect })` registers it with no runtime and no config — show the look on the first frame, load the config later. When that config loads, a baked view whose files match the fingerprint it was baked from is kept as is: no rebake, no new id, no restart.
|
|
592
|
+
- `renderToBuffer({ colorSpace })` takes `'srgb'` (display bytes through the active view), `'linear'` (the working-space accumulation) or a config colour space; `source: 'display'` reads what the viewport shows, denoised, instead of the raw accumulation.
|
|
593
|
+
- Degradations (a view that cannot bake, a display the canvas cannot show) are recorded as warnings in the [degradation contract](#degradation-contract).
|
|
594
|
+
|
|
557
595
|
### engine.environmentManager
|
|
558
596
|
|
|
559
597
|
Environment maps, sky modes, and procedural generation.
|
|
@@ -593,10 +631,8 @@ engine.denoisingManager.setOIDNQuality('high')
|
|
|
593
631
|
engine.denoisingManager.setStrategy('oidn') // OIDN owns the live view; see below
|
|
594
632
|
engine.denoisingManager.continuousDenoiseInterval = 250 // cap refreshes at 4/sec (default 8 = uncapped)
|
|
595
633
|
engine.denoisingManager.setUpscalerEnabled(true)
|
|
596
|
-
engine.denoisingManager.
|
|
597
|
-
engine.denoisingManager.setUpscalerScaleFactor(2) // ESRGAN only; DLSS is fixed at 2x
|
|
634
|
+
engine.denoisingManager.setUpscalerScaleFactor(2) // 2 or 4
|
|
598
635
|
engine.denoisingManager.setUpscalerQuality('high') // ESRGAN only
|
|
599
|
-
engine.denoisingManager.setNeuralRendering(true, { intensity: 1, colorStrength: 0 })
|
|
600
636
|
```
|
|
601
637
|
|
|
602
638
|
### engine.interactionManager
|
|
@@ -954,6 +990,18 @@ import {
|
|
|
954
990
|
PRODUCTION_RENDER_CONFIG,
|
|
955
991
|
INTERACTIVE_RENDER_CONFIG,
|
|
956
992
|
MAX_RESERVABLE_RENDER_SIZE,
|
|
993
|
+
RENDER_PROFILES,
|
|
994
|
+
MATERIAL_DEFAULTS,
|
|
995
|
+
} from 'rayzee';
|
|
996
|
+
|
|
997
|
+
// Colour management — engine.color is the instance a host normally uses; these build UI against
|
|
998
|
+
// it, or reach the view-transform registry without an app
|
|
999
|
+
import {
|
|
1000
|
+
ColorManagement, getActiveColorManagement, isColorManaged, DEFAULT_WORKING_SPACE,
|
|
1001
|
+
listViewTransforms, getViewTransform, addViewTransform, removeViewTransform, onRegistryChange,
|
|
1002
|
+
buildOcioView, addOcioView, addAllOcioViews,
|
|
1003
|
+
convertColor, convertPixelsF32, extractMatrix, hasColorSpace,
|
|
1004
|
+
displayCanvasFit,
|
|
957
1005
|
} from 'rayzee';
|
|
958
1006
|
|
|
959
1007
|
// Leveled/namespaced logging, shared with the workers
|
|
@@ -1017,6 +1065,7 @@ import { setBindingAudit, getBindingAuditFindings, clearBindingAuditFindings } f
|
|
|
1017
1065
|
|---|---|---|
|
|
1018
1066
|
| `oidn-web` | Intel Open Image Denoise for high-quality final renders | Yes — `npm install oidn-web` (**>=0.4.0**) |
|
|
1019
1067
|
| `onnxruntime-web` | AI-powered upscaling | No — loaded from CDN at runtime |
|
|
1068
|
+
| `@bb-studio/ocio` | OpenColorIO runtime for [Colour Management](#colour-management) (~6 MB WebAssembly) | Only to load colour configs — `npm install @bb-studio/ocio`, then pass it as `ocioRuntimeFactory` |
|
|
1020
1069
|
|
|
1021
1070
|
> **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.
|
|
1022
1071
|
|
|
@@ -1204,51 +1253,6 @@ engine.addEventListener(EngineEvents.UPSCALING_END, () => console.log('Upsc
|
|
|
1204
1253
|
|
|
1205
1254
|
**Chaining with OIDN:** Upscaling and OIDN **can** run together — on render completion, OIDN runs first, then its denoised output is fed into the upscaler. Enable both; no manual coordination required.
|
|
1206
1255
|
|
|
1207
|
-
### The DLSS passes
|
|
1208
|
-
|
|
1209
|
-
Two further neural passes, ported from NVIDIA's DLSS weights. Both run once when a render finishes
|
|
1210
|
-
and both need a denoised frame — on raw Monte-Carlo noise they measurably lose to a plain resize, so
|
|
1211
|
-
enable OIDN alongside them.
|
|
1212
|
-
|
|
1213
|
-
Unlike the ONNX upscaler these are **not fetched from a CDN**: `dlss-runtime.js` installs
|
|
1214
|
-
`globalThis.DLSSRuntime` as a side effect and cannot be imported, so the host serves it and points
|
|
1215
|
-
`configureAssets` at it (see above). It lives at `app/public/dlss/` in this repo, and the local edits
|
|
1216
|
-
it carries are written up in `app/public/dlss/PATCHES.md` — re-vendoring it without re-applying them
|
|
1217
|
-
breaks the retouch pass with an explanatory throw.
|
|
1218
|
-
|
|
1219
|
-
```js
|
|
1220
|
-
// Super resolution, as a second AI Upscaler backend
|
|
1221
|
-
engine.denoisingManager.setUpscalerEnabled(true);
|
|
1222
|
-
engine.denoisingManager.setUpscalerBackend('dlss');
|
|
1223
|
-
|
|
1224
|
-
// The retouch pass — appearance, not resolution. Independent of the upscaler.
|
|
1225
|
-
engine.denoisingManager.setNeuralRendering(true, {
|
|
1226
|
-
intensity: 1, // 0..1 how much of the pass reaches the image
|
|
1227
|
-
localTone: 1, // 0..2 local light shaping; 1 is neutral
|
|
1228
|
-
localStructure: 1, // 0..2 fine surface detail; 1 is neutral
|
|
1229
|
-
colorStrength: 0, // 0..1 0 keeps the render's chroma, 1 takes the model's
|
|
1230
|
-
});
|
|
1231
|
-
```
|
|
1232
|
-
|
|
1233
|
-
The chain is **denoise → retouch (at render size) → super resolution (2x) → tone map**, scene-referred
|
|
1234
|
-
throughout. The retouch pass runs *before* the upscale deliberately: it sees a quarter of the pixels
|
|
1235
|
-
there, and above `DLSS_NR_MAX_PIXELS` (4.19 MP) it takes minutes and then loses every GPU device in
|
|
1236
|
-
the page. `DenoisingManager` skips it above that size rather than letting it run.
|
|
1237
|
-
|
|
1238
|
-
| | Super resolution | Retouch |
|
|
1239
|
-
|---|---|---|
|
|
1240
|
-
| What it changes | Resolution, fixed 2x | Appearance only |
|
|
1241
|
-
| Weights | 3.2 MB | 141 MB, fetched on first use |
|
|
1242
|
-
| Cost (1.9M-tri interior, warm) | 260 ms at 1024²→2048², 947 ms at 2048²→4096² | ~6 s at 2048² |
|
|
1243
|
-
| Against a native render | Ties Real-ESRGAN on accuracy, 3x faster, sharper than native | Close to a no-op on path-traced images |
|
|
1244
|
-
|
|
1245
|
-
Exported for building UI against: `SR_SCALE`, `SR_MAX_INPUT`, `DLSS_NR_DEFAULTS`, `DLSS_NR_RANGES`,
|
|
1246
|
-
`DLSS_NR_MAX_PIXELS`, and `renderUpscaled( app, { outputWidth, outputHeight, samples } )` — the
|
|
1247
|
-
offline entry point that drives a half-size render, denoise and upscale in one call.
|
|
1248
|
-
|
|
1249
|
-
⚠️ Each model brings **its own `GPUDevice`**, built by the runtime and not shareable. With both on,
|
|
1250
|
-
the page holds three: the renderer's and one each.
|
|
1251
|
-
|
|
1252
1256
|
## Troubleshooting
|
|
1253
1257
|
|
|
1254
1258
|
**OIDN: `Cannot find module './tza'` (webpack)**
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { t as e } from "./rolldown-runtime-CAFD8bLK.js";
|
|
2
|
-
import {
|
|
3
|
-
//#region src/
|
|
2
|
+
import { S as t, t as n, y as r } from "./ToneMapGPU-BVE852iN.js";
|
|
3
|
+
//#region src/neural/NeuralRetouch.js
|
|
4
4
|
var i = /* @__PURE__ */ e({
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
NeuralRetouch: () => m,
|
|
6
|
+
RETOUCH_COLOR_STRENGTH: () => 0,
|
|
7
|
+
RETOUCH_DEFAULTS: () => a,
|
|
8
|
+
RETOUCH_MAX_PIXELS: () => c,
|
|
9
|
+
RETOUCH_RANGES: () => l,
|
|
10
10
|
enhanceFrame: () => g
|
|
11
11
|
}), a = Object.freeze({
|
|
12
12
|
enabled: !0,
|
|
@@ -29,7 +29,7 @@ function o(e = {}) {
|
|
|
29
29
|
colorStrength: t
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
var s = t("
|
|
32
|
+
var s = t("neural"), c = 2048 * 2048, l = Object.freeze({
|
|
33
33
|
intensity: [0, 1],
|
|
34
34
|
localTone: [0, 2],
|
|
35
35
|
localStructure: [0, 2],
|
|
@@ -39,16 +39,16 @@ var s = t("dlss"), c = 2048 * 2048, l = Object.freeze({
|
|
|
39
39
|
}), u = null;
|
|
40
40
|
function d() {
|
|
41
41
|
if (u) return u;
|
|
42
|
-
let {
|
|
43
|
-
return globalThis.
|
|
44
|
-
if (globalThis.
|
|
42
|
+
let { neuralRuntimeUrl: e, neuralAssetBaseUrl: t } = r();
|
|
43
|
+
return t ? (globalThis.__NEURAL_ASSET_BASE__ = t, u = new Promise((t, n) => {
|
|
44
|
+
if (globalThis.NeuralRuntime?.getSrNrChain) return t(globalThis.NeuralRuntime.getSrNrChain());
|
|
45
45
|
let r = document.createElement("script");
|
|
46
46
|
r.src = e, r.onload = () => {
|
|
47
|
-
let e = globalThis.
|
|
48
|
-
if (!e) return n(/* @__PURE__ */ Error("
|
|
47
|
+
let e = globalThis.NeuralRuntime?.getSrNrChain;
|
|
48
|
+
if (!e) return n(/* @__PURE__ */ Error("Neural runtime loaded but exposed no retouch entry"));
|
|
49
49
|
t(e());
|
|
50
|
-
}, r.onerror = () => n(/* @__PURE__ */ Error(`Failed to load the
|
|
51
|
-
}), u;
|
|
50
|
+
}, r.onerror = () => n(/* @__PURE__ */ Error(`Failed to load the neural runtime from ${e}`)), document.head.appendChild(r);
|
|
51
|
+
}), u) : Promise.reject(/* @__PURE__ */ Error("No neural model location: set neuralAssetBaseUrl with configureAssets()"));
|
|
52
52
|
}
|
|
53
53
|
var f = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1) var<storage, read_write> dst: array<u32>;\n\n@compute @workgroup_size(8, 8)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let size = textureDimensions(src);\n if ( gid.x >= size.x || gid.y >= size.y ) { return; }\n let c = textureLoad(src, vec2<i32>(gid.xy), 0);\n let r = u32(clamp(c.r, 0.0, 1.0) * 255.0 + 0.5);\n let g = u32(clamp(c.g, 0.0, 1.0) * 255.0 + 0.5);\n let b = u32(clamp(c.b, 0.0, 1.0) * 255.0 + 0.5);\n dst[gid.y * size.x + gid.x] = r | (g << 8u) | (b << 16u) | (255u << 24u);\n}\n", p = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1) var<storage, read_write> dst: array<u32>;\n\n@compute @workgroup_size(8, 8)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let size = textureDimensions(src);\n if ( gid.x >= size.x || gid.y >= size.y ) { return; }\n let c = textureLoad(src, vec2<i32>(gid.xy), 0);\n let a = (gid.y * size.x + gid.x) * 2u;\n dst[a] = pack2x16float(c.rg);\n dst[a + 1u] = pack2x16float(vec2<f32>(c.b, 1.0));\n}\n", m = class e {
|
|
54
54
|
constructor(e, t) {
|
|
@@ -64,8 +64,8 @@ var f = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1
|
|
|
64
64
|
return this.chain.pipeline.geometry.validHeight;
|
|
65
65
|
}
|
|
66
66
|
static async create({ width: t, height: n, canvas: r = null, settings: i = {}, onProgress: a = () => {} }) {
|
|
67
|
-
if (!Number.isInteger(t) || !Number.isInteger(n) || t < 1 || n < 1) throw Error("
|
|
68
|
-
if (t * n > 4194304) throw Error(`
|
|
67
|
+
if (!Number.isInteger(t) || !Number.isInteger(n) || t < 1 || n < 1) throw Error("NeuralRetouch.create: width and height must be positive integers");
|
|
68
|
+
if (t * n > 4194304) throw Error(`NeuralRetouch.create: ${t}x${n} is ${(t * n / 1e6).toFixed(1)} MP, above the ${(c / 1e6).toFixed(1)} MP this pass survives. Past it the run takes minutes and then loses every GPU device in the page.`);
|
|
69
69
|
let s = r ?? document.createElement("canvas");
|
|
70
70
|
r || (s.style.cssText = "position:fixed;left:-10000px;top:0;pointer-events:none");
|
|
71
71
|
let l = new e(await (await d()).create(s, t, n, a, o(i).runtime), s);
|
|
@@ -74,21 +74,21 @@ var f = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1
|
|
|
74
74
|
_allocate() {
|
|
75
75
|
let { validWidth: e, validHeight: t } = this.chain.pipeline.geometry;
|
|
76
76
|
this.source = this.device.createBuffer({
|
|
77
|
-
label: "rayzee:
|
|
77
|
+
label: "rayzee:retouch-source",
|
|
78
78
|
size: e * t * 8,
|
|
79
79
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
80
80
|
}), this.staging = new Uint16Array(e * t * 4);
|
|
81
81
|
let n = e * t * 4;
|
|
82
82
|
this._readbackStorage = this.device.createBuffer({
|
|
83
|
-
label: "rayzee:
|
|
83
|
+
label: "rayzee:retouch-readback",
|
|
84
84
|
size: n,
|
|
85
85
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
|
|
86
86
|
}), this._readbackMap = this.device.createBuffer({
|
|
87
|
-
label: "rayzee:
|
|
87
|
+
label: "rayzee:retouch-readback-map",
|
|
88
88
|
size: n,
|
|
89
89
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
90
90
|
}), this._readbackPipeline = this.device.createComputePipeline({
|
|
91
|
-
label: "rayzee:
|
|
91
|
+
label: "rayzee:retouch-readback",
|
|
92
92
|
layout: "auto",
|
|
93
93
|
compute: {
|
|
94
94
|
module: this.device.createShaderModule({ code: f }),
|
|
@@ -97,15 +97,15 @@ var f = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1
|
|
|
97
97
|
});
|
|
98
98
|
let r = e * t * 8;
|
|
99
99
|
this._hdrStorage = this.device.createBuffer({
|
|
100
|
-
label: "rayzee:
|
|
100
|
+
label: "rayzee:retouch-hdr",
|
|
101
101
|
size: r,
|
|
102
102
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
|
|
103
103
|
}), this._hdrMap = this.device.createBuffer({
|
|
104
|
-
label: "rayzee:
|
|
104
|
+
label: "rayzee:retouch-hdr-map",
|
|
105
105
|
size: r,
|
|
106
106
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
107
107
|
}), this._hdrPipeline = this.device.createComputePipeline({
|
|
108
|
-
label: "rayzee:
|
|
108
|
+
label: "rayzee:retouch-hdr",
|
|
109
109
|
layout: "auto",
|
|
110
110
|
compute: {
|
|
111
111
|
module: this.device.createShaderModule({ code: p }),
|
|
@@ -115,7 +115,7 @@ var f = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1
|
|
|
115
115
|
}
|
|
116
116
|
async _packHDR(e, t, n = !1) {
|
|
117
117
|
let r = this.chain.pipeline.slots[this.slot]?.hdrOutput;
|
|
118
|
-
if (!r) throw Error("
|
|
118
|
+
if (!r) throw Error("NeuralRetouch: the runtime has no scene-referred output. Either no frame has been produced yet, or neural-runtime.js was re-vendored without its `rayzee-patch` edits.");
|
|
119
119
|
let i = this.device.createBindGroup({
|
|
120
120
|
layout: this._hdrPipeline.getBindGroupLayout(0),
|
|
121
121
|
entries: [{
|
|
@@ -125,7 +125,7 @@ var f = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1
|
|
|
125
125
|
binding: 1,
|
|
126
126
|
resource: { buffer: this._hdrStorage }
|
|
127
127
|
}]
|
|
128
|
-
}), a = this.device.createCommandEncoder({ label: "rayzee:
|
|
128
|
+
}), a = this.device.createCommandEncoder({ label: "rayzee:retouch-hdr" }), o = a.beginComputePass();
|
|
129
129
|
o.setPipeline(this._hdrPipeline), o.setBindGroup(0, i), o.dispatchWorkgroups(Math.ceil(e / 8), Math.ceil(t / 8)), o.end(), n && a.copyBufferToBuffer(this._hdrStorage, 0, this._hdrMap, 0, e * t * 8), this.device.queue.submit([a.finish()]);
|
|
130
130
|
}
|
|
131
131
|
async readOutputHDR() {
|
|
@@ -139,20 +139,20 @@ var f = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1
|
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
141
|
async readOutputToned(e) {
|
|
142
|
-
let { validWidth: t, validHeight:
|
|
143
|
-
return await this._packHDR(t,
|
|
142
|
+
let { validWidth: t, validHeight: r } = this.chain.pipeline.geometry;
|
|
143
|
+
return await this._packHDR(t, r), this._toneMapper ??= new n(this.device, "rayzee:retouch-tonemap"), this._toneMapper.ensureSize(t, r), {
|
|
144
144
|
rgba8: await this._toneMapper.toRGBA8(this._hdrStorage, {
|
|
145
145
|
exposure: e.exposure ?? 1,
|
|
146
146
|
toneMapping: e.toneMapping ?? 0,
|
|
147
147
|
saturation: e.saturation ?? 1
|
|
148
148
|
}),
|
|
149
149
|
width: t,
|
|
150
|
-
height:
|
|
150
|
+
height: r
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
153
|
async readOutput() {
|
|
154
154
|
let { validWidth: e, validHeight: t } = this.chain.pipeline.geometry, n = this.chain.pipeline.slots[this.slot]?.output;
|
|
155
|
-
if (!n) throw Error("
|
|
155
|
+
if (!n) throw Error("NeuralRetouch.readOutput: the pass has not produced a frame yet");
|
|
156
156
|
let r = this.device.createBindGroup({
|
|
157
157
|
layout: this._readbackPipeline.getBindGroupLayout(0),
|
|
158
158
|
entries: [{
|
|
@@ -162,7 +162,7 @@ var f = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1
|
|
|
162
162
|
binding: 1,
|
|
163
163
|
resource: { buffer: this._readbackStorage }
|
|
164
164
|
}]
|
|
165
|
-
}), i = this.device.createCommandEncoder({ label: "rayzee:
|
|
165
|
+
}), i = this.device.createCommandEncoder({ label: "rayzee:retouch-readback" }), a = i.beginComputePass();
|
|
166
166
|
a.setPipeline(this._readbackPipeline), a.setBindGroup(0, r), a.dispatchWorkgroups(Math.ceil(e / 8), Math.ceil(t / 8)), a.end(), i.copyBufferToBuffer(this._readbackStorage, 0, this._readbackMap, 0, e * t * 4), this.device.queue.submit([i.finish()]), await this._readbackMap.mapAsync(GPUMapMode.READ);
|
|
167
167
|
let o = new Uint8ClampedArray(this._readbackMap.getMappedRange().slice(0));
|
|
168
168
|
return this._readbackMap.unmap(), o;
|
|
@@ -187,9 +187,9 @@ async function g({ source: e, settings: t = {}, instance: n = null, exposure: r
|
|
|
187
187
|
onProgress: a
|
|
188
188
|
});
|
|
189
189
|
let { runtime: c, colorStrength: l } = o(t), u = s.staging;
|
|
190
|
-
if (!e.half || e.half.length !== u.length) throw Error(`
|
|
190
|
+
if (!e.half || e.half.length !== u.length) throw Error(`NeuralRetouch: expected ${u.length} packed halfs, got ${e.half?.length}`);
|
|
191
191
|
u.set(e.half), s.device.queue.writeBuffer(s.source, 0, u), s.chain.pipeline.paperWhite = 1 / Math.min(Math.max(r, 1 / h), h), s.chain.pipeline.colorStrength = l;
|
|
192
|
-
let d = performance.now(), f = s.device.createCommandEncoder({ label: "rayzee:
|
|
192
|
+
let d = performance.now(), f = s.device.createCommandEncoder({ label: "rayzee:retouch" });
|
|
193
193
|
await s.chain.encode(f, s.slot, { buffer: s.source }, c, () => {}, { reset: !0 }), s.device.queue.submit([f.finish()]), await s.device.queue.onSubmittedWorkDone();
|
|
194
194
|
let p = i ? await s.readOutputToned(i) : await s.readOutputHDR();
|
|
195
195
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as e } from "./rolldown-runtime-CAFD8bLK.js";
|
|
2
|
-
import {
|
|
2
|
+
import { S as t, t as n, y as r } from "./ToneMapGPU-BVE852iN.js";
|
|
3
3
|
//#region src/EngineEvents.js
|
|
4
4
|
var i = {
|
|
5
5
|
RENDER_COMPLETE: "engine:renderComplete",
|
|
@@ -35,26 +35,26 @@ var i = {
|
|
|
35
35
|
ISSUE: "engine:issue",
|
|
36
36
|
SCENE_METADATA_APPLIED: "engine:sceneMetadataApplied"
|
|
37
37
|
}, a = /* @__PURE__ */ e({
|
|
38
|
-
|
|
38
|
+
NeuralSuperRes: () => g,
|
|
39
39
|
SR_MAX_INPUT: () => c,
|
|
40
40
|
SR_SCALE: () => 2,
|
|
41
41
|
presentRGBA8: () => b,
|
|
42
42
|
readDenoisedHalf: () => h,
|
|
43
43
|
releaseDenoisedReader: () => m,
|
|
44
44
|
renderUpscaled: () => v
|
|
45
|
-
}), o = 2, s = t("
|
|
45
|
+
}), o = 2, s = t("neural"), c = 2048, l = null;
|
|
46
46
|
function u() {
|
|
47
47
|
if (l) return l;
|
|
48
|
-
let {
|
|
49
|
-
return globalThis.
|
|
50
|
-
if (globalThis.
|
|
48
|
+
let { neuralRuntimeUrl: e, neuralAssetBaseUrl: t } = r();
|
|
49
|
+
return t ? (globalThis.__NEURAL_ASSET_BASE__ = t, l = new Promise((t, n) => {
|
|
50
|
+
if (globalThis.NeuralRuntime?.getNativeSR) return t(globalThis.NeuralRuntime.getNativeSR());
|
|
51
51
|
let r = document.createElement("script");
|
|
52
52
|
r.src = e, r.onload = () => {
|
|
53
|
-
let e = globalThis.
|
|
54
|
-
if (!e) return n(/* @__PURE__ */ Error("
|
|
53
|
+
let e = globalThis.NeuralRuntime?.getNativeSR;
|
|
54
|
+
if (!e) return n(/* @__PURE__ */ Error("Neural runtime loaded but exposed no super-resolution entry"));
|
|
55
55
|
t(e());
|
|
56
|
-
}, r.onerror = () => n(/* @__PURE__ */ Error(`Failed to load the
|
|
57
|
-
}), l;
|
|
56
|
+
}, r.onerror = () => n(/* @__PURE__ */ Error(`Failed to load the neural runtime from ${e}`)), document.head.appendChild(r);
|
|
57
|
+
}), l) : Promise.reject(/* @__PURE__ */ Error("No neural model location: set neuralAssetBaseUrl with configureAssets()"));
|
|
58
58
|
}
|
|
59
59
|
var d = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1) var<storage, read_write> dst: array<u32>;\n\n@compute @workgroup_size(8, 8)\nfn main( @builtin(global_invocation_id) gid: vec3<u32> ) {\n let size = textureDimensions( src );\n if ( gid.x >= size.x || gid.y >= size.y ) { return; }\n let c = textureLoad( src, vec2<i32>( gid.xy ), 0 ).rgb;\n let a = ( gid.y * size.x + gid.x ) * 2u;\n dst[ a ] = pack2x16float( c.rg );\n dst[ a + 1u ] = pack2x16float( vec2<f32>( c.b, 1.0 ) );\n}\n", f = null;
|
|
60
60
|
function p(e, t, n) {
|
|
@@ -65,21 +65,21 @@ function p(e, t, n) {
|
|
|
65
65
|
width: t,
|
|
66
66
|
height: n,
|
|
67
67
|
storage: e.createBuffer({
|
|
68
|
-
label: "rayzee:
|
|
68
|
+
label: "rayzee:neural-sr-pack",
|
|
69
69
|
size: r,
|
|
70
70
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
|
|
71
71
|
}),
|
|
72
72
|
map: e.createBuffer({
|
|
73
|
-
label: "rayzee:
|
|
73
|
+
label: "rayzee:neural-sr-pack-map",
|
|
74
74
|
size: r,
|
|
75
75
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
76
76
|
}),
|
|
77
77
|
pipeline: e.createComputePipeline({
|
|
78
|
-
label: "rayzee:
|
|
78
|
+
label: "rayzee:neural-sr-pack",
|
|
79
79
|
layout: "auto",
|
|
80
80
|
compute: {
|
|
81
81
|
module: e.createShaderModule({
|
|
82
|
-
label: "rayzee:
|
|
82
|
+
label: "rayzee:neural-sr-pack",
|
|
83
83
|
code: d
|
|
84
84
|
}),
|
|
85
85
|
entryPoint: "main"
|
|
@@ -92,7 +92,7 @@ function m() {
|
|
|
92
92
|
}
|
|
93
93
|
async function h(e) {
|
|
94
94
|
let t = e?._outGPUTexture;
|
|
95
|
-
if (!t) throw Error("
|
|
95
|
+
if (!t) throw Error("NeuralSuperRes: no denoised frame — enable OIDN and finish a render first");
|
|
96
96
|
let n = e.gpuDevice, { width: r, height: i } = e._outTexSize, a = p(n, r, i), o = n.createBindGroup({
|
|
97
97
|
layout: a.pipeline.getBindGroupLayout(0),
|
|
98
98
|
entries: [{
|
|
@@ -102,7 +102,7 @@ async function h(e) {
|
|
|
102
102
|
binding: 1,
|
|
103
103
|
resource: { buffer: a.storage }
|
|
104
104
|
}]
|
|
105
|
-
}), s = n.createCommandEncoder({ label: "rayzee:
|
|
105
|
+
}), s = n.createCommandEncoder({ label: "rayzee:neural-sr-pack" }), c = s.beginComputePass();
|
|
106
106
|
c.setPipeline(a.pipeline), c.setBindGroup(0, o), c.dispatchWorkgroups(Math.ceil(r / 8), Math.ceil(i / 8)), c.end(), s.copyBufferToBuffer(a.storage, 0, a.map, 0, r * i * 8), n.queue.submit([s.finish()]), await a.map.mapAsync(GPUMapMode.READ);
|
|
107
107
|
let l = new Uint16Array(a.map.getMappedRange().slice(0));
|
|
108
108
|
return a.map.unmap(), {
|
|
@@ -131,8 +131,8 @@ var g = class e {
|
|
|
131
131
|
return this.sr.geometry.outputHeight;
|
|
132
132
|
}
|
|
133
133
|
static async create({ width: t, height: n, onProgress: r = () => {} } = {}) {
|
|
134
|
-
if (!Number.isInteger(t) || !Number.isInteger(n) || t < 1 || n < 1) throw Error("
|
|
135
|
-
if (t > 2048 || n > 2048) throw Error(`
|
|
134
|
+
if (!Number.isInteger(t) || !Number.isInteger(n) || t < 1 || n < 1) throw Error("NeuralSuperRes.create: width and height must be positive integers");
|
|
135
|
+
if (t > 2048 || n > 2048) throw Error(`NeuralSuperRes.create: input capped at ${c}px per side (${c * 2}px output), got ${t}x${n}. Above that the runtime's history plane exceeds maxTextureDimension2D and it fails later with an unrelated bind-group error.`);
|
|
136
136
|
let i = await u(), a = document.createElement("canvas");
|
|
137
137
|
a.style.cssText = "position:fixed;left:-10000px;top:0;pointer-events:none", document.body.appendChild(a);
|
|
138
138
|
let o;
|
|
@@ -155,11 +155,11 @@ var g = class e {
|
|
|
155
155
|
this._color = new Uint16Array(e * t * 4), this._depth = new Float32Array(e * t), this._motion = new Uint16Array(e * t * 2);
|
|
156
156
|
}
|
|
157
157
|
async _run(e) {
|
|
158
|
-
if (this.disposed) throw Error("
|
|
158
|
+
if (this.disposed) throw Error("NeuralSuperRes: disposed");
|
|
159
159
|
let t = this.inputWidth, n = this.inputHeight;
|
|
160
|
-
if (e.width !== t || e.height !== n) throw Error(`
|
|
160
|
+
if (e.width !== t || e.height !== n) throw Error(`NeuralSuperRes: expected a ${t}x${n} source, got ${e.width}x${e.height}`);
|
|
161
161
|
let r = this._color;
|
|
162
|
-
if (!e.half || e.half.length !== r.length) throw Error(`
|
|
162
|
+
if (!e.half || e.half.length !== r.length) throw Error(`NeuralSuperRes: expected ${r.length} packed halfs from readDenoisedHalf, got ${e.half?.length}`);
|
|
163
163
|
r.set(e.half), this._depth.fill(.5), this._motion.fill(0);
|
|
164
164
|
let i = performance.now(), a = await this.sr.render(this.slot, {
|
|
165
165
|
color: r,
|
|
@@ -174,11 +174,11 @@ var g = class e {
|
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
176
|
async upscaleToRGBA8(e, t = {}) {
|
|
177
|
-
let { result:
|
|
178
|
-
this._toneMapper ??= new
|
|
177
|
+
let { result: r, ms: i } = await this._run(e), { outputWidth: a, outputHeight: o } = this.geometry;
|
|
178
|
+
this._toneMapper ??= new n(this.sr.device, "rayzee:neural-sr-tonemap"), this._toneMapper.ensureSize(a, o);
|
|
179
179
|
let s = performance.now();
|
|
180
180
|
return {
|
|
181
|
-
rgba8: await this._toneMapper.toRGBA8(
|
|
181
|
+
rgba8: await this._toneMapper.toRGBA8(r.hdr.buffer ?? r.hdr, {
|
|
182
182
|
exposure: t.exposure ?? 1,
|
|
183
183
|
toneMapping: t.toneMapping ?? 0,
|
|
184
184
|
saturation: t.saturation ?? 1,
|
|
@@ -211,7 +211,7 @@ function _(e, t, n) {
|
|
|
211
211
|
e.addEventListener(i.DENOISING_END, l);
|
|
212
212
|
let u = setInterval(() => {
|
|
213
213
|
c && s(!0);
|
|
214
|
-
}, 200), d = setTimeout(() => s(!1, /* @__PURE__ */ Error("
|
|
214
|
+
}, 200), d = setTimeout(() => s(!1, /* @__PURE__ */ Error("NeuralSuperRes: timed out waiting for the render to finish")), n);
|
|
215
215
|
});
|
|
216
216
|
}
|
|
217
217
|
async function v(e, { outputWidth: t, outputHeight: n, samples: r = 64, upscaler: i = null, timeoutMs: a = 6e5, present: o = !0, onProgress: s = () => {} } = {}) {
|