rayzee 8.5.0 → 8.6.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 (60) hide show
  1. package/README.md +60 -4
  2. package/dist/{DLSSNeural-BNSJ8V3z.js → DLSSNeural-BhBCBgKf.js} +5 -5
  3. package/dist/{DLSSSuperRes-BoEW89Cl.js → DLSSSuperRes-BCA7F_k5.js} +5 -5
  4. package/dist/ToneMapGPU-BzcLheoR.js +533 -0
  5. package/dist/rayzee.es.js +27198 -9047
  6. package/dist/rayzee.umd.js +255 -187
  7. package/package.json +1 -1
  8. package/src/AssetConfig.js +15 -2
  9. package/src/Color/BakedViews.js +134 -0
  10. package/src/Color/BuiltinViews.js +261 -0
  11. package/src/Color/ColorManagement.js +1130 -0
  12. package/src/Color/ColorSpaces.js +432 -0
  13. package/src/Color/Displays.js +19 -0
  14. package/src/Color/InputColorSpaces.js +243 -0
  15. package/src/Color/LutBake.js +371 -0
  16. package/src/Color/OcioRuntime.js +338 -0
  17. package/src/Color/OcioViews.js +419 -0
  18. package/src/Color/ViewTransforms.js +323 -0
  19. package/src/Color/WorkingMatrix.js +86 -0
  20. package/src/EngineDefaults.js +55 -3
  21. package/src/EngineIssues.js +3 -0
  22. package/src/Passes/AIUpscaler.js +16 -7
  23. package/src/PathTracerApp.js +227 -7
  24. package/src/Processor/AssetLoader.js +1 -2
  25. package/src/Processor/EmissiveTriangleBuilder.js +34 -2
  26. package/src/Processor/GeometryExtractor.js +151 -138
  27. package/src/Processor/LightSerializer.js +8 -3
  28. package/src/Processor/MaterialPacking.js +168 -0
  29. package/src/Processor/PackedRayBuffer.js +6 -2
  30. package/src/Processor/ProceduralSky.js +4 -0
  31. package/src/Processor/SceneProcessor.js +25 -1
  32. package/src/Processor/SimpleSky.js +8 -0
  33. package/src/Processor/TextureCreator.js +137 -91
  34. package/src/Processor/TextureReadback.js +92 -0
  35. package/src/Processor/ToneMapCPU.js +80 -165
  36. package/src/Processor/ToneMapGPU.js +122 -151
  37. package/src/RenderSettings.js +1 -0
  38. package/src/Stages/Compositor.js +11 -2
  39. package/src/Stages/PathTracer.js +2 -0
  40. package/src/Stages/PathTracerStage.js +1 -1
  41. package/src/TSL/Common.js +15 -0
  42. package/src/TSL/Debugger.js +2 -3
  43. package/src/TSL/EmissiveSampling.js +6 -8
  44. package/src/TSL/ExtendKernel.js +9 -0
  45. package/src/TSL/Fresnel.js +23 -1
  46. package/src/TSL/HitFacet.js +102 -0
  47. package/src/TSL/LightsDirect.js +12 -33
  48. package/src/TSL/LightsSampling.js +15 -7
  49. package/src/TSL/MaterialEvaluation.js +26 -19
  50. package/src/TSL/MaterialProperties.js +74 -37
  51. package/src/TSL/MaterialTransmission.js +2 -3
  52. package/src/TSL/ShadeKernel.js +34 -21
  53. package/src/TSL/ShadowTerminator.js +125 -0
  54. package/src/TSL/Struct.js +9 -0
  55. package/src/TSL/Subsurface.js +2 -3
  56. package/src/index.js +29 -1
  57. package/src/managers/EnvironmentManager.js +20 -0
  58. package/src/managers/MaterialDataManager.js +118 -208
  59. package/src/managers/UniformManager.js +1 -0
  60. package/dist/ToneMapGPU-DzqSyzTx.js +0 -341
