rayzee 8.2.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.
@@ -0,0 +1,584 @@
1
+ /**
2
+ * DLSS Super Resolution — final-render output scaling.
3
+ *
4
+ * Traces at half resolution, denoises, then runs one neural upscale to reach the
5
+ * requested output size. On a 1.89M-triangle interior at 32 spp this matched a
6
+ * native full-resolution render for accuracy while resolving visibly more detail,
7
+ * for a quarter of the traced pixels.
8
+ *
9
+ * Deliberately *not* wired into the live pipeline: the network costs ~132 ms of GPU
10
+ * at 1920x1080, which is free against a final render and ruinous against a frame.
11
+ *
12
+ * The pass is GPU-bound by design. Every crossing of the CPU boundary is a compute pass: the
13
+ * denoised frame is packed to rgba16float on the renderer's device (`readDenoisedHalf`), and the
14
+ * result leaves the model's device either as display bytes (`upscaleToRGBA8`) or as packed halves
15
+ * for the detail pass (`upscaleToHalf`). Nothing crosses in floats and no per-pixel JavaScript runs,
16
+ * so the wall clock is the network plus two readbacks.
17
+ *
18
+ * Measured on a 1.89M-triangle interior, warm: 1024² → 2048² fell from 540 ms to 260 ms, and
19
+ * 2048² → 4096² from 2.41 s to 947 ms — of which 922 ms is the network, 9 ms the input read and
20
+ * 13 ms the tone map and its readback. Plumbing is 2 % of the pass, so the only remaining lever on
21
+ * output size is the network itself.
22
+ *
23
+ * ⚠️ The FIRST pass after a resolution change rebuilds the graph: 266 ms vs 136 ms warm at 1024²
24
+ * output, 762 ms vs 531 ms at 2048².
25
+ *
26
+ * ⚠️ Each neural model brings **its own GPUDevice**, built by the runtime and not shareable. With
27
+ * super resolution and the detail pass both on, the page holds three: the renderer's, and one each.
28
+ */
29
+
30
+ import { EngineEvents } from '../EngineEvents.js';
31
+ import { createLogger } from '../utils/Logger.js';
32
+ import { PackedToneMapper } from '../Processor/ToneMapGPU.js';
33
+ import { getAssetConfig } from '../AssetConfig.js';
34
+
35
+ /** The network is a fixed 2x per axis. */
36
+ export const SR_SCALE = 2;
37
+
38
+ const log = createLogger( 'dlss' );
39
+
40
+ /**
41
+ * Largest input side the runtime actually survives — 2048, i.e. 4096 output.
42
+ *
43
+ * The runtime advertises 4096 and validates against it, but two walls sit lower and both were hit
44
+ * by measurement rather than reading:
45
+ *
46
+ * - It allocates a "retained history" plane at **4x the input dimension** and requests no raised
47
+ * limit, so 2049+ input exceeds the default `maxTextureDimension2D` of 8192. The failure surfaces
48
+ * much later and unhelpfully, as an invalid bind group in an unrelated pass. The adapter here
49
+ * supports 16384, so patching the runtime's `requestDevice` would lift this one.
50
+ * - Its "arbitrary exposure" pass dispatches `ceil(w * h / 256)` workgroups in X, so an input area
51
+ * of 16,777,216 px — exactly 4096² — asks for 65,536 against WebGPU's 65,535 limit. One over.
52
+ * That wall does not move without changing the dispatch, so square 8192 output is unreachable
53
+ * even with the texture limit raised.
54
+ *
55
+ * 2048 also happens to be `MAX_STORAGE_TEXTURE_SIZE`, the engine's own default render reserve, so
56
+ * nothing is lost in practice today.
57
+ */
58
+ export const SR_MAX_INPUT = 2048;
59
+
60
+ let _runtimePromise = null;
61
+
62
+ function loadRuntime() {
63
+
64
+ if ( _runtimePromise ) return _runtimePromise;
65
+
66
+ const { dlssRuntimeUrl, dlssAssetBaseUrl } = getAssetConfig();
67
+ globalThis.__DLSS5_ASSET_BASE__ = dlssAssetBaseUrl;
68
+
69
+ _runtimePromise = new Promise( ( resolve, reject ) => {
70
+
71
+ if ( globalThis.DLSSRuntime?.getNativeSR ) return resolve( globalThis.DLSSRuntime.getNativeSR() );
72
+
73
+ const el = document.createElement( 'script' );
74
+ el.src = dlssRuntimeUrl;
75
+ el.onload = () => {
76
+
77
+ const factory = globalThis.DLSSRuntime?.getNativeSR;
78
+ if ( ! factory ) return reject( new Error( 'DLSS runtime loaded but exposed no super-resolution entry' ) );
79
+ resolve( factory() );
80
+
81
+ };
82
+
83
+ el.onerror = () => reject( new Error( `Failed to load the DLSS runtime from ${dlssRuntimeUrl}` ) );
84
+ document.head.appendChild( el );
85
+
86
+ } );
87
+
88
+ return _runtimePromise;
89
+
90
+ }
91
+
92
+ const DENOISED_PACK_WGSL = /* wgsl */ `
93
+ @group(0) @binding(0) var src: texture_2d<f32>;
94
+ @group(0) @binding(1) var<storage, read_write> dst: array<u32>;
95
+
96
+ @compute @workgroup_size(8, 8)
97
+ fn main( @builtin(global_invocation_id) gid: vec3<u32> ) {
98
+ let size = textureDimensions( src );
99
+ if ( gid.x >= size.x || gid.y >= size.y ) { return; }
100
+ let c = textureLoad( src, vec2<i32>( gid.xy ), 0 ).rgb;
101
+ let a = ( gid.y * size.x + gid.x ) * 2u;
102
+ dst[ a ] = pack2x16float( c.rg );
103
+ dst[ a + 1u ] = pack2x16float( vec2<f32>( c.b, 1.0 ) );
104
+ }
105
+ `;
106
+
107
+ /**
108
+ * Cached on the module rather than on the denoiser, which is engine-owned and should not grow a
109
+ * field for this. `releaseDenoisedReader()` is called from `DenoisingManager`'s teardown.
110
+ */
111
+ let _reader = null;
112
+
113
+ function ensureReader( device, width, height ) {
114
+
115
+ if ( _reader && ( _reader.device !== device || _reader.width !== width || _reader.height !== height ) ) {
116
+
117
+ releaseDenoisedReader();
118
+
119
+ }
120
+
121
+ if ( _reader ) return _reader;
122
+
123
+ const bytes = width * height * 8;
124
+ _reader = {
125
+ device,
126
+ width,
127
+ height,
128
+ storage: device.createBuffer( {
129
+ label: 'rayzee:dlss-sr-pack',
130
+ size: bytes,
131
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
132
+ } ),
133
+ map: device.createBuffer( {
134
+ label: 'rayzee:dlss-sr-pack-map',
135
+ size: bytes,
136
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
137
+ } ),
138
+ pipeline: device.createComputePipeline( {
139
+ label: 'rayzee:dlss-sr-pack',
140
+ layout: 'auto',
141
+ compute: {
142
+ module: device.createShaderModule( { label: 'rayzee:dlss-sr-pack', code: DENOISED_PACK_WGSL } ),
143
+ entryPoint: 'main',
144
+ },
145
+ } ),
146
+ };
147
+
148
+ return _reader;
149
+
150
+ }
151
+
152
+ /** Frees the packing buffers — ~26 MB at a 2048² render. */
153
+ export function releaseDenoisedReader() {
154
+
155
+ _reader?.storage.destroy();
156
+ _reader?.map.destroy();
157
+ _reader = null;
158
+
159
+ }
160
+
161
+ /**
162
+ * Reads the denoised picture already packed the way the network wants it: tightly packed
163
+ * rgba16float, alpha 1, no row padding. Feed it straight to `upscale()` / `upscaleToRGBA8()`.
164
+ *
165
+ * The half floats never become JavaScript numbers, which is the whole point — reaching the same
166
+ * bytes through a float array cost two full-image loops and a 50 MB allocation at a 2048² render.
167
+ *
168
+ * @param {import('../Passes/OIDNDenoiser.js').OIDNDenoiser} denoiser
169
+ * @returns {Promise<{half: Uint16Array, width: number, height: number}>}
170
+ */
171
+ export async function readDenoisedHalf( denoiser ) {
172
+
173
+ const texture = denoiser?._outGPUTexture;
174
+ if ( ! texture ) throw new Error( 'DLSSSuperRes: no denoised frame — enable OIDN and finish a render first' );
175
+
176
+ const device = denoiser.gpuDevice;
177
+ const { width, height } = denoiser._outTexSize;
178
+ const reader = ensureReader( device, width, height );
179
+ const group = device.createBindGroup( {
180
+ layout: reader.pipeline.getBindGroupLayout( 0 ),
181
+ entries: [
182
+ { binding: 0, resource: texture.createView() },
183
+ { binding: 1, resource: { buffer: reader.storage } },
184
+ ],
185
+ } );
186
+
187
+ const encoder = device.createCommandEncoder( { label: 'rayzee:dlss-sr-pack' } );
188
+ const pass = encoder.beginComputePass();
189
+ pass.setPipeline( reader.pipeline );
190
+ pass.setBindGroup( 0, group );
191
+ pass.dispatchWorkgroups( Math.ceil( width / 8 ), Math.ceil( height / 8 ) );
192
+ pass.end();
193
+ encoder.copyBufferToBuffer( reader.storage, 0, reader.map, 0, width * height * 8 );
194
+ device.queue.submit( [ encoder.finish() ] );
195
+
196
+ await reader.map.mapAsync( GPUMapMode.READ );
197
+ const half = new Uint16Array( reader.map.getMappedRange().slice( 0 ) );
198
+ reader.map.unmap();
199
+
200
+ return { half, width, height };
201
+
202
+ }
203
+
204
+ export class DLSSSuperRes {
205
+
206
+ constructor( sr, canvas ) {
207
+
208
+ this.sr = sr;
209
+ this.canvas = canvas;
210
+ this.slot = 0;
211
+ this._color = null;
212
+ this._depth = null;
213
+ this._motion = null;
214
+ this._toneMapper = null;
215
+ this.disposed = false;
216
+
217
+ }
218
+
219
+ get geometry() {
220
+
221
+ return this.sr.geometry;
222
+
223
+ }
224
+
225
+ get inputWidth() {
226
+
227
+ return this.sr.geometry.width;
228
+
229
+ }
230
+
231
+ get inputHeight() {
232
+
233
+ return this.sr.geometry.height;
234
+
235
+ }
236
+
237
+ get outputWidth() {
238
+
239
+ return this.sr.geometry.outputWidth;
240
+
241
+ }
242
+
243
+ get outputHeight() {
244
+
245
+ return this.sr.geometry.outputHeight;
246
+
247
+ }
248
+
249
+ /**
250
+ * @param {object} opts
251
+ * @param {number} opts.width input (traced) width — output is twice this
252
+ * @param {number} opts.height input (traced) height
253
+ */
254
+ static async create( { width, height, onProgress = () => {} } = {} ) {
255
+
256
+ if ( ! Number.isInteger( width ) || ! Number.isInteger( height ) || width < 1 || height < 1 ) {
257
+
258
+ throw new Error( 'DLSSSuperRes.create: width and height must be positive integers' );
259
+
260
+ }
261
+
262
+ if ( width > SR_MAX_INPUT || height > SR_MAX_INPUT ) {
263
+
264
+ throw new Error(
265
+ `DLSSSuperRes.create: input capped at ${SR_MAX_INPUT}px per side (${SR_MAX_INPUT * SR_SCALE}px output), ` +
266
+ `got ${width}x${height}. Above that the runtime's history plane exceeds maxTextureDimension2D ` +
267
+ 'and it fails later with an unrelated bind-group error.'
268
+ );
269
+
270
+ }
271
+
272
+ const NativeSR = await loadRuntime();
273
+
274
+ const canvas = document.createElement( 'canvas' );
275
+ canvas.style.cssText = 'position:fixed;left:-10000px;top:0;pointer-events:none';
276
+ document.body.appendChild( canvas );
277
+
278
+ let sr;
279
+ try {
280
+
281
+ sr = await NativeSR.create( canvas, { width, height, onProgress, timestamps: true, nrSettings: null } );
282
+
283
+ } catch ( e ) {
284
+
285
+ canvas.remove();
286
+ throw e;
287
+
288
+ }
289
+
290
+ const instance = new DLSSSuperRes( sr, canvas );
291
+ instance._allocate();
292
+ return instance;
293
+
294
+ }
295
+
296
+ _allocate() {
297
+
298
+ const w = this.inputWidth;
299
+ const h = this.inputHeight;
300
+ this._color = new Uint16Array( w * h * 4 );
301
+ this._depth = new Float32Array( w * h );
302
+ this._motion = new Uint16Array( w * h * 2 );
303
+
304
+ }
305
+
306
+ /** Runs the network once over a frame already in its own layout, as `readDenoisedHalf` returns. */
307
+ async _run( source ) {
308
+
309
+ if ( this.disposed ) throw new Error( 'DLSSSuperRes: disposed' );
310
+
311
+ const w = this.inputWidth;
312
+ const h = this.inputHeight;
313
+ if ( source.width !== w || source.height !== h ) {
314
+
315
+ throw new Error( `DLSSSuperRes: expected a ${w}x${h} source, got ${source.width}x${source.height}` );
316
+
317
+ }
318
+
319
+ const color = this._color;
320
+ if ( ! source.half || source.half.length !== color.length ) {
321
+
322
+ throw new Error(
323
+ `DLSSSuperRes: expected ${color.length} packed halfs from readDenoisedHalf, got ${source.half?.length}`
324
+ );
325
+
326
+ }
327
+
328
+ color.set( source.half );
329
+
330
+ // Depth and motion are both provably inert on this path, so the constants below cost nothing:
331
+ // with `reset: true` the output is bit-identical for depth 0.0 / 0.5 / 1.0 and for motion zero
332
+ // vs 5 % of the frame. That matches the shaders — `raw_motion` is reachable only from a debug
333
+ // branch, and `raw_depth` only from a history-gated address path. Feeding the engine's real
334
+ // depth and motion vectors here would change nothing.
335
+ //
336
+ // Jitter is the one input that DOES move the result, and [0,0] is right for this use: a
337
+ // converged accumulation is the average over the pixel, i.e. its centre.
338
+ this._depth.fill( 0.5 );
339
+ this._motion.fill( 0 );
340
+
341
+ const started = performance.now();
342
+ const result = await this.sr.render( this.slot, {
343
+ color,
344
+ depth: this._depth,
345
+ motion: this._motion,
346
+ reset: true,
347
+ jitter: [ 0, 0 ],
348
+ } );
349
+ await this.sr.device.queue.onSubmittedWorkDone();
350
+
351
+ return { result, ms: performance.now() - started };
352
+
353
+ }
354
+
355
+ /**
356
+ * Upscales one image and tone-maps it to display bytes without leaving the card.
357
+ *
358
+ * @param {{half: Uint16Array, width: number, height: number}} source
359
+ * @param {{exposure: number, toneMapping: number, saturation: number}} tone engine display state
360
+ * @returns {Promise<{rgba8: Uint8ClampedArray, width: number, height: number, ms: number, toneMs: number}>}
361
+ */
362
+ async upscaleToRGBA8( source, tone = {} ) {
363
+
364
+ const { result, ms } = await this._run( source );
365
+ const { outputWidth, outputHeight } = this.geometry;
366
+
367
+ this._toneMapper ??= new PackedToneMapper( this.sr.device, 'rayzee:dlss-sr-tonemap' );
368
+ this._toneMapper.ensureSize( outputWidth, outputHeight );
369
+
370
+ const started = performance.now();
371
+ const rgba8 = await this._toneMapper.toRGBA8( result.hdr.buffer ?? result.hdr, {
372
+ exposure: tone.exposure ?? 1,
373
+ toneMapping: tone.toneMapping ?? 0,
374
+ saturation: tone.saturation ?? 1,
375
+ // The network emits its picture vertically flipped.
376
+ flipY: true,
377
+ } );
378
+
379
+ return { rgba8, width: outputWidth, height: outputHeight, ms, toneMs: performance.now() - started };
380
+
381
+ }
382
+
383
+ dispose() {
384
+
385
+ if ( this.disposed ) return;
386
+ this.disposed = true;
387
+ this._toneMapper?.dispose();
388
+ this._toneMapper = null;
389
+ this.canvas.remove();
390
+ try {
391
+
392
+ this.sr.destroy();
393
+
394
+ } catch ( e ) {
395
+
396
+ log.warn( 'super resolution teardown:', e );
397
+
398
+ }
399
+
400
+ }
401
+
402
+ }
403
+
404
+ function waitForDenoise( app, samples, timeoutMs ) {
405
+
406
+ return new Promise( ( resolve, reject ) => {
407
+
408
+ let settled = false;
409
+ const finish = ( ok, err ) => {
410
+
411
+ if ( settled ) return;
412
+ settled = true;
413
+ app.removeEventListener( EngineEvents.DENOISING_END, onEnd );
414
+ clearInterval( poll );
415
+ clearTimeout( timer );
416
+ ok ? resolve() : reject( err );
417
+
418
+ };
419
+
420
+ // Only a denoise that lands once the sample target is met counts. A denoise left in flight by
421
+ // the previous render can arrive after this listener attaches and would otherwise end the
422
+ // wait on the wrong frame.
423
+ let denoised = false;
424
+ const onEnd = () => {
425
+
426
+ if ( ( app.stages?.pathTracer?.frameCount ?? 0 ) >= samples ) denoised = true;
427
+
428
+ };
429
+
430
+ app.addEventListener( EngineEvents.DENOISING_END, onEnd );
431
+
432
+ const poll = setInterval( () => {
433
+
434
+ if ( denoised ) finish( true );
435
+
436
+ }, 200 );
437
+
438
+ const timer = setTimeout(
439
+ () => finish( false, new Error( 'DLSSSuperRes: timed out waiting for the render to finish' ) ),
440
+ timeoutMs,
441
+ );
442
+
443
+ } );
444
+
445
+ }
446
+
447
+ /**
448
+ * Renders the scene at half the requested size, denoises it, and upscales to full size.
449
+ *
450
+ * Restores render size, sample ceiling and denoiser state before returning, including on failure.
451
+ *
452
+ * @param {object} opts
453
+ * @param {number} opts.outputWidth final width; must be even
454
+ * @param {number} opts.outputHeight final height; must be even
455
+ * @param {number} [opts.samples] samples to accumulate at half resolution
456
+ * @param {DLSSSuperRes} [opts.upscaler] reuse an upscaler already built for this size
457
+ * @param {boolean} [opts.present] draw the result over the viewport
458
+ * @returns {Promise<{rgba8: Uint8ClampedArray, width: number, height: number, timings: object}>}
459
+ */
460
+ export async function renderUpscaled( app, {
461
+ outputWidth,
462
+ outputHeight,
463
+ samples = 64,
464
+ upscaler = null,
465
+ timeoutMs = 600000,
466
+ present = true,
467
+ onProgress = () => {},
468
+ } = {} ) {
469
+
470
+ if ( outputWidth % SR_SCALE || outputHeight % SR_SCALE ) {
471
+
472
+ throw new Error( `renderUpscaled: output must be divisible by ${SR_SCALE}, got ${outputWidth}x${outputHeight}` );
473
+
474
+ }
475
+
476
+ const inW = outputWidth / SR_SCALE;
477
+ const inH = outputHeight / SR_SCALE;
478
+
479
+ const pathTracer = app.stages?.pathTracer;
480
+ const restore = {
481
+ width: pathTracer?.width,
482
+ height: pathTracer?.height,
483
+ maxSamples: app.settings?.get?.( 'maxSamples' ),
484
+ finalDenoise: app.denoisingManager?.finalDenoise,
485
+ paused: app.pauseRendering,
486
+ };
487
+
488
+ const owned = ! upscaler;
489
+ let sr = upscaler;
490
+ const timings = {};
491
+
492
+ try {
493
+
494
+ if ( ! sr ) {
495
+
496
+ const t = performance.now();
497
+ onProgress( 'Loading the super-resolution model' );
498
+ sr = await DLSSSuperRes.create( { width: inW, height: inH, onProgress } );
499
+ timings.load = performance.now() - t;
500
+
501
+ } else if ( sr.inputWidth !== inW || sr.inputHeight !== inH ) {
502
+
503
+ throw new Error( `renderUpscaled: upscaler is ${sr.inputWidth}x${sr.inputHeight}, need ${inW}x${inH}` );
504
+
505
+ }
506
+
507
+ const tRender = performance.now();
508
+ onProgress( `Tracing ${inW}x${inH} at ${samples} spp` );
509
+ app.pauseRendering = false;
510
+ app.setCanvasSize( inW, inH );
511
+ await new Promise( r => setTimeout( r, 300 ) );
512
+
513
+ app.denoisingManager.applyOIDNEnabled( true );
514
+ app.settings.set( 'maxSamples', samples );
515
+ const done = waitForDenoise( app, samples, timeoutMs );
516
+ app.reset();
517
+ await done;
518
+ app.pauseRendering = true;
519
+ timings.render = performance.now() - tRender;
520
+
521
+ onProgress( 'Reading the denoised frame' );
522
+ const tRead = performance.now();
523
+ const source = await readDenoisedHalf( app.denoisingManager?.denoiser );
524
+ timings.read = performance.now() - tRead;
525
+
526
+ onProgress( `Upscaling to ${outputWidth}x${outputHeight}` );
527
+ const tone = {
528
+ exposure: app.renderer.toneMappingExposure,
529
+ toneMapping: app.renderer.toneMapping,
530
+ saturation: app.settings.get( 'saturation' ) ?? 1,
531
+ };
532
+
533
+ const { rgba8, width, height, ms, toneMs } = await sr.upscaleToRGBA8( source, tone );
534
+ timings.upscale = ms;
535
+ timings.tonemap = toneMs;
536
+
537
+ if ( present ) presentToUpscalerCanvas( app.denoisingManager?.upscalerCanvas, rgba8, width, height );
538
+
539
+ return { rgba8, width, height, timings, upscaler: sr };
540
+
541
+ } catch ( e ) {
542
+
543
+ // An upscaler built inside this call has no other owner, so it would otherwise leak its
544
+ // device and 3 MB of weights on the failure path.
545
+ if ( owned ) sr?.dispose();
546
+ throw e;
547
+
548
+ } finally {
549
+
550
+ if ( restore.width && restore.height ) app.setCanvasSize( restore.width, restore.height );
551
+ if ( restore.maxSamples !== undefined ) app.settings?.set?.( 'maxSamples', restore.maxSamples );
552
+ if ( restore.finalDenoise !== undefined ) app.denoisingManager?.applyOIDNEnabled( restore.finalDenoise );
553
+ app.pauseRendering = restore.paused;
554
+
555
+ }
556
+
557
+ }
558
+
559
+ /**
560
+ * Shows the result on the engine's 2D overlay — the same canvas the AI upscaler uses, because this
561
+ * picture is also larger than the render and also in ordinary pixels.
562
+ */
563
+ function presentToUpscalerCanvas( canvas, rgba8, width, height ) {
564
+
565
+ if ( ! canvas ) return false;
566
+
567
+ canvas.width = width;
568
+ canvas.height = height;
569
+ const ctx = canvas.getContext( '2d' );
570
+ ctx.putImageData( new ImageData( new Uint8ClampedArray( rgba8.buffer ?? rgba8 ), width, height ), 0, 0 );
571
+ canvas.style.display = 'block';
572
+ return true;
573
+
574
+ }
575
+
576
+ /**
577
+ * Draws ready RGBA bytes onto the overlay. For the detail pass, whose result comes back as bytes
578
+ * rather than linear floats — it is display-referred by the time we can read it.
579
+ */
580
+ export function presentRGBA8( canvas, rgba8, width, height ) {
581
+
582
+ return presentToUpscalerCanvas( canvas, rgba8, width, height );
583
+
584
+ }
package/src/index.js CHANGED
@@ -97,3 +97,9 @@ export { VideoRenderManager } from './managers/VideoRenderManager.js';
97
97
 
98
98
  // Interaction
99
99
  export { InteractionManager } from './managers/InteractionManager.js';
100
+
101
+ // DLSS neural passes. The app drives these through `denoisingManager` — what is exported here is
102
+ // the offline entry point (`renderUpscaled`, used by the regression bench) plus the two contracts a
103
+ // host needs to build UI against.
104
+ export { renderUpscaled, SR_SCALE, SR_MAX_INPUT } from './dlss/DLSSSuperRes.js';
105
+ export { DLSS_NR_DEFAULTS, DLSS_NR_RANGES, DLSS_NR_MAX_PIXELS } from './dlss/DLSSNeural.js';