UNPKG

three

Version:

JavaScript 3D library

434 lines (285 loc) 9.15 kB
import { float, Fn, If, nodeProxyIntent, uint, int, uvec2, uvec3, uvec4, ivec2, ivec3, ivec4 } from '../tsl/TSLCore.js'; import { bitcast, floatBitsToUint } from './BitcastNode.js'; import MathNode, { negate } from './MathNode.js'; const registeredBitcountFunctions = {}; /** * This node represents an operation that counts the bits of a piece of shader data. * * @augments MathNode */ class BitcountNode extends MathNode { static get type() { return 'BitcountNode'; } /** * Constructs a new math node. * * @param {'countTrailingZeros'|'countLeadingZeros'|'countOneBits'} method - The method name. * @param {Node} aNode - The first input. */ constructor( method, aNode ) { super( method, aNode ); /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isBitcountNode = true; } /** * Casts the input value of the function to an integer if necessary. * * @private * @param {Node<uint>|Node<int>} inputNode - The input value. * @param {Node<uint>} outputNode - The output value. * @param {string} elementType - The type of the input value. */ _resolveElementType( inputNode, outputNode, elementType ) { if ( elementType === 'int' ) { outputNode.assign( bitcast( inputNode, 'uint' ) ); } else { outputNode.assign( inputNode ); } } _returnDataNode( inputType ) { switch ( inputType ) { case 'uint': { return uint; } case 'int': { return int; } case 'uvec2': { return uvec2; } case 'uvec3': { return uvec3; } case 'uvec4': { return uvec4; } case 'ivec2': { return ivec2; } case 'ivec3': { return ivec3; } case 'ivec4': { return ivec4; } } } /** * Creates and registers a reusable GLSL function that emulates the behavior of countTrailingZeros. * * @private * @param {string} method - The name of the function to create. * @param {string} elementType - The type of the input value. * @returns {Function} - The generated function */ _createTrailingZerosBaseLayout( method, elementType ) { const outputConvertNode = this._returnDataNode( elementType ); const fnDef = Fn( ( [ value ] ) => { const v = uint( 0.0 ); this._resolveElementType( value, v, elementType ); const f = float( v.bitAnd( negate( v ) ) ); const uintBits = floatBitsToUint( f ); const numTrailingZeros = ( uintBits.shiftRight( 23 ) ).sub( 127 ); return outputConvertNode( numTrailingZeros ); } ).setLayout( { name: method, type: elementType, inputs: [ { name: 'value', type: elementType } ] } ); return fnDef; } /** * Creates and registers a reusable GLSL function that emulates the behavior of countLeadingZeros. * * @private * @param {string} method - The name of the function to create. * @param {string} elementType - The type of the input value. * @returns {Function} - The generated function */ _createLeadingZerosBaseLayout( method, elementType ) { const outputConvertNode = this._returnDataNode( elementType ); const fnDef = Fn( ( [ value ] ) => { If( value.equal( uint( 0 ) ), () => { return uint( 32 ); } ); const v = uint( 0 ); const n = uint( 0 ); this._resolveElementType( value, v, elementType ); If( v.shiftRight( 16 ).equal( 0 ), () => { n.addAssign( 16 ); v.shiftLeftAssign( 16 ); } ); If( v.shiftRight( 24 ).equal( 0 ), () => { n.addAssign( 8 ); v.shiftLeftAssign( 8 ); } ); If( v.shiftRight( 28 ).equal( 0 ), () => { n.addAssign( 4 ); v.shiftLeftAssign( 4 ); } ); If( v.shiftRight( 30 ).equal( 0 ), () => { n.addAssign( 2 ); v.shiftLeftAssign( 2 ); } ); If( v.shiftRight( 31 ).equal( 0 ), () => { n.addAssign( 1 ); } ); return outputConvertNode( n ); } ).setLayout( { name: method, type: elementType, inputs: [ { name: 'value', type: elementType } ] } ); return fnDef; } /** * Creates and registers a reusable GLSL function that emulates the behavior of countOneBits. * * @private * @param {string} method - The name of the function to create. * @param {string} elementType - The type of the input value. * @returns {Function} - The generated function */ _createOneBitsBaseLayout( method, elementType ) { const outputConvertNode = this._returnDataNode( elementType ); const fnDef = Fn( ( [ value ] ) => { const v = uint( 0.0 ); this._resolveElementType( value, v, elementType ); v.assign( v.sub( v.shiftRight( uint( 1 ) ).bitAnd( uint( 0x55555555 ) ) ) ); v.assign( v.bitAnd( uint( 0x33333333 ) ).add( v.shiftRight( uint( 2 ) ).bitAnd( uint( 0x33333333 ) ) ) ); const numBits = v.add( v.shiftRight( uint( 4 ) ) ).bitAnd( uint( 0xF0F0F0F ) ).mul( uint( 0x1010101 ) ).shiftRight( uint( 24 ) ); return outputConvertNode( numBits ); } ).setLayout( { name: method, type: elementType, inputs: [ { name: 'value', type: elementType } ] } ); return fnDef; } /** * Creates and registers a reusable GLSL function that emulates the behavior of the specified bitcount function. * including considerations for component-wise bitcounts on vector type inputs. * * @private * @param {string} method - The name of the function to create. * @param {string} inputType - The type of the input value. * @param {number} typeLength - The vec length of the input value. * @param {Function} baseFn - The base function that operates on an individual component of the vector. * @returns {Function} - The alias function for the specified bitcount method. */ _createMainLayout( method, inputType, typeLength, baseFn ) { const outputConvertNode = this._returnDataNode( inputType ); const fnDef = Fn( ( [ value ] ) => { if ( typeLength === 1 ) { return outputConvertNode( baseFn( value ) ); } else { const vec = outputConvertNode( 0 ); const components = [ 'x', 'y', 'z', 'w' ]; for ( let i = 0; i < typeLength; i ++ ) { const component = components[ i ]; vec[ component ].assign( baseFn( value[ component ] ) ); } return vec; } } ).setLayout( { name: method, type: inputType, inputs: [ { name: 'value', type: inputType } ] } ); return fnDef; } setup( builder ) { const { method, aNode } = this; const { renderer } = builder; if ( renderer.backend.isWebGPUBackend ) { // use built-in WGSL functions for WebGPU return super.setup( builder ); } const inputType = this.getInputType( builder ); const elementType = builder.getElementType( inputType ); const typeLength = builder.getTypeLength( inputType ); const baseMethod = `${method}_base_${elementType}`; const newMethod = `${method}_${inputType}`; let baseFn = registeredBitcountFunctions[ baseMethod ]; if ( baseFn === undefined ) { switch ( method ) { case BitcountNode.COUNT_LEADING_ZEROS: { baseFn = this._createLeadingZerosBaseLayout( baseMethod, elementType ); break; } case BitcountNode.COUNT_TRAILING_ZEROS: { baseFn = this._createTrailingZerosBaseLayout( baseMethod, elementType ); break; } case BitcountNode.COUNT_ONE_BITS: { baseFn = this._createOneBitsBaseLayout( baseMethod, elementType ); break; } } registeredBitcountFunctions[ baseMethod ] = baseFn; } let fn = registeredBitcountFunctions[ newMethod ]; if ( fn === undefined ) { fn = this._createMainLayout( newMethod, inputType, typeLength, baseFn ); registeredBitcountFunctions[ newMethod ] = fn; } const output = Fn( () => { return fn( aNode, ); } ); return output(); } } export default BitcountNode; BitcountNode.COUNT_TRAILING_ZEROS = 'countTrailingZeros'; BitcountNode.COUNT_LEADING_ZEROS = 'countLeadingZeros'; BitcountNode.COUNT_ONE_BITS = 'countOneBits'; /** * Finds the number of consecutive 0 bits from the least significant bit of the input value, * which is also the index of the least significant bit of the input value. * * Can only be used with {@link WebGPURenderer} and a WebGPU backend. * * @tsl * @function * @param {Node | number} x - The input value. * @returns {Node} */ export const countTrailingZeros = /*@__PURE__*/ nodeProxyIntent( BitcountNode, BitcountNode.COUNT_TRAILING_ZEROS ).setParameterLength( 1 ); /** * Finds the number of consecutive 0 bits starting from the most significant bit of the input value. * * Can only be used with {@link WebGPURenderer} and a WebGPU backend. * * @tsl * @function * @param {Node | number} x - The input value. * @returns {Node} */ export const countLeadingZeros = /*@__PURE__*/ nodeProxyIntent( BitcountNode, BitcountNode.COUNT_LEADING_ZEROS ).setParameterLength( 1 ); /** * Finds the number of '1' bits set in the input value * * Can only be used with {@link WebGPURenderer} and a WebGPU backend. * * @tsl * @function * @returns {Node} */ export const countOneBits = /*@__PURE__*/ nodeProxyIntent( BitcountNode, BitcountNode.COUNT_ONE_BITS ).setParameterLength( 1 );