rayzee 10.0.0 → 11.0.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 +252 -44
- package/dist/ActiveColor-YvuQq44O.js +13 -0
- package/dist/ArchiveImporter-BRQvLggb.js +4300 -0
- package/dist/AssetConfig-DSEntE65.js +30 -0
- package/dist/BidirectionalIntegrator-9lZQxFVH.js +934 -0
- package/dist/BufferLayout-D_IBo2ci.js +150 -0
- package/dist/ColorManagement-oeEk7oSu.js +1488 -0
- package/dist/EngineEvents-Ci8dVWCP.js +73 -0
- package/dist/EngineIssues-CIZnXHsF.js +72 -0
- package/dist/FileFormats-sPGJeCRc.js +102 -0
- package/dist/Logger-dDDGj9Av.js +97 -0
- package/dist/{NeuralRetouch-CYgkQY8Z.js → NeuralRetouch-BCXylThH.js} +8 -6
- package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-sZAeS142.js} +55 -120
- package/dist/PathTracer-CQPW06Mm.js +29971 -0
- package/dist/PhysicalSky-BvqCw_dy.js +1219 -0
- package/dist/RayzeeRenderer-COpwotpU.js +3551 -0
- package/dist/SolarLimb-CPaeZVwz.js +721 -0
- package/dist/ToneMapGPU-CjU3osDd.js +177 -0
- package/dist/ViewTransforms-CYN7bq2w.js +255 -0
- package/dist/WorkingMatrix-C6y233hT.js +27 -0
- package/dist/ZipReader-gDSWr0I5.js +98 -0
- package/dist/addons/archives.es.js +3 -0
- package/dist/addons/bidirectional.es.js +2 -0
- package/dist/addons/color.es.js +3 -0
- package/dist/addons/formats.es.js +2 -0
- package/dist/addons/physical-sky.es.js +2 -0
- package/dist/addons/storage.es.js +3 -0
- package/dist/archiveFormats-BZw7TiDp.js +75 -0
- package/dist/identity-DrQsGT2o.js +39 -0
- package/dist/openStorage-DwcBRUj7.js +820 -0
- package/dist/rayzee-core.es.js +12 -0
- package/dist/rayzee.es.js +2551 -44024
- package/dist/rayzee.umd.js +528 -487
- package/dist/sceneCachePolicy-BSSsKY98.js +443 -0
- package/dist/shared-BGBcSxBQ.js +17 -0
- package/package.json +23 -2
- package/src/Color/ActiveColor.js +34 -0
- package/src/Color/BasicColor.js +147 -0
- package/src/Color/ColorManagement.js +7 -31
- package/src/Color/ViewTransforms.js +5 -1
- package/src/EngineDefaults.js +62 -1029
- package/src/EngineEvents.js +2 -0
- package/src/EngineIssues.js +2 -0
- package/src/Headless.js +6 -5
- package/src/Passes/AIUpscaler.js +58 -141
- package/src/Passes/OIDNDenoiser.js +92 -65
- package/src/Passes/OIDNTemporalHistory.js +1 -1
- package/src/PathTracerApp.js +620 -4026
- package/src/Pipeline/PipelineContext.js +0 -4
- package/src/Platform.js +36 -0
- package/src/Processor/ArchiveCache.js +1 -1
- package/src/Processor/ArchiveImporter.js +1259 -0
- package/src/Processor/AssetLoader.js +327 -1763
- package/src/Processor/AtmosphereModel.js +1 -2
- package/src/Processor/BVHBuilder.js +14 -126
- package/src/Processor/BVHLeafFold.js +4 -6
- package/src/Processor/BVHRefitter.js +67 -46
- package/src/Processor/BufferLayout.js +291 -0
- package/src/Processor/EmissiveTriangleBuilder.js +1 -1
- package/src/Processor/EnvironmentExactTable.js +187 -0
- package/src/Processor/EquirectHDRInfo.js +30 -223
- package/src/Processor/FileFormats.js +116 -0
- package/src/Processor/GLTFDecoders.js +85 -0
- package/src/Processor/GLTFDiffuseTransmission.js +63 -0
- package/src/Processor/GeometryExtractor.js +15 -5
- package/src/Processor/HostMemory.js +7 -1
- package/src/Processor/IESParser.js +47 -35
- package/src/Processor/InstanceTable.js +1 -1
- package/src/Processor/KernelManager.js +35 -2
- package/src/Processor/MaterialPacking.js +10 -8
- package/src/Processor/PBRT/PBRTMaterials.js +129 -41
- package/src/Processor/PBRT/PBRTParser.js +30 -17
- package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
- package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
- package/src/Processor/PBRT/PFM.js +59 -0
- package/src/Processor/PackedRayBuffer.js +116 -9
- package/src/Processor/ParallelBVHBuilder.js +3 -6
- package/src/Processor/PhysicalSky.js +5 -4
- package/src/Processor/PlatformImageLoader.js +50 -6
- package/src/Processor/QueueManager.js +39 -5
- package/src/Processor/ReinsertionOptimizer.js +2 -2
- package/src/Processor/SceneProcessor.js +53 -62
- package/src/Processor/ShaderBuilder.js +6 -16
- package/src/Processor/SimpleSky.js +1 -62
- package/src/Processor/SolarLimb.js +3 -0
- package/src/Processor/SplitBLAS.js +1 -1
- package/src/Processor/StorageTexturePool.js +22 -1
- package/src/Processor/TLASBuilder.js +1 -3
- package/src/Processor/TextureBuckets.js +237 -0
- package/src/Processor/TextureCreator.js +181 -8
- package/src/Processor/ToneMapGPU.js +64 -15
- package/src/Processor/Workers/BVHRefitWorker.js +5 -2
- package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
- package/src/Processor/Workers/BVHWorker.js +5 -18
- package/src/Processor/Workers/CDFWorker.js +6 -183
- package/src/Processor/Workers/TexturesWorker.js +103 -18
- package/src/Processor/archiveFormats.js +7 -0
- package/src/Processor/utils.js +2 -1
- package/src/RayzeeRenderer.js +3899 -0
- package/src/RenderSettings.js +68 -62
- package/src/Stages/ASVGF.js +13 -14
- package/src/Stages/AutoExposure.js +17 -17
- package/src/Stages/BilateralFilter.js +2 -1
- package/src/Stages/Compositor.js +8 -23
- package/src/Stages/DenoiserSettings.js +182 -0
- package/src/Stages/EdgeFilter.js +2 -1
- package/src/Stages/MotionVector.js +1 -1
- package/src/Stages/NRD.js +11 -5
- package/src/Stages/NormalDepth.js +12 -8
- package/src/Stages/PathTracer.js +338 -43
- package/src/Stages/PathTracerStage.js +69 -66
- package/src/Stages/Variance.js +1 -1
- package/src/Storage/BLASCache.js +1 -1
- package/src/Storage/CDFCache.js +18 -17
- package/src/Storage/DownloadCache.js +1 -1
- package/src/Storage/SceneGraphCodec.js +46 -3
- package/src/Storage/SpillStore.js +1 -1
- package/src/Storage/StorageManager.js +45 -9
- package/src/Storage/areas.js +4 -0
- package/src/TSL/BVHTraversal.js +50 -25
- package/src/TSL/Bidirectional.js +204 -0
- package/src/TSL/BidirectionalLamps.js +132 -0
- package/src/TSL/Clearcoat.js +15 -6
- package/src/TSL/Common.js +20 -15
- package/src/TSL/ConnectKernel.js +147 -0
- package/src/TSL/Debugger.js +1 -1
- package/src/TSL/Displacement.js +4 -3
- package/src/TSL/EmissiveSampling.js +65 -18
- package/src/TSL/Environment.js +77 -106
- package/src/TSL/EnvironmentCDF.js +85 -39
- package/src/TSL/ExtendKernel.js +8 -3
- package/src/TSL/FinalWriteKernel.js +4 -4
- package/src/TSL/GenerateKernel.js +18 -4
- package/src/TSL/HitFacet.js +35 -5
- package/src/TSL/LightBVHSampling.js +40 -9
- package/src/TSL/LightGenerateKernel.js +293 -0
- package/src/TSL/LightGuide.js +198 -0
- package/src/TSL/LightSplatKernel.js +165 -0
- package/src/TSL/LightVisibility.js +111 -0
- package/src/TSL/LightsCore.js +13 -38
- package/src/TSL/LightsDirect.js +28 -29
- package/src/TSL/LightsIndirect.js +3 -1
- package/src/TSL/LightsSampling.js +239 -51
- package/src/TSL/MaterialEvaluation.js +113 -31
- package/src/TSL/MaterialProperties.js +44 -17
- package/src/TSL/MaterialTransmission.js +52 -47
- package/src/TSL/MergeKernel.js +218 -0
- package/src/TSL/PathTracerCore.js +65 -29
- package/src/TSL/Random.js +5 -1
- package/src/TSL/SceneResources.js +51 -0
- package/src/TSL/ShadeKernel.js +964 -101
- package/src/TSL/Struct.js +10 -0
- package/src/TSL/Sun.js +1 -1
- package/src/TSL/TextureSampling.js +66 -61
- package/src/addons/archives.js +14 -0
- package/src/addons/bidirectional.js +13 -0
- package/src/addons/color.js +32 -0
- package/src/addons/formats.js +16 -0
- package/src/addons/physicalSky.js +14 -0
- package/src/addons/storage.js +19 -0
- package/src/core.js +31 -0
- package/src/index.js +13 -23
- package/src/integrators/BidirectionalIntegrator.js +833 -0
- package/src/managers/AnimationManager.js +88 -6
- package/src/managers/CameraManager.js +103 -46
- package/src/managers/DenoisingManager.js +14 -10
- package/src/managers/EnvironmentManager.js +125 -103
- package/src/managers/GoboManager.js +9 -0
- package/src/managers/MaterialDataManager.js +48 -37
- package/src/managers/UniformManager.js +32 -10
- package/dist/ToneMapGPU-B6XIe3DU.js +0 -544
- package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
- package/src/Processor/TreeletOptimizer.js +0 -574
package/src/EngineEvents.js
CHANGED
|
@@ -8,6 +8,8 @@ export const EngineEvents = {
|
|
|
8
8
|
RENDER_COMPLETE: 'engine:renderComplete',
|
|
9
9
|
RENDER_RESET: 'engine:renderReset',
|
|
10
10
|
FRAME: 'engine:frame',
|
|
11
|
+
// The path tracer compiles its shaders in the background ({ compiling }); the canvas keeps its last frame meanwhile.
|
|
12
|
+
SHADERS_COMPILING: 'engine:shadersCompiling',
|
|
11
13
|
|
|
12
14
|
// Denoiser
|
|
13
15
|
DENOISING_START: 'engine:denoisingStart',
|
package/src/EngineIssues.js
CHANGED
|
@@ -32,7 +32,9 @@ export const ISSUE_CODES = Object.freeze( {
|
|
|
32
32
|
STORAGE_CACHE_MISMATCH: 'storage.cache_mismatch',
|
|
33
33
|
DENOISER_UNAVAILABLE: 'denoiser.unavailable',
|
|
34
34
|
OUTPUT_SOURCE_FALLBACK: 'output.source_fallback',
|
|
35
|
+
OUTPUT_TONEMAP_FALLBACK: 'output.tonemap_fallback',
|
|
35
36
|
LIGHT_PLACEHOLDER_SKIPPED: 'light.placeholder_skipped',
|
|
37
|
+
CAPABILITY_MISSING: 'capability.missing',
|
|
36
38
|
} );
|
|
37
39
|
|
|
38
40
|
/** Strict throws on ERROR only. */
|
package/src/Headless.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The supported way to render without a person watching. Defaults are the batch renderer's —
|
|
3
|
-
* strict,
|
|
3
|
+
* strict, deterministic — all reversible, none reversible by accident.
|
|
4
4
|
*
|
|
5
5
|
* @example
|
|
6
6
|
* const shot = await renderHeadless( { model: url, width: 1920, height: 1080, samples: 256 } );
|
|
@@ -20,11 +20,10 @@ import { PathTracerApp } from './PathTracerApp.js';
|
|
|
20
20
|
* @param {number} [options.samples=64] - samples to accumulate
|
|
21
21
|
* @param {'linear'|'srgb'} [options.colorSpace='srgb']
|
|
22
22
|
* @param {boolean} [options.strict=true]
|
|
23
|
-
* @param {string} [options.profile='physical'] - see RENDER_PROFILES
|
|
24
23
|
* @param {boolean} [options.deterministic=true]
|
|
25
24
|
* @param {boolean} [options.allowEarlyRetire=false] - needs `deterministic: false` to be reachable
|
|
26
25
|
* @param {boolean} [options.denoise=false] - one final OIDN pass, read back instead of the raw accumulation
|
|
27
|
-
* @param {Object} [options.settings] - applied
|
|
26
|
+
* @param {Object} [options.settings] - applied before the model loads and again after it
|
|
28
27
|
* @param {function(number): void} [options.onProgress] - running sample count
|
|
29
28
|
* @returns {Promise<{data: Float32Array|Uint8ClampedArray, width: number, height: number,
|
|
30
29
|
* colorSpace: string, source: string, samples: number, retiredBy: string, issues: Object[], adapter: Object,
|
|
@@ -95,7 +94,6 @@ export async function openHeadless( {
|
|
|
95
94
|
width = 1920,
|
|
96
95
|
height = 1080,
|
|
97
96
|
strict = true,
|
|
98
|
-
profile = 'physical',
|
|
99
97
|
deterministic = true,
|
|
100
98
|
settings = null,
|
|
101
99
|
storage = false,
|
|
@@ -103,7 +101,7 @@ export async function openHeadless( {
|
|
|
103
101
|
} = {} ) {
|
|
104
102
|
|
|
105
103
|
// Off unless asked for: cached state from an earlier run must not change what a batch renders.
|
|
106
|
-
const app = new PathTracerApp( canvas ?? null, { autoResize: false, strict,
|
|
104
|
+
const app = new PathTracerApp( canvas ?? null, { autoResize: false, strict, storage, hostMemoryGB } );
|
|
107
105
|
|
|
108
106
|
try {
|
|
109
107
|
|
|
@@ -114,7 +112,10 @@ export async function openHeadless( {
|
|
|
114
112
|
await app.init();
|
|
115
113
|
app.setCanvasSize( width, height );
|
|
116
114
|
|
|
115
|
+
// Before the load as well: some are read while a model loads (areaLightIntensityScale).
|
|
116
|
+
if ( settings ) app.settings.setMany( settings, { silent: true } );
|
|
117
117
|
if ( model ) await app.loadModel( model );
|
|
118
|
+
// Again after it, over anything the model's metadata set.
|
|
118
119
|
if ( settings ) app.settings.setMany( settings, { silent: true } );
|
|
119
120
|
|
|
120
121
|
// After the model: scene metadata can turn adaptive sampling back on.
|
package/src/Passes/AIUpscaler.js
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { EventDispatcher, ACESFilmicToneMapping } from 'three';
|
|
2
|
-
import {
|
|
2
|
+
import { PackedToneMapper } from '../Processor/ToneMapGPU.js';
|
|
3
3
|
import { getAssetConfig } from '../AssetConfig.js';
|
|
4
4
|
import AIUpscalerWorker from '../Processor/Workers/AIUpscalerWorker.js?worker&inline';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
// An OCIO view already returned colour encoded for its display, so the gamma step below would
|
|
8
|
-
// encode it twice. A built-in curve returns linear and does want it.
|
|
9
|
-
const displayEncoder = toneMapping => isOutputEncoded( toneMapping )
|
|
10
|
-
? ( c => ( c > 1 ? 1 : c < 0 ? 0 : c ) )
|
|
11
|
-
: ( c => Math.pow( c, SRGB_GAMMA ) );
|
|
12
|
-
|
|
13
6
|
// ─── Model Configuration ───────────────────────────────────────────────────────
|
|
14
7
|
// Quality presets reference relative paths against asset-config `upscalerModelBaseUrl`.
|
|
15
8
|
|
|
@@ -140,13 +133,8 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
140
133
|
this._baseWidth = output.width;
|
|
141
134
|
this._baseHeight = output.height;
|
|
142
135
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
// r184's ReadbackBuffer; we can't use renderer.getArrayBufferAsync directly
|
|
146
|
-
// because our source is a raw GPUTexture, not a Three.js BufferAttribute).
|
|
147
|
-
this._hdrStagingBuffer = null;
|
|
148
|
-
this._hdrStagingWidth = 0;
|
|
149
|
-
this._hdrStagingHeight = 0;
|
|
136
|
+
// Tone-maps the HDR source on the card; its buffers are released after each capture.
|
|
137
|
+
this._toneMapper = null;
|
|
150
138
|
|
|
151
139
|
}
|
|
152
140
|
|
|
@@ -283,7 +271,7 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
283
271
|
// so we must grab the pixels before awaiting model load.
|
|
284
272
|
if ( this.hdr && this.getGPUTextures ) {
|
|
285
273
|
|
|
286
|
-
// HDR path:
|
|
274
|
+
// HDR path: float planes tone-mapped on the GPU, no 8-bit step before the network
|
|
287
275
|
this._capturedSource = await this._captureSourceHDR();
|
|
288
276
|
|
|
289
277
|
} else {
|
|
@@ -403,17 +391,6 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
403
391
|
// The SR model outputs RGB only — alpha would be lost without this.
|
|
404
392
|
this._cacheUpscaledAlpha( sourceImageData, srcW * scale, srcH * scale );
|
|
405
393
|
|
|
406
|
-
// Cache HDR tonemapping state for tile extraction (avoids per-pixel lookups)
|
|
407
|
-
if ( sourceImageData.isHDR ) {
|
|
408
|
-
|
|
409
|
-
this._hdrToneMapFn = TONE_MAP_FNS.get( this.getToneMapping() ) || TONE_MAP_FNS.get( ACESFilmicToneMapping );
|
|
410
|
-
this._hdrEncode = displayEncoder( this.getToneMapping() );
|
|
411
|
-
this._hdrExposure = effectiveExposure( this.getExposure(), this.getToneMapping() );
|
|
412
|
-
this._hdrSaturation = this.getSaturation();
|
|
413
|
-
this._tmOut = new Float32Array( 3 );
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
417
394
|
// Tile-based inference
|
|
418
395
|
const overlap = MODEL_CONFIG.TILE_OVERLAP;
|
|
419
396
|
const tileSize = this.tileSize;
|
|
@@ -503,121 +480,72 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
503
480
|
}
|
|
504
481
|
|
|
505
482
|
/**
|
|
506
|
-
* HDR capture:
|
|
507
|
-
*
|
|
483
|
+
* HDR capture: tone-maps the path tracer's float colour on the card and reads back float
|
|
484
|
+
* planes — r, g, b, a — so the network gets the curve without 8-bit quantization.
|
|
508
485
|
*/
|
|
509
486
|
async _captureSourceHDR() {
|
|
510
487
|
|
|
511
488
|
const gpuTextures = this.getGPUTextures();
|
|
512
489
|
if ( ! gpuTextures?.color ) throw new Error( 'No GPU color texture available for HDR capture' );
|
|
513
490
|
|
|
514
|
-
const device = this.renderer.backend.device;
|
|
515
491
|
const colorTexture = gpuTextures.color;
|
|
516
|
-
const width = colorTexture
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
this._hdrStagingBuffer?.destroy();
|
|
528
|
-
this._hdrStagingBuffer = device.createBuffer( {
|
|
529
|
-
label: 'aiupscaler-hdr-readback',
|
|
530
|
-
size: bufferSize,
|
|
531
|
-
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
|
|
532
|
-
} );
|
|
533
|
-
this._hdrStagingWidth = width;
|
|
534
|
-
this._hdrStagingHeight = height;
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
const stagingBuffer = this._hdrStagingBuffer;
|
|
539
|
-
|
|
540
|
-
const encoder = device.createCommandEncoder();
|
|
541
|
-
encoder.copyTextureToBuffer(
|
|
542
|
-
{ texture: colorTexture },
|
|
543
|
-
{ buffer: stagingBuffer, bytesPerRow, rowsPerImage: height },
|
|
544
|
-
{ width, height, depthOrArrayLayers: 1 }
|
|
545
|
-
);
|
|
546
|
-
device.queue.submit( [ encoder.finish() ] );
|
|
547
|
-
|
|
548
|
-
await stagingBuffer.mapAsync( GPUMapMode.READ );
|
|
549
|
-
const mappedData = new Float32Array( stagingBuffer.getMappedRange() );
|
|
550
|
-
|
|
551
|
-
// Copy to ImageData-like structure (handle row alignment padding)
|
|
552
|
-
const pixelFloats = width * 4;
|
|
553
|
-
const rowFloats = bytesPerRow / 4;
|
|
554
|
-
const data = new Float32Array( width * height * 4 );
|
|
555
|
-
|
|
556
|
-
for ( let y = 0; y < height; y ++ ) {
|
|
557
|
-
|
|
558
|
-
const srcOffset = y * rowFloats;
|
|
559
|
-
const dstOffset = y * pixelFloats;
|
|
560
|
-
data.set( mappedData.subarray( srcOffset, srcOffset + pixelFloats ), dstOffset );
|
|
561
|
-
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
stagingBuffer.unmap();
|
|
492
|
+
const { width, height } = colorTexture;
|
|
493
|
+
|
|
494
|
+
this._toneMapper ??= new PackedToneMapper( this.renderer.backend.device, 'aiupscaler-tonemap', { input: 'texture', output: 'planar' } );
|
|
495
|
+
this._toneMapper.ensureSize( width, height );
|
|
496
|
+
const planes = await this._toneMapper.toPlanar( colorTexture, {
|
|
497
|
+
exposure: this.getExposure(),
|
|
498
|
+
toneMapping: this.getToneMapping() ?? ACESFilmicToneMapping,
|
|
499
|
+
saturation: this.getSaturation(),
|
|
500
|
+
} );
|
|
501
|
+
this._toneMapper.release();
|
|
565
502
|
|
|
566
|
-
|
|
567
|
-
return { data, width, height, isHDR: true };
|
|
503
|
+
return { planes, width, height, isHDR: true };
|
|
568
504
|
|
|
569
505
|
}
|
|
570
506
|
|
|
571
507
|
/**
|
|
572
508
|
* Extracts a tile region from the source image.
|
|
573
509
|
* Returns a Float32Array in NCHW format [1, 3, H, W] with values in [0, 1].
|
|
574
|
-
* Handles both LDR (uint8 ImageData) and HDR (
|
|
510
|
+
* Handles both LDR (uint8 ImageData) and HDR (tone-mapped float planes) sources.
|
|
575
511
|
*/
|
|
576
512
|
_extractTile( sourceImageData, x, y, w, h ) {
|
|
577
513
|
|
|
578
|
-
const {
|
|
579
|
-
const isHDR = sourceImageData.isHDR;
|
|
514
|
+
const { width } = sourceImageData;
|
|
580
515
|
const pixelCount = w * h;
|
|
581
516
|
const floats = new Float32Array( 3 * pixelCount );
|
|
582
517
|
|
|
583
|
-
|
|
518
|
+
if ( sourceImageData.isHDR ) {
|
|
584
519
|
|
|
585
|
-
|
|
520
|
+
const { planes } = sourceImageData;
|
|
521
|
+
const plane = width * sourceImageData.height;
|
|
522
|
+
for ( let c = 0; c < 3; c ++ ) {
|
|
586
523
|
|
|
587
|
-
|
|
588
|
-
const dstIdx = row * w + col;
|
|
524
|
+
for ( let row = 0; row < h; row ++ ) {
|
|
589
525
|
|
|
590
|
-
|
|
526
|
+
const from = c * plane + ( y + row ) * width + x;
|
|
527
|
+
floats.set( planes.subarray( from, from + w ), c * pixelCount + row * w );
|
|
591
528
|
|
|
592
|
-
|
|
593
|
-
// The SR model expects sRGB-range input — we tonemap before the model
|
|
594
|
-
// but keep float32 precision (no uint8 quantization bottleneck).
|
|
595
|
-
const tmFn = this._hdrToneMapFn;
|
|
596
|
-
const exposure = this._hdrExposure;
|
|
597
|
-
const saturation = this._hdrSaturation;
|
|
598
|
-
let er = data[ srcIdx ] * exposure, eg = data[ srcIdx + 1 ] * exposure, eb = data[ srcIdx + 2 ] * exposure;
|
|
599
|
-
if ( saturation !== 1.0 ) {
|
|
529
|
+
}
|
|
600
530
|
|
|
601
|
-
|
|
602
|
-
applySaturation( this._tmOut, saturation );
|
|
603
|
-
er = this._tmOut[ 0 ]; eg = this._tmOut[ 1 ]; eb = this._tmOut[ 2 ];
|
|
531
|
+
}
|
|
604
532
|
|
|
605
|
-
|
|
533
|
+
return floats;
|
|
534
|
+
|
|
535
|
+
}
|
|
606
536
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
floats[ dstIdx ] = encode( this._tmOut[ 0 ] );
|
|
610
|
-
floats[ pixelCount + dstIdx ] = encode( this._tmOut[ 1 ] );
|
|
611
|
-
floats[ 2 * pixelCount + dstIdx ] = encode( this._tmOut[ 2 ] );
|
|
537
|
+
const { data } = sourceImageData;
|
|
538
|
+
for ( let row = 0; row < h; row ++ ) {
|
|
612
539
|
|
|
613
|
-
|
|
540
|
+
for ( let col = 0; col < w; col ++ ) {
|
|
614
541
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
floats[ pixelCount + dstIdx ] = data[ srcIdx + 1 ] / 255;
|
|
618
|
-
floats[ 2 * pixelCount + dstIdx ] = data[ srcIdx + 2 ] / 255;
|
|
542
|
+
const srcIdx = ( ( y + row ) * width + ( x + col ) ) * 4;
|
|
543
|
+
const dstIdx = row * w + col;
|
|
619
544
|
|
|
620
|
-
|
|
545
|
+
// LDR: normalize uint8 [0,255] to [0,1]
|
|
546
|
+
floats[ dstIdx ] = data[ srcIdx ] / 255;
|
|
547
|
+
floats[ pixelCount + dstIdx ] = data[ srcIdx + 1 ] / 255;
|
|
548
|
+
floats[ 2 * pixelCount + dstIdx ] = data[ srcIdx + 2 ] / 255;
|
|
621
549
|
|
|
622
550
|
}
|
|
623
551
|
|
|
@@ -715,14 +643,19 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
715
643
|
*/
|
|
716
644
|
_cacheUpscaledAlpha( sourceImageData, outW, outH ) {
|
|
717
645
|
|
|
718
|
-
const {
|
|
646
|
+
const { width, height } = sourceImageData;
|
|
647
|
+
const count = width * height;
|
|
719
648
|
|
|
649
|
+
// HDR alpha is the fourth plane, LDR every fourth byte.
|
|
720
650
|
const isHDR = sourceImageData.isHDR;
|
|
651
|
+
const data = isHDR ? sourceImageData.planes.subarray( 3 * count ) : sourceImageData.data;
|
|
652
|
+
const stride = isHDR ? 1 : 4;
|
|
653
|
+
const offset = isHDR ? 0 : 3;
|
|
721
654
|
const opaqueVal = isHDR ? 1.0 : 255;
|
|
722
655
|
|
|
723
656
|
// Check if source has any non-opaque pixels
|
|
724
657
|
let hasAlpha = false;
|
|
725
|
-
for ( let i =
|
|
658
|
+
for ( let i = offset; i < data.length; i += stride ) {
|
|
726
659
|
|
|
727
660
|
if ( data[ i ] < opaqueVal ) {
|
|
728
661
|
|
|
@@ -748,10 +681,10 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
748
681
|
const srcCtx = srcCanvas.getContext( '2d' );
|
|
749
682
|
const alphaImage = srcCtx.createImageData( width, height );
|
|
750
683
|
|
|
751
|
-
for ( let i = 0
|
|
684
|
+
for ( let i = 0; i < count; i ++ ) {
|
|
752
685
|
|
|
753
686
|
const a = isHDR
|
|
754
|
-
? Math.min( Math.max( data[ i
|
|
687
|
+
? Math.min( Math.max( data[ i ] * 255, 0 ), 255 ) | 0
|
|
755
688
|
: data[ i * 4 + 3 ];
|
|
756
689
|
alphaImage.data[ i * 4 ] = a;
|
|
757
690
|
alphaImage.data[ i * 4 + 1 ] = a;
|
|
@@ -797,32 +730,18 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
797
730
|
|
|
798
731
|
if ( sourceImageData.isHDR ) {
|
|
799
732
|
|
|
800
|
-
//
|
|
801
|
-
const {
|
|
733
|
+
// Already tone-mapped and encoded on the card; only quantized here.
|
|
734
|
+
const { planes, width, height } = sourceImageData;
|
|
735
|
+
const count = width * height;
|
|
802
736
|
const imageData = ctx.createImageData( width, height );
|
|
803
737
|
const pixels = imageData.data;
|
|
804
|
-
const tmFn = TONE_MAP_FNS.get( this.getToneMapping() ) || TONE_MAP_FNS.get( ACESFilmicToneMapping );
|
|
805
|
-
const exposure = effectiveExposure( this.getExposure(), this.getToneMapping() );
|
|
806
|
-
const encode = displayEncoder( this.getToneMapping() );
|
|
807
|
-
const saturation = this.getSaturation();
|
|
808
|
-
const out = new Float32Array( 3 );
|
|
809
738
|
|
|
810
|
-
for ( let i = 0
|
|
739
|
+
for ( let i = 0; i < count; i ++ ) {
|
|
811
740
|
|
|
812
741
|
const si = i * 4;
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
out[ 0 ] = er; out[ 1 ] = eg; out[ 2 ] = eb;
|
|
817
|
-
applySaturation( out, saturation );
|
|
818
|
-
er = out[ 0 ]; eg = out[ 1 ]; eb = out[ 2 ];
|
|
819
|
-
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
tmFn( er, eg, eb, 1.0, out );
|
|
823
|
-
pixels[ si ] = ( encode( out[ 0 ] ) * 255 + 0.5 ) | 0;
|
|
824
|
-
pixels[ si + 1 ] = ( encode( out[ 1 ] ) * 255 + 0.5 ) | 0;
|
|
825
|
-
pixels[ si + 2 ] = ( encode( out[ 2 ] ) * 255 + 0.5 ) | 0;
|
|
742
|
+
pixels[ si ] = ( planes[ i ] * 255 + 0.5 ) | 0;
|
|
743
|
+
pixels[ si + 1 ] = ( planes[ count + i ] * 255 + 0.5 ) | 0;
|
|
744
|
+
pixels[ si + 2 ] = ( planes[ 2 * count + i ] * 255 + 0.5 ) | 0;
|
|
826
745
|
pixels[ si + 3 ] = 255;
|
|
827
746
|
|
|
828
747
|
}
|
|
@@ -927,10 +846,8 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
927
846
|
this._upscaledAlpha = null;
|
|
928
847
|
this.state.abortController = null;
|
|
929
848
|
|
|
930
|
-
this.
|
|
931
|
-
this.
|
|
932
|
-
this._hdrStagingWidth = 0;
|
|
933
|
-
this._hdrStagingHeight = 0;
|
|
849
|
+
this._toneMapper?.dispose();
|
|
850
|
+
this._toneMapper = null;
|
|
934
851
|
|
|
935
852
|
console.log( 'AIUpscaler disposed' );
|
|
936
853
|
|
|
@@ -182,6 +182,20 @@ fn main( @builtin(global_invocation_id) gid: vec3<u32> ) {
|
|
|
182
182
|
}
|
|
183
183
|
`;
|
|
184
184
|
|
|
185
|
+
const PACK_WG_SIZE = 8;
|
|
186
|
+
const PACK_WGSL = /* wgsl */`
|
|
187
|
+
@group(0) @binding(0) var src: texture_2d<f32>;
|
|
188
|
+
@group(0) @binding(1) var<storage, read_write> dst: array<vec4<f32>>;
|
|
189
|
+
@group(0) @binding(2) var<uniform> size: vec2<u32>;
|
|
190
|
+
|
|
191
|
+
@compute @workgroup_size(${PACK_WG_SIZE}, ${PACK_WG_SIZE})
|
|
192
|
+
fn main( @builtin(global_invocation_id) gid: vec3<u32> ) {
|
|
193
|
+
|
|
194
|
+
if ( gid.x >= size.x || gid.y >= size.y ) { return; }
|
|
195
|
+
dst[ gid.y * size.x + gid.x ] = textureLoad( src, gid.xy, 0 );
|
|
196
|
+
}
|
|
197
|
+
`;
|
|
198
|
+
|
|
185
199
|
export class OIDNDenoiser extends EventDispatcher {
|
|
186
200
|
|
|
187
201
|
/**
|
|
@@ -210,11 +224,10 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
210
224
|
// Cached GPU storage buffers for texture→buffer copies (reused across denoise calls)
|
|
211
225
|
this._gpuInputBuffers = { color: null, albedo: null, normal: null };
|
|
212
226
|
this._gpuInputBufferSize = { width: 0, height: 0 };
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
this.
|
|
217
|
-
this._gpuInputPaddedRowBytes = 0;
|
|
227
|
+
// Packs widths copyTextureToBuffer cannot take; see _beginPack.
|
|
228
|
+
this._packPipeline = null;
|
|
229
|
+
this._packLayout = null;
|
|
230
|
+
this._packParams = null;
|
|
218
231
|
// The autoexposure scale never leaves the GPU — _applyColorScale and the output pack
|
|
219
232
|
// both read _inputScaleBuffer directly.
|
|
220
233
|
this._lumReducePipeline = null;
|
|
@@ -671,42 +684,62 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
671
684
|
// Ensure storage buffers are sized correctly (recreate on resolution change)
|
|
672
685
|
this._ensureGPUInputBuffers( width, height );
|
|
673
686
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
//
|
|
677
|
-
//
|
|
678
|
-
|
|
679
|
-
|
|
687
|
+
this._copyInputs( device, textures, width, height );
|
|
688
|
+
|
|
689
|
+
// Autoexposure and the pre-multiply it drives, both on the GPU and both queued behind the
|
|
690
|
+
// copies above. Nothing is awaited here — an await would drain the queue before the UNet.
|
|
691
|
+
this._computeInputScale( device, width * height );
|
|
692
|
+
this._applyColorScale( device, width * height );
|
|
693
|
+
|
|
694
|
+
// A final denoise is shown tile by tile, so the tiles still to come must show this render
|
|
695
|
+
// rather than the blank or previous-view picture the output holds between runs.
|
|
696
|
+
if ( ! continuous && ! this._hasOutput ) {
|
|
697
|
+
|
|
698
|
+
this._unpackToTexture( this._gpuInputBuffers.color, width, { x: 0, y: 0, width, height }, false );
|
|
699
|
+
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Pass GPU storage buffers to oidn-web (GPUBuffer path, well-tested)
|
|
703
|
+
const config = {
|
|
704
|
+
color: { data: this._gpuInputBuffers.color, width, height },
|
|
705
|
+
albedo: { data: this._gpuInputBuffers.albedo, width, height },
|
|
706
|
+
normal: { data: this._gpuInputBuffers.normal, width, height }
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
return this._executeWithAbortGPU( config, continuous );
|
|
710
|
+
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/** Copies the render's colour, albedo and normal into the tightly packed buffers oidn-web reads. @private */
|
|
714
|
+
_copyInputs( device, textures, width, height ) {
|
|
715
|
+
|
|
716
|
+
// copyTextureToBuffer takes only 256-byte-aligned rows; any other width packs on the GPU.
|
|
680
717
|
const encoder = device.createCommandEncoder( { label: 'oidn-tex-to-buf' } );
|
|
681
718
|
const tightRowBytes = width * 16; // rgba32float
|
|
682
|
-
const
|
|
683
|
-
const needsPadStrip = paddedRowBytes > tightRowBytes;
|
|
684
|
-
const padBuf = this._gpuInputPadBuffer;
|
|
719
|
+
const pass = tightRowBytes % 256 === 0 ? null : this._beginPack( device, encoder, width, height );
|
|
685
720
|
|
|
686
721
|
const copyTex = ( tex, tightBuf ) => {
|
|
687
722
|
|
|
688
|
-
if (
|
|
723
|
+
if ( pass ) {
|
|
689
724
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
725
|
+
pass.setBindGroup( 0, device.createBindGroup( {
|
|
726
|
+
layout: this._packLayout,
|
|
727
|
+
entries: [
|
|
728
|
+
{ binding: 0, resource: tex.createView() },
|
|
729
|
+
{ binding: 1, resource: { buffer: tightBuf } },
|
|
730
|
+
{ binding: 2, resource: { buffer: this._packParams } },
|
|
731
|
+
],
|
|
732
|
+
} ) );
|
|
733
|
+
pass.dispatchWorkgroups( Math.ceil( width / PACK_WG_SIZE ), Math.ceil( height / PACK_WG_SIZE ) );
|
|
695
734
|
|
|
696
735
|
} else {
|
|
697
736
|
|
|
698
737
|
encoder.copyTextureToBuffer(
|
|
699
738
|
{ texture: tex, mipLevel: 0 },
|
|
700
|
-
{ buffer:
|
|
739
|
+
{ buffer: tightBuf, offset: 0, bytesPerRow: tightRowBytes, rowsPerImage: height },
|
|
701
740
|
{ width, height, depthOrArrayLayers: 1 }
|
|
702
741
|
);
|
|
703
742
|
|
|
704
|
-
for ( let row = 0; row < height; row ++ ) {
|
|
705
|
-
|
|
706
|
-
encoder.copyBufferToBuffer( padBuf, row * paddedRowBytes, tightBuf, row * tightRowBytes, tightRowBytes );
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
|
|
710
743
|
}
|
|
711
744
|
|
|
712
745
|
};
|
|
@@ -719,30 +752,9 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
719
752
|
// measured 0.878 -> 2.439 denoise ratio at 64 spp. Do not "fix" it.
|
|
720
753
|
copyTex( textures.normal, this._gpuInputBuffers.normal );
|
|
721
754
|
|
|
755
|
+
pass?.end();
|
|
722
756
|
device.queue.submit( [ encoder.finish() ] );
|
|
723
757
|
|
|
724
|
-
// Autoexposure and the pre-multiply it drives, both on the GPU and both queued behind the
|
|
725
|
-
// copies above. Nothing is awaited here — an await would drain the queue before the UNet.
|
|
726
|
-
this._computeInputScale( device, width * height );
|
|
727
|
-
this._applyColorScale( device, width * height );
|
|
728
|
-
|
|
729
|
-
// A final denoise is shown tile by tile, so the tiles still to come must show this render
|
|
730
|
-
// rather than the blank or previous-view picture the output holds between runs.
|
|
731
|
-
if ( ! continuous && ! this._hasOutput ) {
|
|
732
|
-
|
|
733
|
-
this._unpackToTexture( this._gpuInputBuffers.color, width, { x: 0, y: 0, width, height }, false );
|
|
734
|
-
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
// Pass GPU storage buffers to oidn-web (GPUBuffer path, well-tested)
|
|
738
|
-
const config = {
|
|
739
|
-
color: { data: this._gpuInputBuffers.color, width, height },
|
|
740
|
-
albedo: { data: this._gpuInputBuffers.albedo, width, height },
|
|
741
|
-
normal: { data: this._gpuInputBuffers.normal, width, height }
|
|
742
|
-
};
|
|
743
|
-
|
|
744
|
-
return this._executeWithAbortGPU( config, continuous );
|
|
745
|
-
|
|
746
758
|
}
|
|
747
759
|
|
|
748
760
|
/**
|
|
@@ -767,25 +779,41 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
767
779
|
this._gpuInputBuffers.normal = device.createBuffer( { label: 'oidn-in-normal', size: byteSize, usage } );
|
|
768
780
|
this._gpuInputBufferSize = { width, height };
|
|
769
781
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/** Opens the compute pass that packs unaligned rows; one dispatch per texture follows. @private */
|
|
785
|
+
_beginPack( device, encoder, width, height ) {
|
|
786
|
+
|
|
787
|
+
if ( ! this._packPipeline ) {
|
|
775
788
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
789
|
+
// Explicit: rgba32float is unfilterable, which an auto layout would not declare.
|
|
790
|
+
this._packLayout = device.createBindGroupLayout( {
|
|
791
|
+
label: 'oidn-pack',
|
|
792
|
+
entries: [
|
|
793
|
+
{ binding: 0, visibility: GPUShaderStage.COMPUTE, texture: { sampleType: 'unfilterable-float' } },
|
|
794
|
+
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
|
|
795
|
+
{ binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },
|
|
796
|
+
],
|
|
780
797
|
} );
|
|
781
|
-
this.
|
|
798
|
+
this._packPipeline = device.createComputePipeline( {
|
|
799
|
+
label: 'oidn-pack',
|
|
800
|
+
layout: device.createPipelineLayout( { bindGroupLayouts: [ this._packLayout ] } ),
|
|
801
|
+
compute: { module: device.createShaderModule( { label: 'oidn-pack', code: PACK_WGSL } ), entryPoint: 'main' },
|
|
802
|
+
} );
|
|
803
|
+
|
|
804
|
+
}
|
|
782
805
|
|
|
783
|
-
|
|
806
|
+
if ( ! this._packParams ) {
|
|
784
807
|
|
|
785
|
-
this.
|
|
808
|
+
this._packParams = device.createBuffer( { label: 'oidn-pack-size', size: 8, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST } );
|
|
809
|
+
device.queue.writeBuffer( this._packParams, 0, new Uint32Array( [ width, height ] ) );
|
|
786
810
|
|
|
787
811
|
}
|
|
788
812
|
|
|
813
|
+
const pass = encoder.beginComputePass( { label: 'oidn-pack' } );
|
|
814
|
+
pass.setPipeline( this._packPipeline );
|
|
815
|
+
return pass;
|
|
816
|
+
|
|
789
817
|
}
|
|
790
818
|
|
|
791
819
|
_destroyGPUInputBuffers() {
|
|
@@ -793,7 +821,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
793
821
|
this._gpuInputBuffers.color?.destroy();
|
|
794
822
|
this._gpuInputBuffers.albedo?.destroy();
|
|
795
823
|
this._gpuInputBuffers.normal?.destroy();
|
|
796
|
-
this.
|
|
824
|
+
this._packParams?.destroy();
|
|
797
825
|
|
|
798
826
|
// Bind groups hold the destroyed buffers; pipelines are device-scoped and survive.
|
|
799
827
|
this._colorScaleBindGroup = null;
|
|
@@ -805,8 +833,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
805
833
|
this._inputScaleBuffer?.destroy();
|
|
806
834
|
this._inputScaleBuffer = null;
|
|
807
835
|
this._gpuInputBuffers = { color: null, albedo: null, normal: null };
|
|
808
|
-
this.
|
|
809
|
-
this._gpuInputPaddedRowBytes = 0;
|
|
836
|
+
this._packParams = null;
|
|
810
837
|
this._gpuInputBufferSize = { width: 0, height: 0 };
|
|
811
838
|
|
|
812
839
|
}
|
|
@@ -994,7 +1021,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
994
1021
|
|
|
995
1022
|
const { color, albedo, normal } = this._gpuInputBuffers;
|
|
996
1023
|
const out = this._outGPUTexture ? this._outTexSize.width * this._outTexSize.height * 8 : 0; // rgba16float
|
|
997
|
-
return ( color?.size ?? 0 ) + ( albedo?.size ?? 0 ) + ( normal?.size ?? 0 ) +
|
|
1024
|
+
return ( color?.size ?? 0 ) + ( albedo?.size ?? 0 ) + ( normal?.size ?? 0 ) + out;
|
|
998
1025
|
|
|
999
1026
|
}
|
|
1000
1027
|
|