UNPKG

three

Version:

JavaScript 3D library

1,517 lines (1,027 loc) 77.6 kB
import NodeUniformsGroup from '../../common/nodes/NodeUniformsGroup.js'; import NodeSampler from '../../common/nodes/NodeSampler.js'; import { NodeSampledTexture, NodeSampledCubeTexture, NodeSampledTexture3D } from '../../common/nodes/NodeSampledTexture.js'; import NodeUniformBuffer from '../../common/nodes/NodeUniformBuffer.js'; import NodeStorageBuffer from '../../common/nodes/NodeStorageBuffer.js'; import { NodeBuilder, CodeNode } from '../../../nodes/Nodes.js'; import { getFormat } from '../utils/WebGPUTextureUtils.js'; import WGSLNodeParser from './WGSLNodeParser.js'; import { NodeAccess } from '../../../nodes/core/constants.js'; import VarNode from '../../../nodes/core/VarNode.js'; import ExpressionNode from '../../../nodes/code/ExpressionNode.js'; import { FloatType, RepeatWrapping, ClampToEdgeWrapping, MirroredRepeatWrapping, NearestFilter, Compatibility } from '../../../constants.js'; import { warn, error } from '../../../utils.js'; import { GPUShaderStage } from '../utils/WebGPUConstants.js'; const accessNames = { [ NodeAccess.READ_ONLY ]: 'read', [ NodeAccess.WRITE_ONLY ]: 'write', [ NodeAccess.READ_WRITE ]: 'read_write' }; const wrapNames = { [ RepeatWrapping ]: 'repeat', [ ClampToEdgeWrapping ]: 'clamp', [ MirroredRepeatWrapping ]: 'mirror' }; const gpuShaderStageLib = { 'vertex': GPUShaderStage.VERTEX, 'fragment': GPUShaderStage.FRAGMENT, 'compute': GPUShaderStage.COMPUTE }; const supports = { instance: true, swizzleAssign: false, storageBuffer: true }; const wgslFnOpLib = { '^^': 'tsl_xor' }; const wgslTypeLib = { float: 'f32', int: 'i32', uint: 'u32', bool: 'bool', color: 'vec3<f32>', vec2: 'vec2<f32>', ivec2: 'vec2<i32>', uvec2: 'vec2<u32>', bvec2: 'vec2<bool>', vec3: 'vec3<f32>', ivec3: 'vec3<i32>', uvec3: 'vec3<u32>', bvec3: 'vec3<bool>', vec4: 'vec4<f32>', ivec4: 'vec4<i32>', uvec4: 'vec4<u32>', bvec4: 'vec4<bool>', mat2: 'mat2x2<f32>', mat3: 'mat3x3<f32>', mat4: 'mat4x4<f32>' }; const wgslCodeCache = {}; const wgslPolyfill = { tsl_xor: new CodeNode( 'fn tsl_xor( a : bool, b : bool ) -> bool { return ( a || b ) && !( a && b ); }' ), mod_float: new CodeNode( 'fn tsl_mod_float( x : f32, y : f32 ) -> f32 { return x - y * floor( x / y ); }' ), mod_vec2: new CodeNode( 'fn tsl_mod_vec2( x : vec2f, y : vec2f ) -> vec2f { return x - y * floor( x / y ); }' ), mod_vec3: new CodeNode( 'fn tsl_mod_vec3( x : vec3f, y : vec3f ) -> vec3f { return x - y * floor( x / y ); }' ), mod_vec4: new CodeNode( 'fn tsl_mod_vec4( x : vec4f, y : vec4f ) -> vec4f { return x - y * floor( x / y ); }' ), equals_bool: new CodeNode( 'fn tsl_equals_bool( a : bool, b : bool ) -> bool { return a == b; }' ), equals_bvec2: new CodeNode( 'fn tsl_equals_bvec2( a : vec2f, b : vec2f ) -> vec2<bool> { return vec2<bool>( a.x == b.x, a.y == b.y ); }' ), equals_bvec3: new CodeNode( 'fn tsl_equals_bvec3( a : vec3f, b : vec3f ) -> vec3<bool> { return vec3<bool>( a.x == b.x, a.y == b.y, a.z == b.z ); }' ), equals_bvec4: new CodeNode( 'fn tsl_equals_bvec4( a : vec4f, b : vec4f ) -> vec4<bool> { return vec4<bool>( a.x == b.x, a.y == b.y, a.z == b.z, a.w == b.w ); }' ), repeatWrapping_float: new CodeNode( 'fn tsl_repeatWrapping_float( coord: f32 ) -> f32 { return fract( coord ); }' ), mirrorWrapping_float: new CodeNode( 'fn tsl_mirrorWrapping_float( coord: f32 ) -> f32 { let mirrored = fract( coord * 0.5 ) * 2.0; return 1.0 - abs( 1.0 - mirrored ); }' ), clampWrapping_float: new CodeNode( 'fn tsl_clampWrapping_float( coord: f32 ) -> f32 { return clamp( coord, 0.0, 1.0 ); }' ), inverse_mat2: new CodeNode( /* wgsl */` fn tsl_inverse_mat2( m : mat2x2<f32> ) -> mat2x2<f32> { let det = m[ 0 ][ 0 ] * m[ 1 ][ 1 ] - m[ 0 ][ 1 ] * m[ 1 ][ 0 ]; return mat2x2<f32>( m[ 1 ][ 1 ], - m[ 0 ][ 1 ], - m[ 1 ][ 0 ], m[ 0 ][ 0 ] ) * ( 1.0 / det ); } ` ), inverse_mat3: new CodeNode( /* wgsl */` fn tsl_inverse_mat3( m : mat3x3<f32> ) -> mat3x3<f32> { let a00 = m[ 0 ][ 0 ]; let a01 = m[ 0 ][ 1 ]; let a02 = m[ 0 ][ 2 ]; let a10 = m[ 1 ][ 0 ]; let a11 = m[ 1 ][ 1 ]; let a12 = m[ 1 ][ 2 ]; let a20 = m[ 2 ][ 0 ]; let a21 = m[ 2 ][ 1 ]; let a22 = m[ 2 ][ 2 ]; let b01 = a22 * a11 - a12 * a21; let b11 = - a22 * a10 + a12 * a20; let b21 = a21 * a10 - a11 * a20; let det = a00 * b01 + a01 * b11 + a02 * b21; return mat3x3<f32>( b01, ( - a22 * a01 + a02 * a21 ), ( a12 * a01 - a02 * a11 ), b11, ( a22 * a00 - a02 * a20 ), ( - a12 * a00 + a02 * a10 ), b21, ( - a21 * a00 + a01 * a20 ), ( a11 * a00 - a01 * a10 ) ) * ( 1.0 / det ); } ` ), inverse_mat4: new CodeNode( /* wgsl */` fn tsl_inverse_mat4( m : mat4x4<f32> ) -> mat4x4<f32> { let a00 = m[ 0 ][ 0 ]; let a01 = m[ 0 ][ 1 ]; let a02 = m[ 0 ][ 2 ]; let a03 = m[ 0 ][ 3 ]; let a10 = m[ 1 ][ 0 ]; let a11 = m[ 1 ][ 1 ]; let a12 = m[ 1 ][ 2 ]; let a13 = m[ 1 ][ 3 ]; let a20 = m[ 2 ][ 0 ]; let a21 = m[ 2 ][ 1 ]; let a22 = m[ 2 ][ 2 ]; let a23 = m[ 2 ][ 3 ]; let a30 = m[ 3 ][ 0 ]; let a31 = m[ 3 ][ 1 ]; let a32 = m[ 3 ][ 2 ]; let a33 = m[ 3 ][ 3 ]; let b00 = a00 * a11 - a01 * a10; let b01 = a00 * a12 - a02 * a10; let b02 = a00 * a13 - a03 * a10; let b03 = a01 * a12 - a02 * a11; let b04 = a01 * a13 - a03 * a11; let b05 = a02 * a13 - a03 * a12; let b06 = a20 * a31 - a21 * a30; let b07 = a20 * a32 - a22 * a30; let b08 = a20 * a33 - a23 * a30; let b09 = a21 * a32 - a22 * a31; let b10 = a21 * a33 - a23 * a31; let b11 = a22 * a33 - a23 * a32; let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; return mat4x4<f32>( a11 * b11 - a12 * b10 + a13 * b09, a02 * b10 - a01 * b11 - a03 * b09, a31 * b05 - a32 * b04 + a33 * b03, a22 * b04 - a21 * b05 - a23 * b03, a12 * b08 - a10 * b11 - a13 * b07, a00 * b11 - a02 * b08 + a03 * b07, a32 * b02 - a30 * b05 - a33 * b01, a20 * b05 - a22 * b02 + a23 * b01, a10 * b10 - a11 * b08 + a13 * b06, a01 * b08 - a00 * b10 - a03 * b06, a30 * b04 - a31 * b02 + a33 * b00, a21 * b02 - a20 * b04 - a23 * b00, a11 * b07 - a10 * b09 - a12 * b06, a00 * b09 - a01 * b07 + a02 * b06, a31 * b01 - a30 * b03 - a32 * b00, a20 * b03 - a21 * b01 + a22 * b00 ) * ( 1.0 / det ); } ` ), biquadraticTexture: new CodeNode( /* wgsl */` fn tsl_biquadraticTexture( map : texture_2d<f32>, coord : vec2f, iRes : vec2u, level : u32 ) -> vec4f { let res = vec2f( iRes ); let uvScaled = coord * res; let uvWrapping = ( ( uvScaled % res ) + res ) % res; // https://www.shadertoy.com/view/WtyXRy let uv = uvWrapping - 0.5; let iuv = floor( uv ); let f = fract( uv ); let rg1 = textureLoad( map, vec2u( iuv + vec2( 0.5, 0.5 ) ) % iRes, level ); let rg2 = textureLoad( map, vec2u( iuv + vec2( 1.5, 0.5 ) ) % iRes, level ); let rg3 = textureLoad( map, vec2u( iuv + vec2( 0.5, 1.5 ) ) % iRes, level ); let rg4 = textureLoad( map, vec2u( iuv + vec2( 1.5, 1.5 ) ) % iRes, level ); return mix( mix( rg1, rg2, f.x ), mix( rg3, rg4, f.x ), f.y ); } ` ), biquadraticTextureArray: new CodeNode( /* wgsl */` fn tsl_biquadraticTexture_array( map : texture_2d_array<f32>, coord : vec2f, iRes : vec2u, layer : u32, level : u32 ) -> vec4f { let res = vec2f( iRes ); let uvScaled = coord * res; let uvWrapping = ( ( uvScaled % res ) + res ) % res; // https://www.shadertoy.com/view/WtyXRy let uv = uvWrapping - 0.5; let iuv = floor( uv ); let f = fract( uv ); let rg1 = textureLoad( map, vec2u( iuv + vec2( 0.5, 0.5 ) ) % iRes, layer, level ); let rg2 = textureLoad( map, vec2u( iuv + vec2( 1.5, 0.5 ) ) % iRes, layer, level ); let rg3 = textureLoad( map, vec2u( iuv + vec2( 0.5, 1.5 ) ) % iRes, layer, level ); let rg4 = textureLoad( map, vec2u( iuv + vec2( 1.5, 1.5 ) ) % iRes, layer, level ); return mix( mix( rg1, rg2, f.x ), mix( rg3, rg4, f.x ), f.y ); } ` ) }; const wgslMethods = { dFdx: 'dpdx', dFdy: '- dpdy', mod_float: 'tsl_mod_float', mod_vec2: 'tsl_mod_vec2', mod_vec3: 'tsl_mod_vec3', mod_vec4: 'tsl_mod_vec4', equals_bool: 'tsl_equals_bool', equals_bvec2: 'tsl_equals_bvec2', equals_bvec3: 'tsl_equals_bvec3', equals_bvec4: 'tsl_equals_bvec4', inverse_mat2: 'tsl_inverse_mat2', inverse_mat3: 'tsl_inverse_mat3', inverse_mat4: 'tsl_inverse_mat4', inversesqrt: 'inverseSqrt', bitcast: 'bitcast<f32>', floatpack_snorm_2x16: 'pack2x16snorm', floatpack_unorm_2x16: 'pack2x16unorm', floatpack_float16_2x16: 'pack2x16float', floatunpack_snorm_2x16: 'unpack2x16snorm', floatunpack_unorm_2x16: 'unpack2x16unorm', floatunpack_float16_2x16: 'unpack2x16float' }; // let diagnostics = ''; if ( ( typeof navigator !== 'undefined' && /Firefox|Deno/g.test( navigator.userAgent ) ) !== true ) { diagnostics += 'diagnostic( off, derivative_uniformity );\n'; } /** * A node builder targeting WGSL. * * This module generates WGSL shader code from node materials and also * generates the respective bindings and vertex buffer definitions. These * data are later used by the renderer to create render and compute pipelines * for render objects. * * @augments NodeBuilder */ class WGSLNodeBuilder extends NodeBuilder { /** * Constructs a new WGSL node builder renderer. * * @param {Object3D} object - The 3D object. * @param {Renderer} renderer - The renderer. */ constructor( object, renderer ) { super( object, renderer, new WGSLNodeParser() ); /** * A dictionary that holds for each shader stage ('vertex', 'fragment', 'compute') * another dictionary which manages UBOs per group ('render','frame','object'). * * @type {Object<string,Object<string,NodeUniformsGroup>>} */ this.uniformGroups = {}; /** * A dictionary that holds the assigned binding indices for each uniform group. * This ensures the same binding index is used across all shader stages. * * @type {Object<string,{index: number, id: number}>} */ this.uniformGroupsBindings = {}; /** * A dictionary that holds for each shader stage a Map of builtins. * * @type {Object<string,Map<string,Object>>} */ this.builtins = {}; /** * A dictionary that holds for each shader stage a Set of directives. * * @type {Object<string,Set<string>>} */ this.directives = {}; /** * A map for managing scope arrays. Only relevant for when using * {@link WorkgroupInfoNode} in context of compute shaders. * * @type {Map<string,Object>} */ this.scopedArrays = new Map(); /** * A flag that indicates that early returns are allowed. * * @type {boolean} * @default true */ this.allowEarlyReturns = true; /** * A flag that indicates that global variables are allowed. * * @type {boolean} * @default true */ this.allowGlobalVariables = true; } /** * Generates the WGSL snippet for sampled textures. * * @private * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. * @return {string} The WGSL snippet. */ _generateTextureSample( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, shaderStage = this.shaderStage ) { if ( shaderStage === 'fragment' ) { if ( depthSnippet ) { if ( offsetSnippet ) { return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ offsetSnippet } )`; } return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet } )`; } else { if ( offsetSnippet ) { return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ offsetSnippet } )`; } return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet } )`; } } else { return this.generateTextureSampleLevel( texture, textureProperty, uvSnippet, '0', depthSnippet ); } } /** * Generates the WGSL snippet when sampling textures with explicit mip level. * * @private * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. * @param {string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @return {string} The WGSL snippet. */ generateTextureSampleLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ) { if ( this.isUnfilterable( texture ) === false ) { if ( depthSnippet ) { if ( offsetSnippet ) { return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ levelSnippet }, ${ offsetSnippet } )`; } return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ levelSnippet } )`; } else { if ( offsetSnippet ) { return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ levelSnippet }, ${ offsetSnippet } )`; } return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ levelSnippet } )`; } } else if ( this.isFilteredTexture( texture ) ) { return this.generateFilteredTexture( texture, textureProperty, uvSnippet, offsetSnippet, levelSnippet, depthSnippet ); } else { return this.generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, levelSnippet ); } } /** * Generates a wrap function used in context of textures. * * @param {Texture} texture - The texture to generate the function for. * @return {string} The name of the generated function. */ generateWrapFunction( texture ) { const functionName = `tsl_coord_${ wrapNames[ texture.wrapS ] }S_${ wrapNames[ texture.wrapT ] }T_${ texture.is3DTexture || texture.isData3DTexture ? '3d' : '2d' }`; let nodeCode = wgslCodeCache[ functionName ]; if ( nodeCode === undefined ) { const includes = []; // For 3D textures, use vec3f; for texture arrays, keep vec2f since array index is separate const coordType = texture.is3DTexture || texture.isData3DTexture ? 'vec3f' : 'vec2f'; let code = `fn ${ functionName }( coord : ${ coordType } ) -> ${ coordType } {\n\n\treturn ${ coordType }(\n`; const addWrapSnippet = ( wrap, axis ) => { if ( wrap === RepeatWrapping ) { includes.push( wgslPolyfill.repeatWrapping_float ); code += `\t\ttsl_repeatWrapping_float( coord.${ axis } )`; } else if ( wrap === ClampToEdgeWrapping ) { includes.push( wgslPolyfill.clampWrapping_float ); code += `\t\ttsl_clampWrapping_float( coord.${ axis } )`; } else if ( wrap === MirroredRepeatWrapping ) { includes.push( wgslPolyfill.mirrorWrapping_float ); code += `\t\ttsl_mirrorWrapping_float( coord.${ axis } )`; } else { code += `\t\tcoord.${ axis }`; warn( `WebGPURenderer: Unsupported texture wrap type "${ wrap }" for vertex shader.` ); } }; addWrapSnippet( texture.wrapS, 'x' ); code += ',\n'; addWrapSnippet( texture.wrapT, 'y' ); if ( texture.is3DTexture || texture.isData3DTexture ) { code += ',\n'; addWrapSnippet( texture.wrapR, 'z' ); } code += '\n\t);\n\n}\n'; wgslCodeCache[ functionName ] = nodeCode = new CodeNode( code, includes ); } nodeCode.build( this ); return functionName; } /** * Generates the array declaration string. * * @param {string} type - The type. * @param {?number} [count] - The count. * @return {string} The generated value as a shader string. */ generateArrayDeclaration( type, count ) { return `array< ${ this.getType( type ) }, ${ count } >`; } /** * Generates a WGSL variable that holds the texture dimension of the given texture. * It also returns information about the number of layers (elements) of an arrayed * texture as well as the cube face count of cube textures. * * @param {Texture} texture - The texture to generate the function for. * @param {string} textureProperty - The name of the video texture uniform in the shader. * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. * @return {string} The name of the dimension variable. */ generateTextureDimension( texture, textureProperty, levelSnippet ) { const textureData = this.getDataFromNode( texture, this.shaderStage, this.cache ); if ( textureData.dimensionsSnippet === undefined ) textureData.dimensionsSnippet = {}; let textureDimensionNode = textureData.dimensionsSnippet[ levelSnippet ]; if ( textureData.dimensionsSnippet[ levelSnippet ] === undefined ) { let textureDimensionsParams; let dimensionType; const { primarySamples } = this.renderer.backend.utils.getTextureSampleData( texture ); const isMultisampled = primarySamples > 1; if ( texture.is3DTexture || texture.isData3DTexture ) { dimensionType = 'vec3<u32>'; } else { // Regular 2D textures, depth textures, etc. dimensionType = 'vec2<u32>'; } // Build parameters string based on texture type and multisampling if ( isMultisampled || texture.isStorageTexture ) { textureDimensionsParams = textureProperty; } else { textureDimensionsParams = `${textureProperty}${levelSnippet ? `, u32( ${ levelSnippet } )` : ''}`; } textureDimensionNode = new VarNode( new ExpressionNode( `textureDimensions( ${ textureDimensionsParams } )`, dimensionType ) ); textureData.dimensionsSnippet[ levelSnippet ] = textureDimensionNode; if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.is3DTexture || texture.isData3DTexture ) { textureData.arrayLayerCount = new VarNode( new ExpressionNode( `textureNumLayers(${textureProperty})`, 'u32' ) ); } // For cube textures, we know it's always 6 faces if ( texture.isTextureCube ) { textureData.cubeFaceCount = new VarNode( new ExpressionNode( '6u', 'u32' ) ); } } return textureDimensionNode.build( this ); } /** * Generates the WGSL snippet for a manual filtered texture. * * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @param {string} [levelSnippet='0u'] - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @return {string} The WGSL snippet. */ generateFilteredTexture( texture, textureProperty, uvSnippet, offsetSnippet, levelSnippet = '0u', depthSnippet ) { const wrapFunction = this.generateWrapFunction( texture ); const textureDimension = this.generateTextureDimension( texture, textureProperty, levelSnippet ); if ( offsetSnippet ) { uvSnippet = `${ uvSnippet } + vec2<f32>(${ offsetSnippet }) / ${ textureDimension }`; } if ( depthSnippet ) { this._include( 'biquadraticTextureArray' ); return `tsl_biquadraticTexture_array( ${ textureProperty }, ${ wrapFunction }( ${ uvSnippet } ), ${ textureDimension }, u32( ${ depthSnippet } ), u32( ${ levelSnippet } ) )`; } this._include( 'biquadraticTexture' ); return `tsl_biquadraticTexture( ${ textureProperty }, ${ wrapFunction }( ${ uvSnippet } ), ${ textureDimension }, u32( ${ levelSnippet } ) )`; } /** * Generates the WGSL snippet for a texture lookup with explicit level-of-detail. * Since it's a lookup, no sampling or filtering is applied. * * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @param {string} [levelSnippet='0u'] - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. * @return {string} The WGSL snippet. */ generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, levelSnippet = '0u' ) { // Cube textures cannot use textureLoad in WGSL, must use textureSampleLevel if ( texture.isCubeTexture === true ) { if ( offsetSnippet ) { uvSnippet = `${ uvSnippet } + vec3<f32>(${ offsetSnippet })`; } // Depth textures require integer level, regular textures use float const levelType = texture.isDepthTexture ? 'u32' : 'f32'; return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ levelType }( ${ levelSnippet } ) )`; } const wrapFunction = this.generateWrapFunction( texture ); const textureDimension = this.generateTextureDimension( texture, textureProperty, levelSnippet ); const vecType = texture.is3DTexture || texture.isData3DTexture ? 'vec3' : 'vec2'; const textureDimensionMargin = ( vecType === 'vec3' ) ? 'vec3<u32>( 1, 1, 1 )' : 'vec2<u32>( 1, 1 )'; if ( offsetSnippet ) { uvSnippet = `${ uvSnippet } + ${ vecType }<f32>(${ offsetSnippet }) / ${ vecType }<f32>( ${ textureDimension } )`; } const clampMin = `${ vecType }<f32>( 0 )`; const clampMax = `${ vecType }<f32>( ${ textureDimension } - ${ textureDimensionMargin } )`; uvSnippet = `${ vecType }<u32>( clamp( floor( ${ wrapFunction }( ${ uvSnippet } ) * ${ vecType }<f32>( ${ textureDimension } ) ), ${ clampMin }, ${ clampMax } ) )`; return this.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, null ); } /** * Generates the WGSL snippet that reads a single texel from a storage texture. * * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvIndexSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {?string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @return {string} The WGSL snippet. */ generateStorageTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { if ( offsetSnippet ) { uvIndexSnippet = `${ uvIndexSnippet } + ${ offsetSnippet }`; } let snippet; if ( depthSnippet ) { snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ depthSnippet } )`; } else { snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet } )`; } return snippet; } /** * Generates the WGSL snippet that reads a single texel from a texture without sampling or filtering. * * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvIndexSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {?string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @return {string} The WGSL snippet. */ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { if ( levelSnippet === null ) levelSnippet = '0u'; if ( offsetSnippet ) { uvIndexSnippet = `${ uvIndexSnippet } + ${ offsetSnippet }`; } let snippet; if ( depthSnippet ) { snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ depthSnippet }, u32( ${ levelSnippet } ) )`; } else { snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, u32( ${ levelSnippet } ) )`; if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { snippet += '.x'; } } return snippet; } /** * Generates the WGSL snippet that writes a single texel to a texture. * * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvIndexSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {string} valueSnippet - A WGSL snippet that represent the new texel value. * @return {string} The WGSL snippet. */ generateTextureStore( texture, textureProperty, uvIndexSnippet, depthSnippet, valueSnippet ) { let snippet; if ( depthSnippet ) { snippet = `textureStore( ${ textureProperty }, ${ uvIndexSnippet }, ${ depthSnippet }, ${ valueSnippet } )`; } else { snippet = `textureStore( ${ textureProperty }, ${ uvIndexSnippet }, ${ valueSnippet } )`; } return snippet; } /** * Returns `true` if the sampled values of the given texture should be compared against a reference value. * * @param {Texture} texture - The texture. * @return {boolean} Whether the sampled values of the given texture should be compared against a reference value or not. */ isSampleCompare( texture ) { return texture.isDepthTexture === true && texture.compareFunction !== null && this.renderer.hasCompatibility( Compatibility.TEXTURE_COMPARE ); } /** * Returns `true` if the given texture is unfilterable. * * @param {Texture} texture - The texture. * @return {boolean} Whether the given texture is unfilterable or not. */ isUnfilterable( texture ) { return this.getComponentTypeFromTexture( texture ) !== 'float' || ( ! this.isAvailable( 'float32Filterable' ) && texture.type === FloatType ) || ( this.isSampleCompare( texture ) === false && texture.minFilter === NearestFilter && texture.magFilter === NearestFilter ) || this.renderer.backend.utils.getTextureSampleData( texture ).primarySamples > 1; } /** * Generates the WGSL snippet for sampling/loading the given texture. * * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. * @return {string} The WGSL snippet. */ generateTexture( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, shaderStage = this.shaderStage ) { let snippet = null; if ( this.isUnfilterable( texture ) ) { snippet = this.generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, '0', shaderStage ); } else { snippet = this._generateTextureSample( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, shaderStage ); } return snippet; } /** * Generates the WGSL snippet for sampling/loading the given texture using explicit gradients. * * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {Array<string>} gradSnippet - An array holding both gradient WGSL snippets. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. * @return {string} The WGSL snippet. */ generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet, offsetSnippet, shaderStage = this.shaderStage ) { if ( shaderStage === 'fragment' ) { if ( depthSnippet ) { if ( offsetSnippet ) { return `textureSampleGrad( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] }, ${ offsetSnippet } )`; } return `textureSampleGrad( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] } )`; } else { if ( offsetSnippet ) { return `textureSampleGrad( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] }, ${ offsetSnippet } )`; } return `textureSampleGrad( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] } )`; } } else { error( `WebGPURenderer: THREE.TextureNode.gradient() does not support ${ shaderStage } shader.` ); } } /** * Generates the WGSL snippet for sampling a depth texture and comparing the sampled depth values * against a reference value. * * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {string} compareSnippet - A WGSL snippet that represents the reference value. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. * @return {string} The WGSL snippet. */ generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, offsetSnippet, shaderStage = this.shaderStage ) { if ( shaderStage === 'fragment' ) { if ( texture.isDepthTexture === true && texture.isArrayTexture === true ) { if ( offsetSnippet ) { return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet }, ${ offsetSnippet } )`; } return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet } )`; } if ( offsetSnippet ) { return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ compareSnippet }, ${ offsetSnippet } )`; } return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ compareSnippet } )`; } else { error( `WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${ shaderStage } shader.` ); } } /** * Generates the WGSL snippet for gathering four texels from the given texture. * * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {string} gatherSnippet - A WGSL snippet that represents the index of the channel to read. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @param {?string} flipYSnippet - A WGSL snippet that represents the y-flip. Only used for WebGL. * @return {string} The WGSL snippet. */ generateTextureGather( texture, textureProperty, uvSnippet, gatherSnippet, depthSnippet, offsetSnippet ) { const componentSnippet = texture.isDepthTexture === true ? '' : `${gatherSnippet}, `; if ( depthSnippet ) { if ( offsetSnippet ) { return `textureGather( ${componentSnippet}${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ offsetSnippet } )`; } return `textureGather( ${componentSnippet}${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet } )`; } if ( offsetSnippet ) { return `textureGather( ${componentSnippet}${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ offsetSnippet } )`; } return `textureGather( ${componentSnippet}${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet })`; } /** * Generates the WGSL snippet for performing a depth comparison on four texels in the given depth texture. * * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {string} compareSnippet - A WGSL snippet that represents the reference value. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @param {?string} flipYSnippet - A WGSL snippet that represents the y-flip. Only used for WebGL. * @return {string} The WGSL snippet. */ generateTextureGatherCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, offsetSnippet ) { if ( depthSnippet ) { if ( offsetSnippet ) { return `textureGatherCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet }, ${ offsetSnippet } )`; } return `textureGatherCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet })`; } if ( offsetSnippet ) { return `textureGatherCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ compareSnippet }, ${ offsetSnippet } )`; } return `textureGatherCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ compareSnippet })`; } /** * Generates the WGSL snippet when sampling textures with explicit mip level. * * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. * @return {string} The WGSL snippet. */ generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ) { if ( this.isUnfilterable( texture ) === false ) { if ( depthSnippet ) { if ( offsetSnippet ) { return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ levelSnippet }, ${ offsetSnippet } )`; } return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ levelSnippet } )`; } else { if ( offsetSnippet ) { return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ levelSnippet }, ${ offsetSnippet } )`; } return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ levelSnippet } )`; } } else if ( this.isFilteredTexture( texture ) ) { return this.generateFilteredTexture( texture, textureProperty, uvSnippet, offsetSnippet, levelSnippet, depthSnippet ); } else { return this.generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, offsetSnippet, levelSnippet ); } } /** * Generates the WGSL snippet when sampling textures with a bias to the mip level. * * @param {Texture} texture - The texture. * @param {string} textureProperty - The name of the texture uniform in the shader. * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. * @param {string} biasSnippet - A WGSL snippet that represents the bias to apply to the mip level before sampling. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. * @return {string} The WGSL snippet. */ generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet, depthSnippet, offsetSnippet, shaderStage = this.shaderStage ) { if ( shaderStage === 'fragment' ) { if ( depthSnippet ) { if ( offsetSnippet ) { return `textureSampleBias( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ biasSnippet }, ${ offsetSnippet } )`; } return `textureSampleBias( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ biasSnippet } )`; } else { if ( offsetSnippet ) { return `textureSampleBias( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ biasSnippet }, ${ offsetSnippet } )`; } return `textureSampleBias( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ biasSnippet } )`; } } else { error( `WebGPURenderer: THREE.TextureNode.biasNode does not support ${ shaderStage } shader.` ); } } /** * Returns a WGSL snippet that represents the property name of the given node. * * @param {Node} node - The node. * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. * @return {string} The property name. */ getPropertyName( node, shaderStage = this.shaderStage ) { if ( node.isNodeVarying === true && node.needsInterpolation === true ) { if ( shaderStage === 'vertex' ) { return `varyings.${ node.name }`; } } else if ( node.isNodeUniform === true ) { const name = node.name; const type = node.type; if ( type === 'texture' || type === 'cubeTexture' || type === 'cubeDepthTexture' || type === 'storageTexture' || type === 'texture3D' ) { return name; } else if ( type === 'buffer' || type === 'storageBuffer' || type === 'indirectStorageBuffer' ) { if ( this.isCustomStruct( node ) ) { return name; } return name + '.value'; } else { return node.groupNode.name + '.' + name; } } return super.getPropertyName( node ); } /** * Returns the output struct name. * * @return {string} The name of the output struct. */ getOutputStructName() { return 'output'; } /** * Returns the native shader operator name for a given generic name. * * @param {string} op - The operator name to resolve. * @return {?string} The resolved operator name. */ getFunctionOperator( op ) { const fnOp = wgslFnOpLib[ op ]; if ( fnOp !== undefined ) { this._include( fnOp ); return fnOp; } return null; } /** * Returns the node access for the given node and shader stage. * * @param {StorageTextureNode|StorageBufferNode} node - The storage node. * @param {string} shaderStage - The shader stage. * @return {string} The node access. */ getNodeAccess( node, shaderStage ) { if ( shaderStage !== 'compute' ) { if ( node.isAtomic === true ) { warn( 'WebGPURenderer: Atomic operations are only supported in compute shaders.' ); return NodeAccess.READ_WRITE; } return NodeAccess.READ_ONLY; } return node.access; } /** * Returns A WGSL snippet representing the storage access. * * @param {StorageTextureNode|StorageBufferNode} node - The storage node. * @param {string} shaderStage - The shader stage. * @return {string} The WGSL snippet representing the storage access. */ getStorageAccess( node, shaderStage ) { return accessNames[ this.getNodeAccess( node, shaderStage ) ]; } /** * This method is one of the more important ones since it's responsible * for generating a matching binding instance for the given uniform node. * * These bindings are later used in the renderer to create bind groups * and layouts. * * @param {UniformNode} node - The uniform node. * @param {string} type - The node data type. * @param {string} shaderStage - The shader stage. * @param {?string} [name=null] - An optional uniform name. * @return {NodeUniform} The node uniform object. */ getUniformFromNode( node, type, shaderStage, name = null ) { const uniformNode = super.getUniformFromNode( node, type, shaderStage, name ); const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache ); if ( nodeData.uniformGPU === undefined ) { let uniformGPU; const group = node.groupNode; const groupName = group.name; const bindings = this.getBindGroupArray( groupName, shaderStage ); if ( type === 'texture' || type === 'cubeTexture' || type === 'cubeDepthTexture' || type === 'storageTexture' || type === 'texture3D' ) { let texture = null; const access = this.getNodeAccess( node, shaderStage ); if ( type === 'texture' || type === 'storageTexture' ) { if ( node.value.is3DTexture === true ) { texture = new NodeSampledTexture3D( uniformNode.name, uniformNode.node, group, access ); } else { texture = new NodeSampledTexture( uniformNode.name, uniformNode.node, group, access ); } } else if ( type === 'cubeTexture' || type === 'cubeDepthTexture' ) { texture = new NodeSampledCubeTexture( uniformNode.name, uniformNode.node, group, access ); } else if ( type === 'texture3D' ) { texture = new NodeSampledTexture3D( uniformNode.name, uniformNode.node, group, access ); } texture.store = node.isStorageTextureNode === true; texture.mipLevel = texture.store ? node.mipLevel : 0; texture.setVisibility( gpuShaderStageLib[ shaderStage ] ); // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) // Also textureGather always need sampler. const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; if ( needsSampler ) { const sampler = new NodeSampler( `${ uniformNode.name }_sampler`, uniformNode.node, group ); sampler.setVisibility( gpuShaderStageLib[ shaderStage ] ); bindings.push( sampler, texture ); uniformGPU = [ sampler, texture ]; } else { bindings.push( texture ); uniformGPU = [ texture ]; } } else if ( type === 'buffer' || type === 'storageBuffer' || type === 'indirectStorageBuffer' ) { const sharedData = this.getSharedDataFromNode( node ); let buffer = sharedData.buffer; if ( buffer === undefined ) { const bufferClass = type === 'buffer' ? NodeUniformBuffer : NodeStorageBuffer; buffer = new bufferClass( node, group ); sharedData.buffer = buffer; } buffer.setVisibility( buffer.getVisibility() | gpuShaderStageLib[ shaderStage ] ); bindings.push( buffer ); uniformGPU = buffer; uniformNode.name = name ? name : 'NodeBuffer_' + uniformNode.id; } else { let uniformsGroup = this.uniformGroups[ groupName ]; if ( uniformsGroup === undefined ) { uniformsGroup = new NodeUniformsGroup( groupName, group ); uniformsGroup.setVisibility( GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE ); this.uniformGroups[ groupName ] = uniformsGroup; } // Add to bindings for this stage if not already present if ( bindings.indexOf( uniformsGroup ) === - 1 ) { bindings.push( uniformsGroup ); } uniformGPU = this.getNodeUniform( uniformNode, type ); // Only add uniform if not already present in the group (check by name to avoid duplicates across stages) const uniformName = uniformGPU.name; const alreadyExists = uniformsGroup.uniforms.some( u => u.name === uniformName ); if ( ! alreadyExists ) { uniformsGroup.addUniform( uniformGPU ); } } nodeData.uniformGPU = uniformGPU; } return uniformNode; } /** * This method should be used whenever builtins are required in nodes. * The internal builtins data structure will make sure builtins are * defined in the WGSL source. * * @param {string} name - The builtin name. * @param {string} property - The property name. * @param {string} type - The node data type. * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. * @return {string} The property name. */ getBuiltin( name, property, type, shaderStage = this.shaderStage ) { const map = this.builtins[ shaderStage ] || ( this.builtins[ shaderStage ] = new Map() ); if ( map.has( name ) === false ) { map.set( name, { name, property, type } ); } return property; } /** * Returns `true` if the given builtin is defined in the given shader stage. * * @param {string} name - The builtin name. * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. * @return {boolean} Whether the given builtin is defined in the given shader stage or not. */ hasBuiltin( name, shaderStage = this.shaderStage ) { return ( this.builtins[ shaderStage ] !== undefined && this.builtins[ shaderStage ].has( name ) ); } /** * Returns the vertex index builtin. * * @return {string} The vertex index. */ getVertexIndex() { if ( this.shaderStage === 'vertex' ) { return this.getBuiltin( 'vertex_index', 'vertexIndex', 'u32', 'attribute' ); } return 'vertexIndex'; } /** * Builds the given shader node. * * @param {ShaderNodeInternal} shaderNode - The shader node. * @return {string} The WGSL function code. */ buildFunctionCode( shaderNode ) { const layout = shaderNode.layout; const flowData = this.flowShaderNode( shaderNode ); const parameters = []; for ( const input of layout.inputs ) { parameters.push( input.name + ' : ' + this.getType( input.type ) ); } // let code = `fn ${ layout.name }( ${ parameters.join( ', ' ) } ) -> ${ this.getType( layout.type ) } { ${ flowData.vars } ${ flowData.code } `; if ( flowData.result ) { code += `\treturn ${ flowData.result };\n`; } code += '\n}\n'; // return code; } /** * Contextually returns either the vertex stage instance index builtin * or the linearized index of an compute invocation within a grid of workgroups. * * @return {string} The instance index. */ getInstanceIndex() { if ( this.shaderStage === 'vertex' ) { return this.getBuiltin( 'instance_index', 'instanceIndex', 'u32', 'attribute' ); } return 'instanceIndex'; } /** * Returns a builtin representing the index of a compute invocation within the scope of a workgroup load. * * @return {string} The invocation local index. */ getInvocationLocalIndex() { return this.getBuiltin( 'local_invocation_index', 'invocationLocalIndex', 'u32', 'attribute' ); } /** * Returns a builtin representing the size of a subgroup within the current shader. * * @return {string} The subgroup size. */ getSubgroupSize() { this.enableSubGroups(); return this.getBuiltin( 'subgroup_size', 'subgroupSize', 'u32', 'attribute' ); } /** * Returns a builtin representing the index of a compute invocation within the scope of a subgroup. * * @return {string} The invocation subgroup index. */ g