@@ -1,8 +1,10 @@
1
- import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace, LinearSRGBColorSpace, RepeatWrapping } from "three";
2
- import { alignBucketWidth, TEXTURE_CONSTANTS, MEMORY_CONSTANTS, DEFAULT_TEXTURE_MATRIX, MATERIAL_DATA_LAYOUT, normalizeAttenuationDistance, BVH_LEAF_MARKERS, assertBVHIndexFits, bvhIndexView } from '../EngineDefaults.js';
1
+ import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace, LinearSRGBColorSpace, RepeatWrapping, FloatType, HalfFloatType } from "three";
2
+ import { alignBucketWidth, TEXTURE_CONSTANTS, MEMORY_CONSTANTS, MATERIAL_DATA_LAYOUT, BVH_LEAF_MARKERS, assertBVHIndexFits, bvhIndexView } from '../EngineDefaults.js';
3
+ import { packMaterial } from './MaterialPacking.js';
3
4
  import TexturesWorker from './Workers/TexturesWorker.js?worker&inline';
4
5
  import { ISSUE_CODES } from '../EngineIssues.js';
5
6
  import { linearToSRGB } from './ToneMapCPU.js';
7
+ import { getActiveColorManagement } from '../Color/ColorManagement.js';
6
8
  import { createLogger } from '../utils/Logger.js';
7
9
 
8
10
  const log = createLogger( 'textures' );
@@ -383,7 +385,11 @@ class TextureCache {
383
385
  const height = texture.image.height || 0;
384
386
  const src = texture.image.src || texture.uuid || '';
385
387
  const flipFlag = texture.flipY === false ? 'n' : 'f';
386
- hash += `${width}x${height}_${src.slice( - 8 )}_${flipFlag}_`;
388
+ // What the texture is read *as* is part of its identity: without these, changing a
389
+ // map's colour space handed back the array packed under the old one, and the change
390
+ // silently did nothing.
391
+ const space = `${texture.colorSpace ?? ''}|${texture.userData?.ocioColorSpace ?? ''}`;
392
+ hash += `${width}x${height}_${src.slice( - 8 )}_${flipFlag}_${space}_`;
387
393
 
388
394
  }
389
395
 
@@ -572,6 +578,114 @@ export class TextureCreator {
572
578
 
573
579
  }
574
580
 
581
+ /**
582
+ * Convert each colour layer into the working space, once a config is loaded.
583
+ *
584
+ * Runs after `_harmonizeTransfer`, so every layer is stored the way the colour pool expects:
585
+ * sRGB-encoded. Two routes from there:
586
+ *
587
+ * - **Named** — a tag, an override or one of the config's file rules says what the texture is
588
+ * (ACEScct, a camera log, sRGB). The full OCIO transform runs on the bytes as stored. Runs
589
+ * whether or not a working space is adopted: a log-encoded texture is wrong in linear
590
+ * Rec.709 too.
591
+ * - **Primaries** — nothing explicit, so the texture is what three.js assumed. Only the
592
+ * primaries change, and only once a working space is adopted.
593
+ *
594
+ * The named route reads the bytes as the author stored them, so it is refused for a layer
595
+ * harmonization re-encoded, and for a float source the packer already quantized — in both the
596
+ * bytes are no longer in the space the name describes, and converting them would be confidently
597
+ * wrong. Those fall back to the primaries route and say so.
598
+ *
599
+ * @param {import('three').DataArrayTexture} arrayTex
600
+ * @param {Array<import('three').Texture>} textures - pre-normalization list, in layer order
601
+ * @param {boolean} srgbPool
602
+ * @param {Set<number>} [reencoded] - layers `_harmonizeTransfer` changed
603
+ */
604
+ _applyColorManagement( arrayTex, textures, srgbPool, reencoded = new Set() ) {
605
+
606
+ const cm = getActiveColorManagement();
607
+ if ( ! cm?.hasConfig ) return;
608
+
609
+ const data = arrayTex?.image?.data;
610
+ if ( ! data ) return;
611
+
612
+ // A data pool carries numbers, not colour — a primaries matrix would corrupt it.
613
+ if ( ! srgbPool ) return;
614
+
615
+ const kept = textures.filter( t => t?.image );
616
+ const layerSize = arrayTex.image.width * arrayTex.image.height * 4;
617
+ const encoding = 'srgb';
618
+ let named = 0, primaries = 0;
619
+ const refused = [];
620
+
621
+ for ( let layer = 0; layer < kept.length; layer ++ ) {
622
+
623
+ const start = layer * layerSize;
624
+ const end = Math.min( start + layerSize, data.length );
625
+ if ( start >= data.length ) break;
626
+
627
+ const texture = kept[ layer ];
628
+ const view = data.subarray( start, end );
629
+
630
+ let resolved = null;
631
+ try {
632
+
633
+ resolved = cm.resolveTexture( texture );
634
+
635
+ } catch ( error ) {
636
+
637
+ log.warn( `could not resolve the colour space of layer ${layer}: ${error.message}` );
638
+
639
+ }
640
+
641
+ let space = resolved && resolved.via !== 'three' ? resolved.colorSpace : null;
642
+ const floatSource = texture.type === FloatType || texture.type === HalfFloatType;
643
+ if ( space && ( reencoded.has( layer ) || floatSource ) ) {
644
+
645
+ refused.push( `${texture.name || `layer ${layer}`} (${space})` );
646
+ space = null;
647
+
648
+ }
649
+
650
+ try {
651
+
652
+ if ( cm.convertTextureBytes( view, { space, encoding } ) ) {
653
+
654
+ if ( space ) named ++;
655
+ else primaries ++;
656
+
657
+ }
658
+
659
+ } catch ( error ) {
660
+
661
+ this._issues?.warn(
662
+ ISSUE_CODES.TEXTURE_PROCESSING_FALLBACK,
663
+ `colour conversion failed for layer ${layer} — left in its original space`,
664
+ { cause: String( error?.message ?? error ), space }
665
+ );
666
+
667
+ }
668
+
669
+ }
670
+
671
+ if ( refused.length ) {
672
+
673
+ this._issues?.warn(
674
+ ISSUE_CODES.TEXTURE_PROCESSING_FALLBACK,
675
+ `${refused.length} colour map(s) name a colour space their stored bytes are no longer in ` +
676
+ '(re-encoded or quantized on the way in); converted by primaries only',
677
+ { textures: refused.slice( 0, 8 ) }
678
+ );
679
+
680
+ }
681
+
682
+ if ( named + primaries === 0 ) return;
683
+
684
+ arrayTex.needsUpdate = true;
685
+ log.info( `colour maps into ${cm.workingSpace}: ${named} by named space, ${primaries} by primaries, of ${kept.length}` );
686
+
687
+ }
688
+
575
689
  /**
576
690
  * Re-encodes any layer whose declared colour space disagrees with its pool, so the GPU's
577
691
  * decode — or absence of one — lands on the values the author stored. An untagged texture
@@ -583,8 +697,9 @@ export class TextureCreator {
583
697
  */
