diff --git a/examples/screenshots/webgpu_compute_particles_rain.jpg b/examples/screenshots/webgpu_compute_particles_rain.jpg index 978d9192a9482f..99de8898edfb10 100644 Binary files a/examples/screenshots/webgpu_compute_particles_rain.jpg and b/examples/screenshots/webgpu_compute_particles_rain.jpg differ diff --git a/examples/screenshots/webgpu_compute_particles_snow.jpg b/examples/screenshots/webgpu_compute_particles_snow.jpg index fb4bb58d6a3a7b..fcedbe45f9c7b5 100644 Binary files a/examples/screenshots/webgpu_compute_particles_snow.jpg and b/examples/screenshots/webgpu_compute_particles_snow.jpg differ diff --git a/examples/screenshots/webgpu_tsl_compute_attractors_particles.jpg b/examples/screenshots/webgpu_tsl_compute_attractors_particles.jpg index f2079b0751e3a6..a80bee186490ef 100644 Binary files a/examples/screenshots/webgpu_tsl_compute_attractors_particles.jpg and b/examples/screenshots/webgpu_tsl_compute_attractors_particles.jpg differ diff --git a/src/nodes/core/ConstNode.js b/src/nodes/core/ConstNode.js index 299b8abf96fd26..cfe0854b7b48e7 100644 --- a/src/nodes/core/ConstNode.js +++ b/src/nodes/core/ConstNode.js @@ -34,6 +34,14 @@ class ConstNode extends InputNode { */ this.isConstNode = true; + /** + * Whether this constant is an implicit number whose type can adapt to other operands. + * + * @type {boolean} + * @default false + */ + this.isWeak = false; + } /** @@ -54,7 +62,13 @@ class ConstNode extends InputNode { if ( _regNum.test( type ) && _regNum.test( output ) ) { - return builder.generateConst( output, this.value ); + let value = this.value; + + // Preserve the declared integer value before converting to the output type. + if ( type === 'int' ) value = Math.trunc( value ); + else if ( type === 'uint' ) value = value >= 0 ? Math.trunc( value ) : 0; + + return builder.generateConst( output, value ); } @@ -62,6 +76,22 @@ class ConstNode extends InputNode { } + serialize( data ) { + + super.serialize( data ); + + data.isWeak = this.isWeak; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.isWeak = data.isWeak === true; + + } + } export default ConstNode; diff --git a/src/nodes/core/NodeBuilder.js b/src/nodes/core/NodeBuilder.js index d21292f9c78e0e..572ceb16585f55 100644 --- a/src/nodes/core/NodeBuilder.js +++ b/src/nodes/core/NodeBuilder.js @@ -65,6 +65,8 @@ const _toFloat = ( value ) => { }; +const _componentTypeRanks = { bool: 0, uint: 1, int: 2, float: 3 }; + const _checkWriteUsage = ( data ) => { if ( data.writeUsageCount > 0 ) return true; @@ -1527,8 +1529,8 @@ class NodeBuilder { } if ( type === 'float' ) return _toFloat( value ); - if ( type === 'int' ) return `${ Math.round( value ) }`; - if ( type === 'uint' ) return value >= 0 ? `${ Math.round( value ) }u` : '0u'; + if ( type === 'int' ) return `${ Math.trunc( value ) }`; + if ( type === 'uint' ) return value >= 0 ? `${ Math.trunc( value ) }u` : '0u'; if ( type === 'bool' ) return value ? 'true' : 'false'; if ( type === 'color' ) return `${ this.getType( 'vec3' ) }( ${ _toFloat( value.r ) }, ${ _toFloat( value.g ) }, ${ _toFloat( value.b ) } )`; @@ -1639,6 +1641,18 @@ class NodeBuilder { } + /** + * Whether the given type is a scalar type or not. + * + * @param {string} type - The type to check. + * @return {boolean} Whether the given type is a scalar type or not. + */ + isScalar( type ) { + + return type === 'float' || type === 'bool' || type === 'int' || type === 'uint'; + + } + /** * Returns whether the given name is a reserved keyword of the backend's * shading language. Backends override this method to provide their @@ -1901,6 +1915,52 @@ class NodeBuilder { } + /** + * Returns the common component type of the input nodes. Explicit types are + * promoted first, then weak constants adopt that type, being truncated like other + * integer constants. Only weak constants out of the integer range promote it. + * Inputs consisting only of weak constants default to float. + * + * @param {...?Node} nodes - The input nodes. + * @return {string} The promoted component type. + */ + getPromotedComponentType( ...nodes ) { + + let type = null; + let hasWeak = false; + let fitsUint = true; + let fitsInt = true; + + for ( const node of nodes ) { + + if ( node === null ) continue; + + if ( node.isWeak === true ) { + + const value = Math.trunc( node.value ); + + hasWeak = true; + fitsUint = fitsUint && value >= 0 && value <= 0xffffffff; + fitsInt = fitsInt && value >= - 0x80000000 && value <= 0x7fffffff; + + } else { + + const componentType = this.getComponentType( node.getNodeType( this ) ); + + if ( type === null || _componentTypeRanks[ componentType ] > _componentTypeRanks[ type ] ) type = componentType; + + } + + } + + if ( type === null || ( type === 'bool' && hasWeak ) ) return 'float'; + if ( type === 'uint' && fitsUint === false ) return fitsInt ? 'int' : 'float'; + if ( type === 'int' && fitsInt === false ) return 'float'; + + return type; + + } + /** * Returns the integer type pendant for the given type. * diff --git a/src/nodes/math/MathNode.js b/src/nodes/math/MathNode.js index 69ea51acc96ad0..db6f0947bc3dc5 100644 --- a/src/nodes/math/MathNode.js +++ b/src/nodes/math/MathNode.js @@ -109,21 +109,31 @@ class MathNode extends Node { const bLen = builder.isMatrix( bType ) ? 0 : builder.getTypeLength( bType ); const cLen = builder.isMatrix( cType ) ? 0 : builder.getTypeLength( cType ); + let type; + if ( aLen > bLen && aLen > cLen ) { - return aType; + type = aType; } else if ( bLen > cLen ) { - return bType; + type = bType; } else if ( cLen > aLen ) { - return cType; + type = cType; + + } else { + + type = aType; } - return aType; + if ( builder.isMatrix( type ) ) return type; + + const promotedType = _floatMethods.has( this.method ) ? 'float' : builder.getPromotedComponentType( this.aNode, this.bNode, this.cNode ); + + return builder.changeComponentType( type, promotedType ); } @@ -173,7 +183,7 @@ class MathNode extends Node { } else if ( method === MathNode.RECIPROCAL ) { - outputNode = div( 1.0, aNode ); + outputNode = div( float( 1 ), aNode ); } else if ( method === MathNode.DIFFERENCE ) { @@ -391,6 +401,18 @@ MathNode.REFRACT = 'refract'; MathNode.SMOOTHSTEP = 'smoothstep'; MathNode.FACEFORWARD = 'faceforward'; +// Methods that are only defined for floating-point types. + +const _floatMethods = new Set( [ + MathNode.RADIANS, MathNode.DEGREES, MathNode.EXP, MathNode.EXP2, MathNode.LOG, MathNode.LOG2, + MathNode.SQRT, MathNode.INVERSE_SQRT, MathNode.FLOOR, MathNode.CEIL, MathNode.NORMALIZE, MathNode.FRACT, + MathNode.SIN, MathNode.SINH, MathNode.COS, MathNode.COSH, MathNode.TAN, MathNode.TANH, + MathNode.ASIN, MathNode.ASINH, MathNode.ACOS, MathNode.ACOSH, MathNode.ATAN, MathNode.ATANH, + MathNode.LENGTH, MathNode.DFDX, MathNode.DFDY, MathNode.ROUND, MathNode.TRUNC, MathNode.FWIDTH, MathNode.RECIPROCAL, + MathNode.STEP, MathNode.REFLECT, MathNode.DISTANCE, MathNode.DOT, MathNode.CROSS, MathNode.POW, + MathNode.MIX, MathNode.REFRACT, MathNode.SMOOTHSTEP, MathNode.FACEFORWARD +] ); + export default MathNode; // 1 inputs diff --git a/src/nodes/math/OperatorNode.js b/src/nodes/math/OperatorNode.js index b96ed6d946559b..b14e0e6aa07100 100644 --- a/src/nodes/math/OperatorNode.js +++ b/src/nodes/math/OperatorNode.js @@ -120,10 +120,6 @@ class OperatorNode extends Node { return output || 'void'; - } else if ( op === '%' ) { - - return typeA; - } else if ( op === '~' || op === '&' || op === '|' || op === '^' || op === '>>' || op === '<<' ) { return builder.getIntegerType( typeA ); @@ -144,13 +140,17 @@ class OperatorNode extends Node { return typeLength > 1 ? `bvec${ typeLength }` : 'bool'; + } else if ( typeB === null ) { + + return typeA; + } else { // Handle matrix operations if ( builder.isMatrix( typeA ) ) { - if ( typeB === 'float' ) { + if ( builder.isScalar( typeB ) ) { return typeA; // matrix * scalar = matrix @@ -166,7 +166,7 @@ class OperatorNode extends Node { } else if ( builder.isMatrix( typeB ) ) { - if ( typeA === 'float' ) { + if ( builder.isScalar( typeA ) ) { return typeB; // scalar * matrix = matrix @@ -180,15 +180,13 @@ class OperatorNode extends Node { // Handle non-matrix cases - if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) { + // anytype x anytype: use the greater length vector - // anytype x anytype: use the greater length vector + const type = builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ? typeB : typeA; - return typeB; + const promotedType = builder.getPromotedComponentType( aNode, bNode ); - } - - return typeA; + return builder.changeComponentType( type, promotedType ); } @@ -212,33 +210,18 @@ class OperatorNode extends Node { if ( op === '<' || op === '>' || op === '<=' || op === '>=' || op === '==' || op === '!=' ) { - if ( builder.isVector( typeA ) ) { - - typeB = typeA; + const length = Math.max( builder.getTypeLength( typeA ), builder.getTypeLength( typeB ) ); - } else if ( builder.isVector( typeB ) ) { - - typeA = typeB; - - } else if ( typeA !== typeB ) { - - typeA = typeB = 'float'; - - } + typeA = typeB = builder.getTypeFromLength( length, builder.getPromotedComponentType( aNode, bNode ) ); } else if ( op === '>>' || op === '<<' ) { typeA = type; typeB = builder.changeComponentType( typeB, 'uint' ); - } else if ( op === '%' ) { - - typeA = type; - typeB = builder.isInteger( typeA ) && builder.isInteger( typeB ) ? typeB : typeA; - } else if ( builder.isMatrix( typeA ) ) { - if ( typeB === 'float' ) { + if ( builder.isScalar( typeB ) ) { // Keep matrix type for typeA, but ensure typeB stays float @@ -261,7 +244,7 @@ class OperatorNode extends Node { } else if ( builder.isMatrix( typeB ) ) { - if ( typeA === 'float' ) { + if ( builder.isScalar( typeA ) ) { // Keep matrix type for typeB, but ensure typeA stays float @@ -362,11 +345,11 @@ class OperatorNode extends Node { // Handle matrix operations - if ( builder.isMatrix( typeA ) && typeB === 'float' ) { + if ( builder.isMatrix( typeA ) && builder.isScalar( typeB ) ) { return builder.format( `( ${ b } ${ op } ${ a } )`, type, output ); - } else if ( typeA === 'float' && builder.isMatrix( typeB ) ) { + } else if ( builder.isScalar( typeA ) && builder.isMatrix( typeB ) ) { return builder.format( `${ a } ${ op } ${ b }`, type, output ); @@ -394,7 +377,7 @@ class OperatorNode extends Node { } else { - if ( builder.isMatrix( typeA ) && typeB === 'float' ) { + if ( builder.isMatrix( typeA ) && builder.isScalar( typeB ) ) { return builder.format( `${ b } ${ op } ${ a }`, type, output ); diff --git a/src/nodes/tsl/TSLCore.js b/src/nodes/tsl/TSLCore.js index d5aa020c5a6a2e..ce32167520736b 100644 --- a/src/nodes/tsl/TSLCore.js +++ b/src/nodes/tsl/TSLCore.js @@ -883,7 +883,7 @@ for ( const float of floats ) floatsCacheMap.set( - float, new ConstNode( - floa const cacheMaps = { bool: boolsCacheMap, uint: uintsCacheMap, ints: intsCacheMap, float: floatsCacheMap }; -const constNodesCacheMap = new Map( [ ...boolsCacheMap, ...floatsCacheMap ] ); +const constNodesCacheMap = new Map( boolsCacheMap ); const getConstNode = ( value, type ) => { @@ -897,12 +897,19 @@ const getConstNode = ( value, type ) => { } else { - return new ConstNode( value, type ); + const node = new ConstNode( value, type ); + + // Implicit numbers are weak and can adapt to the type of other operands. + node.isWeak = ! type && typeof value === 'number'; + + return node; } }; +for ( const value of floatsCacheMap.keys() ) constNodesCacheMap.set( value, getConstNode( value ) ); + const ConvertType = function ( type, cacheMap = null ) { return ( ...params ) => { diff --git a/test/unit/addons/tsl/TSLLogicBitwise.tests.js b/test/unit/addons/tsl/TSLLogicBitwise.tests.js index a9201ec2578592..b01b165560c425 100644 --- a/test/unit/addons/tsl/TSLLogicBitwise.tests.js +++ b/test/unit/addons/tsl/TSLLogicBitwise.tests.js @@ -1,5 +1,5 @@ import { - float, int, uint, vec3, + bool, float, int, uint, ivec3, vec3, equal, notEqual, lessThan, greaterThan, lessThanEqual, greaterThanEqual, and, or, not, xor, select, bitAnd, bitOr, bitXor, bitNot, shiftLeft, shiftRight, @@ -22,6 +22,22 @@ export default QUnit.module( 'TSL', () => { QUnit.module( 'logic and comparison operators', () => { + gpuTest( 'mixed-type equality is independent of operand order', ( { assert } ) => { + + assert.eq( vec3( vec3( 1.5 ).equal( ivec3( 1 ) ) ), vec3( 0 ), 'Float and integer vectors differ in every component' ); + assert.eq( vec3( ivec3( 1 ).equal( vec3( 1.5 ) ) ), vec3( 0 ), 'Reversing operands preserves every fractional component' ); + assert.eq( vec3( vec3( 1 ).equal( ivec3( 1 ) ) ), vec3( 1 ), 'Matching vectors compare equal' ); + assert.eq( vec3( ivec3( 1 ).equal( vec3( 1 ) ) ), vec3( 1 ), 'Matching vectors compare equal in reverse order' ); + + assert.eq( float( float( 1.5 ).equal( int( 1 ) ) ), float( 0 ), 'float versus int is false' ); + assert.eq( float( int( 1 ).equal( float( 1.5 ) ) ), float( 0 ), 'int versus float is false' ); + assert.eq( float( bool( true ).equal( uint( 1 ) ) ), float( 1 ), 'bool versus uint is true' ); + assert.eq( float( uint( 1 ).equal( bool( true ) ) ), float( 1 ), 'uint versus bool is true' ); + assert.eq( float( bool( true ).equal( float( 1.5 ) ) ), float( 0 ), 'bool versus float is false' ); + assert.eq( float( float( 1.5 ).equal( bool( true ) ) ), float( 0 ), 'float versus bool is false' ); + + } ); + gpuTest( 'relational operators at and around the boundary', ( { assert } ) => { assert.eq( float( equal( float( 3 ), float( 3 ) ) ), float( 1 ), 'equal(3,3) is true' ); @@ -89,8 +105,8 @@ export default QUnit.module( 'TSL', () => { assert.eq( bitXor( int( 6 ), int( 3 ) ), int( 5 ), '0b0110 ^ 0b0011 == 0b0101 == 5' ); // bitNot is two's-complement: ~x == -x - 1. - assert.eq( bitNot( int( 0 ) ), int( -1 ), '~0 == -1' ); - assert.eq( bitNot( int( 5 ) ), int( -6 ), '~5 == -5-1 == -6' ); + assert.eq( bitNot( int( 0 ) ), int( - 1 ), '~0 == -1' ); + assert.eq( bitNot( int( 5 ) ), int( - 6 ), '~5 == -5-1 == -6' ); } ); diff --git a/test/unit/addons/tsl/TSLTypeConstructors.tests.js b/test/unit/addons/tsl/TSLTypeConstructors.tests.js index e5ce42e48dd055..3a3cca4fd7c8df 100644 --- a/test/unit/addons/tsl/TSLTypeConstructors.tests.js +++ b/test/unit/addons/tsl/TSLTypeConstructors.tests.js @@ -98,6 +98,8 @@ export default QUnit.module( 'TSL', () => { assert.eq( c.r, float( 0.2 ), 'color(0.2,0.4,0.6).r == 0.2' ); assert.eq( c.g, float( 0.4 ), 'color(0.2,0.4,0.6).g == 0.4' ); assert.eq( c.b, float( 0.6 ), 'color(0.2,0.4,0.6).b == 0.6' ); + assert.eq( c, vec3( 0.2, 0.4, 0.6 ), 'color and vec3 have the same shader representation' ); + assert.eq( vec3( 0.2, 0.4, 0.6 ), c, 'vec3 and color are equivalent in either order' ); // Arithmetic works the same as a vec3. closeAbs (not eq) since // this crosses a float32 addition, which isn't guaranteed to be diff --git a/test/unit/addons/tsl/gpu-test-utils.js b/test/unit/addons/tsl/gpu-test-utils.js index 34b2120be331f5..d8fa00b6065d3a 100644 --- a/test/unit/addons/tsl/gpu-test-utils.js +++ b/test/unit/addons/tsl/gpu-test-utils.js @@ -171,8 +171,9 @@ class AssertWriteNode extends Node { setup( builder ) { - const type1 = this.value1.getNodeType( builder ); - const type2 = this.value2.getNodeType( builder ); + // Compare shader representations so aliases such as color and vec3 are equivalent. + const type1 = builder.getVectorType( this.value1.getNodeType( builder ) ); + const type2 = builder.getVectorType( this.value2.getNodeType( builder ) ); if ( type1 !== type2 ) { diff --git a/test/unit/tsl/cases.js b/test/unit/tsl/cases.js index 40df9b145ca23c..cd533f89d1e3bd 100644 --- a/test/unit/tsl/cases.js +++ b/test/unit/tsl/cases.js @@ -1,4 +1,4 @@ -import { Break, Continue, Fn, If, Loop, Switch, array, bool, float, int, inverse, ivec3, mat3, mat4, mix, mul, select, time, uint, uniform, uv, vec2, vec3, vec4 } from '../../../src/Three.TSL.js'; +import { Break, Continue, Fn, If, Loop, Switch, array, bool, clamp, determinant, float, int, inverse, ivec3, mat2, mat3, mat4, mix, mul, select, time, transpose, uint, uniform, uv, vec2, vec3, vec4 } from '../../../src/Three.TSL.js'; // Create a fresh graph for every test and backend. export const cases = { @@ -9,18 +9,91 @@ export const cases = { swizzle: () => vec3( 1, 2, 3 ).zyx.mul( 0.5 ), - // Operand order matters: scalar operators infer their type from the left operand. - autoConvertIntToFloat: () => Fn( () => float( 0.5 ).add( int( 2 ) ) )(), + // Explicit types promote to the broader component type in either operand order. + autoConvertIntToFloat: () => float( 0.5 ).mul( int( 1 ) ), - autoConvertFloatToInt: () => Fn( () => int( 2 ).add( float( 0.5 ) ) )(), + // An implicit integer-valued number adapts to int; an explicit float promotes it. + autoConvertFloatToInt: () => int( 2 ).add( 1 ), - autoConvertUintToFloat: () => Fn( () => float( 0.5 ).mul( uint( 3 ) ) )(), + autoPromoteIntToFloat: () => int( 1 ).mul( float( 0.5 ) ), + + autoConvertUintToFloat: () => float( 0.5 ).mul( uint( 1 ) ), + + autoPromoteUintToFloat: () => uint( 1 ).mul( float( 0.5 ) ), + + autoPromoteUintToInt: () => uint( 1 ).add( int( - 1 ) ), + + autoPromoteIntAndUint: () => int( - 1 ).add( uint( 1 ) ), + + // Equality uses the same common type in either operand order. + equalFloatInt: () => float( 1.5 ).equal( int( 1 ) ), + + equalIntFloat: () => int( 1 ).equal( float( 1.5 ) ), + + equalBoolUint: () => bool( true ).equal( uint( 1 ) ), + + equalUintBool: () => uint( 1 ).equal( bool( true ) ), + + equalBoolFloat: () => bool( true ).equal( float( 1.5 ) ), + + equalFloatBool: () => float( 1.5 ).equal( bool( true ) ), + + equalFloatIntVector: () => vec3( 1.5 ).equal( ivec3( 1 ) ), + + equalIntFloatVector: () => ivec3( 1 ).equal( vec3( 1.5 ) ), + + // Explicit integer constants truncate before promotion to float, including cached values. + truncatedUintToFloat: () => float( 1 ).mul( uint( 0.4 ) ), + + truncatedUintConstants: () => uint( 1.5 ), + + truncatedIntConstants: () => int( - 1.5 ), + + truncatedUintFraction: () => uint( 0.6 ), + + truncatedIntFraction: () => int( - 0.6 ), + + clampedNegativeUint: () => uint( - 0.4 ), autoConvertScalarToVector: () => vec3( 1, 2, 3 ).add( 0.5 ), autoConvertVectorToFloat: () => vec3( 0.5 ).add( ivec3( 1, 2, 3 ) ), - autoConvertVectorToInt: () => ivec3( 1, 2, 3 ).add( vec3( 0.5 ) ), + // A weak scalar adapts to the integer vector; an explicit float vector promotes it. + autoConvertVectorToInt: () => ivec3( 1, 2, 3 ).add( 1 ), + + autoPromoteIntVectorToFloat: () => ivec3( 1, 2, 3 ).add( vec3( 0.5 ) ), + + // Integer-valued weak numbers keep unsigned index math in uint. + weakUintIndexMath: () => uint( 70 ).div( 64 ), + + weakFloatDefault: () => mul( 1, 2 ), + + // Weak numbers adopt the explicit integer type, truncated if needed; values out of its range promote it. + weakUintFraction: () => uint( 1 ).mul( 0.5 ), + + weakIntFraction: () => int( 3 ).mul( 1.5 ), + + weakPromoteNegative: () => uint( 1 ).add( - 1 ), + + weakPromoteOutOfRange: () => uint( 1 ).add( 0x100000000 ), + + // A shared weak constant adapts independently to each use. + weakSharedConstant: () => uint( 1 ).add( 1 ).add( float( 1 ).add( 1 ) ), + + // Math functions promote across all arguments, regardless of their order. + weakMathFunctions: () => clamp( uint( 1 ), 0, 2 ), + + weakMathFunctionsReordered: () => clamp( 0, uint( 1 ), 2 ), + + // Float-only math functions promote integer arguments to float. + floatOnlyMathFunctions: () => int( 2 ).pow( 2 ), + + // Integer math functions keep integer arguments. + integerMathFunctions: () => uint( 1 ).max( 2 ), + + // Matrix functions keep the matrix type. + squareMatrixFunctions: () => determinant( transpose( inverse( mat2( 1, 2, 3, 4 ) ) ) ), vectorComposition: () => vec4( vec2( 1, 2 ), int( 3 ), uint( 4 ) ), diff --git a/test/unit/tsl/code.js b/test/unit/tsl/code.js index 5ea1480d824ef3..02a1d99ce65daf 100644 --- a/test/unit/tsl/code.js +++ b/test/unit/tsl/code.js @@ -2,6 +2,7 @@ import WGSLNodeBuilder from '../../../src/renderers/webgpu/nodes/WGSLNodeBuilder import GLSLNodeBuilder from '../../../src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js'; import { BufferAttribute } from '../../../src/core/BufferAttribute.js'; import { BufferGeometry } from '../../../src/core/BufferGeometry.js'; +import { WebGLCoordinateSystem, WebGPUCoordinateSystem } from '../../../src/constants.js'; export const builders = { wgsl: WGSLNodeBuilder, @@ -11,7 +12,8 @@ export const builders = { export function generateCode( createNode, language ) { // These cases exercise node code generation without a GPU or renderer init. - const renderer = { backend: {}, debug: { diagnostics: { keywords: false } } }; + const coordinateSystem = language === 'glsl' ? WebGLCoordinateSystem : WebGPUCoordinateSystem; + const renderer = { backend: {}, coordinateSystem, debug: { diagnostics: { keywords: false } } }; const geometry = new BufferGeometry(); geometry.setAttribute( 'uv', new BufferAttribute( new Float32Array( 2 ), 2 ) ); const builder = new builders[ language ]( { geometry }, renderer ); diff --git a/test/unit/tsl/reference.glsl b/test/unit/tsl/reference.glsl index 226ae3e009287c..b79aaca2bdcc2f 100644 --- a/test/unit/tsl/reference.glsl +++ b/test/unit/tsl/reference.glsl @@ -12,15 +12,87 @@ vec4( vec3( 1.0, 0.5, 0.0 ), 1.0 ) // auto convert int to float -( 0.5 + 2.0 ) +( 0.5 * 1.0 ) // auto convert float to int ( 2 + 1 ) +// auto promote int to float + +( 1.0 * 0.5 ) + // auto convert uint to float -( 0.5 * 3.0 ) +( 0.5 * 1.0 ) + +// auto promote uint to float + +( 1.0 * 0.5 ) + +// auto promote uint to int + +( 1 + -1 ) + +// auto promote int and uint + +( -1 + 1 ) + +// equal float int + +( 1.5 == 1.0 ) + +// equal int float + +( 1.0 == 1.5 ) + +// equal bool uint + +( uint( true ) == 1u ) + +// equal uint bool + +( 1u == uint( true ) ) + +// equal bool float + +( float( true ) == 1.5 ) + +// equal float bool + +( 1.5 == float( true ) ) + +// equal float int vector + +equal( vec3( 1.5, 1.5, 1.5 ), vec3( ivec3( 1, 1, 1 ) ) ) + +// equal int float vector + +equal( vec3( ivec3( 1, 1, 1 ) ), vec3( 1.5, 1.5, 1.5 ) ) + +// truncated uint to float + +( 1.0 * 0.0 ) + +// truncated uint constants + +1u + +// truncated int constants + +-1 + +// truncated uint fraction + +0u + +// truncated int fraction + +0 + +// clamped negative uint + +0u // auto convert scalar to vector @@ -32,7 +104,59 @@ vec4( vec3( 1.0, 0.5, 0.0 ), 1.0 ) // auto convert vector to int -( ivec3( 1, 2, 3 ) + ivec3( vec3( 0.5, 0.5, 0.5 ) ) ) +( ivec3( 1, 2, 3 ) + ivec3( int( 1.0 ) ) ) + +// auto promote int vector to float + +( vec3( ivec3( 1, 2, 3 ) ) + vec3( 0.5, 0.5, 0.5 ) ) + +// weak uint index math + +( 70u / 64u ) + +// weak float default + +( 1.0 * 2.0 ) + +// weak uint fraction + +( 1u * 0u ) + +// weak int fraction + +( 3 * 1 ) + +// weak promote negative + +( 1 + -1 ) + +// weak promote out of range + +( 1.0 + 4294967296.0 ) + +// weak shared constant + +( float( ( 1u + 1u ) ) + ( 1.0 + 1.0 ) ) + +// weak math functions + +clamp( 1u, 0u, 2u ) + +// weak math functions reordered + +clamp( 0u, 1u, 2u ) + +// float only math functions + +pow( 2.0, 2.0 ) + +// integer math functions + +max( 1u, 2u ) + +// square matrix functions + +determinant( transpose( inverse( mat2( 1.0, 3.0, 2.0, 4.0 ) ) ) ) // vector composition @@ -150,11 +274,11 @@ explicitGlobal = ( explicitGlobal + nodeVar0 ); // comparison and logic -( ( ( 3.0 > 1.0 ) && ( 0.5 <= 1.0 ) ) || ( ! false ) ) +( ( ( 3 > 1 ) && ( 0.5 <= 1.0 ) ) || ( ! false ) ) // vector comparison -all( ( vec3( 1.0, 2.0, 3.0 ) > vec3( 1.0 ) ) ) +all( greaterThan( vec3( 1.0, 2.0, 3.0 ), vec3( 1.0 ) ) ) // select auto conversion @@ -260,13 +384,13 @@ nodeVar0 float nodeVar0 = 0.0; -if ( ( 2.0 == 0.0 ) ) { +if ( ( 2 == 0 ) ) { nodeVar0 = 1.0; } else { - if ( ( ( 2.0 == 1.0 ) || ( 2.0 == 2.0 ) ) ) { + if ( ( ( 2 == 1 ) || ( 2 == 2 ) ) ) { nodeVar0 = 2.0; @@ -286,13 +410,13 @@ float nodeVar0 = 0.0; for ( int i = 0; i < 8; i ++ ) { - if ( ( float( i ) == 2.0 ) ) { + if ( ( i == 2 ) ) { continue; } - if ( ( float( i ) > 5.0 ) ) { + if ( ( i > 5 ) ) { break; @@ -336,7 +460,7 @@ nodeVar0 int nodeVar0 = 0; -while ( ( float( nodeVar0 ) < 3.0 ) ) { +while ( ( nodeVar0 < 3 ) ) { nodeVar0 = ( nodeVar0 + 1 ); diff --git a/test/unit/tsl/reference.wgsl b/test/unit/tsl/reference.wgsl index 2f8873502d2f1c..5b81d5af1986e4 100644 --- a/test/unit/tsl/reference.wgsl +++ b/test/unit/tsl/reference.wgsl @@ -12,15 +12,87 @@ vec4( vec3( 1.0, 0.5, 0.0 ), 1.0 ) // auto convert int to float -( 0.5 + 2.0 ) +( 0.5 * 1.0 ) // auto convert float to int ( 2 + 1 ) +// auto promote int to float + +( 1.0 * 0.5 ) + // auto convert uint to float -( 0.5 * 3.0 ) +( 0.5 * 1.0 ) + +// auto promote uint to float + +( 1.0 * 0.5 ) + +// auto promote uint to int + +( 1 + -1 ) + +// auto promote int and uint + +( -1 + 1 ) + +// equal float int + +( 1.5 == 1.0 ) + +// equal int float + +( 1.0 == 1.5 ) + +// equal bool uint + +( u32( true ) == 1u ) + +// equal uint bool + +( 1u == u32( true ) ) + +// equal bool float + +( f32( true ) == 1.5 ) + +// equal float bool + +( 1.5 == f32( true ) ) + +// equal float int vector + +( vec3( 1.5, 1.5, 1.5 ) == vec3( vec3( 1, 1, 1 ) ) ) + +// equal int float vector + +( vec3( vec3( 1, 1, 1 ) ) == vec3( 1.5, 1.5, 1.5 ) ) + +// truncated uint to float + +( 1.0 * 0.0 ) + +// truncated uint constants + +1u + +// truncated int constants + +-1 + +// truncated uint fraction + +0u + +// truncated int fraction + +0 + +// clamped negative uint + +0u // auto convert scalar to vector @@ -32,7 +104,70 @@ vec4( vec3( 1.0, 0.5, 0.0 ), 1.0 ) // auto convert vector to int -( vec3( 1, 2, 3 ) + vec3( vec3( 0.5, 0.5, 0.5 ) ) ) +( vec3( 1, 2, 3 ) + vec3( i32( 1.0 ) ) ) + +// auto promote int vector to float + +( vec3( vec3( 1, 2, 3 ) ) + vec3( 0.5, 0.5, 0.5 ) ) + +// weak uint index math + +( 70u / 64u ) + +// weak float default + +( 1.0 * 2.0 ) + +// weak uint fraction + +( 1u * 0u ) + +// weak int fraction + +( 3 * 1 ) + +// weak promote negative + +( 1 + -1 ) + +// weak promote out of range + +( 1.0 + 4294967296.0 ) + +// weak shared constant + +( f32( ( 1u + 1u ) ) + ( 1.0 + 1.0 ) ) + +// weak math functions + +clamp( 1u, 0u, 2u ) + +// weak math functions reordered + +clamp( 0u, 1u, 2u ) + +// float only math functions + +pow( 2.0, 2.0 ) + +// integer math functions + +max( 1u, 2u ) + +// square matrix functions + +fn tsl_inverse_mat2( m : mat2x2 ) -> mat2x2 { + + let det = m[ 0 ][ 0 ] * m[ 1 ][ 1 ] - m[ 0 ][ 1 ] * m[ 1 ][ 0 ]; + + return mat2x2( + m[ 1 ][ 1 ], - m[ 0 ][ 1 ], + - m[ 1 ][ 0 ], m[ 0 ][ 0 ] + ) * ( 1.0 / det ); + +} + +determinant( transpose( tsl_inverse_mat2( mat2x2( 1.0, 3.0, 2.0, 4.0 ) ) ) ) // vector composition @@ -156,7 +291,7 @@ explicitGlobal = ( explicitGlobal + nodeVar0 ); // comparison and logic -( ( ( 3.0 > 1.0 ) && ( 0.5 <= 1.0 ) ) || ( ! false ) ) +( ( ( 3 > 1 ) && ( 0.5 <= 1.0 ) ) || ( ! false ) ) // vector comparison @@ -270,13 +405,13 @@ nodeVar0 var nodeVar0 : f32 = 0.0; -if ( ( 2.0 == 0.0 ) ) { +if ( ( 2 == 0 ) ) { nodeVar0 = 1.0; } else { - if ( ( ( 2.0 == 1.0 ) || ( 2.0 == 2.0 ) ) ) { + if ( ( ( 2 == 1 ) || ( 2 == 2 ) ) ) { nodeVar0 = 2.0; @@ -296,13 +431,13 @@ var nodeVar0 : f32 = 0.0; for ( var i : i32 = 0; i < 8; i ++ ) { - if ( ( f32( i ) == 2.0 ) ) { + if ( ( i == 2 ) ) { continue; } - if ( ( f32( i ) > 5.0 ) ) { + if ( ( i > 5 ) ) { break; @@ -346,7 +481,7 @@ nodeVar0 var nodeVar0 : i32 = 0; -while ( ( f32( nodeVar0 ) < 3.0 ) ) { +while ( ( nodeVar0 < 3 ) ) { nodeVar0 = ( nodeVar0 + 1 );