rayzee 9.1.2 → 9.2.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 +174 -15
- package/dist/{NeuralRetouch-BRquqfmZ.js → NeuralRetouch-DgnfldWO.js} +1 -1
- package/dist/{NeuralSuperRes-CbNvQjCw.js → NeuralSuperRes-Baa2w8VU.js} +72 -40
- package/dist/{ToneMapGPU-DpUZDMbQ.js → ToneMapGPU-CNgexVto.js} +75 -71
- package/dist/rayzee.es.js +4915 -4314
- package/dist/rayzee.umd.js +96 -96
- package/package.json +3 -2
- package/src/AssetConfig.js +10 -5
- package/src/EngineEvents.js +41 -1
- package/src/EngineIssues.js +3 -0
- package/src/Headless.js +23 -7
- package/src/HeadlessCanvas.js +104 -0
- package/src/Passes/OIDNDenoiser.js +29 -4
- package/src/Passes/OIDNTemporalHistory.js +7 -0
- package/src/PathTracerApp.js +432 -119
- package/src/Platform.js +76 -0
- package/src/Processor/AssetLoader.js +66 -4
- package/src/Processor/BVHBuilder.js +2 -1
- package/src/Processor/EquirectHDRInfo.js +2 -1
- package/src/Processor/HostMemory.js +14 -0
- package/src/Processor/PBRT/PBRTSceneBuilder.js +26 -0
- package/src/Processor/ParallelBVHBuilder.js +7 -6
- package/src/Processor/PlatformImageLoader.js +118 -0
- package/src/Processor/ResampleRGBA8.js +102 -0
- package/src/Processor/SceneProcessor.js +7 -6
- package/src/Processor/TextureCreator.js +162 -13
- package/src/Processor/Workers/PackWorker.js +28 -0
- package/src/Stages/Compositor.js +25 -6
- package/src/Stages/PathTracer.js +142 -25
- package/src/Stages/PathTracerStage.js +6 -96
- package/src/TSL/DebugKernel.js +1 -1
- package/src/TSL/GenerateKernel.js +2 -5
- package/src/TSL/Random.js +3 -120
- package/src/TSL/ShadeKernel.js +3 -3
- package/src/index.js +2 -1
- package/src/managers/CameraManager.js +2 -2
- package/src/managers/DenoisingManager.js +76 -7
- package/src/managers/GoboManager.js +8 -1
- package/src/managers/IESManager.js +13 -2
- package/src/managers/InteractionManager.js +1 -1
- package/src/managers/LightManager.js +31 -0
- package/src/managers/OverlayManager.js +11 -1
- package/src/managers/VideoRenderManager.js +8 -0
- package/src/node/NodeWorker.js +144 -0
- package/src/node/index.js +9 -0
- package/src/node/nodePlatform.js +86 -0
|
@@ -109,6 +109,37 @@ export class LightManager extends EventDispatcher {
|
|
|
109
109
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* The light the path tracer traces, by UUID from getAll(). A model's own lights are copied in
|
|
114
|
+
* at load, so editing those does nothing: edit this one, then call sync().
|
|
115
|
+
* @param {string} uuid
|
|
116
|
+
* @returns {?import('three').Light}
|
|
117
|
+
*/
|
|
118
|
+
getLight( uuid ) {
|
|
119
|
+
|
|
120
|
+
const light = this.scene.getObjectByProperty( 'uuid', uuid );
|
|
121
|
+
return light?.isLight ? light : null;
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Sets a traced light's intensity (watts, or W/m² for directional — see the README) and uploads it.
|
|
127
|
+
* @param {string} uuid
|
|
128
|
+
* @param {number} intensity
|
|
129
|
+
* @returns {boolean} false when no traced light has that UUID
|
|
130
|
+
*/
|
|
131
|
+
setIntensity( uuid, intensity ) {
|
|
132
|
+
|
|
133
|
+
const light = this.getLight( uuid );
|
|
134
|
+
if ( ! light ) return false;
|
|
135
|
+
|
|
136
|
+
light.intensity = intensity;
|
|
137
|
+
this.updateLights();
|
|
138
|
+
this._onReset?.();
|
|
139
|
+
return true;
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
112
143
|
/**
|
|
113
144
|
* Removes a light by UUID.
|
|
114
145
|
* @param {string} uuid
|
|
@@ -3,6 +3,7 @@ import { OutlineHelper } from './helpers/OutlineHelper.js';
|
|
|
3
3
|
import { ViewOverlayRenderer } from './helpers/ViewOverlayRenderer.js';
|
|
4
4
|
import { createOverlayCanvas, viewPixelSize } from './helpers/overlaySurface.js';
|
|
5
5
|
import { createLogger } from '../utils/Logger.js';
|
|
6
|
+
import { EngineEvents } from '../EngineEvents.js';
|
|
6
7
|
|
|
7
8
|
const log = createLogger( 'overlay' );
|
|
8
9
|
|
|
@@ -84,6 +85,15 @@ export class OverlayManager {
|
|
|
84
85
|
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
/** The overlay surface while it is shown: one presented image and three's output buffers. */
|
|
89
|
+
gpuResources() {
|
|
90
|
+
|
|
91
|
+
const view = this._viewOverlay;
|
|
92
|
+
if ( ! view?.isVisible ) return [];
|
|
93
|
+
return [ { bytes: view.canvas.width * view.canvas.height * 4 }, ...( view.renderer?._frameBufferTargets?.values() ?? [] ) ];
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
87
97
|
/**
|
|
88
98
|
* Sets the SceneHelpers instance used for 3D overlay rendering.
|
|
89
99
|
* @param {import('../SceneHelpers.js').SceneHelpers} helperScene
|
|
@@ -149,7 +159,7 @@ export class OverlayManager {
|
|
|
149
159
|
|
|
150
160
|
tileHelper.setRenderSize( renderWidth || 1, renderHeight || 1 );
|
|
151
161
|
|
|
152
|
-
app.addEventListener(
|
|
162
|
+
app.addEventListener( EngineEvents.RESOLUTION_CHANGED, ( e ) => {
|
|
153
163
|
|
|
154
164
|
tileHelper.setRenderSize( e.width, e.height );
|
|
155
165
|
|
|
@@ -219,6 +219,14 @@ export class VideoRenderManager {
|
|
|
219
219
|
|
|
220
220
|
while ( ! pathTracer.isComplete && ! this._cancelled ) {
|
|
221
221
|
|
|
222
|
+
const wait = pathTracer.readbackWait();
|
|
223
|
+
if ( wait ) {
|
|
224
|
+
|
|
225
|
+
await wait;
|
|
226
|
+
continue;
|
|
227
|
+
|
|
228
|
+
}
|
|
229
|
+
|
|
222
230
|
app.cameraManager.camera.updateMatrixWorld();
|
|
223
231
|
app.pipeline.render();
|
|
224
232
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Worker as ThreadWorker } from 'node:worker_threads';
|
|
2
|
+
import { Buffer, resolveObjectURL } from 'node:buffer';
|
|
3
|
+
|
|
4
|
+
// Evaluated inside the thread: a dedicated worker's `self` over parentPort, then the module. Messages
|
|
5
|
+
// that arrive before the module has installed its handler are held, not dropped. No require(): a
|
|
6
|
+
// thread inherits --input-type=module, which evaluates this as an ES module.
|
|
7
|
+
const BOOTSTRAP = `
|
|
8
|
+
const { parentPort } = process.getBuiltinModule( 'node:worker_threads' );
|
|
9
|
+
const listeners = { message: new Set(), error: new Set(), messageerror: new Set() };
|
|
10
|
+
const held = [];
|
|
11
|
+
let ready = false;
|
|
12
|
+
|
|
13
|
+
const transferable = ( list ) => ( list ?? [] ).filter( ( item ) => item instanceof ArrayBuffer || item?.constructor?.name === 'MessagePort' );
|
|
14
|
+
const deliver = ( data ) => {
|
|
15
|
+
|
|
16
|
+
const event = { type: 'message', data };
|
|
17
|
+
globalThis.onmessage?.( event );
|
|
18
|
+
for ( const fn of listeners.message ) fn( event );
|
|
19
|
+
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
globalThis.self = globalThis;
|
|
23
|
+
globalThis.onmessage = null;
|
|
24
|
+
globalThis.postMessage = ( data, transfer ) => parentPort.postMessage( data, transferable( Array.isArray( transfer ) ? transfer : transfer?.transfer ) );
|
|
25
|
+
globalThis.addEventListener = ( type, fn ) => listeners[ type ]?.add( fn );
|
|
26
|
+
globalThis.removeEventListener = ( type, fn ) => listeners[ type ]?.delete( fn );
|
|
27
|
+
globalThis.close = () => process.exit( 0 );
|
|
28
|
+
|
|
29
|
+
parentPort.on( 'message', ( message ) => {
|
|
30
|
+
|
|
31
|
+
if ( message?.__rayzeeModule !== undefined ) {
|
|
32
|
+
|
|
33
|
+
const release = () => {
|
|
34
|
+
|
|
35
|
+
ready = true;
|
|
36
|
+
for ( const data of held.splice( 0 ) ) deliver( data );
|
|
37
|
+
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// A classic worker is a script in the global scope (three's Draco decoder is one).
|
|
41
|
+
if ( message.classic ) {
|
|
42
|
+
|
|
43
|
+
process.getBuiltinModule( 'node:vm' ).runInThisContext( message.__rayzeeModule );
|
|
44
|
+
release();
|
|
45
|
+
|
|
46
|
+
} else {
|
|
47
|
+
|
|
48
|
+
import( 'data:text/javascript;charset=utf-8,' + encodeURIComponent( message.__rayzeeModule ) ).then( release, ( error ) => { throw error; } );
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return;
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if ( ready ) deliver( message );
|
|
57
|
+
else held.push( message );
|
|
58
|
+
|
|
59
|
+
} );
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
const DATA_URL = /^data:([^,]*?)(;base64)?,(.*)$/s;
|
|
63
|
+
|
|
64
|
+
function readModuleSource( url ) {
|
|
65
|
+
|
|
66
|
+
const href = String( url );
|
|
67
|
+
|
|
68
|
+
if ( href.startsWith( 'blob:' ) ) {
|
|
69
|
+
|
|
70
|
+
const blob = resolveObjectURL( href );
|
|
71
|
+
if ( ! blob ) throw new Error( `NodeWorker: no blob behind ${href}` );
|
|
72
|
+
return blob.text();
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const match = DATA_URL.exec( href );
|
|
77
|
+
if ( ! match ) throw new Error( `NodeWorker: only data: and blob: worker URLs are supported, got ${href.slice( 0, 64 )}` );
|
|
78
|
+
const [ , , base64, body ] = match;
|
|
79
|
+
return base64 ? Buffer.from( body, 'base64' ).toString( 'utf8' ) : decodeURIComponent( body );
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The Web Worker API over `worker_threads`, for hosts with no browser (see nodePlatform). It runs
|
|
85
|
+
* `data:` and `blob:` workers, module or classic: the engine's own, which the published build inlines,
|
|
86
|
+
* and three.js's. Those call the global `Worker`, so a model using Draco or KTX2 also needs
|
|
87
|
+
* `globalThis.Worker = NodeWorker`. ArrayBuffers transfer and
|
|
88
|
+
* SharedArrayBuffers share as in a browser; an ImageBitmap cannot exist here to transfer.
|
|
89
|
+
*/
|
|
90
|
+
export class NodeWorker {
|
|
91
|
+
|
|
92
|
+
constructor( url, options = {} ) {
|
|
93
|
+
|
|
94
|
+
this.onmessage = null;
|
|
95
|
+
this.onerror = null;
|
|
96
|
+
this.onmessageerror = null;
|
|
97
|
+
this._listeners = { message: new Set(), error: new Set(), messageerror: new Set() };
|
|
98
|
+
|
|
99
|
+
this._thread = new ThreadWorker( BOOTSTRAP, { eval: true, name: options.name } );
|
|
100
|
+
this._thread.on( 'message', ( data ) => this._emit( 'message', { type: 'message', data } ) );
|
|
101
|
+
this._thread.on( 'messageerror', ( error ) => this._emit( 'messageerror', { type: 'messageerror', error } ) );
|
|
102
|
+
this._thread.on( 'error', ( error ) => this._emit( 'error', { type: 'error', error, message: error?.message ?? String( error ) } ) );
|
|
103
|
+
|
|
104
|
+
// Anything the caller posts before the module lands is held in the thread, in order.
|
|
105
|
+
const source = readModuleSource( url );
|
|
106
|
+
const load = ( code ) => this._thread.postMessage( { __rayzeeModule: code, classic: options.type !== 'module' } );
|
|
107
|
+
if ( typeof source === 'string' ) load( source );
|
|
108
|
+
else source.then( load, ( error ) => this._emit( 'error', { type: 'error', error, message: error.message } ) );
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
postMessage( data, transfer ) {
|
|
113
|
+
|
|
114
|
+
const list = Array.isArray( transfer ) ? transfer : transfer?.transfer;
|
|
115
|
+
this._thread.postMessage( data, ( list ?? [] ).filter( ( item ) => item instanceof ArrayBuffer ) );
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
addEventListener( type, fn ) {
|
|
120
|
+
|
|
121
|
+
this._listeners[ type ]?.add( fn );
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
removeEventListener( type, fn ) {
|
|
126
|
+
|
|
127
|
+
this._listeners[ type ]?.delete( fn );
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
terminate() {
|
|
132
|
+
|
|
133
|
+
this._thread.terminate();
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
_emit( type, event ) {
|
|
138
|
+
|
|
139
|
+
this[ `on${type}` ]?.( event );
|
|
140
|
+
for ( const fn of this._listeners[ type ] ) fn( event );
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-only helpers, kept out of the main entry so a browser bundle never sees `node:` imports.
|
|
3
|
+
*
|
|
4
|
+
* import { configurePlatform, openHeadless } from 'rayzee';
|
|
5
|
+
* import { nodePlatform } from 'rayzee/node';
|
|
6
|
+
* configurePlatform( nodePlatform( { decodeImage } ) );
|
|
7
|
+
*/
|
|
8
|
+
export { NodeWorker } from './NodeWorker.js';
|
|
9
|
+
export { nodePlatform } from './nodePlatform.js';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { NodeWorker } from './NodeWorker.js';
|
|
2
|
+
|
|
3
|
+
// three.js's FileLoader constructs one on every chunk of a streamed download.
|
|
4
|
+
class NodeProgressEvent extends Event {
|
|
5
|
+
|
|
6
|
+
constructor( type, { lengthComputable = false, loaded = 0, total = 0 } = {} ) {
|
|
7
|
+
|
|
8
|
+
super( type );
|
|
9
|
+
this.lengthComputable = lengthComputable;
|
|
10
|
+
this.loaded = loaded;
|
|
11
|
+
this.total = total;
|
|
12
|
+
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const SHARED_UPLOAD_CHUNK = 64 * 2 ** 20;
|
|
18
|
+
const WRAPPED = Symbol.for( 'rayzee.sharedUploads' );
|
|
19
|
+
|
|
20
|
+
function sharedBytes( data, elementOffset = 0, elementCount = undefined ) {
|
|
21
|
+
|
|
22
|
+
const isView = ArrayBuffer.isView( data );
|
|
23
|
+
const backing = isView ? data.buffer : data;
|
|
24
|
+
if ( ! ( backing instanceof SharedArrayBuffer ) ) return null;
|
|
25
|
+
|
|
26
|
+
const size = isView ? data.BYTES_PER_ELEMENT ?? 1 : 1;
|
|
27
|
+
const start = ( isView ? data.byteOffset : 0 ) + elementOffset * size;
|
|
28
|
+
const length = elementCount !== undefined ? elementCount * size : ( isView ? data.byteLength : backing.byteLength ) - elementOffset * size;
|
|
29
|
+
return new Uint8Array( backing, start, length );
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// dawn.node (webgpu 0.6.1) segfaults writing from a SharedArrayBuffer, which WebGPU allows and the
|
|
34
|
+
// engine's triangle and BVH stores are. Such uploads are copied out first, 64 MB at a time.
|
|
35
|
+
function copySharedUploads() {
|
|
36
|
+
|
|
37
|
+
const Queue = globalThis.GPUQueue;
|
|
38
|
+
if ( ! Queue ) throw new Error( 'nodePlatform(): install the webgpu globals first — Object.assign( globalThis, globals )' );
|
|
39
|
+
if ( Queue.prototype[ WRAPPED ] ) return;
|
|
40
|
+
|
|
41
|
+
const { writeBuffer, writeTexture } = Queue.prototype;
|
|
42
|
+
|
|
43
|
+
Queue.prototype.writeBuffer = function ( ...args ) {
|
|
44
|
+
|
|
45
|
+
const [ buffer, bufferOffset, data, dataOffset, size ] = args;
|
|
46
|
+
const bytes = sharedBytes( data, dataOffset ?? 0, size );
|
|
47
|
+
if ( ! bytes ) return writeBuffer.apply( this, args );
|
|
48
|
+
|
|
49
|
+
for ( let at = 0; at < bytes.length; at += SHARED_UPLOAD_CHUNK ) {
|
|
50
|
+
|
|
51
|
+
writeBuffer.call( this, buffer, bufferOffset + at, bytes.slice( at, at + SHARED_UPLOAD_CHUNK ) );
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
Queue.prototype.writeTexture = function ( ...args ) {
|
|
58
|
+
|
|
59
|
+
const bytes = sharedBytes( args[ 1 ] );
|
|
60
|
+
if ( bytes ) args[ 1 ] = bytes.slice();
|
|
61
|
+
return writeTexture.apply( this, args );
|
|
62
|
+
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
Queue.prototype[ WRAPPED ] = true;
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Everything the engine needs from Node, for `configurePlatform( nodePlatform( { decodeImage } ) )`:
|
|
71
|
+
* the worker class and the host's image decoder — the engine carries none. It also defines
|
|
72
|
+
* `ProgressEvent` where the runtime lacks it, the one browser class three.js's loaders construct.
|
|
73
|
+
* WebGPU itself is the host's: install the `webgpu` package's globals and `navigator.gpu` first, since
|
|
74
|
+
* its queue is wrapped here to copy uploads out of shared memory.
|
|
75
|
+
*
|
|
76
|
+
* @param {Object} [options]
|
|
77
|
+
* @param {( bytes: Uint8Array, mimeType: string ) => Promise<{data: Uint8Array, width: number, height: number}>} [options.decodeImage]
|
|
78
|
+
* PNG/JPEG/WebP bytes to RGBA8 pixels, top row first. Without it a model's textures cannot load.
|
|
79
|
+
*/
|
|
80
|
+
export function nodePlatform( { decodeImage } = {} ) {
|
|
81
|
+
|
|
82
|
+
globalThis.ProgressEvent ??= NodeProgressEvent;
|
|
83
|
+
copySharedUploads();
|
|
84
|
+
return { Worker: NodeWorker, decodeImage: decodeImage ?? null };
|
|
85
|
+
|
|
86
|
+
}
|