diff --git a/package-lock.json b/package-lock.json index 2c9b34141addd9..748c626cda443c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -225,29 +225,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1167,9 +1181,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.19.tgz", - "integrity": "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==", + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1200,9 +1214,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "dev": true, "funding": [ { @@ -1220,11 +1234,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" @@ -1241,9 +1255,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001788", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", - "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", "dev": true, "funding": [ { @@ -1446,9 +1460,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.339", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.339.tgz", - "integrity": "sha512-Is+0BBHJ4NrdpAYiperrmp53pLywG/yV/6lIMTAnhxvzj/Cmn5Q/ogSHC6AKe7X+8kPLxxFk0cs5oc/3j/fxIg==", + "version": "1.5.420", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz", + "integrity": "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==", "dev": true, "license": "ISC" }, @@ -2390,11 +2404,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/object-deep-merge": { "version": "2.0.1", @@ -2987,9 +3004,9 @@ "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", "dev": true, "funding": [ { diff --git a/src/nodes/functions/BSDF/BRDF_Sheen.js b/src/nodes/functions/BSDF/BRDF_Sheen.js index e5ed3467aef810..4450a8d7e86340 100644 --- a/src/nodes/functions/BSDF/BRDF_Sheen.js +++ b/src/nodes/functions/BSDF/BRDF_Sheen.js @@ -28,7 +28,7 @@ const D_Charlie = /*@__PURE__*/ Fn( ( { roughness, dotNH } ) => { const V_Neubelt = /*@__PURE__*/ Fn( ( { dotNV, dotNL } ) => { // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" - return float( 1.0 ).div( float( 4.0 ).mul( dotNL.add( dotNV ).sub( dotNL.mul( dotNV ) ) ) ); + return float( 1.0 ).div( float( 4.0 ).mul( dotNL.add( dotNV ).sub( dotNL.mul( dotNV ) ) ) ).clamp(); } ).setLayout( { name: 'V_Neubelt', diff --git a/src/nodes/utils/LoopNode.js b/src/nodes/utils/LoopNode.js index 451345b6db00ef..26f3a7a77703d0 100644 --- a/src/nodes/utils/LoopNode.js +++ b/src/nodes/utils/LoopNode.js @@ -36,7 +36,7 @@ import { error } from '../../utils.js'; * * } ); * ``` - * The module also provides `Break()` and `Continue()` TSL expression for loop control. + * The module also provides `Break()` and `Continue()` TSL expressions for loop control. * @augments Node */ class LoopNode extends Node { @@ -50,7 +50,7 @@ class LoopNode extends Node { /** * Constructs a new loop node. * - * @param {Array} params - Depending on the loop type, array holds different parameterization values for the loop. + * @param {Array} params - Any number of loop parameters followed by the loop body. */ constructor( params = [] ) { @@ -340,13 +340,13 @@ export default LoopNode; * * @tsl * @function - * @param {...any} params - A list of parameters. + * @param {...(LoopNode~Params|loopBodyCallback)} params - Any number of loop parameters followed by the loop body. * @returns {LoopNode} */ export const Loop = ( ...params ) => new LoopNode( nodeArray( params, 'int' ) ).toStack(); /** - * TSL function for creating a `Continue()` expression. + * TSL function for inserting a `continue` expression into the shader. * * @tsl * @function @@ -355,10 +355,36 @@ export const Loop = ( ...params ) => new LoopNode( nodeArray( params, 'int' ) ). export const Continue = () => expression( 'continue' ).toStack(); /** - * TSL function for creating a `Break()` expression. + * TSL function for inserting a `break` expression into the shader. * * @tsl * @function * @returns {ExpressionNode} */ export const Break = () => expression( 'break' ).toStack(); + +/** + * The parameters of a loop. A number or int/uint node defines the loop's end value, + * a bool node defines a `while` loop and an object allows a more detailed configuration. + * + * @typedef {number|Node|Node|Node|LoopNode~ObjectParams} LoopNode~Params + */ + +/** + * A detailed loop configuration. + * + * @typedef {Object} LoopNode~ObjectParams + * @property {number|Node|Node} [start=0] - The initial value of the loop variable. + * @property {number|Node|Node} [end] - The value the loop variable is compared against. If omitted, the loop counts down from `start - 1` to `0`. + * @property {string} [name] - The name of the loop variable. Defaults to `i`, `j`, `k` and so on. + * @property {string} [type='int'] - The data type of the loop variable. + * @property {('<'|'<='|'>'|'>=')} [condition] - The comparison operator. The loop runs as long as the comparison is true. Inferred from `start` and `end` if not set. + * @property {string|number|Function|Node} [update] - Defines how the loop variable is updated after each iteration. Inferred from `condition` and `type` if not set. + */ + +/** + * The loop body. + * + * @callback loopBodyCallback + * @param {Object} inputs - The loop variables of the current `Loop()` call, keyed by their name. + */ diff --git a/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js b/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js index acd60a1b02b280..c1886d3789f1e0 100644 --- a/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +++ b/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js @@ -10,8 +10,6 @@ import { DataTexture } from '../../../textures/DataTexture.js'; import { error } from '../../../utils.js'; const glslPolyfills = { - bitcast_int_uint: new CodeNode( /* glsl */'uint tsl_bitcast_int_to_uint ( int x ) { return floatBitsToUint( intBitsToFloat ( x ) ); }' ), - bitcast_uint_int: new CodeNode( /* glsl */'int tsl_bitcast_uint_to_int ( uint x ) { return floatBitsToInt( uintBitsToFloat ( x ) ); }' ), textureGather: new CodeNode( /* glsl */` vec4 tsl_textureGather( const int comp, sampler2D map, vec2 coord, ivec2 offset, bool flipY ) { if ( flipY ) offset.y = - offset.y; @@ -84,8 +82,6 @@ const glslMethods = { bitcast_int_float: 'intBitsToFloat', bitcast_uint_float: 'uintBitsToFloat', bitcast_float_uint: 'floatBitsToUint', - bitcast_uint_int: 'tsl_bitcast_uint_to_int', - bitcast_int_uint: 'tsl_bitcast_int_to_uint', floatpack_snorm_2x16: 'packSnorm2x16', floatpack_unorm_2x16: 'packUnorm2x16', floatpack_float16_2x16: 'packHalf2x16', @@ -277,7 +273,19 @@ class GLSLNodeBuilder extends NodeBuilder { */ getBitcastMethod( type, inputType ) { - return this.getMethod( `bitcast_${ inputType }_${ type }` ); + const inputComponentType = this.getComponentType( inputType ); + const componentType = this.getComponentType( type ); + + // integer types support bitcast implicitly via the type constructor + + if ( inputComponentType !== 'float' && componentType !== 'float' && + this.getTypeLength( inputType ) === this.getTypeLength( type ) ) { + + return this.getType( type ); + + } + + return this.getMethod( `bitcast_${ inputComponentType }_${ componentType }` ); } diff --git a/src/renderers/webgl/WebGLAnimation.js b/src/renderers/webgl/WebGLAnimation.js index 385f13e9d0e665..2d69ea64bcda73 100644 --- a/src/renderers/webgl/WebGLAnimation.js +++ b/src/renderers/webgl/WebGLAnimation.js @@ -7,10 +7,10 @@ function WebGLAnimation() { function onAnimationFrame( time, frame ) { - animationLoop( time, frame ); - requestId = context.requestAnimationFrame( onAnimationFrame ); + animationLoop( time, frame ); + } return { diff --git a/test/unit/addons/tsl/TSLBitOps.tests.js b/test/unit/addons/tsl/TSLBitOps.tests.js index 7ed65d921e350f..ff1562c9ed8300 100644 --- a/test/unit/addons/tsl/TSLBitOps.tests.js +++ b/test/unit/addons/tsl/TSLBitOps.tests.js @@ -1,4 +1,4 @@ -import { float, int, uint, bitcast, countLeadingZeros, countOneBits, countTrailingZeros, inversesqrt } from 'three/tsl'; +import { float, int, uint, ivec4, uvec4, bitcast, countLeadingZeros, countOneBits, countTrailingZeros, inversesqrt } from 'three/tsl'; import { gpuTest } from './gpu-test-utils.js'; // Coverage for the generic bitcast() constructor (src/nodes/math/BitcastNode.js @@ -37,6 +37,26 @@ export default QUnit.module( 'TSL', () => { } ); + gpuTest( 'bitcast() reinterprets bits between ivec4 and uvec4', ( { assert } ) => { + + // Each component's two's-complement bit pattern read as unsigned: + // -1 -> 0xFFFFFFFF, -2 -> 0xFFFFFFFE, positive values unchanged. + assert.eq( + bitcast( ivec4( - 1, 1, 2, - 2 ), 'uvec4' ), + uvec4( 4294967295, 1, 2, 4294967294 ), + 'bitcast(ivec4, "uvec4") reinterprets each component' + ); + + // Round trip: bitcast is lossless reinterpretation, so converting + // out and back must recover the exact original vector. + assert.eq( + bitcast( bitcast( ivec4( - 1, 1, 2, - 2 ), 'uvec4' ), 'ivec4' ), + ivec4( - 1, 1, 2, - 2 ), + 'bitcast round trip recovers the exact ivec4' + ); + + } ); + } ); QUnit.module( 'bit counting', () => {