584
698
  _harmonizeTransfer( arrayTex, textures, srgbPool ) {
585
699
 
700
+ const touched = new Set();
586
701
  const data = arrayTex?.image?.data;
587
- if ( ! data ) return;
702
+ if ( ! data ) return touched;
588
703
 
589
704
  // Mirrors _normalizeTexturesForProcessing's skip rule, which is what sets layer order.
590
705
  const kept = textures.filter( t => t?.image );
@@ -612,10 +727,11 @@ export class TextureCreator {
612
727
  }
613
728
 
614
729
  converted.push( kept[ layer ].name || kept[ layer ].source?.uuid?.slice( 0, 8 ) || `layer ${layer}` );
730
+ touched.add( layer );
615
731
 
616
732
  }
617
733
 
618
- if ( converted.length === 0 ) return;
734
+ if ( converted.length === 0 ) return touched;
619
735
 
620
736
  arrayTex.needsUpdate = true;
621
737
  const names = converted.slice( 0, 4 ).join( ', ' ) + ( converted.length > 4 ? `, +${converted.length - 4} more` : '' );
@@ -632,6 +748,8 @@ export class TextureCreator {
632
748
 
633
749
  }
634
750
 
751
+ return touched;
752
+
635
753
  }
636
754
 
637
755
  // Unified texture processing with strategy selection
