rayzee 5.11.0 → 6.0.1

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 (44) hide show
  1. package/README.md +81 -24
  2. package/dist/assets/AIUpscalerWorker-AXN-lKWN.js +2 -0
  3. package/dist/assets/AIUpscalerWorker-AXN-lKWN.js.map +1 -0
  4. package/dist/rayzee.es.js +1238 -1825
  5. package/dist/rayzee.es.js.map +1 -1
  6. package/dist/rayzee.umd.js +50 -74
  7. package/dist/rayzee.umd.js.map +1 -1
  8. package/package.json +1 -4
  9. package/src/AssetConfig.js +56 -0
  10. package/src/EngineDefaults.js +5 -3
  11. package/src/EngineEvents.js +1 -0
  12. package/src/Passes/AIUpscaler.js +44 -22
  13. package/src/Passes/OIDNDenoiser.js +4 -104
  14. package/src/PathTracerApp.js +54 -65
  15. package/src/Processor/AssetLoader.js +5 -2
  16. package/src/Processor/Workers/AIUpscalerWorker.js +21 -6
  17. package/src/Stages/ASVGF.js +6 -27
  18. package/src/Stages/AdaptiveSampling.js +10 -26
  19. package/src/Stages/PathTracer.js +4 -5
  20. package/src/TSL/BVHTraversal.js +3 -30
  21. package/src/TSL/Clearcoat.js +1 -6
  22. package/src/TSL/Common.js +1 -14
  23. package/src/TSL/Debugger.js +0 -12
  24. package/src/TSL/EmissiveSampling.js +0 -16
  25. package/src/TSL/Environment.js +0 -7
  26. package/src/TSL/LightsDirect.js +3 -379
  27. package/src/TSL/LightsIndirect.js +2 -1
  28. package/src/TSL/LightsSampling.js +0 -171
  29. package/src/TSL/MaterialEvaluation.js +3 -103
  30. package/src/TSL/MaterialProperties.js +1 -56
  31. package/src/TSL/MaterialSampling.js +2 -284
  32. package/src/TSL/MaterialTransmission.js +80 -276
  33. package/src/TSL/PathTracerCore.js +8 -1
  34. package/src/TSL/Random.js +0 -23
  35. package/src/TSL/Struct.js +0 -21
  36. package/src/TSL/TextureSampling.js +0 -69
  37. package/src/index.js +5 -2
  38. package/src/managers/DenoisingManager.js +13 -5
  39. package/src/managers/VideoRenderManager.js +4 -4
  40. package/dist/assets/AIUpscalerWorker-D58dcMrY.js +0 -2
  41. package/dist/assets/AIUpscalerWorker-D58dcMrY.js.map +0 -1
  42. package/src/Processor/createRenderTargetHelper.js +0 -521
  43. package/src/TSL/RayIntersection.js +0 -162
  44. package/src/managers/helpers/StatsHelper.js +0 -45
package/src/index.js CHANGED
@@ -14,6 +14,9 @@ export { PathTracerApp } from './PathTracerApp.js';
14
14
  // Event types
15
15
  export { EngineEvents } from './EngineEvents.js';
16
16
 
17
+ // Asset URL / cache namespace overrides (call before constructing PathTracerApp)
18
+ export { configureAssets, getAssetConfig } from './AssetConfig.js';
19
+
17
20
  // Configuration defaults and presets
