ShaderNode.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. import Node, { addNodeClass } from '../core/Node.js';
  2. import ArrayElementNode from '../utils/ArrayElementNode.js';
  3. import ConvertNode from '../utils/ConvertNode.js';
  4. import JoinNode from '../utils/JoinNode.js';
  5. import SplitNode from '../utils/SplitNode.js';
  6. import SetNode from '../utils/SetNode.js';
  7. import ConstNode from '../core/ConstNode.js';
  8. import { getValueFromType, getValueType } from '../core/NodeUtils.js';
  9. //
  10. let currentStack = null;
  11. const NodeElements = new Map(); // @TODO: Currently only a few nodes are added, probably also add others
  12. export function addNodeElement( name, nodeElement ) {
  13. if ( NodeElements.has( name ) ) {
  14. console.warn( `Redefinition of node element ${ name }` );
  15. return;
  16. }
  17. if ( typeof nodeElement !== 'function' ) throw new Error( `Node element ${ name } is not a function` );
  18. NodeElements.set( name, nodeElement );
  19. }
  20. const parseSwizzle = ( props ) => props.replace( /r|s/g, 'x' ).replace( /g|t/g, 'y' ).replace( /b|p/g, 'z' ).replace( /a|q/g, 'w' );
  21. const shaderNodeHandler = {
  22. setup( NodeClosure, params ) {
  23. const inputs = params.shift();
  24. return NodeClosure( nodeObjects( inputs ), ...params );
  25. },
  26. get( node, prop, nodeObj ) {
  27. if ( typeof prop === 'string' && node[ prop ] === undefined ) {
  28. if ( node.isStackNode !== true && prop === 'assign' ) {
  29. return ( ...params ) => {
  30. currentStack.assign( nodeObj, ...params );
  31. return nodeObj;
  32. };
  33. } else if ( NodeElements.has( prop ) ) {
  34. const nodeElement = NodeElements.get( prop );
  35. return node.isStackNode ? ( ...params ) => nodeObj.add( nodeElement( ...params ) ) : ( ...params ) => nodeElement( nodeObj, ...params );
  36. } else if ( prop === 'self' ) {
  37. return node;
  38. } else if ( prop.endsWith( 'Assign' ) && NodeElements.has( prop.slice( 0, prop.length - 'Assign'.length ) ) ) {
  39. const nodeElement = NodeElements.get( prop.slice( 0, prop.length - 'Assign'.length ) );
  40. return node.isStackNode ? ( ...params ) => nodeObj.assign( params[ 0 ], nodeElement( ...params ) ) : ( ...params ) => nodeObj.assign( nodeElement( nodeObj, ...params ) );
  41. } else if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true ) {
  42. // accessing properties ( swizzle )
  43. prop = parseSwizzle( prop );
  44. return nodeObject( new SplitNode( nodeObj, prop ) );
  45. } else if ( /^set[XYZWRGBASTPQ]{1,4}$/.test( prop ) === true ) {
  46. // set properties ( swizzle )
  47. prop = parseSwizzle( prop.slice( 3 ).toLowerCase() );
  48. // sort to xyzw sequence
  49. prop = prop.split( '' ).sort().join( '' );
  50. return ( value ) => nodeObject( new SetNode( node, prop, value ) );
  51. } else if ( prop === 'width' || prop === 'height' || prop === 'depth' ) {
  52. // accessing property
  53. if ( prop === 'width' ) prop = 'x';
  54. else if ( prop === 'height' ) prop = 'y';
  55. else if ( prop === 'depth' ) prop = 'z';
  56. return nodeObject( new SplitNode( node, prop ) );
  57. } else if ( /^\d+$/.test( prop ) === true ) {
  58. // accessing array
  59. return nodeObject( new ArrayElementNode( nodeObj, new ConstNode( Number( prop ), 'uint' ) ) );
  60. }
  61. }
  62. return Reflect.get( node, prop, nodeObj );
  63. },
  64. set( node, prop, value, nodeObj ) {
  65. if ( typeof prop === 'string' && node[ prop ] === undefined ) {
  66. // setting properties
  67. if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true || prop === 'width' || prop === 'height' || prop === 'depth' || /^\d+$/.test( prop ) === true ) {
  68. nodeObj[ prop ].assign( value );
  69. return true;
  70. }
  71. }
  72. return Reflect.set( node, prop, value, nodeObj );
  73. }
  74. };
  75. const nodeObjectsCacheMap = new WeakMap();
  76. const nodeBuilderFunctionsCacheMap = new WeakMap();
  77. const ShaderNodeObject = function ( obj, altType = null ) {
  78. const type = getValueType( obj );
  79. if ( type === 'node' ) {
  80. let nodeObject = nodeObjectsCacheMap.get( obj );
  81. if ( nodeObject === undefined ) {
  82. nodeObject = new Proxy( obj, shaderNodeHandler );
  83. nodeObjectsCacheMap.set( obj, nodeObject );
  84. nodeObjectsCacheMap.set( nodeObject, nodeObject );
  85. }
  86. return nodeObject;
  87. } else if ( ( altType === null && ( type === 'float' || type === 'boolean' ) ) || ( type && type !== 'shader' && type !== 'string' ) ) {
  88. return nodeObject( getConstNode( obj, altType ) );
  89. } else if ( type === 'shader' ) {
  90. return tslFn( obj );
  91. }
  92. return obj;
  93. };
  94. const ShaderNodeObjects = function ( objects, altType = null ) {
  95. for ( const name in objects ) {
  96. objects[ name ] = nodeObject( objects[ name ], altType );
  97. }
  98. return objects;
  99. };
  100. const ShaderNodeArray = function ( array, altType = null ) {
  101. const len = array.length;
  102. for ( let i = 0; i < len; i ++ ) {
  103. array[ i ] = nodeObject( array[ i ], altType );
  104. }
  105. return array;
  106. };
  107. const ShaderNodeProxy = function ( NodeClass, scope = null, factor = null, settings = null ) {
  108. const assignNode = ( node ) => nodeObject( settings !== null ? Object.assign( node, settings ) : node );
  109. if ( scope === null ) {
  110. return ( ...params ) => {
  111. return assignNode( new NodeClass( ...nodeArray( params ) ) );
  112. };
  113. } else if ( factor !== null ) {
  114. factor = nodeObject( factor );
  115. return ( ...params ) => {
  116. return assignNode( new NodeClass( scope, ...nodeArray( params ), factor ) );
  117. };
  118. } else {
  119. return ( ...params ) => {
  120. return assignNode( new NodeClass( scope, ...nodeArray( params ) ) );
  121. };
  122. }
  123. };
  124. const ShaderNodeImmutable = function ( NodeClass, ...params ) {
  125. return nodeObject( new NodeClass( ...nodeArray( params ) ) );
  126. };
  127. class ShaderCallNodeInternal extends Node {
  128. constructor( shaderNode, inputNodes ) {
  129. super();
  130. this.shaderNode = shaderNode;
  131. this.inputNodes = inputNodes;
  132. }
  133. getNodeType( builder ) {
  134. const properties = builder.getNodeProperties( this );
  135. if ( properties.outputNode === null ) {
  136. properties.outputNode = this.setupOutput( builder );
  137. }
  138. return properties.outputNode.getNodeType( builder );
  139. }
  140. call( builder ) {
  141. const { shaderNode, inputNodes } = this;
  142. if ( shaderNode.layout ) {
  143. let functionNodesCacheMap = nodeBuilderFunctionsCacheMap.get( builder.constructor );
  144. if ( functionNodesCacheMap === undefined ) {
  145. functionNodesCacheMap = new WeakMap();
  146. nodeBuilderFunctionsCacheMap.set( builder.constructor, functionNodesCacheMap );
  147. }
  148. let functionNode = functionNodesCacheMap.get( shaderNode );
  149. if ( functionNode === undefined ) {
  150. functionNode = nodeObject( builder.buildFunctionNode( shaderNode ) );
  151. functionNodesCacheMap.set( shaderNode, functionNode );
  152. }
  153. if ( builder.currentFunctionNode !== null ) {
  154. builder.currentFunctionNode.includes.push( functionNode );
  155. }
  156. return nodeObject( functionNode.call( inputNodes ) );
  157. }
  158. const jsFunc = shaderNode.jsFunc;
  159. const outputNode = inputNodes !== null ? jsFunc( inputNodes, builder.stack, builder ) : jsFunc( builder.stack, builder );
  160. return nodeObject( outputNode );
  161. }
  162. setup( builder ) {
  163. const { outputNode } = builder.getNodeProperties( this );
  164. return outputNode || this.setupOutput( builder );
  165. }
  166. setupOutput( builder ) {
  167. builder.addStack();
  168. builder.stack.outputNode = this.call( builder );
  169. return builder.removeStack();
  170. }
  171. generate( builder, output ) {
  172. const { outputNode } = builder.getNodeProperties( this );
  173. if ( outputNode === null ) {
  174. // TSL: It's recommended to use `tslFn` in setup() pass.
  175. return this.call( builder ).build( builder, output );
  176. }
  177. return super.generate( builder, output );
  178. }
  179. }
  180. class ShaderNodeInternal extends Node {
  181. constructor( jsFunc ) {
  182. super();
  183. this.jsFunc = jsFunc;
  184. this.layout = null;
  185. this.global = true;
  186. }
  187. get isArrayInput() {
  188. return /^\((\s+)?\[/.test( this.jsFunc.toString() );
  189. }
  190. setLayout( layout ) {
  191. this.layout = layout;
  192. return this;
  193. }
  194. call( inputs = null ) {
  195. nodeObjects( inputs );
  196. return nodeObject( new ShaderCallNodeInternal( this, inputs ) );
  197. }
  198. setup() {
  199. return this.call();
  200. }
  201. }
  202. const bools = [ false, true ];
  203. const uints = [ 0, 1, 2, 3 ];
  204. const ints = [ - 1, - 2 ];
  205. const floats = [ 0.5, 1.5, 1 / 3, 1e-6, 1e6, Math.PI, Math.PI * 2, 1 / Math.PI, 2 / Math.PI, 1 / ( Math.PI * 2 ), Math.PI / 2 ];
  206. const boolsCacheMap = new Map();
  207. for ( const bool of bools ) boolsCacheMap.set( bool, new ConstNode( bool ) );
  208. const uintsCacheMap = new Map();
  209. for ( const uint of uints ) uintsCacheMap.set( uint, new ConstNode( uint, 'uint' ) );
  210. const intsCacheMap = new Map( [ ...uintsCacheMap ].map( el => new ConstNode( el.value, 'int' ) ) );
  211. for ( const int of ints ) intsCacheMap.set( int, new ConstNode( int, 'int' ) );
  212. const floatsCacheMap = new Map( [ ...intsCacheMap ].map( el => new ConstNode( el.value ) ) );
  213. for ( const float of floats ) floatsCacheMap.set( float, new ConstNode( float ) );
  214. for ( const float of floats ) floatsCacheMap.set( - float, new ConstNode( - float ) );
  215. const cacheMaps = { bool: boolsCacheMap, uint: uintsCacheMap, ints: intsCacheMap, float: floatsCacheMap };
  216. const constNodesCacheMap = new Map( [ ...boolsCacheMap, ...floatsCacheMap ] );
  217. const getConstNode = ( value, type ) => {
  218. if ( constNodesCacheMap.has( value ) ) {
  219. return constNodesCacheMap.get( value );
  220. } else if ( value.isNode === true ) {
  221. return value;
  222. } else {
  223. return new ConstNode( value, type );
  224. }
  225. };
  226. const safeGetNodeType = ( node ) => {
  227. try {
  228. return node.getNodeType();
  229. } catch ( _ ) {
  230. return undefined;
  231. }
  232. };
  233. const ConvertType = function ( type, cacheMap = null ) {
  234. return ( ...params ) => {
  235. if ( params.length === 0 || ( ! [ 'bool', 'float', 'int', 'uint' ].includes( type ) && params.every( param => typeof param !== 'object' ) ) ) {
  236. params = [ getValueFromType( type, ...params ) ];
  237. }
  238. if ( params.length === 1 && cacheMap !== null && cacheMap.has( params[ 0 ] ) ) {
  239. return nodeObject( cacheMap.get( params[ 0 ] ) );
  240. }
  241. if ( params.length === 1 ) {
  242. const node = getConstNode( params[ 0 ], type );
  243. if ( safeGetNodeType( node ) === type ) return nodeObject( node );
  244. return nodeObject( new ConvertNode( node, type ) );
  245. }
  246. const nodes = params.map( param => getConstNode( param ) );
  247. return nodeObject( new JoinNode( nodes, type ) );
  248. };
  249. };
  250. // exports
  251. export const defined = ( value ) => value && value.value;
  252. // utils
  253. export const getConstNodeType = ( value ) => ( value !== undefined && value !== null ) ? ( value.nodeType || value.convertTo || ( typeof value === 'string' ? value : null ) ) : null;
  254. // shader node base
  255. export function ShaderNode( jsFunc ) {
  256. return new Proxy( new ShaderNodeInternal( jsFunc ), shaderNodeHandler );
  257. }
  258. export const nodeObject = ( val, altType = null ) => /* new */ ShaderNodeObject( val, altType );
  259. export const nodeObjects = ( val, altType = null ) => new ShaderNodeObjects( val, altType );
  260. export const nodeArray = ( val, altType = null ) => new ShaderNodeArray( val, altType );
  261. export const nodeProxy = ( ...params ) => new ShaderNodeProxy( ...params );
  262. export const nodeImmutable = ( ...params ) => new ShaderNodeImmutable( ...params );
  263. export const tslFn = ( jsFunc ) => {
  264. const shaderNode = new ShaderNode( jsFunc );
  265. const fn = ( ...params ) => {
  266. let inputs;
  267. nodeObjects( params );
  268. if ( params[ 0 ] && params[ 0 ].isNode ) {
  269. inputs = [ ...params ];
  270. } else {
  271. inputs = params[ 0 ];
  272. }
  273. return shaderNode.call( inputs );
  274. };
  275. fn.shaderNode = shaderNode;
  276. fn.setLayout = ( layout ) => {
  277. shaderNode.setLayout( layout );
  278. return fn;
  279. };
  280. return fn;
  281. };
  282. addNodeClass( 'ShaderNode', ShaderNode );
  283. //
  284. addNodeElement( 'toGlobal', ( node ) => {
  285. node.global = true;
  286. return node;
  287. } );
  288. //
  289. export const setCurrentStack = ( stack ) => {
  290. if ( currentStack === stack ) {
  291. //throw new Error( 'Stack already defined.' );
  292. }
  293. currentStack = stack;
  294. };
  295. export const getCurrentStack = () => currentStack;
  296. export const If = ( ...params ) => currentStack.if( ...params );
  297. export function append( node ) {
  298. if ( currentStack ) currentStack.add( node );
  299. return node;
  300. }
  301. addNodeElement( 'append', append );
  302. // types
  303. // @TODO: Maybe export from ConstNode.js?
  304. export const color = new ConvertType( 'color' );
  305. export const float = new ConvertType( 'float', cacheMaps.float );
  306. export const int = new ConvertType( 'int', cacheMaps.ints );
  307. export const uint = new ConvertType( 'uint', cacheMaps.uint );
  308. export const bool = new ConvertType( 'bool', cacheMaps.bool );
  309. export const vec2 = new ConvertType( 'vec2' );
  310. export const ivec2 = new ConvertType( 'ivec2' );
  311. export const uvec2 = new ConvertType( 'uvec2' );
  312. export const bvec2 = new ConvertType( 'bvec2' );
  313. export const vec3 = new ConvertType( 'vec3' );
  314. export const ivec3 = new ConvertType( 'ivec3' );
  315. export const uvec3 = new ConvertType( 'uvec3' );
  316. export const bvec3 = new ConvertType( 'bvec3' );
  317. export const vec4 = new ConvertType( 'vec4' );
  318. export const ivec4 = new ConvertType( 'ivec4' );
  319. export const uvec4 = new ConvertType( 'uvec4' );
  320. export const bvec4 = new ConvertType( 'bvec4' );
  321. export const mat2 = new ConvertType( 'mat2' );
  322. export const imat2 = new ConvertType( 'imat2' );
  323. export const umat2 = new ConvertType( 'umat2' );
  324. export const bmat2 = new ConvertType( 'bmat2' );
  325. export const mat3 = new ConvertType( 'mat3' );
  326. export const imat3 = new ConvertType( 'imat3' );
  327. export const umat3 = new ConvertType( 'umat3' );
  328. export const bmat3 = new ConvertType( 'bmat3' );
  329. export const mat4 = new ConvertType( 'mat4' );
  330. export const imat4 = new ConvertType( 'imat4' );
  331. export const umat4 = new ConvertType( 'umat4' );
  332. export const bmat4 = new ConvertType( 'bmat4' );
  333. export const string = ( value = '' ) => nodeObject( new ConstNode( value, 'string' ) );
  334. export const arrayBuffer = ( value ) => nodeObject( new ConstNode( value, 'ArrayBuffer' ) );
  335. addNodeElement( 'toColor', color );
  336. addNodeElement( 'toFloat', float );
  337. addNodeElement( 'toInt', int );
  338. addNodeElement( 'toUint', uint );
  339. addNodeElement( 'toBool', bool );
  340. addNodeElement( 'toVec2', vec2 );
  341. addNodeElement( 'toIvec2', ivec2 );
  342. addNodeElement( 'toUvec2', uvec2 );
  343. addNodeElement( 'toBvec2', bvec2 );
  344. addNodeElement( 'toVec3', vec3 );
  345. addNodeElement( 'toIvec3', ivec3 );
  346. addNodeElement( 'toUvec3', uvec3 );
  347. addNodeElement( 'toBvec3', bvec3 );
  348. addNodeElement( 'toVec4', vec4 );
  349. addNodeElement( 'toIvec4', ivec4 );
  350. addNodeElement( 'toUvec4', uvec4 );
  351. addNodeElement( 'toBvec4', bvec4 );
  352. addNodeElement( 'toMat2', mat2 );
  353. addNodeElement( 'toImat2', imat2 );
  354. addNodeElement( 'toUmat2', umat2 );
  355. addNodeElement( 'toBmat2', bmat2 );
  356. addNodeElement( 'toMat3', mat3 );
  357. addNodeElement( 'toImat3', imat3 );
  358. addNodeElement( 'toUmat3', umat3 );
  359. addNodeElement( 'toBmat3', bmat3 );
  360. addNodeElement( 'toMat4', mat4 );
  361. addNodeElement( 'toImat4', imat4 );
  362. addNodeElement( 'toUmat4', umat4 );
  363. addNodeElement( 'toBmat4', bmat4 );
  364. // basic nodes
  365. // HACK - we cannot export them from the corresponding files because of the cyclic dependency
  366. export const element = nodeProxy( ArrayElementNode );
  367. export const convert = ( node, types ) => nodeObject( new ConvertNode( nodeObject( node ), types ) );
  368. export const split = ( node, channels ) => nodeObject( new SplitNode( nodeObject( node ), channels ) );
  369. addNodeElement( 'element', element );
  370. addNodeElement( 'convert', convert );