TSLEncoder.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983
  1. import { REVISION } from 'three/webgpu';
  2. import * as TSL from 'three/tsl';
  3. import { VariableDeclaration, Accessor } from './AST.js';
  4. import { isExpression, isPrimitive } from './TranspilerUtils.js';
  5. const opLib = {
  6. '=': 'assign',
  7. '+': 'add',
  8. '-': 'sub',
  9. '*': 'mul',
  10. '/': 'div',
  11. '%': 'remainder',
  12. '<': 'lessThan',
  13. '>': 'greaterThan',
  14. '<=': 'lessThanEqual',
  15. '>=': 'greaterThanEqual',
  16. '==': 'equal',
  17. '!=': 'notEqual',
  18. '&&': 'and',
  19. '||': 'or',
  20. '^^': 'xor',
  21. '&': 'bitAnd',
  22. '|': 'bitOr',
  23. '^': 'bitXor',
  24. '<<': 'shiftLeft',
  25. '>>': 'shiftRight',
  26. '+=': 'addAssign',
  27. '-=': 'subAssign',
  28. '*=': 'mulAssign',
  29. '/=': 'divAssign',
  30. '%=': 'remainderAssign',
  31. '^=': 'bitXorAssign',
  32. '&=': 'bitAndAssign',
  33. '|=': 'bitOrAssign',
  34. '<<=': 'shiftLeftAssign',
  35. '>>=': 'shiftRightAssign'
  36. };
  37. const unaryLib = {
  38. '+': '', // positive
  39. '-': 'negate',
  40. '~': 'bitNot',
  41. '!': 'not',
  42. '++': 'increment', // incrementBefore
  43. '--': 'decrement' // decrementBefore
  44. };
  45. const textureLookupFunctions = [ 'texture', 'texture2D', 'texture3D', 'textureCube', 'textureLod', 'texelFetch', 'textureGrad' ];
  46. class TSLEncoder {
  47. constructor() {
  48. this.tab = '';
  49. this.imports = new Set();
  50. this.global = new Set();
  51. this.overloadings = new Map();
  52. this.iife = false;
  53. this.reference = false;
  54. this.block = null;
  55. }
  56. addImport( name ) {
  57. // import only if it's a node
  58. name = name.split( '.' )[ 0 ];
  59. if ( TSL[ name ] !== undefined && this.global.has( name ) === false ) {
  60. this.imports.add( name );
  61. }
  62. }
  63. emitUniform( node ) {
  64. let code = `const ${ node.name } = `;
  65. this.global.add( node.name );
  66. if ( this.reference === true ) {
  67. this.addImport( 'reference' );
  68. //code += `reference( '${ node.name }', '${ node.type }', uniforms )`;
  69. // legacy
  70. code += `reference( 'value', '${ node.type }', uniforms[ '${ node.name }' ] )`;
  71. } else {
  72. if ( node.type === 'texture' ) {
  73. this.addImport( 'texture' );
  74. code += 'texture( /* <THREE.Texture> */ )';
  75. } else if ( node.type === 'cubeTexture' ) {
  76. this.addImport( 'cubeTexture' );
  77. code += 'cubeTexture( /* <THREE.CubeTexture> */ )';
  78. } else if ( node.type === 'texture3D' ) {
  79. this.addImport( 'texture3D' );
  80. code += 'texture3D( /* <THREE.Data3DTexture> */ )';
  81. } else {
  82. // default uniform
  83. this.addImport( 'uniform' );
  84. code += `uniform( '${ node.type }' )`;
  85. }
  86. }
  87. return code;
  88. }
  89. emitExpression( node, output = null ) {
  90. let code;
  91. if ( node.isAccessor ) {
  92. if ( node.linker.reference === null ) {
  93. this.addImport( node.property );
  94. }
  95. code = node.property;
  96. } else if ( node.isNumber ) {
  97. code = node.value;
  98. } else if ( node.isString ) {
  99. code = '\'' + node.value + '\'';
  100. } else if ( node.isOperator ) {
  101. const opFn = opLib[ node.type ] || node.type;
  102. const left = this.emitExpression( node.left, output );
  103. const right = this.emitExpression( node.right, output );
  104. if ( node.isNumericExpression ) {
  105. return left + ' ' + node.type + ' ' + right;
  106. }
  107. if ( isPrimitive( left ) ) {
  108. code = opFn + '( ' + left + ', ' + right + ' )';
  109. this.addImport( opFn );
  110. } else if ( opFn === '.' ) {
  111. code = left + opFn + right;
  112. } else {
  113. code = left + '.' + opFn + '( ' + right + ' )';
  114. }
  115. } else if ( node.isFunctionCall ) {
  116. const params = [];
  117. for ( const parameter of node.params ) {
  118. params.push( this.emitExpression( parameter ) );
  119. }
  120. // handle texture lookup function calls in separate branch
  121. if ( textureLookupFunctions.includes( node.name ) ) {
  122. code = `${ params[ 0 ] }.sample( ${ params[ 1 ] } )`;
  123. if ( node.name === 'texture' || node.name === 'texture2D' || node.name === 'texture3D' || node.name === 'textureCube' ) {
  124. if ( params.length === 3 ) {
  125. code += `.bias( ${ params[ 2 ] } )`;
  126. }
  127. } else if ( node.name === 'textureLod' ) {
  128. code += `.level( ${ params[ 2 ] } )`;
  129. } else if ( node.name === 'textureGrad' ) {
  130. code += `.grad( ${ params[ 2 ] }, ${ params[ 3 ] } )`;
  131. } else if ( node.name === 'texelFetch' ) {
  132. code += '.setSampler( false )';
  133. }
  134. } else {
  135. this.addImport( node.name );
  136. const paramsStr = params.length > 0 ? ' ' + params.join( ', ' ) + ' ' : '';
  137. code = `${ node.name }(${ paramsStr })`;
  138. }
  139. } else if ( node.isReturn ) {
  140. code = 'return';
  141. if ( node.value ) {
  142. code += ' ' + this.emitExpression( node.value );
  143. }
  144. } else if ( node.isDiscard ) {
  145. this.addImport( 'Discard' );
  146. code = 'Discard()';
  147. } else if ( node.isBreak ) {
  148. this.addImport( 'Break' );
  149. code = 'Break()';
  150. } else if ( node.isContinue ) {
  151. this.addImport( 'Continue' );
  152. code = 'Continue()';
  153. } else if ( node.isAccessorElements ) {
  154. code = this.emitExpression( node.object );
  155. for ( const element of node.elements ) {
  156. if ( element.isStaticElement ) {
  157. code += '.' + this.emitExpression( element.value );
  158. } else if ( element.isDynamicElement ) {
  159. const value = this.emitExpression( element.value );
  160. if ( isPrimitive( value ) ) {
  161. code += `[ ${ value } ]`;
  162. } else {
  163. code += `.element( ${ value } )`;
  164. }
  165. }
  166. }
  167. } else if ( node.isDynamicElement ) {
  168. code = this.emitExpression( node.value );
  169. } else if ( node.isStaticElement ) {
  170. code = this.emitExpression( node.value );
  171. } else if ( node.isFor ) {
  172. code = this.emitFor( node );
  173. } else if ( node.isWhile ) {
  174. code = this.emitWhile( node );
  175. } else if ( node.isSwitch ) {
  176. code = this.emitSwitch( node );
  177. } else if ( node.isVariableDeclaration ) {
  178. code = this.emitVariables( node );
  179. } else if ( node.isUniform ) {
  180. code = this.emitUniform( node );
  181. } else if ( node.isVarying ) {
  182. code = this.emitVarying( node );
  183. } else if ( node.isStructDefinition ) {
  184. code = this.emitStructDefinition( node );
  185. } else if ( node.isTernary ) {
  186. code = this.emitTernary( node );
  187. } else if ( node.isConditional ) {
  188. code = this.emitConditional( node );
  189. } else if ( node.isUnary && node.expression.isNumber && node.type === '-' ) {
  190. code = '- ' + node.expression.value;
  191. if ( node.expression.type !== 'float' ) {
  192. code = node.expression.type + '( ' + code + ' )';
  193. this.addImport( node.expression.type );
  194. }
  195. } else if ( node.isUnary ) {
  196. let type = unaryLib[ node.type ];
  197. if ( node.hasAssignment ) {
  198. if ( node.after === false && ( node.type === '++' || node.type === '--' ) ) {
  199. type += 'Before';
  200. }
  201. }
  202. const exp = this.emitExpression( node.expression );
  203. if ( isPrimitive( exp ) ) {
  204. this.addImport( type );
  205. code = type + '( ' + exp + ' )';
  206. } else {
  207. code = exp + '.' + type + '()';
  208. }
  209. } else {
  210. console.warn( 'Unknown node type', node );
  211. }
  212. if ( ! code ) code = '/* unknown statement */';
  213. return code;
  214. }
  215. emitBody( body ) {
  216. let code = '';
  217. this.tab += '\t';
  218. for ( const statement of body ) {
  219. code += this.emitExtraLine( statement, body );
  220. if ( statement.isComment ) {
  221. code += this.emitComment( statement, body );
  222. continue;
  223. }
  224. if ( this.block && this.block.isSwitchCase ) {
  225. if ( statement.isBreak ) continue; // skip break statements in switch cases
  226. }
  227. code += this.tab + this.emitExpression( statement );
  228. if ( code.slice( - 1 ) !== '}' ) code += ';';
  229. code += '\n';
  230. }
  231. code = code.slice( 0, - 1 ); // remove the last extra line
  232. this.tab = this.tab.slice( 0, - 1 );
  233. return code;
  234. }
  235. emitTernary( node ) {
  236. const condStr = this.emitExpression( node.cond );
  237. const leftStr = this.emitExpression( node.left );
  238. const rightStr = this.emitExpression( node.right );
  239. this.addImport( 'select' );
  240. return `select( ${ condStr }, ${ leftStr }, ${ rightStr } )`;
  241. }
  242. emitConditional( node ) {
  243. const condStr = this.emitExpression( node.cond );
  244. const bodyStr = this.emitBody( node.body );
  245. let ifStr = `If( ${ condStr }, () => {
  246. ${ bodyStr }
  247. ${ this.tab }} )`;
  248. let current = node;
  249. while ( current.elseConditional ) {
  250. const elseBodyStr = this.emitBody( current.elseConditional.body );
  251. if ( current.elseConditional.cond ) {
  252. const elseCondStr = this.emitExpression( current.elseConditional.cond );
  253. ifStr += `.ElseIf( ${ elseCondStr }, () => {
  254. ${ elseBodyStr }
  255. ${ this.tab }} )`;
  256. } else {
  257. ifStr += `.Else( () => {
  258. ${ elseBodyStr }
  259. ${ this.tab }} )`;
  260. }
  261. current = current.elseConditional;
  262. }
  263. this.imports.add( 'If' );
  264. return ifStr;
  265. }
  266. emitLoop( node ) {
  267. const start = this.emitExpression( node.initialization.value );
  268. const end = this.emitExpression( node.condition.right );
  269. const name = node.initialization.name;
  270. const type = node.initialization.type;
  271. const condition = node.condition.type;
  272. const nameParam = name !== 'i' ? `, name: '${ name }'` : '';
  273. const typeParam = type !== 'int' ? `, type: '${ type }'` : '';
  274. const conditionParam = condition !== '<' ? `, condition: '${ condition }'` : '';
  275. let updateParam = '';
  276. if ( node.afterthought.isUnary ) {
  277. if ( node.afterthought.type !== '++' ) {
  278. updateParam = `, update: '${ node.afterthought.type }'`;
  279. }
  280. } else if ( node.afterthought.isOperator ) {
  281. if ( node.afterthought.right.isAccessor || node.afterthought.right.isNumber ) {
  282. updateParam = `, update: ${ this.emitExpression( node.afterthought.right ) }`;
  283. } else {
  284. updateParam = `, update: ( { i } ) => ${ this.emitExpression( node.afterthought ) }`;
  285. }
  286. }
  287. let loopStr = `Loop( { start: ${ start }, end: ${ end + nameParam + typeParam + conditionParam + updateParam } }, ( { ${ name } } ) => {\n\n`;
  288. loopStr += this.emitBody( node.body ) + '\n\n';
  289. loopStr += this.tab + '} )';
  290. this.imports.add( 'Loop' );
  291. return loopStr;
  292. }
  293. emitSwitch( switchNode ) {
  294. const discriminantString = this.emitExpression( switchNode.discriminant );
  295. this.tab += '\t';
  296. let switchString = `Switch( ${ discriminantString } )\n${ this.tab }`;
  297. const previousBlock = this.block;
  298. for ( const switchCase of switchNode.cases ) {
  299. this.block = switchCase;
  300. let caseBodyString;
  301. if ( ! switchCase.isDefault ) {
  302. const caseConditions = [ ];
  303. for ( const condition of switchCase.conditions ) {
  304. caseConditions.push( this.emitExpression( condition ) );
  305. }
  306. caseBodyString = this.emitBody( switchCase.body );
  307. switchString += `.Case( ${ caseConditions.join( ', ' ) }, `;
  308. } else {
  309. caseBodyString = this.emitBody( switchCase.body );
  310. switchString += '.Default( ';
  311. }
  312. switchString += `() => {
  313. ${ caseBodyString }
  314. ${ this.tab }} )`;
  315. }
  316. this.block = previousBlock;
  317. this.tab = this.tab.slice( 0, - 1 );
  318. this.imports.add( 'Switch' );
  319. return switchString;
  320. }
  321. emitFor( node ) {
  322. const { initialization, condition, afterthought } = node;
  323. if ( ( initialization && initialization.isVariableDeclaration && initialization.next === null ) &&
  324. ( condition && condition.left.isAccessor && condition.left.property === initialization.name ) &&
  325. ( afterthought && (
  326. ( afterthought.isUnary && ( initialization.name === afterthought.expression.property ) ) ||
  327. ( afterthought.isOperator && ( initialization.name === afterthought.left.property ) )
  328. ) )
  329. ) {
  330. return this.emitLoop( node );
  331. }
  332. return this.emitForWhile( node );
  333. }
  334. emitForWhile( node ) {
  335. const initialization = this.emitExpression( node.initialization );
  336. const condition = this.emitExpression( node.condition );
  337. const afterthought = this.emitExpression( node.afterthought );
  338. this.tab += '\t';
  339. let forStr = '{\n\n' + this.tab + initialization + ';\n\n';
  340. forStr += `${ this.tab }Loop( ${ condition }, () => {\n\n`;
  341. forStr += this.emitBody( node.body ) + '\n\n';
  342. forStr += this.tab + '\t' + afterthought + ';\n\n';
  343. forStr += this.tab + '} )\n\n';
  344. this.tab = this.tab.slice( 0, - 1 );
  345. forStr += this.tab + '}';
  346. this.imports.add( 'Loop' );
  347. return forStr;
  348. }
  349. emitWhile( node ) {
  350. const condition = this.emitExpression( node.condition );
  351. let whileStr = `Loop( ${ condition }, () => {\n\n`;
  352. whileStr += this.emitBody( node.body ) + '\n\n';
  353. whileStr += this.tab + '} )';
  354. this.imports.add( 'Loop' );
  355. return whileStr;
  356. }
  357. emitVariables( node, isRoot = true ) {
  358. const { name, type, value, next } = node;
  359. let varStr = isRoot ? 'const ' : '';
  360. varStr += name;
  361. if ( value ) {
  362. let valueStr = this.emitExpression( value );
  363. if ( value.isNumericExpression ) {
  364. // convert JS primitive to node
  365. valueStr = `${ type }( ${ valueStr } )`;
  366. this.addImport( type );
  367. }
  368. varStr += ' = ' + valueStr;
  369. } else {
  370. const program = node.getProgram();
  371. if ( program.structTypes.has( type ) ) {
  372. varStr += ` = ${ type }()`;
  373. } else {
  374. varStr += ` = property( '${ type }' )`;
  375. this.addImport( 'property' );
  376. }
  377. }
  378. if ( next ) {
  379. varStr += ', ' + this.emitVariables( next, false );
  380. }
  381. if ( node.needsToVar ) {
  382. varStr = varStr + '.toVar()';
  383. }
  384. return varStr;
  385. }
  386. emitVarying( node ) {
  387. const { name, type } = node;
  388. this.addImport( 'varying' );
  389. this.addImport( type );
  390. return `const ${ name } = varying( ${ type }(), '${ name }' )`;
  391. }
  392. emitStructDefinition( node ) {
  393. const { name, members } = node;
  394. this.addImport( 'struct' );
  395. let structString = `const ${ name } = struct( {\n`;
  396. for ( let i = 0; i < members.length; i += 1 ) {
  397. const member = members[ i ];
  398. structString += `${this.tab}\t${member.name}: '${member.type}'`;
  399. if ( i != members.length - 1 ) {
  400. structString += ',\n';
  401. }
  402. }
  403. structString += `\n${this.tab}}, \'${name}\' )`;
  404. return structString;
  405. }
  406. emitOverloadingFunction( nodes ) {
  407. const { name } = nodes[ 0 ];
  408. this.addImport( 'overloadingFn' );
  409. const prefix = this.iife === false ? 'export ' : '';
  410. return `${ prefix }const ${ name } = /*@__PURE__*/ overloadingFn( [ ${ nodes.map( node => node.name + '_' + nodes.indexOf( node ) ).join( ', ' ) } ] );\n`;
  411. }
  412. emitFunction( node ) {
  413. const { name, type } = node;
  414. const params = [];
  415. const inputs = [];
  416. const mutableParams = [];
  417. let hasPointer = false;
  418. for ( const param of node.params ) {
  419. let name = param.name;
  420. if ( param.linker.assignments.length > 0 ) {
  421. name = name + '_immutable';
  422. mutableParams.push( param );
  423. }
  424. if ( param.qualifier ) {
  425. if ( param.qualifier === 'inout' || param.qualifier === 'out' ) {
  426. hasPointer = true;
  427. }
  428. }
  429. inputs.push( param.name + ': \'' + param.type + '\'' );
  430. params.push( name );
  431. }
  432. for ( const param of mutableParams ) {
  433. const mutableParam = new VariableDeclaration( param.type, param.name, new Accessor( param.name + '_immutable' ), null, true );
  434. mutableParam.parent = param.parent; // link to the original node
  435. mutableParam.linker.assignments.push( mutableParam );
  436. mutableParam.needsToVar = true; // force var declaration
  437. node.body.unshift( mutableParam );
  438. }
  439. const paramsStr = params.length > 0 ? ' [ ' + params.join( ', ' ) + ' ] ' : '';
  440. const bodyStr = this.emitBody( node.body );
  441. let fnName = name;
  442. let overloadingNodes = null;
  443. if ( this.overloadings.has( name ) ) {
  444. const overloadings = this.overloadings.get( name );
  445. if ( overloadings.length > 1 ) {
  446. const index = overloadings.indexOf( node );
  447. fnName += '_' + index;
  448. if ( index === overloadings.length - 1 ) {
  449. overloadingNodes = overloadings;
  450. }
  451. }
  452. }
  453. const prefix = this.iife === false ? 'export ' : '';
  454. let funcStr = `${ prefix }const ${ fnName } = /*@__PURE__*/ Fn( (${ paramsStr }) => {
  455. ${ bodyStr }
  456. ${ this.tab }}`;
  457. if ( node.layout !== false && hasPointer === false ) {
  458. const inputsStr = inputs.length > 0 ? inputs.join( ', ' ) + ', ' : '';
  459. funcStr += ', { ' + inputsStr + 'return: \'' + type + '\' }';
  460. }
  461. funcStr += ' );\n';
  462. this.imports.add( 'Fn' );
  463. this.global.add( node.name );
  464. if ( overloadingNodes !== null ) {
  465. funcStr += '\n' + this.emitOverloadingFunction( overloadingNodes );
  466. }
  467. return funcStr;
  468. }
  469. emitComment( statement, body ) {
  470. const index = body.indexOf( statement );
  471. const previous = body[ index - 1 ];
  472. const next = body[ index + 1 ];
  473. let output = '';
  474. if ( previous && isExpression( previous ) ) {
  475. output += '\n';
  476. }
  477. output += this.tab + statement.comment.replace( /\n/g, '\n' + this.tab ) + '\n';
  478. if ( next && isExpression( next ) ) {
  479. output += '\n';
  480. }
  481. return output;
  482. }
  483. emitExtraLine( statement, body ) {
  484. const index = body.indexOf( statement );
  485. const previous = body[ index - 1 ];
  486. if ( previous === undefined ) return '';
  487. if ( statement.isReturn ) return '\n';
  488. const lastExp = isExpression( previous );
  489. const currExp = isExpression( statement );
  490. if ( lastExp !== currExp || ( ! lastExp && ! currExp ) ) return '\n';
  491. return '';
  492. }
  493. emit( ast ) {
  494. let code = '\n';
  495. if ( this.iife ) this.tab += '\t';
  496. const overloadings = this.overloadings;
  497. for ( const statement of ast.body ) {
  498. if ( statement.isFunctionDeclaration ) {
  499. if ( overloadings.has( statement.name ) === false ) {
  500. overloadings.set( statement.name, [] );
  501. }
  502. overloadings.get( statement.name ).push( statement );
  503. }
  504. }
  505. for ( const statement of ast.body ) {
  506. code += this.emitExtraLine( statement, ast.body );
  507. if ( statement.isComment ) {
  508. code += this.emitComment( statement, ast.body );
  509. continue;
  510. }
  511. if ( statement.isFunctionDeclaration ) {
  512. code += this.tab + this.emitFunction( statement );
  513. } else {
  514. code += this.tab + this.emitExpression( statement ) + ';\n';
  515. }
  516. }
  517. const imports = [ ...this.imports ];
  518. const exports = [ ...this.global ];
  519. let header = '// Three.js Transpiler r' + REVISION + '\n\n';
  520. let footer = '';
  521. if ( this.iife ) {
  522. header += '( function ( TSL, uniforms ) {\n\n';
  523. header += imports.length > 0 ? '\tconst { ' + imports.join( ', ' ) + ' } = TSL;\n' : '';
  524. footer += exports.length > 0 ? '\treturn { ' + exports.join( ', ' ) + ' };\n' : '';
  525. footer += '\n} );';
  526. } else {
  527. header += imports.length > 0 ? 'import { ' + imports.join( ', ' ) + ' } from \'three/tsl\';\n' : '';
  528. }
  529. return header + code + footer;
  530. }
  531. }
  532. export default TSLEncoder;
粤ICP备19079148号