diff --git a/preview/global/sketch.js b/preview/global/sketch.js
index 35009f1f51..b0cd6c8045 100644
--- a/preview/global/sketch.js
+++ b/preview/global/sketch.js
@@ -1,94 +1,124 @@
p5.disableFriendlyErrors = true;
+
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
-let myModel;
let starShader;
let starStrokeShader;
let stars;
-let ditheringShader;
let originalFrameBuffer;
-let blurredFrameBuffer;
+let pixellizeShader;
+let fresnelShader;
+let bloomShader;
+
+function fresnelShaderCallback() {
+ const fresnelPower = uniformFloat(2);
+ const fresnelBias = uniformFloat(-0.1);
+ const fresnelScale = uniformFloat(2);
+ getCameraInputs((inputs) => {
+ let n = normalize(inputs.normal);
+ let v = normalize(-inputs.position);
+ let base = 1.0 - dot(n, v);
+ let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias;
+ let col = mix([0, 0, 0], [1, .5, .7], fresnel);
+ inputs.color = [col, 1];
+ return inputs;
+ });
+}
function starShaderCallback() {
const time = uniformFloat(() => millis());
+ const skyRadius = uniformFloat(1000);
+
+ function rand2(st) {
+ return fract(sin(dot(st, [12.9898, 78.233])) * 43758.5453123);
+ }
+
+ function semiSphere() {
+ let id = instanceID();
+ let theta = rand2([id, 0.1234]) * TWO_PI;
+ let phi = rand2([id, 3.321]) * PI+time/10000;
+ let r = skyRadius;
+ r *= 1.5 * sin(phi);
+ let x = r * sin(phi) * cos(theta);
+ let y = r * 1.5 * cos(phi);
+ let z = r * sin(phi) * sin(theta);
+ return [x, y, z];
+ }
+
getWorldInputs((inputs) => {
- inputs.position.y += instanceID() * 20 - 1000;
- inputs.position.x += 40 * sin(time * 0.001 + instanceID());
+ inputs.position += semiSphere();
return inputs;
});
+
getObjectInputs((inputs) => {
- inputs.position *= sin(time*0.001 + instanceID());
+ let scale = 1 + 0.1 * sin(time * 0.002 + instanceID());
+ inputs.position *= scale;
return inputs;
- })
+ });
}
-function ditheringCallback() {
- const time = uniformFloat(() => millis())
-
- function rand(co) {
- return fract(sin(dot(co, [12.9898, 78.233])) * 43758.5453);
- }
-
- function grayscale(col) {
- return dot([col.x, col.y, col.z], [0.21, 0.72, 0.07])
- }
-
+function pixellizeShaderCallback() {
+ const pixelSize = uniformFloat(()=> width*.75);
getColor((input, canvasContent) => {
- let col = texture(canvasContent, input.texCoord);
- col.z = 0.55;
- col += rand(input.texCoord + time/10000000000) * 0.15 - 0.05;
- let greyscaleValue = grayscale(col);
- col.x = greyscaleValue
- col.y = greyscaleValue
+ let coord = input.texCoord;
+ coord = floor(coord * pixelSize) / pixelSize;
+ let col = texture(canvasContent, coord);
return col;
});
}
-function bloom() {
- const blurred = uniformTexture(() => blurredFrameBuffer);
- const original = uniformTexture(() => originalFrameBuffer);
-
+function bloomShaderCallback() {
+ const preBlur = uniformTexture(() => originalFrameBuffer);
getColor((input, canvasContent) => {
- const blurredCol = texture(blurred, input.texCoord);
- const originalCol = texture(original, input.texCoord);
- const brightPass = max(originalCol - 0.0, 0.0) * 3.0;
- // const bloom = original + blurred * brightPass;
- // return bloom;
- return texture(blurred, input.texCoord) + texture(original, input.texCoord);
+ const blurredCol = texture(canvasContent, input.texCoord);
+ const originalCol = texture(preBlur, input.texCoord);
+ const brightPass = max(originalCol, 0.3) * 1.5;
+ const bloom = originalCol + blurredCol * brightPass;
+ return bloom;
});
}
async function setup(){
createCanvas(windowWidth, windowHeight, WEBGL);
- stars = buildGeometry(() => sphere(20, 3, 3))
+ stars = buildGeometry(() => sphere(30, 4, 2))
+ originalFrameBuffer = createFramebuffer();
+
starShader = baseMaterialShader().modify(starShaderCallback);
starStrokeShader = baseStrokeShader().modify(starShaderCallback)
- ditheringShader = baseFilterShader().modify(ditheringCallback);
- originalFrameBuffer = createFramebuffer();
- blurredFrameBuffer = createFramebuffer();
- bloomShader = baseFilterShader().modify(bloom);
+ fresnelShader = baseColorShader().modify(fresnelShaderCallback);
+ bloomShader = baseFilterShader().modify(bloomShaderCallback);
+ pixellizeShader = baseFilterShader().modify(pixellizeShaderCallback);
}
function draw(){
originalFrameBuffer.begin();
+ background(0);
orbitControl();
- background(0,0,0);
- push();
- stroke(255,0,255)
- fill(255,200,255)
+
+ push()
+ strokeWeight(4)
+ stroke(255,0,0)
+ rotateX(PI/2 + millis() * 0.0005);
+ fill(255,100, 150)
strokeShader(starStrokeShader)
shader(starShader);
- model(stars, 100);
- pop();
+ model(stars, 2000);
+ pop()
+
+ push()
+ shader(fresnelShader)
+ noStroke()
+ sphere(500);
+ pop()
+ filter(pixellizeShader);
+
originalFrameBuffer.end();
- blurredFrameBuffer.begin();
- image(originalFrameBuffer, -windowWidth/2, -windowHeight/2)
- filter(BLUR)
- blurredFrameBuffer.end();
-
- // image(originalFrameBuffer, -windowWidth/2, -windowHeight/2)
+ imageMode(CENTER)
+ image(originalFrameBuffer, 0, 0)
+
+ filter(BLUR, 20)
filter(bloomShader);
}
diff --git a/src/shape/vertex.js b/src/shape/vertex.js
index f849a6e862..9c42f4d624 100644
--- a/src/shape/vertex.js
+++ b/src/shape/vertex.js
@@ -1419,7 +1419,7 @@ function vertex(p5, fn){
/**
* Sets the shader's vertex property or attribute variables.
*
- * An vertex property or vertex attribute is a variable belonging to a vertex in a shader. p5.js provides some
+ * A vertex property, or vertex attribute, is a variable belonging to a vertex in a shader. p5.js provides some
* default properties, such as `aPosition`, `aNormal`, `aVertexColor`, etc. These are
* set using vertex(), normal()
* and fill() respectively. Custom properties can also
diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js
index 98dd6550e8..b1b75cd251 100644
--- a/src/webgl/ShaderGenerator.js
+++ b/src/webgl/ShaderGenerator.js
@@ -4,13 +4,13 @@
* @for p5
* @requires core
*/
-
import { parse } from 'acorn';
import { ancestor } from 'acorn-walk';
import escodegen from 'escodegen';
function shadergenerator(p5, fn) {
let GLOBAL_SHADER;
+ let BRANCH;
const oldModify = p5.Shader.prototype.modify
@@ -23,18 +23,19 @@ function shadergenerator(p5, fn) {
ecmaVersion: 2021,
locations: options.srcLocations
});
- ancestor(ast, ASTCallbacks);
+ ancestor(ast, ASTCallbacks, undefined, { varyings: {} });
const transpiledSource = escodegen.generate(ast);
generatorFunction = new Function(
- transpiledSource.slice(
- transpiledSource.indexOf("{") + 1,
- transpiledSource.lastIndexOf("}")
- )
+ transpiledSource
+ .slice(
+ transpiledSource.indexOf('{') + 1,
+ transpiledSource.lastIndexOf('}')
+ ).replaceAll(';', '')
);
} else {
generatorFunction = shaderModifier;
}
- const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations)
+ const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations);
const generatedModifyArgument = generator.generate();
return oldModify.call(this, generatedModifyArgument);
}
@@ -43,7 +44,7 @@ function shadergenerator(p5, fn) {
}
}
- // AST Transpiler Callbacks and their helpers
+ // AST Transpiler Callbacks and helper functions
function replaceBinaryOperator(codeSource) {
switch (codeSource) {
case '+': return 'add';
@@ -51,10 +52,75 @@ function shadergenerator(p5, fn) {
case '*': return 'mult';
case '/': return 'div';
case '%': return 'mod';
+ case '==':
+ case '===': return 'equalTo';
+ case '>': return 'greaterThan';
+ case '>=': return 'greaterThanEqualTo';
+ case '<': return 'lessThan';
+ case '&&': return 'and';
+ case '||': return 'or';
}
}
+ function ancestorIsUniform(ancestor) {
+ return ancestor.type === 'CallExpression'
+ && ancestor.callee?.type === 'Identifier'
+ && ancestor.callee?.name.startsWith('uniform');
+ }
+
const ASTCallbacks = {
+ UnaryExpression(node, _state, _ancestors) {
+ if (_ancestors.some(ancestorIsUniform)) { return; }
+
+ const signNode = {
+ type: 'Literal',
+ value: node.operator,
+ }
+
+ const standardReplacement = (node) => {
+ node.type = 'CallExpression'
+ node.callee = {
+ type: 'Identifier',
+ name: 'unaryNode',
+ }
+ node.arguments = [node.argument, signNode]
+ }
+
+ if (node.type === 'MemberExpression') {
+ const property = node.argument.property.name;
+ const swizzleSets = [
+ ['x', 'y', 'z', 'w'],
+ ['r', 'g', 'b', 'a'],
+ ['s', 't', 'p', 'q']
+ ];
+
+ let isSwizzle = swizzleSets.some(set =>
+ [...property].every(char => set.includes(char))
+ ) && node.argument.type === 'MemberExpression';
+
+ if (isSwizzle) {
+ node.type = 'MemberExpression';
+ node.object = {
+ type: 'CallExpression',
+ callee: {
+ type: 'Identifier',
+ name: 'unaryNode'
+ },
+ arguments: [node.argument.object, signNode],
+ };
+ node.property = {
+ type: 'Identifier',
+ name: property
+ };
+ } else {
+ standardReplacement(node);
+ }
+ } else {
+ standardReplacement(node);
+ }
+ delete node.argument;
+ delete node.operator;
+ },
VariableDeclarator(node, _state, _ancestors) {
if (node.init.callee && node.init.callee.name?.startsWith('uniform')) {
const uniformNameLiteral = {
@@ -63,63 +129,110 @@ function shadergenerator(p5, fn) {
}
node.init.arguments.unshift(uniformNameLiteral);
}
+ if (node.init.callee && node.init.callee.name?.startsWith('varying')) {
+ const varyingNameLiteral = {
+ type: 'Literal',
+ value: node.id.name
+ }
+ node.init.arguments.unshift(varyingNameLiteral);
+ _state.varyings[node.id.name] = varyingNameLiteral;
+ }
+ },
+ Identifier(node, _state, _ancestors) {
+ if (_state.varyings[node.name]
+ && !_ancestors.some(a => a.type === 'AssignmentExpression' && a.left === node)) {
+ node.type = 'ExpressionStatement';
+ node.expression = {
+ type: 'CallExpression',
+ callee: {
+ type: 'MemberExpression',
+ object: {
+ type: 'Identifier',
+ name: node.name
+ },
+ property: {
+ type: 'Identifier',
+ name: 'getValue'
+ },
+ },
+ arguments: [],
+ }
+ }
},
// The callbacks for AssignmentExpression and BinaryExpression handle
// operator overloading including +=, *= assignment expressions
+ ArrayExpression(node, _state, _ancestors) {
+ const original = JSON.parse(JSON.stringify(node));
+ node.type = 'CallExpression';
+ node.callee = {
+ type: 'Identifier',
+ name: 'dynamicNode',
+ };
+ node.arguments = [original];
+ },
AssignmentExpression(node, _state, _ancestors) {
if (node.operator !== '=') {
const methodName = replaceBinaryOperator(node.operator.replace('=',''));
const rightReplacementNode = {
type: 'CallExpression',
callee: {
- type: "MemberExpression",
+ type: 'MemberExpression',
object: node.left,
property: {
- type: "Identifier",
+ type: 'Identifier',
name: methodName,
},
- computed: false,
},
arguments: [node.right]
}
node.operator = '=';
node.right = rightReplacementNode;
}
+ if (_state.varyings[node.left.name]) {
+ node.type = 'ExpressionStatement';
+ node.expression = {
+ type: 'CallExpression',
+ callee: {
+ type: 'MemberExpression',
+ object: {
+ type: 'Identifier',
+ name: node.left.name
+ },
+ property: {
+ type: 'Identifier',
+ name: 'bridge',
+ }
+ },
+ arguments: [node.right],
+ }
+ }
},
- BinaryExpression(node, _state, ancestors) {
+ BinaryExpression(node, _state, _ancestors) {
// Don't convert uniform default values to node methods, as
// they should be evaluated at runtime, not compiled.
- const isUniform = (ancestor) => {
- return ancestor.type === 'CallExpression'
- && ancestor.callee?.type === 'Identifier'
- && ancestor.callee?.name.startsWith('uniform');
- }
- if (ancestors.some(isUniform)) {
- return;
- }
+ if (_ancestors.some(ancestorIsUniform)) { return; }
// If the left hand side of an expression is one of these types,
// we should construct a node from it.
- const unsafeTypes = ["Literal", "ArrayExpression"]
+ const unsafeTypes = ['Literal', 'ArrayExpression', 'Identifier'];
if (unsafeTypes.includes(node.left.type)) {
const leftReplacementNode = {
- type: "CallExpression",
+ type: 'CallExpression',
callee: {
- type: "Identifier",
- name: "makeNode",
+ type: 'Identifier',
+ name: 'dynamicNode',
},
- arguments: [node.left, node.right]
+ arguments: [node.left]
}
node.left = leftReplacementNode;
}
-
// Replace the binary operator with a call expression
// in other words a call to BaseNode.mult(), .div() etc.
node.type = 'CallExpression';
node.callee = {
- type: "MemberExpression",
+ type: 'MemberExpression',
object: node.left,
property: {
- type: "Identifier",
+ type: 'Identifier',
name: replaceBinaryOperator(node.operator),
},
};
@@ -127,122 +240,148 @@ function shadergenerator(p5, fn) {
},
}
- // This unfinished function lets you do 1 * 10
- // and turns it into float.mult(10)
- fn.makeNode = function(leftValue, rightValue) {
- if (typeof leftValue === 'number') {
- return new FloatNode(leftValue);
- }
- }
-
// Javascript Node API.
- // These classes are for expressing GLSL functions in Javascript without
- // needing to transpile the user's code.
-
-
class BaseNode {
constructor(isInternal, type) {
if (new.target === BaseNode) {
- throw new TypeError("Cannot construct BaseNode instances directly. This is an abstract class.");
+ throw new TypeError('Cannot construct BaseNode instances directly. This is an abstract class.');
}
this.type = type;
this.componentNames = [];
- this.swizzleChanged = false;
+ this.componentsChanged = false;
// For tracking recursion depth and creating temporary variables
this.isInternal = isInternal;
this.usedIn = [];
this.dependsOn = [];
this.srcLine = null;
+ this.usedInConditional = false;
// Stack Capture is used to get the original line of user code for Debug purposes
if (GLOBAL_SHADER.srcLocations === true && isInternal === false) {
try {
- throw new Error("StackCapture");
+ throw new Error('StackCapture');
} catch (e) {
- const lines = e.stack.split("\n");
+ const lines = e.stack.split('\n');
let userSketchLineIndex = 5;
- if (isBinaryOperatorNode(this)) { userSketchLineIndex--; };
+ if (isBinaryExpressionNode(this)) { userSketchLineIndex--; };
this.srcLine = lines[userSketchLineIndex].trim();
}
}
}
- // get type() {
- // return this._type;
- // }
-
- // set type(value) {
- // this._type = value;
- // }
addVectorComponents() {
if (this.type.startsWith('vec')) {
- const vectorDimensions = +this.type.slice(3);
+ const vectorDimensions = parseInt(this.type.slice(3));
this.componentNames = ['x', 'y', 'z', 'w'].slice(0, vectorDimensions);
-
+ const proxy = this;
for (let componentName of this.componentNames) {
- // let value = new FloatNode()
- let value = new ComponentNode(this, componentName, 'float', true);
+ let value = new ComponentNode(proxy, componentName, 'float', true);
Object.defineProperty(this, componentName, {
get() {
return value;
},
set(newValue) {
- this.swizzleChanged = true;
- value = newValue;
+ this.componentsChanged = true;
+ if (isUnaryExpressionNode(this)) {
+ this.node.value = newValue;
+ } else {
+ value = newValue;
+ }
}
})
}
}
}
+ forceTemporaryVariable() {
+ if (!(isFloatNode(this) && isVectorNode(this.parent)) || !isVariableNode(this))
+ this.useTemp = true;
+ }
+
+ assertUsedInConditional(branch) {
+ this.usedInConditional = true;
+ this.usedIn.push(branch);
+ this.forceTemporaryVariable();
+ }
+
+ isUsedInConditional() {
+ return this.usedInConditional;
+ }
+
+ checkConditionalDependencies(context) {
+ context.ifs.forEach((statement) => {
+ const isUsedSatisfied = () => statement.usedInSatisfied.length >= 1;
+ const isDepsSatisfied = () => statement.dependsOn.length === statement.dependsOnSatisfied.length;
+ if (statement.insertionPoint > -1 || !statement.usedIn.length) return;
+ if (statement.dependsOn.some(d => d.node === this) && !statement.dependsOnSatisfied.includes(this)) {
+ statement.dependsOnSatisfied.push(this);
+ }
+ if (statement.usedIn.includes(this) && !statement.usedInSatisfied.includes(this)) {
+ statement.usedInSatisfied.push(this);
+ }
+ if (isDepsSatisfied() && isUsedSatisfied()) {
+ statement.saveState(context, isDepsSatisfied(), isUsedSatisfied());
+ }
+ });
+ }
+
// The base node implements a version of toGLSL which determines whether the generated code should be stored in a temporary variable.
toGLSLBase(context){
+ let result;
if (this.shouldUseTemporaryVariable()) {
- return this.getTemporaryVariable(context);
- }
- else {
- return this.toGLSL(context);
+ let oldLength = context.declarations.length;
+ result = this.getTemporaryVariable(context);
+ let diff = context.declarations.length - 1 - oldLength;
+ diff = diff > 0 ? diff : undefined;
+ this.dependsOn.forEach(dependency => {
+ if (dependency.isVector) {
+ const dependencies = dependency.originalComponents.map((component, i) =>
+ component === dependency.currentComponents[i]
+ );
+ context.updateComponents(dependency.node, diff, dependencies);
+ } else {
+ context.updateComponents(dependency.node, diff);
+ }
+ });
+ } else {
+ result = this.toGLSL(context);
}
+ this.checkConditionalDependencies(context)
+ return result;
}
shouldUseTemporaryVariable() {
- if (this.swizzleChanged) { return true; }
- if (this.isInternal || isVariableNode(this)) { return false; }
+ if (this.componentsChanged || hasTemporaryVariable(this) || this.useTemp) { return true; }
+ if (this.isInternal || isVariableNode(this) || isConditionalNode(this) || this.type === 'sampler2D') { return false; }
+
+ // return false;
+ // Swizzles must use temporary variables as otherwise they will not be registered
let score = 0;
- score += isBinaryOperatorNode(this);
- score += isVectorNode(this) * 2;
+ score += isFunctionCallNode(this) * 2;
+ score += isBinaryExpressionNode(this) * 2;
+ score += isVectorType(this) * 3;
score += this.usedIn.length;
- return score > 3;
+ return score >= 4;
}
getTemporaryVariable(context) {
if (!this.temporaryVariable) {
this.temporaryVariable = `temp_${context.getNextID()}`;
- let line = "";
+ let line = '';
if (this.srcLine) {
line += `\n// From ${this.srcLine}\n`;
}
- if (this.swizzleChanged) {
- const valueArgs = [];
- for (let componentName of this.componentNames) {
- valueArgs.push(this[componentName])
- }
- const replacement = nodeConstructors[this.type](valueArgs)
- line += this.type + " " + this.temporaryVariable + " = " + this.toGLSL(context) + ";";
- line += `\n` + this.temporaryVariable + " = " + replacement.toGLSL(context) + ";";
- } else {
- line += this.type + " " + this.temporaryVariable + " = " + this.toGLSL(context) + ";";
- }
+ line += ' ' + this.type + ' ' + this.temporaryVariable + ' = ' + this.toGLSL(context) + ';';
context.declarations.push(line);
}
return this.temporaryVariable;
};
// Binary Operators
- add(other) { return new BinaryOperatorNode(this, this.enforceType(other), '+'); }
- sub(other) { return new BinaryOperatorNode(this, this.enforceType(other), '-'); }
- mult(other) { return new BinaryOperatorNode(this, this.enforceType(other), '*'); }
- div(other) { return new BinaryOperatorNode(this, this.enforceType(other), '/'); }
- mod(other) { return new ModulusNode(this, this.enforceType(other)); }
+ add(other) { return binaryExpressionNodeConstructor(this, this.enforceType(other), '+'); }
+ sub(other) { return binaryExpressionNodeConstructor(this, this.enforceType(other), '-'); }
+ mult(other) { return binaryExpressionNodeConstructor(this, this.enforceType(other), '*'); }
+ div(other) { return binaryExpressionNodeConstructor(this, this.enforceType(other), '/'); }
+ mod(other) { return binaryExpressionNodeConstructor(this, this.enforceType(other), '%'); }
// Check that the types of the operands are compatible.
enforceType(other){
@@ -253,37 +392,36 @@ function shadergenerator(p5, fn) {
if (!isGLSLNativeType(other.type)) {
throw new TypeError (`You've tried to perform an operation on a struct of type: ${other.type}. Try accessing a member on that struct with '.'`)
}
- if ((isFloatNode(this) || isVectorNode(this)) && isIntNode(other)) {
+ if ((isFloatType(this) || isVectorType(this)) && isIntType(other)) {
return new FloatNode(other)
}
return other;
}
- else if(typeof other === 'number') {
- if (isIntNode(this)) {
+ else if (typeof other === 'number') {
+ if (isIntType(this)) {
return new IntNode(other);
}
return new FloatNode(other);
}
- else if(Array.isArray(other)) {
- return new VectorNode(other, `vec${other.length}`)
+ else if (Array.isArray(other)) {
+ return nodeConstructors.dynamicVector(other);
+ // return nodeConstructors[`vec${other.length}`](other);
}
else {
- return new this.constructor(other);
+ return nodeConstructors[this.type](other);
}
}
toFloat() {
- if (isFloatNode(this)) {
+ if (isFloatType(this)) {
return this;
- } else if (isIntNode(this)) {
+ } else if (isIntType(this)) {
return new FloatNode(this);
- } else {
- throw new TypeError(`Can't convert from type '${this.type}' to 'float'.`)
}
}
toGLSL(context){
- throw new TypeError("Not supposed to call this function on BaseNode, which is an abstract class.");
+ throw new TypeError('Not supposed to call this function on BaseNode, which is an abstract class.');
}
}
@@ -297,9 +435,9 @@ function shadergenerator(p5, fn) {
toGLSL(context) {
if (isShaderNode(this.x)) {
let code = this.x.toGLSLBase(context);
- return isIntNode(this.x.type) ? code : `int(${code})`;
+ return isIntType(this.x.type) ? code : `int(${code})`;
}
- else if (typeof this.x === "number") {
+ else if (typeof this.x === 'number') {
return `${Math.floor(this.x)}`;
}
else {
@@ -309,17 +447,25 @@ function shadergenerator(p5, fn) {
}
class FloatNode extends BaseNode {
- constructor(x = 0, isInternal = false){
+ constructor(x = 0, isInternal = false, _parent = false){
super(isInternal, 'float');
+ if (Array.isArray(x)) {
+ x = x[0];
+ }
+ if (_parent) {
+ const { parent, name } = _parent
+ this.name = name;
+ this.parent = parent;
+ }
this.x = x;
}
toGLSL(context) {
if (isShaderNode(this.x)) {
let code = this.x.toGLSLBase(context);
- return isFloatNode(this.x) ? code : `float(${code})`;
+ return isFloatType(this.x) ? code : `float(${code})`;
}
- else if (typeof this.x === "number") {
+ else if (typeof this.x === 'number') {
return `${this.x.toFixed(4)}`;
}
else {
@@ -331,60 +477,136 @@ function shadergenerator(p5, fn) {
class VectorNode extends BaseNode {
constructor(values, type, isInternal = false) {
super(isInternal, type);
- this.componentNames = ['x', 'y', 'z', 'w'].slice(0, values.length);
- this.componentNames.forEach((component, i) => {
- this[component] = new FloatNode(values[i], true);
+ this.originalValues = conformVectorParameters(values, parseInt(type.slice(3)));
+ this.componentNames = ['x', 'y', 'z', 'w'].slice(0, this.originalValues.length);
+ }
+
+ addVectorComponents() {
+ const values = this.originalValues;
+ this.componentsChanged = false;
+
+ this.componentNames.forEach((componentName, i) => {
+ const info = { name: componentName, parent: this };
+ let value = isFloatNode(values[i]) ? values[i] : new FloatNode(values[i], true, info);
+ Object.defineProperty(this, componentName, {
+ get() {
+ return value;
+ },
+ set(newValue) {
+ this.componentsChanged = true;
+ if (isUnaryExpressionNode(this)) {
+ this.node.value = newValue;
+ } else {
+ value = isFloatNode(newValue) ? newValue : new FloatNode(newValue, true, info);
+ }
+ }
+ })
});
+ this.originalValues = this.componentNames.map(name => this[name]);
}
toGLSL(context) {
- let glslArgs = ``;
-
- this.componentNames.forEach((component, i) => {
- const comma = i === this.componentNames.length - 1 ? `` : `, `;
- glslArgs += `${this[component].toGLSLBase(context)}${comma}`;
- })
-
- return `${this.type}(${glslArgs})`;
+ if ((!this.componentsChanged || !this.defined) && !this.oldName) {
+ let glslArgs = this.componentNames.map((_name, i) => this.originalValues[i].toGLSLBase(context)).join(', ');
+ this.defined = true;
+ return `${this.type}(${glslArgs})`;
+ } else {
+ return this.temporaryVariable;
+ }
}
}
// Function Call Nodes
class FunctionCallNode extends BaseNode {
- constructor(name, args, properties, isInternal = false) {
- let inferredType = args.find((arg, i) => {
- properties.args[i] === 'genType'
- && isShaderNode(arg)
- })?.type;
- if (!inferredType) {
- let arrayArg = args.find(arg => Array.isArray(arg));
- inferredType = arrayArg ? `vec${arrayArg.length}` : undefined;
- }
- if (!inferredType) {
- inferredType = 'float';
- }
- args = args.map((arg, i) => {
+ constructor(name, userArgs, properties, isInternal = false) {
+ let functionSignature;
+ const determineFunctionSignature = (props) => {
+ let genType;
+ let similarity = 0;
+
+ const valid = userArgs.every((userArg, i) => {
+ const userType = getType(userArg);
+ let expectedArgType = props.args[i];
+
+ if (expectedArgType === 'genType') {
+ // We allow conversions from float -> vec if one argument is a vector.
+ if (genType === undefined || (genType === 'float' && userType.startsWith('vec'))) {
+ genType = userType
+ };
+ expectedArgType = genType;
+ }
+ similarity += (userType === expectedArgType);
+ return userType === expectedArgType || (userType === 'float' && expectedArgType.startsWith('vec'));
+ })
+
+ return { ...props, valid, similarity, genType }
+ }
+
+ if (Array.isArray(properties)) {
+ // Check if the right number of parameters were provided
+ let possibleOverloads = properties.filter(o => o.args.length === userArgs.length);
+ if (possibleOverloads.length === 0) {
+ const argsLengthSet = new Set();
+ const argsLengthArr = [];
+ properties.forEach((p) => argsLengthSet.add(p.args.length));
+ argsLengthSet.forEach((len) => argsLengthArr.push(`${len}`));
+ const argsLengthStr = argsLengthArr.join(' or ');
+ throw new Error(`Function '${name}' has ${properties.length} variants which expect ${argsLengthStr} arguments, but ${userArgs.length} arguments were provided.`);
+ }
+ const findBestOverload = function (best, current) {
+ current = determineFunctionSignature(current);
+ if (!current.valid) { return best; }
+ if (!best || current.similarity > best.similarity) {
+ best = current;
+ }
+ return best;
+ }
+ functionSignature = possibleOverloads.reduce(findBestOverload, null);
+ } else {
+ functionSignature = determineFunctionSignature(properties);
+ }
+
+ if (!functionSignature || !functionSignature.valid) {
+ const argsStrJoin = (args) => `(${args.map((arg) => arg).join(', ')})`;
+ const expectedArgsString = Array.isArray(properties) ?
+ properties.map(prop => argsStrJoin(prop.args)).join(' or ')
+ : argsStrJoin(properties.args);
+ const providedArgsString = argsStrJoin(userArgs.map((a)=>getType(a)));
+ throw new Error(`Function '${name}' was called with wrong arguments. Most likely, you provided mixed lengths vectors as arguments.\nExpected argument types: ${expectedArgsString}\nProvided argument types: ${providedArgsString}\nAll of the arguments with expected type 'genType' should have a matching type. If one of those is different, try to find where it was created.
+ `);
+ }
+
+ if (userArgs.length !== functionSignature.args.length) {
+ throw new Error(`Function '${name}' expects ${functionSignature.args.length} arguments, but ${userArgs.length} were provided.`);
+ }
+
+ userArgs = userArgs.map((arg, i) => {
if (!isShaderNode(arg)) {
- const typeName = properties.args[i] === 'genType' ? inferredType : properties.args[i];
+ const typeName = functionSignature.args[i] === 'genType' ? functionSignature.genType : functionSignature.args[i];
arg = nodeConstructors[typeName](arg);
+ } else if (isFloatType(arg) && functionSignature.args[i] === 'genType' && functionSignature.genType !== 'float') {
+ arg = nodeConstructors[functionSignature.genType](arg);
}
return arg;
})
- if (properties.returnType === 'genType') {
- properties.returnType = inferredType;
+
+ if (functionSignature.returnType === 'genType') {
+ functionSignature.returnType = functionSignature.genType;
}
- super(isInternal, properties.returnType);
+
+ super(isInternal, functionSignature.returnType);
+
this.name = name;
- this.args = args;
- this.argumentTypes = properties.args;
- this.addVectorComponents();
+ this.args = userArgs;
+ this.argumentTypes = functionSignature.args;
}
deconstructArgs(context) {
let argsString = this.args.map((argNode, i) => {
- if (isIntNode(argNode) && this.argumentTypes[i] != 'float') {
+ if (isIntType(argNode) && this.argumentTypes[i] != 'float') {
argNode = argNode.toFloat();
}
+ argNode.toGLSLBase(context);
return argNode.toGLSLBase(context);
}).join(', ');
return argsString;
@@ -400,10 +622,8 @@ function shadergenerator(p5, fn) {
constructor(name, type, isInternal = false) {
super(isInternal, type);
this.name = name;
- this.addVectorComponents();
}
-
toGLSL(context) {
return `${this.name}`;
}
@@ -417,158 +637,453 @@ function shadergenerator(p5, fn) {
this.type = type;
}
toGLSL(context) {
- const parentName = this.parent.toGLSLBase(context);
- // const parentName = this.parent.temporaryVariable ? this.parent.temporaryVariable : this.parent.name;
+ let parentName = this.parent.toGLSLBase(context);
+ if (!isVariableNode(this.parent) && !hasTemporaryVariable(this.parent)) {
+ parentName = `(${parentName})`;
+ }
return `${parentName}.${this.componentName}`;
}
}
+ //
+ class VaryingNode extends VariableNode {
+ constructor(name, type, isInternal = false) {
+ super(name, type, isInternal);
+ this.timesChanged = 0;
+ this.tempVars = 0;
+ }
+
+ getValue() {
+ const context = GLOBAL_SHADER.context;
+ if (!context.varyings[this.name] || !this.timesChanged) {
+ return this;
+ }
+
+ let values = context.varyings[this.name].splice(0, this.timesChanged);
+ let snapshot;
+ values.forEach((val, i) => {
+ let { value } = val;
+ context.declarations.push(` ${this.name} = ${value.toGLSLBase(context)};`);
+ if (i === values.length - 1) {
+ const tempName = `${this.name}_${this.tempVars++}`
+ snapshot = dynamicAddSwizzleTrap(new VariableNode(tempName, this.type, true));
+ context.declarations.push(` ${this.type} ${tempName} = ${this.name};`);
+ }
+ });
+
+ this.timesChanged = 0;
+ return snapshot;
+ }
+
+ bridge(value) {
+ if (!isShaderNode(value) || this.type.startsWith('vec') && getType(value) === 'float') {
+ value = nodeConstructors[this.type](value)
+ }
+ GLOBAL_SHADER.registerVarying(this, value);
+ this.timesChanged += 1
+ }
+ }
+
// Binary Operator Nodes
- class BinaryOperatorNode extends BaseNode {
- constructor(a, b, operator, isInternal = false) {
+ class BinaryExpressionNode extends BaseNode {
+ constructor(left, right, operator, isInternal = false) {
super(isInternal, null);
- this.op = operator;
- this.a = a;
- this.b = b;
- for (const operand of [a, b]) {
+ this.operator = operator;
+ this.left = left;
+ this.right = right;
+ for (const operand of [left, right]) {
operand.usedIn.push(this);
}
this.type = this.determineType();
- this.addVectorComponents();
}
- // We know that both this.a and this.b are nodes because of BaseNode.enforceType
+ // We know that both this.left and this.right are nodes because of BaseNode.enforceType
determineType() {
- if (this.a.type === this.b.type) {
- return this.a.type;
+ if (['==', '>', '>=', '<', '<=', '||', '!', '&&'].includes(this.operator)) {
+ return 'bool';
+ }
+ else if (this.left.type === this.right.type) {
+ return this.left.type;
}
- else if (isVectorNode(this.a) && isFloatNode(this.b)) {
- return this.a.type;
+ else if (isVectorType(this.left) && isFloatType(this.right)) {
+ return this.left.type;
}
- else if (isVectorNode(this.b) && isFloatNode(this.a)) {
- return this.b.type;
+ else if (isVectorType(this.right) && isFloatType(this.left)) {
+ return this.right.type;
}
- else if (isFloatNode(this.a) && isIntNode(this.b)
- || isIntNode(this.a) && isFloatNode(this.b)
+ else if (isFloatType(this.left) && isIntType(this.right)
+ || isIntType(this.left) && isFloatType(this.right)
) {
return 'float';
}
else {
- throw new Error("Incompatible types for binary operator");
+ throw new Error('Incompatible types for binary operator');
}
}
processOperand(operand, context) {
if (operand.temporaryVariable) { return operand.temporaryVariable; }
let code = operand.toGLSLBase(context);
- if (isBinaryOperatorNode(operand) && !operand.temporaryVariable) {
+ if (isBinaryExpressionNode(operand) && !operand.temporaryVariable) {
code = `(${code})`;
}
- if (this.type === 'float' && isIntNode(operand)) {
+ if (this.type === 'float' && isIntType(operand)) {
code = `float(${code})`;
}
return code;
}
toGLSL(context) {
- const a = this.processOperand(this.a, context);
- const b = this.processOperand(this.b, context);
- return `${a} ${this.op} ${b}`;
+ const a = this.processOperand(this.left, context);
+ const b = this.processOperand(this.right, context);
+ return `${a} ${this.operator} ${b}`;
}
}
- class ModulusNode extends BinaryOperatorNode {
- constructor(a, b) {
- super(a, b);
+ class ModulusNode extends BinaryExpressionNode {
+ constructor(a, b, isInternal) {
+ super(a, b, isInternal);
}
toGLSL(context) {
// Switch on type between % or mod()
- if (isVectorNode(this) || isFloatNode(this)) {
- return `mod(${this.a.toGLSLBase(context)}, ${this.b.toGLSLBase(context)})`;
+ if (isVectorType(this) || isFloatType(this)) {
+ return `mod(${this.left.toGLSLBase(context)}, ${this.right.toGLSLBase(context)})`;
}
- return `${this.processOperand(context, this.a)} % ${this.processOperand(context, this.b)}`;
+ return `${this.processOperand(context, this.left)} % ${this.processOperand(context, this.right)}`;
}
}
- // TODO: finish If Node
- class ConditionalNode extends BaseNode {
- constructor(value) {
- super(value);
- this.value = value;
- this.condition = null;
- this.thenBranch = null;
+ class UnaryExpressionNode extends BaseNode {
+ constructor(node, operator, isInternal = false) {
+ super(isInternal, node.type)
+ this.node = node;
+ this.operator = operator;
+ }
+
+ toGLSL(context) {
+ let mainStr = this.node.toGLSLBase(context);
+ if (!isVariableNode(this.node) && !hasTemporaryVariable(this.node) && !isPrimitiveNode(this.node)) {
+ mainStr = `(${mainStr})`
+ }
+ return `${this.operator}${mainStr}`
+ }
+ }
+
+ // Conditions and logical modifiers
+ BaseNode.prototype.equalTo = function(other) {
+ return binaryExpressionNodeConstructor(this, this.enforceType(other), '==');
+ }
+
+ BaseNode.prototype.greaterThan = function(other) {
+ return binaryExpressionNodeConstructor(this, this.enforceType(other), '>');
+ }
+
+ BaseNode.prototype.greaterThanEqualTo = function(other) {
+ return binaryExpressionNodeConstructor(this, this.enforceType(other), '>=');
+ }
+
+ BaseNode.prototype.lessThan = function(other) {
+ return binaryExpressionNodeConstructor(this, this.enforceType(other), '<');
+ }
+
+ BaseNode.prototype.lessThanEqualTo = function(other) {
+ return binaryExpressionNodeConstructor(this, this.enforceType(other), '<='); }
+
+ BaseNode.prototype.not = function() {
+ return new UnaryExpressionNode(this.condition, '!', true);
+ }
+
+ BaseNode.prototype.or = function(other) {
+ return new binaryExpressionNodeConstructor(this, this.enforceType(other), '||', true);
+ }
+
+ BaseNode.prototype.and = function(other) {
+ return new binaryExpressionNodeConstructor(this, this.enforceType(other), '&&', true);
+ }
+
+ function branch(callback) {
+ const branch = new BranchNode();
+ callback();
+ BRANCH = null;
+ return branch;
+ }
+
+ class ConditionalNode {
+ constructor(condition, branchCallback) {
+ this.dependsOn = [];
+ this.usedIn = [];
+ this.dependsOnSatisfied = [];
+ this.usedInSatisfied = [];
+ this.states = [];
+ this.if(condition, branchCallback);
+ this.insertionPoint = -1;
+ this.elseIfs = [];
this.elseBranch = null;
+ GLOBAL_SHADER.context.ifs.push(this);
+ }
+
+ if(condition, branchCallback) {
+ this.condition = condition;
+ this.conditionString = condition.toGLSL(GLOBAL_SHADER.context);
+ this.ifBranch = branch(branchCallback);
+ this.ifBranch.parent = this;
+ }
+
+ elseIf(condition, branchCallback) {
+ let elseBranch = branch(branchCallback);
+ branchCallback.parent = this;
+ this.elseIfs.push({ condition, elseBranch });
+ return this;
}
- // conditions
- equalTo(value){}
- greaterThan(value) {}
- greaterThanEqualTo(value) {}
- lessThan(value) {}
- lessThanEqualTo(value) {}
- // modifiers
- not() {}
- or() {}
- and() {}
- // returns
- thenReturn(value) {}
- elseReturn(value) {}
- //
+
+ else(branchCallback) {
+ this.elseBranch = branch(branchCallback);
+ this.elseBranch.parent = this
+ return this;
+ }
+
thenDiscard() {
- new ConditionalDiscard(this.condition);
+ return new ConditionalDiscard(this.condition);
};
+
+ saveState(context, usedInSatisfied, dependsOnSatisfied) {
+ this.states.push({
+ line: context.declarations.length,
+ usedInSatisfied,
+ dependsOnSatisfied
+ });
+ this.insertionPoint = context.declarations.length - 1;
+ }
+
+ toGLSL(context) {
+ const oldLength = context.declarations.length;
+ this.dependsOn.forEach(dep => context.updateComponents(dep.node));
+ const newLength = context.declarations.length;
+ const diff = newLength - oldLength;
+ this.insertionPoint += diff;
+
+ let codelines = [
+ `\n if (${this.conditionString}) {`,
+ `\n ${this.ifBranch.toGLSL(context)}`,
+ `\n }`
+ ];
+
+ if (this.elseIfs.length) {
+ this.elseIfs.forEach((elif) => {
+ let { condition, elseBranch } = elif;
+ codelines.push(` else if (${condition.toGLSL(context)}) {`);
+ codelines.push(`\n ${elseBranch.toGLSL(context)}`);
+ codelines.push(`\n }`);
+ })
+ }
+
+ if (this.elseBranch) {
+ codelines.push(` else {`);
+ codelines.push(`\n ${this.elseBranch.toGLSL(context)}`);
+ codelines.push(`\n }\n`);
+ }
+ codelines.push('\n');
+ return codelines.flat().join('');
+ }
};
- class ConditionalDiscard extends BaseNode {
+ fn.assign = function(node, value) {
+ if (!BRANCH) {
+ throw new error('assign() is supposed to be used inside of conditional branchs. Use the "=" operator as normal otherwise.');
+ }
+ BRANCH.assign(node, value);
+ }
+
+ class BranchNode {
+ constructor() {
+ BRANCH = this;
+ this.statements = [];
+ this.assignments = [];
+ this.dependsOn = [];
+ this.declarations = [];
+ let parent = null;
+ Object.defineProperty(this, 'parent', {
+ get() {
+ return parent;
+ },
+ set(newParent) {
+ newParent.dependsOn.push(...this.dependsOn)
+ parent = newParent;
+ }
+ })
+ }
+
+ assign(node, value) {
+ if (!isShaderNode(value) || value.type !== node.type) {
+ value = nodeConstructors[node.type](value);
+ this.declarations.push(value);
+ this.assignments.push({ node });
+ } else {
+ this.assignments.push({ node, value });
+ }
+ node = node.parent ? node.parent : node;
+ value = value.parent ? value.parent : value;
+ if ([node, value].some(n => this.dependsOn.some(d=>d.node===n))) {
+ return;
+ }
+ node.assertUsedInConditional(this);
+ this.dependsOn.push(makeDependencyObject(node))
+ if (value.shouldUseTemporaryVariable()) {
+ value.assertUsedInConditional(this);
+ this.dependsOn.push(makeDependencyObject(value));
+ }
+ }
+
+ toGLSL(context) {
+ let declarationsIndex = 0;
+ this.assignments.forEach(({ node, value }) => {
+ let statement;
+ let result;
+
+ if (!value) {
+ let decl = this.declarations[declarationsIndex];
+ declarationsIndex++;
+ decl.temporaryVariable = `temp_${context.getNextID()}`;
+ this.statements.push(
+ `${decl.type} ${decl.temporaryVariable} = ${decl.toGLSL(context)};`
+ );
+ result = decl.toGLSLBase(context);
+ } else {
+ result = value.toGLSLBase(context);
+ }
+
+ if (isVariableNode(node) || hasTemporaryVariable(node)) {
+ statement = `${node.toGLSLBase(context)} = ${result};`;
+ }
+ else if (isFloatNode(node) && node.name) {
+ statement = `${node.parent.toGLSLBase(context)}.${node.name} = ${result};`;
+ }
+ else {
+ node.temporaryVariable = `temp_${context.getNextID()}`;
+ statement = `${node.type} ${node.toGLSLBase(context)} = ${result};`
+ }
+
+ this.statements.push(statement);
+ })
+
+ return this.statements.join(`\n `);
+ }
+ }
+
+ class ConditionalDiscard {
constructor(condition){
this.condition = condition;
}
toGLSL(context) {
- context.discardConditions.push(`if(${this.condition}{discard;})`);
+ context.discardConditions.push(`if (${this.condition}{discard;})`);
}
}
- fn.if = function (value) {
- return new ConditionalNode(value);
+ // Node Helper functions
+ function getType(node) {
+ if (isShaderNode(node)) { return node.type; }
+ else if (Array.isArray(node) && node.length > 1) { return `vec${node.length}`; }
+ else if (typeof node === 'number' || (Array.isArray(node) && node.length === 1)) {
+ return 'float';
+ }
+ return undefined;
+ }
+
+ function computeVectorLength(values) {
+ let length = 0;
+ if (Array.isArray(values)) {
+ for(let val of values) {
+ if (isVectorType(val)) {
+ length += parseInt(val.type.slice(3));
+ }
+ else length += 1;
+ }
+ }
+ else if (isVectorType(values)) {
+ length += parseInt(val.type.slice(3));
+ }
+ if (![2, 3, 4].includes(length)) {
+ throw new Error(`You have attempted to construct a vector with ${length} values. Only vec2, vec3, and vec4 types are supported.`)
+ }
+ return length
+ }
+
+ fn.dynamicNode = function (input) {
+ if (isShaderNode(input)) {
+ return input;
+ }
+ else if (typeof input === 'number') {
+ return new FloatNode(input);
+ }
+ else if (Array.isArray(input)) {
+ return nodeConstructors.dynamicVector(input);
+ }
+ }
+
+ // For replacing unary expressions
+ fn.unaryNode = function(input, sign) {
+ input = dynamicNode(input);
+ return dynamicAddSwizzleTrap(new UnaryExpressionNode(input, sign));
}
- // Node Helper functions
function isShaderNode(node) {
return (node instanceof BaseNode);
}
- function isIntNode(node) {
+ function isIntType(node) {
return (isShaderNode(node) && (node.type === 'int'));
}
- function isFloatNode(node) {
+ function isFloatType(node) {
return (isShaderNode(node) && (node.type === 'float'));
}
- function isVectorNode(node) {
+ function isFloatNode(node) {
+ return (node instanceof FloatNode);
+ }
+
+ function isVectorType(node) {
return (isShaderNode(node) && (node.type === 'vec2'|| node.type === 'vec3' || node.type === 'vec4'));
}
- function isBinaryOperatorNode(node) {
- return (node instanceof BinaryOperatorNode);
+ function isBinaryExpressionNode(node) {
+ return (node instanceof BinaryExpressionNode);
}
function isVariableNode(node) {
- return (node instanceof VariableNode || node instanceof ComponentNode || typeof(node.temporaryVariable) != 'undefined');
+ return (node instanceof VariableNode || node instanceof ComponentNode);
}
- // Helper function to check if a type is a user defined struct or native type
- function isGLSLNativeType(typeName) {
- // Supported types for now
- const glslNativeTypes = ['int', 'float', 'vec2', 'vec3', 'vec4', 'sampler2D'];
- return glslNativeTypes.includes(typeName);
+ function isConditionalNode(node) {
+ return (node instanceof ConditionalNode || node instanceof BranchNode)
}
- // Helper function to check if a type is a user defined struct or native type
- function isGLSLNativeType(typeName) {
- // Supported types for now
- const glslNativeTypes = ['int', 'float', 'vec2', 'vec3', 'vec4', 'sampler2D'];
- return glslNativeTypes.includes(typeName);
- }
+ function hasTemporaryVariable(node) {
+ return (node.temporaryVariable);
+ }
+
+ function isPrimitiveNode(node) {
+ return (node instanceof FloatNode || node instanceof IntNode || node instanceof VectorNode);
+ }
+
+ function isFunctionCallNode(node) {
+ return (node instanceof FunctionCallNode);
+ }
+
+ function isVectorNode(node) {
+ return (node instanceof VectorNode)
+ }
+
+ function isUnaryExpressionNode(node) {
+ return (node instanceof UnaryExpressionNode)
+ }
+
+ // Helper function to check if a type is a user defined struct or native type
+ function isGLSLNativeType(typeName) {
+ // Supported types for now
+ const glslNativeTypes = ['int', 'float', 'vec2', 'vec3', 'vec4', 'sampler2D'];
+ return glslNativeTypes.includes(typeName);
+ }
// Shader Generator
// This class is responsible for converting the nodes into an object containing GLSL code, to be used by p5.Shader.modify
@@ -577,13 +1092,15 @@ function shadergenerator(p5, fn) {
constructor(userCallback, originalShader, srcLocations) {
GLOBAL_SHADER = this;
this.userCallback = userCallback;
- this.userCallback = userCallback;
this.srcLocations = srcLocations;
this.cleanup = () => {};
this.generateHookOverrides(originalShader);
this.output = {
+ vertexDeclarations: new Set(),
+ fragmentDeclarations: new Set(),
uniforms: {},
}
+ this.uniformNodes = [];
this.resetGLSLContext();
this.isGenerating = false;
}
@@ -595,6 +1112,8 @@ function shadergenerator(p5, fn) {
this.isGenerating = true;
this.userCallback();
+ this.output.vertexDeclarations = [...this.output.vertexDeclarations].join('\n');
+ this.output.fragmentDeclarations = [...this.output.fragmentDeclarations].join('\n');
this.isGenerating = false;
this.cleanup();
@@ -613,6 +1132,8 @@ function shadergenerator(p5, fn) {
Object.keys(availableHooks).forEach((hookName) => {
const hookTypes = originalShader.hookTypes(hookName);
+
+ // These functions are where the user code is executed
this[hookTypes.name] = function(userCallback) {
// Create the initial nodes which are passed to the user callback
// Also generate a string of the arguments for the code generation
@@ -624,12 +1145,12 @@ function shadergenerator(p5, fn) {
if (!isGLSLNativeType(parameter.type.typeName)) {
const structArg = {};
parameter.type.properties.forEach((property) => {
- structArg[property.name] = new VariableNode(`${parameter.name}.${property.name}`, property.type.typeName, true);
+ structArg[property.name] = variableConstructor(`${parameter.name}.${property.name}`, property.type.typeName, true);
});
argNodes.push(structArg);
} else {
argNodes.push(
- new VariableNode(parameter.name, parameter.type.typeName, true)
+ variableConstructor(parameter.name, parameter.type.typeName, true)
);
}
const qualifiers = parameter.type.qualifiers.length > 0 ? parameter.type.qualifiers.join(' ') : '';
@@ -643,34 +1164,55 @@ function shadergenerator(p5, fn) {
// If the expected return type is a struct we need to evaluate each of its properties
if (!isGLSLNativeType(expectedReturnType.typeName)) {
Object.entries(returnedValue).forEach(([propertyName, propertyNode]) => {
+ propertyNode = dynamicNode(propertyNode);
toGLSLResults[propertyName] = propertyNode.toGLSLBase(this.context);
- })
+ this.context.updateComponents(propertyNode);
+ });
} else {
- // We can accept raw numbers or arrays otherwise
if (!isShaderNode(returnedValue)) {
returnedValue = nodeConstructors[expectedReturnType.typeName](returnedValue)
+ } else if (isFloatType(returnedValue) && expectedReturnType.typeName.startsWith('vec')) {
+ returnedValue = nodeConstructors[expectedReturnType.typeName](returnedValue);
}
toGLSLResults['notAProperty'] = returnedValue.toGLSLBase(this.context);
+ this.context.updateComponents(returnedValue);
}
+ this.context.ifs.forEach((statement) => {
+ if (statement.usedIn.length === 0) { return; }
+ const lines = statement.toGLSL(this.context);
+ this.context.declarations.splice(statement.insertionPoint, 0, lines);
+ })
// Build the final GLSL string.
// The order of this code is a bit confusing, we need to call toGLSLBase
let codeLines = [
`(${argsArray.join(', ')}) {`,
...this.context.declarations,
- `${hookTypes.returnType.typeName} finalReturnValue;`
+ `\n ${hookTypes.returnType.typeName} finalReturnValue;`
];
Object.entries(toGLSLResults).forEach(([propertyName, result]) => {
const propString = expectedReturnType.properties ? `.${propertyName}` : '';
- codeLines.push(`finalReturnValue${propString} = ${result};`)
- })
-
- codeLines.push('return finalReturnValue;', '}');
+ codeLines.push(` finalReturnValue${propString} = ${result};`)
+ });
+
+ this.context.declarations = [];
+ for (let key in this.context.varyings) {
+ const declArray = this.context.varyings[key];
+ const finalVaryingAssignments = [];
+ declArray.forEach(obj => {
+ const { node, value } = obj;
+ finalVaryingAssignments.push(` ${node.name} = ${value.toGLSLBase(this.context)};`)
+ finalVaryingAssignments.unshift(...this.context.declarations);
+ node.timesChanged = 0;
+ });
+ codeLines.push(...finalVaryingAssignments);
+ }
+
+ codeLines.push(' return finalReturnValue;', '}');
this.output[hookName] = codeLines.join('\n');
this.resetGLSLContext();
}
-
windowOverrides[hookTypes.name] = window[hookTypes.name];
// Expose the Functions to global scope for users to use
@@ -678,6 +1220,7 @@ function shadergenerator(p5, fn) {
GLOBAL_SHADER[hookTypes.name](userOverride);
};
});
+
this.cleanup = () => {
for (const key in windowOverrides) {
@@ -686,35 +1229,168 @@ function shadergenerator(p5, fn) {
};
}
+ registerVarying(node, value) {
+ if (!Array.isArray(this.context.varyings[node.name])) {
+ this.context.varyings[node.name] = [];
+ }
+ this.context.varyings[node.name].push({ node, value });
+ this.output.vertexDeclarations.add(`OUT ${node.type} ${node.name};`);
+ this.output.fragmentDeclarations.add(`IN ${node.type} ${node.name};`);
+ }
+
resetGLSLContext() {
+ this.uniformNodes.forEach((node) => {
+ node.usedIn = [];
+ node.temporaryVariable = undefined;
+ });
this.context = {
id: 0,
- getNextID: function() { return this.id++ },
+ getNextID() { return this.id++ },
declarations: [],
+ varyings: [],
+ ifs: [],
+ updateComponents: function(node, _emplaceAt, _changedComponents) {
+ if (node.componentsChanged) {
+ if (!_changedComponents) {
+ _changedComponents = node.componentNames.map(() => true);
+ }
+ const lines = [];
+ if (isVectorNode(node)) {
+ node.componentNames.forEach((name, i) => {
+ if (!_changedComponents[i]) return;
+ if (node[name] !== node.originalValues[i]) {
+ const replacement = nodeConstructors['float'](node[name]);
+ const line = ` ${node.temporaryVariable}.${name} = ${replacement.toGLSLBase(this)};`;
+ lines.push(line);
+ }
+ });
+ } else {
+ const components = node.componentNames.map((name) => {
+ return node[name]
+ });
+ const replacement = nodeConstructors[node.type](components);
+ const line = ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this)};`
+ lines.push(line);
+ }
+ if (_emplaceAt) {
+ this.declarations.splice(_emplaceAt, 0, ...lines)
+ } else {
+ this.declarations.push(...lines);
+ }
+ node.componentsChanged = false;
+ }
+ }
}
+ this.uniformNodes = [];
}
}
// User function helpers
+ function makeDependencyObject(dep) {
+ if (isVectorType(dep)) {
+ return {
+ node: dep,
+ isVector: true,
+ originalComponents: [...dep.componentNames.map(name => dep[name])],
+ get currentComponents() {
+ return dep.componentNames.map(name => dep[name]);
+ }
+ };
+ } else {
+ return {
+ node: dep,
+ isVector: false
+ };
+ }
+ }
+
+ function makeDependencyArray(dependencies) {
+ return dependencies.map(dep => makeDependencyObject(dep));
+ }
+
function conformVectorParameters(value, vectorDimensions) {
- // Allow arguments as arrays ([0,0,0,0]) or not (0,0,0,0)
+ // Allow arguments as arrays or otherwise. The following are all equivalent:
+ // ([0,0,0,0]) (0,0,0,0) (0) ([0])
+ if (!Array.isArray(value)) {
+ value = [value];
+ }
value = value.flat();
+ value = value.map(val => {
+ if (isVectorType(val)) {
+ const componentArray = val.componentNames.map(comp => val[comp]);
+ return componentArray;
+ } else {
+ return val;
+ }
+ }).flat();
// Populate arguments so uniformVector3(0) becomes [0,0,0]
- if (value.length === 1) {
+ if (value.length === 1 && !isVectorNode(value[0])) {
value = Array(vectorDimensions).fill(value[0]);
}
return value;
}
+ function swizzleTrap(size) {
+ const swizzleSets = [
+ ['x', 'y', 'z', 'w'],
+ ['r', 'g', 'b', 'a'],
+ ['s', 't', 'p', 'q']
+ ].map(s => s.slice(0, size));
+ return {
+ get(target, property, receiver) {
+ if (property in target) {
+ return Reflect.get(...arguments);
+ } else {
+ for (const set of swizzleSets) {
+ if ([...property].every(char => set.includes(char))) {
+ if (property.length === 1) {
+ return target[swizzleSets[0][set.indexOf(property[0])]]
+ }
+ const components = [...property].map(char => {
+ const index = set.indexOf(char);
+ const mappedChar = swizzleSets[0][index];
+ return target[mappedChar];
+ });
+
+ const type = `vec${property.length}`;
+ return nodeConstructors[type](components);
+ }
+ }
+ }
+ },
+ set(target, property, value, receiver) {
+ for (const set of swizzleSets) {
+ const propertyCharArray = [...property];
+ if (propertyCharArray.every(char => set.includes(char))) {
+ const newValues = Array.isArray(value) ? value : Array(property.length).fill(value);
+ propertyCharArray.forEach((char, i) => {
+ const index = set.indexOf(char);
+ const realProperty = swizzleSets[0][index];
+ const descriptor = Object.getOwnPropertyDescriptor(target, realProperty);
+ Reflect.set(target, realProperty, newValues[i], receiver);
+ });
+ return true;
+ }
+ }
+ return Reflect.set(...arguments);
+ }
+ }
+ }
+
// User functions
- fn.instanceID = function() {
- return new VariableNode('gl_InstanceID', 'int');
+ fn.If = function (condition, branch) {
+ return new ConditionalNode(condition, branch);
}
- fn.discard = function() {
- return new VariableNode('discard', 'keyword');
+ fn.instanceID = function() {
+ return variableConstructor('gl_InstanceID', 'int');
}
+ fn.getTexture = function(...userArgs) {
+ const props = { args: ['sampler2D', 'vec2'], returnType: 'vec4', isp5Function: true };
+ return fnNodeConstructor('getTexture', userArgs, props);
+ }
+
// Generating uniformFloat, uniformVec, createFloat, etc functions
// Maps a GLSL type to the name suffix for method names
const GLSLTypesToIdentifiers = {
@@ -726,12 +1402,59 @@ function shadergenerator(p5, fn) {
sampler2D: 'Texture',
};
+ function dynamicAddSwizzleTrap(node, _size) {
+ if (node.type.startsWith('vec') || _size) {
+ const size = _size ? _size : parseInt(node.type.slice(3));
+ node = new Proxy(node, swizzleTrap(size));
+ node.addVectorComponents();
+ }
+ return node;
+ }
+
+ function binaryExpressionNodeConstructor(a, b, operator, isInternal) {
+ let node;
+ if (operator === '%') {
+ node = new ModulusNode(a, b);
+ } else {
+ node = new BinaryExpressionNode(a, b, operator, isInternal);
+ }
+ return dynamicAddSwizzleTrap(node);
+ }
+
+ function variableConstructor(name, type, isInternal) {
+ const node = new VariableNode(name, type, isInternal);
+ return dynamicAddSwizzleTrap(node);
+ }
+
+ function fnNodeConstructor(name, userArgs, properties, isInternal) {
+ let node = new FunctionCallNode(name, userArgs, properties, isInternal);
+ node = dynamicAddSwizzleTrap(node);
+ node.dependsOn = makeDependencyArray(node.args);
+ const dependsOnConditionals = node.args.map(arg => {
+ const conditionals = arg.usedIn.filter(n => isConditionalNode(n)).map(c => {
+ if (c instanceof BranchNode) {
+ return c.parent;
+ } else {
+ return c;
+ }
+ });
+ return conditionals;
+ }).flat();
+ dependsOnConditionals.forEach(conditional => conditional.usedIn.push(node));
+
+ return node;
+ }
+
const nodeConstructors = {
int: (value) => new IntNode(value),
float: (value) => new FloatNode(value),
- vec2: (value) => new VectorNode(value, 'vec2'),
- vec3: (value) => new VectorNode(value, 'vec3'),
- vec4: (value) => new VectorNode(value, 'vec4'),
+ vec2: (value) => dynamicAddSwizzleTrap(new VectorNode(value, 'vec2')),
+ vec3: (value) => dynamicAddSwizzleTrap(new VectorNode(value, 'vec3')),
+ vec4: (value) => dynamicAddSwizzleTrap(new VectorNode(value, 'vec4')),
+ dynamicVector: function(value) {
+ const size = computeVectorLength(value);
+ return this[`vec${size}`](value);
+ },
};
for (const glslType in GLSLTypesToIdentifiers) {
@@ -741,15 +1464,15 @@ function shadergenerator(p5, fn) {
ShaderGenerator.prototype[uniformMethodName] = function(...args) {
let [name, ...defaultValue] = args;
-
- if(glslType.startsWith('vec')) {
- defaultValue = conformVectorParameters(defaultValue, +glslType.slice(3));
+ if (glslType.startsWith('vec') && !(defaultValue[0] instanceof Function)) {
+ defaultValue = conformVectorParameters(defaultValue, parseInt(glslType.slice(3)));
this.output.uniforms[`${glslType} ${name}`] = defaultValue;
}
else {
this.output.uniforms[`${glslType} ${name}`] = defaultValue[0];
}
- const uniform = new VariableNode(name, glslType, false);
+ const uniform = variableConstructor(name, glslType, false);
+ this.uniformNodes.push(uniform);
return uniform;
};
@@ -757,14 +1480,24 @@ function shadergenerator(p5, fn) {
return GLOBAL_SHADER[uniformMethodName](...args);
};
+
// We don't need a createTexture method.
if (glslType === 'sampler2D') { continue; }
+ const varyingMethodName = `varying${typeIdentifier}`;
+ ShaderGenerator.prototype[varyingMethodName] = function(name) {
+ return dynamicAddSwizzleTrap(new VaryingNode(name, glslType, false));
+ }
+
+ fn[varyingMethodName] = function (name) {
+ return GLOBAL_SHADER[varyingMethodName](name);
+ };
+
// Generate the create*() Methods for creating variables in shaders
const createMethodName = `create${typeIdentifier}`;
fn[createMethodName] = function (...value) {
- if(glslType.startsWith('vec')) {
- value = conformVectorParameters(value, +glslType.slice(3));
+ if (glslType.startsWith('vec')) {
+ value = conformVectorParameters(value, parseInt(glslType.slice(3)));
} else {
value = value[0];
}
@@ -781,7 +1514,10 @@ function shadergenerator(p5, fn) {
'acosh': { args: ['genType'], returnType: 'genType', isp5Function: false},
'asin': { args: ['genType'], returnType: 'genType', isp5Function: true},
'asinh': { args: ['genType'], returnType: 'genType', isp5Function: false},
- 'atan': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: false},
+ 'atan': [
+ { args: ['genType'], returnType: 'genType', isp5Function: false},
+ { args: ['genType', 'genType'], returnType: 'genType', isp5Function: false},
+ ],
'atanh': { args: ['genType'], returnType: 'genType', isp5Function: false},
'cos': { args: ['genType'], returnType: 'genType', isp5Function: true},
'cosh': { args: ['genType'], returnType: 'genType', isp5Function: false},
@@ -809,16 +1545,28 @@ function shadergenerator(p5, fn) {
// 'isnan': {},
'log': { args: ['genType'], returnType: 'genType', isp5Function: true},
'log2': { args: ['genType'], returnType: 'genType', isp5Function: false},
- 'max': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: true},
- 'min': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: true},
- 'mix': { args: ['genType', 'genType', 'genType'], returnType: 'genType', isp5Function: false},
+ 'max': [
+ { args: ['genType', 'genType'], returnType: 'genType', isp5Function: true},
+ { args: ['genType', 'float'], returnType: 'genType', isp5Function: true},
+ ],
+ 'min': [
+ { args: ['genType', 'genType'], returnType: 'genType', isp5Function: true},
+ { args: ['genType', 'float'], returnType: 'genType', isp5Function: true},
+ ],
+ 'mix': [
+ { args: ['genType', 'genType', 'genType'], returnType: 'genType', isp5Function: false},
+ { args: ['genType', 'genType', 'float'], returnType: 'genType', isp5Function: false},
+ ],
// 'mod': {},
// 'modf': {},
'pow': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: true},
'round': { args: ['genType'], returnType: 'genType', isp5Function: true},
'roundEven': { args: ['genType'], returnType: 'genType', isp5Function: false},
// 'sign': {},
- 'smoothstep': { args: ['genType', 'genType', 'genType'], returnType: 'genType', isp5Function: false},
+ 'smoothstep': [
+ { args: ['genType', 'genType', 'genType'], returnType: 'genType', isp5Function: false},
+ { args: ['float', 'float', 'genType'], returnType: 'genType', isp5Function: false},
+ ],
'sqrt': { args: ['genType'], returnType: 'genType', isp5Function: true},
'step': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: false},
'trunc': { args: ['genType'], returnType: 'genType', isp5Function: false},
@@ -834,16 +1582,18 @@ function shadergenerator(p5, fn) {
// 'notEqual': {},
'reflect': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: false},
'refract': { args: ['genType', 'genType', 'float'], returnType: 'genType', isp5Function: false},
- // Texture sampling
+
+ ////////// Texture sampling //////////
'texture': {args: ['sampler2D', 'vec2'], returnType: 'vec4', isp5Function: true},
}
Object.entries(builtInGLSLFunctions).forEach(([functionName, properties]) => {
- if (properties.isp5Function) {
+ const isp5Function = Array.isArray(properties) ? properties[0].isp5Function : properties.isp5Function;
+ if (isp5Function) {
const originalFn = fn[functionName];
fn[functionName] = function (...args) {
if (GLOBAL_SHADER?.isGenerating) {
- return new FunctionCallNode(functionName, args, properties)
+ return fnNodeConstructor(functionName, args, properties)
} else {
return originalFn.apply(this, args);
}
@@ -851,7 +1601,7 @@ function shadergenerator(p5, fn) {
} else {
fn[functionName] = function (...args) {
if (GLOBAL_SHADER?.isGenerating) {
- return new FunctionCallNode(functionName, args, properties);
+ return new fnNodeConstructor(functionName, args, properties);
} else {
p5._friendlyError(
`It looks like you've called ${functionName} outside of a shader's modify() function.`
@@ -860,15 +1610,6 @@ function shadergenerator(p5, fn) {
}
}
})
-
- // const oldTexture = p5.prototype.texture;
- // p5.prototype.texture = function(...args) {
- // if (isShaderNode(args[0])) {
- // return new FunctionCallNode('texture', args, 'vec4');
- // } else {
- // return oldTexture.apply(this, args);
- // }
- // }
}
export default shadergenerator;
diff --git a/src/webgl/p5.Geometry.js b/src/webgl/p5.Geometry.js
index c600e7d910..22e3a481c4 100644
--- a/src/webgl/p5.Geometry.js
+++ b/src/webgl/p5.Geometry.js
@@ -1738,7 +1738,7 @@ class Geometry {
/** Sets the shader's vertex property or attribute variables.
*
- * An vertex property or vertex attribute is a variable belonging to a vertex in a shader. p5.js provides some
+ * A vertex property, or vertex attribute, is a variable belonging to a vertex in a shader. p5.js provides some
* default properties, such as `aPosition`, `aNormal`, `aVertexColor`, etc. These are
* set using vertex(), normal()
* and fill() respectively. Custom properties can also