rayzee 8.3.0 → 8.4.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 +54 -2
- package/dist/DLSSNeural-BNSJ8V3z.js +202 -0
- package/dist/DLSSSuperRes-BoEW89Cl.js +266 -0
- package/dist/ToneMapGPU-DzqSyzTx.js +341 -0
- package/dist/rayzee.es.js +4523 -4577
- package/dist/rayzee.umd.js +260 -66
- package/dist/rolldown-runtime-CAFD8bLK.js +11 -0
- package/package.json +1 -1
- package/src/AssetConfig.js +7 -0
- package/src/EngineDefaults.js +5 -1
- package/src/PathTracerApp.js +6 -4
- package/src/Processor/ToneMapGPU.js +317 -0
- package/src/dlss/DLSSNeural.js +530 -0
- package/src/dlss/DLSSSuperRes.js +584 -0
- package/src/index.js +6 -0
- package/src/managers/DenoisingManager.js +272 -7
|
@@ -2,6 +2,11 @@ import { EventDispatcher } from 'three';
|
|
|
2
2
|
import { OIDNDenoiser } from '../Passes/OIDNDenoiser.js';
|
|
3
3
|
import { AIUpscaler } from '../Passes/AIUpscaler.js';
|
|
4
4
|
import { EngineEvents } from '../EngineEvents.js';
|
|
5
|
+
import { createLogger } from '../utils/Logger.js';
|
|
6
|
+
|
|
7
|
+
// The neural passes live in `../dlss/` but report through the manager that drives them, so they
|
|
8
|
+
// share one namespace: `rayzee.log.only( 'dlss' )` shows the whole chain.
|
|
9
|
+
const dlssLog = createLogger( 'dlss' );
|
|
5
10
|
import { ENGINE_DEFAULTS as DEFAULT_STATE, ASVGF_QUALITY_PRESETS, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS } from '../EngineDefaults.js';
|
|
6
11
|
|
|
7
12
|
// A refresh slower than this is a slideshow, not a live view, so the cadence swaps to a cheaper
|
|
@@ -107,6 +112,17 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
107
112
|
|
|
108
113
|
this.denoiser = null;
|
|
109
114
|
this.upscaler = null;
|
|
115
|
+
// Which model the AI upscaler runs. 'esrgan' is the ONNX chain; 'dlss' is the neural
|
|
116
|
+
// super-resolution pass, which is fixed at 2x and needs a denoised source.
|
|
117
|
+
this.upscalerBackend = 'esrgan';
|
|
118
|
+
this._dlssUpscaler = null;
|
|
119
|
+
// The neural-rendering (detail) pass is independent of the upscaler: it changes appearance,
|
|
120
|
+
// not resolution, and runs last because its result cannot be read back.
|
|
121
|
+
this.neuralRendering = false;
|
|
122
|
+
this.neuralRenderingSettings = {};
|
|
123
|
+
this._dlssNeural = null;
|
|
124
|
+
this._dlssModule = null;
|
|
125
|
+
this._neuralPostBusy = false;
|
|
110
126
|
|
|
111
127
|
// The tier the finished image uses. The loaded tier is not always this one: while the
|
|
112
128
|
// image is still accumulating we run a cheaper model (see previewQuality).
|
|
@@ -786,6 +802,242 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
786
802
|
|
|
787
803
|
}
|
|
788
804
|
|
|
805
|
+
/**
|
|
806
|
+
* Selects the AI upscaler's model. 'dlss' is fixed at 2x and reads the denoised picture, so it
|
|
807
|
+
* is a no-op without a denoiser — feeding it raw Monte-Carlo noise measurably loses to bilinear.
|
|
808
|
+
*
|
|
809
|
+
* @param {'esrgan'|'dlss'} backend
|
|
810
|
+
*/
|
|
811
|
+
setUpscalerBackend( backend ) {
|
|
812
|
+
|
|
813
|
+
const next = backend === 'dlss' ? 'dlss' : 'esrgan';
|
|
814
|
+
if ( next === this.upscalerBackend ) return;
|
|
815
|
+
|
|
816
|
+
this.upscalerBackend = next;
|
|
817
|
+
// Whatever is on screen came from the model being switched away from.
|
|
818
|
+
this._restoreRenderDisplay();
|
|
819
|
+
if ( next !== 'dlss' ) this._releaseDLSSUpscaler();
|
|
820
|
+
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Puts the render canvas back in front and tells the host the output is the render size again.
|
|
825
|
+
*
|
|
826
|
+
* Guarded on the overlay actually being visible: `abort()` runs on every reset, which is every
|
|
827
|
+
* frame of a camera drag, and an unguarded event there would be a per-frame storm.
|
|
828
|
+
*
|
|
829
|
+
* @returns {boolean} whether anything was actually restored
|
|
830
|
+
*/
|
|
831
|
+
_restoreRenderDisplay() {
|
|
832
|
+
|
|
833
|
+
const wasShowing = this.upscalerCanvas && this.upscalerCanvas.style.display !== 'none';
|
|
834
|
+
|
|
835
|
+
if ( this.upscalerCanvas ) this.upscalerCanvas.style.display = 'none';
|
|
836
|
+
if ( this.mainCanvas ) this.mainCanvas.style.opacity = '1';
|
|
837
|
+
|
|
838
|
+
if ( wasShowing ) {
|
|
839
|
+
|
|
840
|
+
const stage = this._stages?.pathTracer;
|
|
841
|
+
if ( stage?.width && stage?.height ) {
|
|
842
|
+
|
|
843
|
+
this.dispatchEvent( { type: 'resolution_changed', width: stage.width, height: stage.height } );
|
|
844
|
+
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return !! wasShowing;
|
|
850
|
+
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
_releaseDLSSUpscaler() {
|
|
854
|
+
|
|
855
|
+
this._dlssUpscaler?.dispose();
|
|
856
|
+
this._dlssUpscaler = null;
|
|
857
|
+
|
|
858
|
+
// The frame packer lives on the module, not on the upscaler, and holds two buffers the size
|
|
859
|
+
// of the render. Nothing else reaches it, so this is its only release point. Guarded on the
|
|
860
|
+
// module having been loaded, so a teardown never pulls in 1 MB of runtime to call a no-op.
|
|
861
|
+
this._dlssModule?.releaseDenoisedReader();
|
|
862
|
+
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/** Turns the neural-rendering (detail) pass on or off, and sets its appearance controls. */
|
|
866
|
+
setNeuralRendering( enabled, settings = null ) {
|
|
867
|
+
|
|
868
|
+
this.neuralRendering = !! enabled;
|
|
869
|
+
if ( settings ) this.neuralRenderingSettings = { ...this.neuralRenderingSettings, ...settings };
|
|
870
|
+
if ( ! this.neuralRendering ) {
|
|
871
|
+
|
|
872
|
+
this._releaseDLSSNeural();
|
|
873
|
+
this._restoreRenderDisplay();
|
|
874
|
+
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
_releaseDLSSNeural() {
|
|
880
|
+
|
|
881
|
+
this._dlssNeural?.dispose();
|
|
882
|
+
this._dlssNeural = null;
|
|
883
|
+
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* The neural post-chain over the picture the closing denoise just produced: super resolution
|
|
888
|
+
* first, then the detail pass, either or both.
|
|
889
|
+
*
|
|
890
|
+
* Order is forced by the models. Super resolution hands back light that can be fed onward; the
|
|
891
|
+
* detail pass writes an `rgba8unorm` texture with no `COPY_SRC`, so nothing can read its result
|
|
892
|
+
* and it has to be last — and it presents in its own display space rather than the engine's tone
|
|
893
|
+
* curve. Which shape super resolution returns therefore depends on what follows it: packed
|
|
894
|
+
* halves when the detail pass is next, display bytes when it is the end of the chain.
|
|
895
|
+
*
|
|
896
|
+
* Deliberately fire-and-forget: the render is already finished and a failure must not break it.
|
|
897
|
+
*/
|
|
898
|
+
async _runNeuralPost( isStillComplete ) {
|
|
899
|
+
|
|
900
|
+
const wantSR = this.upscaler?.enabled && this.upscalerBackend === 'dlss';
|
|
901
|
+
const wantNR = this.neuralRendering;
|
|
902
|
+
if ( ! wantSR && ! wantNR ) return;
|
|
903
|
+
|
|
904
|
+
// A second completion can land while the first pass is still in flight — the models are
|
|
905
|
+
// created lazily and reused through fields, so two runs would race over the same handles.
|
|
906
|
+
if ( this._neuralPostBusy ) return;
|
|
907
|
+
|
|
908
|
+
if ( ! this.upscalerCanvas ) {
|
|
909
|
+
|
|
910
|
+
dlssLog.warn( 'neural pass skipped: the engine has no overlay canvas to present on' );
|
|
911
|
+
return;
|
|
912
|
+
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if ( ! this.pipeline?.context?.getTexture( 'oidn:output' ) ) {
|
|
916
|
+
|
|
917
|
+
dlssLog.warn( 'neural pass skipped: no denoised picture — it needs OIDN' );
|
|
918
|
+
return;
|
|
919
|
+
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
this._neuralPostBusy = true;
|
|
923
|
+
this.dispatchEvent( { type: EngineEvents.UPSCALING_START } );
|
|
924
|
+
|
|
925
|
+
try {
|
|
926
|
+
|
|
927
|
+
const sr = await import( '../dlss/DLSSSuperRes.js' );
|
|
928
|
+
this._dlssModule = sr;
|
|
929
|
+
|
|
930
|
+
// The detail pass has a size ceiling — above it a run takes minutes and then loses every GPU
|
|
931
|
+
// device in the page. Running it FIRST is what keeps it under: at render size it sees a
|
|
932
|
+
// quarter of the pixels it would after a 2x upscale, and the render reserve already caps
|
|
933
|
+
// that at 2048. The check stays as a floor under a raised reserve.
|
|
934
|
+
const nrModule = wantNR ? await import( '../dlss/DLSSNeural.js' ) : null;
|
|
935
|
+
const srcSize = this.denoiser?._outTexSize;
|
|
936
|
+
const nrPixels = ( srcSize?.width ?? 0 ) * ( srcSize?.height ?? 0 );
|
|
937
|
+
const runNR = wantNR && nrPixels <= nrModule.DLSS_NR_MAX_PIXELS;
|
|
938
|
+
|
|
939
|
+
if ( wantNR && ! runNR ) {
|
|
940
|
+
|
|
941
|
+
dlssLog.warn(
|
|
942
|
+
`Neural rendering skipped: the render is ${( nrPixels / 1e6 ).toFixed( 1 )} MP, above the ` +
|
|
943
|
+
`${( nrModule.DLSS_NR_MAX_PIXELS / 1e6 ).toFixed( 1 )} MP the pass survives.` +
|
|
944
|
+
( wantSR ? ' The upscale still ran.' : '' )
|
|
945
|
+
);
|
|
946
|
+
this._releaseDLSSNeural();
|
|
947
|
+
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if ( ! wantSR && ! runNR ) return;
|
|
951
|
+
|
|
952
|
+
// Must be the effective value, not the settings one: AutoExposure overwrites
|
|
953
|
+
// `renderer.toneMappingExposure` every frame and never touches settings, so reading
|
|
954
|
+
// settings — as this did — silently ignores auto-exposure. Same helper OIDN uses.
|
|
955
|
+
const tone = {
|
|
956
|
+
exposure: this._getEffectiveExposure(),
|
|
957
|
+
toneMapping: this._getToneMapping(),
|
|
958
|
+
saturation: this._getSaturation?.() ?? 1,
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
// Scene-referred throughout. Exposure reaches the detail pass through its own `paper_white`
|
|
962
|
+
// and comes back out again, so the engine's tone curve is still the only one applied.
|
|
963
|
+
const source = await sr.readDenoisedHalf( this.denoiser );
|
|
964
|
+
|
|
965
|
+
if ( wantSR ) {
|
|
966
|
+
|
|
967
|
+
if ( this._dlssUpscaler
|
|
968
|
+
&& ( this._dlssUpscaler.inputWidth !== source.width || this._dlssUpscaler.inputHeight !== source.height ) ) {
|
|
969
|
+
|
|
970
|
+
this._releaseDLSSUpscaler();
|
|
971
|
+
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if ( ! this._dlssUpscaler ) {
|
|
975
|
+
|
|
976
|
+
this._dlssUpscaler = await sr.DLSSSuperRes.create( { width: source.width, height: source.height } );
|
|
977
|
+
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
let image = source;
|
|
983
|
+
|
|
984
|
+
// Detail first, upscale second. The detail pass hands back scene-referred light (see
|
|
985
|
+
// `app/public/dlss/PATCHES.md`), so the upscaler still gets the linear HDR it expects —
|
|
986
|
+
// which is what makes this order possible at all.
|
|
987
|
+
if ( runNR ) {
|
|
988
|
+
|
|
989
|
+
const result = await nrModule.enhanceFrame( {
|
|
990
|
+
source: image,
|
|
991
|
+
settings: this.neuralRenderingSettings,
|
|
992
|
+
instance: this._dlssNeural,
|
|
993
|
+
exposure: tone.exposure,
|
|
994
|
+
tone: wantSR ? null : tone,
|
|
995
|
+
} );
|
|
996
|
+
this._dlssNeural = result.instance;
|
|
997
|
+
image = result;
|
|
998
|
+
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if ( wantSR ) image = await this._dlssUpscaler.upscaleToRGBA8( image, tone );
|
|
1002
|
+
|
|
1003
|
+
if ( ! isStillComplete() ) {
|
|
1004
|
+
|
|
1005
|
+
this.dropDisplay();
|
|
1006
|
+
return;
|
|
1007
|
+
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
sr.presentRGBA8( this.upscalerCanvas, image.rgba8, image.width, image.height );
|
|
1011
|
+
|
|
1012
|
+
if ( ! isStillComplete() ) {
|
|
1013
|
+
|
|
1014
|
+
this.dropDisplay();
|
|
1015
|
+
return;
|
|
1016
|
+
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// The 2D canvas sits BEHIND the render canvas in the DOM (`insertBefore`), so making it
|
|
1020
|
+
// `display:block` is not enough — the render canvas has to get out of the way. This is
|
|
1021
|
+
// what `AIUpscaler._revealOutput` does for the other backend, and `abort()` already
|
|
1022
|
+
// restores both sides.
|
|
1023
|
+
if ( this.mainCanvas ) this.mainCanvas.style.opacity = '0';
|
|
1024
|
+
|
|
1025
|
+
// The host shows the output dimensions; without this they keep reading the render size.
|
|
1026
|
+
this.dispatchEvent( { type: 'resolution_changed', width: image.width, height: image.height } );
|
|
1027
|
+
|
|
1028
|
+
} catch ( error ) {
|
|
1029
|
+
|
|
1030
|
+
dlssLog.warn( 'neural pass failed', error );
|
|
1031
|
+
|
|
1032
|
+
} finally {
|
|
1033
|
+
|
|
1034
|
+
this._neuralPostBusy = false;
|
|
1035
|
+
this.dispatchEvent( { type: EngineEvents.UPSCALING_END } );
|
|
1036
|
+
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
}
|
|
1040
|
+
|
|
789
1041
|
onRenderComplete( { isStillComplete, context } ) {
|
|
790
1042
|
|
|
791
1043
|
// Remove any stale completion-chain listener from a previous render cycle
|
|
@@ -805,12 +1057,17 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
805
1057
|
|
|
806
1058
|
if ( ! isStillComplete() ) return;
|
|
807
1059
|
|
|
808
|
-
|
|
1060
|
+
// One owner of the overlay canvas, so the neural chain and the ONNX upscaler are
|
|
1061
|
+
// exclusive. The neural chain also runs on its own when only its detail pass is on.
|
|
1062
|
+
if ( this.neuralRendering || ( this.upscaler?.enabled && this.upscalerBackend === 'dlss' ) ) {
|
|
809
1063
|
|
|
810
|
-
this.
|
|
1064
|
+
this._runNeuralPost( isStillComplete );
|
|
1065
|
+
return;
|
|
811
1066
|
|
|
812
1067
|
}
|
|
813
1068
|
|
|
1069
|
+
if ( this.upscaler?.enabled ) this.upscaler.start();
|
|
1070
|
+
|
|
814
1071
|
};
|
|
815
1072
|
|
|
816
1073
|
if ( ! closing ) {
|
|
@@ -887,8 +1144,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
887
1144
|
|
|
888
1145
|
// The 2D canvas is the upscaler's alone: a reset means its enlarged result no longer
|
|
889
1146
|
// describes anything, so the render canvas comes back.
|
|
890
|
-
|
|
891
|
-
if ( this.
|
|
1147
|
+
this._restoreRenderDisplay();
|
|
1148
|
+
if ( mainCanvas && mainCanvas !== this.mainCanvas ) mainCanvas.style.opacity = '1';
|
|
892
1149
|
if ( this.upscaler ) this.upscaler.abort();
|
|
893
1150
|
|
|
894
1151
|
const moving = !! this._stages.pathTracer?.viewIsChanging;
|
|
@@ -917,9 +1174,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
917
1174
|
dropDisplay() {
|
|
918
1175
|
|
|
919
1176
|
this._unpublishOutput();
|
|
920
|
-
|
|
921
|
-
if ( this.upscalerCanvas ) this.upscalerCanvas.style.display = 'none';
|
|
922
|
-
if ( this.mainCanvas ) this.mainCanvas.style.opacity = '1';
|
|
1177
|
+
this._restoreRenderDisplay();
|
|
923
1178
|
|
|
924
1179
|
}
|
|
925
1180
|
|
|
@@ -927,6 +1182,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
927
1182
|
|
|
928
1183
|
// Remove pending completion-chain listener
|
|
929
1184
|
this._cleanupCompletionListener();
|
|
1185
|
+
// Owns a second GPUDevice plus 3 MB of weights, so it must not outlive the manager.
|
|
1186
|
+
this._releaseDLSSUpscaler();
|
|
1187
|
+
this._releaseDLSSNeural();
|
|
1188
|
+
|
|
930
1189
|
// Before the denoiser destroys the picture the Compositor would otherwise still sample.
|
|
931
1190
|
this._unpublishOutput();
|
|
932
1191
|
|
|
@@ -947,6 +1206,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
947
1206
|
if ( this._upscalerEndHandler ) this.upscaler.removeEventListener( 'end', this._upscalerEndHandler );
|
|
948
1207
|
this.upscaler.dispose();
|
|
949
1208
|
this.upscaler = null;
|
|
1209
|
+
// Which model the AI upscaler runs. 'esrgan' is the ONNX chain; 'dlss' is the neural
|
|
1210
|
+
// super-resolution pass, which is fixed at 2x and needs a denoised source.
|
|
1211
|
+
this.upscalerBackend = 'esrgan';
|
|
1212
|
+
this._dlssUpscaler = null;
|
|
950
1213
|
|
|
951
1214
|
}
|
|
952
1215
|
|
|
@@ -1128,6 +1391,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1128
1391
|
setUpscalerEnabled( enabled ) {
|
|
1129
1392
|
|
|
1130
1393
|
if ( this.upscaler ) this.upscaler.enabled = enabled;
|
|
1394
|
+
// The enlarged picture describes a setting that is no longer on.
|
|
1395
|
+
if ( ! enabled ) this._restoreRenderDisplay();
|
|
1131
1396
|
|
|
1132
1397
|
}
|
|
1133
1398
|
|