Skip to content

Commit

Permalink
ShaderToyDecoder, example and revision
Browse files Browse the repository at this point in the history
  • Loading branch information
sunag committed Oct 19, 2023
1 parent 09463b3 commit 05d3e32
Show file tree
Hide file tree
Showing 10 changed files with 503 additions and 43 deletions.
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@
"webgpu_points_fat",
"webgpu_rtt",
"webgpu_sandbox",
"webgpu_shadertoy",
"webgpu_shadowmap",
"webgpu_skinning",
"webgpu_skinning_instancing",
Expand Down
23 changes: 20 additions & 3 deletions examples/jsm/nodes/core/NodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -817,11 +817,28 @@ class NodeBuilder {
flowShaderNode( shaderNode ) {

const layout = shaderNode.layout;
const inputs = {};

for ( const input of layout.inputs ) {
let inputs;

inputs[ input.name ] = new PropertyNode( input.type, input.name, false )
if ( shaderNode.isArrayInput ) {

inputs = [];

for ( const input of layout.inputs ) {

inputs.push( new PropertyNode( input.type, input.name, false ) );

}

} else {

inputs = {};

for ( const input of layout.inputs ) {

inputs[ input.name ] = new PropertyNode( input.type, input.name, false );

}

}

Expand Down
3 changes: 2 additions & 1 deletion examples/jsm/nodes/core/VarNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default VarNode;

export const temp = nodeProxy( VarNode );

addNodeElement( 'temp', temp );
addNodeElement( 'temp', temp ); // @TODO: Will be removed in the future
addNodeElement( 'toVar', temp );

addNodeClass( 'VarNode', VarNode );
38 changes: 31 additions & 7 deletions examples/jsm/nodes/shadernode/ShaderNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ export function addNodeElement( name, nodeElement ) {

}

export const getNodeElement = ( name ) => NodeElements.get( name );

const parseSwizzle = ( props ) => props.replace( /r|s/g, 'x' ).replace( /g|t/g, 'y' ).replace( /b|p/g, 'z' ).replace( /a|q/g, 'w' );

const shaderNodeHandler = {
Expand Down Expand Up @@ -273,12 +271,12 @@ class ShaderCallNodeInternal extends Node {

}

return nodeObject( functionNode.call( nodeObjects( inputNodes ) ) );
return nodeObject( functionNode.call( inputNodes ) );

}

const jsFunc = shaderNode.jsFunc;
const outputNode = inputNodes !== null ? jsFunc( nodeObjects( inputNodes ), builder.stack, builder ) : jsFunc( builder.stack, builder );
const outputNode = inputNodes !== null ? jsFunc( inputNodes, builder.stack, builder ) : jsFunc( builder.stack, builder );

return nodeObject( outputNode );

Expand Down Expand Up @@ -323,6 +321,12 @@ class ShaderNodeInternal extends Node {

}

get isArrayInput() {

return /^\(\s+?\[/.test( this.jsFunc.toString() );

}

setLayout( layout ) {

this.layout = layout;
Expand All @@ -333,6 +337,8 @@ class ShaderNodeInternal extends Node {

call( inputs = null ) {

nodeObjects( inputs );

return nodeObject( new ShaderCallNodeInternal( this, inputs ) );

}
Expand Down Expand Up @@ -462,16 +468,34 @@ export const tslFn = ( jsFunc ) => {

const shaderNode = new ShaderNode( jsFunc );

const fn = ( inputs ) => shaderNode.call( inputs );
fn.shaderNode = shaderNode;
const fn = ( ...params ) => {

let inputs;

nodeObjects( params );

if ( params[ 0 ] && params[ 0 ].isNode ) {

inputs = [ ...params ];

} else {

inputs = params[ 0 ];

}

return shaderNode.call( inputs );

};

fn.shaderNode = shaderNode;
fn.setLayout = ( layout ) => {

shaderNode.setLayout( layout );

return fn;

}
};

return fn;

Expand Down
39 changes: 38 additions & 1 deletion examples/jsm/nodes/transpiler/AST.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,48 @@ export class Return {

export class Accessor {

constructor( property ) {

this.property = property;

this.isAccessor = true;

}

}

export class StaticElement {

constructor( value ) {

this.value = value;

this.isAccessor = true;
this.isStaticElement = true;

}

}

export class DynamicElement {

constructor( value ) {

this.value = value;

this.isDynamicElement = true;

}

}

export class AccessorElements {

constructor( property, elements = [] ) {

this.property = property;
this.elements = elements;

this.isAccessorElements = true;

}

Expand Down
80 changes: 68 additions & 12 deletions examples/jsm/nodes/transpiler/GLSLDecoder.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Program, FunctionDeclaration, For, FunctionParameter, Unary, Conditional, VariableDeclaration, Operator, Number, FunctionCall, Return, Accessor } from './AST.js';
import { Program, FunctionDeclaration, For, AccessorElements, DynamicElement, StaticElement, FunctionParameter, Unary, Conditional, VariableDeclaration, Operator, Number, FunctionCall, Return, Accessor } from './AST.js';

const unaryOperators = [
'+', '-', '~', '!', '++', '--'
Expand Down Expand Up @@ -37,6 +37,15 @@ const operatorsRegExp = new RegExp( '^(\\' + [
'.', ',', ';', '!', '=', '~', '*', '/', '%', '+', '-', '<', '>', '&', '^', '|', '?', ':', '#'
].join( '$' ).split( '' ).join( '\\' ).replace( /\\\$/g, '|' ) + ')' );

function getGroupDelta( str ) {

if ( str === '(' || str === '[' || str === '{' ) return 1;
if ( str === ')' || str === ']' || str === '}' ) return - 1;

return 0;

}

class Token {

constructor( tokenizer, type, str, pos ) {
Expand Down Expand Up @@ -235,8 +244,7 @@ class GLSLDecoder {

const token = tokens[ i ];

if ( token.str === '(' ) groupIndex ++;
else if ( token.str === ')' ) groupIndex --;
groupIndex += getGroupDelta( token.str );

output.push( token );

Expand Down Expand Up @@ -328,8 +336,7 @@ class GLSLDecoder {

}

if ( token.str === '(' ) groupIndex ++;
else if ( token.str === ')' ) groupIndex --;
groupIndex += getGroupDelta( token.str );

if ( groupIndex < 0 ) {

Expand Down Expand Up @@ -375,11 +382,61 @@ class GLSLDecoder {

const secondToken = tokens[ 1 ];

if ( secondToken && secondToken.str === '(' ) {
if ( secondToken ) {

if ( secondToken.str === '(' ) {

// function call

const paramsTokens = this.parseFunctionParametersFromTokens( tokens.slice( 2, tokens.length - 1 ) );

return new FunctionCall( firstToken.str, paramsTokens );

} else if ( secondToken.str === '[' ) {

// array accessor

const elements = [];

let currentTokens = tokens.slice( 1 );

while ( currentTokens.length > 0 ) {

const token = currentTokens[ 0 ];

if ( token.str === '[' ) {

const accessorTokens = this.getTokensUntil( ']', currentTokens );

const element = this.parseExpressionFromTokens( accessorTokens.slice( 1, accessorTokens.length - 1 ) );

const paramsTokens = this.parseFunctionParametersFromTokens( tokens.slice( 2, tokens.length - 1 ) );
currentTokens = currentTokens.slice( accessorTokens.length );

return new FunctionCall( firstToken.str, paramsTokens );
elements.push( new DynamicElement( element ) );

} else if ( token.str === '.' ) {

const accessorTokens = currentTokens.slice( 1, 2 );

const element = this.parseExpressionFromTokens( accessorTokens );

currentTokens = currentTokens.slice( 2 );

elements.push( new StaticElement( element ) );

} else {

console.error( 'Unknown accessor expression', token );

break;

}

}

return new AccessorElements( firstToken.str, elements );

}

}

Expand Down Expand Up @@ -642,16 +699,15 @@ class GLSLDecoder {

}

let groupIndex = 0;

while ( this.index < this.tokens.length ) {

const token = this.getToken();

let statement = null;

let groupIndex = 0;

if ( token.isOperator && token.str === '{' ) groupIndex ++;
else if ( token.isOperator && token.str === '}' ) groupIndex --;
groupIndex += getGroupDelta( token.str );

if ( groupIndex < 0 ) {

Expand Down
63 changes: 63 additions & 0 deletions examples/jsm/nodes/transpiler/ShaderToyDecoder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Return, VariableDeclaration, Accessor } from './AST.js';
import GLSLDecoder from './GLSLDecoder.js';

const keywords = [
{ name: 'iTime', polyfill: 'float iTime = timerLocal();' },
{ name: 'iResolution', polyfill: 'vec3 iResolution = viewportResolution;' },
{ name: 'fragCoord', polyfill: 'vec2 fragCoord = viewportCoordinate.setY( viewportCoordinate.y.oneMinus() ).toVar();' }
];

class ShaderToyDecoder extends GLSLDecoder {

constructor() {

super();

}

parseFunction() {

const node = super.parseFunction();

if ( node.name === 'mainImage' ) {

node.params = [];
node.type = 'vec4';
node.layout = false; // for now

node.body.unshift( new VariableDeclaration( 'vec4', 'fragColor' ) );
node.body.push( new Return( new Accessor( 'fragColor' ) ) );

}

return node;

}

parse( source ) {

let polyfill = '';

for ( const keyword of keywords ) {

if ( new RegExp( `(^|\\b)${ keyword.name }($|\\b)`, 'gm' ).test( source ) ) {

polyfill += keyword.polyfill + '\n';

}

}

if ( polyfill ) {

polyfill = '// Polyfills\n\n' + polyfill + '\n';

}

return super.parse( polyfill + source );

}

}

export default ShaderToyDecoder;
Loading

0 comments on commit 05d3e32

Please sign in to comment.