rayzee 7.30.0 → 7.30.2
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/dist/rayzee.es.js +1761 -1633
- package/dist/rayzee.umd.js +49 -49
- package/package.json +1 -1
- package/src/EngineDefaults.js +197 -19
- package/src/EngineIssues.js +1 -0
- package/src/Processor/GeometryExtractor.js +21 -3
- package/src/Processor/SceneProcessor.js +71 -24
- package/src/Processor/TextureCreator.js +8 -9
- package/src/Processor/Workers/TexturesWorker.js +13 -14
- package/src/Processor/utils.js +0 -6
- package/src/TSL/BVHTraversal.js +3 -1
- package/src/TSL/TextureSampling.js +3 -2
package/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -666,11 +666,11 @@ export const TEXTURE_CONSTANTS = {
|
|
|
666
666
|
MAX_TEXTURE_SIZE: 8192,
|
|
667
667
|
// Default cap applied when no maxTextureSize is supplied (engine standalone use).
|
|
668
668
|
DEFAULT_MAX_TEXTURE_SIZE: 4096,
|
|
669
|
-
//
|
|
670
|
-
MAX_TEXTURES_LIMIT:
|
|
671
|
-
//
|
|
672
|
-
//
|
|
673
|
-
// footprint. 4 → ~8 bound material arrays (4 sRGB + 4 linear).
|
|
669
|
+
// Per-map-type ceiling before bucketing; the real cap is per (pool, bucket) in _bucketTextures.
|
|
670
|
+
MAX_TEXTURES_LIMIT: 4 * 256,
|
|
671
|
+
// Array shapes per colorSpace pool. Material maps are grouped into this many
|
|
672
|
+
// (width, height) classes so a small or oddly-shaped map no longer pays a large
|
|
673
|
+
// neighbour's footprint. 4 → ~8 bound material arrays (4 sRGB + 4 linear).
|
|
674
674
|
MATERIAL_BUCKET_COUNT: 4,
|
|
675
675
|
// Packing stride: a map's stored index encodes bucketId * BUCKET_LAYER_STRIDE + layer.
|
|
676
676
|
// Also the per-bucket layer cap. Kept at the WebGPU portable maxTextureArrayLayers floor (256)
|
|
@@ -678,30 +678,208 @@ export const TEXTURE_CONSTANTS = {
|
|
|
678
678
|
BUCKET_LAYER_STRIDE: 256,
|
|
679
679
|
};
|
|
680
680
|
|
|
681
|
-
//
|
|
682
|
-
|
|
683
|
-
export function getTextureBucketSizes( maxTextureSize, count = TEXTURE_CONSTANTS.MATERIAL_BUCKET_COUNT ) {
|
|
681
|
+
// An RGBA8 row upload must be a multiple of 256 bytes, so bucket widths land on 64 texels.
|
|
682
|
+
const BUCKET_WIDTH_ALIGN = 64;
|
|
684
683
|
|
|
685
|
-
|
|
684
|
+
export function alignBucketWidth( width, maxTextureSize ) {
|
|
685
|
+
|
|
686
|
+
const aligned = Math.ceil( Math.max( 1, width ) / BUCKET_WIDTH_ALIGN ) * BUCKET_WIDTH_ALIGN;
|
|
687
|
+
return Math.min( maxTextureSize, Math.max( TEXTURE_CONSTANTS.MIN_TEXTURE_WIDTH, aligned ) );
|
|
688
|
+
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function bucketOf( units ) {
|
|
692
|
+
|
|
693
|
+
let width = 0, height = 0, count = 0;
|
|
694
|
+
for ( const unit of units ) {
|
|
695
|
+
|
|
696
|
+
width = Math.max( width, unit.width );
|
|
697
|
+
height = Math.max( height, unit.height );
|
|
698
|
+
count += unit.count;
|
|
699
|
+
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return { units, width, height, count };
|
|
703
|
+
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Footprint times layer count.
|
|
707
|
+
function bucketCost( bucket, maxTextureSize ) {
|
|
708
|
+
|
|
709
|
+
return alignBucketWidth( bucket.width, maxTextureSize ) * bucket.height * bucket.count * 4;
|
|
710
|
+
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function totalCost( buckets, maxTextureSize ) {
|
|
714
|
+
|
|
715
|
+
return buckets.reduce( ( sum, b ) => sum + bucketCost( b, maxTextureSize ), 0 );
|
|
716
|
+
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Repeatedly fuse the pair of buckets whose union wastes least, until `count` remain.
|
|
720
|
+
function mergeDown( buckets, count, maxTextureSize ) {
|
|
721
|
+
|
|
722
|
+
let current = buckets.slice();
|
|
723
|
+
|
|
724
|
+
while ( current.length > count ) {
|
|
725
|
+
|
|
726
|
+
let best = null;
|
|
727
|
+
|
|
728
|
+
for ( let i = 0; i < current.length; i ++ ) {
|
|
729
|
+
|
|
730
|
+
for ( let j = i + 1; j < current.length; j ++ ) {
|
|
731
|
+
|
|
732
|
+
const fused = bucketOf( current[ i ].units.concat( current[ j ].units ) );
|
|
733
|
+
const delta = bucketCost( fused, maxTextureSize )
|
|
734
|
+
- bucketCost( current[ i ], maxTextureSize ) - bucketCost( current[ j ], maxTextureSize );
|
|
735
|
+
if ( ! best || delta < best.delta ) best = { i, j, delta, fused };
|
|
736
|
+
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
current = current.filter( ( _, k ) => k !== best.i && k !== best.j ).concat( [ best.fused ] );
|
|
742
|
+
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
return current;
|
|
746
|
+
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// The pre-shape-aware layout, kept as a rival opening: square scenes still do well on it.
|
|
750
|
+
function ladderBuckets( units, count, maxTextureSize ) {
|
|
751
|
+
|
|
752
|
+
const ladder = [];
|
|
686
753
|
for ( let i = count - 1; i >= 0; i -- ) {
|
|
687
754
|
|
|
688
|
-
|
|
755
|
+
ladder.push( Math.max( TEXTURE_CONSTANTS.MIN_TEXTURE_WIDTH, Math.round( maxTextureSize / Math.pow( 2, i ) ) ) );
|
|
756
|
+
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const bins = ladder.map( () => [] );
|
|
760
|
+
|
|
761
|
+
for ( const unit of units ) {
|
|
762
|
+
|
|
763
|
+
const longest = Math.pow( 2, Math.ceil( Math.log2( Math.max( unit.width, unit.height ) ) ) );
|
|
764
|
+
let slot = ladder.findIndex( size => longest <= size );
|
|
765
|
+
if ( slot < 0 ) slot = ladder.length - 1;
|
|
766
|
+
bins[ slot ].push( unit );
|
|
689
767
|
|
|
690
768
|
}
|
|
691
769
|
|
|
692
|
-
return
|
|
770
|
+
return bins.filter( bin => bin.length ).map( bucketOf );
|
|
693
771
|
|
|
694
772
|
}
|
|
695
773
|
|
|
696
|
-
//
|
|
697
|
-
//
|
|
698
|
-
|
|
774
|
+
// Both openings above are greedy and stop short on mixed aspects; moving one shape at a time
|
|
775
|
+
// to whichever bucket makes the pool cheapest recovers the difference.
|
|
776
|
+
function refine( buckets, maxTextureSize ) {
|
|
777
|
+
|
|
778
|
+
let current = buckets;
|
|
779
|
+
|
|
780
|
+
for ( let pass = 0; pass < 8; pass ++ ) {
|
|
781
|
+
|
|
782
|
+
let improved = false;
|
|
783
|
+
|
|
784
|
+
for ( const unit of current.flatMap( b => b.units ) ) {
|
|
785
|
+
|
|
786
|
+
const from = current.findIndex( b => b.units.includes( unit ) );
|
|
787
|
+
if ( from < 0 ) continue;
|
|
788
|
+
|
|
789
|
+
for ( let to = 0; to < current.length; to ++ ) {
|
|
790
|
+
|
|
791
|
+
if ( to === from ) continue;
|
|
792
|
+
|
|
793
|
+
const moved = [];
|
|
794
|
+
for ( let i = 0; i < current.length; i ++ ) {
|
|
795
|
+
|
|
796
|
+
if ( i === from ) {
|
|
797
|
+
|
|
798
|
+
const rest = current[ i ].units.filter( u => u !== unit );
|
|
799
|
+
if ( rest.length ) moved.push( bucketOf( rest ) );
|
|
800
|
+
|
|
801
|
+
} else if ( i === to ) moved.push( bucketOf( current[ i ].units.concat( [ unit ] ) ) );
|
|
802
|
+
else moved.push( current[ i ] );
|
|
803
|
+
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if ( totalCost( moved, maxTextureSize ) < totalCost( current, maxTextureSize ) ) {
|
|
807
|
+
|
|
808
|
+
current = moved;
|
|
809
|
+
improved = true;
|
|
810
|
+
break;
|
|
811
|
+
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if ( ! improved ) break;
|
|
819
|
+
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return current;
|
|
823
|
+
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Choose up to `count` array shapes for one colorSpace pool. Grouping by longest edge alone
|
|
828
|
+
* forces a 2000x453 banner into a 2048x2048 array; grouping on both axes does not.
|
|
829
|
+
*
|
|
830
|
+
* @param {Array<{width: number, height: number}>} sizes - every texture in the pool
|
|
831
|
+
* @param {number} maxTextureSize
|
|
832
|
+
* @param {number} [count]
|
|
833
|
+
* @returns {Array<{width: number, height: number}>} ascending by footprint
|
|
834
|
+
*/
|
|
835
|
+
export function planTextureBuckets( sizes, maxTextureSize, count = TEXTURE_CONSTANTS.MATERIAL_BUCKET_COUNT ) {
|
|
836
|
+
|
|
837
|
+
const distinct = new Map();
|
|
838
|
+
|
|
839
|
+
for ( const { width, height } of sizes ) {
|
|
840
|
+
|
|
841
|
+
const w = Math.min( maxTextureSize, Math.max( 1, width || 1 ) );
|
|
842
|
+
const h = Math.min( maxTextureSize, Math.max( 1, height || 1 ) );
|
|
843
|
+
const key = `${w}x${h}`;
|
|
844
|
+
const seen = distinct.get( key );
|
|
845
|
+
if ( seen ) seen.count ++;
|
|
846
|
+
else distinct.set( key, { width: w, height: h, count: 1 } );
|
|
847
|
+
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const units = [ ...distinct.values() ];
|
|
851
|
+
let buckets = units.map( unit => bucketOf( [ unit ] ) );
|
|
852
|
+
|
|
853
|
+
if ( buckets.length > count ) {
|
|
854
|
+
|
|
855
|
+
const ladder = ladderBuckets( units, count, maxTextureSize );
|
|
856
|
+
const merged = mergeDown( buckets, count, maxTextureSize );
|
|
857
|
+
buckets = totalCost( ladder, maxTextureSize ) < totalCost( merged, maxTextureSize ) ? ladder : merged;
|
|
858
|
+
buckets = refine( buckets, maxTextureSize );
|
|
859
|
+
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
return buckets
|
|
863
|
+
.map( b => ( { width: alignBucketWidth( b.width, maxTextureSize ), height: b.height } ) )
|
|
864
|
+
.sort( ( a, b ) => a.width * a.height - b.width * b.height );
|
|
865
|
+
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Index of the cheapest planned bucket that can hold a texture at its native size.
|
|
870
|
+
* A texture larger than every bucket lands in the largest and is downscaled there.
|
|
871
|
+
*
|
|
872
|
+
* @param {number} width
|
|
873
|
+
* @param {number} height
|
|
874
|
+
* @param {Array<{width: number, height: number}>} shapes - from {@link planTextureBuckets}
|
|
875
|
+
* @returns {number}
|
|
876
|
+
*/
|
|
877
|
+
export function getTextureBucketId( width, height, shapes ) {
|
|
699
878
|
|
|
700
|
-
const
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
return sizes.length - 1;
|
|
879
|
+
const w = Math.max( 1, width || 1 );
|
|
880
|
+
const h = Math.max( 1, height || 1 );
|
|
881
|
+
for ( let i = 0; i < shapes.length; i ++ ) if ( shapes[ i ].width >= w && shapes[ i ].height >= h ) return i;
|
|
882
|
+
return Math.max( 0, shapes.length - 1 );
|
|
705
883
|
|
|
706
884
|
}
|
|
707
885
|
|
package/src/EngineIssues.js
CHANGED
|
@@ -9,6 +9,7 @@ export const ISSUE_CODES = Object.freeze( {
|
|
|
9
9
|
ASSET_UNREACHABLE: 'asset.unreachable',
|
|
10
10
|
TEXTURE_BUILD_FAILED: 'texture.build_failed',
|
|
11
11
|
TEXTURE_PROCESSING_FALLBACK: 'texture.processing_fallback',
|
|
12
|
+
TEXTURE_LIMIT_EXCEEDED: 'texture.limit_exceeded',
|
|
12
13
|
ENVIRONMENT_LOAD_FAILED: 'environment.load_failed',
|
|
13
14
|
SETTING_UNKNOWN_KEY: 'setting.unknown_key',
|
|
14
15
|
RENDER_SIZE_DECLINED: 'render.size_declined',
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Vector3, Vector2, Color, Matrix3, Matrix4, FrontSide, BackSide, DoubleSide, RGBAFormat } from "three";
|
|
2
|
-
import { TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
|
|
2
|
+
import { TEXTURE_CONSTANTS, TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
|
|
3
|
+
import { ISSUE_CODES } from '../EngineIssues.js';
|
|
3
4
|
import { createLogger, fmt, warnOnce } from '../utils/Logger.js';
|
|
4
5
|
|
|
5
6
|
const log = createLogger( 'geometry' );
|
|
6
7
|
|
|
7
|
-
const MAX_TEXTURES_LIMIT =
|
|
8
|
+
const MAX_TEXTURES_LIMIT = TEXTURE_CONSTANTS.MAX_TEXTURES_LIMIT;
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* glTF 2.0 alphaMode for a three.js material: 0 OPAQUE, 1 MASK, 2 BLEND.
|
|
@@ -55,7 +56,11 @@ export function deriveAlphaMode( material ) {
|
|
|
55
56
|
|
|
56
57
|
export class GeometryExtractor {
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
/** @param {{issues?: import('../EngineIssues.js').IssueLog}} [options] */
|
|
60
|
+
constructor( { issues = null } = {} ) {
|
|
61
|
+
|
|
62
|
+
this._issues = issues;
|
|
63
|
+
this._droppedTextures = 0;
|
|
59
64
|
|
|
60
65
|
// Object pools for reusing objects
|
|
61
66
|
this._vectorPool = {
|
|
@@ -528,6 +533,17 @@ export class GeometryExtractor {
|
|
|
528
533
|
|
|
529
534
|
}
|
|
530
535
|
|
|
536
|
+
this._droppedTextures ++;
|
|
537
|
+
if ( this._droppedTextures === 1 ) {
|
|
538
|
+
|
|
539
|
+
this._issues?.record(
|
|
540
|
+
ISSUE_CODES.TEXTURE_LIMIT_EXCEEDED,
|
|
541
|
+
`scene needs more than ${MAX_TEXTURES_LIMIT} distinct textures for one map type; the rest render untextured`,
|
|
542
|
+
{ limit: MAX_TEXTURES_LIMIT, firstDropped: texture.name || texture.source?.uuid }
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
}
|
|
546
|
+
|
|
531
547
|
return - 1;
|
|
532
548
|
|
|
533
549
|
}
|
|
@@ -800,6 +816,8 @@ export class GeometryExtractor {
|
|
|
800
816
|
|
|
801
817
|
resetArrays() {
|
|
802
818
|
|
|
819
|
+
this._droppedTextures = 0;
|
|
820
|
+
|
|
803
821
|
// Reset triangle data
|
|
804
822
|
this.triangleData = null;
|
|
805
823
|
this.triangleCount = 0;
|
|
@@ -11,7 +11,8 @@ import { updateLoading } from '../Processor/utils.js';
|
|
|
11
11
|
import { BuildTimer } from './BuildTimer.js';
|
|
12
12
|
import { createLogger, fmt, workerLogLevel } from '../utils/Logger.js';
|
|
13
13
|
import { SRGBColorSpace } from 'three';
|
|
14
|
-
import { TRIANGLE_DATA_LAYOUT, TEXTURE_CONSTANTS, getTextureBucketId, packTextureIndex } from '../EngineDefaults.js';
|
|
14
|
+
import { TRIANGLE_DATA_LAYOUT, TEXTURE_CONSTANTS, getTextureBucketId, packTextureIndex, planTextureBuckets } from '../EngineDefaults.js';
|
|
15
|
+
import { ISSUE_CODES } from '../EngineIssues.js';
|
|
15
16
|
import BVHWorker from './Workers/BVHWorker.js?worker&inline';
|
|
16
17
|
import BVHRefitWorker from './Workers/BVHRefitWorker.js?worker&inline';
|
|
17
18
|
|
|
@@ -145,7 +146,7 @@ export class SceneProcessor {
|
|
|
145
146
|
_initProcessors() {
|
|
146
147
|
|
|
147
148
|
// Create and configure geometry extractor
|
|
148
|
-
this.geometryExtractor = new GeometryExtractor();
|
|
149
|
+
this.geometryExtractor = new GeometryExtractor( { issues: this.config.issues } );
|
|
149
150
|
|
|
150
151
|
// Create and configure BVH builder
|
|
151
152
|
this.bvhBuilder = new BVHBuilder();
|
|
@@ -869,8 +870,9 @@ export class SceneProcessor {
|
|
|
869
870
|
/**
|
|
870
871
|
* Group the extractor's seven per-type texture arrays into two consolidated colorSpace
|
|
871
872
|
* pools (sRGB: albedo+emissive; linear: normal/bump/roughness/metalness/displacement),
|
|
872
|
-
* each split into
|
|
873
|
-
* across types within a (pool, bucket)
|
|
873
|
+
* each split into at most MATERIAL_BUCKET_COUNT buckets whose shapes are planned from the
|
|
874
|
+
* pool's own texture dimensions. Textures are deduped across types within a (pool, bucket)
|
|
875
|
+
* so a shared image (e.g. ORM) costs one layer.
|
|
874
876
|
* @returns {{ srgbLists: Array<Array>, linearLists: Array<Array>, remap: Object }}
|
|
875
877
|
* bucket lists + per-type remap arrays (old per-type layer → packed bucket index).
|
|
876
878
|
* @private
|
|
@@ -881,6 +883,43 @@ export class SceneProcessor {
|
|
|
881
883
|
const K = TEXTURE_CONSTANTS.MATERIAL_BUCKET_COUNT;
|
|
882
884
|
const STRIDE = TEXTURE_CONSTANTS.BUCKET_LAYER_STRIDE;
|
|
883
885
|
|
|
886
|
+
const poolSizes = ( types ) => {
|
|
887
|
+
|
|
888
|
+
const seen = new Set();
|
|
889
|
+
const sizes = [];
|
|
890
|
+
|
|
891
|
+
for ( const arr of types ) {
|
|
892
|
+
|
|
893
|
+
for ( const tex of arr || [] ) {
|
|
894
|
+
|
|
895
|
+
if ( ! tex?.image ) continue;
|
|
896
|
+
const uuid = tex.source?.uuid ?? tex.uuid;
|
|
897
|
+
if ( seen.has( uuid ) ) continue;
|
|
898
|
+
seen.add( uuid );
|
|
899
|
+
sizes.push( { width: tex.image.width, height: tex.image.height } );
|
|
900
|
+
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
return sizes;
|
|
906
|
+
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
const srgbTypes = [ this.maps, this.emissiveMaps, this.sheenColorMaps, this.specularColorMaps ];
|
|
910
|
+
const linearTypes = [
|
|
911
|
+
this.normalMaps, this.bumpMaps, this.roughnessMaps, this.metalnessMaps, this.displacementMaps,
|
|
912
|
+
this.anisotropyMaps, this.transmissionMaps, this.clearcoatMaps, this.clearcoatRoughnessMaps,
|
|
913
|
+
this.sheenRoughnessMaps, this.iridescenceMaps, this.iridescenceThicknessMaps, this.specularIntensityMaps,
|
|
914
|
+
];
|
|
915
|
+
|
|
916
|
+
const srgbShapes = planTextureBuckets( poolSizes( srgbTypes ), cap, K );
|
|
917
|
+
const linearShapes = planTextureBuckets( poolSizes( linearTypes ), cap, K );
|
|
918
|
+
this._srgbBucketShapes = srgbShapes;
|
|
919
|
+
this._linearBucketShapes = linearShapes;
|
|
920
|
+
|
|
921
|
+
// Always K-length even when the plan needs fewer shapes: downstream nodes are built
|
|
922
|
+
// per MATERIAL_BUCKET_COUNT and a short array would leave stale bindings behind.
|
|
884
923
|
const srgbLists = Array.from( { length: K }, () => [] );
|
|
885
924
|
const linearLists = Array.from( { length: K }, () => [] );
|
|
886
925
|
const srgbDedup = Array.from( { length: K }, () => new Map() );
|
|
@@ -891,17 +930,25 @@ export class SceneProcessor {
|
|
|
891
930
|
this._srgbTexPacked = new Map();
|
|
892
931
|
this._linearTexPacked = new Map();
|
|
893
932
|
|
|
933
|
+
let bucketOverflowReported = false;
|
|
934
|
+
|
|
894
935
|
// Assign one texture to its (bucket, layer) within a pool; dedup by source uuid.
|
|
895
|
-
const assign = ( tex, lists, dedup, flat ) => {
|
|
936
|
+
const assign = ( tex, lists, dedup, flat, shapes ) => {
|
|
896
937
|
|
|
897
938
|
if ( ! tex || ! tex.image ) return - 1;
|
|
898
|
-
const bucket = getTextureBucketId( tex.image.width, tex.image.height,
|
|
939
|
+
const bucket = getTextureBucketId( tex.image.width, tex.image.height, shapes );
|
|
899
940
|
const uuid = tex.source?.uuid ?? tex.uuid;
|
|
900
941
|
const seen = dedup[ bucket ].get( uuid );
|
|
901
942
|
if ( seen !== undefined ) return packTextureIndex( bucket, seen );
|
|
902
943
|
if ( lists[ bucket ].length >= STRIDE ) {
|
|
903
944
|
|
|
904
945
|
log.warn( `texture bucket ${bucket} full (${STRIDE}); dropping a map` );
|
|
946
|
+
if ( ! bucketOverflowReported ) this.config.issues?.record(
|
|
947
|
+
ISSUE_CODES.TEXTURE_LIMIT_EXCEEDED,
|
|
948
|
+
`texture bucket ${bucket} full (${STRIDE} layers); the rest render untextured`,
|
|
949
|
+
{ bucket, limit: STRIDE }
|
|
950
|
+
);
|
|
951
|
+
bucketOverflowReported = true;
|
|
905
952
|
return - 1;
|
|
906
953
|
|
|
907
954
|
}
|
|
@@ -917,27 +964,27 @@ export class SceneProcessor {
|
|
|
917
964
|
|
|
918
965
|
// Per-type arrays hold unique textures indexed by the layer the extractor assigned
|
|
919
966
|
// (= array position), so remap[type][oldLayer] = packed index.
|
|
920
|
-
const remapType = ( arr, lists, dedup, flat ) => ( arr || [] ).map( tex => assign( tex, lists, dedup, flat ) );
|
|
967
|
+
const remapType = ( arr, lists, dedup, flat, shapes ) => ( arr || [] ).map( tex => assign( tex, lists, dedup, flat, shapes ) );
|
|
921
968
|
|
|
922
969
|
const remap = {
|
|
923
|
-
albedo: remapType( this.maps, srgbLists, srgbDedup, this._srgbTexPacked ),
|
|
924
|
-
emissive: remapType( this.emissiveMaps, srgbLists, srgbDedup, this._srgbTexPacked ),
|
|
925
|
-
normal: remapType( this.normalMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
926
|
-
bump: remapType( this.bumpMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
927
|
-
roughness: remapType( this.roughnessMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
928
|
-
metalness: remapType( this.metalnessMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
929
|
-
displacement: remapType( this.displacementMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
930
|
-
anisotropy: remapType( this.anisotropyMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
970
|
+
albedo: remapType( this.maps, srgbLists, srgbDedup, this._srgbTexPacked, srgbShapes ),
|
|
971
|
+
emissive: remapType( this.emissiveMaps, srgbLists, srgbDedup, this._srgbTexPacked, srgbShapes ),
|
|
972
|
+
normal: remapType( this.normalMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
973
|
+
bump: remapType( this.bumpMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
974
|
+
roughness: remapType( this.roughnessMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
975
|
+
metalness: remapType( this.metalnessMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
976
|
+
displacement: remapType( this.displacementMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
977
|
+
anisotropy: remapType( this.anisotropyMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
931
978
|
// Extension maps — data maps → linear pool; color maps (sheenColor, specularColor) → sRGB pool.
|
|
932
|
-
transmission: remapType( this.transmissionMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
933
|
-
clearcoat: remapType( this.clearcoatMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
934
|
-
clearcoatRoughness: remapType( this.clearcoatRoughnessMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
935
|
-
sheenColor: remapType( this.sheenColorMaps, srgbLists, srgbDedup, this._srgbTexPacked ),
|
|
936
|
-
sheenRoughness: remapType( this.sheenRoughnessMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
937
|
-
iridescence: remapType( this.iridescenceMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
938
|
-
iridescenceThickness: remapType( this.iridescenceThicknessMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
939
|
-
specularIntensity: remapType( this.specularIntensityMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
940
|
-
specularColor: remapType( this.specularColorMaps, srgbLists, srgbDedup, this._srgbTexPacked ),
|
|
979
|
+
transmission: remapType( this.transmissionMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
980
|
+
clearcoat: remapType( this.clearcoatMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
981
|
+
clearcoatRoughness: remapType( this.clearcoatRoughnessMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
982
|
+
sheenColor: remapType( this.sheenColorMaps, srgbLists, srgbDedup, this._srgbTexPacked, srgbShapes ),
|
|
983
|
+
sheenRoughness: remapType( this.sheenRoughnessMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
984
|
+
iridescence: remapType( this.iridescenceMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
985
|
+
iridescenceThickness: remapType( this.iridescenceThicknessMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
986
|
+
specularIntensity: remapType( this.specularIntensityMaps, linearLists, linearDedup, this._linearTexPacked, linearShapes ),
|
|
987
|
+
specularColor: remapType( this.specularColorMaps, srgbLists, srgbDedup, this._srgbTexPacked, srgbShapes ),
|
|
941
988
|
};
|
|
942
989
|
|
|
943
990
|
return { srgbLists, linearLists, remap };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace, LinearSRGBColorSpace, RepeatWrapping } from "three";
|
|
2
|
-
import { TEXTURE_CONSTANTS, MEMORY_CONSTANTS, DEFAULT_TEXTURE_MATRIX, MATERIAL_DATA_LAYOUT, normalizeAttenuationDistance } from '../EngineDefaults.js';
|
|
2
|
+
import { alignBucketWidth, TEXTURE_CONSTANTS, MEMORY_CONSTANTS, DEFAULT_TEXTURE_MATRIX, MATERIAL_DATA_LAYOUT, normalizeAttenuationDistance } from '../EngineDefaults.js';
|
|
3
3
|
import TexturesWorker from './Workers/TexturesWorker.js?worker&inline';
|
|
4
4
|
import { ISSUE_CODES } from '../EngineIssues.js';
|
|
5
5
|
import { linearToSRGB } from './ToneMapCPU.js';
|
|
@@ -1318,18 +1318,17 @@ export class TextureCreator {
|
|
|
1318
1318
|
|
|
1319
1319
|
}
|
|
1320
1320
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1321
|
+
// Scale down proportionally only when the cap is exceeded; native res is kept below it.
|
|
1322
|
+
const longest = Math.max( maxWidth, maxHeight );
|
|
1323
|
+
if ( longest > this.maxTextureSize ) {
|
|
1323
1324
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
maxWidth = Math.max( 1, Math.floor( maxWidth / 2 ) );
|
|
1328
|
-
maxHeight = Math.max( 1, Math.floor( maxHeight / 2 ) );
|
|
1325
|
+
const scale = this.maxTextureSize / longest;
|
|
1326
|
+
maxWidth = Math.round( maxWidth * scale );
|
|
1327
|
+
maxHeight = Math.round( maxHeight * scale );
|
|
1329
1328
|
|
|
1330
1329
|
}
|
|
1331
1330
|
|
|
1332
|
-
return { maxWidth, maxHeight };
|
|
1331
|
+
return { maxWidth: alignBucketWidth( maxWidth, this.maxTextureSize ), maxHeight: Math.max( 1, maxHeight ) };
|
|
1333
1332
|
|
|
1334
1333
|
}
|
|
1335
1334
|
|
|
@@ -619,23 +619,22 @@ function calculateOptimalDimensions( textures, maxTextureSize ) {
|
|
|
619
619
|
|
|
620
620
|
}
|
|
621
621
|
|
|
622
|
-
//
|
|
623
|
-
|
|
624
|
-
|
|
622
|
+
// Scale down proportionally only when the cap is exceeded; native res is kept below it.
|
|
623
|
+
const cap = Math.min( maxTextureSize, MEMORY_LIMITS.MAX_TEXTURE_DIMENSION );
|
|
624
|
+
const longest = Math.max( maxWidth, maxHeight );
|
|
625
|
+
if ( longest > cap ) {
|
|
625
626
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
// Halve only while a dimension exceeds the cap (preserves native res up to the cap).
|
|
631
|
-
while ( maxWidth > maxTextureSize || maxHeight > maxTextureSize ) {
|
|
632
|
-
|
|
633
|
-
maxWidth = Math.max( 1, Math.floor( maxWidth / 2 ) );
|
|
634
|
-
maxHeight = Math.max( 1, Math.floor( maxHeight / 2 ) );
|
|
627
|
+
const scale = cap / longest;
|
|
628
|
+
maxWidth = Math.round( maxWidth * scale );
|
|
629
|
+
maxHeight = Math.round( maxHeight * scale );
|
|
635
630
|
|
|
636
631
|
}
|
|
637
632
|
|
|
638
|
-
|
|
633
|
+
// An RGBA8 row upload must be a multiple of 256 bytes, so widths land on 64 texels.
|
|
634
|
+
// Mirrors alignBucketWidth in EngineDefaults (not importable from worker context).
|
|
635
|
+
maxWidth = Math.min( cap, Math.max( 4, Math.ceil( Math.max( 1, maxWidth ) / 64 ) * 64 ) );
|
|
636
|
+
|
|
637
|
+
return { maxWidth, maxHeight: Math.max( 1, maxHeight ) };
|
|
639
638
|
|
|
640
639
|
}
|
|
641
640
|
|
|
@@ -645,7 +644,7 @@ function calculateReducedDimensions( textures, maxTextureSize ) {
|
|
|
645
644
|
const original = calculateOptimalDimensions( textures, maxTextureSize );
|
|
646
645
|
|
|
647
646
|
return {
|
|
648
|
-
maxWidth: Math.max(
|
|
647
|
+
maxWidth: Math.max( 64, Math.ceil( original.maxWidth / 128 ) * 64 ),
|
|
649
648
|
maxHeight: Math.max( 1, Math.floor( original.maxHeight / 2 ) )
|
|
650
649
|
};
|
|
651
650
|
|
package/src/Processor/utils.js
CHANGED
|
@@ -38,12 +38,6 @@ export function getDisplaySamples( pathTracerStage ) {
|
|
|
38
38
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export function getNearestPowerOf2( size ) {
|
|
42
|
-
|
|
43
|
-
return Math.pow( 2, Math.ceil( Math.log2( size ) ) );
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
41
|
export function disposeMaterial( material ) {
|
|
48
42
|
|
|
49
43
|
if ( Array.isArray( material ) ) {
|
package/src/TSL/BVHTraversal.js
CHANGED
|
@@ -24,7 +24,9 @@ import { HitInfo } from './Struct.js';
|
|
|
24
24
|
import { getDatafromStorageBuffer } from './Common.js';
|
|
25
25
|
|
|
26
26
|
const MAX_STACK_DEPTH = 32;
|
|
27
|
-
|
|
27
|
+
// Hang guard only — tripping it reports a MISS, which the shade kernel pays out as
|
|
28
|
+
// full-intensity environment. San Miguel needs ~1024; cost is flat from 1024 up.
|
|
29
|
+
const MAX_BVH_ITERATIONS = 4096;
|
|
28
30
|
const BVH_STRIDE = 4;
|
|
29
31
|
const TRI_STRIDE = 8;
|
|
30
32
|
const HUGE_VAL = 1e8;
|
|
@@ -10,11 +10,12 @@ import { getDatafromStorageBuffer } from './Common.js';
|
|
|
10
10
|
import { TEXTURE_CONSTANTS } from '../EngineDefaults.js';
|
|
11
11
|
|
|
12
12
|
// ================================================================================
|
|
13
|
-
// CONSOLIDATED
|
|
13
|
+
// CONSOLIDATED SHAPE-BUCKETED MATERIAL TEXTURES
|
|
14
14
|
// ================================================================================
|
|
15
15
|
// Material maps are packed into two colorSpace pools (sRGB: albedo+emissive; linear:
|
|
16
16
|
// normal/bump/roughness/metalness/displacement), each split into MATERIAL_BUCKET_COUNT
|
|
17
|
-
//
|
|
17
|
+
// (width, height) buckets so a small or oddly-shaped map no longer pays a large neighbour's
|
|
18
|
+
// footprint. Bucket shapes are planned per scene (planTextureBuckets) — not powers of two.
|
|
18
19
|
// A map's stored index encodes (bucket, layer) as bucket * BUCKET_LAYER_STRIDE + layer.
|
|
19
20
|
//
|
|
20
21
|
// The bucket texture nodes live at module level (same pattern as gobo/IES/shadowAlbedo):
|