From 75f252a390702d99c4926bd9a1fc756e513172e3 Mon Sep 17 00:00:00 2001 From: lp407 <lukeplowden@gmail.com> Date: Thu, 13 Feb 2025 15:52:16 +0000 Subject: [PATCH 01/54] local dev server vite path changed --- preview/global/index.html | 4 ++-- preview/global/vite.config.mjs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/preview/global/index.html b/preview/global/index.html index 7c8fa7d0d4..3235c1e5bd 100644 --- a/preview/global/index.html +++ b/preview/global/index.html @@ -13,9 +13,9 @@ } </style> - <script src="./p5.js"></script> + <script src="/p5.js"></script> </head> <body> -<script src="./sketch.js"></script> +<script src="/sketch.js"></script> </body> </html> \ No newline at end of file diff --git a/preview/global/vite.config.mjs b/preview/global/vite.config.mjs index e322f16aa4..ed95741666 100644 --- a/preview/global/vite.config.mjs +++ b/preview/global/vite.config.mjs @@ -17,7 +17,7 @@ export default defineConfig({ name: 'reload', configureServer(server) { const { ws, watcher } = server; - const buildLibPath = path.resolve(libPath, './p5.rollup.js'); + const buildLibPath = path.resolve(libPath, './p5.js'); watcher.add(buildLibPath); watcher.on('change', file => { if(file === buildLibPath){ From 80b5351c983e385caf0248e341671348d5a1b0b3 Mon Sep 17 00:00:00 2001 From: lp407 <lukeplowden@gmail.com> Date: Thu, 13 Feb 2025 16:31:32 +0000 Subject: [PATCH 02/54] import system working --- src/webgl/ShaderGen.js | 25 +++++++++++++++++++++++++ src/webgl/index.js | 2 ++ 2 files changed, 27 insertions(+) create mode 100644 src/webgl/ShaderGen.js diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js new file mode 100644 index 0000000000..11dca9b157 --- /dev/null +++ b/src/webgl/ShaderGen.js @@ -0,0 +1,25 @@ +/** + * @module 3D + * @submodule ShaderGenerator + * @for p5 + * @requires core + */ + +function shadergen(p5, fn) { + const oldModify = p5.Shader.prototype.modify + p5.Shader.prototype.modify = function(arg) { + if (arg instanceof Function) { + // const program = new ShaderProgram(arg) + // const newArg = program.run() + return oldModify.call(this, arg) + } else { + return oldModify.call(this, arg) + } + } + } + + if (typeof p5 !== 'undefined') { + p5.registerAddon(shadergen) +} + +export default shadergen; \ No newline at end of file diff --git a/src/webgl/index.js b/src/webgl/index.js index c2515fce5a..2ce0957fa6 100644 --- a/src/webgl/index.js +++ b/src/webgl/index.js @@ -14,6 +14,7 @@ import shader from './p5.Shader'; import camera from './p5.Camera'; import texture from './p5.Texture'; import rendererGL from './p5.RendererGL'; +import shadergen from './ShaderGen'; export default function(p5){ rendererGL(p5, p5.prototype); @@ -32,4 +33,5 @@ export default function(p5){ dataArray(p5, p5.prototype); shader(p5, p5.prototype); texture(p5, p5.prototype); + shadergen(p5, p5.prototype); } From 76eef1bf20b078b390dc98910c5b2196058c4ec1 Mon Sep 17 00:00:00 2001 From: lp407 <lukeplowden@gmail.com> Date: Thu, 13 Feb 2025 18:31:43 +0000 Subject: [PATCH 03/54] Most of the code from original file ported here --- src/webgl/ShaderGen.js | 454 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 439 insertions(+), 15 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 11dca9b157..a72013020e 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -1,25 +1,449 @@ /** - * @module 3D - * @submodule ShaderGenerator - * @for p5 - * @requires core - */ +* @module 3D +* @submodule ShaderGenerator +* @for p5 +* @requires core +*/ function shadergen(p5, fn) { - const oldModify = p5.Shader.prototype.modify - p5.Shader.prototype.modify = function(arg) { - if (arg instanceof Function) { - // const program = new ShaderProgram(arg) - // const newArg = program.run() - return oldModify.call(this, arg) + const oldModify = p5.Shader.prototype.modify + let GLOBAL_SHADER; + p5.Shader.prototype.modify = function(arg) { + if (arg instanceof Function) { + const program = new ShaderProgram(arg) + const newArg = program.generate(); + console.log(newArg) + return oldModify.call(this, newArg); + } else { + return oldModify.call(this, arg) + } + } + + class BaseNode { + constructor() { + if (new.target === BaseNode) { + throw new TypeError("Cannot construct BaseNode instances directly. This is an abstract class."); + } + this.type = null; + + // For tracking recursion depth and creating temporary variables + this.isInternal = false; // TODO: dont use temp nodes for internal nodes + this.usedIn = []; + this.dependsOn = []; + this.srcLine = null; + try { + throw new Error("StackCapture"); + } catch (e) { + const lines = e.stack.split("\n"); + console.log(lines) + this.srcLine = lines[4].trim(); + } + } + + // The base node implements a version of toGLSL which determines whether the generated code should be stored in a temporary variable. + toGLSLBase(context){ + if (this.useTempVar()) { + return this.getTemp(context); + } + else { + return this.toGLSL(context); + } + } + + useTempVar() { + if (this.isInternal) { return false; } + if (isVariableNode(this)) { return false; } + if (isVectorNode(this)) { return true; } + return (this.usedIn.length > 1); + }; + + getTemp(context) { + if (!this.temporaryVariable) { + this.temporaryVariable = `temp_${context.getNextID()}`; + let line = ""; + if (this.srcLine) { + line += `\n// From ${this.srcLine}\n`; + } + line += this.type + " " + this.temporaryVariable + " = " + this.toGLSLNoTemp(context) + ";"; + context.declarations.push(line); + } + return this.temporaryVariable; + }; + + // TODO: Add more operations here + add(other) { return new AdditionNode(this, this.enforceType(other)); } + sub(other) { return new SubtractionNode(this, this.enforceType(other)); } + mult(other) { return new MultiplicationNode(this, this.enforceType(other)); } + div(other) { return new DivisionNode(this, this.enforceType(other)); } + mod(other) { return new ModulusNode(this, this.enforceType(other)); } + sin() { return new FunctionCallNode('sin', this, 'float'); } + cos() { return new FunctionCallNode('cos', this, 'float'); } + radians() { return new FunctionCallNode('radians', this, 'float'); } + + // Check that the types of the operands are compatible. + // TODO: improve this with less branching if elses + enforceType(other){ + if (isShaderNode(other)){ + if ((isFloatNode(this) || isVectorNode(this)) && isIntNode(other)) { + return new FloatNode(other) + } + return other; + } + else if(typeof other === 'number') { + if (isIntNode(this)) { + return new IntNode(other); + } + return new FloatNode(other); + } + else { + return new this.constructor(other); + } + } + + toGLSL(context){ + throw new TypeError("Not supposed to call this function on BaseNode, which is an abstract class."); + } + } + + // Primitive Types + class IntNode extends BaseNode { + constructor(x = 0) { + super(); + this.x = x; + this.type = 'int'; + } + toGLSL(context) { + if (isShaderNode(this.x)) { + let code = this.x.toGLSL(context); + return isIntNode(this.x.type) ? code : `int(${code})`; + } + else if (typeof this.x === "number") { + return `${Math.floor(this.x)}`; + } + else { + return `int(${this.x})`; + } + } + } + + class FloatNode extends BaseNode { + constructor(x = 0){ + super(); + this.x = x; + this.type = 'float'; + } + toGLSL(context) { + if (isShaderNode(this.x)) { + let code = this.x.toGLSL(context); + console.log(code) + return isFloatNode(this.x) ? code : `float(${code})`; + } + else if (typeof this.x === "number") { + return `${this.x.toFixed(4)}`; + } + else { + return `float(${this.x})`; + } + } + } + + class VectorNode extends BaseNode { + constructor(values, type) { + super(); + this.x = new FloatNode(x); + this.y = new FloatNode(y); + this.type = type; + } + + toGLSL(context) { + return `${this.type}(${this.x.toGLSLBase(context)}, ${this.y.toGLSLBase(context)})` + } + } + + // Function Call Nodes + class FunctionCallNode extends BaseNode { + constructor(name, args, type) { + super(); + this.name = name; + this.args = args; + this.type = type; + } + deconstructArgs(context) { + if (this.args.constructor === Array) { + let argsString = `${this.args[0].toGLSL(context)}` + for (let arg of this.args.slice(1)) { + argsString += `, ${arg.toGLSL(context)}` + return argsString; + } } else { - return oldModify.call(this, arg) + return `${this.args.toGLSL(context)}`; + } + } + toGLSL(context) { + return `${this.name}(${this.deconstructArgs(context)})`; + } + } + + // Variables and member variable nodes + class VariableNode extends BaseNode { + constructor(name, type) { + super() + this.name = name; + this.type = type; + switch (type) { + case 'float': + break; + case 'vec2': + this.addSwizzles('x', 'y') + break; + case 'vec3': + this.addSwizzles('x', 'y', 'z') + break; + case 'vec4': + this.addSwizzles('x', 'y', 'z', 'w'); + break; } } + addSwizzles() { + for (let name of arguments) { + this[name] = new ComponentNode(this.name, name); + } + } + toGLSL(context) { + return `${this.name}`; + } } - if (typeof p5 !== 'undefined') { - p5.registerAddon(shadergen) + class ComponentNode extends BaseNode { + constructor(parent, component) { + super(); + this.varName = parent; + this.component = component; + this.type = 'float'; + } + toGLSL(context) { + return `${this.varName}.${this.component}`; + } + } + + // Binary Operator Nodes + class BinaryOperatorNode extends BaseNode { + constructor(a, b) { + super(); + this.a = a; + this.b = b; + for (const param of arguments) { + param.usedIn.push(this); + } + this.type = this.determineType(); + } + + // We know that both this.a and this.b are nodes because of PrimitiveNode.enforceType + determineType() { + if (this.a.type === this.b.type) { + return this.a.type; + } + else if (isVectorNode(this.a) && isFloatNode(this.b)) { + return this.a.type; + } + else if (isVectorNode(this.b) && isFloatNode(this.a)) { + return this.b.type; + } + else if (isFloatNode(this.a) && isIntNode(this.b) + || isIntNode(this.a) && isFloatNode(this.b) + ) { + return 'float'; + } + else { + throw new Error("Incompatible types for binary operator"); + } + } + + processOperand(context, operand) { + const code = operand.toGLSL(context); + if (this.type === 'float' && isIntNode(operand)) { + return `float${code}`; + } + return code; + } + } + + class MultiplicationNode extends BinaryOperatorNode { + constructor(a, b) { + super(a, b) + } + toGLSLNoTemp(context) { + return `(${this.processOperand(context, this.a)} * ${this.processOperand(context, this.b)})`; + } + } + + class DivisionNode extends BinaryOperatorNode { + constructor(a, b) { + super(a, b) + } + toGLSLNoTemp(context) { + return `(${this.processOperand(context, this.a)} / ${this.processOperand(context, this.b)})`; + } + } + + class AdditionNode extends BinaryOperatorNode { + constructor(a, b) { + super(a, b) + } + toGLSLNoTemp(context) { + return `(${this.processOperand(context, this.a)} + ${this.processOperand(context, this.b)})`; + } + } + + class SubtractionNode extends BinaryOperatorNode { + constructor(a, b) { + super(a, b) + } + toGLSLNoTemp(context) { + return `(${this.processOperand(context, this.a)} - ${this.processOperand(context, this.b)})`; + } + } + + // TODO: Correct the implementation for floats/ genType etc + class ModulusNode extends BinaryOperatorNode { + constructor(a, b) { + super(a, b); + } + toGLSLNoTemp(context) { + // Switch on type between % or mod() + if (isVectorNode(this) || isFloatNode(this)) { + return `mod(${this.a.toGLSL(context)}, ${this.b.toGLSL(context)})`; + } + return `(${this.processOperand(context, this.a)} % ${this.processOperand(context, this.b)})`; + } + } + + // Helper functions + function isShaderNode(value) { return (value instanceof BaseNode); } + + function isIntNode(value) { + return (isShaderNode(value) && (value.type === 'int')); + } + + function isFloatNode(value) { + return (isShaderNode(value) && (value.type === 'float')); + } + + function isVectorNode(value) { + return (isShaderNode(value) && (value.type === 'vec2'|| value.type === 'vec3' || value.type === 'vec4')); + } + + function isVariableNode(node) { + return (node instanceof VariableNode || node instanceof ComponentNode); + } + + // Shader program + // This class is responsible for converting the nodes into an object containing GLSL code, to be used by p5.Shader.modify + + class ShaderProgram { + constructor(modifyFunction) { + this.uniforms = { + } + this.functions = { + getWorldPosition: null + } + this.resetGLSLContext(); + global.GLOBAL_SHADER = this; + this.generator = modifyFunction; + } + generate() { + this.generator(); + return { + uniforms: this.uniforms, + functions: this.functions, + vertex: this.functions.getWorldPosition, + fragment: this.functions.getFinalColor, + } + } + resetGLSLContext() { + this.context = { + id: 0, + getNextID: function() { return this.id++ }, + declarations: [], + } + } + uniform(name, value) { + this.uniforms[name] = value; + return new VariableNode(name, value.type); + } + buildFunction(argumentName, argumentType, callback) { + let functionArgument = new VariableNode(argumentName, argumentType); + const finalLine = callback(functionArgument).toGLSL(this.context); + let codeLines = this.context.declarations.slice(); + codeLines.push(`\n${argumentName} = ${finalLine}; \nreturn ${argumentName};`) + this.resetGLSLContext(); + return codeLines.join("\n"); + } + getWorldPosition(func) { + this.functions.getWorldPosition = this.buildFunction("pos", "vec3", func); + } + getFinalColor(func) { + this.functions.getFinalColor = this.buildFunction("col", "vec3", func); + } + } + + // User functions + fn.createVector2 = function(x, y) { + return new VectorNode(x, y, 'vec2'); + } + + fn.createVector3 = function(x, y, z) { + return new VectorNode(x, y, z, 'vec3'); + } + + fn.createVector4 = function(x, y, z, w) { + return new VectorNode(x, y, z, w, 'vec4'); + } + + fn.createFloat = function(x) { + return new FloatNode(x); + } + + fn.createInt = function(x) { + return new IntNode(x); + } + + fn.instanceID = function() { + return new VariableNode('gl_InstanceID', 'int'); + } + + fn.uvCoords = function() { + return new VariableNode('vTexCoord', 'vec2'); + } + + fn.discard = function() { + return new VariableNode('discard', 'keyword'); + } + + fn.uniform = function(name, value) { + let result = GLOBAL_SHADER.uniform(name, value) + return result; + } + + function getWorldPosition(func){ + GLOBAL_SHADER.getWorldPosition(func) + } + function getFinalColor(func){ + GLOBAL_SHADER.getFinalColor(func) + } + + 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 shadergen; \ No newline at end of file +export default shadergen; + +if (typeof p5 !== 'undefined') { + p5.registerAddon(shadergen) +} \ No newline at end of file From 4ba46ee7ba1da1830964d51f2ca7d003ddf74a5a Mon Sep 17 00:00:00 2001 From: Luke Plowden <lukeplowden@gmail.com> Date: Fri, 14 Feb 2025 17:57:58 +0000 Subject: [PATCH 04/54] example sketch --- preview/global/sketch.js | 58 +++++++++------------------------------- 1 file changed, 12 insertions(+), 46 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 4789f83f36..927489719f 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -1,55 +1,21 @@ -const vertSrc = `#version 300 es - precision mediump float; - uniform mat4 uModelViewMatrix; - uniform mat4 uProjectionMatrix; - - in vec3 aPosition; - in vec2 aOffset; - - void main(){ - vec4 positionVec4 = vec4(aPosition.xyz, 1.0); - positionVec4.xy += aOffset; - gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4; - } -`; - -const fragSrc = `#version 300 es - precision mediump float; - out vec4 outColor; - void main(){ - outColor = vec4(0.0, 1.0, 1.0, 1.0); - } -`; - let myShader; function setup(){ - createCanvas(100, 100, WEBGL); + createCanvas(windowWidth, windowHeight, WEBGL); // Create and use the custom shader. - myShader = createShader(vertSrc, fragSrc); - - describe('A wobbly, cyan circle on a gray background.'); + myShader = baseMaterialShader().modify( + () => { + const x = createVector3(1, 2) + + getWorldPosition((pos) => { + pos = pos.add(x); + return pos; + }) + } + ); } function draw(){ // Set the styles - background(125); - noStroke(); - shader(myShader); - - // Draw the circle. - beginShape(); - for (let i = 0; i < 30; i++){ - const x = 40 * cos(i/30 * TWO_PI); - const y = 40 * sin(i/30 * TWO_PI); - - // Apply some noise to the coordinates. - const xOff = 10 * noise(x + millis()/1000) - 5; - const yOff = 10 * noise(y + millis()/1000) - 5; - - // Apply these noise values to the following vertex. - vertexProperty('aOffset', [xOff, yOff]); - vertex(x, y); - } - endShape(CLOSE); + background(0) } From 253f6fe326a306bd60e953855893c215c31b9a15 Mon Sep 17 00:00:00 2001 From: Luke Plowden <lukeplowden@gmail.com> Date: Fri, 14 Feb 2025 17:59:10 +0000 Subject: [PATCH 05/54] reduce vector2,3, and 4 classes to a single vector class to avoid repitition --- src/webgl/ShaderGen.js | 94 +++++++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 33 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index a72013020e..89db1f3f61 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -20,23 +20,25 @@ function shadergen(p5, fn) { } class BaseNode { - constructor() { + constructor(isInternal) { if (new.target === BaseNode) { throw new TypeError("Cannot construct BaseNode instances directly. This is an abstract class."); } this.type = null; // For tracking recursion depth and creating temporary variables - this.isInternal = false; // TODO: dont use temp nodes for internal nodes + this.isInternal = isInternal; // TODO: dont use temp nodes for internal nodes this.usedIn = []; this.dependsOn = []; this.srcLine = null; - try { - throw new Error("StackCapture"); - } catch (e) { - const lines = e.stack.split("\n"); - console.log(lines) - this.srcLine = lines[4].trim(); + if (!isInternal) { + try { + throw new Error("StackCapture"); + } catch (e) { + const lines = e.stack.split("\n"); + console.log(lines) + this.srcLine = lines[3].trim(); + } } } @@ -107,8 +109,8 @@ function shadergen(p5, fn) { // Primitive Types class IntNode extends BaseNode { - constructor(x = 0) { - super(); + constructor(x = 0, isInternal = false) { + super(isInternal); this.x = x; this.type = 'int'; } @@ -127,8 +129,8 @@ function shadergen(p5, fn) { } class FloatNode extends BaseNode { - constructor(x = 0){ - super(); + constructor(x = 0, isInternal = false){ + super(isInternal); this.x = x; this.type = 'float'; } @@ -147,11 +149,28 @@ function shadergen(p5, fn) { } } + // There is a possibility that since we *always* use a temporary variable for vectors + // that we don't actually need a Float Node for every component. They could be component node's instead? + // May need to then store the temporary variable name in this class. + + // I think swizzles could be easy then + class VectorNode extends BaseNode { - constructor(values, type) { - super(); - this.x = new FloatNode(x); - this.y = new FloatNode(y); + constructor(values, type, isInternal = false) { + super(isInternal); + const componentVariants = { + pos: ['x', 'y', 'z', 'w'], + col: ['r', 'g', 'b', 'a'], + uv: ['s', 't', 'p', 'q'] + } + for (let variant in componentVariants) { + for (let i = 0; i < arguments[0].length; i++) { + let componentCollection = componentVariants[variant]; + let component = componentCollection[i]; + this[component] = new FloatNode(arguments[0][i], isInternal = true); + } + } + console.log(this); this.type = type; } @@ -162,8 +181,8 @@ function shadergen(p5, fn) { // Function Call Nodes class FunctionCallNode extends BaseNode { - constructor(name, args, type) { - super(); + constructor(name, args, type, isInternal = false) { + super(isInternal); this.name = name; this.args = args; this.type = type; @@ -186,25 +205,25 @@ function shadergen(p5, fn) { // Variables and member variable nodes class VariableNode extends BaseNode { - constructor(name, type) { - super() + constructor(name, type, isInternal = false) { + super(isInternal) this.name = name; this.type = type; switch (type) { case 'float': break; case 'vec2': - this.addSwizzles('x', 'y') + this.addComponents('x', 'y') break; case 'vec3': - this.addSwizzles('x', 'y', 'z') + this.addComponents('x', 'y', 'z') break; case 'vec4': - this.addSwizzles('x', 'y', 'z', 'w'); + this.addComponents('x', 'y', 'z', 'w'); break; } } - addSwizzles() { + addComponents() { for (let name of arguments) { this[name] = new ComponentNode(this.name, name); } @@ -215,8 +234,8 @@ function shadergen(p5, fn) { } class ComponentNode extends BaseNode { - constructor(parent, component) { - super(); + constructor(parent, component, isInternal = false) { + super(isInternal); this.varName = parent; this.component = component; this.type = 'float'; @@ -228,8 +247,8 @@ function shadergen(p5, fn) { // Binary Operator Nodes class BinaryOperatorNode extends BaseNode { - constructor(a, b) { - super(); + constructor(a, b, isInternal = false) { + super(isInternal); this.a = a; this.b = b; for (const param of arguments) { @@ -348,7 +367,7 @@ function shadergen(p5, fn) { getWorldPosition: null } this.resetGLSLContext(); - global.GLOBAL_SHADER = this; + GLOBAL_SHADER = this; this.generator = modifyFunction; } generate() { @@ -371,14 +390,16 @@ function shadergen(p5, fn) { this.uniforms[name] = value; return new VariableNode(name, value.type); } + buildFunction(argumentName, argumentType, callback) { let functionArgument = new VariableNode(argumentName, argumentType); - const finalLine = callback(functionArgument).toGLSL(this.context); + const finalLine = callback(functionArgument).toGLSLBase(this.context); let codeLines = this.context.declarations.slice(); codeLines.push(`\n${argumentName} = ${finalLine}; \nreturn ${argumentName};`) this.resetGLSLContext(); return codeLines.join("\n"); } + getWorldPosition(func) { this.functions.getWorldPosition = this.buildFunction("pos", "vec3", func); } @@ -388,16 +409,23 @@ function shadergen(p5, fn) { } // User functions + fn.getWorldPosition = function(func){ + GLOBAL_SHADER.getWorldPosition(func) + } + fn.getFinalColor = function(func){ + GLOBAL_SHADER.getFinalColor(func) + } + fn.createVector2 = function(x, y) { - return new VectorNode(x, y, 'vec2'); + return new VectorNode([x, y], 'vec2'); } fn.createVector3 = function(x, y, z) { - return new VectorNode(x, y, z, 'vec3'); + return new VectorNode([x, y, z], 'vec3'); } fn.createVector4 = function(x, y, z, w) { - return new VectorNode(x, y, z, w, 'vec4'); + return new VectorNode([x, y, z], w, 'vec4'); } fn.createFloat = function(x) { From f513b05c7d421e6c92efa8239ddaddb664e51a91 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Mon, 17 Feb 2025 12:56:34 +0000 Subject: [PATCH 06/54] ComponentNode.toGLSL() currently broken. improving vector API. --- src/webgl/ShaderGen.js | 117 +++++++++++++++++++++++++---------------- 1 file changed, 73 insertions(+), 44 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 89db1f3f61..5cc21b578a 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -6,15 +6,18 @@ */ function shadergen(p5, fn) { - const oldModify = p5.Shader.prototype.modify let GLOBAL_SHADER; + + const oldModify = p5.Shader.prototype.modify + p5.Shader.prototype.modify = function(arg) { if (arg instanceof Function) { const program = new ShaderProgram(arg) const newArg = program.generate(); - console.log(newArg) + console.log(newArg.vertex) return oldModify.call(this, newArg); - } else { + } + else { return oldModify.call(this, arg) } } @@ -27,17 +30,19 @@ function shadergen(p5, fn) { this.type = null; // For tracking recursion depth and creating temporary variables - this.isInternal = isInternal; // TODO: dont use temp nodes for internal nodes + this.isInternal = isInternal; this.usedIn = []; this.dependsOn = []; this.srcLine = null; - if (!isInternal) { + + if (isInternal === false) { try { throw new Error("StackCapture"); } catch (e) { const lines = e.stack.split("\n"); console.log(lines) - this.srcLine = lines[3].trim(); + let index = 5; + this.srcLine = lines[index].trim(); } } } @@ -66,7 +71,7 @@ function shadergen(p5, fn) { if (this.srcLine) { line += `\n// From ${this.srcLine}\n`; } - line += this.type + " " + this.temporaryVariable + " = " + this.toGLSLNoTemp(context) + ";"; + line += this.type + " " + this.temporaryVariable + " = " + this.toGLSL(context) + ";"; context.declarations.push(line); } return this.temporaryVariable; @@ -116,7 +121,7 @@ function shadergen(p5, fn) { } toGLSL(context) { if (isShaderNode(this.x)) { - let code = this.x.toGLSL(context); + let code = this.x.toGLSLBase(context); return isIntNode(this.x.type) ? code : `int(${code})`; } else if (typeof this.x === "number") { @@ -136,8 +141,7 @@ function shadergen(p5, fn) { } toGLSL(context) { if (isShaderNode(this.x)) { - let code = this.x.toGLSL(context); - console.log(code) + let code = this.x.toGLSLBase(context); return isFloatNode(this.x) ? code : `float(${code})`; } else if (typeof this.x === "number") { @@ -153,29 +157,41 @@ function shadergen(p5, fn) { // that we don't actually need a Float Node for every component. They could be component node's instead? // May need to then store the temporary variable name in this class. - // I think swizzles could be easy then + // This would be the next step before adding all swizzles class VectorNode extends BaseNode { constructor(values, type, isInternal = false) { super(isInternal); + const componentVariants = { pos: ['x', 'y', 'z', 'w'], col: ['r', 'g', 'b', 'a'], uv: ['s', 't', 'p', 'q'] } for (let variant in componentVariants) { - for (let i = 0; i < arguments[0].length; i++) { + for (let i = 0; i < values.length; i++) { let componentCollection = componentVariants[variant]; let component = componentCollection[i]; - this[component] = new FloatNode(arguments[0][i], isInternal = true); + this[component] = new ComponentNode(this, component, true); + // this[component] = new FloatNode(values[i], true); } } - console.log(this); + this.type = type; + this.size = values.length; } toGLSL(context) { - return `${this.type}(${this.x.toGLSLBase(context)}, ${this.y.toGLSLBase(context)})` + let glslArgs = ``; + const components = ['x', 'y', 'z', 'w'].slice(0, this.size); + + for (let i = 0; i < this.size; i++) { + const comma = i === this.size - 1 ? `` : `, `; + const component = components[i] + glslArgs += `${this[component].toGLSLBase(context)}${comma}`; + } + + return `${this.type}(${glslArgs})`; } } @@ -187,17 +203,19 @@ function shadergen(p5, fn) { this.args = args; this.type = type; } + deconstructArgs(context) { if (this.args.constructor === Array) { - let argsString = `${this.args[0].toGLSL(context)}` + let argsString = `${this.args[0].toGLSLBase(context)}` for (let arg of this.args.slice(1)) { - argsString += `, ${arg.toGLSL(context)}` + argsString += `, ${arg.toGLSLBase(context)}` return argsString; } } else { - return `${this.args.toGLSL(context)}`; + return `${this.args.toGLSLBase(context)}`; } } + toGLSL(context) { return `${this.name}(${this.deconstructArgs(context)})`; } @@ -213,19 +231,19 @@ function shadergen(p5, fn) { case 'float': break; case 'vec2': - this.addComponents('x', 'y') + this.addComponents(['x', 'y']) break; case 'vec3': - this.addComponents('x', 'y', 'z') + this.addComponents(['x', 'y', 'z']) break; case 'vec4': - this.addComponents('x', 'y', 'z', 'w'); + this.addComponents(['x', 'y', 'z', 'w']); break; } } - addComponents() { - for (let name of arguments) { - this[name] = new ComponentNode(this.name, name); + addComponents(componentNames) { + for (let componentName of componentNames) { + this[componentName] = new ComponentNode(this, componentName, true); } } toGLSL(context) { @@ -236,12 +254,14 @@ function shadergen(p5, fn) { class ComponentNode extends BaseNode { constructor(parent, component, isInternal = false) { super(isInternal); - this.varName = parent; + this.parent = parent; this.component = component; this.type = 'float'; } toGLSL(context) { - return `${this.varName}.${this.component}`; + // CURRENTLY BROKEN: + const parentName = this.parent.toGLSL(context); + return `${parentName}.${this.component}`; } } @@ -279,7 +299,10 @@ function shadergen(p5, fn) { } processOperand(context, operand) { - const code = operand.toGLSL(context); + const code = operand.toGLSLBase(context); + if (operand.temporaryVariable) { + return operand.temporaryVariable; + } if (this.type === 'float' && isIntNode(operand)) { return `float${code}`; } @@ -291,7 +314,7 @@ function shadergen(p5, fn) { constructor(a, b) { super(a, b) } - toGLSLNoTemp(context) { + toGLSL(context) { return `(${this.processOperand(context, this.a)} * ${this.processOperand(context, this.b)})`; } } @@ -300,7 +323,7 @@ function shadergen(p5, fn) { constructor(a, b) { super(a, b) } - toGLSLNoTemp(context) { + toGLSL(context) { return `(${this.processOperand(context, this.a)} / ${this.processOperand(context, this.b)})`; } } @@ -309,7 +332,7 @@ function shadergen(p5, fn) { constructor(a, b) { super(a, b) } - toGLSLNoTemp(context) { + toGLSL(context) { return `(${this.processOperand(context, this.a)} + ${this.processOperand(context, this.b)})`; } } @@ -318,7 +341,7 @@ function shadergen(p5, fn) { constructor(a, b) { super(a, b) } - toGLSLNoTemp(context) { + toGLSL(context) { return `(${this.processOperand(context, this.a)} - ${this.processOperand(context, this.b)})`; } } @@ -328,32 +351,39 @@ function shadergen(p5, fn) { constructor(a, b) { super(a, b); } - toGLSLNoTemp(context) { + toGLSL(context) { // Switch on type between % or mod() if (isVectorNode(this) || isFloatNode(this)) { - return `mod(${this.a.toGLSL(context)}, ${this.b.toGLSL(context)})`; + return `mod(${this.a.toGLSLBase(context)}, ${this.b.toGLSLBase(context)})`; } return `(${this.processOperand(context, this.a)} % ${this.processOperand(context, this.b)})`; } } // Helper functions - function isShaderNode(value) { return (value instanceof BaseNode); } + function isShaderNode(node) { + return (node instanceof BaseNode); + } + + function isIntNode(node) { + return (isShaderNode(node) && (node.type === 'int')); + } - function isIntNode(value) { - return (isShaderNode(value) && (value.type === 'int')); + function isFloatNode(node) { + return (isShaderNode(node) && (node.type === 'float')); } - function isFloatNode(value) { - return (isShaderNode(value) && (value.type === 'float')); + function isVectorNode(node) { + return (isShaderNode(node) && (node.type === 'vec2'|| node.type === 'vec3' || node.type === 'vec4')); } - function isVectorNode(value) { - return (isShaderNode(value) && (value.type === 'vec2'|| value.type === 'vec3' || value.type === 'vec4')); + function isBinaryOperatorNode(node) { + return (node instanceof BinaryOperatorNode); } function isVariableNode(node) { - return (node instanceof VariableNode || node instanceof ComponentNode); + console.log(node.temporaryVariable); + return (node instanceof VariableNode || node instanceof ComponentNode || typeof(node.temporaryVariable) != 'undefined'); } // Shader program @@ -364,7 +394,6 @@ function shadergen(p5, fn) { this.uniforms = { } this.functions = { - getWorldPosition: null } this.resetGLSLContext(); GLOBAL_SHADER = this; @@ -392,7 +421,7 @@ function shadergen(p5, fn) { } buildFunction(argumentName, argumentType, callback) { - let functionArgument = new VariableNode(argumentName, argumentType); + let functionArgument = new VariableNode(argumentName, argumentType, true); const finalLine = callback(functionArgument).toGLSLBase(this.context); let codeLines = this.context.declarations.slice(); codeLines.push(`\n${argumentName} = ${finalLine}; \nreturn ${argumentName};`) @@ -425,7 +454,7 @@ function shadergen(p5, fn) { } fn.createVector4 = function(x, y, z, w) { - return new VectorNode([x, y, z], w, 'vec4'); + return new VectorNode([x, y, z, w], 'vec4'); } fn.createFloat = function(x) { From 9ef9cc7f7a4a2d177602247a68f79097db92b8b7 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Mon, 17 Feb 2025 12:56:46 +0000 Subject: [PATCH 07/54] updating sketch --- preview/global/sketch.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 927489719f..e6be4a4221 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -1,14 +1,21 @@ let myShader; +p5.disableFriendlyErrors = true; function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); // Create and use the custom shader. myShader = baseMaterialShader().modify( () => { - const x = createVector3(1, 2) - getWorldPosition((pos) => { - pos = pos.add(x); + let a = createVector3(1, 2, 3); + let b = createVector3(3,4,5); + a = a.add(b); + + let c = a.add(b); + + c.x = b.x.add(1).sin(); + pos = pos.add(c); + return pos; }) } From 59b443efde73253751072ffd9eb54763304fdc02 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Mon, 17 Feb 2025 12:56:46 +0000 Subject: [PATCH 08/54] put temporary fix in comment --- preview/global/sketch.js | 13 ++++++++++--- src/webgl/ShaderGen.js | 3 ++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 927489719f..e6be4a4221 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -1,14 +1,21 @@ let myShader; +p5.disableFriendlyErrors = true; function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); // Create and use the custom shader. myShader = baseMaterialShader().modify( () => { - const x = createVector3(1, 2) - getWorldPosition((pos) => { - pos = pos.add(x); + let a = createVector3(1, 2, 3); + let b = createVector3(3,4,5); + a = a.add(b); + + let c = a.add(b); + + c.x = b.x.add(1).sin(); + pos = pos.add(c); + return pos; }) } diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 5cc21b578a..195e34d13c 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -260,7 +260,8 @@ function shadergen(p5, fn) { } toGLSL(context) { // CURRENTLY BROKEN: - const parentName = this.parent.toGLSL(context); + const parentName = this.parent.toGLSLBase(context); + // const parentName = this.parent.temporaryVariable ? this.parent.temporaryVariable : this.parent.name; return `${parentName}.${this.component}`; } } From 17fb0a6e35cf76c98faf7ef64f53fdb61637459d Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Mon, 17 Feb 2025 20:29:25 +0000 Subject: [PATCH 09/54] adding proxy for vectors to make swizzling simpler --- preview/global/sketch.js | 11 +++- src/webgl/ShaderGen.js | 125 +++++++++++++++++++++++++++++---------- 2 files changed, 102 insertions(+), 34 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index e6be4a4221..d5c9e20fff 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -6,14 +6,19 @@ function setup(){ // Create and use the custom shader. myShader = baseMaterialShader().modify( () => { + const offset = uniform('offset', createVector3()) getWorldPosition((pos) => { let a = createVector3(1, 2, 3); - let b = createVector3(3,4,5); + let b = createVector3(3, 4, 5); a = a.add(b); let c = a.add(b); - - c.x = b.x.add(1).sin(); + c = c.add(offset); + c.x = b.x.add(1); + + + console.log("TEST:") + console.log(c.x); pos = pos.add(c); return pos; diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 195e34d13c..9429707151 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -40,7 +40,6 @@ function shadergen(p5, fn) { throw new Error("StackCapture"); } catch (e) { const lines = e.stack.split("\n"); - console.log(lines) let index = 5; this.srcLine = lines[index].trim(); } @@ -159,10 +158,43 @@ function shadergen(p5, fn) { // This would be the next step before adding all swizzles + // I think it is also possible to make a proxy for vector nodes to check if the user accesses or sets any + // property .xyz / .rgb /.zxx etc as then we can automatically assign swizzles and only define .xyzw on the actual + // object. + + // Ultimately vectors are the focus of & most complex type in glsl (before we add matrices...) which justifies the complexity + // of this class. + + // I am interested in the 'prop' value of get and set then + + const VectorNodeHandler = { + swizzles: [ + ['x', 'y', 'z', 'w'], + ['r', 'b', 'g', 'a'], + ['s', 't', 'p', 'q'], + ], + get(target, prop, receiver) { + // if (!this.isInternal) { + // console.log("TARGET: ", target); + // console.log("PROP: ", prop); + // console.log("RECEIVER: ", receiver); + // } + return Reflect.get(...arguments); + }, + set(obj, prop, receiver) { + // if (!this.isInternal) { + // console.log("OBJ: ", obj); + // console.log("PROP: ", prop); + // console.log("RECEIVER: ", receiver); + // } + obj[prop] = receiver; + return true; + } + } + class VectorNode extends BaseNode { constructor(values, type, isInternal = false) { super(isInternal); - const componentVariants = { pos: ['x', 'y', 'z', 'w'], col: ['r', 'g', 'b', 'a'], @@ -172,8 +204,8 @@ function shadergen(p5, fn) { for (let i = 0; i < values.length; i++) { let componentCollection = componentVariants[variant]; let component = componentCollection[i]; - this[component] = new ComponentNode(this, component, true); - // this[component] = new FloatNode(values[i], true); + // this[component] = new ComponentNode(this, component, true); + this[component] = new FloatNode(values[i], true); } } @@ -182,12 +214,13 @@ function shadergen(p5, fn) { } toGLSL(context) { + console.log(this) let glslArgs = ``; const components = ['x', 'y', 'z', 'w'].slice(0, this.size); for (let i = 0; i < this.size; i++) { const comma = i === this.size - 1 ? `` : `, `; - const component = components[i] + const component = components[i]; glslArgs += `${this[component].toGLSLBase(context)}${comma}`; } @@ -202,6 +235,11 @@ function shadergen(p5, fn) { this.name = name; this.args = args; this.type = type; + + // TODO: + this.argumentTypes = { + + }; } deconstructArgs(context) { @@ -227,19 +265,29 @@ function shadergen(p5, fn) { super(isInternal) this.name = name; this.type = type; - switch (type) { - case 'float': - break; - case 'vec2': - this.addComponents(['x', 'y']) - break; - case 'vec3': - this.addComponents(['x', 'y', 'z']) - break; - case 'vec4': - this.addComponents(['x', 'y', 'z', 'w']); - break; - } + this.addComponents( + (() => { + switch (type) { + case 'vec2': return ['x', 'y']; + case 'vec3': return ['x', 'y', 'z']; + case 'vec4': return ['x', 'y', 'z', 'w']; + default: return []; + } + })() + ); + // switch (type) { + // case 'float': + // break; + // case 'vec2': + // this.addComponents(['x', 'y']) + // break; + // case 'vec3': + // this.addComponents(['x', 'y', 'z']) + // break; + // case 'vec4': + // this.addComponents(['x', 'y', 'z', 'w']); + // break; + // } } addComponents(componentNames) { for (let componentName of componentNames) { @@ -260,8 +308,8 @@ function shadergen(p5, fn) { } toGLSL(context) { // CURRENTLY BROKEN: - const parentName = this.parent.toGLSLBase(context); - // const parentName = this.parent.temporaryVariable ? this.parent.temporaryVariable : this.parent.name; + // const parentName = this.parent.toGLSLBase(context); + const parentName = this.parent.temporaryVariable ? this.parent.temporaryVariable : this.parent.name; return `${parentName}.${this.component}`; } } @@ -299,13 +347,16 @@ function shadergen(p5, fn) { } } + // TODO: change order of parameters processOperand(context, operand) { - const code = operand.toGLSLBase(context); - if (operand.temporaryVariable) { - return operand.temporaryVariable; + if (operand.temporaryVariable) { return operand.temporaryVariable; } + let code = operand.toGLSLBase(context); + if (isBinaryOperatorNode(operand)) { + console.log(operand) + code = `(${code})`; } if (this.type === 'float' && isIntNode(operand)) { - return `float${code}`; + code = `float(${code})`; } return code; } @@ -316,7 +367,7 @@ function shadergen(p5, fn) { super(a, b) } toGLSL(context) { - return `(${this.processOperand(context, this.a)} * ${this.processOperand(context, this.b)})`; + return `${this.processOperand(context, this.a)} * ${this.processOperand(context, this.b)}`; } } @@ -325,7 +376,7 @@ function shadergen(p5, fn) { super(a, b) } toGLSL(context) { - return `(${this.processOperand(context, this.a)} / ${this.processOperand(context, this.b)})`; + return `${this.processOperand(context, this.a)} / ${this.processOperand(context, this.b)}`; } } @@ -334,7 +385,7 @@ function shadergen(p5, fn) { super(a, b) } toGLSL(context) { - return `(${this.processOperand(context, this.a)} + ${this.processOperand(context, this.b)})`; + return `${this.processOperand(context, this.a)} + ${this.processOperand(context, this.b)}`; } } @@ -343,7 +394,7 @@ function shadergen(p5, fn) { super(a, b) } toGLSL(context) { - return `(${this.processOperand(context, this.a)} - ${this.processOperand(context, this.b)})`; + return `${this.processOperand(context, this.a)} - ${this.processOperand(context, this.b)}`; } } @@ -357,7 +408,7 @@ function shadergen(p5, fn) { if (isVectorNode(this) || isFloatNode(this)) { return `mod(${this.a.toGLSLBase(context)}, ${this.b.toGLSLBase(context)})`; } - return `(${this.processOperand(context, this.a)} % ${this.processOperand(context, this.b)})`; + return `${this.processOperand(context, this.a)} % ${this.processOperand(context, this.b)}`; } } @@ -383,7 +434,6 @@ function shadergen(p5, fn) { } function isVariableNode(node) { - console.log(node.temporaryVariable); return (node instanceof VariableNode || node instanceof ComponentNode || typeof(node.temporaryVariable) != 'undefined'); } @@ -416,6 +466,16 @@ function shadergen(p5, fn) { declarations: [], } } + // TODO: + uniformInt() { + return + } + uniformFloat() { + return + } + uniformVector2() { + return + } uniform(name, value) { this.uniforms[name] = value; return new VariableNode(name, value.type); @@ -447,14 +507,17 @@ function shadergen(p5, fn) { } fn.createVector2 = function(x, y) { - return new VectorNode([x, y], 'vec2'); + return new Proxy(new VectorNode([x, y], 'vec2'), VectorNodeHandler); } fn.createVector3 = function(x, y, z) { + return new Proxy(new VectorNode([x, y, z], 'vec3'), VectorNodeHandler); + return new VectorNode([x, y, z], 'vec3'); } fn.createVector4 = function(x, y, z, w) { + return new Proxy(new VectorNode([x, y, z, w], 'vec4'), VectorNodeHandler); return new VectorNode([x, y, z, w], 'vec4'); } From a1b51b08f2f69b97ac74f9c6c136e991d7ffc419 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 18 Feb 2025 16:11:32 +0000 Subject: [PATCH 10/54] add estraverse --- package-lock.json | 2 +- package.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index edccbe8993..cfe61bad9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "acorn": "^8.12.1", "acorn-walk": "^8.3.4", "colorjs.io": "^0.5.2", + "estraverse": "^5.3.0", "file-saver": "^1.3.8", "gifenc": "^1.0.3", "i18next": "^19.0.2", @@ -4607,7 +4608,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" diff --git a/package.json b/package.json index e34a8ef5c7..dd9b44a6c0 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "acorn": "^8.12.1", "acorn-walk": "^8.3.4", "colorjs.io": "^0.5.2", + "estraverse": "^5.3.0", "file-saver": "^1.3.8", "gifenc": "^1.0.3", "i18next": "^19.0.2", From fb9fd94d790fe9225c3f79e9d32824c28338a8e3 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 18 Feb 2025 16:15:45 +0000 Subject: [PATCH 11/54] add AST modules --- src/webgl/ShaderGen.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 9429707151..1e074b38d2 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -5,17 +5,28 @@ * @requires core */ +import { parse } from 'acorn'; +import { simple as walk } from 'acorn-walk'; +import estraverse from 'estraverse'; + function shadergen(p5, fn) { let GLOBAL_SHADER; const oldModify = p5.Shader.prototype.modify p5.Shader.prototype.modify = function(arg) { + if (arg instanceof Function) { - const program = new ShaderProgram(arg) - const newArg = program.generate(); - console.log(newArg.vertex) - return oldModify.call(this, newArg); + const fnSource = arg.toString() + const ast = parse(fnSource, { ecmaVersion: 2021, locations: true }); + const result = estraverse.traverse(ast, { + enter: (node) => console.log(node), + }) + + // const program = new ShaderProgram(arg) + // const newArg = program.generate(); + // console.log(newArg.vertex) + // return oldModify.call(this, newArg); } else { return oldModify.call(this, arg) From e8435ceddc4c8737c8958a21d07de6bd5f321cf9 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 18 Feb 2025 16:23:12 +0000 Subject: [PATCH 12/54] remove walk --- src/webgl/ShaderGen.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 1e074b38d2..9d514edf41 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -6,7 +6,6 @@ */ import { parse } from 'acorn'; -import { simple as walk } from 'acorn-walk'; import estraverse from 'estraverse'; function shadergen(p5, fn) { From aaa8aaceaee1e6678588ae5f392e5e8d8780649e Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 18 Feb 2025 16:25:08 +0000 Subject: [PATCH 13/54] refactor binary operator nodes to remove redundant classes and repitition --- src/webgl/ShaderGen.js | 55 ++++++++++-------------------------------- 1 file changed, 13 insertions(+), 42 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 9d514edf41..1dd58745c2 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -51,6 +51,7 @@ function shadergen(p5, fn) { } catch (e) { const lines = e.stack.split("\n"); let index = 5; + if (isBinaryOperatorNode(this)) { index--; }; this.srcLine = lines[index].trim(); } } @@ -87,10 +88,10 @@ function shadergen(p5, fn) { }; // TODO: Add more operations here - add(other) { return new AdditionNode(this, this.enforceType(other)); } - sub(other) { return new SubtractionNode(this, this.enforceType(other)); } - mult(other) { return new MultiplicationNode(this, this.enforceType(other)); } - div(other) { return new DivisionNode(this, this.enforceType(other)); } + 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)); } sin() { return new FunctionCallNode('sin', this, 'float'); } cos() { return new FunctionCallNode('cos', this, 'float'); } @@ -326,12 +327,13 @@ function shadergen(p5, fn) { // Binary Operator Nodes class BinaryOperatorNode extends BaseNode { - constructor(a, b, isInternal = false) { + constructor(a, b, operator, isInternal = false) { super(isInternal); + this.op = operator; this.a = a; this.b = b; - for (const param of arguments) { - param.usedIn.push(this); + for (const operand of [a, b]) { + operand.usedIn.push(this); } this.type = this.determineType(); } @@ -361,8 +363,7 @@ function shadergen(p5, fn) { processOperand(context, operand) { if (operand.temporaryVariable) { return operand.temporaryVariable; } let code = operand.toGLSLBase(context); - if (isBinaryOperatorNode(operand)) { - console.log(operand) + if (isBinaryOperatorNode(operand) && !operand.temporaryVariable) { code = `(${code})`; } if (this.type === 'float' && isIntNode(operand)) { @@ -370,41 +371,11 @@ function shadergen(p5, fn) { } return code; } - } - class MultiplicationNode extends BinaryOperatorNode { - constructor(a, b) { - super(a, b) - } - toGLSL(context) { - return `${this.processOperand(context, this.a)} * ${this.processOperand(context, this.b)}`; - } - } - - class DivisionNode extends BinaryOperatorNode { - constructor(a, b) { - super(a, b) - } - toGLSL(context) { - return `${this.processOperand(context, this.a)} / ${this.processOperand(context, this.b)}`; - } - } - - class AdditionNode extends BinaryOperatorNode { - constructor(a, b) { - super(a, b) - } - toGLSL(context) { - return `${this.processOperand(context, this.a)} + ${this.processOperand(context, this.b)}`; - } - } - - class SubtractionNode extends BinaryOperatorNode { - constructor(a, b) { - super(a, b) - } toGLSL(context) { - return `${this.processOperand(context, this.a)} - ${this.processOperand(context, this.b)}`; + const a = this.processOperand(this.a, context); + const b = this.processOperand(this.b, context); + return `${a} ${this.op} ${b}`; } } From 057dc0eef97df7c2210bfeb288dad8e6d46aec18 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 18 Feb 2025 16:26:10 +0000 Subject: [PATCH 14/54] add type specific uniforms --- src/webgl/ShaderGen.js | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 1dd58745c2..c70ff6aa96 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -424,6 +424,12 @@ function shadergen(p5, fn) { class ShaderProgram { constructor(modifyFunction) { this.uniforms = { + int: {}, + float: {}, + vec2: {}, + vec3: {}, + vec4: {}, + texture: {}, } this.functions = { } @@ -448,18 +454,33 @@ function shadergen(p5, fn) { } } // TODO: - uniformInt() { - return + uniformInt(name, defaultValue) { + this.uniforms.int[name] = defaultValue; + return new VariableNode(name, 'int'); } - uniformFloat() { - return + uniformFloat(name, defaultValue) { + this.uniforms.float[name] = defaultValue; + return new VariableNode(name, 'float'); } - uniformVector2() { - return + uniformVector2(name, defaultValue) { + this.uniforms.vec2[name] = defaultValue; + return new VariableNode(name, 'vec2'); } - uniform(name, value) { - this.uniforms[name] = value; - return new VariableNode(name, value.type); + uniformVector2(name, defaultValue) { + this.uniforms.vec3[name] = defaultValue; + return new VariableNode(name, 'vec3'); + } + uniformVector2(name, defaultValue) { + this.uniforms.vec4[name] = defaultValue; + return new VariableNode(name, 'vec4'); + } + uniformTexture(name, defaultValue) { + this.uniforms.texture[name] = defaultValue; + return new VariableNode(name, 'vec4'); + } + uniform(name, defaultValue) { + this.uniforms[name] = defaultValue; + return new VariableNode(name, defaultValue.type); } buildFunction(argumentName, argumentType, callback) { From 23220797ea7651e31b379619184f43f342ac8771 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 18 Feb 2025 16:26:40 +0000 Subject: [PATCH 15/54] swap parameter's order in processOperand --- src/webgl/ShaderGen.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index c70ff6aa96..5ebc068f03 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -359,8 +359,7 @@ function shadergen(p5, fn) { } } - // TODO: change order of parameters - processOperand(context, operand) { + processOperand(operand, context) { if (operand.temporaryVariable) { return operand.temporaryVariable; } let code = operand.toGLSLBase(context); if (isBinaryOperatorNode(operand) && !operand.temporaryVariable) { From 95d9da320a38784f2ab22fac0e0d985613570a0e Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 18 Feb 2025 16:27:03 +0000 Subject: [PATCH 16/54] remove comment --- src/webgl/ShaderGen.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 5ebc068f03..746ece0f05 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -286,19 +286,6 @@ function shadergen(p5, fn) { } })() ); - // switch (type) { - // case 'float': - // break; - // case 'vec2': - // this.addComponents(['x', 'y']) - // break; - // case 'vec3': - // this.addComponents(['x', 'y', 'z']) - // break; - // case 'vec4': - // this.addComponents(['x', 'y', 'z', 'w']); - // break; - // } } addComponents(componentNames) { for (let componentName of componentNames) { From d212480acd23c7d7a2beb59cc541ee6ebdd32c89 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 18 Feb 2025 16:30:01 +0000 Subject: [PATCH 17/54] change temporary variable logic (testing) --- src/webgl/ShaderGen.js | 57 ++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 746ece0f05..d382cef8a0 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -67,12 +67,26 @@ function shadergen(p5, fn) { } } + // useTempVar() { + // if (isBinaryOperatorNode(this)) { + // console.log(this.a); + // console.log(this.b); + // return false; + // } + // if (this.isInternal) { return false; } + // if (isVariableNode(this)) { return false; } + // if (isVectorNode(this)) { return true; } + // return (this.usedIn.length > 1); + // }; + useTempVar() { - if (this.isInternal) { return false; } - if (isVariableNode(this)) { return false; } - if (isVectorNode(this)) { return true; } - return (this.usedIn.length > 1); - }; + if (this.isInternal || isVariableNode(this)) { return false; } + let score = 0; + score += isBinaryOperatorNode(this); + score += isVectorNode(this) * 2; + score += this.usedIn.length; + return score > 3; + } getTemp(context) { if (!this.temporaryVariable) { @@ -206,35 +220,24 @@ function shadergen(p5, fn) { class VectorNode extends BaseNode { constructor(values, type, isInternal = false) { super(isInternal); - const componentVariants = { - pos: ['x', 'y', 'z', 'w'], - col: ['r', 'g', 'b', 'a'], - uv: ['s', 't', 'p', 'q'] - } - for (let variant in componentVariants) { - for (let i = 0; i < values.length; i++) { - let componentCollection = componentVariants[variant]; - let component = componentCollection[i]; - // this[component] = new ComponentNode(this, component, true); - this[component] = new FloatNode(values[i], true); - } - } + + this.components = ['x', 'y', 'z', 'w'].slice(0, values.length); + this.components.forEach((component, i) => { + this[component] = new FloatNode(values[i], true); + // this[component] = new ComponentNode(this, component, true); + }); this.type = type; - this.size = values.length; } toGLSL(context) { - console.log(this) let glslArgs = ``; - const components = ['x', 'y', 'z', 'w'].slice(0, this.size); - - for (let i = 0; i < this.size; i++) { - const comma = i === this.size - 1 ? `` : `, `; - const component = components[i]; + + this.components.forEach((component, i) => { + const comma = i === this.components.length - 1 ? `` : `, `; glslArgs += `${this[component].toGLSLBase(context)}${comma}`; - } - + }) + return `${this.type}(${glslArgs})`; } } From 1b7560867aab496a12e1f484fa5c805696ac8dc1 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 18 Feb 2025 16:30:29 +0000 Subject: [PATCH 18/54] Start working on Proxy for swizzles --- src/webgl/ShaderGen.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index d382cef8a0..76f982cc8c 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -199,21 +199,20 @@ function shadergen(p5, fn) { ['s', 't', 'p', 'q'], ], get(target, prop, receiver) { - // if (!this.isInternal) { - // console.log("TARGET: ", target); - // console.log("PROP: ", prop); - // console.log("RECEIVER: ", receiver); - // } - return Reflect.get(...arguments); + if (prop in target) { + return Reflect.get(target, prop, receiver); + } else { + console.log(prop); + } + let modifiedProp; + return Reflect.get(target, modifiedProp, receiver); }, set(obj, prop, receiver) { - // if (!this.isInternal) { - // console.log("OBJ: ", obj); - // console.log("PROP: ", prop); - // console.log("RECEIVER: ", receiver); - // } - obj[prop] = receiver; - return true; + console.log("OBJect:",obj,"PROPERTY", prop, "RECEIVER", receiver); + + if (prop in obj) { return Reflect.set(...arguments); } + let modifiedProp; + return Reflect.set(obj, modifiedProp, receiver); } } From 9813cb5a9a2802a245b5aa5e560fd732f557ba08 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 18 Feb 2025 16:30:49 +0000 Subject: [PATCH 19/54] Whitespace --- src/webgl/ShaderGen.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 76f982cc8c..feeb1723b5 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -143,6 +143,7 @@ function shadergen(p5, fn) { this.x = x; this.type = 'int'; } + toGLSL(context) { if (isShaderNode(this.x)) { let code = this.x.toGLSLBase(context); @@ -163,6 +164,7 @@ function shadergen(p5, fn) { this.x = x; this.type = 'float'; } + toGLSL(context) { if (isShaderNode(this.x)) { let code = this.x.toGLSLBase(context); @@ -177,6 +179,7 @@ function shadergen(p5, fn) { } } + // TODO: // There is a possibility that since we *always* use a temporary variable for vectors // that we don't actually need a Float Node for every component. They could be component node's instead? // May need to then store the temporary variable name in this class. @@ -289,11 +292,13 @@ function shadergen(p5, fn) { })() ); } + addComponents(componentNames) { for (let componentName of componentNames) { this[componentName] = new ComponentNode(this, componentName, true); } } + toGLSL(context) { return `${this.name}`; } @@ -502,7 +507,6 @@ function shadergen(p5, fn) { fn.createVector3 = function(x, y, z) { return new Proxy(new VectorNode([x, y, z], 'vec3'), VectorNodeHandler); - return new VectorNode([x, y, z], 'vec3'); } From 9ca999eeefd52eeb726ae876304be112e76f0f06 Mon Sep 17 00:00:00 2001 From: lp407 <lukeplowden@gmail.com> Date: Thu, 20 Feb 2025 17:24:55 +0000 Subject: [PATCH 20/54] add escodegen, remove estree-walker --- package-lock.json | 53 +++++++++++++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfe61bad9d..da6f5826f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "acorn": "^8.12.1", "acorn-walk": "^8.3.4", "colorjs.io": "^0.5.2", - "estraverse": "^5.3.0", + "escodegen": "^2.1.0", "file-saver": "^1.3.8", "gifenc": "^1.0.3", "i18next": "^19.0.2", @@ -1426,6 +1426,12 @@ } } }, + "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/@rollup/plugin-json": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", @@ -1540,6 +1546,12 @@ } } }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.34.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.0.tgz", @@ -2100,16 +2112,6 @@ } } }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/@vitest/pretty-format": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", @@ -2195,6 +2197,13 @@ "source-map-js": "^1.2.0" } }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "optional": true + }, "node_modules/@vue/compiler-dom": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", @@ -2226,6 +2235,13 @@ "source-map-js": "^1.2.0" } }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "optional": true + }, "node_modules/@vue/compiler-ssr": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", @@ -4388,8 +4404,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -4410,7 +4424,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "optional": true, "engines": { @@ -4568,7 +4581,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -4614,17 +4626,18 @@ } }, "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, - "license": "MIT" + "dependencies": { + "@types/estree": "^1.0.0" + } }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" diff --git a/package.json b/package.json index dd9b44a6c0..90f53154ff 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "acorn": "^8.12.1", "acorn-walk": "^8.3.4", "colorjs.io": "^0.5.2", - "estraverse": "^5.3.0", + "escodegen": "^2.1.0", "file-saver": "^1.3.8", "gifenc": "^1.0.3", "i18next": "^19.0.2", From 2d79ac11c23cc08e4081b438a5f9935ebd31ed44 Mon Sep 17 00:00:00 2001 From: lp407 <lukeplowden@gmail.com> Date: Thu, 20 Feb 2025 17:26:07 +0000 Subject: [PATCH 21/54] first AST conversion (binary operators partly working) --- preview/global/sketch.js | 63 ++++++++++++++++++---------- src/webgl/ShaderGen.js | 88 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 124 insertions(+), 27 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index d5c9e20fff..951412c4b8 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -1,30 +1,51 @@ let myShader; p5.disableFriendlyErrors = true; + +function calculateOffset() { + return 30; +} + function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); + // Raw example + myShader = baseMaterialShader().modify(() => { + const offset = calculateOffset(); + + getWorldPosition((pos) => { + let a = createVector3(1, 2, 3); + let b = createVector3(3, 4, 5); + + a = (a * b + offset) / 10; + + pos += a; + + return pos; + }); + }); + + // Create and use the custom shader. - myShader = baseMaterialShader().modify( - () => { - const offset = uniform('offset', createVector3()) - getWorldPosition((pos) => { - let a = createVector3(1, 2, 3); - let b = createVector3(3, 4, 5); - a = a.add(b); - - let c = a.add(b); - c = c.add(offset); - c.x = b.x.add(1); - - - console.log("TEST:") - console.log(c.x); - pos = pos.add(c); - - return pos; - }) - } - ); + // myShader = baseMaterialShader().modify( + // () => { + // const offset = uniform('offset', () => calculateOffset) + + // getWorldPosition((pos) => { + // let a = createVector3(1, 2, 3); + // let b = createVector3(3, 4, 5); + // a = a.add(b); + + // let c = a.add(b); + // c = c.add(offset); + // c.x = b.x.add(1); + + + // pos = pos.add(c); + + // return pos; + // }) + // } + // ); } function draw(){ diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index feeb1723b5..6cd9b4d8e6 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -6,7 +6,8 @@ */ import { parse } from 'acorn'; -import estraverse from 'estraverse'; +import * as walk from 'acorn-walk'; +import escodegen from 'escodegen'; function shadergen(p5, fn) { let GLOBAL_SHADER; @@ -16,12 +17,14 @@ function shadergen(p5, fn) { p5.Shader.prototype.modify = function(arg) { if (arg instanceof Function) { - const fnSource = arg.toString() - const ast = parse(fnSource, { ecmaVersion: 2021, locations: true }); - const result = estraverse.traverse(ast, { - enter: (node) => console.log(node), - }) + const code = arg.toString() + const ast = parse(code, { ecmaVersion: 2021, locations: true }); + + walk.ancestor(ast, ASTCallbacks, null, {myData: 123}); + const transformed = escodegen.generate(ast); + console.log(transformed) + // const program = new ShaderProgram(arg) // const newArg = program.generate(); // console.log(newArg.vertex) @@ -32,6 +35,79 @@ function shadergen(p5, fn) { } } + // Transpiler + + function replaceBinaryOperator(codeSource) { + switch (codeSource) { + case '+': return 'add'; + case '-': return 'sub'; + case '*': return 'mult'; + case '/': return 'div'; + case '%': return 'mod'; + } + } + + const ASTCallbacks = { + Literal(node, state, ancestors) { + }, + AssignmentExpression(node, _state, ancestors) { + if (node.operator != '=') { + const rightReplacementNode = { + type: 'CallExpression', + callee: { + type: "MemberExpression", + object: { + type: "Identifier", + name: node.left.name + }, + property: { + type: "Identifier", + name: replaceBinaryOperator(node.operator.replace('=','')), + }, + }, + arguments: [node.right] + } + + node.operator = '='; + node.right = rightReplacementNode; + } + }, + BinaryExpression(node, state, ancestors) { + // let i = ancestors.length - 1; + // let ancestor = ancestors[i]; // ancestor === node + // while (ancestor.type === 'BinaryExpression') { + // ancestor = ancestors[i--]; + // } + + console.log("\n NEW NODE:") + + const transformed = escodegen.generate(node); + const l = escodegen.generate(node.left); + const r = escodegen.generate(node.right); + console.log("Transformed: ", transformed); + console.log("Left: ", l); + console.log("Right: ", r); + + console.log(node.left); + + node.type = 'CallExpression'; + console.log("OPERATOR: ", node.operator) + node.callee = { + type: "MemberExpression", + object: node.left, + property: { + type: "Identifier", + name: replaceBinaryOperator(node.operator), + }, + }; + node.arguments = [node.right]; + + }, + } + + + // JS API + class BaseNode { constructor(isInternal) { if (new.target === BaseNode) { From 17af9126a4952c9b52cb8c850bb429229fefe4c5 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Fri, 28 Feb 2025 18:08:52 +0000 Subject: [PATCH 22/54] slight refactoras will need a more general addComponent function --- src/webgl/ShaderGen.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 6cd9b4d8e6..e5d27b3351 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -357,21 +357,22 @@ function shadergen(p5, fn) { super(isInternal) this.name = name; this.type = type; - this.addComponents( - (() => { - switch (type) { - case 'vec2': return ['x', 'y']; - case 'vec3': return ['x', 'y', 'z']; - case 'vec4': return ['x', 'y', 'z', 'w']; - default: return []; - } - })() - ); - } - - addComponents(componentNames) { + this.autoAddVectorComponents(); + } + + addComponent(componentName) { + this[componentName] = new ComponentNode(this, componentName, true); + } + + autoAddVectorComponents() { + const options = { + vec2: ['x', 'y'], + vec3: ['x', 'y', 'z'], + vec4: ['x', 'y', 'z', 'w'] + }; + const componentNames = options[this.type] || []; for (let componentName of componentNames) { - this[componentName] = new ComponentNode(this, componentName, true); + this.addComponent(componentName); } } From 297d806c0fdbb727d89c4b6958528f99c64c5b70 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Fri, 28 Feb 2025 18:09:23 +0000 Subject: [PATCH 23/54] add some more built in functions (see TODO, unfinished) --- src/webgl/ShaderGen.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index e5d27b3351..24e358716b 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -186,7 +186,23 @@ function shadergen(p5, fn) { sin() { return new FunctionCallNode('sin', this, 'float'); } cos() { return new FunctionCallNode('cos', this, 'float'); } radians() { return new FunctionCallNode('radians', this, 'float'); } + abs() { return new FunctionCallNode('abs',this, this.type) }; + ceil() { return new FunctionCallNode(); } + + // TODO: + // Add a whole lot of these functions. Probably should take them out of the primitive node and just attach them to global instead. + // https://docs.gl/el3/ + max() { return new FunctionCallNode(); } + min() { return new FunctionCallNode(); } + ceil() { return new FunctionCallNode(); } + round() { return new FunctionCallNode(); } + roundEven() { return new FunctionCallNode(); } + sqrt() { return new FunctionCallNode(); } + log() { return new FunctionCallNode(); } + exp() { return new FunctionCallNode(); } + + // Check that the types of the operands are compatible. // TODO: improve this with less branching if elses enforceType(other){ From 1593916100e9cc8eb437a7abaac8f894afc5a330 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Fri, 28 Feb 2025 18:10:06 +0000 Subject: [PATCH 24/54] comment for clarity --- src/webgl/ShaderGen.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 24e358716b..9485bbb5f8 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -51,6 +51,7 @@ function shadergen(p5, fn) { Literal(node, state, ancestors) { }, AssignmentExpression(node, _state, ancestors) { + // Operator overloading if (node.operator != '=') { const rightReplacementNode = { type: 'CallExpression', From c4f3e4ee3e09e641097a2987e364ac49288095e0 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Fri, 28 Feb 2025 18:11:08 +0000 Subject: [PATCH 25/54] adding Conditionals (should cover ternaries and if statements) --- src/webgl/ShaderGen.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 9485bbb5f8..d7fb99ad95 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -480,6 +480,41 @@ function shadergen(p5, fn) { } } + // TODO: finish If Node + class ConditionalNode { + constructor(value) { + this.value = value; + this.condition = null; + this.thenBranch = null; + this.elseBranch = null; + } + //helper + checkType(value) { + + } + // conditions + equalTo(value){} + greaterThan(value) {} + greaterThanEqualTo(value) {} + lessThan(value) {} + lessThanEqualTo(value) {} + // modifiers + not() {} + or() {} + and() {} + // returns + thenReturn(value) {} + elseReturn(value) {} + // Then? + then() { + GLOBAL_SHADER.context.declarations.push() + } + }; + + fn.if = function (value) { + return new ConditionalNode(value); + } + // Helper functions function isShaderNode(node) { return (node instanceof BaseNode); From 168a50b314367040a516c8879eb1452dc67b43e6 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Fri, 28 Feb 2025 18:11:47 +0000 Subject: [PATCH 26/54] whitespace --- src/webgl/ShaderGen.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index d7fb99ad95..6e845adade 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -672,6 +672,7 @@ function shadergen(p5, fn) { function getWorldPosition(func){ GLOBAL_SHADER.getWorldPosition(func) } + function getFinalColor(func){ GLOBAL_SHADER.getFinalColor(func) } From 1f0a50fe34d82da28851e182af0b287961c54eca Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Sun, 2 Mar 2025 13:26:16 +0000 Subject: [PATCH 27/54] comment proxy for vectors until further work can be done on it --- src/webgl/ShaderGen.js | 53 +++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 6e845adade..f771fd6499 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -288,29 +288,29 @@ function shadergen(p5, fn) { // I am interested in the 'prop' value of get and set then - const VectorNodeHandler = { - swizzles: [ - ['x', 'y', 'z', 'w'], - ['r', 'b', 'g', 'a'], - ['s', 't', 'p', 'q'], - ], - get(target, prop, receiver) { - if (prop in target) { - return Reflect.get(target, prop, receiver); - } else { - console.log(prop); - } - let modifiedProp; - return Reflect.get(target, modifiedProp, receiver); - }, - set(obj, prop, receiver) { - console.log("OBJect:",obj,"PROPERTY", prop, "RECEIVER", receiver); - - if (prop in obj) { return Reflect.set(...arguments); } - let modifiedProp; - return Reflect.set(obj, modifiedProp, receiver); - } - } + // const VectorNodeHandler = { + // swizzles: [ + // ['x', 'y', 'z', 'w'], + // ['r', 'b', 'g', 'a'], + // ['s', 't', 'p', 'q'], + // ], + // get(target, prop, receiver) { + // if (prop in target) { + // return Reflect.get(target, prop, receiver); + // } else { + // console.log(prop); + // } + // let modifiedProp; + // return Reflect.get(target, modifiedProp, receiver); + // }, + // set(obj, prop, receiver) { + // console.log("OBJect:",obj,"PROPERTY", prop, "RECEIVER", receiver); + + // if (prop in obj) { return Reflect.set(...arguments); } + // let modifiedProp; + // return Reflect.set(obj, modifiedProp, receiver); + // } + // } class VectorNode extends BaseNode { constructor(values, type, isInternal = false) { @@ -631,16 +631,17 @@ function shadergen(p5, fn) { } fn.createVector2 = function(x, y) { - return new Proxy(new VectorNode([x, y], 'vec2'), VectorNodeHandler); + // return new Proxy(new VectorNode([x, y], 'vec2'), VectorNodeHandler); + return new VectorNode([x, y], 'vec2'); } fn.createVector3 = function(x, y, z) { - return new Proxy(new VectorNode([x, y, z], 'vec3'), VectorNodeHandler); + // return new Proxy(new VectorNode([x, y, z], 'vec3'), VectorNodeHandler); return new VectorNode([x, y, z], 'vec3'); } fn.createVector4 = function(x, y, z, w) { - return new Proxy(new VectorNode([x, y, z, w], 'vec4'), VectorNodeHandler); + // return new Proxy(new VectorNode([x, y, z, w], 'vec4'), VectorNodeHandler); return new VectorNode([x, y, z, w], 'vec4'); } From 62107b71c54bd0569497daef99d08ccda8918f86 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Sun, 2 Mar 2025 13:26:26 +0000 Subject: [PATCH 28/54] switch to simple walk --- src/webgl/ShaderGen.js | 60 ++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index f771fd6499..9d83fc00fa 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -6,7 +6,7 @@ */ import { parse } from 'acorn'; -import * as walk from 'acorn-walk'; +import { simple } from 'acorn-walk'; import escodegen from 'escodegen'; function shadergen(p5, fn) { @@ -14,24 +14,23 @@ function shadergen(p5, fn) { const oldModify = p5.Shader.prototype.modify - p5.Shader.prototype.modify = function(arg) { - - if (arg instanceof Function) { - const code = arg.toString() - const ast = parse(code, { ecmaVersion: 2021, locations: true }); + p5.Shader.prototype.modify = function(modifier) { + if (modifier instanceof Function) { + const code = modifier.toString() + const ast = parse(code, { ecmaVersion: 2021 /*, locations: true*/ }); - walk.ancestor(ast, ASTCallbacks, null, {myData: 123}); + simple(ast, ASTCallbacks); - const transformed = escodegen.generate(ast); - console.log(transformed) + const transpiledArg = escodegen.generate(ast); + console.log(transpiledArg) - // const program = new ShaderProgram(arg) + // const program = new ShaderProgram(modifier) // const newArg = program.generate(); // console.log(newArg.vertex) // return oldModify.call(this, newArg); } else { - return oldModify.call(this, arg) + return oldModify.call(this, modifier) } } @@ -48,11 +47,14 @@ function shadergen(p5, fn) { } const ASTCallbacks = { - Literal(node, state, ancestors) { + // TODO: automatically making uniforms + Literal(node) { }, - AssignmentExpression(node, _state, ancestors) { - // Operator overloading - if (node.operator != '=') { + + // The callbacks for AssignmentExpression and BinaryExpression handle + // operator overloading including +=, *= assignment expressions + AssignmentExpression(node) { + if (node.operator !== '=') { const rightReplacementNode = { type: 'CallExpression', callee: { @@ -68,31 +70,12 @@ function shadergen(p5, fn) { }, arguments: [node.right] } - node.operator = '='; node.right = rightReplacementNode; } }, - BinaryExpression(node, state, ancestors) { - // let i = ancestors.length - 1; - // let ancestor = ancestors[i]; // ancestor === node - // while (ancestor.type === 'BinaryExpression') { - // ancestor = ancestors[i--]; - // } - - console.log("\n NEW NODE:") - - const transformed = escodegen.generate(node); - const l = escodegen.generate(node.left); - const r = escodegen.generate(node.right); - console.log("Transformed: ", transformed); - console.log("Left: ", l); - console.log("Right: ", r); - - console.log(node.left); - + BinaryExpression(node) { node.type = 'CallExpression'; - console.log("OPERATOR: ", node.operator) node.callee = { type: "MemberExpression", object: node.left, @@ -102,12 +85,13 @@ function shadergen(p5, fn) { }, }; node.arguments = [node.right]; - }, } - // JS API + // Javascript Node API. + // These classes are for expressing GLSL functions in Javascript with + // needing to transpile the user's code. class BaseNode { constructor(isInternal) { @@ -121,7 +105,7 @@ function shadergen(p5, fn) { this.usedIn = []; this.dependsOn = []; this.srcLine = null; - + // Stack Capture is used to get the original line of user code for Debug purposes if (isInternal === false) { try { throw new Error("StackCapture"); From 41068cbfc3b8f9f490398bacfa866128bff3f3d1 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Wed, 5 Mar 2025 11:17:36 +0000 Subject: [PATCH 29/54] getting the options object populated properly. Create uniform functions programatically --- preview/global/sketch.js | 50 ++++++++++----------- src/webgl/ShaderGen.js | 93 ++++++++++++++++------------------------ 2 files changed, 63 insertions(+), 80 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 951412c4b8..4ccc11fe80 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -9,43 +9,43 @@ function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); // Raw example - myShader = baseMaterialShader().modify(() => { - const offset = calculateOffset(); + // myShader = baseMaterialShader().modify(() => { + // const offset = calculateOffset(); - getWorldPosition((pos) => { - let a = createVector3(1, 2, 3); - let b = createVector3(3, 4, 5); + // getWorldPosition((pos) => { + // let a = createVector3(1, 2, 3); + // let b = createVector3(3, 4, 5); - a = (a * b + offset) / 10; + // a = (a * b + offset) / 10; - pos += a; + // pos += a; - return pos; - }); - }); + // return pos; + // }); + // }); // Create and use the custom shader. - // myShader = baseMaterialShader().modify( - // () => { - // const offset = uniform('offset', () => calculateOffset) + myShader = baseMaterialShader().modify( + () => { + const offset = uniformFloat('offset', () => calculateOffset) - // getWorldPosition((pos) => { - // let a = createVector3(1, 2, 3); - // let b = createVector3(3, 4, 5); - // a = a.add(b); + getWorldPosition((pos) => { + let a = createVector3(1, 2, 3); + let b = createVector3(3, 4, 5); + a = a.add(b); - // let c = a.add(b); - // c = c.add(offset); - // c.x = b.x.add(1); + let c = a.add(b); + c += c.add(offset); + c.x = b.x.add(1); - // pos = pos.add(c); + pos = pos.add(c); - // return pos; - // }) - // } - // ); + return pos; + }) + } + ); } function draw(){ diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 9d83fc00fa..25a1ecef01 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -18,15 +18,17 @@ function shadergen(p5, fn) { if (modifier instanceof Function) { const code = modifier.toString() const ast = parse(code, { ecmaVersion: 2021 /*, locations: true*/ }); - simple(ast, ASTCallbacks); - const transpiledArg = escodegen.generate(ast); - console.log(transpiledArg) - - // const program = new ShaderProgram(modifier) - // const newArg = program.generate(); - // console.log(newArg.vertex) + const transpiledFn = new Function(transpiledArg + .slice(transpiledArg.indexOf("{") + 1, transpiledArg.lastIndexOf("}")) + ); + + const generator = new ShaderGenerator(transpiledFn, this) + const newArg = generator.callModifyFunction(); + console.log(code); + console.log(transpiledArg); + console.log(newArg) // return oldModify.call(this, newArg); } else { @@ -111,6 +113,7 @@ function shadergen(p5, fn) { throw new Error("StackCapture"); } catch (e) { const lines = e.stack.split("\n"); + // console.log(lines); let index = 5; if (isBinaryOperatorNode(this)) { index--; }; this.srcLine = lines[index].trim(); @@ -527,24 +530,18 @@ function shadergen(p5, fn) { // Shader program // This class is responsible for converting the nodes into an object containing GLSL code, to be used by p5.Shader.modify - class ShaderProgram { - constructor(modifyFunction) { - this.uniforms = { - int: {}, - float: {}, - vec2: {}, - vec3: {}, - vec4: {}, - texture: {}, - } - this.functions = { - } - this.resetGLSLContext(); + class ShaderGenerator { + constructor(modifyFunction, shaderToModify) { + this.modifyFunction = modifyFunction; + this.shaderToModify = shaderToModify; + shaderToModify.inspectHooks(); GLOBAL_SHADER = this; - this.generator = modifyFunction; + this.uniforms = {}; + this.functions = {}; + this.resetGLSLContext(); } - generate() { - this.generator(); + callModifyFunction() { + this.modifyFunction(); return { uniforms: this.uniforms, functions: this.functions, @@ -560,35 +557,6 @@ function shadergen(p5, fn) { } } // TODO: - uniformInt(name, defaultValue) { - this.uniforms.int[name] = defaultValue; - return new VariableNode(name, 'int'); - } - uniformFloat(name, defaultValue) { - this.uniforms.float[name] = defaultValue; - return new VariableNode(name, 'float'); - } - uniformVector2(name, defaultValue) { - this.uniforms.vec2[name] = defaultValue; - return new VariableNode(name, 'vec2'); - } - uniformVector2(name, defaultValue) { - this.uniforms.vec3[name] = defaultValue; - return new VariableNode(name, 'vec3'); - } - uniformVector2(name, defaultValue) { - this.uniforms.vec4[name] = defaultValue; - return new VariableNode(name, 'vec4'); - } - uniformTexture(name, defaultValue) { - this.uniforms.texture[name] = defaultValue; - return new VariableNode(name, 'vec4'); - } - uniform(name, defaultValue) { - this.uniforms[name] = defaultValue; - return new VariableNode(name, defaultValue.type); - } - buildFunction(argumentName, argumentType, callback) { let functionArgument = new VariableNode(argumentName, argumentType, true); const finalLine = callback(functionArgument).toGLSLBase(this.context); @@ -649,9 +617,24 @@ function shadergen(p5, fn) { return new VariableNode('discard', 'keyword'); } - fn.uniform = function(name, value) { - let result = GLOBAL_SHADER.uniform(name, value) - return result; + // Uniforms and attributes + const uniformFns = { + 'int': 'Int', + 'float': 'Float', + 'vec2': 'Vector2', + 'vec3': 'Vector3', + 'vec4': 'Vector4', + 'sampler2D': 'Texture', + }; + for (const type in uniformFns) { + const uniformFnVariant = `uniform${uniformFns[type]}`; + ShaderGenerator.prototype[uniformFnVariant] = function(name, defaultValue) { + this.uniforms[`${type} ${name}`] = defaultValue; + return new VariableNode(name, type); + }; + fn[uniformFnVariant] = function (name, value) { + return GLOBAL_SHADER[uniformFnVariant](name, value); + }; } function getWorldPosition(func){ From 6c582b9b1bb21f5953c12317712f014f387dbe56 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Wed, 5 Mar 2025 16:07:46 +0000 Subject: [PATCH 30/54] build shader hooks from existing API --- preview/global/sketch.js | 54 +++++------ src/webgl/ShaderGen.js | 203 +++++++++++++++++++++++++-------------- 2 files changed, 157 insertions(+), 100 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 4ccc11fe80..cf62f93bc2 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -8,44 +8,42 @@ function calculateOffset() { function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); - // Raw example - // myShader = baseMaterialShader().modify(() => { - // const offset = calculateOffset(); + // // Raw example + myShader = baseMaterialShader().modify(() => { - // getWorldPosition((pos) => { - // let a = createVector3(1, 2, 3); - // let b = createVector3(3, 4, 5); + const offset = uniformFloat(1); - // a = (a * b + offset) / 10; - - // pos += a; - - // return pos; - // }); - // }); + getFinalColor((col) => { + let a = createVector4(1, 2, 3, 4); + let b = createVector4(3, 4, 5, 6); + a = (a * b + offset) / 10; + col += a; + return col; + }); + }); // Create and use the custom shader. - myShader = baseMaterialShader().modify( - () => { - const offset = uniformFloat('offset', () => calculateOffset) + // myShader = baseMaterialShader().modify( + // () => { + // const offset = uniformFloat('offset', () => calculateOffset) - getWorldPosition((pos) => { - let a = createVector3(1, 2, 3); - let b = createVector3(3, 4, 5); - a = a.add(b); + // getWorldPosition((pos) => { + // let a = createVector3(1, 2, 3); + // let b = createVector3(3, 4, 5); + // a = a.add(b); - let c = a.add(b); - c += c.add(offset); - c.x = b.x.add(1); + // let c = a.add(b); + // c += c.add(offset); + // c.x = b.x.add(1); - pos = pos.add(c); + // pos = pos.add(c); - return pos; - }) - } - ); + // return pos; + // }) + // } + // ); } function draw(){ diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 25a1ecef01..f6000f409a 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -36,8 +36,7 @@ function shadergen(p5, fn) { } } - // Transpiler - + // AST Transpiler Callbacks and their helpers function replaceBinaryOperator(codeSource) { switch (codeSource) { case '+': return 'add'; @@ -50,8 +49,7 @@ function shadergen(p5, fn) { const ASTCallbacks = { // TODO: automatically making uniforms - Literal(node) { - }, + // The callbacks for AssignmentExpression and BinaryExpression handle // operator overloading including +=, *= assignment expressions @@ -92,9 +90,8 @@ function shadergen(p5, fn) { // Javascript Node API. - // These classes are for expressing GLSL functions in Javascript with + // These classes are for expressing GLSL functions in Javascript without // needing to transpile the user's code. - class BaseNode { constructor(isInternal) { if (new.target === BaseNode) { @@ -125,7 +122,7 @@ function shadergen(p5, fn) { toGLSLBase(context){ if (this.useTempVar()) { return this.getTemp(context); - } + } else { return this.toGLSL(context); } @@ -171,25 +168,6 @@ function shadergen(p5, fn) { 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)); } - sin() { return new FunctionCallNode('sin', this, 'float'); } - cos() { return new FunctionCallNode('cos', this, 'float'); } - radians() { return new FunctionCallNode('radians', this, 'float'); } - abs() { return new FunctionCallNode('abs',this, this.type) }; - ceil() { return new FunctionCallNode(); } - - // TODO: - // Add a whole lot of these functions. Probably should take them out of the primitive node and just attach them to global instead. - // https://docs.gl/el3/ - - max() { return new FunctionCallNode(); } - min() { return new FunctionCallNode(); } - ceil() { return new FunctionCallNode(); } - round() { return new FunctionCallNode(); } - roundEven() { return new FunctionCallNode(); } - sqrt() { return new FunctionCallNode(); } - log() { return new FunctionCallNode(); } - exp() { return new FunctionCallNode(); } - // Check that the types of the operands are compatible. // TODO: improve this with less branching if elses @@ -302,13 +280,10 @@ function shadergen(p5, fn) { class VectorNode extends BaseNode { constructor(values, type, isInternal = false) { super(isInternal); - this.components = ['x', 'y', 'z', 'w'].slice(0, values.length); this.components.forEach((component, i) => { this[component] = new FloatNode(values[i], true); - // this[component] = new ComponentNode(this, component, true); }); - this.type = type; } @@ -468,16 +443,13 @@ function shadergen(p5, fn) { } // TODO: finish If Node - class ConditionalNode { + class ConditionalNode extends BaseNode { constructor(value) { + super(value); this.value = value; this.condition = null; this.thenBranch = null; this.elseBranch = null; - } - //helper - checkType(value) { - } // conditions equalTo(value){} @@ -492,12 +464,21 @@ function shadergen(p5, fn) { // returns thenReturn(value) {} elseReturn(value) {} - // Then? - then() { - GLOBAL_SHADER.context.declarations.push() - } + // + thenDiscard() { + new ConditionalDiscard(this.condition); + }; }; + class ConditionalDiscard extends Base{ + constructor(condition){ + this.condition = condition; + } + toGLSL(context) { + context.discardConditions.push(`if(${this.condition}{discard;})`); + } + } + fn.if = function (value) { return new ConditionalNode(value); } @@ -531,24 +512,61 @@ function shadergen(p5, fn) { // This class is responsible for converting the nodes into an object containing GLSL code, to be used by p5.Shader.modify class ShaderGenerator { - constructor(modifyFunction, shaderToModify) { - this.modifyFunction = modifyFunction; - this.shaderToModify = shaderToModify; - shaderToModify.inspectHooks(); + constructor(modifyFunction, originalShader) { GLOBAL_SHADER = this; - this.uniforms = {}; - this.functions = {}; + this.modifyFunction = modifyFunction; + this.generateHookBuilders(originalShader); + this.output = { + uniforms: {}, + } this.resetGLSLContext(); } + callModifyFunction() { this.modifyFunction(); - return { - uniforms: this.uniforms, - functions: this.functions, - vertex: this.functions.getWorldPosition, - fragment: this.functions.getFinalColor, + return this.output; + } + + generateHookBuilders(originalShader) { + const availableHooks = { + ...originalShader.hooks.vertex, + ...originalShader.hooks.fragment, } + + // Defines a function for each of the hooks for the shader we are modifying. + Object.keys(availableHooks).forEach((hookName) => { + const hookTypes = originalShader.hookTypes(hookName) + this[hookTypes.name] = function(userOverride) { + let hookArgs = [] + let argsArray = []; + + hookTypes.parameters.forEach((parameter, i) => { + hookArgs.push( + new VariableNode(parameter.name, parameter.type.typeName, true) + ); + if (i === 0) { + argsArray.push(`${parameter.type.typeName} ${parameter.name}`); + } else { + argsArray.push(`, ${parameter.type.typeName} ${parameter.name}`); + } + }) + + const toGLSLResult = userOverride(...hookArgs).toGLSLBase(this.context); + let codeLines = [`(${argsArray.join(', ')}) {`, this.context.declarations.slice()].flat(); + codeLines.push(`\n${hookTypes.returnType.typeName} finalReturnValue = ${toGLSLResult}; + \nreturn finalReturnValue; + \n}`); + this.output[hookName] = codeLines.join('\n'); + this.resetGLSLContext(); + } + + // Expose the Functions to global scope for users to use + window[hookTypes.name] = function(userOverride) { + GLOBAL_SHADER[hookTypes.name](userOverride); + }; + }) } + resetGLSLContext() { this.context = { id: 0, @@ -556,22 +574,6 @@ function shadergen(p5, fn) { declarations: [], } } - // TODO: - buildFunction(argumentName, argumentType, callback) { - let functionArgument = new VariableNode(argumentName, argumentType, true); - const finalLine = callback(functionArgument).toGLSLBase(this.context); - let codeLines = this.context.declarations.slice(); - codeLines.push(`\n${argumentName} = ${finalLine}; \nreturn ${argumentName};`) - this.resetGLSLContext(); - return codeLines.join("\n"); - } - - getWorldPosition(func) { - this.functions.getWorldPosition = this.buildFunction("pos", "vec3", func); - } - getFinalColor(func) { - this.functions.getFinalColor = this.buildFunction("col", "vec3", func); - } } // User functions @@ -629,7 +631,7 @@ function shadergen(p5, fn) { for (const type in uniformFns) { const uniformFnVariant = `uniform${uniformFns[type]}`; ShaderGenerator.prototype[uniformFnVariant] = function(name, defaultValue) { - this.uniforms[`${type} ${name}`] = defaultValue; + this.output.uniforms[`${type} ${name}`] = defaultValue; return new VariableNode(name, type); }; fn[uniformFnVariant] = function (name, value) { @@ -637,12 +639,69 @@ function shadergen(p5, fn) { }; } - function getWorldPosition(func){ - GLOBAL_SHADER.getWorldPosition(func) - } - - function getFinalColor(func){ - GLOBAL_SHADER.getFinalColor(func) + // GLSL Built in functions + // TODO: + // Add a whole lot of these functions. + // https://docs.gl/el3/abs + const builtInFunctions = { + // Trigonometry + 'acos': {}, + 'acosh': {}, + 'asin': {}, + 'asinh': {}, + 'atan': {}, + 'atanh': {}, + 'cos': {}, + 'cosh': {}, + 'degrees': {}, + 'radians': {}, + 'sin': {}, + 'sinh': {}, + 'tan': {}, + 'tanh': {}, + // Mathematics + 'abs': {}, + 'ceil': {}, + 'clamp': {}, + 'dFdx': {}, + 'dFdy': {}, + 'exp': {}, + 'exp2': {}, + 'floor': {}, + 'fma': {}, + 'fract': {}, + 'fwidth': {}, + 'inversesqrt': {}, + 'isinf': {}, + 'isnan': {}, + 'log': {}, + 'log2': {}, + 'max': {}, + 'min': {}, + 'mix': {}, + 'mod': {}, + 'modf': {}, + 'pow': {}, + 'round': {}, + 'roundEven': {}, + 'sign': {}, + 'smoothstep': {}, + 'sqrt': {}, + 'step': {}, + 'trunc': {}, + // Vector + 'cross': {}, + 'distance': {}, + 'dot': {}, + 'equal': {}, + 'faceforward': {}, + 'length': {}, + 'normalize': {}, + 'notEqual': {}, + 'reflect': {}, + 'refract': {}, + // Texture sampling + 'texture': {}, } const oldTexture = p5.prototype.texture; From 7bae49583d6e43b051577dd698d7e9f5395246f7 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Wed, 5 Mar 2025 16:41:53 +0000 Subject: [PATCH 31/54] added automatic uniform names --- src/webgl/ShaderGen.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index f6000f409a..1825b1931d 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -48,9 +48,15 @@ function shadergen(p5, fn) { } const ASTCallbacks = { - // TODO: automatically making uniforms - - + VariableDeclarator(node) { + if (node.init.callee && node.init.callee.name.slice(0, 7) === 'uniform') { + const uniformNameLiteral = { + type: 'Literal', + value: node.id.name + } + node.init.arguments.unshift(uniformNameLiteral); + } + }, // The callbacks for AssignmentExpression and BinaryExpression handle // operator overloading including +=, *= assignment expressions AssignmentExpression(node) { @@ -632,7 +638,8 @@ function shadergen(p5, fn) { const uniformFnVariant = `uniform${uniformFns[type]}`; ShaderGenerator.prototype[uniformFnVariant] = function(name, defaultValue) { this.output.uniforms[`${type} ${name}`] = defaultValue; - return new VariableNode(name, type); + let safeType = type === 'sampler2D' ? 'vec4' : type; + return new VariableNode(name, safeType); }; fn[uniformFnVariant] = function (name, value) { return GLOBAL_SHADER[uniformFnVariant](name, value); From d6092404b96aca095f584210abb1bbca32917948 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 11 Mar 2025 15:33:20 +0000 Subject: [PATCH 32/54] fixing component assignments for binary operators and variables --- src/webgl/ShaderGen.js | 360 ++++++++++++++++++----------------------- 1 file changed, 161 insertions(+), 199 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 1825b1931d..1f7099ce84 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -14,25 +14,35 @@ function shadergen(p5, fn) { const oldModify = p5.Shader.prototype.modify - p5.Shader.prototype.modify = function(modifier) { - if (modifier instanceof Function) { - const code = modifier.toString() - const ast = parse(code, { ecmaVersion: 2021 /*, locations: true*/ }); - simple(ast, ASTCallbacks); - const transpiledArg = escodegen.generate(ast); - const transpiledFn = new Function(transpiledArg - .slice(transpiledArg.indexOf("{") + 1, transpiledArg.lastIndexOf("}")) - ); - - const generator = new ShaderGenerator(transpiledFn, this) - const newArg = generator.callModifyFunction(); - console.log(code); - console.log(transpiledArg); - console.log(newArg) - // return oldModify.call(this, newArg); + p5.Shader.prototype.modify = function(shaderModifier, options = { parser: true, srcLocations: false }) { + if (shaderModifier instanceof Function) { + let generatorFunction; + if (options.parser) { + const sourceString = shaderModifier.toString() + const ast = parse(sourceString, { + ecmaVersion: 2021, + locations: options.srcLocations + }); + simple(ast, ASTCallbacks); + const transpiledSource = escodegen.generate(ast); + generatorFunction = new Function( + transpiledSource.slice( + transpiledSource.indexOf("{") + 1, + transpiledSource.lastIndexOf("}") + ) + ); + } else { + generatorFunction = shaderModifier; + } + const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations) + const generatedModifyArgument = generator.hookCallbacks(); + console.log("SRC STRING: ", generatorFunction); + console.log("NEW OPTIONS:", generatedModifyArgument['vec4 getFinalColor']) + + return oldModify.call(this, generatedModifyArgument); } else { - return oldModify.call(this, modifier) + return oldModify.call(this, shaderModifier) } } @@ -49,7 +59,7 @@ function shadergen(p5, fn) { const ASTCallbacks = { VariableDeclarator(node) { - if (node.init.callee && node.init.callee.name.slice(0, 7) === 'uniform') { + if (node.init.callee && node.init.callee.name.startsWith('uniform')) { const uniformNameLiteral = { type: 'Literal', value: node.id.name @@ -61,18 +71,17 @@ function shadergen(p5, fn) { // operator overloading including +=, *= assignment expressions AssignmentExpression(node) { if (node.operator !== '=') { + const methodName = replaceBinaryOperator(node.operator.replace('=','')); const rightReplacementNode = { type: 'CallExpression', callee: { type: "MemberExpression", - object: { - type: "Identifier", - name: node.left.name - }, + object: node.left, property: { type: "Identifier", - name: replaceBinaryOperator(node.operator.replace('=','')), + name: methodName, }, + computed: false, }, arguments: [node.right] } @@ -99,54 +108,42 @@ function shadergen(p5, fn) { // These classes are for expressing GLSL functions in Javascript without // needing to transpile the user's code. class BaseNode { - constructor(isInternal) { + constructor(isInternal, type) { if (new.target === BaseNode) { throw new TypeError("Cannot construct BaseNode instances directly. This is an abstract class."); } - this.type = null; - + this.type = type; + // For tracking recursion depth and creating temporary variables this.isInternal = isInternal; this.usedIn = []; this.dependsOn = []; this.srcLine = null; // Stack Capture is used to get the original line of user code for Debug purposes - if (isInternal === false) { + if (GLOBAL_SHADER.srcLocations === true && isInternal === false) { try { throw new Error("StackCapture"); } catch (e) { const lines = e.stack.split("\n"); - // console.log(lines); - let index = 5; - if (isBinaryOperatorNode(this)) { index--; }; - this.srcLine = lines[index].trim(); + let userSketchLineIndex = 5; + if (isBinaryOperatorNode(this)) { userSketchLineIndex--; }; + this.srcLine = lines[userSketchLineIndex].trim(); } } } // The base node implements a version of toGLSL which determines whether the generated code should be stored in a temporary variable. toGLSLBase(context){ - if (this.useTempVar()) { - return this.getTemp(context); + if (this.shouldUseTemporaryVariable()) { + return this.getTemporaryVariable(context); } else { return this.toGLSL(context); } } - // useTempVar() { - // if (isBinaryOperatorNode(this)) { - // console.log(this.a); - // console.log(this.b); - // return false; - // } - // if (this.isInternal) { return false; } - // if (isVariableNode(this)) { return false; } - // if (isVectorNode(this)) { return true; } - // return (this.usedIn.length > 1); - // }; - - useTempVar() { + shouldUseTemporaryVariable() { + if (this.temporaryVariable) if (this.isInternal || isVariableNode(this)) { return false; } let score = 0; score += isBinaryOperatorNode(this); @@ -155,7 +152,7 @@ function shadergen(p5, fn) { return score > 3; } - getTemp(context) { + getTemporaryVariable(context) { if (!this.temporaryVariable) { this.temporaryVariable = `temp_${context.getNextID()}`; let line = ""; @@ -168,7 +165,7 @@ function shadergen(p5, fn) { return this.temporaryVariable; }; - // TODO: Add more operations here + // 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), '*'); } @@ -194,6 +191,22 @@ function shadergen(p5, fn) { return new this.constructor(other); } } + + addComponent(componentName, type='float') { + this[componentName] = new ComponentNode(this, componentName, type, true); + } + + autoAddVectorComponents() { + const options = { + vec2: ['x', 'y'], + vec3: ['x', 'y', 'z'], + vec4: ['x', 'y', 'z', 'w'] + }; + const componentNames = options[this.type] || []; + for (let componentName of componentNames) { + this.addComponent(componentName); + } + } toGLSL(context){ throw new TypeError("Not supposed to call this function on BaseNode, which is an abstract class."); @@ -203,9 +216,8 @@ function shadergen(p5, fn) { // Primitive Types class IntNode extends BaseNode { constructor(x = 0, isInternal = false) { - super(isInternal); + super(isInternal, 'int'); this.x = x; - this.type = 'int'; } toGLSL(context) { @@ -224,9 +236,8 @@ function shadergen(p5, fn) { class FloatNode extends BaseNode { constructor(x = 0, isInternal = false){ - super(isInternal); + super(isInternal, 'float'); this.x = x; - this.type = 'float'; } toGLSL(context) { @@ -243,54 +254,14 @@ function shadergen(p5, fn) { } } - // TODO: - // There is a possibility that since we *always* use a temporary variable for vectors - // that we don't actually need a Float Node for every component. They could be component node's instead? - // May need to then store the temporary variable name in this class. - - // This would be the next step before adding all swizzles - - // I think it is also possible to make a proxy for vector nodes to check if the user accesses or sets any - // property .xyz / .rgb /.zxx etc as then we can automatically assign swizzles and only define .xyzw on the actual - // object. - - // Ultimately vectors are the focus of & most complex type in glsl (before we add matrices...) which justifies the complexity - // of this class. - - // I am interested in the 'prop' value of get and set then - - // const VectorNodeHandler = { - // swizzles: [ - // ['x', 'y', 'z', 'w'], - // ['r', 'b', 'g', 'a'], - // ['s', 't', 'p', 'q'], - // ], - // get(target, prop, receiver) { - // if (prop in target) { - // return Reflect.get(target, prop, receiver); - // } else { - // console.log(prop); - // } - // let modifiedProp; - // return Reflect.get(target, modifiedProp, receiver); - // }, - // set(obj, prop, receiver) { - // console.log("OBJect:",obj,"PROPERTY", prop, "RECEIVER", receiver); - - // if (prop in obj) { return Reflect.set(...arguments); } - // let modifiedProp; - // return Reflect.set(obj, modifiedProp, receiver); - // } - // } class VectorNode extends BaseNode { constructor(values, type, isInternal = false) { - super(isInternal); + super(isInternal, type); this.components = ['x', 'y', 'z', 'w'].slice(0, values.length); this.components.forEach((component, i) => { this[component] = new FloatNode(values[i], true); }); - this.type = type; } toGLSL(context) { @@ -308,11 +279,9 @@ function shadergen(p5, fn) { // Function Call Nodes class FunctionCallNode extends BaseNode { constructor(name, args, type, isInternal = false) { - super(isInternal); + super(isInternal, type); this.name = name; this.args = args; - this.type = type; - // TODO: this.argumentTypes = { @@ -320,12 +289,8 @@ function shadergen(p5, fn) { } deconstructArgs(context) { - if (this.args.constructor === Array) { - let argsString = `${this.args[0].toGLSLBase(context)}` - for (let arg of this.args.slice(1)) { - argsString += `, ${arg.toGLSLBase(context)}` - return argsString; - } + if (Array.isArray(this.args)) { + return this.args.map((argNode) => argNode.toGLSLBase(context)).join(', '); } else { return `${this.args.toGLSLBase(context)}`; } @@ -339,27 +304,10 @@ function shadergen(p5, fn) { // Variables and member variable nodes class VariableNode extends BaseNode { constructor(name, type, isInternal = false) { - super(isInternal) + super(isInternal, type) this.name = name; - this.type = type; this.autoAddVectorComponents(); } - - addComponent(componentName) { - this[componentName] = new ComponentNode(this, componentName, true); - } - - autoAddVectorComponents() { - const options = { - vec2: ['x', 'y'], - vec3: ['x', 'y', 'z'], - vec4: ['x', 'y', 'z', 'w'] - }; - const componentNames = options[this.type] || []; - for (let componentName of componentNames) { - this.addComponent(componentName); - } - } toGLSL(context) { return `${this.name}`; @@ -367,14 +315,13 @@ function shadergen(p5, fn) { } class ComponentNode extends BaseNode { - constructor(parent, component, isInternal = false) { - super(isInternal); + constructor(parent, componentName, type, isInternal = false) { + super(isInternal, type); this.parent = parent; - this.component = component; + this.component = componentName; this.type = 'float'; } toGLSL(context) { - // CURRENTLY BROKEN: // const parentName = this.parent.toGLSLBase(context); const parentName = this.parent.temporaryVariable ? this.parent.temporaryVariable : this.parent.name; return `${parentName}.${this.component}`; @@ -384,7 +331,7 @@ function shadergen(p5, fn) { // Binary Operator Nodes class BinaryOperatorNode extends BaseNode { constructor(a, b, operator, isInternal = false) { - super(isInternal); + super(isInternal, null); this.op = operator; this.a = a; this.b = b; @@ -394,7 +341,7 @@ function shadergen(p5, fn) { this.type = this.determineType(); } - // We know that both this.a and this.b are nodes because of PrimitiveNode.enforceType + // We know that both this.a and this.b are nodes because of BaseNode.enforceType determineType() { if (this.a.type === this.b.type) { return this.a.type; @@ -476,7 +423,7 @@ function shadergen(p5, fn) { }; }; - class ConditionalDiscard extends Base{ + class ConditionalDiscard extends BaseNode { constructor(condition){ this.condition = condition; } @@ -489,7 +436,7 @@ function shadergen(p5, fn) { return new ConditionalNode(value); } - // Helper functions + // Node Helper functions function isShaderNode(node) { return (node instanceof BaseNode); } @@ -514,13 +461,14 @@ function shadergen(p5, fn) { return (node instanceof VariableNode || node instanceof ComponentNode || typeof(node.temporaryVariable) != 'undefined'); } - // Shader program + // Shader Generator // This class is responsible for converting the nodes into an object containing GLSL code, to be used by p5.Shader.modify class ShaderGenerator { - constructor(modifyFunction, originalShader) { + constructor(modifyFunction, originalShader, srcLocations) { GLOBAL_SHADER = this; this.modifyFunction = modifyFunction; + this.srcLocations = srcLocations; this.generateHookBuilders(originalShader); this.output = { uniforms: {}, @@ -528,7 +476,7 @@ function shadergen(p5, fn) { this.resetGLSLContext(); } - callModifyFunction() { + hookCallbacks() { this.modifyFunction(); return this.output; } @@ -556,6 +504,7 @@ function shadergen(p5, fn) { argsArray.push(`, ${parameter.type.typeName} ${parameter.name}`); } }) + console.log(hookArgs) const toGLSLResult = userOverride(...hookArgs).toGLSLBase(this.context); let codeLines = [`(${argsArray.join(', ')}) {`, this.context.declarations.slice()].flat(); @@ -582,37 +531,18 @@ function shadergen(p5, fn) { } } - // User functions - fn.getWorldPosition = function(func){ - GLOBAL_SHADER.getWorldPosition(func) - } - fn.getFinalColor = function(func){ - GLOBAL_SHADER.getFinalColor(func) - } - - fn.createVector2 = function(x, y) { - // return new Proxy(new VectorNode([x, y], 'vec2'), VectorNodeHandler); - return new VectorNode([x, y], 'vec2'); - } - - fn.createVector3 = function(x, y, z) { - // return new Proxy(new VectorNode([x, y, z], 'vec3'), VectorNodeHandler); - return new VectorNode([x, y, z], 'vec3'); + // User function helpers + function conformVectorParameters(value, vectorDimensions) { + // Allow arguments as arrays ([0,0,0,0]) or not (0,0,0,0) + value = value.flat(); + // Populate arguments so uniformVector3(0) becomes [0,0,0] + if (value.length === 1) { + value = Array(vectorDimensions).fill(value[0]); + } + return value; } - fn.createVector4 = function(x, y, z, w) { - // return new Proxy(new VectorNode([x, y, z, w], 'vec4'), VectorNodeHandler); - return new VectorNode([x, y, z, w], 'vec4'); - } - - fn.createFloat = function(x) { - return new FloatNode(x); - } - - fn.createInt = function(x) { - return new IntNode(x); - } - + // User functions fn.instanceID = function() { return new VariableNode('gl_InstanceID', 'int'); } @@ -625,47 +555,76 @@ function shadergen(p5, fn) { return new VariableNode('discard', 'keyword'); } - // Uniforms and attributes - const uniformFns = { - 'int': 'Int', - 'float': 'Float', - 'vec2': 'Vector2', - 'vec3': 'Vector3', - 'vec4': 'Vector4', - 'sampler2D': 'Texture', + // Generating uniformFloat, uniformVec, createFloat, etc functions + // Maps a GLSL type to the name suffix for method names + const GLSLTypesToSuffixes = { + int: 'Int', + float: 'Float', + vec2: 'Vector2', + vec3: 'Vector3', + vec4: 'Vector4', + sampler2D: 'Texture', + }; + 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'), }; - for (const type in uniformFns) { - const uniformFnVariant = `uniform${uniformFns[type]}`; - ShaderGenerator.prototype[uniformFnVariant] = function(name, defaultValue) { - this.output.uniforms[`${type} ${name}`] = defaultValue; - let safeType = type === 'sampler2D' ? 'vec4' : type; + for (const glslType in GLSLTypesToSuffixes) { + // Generate uniform*() Methods for creating uniforms + const typeIdentifier = GLSLTypesToSuffixes[glslType]; + const uniformMethodName = `uniform${typeIdentifier}`; + ShaderGenerator.prototype[uniformMethodName] = function(...args) { + let [name, ...defaultValue] = args; + if(glslType.startsWith('vec')) { + defaultValue = conformVectorParameters(defaultValue, +glslType.slice(3)); + } + this.output.uniforms[`${glslType} ${name}`] = defaultValue; + let safeType = glslType === 'sampler2D' ? 'vec4' : glslType; return new VariableNode(name, safeType); }; - fn[uniformFnVariant] = function (name, value) { - return GLOBAL_SHADER[uniformFnVariant](name, value); + fn[uniformMethodName] = function (...args) { + return GLOBAL_SHADER[uniformMethodName](...args); }; + + // Generate the create*() Methods for creating variables in shaders + if (glslType === 'sampler2D') { continue; } + const createMethodName = `create${typeIdentifier}`; + fn[createMethodName] = function (...value) { + if(glslType.startsWith('vec')) { + value = conformVectorParameters(value, +glslType.slice(3)); + } else { + value = value[0]; + } + return nodeConstructors[glslType](value); + } } // GLSL Built in functions // TODO: // Add a whole lot of these functions. // https://docs.gl/el3/abs + + // constructor(name, args, type, isInternal = false) { + const builtInFunctions = { // Trigonometry 'acos': {}, - 'acosh': {}, + // 'acosh': {}, 'asin': {}, - 'asinh': {}, + // 'asinh': {}, 'atan': {}, - 'atanh': {}, + // 'atanh': {}, 'cos': {}, - 'cosh': {}, + // 'cosh': {}, 'degrees': {}, 'radians': {}, 'sin': {}, - 'sinh': {}, + // 'sinh': {}, 'tan': {}, - 'tanh': {}, + // 'tanh': {}, // Mathematics 'abs': {}, 'ceil': {}, @@ -675,42 +634,45 @@ function shadergen(p5, fn) { 'exp': {}, 'exp2': {}, 'floor': {}, - 'fma': {}, + // 'fma': {}, 'fract': {}, - 'fwidth': {}, - 'inversesqrt': {}, - 'isinf': {}, - 'isnan': {}, - 'log': {}, - 'log2': {}, + // 'fwidth': {}, + // 'inversesqrt': {}, + // 'isinf': {}, + // 'isnan': {}, + // 'log': {}, + // 'log2': {}, 'max': {}, 'min': {}, 'mix': {}, - 'mod': {}, - 'modf': {}, + // 'mod': {}, + // 'modf': {}, 'pow': {}, - 'round': {}, - 'roundEven': {}, - 'sign': {}, + // 'round': {}, + // 'roundEven': {}, + // 'sign': {}, 'smoothstep': {}, 'sqrt': {}, 'step': {}, - 'trunc': {}, + // 'trunc': {}, // Vector 'cross': {}, 'distance': {}, 'dot': {}, - 'equal': {}, - 'faceforward': {}, + // 'equal': {}, + // 'faceforward': {}, 'length': {}, 'normalize': {}, - 'notEqual': {}, - 'reflect': {}, - 'refract': {}, + // 'notEqual': {}, + // 'reflect': {}, + // 'refract': {}, // Texture sampling 'texture': {}, } + // Object.entries(builtInFunctions).forEach(([glslFnName, properties]) => { + // }) + const oldTexture = p5.prototype.texture; p5.prototype.texture = function(...args) { if (isShaderNode(args[0])) { From b473ac8d342f4ff3071f50aa99a7cf7bd5c5f444 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 11 Mar 2025 18:26:24 +0000 Subject: [PATCH 33/54] got the swizzle assignments working --- src/webgl/ShaderGen.js | 117 +++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 40 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 1f7099ce84..df54b495d1 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -35,7 +35,7 @@ function shadergen(p5, fn) { generatorFunction = shaderModifier; } const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations) - const generatedModifyArgument = generator.hookCallbacks(); + const generatedModifyArgument = generator.generate(); console.log("SRC STRING: ", generatorFunction); console.log("NEW OPTIONS:", generatedModifyArgument['vec4 getFinalColor']) @@ -113,7 +113,9 @@ function shadergen(p5, fn) { throw new TypeError("Cannot construct BaseNode instances directly. This is an abstract class."); } this.type = type; - + this.componentNames = []; + this.swizzleAccessed = false; + this.swizzleChanged = false; // For tracking recursion depth and creating temporary variables this.isInternal = isInternal; this.usedIn = []; @@ -131,7 +133,36 @@ function shadergen(p5, fn) { } } } - + // get type() { + // return this._type; + // } + + // set type(value) { + // this._type = value; + // } + + addVectorComponents() { + if (this.type.startsWith('vec')) { + const vectorDimensions = +this.type.slice(3); + this.componentNames = ['x', 'y', 'z', 'w'].slice(0, vectorDimensions); + + for (let componentName of this.componentNames) { + // let value = new FloatNode() + let value = new ComponentNode(this, componentName, 'float', true); + Object.defineProperty(this, componentName, { + get() { + this.swizzleAccessed = true; + return value; + }, + set(newValue) { + this.swizzleChanged = true; + value = newValue; + } + }) + } + } + } + // The base node implements a version of toGLSL which determines whether the generated code should be stored in a temporary variable. toGLSLBase(context){ if (this.shouldUseTemporaryVariable()) { @@ -143,7 +174,7 @@ function shadergen(p5, fn) { } shouldUseTemporaryVariable() { - if (this.temporaryVariable) + if (this.swizzleChanged) { return true; } if (this.isInternal || isVariableNode(this)) { return false; } let score = 0; score += isBinaryOperatorNode(this); @@ -159,7 +190,18 @@ function shadergen(p5, fn) { if (this.srcLine) { line += `\n// From ${this.srcLine}\n`; } - line += this.type + " " + this.temporaryVariable + " = " + this.toGLSL(context) + ";"; + if (this.swizzleChanged) { + const valueArgs = []; + for (let componentName of this.componentNames) { + valueArgs.push(this[componentName]) + } + console.log(this, valueArgs) + 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) + ";"; + } context.declarations.push(line); } return this.temporaryVariable; @@ -191,22 +233,6 @@ function shadergen(p5, fn) { return new this.constructor(other); } } - - addComponent(componentName, type='float') { - this[componentName] = new ComponentNode(this, componentName, type, true); - } - - autoAddVectorComponents() { - const options = { - vec2: ['x', 'y'], - vec3: ['x', 'y', 'z'], - vec4: ['x', 'y', 'z', 'w'] - }; - const componentNames = options[this.type] || []; - for (let componentName of componentNames) { - this.addComponent(componentName); - } - } toGLSL(context){ throw new TypeError("Not supposed to call this function on BaseNode, which is an abstract class."); @@ -254,12 +280,11 @@ function shadergen(p5, fn) { } } - class VectorNode extends BaseNode { constructor(values, type, isInternal = false) { super(isInternal, type); - this.components = ['x', 'y', 'z', 'w'].slice(0, values.length); - this.components.forEach((component, i) => { + this.componentNames = ['x', 'y', 'z', 'w'].slice(0, values.length); + this.componentNames.forEach((component, i) => { this[component] = new FloatNode(values[i], true); }); } @@ -267,8 +292,8 @@ function shadergen(p5, fn) { toGLSL(context) { let glslArgs = ``; - this.components.forEach((component, i) => { - const comma = i === this.components.length - 1 ? `` : `, `; + this.componentNames.forEach((component, i) => { + const comma = i === this.componentNames.length - 1 ? `` : `, `; glslArgs += `${this[component].toGLSLBase(context)}${comma}`; }) @@ -304,11 +329,10 @@ function shadergen(p5, fn) { // Variables and member variable nodes class VariableNode extends BaseNode { constructor(name, type, isInternal = false) { - super(isInternal, type) + super(isInternal, type); this.name = name; - this.autoAddVectorComponents(); + this.addVectorComponents(); } - toGLSL(context) { return `${this.name}`; } @@ -318,13 +342,13 @@ function shadergen(p5, fn) { constructor(parent, componentName, type, isInternal = false) { super(isInternal, type); this.parent = parent; - this.component = componentName; - this.type = 'float'; + this.componentName = componentName; + this.type = type; } toGLSL(context) { - // const parentName = this.parent.toGLSLBase(context); - const parentName = this.parent.temporaryVariable ? this.parent.temporaryVariable : this.parent.name; - return `${parentName}.${this.component}`; + const parentName = this.parent.toGLSLBase(context); + // const parentName = this.parent.temporaryVariable ? this.parent.temporaryVariable : this.parent.name; + return `${parentName}.${this.componentName}`; } } @@ -339,6 +363,7 @@ function shadergen(p5, fn) { 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 @@ -476,8 +501,9 @@ function shadergen(p5, fn) { this.resetGLSLContext(); } - hookCallbacks() { + generate() { this.modifyFunction(); + console.log(this.output); return this.output; } @@ -504,8 +530,6 @@ function shadergen(p5, fn) { argsArray.push(`, ${parameter.type.typeName} ${parameter.name}`); } }) - console.log(hookArgs) - const toGLSLResult = userOverride(...hookArgs).toGLSLBase(this.context); let codeLines = [`(${argsArray.join(', ')}) {`, this.context.declarations.slice()].flat(); codeLines.push(`\n${hookTypes.returnType.typeName} finalReturnValue = ${toGLSLResult}; @@ -565,6 +589,7 @@ function shadergen(p5, fn) { vec4: 'Vector4', sampler2D: 'Texture', }; + const nodeConstructors = { int: (value) => new IntNode(value), float: (value) => new FloatNode(value), @@ -572,25 +597,37 @@ function shadergen(p5, fn) { vec3: (value) => new VectorNode(value, 'vec3'), vec4: (value) => new VectorNode(value, 'vec4'), }; + for (const glslType in GLSLTypesToSuffixes) { // Generate uniform*() Methods for creating uniforms const typeIdentifier = GLSLTypesToSuffixes[glslType]; const uniformMethodName = `uniform${typeIdentifier}`; + ShaderGenerator.prototype[uniformMethodName] = function(...args) { let [name, ...defaultValue] = args; if(glslType.startsWith('vec')) { defaultValue = conformVectorParameters(defaultValue, +glslType.slice(3)); + this.output.uniforms[`${glslType} ${name}`] = defaultValue; + } + else { + console.log("defaultValue: ",defaultValue); + console.log("defaultValue[0]: ",defaultValue[0]) + this.output.uniforms[`${glslType} ${name}`] = defaultValue[0]; } - this.output.uniforms[`${glslType} ${name}`] = defaultValue; + let safeType = glslType === 'sampler2D' ? 'vec4' : glslType; - return new VariableNode(name, safeType); + const uniform = new VariableNode(name, safeType, false); + return uniform; }; + fn[uniformMethodName] = function (...args) { return GLOBAL_SHADER[uniformMethodName](...args); }; - // Generate the create*() Methods for creating variables in shaders + // We don't need a createTexture method. if (glslType === 'sampler2D') { continue; } + + // Generate the create*() Methods for creating variables in shaders const createMethodName = `create${typeIdentifier}`; fn[createMethodName] = function (...value) { if(glslType.startsWith('vec')) { From a0199d097c7e265174c9264347af237d7cc183d6 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 11 Mar 2025 18:26:36 +0000 Subject: [PATCH 34/54] dev sketch --- preview/global/sketch.js | 49 ++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index cf62f93bc2..d0218e9cea 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -1,52 +1,57 @@ let myShader; +let myShader2; p5.disableFriendlyErrors = true; - +function windowResized() { + resizeCanvas(windowWidth, windowHeight); +} function calculateOffset() { return 30; } function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); - // // Raw example myShader = baseMaterialShader().modify(() => { - - const offset = uniformFloat(1); - + const uCol = uniformVector4(0.1,0.1,0.1,1); + const time = uniformFloat(()=>millis); getFinalColor((col) => { - let a = createVector4(1, 2, 3, 4); - let b = createVector4(3, 4, 5, 6); - a = (a * b + offset) / 10; - col += a; + let x = createFloat(0.5); + col.x = createFloat(time); + col.w = 1; + col /= uCol; return col; }); - }); - + }, { parser: true, srcLocations: true }); + console.log(myShader) // Create and use the custom shader. - // myShader = baseMaterialShader().modify( + // myShader2 = baseMaterialShader().modify( // () => { - // const offset = uniformFloat('offset', () => calculateOffset) + // // const offset = uniformFloat('offset', 1) - // getWorldPosition((pos) => { - // let a = createVector3(1, 2, 3); - // let b = createVector3(3, 4, 5); + // getFinalColor((pos) => { + // let a = createVector4(1, 2, 3); + // let b = createVector4(3, 4, 5); // a = a.add(b); // let c = a.add(b); - // c += c.add(offset); - // c.x = b.x.add(1); - + // // c += c.add(offset); + // // c.x = b.x.add(1); // pos = pos.add(c); // return pos; // }) - // } - // ); + // }, { parser: false, srcLocations: true }); } function draw(){ // Set the styles - background(0) + background(0); + // fill(0) + shader(myShader); + stroke('red') + fill(255,0,0) + // myShader.setUniform('uCol', [0.1,2,0,1]) + sphere(100); } From 79602a7406f7c75f00e1f831bfbe6559a6a622e0 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 11 Mar 2025 18:26:36 +0000 Subject: [PATCH 35/54] Console log the whole modify argument --- preview/global/sketch.js | 49 ++++++++++++++++++++++------------------ src/webgl/ShaderGen.js | 2 +- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index cf62f93bc2..d0218e9cea 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -1,52 +1,57 @@ let myShader; +let myShader2; p5.disableFriendlyErrors = true; - +function windowResized() { + resizeCanvas(windowWidth, windowHeight); +} function calculateOffset() { return 30; } function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); - // // Raw example myShader = baseMaterialShader().modify(() => { - - const offset = uniformFloat(1); - + const uCol = uniformVector4(0.1,0.1,0.1,1); + const time = uniformFloat(()=>millis); getFinalColor((col) => { - let a = createVector4(1, 2, 3, 4); - let b = createVector4(3, 4, 5, 6); - a = (a * b + offset) / 10; - col += a; + let x = createFloat(0.5); + col.x = createFloat(time); + col.w = 1; + col /= uCol; return col; }); - }); - + }, { parser: true, srcLocations: true }); + console.log(myShader) // Create and use the custom shader. - // myShader = baseMaterialShader().modify( + // myShader2 = baseMaterialShader().modify( // () => { - // const offset = uniformFloat('offset', () => calculateOffset) + // // const offset = uniformFloat('offset', 1) - // getWorldPosition((pos) => { - // let a = createVector3(1, 2, 3); - // let b = createVector3(3, 4, 5); + // getFinalColor((pos) => { + // let a = createVector4(1, 2, 3); + // let b = createVector4(3, 4, 5); // a = a.add(b); // let c = a.add(b); - // c += c.add(offset); - // c.x = b.x.add(1); - + // // c += c.add(offset); + // // c.x = b.x.add(1); // pos = pos.add(c); // return pos; // }) - // } - // ); + // }, { parser: false, srcLocations: true }); } function draw(){ // Set the styles - background(0) + background(0); + // fill(0) + shader(myShader); + stroke('red') + fill(255,0,0) + // myShader.setUniform('uCol', [0.1,2,0,1]) + sphere(100); } diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index df54b495d1..36d0606257 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -37,7 +37,7 @@ function shadergen(p5, fn) { const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations) const generatedModifyArgument = generator.generate(); console.log("SRC STRING: ", generatorFunction); - console.log("NEW OPTIONS:", generatedModifyArgument['vec4 getFinalColor']) + console.log("NEW OPTIONS:", generatedModifyArgument) return oldModify.call(this, generatedModifyArgument); } From 0797a1ba2b094e00ea91cbf8b4cf005211595e1e Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Tue, 11 Mar 2025 19:54:33 +0000 Subject: [PATCH 36/54] refactor / add qualifiers to hook arguments (i.e. in sampler2D etc). next to fix structs --- src/webgl/ShaderGen.js | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 36d0606257..1197ac0182 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -494,7 +494,7 @@ function shadergen(p5, fn) { GLOBAL_SHADER = this; this.modifyFunction = modifyFunction; this.srcLocations = srcLocations; - this.generateHookBuilders(originalShader); + this.generateHookOverrides(originalShader); this.output = { uniforms: {}, } @@ -507,31 +507,28 @@ function shadergen(p5, fn) { return this.output; } - generateHookBuilders(originalShader) { + generateHookOverrides(originalShader) { const availableHooks = { ...originalShader.hooks.vertex, ...originalShader.hooks.fragment, } - // Defines a function for each of the hooks for the shader we are modifying. Object.keys(availableHooks).forEach((hookName) => { const hookTypes = originalShader.hookTypes(hookName) + console.log(hookTypes); this[hookTypes.name] = function(userOverride) { - let hookArgs = [] - let argsArray = []; - - hookTypes.parameters.forEach((parameter, i) => { - hookArgs.push( + let argNodes = [] + const argsArray = hookTypes.parameters.map((parameter) => { + argNodes.push( new VariableNode(parameter.name, parameter.type.typeName, true) ); - if (i === 0) { - argsArray.push(`${parameter.type.typeName} ${parameter.name}`); - } else { - argsArray.push(`, ${parameter.type.typeName} ${parameter.name}`); - } - }) - const toGLSLResult = userOverride(...hookArgs).toGLSLBase(this.context); - let codeLines = [`(${argsArray.join(', ')}) {`, this.context.declarations.slice()].flat(); + const qualifiers = parameter.type.qualifiers.length > 0 ? parameter.type.qualifiers.join(' ') : ''; + return `${qualifiers} ${parameter.type.typeName} ${parameter.name}`.trim(); + }); + + const argsString = `(${argsArray.join(', ')}) {`; + const toGLSLResult = userOverride(...argNodes).toGLSLBase(this.context); + let codeLines = [ argsString, ...this.context.declarations ]; codeLines.push(`\n${hookTypes.returnType.typeName} finalReturnValue = ${toGLSLResult}; \nreturn finalReturnValue; \n}`); From 98a78a7df34defe123fbecfcf0187d21f3d36333 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Wed, 12 Mar 2025 15:37:04 +0000 Subject: [PATCH 37/54] adding types to builtin functions --- src/webgl/ShaderGen.js | 105 ++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 43 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 1197ac0182..1fdd753c58 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -6,7 +6,7 @@ */ import { parse } from 'acorn'; -import { simple } from 'acorn-walk'; +import { ancestor } from 'acorn-walk'; import escodegen from 'escodegen'; function shadergen(p5, fn) { @@ -23,7 +23,7 @@ function shadergen(p5, fn) { ecmaVersion: 2021, locations: options.srcLocations }); - simple(ast, ASTCallbacks); + ancestor(ast, ASTCallbacks); const transpiledSource = escodegen.generate(ast); generatorFunction = new Function( transpiledSource.slice( @@ -103,7 +103,6 @@ function shadergen(p5, fn) { }, } - // Javascript Node API. // These classes are for expressing GLSL functions in Javascript without // needing to transpile the user's code. @@ -114,7 +113,6 @@ function shadergen(p5, fn) { } this.type = type; this.componentNames = []; - this.swizzleAccessed = false; this.swizzleChanged = false; // For tracking recursion depth and creating temporary variables this.isInternal = isInternal; @@ -151,7 +149,6 @@ function shadergen(p5, fn) { let value = new ComponentNode(this, componentName, 'float', true); Object.defineProperty(this, componentName, { get() { - this.swizzleAccessed = true; return value; }, set(newValue) { @@ -215,7 +212,6 @@ function shadergen(p5, fn) { mod(other) { return new ModulusNode(this, this.enforceType(other)); } // Check that the types of the operands are compatible. - // TODO: improve this with less branching if elses enforceType(other){ if (isShaderNode(other)){ if ((isFloatNode(this) || isVectorNode(this)) && isIntNode(other)) { @@ -228,7 +224,10 @@ function shadergen(p5, fn) { return new IntNode(other); } return new FloatNode(other); - } + } + else if(Array.isArray(other)) { + return new VectorNode(other, `vec${other.length}`) + } else { return new this.constructor(other); } @@ -474,7 +473,7 @@ function shadergen(p5, fn) { return (isShaderNode(node) && (node.type === 'float')); } - function isVectorNode(node) { + function isVectorNode(node) { return (isShaderNode(node) && (node.type === 'vec2'|| node.type === 'vec3' || node.type === 'vec4')); } @@ -507,17 +506,20 @@ function shadergen(p5, fn) { return this.output; } + // This method generates the hook overrides which the user calls in their modify function. generateHookOverrides(originalShader) { const availableHooks = { ...originalShader.hooks.vertex, ...originalShader.hooks.fragment, } - // Defines a function for each of the hooks for the shader we are modifying. + Object.keys(availableHooks).forEach((hookName) => { const hookTypes = originalShader.hookTypes(hookName) console.log(hookTypes); + this[hookTypes.name] = function(userOverride) { let argNodes = [] + const argsArray = hookTypes.parameters.map((parameter) => { argNodes.push( new VariableNode(parameter.name, parameter.type.typeName, true) @@ -525,9 +527,14 @@ function shadergen(p5, fn) { const qualifiers = parameter.type.qualifiers.length > 0 ? parameter.type.qualifiers.join(' ') : ''; return `${qualifiers} ${parameter.type.typeName} ${parameter.name}`.trim(); }); - const argsString = `(${argsArray.join(', ')}) {`; - const toGLSLResult = userOverride(...argNodes).toGLSLBase(this.context); + + let returnedValue = userOverride(...argNodes); + if (!isShaderNode(returnedValue)) { + const expectedReturnType = hookTypes.returnType; + returnedValue = nodeConstructors[expectedReturnType.typeName](returnedValue) + } + const toGLSLResult = returnedValue.toGLSLBase(this.context); let codeLines = [ argsString, ...this.context.declarations ]; codeLines.push(`\n${hookTypes.returnType.typeName} finalReturnValue = ${toGLSLResult}; \nreturn finalReturnValue; @@ -602,13 +609,12 @@ function shadergen(p5, fn) { ShaderGenerator.prototype[uniformMethodName] = function(...args) { let [name, ...defaultValue] = args; + if(glslType.startsWith('vec')) { defaultValue = conformVectorParameters(defaultValue, +glslType.slice(3)); this.output.uniforms[`${glslType} ${name}`] = defaultValue; } else { - console.log("defaultValue: ",defaultValue); - console.log("defaultValue[0]: ",defaultValue[0]) this.output.uniforms[`${glslType} ${name}`] = defaultValue[0]; } @@ -637,59 +643,69 @@ function shadergen(p5, fn) { } // GLSL Built in functions - // TODO: // Add a whole lot of these functions. // https://docs.gl/el3/abs + // TODO: + // In reality many of these have multiple overrides which will need to address later. + // Also, their return types depend on the genType which will need to address urgently + // genType clamp(genType x, + // genType minVal, + // genType maxVal); + // genType clamp(genType x, + // float minVal, + // float maxVal); + - // constructor(name, args, type, isInternal = false) { const builtInFunctions = { - // Trigonometry - 'acos': {}, + //////////// Trigonometry ////////// + 'acos': { args: ['genType'], returnType: 'float'}, // 'acosh': {}, - 'asin': {}, + 'asin': { args: ['genType'], returnType: 'float'}, // 'asinh': {}, - 'atan': {}, + 'atan': { args: ['genType'], returnType: 'float'}, // 'atanh': {}, - 'cos': {}, + 'cos': { args: ['genType'], returnType: 'float', isp5Function: true}, // 'cosh': {}, - 'degrees': {}, - 'radians': {}, - 'sin': {}, + 'degrees': { args: ['genType'], returnType: 'float'}, + 'radians': { args: ['genType'], returnType: 'float'}, + 'sin': { args: ['genType'], returnType: 'float' , isp5Function: true}, // 'sinh': {}, - 'tan': {}, + 'tan': { args: ['genType'], returnType: 'float', isp5Function: true}, // 'tanh': {}, - // Mathematics - 'abs': {}, - 'ceil': {}, - 'clamp': {}, - 'dFdx': {}, - 'dFdy': {}, - 'exp': {}, - 'exp2': {}, - 'floor': {}, + + ////////// Mathematics ////////// + 'abs': { args: ['genType'], returnType: 'float'}, + 'ceil': { args: ['genType'], returnType: 'float'}, + 'clamp': { args: ['genType', 'genType', 'genType'], returnType: 'float'}, + // 'dFdx': {}, + // 'dFdy': {}, + 'exp': { args: ['genType'], returnType: 'float'}, + 'exp2': { args: ['genType'], returnType: 'float'}, + 'floor': { args: ['genType'], returnType: 'float'}, // 'fma': {}, - 'fract': {}, + 'fract': { args: ['genType'], returnType: 'float'}, // 'fwidth': {}, // 'inversesqrt': {}, // 'isinf': {}, // 'isnan': {}, // 'log': {}, // 'log2': {}, - 'max': {}, - 'min': {}, - 'mix': {}, + 'max': { args: ['genType'], returnType: 'genType'}, + 'min': { args: ['genType'], returnType: 'genType'}, + 'mix': { args: ['genType'], returnType: 'genType'}, // 'mod': {}, // 'modf': {}, - 'pow': {}, + 'pow': { args: ['genType'], returnType: 'float'}, // 'round': {}, // 'roundEven': {}, // 'sign': {}, - 'smoothstep': {}, - 'sqrt': {}, - 'step': {}, + 'smoothstep': { args: ['genType', 'genType', 'genType'], returnType: 'float'}, + 'sqrt': { args: ['genType'], returnType: 'genType'}, + 'step': { args: ['genType', 'genType'], returnType: 'genType'}, // 'trunc': {}, - // Vector + + ////////// Vector ////////// 'cross': {}, 'distance': {}, 'dot': {}, @@ -704,7 +720,10 @@ function shadergen(p5, fn) { 'texture': {}, } - // Object.entries(builtInFunctions).forEach(([glslFnName, properties]) => { + // Object.entries(builtInFunctions).forEach(([functionName, properties]) => { + // fn[functionName] = function () { + // new FunctionCallNode(functionName, args, type, isInternal = false) + // } // }) const oldTexture = p5.prototype.texture; From b8072f985abcea2a113307ffe51f2fd660954fdf Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Wed, 12 Mar 2025 15:37:19 +0000 Subject: [PATCH 38/54] sketch example --- preview/global/sketch.js | 72 +++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index d0218e9cea..7477adfa50 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -1,5 +1,6 @@ let myShader; let myShader2; + p5.disableFriendlyErrors = true; function windowResized() { resizeCanvas(windowWidth, windowHeight); @@ -8,50 +9,51 @@ function calculateOffset() { return 30; } +function myCol() { + const col = (sin(millis() * 0.001) + 1)/2; + return col; +} + function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); - // // Raw example + myShader = baseMaterialShader().modify(() => { - const uCol = uniformVector4(0.1,0.1,0.1,1); - const time = uniformFloat(()=>millis); - getFinalColor((col) => { - let x = createFloat(0.5); - col.x = createFloat(time); - col.w = 1; - col /= uCol; - return col; + const uCol = uniformVector4(1,0, 0,1); + const time = uniformFloat(() => millis()); + getFinalColor((color) => { + color = uCol; + color.y = 1; + let x = createVector4(time); + x.x += createFloat(2); + color += x; + return color; }); + // getWorldInputs((Inputs) => { + // console.log(Inputs) + // }) }, { parser: true, srcLocations: true }); - - console.log(myShader) - // Create and use the custom shader. - // myShader2 = baseMaterialShader().modify( - // () => { - // // const offset = uniformFloat('offset', 1) - - // getFinalColor((pos) => { - // let a = createVector4(1, 2, 3); - // let b = createVector4(3, 4, 5); - // a = a.add(b); - - // let c = a.add(b); - // // c += c.add(offset); - // // c.x = b.x.add(1); - - // pos = pos.add(c); - - // return pos; - // }) - // }, { parser: false, srcLocations: true }); } function draw(){ - // Set the styles background(0); - // fill(0) shader(myShader); - stroke('red') - fill(255,0,0) - // myShader.setUniform('uCol', [0.1,2,0,1]) + fill(0,0,0) sphere(100); } + +`(vec4 color) { + // From at <computed> [as uniformVector4] (http://localhost:5173/p5.js:86002:25) + vec4 temp_0 = uCol; + temp_0 = vec4(temp_0.x, 1.0000, temp_0.z, temp_0.w); + vec4 finalReturnValue = temp_0; + return finalReturnValue; +}` +` +(vec4 color) { + +// From at <computed> [as uniformVector4] (http://localhost:5173/p5.js:86002:25) +vec4 temp_0 = uCol; +temp_0 = vec4(temp_0.x, 1.0000, temp_0.z, temp_0.w); +vec4 finalReturnValue = temp_0 + vec4(0.0000 + 2.0000, 0.0000, 0.0000, 0.0000); +return finalReturnValue; +}` \ No newline at end of file From 926f79feced8d9f0fb780007f484097cf1d6a579 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Thu, 13 Mar 2025 13:18:05 +0000 Subject: [PATCH 39/54] allow literals on lhs before transpile, dont transpile uniform default vals --- src/webgl/ShaderGen.js | 46 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 1fdd753c58..54157f2a9e 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -38,7 +38,6 @@ function shadergen(p5, fn) { const generatedModifyArgument = generator.generate(); console.log("SRC STRING: ", generatorFunction); console.log("NEW OPTIONS:", generatedModifyArgument) - return oldModify.call(this, generatedModifyArgument); } else { @@ -58,7 +57,7 @@ function shadergen(p5, fn) { } const ASTCallbacks = { - VariableDeclarator(node) { + VariableDeclarator(node, _state, _ancestors) { if (node.init.callee && node.init.callee.name.startsWith('uniform')) { const uniformNameLiteral = { type: 'Literal', @@ -69,7 +68,7 @@ function shadergen(p5, fn) { }, // The callbacks for AssignmentExpression and BinaryExpression handle // operator overloading including +=, *= assignment expressions - AssignmentExpression(node) { + AssignmentExpression(node, _state, _ancestors) { if (node.operator !== '=') { const methodName = replaceBinaryOperator(node.operator.replace('=','')); const rightReplacementNode = { @@ -89,7 +88,35 @@ function shadergen(p5, fn) { node.right = rightReplacementNode; } }, - BinaryExpression(node) { + 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 the left hand side of an expression is one of these types, + // we should construct a node from it. + const unsafeTypes = ["Literal", "ArrayExpression"] + if (unsafeTypes.includes(node.left.type)) { + const leftReplacementNode = { + type: "CallExpression", + callee: { + type: "Identifier", + name: "makeNode", + }, + arguments: [node.left, node.right] + } + node.left = leftReplacementNode; + console.log(escodegen.generate(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", @@ -103,6 +130,14 @@ function shadergen(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. @@ -192,7 +227,6 @@ function shadergen(p5, fn) { for (let componentName of this.componentNames) { valueArgs.push(this[componentName]) } - console.log(this, valueArgs) const replacement = nodeConstructors[this.type](valueArgs) line += this.type + " " + this.temporaryVariable + " = " + this.toGLSL(context) + ";"; line += `\n` + this.temporaryVariable + " = " + replacement.toGLSL(context) + ";"; @@ -515,7 +549,7 @@ function shadergen(p5, fn) { Object.keys(availableHooks).forEach((hookName) => { const hookTypes = originalShader.hookTypes(hookName) - console.log(hookTypes); + // console.log(hookTypes); this[hookTypes.name] = function(userOverride) { let argNodes = [] From 01ef185bce77d293631b7ff9ce71c883e0de38b6 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Thu, 13 Mar 2025 15:16:32 +0000 Subject: [PATCH 40/54] example sketch --- preview/global/sketch.js | 53 +++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 0ae3b8faa1..46b7d00a22 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -1,13 +1,9 @@ -let myShader; - -let myShader2; p5.disableFriendlyErrors = true; function windowResized() { resizeCanvas(windowWidth, windowHeight); } -function calculateOffset() { - return 30; -} + +let myShader; function myCol() { const col = (sin(millis() * 0.001) + 1)/2; @@ -16,25 +12,42 @@ function myCol() { function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); - myShader = baseMaterialShader().modify(() => { - - const offset = uniformFloat(1); - + const time = uniformFloat(() => sin(millis()*0.001)); getFinalColor((col) => { - let a = createVector4(1, 2, 3, 4); - let b = createVector4(3, 4, 5, 6); - a = (a * b + offset) / 10; - col += a; + col.x = uvCoords().x; + col.y = uvCoords().y; return col; }); - // getWorldInputs((Inputs) => { - // console.log(Inputs) - // }) - }, { parser: true, srcLocations: true }); + getWorldInputs((inputs) => { + inputs.position.x += time * inputs.position.y; + return inputs; + }) + }, { parser: true, srcLocations: false }); } function draw(){ - // Set the styles - background(0) + orbitControl(); + background(0); + shader(myShader); + noStroke(); + fill(255,0,0) + sphere(100); } + +// `(vec4 color) { +// // From at <computed> [as uniformVector4] (http://localhost:5173/p5.js:86002:25) +// vec4 temp_0 = uCol; +// temp_0 = vec4(temp_0.x, 1.0000, temp_0.z, temp_0.w); +// vec4 finalReturnValue = temp_0; +// return finalReturnValue; +// }` +// ` +// (vec4 color) { + +// // From at <computed> [as uniformVector4] (http://localhost:5173/p5.js:86002:25) +// vec4 temp_0 = uCol; +// temp_0 = vec4(temp_0.x, 1.0000, temp_0.z, temp_0.w); +// vec4 finalReturnValue = temp_0 + vec4(0.0000 + 2.0000, 0.0000, 0.0000, 0.0000); +// return finalReturnValue; +// }` \ No newline at end of file From 9cac0726c5e7a4a60970f5c98655376def14133e Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Thu, 13 Mar 2025 15:16:47 +0000 Subject: [PATCH 41/54] input structs now working --- src/webgl/ShaderGen.js | 87 ++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 28 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 54157f2a9e..febb2a1192 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -112,7 +112,6 @@ function shadergen(p5, fn) { arguments: [node.left, node.right] } node.left = leftReplacementNode; - console.log(escodegen.generate(leftReplacementNode)) } // Replace the binary operator with a call expression @@ -141,6 +140,7 @@ function shadergen(p5, fn) { // 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) { @@ -248,6 +248,9 @@ function shadergen(p5, fn) { // Check that the types of the operands are compatible. enforceType(other){ if (isShaderNode(other)){ + 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)) { return new FloatNode(other) } @@ -366,6 +369,7 @@ function shadergen(p5, fn) { this.name = name; this.addVectorComponents(); } + toGLSL(context) { return `${this.name}`; } @@ -519,13 +523,20 @@ function shadergen(p5, fn) { return (node instanceof VariableNode || node instanceof ComponentNode || typeof(node.temporaryVariable) != 'undefined'); } + // 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 class ShaderGenerator { - constructor(modifyFunction, originalShader, srcLocations) { + constructor(userCallback, originalShader, srcLocations) { GLOBAL_SHADER = this; - this.modifyFunction = modifyFunction; + this.userCallback = userCallback; this.srcLocations = srcLocations; this.generateHookOverrides(originalShader); this.output = { @@ -535,8 +546,7 @@ function shadergen(p5, fn) { } generate() { - this.modifyFunction(); - console.log(this.output); + this.userCallback(); return this.output; } @@ -548,31 +558,52 @@ function shadergen(p5, fn) { } Object.keys(availableHooks).forEach((hookName) => { - const hookTypes = originalShader.hookTypes(hookName) - // console.log(hookTypes); - - this[hookTypes.name] = function(userOverride) { + const hookTypes = originalShader.hookTypes(hookName); + 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 let argNodes = [] - - const argsArray = hookTypes.parameters.map((parameter) => { - argNodes.push( - new VariableNode(parameter.name, parameter.type.typeName, true) - ); + let argsArray = []; + hookTypes.parameters.forEach((parameter) => { + if (!isGLSLNativeType(parameter.type.typeName)) { + let structArg = {}; + parameter.type.properties.forEach((property) => { + structArg[property.name] = new VariableNode(`${parameter.name}.${property.name}`, property.type.typeName, true); + }); + argNodes.push(structArg); + } else { + argNodes.push( + new VariableNode(parameter.name, parameter.type.typeName, true) + ); + } const qualifiers = parameter.type.qualifiers.length > 0 ? parameter.type.qualifiers.join(' ') : ''; - return `${qualifiers} ${parameter.type.typeName} ${parameter.name}`.trim(); - }); - const argsString = `(${argsArray.join(', ')}) {`; + argsArray.push(`${qualifiers} ${parameter.type.typeName} ${parameter.name}`.trim()) + }) - let returnedValue = userOverride(...argNodes); - if (!isShaderNode(returnedValue)) { - const expectedReturnType = hookTypes.returnType; + let returnedValue = userCallback(...argNodes); + const expectedReturnType = hookTypes.returnType; + const toGLSLResults = {}; + // 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]) => { + toGLSLResults[propertyName] = propertyNode.toGLSLBase(this.context); + }) + } else { + // We can accept raw numbers or arrays otherwise + if (!isShaderNode(returnedValue)) { returnedValue = nodeConstructors[expectedReturnType.typeName](returnedValue) + } + toGLSLResults['notAProperty'] = returnedValue.toGLSLBase(this.context); } - const toGLSLResult = returnedValue.toGLSLBase(this.context); - let codeLines = [ argsString, ...this.context.declarations ]; - codeLines.push(`\n${hookTypes.returnType.typeName} finalReturnValue = ${toGLSLResult}; - \nreturn finalReturnValue; - \n}`); + + // Build the final GLSL string: + let codeLines = [ `(${argsArray.join(', ')}) {`, ...this.context.declarations ]; + codeLines.push(`${hookTypes.returnType.typeName} finalReturnValue;`); + Object.entries(toGLSLResults).forEach(([propertyName, result]) => { + const propString = expectedReturnType.properties ? `.${propertyName}` : ''; + codeLines.push(`finalReturnValue${propString} = ${result};`) + }) + codeLines.push('return finalReturnValue;', '}'); this.output[hookName] = codeLines.join('\n'); this.resetGLSLContext(); } @@ -619,7 +650,7 @@ function shadergen(p5, fn) { // Generating uniformFloat, uniformVec, createFloat, etc functions // Maps a GLSL type to the name suffix for method names - const GLSLTypesToSuffixes = { + const GLSLTypesToIdentifiers = { int: 'Int', float: 'Float', vec2: 'Vector2', @@ -636,9 +667,9 @@ function shadergen(p5, fn) { vec4: (value) => new VectorNode(value, 'vec4'), }; - for (const glslType in GLSLTypesToSuffixes) { + for (const glslType in GLSLTypesToIdentifiers) { // Generate uniform*() Methods for creating uniforms - const typeIdentifier = GLSLTypesToSuffixes[glslType]; + const typeIdentifier = GLSLTypesToIdentifiers[glslType]; const uniformMethodName = `uniform${typeIdentifier}`; ShaderGenerator.prototype[uniformMethodName] = function(...args) { From 951ee60cb1e6f6274d67dc1a018453b290ba7d98 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Thu, 13 Mar 2025 15:16:47 +0000 Subject: [PATCH 42/54] input structs now working --- src/webgl/ShaderGen.js | 98 +++++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js index 54157f2a9e..5e44c5de19 100644 --- a/src/webgl/ShaderGen.js +++ b/src/webgl/ShaderGen.js @@ -112,7 +112,6 @@ function shadergen(p5, fn) { arguments: [node.left, node.right] } node.left = leftReplacementNode; - console.log(escodegen.generate(leftReplacementNode)) } // Replace the binary operator with a call expression @@ -141,6 +140,7 @@ function shadergen(p5, fn) { // 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) { @@ -248,6 +248,9 @@ function shadergen(p5, fn) { // Check that the types of the operands are compatible. enforceType(other){ if (isShaderNode(other)){ + 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)) { return new FloatNode(other) } @@ -366,6 +369,7 @@ function shadergen(p5, fn) { this.name = name; this.addVectorComponents(); } + toGLSL(context) { return `${this.name}`; } @@ -519,13 +523,20 @@ function shadergen(p5, fn) { return (node instanceof VariableNode || node instanceof ComponentNode || typeof(node.temporaryVariable) != 'undefined'); } + // 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 class ShaderGenerator { - constructor(modifyFunction, originalShader, srcLocations) { + constructor(userCallback, originalShader, srcLocations) { GLOBAL_SHADER = this; - this.modifyFunction = modifyFunction; + this.userCallback = userCallback; this.srcLocations = srcLocations; this.generateHookOverrides(originalShader); this.output = { @@ -535,8 +546,7 @@ function shadergen(p5, fn) { } generate() { - this.modifyFunction(); - console.log(this.output); + this.userCallback(); return this.output; } @@ -548,31 +558,61 @@ function shadergen(p5, fn) { } Object.keys(availableHooks).forEach((hookName) => { - const hookTypes = originalShader.hookTypes(hookName) - // console.log(hookTypes); - - this[hookTypes.name] = function(userOverride) { - let argNodes = [] - - const argsArray = hookTypes.parameters.map((parameter) => { - argNodes.push( - new VariableNode(parameter.name, parameter.type.typeName, true) - ); + const hookTypes = originalShader.hookTypes(hookName); + 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 + const argNodes = [] + const argsArray = []; + + hookTypes.parameters.forEach((parameter) => { + // For hooks with structs as input we should pass an object populated with variable nodes + 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); + }); + argNodes.push(structArg); + } else { + argNodes.push( + new VariableNode(parameter.name, parameter.type.typeName, true) + ); + } const qualifiers = parameter.type.qualifiers.length > 0 ? parameter.type.qualifiers.join(' ') : ''; - return `${qualifiers} ${parameter.type.typeName} ${parameter.name}`.trim(); - }); - const argsString = `(${argsArray.join(', ')}) {`; + argsArray.push(`${qualifiers} ${parameter.type.typeName} ${parameter.name}`.trim()) + }) - let returnedValue = userOverride(...argNodes); - if (!isShaderNode(returnedValue)) { - const expectedReturnType = hookTypes.returnType; + let returnedValue = userCallback(...argNodes); + const expectedReturnType = hookTypes.returnType; + const toGLSLResults = {}; + + // 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]) => { + toGLSLResults[propertyName] = propertyNode.toGLSLBase(this.context); + }) + } else { + // We can accept raw numbers or arrays otherwise + if (!isShaderNode(returnedValue)) { returnedValue = nodeConstructors[expectedReturnType.typeName](returnedValue) + } + toGLSLResults['notAProperty'] = returnedValue.toGLSLBase(this.context); } - const toGLSLResult = returnedValue.toGLSLBase(this.context); - let codeLines = [ argsString, ...this.context.declarations ]; - codeLines.push(`\n${hookTypes.returnType.typeName} finalReturnValue = ${toGLSLResult}; - \nreturn finalReturnValue; - \n}`); + + // 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;` + ]; + + Object.entries(toGLSLResults).forEach(([propertyName, result]) => { + const propString = expectedReturnType.properties ? `.${propertyName}` : ''; + codeLines.push(`finalReturnValue${propString} = ${result};`) + }) + + codeLines.push('return finalReturnValue;', '}'); this.output[hookName] = codeLines.join('\n'); this.resetGLSLContext(); } @@ -619,7 +659,7 @@ function shadergen(p5, fn) { // Generating uniformFloat, uniformVec, createFloat, etc functions // Maps a GLSL type to the name suffix for method names - const GLSLTypesToSuffixes = { + const GLSLTypesToIdentifiers = { int: 'Int', float: 'Float', vec2: 'Vector2', @@ -636,9 +676,9 @@ function shadergen(p5, fn) { vec4: (value) => new VectorNode(value, 'vec4'), }; - for (const glslType in GLSLTypesToSuffixes) { + for (const glslType in GLSLTypesToIdentifiers) { // Generate uniform*() Methods for creating uniforms - const typeIdentifier = GLSLTypesToSuffixes[glslType]; + const typeIdentifier = GLSLTypesToIdentifiers[glslType]; const uniformMethodName = `uniform${typeIdentifier}`; ShaderGenerator.prototype[uniformMethodName] = function(...args) { From a6101c7c7f846a2781d35ca2600d9603e084a6cf Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Thu, 13 Mar 2025 15:35:55 +0000 Subject: [PATCH 43/54] fix merge issues --- src/webgl/ShaderGenerator.js | 830 +++++++++++++++++++++++++++++++++++ 1 file changed, 830 insertions(+) create mode 100644 src/webgl/ShaderGenerator.js diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js new file mode 100644 index 0000000000..0da6b2f523 --- /dev/null +++ b/src/webgl/ShaderGenerator.js @@ -0,0 +1,830 @@ +/** +* @module 3D +* @submodule ShaderGenerator +* @for p5 +* @requires core +*/ + +import { parse } from 'acorn'; +import { ancestor } from 'acorn-walk'; +import escodegen from 'escodegen'; + +function shadergen(p5, fn) { + let GLOBAL_SHADER; + + const oldModify = p5.Shader.prototype.modify + + p5.Shader.prototype.modify = function(shaderModifier, options = { parser: true, srcLocations: false }) { + if (shaderModifier instanceof Function) { + let generatorFunction; + if (options.parser) { + const sourceString = shaderModifier.toString() + const ast = parse(sourceString, { + ecmaVersion: 2021, + locations: options.srcLocations + }); + ancestor(ast, ASTCallbacks); + const transpiledSource = escodegen.generate(ast); + generatorFunction = new Function( + transpiledSource.slice( + transpiledSource.indexOf("{") + 1, + transpiledSource.lastIndexOf("}") + ) + ); + } else { + generatorFunction = shaderModifier; + } + const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations) + const generatedModifyArgument = generator.generate(); + console.log("SRC STRING: ", generatorFunction); + console.log("NEW OPTIONS:", generatedModifyArgument) + return oldModify.call(this, generatedModifyArgument); + } + else { + return oldModify.call(this, shaderModifier) + } + } + + // AST Transpiler Callbacks and their helpers + function replaceBinaryOperator(codeSource) { + switch (codeSource) { + case '+': return 'add'; + case '-': return 'sub'; + case '*': return 'mult'; + case '/': return 'div'; + case '%': return 'mod'; + } + } + + const ASTCallbacks = { + VariableDeclarator(node, _state, _ancestors) { + if (node.init.callee && node.init.callee.name.startsWith('uniform')) { + const uniformNameLiteral = { + type: 'Literal', + value: node.id.name + } + node.init.arguments.unshift(uniformNameLiteral); + } + }, + // The callbacks for AssignmentExpression and BinaryExpression handle + // operator overloading including +=, *= assignment expressions + AssignmentExpression(node, _state, _ancestors) { + if (node.operator !== '=') { + const methodName = replaceBinaryOperator(node.operator.replace('=','')); + const rightReplacementNode = { + type: 'CallExpression', + callee: { + type: "MemberExpression", + object: node.left, + property: { + type: "Identifier", + name: methodName, + }, + computed: false, + }, + arguments: [node.right] + } + node.operator = '='; + node.right = rightReplacementNode; + } + }, + 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 the left hand side of an expression is one of these types, + // we should construct a node from it. + const unsafeTypes = ["Literal", "ArrayExpression"] + if (unsafeTypes.includes(node.left.type)) { + const leftReplacementNode = { + type: "CallExpression", + callee: { + type: "Identifier", + name: "makeNode", + }, + arguments: [node.left, node.right] + } + 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", + object: node.left, + property: { + type: "Identifier", + name: replaceBinaryOperator(node.operator), + }, + }; + node.arguments = [node.right]; + }, + } + + // 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."); + } + this.type = type; + this.componentNames = []; + this.swizzleChanged = false; + // For tracking recursion depth and creating temporary variables + this.isInternal = isInternal; + this.usedIn = []; + this.dependsOn = []; + this.srcLine = null; + // 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"); + } catch (e) { + const lines = e.stack.split("\n"); + let userSketchLineIndex = 5; + if (isBinaryOperatorNode(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); + this.componentNames = ['x', 'y', 'z', 'w'].slice(0, vectorDimensions); + + for (let componentName of this.componentNames) { + // let value = new FloatNode() + let value = new ComponentNode(this, componentName, 'float', true); + Object.defineProperty(this, componentName, { + get() { + return value; + }, + set(newValue) { + this.swizzleChanged = true; + value = newValue; + } + }) + } + } + } + + // The base node implements a version of toGLSL which determines whether the generated code should be stored in a temporary variable. + toGLSLBase(context){ + if (this.shouldUseTemporaryVariable()) { + return this.getTemporaryVariable(context); + } + else { + return this.toGLSL(context); + } + } + + shouldUseTemporaryVariable() { + if (this.swizzleChanged) { return true; } + if (this.isInternal || isVariableNode(this)) { return false; } + let score = 0; + score += isBinaryOperatorNode(this); + score += isVectorNode(this) * 2; + score += this.usedIn.length; + return score > 3; + } + + getTemporaryVariable(context) { + if (!this.temporaryVariable) { + this.temporaryVariable = `temp_${context.getNextID()}`; + 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) + ";"; + } + 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)); } + + // Check that the types of the operands are compatible. + enforceType(other){ + if (isShaderNode(other)){ + 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 (!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)) { + return new FloatNode(other) + } + return other; + } + else if(typeof other === 'number') { + if (isIntNode(this)) { + return new IntNode(other); + } + return new FloatNode(other); + } + else if(Array.isArray(other)) { + return new VectorNode(other, `vec${other.length}`) + } + else { + return new this.constructor(other); + } + } + + toGLSL(context){ + throw new TypeError("Not supposed to call this function on BaseNode, which is an abstract class."); + } + } + + // Primitive Types + class IntNode extends BaseNode { + constructor(x = 0, isInternal = false) { + super(isInternal, 'int'); + this.x = x; + } + + toGLSL(context) { + if (isShaderNode(this.x)) { + let code = this.x.toGLSLBase(context); + return isIntNode(this.x.type) ? code : `int(${code})`; + } + else if (typeof this.x === "number") { + return `${Math.floor(this.x)}`; + } + else { + return `int(${this.x})`; + } + } + } + + class FloatNode extends BaseNode { + constructor(x = 0, isInternal = false){ + super(isInternal, 'float'); + this.x = x; + } + + toGLSL(context) { + if (isShaderNode(this.x)) { + let code = this.x.toGLSLBase(context); + return isFloatNode(this.x) ? code : `float(${code})`; + } + else if (typeof this.x === "number") { + return `${this.x.toFixed(4)}`; + } + else { + return `float(${this.x})`; + } + } + } + + 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); + }); + } + + 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})`; + } + } + + // Function Call Nodes + class FunctionCallNode extends BaseNode { + constructor(name, args, type, isInternal = false) { + super(isInternal, type); + this.name = name; + this.args = args; + // TODO: + this.argumentTypes = args; + } + + deconstructArgs(context) { + if (Array.isArray(this.args)) { + return this.args.map((argNode) => argNode.toGLSLBase(context)).join(', '); + } else { + return `${this.args.toGLSLBase(context)}`; + } + } + + toGLSL(context) { + return `${this.name}(${this.deconstructArgs(context)})`; + } + } + + // Variables and member variable nodes + class VariableNode extends BaseNode { + constructor(name, type, isInternal = false) { + super(isInternal, type); + this.name = name; + this.addVectorComponents(); + } + + + toGLSL(context) { + return `${this.name}`; + } + } + + class ComponentNode extends BaseNode { + constructor(parent, componentName, type, isInternal = false) { + super(isInternal, type); + this.parent = parent; + this.componentName = componentName; + this.type = type; + } + toGLSL(context) { + const parentName = this.parent.toGLSLBase(context); + // const parentName = this.parent.temporaryVariable ? this.parent.temporaryVariable : this.parent.name; + return `${parentName}.${this.componentName}`; + } + } + + // Binary Operator Nodes + class BinaryOperatorNode extends BaseNode { + constructor(a, b, operator, isInternal = false) { + super(isInternal, null); + this.op = operator; + this.a = a; + this.b = b; + for (const operand of [a, b]) { + 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 + determineType() { + if (this.a.type === this.b.type) { + return this.a.type; + } + else if (isVectorNode(this.a) && isFloatNode(this.b)) { + return this.a.type; + } + else if (isVectorNode(this.b) && isFloatNode(this.a)) { + return this.b.type; + } + else if (isFloatNode(this.a) && isIntNode(this.b) + || isIntNode(this.a) && isFloatNode(this.b) + ) { + return 'float'; + } + else { + 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) { + code = `(${code})`; + } + if (this.type === 'float' && isIntNode(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}`; + } + } + + // TODO: Correct the implementation for floats/ genType etc + class ModulusNode extends BinaryOperatorNode { + constructor(a, b) { + super(a, b); + } + toGLSL(context) { + // Switch on type between % or mod() + if (isVectorNode(this) || isFloatNode(this)) { + return `mod(${this.a.toGLSLBase(context)}, ${this.b.toGLSLBase(context)})`; + } + return `${this.processOperand(context, this.a)} % ${this.processOperand(context, this.b)}`; + } + } + + // TODO: finish If Node + class ConditionalNode extends BaseNode { + constructor(value) { + super(value); + this.value = value; + this.condition = null; + this.thenBranch = null; + this.elseBranch = null; + } + // conditions + equalTo(value){} + greaterThan(value) {} + greaterThanEqualTo(value) {} + lessThan(value) {} + lessThanEqualTo(value) {} + // modifiers + not() {} + or() {} + and() {} + // returns + thenReturn(value) {} + elseReturn(value) {} + // + thenDiscard() { + new ConditionalDiscard(this.condition); + }; + }; + + class ConditionalDiscard extends BaseNode { + constructor(condition){ + this.condition = condition; + } + toGLSL(context) { + context.discardConditions.push(`if(${this.condition}{discard;})`); + } + } + + fn.if = function (value) { + return new ConditionalNode(value); + } + + // Node Helper functions + function isShaderNode(node) { + return (node instanceof BaseNode); + } + + function isIntNode(node) { + return (isShaderNode(node) && (node.type === 'int')); + } + + function isFloatNode(node) { + return (isShaderNode(node) && (node.type === 'float')); + } + + function isVectorNode(node) { + return (isShaderNode(node) && (node.type === 'vec2'|| node.type === 'vec3' || node.type === 'vec4')); + } + + function isBinaryOperatorNode(node) { + return (node instanceof BinaryOperatorNode); + } + + function isVariableNode(node) { + return (node instanceof VariableNode || node instanceof ComponentNode || typeof(node.temporaryVariable) != 'undefined'); + } + + // 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); + } + } + + // 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 + + class ShaderGenerator { + constructor(userCallback, originalShader, srcLocations) { + GLOBAL_SHADER = this; + this.userCallback = userCallback; + this.userCallback = userCallback; + this.srcLocations = srcLocations; + this.generateHookOverrides(originalShader); + this.output = { + uniforms: {}, + } + this.resetGLSLContext(); + } + + generate() { + this.userCallback(); + return this.output; + } + + // This method generates the hook overrides which the user calls in their modify function. + generateHookOverrides(originalShader) { + const availableHooks = { + ...originalShader.hooks.vertex, + ...originalShader.hooks.fragment, + } + + Object.keys(availableHooks).forEach((hookName) => { + const hookTypes = originalShader.hookTypes(hookName); + 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 + const argNodes = [] + const argsArray = []; + + hookTypes.parameters.forEach((parameter) => { + // For hooks with structs as input we should pass an object populated with variable nodes + 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); + }); + argNodes.push(structArg); + } else { + argNodes.push( + new VariableNode(parameter.name, parameter.type.typeName, true) + ); + } + const qualifiers = parameter.type.qualifiers.length > 0 ? parameter.type.qualifiers.join(' ') : ''; + argsArray.push(`${qualifiers} ${parameter.type.typeName} ${parameter.name}`.trim()) + }) + + let returnedValue = userCallback(...argNodes); + const expectedReturnType = hookTypes.returnType; + const toGLSLResults = {}; + + // 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]) => { + toGLSLResults[propertyName] = propertyNode.toGLSLBase(this.context); + }) + } else { + // We can accept raw numbers or arrays otherwise + if (!isShaderNode(returnedValue)) { + returnedValue = nodeConstructors[expectedReturnType.typeName](returnedValue) + } + toGLSLResults['notAProperty'] = returnedValue.toGLSLBase(this.context); + } + + // 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;` + ]; + + Object.entries(toGLSLResults).forEach(([propertyName, result]) => { + const propString = expectedReturnType.properties ? `.${propertyName}` : ''; + codeLines.push(`finalReturnValue${propString} = ${result};`) + }) + + codeLines.push('return finalReturnValue;', '}'); + this.output[hookName] = codeLines.join('\n'); + this.resetGLSLContext(); + } + + // Expose the Functions to global scope for users to use + window[hookTypes.name] = function(userOverride) { + GLOBAL_SHADER[hookTypes.name](userOverride); + }; + }) + } + + resetGLSLContext() { + this.context = { + id: 0, + getNextID: function() { return this.id++ }, + declarations: [], + } + } + } + + // User function helpers + function conformVectorParameters(value, vectorDimensions) { + // Allow arguments as arrays ([0,0,0,0]) or not (0,0,0,0) + value = value.flat(); + // Populate arguments so uniformVector3(0) becomes [0,0,0] + if (value.length === 1) { + value = Array(vectorDimensions).fill(value[0]); + } + return value; + } + + // User functions + fn.instanceID = function() { + return new VariableNode('gl_InstanceID', 'int'); + } + + fn.uvCoords = function() { + return new VariableNode('vTexCoord', 'vec2'); + } + + fn.discard = function() { + return new VariableNode('discard', 'keyword'); + } + + // Generating uniformFloat, uniformVec, createFloat, etc functions + // Maps a GLSL type to the name suffix for method names + const GLSLTypesToIdentifiers = { + int: 'Int', + float: 'Float', + vec2: 'Vector2', + vec3: 'Vector3', + vec4: 'Vector4', + sampler2D: 'Texture', + }; + + 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'), + }; + + for (const glslType in GLSLTypesToIdentifiers) { + for (const glslType in GLSLTypesToIdentifiers) { + // Generate uniform*() Methods for creating uniforms + const typeIdentifier = GLSLTypesToIdentifiers[glslType]; + const uniformMethodName = `uniform${typeIdentifier}`; + + ShaderGenerator.prototype[uniformMethodName] = function(...args) { + let [name, ...defaultValue] = args; + + if(glslType.startsWith('vec')) { + defaultValue = conformVectorParameters(defaultValue, +glslType.slice(3)); + this.output.uniforms[`${glslType} ${name}`] = defaultValue; + } + else { + this.output.uniforms[`${glslType} ${name}`] = defaultValue[0]; + } + + let safeType = glslType === 'sampler2D' ? 'vec4' : glslType; + const uniform = new VariableNode(name, safeType, false); + return uniform; + }; + + fn[uniformMethodName] = function (...args) { + return GLOBAL_SHADER[uniformMethodName](...args); + }; + + // We don't need a createTexture method. + if (glslType === 'sampler2D') { continue; } + + // 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)); + } else { + value = value[0]; + } + return nodeConstructors[glslType](value); + } + } + + // GLSL Built in functions + // Add a whole lot of these functions. + // https://docs.gl/el3/abs + // TODO: + // In reality many of these have multiple overrides which will need to address later. + // Also, their return types depend on the genType which will need to address urgently + // genType clamp(genType x, + // genType minVal, + // genType maxVal); + // genType clamp(genType x, + // float minVal, + // float maxVal); + + + + const builtInFunctions = { + //////////// Trigonometry ////////// + 'acos': { args: ['genType'], returnType: 'float'}, + // 'acosh': {}, + 'asin': { args: ['genType'], returnType: 'float'}, + // 'asinh': {}, + 'atan': { args: ['genType'], returnType: 'float'}, + // 'atanh': {}, + 'cos': { args: ['genType'], returnType: 'float', isp5Function: true}, + // 'cosh': {}, + 'degrees': { args: ['genType'], returnType: 'float'}, + 'radians': { args: ['genType'], returnType: 'float'}, + 'sin': { args: ['genType'], returnType: 'float' , isp5Function: true}, + // 'sinh': {}, + 'tan': { args: ['genType'], returnType: 'float', isp5Function: true}, + // 'tanh': {}, + + ////////// Mathematics ////////// + 'abs': { args: ['genType'], returnType: 'float'}, + 'ceil': { args: ['genType'], returnType: 'float'}, + 'clamp': { args: ['genType', 'genType', 'genType'], returnType: 'float'}, + // 'dFdx': {}, + // 'dFdy': {}, + 'exp': { args: ['genType'], returnType: 'float'}, + 'exp2': { args: ['genType'], returnType: 'float'}, + 'floor': { args: ['genType'], returnType: 'float'}, + // 'fma': {}, + 'fract': { args: ['genType'], returnType: 'float'}, + // 'fwidth': {}, + // 'inversesqrt': {}, + // 'isinf': {}, + // 'isnan': {}, + // 'log': {}, + // 'log2': {}, + 'max': { args: ['genType'], returnType: 'genType'}, + 'min': { args: ['genType'], returnType: 'genType'}, + 'mix': { args: ['genType'], returnType: 'genType'}, + // 'mod': {}, + // 'modf': {}, + 'pow': { args: ['genType'], returnType: 'float'}, + // 'round': {}, + // 'roundEven': {}, + // 'sign': {}, + 'smoothstep': { args: ['genType', 'genType', 'genType'], returnType: 'float'}, + 'sqrt': { args: ['genType'], returnType: 'genType'}, + 'step': { args: ['genType', 'genType'], returnType: 'genType'}, + // 'trunc': {}, + + ////////// Vector ////////// + 'cross': {}, + 'distance': {}, + 'dot': {}, + // 'equal': {}, + // 'faceforward': {}, + 'length': {}, + 'normalize': {}, + // 'notEqual': {}, + // 'reflect': {}, + // 'refract': {}, + // Texture sampling + 'texture': {}, + } + + // Object.entries(builtInFunctions).forEach(([functionName, properties]) => { + // fn[functionName] = function () { + // new FunctionCallNode(functionName, args, type, isInternal = false) + // } + // }) + + 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 shadergen; + +if (typeof p5 !== 'undefined') { + p5.registerAddon(shadergen) +} \ No newline at end of file From 435a7187d2928ea8385e840e8e4b9081d7ef6226 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Thu, 13 Mar 2025 16:29:37 +0000 Subject: [PATCH 44/54] add builtin GLSL functions & change file name --- src/webgl/ShaderGen.js | 835 ----------------------------------- src/webgl/ShaderGenerator.js | 155 +++---- src/webgl/index.js | 4 +- 3 files changed, 82 insertions(+), 912 deletions(-) delete mode 100644 src/webgl/ShaderGen.js diff --git a/src/webgl/ShaderGen.js b/src/webgl/ShaderGen.js deleted file mode 100644 index d06b7dbd65..0000000000 --- a/src/webgl/ShaderGen.js +++ /dev/null @@ -1,835 +0,0 @@ -/** -* @module 3D -* @submodule ShaderGenerator -* @for p5 -* @requires core -*/ - -import { parse } from 'acorn'; -import { ancestor } from 'acorn-walk'; -import escodegen from 'escodegen'; - -function shadergen(p5, fn) { - let GLOBAL_SHADER; - - const oldModify = p5.Shader.prototype.modify - - p5.Shader.prototype.modify = function(shaderModifier, options = { parser: true, srcLocations: false }) { - if (shaderModifier instanceof Function) { - let generatorFunction; - if (options.parser) { - const sourceString = shaderModifier.toString() - const ast = parse(sourceString, { - ecmaVersion: 2021, - locations: options.srcLocations - }); - ancestor(ast, ASTCallbacks); - const transpiledSource = escodegen.generate(ast); - generatorFunction = new Function( - transpiledSource.slice( - transpiledSource.indexOf("{") + 1, - transpiledSource.lastIndexOf("}") - ) - ); - } else { - generatorFunction = shaderModifier; - } - const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations) - const generatedModifyArgument = generator.generate(); - console.log("SRC STRING: ", generatorFunction); - console.log("NEW OPTIONS:", generatedModifyArgument) - return oldModify.call(this, generatedModifyArgument); - } - else { - return oldModify.call(this, shaderModifier) - } - } - - // AST Transpiler Callbacks and their helpers - function replaceBinaryOperator(codeSource) { - switch (codeSource) { - case '+': return 'add'; - case '-': return 'sub'; - case '*': return 'mult'; - case '/': return 'div'; - case '%': return 'mod'; - } - } - - const ASTCallbacks = { - VariableDeclarator(node, _state, _ancestors) { - if (node.init.callee && node.init.callee.name.startsWith('uniform')) { - const uniformNameLiteral = { - type: 'Literal', - value: node.id.name - } - node.init.arguments.unshift(uniformNameLiteral); - } - }, - // The callbacks for AssignmentExpression and BinaryExpression handle - // operator overloading including +=, *= assignment expressions - AssignmentExpression(node, _state, _ancestors) { - if (node.operator !== '=') { - const methodName = replaceBinaryOperator(node.operator.replace('=','')); - const rightReplacementNode = { - type: 'CallExpression', - callee: { - type: "MemberExpression", - object: node.left, - property: { - type: "Identifier", - name: methodName, - }, - computed: false, - }, - arguments: [node.right] - } - node.operator = '='; - node.right = rightReplacementNode; - } - }, - 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 the left hand side of an expression is one of these types, - // we should construct a node from it. - const unsafeTypes = ["Literal", "ArrayExpression"] - if (unsafeTypes.includes(node.left.type)) { - const leftReplacementNode = { - type: "CallExpression", - callee: { - type: "Identifier", - name: "makeNode", - }, - arguments: [node.left, node.right] - } - 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", - object: node.left, - property: { - type: "Identifier", - name: replaceBinaryOperator(node.operator), - }, - }; - node.arguments = [node.right]; - }, - } - - // 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."); - } - this.type = type; - this.componentNames = []; - this.swizzleChanged = false; - // For tracking recursion depth and creating temporary variables - this.isInternal = isInternal; - this.usedIn = []; - this.dependsOn = []; - this.srcLine = null; - // 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"); - } catch (e) { - const lines = e.stack.split("\n"); - let userSketchLineIndex = 5; - if (isBinaryOperatorNode(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); - this.componentNames = ['x', 'y', 'z', 'w'].slice(0, vectorDimensions); - - for (let componentName of this.componentNames) { - // let value = new FloatNode() - let value = new ComponentNode(this, componentName, 'float', true); - Object.defineProperty(this, componentName, { - get() { - return value; - }, - set(newValue) { - this.swizzleChanged = true; - value = newValue; - } - }) - } - } - } - - // The base node implements a version of toGLSL which determines whether the generated code should be stored in a temporary variable. - toGLSLBase(context){ - if (this.shouldUseTemporaryVariable()) { - return this.getTemporaryVariable(context); - } - else { - return this.toGLSL(context); - } - } - - shouldUseTemporaryVariable() { - if (this.swizzleChanged) { return true; } - if (this.isInternal || isVariableNode(this)) { return false; } - let score = 0; - score += isBinaryOperatorNode(this); - score += isVectorNode(this) * 2; - score += this.usedIn.length; - return score > 3; - } - - getTemporaryVariable(context) { - if (!this.temporaryVariable) { - this.temporaryVariable = `temp_${context.getNextID()}`; - 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) + ";"; - } - 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)); } - - // Check that the types of the operands are compatible. - enforceType(other){ - if (isShaderNode(other)){ - 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 (!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)) { - return new FloatNode(other) - } - return other; - } - else if(typeof other === 'number') { - if (isIntNode(this)) { - return new IntNode(other); - } - return new FloatNode(other); - } - else if(Array.isArray(other)) { - return new VectorNode(other, `vec${other.length}`) - } - else { - return new this.constructor(other); - } - } - - toGLSL(context){ - throw new TypeError("Not supposed to call this function on BaseNode, which is an abstract class."); - } - } - - // Primitive Types - class IntNode extends BaseNode { - constructor(x = 0, isInternal = false) { - super(isInternal, 'int'); - this.x = x; - } - - toGLSL(context) { - if (isShaderNode(this.x)) { - let code = this.x.toGLSLBase(context); - return isIntNode(this.x.type) ? code : `int(${code})`; - } - else if (typeof this.x === "number") { - return `${Math.floor(this.x)}`; - } - else { - return `int(${this.x})`; - } - } - } - - class FloatNode extends BaseNode { - constructor(x = 0, isInternal = false){ - super(isInternal, 'float'); - this.x = x; - } - - toGLSL(context) { - if (isShaderNode(this.x)) { - let code = this.x.toGLSLBase(context); - return isFloatNode(this.x) ? code : `float(${code})`; - } - else if (typeof this.x === "number") { - return `${this.x.toFixed(4)}`; - } - else { - return `float(${this.x})`; - } - } - } - - 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); - }); - } - - 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})`; - } - } - - // Function Call Nodes - class FunctionCallNode extends BaseNode { - constructor(name, args, type, isInternal = false) { - super(isInternal, type); - this.name = name; - this.args = args; - // TODO: - this.argumentTypes = { - - }; - } - - deconstructArgs(context) { - if (Array.isArray(this.args)) { - return this.args.map((argNode) => argNode.toGLSLBase(context)).join(', '); - } else { - return `${this.args.toGLSLBase(context)}`; - } - } - - toGLSL(context) { - return `${this.name}(${this.deconstructArgs(context)})`; - } - } - - // Variables and member variable nodes - class VariableNode extends BaseNode { - constructor(name, type, isInternal = false) { - super(isInternal, type); - this.name = name; - this.addVectorComponents(); - } - - - toGLSL(context) { - return `${this.name}`; - } - } - - class ComponentNode extends BaseNode { - constructor(parent, componentName, type, isInternal = false) { - super(isInternal, type); - this.parent = parent; - this.componentName = componentName; - this.type = type; - } - toGLSL(context) { - const parentName = this.parent.toGLSLBase(context); - // const parentName = this.parent.temporaryVariable ? this.parent.temporaryVariable : this.parent.name; - return `${parentName}.${this.componentName}`; - } - } - - // Binary Operator Nodes - class BinaryOperatorNode extends BaseNode { - constructor(a, b, operator, isInternal = false) { - super(isInternal, null); - this.op = operator; - this.a = a; - this.b = b; - for (const operand of [a, b]) { - 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 - determineType() { - if (this.a.type === this.b.type) { - return this.a.type; - } - else if (isVectorNode(this.a) && isFloatNode(this.b)) { - return this.a.type; - } - else if (isVectorNode(this.b) && isFloatNode(this.a)) { - return this.b.type; - } - else if (isFloatNode(this.a) && isIntNode(this.b) - || isIntNode(this.a) && isFloatNode(this.b) - ) { - return 'float'; - } - else { - 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) { - code = `(${code})`; - } - if (this.type === 'float' && isIntNode(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}`; - } - } - - // TODO: Correct the implementation for floats/ genType etc - class ModulusNode extends BinaryOperatorNode { - constructor(a, b) { - super(a, b); - } - toGLSL(context) { - // Switch on type between % or mod() - if (isVectorNode(this) || isFloatNode(this)) { - return `mod(${this.a.toGLSLBase(context)}, ${this.b.toGLSLBase(context)})`; - } - return `${this.processOperand(context, this.a)} % ${this.processOperand(context, this.b)}`; - } - } - - // TODO: finish If Node - class ConditionalNode extends BaseNode { - constructor(value) { - super(value); - this.value = value; - this.condition = null; - this.thenBranch = null; - this.elseBranch = null; - } - // conditions - equalTo(value){} - greaterThan(value) {} - greaterThanEqualTo(value) {} - lessThan(value) {} - lessThanEqualTo(value) {} - // modifiers - not() {} - or() {} - and() {} - // returns - thenReturn(value) {} - elseReturn(value) {} - // - thenDiscard() { - new ConditionalDiscard(this.condition); - }; - }; - - class ConditionalDiscard extends BaseNode { - constructor(condition){ - this.condition = condition; - } - toGLSL(context) { - context.discardConditions.push(`if(${this.condition}{discard;})`); - } - } - - fn.if = function (value) { - return new ConditionalNode(value); - } - - // Node Helper functions - function isShaderNode(node) { - return (node instanceof BaseNode); - } - - function isIntNode(node) { - return (isShaderNode(node) && (node.type === 'int')); - } - - function isFloatNode(node) { - return (isShaderNode(node) && (node.type === 'float')); - } - - function isVectorNode(node) { - return (isShaderNode(node) && (node.type === 'vec2'|| node.type === 'vec3' || node.type === 'vec4')); - } - - function isBinaryOperatorNode(node) { - return (node instanceof BinaryOperatorNode); - } - - function isVariableNode(node) { - return (node instanceof VariableNode || node instanceof ComponentNode || typeof(node.temporaryVariable) != 'undefined'); - } - - // 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); - } - } - - // 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 - - class ShaderGenerator { - constructor(userCallback, originalShader, srcLocations) { - constructor(userCallback, originalShader, srcLocations) { - GLOBAL_SHADER = this; - this.userCallback = userCallback; - this.userCallback = userCallback; - this.srcLocations = srcLocations; - this.generateHookOverrides(originalShader); - this.output = { - uniforms: {}, - } - this.resetGLSLContext(); - } - - generate() { - this.userCallback(); - return this.output; - } - - // This method generates the hook overrides which the user calls in their modify function. - generateHookOverrides(originalShader) { - const availableHooks = { - ...originalShader.hooks.vertex, - ...originalShader.hooks.fragment, - } - - Object.keys(availableHooks).forEach((hookName) => { - const hookTypes = originalShader.hookTypes(hookName); - 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 - const argNodes = [] - const argsArray = []; - - hookTypes.parameters.forEach((parameter) => { - // For hooks with structs as input we should pass an object populated with variable nodes - 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); - }); - argNodes.push(structArg); - } else { - argNodes.push( - new VariableNode(parameter.name, parameter.type.typeName, true) - ); - } - const qualifiers = parameter.type.qualifiers.length > 0 ? parameter.type.qualifiers.join(' ') : ''; - argsArray.push(`${qualifiers} ${parameter.type.typeName} ${parameter.name}`.trim()) - }) - - let returnedValue = userCallback(...argNodes); - const expectedReturnType = hookTypes.returnType; - const toGLSLResults = {}; - - // 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]) => { - toGLSLResults[propertyName] = propertyNode.toGLSLBase(this.context); - }) - } else { - // We can accept raw numbers or arrays otherwise - if (!isShaderNode(returnedValue)) { - returnedValue = nodeConstructors[expectedReturnType.typeName](returnedValue) - } - toGLSLResults['notAProperty'] = returnedValue.toGLSLBase(this.context); - } - - // 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;` - ]; - - Object.entries(toGLSLResults).forEach(([propertyName, result]) => { - const propString = expectedReturnType.properties ? `.${propertyName}` : ''; - codeLines.push(`finalReturnValue${propString} = ${result};`) - }) - - codeLines.push('return finalReturnValue;', '}'); - this.output[hookName] = codeLines.join('\n'); - this.resetGLSLContext(); - } - - // Expose the Functions to global scope for users to use - window[hookTypes.name] = function(userOverride) { - GLOBAL_SHADER[hookTypes.name](userOverride); - }; - }) - } - - resetGLSLContext() { - this.context = { - id: 0, - getNextID: function() { return this.id++ }, - declarations: [], - } - } - } - - // User function helpers - function conformVectorParameters(value, vectorDimensions) { - // Allow arguments as arrays ([0,0,0,0]) or not (0,0,0,0) - value = value.flat(); - // Populate arguments so uniformVector3(0) becomes [0,0,0] - if (value.length === 1) { - value = Array(vectorDimensions).fill(value[0]); - } - return value; - } - - // User functions - fn.instanceID = function() { - return new VariableNode('gl_InstanceID', 'int'); - } - - fn.uvCoords = function() { - return new VariableNode('vTexCoord', 'vec2'); - } - - fn.discard = function() { - return new VariableNode('discard', 'keyword'); - } - - // Generating uniformFloat, uniformVec, createFloat, etc functions - // Maps a GLSL type to the name suffix for method names - const GLSLTypesToIdentifiers = { - const GLSLTypesToIdentifiers = { - int: 'Int', - float: 'Float', - vec2: 'Vector2', - vec3: 'Vector3', - vec4: 'Vector4', - sampler2D: 'Texture', - }; - - 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'), - }; - - for (const glslType in GLSLTypesToIdentifiers) { - for (const glslType in GLSLTypesToIdentifiers) { - // Generate uniform*() Methods for creating uniforms - const typeIdentifier = GLSLTypesToIdentifiers[glslType]; - const typeIdentifier = GLSLTypesToIdentifiers[glslType]; - const uniformMethodName = `uniform${typeIdentifier}`; - - ShaderGenerator.prototype[uniformMethodName] = function(...args) { - let [name, ...defaultValue] = args; - - if(glslType.startsWith('vec')) { - defaultValue = conformVectorParameters(defaultValue, +glslType.slice(3)); - this.output.uniforms[`${glslType} ${name}`] = defaultValue; - } - else { - this.output.uniforms[`${glslType} ${name}`] = defaultValue[0]; - } - - let safeType = glslType === 'sampler2D' ? 'vec4' : glslType; - const uniform = new VariableNode(name, safeType, false); - return uniform; - }; - - fn[uniformMethodName] = function (...args) { - return GLOBAL_SHADER[uniformMethodName](...args); - }; - - // We don't need a createTexture method. - if (glslType === 'sampler2D') { continue; } - - // 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)); - } else { - value = value[0]; - } - return nodeConstructors[glslType](value); - } - } - - // GLSL Built in functions - // Add a whole lot of these functions. - // https://docs.gl/el3/abs - // TODO: - // In reality many of these have multiple overrides which will need to address later. - // Also, their return types depend on the genType which will need to address urgently - // genType clamp(genType x, - // genType minVal, - // genType maxVal); - // genType clamp(genType x, - // float minVal, - // float maxVal); - - - - const builtInFunctions = { - //////////// Trigonometry ////////// - 'acos': { args: ['genType'], returnType: 'float'}, - // 'acosh': {}, - 'asin': { args: ['genType'], returnType: 'float'}, - // 'asinh': {}, - 'atan': { args: ['genType'], returnType: 'float'}, - // 'atanh': {}, - 'cos': { args: ['genType'], returnType: 'float', isp5Function: true}, - // 'cosh': {}, - 'degrees': { args: ['genType'], returnType: 'float'}, - 'radians': { args: ['genType'], returnType: 'float'}, - 'sin': { args: ['genType'], returnType: 'float' , isp5Function: true}, - // 'sinh': {}, - 'tan': { args: ['genType'], returnType: 'float', isp5Function: true}, - // 'tanh': {}, - - ////////// Mathematics ////////// - 'abs': { args: ['genType'], returnType: 'float'}, - 'ceil': { args: ['genType'], returnType: 'float'}, - 'clamp': { args: ['genType', 'genType', 'genType'], returnType: 'float'}, - // 'dFdx': {}, - // 'dFdy': {}, - 'exp': { args: ['genType'], returnType: 'float'}, - 'exp2': { args: ['genType'], returnType: 'float'}, - 'floor': { args: ['genType'], returnType: 'float'}, - // 'fma': {}, - 'fract': { args: ['genType'], returnType: 'float'}, - // 'fwidth': {}, - // 'inversesqrt': {}, - // 'isinf': {}, - // 'isnan': {}, - // 'log': {}, - // 'log2': {}, - 'max': { args: ['genType'], returnType: 'genType'}, - 'min': { args: ['genType'], returnType: 'genType'}, - 'mix': { args: ['genType'], returnType: 'genType'}, - // 'mod': {}, - // 'modf': {}, - 'pow': { args: ['genType'], returnType: 'float'}, - // 'round': {}, - // 'roundEven': {}, - // 'sign': {}, - 'smoothstep': { args: ['genType', 'genType', 'genType'], returnType: 'float'}, - 'sqrt': { args: ['genType'], returnType: 'genType'}, - 'step': { args: ['genType', 'genType'], returnType: 'genType'}, - // 'trunc': {}, - - ////////// Vector ////////// - 'cross': {}, - 'distance': {}, - 'dot': {}, - // 'equal': {}, - // 'faceforward': {}, - 'length': {}, - 'normalize': {}, - // 'notEqual': {}, - // 'reflect': {}, - // 'refract': {}, - // Texture sampling - 'texture': {}, - } - - // Object.entries(builtInFunctions).forEach(([functionName, properties]) => { - // fn[functionName] = function () { - // new FunctionCallNode(functionName, args, type, isInternal = false) - // } - // }) - - 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 shadergen; - -if (typeof p5 !== 'undefined') { - p5.registerAddon(shadergen) -} \ No newline at end of file diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 0da6b2f523..554c6ede97 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -9,7 +9,7 @@ import { parse } from 'acorn'; import { ancestor } from 'acorn-walk'; import escodegen from 'escodegen'; -function shadergen(p5, fn) { +function shadergenerator(p5, fn) { let GLOBAL_SHADER; const oldModify = p5.Shader.prototype.modify @@ -343,12 +343,17 @@ function shadergen(p5, fn) { // Function Call Nodes class FunctionCallNode extends BaseNode { - constructor(name, args, type, isInternal = false) { - super(isInternal, type); + constructor(name, args, properties, isInternal = false) { + let returnType = properties.returnType; + if (returnType === 'genType') { + returnType = args[0].type; + console.log("GENTYPE") + } + super(isInternal, returnType); this.name = name; this.args = args; - // TODO: - this.argumentTypes = args; + this.argumentTypes = properties.args; + this.addVectorComponents(); } deconstructArgs(context) { @@ -531,7 +536,6 @@ function shadergen(p5, fn) { // Supported types for now const glslNativeTypes = ['int', 'float', 'vec2', 'vec3', 'vec4', 'sampler2D']; return glslNativeTypes.includes(typeName); - } } // Helper function to check if a type is a user defined struct or native type @@ -688,7 +692,6 @@ function shadergen(p5, fn) { vec4: (value) => new VectorNode(value, 'vec4'), }; - for (const glslType in GLSLTypesToIdentifiers) { for (const glslType in GLSLTypesToIdentifiers) { // Generate uniform*() Methods for creating uniforms const typeIdentifier = GLSLTypesToIdentifiers[glslType]; @@ -704,9 +707,7 @@ function shadergen(p5, fn) { else { this.output.uniforms[`${glslType} ${name}`] = defaultValue[0]; } - - let safeType = glslType === 'sampler2D' ? 'vec4' : glslType; - const uniform = new VariableNode(name, safeType, false); + const uniform = new VariableNode(name, glslType, false); return uniform; }; @@ -732,7 +733,6 @@ function shadergen(p5, fn) { // GLSL Built in functions // Add a whole lot of these functions. // https://docs.gl/el3/abs - // TODO: // In reality many of these have multiple overrides which will need to address later. // Also, their return types depend on the genType which will need to address urgently // genType clamp(genType x, @@ -741,90 +741,95 @@ function shadergen(p5, fn) { // genType clamp(genType x, // float minVal, // float maxVal); - - - const builtInFunctions = { //////////// Trigonometry ////////// - 'acos': { args: ['genType'], returnType: 'float'}, - // 'acosh': {}, - 'asin': { args: ['genType'], returnType: 'float'}, - // 'asinh': {}, - 'atan': { args: ['genType'], returnType: 'float'}, - // 'atanh': {}, - 'cos': { args: ['genType'], returnType: 'float', isp5Function: true}, - // 'cosh': {}, - 'degrees': { args: ['genType'], returnType: 'float'}, - 'radians': { args: ['genType'], returnType: 'float'}, - 'sin': { args: ['genType'], returnType: 'float' , isp5Function: true}, - // 'sinh': {}, - 'tan': { args: ['genType'], returnType: 'float', isp5Function: true}, - // 'tanh': {}, + 'acos': { args: ['genType'], returnType: 'genType', isp5Function: true}, + '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}, + 'atanh': { args: ['genType'], returnType: 'genType', isp5Function: false}, + 'cos': { args: ['genType'], returnType: 'genType', isp5Function: true}, + 'cosh': { args: ['genType'], returnType: 'genType', isp5Function: false}, + 'degrees': { args: ['genType'], returnType: 'genType', isp5Function: true}, + 'radians': { args: ['genType'], returnType: 'genType', isp5Function: true}, + 'sin': { args: ['genType'], returnType: 'genType' , isp5Function: true}, + 'sinh': { args: ['genType'], returnType: 'genType', isp5Function: false}, + 'tan': { args: ['genType'], returnType: 'genType', isp5Function: true}, + 'tanh': { args: ['genType'], returnType: 'genType', isp5Function: false}, ////////// Mathematics ////////// - 'abs': { args: ['genType'], returnType: 'float'}, - 'ceil': { args: ['genType'], returnType: 'float'}, - 'clamp': { args: ['genType', 'genType', 'genType'], returnType: 'float'}, - // 'dFdx': {}, - // 'dFdy': {}, - 'exp': { args: ['genType'], returnType: 'float'}, - 'exp2': { args: ['genType'], returnType: 'float'}, - 'floor': { args: ['genType'], returnType: 'float'}, - // 'fma': {}, - 'fract': { args: ['genType'], returnType: 'float'}, - // 'fwidth': {}, - // 'inversesqrt': {}, + 'abs': { args: ['genType'], returnType: 'genType', isp5Function: true}, + 'ceil': { args: ['genType'], returnType: 'genType', isp5Function: true}, + 'clamp': { args: ['genType', 'genType', 'genType'], returnType: 'genType', isp5Function: false}, + 'dFdx': { args: ['genType'], returnType: 'genType', isp5Function: false}, + 'dFdy': { args: ['genType'], returnType: 'genType', isp5Function: false}, + 'exp': { args: ['genType'], returnType: 'genType', isp5Function: true}, + 'exp2': { args: ['genType'], returnType: 'genType', isp5Function: false}, + 'floor': { args: ['genType'], returnType: 'genType', isp5Function: true}, + 'fma': { args: ['genType', 'genType', 'genType'], returnType: 'genType', isp5Function: false}, + 'fract': { args: ['genType'], returnType: 'genType', isp5Function: true}, + 'fwidth': { args: ['genType'], returnType: 'genType', isp5Function: false}, + 'inversesqrt': { args: ['genType'], returnType: 'genType', isp5Function: true}, // 'isinf': {}, // 'isnan': {}, - // 'log': {}, - // 'log2': {}, - 'max': { args: ['genType'], returnType: 'genType'}, - 'min': { args: ['genType'], returnType: 'genType'}, - 'mix': { args: ['genType'], returnType: 'genType'}, + 'log': { args: ['genType'], returnType: 'genType', isp5Function: true}, + 'log2': { args: ['genType'], returnType: 'genType', isp5Function: false}, + 'max': { args: ['genType'], returnType: 'genType', isp5Function: true}, + 'min': { args: ['genType'], returnType: 'genType', isp5Function: true}, + 'mix': { args: ['genType'], returnType: 'genType', isp5Function: false}, // 'mod': {}, // 'modf': {}, - 'pow': { args: ['genType'], returnType: 'float'}, - // 'round': {}, - // 'roundEven': {}, + 'pow': { args: ['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: 'float'}, - 'sqrt': { args: ['genType'], returnType: 'genType'}, - 'step': { args: ['genType', 'genType'], returnType: 'genType'}, - // 'trunc': {}, + 'smoothstep': { args: ['genType', 'genType', '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}, ////////// Vector ////////// - 'cross': {}, - 'distance': {}, - 'dot': {}, + 'cross': { args: ['vec3', 'vec3'], returnType: 'vec3', isp5Function: true}, + 'distance': { args: ['genType', 'genType'], returnType: 'float', isp5Function: true}, + 'dot': { args: ['genType', 'genType'], returnType: 'float', isp5Function: true}, // 'equal': {}, - // 'faceforward': {}, - 'length': {}, - 'normalize': {}, + 'faceforward': { args: ['genType', 'genType', 'genType'], returnType: 'genType', isp5Function: false}, + 'length': { args: ['genType'], returnType: 'float', isp5Function: false}, + 'normalize': { args: ['genType'], returnType: 'genType', isp5Function: true}, // 'notEqual': {}, - // 'reflect': {}, - // 'refract': {}, + 'reflect': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: false}, + 'refract': { args: ['genType', 'genType', 'float'], returnType: 'genType', isp5Function: false}, // Texture sampling - 'texture': {}, + 'texture': {args: ['sampler2D', 'vec2'], returnType: 'vec4', isp5Function: true}, } - // Object.entries(builtInFunctions).forEach(([functionName, properties]) => { - // fn[functionName] = function () { - // new FunctionCallNode(functionName, args, type, isInternal = false) - // } - // }) - - const oldTexture = p5.prototype.texture; - p5.prototype.texture = function(...args) { - if (isShaderNode(args[0])) { - return new FunctionCallNode('texture', args, 'vec4'); + Object.entries(builtInFunctions).forEach(([functionName, properties]) => { + if (properties.isp5Function) { + const originalFn = fn[functionName]; + + fn[functionName] = function (...args) { + return new FunctionCallNode(functionName, args, properties) + } } else { - return oldTexture.apply(this, args); + fn[functionName] = function (...args) { + return new FunctionCallNode(functionName, args, properties); + } } - } + }) + + // 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 shadergen; +export default shadergenerator; if (typeof p5 !== 'undefined') { - p5.registerAddon(shadergen) + p5.registerAddon(shadergenerator) } \ No newline at end of file diff --git a/src/webgl/index.js b/src/webgl/index.js index 2ce0957fa6..7ba587b132 100644 --- a/src/webgl/index.js +++ b/src/webgl/index.js @@ -14,7 +14,7 @@ import shader from './p5.Shader'; import camera from './p5.Camera'; import texture from './p5.Texture'; import rendererGL from './p5.RendererGL'; -import shadergen from './ShaderGen'; +import shadergenerator from './ShaderGenerator'; export default function(p5){ rendererGL(p5, p5.prototype); @@ -33,5 +33,5 @@ export default function(p5){ dataArray(p5, p5.prototype); shader(p5, p5.prototype); texture(p5, p5.prototype); - shadergen(p5, p5.prototype); + shadergenerator(p5, p5.prototype); } From 9932797a19ee73ad407fd3b99935886ecca6166a Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Thu, 13 Mar 2025 16:35:22 +0000 Subject: [PATCH 45/54] sketch example --- preview/global/sketch.js | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 46b7d00a22..e616d5ac2e 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -2,28 +2,26 @@ p5.disableFriendlyErrors = true; function windowResized() { resizeCanvas(windowWidth, windowHeight); } - let myShader; -function myCol() { - const col = (sin(millis() * 0.001) + 1)/2; - return col; -} - function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); + myShader = baseMaterialShader().modify(() => { - const time = uniformFloat(() => sin(millis()*0.001)); + const time = uniformFloat(() => millis()); + getFinalColor((col) => { col.x = uvCoords().x; col.y = uvCoords().y; + col.z = 1; return col; }); + getWorldInputs((inputs) => { - inputs.position.x += time * inputs.position.y; + inputs.position.y += 20 * sin(time * 0.001 + inputs.position.x * 0.05); return inputs; }) - }, { parser: true, srcLocations: false }); + }); } function draw(){ @@ -34,20 +32,3 @@ function draw(){ fill(255,0,0) sphere(100); } - -// `(vec4 color) { -// // From at <computed> [as uniformVector4] (http://localhost:5173/p5.js:86002:25) -// vec4 temp_0 = uCol; -// temp_0 = vec4(temp_0.x, 1.0000, temp_0.z, temp_0.w); -// vec4 finalReturnValue = temp_0; -// return finalReturnValue; -// }` -// ` -// (vec4 color) { - -// // From at <computed> [as uniformVector4] (http://localhost:5173/p5.js:86002:25) -// vec4 temp_0 = uCol; -// temp_0 = vec4(temp_0.x, 1.0000, temp_0.z, temp_0.w); -// vec4 finalReturnValue = temp_0 + vec4(0.0000 + 2.0000, 0.0000, 0.0000, 0.0000); -// return finalReturnValue; -// }` \ No newline at end of file From a41f7732aef4fc7d923044718b9c88bdf4df06e0 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Fri, 14 Mar 2025 15:33:28 +0000 Subject: [PATCH 46/54] remove console log --- src/webgl/ShaderGenerator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 554c6ede97..cc7db4277d 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -347,7 +347,6 @@ function shadergenerator(p5, fn) { let returnType = properties.returnType; if (returnType === 'genType') { returnType = args[0].type; - console.log("GENTYPE") } super(isInternal, returnType); this.name = name; @@ -575,6 +574,7 @@ function shadergenerator(p5, fn) { Object.keys(availableHooks).forEach((hookName) => { const hookTypes = originalShader.hookTypes(hookName); + console.log(hookTypes); 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 From 2655de2203205b50b670fb44b80b34bb1fd17a7f Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Fri, 14 Mar 2025 17:17:33 +0000 Subject: [PATCH 47/54] infer types from function calls --- src/webgl/ShaderGenerator.js | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index cc7db4277d..8a27a76634 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -58,7 +58,7 @@ function shadergenerator(p5, fn) { const ASTCallbacks = { VariableDeclarator(node, _state, _ancestors) { - if (node.init.callee && node.init.callee.name.startsWith('uniform')) { + if (node.init.callee && node.init.callee.name?.startsWith('uniform')) { const uniformNameLiteral = { type: 'Literal', value: node.id.name @@ -344,11 +344,28 @@ function shadergenerator(p5, fn) { // Function Call Nodes class FunctionCallNode extends BaseNode { constructor(name, args, properties, isInternal = false) { - let returnType = properties.returnType; - if (returnType === 'genType') { - returnType = args[0].type; + 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) => { + if (!isShaderNode(arg)) { + const typeName = properties.args[i] === 'genType' ? inferredType : properties.args[i]; + arg = nodeConstructors[typeName](arg); + } + return arg; + }) + if (properties.returnType === 'genType') { + properties.returnType = inferredType; } - super(isInternal, returnType); + super(isInternal, properties.returnType); this.name = name; this.args = args; this.argumentTypes = properties.args; @@ -741,7 +758,7 @@ function shadergenerator(p5, fn) { // genType clamp(genType x, // float minVal, // float maxVal); - const builtInFunctions = { + const builtInGLSLFunctions = { //////////// Trigonometry ////////// 'acos': { args: ['genType'], returnType: 'genType', isp5Function: true}, 'acosh': { args: ['genType'], returnType: 'genType', isp5Function: false}, @@ -804,7 +821,7 @@ function shadergenerator(p5, fn) { 'texture': {args: ['sampler2D', 'vec2'], returnType: 'vec4', isp5Function: true}, } - Object.entries(builtInFunctions).forEach(([functionName, properties]) => { + Object.entries(builtInGLSLFunctions).forEach(([functionName, properties]) => { if (properties.isp5Function) { const originalFn = fn[functionName]; From 3d9156b547288984aa9d412cb52b8a6f9f99d424 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Fri, 14 Mar 2025 17:17:59 +0000 Subject: [PATCH 48/54] filter shader example --- preview/global/sketch.js | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index e616d5ac2e..f9cede49d5 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -1,12 +1,29 @@ -p5.disableFriendlyErrors = true; +// p5.disableFriendlyErrors = true; function windowResized() { resizeCanvas(windowWidth, windowHeight); } let myShader; +let filterShader; +let video; -function setup(){ + +async function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); + // filterShader = baseFi + + filterShader = baseFilterShader().modify(() => { + const time = uniformFloat(() => millis()); + + getColor((input, canvasContents) => { + let myColor = texture(canvasContents, uvCoords()); + const d = distance(input.texCoord, [0.5, 0.5]); + myColor.x = smoothstep(0, 0.5, d); + myColor.y = sin(time*0.001)/2; + return myColor; + }) + }); + myShader = baseMaterialShader().modify(() => { const time = uniformFloat(() => millis()); @@ -25,10 +42,12 @@ function setup(){ } function draw(){ - orbitControl(); + // orbitControl(); background(0); - shader(myShader); noStroke(); - fill(255,0,0) - sphere(100); + fill('blue') + sphere(100) + shader(myShader); + filter(filterShader); + // filterShader.setUniform('time', millis()); } From 9b78c4b321e49cf26a5a6c95563fb45632d16fff Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Fri, 14 Mar 2025 17:34:30 +0000 Subject: [PATCH 49/54] set default uniforms on filters outside of WebGL mode --- src/image/filterRenderer2D.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/image/filterRenderer2D.js b/src/image/filterRenderer2D.js index 243652d5f8..5e2dcc3107 100644 --- a/src/image/filterRenderer2D.js +++ b/src/image/filterRenderer2D.js @@ -234,7 +234,8 @@ class FilterRenderer2D { this._shader.setUniform('canvasSize', [this.pInst.width, this.pInst.height]); this._shader.setUniform('radius', Math.max(1, this.filterParameter)); this._shader.setUniform('filterParameter', this.filterParameter); - + this._shader.setDefaultUniforms(); + this.pInst.states.setValue('rectMode', constants.CORNER); this.pInst.states.setValue('imageMode', constants.CORNER); this.pInst.blendMode(constants.BLEND); From 91ea2aca51f4a962044247417fc520a072f2548d Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Fri, 14 Mar 2025 18:00:28 +0000 Subject: [PATCH 50/54] fix user fill shaders overriding filters --- src/webgl/p5.RendererGL.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 4f988c425c..3e1b440cc4 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -636,7 +636,7 @@ class RendererGL extends Renderer { this._useVertexColor = geometry.vertexColors.length > 0; const shader = - this._drawingFilter && this.states.userFillShader + !this._drawingFilter && this.states.userFillShader ? this.states.userFillShader : this._getFillShader(); shader.bindShader(); From 9c4aef8cf6d2415b57ca09805da60f7b0f239a97 Mon Sep 17 00:00:00 2001 From: Dave Pagurek <davepagurek@gmail.com> Date: Fri, 14 Mar 2025 19:12:25 -0400 Subject: [PATCH 51/54] Change FES behaviour while executing a shader generator --- src/webgl/ShaderGenerator.js | 137 +++++++++++++++++++++-------------- 1 file changed, 84 insertions(+), 53 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 8a27a76634..b98ae876f4 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -11,7 +11,7 @@ import escodegen from 'escodegen'; function shadergenerator(p5, fn) { let GLOBAL_SHADER; - + const oldModify = p5.Shader.prototype.modify p5.Shader.prototype.modify = function(shaderModifier, options = { parser: true, srcLocations: false }) { @@ -19,9 +19,9 @@ function shadergenerator(p5, fn) { let generatorFunction; if (options.parser) { const sourceString = shaderModifier.toString() - const ast = parse(sourceString, { - ecmaVersion: 2021, - locations: options.srcLocations + const ast = parse(sourceString, { + ecmaVersion: 2021, + locations: options.srcLocations }); ancestor(ast, ASTCallbacks); const transpiledSource = escodegen.generate(ast); @@ -39,7 +39,7 @@ function shadergenerator(p5, fn) { console.log("SRC STRING: ", generatorFunction); console.log("NEW OPTIONS:", generatedModifyArgument) return oldModify.call(this, generatedModifyArgument); - } + } else { return oldModify.call(this, shaderModifier) } @@ -67,7 +67,7 @@ function shadergenerator(p5, fn) { } }, // The callbacks for AssignmentExpression and BinaryExpression handle - // operator overloading including +=, *= assignment expressions + // operator overloading including +=, *= assignment expressions AssignmentExpression(node, _state, _ancestors) { if (node.operator !== '=') { const methodName = replaceBinaryOperator(node.operator.replace('=','')); @@ -89,7 +89,7 @@ function shadergenerator(p5, fn) { } }, BinaryExpression(node, _state, ancestors) { - // Don't convert uniform default values to node methods, as + // 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' @@ -215,7 +215,7 @@ function shadergenerator(p5, fn) { score += this.usedIn.length; return score > 3; } - + getTemporaryVariable(context) { if (!this.temporaryVariable) { this.temporaryVariable = `temp_${context.getNextID()}`; @@ -238,7 +238,7 @@ function shadergenerator(p5, fn) { } return this.temporaryVariable; }; - + // Binary Operators add(other) { return new BinaryOperatorNode(this, this.enforceType(other), '+'); } sub(other) { return new BinaryOperatorNode(this, this.enforceType(other), '-'); } @@ -273,7 +273,7 @@ function shadergenerator(p5, fn) { return new this.constructor(other); } } - + toGLSL(context){ throw new TypeError("Not supposed to call this function on BaseNode, which is an abstract class."); } @@ -299,7 +299,7 @@ function shadergenerator(p5, fn) { } } } - + class FloatNode extends BaseNode { constructor(x = 0, isInternal = false){ super(isInternal, 'float'); @@ -319,7 +319,7 @@ function shadergenerator(p5, fn) { } } } - + class VectorNode extends BaseNode { constructor(values, type, isInternal = false) { super(isInternal, type); @@ -328,7 +328,7 @@ function shadergenerator(p5, fn) { this[component] = new FloatNode(values[i], true); }); } - + toGLSL(context) { let glslArgs = ``; @@ -353,7 +353,7 @@ function shadergenerator(p5, fn) { inferredType = arrayArg ? `vec${arrayArg.length}` : undefined; } if (!inferredType) { - inferredType = 'float'; + inferredType = 'float'; } args = args.map((arg, i) => { if (!isShaderNode(arg)) { @@ -384,7 +384,7 @@ function shadergenerator(p5, fn) { return `${this.name}(${this.deconstructArgs(context)})`; } } - + // Variables and member variable nodes class VariableNode extends BaseNode { constructor(name, type, isInternal = false) { @@ -398,7 +398,7 @@ function shadergenerator(p5, fn) { return `${this.name}`; } } - + class ComponentNode extends BaseNode { constructor(parent, componentName, type, isInternal = false) { super(isInternal, type); @@ -408,7 +408,7 @@ function shadergenerator(p5, fn) { } toGLSL(context) { const parentName = this.parent.toGLSLBase(context); - // const parentName = this.parent.temporaryVariable ? this.parent.temporaryVariable : this.parent.name; + // const parentName = this.parent.temporaryVariable ? this.parent.temporaryVariable : this.parent.name; return `${parentName}.${this.componentName}`; } } @@ -426,19 +426,19 @@ function shadergenerator(p5, fn) { this.type = this.determineType(); this.addVectorComponents(); } - + // We know that both this.a and this.b are nodes because of BaseNode.enforceType determineType() { if (this.a.type === this.b.type) { return this.a.type; - } + } else if (isVectorNode(this.a) && isFloatNode(this.b)) { return this.a.type; - } + } else if (isVectorNode(this.b) && isFloatNode(this.a)) { return this.b.type; - } - else if (isFloatNode(this.a) && isIntNode(this.b) + } + else if (isFloatNode(this.a) && isIntNode(this.b) || isIntNode(this.a) && isFloatNode(this.b) ) { return 'float'; @@ -450,7 +450,7 @@ function shadergenerator(p5, fn) { processOperand(operand, context) { if (operand.temporaryVariable) { return operand.temporaryVariable; } - let code = operand.toGLSLBase(context); + let code = operand.toGLSLBase(context); if (isBinaryOperatorNode(operand) && !operand.temporaryVariable) { code = `(${code})`; } @@ -503,7 +503,7 @@ function shadergenerator(p5, fn) { // returns thenReturn(value) {} elseReturn(value) {} - // + // thenDiscard() { new ConditionalDiscard(this.condition); }; @@ -523,28 +523,28 @@ function shadergenerator(p5, fn) { } // Node Helper functions - function isShaderNode(node) { - return (node instanceof BaseNode); + function isShaderNode(node) { + return (node instanceof BaseNode); } - function isIntNode(node) { - return (isShaderNode(node) && (node.type === 'int')); + function isIntNode(node) { + return (isShaderNode(node) && (node.type === 'int')); } - function isFloatNode(node) { - return (isShaderNode(node) && (node.type === 'float')); + function isFloatNode(node) { + return (isShaderNode(node) && (node.type === 'float')); } function isVectorNode(node) { - return (isShaderNode(node) && (node.type === 'vec2'|| node.type === 'vec3' || node.type === 'vec4')); + return (isShaderNode(node) && (node.type === 'vec2'|| node.type === 'vec3' || node.type === 'vec4')); } function isBinaryOperatorNode(node) { return (node instanceof BinaryOperatorNode); } - function isVariableNode(node) { - return (node instanceof VariableNode || node instanceof ComponentNode || typeof(node.temporaryVariable) != 'undefined'); + function isVariableNode(node) { + return (node instanceof VariableNode || node instanceof ComponentNode || typeof(node.temporaryVariable) != 'undefined'); } // Helper function to check if a type is a user defined struct or native type @@ -570,15 +570,26 @@ function shadergenerator(p5, fn) { this.userCallback = userCallback; this.userCallback = userCallback; this.srcLocations = srcLocations; + this.cleanup = () => {}; this.generateHookOverrides(originalShader); this.output = { uniforms: {}, } this.resetGLSLContext(); + this.isGenerating = false; } generate() { + const prevFESDisabled = p5.disableFriendlyErrors; + // We need a custom error handling system within shader generation + p5.disableFriendlyErrors = true; + + this.isGenerating = true; this.userCallback(); + this.isGenerating = false; + + this.cleanup(); + p5.disableFriendlyErrors = prevFESDisabled; return this.output; } @@ -589,6 +600,8 @@ function shadergenerator(p5, fn) { ...originalShader.hooks.fragment, } + const windowOverrides = {}; + Object.keys(availableHooks).forEach((hookName) => { const hookTypes = originalShader.hookTypes(hookName); console.log(hookTypes); @@ -599,7 +612,7 @@ function shadergenerator(p5, fn) { const argsArray = []; hookTypes.parameters.forEach((parameter) => { - // For hooks with structs as input we should pass an object populated with variable nodes + // For hooks with structs as input we should pass an object populated with variable nodes if (!isGLSLNativeType(parameter.type.typeName)) { const structArg = {}; parameter.type.properties.forEach((property) => { @@ -614,7 +627,7 @@ function shadergenerator(p5, fn) { const qualifiers = parameter.type.qualifiers.length > 0 ? parameter.type.qualifiers.join(' ') : ''; argsArray.push(`${qualifiers} ${parameter.type.typeName} ${parameter.name}`.trim()) }) - + let returnedValue = userCallback(...argNodes); const expectedReturnType = hookTypes.returnType; const toGLSLResults = {}; @@ -631,10 +644,10 @@ function shadergenerator(p5, fn) { } toGLSLResults['notAProperty'] = returnedValue.toGLSLBase(this.context); } - + // Build the final GLSL string. // The order of this code is a bit confusing, we need to call toGLSLBase - let codeLines = [ + let codeLines = [ `(${argsArray.join(', ')}) {`, ...this.context.declarations, `${hookTypes.returnType.typeName} finalReturnValue;` @@ -650,14 +663,22 @@ function shadergenerator(p5, fn) { this.resetGLSLContext(); } + windowOverrides[hookTypes.name] = window[hookTypes.name]; + // Expose the Functions to global scope for users to use window[hookTypes.name] = function(userOverride) { - GLOBAL_SHADER[hookTypes.name](userOverride); + GLOBAL_SHADER[hookTypes.name](userOverride); }; - }) + }); + + this.cleanup = () => { + for (const key in windowOverrides) { + window[key] = windowOverrides[key]; + } + }; } - resetGLSLContext() { + resetGLSLContext() { this.context = { id: 0, getNextID: function() { return this.id++ }, @@ -670,7 +691,7 @@ function shadergenerator(p5, fn) { function conformVectorParameters(value, vectorDimensions) { // Allow arguments as arrays ([0,0,0,0]) or not (0,0,0,0) value = value.flat(); - // Populate arguments so uniformVector3(0) becomes [0,0,0] + // Populate arguments so uniformVector3(0) becomes [0,0,0] if (value.length === 1) { value = Array(vectorDimensions).fill(value[0]); } @@ -681,7 +702,7 @@ function shadergenerator(p5, fn) { fn.instanceID = function() { return new VariableNode('gl_InstanceID', 'int'); } - + fn.uvCoords = function() { return new VariableNode('vTexCoord', 'vec2'); } @@ -689,7 +710,7 @@ function shadergenerator(p5, fn) { fn.discard = function() { return new VariableNode('discard', 'keyword'); } - + // Generating uniformFloat, uniformVec, createFloat, etc functions // Maps a GLSL type to the name suffix for method names const GLSLTypesToIdentifiers = { @@ -720,7 +741,7 @@ function shadergenerator(p5, fn) { if(glslType.startsWith('vec')) { defaultValue = conformVectorParameters(defaultValue, +glslType.slice(3)); this.output.uniforms[`${glslType} ${name}`] = defaultValue; - } + } else { this.output.uniforms[`${glslType} ${name}`] = defaultValue[0]; } @@ -728,13 +749,13 @@ function shadergenerator(p5, fn) { return uniform; }; - fn[uniformMethodName] = function (...args) { - return GLOBAL_SHADER[uniformMethodName](...args); + fn[uniformMethodName] = function (...args) { + return GLOBAL_SHADER[uniformMethodName](...args); }; // We don't need a createTexture method. if (glslType === 'sampler2D') { continue; } - + // Generate the create*() Methods for creating variables in shaders const createMethodName = `create${typeIdentifier}`; fn[createMethodName] = function (...value) { @@ -746,9 +767,9 @@ function shadergenerator(p5, fn) { return nodeConstructors[glslType](value); } } - + // GLSL Built in functions - // Add a whole lot of these functions. + // Add a whole lot of these functions. // https://docs.gl/el3/abs // In reality many of these have multiple overrides which will need to address later. // Also, their return types depend on the genType which will need to address urgently @@ -824,13 +845,23 @@ function shadergenerator(p5, fn) { Object.entries(builtInGLSLFunctions).forEach(([functionName, properties]) => { if (properties.isp5Function) { const originalFn = fn[functionName]; - + fn[functionName] = function (...args) { - return new FunctionCallNode(functionName, args, properties) + if (GLOBAL_SHADER?.isGenerating) { + return new FunctionCallNode(functionName, args, properties) + } else { + return originalFn.apply(this, args); + } } } else { fn[functionName] = function (...args) { - return new FunctionCallNode(functionName, args, properties); + if (GLOBAL_SHADER?.isGenerating) { + return new FunctionCallNode(functionName, args, properties); + } else { + p5._friendlyError( + `It looks like you've called ${functionName} outside of a shader's modify() function.` + ); + } } } }) @@ -849,4 +880,4 @@ export default shadergenerator; if (typeof p5 !== 'undefined') { p5.registerAddon(shadergenerator) -} \ No newline at end of file +} From d4f6cef905c87584f819b1bb8c0338dcecdf1852 Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Thu, 20 Mar 2025 14:40:30 +0000 Subject: [PATCH 52/54] add toFloat() for ease of use with instanceID() --- src/webgl/ShaderGenerator.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index b98ae876f4..0ff4adfc54 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -274,6 +274,16 @@ function shadergenerator(p5, fn) { } } + toFloat() { + if (isFloatNode(this)) { + return this; + } else if (isIntNode(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."); } @@ -373,11 +383,13 @@ function shadergenerator(p5, fn) { } deconstructArgs(context) { - if (Array.isArray(this.args)) { - return this.args.map((argNode) => argNode.toGLSLBase(context)).join(', '); - } else { - return `${this.args.toGLSLBase(context)}`; - } + let argsString = this.args.map((argNode, i) => { + if (isIntNode(argNode) && this.argumentTypes[i] != 'float') { + argNode = argNode.toFloat(); + } + return argNode.toGLSLBase(context); + }).join(', '); + return argsString; } toGLSL(context) { From a5e76ddd8f023984488cdb8b3bf79c7e053184ba Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Thu, 20 Mar 2025 14:40:52 +0000 Subject: [PATCH 53/54] remove uvCoords function as vTexCoord is not always defined in a given shader --- src/webgl/ShaderGenerator.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 0ff4adfc54..7dfd76be32 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -715,10 +715,6 @@ function shadergenerator(p5, fn) { return new VariableNode('gl_InstanceID', 'int'); } - fn.uvCoords = function() { - return new VariableNode('vTexCoord', 'vec2'); - } - fn.discard = function() { return new VariableNode('discard', 'keyword'); } From 3dc08f6087a4a98aa6a13ea13aa05d883c927b4e Mon Sep 17 00:00:00 2001 From: 23036879 <l.plowden0620231@arts.ac.uk> Date: Thu, 20 Mar 2025 14:41:07 +0000 Subject: [PATCH 54/54] working on tutorial sketch --- preview/global/sketch.js | 79 +++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index f9cede49d5..f6d045ff6b 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -1,53 +1,50 @@ -// p5.disableFriendlyErrors = true; +p5.disableFriendlyErrors = true; function windowResized() { resizeCanvas(windowWidth, windowHeight); } -let myShader; -let filterShader; -let video; +let myModel; +let starShader; +let starStrokeShader; +let stars; + +function starShaderCallback() { + const time = uniformFloat(() => millis()); + getWorldInputs((inputs) => { + inputs.position.y += instanceID() * 20 - 1000; + inputs.position.x += 40*sin(time * 0.001 + instanceID()); + return inputs; + }); + getObjectInputs((inputs) => { + inputs.position *= sin(time*0.001 + instanceID()); + return inputs; + }) +} async function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); - - // filterShader = baseFi - - filterShader = baseFilterShader().modify(() => { - const time = uniformFloat(() => millis()); - - getColor((input, canvasContents) => { - let myColor = texture(canvasContents, uvCoords()); - const d = distance(input.texCoord, [0.5, 0.5]); - myColor.x = smoothstep(0, 0.5, d); - myColor.y = sin(time*0.001)/2; - return myColor; - }) - }); - - myShader = baseMaterialShader().modify(() => { - const time = uniformFloat(() => millis()); - - getFinalColor((col) => { - col.x = uvCoords().x; - col.y = uvCoords().y; - col.z = 1; - return col; - }); - - getWorldInputs((inputs) => { - inputs.position.y += 20 * sin(time * 0.001 + inputs.position.x * 0.05); - return inputs; - }) - }); + stars = buildGeometry(() => sphere(20, 7, 4)) + starShader = baseMaterialShader().modify(starShaderCallback); + starStrokeShader = baseStrokeShader().modify(starShaderCallback) } function draw(){ - // orbitControl(); - background(0); + background(0,200,240); + orbitControl(); + // noStroke(); + + push(); + stroke(255,0,255) + fill(255,200,255) + strokeShader(starStrokeShader) + shader(starShader); + model(stars, 100); + pop(); + push(); + shader(baseMaterialShader()); noStroke(); - fill('blue') - sphere(100) - shader(myShader); - filter(filterShader); - // filterShader.setUniform('time', millis()); + rotateX(HALF_PI); + translate(0, 0, -250); + plane(10000) + pop(); }