@@ -640,8 +758,13 @@ export class TextureCreator {
640
758
  if ( ! textures || textures.length === 0 ) return null;
641
759
 
642
760
  // Pool is part of the identity: the same list packed for the colour pool and the data pool
643
- // gets different pixels (see _harmonizeTransfer), so one key must not serve both.
644
- const cacheKey = `${srgbPool ? 's' : 'l'}:${this.textureCache.generateHash( textures )}`;
761
+ // gets different pixels (see _harmonizeTransfer), so one key must not serve both. The
762
+ // working space is part of it for the same reason — adopting one rewrites every colour
763
+ // map, and a key that ignored it would hand back pixels converted for the previous space.
764
+ // So is the config and any override, which change what a texture is read *as*.
765
+ const cm = getActiveColorManagement();
766
+ const colour = cm?.inputKey ?? '-';
767
+ const cacheKey = `${srgbPool ? 's' : 'l'}:${colour}:${this.textureCache.generateHash( textures )}`;
645
768
  const cached = this.textureCache.get( cacheKey );
646
769
  if ( cached ) return cached;
647
770
 
@@ -673,7 +796,8 @@ export class TextureCreator {
673
796
  // Cache successful result
674
797
  if ( result ) {
675
798
 
676
- this._harmonizeTransfer( result, textures, srgbPool );
799
+ const reencoded = this._harmonizeTransfer( result, textures, srgbPool );
800
+ this._applyColorManagement( result, textures, srgbPool, reencoded );
677
801
  this.textureCache.set( cacheKey, result );
678
802
 
679
803
  }
@@ -688,7 +812,8 @@ export class TextureCreator {
688
812
  { cause: String( error?.message ?? error ) }
689
813
  );
690
814
  const fallback = await this.processOnMainThreadSync( normalized );
691
- this._harmonizeTransfer( fallback, textures, srgbPool );
815
+ const reencoded = this._harmonizeTransfer( fallback, textures, srgbPool );
816
+ this._applyColorManagement( fallback, textures, srgbPool, reencoded );
692
817
  return fallback;
693
818
 
694
819
  } finally {
@@ -1036,88 +1161,9 @@ export class TextureCreator {
1036
1161
  */
1037
1162
  createMaterialRawData( materials ) {
1038
1163
 
1039
- // Layout is defined by MATERIAL_DATA_LAYOUT in EngineDefaults.js.
1040
- // The inline array below must match that layout exactly (positional order = canonical layout).
1041
- const dataLengthPerMaterial = MATERIAL_DATA_LAYOUT.FLOATS_PER_MATERIAL;
1042
- const totalMaterials = materials.length;
1043
-
1044
- const size = totalMaterials * dataLengthPerMaterial;
1045
- const data = new Float32Array( size );
1046
-
1047
- for ( let i = 0; i < totalMaterials; i ++ ) {
1048
-
1049
- const mat = materials[ i ];
1050
- const stride = i * dataLengthPerMaterial;
1051
-
1052
- const mapMatrix = mat.mapMatrix ?? DEFAULT_TEXTURE_MATRIX;
1053
- const normalMapMatrices = mat.normalMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
1054
- const roughnessMapMatrices = mat.roughnessMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
1055
- const metalnessMapMatrices = mat.metalnessMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
1056
- const emissiveMapMatrices = mat.emissiveMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
1057
- const bumpMapMatrices = mat.bumpMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
1058
- const displacementMapMatrices = mat.displacementMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
1059
-
1060
- // Slot order: shadow/culling → BxDF core → maps → extended → displacement → transforms
1061
- // Must match MATERIAL_DATA_LAYOUT in EngineDefaults.js exactly.
1062
- const materialData = [
1063
- // Slot 0: shadow core (ior, transmission, thickness, emissiveIntensity)
1064
- mat.ior, mat.transmission, mat.thickness, mat.emissiveIntensity,
1065
- // Slot 1: shadow (attenuationColor, attenuationDistance)
1066
- mat.attenuationColor.r, mat.attenuationColor.g, mat.attenuationColor.b, normalizeAttenuationDistance( mat.attenuationDistance ),
1067
- // Slot 2: shadow + culling (opacity, side, transparent, alphaTest)
1068
- mat.opacity, mat.side, mat.transparent, mat.alphaTest,
1069
- // Slot 3: shadow (alphaMode, depthWrite, normalScale)
1070
- mat.alphaMode, mat.depthWrite, mat.normalScale?.x ?? 1, mat.normalScale?.y ?? 1,
1071
- // Slot 4: BxDF core (color, metalness)
1072
- mat.color.r, mat.color.g, mat.color.b, mat.metalness,
1073
- // Slot 5: BxDF core (emissive, roughness)
1074
- mat.emissive.r, mat.emissive.g, mat.emissive.b, mat.roughness,
1075
- // Slot 6: map indices A (albedo, normal, roughness, metalness)
1076
- mat.map, mat.normalMap, mat.roughnessMap, mat.metalnessMap,
1077
- // Slot 7: map indices B (emissive, bump, clearcoat, clearcoatRoughness)
1078
- mat.emissiveMap, mat.bumpMap, mat.clearcoat, mat.clearcoatRoughness,
1079
- // Slot 8: extended BxDF (dispersion, visible, sheen, sheenRoughness)
1080
- mat.dispersion, mat.visible, mat.sheen, mat.sheenRoughness,
1081
- // Slot 9: extended BxDF (sheenColor, reserved)
1082
- mat.sheenColor.r, mat.sheenColor.g, mat.sheenColor.b, 1,
1083
- // Slot 10: extended BxDF (specularIntensity, specularColor)
1084
- mat.specularIntensity, mat.specularColor.r, mat.specularColor.g, mat.specularColor.b,
1085
- // Slot 11: extended BxDF (iridescence)
1086
- mat.iridescence, mat.iridescenceIOR, mat.iridescenceThicknessRange[ 0 ], mat.iridescenceThicknessRange[ 1 ],
1087
- // Slot 12: displacement
1088
- mat.bumpScale, mat.displacementScale, mat.displacementMap, 0,
1089
- mapMatrix[ 0 ], mapMatrix[ 1 ], mapMatrix[ 2 ], mapMatrix[ 3 ],
1090
- mapMatrix[ 4 ], mapMatrix[ 5 ], mapMatrix[ 6 ], mapMatrix[ 7 ],
1091
- normalMapMatrices[ 0 ], normalMapMatrices[ 1 ], normalMapMatrices[ 2 ], normalMapMatrices[ 3 ],
1092
- normalMapMatrices[ 4 ], normalMapMatrices[ 5 ], normalMapMatrices[ 6 ], normalMapMatrices[ 7 ],
1093
- roughnessMapMatrices[ 0 ], roughnessMapMatrices[ 1 ], roughnessMapMatrices[ 2 ], roughnessMapMatrices[ 3 ],
1094
- roughnessMapMatrices[ 4 ], roughnessMapMatrices[ 5 ], roughnessMapMatrices[ 6 ], roughnessMapMatrices[ 7 ],
1095
- metalnessMapMatrices[ 0 ], metalnessMapMatrices[ 1 ], metalnessMapMatrices[ 2 ], metalnessMapMatrices[ 3 ],
1096
- metalnessMapMatrices[ 4 ], metalnessMapMatrices[ 5 ], metalnessMapMatrices[ 6 ], metalnessMapMatrices[ 7 ],
1097
- emissiveMapMatrices[ 0 ], emissiveMapMatrices[ 1 ], emissiveMapMatrices[ 2 ], emissiveMapMatrices[ 3 ],
1098
- emissiveMapMatrices[ 4 ], emissiveMapMatrices[ 5 ], emissiveMapMatrices[ 6 ], emissiveMapMatrices[ 7 ],
1099
- bumpMapMatrices[ 0 ], bumpMapMatrices[ 1 ], bumpMapMatrices[ 2 ], bumpMapMatrices[ 3 ],
1100
- bumpMapMatrices[ 4 ], bumpMapMatrices[ 5 ], bumpMapMatrices[ 6 ], bumpMapMatrices[ 7 ],
1101
- displacementMapMatrices[ 0 ], displacementMapMatrices[ 1 ], displacementMapMatrices[ 2 ], displacementMapMatrices[ 3 ],
1102
- displacementMapMatrices[ 4 ], displacementMapMatrices[ 5 ], displacementMapMatrices[ 6 ], displacementMapMatrices[ 7 ],
1103
- // Slot 27: subsurface (subsurfaceColor.rgb, subsurface weight)
1104
- mat.subsurfaceColor?.r ?? 1, mat.subsurfaceColor?.g ?? 1, mat.subsurfaceColor?.b ?? 1, mat.subsurface ?? 0,
1105
- // Slot 28: subsurface (subsurfaceRadius.rgb, subsurfaceRadiusScale)
1106
- mat.subsurfaceRadius?.[ 0 ] ?? 1, mat.subsurfaceRadius?.[ 1 ] ?? 0.2, mat.subsurfaceRadius?.[ 2 ] ?? 0.1, mat.subsurfaceRadiusScale ?? 1,
1107
- // Slot 29: subsurfaceAnisotropy g + surface anisotropy (strength, rotation, map index)
1108
- mat.subsurfaceAnisotropy ?? 0, mat.anisotropy ?? 0, mat.anisotropyRotation ?? 0, mat.anisotropyMap ?? - 1,
1109
- // Slot 30: extension map indices A (transmission, clearcoat, clearcoatRoughness, sheenColor)
1110
- mat.transmissionMap ?? - 1, mat.clearcoatMap ?? - 1, mat.clearcoatRoughnessMap ?? - 1, mat.sheenColorMap ?? - 1,
1111
- // Slot 31: extension map indices B (sheenRoughness, iridescence, iridescenceThickness, specularIntensity)
1112
- mat.sheenRoughnessMap ?? - 1, mat.iridescenceMap ?? - 1, mat.iridescenceThicknessMap ?? - 1, mat.specularIntensityMap ?? - 1,
1113
- // Slot 32: extension map indices C (specularColor + 3 reserved)
1114
- mat.specularColorMap ?? - 1, 0, 0, 0,
1115
- ];
1116
-
1117
- data.set( materialData, stride );
1118
-
1119
- }
1120
-
1164
+ const stride = MATERIAL_DATA_LAYOUT.FLOATS_PER_MATERIAL;
1165
+ const data = new Float32Array( materials.length * stride );
1166
+ for ( let i = 0; i < materials.length; i ++ ) packMaterial( data, i * stride, materials[ i ] );
1121
1167
  return data;
1122
1168
 
1123
1169
  }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Any texture the pipeline publishes, read back as linear float RGBA.
3
+ *
4
+ * The denoisers publish their output as whatever they happen to own — a render target's texture,
5
+ * or, for OIDN, an ExternalTexture wrapping a raw GPUTexture that no render target owns — and only a
6
+ * render target can be read back. So this draws the texture into one float target of its own and
7
+ * reads that.
8
+ *
9
+ * ⚠️ Drawn through `outputNode`, not `colorNode`: a colour node gets the renderer's tone mapping,
10
+ * output transfer and a forced alpha of 1, and this has to return the light itself.
11
+ */
12
+
13
+ import { RenderTarget, FloatType, RGBAFormat, NearestFilter } from 'three';
14
+ import { QuadMesh, NodeMaterial } from 'three/webgpu';
15
+ import { texture as tslTexture, screenCoordinate, ivec2 } from 'three/tsl';
16
+
17
+ export class TextureReadback {
18
+
19
+ constructor( renderer ) {
20
+
21
+ this.renderer = renderer;
22
+ this.target = null;
23
+ this.material = new NodeMaterial();
24
+ this.material.name = 'rayzee:texture-readback';
25
+ this.quad = new QuadMesh( this.material );
26
+ this._node = null;
27
+ this._source = null;
28
+
29
+ }
30
+
31
+ _ensureTarget( width, height ) {
32
+
33
+ if ( this.target && this.target.width === width && this.target.height === height ) return;
34
+
35
+ this.target?.dispose();
36
+ this.target = new RenderTarget( width, height, {
37
+ type: FloatType, format: RGBAFormat, depthBuffer: false,
38
+ minFilter: NearestFilter, magFilter: NearestFilter,
39
+ } );
40
+
41
+ }
42
+
43
+ /**
44
+ * @param {import('three').Texture} source
45
+ * @returns {Promise<Float32Array>} `width * height * 4`, top row first
46
+ */
47
+ async read( source, width, height ) {
48
+
49
+ this._ensureTarget( width, height );
50
+
51
+ if ( this._source !== source ) {
52
+
53
+ // Fetched by pixel, not sampled over 0..1: the pools are allocated larger than the frame,
54
+ // and sampling would squash the whole allocation into it.
55
+ this._node = tslTexture( source ).load( ivec2( screenCoordinate.xy ) );
56
+ this.material.outputNode = this._node;
57
+ this.material.needsUpdate = true;
58
+ this._source = source;
59
+
60
+ }
61
+
62
+ const previous = this.renderer.getRenderTarget();
63
+ this.renderer.setRenderTarget( this.target );
64
+ this.quad.render( this.renderer );
65
+ this.renderer.setRenderTarget( previous );
66
+
67
+ try {
68
+
69
+ return await this.renderer.readRenderTargetPixelsAsync( this.target, 0, 0, width, height );
70
+
71
+ } finally {
72
+
73
+ // A float target the size of the frame — 132 MB at 4K — for something done once per
74
+ // saved file. Not worth keeping between saves.
75
+ this.target.dispose();
76
+ this.target = null;
77
+
78
+ }
79
+
80
+ }
81
+
82
+ dispose() {
83
+
84
+ this.target?.dispose();
85
+ this.material.dispose();
86
+ this.target = null;
87
+ this._source = null;
88
+ this._node = null;
89
+
90
+ }
91
+
92
+ }