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.
Files changed (46) hide show
  1. package/README.md +174 -15
  2. package/dist/{NeuralRetouch-BRquqfmZ.js → NeuralRetouch-DgnfldWO.js} +1 -1
  3. package/dist/{NeuralSuperRes-CbNvQjCw.js → NeuralSuperRes-Baa2w8VU.js} +72 -40
  4. package/dist/{ToneMapGPU-DpUZDMbQ.js → ToneMapGPU-CNgexVto.js} +75 -71
  5. package/dist/rayzee.es.js +4915 -4314
  6. package/dist/rayzee.umd.js +96 -96
  7. package/package.json +3 -2
  8. package/src/AssetConfig.js +10 -5
  9. package/src/EngineEvents.js +41 -1
  10. package/src/EngineIssues.js +3 -0
  11. package/src/Headless.js +23 -7
  12. package/src/HeadlessCanvas.js +104 -0
  13. package/src/Passes/OIDNDenoiser.js +29 -4
  14. package/src/Passes/OIDNTemporalHistory.js +7 -0
  15. package/src/PathTracerApp.js +432 -119
  16. package/src/Platform.js +76 -0
  17. package/src/Processor/AssetLoader.js +66 -4
  18. package/src/Processor/BVHBuilder.js +2 -1
  19. package/src/Processor/EquirectHDRInfo.js +2 -1
  20. package/src/Processor/HostMemory.js +14 -0
  21. package/src/Processor/PBRT/PBRTSceneBuilder.js +26 -0
  22. package/src/Processor/ParallelBVHBuilder.js +7 -6
  23. package/src/Processor/PlatformImageLoader.js +118 -0
  24. package/src/Processor/ResampleRGBA8.js +102 -0
  25. package/src/Processor/SceneProcessor.js +7 -6
  26. package/src/Processor/TextureCreator.js +162 -13
  27. package/src/Processor/Workers/PackWorker.js +28 -0
  28. package/src/Stages/Compositor.js +25 -6
  29. package/src/Stages/PathTracer.js +142 -25
  30. package/src/Stages/PathTracerStage.js +6 -96
  31. package/src/TSL/DebugKernel.js +1 -1
  32. package/src/TSL/GenerateKernel.js +2 -5
  33. package/src/TSL/Random.js +3 -120
  34. package/src/TSL/ShadeKernel.js +3 -3
  35. package/src/index.js +2 -1
  36. package/src/managers/CameraManager.js +2 -2
  37. package/src/managers/DenoisingManager.js +76 -7
  38. package/src/managers/GoboManager.js +8 -1
  39. package/src/managers/IESManager.js +13 -2
  40. package/src/managers/InteractionManager.js +1 -1
  41. package/src/managers/LightManager.js +31 -0
  42. package/src/managers/OverlayManager.js +11 -1
  43. package/src/managers/VideoRenderManager.js +8 -0
  44. package/src/node/NodeWorker.js +144 -0
  45. package/src/node/index.js +9 -0
  46. 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( 'resolution_changed', ( e ) => {
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
+ }