feat: initialize project with core dependencies and game entry point
This commit is contained in:
675
node_modules/three/examples/jsm/transpiler/AST.js
generated
vendored
Normal file
675
node_modules/three/examples/jsm/transpiler/AST.js
generated
vendored
Normal file
@@ -0,0 +1,675 @@
|
||||
import { toFloatType } from './TranspilerUtils.js';
|
||||
|
||||
export class ASTNode {
|
||||
|
||||
constructor() {
|
||||
|
||||
this.isASTNode = true;
|
||||
|
||||
this.linker = {
|
||||
reference: null,
|
||||
accesses: [],
|
||||
assignments: []
|
||||
};
|
||||
|
||||
this.parent = null;
|
||||
|
||||
}
|
||||
|
||||
get isNumericExpression() {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
get hasAssignment() {
|
||||
|
||||
if ( this.isAssignment === true ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
if ( this.parent === null ) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return this.parent.hasAssignment;
|
||||
|
||||
}
|
||||
|
||||
getType() {
|
||||
|
||||
return this.type || null;
|
||||
|
||||
}
|
||||
|
||||
getProgram() {
|
||||
|
||||
let current = this;
|
||||
|
||||
while ( current.parent !== null ) {
|
||||
|
||||
current = current.parent;
|
||||
|
||||
}
|
||||
|
||||
return current.isProgram === true ? current : null;
|
||||
|
||||
}
|
||||
|
||||
getParent( parents = [] ) {
|
||||
|
||||
if ( this.parent === null ) {
|
||||
|
||||
return parents;
|
||||
|
||||
}
|
||||
|
||||
parents.push( this.parent );
|
||||
|
||||
return this.parent.getParent( parents );
|
||||
|
||||
}
|
||||
|
||||
initialize() {
|
||||
|
||||
for ( const key in this ) {
|
||||
|
||||
if ( this[ key ] && this[ key ].isASTNode ) {
|
||||
|
||||
this[ key ].parent = this;
|
||||
|
||||
} else if ( Array.isArray( this[ key ] ) ) {
|
||||
|
||||
const array = this[ key ];
|
||||
|
||||
for ( const item of array ) {
|
||||
|
||||
if ( item && item.isASTNode ) {
|
||||
|
||||
item.parent = this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Comment extends ASTNode {
|
||||
|
||||
constructor( comment ) {
|
||||
|
||||
super();
|
||||
|
||||
this.comment = comment;
|
||||
|
||||
this.isComment = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class Program extends ASTNode {
|
||||
|
||||
constructor( body = [] ) {
|
||||
|
||||
super();
|
||||
|
||||
this.body = body;
|
||||
this.structTypes = new Map();
|
||||
|
||||
this.isProgram = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class VariableDeclaration extends ASTNode {
|
||||
|
||||
constructor( type, name, value = null, next = null, immutable = false ) {
|
||||
|
||||
super();
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.next = next;
|
||||
|
||||
this.immutable = immutable;
|
||||
|
||||
this.isVariableDeclaration = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
get isAssignment() {
|
||||
|
||||
return this.value !== null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Uniform extends ASTNode {
|
||||
|
||||
constructor( type, name ) {
|
||||
|
||||
super();
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
|
||||
this.isUniform = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Varying extends ASTNode {
|
||||
|
||||
constructor( type, name ) {
|
||||
|
||||
super();
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
|
||||
this.isVarying = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FunctionParameter extends ASTNode {
|
||||
|
||||
constructor( type, name, qualifier = null, immutable = true ) {
|
||||
|
||||
super();
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.qualifier = qualifier;
|
||||
this.immutable = immutable;
|
||||
|
||||
this.isFunctionParameter = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FunctionDeclaration extends ASTNode {
|
||||
|
||||
constructor( type, name, params = [], body = [] ) {
|
||||
|
||||
super();
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.params = params;
|
||||
this.body = body;
|
||||
|
||||
this.isFunctionDeclaration = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Expression extends ASTNode {
|
||||
|
||||
constructor( expression ) {
|
||||
|
||||
super();
|
||||
|
||||
this.expression = expression;
|
||||
|
||||
this.isExpression = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Ternary extends ASTNode {
|
||||
|
||||
constructor( cond, left, right ) {
|
||||
|
||||
super();
|
||||
|
||||
this.cond = cond;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
|
||||
this.isTernary = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Operator extends ASTNode {
|
||||
|
||||
constructor( type, left, right ) {
|
||||
|
||||
super();
|
||||
|
||||
this.type = type;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
|
||||
this.isOperator = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
get isAssignment() {
|
||||
|
||||
return /^(=|\+=|-=|\*=|\/=|%=|<<=|>>=|>>>=|&=|\^=|\|=)$/.test( this.type );
|
||||
|
||||
}
|
||||
|
||||
get isNumericExpression() {
|
||||
|
||||
if ( this.left.isNumericExpression && this.right.isNumericExpression ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
getType() {
|
||||
|
||||
const leftType = this.left.getType();
|
||||
const rightType = this.right.getType();
|
||||
|
||||
if ( leftType === rightType ) {
|
||||
|
||||
return leftType;
|
||||
|
||||
} else if ( toFloatType( leftType ) === toFloatType( rightType ) ) {
|
||||
|
||||
return toFloatType( leftType );
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class Unary extends ASTNode {
|
||||
|
||||
constructor( type, expression, after = false ) {
|
||||
|
||||
super();
|
||||
|
||||
this.type = type;
|
||||
this.expression = expression;
|
||||
this.after = after;
|
||||
|
||||
this.isUnary = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
get isAssignment() {
|
||||
|
||||
return /^(\+\+|--)$/.test( this.type );
|
||||
|
||||
}
|
||||
|
||||
get isNumericExpression() {
|
||||
|
||||
if ( this.expression.isNumber ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Number extends ASTNode {
|
||||
|
||||
constructor( value, type = 'float' ) {
|
||||
|
||||
super();
|
||||
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
|
||||
this.isNumber = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
get isNumericExpression() {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class String extends ASTNode {
|
||||
|
||||
constructor( value ) {
|
||||
|
||||
super();
|
||||
|
||||
this.value = value;
|
||||
|
||||
this.isString = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class Conditional extends ASTNode {
|
||||
|
||||
constructor( cond = null, body = [] ) {
|
||||
|
||||
super();
|
||||
|
||||
this.cond = cond;
|
||||
this.body = body;
|
||||
this.elseConditional = null;
|
||||
|
||||
this.isConditional = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FunctionCall extends ASTNode {
|
||||
|
||||
constructor( name, params = [] ) {
|
||||
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
this.params = params;
|
||||
|
||||
this.isFunctionCall = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Return extends ASTNode {
|
||||
|
||||
constructor( value ) {
|
||||
|
||||
super();
|
||||
|
||||
this.value = value;
|
||||
|
||||
this.isReturn = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Discard extends ASTNode {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
this.isDiscard = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Continue extends ASTNode {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
this.isContinue = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Break extends ASTNode {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
this.isBreak = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Accessor extends ASTNode {
|
||||
|
||||
constructor( property ) {
|
||||
|
||||
super();
|
||||
|
||||
this.property = property;
|
||||
|
||||
this.isAccessor = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
getType() {
|
||||
|
||||
if ( this.linker.reference ) {
|
||||
|
||||
return this.linker.reference.getType();
|
||||
|
||||
}
|
||||
|
||||
return super.getType();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class StaticElement extends ASTNode {
|
||||
|
||||
constructor( value ) {
|
||||
|
||||
super();
|
||||
|
||||
this.value = value;
|
||||
|
||||
this.isStaticElement = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class DynamicElement extends ASTNode {
|
||||
|
||||
constructor( value ) {
|
||||
|
||||
super();
|
||||
|
||||
this.value = value;
|
||||
|
||||
this.isDynamicElement = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AccessorElements extends ASTNode {
|
||||
|
||||
constructor( object, elements = [] ) {
|
||||
|
||||
super();
|
||||
|
||||
this.object = object;
|
||||
this.elements = elements;
|
||||
|
||||
this.isAccessorElements = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class For extends ASTNode {
|
||||
|
||||
constructor( initialization, condition, afterthought, body = [] ) {
|
||||
|
||||
super();
|
||||
|
||||
this.initialization = initialization;
|
||||
this.condition = condition;
|
||||
this.afterthought = afterthought;
|
||||
this.body = body;
|
||||
|
||||
this.isFor = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class While extends ASTNode {
|
||||
|
||||
constructor( condition, body = [] ) {
|
||||
|
||||
super();
|
||||
|
||||
this.condition = condition;
|
||||
this.body = body;
|
||||
|
||||
this.isWhile = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class Switch extends ASTNode {
|
||||
|
||||
constructor( discriminant, cases ) {
|
||||
|
||||
super();
|
||||
|
||||
this.discriminant = discriminant;
|
||||
this.cases = cases;
|
||||
|
||||
this.isSwitch = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class SwitchCase extends ASTNode {
|
||||
|
||||
constructor( body, conditions = null ) {
|
||||
|
||||
super();
|
||||
|
||||
this.body = body;
|
||||
this.conditions = conditions;
|
||||
|
||||
this.isDefault = conditions === null ? true : false;
|
||||
this.isSwitchCase = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// helper class for StructDefinition
|
||||
export class StructMember {
|
||||
|
||||
constructor( type, name ) {
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.isStructMember = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class StructDefinition extends ASTNode {
|
||||
|
||||
constructor( name, members = [] ) {
|
||||
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
this.members = members;
|
||||
this.isStructDefinition = true;
|
||||
|
||||
this.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
1228
node_modules/three/examples/jsm/transpiler/GLSLDecoder.js
generated
vendored
Normal file
1228
node_modules/three/examples/jsm/transpiler/GLSLDecoder.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
327
node_modules/three/examples/jsm/transpiler/Linker.js
generated
vendored
Normal file
327
node_modules/three/examples/jsm/transpiler/Linker.js
generated
vendored
Normal file
@@ -0,0 +1,327 @@
|
||||
class Block {
|
||||
|
||||
constructor( node, parent = null ) {
|
||||
|
||||
this.node = node;
|
||||
this.parent = parent;
|
||||
|
||||
this.properties = {};
|
||||
|
||||
}
|
||||
|
||||
setProperty( name, value ) {
|
||||
|
||||
this.properties[ name ] = value;
|
||||
|
||||
}
|
||||
|
||||
getProperty( name ) {
|
||||
|
||||
let value = this.properties[ name ];
|
||||
|
||||
if ( value === undefined && this.parent !== null ) {
|
||||
|
||||
value = this.parent.getProperty( name );
|
||||
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Linker {
|
||||
|
||||
constructor() {
|
||||
|
||||
this.block = null;
|
||||
|
||||
}
|
||||
|
||||
addBlock( node ) {
|
||||
|
||||
this.block = new Block( node, this.block );
|
||||
|
||||
}
|
||||
|
||||
removeBlock( node ) {
|
||||
|
||||
if ( this.block === null || this.block.node !== node ) {
|
||||
|
||||
throw new Error( 'No block to remove or block mismatch.' );
|
||||
|
||||
}
|
||||
|
||||
this.block = this.block.parent;
|
||||
|
||||
}
|
||||
|
||||
processVariables( node ) {
|
||||
|
||||
this.block.setProperty( node.name, node );
|
||||
|
||||
if ( node.value ) {
|
||||
|
||||
this.processExpression( node.value );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
processUniform( node ) {
|
||||
|
||||
this.block.setProperty( node.name, node );
|
||||
|
||||
}
|
||||
|
||||
processVarying( node ) {
|
||||
|
||||
this.block.setProperty( node.name, node );
|
||||
|
||||
}
|
||||
|
||||
evalProperty( node ) {
|
||||
|
||||
let property = '';
|
||||
|
||||
if ( node.isAccessor ) {
|
||||
|
||||
property += node.property;
|
||||
|
||||
}
|
||||
|
||||
return property;
|
||||
|
||||
}
|
||||
|
||||
processExpression( node ) {
|
||||
|
||||
if ( node.isAccessor ) {
|
||||
|
||||
const property = this.block.getProperty( this.evalProperty( node ) );
|
||||
|
||||
if ( property ) {
|
||||
|
||||
node.linker.reference = property;
|
||||
|
||||
property.linker.accesses.push( node );
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isNumber || node.isString ) {
|
||||
|
||||
// Process primitive values
|
||||
|
||||
} else if ( node.isOperator ) {
|
||||
|
||||
this.processExpression( node.left );
|
||||
this.processExpression( node.right );
|
||||
|
||||
if ( node.isAssignment ) {
|
||||
|
||||
const property = this.block.getProperty( this.evalProperty( node.left ) );
|
||||
|
||||
if ( property ) {
|
||||
|
||||
property.linker.assignments.push( node );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isFunctionCall ) {
|
||||
|
||||
for ( const param of node.params ) {
|
||||
|
||||
this.processExpression( param );
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isReturn ) {
|
||||
|
||||
if ( node.value ) this.processExpression( node.value );
|
||||
|
||||
} else if ( node.isDiscard || node.isBreak || node.isContinue ) {
|
||||
|
||||
// Process control flow
|
||||
|
||||
} else if ( node.isAccessorElements ) {
|
||||
|
||||
this.processExpression( node.object );
|
||||
|
||||
for ( const element of node.elements ) {
|
||||
|
||||
this.processExpression( element.value );
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isDynamicElement || node.isStaticElement ) {
|
||||
|
||||
this.processExpression( node.value );
|
||||
|
||||
} else if ( node.isFor || node.isWhile ) {
|
||||
|
||||
this.processForWhile( node );
|
||||
|
||||
} else if ( node.isSwitch ) {
|
||||
|
||||
this.processSwitch( node );
|
||||
|
||||
} else if ( node.isVariableDeclaration ) {
|
||||
|
||||
this.processVariables( node );
|
||||
|
||||
} else if ( node.isUniform ) {
|
||||
|
||||
this.processUniform( node );
|
||||
|
||||
} else if ( node.isVarying ) {
|
||||
|
||||
this.processVarying( node );
|
||||
|
||||
} else if ( node.isTernary ) {
|
||||
|
||||
this.processExpression( node.cond );
|
||||
this.processExpression( node.left );
|
||||
this.processExpression( node.right );
|
||||
|
||||
} else if ( node.isConditional ) {
|
||||
|
||||
this.processConditional( node );
|
||||
|
||||
} else if ( node.isUnary ) {
|
||||
|
||||
this.processExpression( node.expression );
|
||||
|
||||
if ( node.isAssignment ) {
|
||||
|
||||
if ( node.parent.hasAssignment !== true ) {
|
||||
|
||||
// optimize increment/decrement operator
|
||||
// to avoid creating a new variable
|
||||
|
||||
node.after = false;
|
||||
|
||||
}
|
||||
|
||||
const property = this.block.getProperty( this.evalProperty( node.expression ) );
|
||||
|
||||
if ( property ) {
|
||||
|
||||
property.linker.assignments.push( node );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
processBody( body ) {
|
||||
|
||||
for ( const statement of body ) {
|
||||
|
||||
this.processExpression( statement );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
processConditional( node ) {
|
||||
|
||||
this.processExpression( node.cond );
|
||||
this.processBody( node.body );
|
||||
|
||||
let current = node;
|
||||
|
||||
while ( current.elseConditional ) {
|
||||
|
||||
if ( current.elseConditional.cond ) {
|
||||
|
||||
this.processExpression( current.elseConditional.cond );
|
||||
|
||||
}
|
||||
|
||||
this.processBody( current.elseConditional.body );
|
||||
|
||||
current = current.elseConditional;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
processForWhile( node ) {
|
||||
|
||||
if ( node.initialization ) this.processExpression( node.initialization );
|
||||
if ( node.condition ) this.processExpression( node.condition );
|
||||
if ( node.afterthought ) this.processExpression( node.afterthought );
|
||||
|
||||
this.processBody( node.body );
|
||||
|
||||
}
|
||||
|
||||
processSwitch( switchNode ) {
|
||||
|
||||
this.processExpression( switchNode.discriminant );
|
||||
|
||||
for ( const switchCase of switchNode.cases ) {
|
||||
|
||||
if ( switchCase.isDefault !== true ) {
|
||||
|
||||
for ( const condition of switchCase.conditions ) {
|
||||
|
||||
this.processExpression( condition );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.processBody( switchCase.body );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
processFunction( node ) {
|
||||
|
||||
this.addBlock( node );
|
||||
|
||||
for ( const param of node.params ) {
|
||||
|
||||
this.block.setProperty( param.name, param );
|
||||
|
||||
}
|
||||
|
||||
this.processBody( node.body );
|
||||
|
||||
this.removeBlock( node );
|
||||
|
||||
}
|
||||
|
||||
process( ast ) {
|
||||
|
||||
this.addBlock( ast );
|
||||
|
||||
for ( const statement of ast.body ) {
|
||||
|
||||
if ( statement.isFunctionDeclaration ) {
|
||||
|
||||
this.processFunction( statement );
|
||||
|
||||
} else {
|
||||
|
||||
this.processExpression( statement );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.removeBlock( ast );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Linker;
|
||||
51
node_modules/three/examples/jsm/transpiler/ShaderToyDecoder.js
generated
vendored
Normal file
51
node_modules/three/examples/jsm/transpiler/ShaderToyDecoder.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Return, VariableDeclaration, Accessor } from './AST.js';
|
||||
import GLSLDecoder from './GLSLDecoder.js';
|
||||
|
||||
class ShaderToyDecoder extends GLSLDecoder {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
this.addPolyfill( 'iTime', 'float iTime = time;' );
|
||||
this.addPolyfill( 'iResolution', 'vec2 iResolution = screenSize;' );
|
||||
this.addPolyfill( 'fragCoord', 'vec3 fragCoord = vec3( screenCoordinate.x, screenSize.y - screenCoordinate.y, screenCoordinate.z );' );
|
||||
|
||||
}
|
||||
|
||||
parseFunction() {
|
||||
|
||||
const node = super.parseFunction();
|
||||
|
||||
if ( node.name === 'mainImage' ) {
|
||||
|
||||
node.params = []; // remove default parameters
|
||||
node.type = 'vec4';
|
||||
node.layout = false; // for now
|
||||
|
||||
const fragColor = new Accessor( 'fragColor' );
|
||||
|
||||
for ( const subNode of node.body ) {
|
||||
|
||||
if ( subNode.isReturn ) {
|
||||
|
||||
subNode.value = fragColor;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
node.body.unshift( new VariableDeclaration( 'vec4', 'fragColor' ) );
|
||||
node.body.push( new Return( fragColor ) );
|
||||
|
||||
node.initialize();
|
||||
|
||||
}
|
||||
|
||||
return node;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ShaderToyDecoder;
|
||||
983
node_modules/three/examples/jsm/transpiler/TSLEncoder.js
generated
vendored
Normal file
983
node_modules/three/examples/jsm/transpiler/TSLEncoder.js
generated
vendored
Normal file
@@ -0,0 +1,983 @@
|
||||
import { REVISION } from 'three/webgpu';
|
||||
import * as TSL from 'three/tsl';
|
||||
|
||||
import { VariableDeclaration, Accessor } from './AST.js';
|
||||
import { isExpression, isPrimitive } from './TranspilerUtils.js';
|
||||
|
||||
const opLib = {
|
||||
'=': 'assign',
|
||||
'+': 'add',
|
||||
'-': 'sub',
|
||||
'*': 'mul',
|
||||
'/': 'div',
|
||||
'%': 'remainder',
|
||||
'<': 'lessThan',
|
||||
'>': 'greaterThan',
|
||||
'<=': 'lessThanEqual',
|
||||
'>=': 'greaterThanEqual',
|
||||
'==': 'equal',
|
||||
'!=': 'notEqual',
|
||||
'&&': 'and',
|
||||
'||': 'or',
|
||||
'^^': 'xor',
|
||||
'&': 'bitAnd',
|
||||
'|': 'bitOr',
|
||||
'^': 'bitXor',
|
||||
'<<': 'shiftLeft',
|
||||
'>>': 'shiftRight',
|
||||
'+=': 'addAssign',
|
||||
'-=': 'subAssign',
|
||||
'*=': 'mulAssign',
|
||||
'/=': 'divAssign',
|
||||
'%=': 'remainderAssign',
|
||||
'^=': 'bitXorAssign',
|
||||
'&=': 'bitAndAssign',
|
||||
'|=': 'bitOrAssign',
|
||||
'<<=': 'shiftLeftAssign',
|
||||
'>>=': 'shiftRightAssign'
|
||||
};
|
||||
|
||||
const unaryLib = {
|
||||
'+': '', // positive
|
||||
'-': 'negate',
|
||||
'~': 'bitNot',
|
||||
'!': 'not',
|
||||
'++': 'increment', // incrementBefore
|
||||
'--': 'decrement' // decrementBefore
|
||||
};
|
||||
|
||||
const textureLookupFunctions = [ 'texture', 'texture2D', 'texture3D', 'textureCube', 'textureLod', 'texelFetch', 'textureGrad' ];
|
||||
|
||||
class TSLEncoder {
|
||||
|
||||
constructor() {
|
||||
|
||||
this.tab = '';
|
||||
this.imports = new Set();
|
||||
this.global = new Set();
|
||||
this.overloadings = new Map();
|
||||
this.iife = false;
|
||||
this.reference = false;
|
||||
|
||||
this.block = null;
|
||||
|
||||
}
|
||||
|
||||
addImport( name ) {
|
||||
|
||||
// import only if it's a node
|
||||
|
||||
name = name.split( '.' )[ 0 ];
|
||||
|
||||
if ( TSL[ name ] !== undefined && this.global.has( name ) === false ) {
|
||||
|
||||
this.imports.add( name );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
emitUniform( node ) {
|
||||
|
||||
let code = `const ${ node.name } = `;
|
||||
this.global.add( node.name );
|
||||
|
||||
if ( this.reference === true ) {
|
||||
|
||||
this.addImport( 'reference' );
|
||||
|
||||
//code += `reference( '${ node.name }', '${ node.type }', uniforms )`;
|
||||
|
||||
// legacy
|
||||
code += `reference( 'value', '${ node.type }', uniforms[ '${ node.name }' ] )`;
|
||||
|
||||
} else {
|
||||
|
||||
if ( node.type === 'texture' ) {
|
||||
|
||||
this.addImport( 'texture' );
|
||||
|
||||
code += 'texture( /* <THREE.Texture> */ )';
|
||||
|
||||
} else if ( node.type === 'cubeTexture' ) {
|
||||
|
||||
this.addImport( 'cubeTexture' );
|
||||
|
||||
code += 'cubeTexture( /* <THREE.CubeTexture> */ )';
|
||||
|
||||
} else if ( node.type === 'texture3D' ) {
|
||||
|
||||
this.addImport( 'texture3D' );
|
||||
|
||||
code += 'texture3D( /* <THREE.Data3DTexture> */ )';
|
||||
|
||||
} else {
|
||||
|
||||
// default uniform
|
||||
|
||||
this.addImport( 'uniform' );
|
||||
|
||||
code += `uniform( '${ node.type }' )`;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return code;
|
||||
|
||||
}
|
||||
|
||||
emitExpression( node, output = null ) {
|
||||
|
||||
let code;
|
||||
|
||||
if ( node.isAccessor ) {
|
||||
|
||||
if ( node.linker.reference === null ) {
|
||||
|
||||
this.addImport( node.property );
|
||||
|
||||
}
|
||||
|
||||
code = node.property;
|
||||
|
||||
} else if ( node.isNumber ) {
|
||||
|
||||
code = node.value;
|
||||
|
||||
} else if ( node.isString ) {
|
||||
|
||||
code = '\'' + node.value + '\'';
|
||||
|
||||
} else if ( node.isOperator ) {
|
||||
|
||||
const opFn = opLib[ node.type ] || node.type;
|
||||
|
||||
const left = this.emitExpression( node.left, output );
|
||||
const right = this.emitExpression( node.right, output );
|
||||
|
||||
if ( node.isNumericExpression ) {
|
||||
|
||||
return left + ' ' + node.type + ' ' + right;
|
||||
|
||||
}
|
||||
|
||||
if ( isPrimitive( left ) ) {
|
||||
|
||||
code = opFn + '( ' + left + ', ' + right + ' )';
|
||||
|
||||
this.addImport( opFn );
|
||||
|
||||
} else if ( opFn === '.' ) {
|
||||
|
||||
code = left + opFn + right;
|
||||
|
||||
} else {
|
||||
|
||||
code = left + '.' + opFn + '( ' + right + ' )';
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isFunctionCall ) {
|
||||
|
||||
const params = [];
|
||||
|
||||
for ( const parameter of node.params ) {
|
||||
|
||||
params.push( this.emitExpression( parameter ) );
|
||||
|
||||
}
|
||||
|
||||
// handle texture lookup function calls in separate branch
|
||||
|
||||
if ( textureLookupFunctions.includes( node.name ) ) {
|
||||
|
||||
code = `${ params[ 0 ] }.sample( ${ params[ 1 ] } )`;
|
||||
|
||||
if ( node.name === 'texture' || node.name === 'texture2D' || node.name === 'texture3D' || node.name === 'textureCube' ) {
|
||||
|
||||
if ( params.length === 3 ) {
|
||||
|
||||
code += `.bias( ${ params[ 2 ] } )`;
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.name === 'textureLod' ) {
|
||||
|
||||
code += `.level( ${ params[ 2 ] } )`;
|
||||
|
||||
} else if ( node.name === 'textureGrad' ) {
|
||||
|
||||
code += `.grad( ${ params[ 2 ] }, ${ params[ 3 ] } )`;
|
||||
|
||||
} else if ( node.name === 'texelFetch' ) {
|
||||
|
||||
code += '.setSampler( false )';
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
this.addImport( node.name );
|
||||
|
||||
const paramsStr = params.length > 0 ? ' ' + params.join( ', ' ) + ' ' : '';
|
||||
|
||||
code = `${ node.name }(${ paramsStr })`;
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isReturn ) {
|
||||
|
||||
code = 'return';
|
||||
|
||||
if ( node.value ) {
|
||||
|
||||
code += ' ' + this.emitExpression( node.value );
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isDiscard ) {
|
||||
|
||||
this.addImport( 'Discard' );
|
||||
|
||||
code = 'Discard()';
|
||||
|
||||
} else if ( node.isBreak ) {
|
||||
|
||||
this.addImport( 'Break' );
|
||||
|
||||
code = 'Break()';
|
||||
|
||||
} else if ( node.isContinue ) {
|
||||
|
||||
this.addImport( 'Continue' );
|
||||
|
||||
code = 'Continue()';
|
||||
|
||||
} else if ( node.isAccessorElements ) {
|
||||
|
||||
code = this.emitExpression( node.object );
|
||||
|
||||
for ( const element of node.elements ) {
|
||||
|
||||
if ( element.isStaticElement ) {
|
||||
|
||||
code += '.' + this.emitExpression( element.value );
|
||||
|
||||
} else if ( element.isDynamicElement ) {
|
||||
|
||||
const value = this.emitExpression( element.value );
|
||||
|
||||
if ( isPrimitive( value ) ) {
|
||||
|
||||
code += `[ ${ value } ]`;
|
||||
|
||||
} else {
|
||||
|
||||
code += `.element( ${ value } )`;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isDynamicElement ) {
|
||||
|
||||
code = this.emitExpression( node.value );
|
||||
|
||||
} else if ( node.isStaticElement ) {
|
||||
|
||||
code = this.emitExpression( node.value );
|
||||
|
||||
} else if ( node.isFor ) {
|
||||
|
||||
code = this.emitFor( node );
|
||||
|
||||
} else if ( node.isWhile ) {
|
||||
|
||||
code = this.emitWhile( node );
|
||||
|
||||
} else if ( node.isSwitch ) {
|
||||
|
||||
code = this.emitSwitch( node );
|
||||
|
||||
} else if ( node.isVariableDeclaration ) {
|
||||
|
||||
code = this.emitVariables( node );
|
||||
|
||||
} else if ( node.isUniform ) {
|
||||
|
||||
code = this.emitUniform( node );
|
||||
|
||||
} else if ( node.isVarying ) {
|
||||
|
||||
code = this.emitVarying( node );
|
||||
|
||||
} else if ( node.isStructDefinition ) {
|
||||
|
||||
code = this.emitStructDefinition( node );
|
||||
|
||||
} else if ( node.isTernary ) {
|
||||
|
||||
code = this.emitTernary( node );
|
||||
|
||||
} else if ( node.isConditional ) {
|
||||
|
||||
code = this.emitConditional( node );
|
||||
|
||||
} else if ( node.isUnary && node.expression.isNumber && node.type === '-' ) {
|
||||
|
||||
code = '- ' + node.expression.value;
|
||||
|
||||
if ( node.expression.type !== 'float' ) {
|
||||
|
||||
code = node.expression.type + '( ' + code + ' )';
|
||||
|
||||
this.addImport( node.expression.type );
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isUnary ) {
|
||||
|
||||
let type = unaryLib[ node.type ];
|
||||
|
||||
if ( node.hasAssignment ) {
|
||||
|
||||
if ( node.after === false && ( node.type === '++' || node.type === '--' ) ) {
|
||||
|
||||
type += 'Before';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const exp = this.emitExpression( node.expression );
|
||||
|
||||
if ( isPrimitive( exp ) ) {
|
||||
|
||||
this.addImport( type );
|
||||
|
||||
code = type + '( ' + exp + ' )';
|
||||
|
||||
} else {
|
||||
|
||||
code = exp + '.' + type + '()';
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
console.warn( 'Unknown node type', node );
|
||||
|
||||
}
|
||||
|
||||
if ( ! code ) code = '/* unknown statement */';
|
||||
|
||||
return code;
|
||||
|
||||
}
|
||||
|
||||
emitBody( body ) {
|
||||
|
||||
let code = '';
|
||||
|
||||
this.tab += '\t';
|
||||
|
||||
for ( const statement of body ) {
|
||||
|
||||
code += this.emitExtraLine( statement, body );
|
||||
|
||||
if ( statement.isComment ) {
|
||||
|
||||
code += this.emitComment( statement, body );
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
if ( this.block && this.block.isSwitchCase ) {
|
||||
|
||||
if ( statement.isBreak ) continue; // skip break statements in switch cases
|
||||
|
||||
}
|
||||
|
||||
code += this.tab + this.emitExpression( statement );
|
||||
|
||||
if ( code.slice( - 1 ) !== '}' ) code += ';';
|
||||
|
||||
code += '\n';
|
||||
|
||||
}
|
||||
|
||||
code = code.slice( 0, - 1 ); // remove the last extra line
|
||||
|
||||
this.tab = this.tab.slice( 0, - 1 );
|
||||
|
||||
return code;
|
||||
|
||||
|
||||
}
|
||||
|
||||
emitTernary( node ) {
|
||||
|
||||
const condStr = this.emitExpression( node.cond );
|
||||
const leftStr = this.emitExpression( node.left );
|
||||
const rightStr = this.emitExpression( node.right );
|
||||
|
||||
this.addImport( 'select' );
|
||||
|
||||
return `select( ${ condStr }, ${ leftStr }, ${ rightStr } )`;
|
||||
|
||||
}
|
||||
|
||||
emitConditional( node ) {
|
||||
|
||||
const condStr = this.emitExpression( node.cond );
|
||||
const bodyStr = this.emitBody( node.body );
|
||||
|
||||
let ifStr = `If( ${ condStr }, () => {
|
||||
|
||||
${ bodyStr }
|
||||
|
||||
${ this.tab }} )`;
|
||||
|
||||
let current = node;
|
||||
|
||||
while ( current.elseConditional ) {
|
||||
|
||||
const elseBodyStr = this.emitBody( current.elseConditional.body );
|
||||
|
||||
if ( current.elseConditional.cond ) {
|
||||
|
||||
const elseCondStr = this.emitExpression( current.elseConditional.cond );
|
||||
|
||||
ifStr += `.ElseIf( ${ elseCondStr }, () => {
|
||||
|
||||
${ elseBodyStr }
|
||||
|
||||
${ this.tab }} )`;
|
||||
|
||||
} else {
|
||||
|
||||
ifStr += `.Else( () => {
|
||||
|
||||
${ elseBodyStr }
|
||||
|
||||
${ this.tab }} )`;
|
||||
|
||||
}
|
||||
|
||||
current = current.elseConditional;
|
||||
|
||||
|
||||
}
|
||||
|
||||
this.imports.add( 'If' );
|
||||
|
||||
return ifStr;
|
||||
|
||||
}
|
||||
|
||||
emitLoop( node ) {
|
||||
|
||||
const start = this.emitExpression( node.initialization.value );
|
||||
const end = this.emitExpression( node.condition.right );
|
||||
|
||||
const name = node.initialization.name;
|
||||
const type = node.initialization.type;
|
||||
const condition = node.condition.type;
|
||||
|
||||
const nameParam = name !== 'i' ? `, name: '${ name }'` : '';
|
||||
const typeParam = type !== 'int' ? `, type: '${ type }'` : '';
|
||||
const conditionParam = condition !== '<' ? `, condition: '${ condition }'` : '';
|
||||
|
||||
let updateParam = '';
|
||||
|
||||
if ( node.afterthought.isUnary ) {
|
||||
|
||||
if ( node.afterthought.type !== '++' ) {
|
||||
|
||||
updateParam = `, update: '${ node.afterthought.type }'`;
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.afterthought.isOperator ) {
|
||||
|
||||
if ( node.afterthought.right.isAccessor || node.afterthought.right.isNumber ) {
|
||||
|
||||
updateParam = `, update: ${ this.emitExpression( node.afterthought.right ) }`;
|
||||
|
||||
} else {
|
||||
|
||||
updateParam = `, update: ( { i } ) => ${ this.emitExpression( node.afterthought ) }`;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let loopStr = `Loop( { start: ${ start }, end: ${ end + nameParam + typeParam + conditionParam + updateParam } }, ( { ${ name } } ) => {\n\n`;
|
||||
|
||||
loopStr += this.emitBody( node.body ) + '\n\n';
|
||||
|
||||
loopStr += this.tab + '} )';
|
||||
|
||||
this.imports.add( 'Loop' );
|
||||
|
||||
return loopStr;
|
||||
|
||||
}
|
||||
|
||||
|
||||
emitSwitch( switchNode ) {
|
||||
|
||||
const discriminantString = this.emitExpression( switchNode.discriminant );
|
||||
|
||||
this.tab += '\t';
|
||||
|
||||
let switchString = `Switch( ${ discriminantString } )\n${ this.tab }`;
|
||||
|
||||
const previousBlock = this.block;
|
||||
|
||||
for ( const switchCase of switchNode.cases ) {
|
||||
|
||||
this.block = switchCase;
|
||||
|
||||
let caseBodyString;
|
||||
|
||||
if ( ! switchCase.isDefault ) {
|
||||
|
||||
const caseConditions = [ ];
|
||||
|
||||
for ( const condition of switchCase.conditions ) {
|
||||
|
||||
caseConditions.push( this.emitExpression( condition ) );
|
||||
|
||||
}
|
||||
|
||||
caseBodyString = this.emitBody( switchCase.body );
|
||||
|
||||
switchString += `.Case( ${ caseConditions.join( ', ' ) }, `;
|
||||
|
||||
} else {
|
||||
|
||||
caseBodyString = this.emitBody( switchCase.body );
|
||||
|
||||
switchString += '.Default( ';
|
||||
|
||||
}
|
||||
|
||||
switchString += `() => {
|
||||
|
||||
${ caseBodyString }
|
||||
|
||||
${ this.tab }} )`;
|
||||
|
||||
}
|
||||
|
||||
this.block = previousBlock;
|
||||
|
||||
this.tab = this.tab.slice( 0, - 1 );
|
||||
|
||||
this.imports.add( 'Switch' );
|
||||
|
||||
return switchString;
|
||||
|
||||
}
|
||||
|
||||
emitFor( node ) {
|
||||
|
||||
const { initialization, condition, afterthought } = node;
|
||||
|
||||
if ( ( initialization && initialization.isVariableDeclaration && initialization.next === null ) &&
|
||||
( condition && condition.left.isAccessor && condition.left.property === initialization.name ) &&
|
||||
( afterthought && (
|
||||
( afterthought.isUnary && ( initialization.name === afterthought.expression.property ) ) ||
|
||||
( afterthought.isOperator && ( initialization.name === afterthought.left.property ) )
|
||||
) )
|
||||
) {
|
||||
|
||||
return this.emitLoop( node );
|
||||
|
||||
}
|
||||
|
||||
return this.emitForWhile( node );
|
||||
|
||||
}
|
||||
|
||||
emitForWhile( node ) {
|
||||
|
||||
const initialization = this.emitExpression( node.initialization );
|
||||
const condition = this.emitExpression( node.condition );
|
||||
const afterthought = this.emitExpression( node.afterthought );
|
||||
|
||||
this.tab += '\t';
|
||||
|
||||
let forStr = '{\n\n' + this.tab + initialization + ';\n\n';
|
||||
forStr += `${ this.tab }Loop( ${ condition }, () => {\n\n`;
|
||||
|
||||
forStr += this.emitBody( node.body ) + '\n\n';
|
||||
|
||||
forStr += this.tab + '\t' + afterthought + ';\n\n';
|
||||
|
||||
forStr += this.tab + '} )\n\n';
|
||||
|
||||
this.tab = this.tab.slice( 0, - 1 );
|
||||
|
||||
forStr += this.tab + '}';
|
||||
|
||||
this.imports.add( 'Loop' );
|
||||
|
||||
return forStr;
|
||||
|
||||
}
|
||||
|
||||
emitWhile( node ) {
|
||||
|
||||
const condition = this.emitExpression( node.condition );
|
||||
|
||||
let whileStr = `Loop( ${ condition }, () => {\n\n`;
|
||||
|
||||
whileStr += this.emitBody( node.body ) + '\n\n';
|
||||
|
||||
whileStr += this.tab + '} )';
|
||||
|
||||
this.imports.add( 'Loop' );
|
||||
|
||||
return whileStr;
|
||||
|
||||
}
|
||||
|
||||
emitVariables( node, isRoot = true ) {
|
||||
|
||||
const { name, type, value, next } = node;
|
||||
|
||||
let varStr = isRoot ? 'const ' : '';
|
||||
varStr += name;
|
||||
|
||||
if ( value ) {
|
||||
|
||||
let valueStr = this.emitExpression( value );
|
||||
|
||||
if ( value.isNumericExpression ) {
|
||||
|
||||
// convert JS primitive to node
|
||||
|
||||
valueStr = `${ type }( ${ valueStr } )`;
|
||||
|
||||
this.addImport( type );
|
||||
|
||||
}
|
||||
|
||||
varStr += ' = ' + valueStr;
|
||||
|
||||
} else {
|
||||
|
||||
const program = node.getProgram();
|
||||
|
||||
if ( program.structTypes.has( type ) ) {
|
||||
|
||||
varStr += ` = ${ type }()`;
|
||||
|
||||
} else {
|
||||
|
||||
varStr += ` = property( '${ type }' )`;
|
||||
|
||||
this.addImport( 'property' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( next ) {
|
||||
|
||||
varStr += ', ' + this.emitVariables( next, false );
|
||||
|
||||
}
|
||||
|
||||
if ( node.needsToVar ) {
|
||||
|
||||
varStr = varStr + '.toVar()';
|
||||
|
||||
}
|
||||
|
||||
return varStr;
|
||||
|
||||
}
|
||||
|
||||
emitVarying( node ) {
|
||||
|
||||
const { name, type } = node;
|
||||
|
||||
this.addImport( 'varying' );
|
||||
this.addImport( type );
|
||||
|
||||
return `const ${ name } = varying( ${ type }(), '${ name }' )`;
|
||||
|
||||
}
|
||||
|
||||
emitStructDefinition( node ) {
|
||||
|
||||
const { name, members } = node;
|
||||
|
||||
this.addImport( 'struct' );
|
||||
|
||||
let structString = `const ${ name } = struct( {\n`;
|
||||
|
||||
for ( let i = 0; i < members.length; i += 1 ) {
|
||||
|
||||
const member = members[ i ];
|
||||
|
||||
structString += `${this.tab}\t${member.name}: '${member.type}'`;
|
||||
|
||||
if ( i != members.length - 1 ) {
|
||||
|
||||
structString += ',\n';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
structString += `\n${this.tab}}, \'${name}\' )`;
|
||||
|
||||
return structString;
|
||||
|
||||
}
|
||||
|
||||
emitOverloadingFunction( nodes ) {
|
||||
|
||||
const { name } = nodes[ 0 ];
|
||||
|
||||
this.addImport( 'overloadingFn' );
|
||||
|
||||
const prefix = this.iife === false ? 'export ' : '';
|
||||
|
||||
return `${ prefix }const ${ name } = /*@__PURE__*/ overloadingFn( [ ${ nodes.map( node => node.name + '_' + nodes.indexOf( node ) ).join( ', ' ) } ] );\n`;
|
||||
|
||||
}
|
||||
|
||||
emitFunction( node ) {
|
||||
|
||||
const { name, type } = node;
|
||||
|
||||
const params = [];
|
||||
const inputs = [];
|
||||
const mutableParams = [];
|
||||
|
||||
let hasPointer = false;
|
||||
|
||||
for ( const param of node.params ) {
|
||||
|
||||
let name = param.name;
|
||||
|
||||
if ( param.linker.assignments.length > 0 ) {
|
||||
|
||||
name = name + '_immutable';
|
||||
|
||||
mutableParams.push( param );
|
||||
|
||||
}
|
||||
|
||||
if ( param.qualifier ) {
|
||||
|
||||
if ( param.qualifier === 'inout' || param.qualifier === 'out' ) {
|
||||
|
||||
hasPointer = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inputs.push( param.name + ': \'' + param.type + '\'' );
|
||||
params.push( name );
|
||||
|
||||
}
|
||||
|
||||
for ( const param of mutableParams ) {
|
||||
|
||||
const mutableParam = new VariableDeclaration( param.type, param.name, new Accessor( param.name + '_immutable' ), null, true );
|
||||
mutableParam.parent = param.parent; // link to the original node
|
||||
mutableParam.linker.assignments.push( mutableParam );
|
||||
mutableParam.needsToVar = true; // force var declaration
|
||||
|
||||
node.body.unshift( mutableParam );
|
||||
|
||||
}
|
||||
|
||||
const paramsStr = params.length > 0 ? ' [ ' + params.join( ', ' ) + ' ] ' : '';
|
||||
const bodyStr = this.emitBody( node.body );
|
||||
|
||||
let fnName = name;
|
||||
let overloadingNodes = null;
|
||||
|
||||
if ( this.overloadings.has( name ) ) {
|
||||
|
||||
const overloadings = this.overloadings.get( name );
|
||||
|
||||
if ( overloadings.length > 1 ) {
|
||||
|
||||
const index = overloadings.indexOf( node );
|
||||
|
||||
fnName += '_' + index;
|
||||
|
||||
if ( index === overloadings.length - 1 ) {
|
||||
|
||||
overloadingNodes = overloadings;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const prefix = this.iife === false ? 'export ' : '';
|
||||
|
||||
let funcStr = `${ prefix }const ${ fnName } = /*@__PURE__*/ Fn( (${ paramsStr }) => {
|
||||
|
||||
${ bodyStr }
|
||||
|
||||
${ this.tab }}`;
|
||||
|
||||
if ( node.layout !== false && hasPointer === false ) {
|
||||
|
||||
const inputsStr = inputs.length > 0 ? inputs.join( ', ' ) + ', ' : '';
|
||||
funcStr += ', { ' + inputsStr + 'return: \'' + type + '\' }';
|
||||
|
||||
}
|
||||
|
||||
funcStr += ' );\n';
|
||||
|
||||
this.imports.add( 'Fn' );
|
||||
|
||||
this.global.add( node.name );
|
||||
|
||||
if ( overloadingNodes !== null ) {
|
||||
|
||||
funcStr += '\n' + this.emitOverloadingFunction( overloadingNodes );
|
||||
|
||||
}
|
||||
|
||||
return funcStr;
|
||||
|
||||
}
|
||||
|
||||
emitComment( statement, body ) {
|
||||
|
||||
const index = body.indexOf( statement );
|
||||
const previous = body[ index - 1 ];
|
||||
const next = body[ index + 1 ];
|
||||
|
||||
let output = '';
|
||||
|
||||
if ( previous && isExpression( previous ) ) {
|
||||
|
||||
output += '\n';
|
||||
|
||||
}
|
||||
|
||||
output += this.tab + statement.comment.replace( /\n/g, '\n' + this.tab ) + '\n';
|
||||
|
||||
if ( next && isExpression( next ) ) {
|
||||
|
||||
output += '\n';
|
||||
|
||||
}
|
||||
|
||||
return output;
|
||||
|
||||
}
|
||||
|
||||
emitExtraLine( statement, body ) {
|
||||
|
||||
const index = body.indexOf( statement );
|
||||
const previous = body[ index - 1 ];
|
||||
|
||||
if ( previous === undefined ) return '';
|
||||
|
||||
if ( statement.isReturn ) return '\n';
|
||||
|
||||
const lastExp = isExpression( previous );
|
||||
const currExp = isExpression( statement );
|
||||
|
||||
if ( lastExp !== currExp || ( ! lastExp && ! currExp ) ) return '\n';
|
||||
|
||||
return '';
|
||||
|
||||
}
|
||||
|
||||
emit( ast ) {
|
||||
|
||||
let code = '\n';
|
||||
|
||||
if ( this.iife ) this.tab += '\t';
|
||||
|
||||
const overloadings = this.overloadings;
|
||||
|
||||
for ( const statement of ast.body ) {
|
||||
|
||||
if ( statement.isFunctionDeclaration ) {
|
||||
|
||||
if ( overloadings.has( statement.name ) === false ) {
|
||||
|
||||
overloadings.set( statement.name, [] );
|
||||
|
||||
}
|
||||
|
||||
overloadings.get( statement.name ).push( statement );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( const statement of ast.body ) {
|
||||
|
||||
code += this.emitExtraLine( statement, ast.body );
|
||||
|
||||
if ( statement.isComment ) {
|
||||
|
||||
code += this.emitComment( statement, ast.body );
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
if ( statement.isFunctionDeclaration ) {
|
||||
|
||||
code += this.tab + this.emitFunction( statement );
|
||||
|
||||
} else {
|
||||
|
||||
code += this.tab + this.emitExpression( statement ) + ';\n';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const imports = [ ...this.imports ];
|
||||
const exports = [ ...this.global ];
|
||||
|
||||
let header = '// Three.js Transpiler r' + REVISION + '\n\n';
|
||||
let footer = '';
|
||||
|
||||
if ( this.iife ) {
|
||||
|
||||
header += '( function ( TSL, uniforms ) {\n\n';
|
||||
|
||||
header += imports.length > 0 ? '\tconst { ' + imports.join( ', ' ) + ' } = TSL;\n' : '';
|
||||
footer += exports.length > 0 ? '\treturn { ' + exports.join( ', ' ) + ' };\n' : '';
|
||||
|
||||
footer += '\n} );';
|
||||
|
||||
} else {
|
||||
|
||||
header += imports.length > 0 ? 'import { ' + imports.join( ', ' ) + ' } from \'three/tsl\';\n' : '';
|
||||
|
||||
}
|
||||
|
||||
return header + code + footer;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default TSLEncoder;
|
||||
67
node_modules/three/examples/jsm/transpiler/Transpiler.js
generated
vendored
Normal file
67
node_modules/three/examples/jsm/transpiler/Transpiler.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
import Linker from './Linker.js';
|
||||
|
||||
/**
|
||||
* A class that transpiles shader code from one language into another.
|
||||
*
|
||||
* `Transpiler` can only be used to convert GLSL into TSL right now. It is intended
|
||||
* to support developers when they want to migrate their custom materials from the
|
||||
* current to the new node-based material system.
|
||||
*
|
||||
* @three_import import Transpiler from 'three/addons/transpiler/Transpiler.js';
|
||||
*/
|
||||
class Transpiler {
|
||||
|
||||
/**
|
||||
* Constructs a new transpiler.
|
||||
*
|
||||
* @param {GLSLDecoder} decoder - The GLSL decoder.
|
||||
* @param {TSLEncoder} encoder - The TSL encoder.
|
||||
*/
|
||||
constructor( decoder, encoder ) {
|
||||
|
||||
/**
|
||||
* The GLSL decoder. This component parse GLSL and produces
|
||||
* a language-independent AST for further processing.
|
||||
*
|
||||
* @type {GLSLDecoder}
|
||||
*/
|
||||
this.decoder = decoder;
|
||||
|
||||
/**
|
||||
* The TSL encoder. It takes the AST and emits TSL code.
|
||||
*
|
||||
* @type {TSLEncoder}
|
||||
*/
|
||||
this.encoder = encoder;
|
||||
|
||||
/**
|
||||
* The linker. It processes the AST and resolves
|
||||
* variable and function references, ensuring that all
|
||||
* dependencies are properly linked.
|
||||
*
|
||||
* @type {Linker}
|
||||
*/
|
||||
this.linker = new Linker();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given GLSL source and returns TSL syntax.
|
||||
*
|
||||
* @param {string} source - The GLSL source.
|
||||
* @return {string} The TSL code.
|
||||
*/
|
||||
parse( source ) {
|
||||
|
||||
const ast = this.decoder.parse( source );
|
||||
|
||||
// Process the AST to resolve variable and function references and optimizations.
|
||||
this.linker.process( ast );
|
||||
|
||||
return this.encoder.emit( ast );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Transpiler;
|
||||
29
node_modules/three/examples/jsm/transpiler/TranspilerUtils.js
generated
vendored
Normal file
29
node_modules/three/examples/jsm/transpiler/TranspilerUtils.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
export function isExpression( st ) {
|
||||
|
||||
return st.isFunctionDeclaration !== true && st.isFor !== true && st.isWhile !== true && st.isConditional !== true && st.isSwitch !== true && st.isStructDefinition !== true;
|
||||
|
||||
}
|
||||
|
||||
export function isPrimitive( value ) {
|
||||
|
||||
return /^(true|false|-?(\d|\.\d))/.test( value );
|
||||
|
||||
}
|
||||
|
||||
export function isBuiltinType( str ) {
|
||||
|
||||
return /^(void|bool|float|u?int|mat[234]|mat[234]x[234]|(u|i|b)?vec[234])$/.test( str );
|
||||
|
||||
}
|
||||
|
||||
export function toFloatType( type ) {
|
||||
|
||||
if ( /^(i?int)$/.test( type ) ) return 'float';
|
||||
|
||||
const vecMatch = /^(i|u)?vec([234])$/.exec( type );
|
||||
|
||||
if ( vecMatch ) return 'vec' + vecMatch[ 2 ];
|
||||
|
||||
return type;
|
||||
|
||||
}
|
||||
839
node_modules/three/examples/jsm/transpiler/WGSLEncoder.js
generated
vendored
Normal file
839
node_modules/three/examples/jsm/transpiler/WGSLEncoder.js
generated
vendored
Normal file
@@ -0,0 +1,839 @@
|
||||
import { REVISION } from 'three/webgpu';
|
||||
|
||||
import { VariableDeclaration, Accessor } from './AST.js';
|
||||
import { isExpression } from './TranspilerUtils.js';
|
||||
|
||||
// Note: This is a simplified list. A complete implementation would need more mappings.
|
||||
const typeMap = {
|
||||
'float': 'f32',
|
||||
'int': 'i32',
|
||||
'uint': 'u32',
|
||||
'bool': 'bool',
|
||||
'vec2': 'vec2f',
|
||||
'ivec2': 'vec2i',
|
||||
'uvec2': 'vec2u',
|
||||
'bvec2': 'vec2b',
|
||||
'vec3': 'vec3f',
|
||||
'ivec3': 'vec3i',
|
||||
'uvec3': 'vec3u',
|
||||
'bvec3': 'vec3b',
|
||||
'vec4': 'vec4f',
|
||||
'ivec4': 'vec4i',
|
||||
'uvec4': 'vec4u',
|
||||
'bvec4': 'vec4b',
|
||||
'mat3': 'mat3x3<f32>',
|
||||
'mat4': 'mat4x4<f32>',
|
||||
'texture': 'texture_2d<f32>',
|
||||
'textureCube': 'texture_cube<f32>',
|
||||
'texture3D': 'texture_3d<f32>',
|
||||
};
|
||||
|
||||
// GLSL to WGSL built-in function mapping
|
||||
const wgslLib = {
|
||||
'abs': 'abs',
|
||||
'acos': 'acos',
|
||||
'asin': 'asin',
|
||||
'atan': 'atan',
|
||||
'atan2': 'atan2',
|
||||
'ceil': 'ceil',
|
||||
'clamp': 'clamp',
|
||||
'cos': 'cos',
|
||||
'cross': 'cross',
|
||||
'degrees': 'degrees',
|
||||
'distance': 'distance',
|
||||
'dot': 'dot',
|
||||
'exp': 'exp',
|
||||
'exp2': 'exp2',
|
||||
'faceforward': 'faceForward',
|
||||
'floor': 'floor',
|
||||
'fract': 'fract',
|
||||
'inverse': 'inverse',
|
||||
'inversesqrt': 'inverseSqrt',
|
||||
'length': 'length',
|
||||
'log': 'log',
|
||||
'log2': 'log2',
|
||||
'max': 'max',
|
||||
'min': 'min',
|
||||
'mix': 'mix',
|
||||
'normalize': 'normalize',
|
||||
'pow': 'pow',
|
||||
'radians': 'radians',
|
||||
'reflect': 'reflect',
|
||||
'refract': 'refract',
|
||||
'round': 'round',
|
||||
'sign': 'sign',
|
||||
'sin': 'sin',
|
||||
'smoothstep': 'smoothstep',
|
||||
'sqrt': 'sqrt',
|
||||
'step': 'step',
|
||||
'tan': 'tan',
|
||||
'transpose': 'transpose',
|
||||
'trunc': 'trunc',
|
||||
'dFdx': 'dpdx',
|
||||
'dFdy': 'dpdy',
|
||||
'fwidth': 'fwidth',
|
||||
// Texture functions are handled separately
|
||||
'texture': 'textureSample',
|
||||
'texture2D': 'textureSample',
|
||||
'texture3D': 'textureSample',
|
||||
'textureCube': 'textureSample',
|
||||
'textureLod': 'textureSampleLevel',
|
||||
'texelFetch': 'textureLoad',
|
||||
'textureGrad': 'textureSampleGrad',
|
||||
'floatBitsToInt': 'bitcast<i32>',
|
||||
'floatBitsToUint': 'bitcast<u32>',
|
||||
'intBitsToFloat': 'bitcast<f32>',
|
||||
'uintBitsToFloat': 'bitcast<f32>',
|
||||
};
|
||||
|
||||
class WGSLEncoder {
|
||||
|
||||
constructor() {
|
||||
|
||||
this.tab = '';
|
||||
this.functions = new Map();
|
||||
this.uniforms = [];
|
||||
this.varyings = [];
|
||||
this.structs = new Map();
|
||||
this.polyfills = new Map();
|
||||
|
||||
// Assume a single group for simplicity
|
||||
this.groupIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
getWgslType( type ) {
|
||||
|
||||
return typeMap[ type ] || type;
|
||||
|
||||
}
|
||||
|
||||
emitExpression( node ) {
|
||||
|
||||
if ( ! node ) return '';
|
||||
|
||||
let code;
|
||||
|
||||
if ( node.isAccessor ) {
|
||||
|
||||
// Check if this accessor is part of a uniform struct
|
||||
const uniform = this.uniforms.find( u => u.name === node.property );
|
||||
|
||||
if ( uniform && ! uniform.type.includes( 'texture' ) ) {
|
||||
|
||||
return `uniforms.${node.property}`;
|
||||
|
||||
}
|
||||
|
||||
code = node.property;
|
||||
|
||||
} else if ( node.isNumber ) {
|
||||
|
||||
code = node.value;
|
||||
|
||||
// WGSL requires floating point numbers to have a decimal
|
||||
if ( node.type === 'float' && ! code.includes( '.' ) ) {
|
||||
|
||||
code += '.0';
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isOperator ) {
|
||||
|
||||
const left = this.emitExpression( node.left );
|
||||
const right = this.emitExpression( node.right );
|
||||
|
||||
code = `${ left } ${ node.type } ${ right }`;
|
||||
|
||||
if ( node.parent.isAssignment !== true && node.parent.isOperator ) {
|
||||
|
||||
code = `( ${ code } )`;
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isFunctionCall ) {
|
||||
|
||||
const fnName = wgslLib[ node.name ] || node.name;
|
||||
|
||||
if ( fnName === 'mod' ) {
|
||||
|
||||
const snippets = node.params.map( p => this.emitExpression( p ) );
|
||||
const types = node.params.map( p => p.getType() );
|
||||
|
||||
const modFnName = 'mod_' + types.join( '_' );
|
||||
|
||||
if ( this.polyfills.has( modFnName ) === false ) {
|
||||
|
||||
this.polyfills.set( modFnName, `fn ${ modFnName }( x: ${ this.getWgslType( types[ 0 ] ) }, y: ${ this.getWgslType( types[ 1 ] ) } ) -> ${ this.getWgslType( types[ 0 ] ) } {
|
||||
|
||||
return x - y * floor( x / y );
|
||||
|
||||
}` );
|
||||
|
||||
}
|
||||
|
||||
code = `${ modFnName }( ${ snippets.join( ', ' ) } )`;
|
||||
|
||||
} else if ( fnName.startsWith( 'bitcast' ) ) {
|
||||
|
||||
const params = node.params.map( p => this.emitExpression( p ) ).join( ',' );
|
||||
const types = node.params.map( p => p.getType() );
|
||||
|
||||
if ( /.*vec[234]/.test( types[ 0 ] ) ) {
|
||||
|
||||
const conversionType = fnName.substring( 8, fnName.length - 1 );
|
||||
const vectorType = types[ 0 ].substring( - 1 );
|
||||
|
||||
code = `bitcast<${ vectorType }<${ conversionType }>>`;
|
||||
|
||||
} else {
|
||||
|
||||
code = fnName;
|
||||
|
||||
}
|
||||
|
||||
code += `( ${ params } )`;
|
||||
|
||||
} else if ( fnName.startsWith( 'texture' ) ) {
|
||||
|
||||
// Handle texture functions separately due to sampler handling
|
||||
|
||||
code = this.emitTextureAccess( node );
|
||||
|
||||
} else {
|
||||
|
||||
const params = node.params.map( p => this.emitExpression( p ) );
|
||||
|
||||
if ( typeMap[ fnName ] ) {
|
||||
|
||||
// Handle type constructors like vec3(...)
|
||||
|
||||
code = this.getWgslType( fnName );
|
||||
|
||||
} else {
|
||||
|
||||
code = fnName;
|
||||
|
||||
}
|
||||
|
||||
if ( params.length > 0 ) {
|
||||
|
||||
code += '( ' + params.join( ', ' ) + ' )';
|
||||
|
||||
} else {
|
||||
|
||||
code += '()';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isReturn ) {
|
||||
|
||||
code = 'return';
|
||||
|
||||
if ( node.value ) {
|
||||
|
||||
code += ' ' + this.emitExpression( node.value );
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isDiscard ) {
|
||||
|
||||
code = 'discard';
|
||||
|
||||
} else if ( node.isBreak ) {
|
||||
|
||||
if ( node.parent.isSwitchCase !== true ) {
|
||||
|
||||
code = 'break';
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isContinue ) {
|
||||
|
||||
code = 'continue';
|
||||
|
||||
} else if ( node.isAccessorElements ) {
|
||||
|
||||
code = this.emitExpression( node.object );
|
||||
|
||||
for ( const element of node.elements ) {
|
||||
|
||||
const value = this.emitExpression( element.value );
|
||||
|
||||
if ( element.isStaticElement ) {
|
||||
|
||||
code += '.' + value;
|
||||
|
||||
} else if ( element.isDynamicElement ) {
|
||||
|
||||
code += `[${value}]`;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isFor ) {
|
||||
|
||||
code = this.emitFor( node );
|
||||
|
||||
} else if ( node.isWhile ) {
|
||||
|
||||
code = this.emitWhile( node );
|
||||
|
||||
} else if ( node.isSwitch ) {
|
||||
|
||||
code = this.emitSwitch( node );
|
||||
|
||||
} else if ( node.isVariableDeclaration ) {
|
||||
|
||||
code = this.emitVariables( node );
|
||||
|
||||
} else if ( node.isUniform ) {
|
||||
|
||||
this.uniforms.push( node );
|
||||
return ''; // Defer emission to the header
|
||||
|
||||
} else if ( node.isVarying ) {
|
||||
|
||||
this.varyings.push( node );
|
||||
return ''; // Defer emission to the header
|
||||
|
||||
} else if ( node.isStructDefinition ) {
|
||||
|
||||
code = this.emitStructDefinition( node );
|
||||
|
||||
} else if ( node.isTernary ) {
|
||||
|
||||
const cond = this.emitExpression( node.cond );
|
||||
const left = this.emitExpression( node.left );
|
||||
const right = this.emitExpression( node.right );
|
||||
|
||||
// WGSL's equivalent to the ternary operator is select(false_val, true_val, condition)
|
||||
code = `select( ${ right }, ${ left }, ${ cond } )`;
|
||||
|
||||
} else if ( node.isConditional ) {
|
||||
|
||||
code = this.emitConditional( node );
|
||||
|
||||
} else if ( node.isUnary ) {
|
||||
|
||||
const expr = this.emitExpression( node.expression );
|
||||
|
||||
if ( node.type === '++' || node.type === '--' ) {
|
||||
|
||||
const op = node.type === '++' ? '+' : '-';
|
||||
|
||||
code = `${ expr } = ${ expr } ${ op } 1`;
|
||||
|
||||
} else {
|
||||
|
||||
code = `${ node.type }${ expr }`;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
console.warn( 'Unknown node type in WGSL Encoder:', node );
|
||||
|
||||
code = `/* unknown node: ${ node.constructor.name } */`;
|
||||
|
||||
}
|
||||
|
||||
return code;
|
||||
|
||||
}
|
||||
|
||||
emitTextureAccess( node ) {
|
||||
|
||||
const wgslFn = wgslLib[ node.name ];
|
||||
const textureName = this.emitExpression( node.params[ 0 ] );
|
||||
const uv = this.emitExpression( node.params[ 1 ] );
|
||||
|
||||
// WGSL requires explicit samplers. We assume a naming convention.
|
||||
const samplerName = `${textureName}_sampler`;
|
||||
|
||||
let code;
|
||||
|
||||
switch ( node.name ) {
|
||||
|
||||
case 'texture':
|
||||
case 'texture2D':
|
||||
case 'texture3D':
|
||||
case 'textureCube':
|
||||
// format: textureSample(texture, sampler, coords, [offset])
|
||||
code = `${wgslFn}(${textureName}, ${samplerName}, ${uv}`;
|
||||
// Handle optional bias parameter (note: WGSL uses textureSampleBias)
|
||||
if ( node.params.length === 3 ) {
|
||||
|
||||
const bias = this.emitExpression( node.params[ 2 ] );
|
||||
code = `textureSampleBias(${textureName}, ${samplerName}, ${uv}, ${bias})`;
|
||||
|
||||
} else {
|
||||
|
||||
code += ')';
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'textureLod':
|
||||
// format: textureSampleLevel(texture, sampler, coords, level)
|
||||
const lod = this.emitExpression( node.params[ 2 ] );
|
||||
code = `${wgslFn}(${textureName}, ${samplerName}, ${uv}, ${lod})`;
|
||||
break;
|
||||
|
||||
case 'textureGrad':
|
||||
// format: textureSampleGrad(texture, sampler, coords, ddx, ddy)
|
||||
const ddx = this.emitExpression( node.params[ 2 ] );
|
||||
const ddy = this.emitExpression( node.params[ 3 ] );
|
||||
code = `${wgslFn}(${textureName}, ${samplerName}, ${uv}, ${ddx}, ${ddy})`;
|
||||
break;
|
||||
|
||||
case 'texelFetch':
|
||||
// format: textureLoad(texture, coords, [level])
|
||||
const coords = this.emitExpression( node.params[ 1 ] ); // should be ivec
|
||||
const lodFetch = node.params.length > 2 ? this.emitExpression( node.params[ 2 ] ) : '0';
|
||||
code = `${wgslFn}(${textureName}, ${coords}, ${lodFetch})`;
|
||||
break;
|
||||
|
||||
default:
|
||||
code = `/* unsupported texture op: ${node.name} */`;
|
||||
|
||||
}
|
||||
|
||||
return code;
|
||||
|
||||
}
|
||||
|
||||
emitBody( body ) {
|
||||
|
||||
let code = '';
|
||||
this.tab += '\t';
|
||||
|
||||
for ( const statement of body ) {
|
||||
|
||||
code += this.emitExtraLine( statement, body );
|
||||
|
||||
if ( statement.isComment ) {
|
||||
|
||||
code += this.emitComment( statement, body );
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
const statementCode = this.emitExpression( statement );
|
||||
|
||||
if ( statementCode ) {
|
||||
|
||||
code += this.tab + statementCode;
|
||||
|
||||
if ( ! statementCode.endsWith( '}' ) && ! statementCode.endsWith( '{' ) ) {
|
||||
|
||||
code += ';';
|
||||
|
||||
}
|
||||
|
||||
code += '\n';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.tab = this.tab.slice( 0, - 1 );
|
||||
return code.slice( 0, - 1 ); // remove the last extra line
|
||||
|
||||
}
|
||||
|
||||
emitConditional( node ) {
|
||||
|
||||
const condStr = this.emitExpression( node.cond );
|
||||
const bodyStr = this.emitBody( node.body );
|
||||
|
||||
let ifStr = `if ( ${ condStr } ) {\n\n${ bodyStr }\n\n${ this.tab }}`;
|
||||
|
||||
let current = node;
|
||||
|
||||
while ( current.elseConditional ) {
|
||||
|
||||
current = current.elseConditional;
|
||||
const elseBodyStr = this.emitBody( current.body );
|
||||
|
||||
if ( current.cond ) { // This is an 'else if'
|
||||
|
||||
const elseCondStr = this.emitExpression( current.cond );
|
||||
|
||||
ifStr += ` else if ( ${ elseCondStr } ) {\n\n${ elseBodyStr }\n\n${ this.tab }}`;
|
||||
|
||||
} else { // This is an 'else'
|
||||
|
||||
ifStr += ` else {\n\n${ elseBodyStr }\n\n${ this.tab }}`;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ifStr;
|
||||
|
||||
}
|
||||
|
||||
emitFor( node ) {
|
||||
|
||||
const init = this.emitExpression( node.initialization );
|
||||
const cond = this.emitExpression( node.condition );
|
||||
const after = this.emitExpression( node.afterthought );
|
||||
const body = this.emitBody( node.body );
|
||||
|
||||
return `for ( ${ init }; ${ cond }; ${ after } ) {\n\n${ body }\n\n${ this.tab }}`;
|
||||
|
||||
}
|
||||
|
||||
emitWhile( node ) {
|
||||
|
||||
const cond = this.emitExpression( node.condition );
|
||||
const body = this.emitBody( node.body );
|
||||
|
||||
return `while ( ${ cond } ) {\n\n${ body }\n\n${ this.tab }}`;
|
||||
|
||||
}
|
||||
|
||||
emitSwitch( node ) {
|
||||
|
||||
const discriminant = this.emitExpression( node.discriminant );
|
||||
|
||||
let switchStr = `switch ( ${ discriminant } ) {\n\n`;
|
||||
|
||||
this.tab += '\t';
|
||||
|
||||
for ( const switchCase of node.cases ) {
|
||||
|
||||
const body = this.emitBody( switchCase.body );
|
||||
|
||||
if ( switchCase.isDefault ) {
|
||||
|
||||
switchStr += `${ this.tab }default: {\n\n${ body }\n\n${ this.tab }}\n\n`;
|
||||
|
||||
} else {
|
||||
|
||||
const cases = switchCase.conditions.map( c => this.emitExpression( c ) ).join( ', ' );
|
||||
|
||||
switchStr += `${ this.tab }case ${ cases }: {\n\n${ body }\n\n${ this.tab }}\n\n`;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.tab = this.tab.slice( 0, - 1 );
|
||||
|
||||
switchStr += `${this.tab}}`;
|
||||
|
||||
return switchStr;
|
||||
|
||||
}
|
||||
|
||||
emitVariables( node ) {
|
||||
|
||||
const declarations = [];
|
||||
|
||||
let current = node;
|
||||
|
||||
while ( current ) {
|
||||
|
||||
const type = this.getWgslType( current.type );
|
||||
|
||||
let valueStr = '';
|
||||
|
||||
if ( current.value ) {
|
||||
|
||||
valueStr = ` = ${this.emitExpression( current.value )}`;
|
||||
|
||||
}
|
||||
|
||||
// The AST linker tracks if a variable is ever reassigned.
|
||||
// If so, use 'var'; otherwise, use 'let'.
|
||||
|
||||
let keyword;
|
||||
|
||||
if ( current.linker ) {
|
||||
|
||||
if ( current.linker.assignments.length > 0 ) {
|
||||
|
||||
keyword = 'var'; // Reassigned variable
|
||||
|
||||
} else {
|
||||
|
||||
if ( current.value && current.value.isNumericExpression ) {
|
||||
|
||||
keyword = 'const'; // Immutable numeric expression
|
||||
|
||||
} else {
|
||||
|
||||
keyword = 'let'; // Immutable variable
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
declarations.push( `${ keyword } ${ current.name }: ${ type }${ valueStr }` );
|
||||
|
||||
current = current.next;
|
||||
|
||||
}
|
||||
|
||||
// In WGSL, multiple declarations in one line are not supported, so join with semicolons.
|
||||
return declarations.join( ';\n' + this.tab );
|
||||
|
||||
}
|
||||
|
||||
emitStructDefinition( node ) {
|
||||
|
||||
const { name, members } = node;
|
||||
|
||||
let structString = `struct ${ name } {\n`;
|
||||
|
||||
for ( let i = 0; i < members.length; i += 1 ) {
|
||||
|
||||
const member = members[ i ];
|
||||
|
||||
structString += `${ this.tab }\t${ member.name }: ${ this.getWgslType( member.type ) }`;
|
||||
|
||||
const delimiter = ( i != members.length - 1 ) ? ',\n' : '\n';
|
||||
structString += delimiter;
|
||||
|
||||
}
|
||||
|
||||
structString += this.tab + '}';
|
||||
|
||||
return structString;
|
||||
|
||||
}
|
||||
|
||||
emitFunction( node ) {
|
||||
|
||||
const name = node.name;
|
||||
const returnType = this.getWgslType( node.type );
|
||||
|
||||
const params = [];
|
||||
// We will prepend to a copy of the body, not the original AST node.
|
||||
const body = [ ...node.body ];
|
||||
|
||||
for ( const param of node.params ) {
|
||||
|
||||
const paramName = param.name;
|
||||
let paramType = this.getWgslType( param.type );
|
||||
|
||||
// Handle 'inout' and 'out' qualifiers using pointers. They are already mutable.
|
||||
if ( param.qualifier === 'inout' || param.qualifier === 'out' ) {
|
||||
|
||||
paramType = `ptr<function, ${paramType}>`;
|
||||
params.push( `${paramName}: ${paramType}` );
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
// If the parameter is reassigned within the function, we need to
|
||||
// create a local, mutable variable that shadows the parameter's name.
|
||||
if ( param.linker && param.linker.assignments.length > 0 ) {
|
||||
|
||||
// 1. Rename the incoming parameter to avoid name collision.
|
||||
const immutableParamName = `${paramName}_in`;
|
||||
params.push( `${immutableParamName}: ${paramType}` );
|
||||
|
||||
// 2. Create a new Accessor node for the renamed immutable parameter.
|
||||
const immutableAccessor = new Accessor( immutableParamName );
|
||||
immutableAccessor.isAccessor = true;
|
||||
immutableAccessor.property = immutableParamName;
|
||||
|
||||
// 3. Create a new VariableDeclaration node for the mutable local variable.
|
||||
// This new variable will have the original parameter's name.
|
||||
const mutableVar = new VariableDeclaration( param.type, param.name, immutableAccessor );
|
||||
|
||||
// 4. Mark this new variable as mutable so `emitVariables` uses `var`.
|
||||
mutableVar.linker = { assignments: [ true ] };
|
||||
|
||||
// 5. Prepend this new declaration to the function's body.
|
||||
body.unshift( mutableVar );
|
||||
|
||||
} else {
|
||||
|
||||
// This parameter is not reassigned, so treat it as a normal immutable parameter.
|
||||
params.push( `${paramName}: ${paramType}` );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const paramsStr = params.length > 0 ? ' ' + params.join( ', ' ) + ' ' : '';
|
||||
const returnStr = ( returnType && returnType !== 'void' ) ? ` -> ${returnType}` : '';
|
||||
|
||||
// Emit the function body, which now includes our injected variable declarations.
|
||||
const bodyStr = this.emitBody( body );
|
||||
|
||||
return `fn ${name}(${paramsStr})${returnStr} {\n\n${bodyStr}\n\n${this.tab}}`;
|
||||
|
||||
}
|
||||
|
||||
emitComment( statement, body ) {
|
||||
|
||||
const index = body.indexOf( statement );
|
||||
const previous = body[ index - 1 ];
|
||||
const next = body[ index + 1 ];
|
||||
|
||||
let output = '';
|
||||
|
||||
if ( previous && isExpression( previous ) ) {
|
||||
|
||||
output += '\n';
|
||||
|
||||
}
|
||||
|
||||
output += this.tab + statement.comment.replace( /\n/g, '\n' + this.tab ) + '\n';
|
||||
|
||||
if ( next && isExpression( next ) ) {
|
||||
|
||||
output += '\n';
|
||||
|
||||
}
|
||||
|
||||
return output;
|
||||
|
||||
}
|
||||
|
||||
emitExtraLine( statement, body ) {
|
||||
|
||||
const index = body.indexOf( statement );
|
||||
const previous = body[ index - 1 ];
|
||||
|
||||
if ( previous === undefined ) return '';
|
||||
|
||||
if ( statement.isReturn ) return '\n';
|
||||
|
||||
const lastExp = isExpression( previous );
|
||||
const currExp = isExpression( statement );
|
||||
|
||||
if ( lastExp !== currExp || ( ! lastExp && ! currExp ) ) return '\n';
|
||||
|
||||
return '';
|
||||
|
||||
}
|
||||
|
||||
emit( ast ) {
|
||||
|
||||
const header = '// Three.js Transpiler r' + REVISION + '\n\n';
|
||||
|
||||
let globals = '';
|
||||
let functions = '';
|
||||
let dependencies = '';
|
||||
|
||||
// 1. Pre-process to find all global declarations
|
||||
for ( const statement of ast.body ) {
|
||||
|
||||
if ( statement.isFunctionDeclaration ) {
|
||||
|
||||
this.functions.set( statement.name, statement );
|
||||
|
||||
} else if ( statement.isUniform ) {
|
||||
|
||||
this.uniforms.push( statement );
|
||||
|
||||
} else if ( statement.isVarying ) {
|
||||
|
||||
this.varyings.push( statement );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2. Build resource bindings (uniforms, textures, samplers)
|
||||
if ( this.uniforms.length > 0 ) {
|
||||
|
||||
let bindingIndex = 0;
|
||||
const uniformStructMembers = [];
|
||||
const textureGlobals = [];
|
||||
|
||||
for ( const uniform of this.uniforms ) {
|
||||
|
||||
// Textures are declared as separate global variables, not in the UBO
|
||||
if ( uniform.type.includes( 'texture' ) ) {
|
||||
|
||||
textureGlobals.push( `@group(${this.groupIndex}) @binding(${bindingIndex ++}) var ${uniform.name}: ${this.getWgslType( uniform.type )};` );
|
||||
textureGlobals.push( `@group(${this.groupIndex}) @binding(${bindingIndex ++}) var ${uniform.name}_sampler: sampler;` );
|
||||
|
||||
} else {
|
||||
|
||||
uniformStructMembers.push( `\t${uniform.name}: ${this.getWgslType( uniform.type )},` );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Create a UBO struct if there are any non-texture uniforms
|
||||
if ( uniformStructMembers.length > 0 ) {
|
||||
|
||||
globals += 'struct Uniforms {\n';
|
||||
globals += uniformStructMembers.join( '\n' );
|
||||
globals += '\n};\n';
|
||||
globals += `@group(${this.groupIndex}) @binding(${bindingIndex ++}) var<uniform> uniforms: Uniforms;\n\n`;
|
||||
|
||||
}
|
||||
|
||||
// Add the texture and sampler globals
|
||||
globals += textureGlobals.join( '\n' ) + '\n\n';
|
||||
|
||||
}
|
||||
|
||||
// 3. Build varying structs for stage I/O
|
||||
// This is a simplification; a full implementation would need to know the shader stage.
|
||||
if ( this.varyings.length > 0 ) {
|
||||
|
||||
globals += 'struct Varyings {\n';
|
||||
let location = 0;
|
||||
for ( const varying of this.varyings ) {
|
||||
|
||||
globals += `\t@location(${location ++}) ${varying.name}: ${this.getWgslType( varying.type )},\n`;
|
||||
|
||||
}
|
||||
|
||||
globals += '};\n\n';
|
||||
|
||||
}
|
||||
|
||||
// 4. Emit all functions and other global statements
|
||||
for ( const statement of ast.body ) {
|
||||
|
||||
functions += this.emitExtraLine( statement, ast.body );
|
||||
|
||||
if ( statement.isFunctionDeclaration ) {
|
||||
|
||||
functions += this.emitFunction( statement ) + '\n';
|
||||
|
||||
} else if ( statement.isComment ) {
|
||||
|
||||
functions += this.emitComment( statement, ast.body );
|
||||
|
||||
} else if ( ! statement.isUniform && ! statement.isVarying ) {
|
||||
|
||||
// Handle other top-level statements like 'const'
|
||||
functions += this.emitExpression( statement ) + ';\n';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 4. Build dependencies
|
||||
for ( const value of this.polyfills.values() ) {
|
||||
|
||||
dependencies = `${ value }\n\n`;
|
||||
|
||||
}
|
||||
|
||||
return header + dependencies + globals + functions.trimEnd() + '\n';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default WGSLEncoder;
|
||||
Reference in New Issue
Block a user