18
21
  export {
19
22
  ENGINE_DEFAULTS,
@@ -28,8 +31,8 @@ export {
28
31
  TEXTURE_CONSTANTS,
29
32
  DEFAULT_TEXTURE_MATRIX,
30
33
  MEMORY_CONSTANTS,
31
- FINAL_RENDER_CONFIG,
32
- PREVIEW_RENDER_CONFIG,
34
+ PRODUCTION_RENDER_CONFIG,
35
+ INTERACTIVE_RENDER_CONFIG,
33
36
  } from './EngineDefaults.js';
34
37
 
35
38
  // Settings & managers (for advanced consumers)
@@ -325,7 +325,7 @@ export class DenoisingManager extends EventDispatcher {
325
325
  if ( s.adaptiveSampling ) {
326
326
 
327
327
  s.adaptiveSampling.enabled = enabled;
328
- s.adaptiveSampling.toggleHelper( false );
328
+ s.adaptiveSampling.setHeatmapEnabled( false );
329
329
 
330
330
  }
331
331
 
@@ -517,10 +517,14 @@ export class DenoisingManager extends EventDispatcher {
517
517
 
518
518
  }
519
519
 
520
- /** Toggles the ASVGF heatmap debug overlay. */
520
+ /**
521
+ * Toggle the ASVGF heatmap compute pass. When enabled, the stage writes
522
+ * the heatmap to its public `heatmapTarget` RenderTarget — the host is
523
+ * responsible for rendering it.
524
+ */
521
525
  toggleASVGFHeatmap( enabled ) {
522
526
 
523
- this._stages.asvgf?.toggleHeatmap?.( enabled );
527
+ this._stages.asvgf?.setHeatmapEnabled?.( enabled );
524
528
 
525
529
  }
526
530
 
@@ -577,10 +581,14 @@ export class DenoisingManager extends EventDispatcher {
577
581
 
578
582
  }
579
583
 
580
- /** Toggles the adaptive sampling debug helper. */
584
+ /**
585
+ * Toggle the AdaptiveSampling heatmap compute pass. When enabled, the
586
+ * stage writes the heatmap to its public `heatmapTarget` RenderTarget —
587
+ * the host is responsible for rendering it.
588
+ */
581
589
  toggleAdaptiveSamplingHelper( enabled ) {
582
590
 
583
- this._stages.adaptiveSampling?.toggleHelper( enabled );
591
+ this._stages.adaptiveSampling?.setHeatmapEnabled( enabled );
584
592
 
585
593
  }
586
594
 
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { EngineEvents } from '../EngineEvents.js';
10
- import { FINAL_RENDER_CONFIG } from '../EngineDefaults.js';
10
+ import { PRODUCTION_RENDER_CONFIG } from '../EngineDefaults.js';
11
11
  import { updateStats, getDisplaySamples } from '../Processor/utils.js';
12
12
 
13
13
  export class VideoRenderManager {
@@ -26,7 +26,7 @@ export class VideoRenderManager {
26
26
  * @param {Object} options
27
27
  * @param {number} [options.clipIndex=0] - Animation clip index
28
28
  * @param {number} [options.fps=30] - Output frame rate
29
- * @param {number} [options.samplesPerFrame] - SPP per frame (defaults to FINAL_RENDER_CONFIG.maxSamples)
29
+ * @param {number} [options.samplesPerFrame] - SPP per frame (defaults to PRODUCTION_RENDER_CONFIG.maxSamples)
30
30
  * @param {boolean} [options.enableOIDN=true] - Run OIDN denoiser per frame
31
31
  * @param {number} [options.speed=1] - Playback speed multiplier (maps video time to animation time)
32
32
  * @param {number} [options.totalFrames] - Override total frame count (for looped animations)
@@ -40,7 +40,7 @@ export class VideoRenderManager {
40
40
  clipIndex = 0,
41
41
  fps = 30,
42
42
  speed = 1,
43
- samplesPerFrame = FINAL_RENDER_CONFIG.maxSamples,
43
+ samplesPerFrame = PRODUCTION_RENDER_CONFIG.maxSamples,
44
44
  enableOIDN = true,
45
45
  onFrame,
46
46
  onProgress,
@@ -80,7 +80,7 @@ export class VideoRenderManager {
80
80
  app.stopAnimation();
81
81
 
82
82
  // Configure for high-quality offline rendering
83
- app.configureForMode( 'final-render' );
83
+ app.configureForMode( 'production' );
84
84
 
85
85
  // Override samples per frame
86
86
  app.settings.setMany( { maxSamples: samplesPerFrame }, { silent: true } );
@@ -1,2 +0,0 @@
1
- (function(){let e=null;async function t(){return e||(e=await import(`https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.webgpu.bundle.min.mjs`),e.env.wasm.wasmPaths=`https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/`,e.env.logLevel=`error`,e)}let n=`models`,r=null,i=null;function a(){return new Promise((e,t)=>{let r=indexedDB.open(`ai-upscaler-models`,1);r.onupgradeneeded=()=>r.result.createObjectStore(n),r.onsuccess=()=>e(r.result),r.onerror=()=>t(r.error)})}async function o(e){try{let t=await a();return await new Promise((r,i)=>{let a=t.transaction(n,`readonly`).objectStore(n).get(e);a.onsuccess=()=>r(a.result||null),a.onerror=()=>i(a.error)})}catch{return null}}async function s(e,t){try{let r=await a();await new Promise((i,a)=>{let o=r.transaction(n,`readwrite`);o.objectStore(n).put(t,e),o.oncomplete=()=>i(),o.onerror=()=>a(o.error)})}catch{}}async function c(e){let t=await o(e);if(t)return console.log(`AI Upscaler Worker: model loaded from cache (${(t.byteLength/1024/1024).toFixed(1)}MB)`),t;let n=await fetch(e);if(!n.ok)throw Error(`Failed to fetch model: ${n.status}`);let r=await n.arrayBuffer();return s(e,r.slice(0)),r}async function l(e,n){if(r&&i===e){self.postMessage({type:`loaded`,backend:`webgpu`});return}r&&=(await r.release(),null);let[a,o]=await Promise.all([c(e),t()]);r=await o.InferenceSession.create(a,n),i=e;let s=512;try{let e=await navigator.gpu?.requestAdapter(),t=await e?.requestAdapterInfo?.()||e?.info,n=/apple|swiftshader|llvmpipe/i.test(t?.vendor||``)||/apple|swiftshader/i.test(t?.architecture||``),r=t?.device?.toLowerCase?.()?.includes(`integrated`)||/intel.*iris|intel.*uhd|intel.*hd|amd.*vega|radeon.*graphics/i.test(t?.description||``);s=n?128:r?256:512,console.log(`AI Upscaler Worker: GPU="${t?.description||t?.device||`unknown`}", tileSize=${s}`)}catch{}let l=(a.byteLength/1024/1024).toFixed(1);console.log(`AI Upscaler Worker: model loaded (${l}MB), backend: webgpu`),self.postMessage({type:`loaded`,backend:`webgpu`,tileSize:s})}async function u(e,n,i,a){let o=await t(),s=r.inputNames[0],c=r.outputNames[0],l=new o.Tensor(`float32`,e,[1,3,i,n]),u=(await r.run({[s]:l}))[c].data;self.postMessage({type:`inferred`,outputData:u,id:a},[u.buffer])}self.onmessage=async e=>{let{type:t}=e.data;try{t===`load`?await l(e.data.url,e.data.sessionOptions):t===`infer`?await u(e.data.tileData,e.data.width,e.data.height,e.data.id):t===`dispose`&&r&&(await r.release(),r=null,i=null)}catch(t){self.postMessage({type:`error`,message:t.message,id:e.data?.id})}}})();
2
- //# sourceMappingURL=AIUpscalerWorker-D58dcMrY.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AIUpscalerWorker-D58dcMrY.js","names":[],"sources":["../../src/Processor/Workers/AIUpscalerWorker.js"],"sourcesContent":["/**\n * Web Worker for AI Upscaler inference.\n * Handles ONNX model loading and tile-based inference off the main thread.\n *\n * Messages:\n * Main → Worker:\n * { type: 'load', url, sessionOptions } — load/switch model\n * { type: 'infer', tileData, width, height, id } — run inference on a tile\n * { type: 'dispose' } — release session\n *\n * Worker → Main:\n * { type: 'loaded', backend }\n * { type: 'inferred', outputData, id }\n * { type: 'error', message, id? }\n */\n\n// Loaded lazily via CDN to avoid bundling the 69 MB onnxruntime-web package\nconst ORT_CDN_URL = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.webgpu.bundle.min.mjs';\n\nlet ort = null;\n\nasync function getOrt() {\n\n\tif ( ort ) return ort;\n\n\tort = await import( /* @vite-ignore */ ORT_CDN_URL );\n\n\t// WASM paths for CDN delivery — WebGPU EP still uses WASM for lightweight shape ops\n\tort.env.wasm.wasmPaths = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/';\n\tort.env.logLevel = 'error';\n\n\treturn ort;\n\n}\n\nconst IDB_NAME = 'ai-upscaler-models';\nconst IDB_STORE = 'models';\n\nlet session = null;\nlet currentModelUrl = null;\n\n// ─── IndexedDB Model Cache ───────────────────────────────────────────────────\n\nfunction openDB() {\n\n\treturn new Promise( ( resolve, reject ) => {\n\n\t\tconst req = indexedDB.open( IDB_NAME, 1 );\n\t\treq.onupgradeneeded = () => req.result.createObjectStore( IDB_STORE );\n\t\treq.onsuccess = () => resolve( req.result );\n\t\treq.onerror = () => reject( req.error );\n\n\t} );\n\n}\n\nasync function getCachedModel( url ) {\n\n\ttry {\n\n\t\tconst db = await openDB();\n\t\treturn await new Promise( ( resolve, reject ) => {\n\n\t\t\tconst tx = db.transaction( IDB_STORE, 'readonly' );\n\t\t\tconst req = tx.objectStore( IDB_STORE ).get( url );\n\t\t\treq.onsuccess = () => resolve( req.result || null );\n\t\t\treq.onerror = () => reject( req.error );\n\n\t\t} );\n\n\t} catch {\n\n\t\treturn null;\n\n\t}\n\n}\n\nasync function cacheModel( url, buffer ) {\n\n\ttry {\n\n\t\tconst db = await openDB();\n\t\tawait new Promise( ( resolve, reject ) => {\n\n\t\t\tconst tx = db.transaction( IDB_STORE, 'readwrite' );\n\t\t\ttx.objectStore( IDB_STORE ).put( buffer, url );\n\t\t\ttx.oncomplete = () => resolve();\n\t\t\ttx.onerror = () => reject( tx.error );\n\n\t\t} );\n\n\t} catch {\n\n\t\t// Cache write failure is non-fatal\n\t}\n\n}\n\n// ─── Model Loading ───────────────────────────────────────────────────────────\n\nasync function fetchModel( url ) {\n\n\t// Try IndexedDB cache first\n\tconst cached = await getCachedModel( url );\n\tif ( cached ) {\n\n\t\tconsole.log( `AI Upscaler Worker: model loaded from cache (${( cached.byteLength / 1024 / 1024 ).toFixed( 1 )}MB)` );\n\t\treturn cached;\n\n\t}\n\n\t// Network fetch + cache\n\tconst response = await fetch( url );\n\tif ( ! response.ok ) throw new Error( `Failed to fetch model: ${response.status}` );\n\tconst buffer = await response.arrayBuffer();\n\n\t// Cache in background (don't block session creation)\n\tcacheModel( url, buffer.slice( 0 ) );\n\n\treturn buffer;\n\n}\n\nasync function loadModel( url, sessionOptions ) {\n\n\tif ( session && currentModelUrl === url ) {\n\n\t\tconst backend = 'webgpu';\n\t\tself.postMessage( { type: 'loaded', backend } );\n\t\treturn;\n\n\t}\n\n\t// Dispose previous session\n\tif ( session ) {\n\n\t\tawait session.release();\n\t\tsession = null;\n\n\t}\n\n\tconst [ modelBuffer, ortLib ] = await Promise.all( [ fetchModel( url ), getOrt() ] );\n\n\tsession = await ortLib.InferenceSession.create( modelBuffer, sessionOptions );\n\tcurrentModelUrl = url;\n\n\t// Detect GPU and recommend tile size based on device type\n\tlet tileSize = 512; // default\n\ttry {\n\n\t\tconst adapter = await navigator.gpu?.requestAdapter();\n\t\tconst info = await adapter?.requestAdapterInfo?.() || adapter?.info;\n\t\tconst isMobile = /apple|swiftshader|llvmpipe/i.test( info?.vendor || '' )\n\t\t\t|| /apple|swiftshader/i.test( info?.architecture || '' );\n\t\tconst isIntegrated = info?.device?.toLowerCase?.()?.includes( 'integrated' )\n\t\t\t|| /intel.*iris|intel.*uhd|intel.*hd|amd.*vega|radeon.*graphics/i.test( info?.description || '' );\n\n\t\tif ( isMobile ) {\n\n\t\t\ttileSize = 128;\n\n\t\t} else if ( isIntegrated ) {\n\n\t\t\ttileSize = 256;\n\n\t\t} else {\n\n\t\t\ttileSize = 512;\n\n\t\t}\n\n\t\tconsole.log( `AI Upscaler Worker: GPU=\"${info?.description || info?.device || 'unknown'}\", tileSize=${tileSize}` );\n\n\t} catch { /* fallback to default */ }\n\n\tconst sizeMB = ( modelBuffer.byteLength / 1024 / 1024 ).toFixed( 1 );\n\tconsole.log( `AI Upscaler Worker: model loaded (${sizeMB}MB), backend: webgpu` );\n\n\tself.postMessage( { type: 'loaded', backend: 'webgpu', tileSize } );\n\n}\n\nasync function inferTile( tileData, width, height, id ) {\n\n\tconst ortLib = await getOrt();\n\tconst inputName = session.inputNames[ 0 ];\n\tconst outputName = session.outputNames[ 0 ];\n\tconst inputTensor = new ortLib.Tensor( 'float32', tileData, [ 1, 3, height, width ] );\n\n\tconst results = await session.run( { [ inputName ]: inputTensor } );\n\tconst outputData = results[ outputName ].data;\n\n\t// Transfer the output buffer (zero-copy)\n\tself.postMessage( { type: 'inferred', outputData, id }, [ outputData.buffer ] );\n\n}\n\nself.onmessage = async ( e ) => {\n\n\tconst { type } = e.data;\n\n\ttry {\n\n\t\tif ( type === 'load' ) {\n\n\t\t\tawait loadModel( e.data.url, e.data.sessionOptions );\n\n\t\t} else if ( type === 'infer' ) {\n\n\t\t\tawait inferTile( e.data.tileData, e.data.width, e.data.height, e.data.id );\n\n\t\t} else if ( type === 'dispose' ) {\n\n\t\t\tif ( session ) {\n\n\t\t\t\tawait session.release();\n\t\t\t\tsession = null;\n\t\t\t\tcurrentModelUrl = null;\n\n\t\t\t}\n\n\t\t}\n\n\t} catch ( error ) {\n\n\t\tself.postMessage( { type: 'error', message: error.message, id: e.data?.id } );\n\n\t}\n\n};\n"],"mappings":"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"}
@@ -1,521 +0,0 @@
1
- /**
2
- * createRenderTargetHelper - A component for displaying Three.js render targets in a resizable window
3
- *
4
- * Uses async readback for pixel data.
5
- *
6
- * @param {THREE.WebGPURenderer} renderer - The renderer instance
7
- * @param {THREE.RenderTarget} renderTargetOrTexture - The render target to display
8
- * @param {Object} options - Optional configuration
9
- * @param {number} options.width - Initial width of the view (default: 200)
10
- * @param {number} options.height - Initial height of the view (default: 200)
11
- * @param {string} options.position - Position on screen ('bottom-right', 'bottom-left', 'top-right', 'top-left') (default: 'bottom-right')
12
- * @param {boolean} options.flipX - Flip the image horizontally (default: true)
13
- * @param {boolean} options.flipY - Flip the image vertically (default: true)
14
- * @param {boolean} options.autoUpdate - Whether to automatically update on animation frames (default: false)
15
- * @param {string} options.title - Title to display in the header (default: 'Render Target')
16
- * @param {number} options.textureIndex - For MRT render targets, which texture attachment to read (default: 0)
17
- * @param {string} options.transform - Optional shader transform: 'normal-remap' remaps [0,1] to visible range
18
- * @returns {HTMLElement} The container element with attached methods
19
- */
20
- export function createRenderTargetHelper( renderer, renderTargetOrTexture, options = {} ) {
21
-
22
- // MRT texture index for readback
23
- const textureIndex = options.textureIndex || 0;
24
-
25
- // Detect if input is a Texture or RenderTarget
26
- const isTexture = renderTargetOrTexture.isTexture === true;
27
-
28
- let renderTarget;
29
-
30
- if ( isTexture ) {
31
-
32
- console.warn( 'RenderTargetHelper: Direct Texture input is not supported. Pass a RenderTarget instead.' );
33
- renderTarget = null;
34
-
35
- } else {
36
-
37
- renderTarget = renderTargetOrTexture;
38
-
39
- }
40
-
41
- // Default options
42
- const config = {
43
- width: options.width || 200,
44
- height: options.height || 200,
45
- position: options.position || 'bottom-right',
46
- flipX: options.flipX !== undefined ? options.flipX : false,
47
- flipY: options.flipY !== undefined ? options.flipY : false,
48
- autoUpdate: options.autoUpdate || false,
49
- theme: options.theme || 'dark', // 'light' or 'dark' - changed default to dark to match the app's theme
50
- title: options.title || renderTarget?.name || 'Render Target'
51
- };
52
-
53
- // Create container
54
- const container = document.createElement( 'div' );
55
- container.className = 'render-target-helper';
56
-
57
- // Apply styles based on position
58
- const positionStyles = {
59
- 'bottom-right': { bottom: '48px', right: '10px' }, // Adjusted for stats position
60
- 'bottom-left': { bottom: '10px', left: '10px' },
61
- 'top-right': { top: '10px', right: '10px' },
62
- 'top-left': { top: '10px', left: '10px' }
63
- };
64
-
65
- // Theme styles
66
- const themeStyles = {
67
- 'light': {
68
- backgroundColor: 'white',
69
- border: '1px solid #ddd',
70
- color: '#333'
71
- },
72
- 'dark': {
73
- backgroundColor: '#1e293b', // Match app's slate color
74
- border: '1px solid #334155',
75
- color: '#f8fafc'
76
- }
77
- };
78
-
79
- Object.assign( container.style, {
80
- display: 'flex',
81
- flexDirection: 'column',
82
- position: 'fixed',
83
- resize: 'both',
84
- overflow: 'hidden',
85
- padding: '8px',
86
- borderRadius: '4px',
87
- boxShadow: '0 5px 15px rgba(0,0,0,0.3)',
88
- transition: 'opacity 0.2s ease',
89
- zIndex: '1000',
90
- minWidth: '100px',
91
- minHeight: '100px',
92
- maxWidth: '500px',
93
- maxHeight: '500px',
94
- width: `${config.width}px`,
95
- height: `${config.height}px`,
96
- ...positionStyles[ config.position ],
97
- ...themeStyles[ config.theme ]
98
- } );
99
-
100
- // Create title bar with controls
101
- const titleBar = document.createElement( 'div' );
102
- titleBar.style.display = 'flex';
103
- titleBar.style.justifyContent = 'space-between';
104
- titleBar.style.alignItems = 'center';
105
- titleBar.style.marginBottom = '4px';
106
- titleBar.style.cursor = 'move';
107
- titleBar.style.userSelect = 'none';
108
-
109
- // Title
110
- const title = document.createElement( 'span' );
111
- title.textContent = config.title;
112
- title.style.fontSize = '12px';
113
- title.style.fontFamily = 'monospace';
114
- title.style.color = themeStyles[ config.theme ].color;
115
-
116
- // Controls
117
- const controls = document.createElement( 'div' );
118
-
119
- // Close button
120
- const closeBtn = document.createElement( 'button' );
121
- closeBtn.innerHTML = '×';
122
- closeBtn.style.background = 'none';
123
- closeBtn.style.border = 'none';
124
- closeBtn.style.cursor = 'pointer';
125
- closeBtn.style.fontSize = '16px';
126
- closeBtn.style.color = themeStyles[ config.theme ].color;
127
- closeBtn.style.padding = '0 4px';
128
- closeBtn.title = 'Close';
129
-
130
- closeBtn.onclick = () => {
131
-
132
- container.style.display = 'none';
133
- if ( config.autoUpdate ) {
134
-
135
- cancelAnimationFrame( animFrameId );
136
- animFrameId = null;
137
-
138
- }
139
-
140
- };
141
-
142
- // Add refresh button
143
- const refreshBtn = document.createElement( 'button' );
144
- refreshBtn.innerHTML = '⟳';
145
- refreshBtn.style.background = 'none';
146
- refreshBtn.style.border = 'none';
147
- refreshBtn.style.cursor = 'pointer';
148
- refreshBtn.style.fontSize = '14px';
149
- refreshBtn.style.color = themeStyles[ config.theme ].color;
150
- refreshBtn.style.padding = '0 4px';
151
- refreshBtn.title = 'Refresh';
152
-
153
- refreshBtn.onclick = () => {
154
-
155
- container.update();
156
-
157
- };
158
-
159
- controls.appendChild( refreshBtn );
160
- controls.appendChild( closeBtn );
161
- titleBar.appendChild( title );
162
- titleBar.appendChild( controls );
163
- container.appendChild( titleBar );
164
-
165
- // Canvas for displaying the render target
166
- const domCanvas = document.createElement( 'canvas' );
167
- domCanvas.style.width = '100%';
168
- domCanvas.style.height = 'calc(100% - 20px)';
169
-
170
-
171
- // Apply flipping if needed
172
- let transform = '';
173
- if ( config.flipX ) transform += 'scaleX(-1) ';
174
- if ( config.flipY ) transform += 'scaleY(-1) ';
175
- domCanvas.style.transform = transform.trim();
176
-
177
- container.appendChild( domCanvas );
178
-
179
- // Get render target dimensions
180
- let width = renderTarget ? renderTarget.width : 1;
181
- let height = renderTarget ? renderTarget.height : 1;
182
-
183
- // Initialize canvas
184
- domCanvas.width = width;
185
- domCanvas.height = height;
186
-
187
- const context = domCanvas.getContext( '2d' );
188
-
189
- // Pixel data buffer
190
- let clampedPixels = new Uint8ClampedArray( 4 * width * height );
191
-
192
- // Guard against concurrent async reads (WebGPU)
193
- let pendingRead = false;
194
-
195
- // Make container draggable
196
- let isDragging = false;
197
- let dragOffsetX = 0;
198
- let dragOffsetY = 0;
199
-
200
- titleBar.addEventListener( 'pointerdown', ( e ) => {
201
-
202
- isDragging = true;
203
- dragOffsetX = e.clientX - container.offsetLeft;
204
- dragOffsetY = e.clientY - container.offsetTop;
205
- document.body.style.userSelect = 'none'; // Prevent text selection during drag
206
-
207
- } );
208
-
209
- function onPointerMove( e ) {
210
-
211
- if ( ! isDragging ) return;
212
-
213
- const newLeft = e.clientX - dragOffsetX;
214
- const newTop = e.clientY - dragOffsetY;
215
-
216
- const maxX = window.innerWidth - container.offsetWidth;
217
- const maxY = window.innerHeight - container.offsetHeight;
218
-
219
- container.style.left = `${Math.max( 0, Math.min( newLeft, maxX ) )}px`;
220
- container.style.top = `${Math.max( 0, Math.min( newTop, maxY ) )}px`;
221
-
222
- container.style.bottom = 'auto';
223
- container.style.right = 'auto';
224
-
225
- }
226
-
227
- function onPointerUp() {
228
-
229
- isDragging = false;
230
- document.body.style.userSelect = '';
231
-
232
- }
233
-
234
- window.addEventListener( 'pointermove', onPointerMove );
235
- window.addEventListener( 'pointerup', onPointerUp );
236
-
237
- // Optimize resize handling
238
- function handleResize() {
239
-
240
- if ( ! renderTarget ) return;
241
-
242
- // Get current dimensions from source
243
- const currentWidth = renderTarget.width;
244
- const currentHeight = renderTarget.height;
245
-
246
- // Check if dimensions have changed
247
- if ( width !== currentWidth || height !== currentHeight ) {
248
-
249
- width = currentWidth;
250
- height = currentHeight;
251
-
252
- // Resize canvas to match dimensions
253
- domCanvas.width = width;
254
- domCanvas.height = height;
255
-
256
- // Recreate pixel buffer
257
- clampedPixels = new Uint8ClampedArray( 4 * width * height );
258
-
259
- }
260
-
261
- // Update dimensions display
262
- title.textContent = `${config.title} (${width}×${height})`;
263
-
264
- }
265
-
266
- /**
267
- * Decode a 16-bit half-float to a 32-bit float.
268
- */
269
- function halfToFloat( h ) {
270
-
271
- const s = ( h & 0x8000 ) >> 15;
272
- const e = ( h & 0x7C00 ) >> 10;
273
- const f = h & 0x03FF;
274
-
275
- if ( e === 0 ) return ( s ? - 1 : 1 ) * Math.pow( 2, - 14 ) * ( f / 1024 );
276
- if ( e === 31 ) return f ? NaN : ( s ? - Infinity : Infinity );
277
- return ( s ? - 1 : 1 ) * Math.pow( 2, e - 15 ) * ( 1 + f / 1024 );
278
-
279
- }
280
-
281
- /**
282
- * Draw a pixel buffer to the 2D canvas.
283
- * Handles Float32Array (float 0-1), Uint16Array (half-float), and Uint8Array (0-255).
284
- */
285
- function drawPixelBuffer( buffer ) {
286
-
287
- const len = Math.min( buffer.length, clampedPixels.length );
288
-
289
- if ( buffer instanceof Uint8Array || buffer instanceof Uint8ClampedArray ) {
290
-
291
- // Values already in 0-255 range
292
- clampedPixels.set( buffer.subarray( 0, len ) );
293
-
294
- } else if ( buffer instanceof Uint16Array ) {
295
-
296
- // Half-float values — decode then scale to 0-255
297
- for ( let i = 0; i < len; i ++ ) {
298
-
299
- const val = halfToFloat( buffer[ i ] );
300
- clampedPixels[ i ] = Math.min( 255, Math.max( 0, ( val || 0 ) * 255 ) );
301
-
302
- }
303
-
304
- } else {
305
-
306
- // Float32 values — scale to 0-255
307
- for ( let i = 0; i < len; i ++ ) {
308
-
309
- clampedPixels[ i ] = Math.min( 255, Math.max( 0, buffer[ i ] * 255 ) );
310
-
311
- }
312
-
313
- }
314
-
315
- if ( width === 0 || height === 0 ) return;
316
-
317
- const imageData = new ImageData( clampedPixels, width, height );
318
- context.putImageData( imageData, 0, 0 );
319
-
320
- }
321
-
322
- // Update the display with the current render target contents
323
- container.update = function update() {
324
-
325
- if ( ! renderTarget ) return;
326
-
327
- handleResize();
328
-
329
- try {
330
-
331
- // Asynchronous pixel readback
332
- if ( pendingRead ) return; // skip if previous read is still in flight
333
- pendingRead = true;
334
-
335
- renderer.readRenderTargetPixelsAsync( renderTarget, 0, 0, width, height, textureIndex )
336
- .then( ( buffer ) => {
337
-
338
- pendingRead = false;
339
- drawPixelBuffer( buffer );
340
-
341
- } )
342
- .catch( ( err ) => {
343
-
344
- pendingRead = false;
345
- console.error( 'RenderTargetHelper: readback error:', err );
346
-
347
- } );
348
-
349
- } catch ( error ) {
350
-
351
- console.error( 'Error updating render target helper:', error );
352
-
353
- }
354
-
355
- };
356
-
357
- function onContainerMouseDown() {
358
-
359
- window.addEventListener( 'mousemove', handleResize );
360
-
361
- }
362
-
363
- function onMouseUp() {
364
-
365
- window.removeEventListener( 'mousemove', handleResize );
366
-
367
- }
368
-
369
- container.addEventListener( 'mousedown', onContainerMouseDown );
370
- window.addEventListener( 'mouseup', onMouseUp );
371
- window.addEventListener( 'resize', handleResize );
372
-
373
- // Auto-update animation frame
374
- let animFrameId = null;
375
-
376
- /**
377
- * Show the helper if hidden
378
- */
379
- container.show = function show() {
380
-
381
- container.style.display = 'flex';
382
- if ( config.autoUpdate && ! animFrameId ) {
383
-
384
- container.startAutoUpdate();
385
-
386
- }
387
-
388
- };
389
-
390
- /**
391
- * Hide the helper
392
- */
393
- container.hide = function hide() {
394
-
395
- container.style.display = 'none';
396
- if ( config.autoUpdate && animFrameId ) {
397
-
398
- cancelAnimationFrame( animFrameId );
399
- animFrameId = null;
400
-
401
- }
402
-
403
- };
404
-
405
- /**
406
- * Toggle visibility
407
- */
408
- container.toggle = function toggle() {
409
-
410
- if ( container.style.display === 'none' ) {
411
-
412
- container.show();
413
-
414
- } else {
415
-
416
- container.hide();
417
-
418
- }
419
-
420
- return container.style.display !== 'none';
421
-
422
- };
423
-
424
- /**
425
- * Start auto-updating
426
- */
427
- container.startAutoUpdate = function startAutoUpdate() {
428
-
429
- if ( animFrameId ) return;
430
-
431
- const updateLoop = () => {
432
-
433
- container.update();
434
- animFrameId = requestAnimationFrame( updateLoop );
435
-
436
- };
437
-
438
- animFrameId = requestAnimationFrame( updateLoop );
439
-
440
- };
441
-
442
- /**
443
- * Stop auto-updating
444
- */
445
- container.stopAutoUpdate = function stopAutoUpdate() {
446
-
447
- if ( animFrameId ) {
448
-
449
- cancelAnimationFrame( animFrameId );
450
- animFrameId = null;
451
-
452
- }
453
-
454
- };
455
-
456
- /**
457
- * Dispose and clean up resources
458
- */
459
- container.dispose = function dispose() {
460
-
461
- if ( animFrameId ) {
462
-
463
- cancelAnimationFrame( animFrameId );
464
- animFrameId = null;
465
-
466
- }
467
-
468
- // Remove window listeners — these close over renderer/renderTarget/container
469
- // and pin the entire helper graph alive until the page unloads if not cleaned up.
470
- window.removeEventListener( 'pointermove', onPointerMove );
471
- window.removeEventListener( 'pointerup', onPointerUp );
472
- window.removeEventListener( 'mouseup', onMouseUp );
473
- window.removeEventListener( 'mousemove', handleResize );
474
- window.removeEventListener( 'resize', handleResize );
475
-
476
- if ( container.parentNode ) {
477
-
478
- container.parentNode.removeChild( container );
479
-
480
- }
481
-
482
- // Drop closures attached to the container. These close over `renderer`,
483
- // `renderTargetOrTexture`, and the canvas — keeping them around after the
484
- // owning stage has been disposed retains the entire Three.js WebGPU graph.
485
- container.startAutoUpdate = null;
486
- container.stopAutoUpdate = null;
487
- container.show = null;
488
- container.hide = null;
489
- container.toggle = null;
490
- container.update = null;
491
- container.dispose = null;
492
-
493
- if ( domCanvas ) {
494
-
495
- domCanvas.width = 0;
496
- domCanvas.height = 0;
497
-
498
- }
499
-
500
- clampedPixels = null;
501
-
502
- };
503
-
504
- // Start auto-update if configured
505
- if ( config.autoUpdate ) {
506
-
507
- container.startAutoUpdate();
508
-
509
- }
510
-
511
- // Maintain backward compatibility with original implementation
512
- if ( container.style.display === 'none' ) {
513
-
514
- container.style.display = 'flex';
515
-
516
- }
517
-
518
- return container;
519
-
520
- }
521
-