From eed0a3003a5da52dcfd5543b0f087084a91db673 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Tue, 25 Mar 2025 18:36:54 +0000 Subject: [PATCH 01/49] example sketch --- preview/global/sketch.js | 168 +++++++++++++++++++++++++++------------ 1 file changed, 119 insertions(+), 49 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 35009f1f51..bb144e2e8f 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -1,4 +1,5 @@ p5.disableFriendlyErrors = true; + function windowResized() { resizeCanvas(windowWidth, windowHeight); } @@ -6,89 +7,158 @@ function windowResized() { let myModel; let starShader; let starStrokeShader; +let waterShader; let stars; -let ditheringShader; let originalFrameBuffer; -let blurredFrameBuffer; +let pixellizeShader; +let fresnelShader; +let bloomShader; +let myCamera; + +function fresnelShaderCallback() { + const fresnelPower = uniformFloat(2); + const fresnelBias = uniformFloat(-0.1); + const fresnelScale = uniformFloat(2); + const viewDir = uniformVector3(); + const time = uniformFloat(() => millis()); + + getWorldInputs((inputs) => { + let n = normalize(inputs.normal); + let v = normalize(viewDir); + + let base = 1.0 - dot(n, v); + let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias; + let col = mix([0,0,0], [1, .5, .7], fresnel); + inputs.color = createVector4(col.x, col.y, col.z, 1); + + inputs.position.x += 10 * sin(inputs.position.z + time * 0.01); + return inputs; + }); +} function starShaderCallback() { const time = uniformFloat(() => millis()); + const skyRadius = uniformFloat(1000); + + function rand2(st) { + return fract(sin(dot(st, createVector2(12.9898, 78.233))) * 43758.5453123); + } + + function semiSphere() { + let id = instanceID(); + let theta = rand2(createVector2(id, 0.1234)) * TWO_PI; + let phi = rand2(createVector2(id, 3.321)) * PI+time/10000; + let r = skyRadius; + r *= 1.5 * sin(phi); + let x = r * sin(phi) * cos(theta); + let y = r *1.5* cos(phi); + let z = r * sin(phi) * sin(theta); + return createVector3(x, y, z); + } + getWorldInputs((inputs) => { - inputs.position.y += instanceID() * 20 - 1000; - inputs.position.x += 40 * sin(time * 0.001 + instanceID()); + inputs.position += semiSphere(); return inputs; }); + getObjectInputs((inputs) => { - inputs.position *= sin(time*0.001 + instanceID()); + let scale = 1 + 0.1 * sin(time * 0.002 + instanceID()); + inputs.position *= scale; return inputs; - }) + }); } -function ditheringCallback() { - const time = uniformFloat(() => millis()) - - function rand(co) { - return fract(sin(dot(co, [12.9898, 78.233])) * 43758.5453); - } - - function grayscale(col) { - return dot([col.x, col.y, col.z], [0.21, 0.72, 0.07]) +function waterShaderCallback() { + const time = uniformFloat(() => millis()); + const speed = uniformFloat(0.005); + + function getOffset(st) { + st = st * 12 - 6; + let d = length(st); + return 0.5 + 0.5 * sin(d * 10.0 - time * speed); } + getObjectInputs((inputs) => { + const offset = getOffset(inputs.uv); + inputs.position.z += offset * 200 - 100; + return inputs; + }); + + getPixelInputs((inputs) => { + const offset = getOffset(inputs.texCoord); + inputs.color = createVector4(0, 0.3*offset, offset, 1); + return inputs; + }); +} + +function pixellizeShaderCallback() { + const pixelSize = uniformFloat(()=> width/2); getColor((input, canvasContent) => { - let col = texture(canvasContent, input.texCoord); - col.z = 0.55; - col += rand(input.texCoord + time/10000000000) * 0.15 - 0.05; - let greyscaleValue = grayscale(col); - col.x = greyscaleValue - col.y = greyscaleValue + let coord = input.texCoord; + coord = floor(coord * pixelSize) / pixelSize; + let col = texture(canvasContent, coord); return col; }); } -function bloom() { - const blurred = uniformTexture(() => blurredFrameBuffer); - const original = uniformTexture(() => originalFrameBuffer); - +function bloomShaderCallback() { + const preBlur = uniformTexture(() => originalFrameBuffer); getColor((input, canvasContent) => { - const blurredCol = texture(blurred, input.texCoord); - const originalCol = texture(original, input.texCoord); - const brightPass = max(originalCol - 0.0, 0.0) * 3.0; - // const bloom = original + blurred * brightPass; - // return bloom; - return texture(blurred, input.texCoord) + texture(original, input.texCoord); + const blurredCol = texture(canvasContent, input.texCoord); + const originalCol = texture(preBlur, input.texCoord); + const brightPass = max(originalCol - 0.3, 0.7) * 1.2; + const bloom = originalCol + blurredCol * brightPass; + return bloom; }); } async function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); - stars = buildGeometry(() => sphere(20, 3, 3)) - starShader = baseMaterialShader().modify(starShaderCallback); - starStrokeShader = baseStrokeShader().modify(starShaderCallback) - ditheringShader = baseFilterShader().modify(ditheringCallback); + stars = buildGeometry(() => sphere(20, 4, 2)) originalFrameBuffer = createFramebuffer(); blurredFrameBuffer = createFramebuffer(); - bloomShader = baseFilterShader().modify(bloom); + + starShader = baseMaterialShader().modify(starShaderCallback); + starStrokeShader = baseStrokeShader().modify(starShaderCallback) + console.log("FRESNEL") + fresnelShader = baseColorShader().modify(fresnelShaderCallback); + // waterShader = baseMaterialShader().modify(waterShaderCallback); + bloomShader = baseFilterShader().modify(bloomShaderCallback); + pixellizeShader = baseFilterShader().modify(pixellizeShaderCallback); + + let myCamera = createCamera(); + myCamera.setPosition(0, 0, 1000); } function draw(){ originalFrameBuffer.begin(); - orbitControl(); - background(0,0,0); - push(); - stroke(255,0,255) - fill(255,200,255) + background(0); + orbitControl(); + + push() + strokeWeight(4) + stroke(255,0,0) + rotateX(PI/2 + millis() * 0.0005); + fill(255,100, 150) strokeShader(starStrokeShader) shader(starShader); - model(stars, 100); - pop(); + model(stars, 5000); + pop() + + push() + shader(fresnelShader) + let viewDir = [originalFrameBuffer.defaultCamera.eyeX, originalFrameBuffer.defaultCamera.eyeY, originalFrameBuffer.defaultCamera.eyeZ]; + fresnelShader.setUniform("viewDir", viewDir) + stroke('cyan') + sphere(500); + pop() + + filter(pixellizeShader); originalFrameBuffer.end(); - blurredFrameBuffer.begin(); - image(originalFrameBuffer, -windowWidth/2, -windowHeight/2) - filter(BLUR) - blurredFrameBuffer.end(); - - // image(originalFrameBuffer, -windowWidth/2, -windowHeight/2) + imageMode(CENTER) + image(originalFrameBuffer, 0, 0) + + filter(BLUR, 20) filter(bloomShader); } From 9db6d15da30e280101f36107a0951218812e7b42 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Tue, 25 Mar 2025 18:43:31 +0000 Subject: [PATCH 02/49] example sketch --- preview/global/sketch.js | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index bb144e2e8f..29c67d77fe 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -4,16 +4,13 @@ function windowResized() { resizeCanvas(windowWidth, windowHeight); } -let myModel; let starShader; let starStrokeShader; -let waterShader; let stars; let originalFrameBuffer; let pixellizeShader; let fresnelShader; let bloomShader; -let myCamera; function fresnelShaderCallback() { const fresnelPower = uniformFloat(2); @@ -68,29 +65,6 @@ function starShaderCallback() { }); } -function waterShaderCallback() { - const time = uniformFloat(() => millis()); - const speed = uniformFloat(0.005); - - function getOffset(st) { - st = st * 12 - 6; - let d = length(st); - return 0.5 + 0.5 * sin(d * 10.0 - time * speed); - } - - getObjectInputs((inputs) => { - const offset = getOffset(inputs.uv); - inputs.position.z += offset * 200 - 100; - return inputs; - }); - - getPixelInputs((inputs) => { - const offset = getOffset(inputs.texCoord); - inputs.color = createVector4(0, 0.3*offset, offset, 1); - return inputs; - }); -} - function pixellizeShaderCallback() { const pixelSize = uniformFloat(()=> width/2); getColor((input, canvasContent) => { @@ -116,18 +90,12 @@ async function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); stars = buildGeometry(() => sphere(20, 4, 2)) originalFrameBuffer = createFramebuffer(); - blurredFrameBuffer = createFramebuffer(); starShader = baseMaterialShader().modify(starShaderCallback); starStrokeShader = baseStrokeShader().modify(starShaderCallback) - console.log("FRESNEL") fresnelShader = baseColorShader().modify(fresnelShaderCallback); - // waterShader = baseMaterialShader().modify(waterShaderCallback); bloomShader = baseFilterShader().modify(bloomShaderCallback); pixellizeShader = baseFilterShader().modify(pixellizeShaderCallback); - - let myCamera = createCamera(); - myCamera.setPosition(0, 0, 1000); } function draw(){ @@ -149,7 +117,7 @@ function draw(){ shader(fresnelShader) let viewDir = [originalFrameBuffer.defaultCamera.eyeX, originalFrameBuffer.defaultCamera.eyeY, originalFrameBuffer.defaultCamera.eyeZ]; fresnelShader.setUniform("viewDir", viewDir) - stroke('cyan') + noStroke() sphere(500); pop() From e9b361e31c2d552a7464bd24b63c4129017f4fe0 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Wed, 26 Mar 2025 13:58:07 +0000 Subject: [PATCH 03/49] ensure property nodes are nodes when generating glsl --- src/webgl/ShaderGenerator.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 98dd6550e8..8ae16eebd8 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -133,6 +133,9 @@ function shadergenerator(p5, fn) { if (typeof leftValue === 'number') { return new FloatNode(leftValue); } + else if (Array.isArray(leftValue)) { + return new VectorNode(leftValue, `vec${leftValue.length}`); + } } // Javascript Node API. @@ -643,6 +646,9 @@ function shadergenerator(p5, fn) { // If the expected return type is a struct we need to evaluate each of its properties if (!isGLSLNativeType(expectedReturnType.typeName)) { Object.entries(returnedValue).forEach(([propertyName, propertyNode]) => { + if (!isShaderNode(propertyNode)) { + propertyNode = makeNode(propertyNode); + } toGLSLResults[propertyName] = propertyNode.toGLSLBase(this.context); }) } else { From fd707a5ae0ba98ae3dec5486ee0df17b42d9b918 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Wed, 26 Mar 2025 16:05:26 +0000 Subject: [PATCH 04/49] Couple of bug fixes --- src/webgl/ShaderGenerator.js | 56 +++++++++++++++++------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 8ae16eebd8..1386b13e0f 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -34,8 +34,9 @@ function shadergenerator(p5, fn) { } else { generatorFunction = shaderModifier; } - const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations) + const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations); const generatedModifyArgument = generator.generate(); + console.log(generatedModifyArgument) return oldModify.call(this, generatedModifyArgument); } else { @@ -99,15 +100,15 @@ function shadergenerator(p5, fn) { } // If the left hand side of an expression is one of these types, // we should construct a node from it. - const unsafeTypes = ["Literal", "ArrayExpression"] + const unsafeTypes = ["Literal", "ArrayExpression", "Identifier"]; if (unsafeTypes.includes(node.left.type)) { const leftReplacementNode = { type: "CallExpression", callee: { type: "Identifier", - name: "makeNode", + name: "dynamicNode", }, - arguments: [node.left, node.right] + arguments: [node.left] } node.left = leftReplacementNode; } @@ -129,12 +130,15 @@ function shadergenerator(p5, fn) { // This unfinished function lets you do 1 * 10 // and turns it into float.mult(10) - fn.makeNode = function(leftValue, rightValue) { - if (typeof leftValue === 'number') { - return new FloatNode(leftValue); + fn.dynamicNode = function (input) { + if (isShaderNode(input)) { + return input; } - else if (Array.isArray(leftValue)) { - return new VectorNode(leftValue, `vec${leftValue.length}`); + else if (typeof input === 'number') { + return new FloatNode(input); + } + else if (Array.isArray(input)) { + return new VectorNode(input, `vec${input.length}`); } } @@ -154,7 +158,6 @@ function shadergenerator(p5, fn) { // 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) { @@ -168,13 +171,6 @@ function shadergenerator(p5, fn) { } } } - // get type() { - // return this._type; - // } - - // set type(value) { - // this._type = value; - // } addVectorComponents() { if (this.type.startsWith('vec')) { @@ -211,10 +207,10 @@ function shadergenerator(p5, fn) { if (this.swizzleChanged) { return true; } if (this.isInternal || isVariableNode(this)) { return false; } let score = 0; - score += isBinaryOperatorNode(this); + score += isBinaryOperatorNode(this) * 2; score += isVectorNode(this) * 2; - score += this.usedIn.length; - return score > 3; + score += Math.max(0, this.usedIn.length - 1); + return score >= 3; } getTemporaryVariable(context) { @@ -230,10 +226,10 @@ function shadergenerator(p5, fn) { 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) + ";"; + line += " " + this.type + " " + this.temporaryVariable + " = " + this.toGLSL(context) + ";"; + line += `\n` + " " + this.temporaryVariable + " = " + replacement.toGLSL(context) + ";"; } else { - line += this.type + " " + this.temporaryVariable + " = " + this.toGLSL(context) + ";"; + line += " " + this.type + " " + this.temporaryVariable + " = " + this.toGLSL(context) + ";"; } context.declarations.push(line); } @@ -421,8 +417,7 @@ function shadergenerator(p5, fn) { } toGLSL(context) { const parentName = this.parent.toGLSLBase(context); - // const parentName = this.parent.temporaryVariable ? this.parent.temporaryVariable : this.parent.name; - return `${parentName}.${this.componentName}`; + return `(${parentName}).${this.componentName}`; } } @@ -647,11 +642,12 @@ function shadergenerator(p5, fn) { if (!isGLSLNativeType(expectedReturnType.typeName)) { Object.entries(returnedValue).forEach(([propertyName, propertyNode]) => { if (!isShaderNode(propertyNode)) { - propertyNode = makeNode(propertyNode); + propertyNode = dynamicNode(propertyNode); } toGLSLResults[propertyName] = propertyNode.toGLSLBase(this.context); }) - } else { + } + else { // We can accept raw numbers or arrays otherwise if (!isShaderNode(returnedValue)) { returnedValue = nodeConstructors[expectedReturnType.typeName](returnedValue) @@ -664,15 +660,15 @@ function shadergenerator(p5, fn) { let codeLines = [ `(${argsArray.join(', ')}) {`, ...this.context.declarations, - `${hookTypes.returnType.typeName} finalReturnValue;` + `\n ${hookTypes.returnType.typeName} finalReturnValue;` ]; Object.entries(toGLSLResults).forEach(([propertyName, result]) => { const propString = expectedReturnType.properties ? `.${propertyName}` : ''; - codeLines.push(`finalReturnValue${propString} = ${result};`) + codeLines.push(` finalReturnValue${propString} = ${result};`) }) - codeLines.push('return finalReturnValue;', '}'); + codeLines.push(' return finalReturnValue;', '}'); this.output[hookName] = codeLines.join('\n'); this.resetGLSLContext(); } From a75f9b2ca12c472966529fd55f1d341c7f2bd966 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Wed, 26 Mar 2025 16:06:26 +0000 Subject: [PATCH 05/49] remove console debug logs --- src/webgl/ShaderGenerator.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 1386b13e0f..0ad26568aa 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -36,7 +36,6 @@ function shadergenerator(p5, fn) { } const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations); const generatedModifyArgument = generator.generate(); - console.log(generatedModifyArgument) return oldModify.call(this, generatedModifyArgument); } else { From 1938df29e455790a07327b1c10f0e0fb26a6cda3 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Wed, 26 Mar 2025 16:23:05 +0000 Subject: [PATCH 06/49] fix bug, now possible vector uniform defaults to be defined by functions --- 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 0ad26568aa..8a97d8bac2 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -743,7 +743,7 @@ function shadergenerator(p5, fn) { ShaderGenerator.prototype[uniformMethodName] = function(...args) { let [name, ...defaultValue] = args; - if(glslType.startsWith('vec')) { + if(glslType.startsWith('vec') && !(defaultValue[0] instanceof Function)) { defaultValue = conformVectorParameters(defaultValue, +glslType.slice(3)); this.output.uniforms[`${glslType} ${name}`] = defaultValue; } From ca66bf9ea82a7a5fd93fbefad20f23800dfe7880 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Wed, 26 Mar 2025 16:27:10 +0000 Subject: [PATCH 07/49] example sketch final --- preview/global/sketch.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 29c67d77fe..fd242398ab 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -12,12 +12,14 @@ let pixellizeShader; let fresnelShader; let bloomShader; + function fresnelShaderCallback() { const fresnelPower = uniformFloat(2); const fresnelBias = uniformFloat(-0.1); const fresnelScale = uniformFloat(2); const viewDir = uniformVector3(); const time = uniformFloat(() => millis()); + const mouseIntensity = uniformVector2(()=>[map(mouseX, 0, width, 0, 0.15), map(mouseY, 0, height, 0, 0.15)]); getWorldInputs((inputs) => { let n = normalize(inputs.normal); @@ -25,8 +27,8 @@ function fresnelShaderCallback() { let base = 1.0 - dot(n, v); let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias; - let col = mix([0,0,0], [1, .5, .7], fresnel); - inputs.color = createVector4(col.x, col.y, col.z, 1); + let col = mix([mouseIntensity.y, 0, mouseIntensity.x], [1, .5, .7], fresnel); + inputs.color = [col.x, col.y, col.z, 1]; inputs.position.x += 10 * sin(inputs.position.z + time * 0.01); return inputs; @@ -38,19 +40,19 @@ function starShaderCallback() { const skyRadius = uniformFloat(1000); function rand2(st) { - return fract(sin(dot(st, createVector2(12.9898, 78.233))) * 43758.5453123); + return fract(sin(dot(st, [12.9898, 78.233])) * 43758.5453123); } function semiSphere() { let id = instanceID(); - let theta = rand2(createVector2(id, 0.1234)) * TWO_PI; - let phi = rand2(createVector2(id, 3.321)) * PI+time/10000; + let theta = rand2([id, 0.1234]) * TWO_PI; + let phi = rand2([id, 3.321]) * PI+time/10000; let r = skyRadius; r *= 1.5 * sin(phi); let x = r * sin(phi) * cos(theta); let y = r *1.5* cos(phi); let z = r * sin(phi) * sin(theta); - return createVector3(x, y, z); + return [x, y, z]; } getWorldInputs((inputs) => { From a462487cee6a4f20061d86123f62dd8feb8b8ede Mon Sep 17 00:00:00 2001 From: 23036879 Date: Thu, 27 Mar 2025 13:01:54 +0000 Subject: [PATCH 08/49] Allow for swizzling on arrays --- src/webgl/ShaderGenerator.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 8a97d8bac2..d2d29cb882 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -85,6 +85,16 @@ function shadergenerator(p5, fn) { node.operator = '='; node.right = rightReplacementNode; } + if (node.right.type === 'ArrayExpression') { + node.right = { + type: 'CallExpression', + callee: { + type: "Identifier", + name: "dynamicNode", + }, + arguments: [node.right] + } + } }, BinaryExpression(node, _state, ancestors) { // Don't convert uniform default values to node methods, as From cd94ec6779674b4cf5f73e677266458abe2084be Mon Sep 17 00:00:00 2001 From: 23036879 Date: Thu, 27 Mar 2025 13:05:15 +0000 Subject: [PATCH 09/49] Fixed some problems with temporary variables. work in progress --- src/webgl/ShaderGenerator.js | 39 ++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index d2d29cb882..307703a79d 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -213,12 +213,19 @@ function shadergenerator(p5, fn) { } shouldUseTemporaryVariable() { - if (this.swizzleChanged) { return true; } + if (this.type === 'sampler2D') { return false; } + return true; if (this.isInternal || isVariableNode(this)) { return false; } + if (this.swizzleChanged) { return true; } + if (isFunctionCallNode(this)) { return true; } + if (this.usedIn.length > 1) { return true; } let score = 0; - score += isBinaryOperatorNode(this) * 2; - score += isVectorNode(this) * 2; - score += Math.max(0, this.usedIn.length - 1); + score += isBinaryExpressionNode(this) * 2; + score += isVectorType(this) * 2; + score += isFloatType(this); + // score += isLiteralNode(this); + score += isVectorNode(this); + score += Math.max(0, this.usedIn.length); return score >= 3; } @@ -229,17 +236,7 @@ function shadergenerator(p5, fn) { if (this.srcLine) { line += `\n// From ${this.srcLine}\n`; } - if (this.swizzleChanged) { - const valueArgs = []; - for (let componentName of this.componentNames) { - valueArgs.push(this[componentName]) - } - const replacement = nodeConstructors[this.type](valueArgs) - line += " " + this.type + " " + this.temporaryVariable + " = " + this.toGLSL(context) + ";"; - line += `\n` + " " + this.temporaryVariable + " = " + replacement.toGLSL(context) + ";"; - } else { - line += " " + this.type + " " + this.temporaryVariable + " = " + this.toGLSL(context) + ";"; - } + line += " " + this.type + " " + this.temporaryVariable + " = " + this.toGLSL(context) + ";"; context.declarations.push(line); } return this.temporaryVariable; @@ -382,6 +379,9 @@ function shadergenerator(p5, fn) { properties.returnType = inferredType; } super(isInternal, properties.returnType); + for (const arg of args) { + arg.usedIn.push(this); + } this.name = name; this.args = args; this.argumentTypes = properties.args; @@ -591,6 +591,7 @@ function shadergenerator(p5, fn) { this.output = { uniforms: {}, } + this.uniformNodes = []; this.resetGLSLContext(); this.isGenerating = false; } @@ -620,6 +621,8 @@ function shadergenerator(p5, fn) { Object.keys(availableHooks).forEach((hookName) => { const hookTypes = originalShader.hookTypes(hookName); + + // These functions are where the user code is executed this[hookTypes.name] = function(userCallback) { // Create the initial nodes which are passed to the user callback // Also generate a string of the arguments for the code generation @@ -698,11 +701,16 @@ function shadergenerator(p5, fn) { } resetGLSLContext() { + this.uniformNodes.forEach((node) => { + node.usedIn = []; + node.temporaryVariable = undefined; + }); this.context = { id: 0, getNextID: function() { return this.id++ }, declarations: [], } + this.uniformNodes = []; } } @@ -761,6 +769,7 @@ function shadergenerator(p5, fn) { this.output.uniforms[`${glslType} ${name}`] = defaultValue[0]; } const uniform = new VariableNode(name, glslType, false); + this.uniformNodes.push(uniform); return uniform; }; From 4cc5956c57ec2044f096bace3125024b6116b598 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Thu, 27 Mar 2025 13:06:01 +0000 Subject: [PATCH 10/49] clarify helper functions for types/ nodes, more accurate naming --- src/webgl/ShaderGenerator.js | 76 +++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 307703a79d..f16819c372 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -175,7 +175,7 @@ function shadergenerator(p5, fn) { } catch (e) { const lines = e.stack.split("\n"); let userSketchLineIndex = 5; - if (isBinaryOperatorNode(this)) { userSketchLineIndex--; }; + if (isBinaryExpressionNode(this)) { userSketchLineIndex--; }; this.srcLine = lines[userSketchLineIndex].trim(); } } @@ -243,10 +243,10 @@ function shadergenerator(p5, fn) { }; // 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), '/'); } + add(other) { return new BinaryExpressionNode(this, this.enforceType(other), '+'); } + sub(other) { return new BinaryExpressionNode(this, this.enforceType(other), '-'); } + mult(other) { return new BinaryExpressionNode(this, this.enforceType(other), '*'); } + div(other) { return new BinaryExpressionNode(this, this.enforceType(other), '/'); } mod(other) { return new ModulusNode(this, this.enforceType(other)); } // Check that the types of the operands are compatible. @@ -258,13 +258,13 @@ function shadergenerator(p5, fn) { if (!isGLSLNativeType(other.type)) { throw new TypeError (`You've tried to perform an operation on a struct of type: ${other.type}. Try accessing a member on that struct with '.'`) } - if ((isFloatNode(this) || isVectorNode(this)) && isIntNode(other)) { + if ((isFloatType(this) || isVectorType(this)) && isIntType(other)) { return new FloatNode(other) } return other; } else if(typeof other === 'number') { - if (isIntNode(this)) { + if (isIntType(this)) { return new IntNode(other); } return new FloatNode(other); @@ -278,9 +278,9 @@ function shadergenerator(p5, fn) { } toFloat() { - if (isFloatNode(this)) { + if (isFloatType(this)) { return this; - } else if (isIntNode(this)) { + } else if (isIntType(this)) { return new FloatNode(this); } else { throw new TypeError(`Can't convert from type '${this.type}' to 'float'.`) @@ -302,7 +302,7 @@ function shadergenerator(p5, fn) { toGLSL(context) { if (isShaderNode(this.x)) { let code = this.x.toGLSLBase(context); - return isIntNode(this.x.type) ? code : `int(${code})`; + return isIntType(this.x.type) ? code : `int(${code})`; } else if (typeof this.x === "number") { return `${Math.floor(this.x)}`; @@ -322,7 +322,7 @@ function shadergenerator(p5, fn) { toGLSL(context) { if (isShaderNode(this.x)) { let code = this.x.toGLSLBase(context); - return isFloatNode(this.x) ? code : `float(${code})`; + return isFloatType(this.x) ? code : `float(${code})`; } else if (typeof this.x === "number") { return `${this.x.toFixed(4)}`; @@ -358,8 +358,8 @@ function shadergenerator(p5, fn) { class FunctionCallNode extends BaseNode { constructor(name, args, properties, isInternal = false) { let inferredType = args.find((arg, i) => { - properties.args[i] === 'genType' - && isShaderNode(arg) + if (i >= properties.args.length) return false; + return properties.args[i] === 'genType' && isShaderNode(arg); })?.type; if (!inferredType) { let arrayArg = args.find(arg => Array.isArray(arg)); @@ -390,7 +390,7 @@ function shadergenerator(p5, fn) { deconstructArgs(context) { let argsString = this.args.map((argNode, i) => { - if (isIntNode(argNode) && this.argumentTypes[i] != 'float') { + if (isIntType(argNode) && this.argumentTypes[i] != 'float') { argNode = argNode.toFloat(); } return argNode.toGLSLBase(context); @@ -425,13 +425,16 @@ function shadergenerator(p5, fn) { this.type = type; } toGLSL(context) { - const parentName = this.parent.toGLSLBase(context); - return `(${parentName}).${this.componentName}`; + let parentName = this.parent.toGLSLBase(context); + if (isBinaryExpressionNode(this.parent) && !this.parent.temporaryVariable) { + parentName = `(${parentName})`; + } + return `${parentName}.${this.componentName}`; } } // Binary Operator Nodes - class BinaryOperatorNode extends BaseNode { + class BinaryExpressionNode extends BaseNode { constructor(a, b, operator, isInternal = false) { super(isInternal, null); this.op = operator; @@ -449,14 +452,14 @@ function shadergenerator(p5, fn) { if (this.a.type === this.b.type) { return this.a.type; } - else if (isVectorNode(this.a) && isFloatNode(this.b)) { + else if (isVectorType(this.a) && isFloatType(this.b)) { return this.a.type; } - else if (isVectorNode(this.b) && isFloatNode(this.a)) { + else if (isVectorType(this.b) && isFloatType(this.a)) { return this.b.type; } - else if (isFloatNode(this.a) && isIntNode(this.b) - || isIntNode(this.a) && isFloatNode(this.b) + else if (isFloatType(this.a) && isIntType(this.b) + || isIntType(this.a) && isFloatType(this.b) ) { return 'float'; } @@ -468,10 +471,10 @@ function shadergenerator(p5, fn) { processOperand(operand, context) { if (operand.temporaryVariable) { return operand.temporaryVariable; } let code = operand.toGLSLBase(context); - if (isBinaryOperatorNode(operand) && !operand.temporaryVariable) { + if (isBinaryExpressionNode(operand) && !operand.temporaryVariable) { code = `(${code})`; } - if (this.type === 'float' && isIntNode(operand)) { + if (this.type === 'float' && isIntType(operand)) { code = `float(${code})`; } return code; @@ -484,13 +487,13 @@ function shadergenerator(p5, fn) { } } - class ModulusNode extends BinaryOperatorNode { + class ModulusNode extends BinaryExpressionNode { constructor(a, b) { super(a, b); } toGLSL(context) { // Switch on type between % or mod() - if (isVectorNode(this) || isFloatNode(this)) { + if (isVectorType(this) || isFloatType(this)) { return `mod(${this.a.toGLSLBase(context)}, ${this.b.toGLSLBase(context)})`; } return `${this.processOperand(context, this.a)} % ${this.processOperand(context, this.b)}`; @@ -543,26 +546,38 @@ function shadergenerator(p5, fn) { return (node instanceof BaseNode); } - function isIntNode(node) { + function isIntType(node) { return (isShaderNode(node) && (node.type === 'int')); } - function isFloatNode(node) { + function isFloatType(node) { return (isShaderNode(node) && (node.type === 'float')); } - function isVectorNode(node) { + function isVectorType(node) { return (isShaderNode(node) && (node.type === 'vec2'|| node.type === 'vec3' || node.type === 'vec4')); } - function isBinaryOperatorNode(node) { - return (node instanceof BinaryOperatorNode); + function isBinaryExpressionNode(node) { + return (node instanceof BinaryExpressionNode); } function isVariableNode(node) { return (node instanceof VariableNode || node instanceof ComponentNode || typeof(node.temporaryVariable) != 'undefined'); } + function isLiteralNode(node) { + return (node instanceof FloatNode || node instanceof IntNode); + } + + function isFunctionCallNode(node) { + return (node instanceof FunctionCallNode); + } + + function isVectorNode(node) { + return (node instanceof VectorNode) + } + // Helper function to check if a type is a user defined struct or native type function isGLSLNativeType(typeName) { // Supported types for now @@ -584,7 +599,6 @@ function shadergenerator(p5, fn) { constructor(userCallback, originalShader, srcLocations) { GLOBAL_SHADER = this; this.userCallback = userCallback; - this.userCallback = userCallback; this.srcLocations = srcLocations; this.cleanup = () => {}; this.generateHookOverrides(originalShader); From 7daa0bc085d38377b167378ffc7920f6c61f31ef Mon Sep 17 00:00:00 2001 From: 23036879 Date: Thu, 27 Mar 2025 13:06:45 +0000 Subject: [PATCH 11/49] use camera space instead of sending camera position as a uniform --- preview/global/sketch.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index fd242398ab..7d405e96b5 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -17,20 +17,15 @@ function fresnelShaderCallback() { const fresnelPower = uniformFloat(2); const fresnelBias = uniformFloat(-0.1); const fresnelScale = uniformFloat(2); - const viewDir = uniformVector3(); - const time = uniformFloat(() => millis()); const mouseIntensity = uniformVector2(()=>[map(mouseX, 0, width, 0, 0.15), map(mouseY, 0, height, 0, 0.15)]); - getWorldInputs((inputs) => { + getCameraInputs((inputs) => { let n = normalize(inputs.normal); - let v = normalize(viewDir); - + let v = normalize(0 - inputs.position); let base = 1.0 - dot(n, v); - let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias; + let fresnel = createVector3(fresnelScale * pow(base, fresnelPower) + fresnelBias); let col = mix([mouseIntensity.y, 0, mouseIntensity.x], [1, .5, .7], fresnel); inputs.color = [col.x, col.y, col.z, 1]; - - inputs.position.x += 10 * sin(inputs.position.z + time * 0.01); return inputs; }); } @@ -117,8 +112,6 @@ function draw(){ push() shader(fresnelShader) - let viewDir = [originalFrameBuffer.defaultCamera.eyeX, originalFrameBuffer.defaultCamera.eyeY, originalFrameBuffer.defaultCamera.eyeZ]; - fresnelShader.setUniform("viewDir", viewDir) noStroke() sphere(500); pop() From 744d4a1c9b9f1f6e506f74365e67df75d9333000 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Thu, 27 Mar 2025 16:10:57 +0000 Subject: [PATCH 12/49] update function call node to prioritize vectors as gentypes, as some function signatures allow for vectors as gentype + float --- src/webgl/ShaderGenerator.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index f16819c372..b4b46dd5e3 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -357,26 +357,28 @@ function shadergenerator(p5, fn) { // Function Call Nodes class FunctionCallNode extends BaseNode { constructor(name, args, properties, isInternal = false) { - let inferredType = args.find((arg, i) => { - if (i >= properties.args.length) return false; - return properties.args[i] === 'genType' && isShaderNode(arg); - })?.type; - if (!inferredType) { + if (args.length !== properties.args.length) { + throw new Error(`Function ${name} expects ${properties.args.length} arguments, but ${args.length} were provided.`); + } + let inferredGenType = args.find((arg, i) => + properties.args[i] === 'genType' && isShaderNode(arg) + )?.type; + if (!inferredGenType || inferredGenType === 'float') { let arrayArg = args.find(arg => Array.isArray(arg)); - inferredType = arrayArg ? `vec${arrayArg.length}` : undefined; + inferredGenType = arrayArg ? `vec${arrayArg.length}` : undefined; } - if (!inferredType) { - inferredType = 'float'; + if (!inferredGenType) { + inferredGenType = 'float'; } args = args.map((arg, i) => { if (!isShaderNode(arg)) { - const typeName = properties.args[i] === 'genType' ? inferredType : properties.args[i]; + const typeName = properties.args[i] === 'genType' ? inferredGenType : properties.args[i]; arg = nodeConstructors[typeName](arg); } return arg; }) if (properties.returnType === 'genType') { - properties.returnType = inferredType; + properties.returnType = inferredGenType; } super(isInternal, properties.returnType); for (const arg of args) { From a66ff2c9539c4eb839a9c5e42f055be94e349d30 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Thu, 27 Mar 2025 16:12:35 +0000 Subject: [PATCH 13/49] fix some issues with components and using temp vars before theyre declared --- src/webgl/ShaderGenerator.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index b4b46dd5e3..72e09818a6 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -163,7 +163,7 @@ function shadergenerator(p5, fn) { } this.type = type; this.componentNames = []; - this.swizzleChanged = false; + this.componentsChanged = false; // For tracking recursion depth and creating temporary variables this.isInternal = isInternal; this.usedIn = []; @@ -194,7 +194,7 @@ function shadergenerator(p5, fn) { return value; }, set(newValue) { - this.swizzleChanged = true; + this.componentsChanged = true; value = newValue; } }) @@ -666,21 +666,31 @@ function shadergenerator(p5, fn) { const expectedReturnType = hookTypes.returnType; const toGLSLResults = {}; + const updateComponents = (node) => { + if (node.componentsChanged) { + const components = node.componentNames.map((componentName) => { + return node[componentName] + }); + const replacement = nodeConstructors[node.type](components); + this.context.declarations.push( + ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this.context)};` + ); + } + } + // 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]) => { - if (!isShaderNode(propertyNode)) { - propertyNode = dynamicNode(propertyNode); - } + propertyNode = dynamicNode(propertyNode); toGLSLResults[propertyName] = propertyNode.toGLSLBase(this.context); - }) - } - else { - // We can accept raw numbers or arrays otherwise + updateComponents(propertyNode); + }); + } else { if (!isShaderNode(returnedValue)) { returnedValue = nodeConstructors[expectedReturnType.typeName](returnedValue) } toGLSLResults['notAProperty'] = returnedValue.toGLSLBase(this.context); + updateComponents(returnedValue); } // Build the final GLSL string. From 6cad8d0094bc71c9a7825f80bf3adf225ea7f35f Mon Sep 17 00:00:00 2001 From: 23036879 Date: Thu, 27 Mar 2025 16:13:43 +0000 Subject: [PATCH 14/49] update temp var scoring system --- src/webgl/ShaderGenerator.js | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 72e09818a6..819daec45e 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -213,20 +213,15 @@ function shadergenerator(p5, fn) { } shouldUseTemporaryVariable() { - if (this.type === 'sampler2D') { return false; } - return true; - if (this.isInternal || isVariableNode(this)) { return false; } - if (this.swizzleChanged) { return true; } - if (isFunctionCallNode(this)) { return true; } - if (this.usedIn.length > 1) { return true; } + if (this.isInternal || isVariableNode(this) || this.type === 'sampler2D') { return false; } + // Swizzles must use temporary variables as otherwise they will not be registered + if (this.componentsChanged || hasTemporaryVariable(this)) { return true; } let score = 0; + score += isFunctionCallNode(this) * 2; score += isBinaryExpressionNode(this) * 2; - score += isVectorType(this) * 2; - score += isFloatType(this); - // score += isLiteralNode(this); - score += isVectorNode(this); - score += Math.max(0, this.usedIn.length); - return score >= 3; + score += isVectorType(this) * 3; + score += this.usedIn.length; + return score >= 4; } getTemporaryVariable(context) { @@ -556,6 +551,10 @@ function shadergenerator(p5, fn) { return (isShaderNode(node) && (node.type === 'float')); } + function isFloatNode(node) { + return (node instanceof FloatNode); + } + function isVectorType(node) { return (isShaderNode(node) && (node.type === 'vec2'|| node.type === 'vec3' || node.type === 'vec4')); } @@ -565,7 +564,11 @@ function shadergenerator(p5, fn) { } function isVariableNode(node) { - return (node instanceof VariableNode || node instanceof ComponentNode || typeof(node.temporaryVariable) != 'undefined'); + return (node instanceof VariableNode || node instanceof ComponentNode); + } + + function hasTemporaryVariable(node) { + return (node.temporaryVariable); } function isLiteralNode(node) { @@ -742,7 +745,11 @@ function shadergenerator(p5, fn) { // User function helpers function conformVectorParameters(value, vectorDimensions) { - // Allow arguments as arrays ([0,0,0,0]) or not (0,0,0,0) + // Allow arguments as arrays or otherwise. The following are all equivalent: + // ([0,0,0,0]) (0,0,0,0) (0) ([0]) + if (!Array.isArray(value)) { + value = [value]; + } value = value.flat(); // Populate arguments so uniformVector3(0) becomes [0,0,0] if (value.length === 1) { From decdc053e4e8172c883d5519392791e6a7b88382 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Thu, 27 Mar 2025 16:15:43 +0000 Subject: [PATCH 15/49] fix some issues with constructing vectors. allow vector construction from float nodes, and reinforce that components are nodes on toGLSL() --- src/webgl/ShaderGenerator.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 819daec45e..0f26d764f3 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -277,8 +277,6 @@ function shadergenerator(p5, fn) { return this; } else if (isIntType(this)) { return new FloatNode(this); - } else { - throw new TypeError(`Can't convert from type '${this.type}' to 'float'.`) } } @@ -331,9 +329,10 @@ function shadergenerator(p5, fn) { class VectorNode extends BaseNode { constructor(values, type, isInternal = false) { super(isInternal, type); + values = conformVectorParameters(values, +type.slice(3)); this.componentNames = ['x', 'y', 'z', 'w'].slice(0, values.length); this.componentNames.forEach((component, i) => { - this[component] = new FloatNode(values[i], true); + this[component] = isFloatNode(values[i]) ? values[i] : new FloatNode(values[i], true); }); } @@ -342,6 +341,9 @@ function shadergenerator(p5, fn) { this.componentNames.forEach((component, i) => { const comma = i === this.componentNames.length - 1 ? `` : `, `; + if (!isShaderNode(this[component])) { + this[component] = new FloatNode(this[component]); + } glslArgs += `${this[component].toGLSLBase(context)}${comma}`; }) From ea6b41457ec36b3f1da23616fa1b8acf5337c0c4 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Thu, 27 Mar 2025 16:16:55 +0000 Subject: [PATCH 16/49] differentiate betwen VariableNode and nodes with a temporary variable --- 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 0f26d764f3..100057fd32 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -425,7 +425,7 @@ function shadergenerator(p5, fn) { } toGLSL(context) { let parentName = this.parent.toGLSLBase(context); - if (isBinaryExpressionNode(this.parent) && !this.parent.temporaryVariable) { + if (!isVariableNode(this.parent) || !hasTemporaryVariable(this.parent)) { parentName = `(${parentName})`; } return `${parentName}.${this.componentName}`; From 362cdd10cfd9822971adcb014cc18978d4a28eb0 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Thu, 27 Mar 2025 16:17:10 +0000 Subject: [PATCH 17/49] code formatting --- src/webgl/ShaderGenerator.js | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 100057fd32..02b03a5c3a 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -410,7 +410,6 @@ function shadergenerator(p5, fn) { this.addVectorComponents(); } - toGLSL(context) { return `${this.name}`; } @@ -585,20 +584,13 @@ function shadergenerator(p5, fn) { return (node instanceof VectorNode) } - // 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); } - // 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 @@ -795,7 +787,6 @@ function shadergenerator(p5, fn) { ShaderGenerator.prototype[uniformMethodName] = function(...args) { let [name, ...defaultValue] = args; - if(glslType.startsWith('vec') && !(defaultValue[0] instanceof Function)) { defaultValue = conformVectorParameters(defaultValue, +glslType.slice(3)); this.output.uniforms[`${glslType} ${name}`] = defaultValue; @@ -889,7 +880,8 @@ function shadergenerator(p5, fn) { // 'notEqual': {}, 'reflect': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: false}, 'refract': { args: ['genType', 'genType', 'float'], returnType: 'genType', isp5Function: false}, - // Texture sampling + + ////////// Texture sampling ////////// 'texture': {args: ['sampler2D', 'vec2'], returnType: 'vec4', isp5Function: true}, } @@ -915,15 +907,6 @@ function shadergenerator(p5, fn) { } } }) - - // const oldTexture = p5.prototype.texture; - // p5.prototype.texture = function(...args) { - // if (isShaderNode(args[0])) { - // return new FunctionCallNode('texture', args, 'vec4'); - // } else { - // return oldTexture.apply(this, args); - // } - // } } export default shadergenerator; From 8aafa73a4ca7ca7b5f71be06e7d7da27257255a7 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Thu, 27 Mar 2025 17:51:51 +0000 Subject: [PATCH 18/49] added function overloads. working but will need extending in the future --- src/webgl/ShaderGenerator.js | 65 +++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 02b03a5c3a..6802090217 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -354,19 +354,53 @@ function shadergenerator(p5, fn) { // Function Call Nodes class FunctionCallNode extends BaseNode { constructor(name, args, properties, isInternal = false) { + + // Some functions in GLSL have overloads, therefore we should determine which function signature + // the user is trying to call. + if (Array.isArray(properties)) { + let possibleOverloads = properties.filter(overload => overload.args.length === args.length); + + // We may want a deeper check to ensure that there are no unnecessary casts: + // let indicesOfDifference = {}; + // for (let argIndex = 0; argIndex < args.length; argIndex++) { + // indicesOfDifference[argIndex] = new Set(); + // for (let overload = 0; overload < possibleOverloads.length; overload++) { + // indicesOfDifference[argIndex].add(possibleOverloads[overload].args[argIndex]) + // } + // if (indicesOfDifference[argIndex].size === 1) { delete indicesOfDifference[argIndex]} + // } + + // This works for now: + let selectedOverload = possibleOverloads.find(overload => { + return args.every((arg, i) => { + let expectedArgType = overload.args[i]; + if (expectedArgType != 'genType') { + if (isShaderNode(arg) && arg.type === expectedArgType) { return true; } + if (typeof arg === 'number' && expectedArgType === 'float') { return true; } // could be int later + if (Array.isArray(arg) && expectedArgType === 'genType') { return true; } + } + return true; + }); + }); + properties = selectedOverload; + } if (args.length !== properties.args.length) { throw new Error(`Function ${name} expects ${properties.args.length} arguments, but ${args.length} were provided.`); } + let inferredGenType = args.find((arg, i) => properties.args[i] === 'genType' && isShaderNode(arg) )?.type; + if (!inferredGenType || inferredGenType === 'float') { let arrayArg = args.find(arg => Array.isArray(arg)); inferredGenType = arrayArg ? `vec${arrayArg.length}` : undefined; } + if (!inferredGenType) { inferredGenType = 'float'; } + args = args.map((arg, i) => { if (!isShaderNode(arg)) { const typeName = properties.args[i] === 'genType' ? inferredGenType : properties.args[i]; @@ -374,13 +408,13 @@ function shadergenerator(p5, fn) { } return arg; }) + if (properties.returnType === 'genType') { properties.returnType = inferredGenType; } + super(isInternal, properties.returnType); - for (const arg of args) { - arg.usedIn.push(this); - } + args.forEach(arg => arg.usedIn.push(this)); this.name = name; this.args = args; this.argumentTypes = properties.args; @@ -827,7 +861,10 @@ function shadergenerator(p5, fn) { 'acosh': { args: ['genType'], returnType: 'genType', isp5Function: false}, 'asin': { args: ['genType'], returnType: 'genType', isp5Function: true}, 'asinh': { args: ['genType'], returnType: 'genType', isp5Function: false}, - 'atan': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: false}, + 'atan': [ + { args: ['genType'], returnType: 'genType', isp5Function: false}, + { args: ['genType', 'genType'], returnType: 'genType', isp5Function: false}, + ], 'atanh': { args: ['genType'], returnType: 'genType', isp5Function: false}, 'cos': { args: ['genType'], returnType: 'genType', isp5Function: true}, 'cosh': { args: ['genType'], returnType: 'genType', isp5Function: false}, @@ -855,16 +892,28 @@ function shadergenerator(p5, fn) { // 'isnan': {}, 'log': { args: ['genType'], returnType: 'genType', isp5Function: true}, 'log2': { args: ['genType'], returnType: 'genType', isp5Function: false}, - 'max': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: true}, - 'min': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: true}, - 'mix': { args: ['genType', 'genType', 'genType'], returnType: 'genType', isp5Function: false}, + 'max': [ + { args: ['genType', 'genType'], returnType: 'genType', isp5Function: true}, + { args: ['genType', 'float'], returnType: 'genType', isp5Function: true}, + ], + 'min': [ + { args: ['genType', 'genType'], returnType: 'genType', isp5Function: true}, + { args: ['genType', 'float'], returnType: 'genType', isp5Function: true}, + ], + 'mix': [ + { args: ['genType', 'genType', 'genType'], returnType: 'genType', isp5Function: false}, + { args: ['genType', 'genType', 'float'], returnType: 'genType', isp5Function: false}, + ], // 'mod': {}, // 'modf': {}, 'pow': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: true}, 'round': { args: ['genType'], returnType: 'genType', isp5Function: true}, 'roundEven': { args: ['genType'], returnType: 'genType', isp5Function: false}, // 'sign': {}, - 'smoothstep': { args: ['genType', 'genType', 'genType'], returnType: 'genType', isp5Function: false}, + 'smoothstep': [ + { args: ['genType', 'genType', 'genType'], returnType: 'genType', isp5Function: false}, + { args: ['float', 'float', 'genType'], returnType: 'genType', isp5Function: false}, + ], 'sqrt': { args: ['genType'], returnType: 'genType', isp5Function: true}, 'step': { args: ['genType', 'genType'], returnType: 'genType', isp5Function: false}, 'trunc': { args: ['genType'], returnType: 'genType', isp5Function: false}, From 4f3acf8cd02fd8a47262fe25a2ba805d27383d69 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 28 Mar 2025 13:02:20 +0000 Subject: [PATCH 19/49] function overloads are working, including parameter validation. Can be converted to FES later. --- preview/global/sketch.js | 2 +- src/webgl/ShaderGenerator.js | 129 ++++++++++++++++++++++------------- 2 files changed, 81 insertions(+), 50 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 7d405e96b5..4e9b70261a 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -23,7 +23,7 @@ function fresnelShaderCallback() { let n = normalize(inputs.normal); let v = normalize(0 - inputs.position); let base = 1.0 - dot(n, v); - let fresnel = createVector3(fresnelScale * pow(base, fresnelPower) + fresnelBias); + let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias; let col = mix([mouseIntensity.y, 0, mouseIntensity.x], [1, .5, .7], fresnel); inputs.color = [col.x, col.y, col.z, 1]; return inputs; diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 6802090217..77a1376048 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -309,6 +309,9 @@ function shadergenerator(p5, fn) { class FloatNode extends BaseNode { constructor(x = 0, isInternal = false){ super(isInternal, 'float'); + if (Array.isArray(x)) { + x = x[0]; + } this.x = x; } @@ -353,71 +356,89 @@ function shadergenerator(p5, fn) { // Function Call Nodes class FunctionCallNode extends BaseNode { - constructor(name, args, properties, isInternal = false) { + constructor(name, userArgs, properties, isInternal = false) { + + let functionSignature; + const determineFunctionSignature = (props) => { + let genType; + let similarity = 0; + + const valid = userArgs.every((userArg, i) => { + const userType = getType(userArg); + let expectedArgType = props.args[i]; + + if (expectedArgType === 'genType') { + // We allow conversions from float -> vec if one argument is a vector. + if (genType === undefined || (genType === 'float' && userType.startsWith('vec'))) { + genType = userType + }; + expectedArgType = genType; + } + similarity += (userType === expectedArgType); + return userType === expectedArgType; + }) - // Some functions in GLSL have overloads, therefore we should determine which function signature - // the user is trying to call. - if (Array.isArray(properties)) { - let possibleOverloads = properties.filter(overload => overload.args.length === args.length); - - // We may want a deeper check to ensure that there are no unnecessary casts: - // let indicesOfDifference = {}; - // for (let argIndex = 0; argIndex < args.length; argIndex++) { - // indicesOfDifference[argIndex] = new Set(); - // for (let overload = 0; overload < possibleOverloads.length; overload++) { - // indicesOfDifference[argIndex].add(possibleOverloads[overload].args[argIndex]) - // } - // if (indicesOfDifference[argIndex].size === 1) { delete indicesOfDifference[argIndex]} - // } - - // This works for now: - let selectedOverload = possibleOverloads.find(overload => { - return args.every((arg, i) => { - let expectedArgType = overload.args[i]; - if (expectedArgType != 'genType') { - if (isShaderNode(arg) && arg.type === expectedArgType) { return true; } - if (typeof arg === 'number' && expectedArgType === 'float') { return true; } // could be int later - if (Array.isArray(arg) && expectedArgType === 'genType') { return true; } - } - return true; - }); - }); - properties = selectedOverload; - } - if (args.length !== properties.args.length) { - throw new Error(`Function ${name} expects ${properties.args.length} arguments, but ${args.length} were provided.`); + return { ...props, valid, similarity, genType } } - let inferredGenType = args.find((arg, i) => - properties.args[i] === 'genType' && isShaderNode(arg) - )?.type; + if (Array.isArray(properties)) { + // Check if the right amount of + let possibleOverloads = properties.filter(o => o.args.length === userArgs.length); + if (possibleOverloads.length === 0) { + const argsLengthSet = new Set(); + const argsLengthArr = []; + properties.forEach((p) => argsLengthSet.add(p.args.length)); + argsLengthSet.forEach((len) => argsLengthArr.push(`${len}`)); + const argsLengthStr = argsLengthArr.join(' or '); + throw new Error(`Function '${name}' has ${properties.length} variants which expect ${argsLengthStr} arguments, but ${userArgs.length} arguments were provided.`); + } + + const findBestOverload = function (best, current) { + current = determineFunctionSignature(current); + if (!current.valid) { return best; } + if (!best || current.similarity > best.similarity) { + best = current; + } + return best; + } + functionSignature = possibleOverloads.reduce(findBestOverload, null); + } else { + functionSignature = determineFunctionSignature(properties); + } - if (!inferredGenType || inferredGenType === 'float') { - let arrayArg = args.find(arg => Array.isArray(arg)); - inferredGenType = arrayArg ? `vec${arrayArg.length}` : undefined; + if (!functionSignature || !functionSignature.valid) { + const argsStrJoin = (args) => `(${args.map((arg) => arg).join(', ')})`; + const expectedArgsString = Array.isArray(properties) ? + properties.map(prop => argsStrJoin(prop.args)).join(' or ') + : argsStrJoin(properties.args); + const providedArgsString = argsStrJoin(userArgs.map((a)=>getType(a))); + throw new Error(`Function '${name}' was called with wrong arguments. Most likely, you provided mixed lengths vectors as arguments.\nExpected argument types: ${expectedArgsString}\nProvided argument types: ${providedArgsString}\nAll of the arguments with expected type 'genType' should have a matching type. If one of those is different, try to find where it was created. + `); } - if (!inferredGenType) { - inferredGenType = 'float'; + if (userArgs.length !== functionSignature.args.length) { + throw new Error(`Function '${name}' expects ${functionSignature.args.length} arguments, but ${userArgs.length} were provided.`); } - args = args.map((arg, i) => { + userArgs = userArgs.map((arg, i) => { if (!isShaderNode(arg)) { - const typeName = properties.args[i] === 'genType' ? inferredGenType : properties.args[i]; + const typeName = functionSignature.args[i] === 'genType' ? functionSignature.genType : functionSignature.args[i]; arg = nodeConstructors[typeName](arg); + } else if (isFloatType(arg) && functionSignature.args[i] ==='genType' && functionSignature.genType !== 'float') { + arg = nodeConstructors[functionSignature.genType](arg); } return arg; }) - if (properties.returnType === 'genType') { - properties.returnType = inferredGenType; + if (functionSignature.returnType === 'genType') { + functionSignature.returnType = functionSignature.genType; } - super(isInternal, properties.returnType); - args.forEach(arg => arg.usedIn.push(this)); + super(isInternal, functionSignature.returnType); + userArgs.forEach(arg => arg.usedIn.push(this)); this.name = name; - this.args = args; - this.argumentTypes = properties.args; + this.args = userArgs; + this.argumentTypes = functionSignature.args; this.addVectorComponents(); } @@ -574,6 +595,15 @@ function shadergenerator(p5, fn) { } // Node Helper functions + function getType(node) { + if (isShaderNode(node)) { return node.type; } + else if (Array.isArray(node) && node.length > 1) { return `vec${node.length}`; } + else if (typeof node === 'number' || (Array.isArray(node) && node.length === 1)) { + return 'float'; + } + return undefined; + } + function isShaderNode(node) { return (node instanceof BaseNode); } @@ -935,7 +965,8 @@ function shadergenerator(p5, fn) { } Object.entries(builtInGLSLFunctions).forEach(([functionName, properties]) => { - if (properties.isp5Function) { + const isp5Function = Array.isArray(properties) ? properties[0].isp5Function : properties.isp5Function; + if (isp5Function) { const originalFn = fn[functionName]; fn[functionName] = function (...args) { if (GLOBAL_SHADER?.isGenerating) { From ec165aba24f951de1c81b91ba4fcda6af2754376 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 28 Mar 2025 13:08:14 +0000 Subject: [PATCH 20/49] convert float -> vec --- src/webgl/ShaderGenerator.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 77a1376048..c128e09a6f 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -375,7 +375,7 @@ function shadergenerator(p5, fn) { expectedArgType = genType; } similarity += (userType === expectedArgType); - return userType === expectedArgType; + return userType === expectedArgType || (userType === 'float' && expectedArgType.startsWith('vec')); }) return { ...props, valid, similarity, genType } @@ -392,7 +392,6 @@ function shadergenerator(p5, fn) { const argsLengthStr = argsLengthArr.join(' or '); throw new Error(`Function '${name}' has ${properties.length} variants which expect ${argsLengthStr} arguments, but ${userArgs.length} arguments were provided.`); } - const findBestOverload = function (best, current) { current = determineFunctionSignature(current); if (!current.valid) { return best; } From c0729eb91e481adc62d222275c2f7279f720c2da Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 28 Mar 2025 14:45:15 +0000 Subject: [PATCH 21/49] add support for unary operators --- src/webgl/ShaderGenerator.js | 75 ++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index c128e09a6f..184f7e0479 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -43,7 +43,7 @@ function shadergenerator(p5, fn) { } } - // AST Transpiler Callbacks and their helpers + // AST Transpiler Callbacks and helper functions function replaceBinaryOperator(codeSource) { switch (codeSource) { case '+': return 'add'; @@ -54,6 +54,28 @@ function shadergenerator(p5, fn) { } } + function replaceUnaryExpression(node) { + const sign = { + type: 'Literal', + value: node.operator, + } + const replacement ={ + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'unaryNode' + }, + arguments: [node.argument, sign] + } + return replacement; + } + + const isUniform = (ancestor) => { + return ancestor.type === 'CallExpression' + && ancestor.callee?.type === 'Identifier' + && ancestor.callee?.name.startsWith('uniform'); + } + const ASTCallbacks = { VariableDeclarator(node, _state, _ancestors) { if (node.init.callee && node.init.callee.name?.startsWith('uniform')) { @@ -63,10 +85,16 @@ function shadergenerator(p5, fn) { } node.init.arguments.unshift(uniformNameLiteral); } + if (node.init.type === 'UnaryExpression') { + node.init = replaceUnaryExpression(node.init) + } }, // The callbacks for AssignmentExpression and BinaryExpression handle // operator overloading including +=, *= assignment expressions AssignmentExpression(node, _state, _ancestors) { + if (node.right.type === 'UnaryExpression') { + node.right = replaceUnaryExpression(node.right) + } if (node.operator !== '=') { const methodName = replaceBinaryOperator(node.operator.replace('=','')); const rightReplacementNode = { @@ -96,17 +124,10 @@ function shadergenerator(p5, fn) { } } }, - BinaryExpression(node, _state, ancestors) { + BinaryExpression(node, _state, _ancestors) { // Don't convert uniform default values to node methods, as // they should be evaluated at runtime, not compiled. - const isUniform = (ancestor) => { - return ancestor.type === 'CallExpression' - && ancestor.callee?.type === 'Identifier' - && ancestor.callee?.name.startsWith('uniform'); - } - if (ancestors.some(isUniform)) { - return; - } + if (_ancestors.some(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", "Identifier"]; @@ -121,7 +142,9 @@ function shadergenerator(p5, fn) { } node.left = leftReplacementNode; } - + [node.left, node.right] = [node.left, node.right].map((op) => + op.type === 'UnaryExpression' ? replaceUnaryExpression(op) : op + ) // Replace the binary operator with a call expression // in other words a call to BaseNode.mult(), .div() etc. node.type = 'CallExpression'; @@ -135,6 +158,14 @@ function shadergenerator(p5, fn) { }; node.arguments = [node.right]; }, + ArrayExpression(node, _state, _ancestors) { + if (_ancestors.some(isUniform)) { return; } + node.elements = node.elements.map(el => el.type === 'UnaryExpression' ? replaceUnaryExpression(el) : el); + }, + CallExpression(node, _state, _ancestors) { + if (_ancestors.some(isUniform)) { return; } + node.arguments = node.arguments.map(a => a.type === 'UnaryExpression' ? replaceUnaryExpression(a) : a); + } } // This unfinished function lets you do 1 * 10 @@ -151,6 +182,11 @@ function shadergenerator(p5, fn) { } } + fn.unaryNode = function(input, sign) { + input = dynamicNode(input); + return new UnaryNode(input, sign); + } + // Javascript Node API. // These classes are for expressing GLSL functions in Javascript without // needing to transpile the user's code. @@ -478,7 +514,7 @@ function shadergenerator(p5, fn) { } toGLSL(context) { let parentName = this.parent.toGLSLBase(context); - if (!isVariableNode(this.parent) || !hasTemporaryVariable(this.parent)) { + if (!isVariableNode(this.parent) && !hasTemporaryVariable(this.parent)) { parentName = `(${parentName})`; } return `${parentName}.${this.componentName}`; @@ -552,6 +588,21 @@ function shadergenerator(p5, fn) { } } + class UnaryNode extends BaseNode { + constructor(a, operator, isInternal = false) { + super(isInternal, a.type) + this.a = a; + this.operator = operator; + } + toGLSL(context) { + let mainStr = this.a.toGLSLBase(context); + if (!isVariableNode(this.a) && !hasTemporaryVariable(this.a)) { + mainStr = `(${mainStr})` + } + return `${this.operator}${mainStr}` + } + } + // TODO: finish If Node class ConditionalNode extends BaseNode { constructor(value) { From fd152a17ad1f9eac370e1eebe319415ac103f379 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 28 Mar 2025 15:29:25 +0000 Subject: [PATCH 22/49] simplify unary overload --- src/webgl/ShaderGenerator.js | 59 +++++++++++++++--------------------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 184f7e0479..97d3e8c651 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -54,29 +54,28 @@ function shadergenerator(p5, fn) { } } - function replaceUnaryExpression(node) { - const sign = { - type: 'Literal', - value: node.operator, - } - const replacement ={ - type: 'CallExpression', - callee: { - type: 'Identifier', - name: 'unaryNode' - }, - arguments: [node.argument, sign] - } - return replacement; - } - - const isUniform = (ancestor) => { + function ancestorIsUniform(ancestor) { return ancestor.type === 'CallExpression' && ancestor.callee?.type === 'Identifier' && ancestor.callee?.name.startsWith('uniform'); } const ASTCallbacks = { + UnaryExpression(node, _state, _ancestors) { + if (_ancestors.some(ancestorIsUniform)) { return; } + const signNode = { + type: 'Literal', + value: node.operator, + } + node.type = 'CallExpression' + node.callee = { + type: 'Identifier', + name: 'unaryNode', + }; + node.arguments = [node.argument, signNode] + delete node.argument; + delete node.operator; + }, VariableDeclarator(node, _state, _ancestors) { if (node.init.callee && node.init.callee.name?.startsWith('uniform')) { const uniformNameLiteral = { @@ -85,16 +84,10 @@ function shadergenerator(p5, fn) { } node.init.arguments.unshift(uniformNameLiteral); } - if (node.init.type === 'UnaryExpression') { - node.init = replaceUnaryExpression(node.init) - } }, // The callbacks for AssignmentExpression and BinaryExpression handle // operator overloading including +=, *= assignment expressions AssignmentExpression(node, _state, _ancestors) { - if (node.right.type === 'UnaryExpression') { - node.right = replaceUnaryExpression(node.right) - } if (node.operator !== '=') { const methodName = replaceBinaryOperator(node.operator.replace('=','')); const rightReplacementNode = { @@ -127,7 +120,7 @@ function shadergenerator(p5, fn) { BinaryExpression(node, _state, _ancestors) { // Don't convert uniform default values to node methods, as // they should be evaluated at runtime, not compiled. - if (_ancestors.some(isUniform)) { return; } + if (_ancestors.some(ancestorIsUniform)) { return; } // If the left hand side of an expression is one of these types, // we should construct a node from it. const unsafeTypes = ["Literal", "ArrayExpression", "Identifier"]; @@ -142,9 +135,6 @@ function shadergenerator(p5, fn) { } node.left = leftReplacementNode; } - [node.left, node.right] = [node.left, node.right].map((op) => - op.type === 'UnaryExpression' ? replaceUnaryExpression(op) : op - ) // Replace the binary operator with a call expression // in other words a call to BaseNode.mult(), .div() etc. node.type = 'CallExpression'; @@ -158,14 +148,6 @@ function shadergenerator(p5, fn) { }; node.arguments = [node.right]; }, - ArrayExpression(node, _state, _ancestors) { - if (_ancestors.some(isUniform)) { return; } - node.elements = node.elements.map(el => el.type === 'UnaryExpression' ? replaceUnaryExpression(el) : el); - }, - CallExpression(node, _state, _ancestors) { - if (_ancestors.some(isUniform)) { return; } - node.arguments = node.arguments.map(a => a.type === 'UnaryExpression' ? replaceUnaryExpression(a) : a); - } } // This unfinished function lets you do 1 * 10 @@ -917,9 +899,16 @@ function shadergenerator(p5, fn) { return GLOBAL_SHADER[uniformMethodName](...args); }; + // We don't need a createTexture method. if (glslType === 'sampler2D') { continue; } + const varyingMethodName = `varying${typeIdentifier}`; + ShaderGenerator.prototype[varyingMethodName] = function(...args) { + let [name, ...value] = args; + // if () + } + // Generate the create*() Methods for creating variables in shaders const createMethodName = `create${typeIdentifier}`; fn[createMethodName] = function (...value) { From e1e437c5e36ed020655a95b8a66189a3412709dc Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 28 Mar 2025 15:30:05 +0000 Subject: [PATCH 23/49] utilise new unary operator --- preview/global/sketch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 4e9b70261a..0dde4abea8 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -21,7 +21,7 @@ function fresnelShaderCallback() { getCameraInputs((inputs) => { let n = normalize(inputs.normal); - let v = normalize(0 - inputs.position); + let v = normalize(-inputs.position); let base = 1.0 - dot(n, v); let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias; let col = mix([mouseIntensity.y, 0, mouseIntensity.x], [1, .5, .7], fresnel); From 40fd5a33926d43b7488112af6521d765c113256a Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 28 Mar 2025 17:41:05 +0000 Subject: [PATCH 24/49] add in/ out variables --- src/webgl/ShaderGenerator.js | 117 +++++++++++++++++++++++++++-------- 1 file changed, 90 insertions(+), 27 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 97d3e8c651..c1d8aa27ca 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -27,8 +27,8 @@ function shadergenerator(p5, fn) { const transpiledSource = escodegen.generate(ast); generatorFunction = new Function( transpiledSource.slice( - transpiledSource.indexOf("{") + 1, - transpiledSource.lastIndexOf("}") + transpiledSource.indexOf('{') + 1, + transpiledSource.lastIndexOf('}') ) ); } else { @@ -84,6 +84,14 @@ function shadergenerator(p5, fn) { } node.init.arguments.unshift(uniformNameLiteral); } + if (node.init.callee && node.init.callee.name?.startsWith('varying')) { + const varyingNameLiteral = { + type: 'Literal', + value: node.id.name + } + node.init.arguments.unshift(varyingNameLiteral); + _state[node.id.name] = varyingNameLiteral; + } }, // The callbacks for AssignmentExpression and BinaryExpression handle // operator overloading including +=, *= assignment expressions @@ -93,10 +101,10 @@ function shadergenerator(p5, fn) { const rightReplacementNode = { type: 'CallExpression', callee: { - type: "MemberExpression", + type: 'MemberExpression', object: node.left, property: { - type: "Identifier", + type: 'Identifier', name: methodName, }, computed: false, @@ -110,12 +118,30 @@ function shadergenerator(p5, fn) { node.right = { type: 'CallExpression', callee: { - type: "Identifier", - name: "dynamicNode", + type: 'Identifier', + name: 'dynamicNode', }, arguments: [node.right] } } + if (_state[node.left.name]) { + node.type = 'ExpressionStatement'; + node.expression = { + type: 'CallExpression', + callee: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: node.left.name + }, + property: { + type: 'Identifier', + name: 'bridge', + } + }, + arguments: [node.right], + } + } }, BinaryExpression(node, _state, _ancestors) { // Don't convert uniform default values to node methods, as @@ -123,13 +149,13 @@ function shadergenerator(p5, fn) { if (_ancestors.some(ancestorIsUniform)) { return; } // If the left hand side of an expression is one of these types, // we should construct a node from it. - const unsafeTypes = ["Literal", "ArrayExpression", "Identifier"]; + const unsafeTypes = ['Literal', 'ArrayExpression', 'Identifier']; if (unsafeTypes.includes(node.left.type)) { const leftReplacementNode = { - type: "CallExpression", + type: 'CallExpression', callee: { - type: "Identifier", - name: "dynamicNode", + type: 'Identifier', + name: 'dynamicNode', }, arguments: [node.left] } @@ -139,10 +165,10 @@ function shadergenerator(p5, fn) { // in other words a call to BaseNode.mult(), .div() etc. node.type = 'CallExpression'; node.callee = { - type: "MemberExpression", + type: 'MemberExpression', object: node.left, property: { - type: "Identifier", + type: 'Identifier', name: replaceBinaryOperator(node.operator), }, }; @@ -177,7 +203,7 @@ function shadergenerator(p5, fn) { class BaseNode { constructor(isInternal, type) { if (new.target === BaseNode) { - throw new TypeError("Cannot construct BaseNode instances directly. This is an abstract class."); + throw new TypeError('Cannot construct BaseNode instances directly. This is an abstract class.'); } this.type = type; this.componentNames = []; @@ -189,9 +215,9 @@ function shadergenerator(p5, fn) { // Stack Capture is used to get the original line of user code for Debug purposes if (GLOBAL_SHADER.srcLocations === true && isInternal === false) { try { - throw new Error("StackCapture"); + throw new Error('StackCapture'); } catch (e) { - const lines = e.stack.split("\n"); + const lines = e.stack.split('\n'); let userSketchLineIndex = 5; if (isBinaryExpressionNode(this)) { userSketchLineIndex--; }; this.srcLine = lines[userSketchLineIndex].trim(); @@ -205,7 +231,6 @@ function shadergenerator(p5, fn) { 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() { @@ -245,11 +270,11 @@ function shadergenerator(p5, fn) { getTemporaryVariable(context) { if (!this.temporaryVariable) { this.temporaryVariable = `temp_${context.getNextID()}`; - let line = ""; + let line = ''; if (this.srcLine) { line += `\n// From ${this.srcLine}\n`; } - line += " " + this.type + " " + this.temporaryVariable + " = " + this.toGLSL(context) + ";"; + line += ' ' + this.type + ' ' + this.temporaryVariable + ' = ' + this.toGLSL(context) + ';'; context.declarations.push(line); } return this.temporaryVariable; @@ -299,7 +324,7 @@ function shadergenerator(p5, fn) { } toGLSL(context){ - throw new TypeError("Not supposed to call this function on BaseNode, which is an abstract class."); + throw new TypeError('Not supposed to call this function on BaseNode, which is an abstract class.'); } } @@ -315,7 +340,7 @@ function shadergenerator(p5, fn) { let code = this.x.toGLSLBase(context); return isIntType(this.x.type) ? code : `int(${code})`; } - else if (typeof this.x === "number") { + else if (typeof this.x === 'number') { return `${Math.floor(this.x)}`; } else { @@ -338,7 +363,7 @@ function shadergenerator(p5, fn) { let code = this.x.toGLSLBase(context); return isFloatType(this.x) ? code : `float(${code})`; } - else if (typeof this.x === "number") { + else if (typeof this.x === 'number') { return `${this.x.toFixed(4)}`; } else { @@ -503,6 +528,20 @@ function shadergenerator(p5, fn) { } } + class VaryingNode extends VariableNode { + constructor(name, type, isInternal = false) { + super(name, type, isInternal); + this.isSet = false; + this.value = null; + } + bridge(value) { + if (!isShaderNode(value) || this.type.startsWith('vec') && getType(value) === 'float') { + value = nodeConstructors[this.type](value) + } + GLOBAL_SHADER.registerVarying(this, value); + } + } + // Binary Operator Nodes class BinaryExpressionNode extends BaseNode { constructor(a, b, operator, isInternal = false) { @@ -534,7 +573,7 @@ function shadergenerator(p5, fn) { return 'float'; } else { - throw new Error("Incompatible types for binary operator"); + throw new Error('Incompatible types for binary operator'); } } @@ -698,6 +737,8 @@ function shadergenerator(p5, fn) { this.cleanup = () => {}; this.generateHookOverrides(originalShader); this.output = { + vertexDeclarations: new Set(), + fragmentDeclarations: new Set(), uniforms: {}, } this.uniformNodes = []; @@ -712,6 +753,8 @@ function shadergenerator(p5, fn) { this.isGenerating = true; this.userCallback(); + this.output.vertexDeclarations = [...this.output.vertexDeclarations].join('\n'); + this.output.fragmentDeclarations = [...this.output.fragmentDeclarations].join('\n'); this.isGenerating = false; this.cleanup(); @@ -771,6 +814,7 @@ function shadergenerator(p5, fn) { } } + // If the expected return type is a struct we need to evaluate each of its properties if (!isGLSLNativeType(expectedReturnType.typeName)) { Object.entries(returnedValue).forEach(([propertyName, propertyNode]) => { @@ -781,11 +825,22 @@ function shadergenerator(p5, fn) { } else { if (!isShaderNode(returnedValue)) { returnedValue = nodeConstructors[expectedReturnType.typeName](returnedValue) + } else if (isFloatType(returnedValue) && expectedReturnType.typeName.startsWith('vec')) { + returnedValue = nodeConstructors[expectedReturnType.typeName](returnedValue); } toGLSLResults['notAProperty'] = returnedValue.toGLSLBase(this.context); updateComponents(returnedValue); } + this.context.varyings.forEach(varying => { + let { node, value } = varying; + this.context.declarations.push( + ` ${node.name} = ${value.toGLSLBase(this.context)};` + ); + this.output.vertexDeclarations.add(`out ${node.type} ${node.name};`); + this.output.fragmentDeclarations.add(`in ${node.type} ${node.name};`); + }) + // Build the final GLSL string. // The order of this code is a bit confusing, we need to call toGLSLBase let codeLines = [ @@ -803,7 +858,6 @@ function shadergenerator(p5, fn) { this.output[hookName] = codeLines.join('\n'); this.resetGLSLContext(); } - windowOverrides[hookTypes.name] = window[hookTypes.name]; // Expose the Functions to global scope for users to use @@ -811,6 +865,7 @@ function shadergenerator(p5, fn) { GLOBAL_SHADER[hookTypes.name](userOverride); }; }); + this.cleanup = () => { for (const key in windowOverrides) { @@ -819,6 +874,10 @@ function shadergenerator(p5, fn) { }; } + registerVarying(node, value) { + this.context.varyings.push({ node, value }) + } + resetGLSLContext() { this.uniformNodes.forEach((node) => { node.usedIn = []; @@ -826,8 +885,9 @@ function shadergenerator(p5, fn) { }); this.context = { id: 0, - getNextID: function() { return this.id++ }, + getNextID() { return this.id++ }, declarations: [], + varyings: [], } this.uniformNodes = []; } @@ -904,11 +964,14 @@ function shadergenerator(p5, fn) { if (glslType === 'sampler2D') { continue; } const varyingMethodName = `varying${typeIdentifier}`; - ShaderGenerator.prototype[varyingMethodName] = function(...args) { - let [name, ...value] = args; - // if () + ShaderGenerator.prototype[varyingMethodName] = function(name) { + return new VaryingNode(name, glslType, false); } + fn[varyingMethodName] = function (name) { + return GLOBAL_SHADER[varyingMethodName](name); + }; + // Generate the create*() Methods for creating variables in shaders const createMethodName = `create${typeIdentifier}`; fn[createMethodName] = function (...value) { From 9dfd4543ecb05b798e197f47449de617f0d1e97c Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 28 Mar 2025 17:41:29 +0000 Subject: [PATCH 25/49] use varying in sketch --- preview/global/sketch.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 0dde4abea8..55f2c985e6 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -18,16 +18,23 @@ function fresnelShaderCallback() { const fresnelBias = uniformFloat(-0.1); const fresnelScale = uniformFloat(2); const mouseIntensity = uniformVector2(()=>[map(mouseX, 0, width, 0, 0.15), map(mouseY, 0, height, 0, 0.15)]); + let val = varyingFloat('val'); + let colr = varyingVector4('colr'); getCameraInputs((inputs) => { let n = normalize(inputs.normal); let v = normalize(-inputs.position); let base = 1.0 - dot(n, v); + val = mouseIntensity.y; let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias; let col = mix([mouseIntensity.y, 0, mouseIntensity.x], [1, .5, .7], fresnel); inputs.color = [col.x, col.y, col.z, 1]; return inputs; }); + + getFinalColor((col) => { + return col * [val,col.y,col.y,1]; + }) } function starShaderCallback() { From c3eebca972a47a25ac2589444d2607e2306b0c83 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 28 Mar 2025 19:47:45 +0000 Subject: [PATCH 26/49] swizzles semi working --- src/webgl/ShaderGenerator.js | 86 ++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index c1d8aa27ca..47aa8fa9f9 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -186,7 +186,7 @@ function shadergenerator(p5, fn) { return new FloatNode(input); } else if (Array.isArray(input)) { - return new VectorNode(input, `vec${input.length}`); + return nodeConstructors[`vec${input.length}`](input); } } @@ -242,9 +242,18 @@ function shadergenerator(p5, fn) { } }) } + this.addSwizzleTrap(); } } + addSwizzleTrap() { + const possibleSwizzles = [ + ['x', 'y', 'z', 'w'], + ['r', 'g', 'b', 'a'], + ['s', 't', 'p', 'q'] + ].map(s => s.slice(0, this.componentNames.length)); + } + // 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()) { @@ -308,10 +317,10 @@ function shadergenerator(p5, fn) { return new FloatNode(other); } else if(Array.isArray(other)) { - return new VectorNode(other, `vec${other.length}`) + return nodeConstructors[`vec${other.length}`](other); } else { - return new this.constructor(other); + return nodeConstructors[this.type](other); } } @@ -466,7 +475,7 @@ function shadergenerator(p5, fn) { if (!isShaderNode(arg)) { const typeName = functionSignature.args[i] === 'genType' ? functionSignature.genType : functionSignature.args[i]; arg = nodeConstructors[typeName](arg); - } else if (isFloatType(arg) && functionSignature.args[i] ==='genType' && functionSignature.genType !== 'float') { + } else if (isFloatType(arg) && functionSignature.args[i] === 'genType' && functionSignature.genType !== 'float') { arg = nodeConstructors[functionSignature.genType](arg); } return arg; @@ -786,12 +795,12 @@ function shadergenerator(p5, fn) { if (!isGLSLNativeType(parameter.type.typeName)) { const structArg = {}; parameter.type.properties.forEach((property) => { - structArg[property.name] = new VariableNode(`${parameter.name}.${property.name}`, property.type.typeName, true); + structArg[property.name] = variableConstructor(`${parameter.name}.${property.name}`, property.type.typeName, true); }); argNodes.push(structArg); } else { argNodes.push( - new VariableNode(parameter.name, parameter.type.typeName, true) + variableConstructor(parameter.name, parameter.type.typeName, true) ); } const qualifiers = parameter.type.qualifiers.length > 0 ? parameter.type.qualifiers.join(' ') : ''; @@ -910,11 +919,42 @@ function shadergenerator(p5, fn) { // User functions fn.instanceID = function() { - return new VariableNode('gl_InstanceID', 'int'); + return variableConstructor('gl_InstanceID', 'int'); } fn.discard = function() { - return new VariableNode('discard', 'keyword'); + return variableConstructor('discard', 'keyword'); + } + + function swizzleTrap(size) { + const swizzleSets = [ + ['x', 'y', 'z', 'w'], + ['r', 'g', 'b', 'a'], + ['s', 't', 'p', 'q'] + ].map(s => s.slice(0, size)); + return { + get(object, property, receiver) { + if(object[property]) { + return Reflect.get(...arguments); + } else { + for (const group of swizzleSets) { + if ([...property].every(char => group.includes(char)) && property.length <= size) { + const components = [...property].map(char => { + const index = group.indexOf(char); + const mappedChar = swizzleSets[0][index]; + return object[mappedChar]; + }); + const type = object['type']; + const node = nodeConstructors[type](components); + return node; + } + } + } + }, + set(object, property, receiver) { + return Reflect.set(...arguments); + } + } } // Generating uniformFloat, uniformVec, createFloat, etc functions @@ -928,12 +968,30 @@ function shadergenerator(p5, fn) { sampler2D: 'Texture', }; + function variableConstructor(name, type, isInternal) { + const node = new VariableNode(name, type, isInternal); + if (node.type.startsWith('vec')) { + return new Proxy(node, swizzleTrap(+node.type.slice(3))); + } else { + return node; + } + } + + function fnNodeConstructor(name, userArgs, properties, isInternal) { + const node = new FunctionCallNode(name, userArgs, properties, isInternal) + if (node.type.startsWith('vec')) { + return new Proxy(node, swizzleTrap(+node.type.slice(3))) + } else { + return node; + } + } + const nodeConstructors = { int: (value) => new IntNode(value), float: (value) => new FloatNode(value), - vec2: (value) => new VectorNode(value, 'vec2'), - vec3: (value) => new VectorNode(value, 'vec3'), - vec4: (value) => new VectorNode(value, 'vec4'), + vec2: (value) => new Proxy(new VectorNode(value, 'vec2'), swizzleTrap(2)), + vec3: (value) => new Proxy(new VectorNode(value, 'vec3'), swizzleTrap(3)), + vec4: (value) => new Proxy(new VectorNode(value, 'vec4'), swizzleTrap(4)), }; for (const glslType in GLSLTypesToIdentifiers) { @@ -950,7 +1008,7 @@ function shadergenerator(p5, fn) { else { this.output.uniforms[`${glslType} ${name}`] = defaultValue[0]; } - const uniform = new VariableNode(name, glslType, false); + const uniform = variableConstructor(name, glslType, false); this.uniformNodes.push(uniform); return uniform; }; @@ -1072,7 +1130,7 @@ function shadergenerator(p5, fn) { const originalFn = fn[functionName]; fn[functionName] = function (...args) { if (GLOBAL_SHADER?.isGenerating) { - return new FunctionCallNode(functionName, args, properties) + return fnNodeConstructor(functionName, args, properties) } else { return originalFn.apply(this, args); } @@ -1080,7 +1138,7 @@ function shadergenerator(p5, fn) { } else { fn[functionName] = function (...args) { if (GLOBAL_SHADER?.isGenerating) { - return new FunctionCallNode(functionName, args, properties); + return new fnNodeConstructor(functionName, args, properties); } else { p5._friendlyError( `It looks like you've called ${functionName} outside of a shader's modify() function.` From 0aafb752a81f619c16a87d533c3cbe4b5602ee00 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 28 Mar 2025 19:47:57 +0000 Subject: [PATCH 27/49] testing swizzles --- preview/global/sketch.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 55f2c985e6..eebc0b1dd0 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -25,16 +25,16 @@ function fresnelShaderCallback() { let n = normalize(inputs.normal); let v = normalize(-inputs.position); let base = 1.0 - dot(n, v); - val = mouseIntensity.y; + val = mouseIntensity.x; let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias; let col = mix([mouseIntensity.y, 0, mouseIntensity.x], [1, .5, .7], fresnel); inputs.color = [col.x, col.y, col.z, 1]; return inputs; }); - getFinalColor((col) => { - return col * [val,col.y,col.y,1]; - }) + // getFinalColor((col) => { + // return col * [val,col.y,col.y,1]; + // }) } function starShaderCallback() { From 2f8adb15e054574ae2ae7eaf74e450d72f4e4bc5 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 28 Mar 2025 19:59:45 +0000 Subject: [PATCH 28/49] swizzles work to reconstruct kinda --- src/webgl/ShaderGenerator.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 47aa8fa9f9..1976eb5fea 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -242,17 +242,9 @@ function shadergenerator(p5, fn) { } }) } - this.addSwizzleTrap(); } } - addSwizzleTrap() { - const possibleSwizzles = [ - ['x', 'y', 'z', 'w'], - ['r', 'g', 'b', 'a'], - ['s', 't', 'p', 'q'] - ].map(s => s.slice(0, this.componentNames.length)); - } // The base node implements a version of toGLSL which determines whether the generated code should be stored in a temporary variable. toGLSLBase(context){ @@ -387,6 +379,10 @@ function shadergenerator(p5, fn) { values = conformVectorParameters(values, +type.slice(3)); this.componentNames = ['x', 'y', 'z', 'w'].slice(0, values.length); this.componentNames.forEach((component, i) => { + let value = values[i]; + if (isShaderNode(value)) { + value = new FloatNode() + } this[component] = isFloatNode(values[i]) ? values[i] : new FloatNode(values[i], true); }); } @@ -938,13 +934,14 @@ function shadergenerator(p5, fn) { return Reflect.get(...arguments); } else { for (const group of swizzleSets) { - if ([...property].every(char => group.includes(char)) && property.length <= size) { + if ([...property].every(char => group.includes(char))) { + if (property.length === 1) { return object[swizzleSets[0][0]] } const components = [...property].map(char => { const index = group.indexOf(char); const mappedChar = swizzleSets[0][index]; return object[mappedChar]; }); - const type = object['type']; + const type = `vec${property.length}`; const node = nodeConstructors[type](components); return node; } From f49f6aa0856a840e7784c4d0864960a06d4bbe32 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 28 Mar 2025 19:59:56 +0000 Subject: [PATCH 29/49] testing swizzles --- preview/global/sketch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index eebc0b1dd0..11d3ba9310 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -28,7 +28,7 @@ function fresnelShaderCallback() { val = mouseIntensity.x; let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias; let col = mix([mouseIntensity.y, 0, mouseIntensity.x], [1, .5, .7], fresnel); - inputs.color = [col.x, col.y, col.z, 1]; + inputs.color = col.xxxx; return inputs; }); From 7144dcdacb1d96cc708c012e1c7a7e9e1005e726 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Sat, 29 Mar 2025 12:01:32 +0000 Subject: [PATCH 30/49] Vector swizzling supported --- src/webgl/ShaderGenerator.js | 157 +++++++++++++++++++++-------------- 1 file changed, 93 insertions(+), 64 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 1976eb5fea..af5cd07905 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -176,30 +176,7 @@ function shadergenerator(p5, fn) { }, } - // This unfinished function lets you do 1 * 10 - // and turns it into float.mult(10) - fn.dynamicNode = function (input) { - if (isShaderNode(input)) { - return input; - } - else if (typeof input === 'number') { - return new FloatNode(input); - } - else if (Array.isArray(input)) { - return nodeConstructors[`vec${input.length}`](input); - } - } - - fn.unaryNode = function(input, sign) { - input = dynamicNode(input); - return new UnaryNode(input, sign); - } - // 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) { @@ -282,11 +259,11 @@ function shadergenerator(p5, fn) { }; // Binary Operators - add(other) { return new BinaryExpressionNode(this, this.enforceType(other), '+'); } - sub(other) { return new BinaryExpressionNode(this, this.enforceType(other), '-'); } - mult(other) { return new BinaryExpressionNode(this, this.enforceType(other), '*'); } - div(other) { return new BinaryExpressionNode(this, this.enforceType(other), '/'); } - mod(other) { return new ModulusNode(this, this.enforceType(other)); } + add(other) { return binaryExpressionNodeConstructor(this, this.enforceType(other), '+'); } + sub(other) { return binaryExpressionNodeConstructor(this, this.enforceType(other), '-'); } + mult(other) { return binaryExpressionNodeConstructor(this, this.enforceType(other), '*'); } + div(other) { return binaryExpressionNodeConstructor(this, this.enforceType(other), '/'); } + mod(other) { return binaryExpressionNodeConstructor(this, this.enforceType(other), '%'); } // Check that the types of the operands are compatible. enforceType(other){ @@ -309,7 +286,8 @@ function shadergenerator(p5, fn) { return new FloatNode(other); } else if(Array.isArray(other)) { - return nodeConstructors[`vec${other.length}`](other); + return nodeConstructors.dynamicVector(other); + // return nodeConstructors[`vec${other.length}`](other); } else { return nodeConstructors[this.type](other); @@ -378,26 +356,20 @@ function shadergenerator(p5, fn) { super(isInternal, type); values = conformVectorParameters(values, +type.slice(3)); this.componentNames = ['x', 'y', 'z', 'w'].slice(0, values.length); + this.addVectorComponents(values); + } + + addVectorComponents(values) { this.componentNames.forEach((component, i) => { - let value = values[i]; - if (isShaderNode(value)) { - value = new FloatNode() - } this[component] = isFloatNode(values[i]) ? values[i] : new FloatNode(values[i], true); }); } toGLSL(context) { - let glslArgs = ``; - - this.componentNames.forEach((component, i) => { - const comma = i === this.componentNames.length - 1 ? `` : `, `; - if (!isShaderNode(this[component])) { - this[component] = new FloatNode(this[component]); - } - glslArgs += `${this[component].toGLSLBase(context)}${comma}`; - }) - + let values = this.componentNames.map(c => this[c]) + values = conformVectorParameters(values); + this.addVectorComponents(values); + let glslArgs = this.componentNames.map((comp) => this[comp].toGLSLBase(context)).join(', '); return `${this.type}(${glslArgs})`; } } @@ -430,7 +402,7 @@ function shadergenerator(p5, fn) { } if (Array.isArray(properties)) { - // Check if the right amount of + // Check if the right number of parameters were provided let possibleOverloads = properties.filter(o => o.args.length === userArgs.length); if (possibleOverloads.length === 0) { const argsLengthSet = new Set(); @@ -602,8 +574,8 @@ function shadergenerator(p5, fn) { } class ModulusNode extends BinaryExpressionNode { - constructor(a, b) { - super(a, b); + constructor(a, b, isInternal) { + super(a, b, isInternal); } toGLSL(context) { // Switch on type between % or mod() @@ -666,10 +638,6 @@ function shadergenerator(p5, fn) { } } - fn.if = function (value) { - return new ConditionalNode(value); - } - // Node Helper functions function getType(node) { if (isShaderNode(node)) { return node.type; } @@ -680,6 +648,41 @@ function shadergenerator(p5, fn) { return undefined; } + function computeVectorLength(values) { + let length = 0; + if (Array.isArray(values)) { + for(let val of values) { + if (isVectorType(val)) length += parseInt(val.type.slice(3)); + else length += 1; + } + } + else if (isVectorType(values)) { + length += parseInt(val.type.slice(3)); + } + if (![2, 3, 4].includes(length)) { + throw new Error(`You have attempted to construct a vector with ${length} values. Only vec2, vec3, and vec4 types are supported.`) + } + return length + } + + fn.dynamicNode = function (input) { + if (isShaderNode(input)) { + return input; + } + else if (typeof input === 'number') { + return new FloatNode(input); + } + else if (Array.isArray(input)) { + return nodeConstructors.dynamicVector(input); + } + } + + // For replacing unary expressions + fn.unaryNode = function(input, sign) { + input = dynamicNode(input); + return new UnaryNode(input, sign); + } + function isShaderNode(node) { return (node instanceof BaseNode); } @@ -906,22 +909,21 @@ function shadergenerator(p5, fn) { value = [value]; } value = value.flat(); + value = value.map(val => { + if (isVectorType(val)) { + const componentArray = val.componentNames.map(comp => val[comp]); + return componentArray; + } else { + return val; + } + }).flat(); // Populate arguments so uniformVector3(0) becomes [0,0,0] - if (value.length === 1) { + if (value.length === 1 && !isVectorNode(value[0])) { value = Array(vectorDimensions).fill(value[0]); } return value; } - // User functions - fn.instanceID = function() { - return variableConstructor('gl_InstanceID', 'int'); - } - - fn.discard = function() { - return variableConstructor('discard', 'keyword'); - } - function swizzleTrap(size) { const swizzleSets = [ ['x', 'y', 'z', 'w'], @@ -953,7 +955,16 @@ function shadergenerator(p5, fn) { } } } + + // User functions + fn.if = function (value) { + return new ConditionalNode(value); + } + fn.instanceID = function() { + return variableConstructor('gl_InstanceID', 'int'); + } + // Generating uniformFloat, uniformVec, createFloat, etc functions // Maps a GLSL type to the name suffix for method names const GLSLTypesToIdentifiers = { @@ -965,8 +976,7 @@ function shadergenerator(p5, fn) { sampler2D: 'Texture', }; - function variableConstructor(name, type, isInternal) { - const node = new VariableNode(name, type, isInternal); + function dynamicAddSwizzleTrap(node) { if (node.type.startsWith('vec')) { return new Proxy(node, swizzleTrap(+node.type.slice(3))); } else { @@ -974,6 +984,21 @@ function shadergenerator(p5, fn) { } } + function binaryExpressionNodeConstructor(a, b, operator, isInternal) { + let node; + if (operator === '%') { + node = new ModulusNode(a, b); + } else { + node = new BinaryExpressionNode(a, b, operator, isInternal); + } + return dynamicAddSwizzleTrap(node); + } + + function variableConstructor(name, type, isInternal) { + const node = new VariableNode(name, type, isInternal); + return dynamicAddSwizzleTrap(node); + } + function fnNodeConstructor(name, userArgs, properties, isInternal) { const node = new FunctionCallNode(name, userArgs, properties, isInternal) if (node.type.startsWith('vec')) { @@ -989,6 +1014,10 @@ function shadergenerator(p5, fn) { vec2: (value) => new Proxy(new VectorNode(value, 'vec2'), swizzleTrap(2)), vec3: (value) => new Proxy(new VectorNode(value, 'vec3'), swizzleTrap(3)), vec4: (value) => new Proxy(new VectorNode(value, 'vec4'), swizzleTrap(4)), + dynamicVector: function(value) { + const size = computeVectorLength(value); + return this[`vec${size}`](value); + }, }; for (const glslType in GLSLTypesToIdentifiers) { @@ -999,7 +1028,7 @@ function shadergenerator(p5, fn) { ShaderGenerator.prototype[uniformMethodName] = function(...args) { let [name, ...defaultValue] = args; if(glslType.startsWith('vec') && !(defaultValue[0] instanceof Function)) { - defaultValue = conformVectorParameters(defaultValue, +glslType.slice(3)); + defaultValue = conformVectorParameters(defaultValue, parseInt(glslType.slice(3))); this.output.uniforms[`${glslType} ${name}`] = defaultValue; } else { @@ -1031,7 +1060,7 @@ function shadergenerator(p5, fn) { const createMethodName = `create${typeIdentifier}`; fn[createMethodName] = function (...value) { if(glslType.startsWith('vec')) { - value = conformVectorParameters(value, +glslType.slice(3)); + value = conformVectorParameters(value, parseInt(glslType.slice(3))); } else { value = value[0]; } From 687f78815ab5ae960bd7621afcaeba6e5dc06d95 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Sat, 29 Mar 2025 12:55:40 +0000 Subject: [PATCH 31/49] fixed name clashes, and unary node swizzling bugs --- src/webgl/ShaderGenerator.js | 74 +++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index af5cd07905..299bf70bd9 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -211,18 +211,24 @@ function shadergenerator(p5, fn) { let value = new ComponentNode(this, componentName, 'float', true); Object.defineProperty(this, componentName, { get() { + if (isUnaryNode(this)) { + return this.node.value; + } return value; }, set(newValue) { this.componentsChanged = true; - value = newValue; + if (isUnaryNode(this)) { + this.node.value = newValue; + } else { + 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()) { @@ -521,31 +527,31 @@ function shadergenerator(p5, fn) { // Binary Operator Nodes class BinaryExpressionNode extends BaseNode { - constructor(a, b, operator, isInternal = false) { + constructor(left, right, operator, isInternal = false) { super(isInternal, null); this.op = operator; - this.a = a; - this.b = b; - for (const operand of [a, b]) { + this.left = left; + this.right = right; + for (const operand of [left, right]) { operand.usedIn.push(this); } this.type = this.determineType(); this.addVectorComponents(); } - // We know that both this.a and this.b are nodes because of BaseNode.enforceType + // We know that both this.left and this.right are nodes because of BaseNode.enforceType determineType() { - if (this.a.type === this.b.type) { - return this.a.type; + if (this.left.type === this.right.type) { + return this.left.type; } - else if (isVectorType(this.a) && isFloatType(this.b)) { - return this.a.type; + else if (isVectorType(this.left) && isFloatType(this.right)) { + return this.left.type; } - else if (isVectorType(this.b) && isFloatType(this.a)) { - return this.b.type; + else if (isVectorType(this.right) && isFloatType(this.left)) { + return this.right.type; } - else if (isFloatType(this.a) && isIntType(this.b) - || isIntType(this.a) && isFloatType(this.b) + else if (isFloatType(this.left) && isIntType(this.right) + || isIntType(this.left) && isFloatType(this.right) ) { return 'float'; } @@ -567,8 +573,8 @@ function shadergenerator(p5, fn) { } toGLSL(context) { - const a = this.processOperand(this.a, context); - const b = this.processOperand(this.b, context); + const a = this.processOperand(this.left, context); + const b = this.processOperand(this.right, context); return `${a} ${this.op} ${b}`; } } @@ -580,21 +586,25 @@ function shadergenerator(p5, fn) { toGLSL(context) { // Switch on type between % or mod() if (isVectorType(this) || isFloatType(this)) { - return `mod(${this.a.toGLSLBase(context)}, ${this.b.toGLSLBase(context)})`; + return `mod(${this.left.toGLSLBase(context)}, ${this.right.toGLSLBase(context)})`; } - return `${this.processOperand(context, this.a)} % ${this.processOperand(context, this.b)}`; + return `${this.processOperand(context, this.left)} % ${this.processOperand(context, this.right)}`; } } class UnaryNode extends BaseNode { - constructor(a, operator, isInternal = false) { - super(isInternal, a.type) - this.a = a; + constructor(node, operator, isInternal = false) { + super(isInternal, node.type) + this.node = node; this.operator = operator; + if (isVectorType(this)) { + this.addVectorComponents(); + } } + toGLSL(context) { - let mainStr = this.a.toGLSLBase(context); - if (!isVariableNode(this.a) && !hasTemporaryVariable(this.a)) { + let mainStr = this.node.toGLSLBase(context); + if (!isVariableNode(this.node) && !hasTemporaryVariable(this.node)) { mainStr = `(${mainStr})` } return `${this.operator}${mainStr}` @@ -652,7 +662,9 @@ function shadergenerator(p5, fn) { let length = 0; if (Array.isArray(values)) { for(let val of values) { - if (isVectorType(val)) length += parseInt(val.type.slice(3)); + if (isVectorType(val)) { + length += parseInt(val.type.slice(3)); + } else length += 1; } } @@ -680,7 +692,7 @@ function shadergenerator(p5, fn) { // For replacing unary expressions fn.unaryNode = function(input, sign) { input = dynamicNode(input); - return new UnaryNode(input, sign); + return dynamicAddSwizzleTrap(new UnaryNode(input, sign)); } function isShaderNode(node) { @@ -727,6 +739,10 @@ function shadergenerator(p5, fn) { return (node instanceof VectorNode) } + function isUnaryNode(node) { + return (node instanceof UnaryNode) + } + // Helper function to check if a type is a user defined struct or native type function isGLSLNativeType(typeName) { // Supported types for now @@ -935,9 +951,13 @@ function shadergenerator(p5, fn) { if(object[property]) { return Reflect.get(...arguments); } else { + let u = false; + if (isUnaryNode(object)) { object = object.node; u = true; } for (const group of swizzleSets) { if ([...property].every(char => group.includes(char))) { - if (property.length === 1) { return object[swizzleSets[0][0]] } + if (property.length === 1) { + return object[swizzleSets[0][group.indexOf(property[0])]] + } const components = [...property].map(char => { const index = group.indexOf(char); const mappedChar = swizzleSets[0][index]; From cb962711b5c5b1967a781a2ab2f554c4be50967c Mon Sep 17 00:00:00 2001 From: 23036879 Date: Sat, 29 Mar 2025 15:18:31 +0000 Subject: [PATCH 32/49] swizzling for lh values added. fixed bug with unary swizzles --- src/webgl/ShaderGenerator.js | 99 ++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 28 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 299bf70bd9..dc52026faa 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -63,16 +63,53 @@ function shadergenerator(p5, fn) { const ASTCallbacks = { UnaryExpression(node, _state, _ancestors) { if (_ancestors.some(ancestorIsUniform)) { return; } + const signNode = { type: 'Literal', value: node.operator, } - node.type = 'CallExpression' - node.callee = { - type: 'Identifier', - name: 'unaryNode', - }; - node.arguments = [node.argument, signNode] + + const standardReplacement = (node) => { + node.type = 'CallExpression' + node.callee = { + type: 'Identifier', + name: 'unaryNode', + } + node.arguments = [node.argument, signNode] + } + + if (node.type === 'MemberExpression') { + const property = node.argument.property.name; + const swizzleSets = [ + ['x', 'y', 'z', 'w'], + ['r', 'g', 'b', 'a'], + ['s', 't', 'p', 'q'] + ]; + + let isSwizzle = swizzleSets.some(set => + [...property].every(char => set.includes(char)) + ) && node.argument.type === 'MemberExpression'; + + if (isSwizzle) { + node.type = 'MemberExpression'; + node.object = { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'unaryNode' + }, + arguments: [node.argument.object, signNode], + }; + node.property = { + type: 'Identifier', + name: property + }; + } else { + standardReplacement(node); + } + } else { + standardReplacement(node); + } delete node.argument; delete node.operator; }, @@ -107,7 +144,6 @@ function shadergenerator(p5, fn) { type: 'Identifier', name: methodName, }, - computed: false, }, arguments: [node.right] } @@ -211,9 +247,6 @@ function shadergenerator(p5, fn) { let value = new ComponentNode(this, componentName, 'float', true); Object.defineProperty(this, componentName, { get() { - if (isUnaryNode(this)) { - return this.node.value; - } return value; }, set(newValue) { @@ -604,7 +637,7 @@ function shadergenerator(p5, fn) { toGLSL(context) { let mainStr = this.node.toGLSLBase(context); - if (!isVariableNode(this.node) && !hasTemporaryVariable(this.node)) { + if (!isVariableNode(this.node) && !hasTemporaryVariable(this.node) && !isPrimitiveNode(this.node)) { mainStr = `(${mainStr})` } return `${this.operator}${mainStr}` @@ -727,8 +760,8 @@ function shadergenerator(p5, fn) { return (node.temporaryVariable); } - function isLiteralNode(node) { - return (node instanceof FloatNode || node instanceof IntNode); + function isPrimitiveNode(node) { + return (node instanceof FloatNode || node instanceof IntNode || node instanceof VectorNode); } function isFunctionCallNode(node) { @@ -947,31 +980,41 @@ function shadergenerator(p5, fn) { ['s', 't', 'p', 'q'] ].map(s => s.slice(0, size)); return { - get(object, property, receiver) { - if(object[property]) { + get(target, property, receiver) { + if(target[property]) { return Reflect.get(...arguments); } else { - let u = false; - if (isUnaryNode(object)) { object = object.node; u = true; } - for (const group of swizzleSets) { - if ([...property].every(char => group.includes(char))) { - if (property.length === 1) { - return object[swizzleSets[0][group.indexOf(property[0])]] + for (const set of swizzleSets) { + if ([...property].every(char => set.includes(char))) { + if (property.length === 1) { + return target[swizzleSets[0][set.indexOf(property[0])]] } const components = [...property].map(char => { - const index = group.indexOf(char); + const index = set.indexOf(char); const mappedChar = swizzleSets[0][index]; - return object[mappedChar]; + return target[mappedChar]; }); const type = `vec${property.length}`; - const node = nodeConstructors[type](components); - return node; + return nodeConstructors[type](components); } } } }, - set(object, property, receiver) { - return Reflect.set(...arguments); + set(target, property, value, receiver) { + for (const set of swizzleSets) { + if ([...property].every(char => set.includes(char))) { + const newValues = Array.isArray(value) + ? value + : Array(property.length).fill(value); + [...property].forEach((char, i) => { + const index = set.indexOf(char); + const realProperty = swizzleSets[0][index]; + Reflect.set(target, realProperty, newValues[i]); + }); + return true; + } + } + return Reflect.set(target, property, value, receiver); } } } @@ -998,7 +1041,7 @@ function shadergenerator(p5, fn) { function dynamicAddSwizzleTrap(node) { if (node.type.startsWith('vec')) { - return new Proxy(node, swizzleTrap(+node.type.slice(3))); + return new Proxy(node, swizzleTrap(parseInt(node.type.slice(3)))); } else { return node; } From 57a11b4241344bdec95bd25d0bbb35a272ea9a13 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Mon, 31 Mar 2025 15:06:06 +0100 Subject: [PATCH 33/49] code gen changes, remove extra semicolons, add state obj --- src/webgl/ShaderGenerator.js | 199 +++++++++++++++++++++++++---------- 1 file changed, 146 insertions(+), 53 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index dc52026faa..aa768156a5 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -23,13 +23,14 @@ function shadergenerator(p5, fn) { ecmaVersion: 2021, locations: options.srcLocations }); - ancestor(ast, ASTCallbacks); + ancestor(ast, ASTCallbacks, undefined, { varyings: {} }); const transpiledSource = escodegen.generate(ast); generatorFunction = new Function( - transpiledSource.slice( + transpiledSource + .slice( transpiledSource.indexOf('{') + 1, transpiledSource.lastIndexOf('}') - ) + ).replaceAll(';', '') ); } else { generatorFunction = shaderModifier; @@ -127,7 +128,28 @@ function shadergenerator(p5, fn) { value: node.id.name } node.init.arguments.unshift(varyingNameLiteral); - _state[node.id.name] = varyingNameLiteral; + _state.varyings[node.id.name] = varyingNameLiteral; + } + }, + Identifier(node, _state, _ancestors) { + if (_state.varyings[node.name] + && !_ancestors.some(a => a.type === 'AssignmentExpression' && a.left === node)) { + node.type = 'ExpressionStatement'; + node.expression = { + type: 'CallExpression', + callee: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: node.name + }, + property: { + type: 'Identifier', + name: 'getValue' + }, + }, + arguments: [], + } } }, // The callbacks for AssignmentExpression and BinaryExpression handle @@ -160,7 +182,7 @@ function shadergenerator(p5, fn) { arguments: [node.right] } } - if (_state[node.left.name]) { + if (_state.varyings[node.left.name]) { node.type = 'ExpressionStatement'; node.expression = { type: 'CallExpression', @@ -251,7 +273,7 @@ function shadergenerator(p5, fn) { }, set(newValue) { this.componentsChanged = true; - if (isUnaryNode(this)) { + if (isUnaryExpressionNode(this)) { this.node.value = newValue; } else { value = newValue; @@ -318,13 +340,13 @@ function shadergenerator(p5, fn) { } return other; } - else if(typeof other === 'number') { + else if (typeof other === 'number') { if (isIntType(this)) { return new IntNode(other); } return new FloatNode(other); } - else if(Array.isArray(other)) { + else if (Array.isArray(other)) { return nodeConstructors.dynamicVector(other); // return nodeConstructors[`vec${other.length}`](other); } @@ -544,17 +566,42 @@ function shadergenerator(p5, fn) { } } + // class VaryingNode extends VariableNode { constructor(name, type, isInternal = false) { super(name, type, isInternal); - this.isSet = false; - this.value = null; + this.timesChanged = 0; + this.tempVars = 0; } + + getValue() { + const context = GLOBAL_SHADER.context; + if (!context.varyings[this.name] || !this.timesChanged) { + return this; + } + + let values = context.varyings[this.name].splice(0, this.timesChanged); + let snapshot; + values.forEach((val, i) => { + let { value } = val; + context.declarations.push(` ${this.name} = ${value.toGLSLBase(context)};`); + if (i === values.length - 1) { + const tempName = `${this.name}_${this.tempVars++}` + snapshot = dynamicAddSwizzleTrap(new VariableNode(tempName, this.type, true)); + context.declarations.push(` ${this.type} ${tempName} = ${this.name};`); + } + }); + + this.timesChanged = 0; + return snapshot; + } + bridge(value) { if (!isShaderNode(value) || this.type.startsWith('vec') && getType(value) === 'float') { value = nodeConstructors[this.type](value) - } + } GLOBAL_SHADER.registerVarying(this, value); + this.timesChanged += 1 } } @@ -562,7 +609,7 @@ function shadergenerator(p5, fn) { class BinaryExpressionNode extends BaseNode { constructor(left, right, operator, isInternal = false) { super(isInternal, null); - this.op = operator; + this.operator = operator; this.left = left; this.right = right; for (const operand of [left, right]) { @@ -574,7 +621,10 @@ function shadergenerator(p5, fn) { // We know that both this.left and this.right are nodes because of BaseNode.enforceType determineType() { - if (this.left.type === this.right.type) { + if (['==', '>', '>=', '<', '<=', '||', '!', '&&'].includes(this.operator)) { + return 'bool'; + } + else if (this.left.type === this.right.type) { return this.left.type; } else if (isVectorType(this.left) && isFloatType(this.right)) { @@ -608,7 +658,7 @@ function shadergenerator(p5, fn) { toGLSL(context) { const a = this.processOperand(this.left, context); const b = this.processOperand(this.right, context); - return `${a} ${this.op} ${b}`; + return `${a} ${this.operator} ${b}`; } } @@ -625,7 +675,7 @@ function shadergenerator(p5, fn) { } } - class UnaryNode extends BaseNode { + class UnaryExpressionNode extends BaseNode { constructor(node, operator, isInternal = false) { super(isInternal, node.type) this.node = node; @@ -644,32 +694,62 @@ function shadergenerator(p5, fn) { } } + // Conditions and logical modifiers + BaseNode.prototype.equalTo = function(other) { + return binaryExpressionNodeConstructor(this, this.enforceType(other), '=='); + } + + BaseNode.prototype.greaterThan = function(other) { + return binaryExpressionNodeConstructor(this, this.enforceType(other), '>'); + } + + BaseNode.prototype.greaterThanEqualTo = function(other) { + return binaryExpressionNodeConstructor(this, this.enforceType(other), '>='); + } + + BaseNode.prototype.lessThan = function(other) { + return binaryExpressionNodeConstructor(this, this.enforceType(other), '<'); + } + + BaseNode.prototype.lessThanEqualTo = function(other) { + return binaryExpressionNodeConstructor(this, this.enforceType(other), '<='); } + + BaseNode.prototype.not = function() { + return new UnaryExpressionNode(this.condition, '!', true); + } + + BaseNode.prototype.or = function(other) { + return new binaryExpressionNodeConstructor(this, this.enforceType(other), '||', true); + } + + BaseNode.prototype.and = function(other) { + return new binaryExpressionNodeConstructor(this, this.enforceType(other), '&&', true); + } + // TODO: finish If Node class ConditionalNode extends BaseNode { - constructor(value) { - super(value); - this.value = value; - this.condition = null; + constructor(condition) { + this.condition = condition; 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) {} - // + thenReturn(value) { + this.thenBranch = value + } + elseReturn(value) { + this.elseBranch = value + } thenDiscard() { - new ConditionalDiscard(this.condition); + return new ConditionalDiscard(this.condition); }; + + toGLSL(context) { + let str = `if (${this.condition}) { + + }` + } }; class ConditionalDiscard extends BaseNode { @@ -677,7 +757,7 @@ function shadergenerator(p5, fn) { this.condition = condition; } toGLSL(context) { - context.discardConditions.push(`if(${this.condition}{discard;})`); + context.discardConditions.push(`if (${this.condition}{discard;})`); } } @@ -725,7 +805,7 @@ function shadergenerator(p5, fn) { // For replacing unary expressions fn.unaryNode = function(input, sign) { input = dynamicNode(input); - return dynamicAddSwizzleTrap(new UnaryNode(input, sign)); + return dynamicAddSwizzleTrap(new UnaryExpressionNode(input, sign)); } function isShaderNode(node) { @@ -756,6 +836,10 @@ function shadergenerator(p5, fn) { return (node instanceof VariableNode || node instanceof ComponentNode); } + function isVaryingNode(node) { + return (node instanceof VaryingNode); + } + function hasTemporaryVariable(node) { return (node.temporaryVariable); } @@ -772,8 +856,8 @@ function shadergenerator(p5, fn) { return (node instanceof VectorNode) } - function isUnaryNode(node) { - return (node instanceof UnaryNode) + function isUnaryExpressionNode(node) { + return (node instanceof UnaryExpressionNode) } // Helper function to check if a type is a user defined struct or native type @@ -871,7 +955,6 @@ function shadergenerator(p5, fn) { } } - // If the expected return type is a struct we need to evaluate each of its properties if (!isGLSLNativeType(expectedReturnType.typeName)) { Object.entries(returnedValue).forEach(([propertyName, propertyNode]) => { @@ -889,14 +972,6 @@ function shadergenerator(p5, fn) { updateComponents(returnedValue); } - this.context.varyings.forEach(varying => { - let { node, value } = varying; - this.context.declarations.push( - ` ${node.name} = ${value.toGLSLBase(this.context)};` - ); - this.output.vertexDeclarations.add(`out ${node.type} ${node.name};`); - this.output.fragmentDeclarations.add(`in ${node.type} ${node.name};`); - }) // Build the final GLSL string. // The order of this code is a bit confusing, we need to call toGLSLBase @@ -909,8 +984,21 @@ function shadergenerator(p5, fn) { Object.entries(toGLSLResults).forEach(([propertyName, result]) => { const propString = expectedReturnType.properties ? `.${propertyName}` : ''; codeLines.push(` finalReturnValue${propString} = ${result};`) - }) + }); + this.context.declarations = []; + for (let key in this.context.varyings) { + const declArray = this.context.varyings[key]; + const finalVaryingAssignments = []; + declArray.forEach(obj => { + const { node, value } = obj; + finalVaryingAssignments.push(` ${node.name} = ${value.toGLSLBase(this.context)};`) + finalVaryingAssignments.unshift(...this.context.declarations); + node.timesChanged = 0; + }); + codeLines.push(...finalVaryingAssignments); + } + codeLines.push(' return finalReturnValue;', '}'); this.output[hookName] = codeLines.join('\n'); this.resetGLSLContext(); @@ -932,7 +1020,12 @@ function shadergenerator(p5, fn) { } registerVarying(node, value) { - this.context.varyings.push({ node, value }) + if (!Array.isArray(this.context.varyings[node.name])) { + this.context.varyings[node.name] = []; + } + this.context.varyings[node.name].push({ node, value }); + this.output.vertexDeclarations.add(`out ${node.type} ${node.name};`); + this.output.fragmentDeclarations.add(`in ${node.type} ${node.name};`); } resetGLSLContext() { @@ -981,7 +1074,7 @@ function shadergenerator(p5, fn) { ].map(s => s.slice(0, size)); return { get(target, property, receiver) { - if(target[property]) { + if (target[property]) { return Reflect.get(...arguments); } else { for (const set of swizzleSets) { @@ -1020,7 +1113,7 @@ function shadergenerator(p5, fn) { } // User functions - fn.if = function (value) { + fn.If = function (value) { return new ConditionalNode(value); } @@ -1090,7 +1183,7 @@ function shadergenerator(p5, fn) { ShaderGenerator.prototype[uniformMethodName] = function(...args) { let [name, ...defaultValue] = args; - if(glslType.startsWith('vec') && !(defaultValue[0] instanceof Function)) { + if (glslType.startsWith('vec') && !(defaultValue[0] instanceof Function)) { defaultValue = conformVectorParameters(defaultValue, parseInt(glslType.slice(3))); this.output.uniforms[`${glslType} ${name}`] = defaultValue; } @@ -1112,7 +1205,7 @@ function shadergenerator(p5, fn) { const varyingMethodName = `varying${typeIdentifier}`; ShaderGenerator.prototype[varyingMethodName] = function(name) { - return new VaryingNode(name, glslType, false); + return dynamicAddSwizzleTrap(new VaryingNode(name, glslType, false)); } fn[varyingMethodName] = function (name) { @@ -1122,7 +1215,7 @@ function shadergenerator(p5, fn) { // Generate the create*() Methods for creating variables in shaders const createMethodName = `create${typeIdentifier}`; fn[createMethodName] = function (...value) { - if(glslType.startsWith('vec')) { + if (glslType.startsWith('vec')) { value = conformVectorParameters(value, parseInt(glslType.slice(3))); } else { value = value[0]; From 0b22cb6dc1bdb9459383ce876095de229b0f397d Mon Sep 17 00:00:00 2001 From: 23036879 Date: Mon, 31 Mar 2025 20:14:28 +0100 Subject: [PATCH 34/49] IF statements added to js node API --- src/webgl/ShaderGenerator.js | 227 +++++++++++++++++++++++++++-------- 1 file changed, 179 insertions(+), 48 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index aa768156a5..dc95c89e2f 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -11,6 +11,7 @@ import escodegen from 'escodegen'; function shadergenerator(p5, fn) { let GLOBAL_SHADER; + let BRANCH; const oldModify = p5.Shader.prototype.modify @@ -52,7 +53,14 @@ function shadergenerator(p5, fn) { case '*': return 'mult'; case '/': return 'div'; case '%': return 'mod'; - } + case '==': + case '===': return 'equalTo'; + case '>': return 'greaterThan'; + case '>=': return 'greaterThanEqualTo'; + case '<': return 'lessThan'; + case '&&': return 'and'; + case '||': return 'or'; + } } function ancestorIsUniform(ancestor) { @@ -154,6 +162,15 @@ function shadergenerator(p5, fn) { }, // The callbacks for AssignmentExpression and BinaryExpression handle // operator overloading including +=, *= assignment expressions + ArrayExpression(node, _state, _ancestors) { + const original = JSON.parse(JSON.stringify(node)); + node.type = 'CallExpression'; + node.callee = { + type: 'Identifier', + name: 'dynamicNode', + }; + node.arguments = [original]; + }, AssignmentExpression(node, _state, _ancestors) { if (node.operator !== '=') { const methodName = replaceBinaryOperator(node.operator.replace('=','')); @@ -172,16 +189,16 @@ function shadergenerator(p5, fn) { node.operator = '='; node.right = rightReplacementNode; } - if (node.right.type === 'ArrayExpression') { - node.right = { - type: 'CallExpression', - callee: { - type: 'Identifier', - name: 'dynamicNode', - }, - arguments: [node.right] - } - } + // if (node.right.type === 'ArrayExpression') { + // node.right = { + // type: 'CallExpression', + // callee: { + // type: 'Identifier', + // name: 'dynamicNode', + // }, + // arguments: [node.right] + // } + // } if (_state.varyings[node.left.name]) { node.type = 'ExpressionStatement'; node.expression = { @@ -284,20 +301,35 @@ function shadergenerator(p5, fn) { } } + checkIfUsedInConditional(context) { + context.ifs.forEach((statement) => { + if (statement.insertionPoint > -1) return; + if (statement.dependsOn.includes(this)) { + const i = statement.dependsOn.indexOf(this); + statement.dependsOn.splice(i, 1); + } + if (statement.dependsOn.length === 0) { + statement.saveState(); + } + }); + } + // The base node implements a version of toGLSL which determines whether the generated code should be stored in a temporary variable. toGLSLBase(context){ + let result; if (this.shouldUseTemporaryVariable()) { - return this.getTemporaryVariable(context); - } - else { - return this.toGLSL(context); + result = this.getTemporaryVariable(context); + } else { + result = this.toGLSL(context); } + this.checkIfUsedInConditional(context); + return result; } shouldUseTemporaryVariable() { - if (this.isInternal || isVariableNode(this) || this.type === 'sampler2D') { return false; } + if (this.isInternal || isVariableNode(this) || isConditionalNode(this) || this.type === 'sampler2D') { return false; } // Swizzles must use temporary variables as otherwise they will not be registered - if (this.componentsChanged || hasTemporaryVariable(this)) { return true; } + if (this.componentsChanged) { return true; } let score = 0; score += isFunctionCallNode(this) * 2; score += isBinaryExpressionNode(this) * 2; @@ -725,34 +757,129 @@ function shadergenerator(p5, fn) { BaseNode.prototype.and = function(other) { return new binaryExpressionNodeConstructor(this, this.enforceType(other), '&&', true); } + + function branch(callback) { + const branch = new BranchNode(); + callback(); + BRANCH = null; + return branch; + } - // TODO: finish If Node - class ConditionalNode extends BaseNode { - constructor(condition) { - this.condition = condition; - this.thenBranch = null; + class ConditionalNode { + constructor(condition, branchCallback) { + this.dependsOn = []; + this.if(condition, branchCallback); + this.insertionPoint = -1; + this.elseIfs = []; this.elseBranch = null; + GLOBAL_SHADER.context.ifs.push(this); } - // returns - thenReturn(value) { - this.thenBranch = value + if(condition, branchCallback) { + this.condition = dynamicNode(condition); + this.dependsOn.push(this.condition.left, this.condition.right); + this.ifBranch = branch(branchCallback); + this.ifBranch.parent = this; } - elseReturn(value) { - this.elseBranch = value + + elseIf(condition, branchCallback) { + let elseBranch = branch(branchCallback); + branchCallback.parent = this; + this.dependsOn.push(condition.left, condition.right); + this.elseIfs.push({ condition, elseBranch }); + return this; + } + + else(branchCallback) { + this.elseBranch = branch(branchCallback); + this.elseBranch.parent = this + return this; } + thenDiscard() { return new ConditionalDiscard(this.condition); }; + saveState() { + if (this.insertionPoint = -1) { + this.insertionPoint = GLOBAL_SHADER.context.declarations.length; + } + console.log(this.insertionPoint) + } + toGLSL(context) { - let str = `if (${this.condition}) { + let str = ` if (${this.condition.toGLSL(context)}) {` + str += `\n ${this.ifBranch.toGLSL(context)}` + str += `\n }`; + + if (this.elseIfs.length) { + this.elseIfs.forEach((elif) => { + let { condition, elseBranch } = elif; + str += ` else if (${condition.toGLSL(context)}) {` + str += `\n ${elseBranch.toGLSL(context)}` + str += `\n }`; + }) + } + + if (this.elseBranch) { + str += ` else {` + str += `\n ${this.elseBranch.toGLSL(context)}` + str += `\n }`; + } - }` + return str; } }; - class ConditionalDiscard extends BaseNode { + fn.assign = function(node, value) { + if (!BRANCH) { + throw new error('assign() is supposed to be used inside of conditional branchs. Use the "=" operator as normal otherwise.'); + } + BRANCH.assign(node, value); + } + + class BranchNode { + constructor(_condition) { + BRANCH = this; + this.statements = []; + this.dependsOn = []; + this.values = []; + Object.defineProperty(this, 'parent', { + set(newParent) { + newParent.dependsOn.push(this.dependsOn); + newParent.dependsOn = newParent.dependsOn.flat(); + parent = newParent; + } + }) + } + + assign(node, value) { + this.dependsOn.push(node); + this.values.push(value); + } + + toGLSL(context) { + this.dependsOn.forEach((node, i) => { + let statement; + const value = this.values[i]; + const result = nodeConstructors[node.type](value) + .toGLSL(context); + + if (isVariableNode(node) || hasTemporaryVariable(node)) { + statement = `${node.toGLSLBase(context)} = ${result};`; + } else { + node.temporaryVariable = `temp_${context.getNextID()}`; + statement = `${node.type} ${node.toGLSLBase(context)} = ${result};` + } + + this.statements.push(statement); + }) + + return this.statements.join (`\n `) + } + } + + class ConditionalDiscard { constructor(condition){ this.condition = condition; } @@ -836,8 +963,8 @@ function shadergenerator(p5, fn) { return (node instanceof VariableNode || node instanceof ComponentNode); } - function isVaryingNode(node) { - return (node instanceof VaryingNode); + function isConditionalNode(node) { + return (node instanceof ConditionalNode) } function hasTemporaryVariable(node) { @@ -972,7 +1099,10 @@ function shadergenerator(p5, fn) { updateComponents(returnedValue); } - + this.context.ifs.forEach((statement, index) => { + const lines = statement.toGLSL(this.context); + this.context.declarations.splice(statement.insertionPoint, 0, lines); + }) // Build the final GLSL string. // The order of this code is a bit confusing, we need to call toGLSLBase let codeLines = [ @@ -1038,6 +1168,7 @@ function shadergenerator(p5, fn) { getNextID() { return this.id++ }, declarations: [], varyings: [], + ifs: [], } this.uniformNodes = []; } @@ -1087,6 +1218,7 @@ function shadergenerator(p5, fn) { const mappedChar = swizzleSets[0][index]; return target[mappedChar]; }); + const type = `vec${property.length}`; return nodeConstructors[type](components); } @@ -1094,27 +1226,26 @@ function shadergenerator(p5, fn) { } }, set(target, property, value, receiver) { - for (const set of swizzleSets) { - if ([...property].every(char => set.includes(char))) { - const newValues = Array.isArray(value) - ? value - : Array(property.length).fill(value); - [...property].forEach((char, i) => { - const index = set.indexOf(char); - const realProperty = swizzleSets[0][index]; - Reflect.set(target, realProperty, newValues[i]); - }); - return true; + for (const set of swizzleSets) { + const propertyCharArray = [...property]; + if (propertyCharArray.every(char => set.includes(char))) { + const newValues = Array.isArray(value) ? value : Array(property.length).fill(value); + propertyCharArray.forEach((char, i) => { + const index = set.indexOf(char); + const realProperty = swizzleSets[0][index]; + Reflect.set(target, realProperty, newValues[i]); + }); + return true; + } } - } - return Reflect.set(target, property, value, receiver); + return Reflect.set(...arguments); } } } // User functions - fn.If = function (value) { - return new ConditionalNode(value); + fn.If = function (condition, branch) { + return new ConditionalNode(condition, branch); } fn.instanceID = function() { From 75318cede73bb4e1e4bc06856fcbda2f80925075 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Mon, 31 Mar 2025 20:14:39 +0100 Subject: [PATCH 35/49] testing conditionals --- preview/global/sketch.js | 50 ++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index 11d3ba9310..bcc3363c1d 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -17,24 +17,20 @@ function fresnelShaderCallback() { const fresnelPower = uniformFloat(2); const fresnelBias = uniformFloat(-0.1); const fresnelScale = uniformFloat(2); - const mouseIntensity = uniformVector2(()=>[map(mouseX, 0, width, 0, 0.15), map(mouseY, 0, height, 0, 0.15)]); - let val = varyingFloat('val'); - let colr = varyingVector4('colr'); - getCameraInputs((inputs) => { + let myValue = [1,0,0]; let n = normalize(inputs.normal); + If(n == n, () => assign(myValue, 1)); let v = normalize(-inputs.position); let base = 1.0 - dot(n, v); - val = mouseIntensity.x; let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias; - let col = mix([mouseIntensity.y, 0, mouseIntensity.x], [1, .5, .7], fresnel); - inputs.color = col.xxxx; + + + let col = mix(myValue, [1, .5, .7], fresnel); + + inputs.color = [col, 1]; return inputs; }); - - // getFinalColor((col) => { - // return col * [val,col.y,col.y,1]; - // }) } function starShaderCallback() { @@ -95,11 +91,11 @@ async function setup(){ stars = buildGeometry(() => sphere(20, 4, 2)) originalFrameBuffer = createFramebuffer(); - starShader = baseMaterialShader().modify(starShaderCallback); - starStrokeShader = baseStrokeShader().modify(starShaderCallback) + // starShader = baseMaterialShader().modify(starShaderCallback); + // starStrokeShader = baseStrokeShader().modify(starShaderCallback) fresnelShader = baseColorShader().modify(fresnelShaderCallback); - bloomShader = baseFilterShader().modify(bloomShaderCallback); - pixellizeShader = baseFilterShader().modify(pixellizeShaderCallback); + // bloomShader = baseFilterShader().modify(bloomShaderCallback); + // pixellizeShader = baseFilterShader().modify(pixellizeShaderCallback); } function draw(){ @@ -107,15 +103,15 @@ function draw(){ background(0); orbitControl(); - push() - strokeWeight(4) - stroke(255,0,0) - rotateX(PI/2 + millis() * 0.0005); - fill(255,100, 150) - strokeShader(starStrokeShader) - shader(starShader); - model(stars, 5000); - pop() + // push() + // strokeWeight(4) + // stroke(255,0,0) + // rotateX(PI/2 + millis() * 0.0005); + // fill(255,100, 150) + // strokeShader(starStrokeShader) + // shader(starShader); + // model(stars, 5000); + // pop() push() shader(fresnelShader) @@ -123,12 +119,12 @@ function draw(){ sphere(500); pop() - filter(pixellizeShader); + // filter(pixellizeShader); originalFrameBuffer.end(); imageMode(CENTER) image(originalFrameBuffer, 0, 0) - filter(BLUR, 20) - filter(bloomShader); + // filter(BLUR, 20) + // filter(bloomShader); } From ad6c0df3135f874e4cb982399da2420234d3bec2 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Mon, 31 Mar 2025 20:27:40 +0100 Subject: [PATCH 36/49] helper func for condition dependencies --- src/webgl/ShaderGenerator.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index dc95c89e2f..0330fb958b 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -777,13 +777,14 @@ function shadergenerator(p5, fn) { if(condition, branchCallback) { this.condition = dynamicNode(condition); - this.dependsOn.push(this.condition.left, this.condition.right); + this.checkConditionDependencies(condition); this.ifBranch = branch(branchCallback); this.ifBranch.parent = this; } elseIf(condition, branchCallback) { let elseBranch = branch(branchCallback); + this.checkConditionDependencies(condition); branchCallback.parent = this; this.dependsOn.push(condition.left, condition.right); this.elseIfs.push({ condition, elseBranch }); @@ -804,7 +805,14 @@ function shadergenerator(p5, fn) { if (this.insertionPoint = -1) { this.insertionPoint = GLOBAL_SHADER.context.declarations.length; } - console.log(this.insertionPoint) + } + + checkConditionDependencies(condition) { + [condition.left, condition.right].forEach((node) => { + if (node.shouldUseTemporaryVariable() || isVariableNode(node)) { + this.dependsOn.push(node); + } + }); } toGLSL(context) { From 22ea418ffbb1525d5465f32a6a3e9d5bda405818 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Tue, 1 Apr 2025 13:20:11 +0100 Subject: [PATCH 37/49] put updateComponents into context --- src/webgl/ShaderGenerator.js | 149 ++++++++++++++++++++++++----------- 1 file changed, 104 insertions(+), 45 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 0330fb958b..2a24131bee 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -38,6 +38,8 @@ function shadergenerator(p5, fn) { } const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations); const generatedModifyArgument = generator.generate(); + console.log(generatorFunction.toString()) + console.log(generatedModifyArgument['Vertex getCameraInputs']) return oldModify.call(this, generatedModifyArgument); } else { @@ -263,6 +265,7 @@ function shadergenerator(p5, fn) { // 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) { @@ -279,7 +282,7 @@ function shadergenerator(p5, fn) { addVectorComponents() { if (this.type.startsWith('vec')) { - const vectorDimensions = +this.type.slice(3); + const vectorDimensions = parseInt(this.type.slice(3)); this.componentNames = ['x', 'y', 'z', 'w'].slice(0, vectorDimensions); for (let componentName of this.componentNames) { @@ -304,12 +307,11 @@ function shadergenerator(p5, fn) { checkIfUsedInConditional(context) { context.ifs.forEach((statement) => { if (statement.insertionPoint > -1) return; - if (statement.dependsOn.includes(this)) { - const i = statement.dependsOn.indexOf(this); - statement.dependsOn.splice(i, 1); - } - if (statement.dependsOn.length === 0) { - statement.saveState(); + statement.dependeciesFulfilled += statement.dependsOn + .filter((dependecy) => dependecy != this) + .length; + if (statement.dependsOn.length === statement.dependeciesFulfilled) { + statement.saveState(context); } }); } @@ -327,9 +329,9 @@ function shadergenerator(p5, fn) { } shouldUseTemporaryVariable() { + if (this.componentsChanged) { return true; } if (this.isInternal || isVariableNode(this) || isConditionalNode(this) || this.type === 'sampler2D') { return false; } // Swizzles must use temporary variables as otherwise they will not be registered - if (this.componentsChanged) { return true; } let score = 0; score += isFunctionCallNode(this) * 2; score += isBinaryExpressionNode(this) * 2; @@ -450,20 +452,45 @@ function shadergenerator(p5, fn) { values = conformVectorParameters(values, +type.slice(3)); this.componentNames = ['x', 'y', 'z', 'w'].slice(0, values.length); this.addVectorComponents(values); + this.originalValues = this.componentNames.map(name => this[name]); + console.log(this.originalValues); } addVectorComponents(values) { - this.componentNames.forEach((component, i) => { - this[component] = isFloatNode(values[i]) ? values[i] : new FloatNode(values[i], true); + this.componentsChanged = false; + this.componentNames.forEach((componentName, i) => { + let value = isFloatNode(values[i]) ? values[i] : new FloatNode(values[i], true); + Object.defineProperty(this, componentName, { + get() { + return value; + }, + set(newValue) { + this.componentsChanged = true; + if (isUnaryExpressionNode(this)) { + this.node.value = newValue; + } else { + value = new ComponentNode(this, componentName, 'float', true); + // value = newValue; + } + } + }) }); } + // updateVectorComponents(values) { + // this.componentNames.forEach((component, i) => { + // this[component] = isFloatNode(values[i]) ? values[i] : new FloatNode(values[i], true); + // }); + // } + toGLSL(context) { - let values = this.componentNames.map(c => this[c]) - values = conformVectorParameters(values); - this.addVectorComponents(values); - let glslArgs = this.componentNames.map((comp) => this[comp].toGLSLBase(context)).join(', '); - return `${this.type}(${glslArgs})`; + if (!this.componentsChanged || !this.defined) { + let glslArgs = this.componentNames.map((name, i) => this.originalValues[i].toGLSLBase(context)).join(', '); + this.defined = true; + return `${this.type}(${glslArgs})`; + } else { + return this.temporaryVariable; + } } } @@ -769,14 +796,24 @@ function shadergenerator(p5, fn) { constructor(condition, branchCallback) { this.dependsOn = []; this.if(condition, branchCallback); + this.dependeciesFulfilled = 0; this.insertionPoint = -1; this.elseIfs = []; this.elseBranch = null; GLOBAL_SHADER.context.ifs.push(this); } + checkConditionDependencies(condition) { + [condition.left, condition.right].forEach((node) => { + if (node.shouldUseTemporaryVariable() || isVariableNode(node)) { + this.dependsOn.push(node); + } + }); + } + if(condition, branchCallback) { - this.condition = dynamicNode(condition); + this.condition = condition; + this.conditionString = condition.toGLSLBase(GLOBAL_SHADER.context); this.checkConditionDependencies(condition); this.ifBranch = branch(branchCallback); this.ifBranch.parent = this; @@ -801,22 +838,21 @@ function shadergenerator(p5, fn) { return new ConditionalDiscard(this.condition); }; - saveState() { + saveState(context) { if (this.insertionPoint = -1) { - this.insertionPoint = GLOBAL_SHADER.context.declarations.length; + this.insertionPoint = context.declarations.length; } } - checkConditionDependencies(condition) { - [condition.left, condition.right].forEach((node) => { - if (node.shouldUseTemporaryVariable() || isVariableNode(node)) { - this.dependsOn.push(node); - } - }); - } - toGLSL(context) { + const oldLength = context.declarations.length; + this.dependsOn.forEach(dep => context.updateComponents(dep)); + const newLength = context.declarations.length; + const diff = newLength - oldLength; + this.insertionPoint += diff; + let str = ` if (${this.condition.toGLSL(context)}) {` + // let str = ` if (${this.conditionString}) {` str += `\n ${this.ifBranch.toGLSL(context)}` str += `\n }`; @@ -854,7 +890,9 @@ function shadergenerator(p5, fn) { this.values = []; Object.defineProperty(this, 'parent', { set(newParent) { - newParent.dependsOn.push(this.dependsOn); + newParent.dependsOn.push(this.dependsOn, this.values.filter( + (v) => isShaderNode(v) && (isVariableNode(v) || v.shouldUseTemporaryVariable()) + )); newParent.dependsOn = newParent.dependsOn.flat(); parent = newParent; } @@ -870,8 +908,12 @@ function shadergenerator(p5, fn) { this.dependsOn.forEach((node, i) => { let statement; const value = this.values[i]; - const result = nodeConstructors[node.type](value) - .toGLSL(context); + let result; + if (isShaderNode(value) && node.type === value.type) { + result = value.toGLSLBase(context); + } else { + result = nodeConstructors[node.type](value).toGLSLBase(context); + } if (isVariableNode(node) || hasTemporaryVariable(node)) { statement = `${node.toGLSLBase(context)} = ${result};`; @@ -1078,24 +1120,26 @@ function shadergenerator(p5, fn) { const expectedReturnType = hookTypes.returnType; const toGLSLResults = {}; - const updateComponents = (node) => { - if (node.componentsChanged) { - const components = node.componentNames.map((componentName) => { - return node[componentName] - }); - const replacement = nodeConstructors[node.type](components); - this.context.declarations.push( - ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this.context)};` - ); - } - } + // const updateComponents = (node) => { + // if (node.componentsChanged) { + // const components = node.componentNames.map((componentName) => { + // return node[componentName] + // }); + // const replacement = nodeConstructors[node.type](components); + // const tempVar = node.getTemporaryVariable(this.context); + // this.context.declarations.push( + // ` ${tempVar} = ${replacement.toGLSLBase(this.context)};` + // ); + // } + + // } // If the expected return type is a struct we need to evaluate each of its properties if (!isGLSLNativeType(expectedReturnType.typeName)) { Object.entries(returnedValue).forEach(([propertyName, propertyNode]) => { propertyNode = dynamicNode(propertyNode); toGLSLResults[propertyName] = propertyNode.toGLSLBase(this.context); - updateComponents(propertyNode); + this.context.updateComponents(propertyNode); }); } else { if (!isShaderNode(returnedValue)) { @@ -1104,10 +1148,10 @@ function shadergenerator(p5, fn) { returnedValue = nodeConstructors[expectedReturnType.typeName](returnedValue); } toGLSLResults['notAProperty'] = returnedValue.toGLSLBase(this.context); - updateComponents(returnedValue); + this.context.updateComponents(returnedValue); } - this.context.ifs.forEach((statement, index) => { + this.context.ifs.forEach((statement) => { const lines = statement.toGLSL(this.context); this.context.declarations.splice(statement.insertionPoint, 0, lines); }) @@ -1166,6 +1210,7 @@ function shadergenerator(p5, fn) { this.output.fragmentDeclarations.add(`in ${node.type} ${node.name};`); } + resetGLSLContext() { this.uniformNodes.forEach((node) => { node.usedIn = []; @@ -1177,6 +1222,19 @@ function shadergenerator(p5, fn) { declarations: [], varyings: [], ifs: [], + updateComponents: function(node) { + if (node.componentsChanged) { + const components = node.componentNames.map((name) => { + return node[name] + }); + const replacement = nodeConstructors[node.type](components); + const line = ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this)};`; + console.log(line) + this.declarations.push( + ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this)};` + ); + } + } } this.uniformNodes = []; } @@ -1213,7 +1271,7 @@ function shadergenerator(p5, fn) { ].map(s => s.slice(0, size)); return { get(target, property, receiver) { - if (target[property]) { + if (property in target) { return Reflect.get(...arguments); } else { for (const set of swizzleSets) { @@ -1241,7 +1299,8 @@ function shadergenerator(p5, fn) { propertyCharArray.forEach((char, i) => { const index = set.indexOf(char); const realProperty = swizzleSets[0][index]; - Reflect.set(target, realProperty, newValues[i]); + const descriptor = Object.getOwnPropertyDescriptor(target, realProperty); + Reflect.set(target, realProperty, newValues[i], receiver); }); return true; } From e54fa0f64cdd296feb8167138aba71ace279e6c1 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Tue, 1 Apr 2025 13:39:40 +0100 Subject: [PATCH 38/49] update components for vector nodes properly --- src/webgl/ShaderGenerator.js | 57 +++++++++++++++--------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 2a24131bee..15a7fa7859 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -449,11 +449,10 @@ function shadergenerator(p5, fn) { class VectorNode extends BaseNode { constructor(values, type, isInternal = false) { super(isInternal, type); - values = conformVectorParameters(values, +type.slice(3)); + values = conformVectorParameters(values, parseInt(type.slice(3))); this.componentNames = ['x', 'y', 'z', 'w'].slice(0, values.length); this.addVectorComponents(values); this.originalValues = this.componentNames.map(name => this[name]); - console.log(this.originalValues); } addVectorComponents(values) { @@ -469,20 +468,13 @@ function shadergenerator(p5, fn) { if (isUnaryExpressionNode(this)) { this.node.value = newValue; } else { - value = new ComponentNode(this, componentName, 'float', true); - // value = newValue; + value = isFloatNode(newValue) ? newValue : new FloatNode(newValue, true); } } }) }); } - // updateVectorComponents(values) { - // this.componentNames.forEach((component, i) => { - // this[component] = isFloatNode(values[i]) ? values[i] : new FloatNode(values[i], true); - // }); - // } - toGLSL(context) { if (!this.componentsChanged || !this.defined) { let glslArgs = this.componentNames.map((name, i) => this.originalValues[i].toGLSLBase(context)).join(', '); @@ -850,7 +842,7 @@ function shadergenerator(p5, fn) { const newLength = context.declarations.length; const diff = newLength - oldLength; this.insertionPoint += diff; - + let str = ` if (${this.condition.toGLSL(context)}) {` // let str = ` if (${this.conditionString}) {` str += `\n ${this.ifBranch.toGLSL(context)}` @@ -1120,20 +1112,6 @@ function shadergenerator(p5, fn) { const expectedReturnType = hookTypes.returnType; const toGLSLResults = {}; - // const updateComponents = (node) => { - // if (node.componentsChanged) { - // const components = node.componentNames.map((componentName) => { - // return node[componentName] - // }); - // const replacement = nodeConstructors[node.type](components); - // const tempVar = node.getTemporaryVariable(this.context); - // this.context.declarations.push( - // ` ${tempVar} = ${replacement.toGLSLBase(this.context)};` - // ); - // } - - // } - // 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]) => { @@ -1224,15 +1202,26 @@ function shadergenerator(p5, fn) { ifs: [], updateComponents: function(node) { if (node.componentsChanged) { - const components = node.componentNames.map((name) => { - return node[name] - }); - const replacement = nodeConstructors[node.type](components); - const line = ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this)};`; - console.log(line) - this.declarations.push( - ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this)};` - ); + if (isVectorNode(node)) { + node.componentNames.forEach((name, i) => { + const lines = []; + if (node[name] !== node.originalValues[i]) { + const replacement = nodeConstructors['float'](node[name]); + const line = `${node.temporaryVariable}.${name} = ${replacement.toGLSLBase(this)};`; + lines.push(line); + } + this.declarations.push(...lines); + }); + } else { + const components = node.componentNames.map((name) => { + return node[name] + }); + const replacement = nodeConstructors[node.type](components); + const line = ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this)};`; + this.declarations.push( + ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this)};` + ); + } } } } From e512e2e84a4f2c1f9927b64f4b79ccfddc57a9c6 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Tue, 1 Apr 2025 19:54:48 +0100 Subject: [PATCH 39/49] more dependency fixes --- src/webgl/ShaderGenerator.js | 81 ++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 15a7fa7859..a6e9ae708f 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -38,8 +38,6 @@ function shadergenerator(p5, fn) { } const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations); const generatedModifyArgument = generator.generate(); - console.log(generatorFunction.toString()) - console.log(generatedModifyArgument['Vertex getCameraInputs']) return oldModify.call(this, generatedModifyArgument); } else { @@ -308,8 +306,8 @@ function shadergenerator(p5, fn) { context.ifs.forEach((statement) => { if (statement.insertionPoint > -1) return; statement.dependeciesFulfilled += statement.dependsOn - .filter((dependecy) => dependecy != this) - .length; + .filter((dependecy) => dependecy === this) + .length; if (statement.dependsOn.length === statement.dependeciesFulfilled) { statement.saveState(context); } @@ -320,7 +318,10 @@ function shadergenerator(p5, fn) { toGLSLBase(context){ let result; if (this.shouldUseTemporaryVariable()) { + let oldLength = context.declarations.length; result = this.getTemporaryVariable(context); + let diff = context.declarations.length - 1 - oldLength; + this.dependsOn.forEach(d => context.updateComponents(d, diff > 0 ? diff : undefined)); } else { result = this.toGLSL(context); } @@ -329,7 +330,7 @@ function shadergenerator(p5, fn) { } shouldUseTemporaryVariable() { - if (this.componentsChanged) { return true; } + if (this.componentsChanged || hasTemporaryVariable(this)) { return true; } if (this.isInternal || isVariableNode(this) || isConditionalNode(this) || this.type === 'sampler2D') { return false; } // Swizzles must use temporary variables as otherwise they will not be registered let score = 0; @@ -489,7 +490,6 @@ function shadergenerator(p5, fn) { // Function Call Nodes class FunctionCallNode extends BaseNode { constructor(name, userArgs, properties, isInternal = false) { - let functionSignature; const determineFunctionSignature = (props) => { let genType; @@ -566,7 +566,10 @@ function shadergenerator(p5, fn) { } super(isInternal, functionSignature.returnType); - userArgs.forEach(arg => arg.usedIn.push(this)); + userArgs.forEach(arg => { + arg.usedIn.push(this); + this.dependsOn.push(arg); + }); this.name = name; this.args = userArgs; this.argumentTypes = functionSignature.args; @@ -806,14 +809,14 @@ function shadergenerator(p5, fn) { if(condition, branchCallback) { this.condition = condition; this.conditionString = condition.toGLSLBase(GLOBAL_SHADER.context); - this.checkConditionDependencies(condition); + // this.checkConditionDependencies(condition); this.ifBranch = branch(branchCallback); this.ifBranch.parent = this; } elseIf(condition, branchCallback) { let elseBranch = branch(branchCallback); - this.checkConditionDependencies(condition); + // this.checkConditionDependencies(condition); branchCallback.parent = this; this.dependsOn.push(condition.left, condition.right); this.elseIfs.push({ condition, elseBranch }); @@ -879,39 +882,53 @@ function shadergenerator(p5, fn) { BRANCH = this; this.statements = []; this.dependsOn = []; - this.values = []; + this.declarations = []; + let parent = null; Object.defineProperty(this, 'parent', { + get() { + return parent; + }, set(newParent) { - newParent.dependsOn.push(this.dependsOn, this.values.filter( - (v) => isShaderNode(v) && (isVariableNode(v) || v.shouldUseTemporaryVariable()) - )); - newParent.dependsOn = newParent.dependsOn.flat(); + const flatDependencies = this.dependsOn.map(d => [...Object.values(d)]).flat(); + newParent.dependsOn.push(...flatDependencies) parent = newParent; } }) } assign(node, value) { - this.dependsOn.push(node); - this.values.push(value); + if (!isShaderNode(value) || value.type !== node.type) { + value = nodeConstructors[node.type](value); + this.declarations.push(value); + this.dependsOn.push({ node }); + } else { + this.dependsOn.push( { node, value }); + } } toGLSL(context) { - this.dependsOn.forEach((node, i) => { + let declarationsIndex = 0; + this.dependsOn.forEach(({ node, value }) => { let statement; - const value = this.values[i]; let result; - if (isShaderNode(value) && node.type === value.type) { - result = value.toGLSLBase(context); + // if (isShadeNode(value) && node.type === value.type) { + if (!value) { + let decl = this.declarations[declarationsIndex]; + declarationsIndex++; + decl.temporaryVariable = `temp_${context.getNextID()}`; + this.statements.push( + `${decl.type} ${decl.temporaryVariable} = ${decl.toGLSL(context)};` + ); + result = decl.toGLSLBase(context); } else { - result = nodeConstructors[node.type](value).toGLSLBase(context); + result = value.toGLSLBase(context); } if (isVariableNode(node) || hasTemporaryVariable(node)) { - statement = `${node.toGLSLBase(context)} = ${result};`; + statement = `${node.toGLSLBase(context)} = ${result};`; } else { - node.temporaryVariable = `temp_${context.getNextID()}`; - statement = `${node.type} ${node.toGLSLBase(context)} = ${result};` + node.temporaryVariable = `temp_${context.getNextID()}`; + statement = `${node.type} ${node.toGLSLBase(context)} = ${result};` } this.statements.push(statement); @@ -1200,28 +1217,30 @@ function shadergenerator(p5, fn) { declarations: [], varyings: [], ifs: [], - updateComponents: function(node) { + updateComponents: function(node, _emplaceAt) { if (node.componentsChanged) { if (isVectorNode(node)) { node.componentNames.forEach((name, i) => { const lines = []; if (node[name] !== node.originalValues[i]) { const replacement = nodeConstructors['float'](node[name]); - const line = `${node.temporaryVariable}.${name} = ${replacement.toGLSLBase(this)};`; + const line = ` ${node.temporaryVariable}.${name} = ${replacement.toGLSLBase(this)};`; lines.push(line); } - this.declarations.push(...lines); + if (_emplaceAt) { + this.declarations.splice(_emplaceAt, 0, ...lines) + } else { + this.declarations.push(...lines); + } }); } else { const components = node.componentNames.map((name) => { return node[name] }); const replacement = nodeConstructors[node.type](components); - const line = ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this)};`; - this.declarations.push( - ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this)};` - ); + this.declarations.push(line); } + node.componentsChanged = false; } } } From ecbf0618370899e06c15e8885c863500ae596e84 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Tue, 1 Apr 2025 19:59:19 +0100 Subject: [PATCH 40/49] fix updatecomponents --- src/webgl/ShaderGenerator.js | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index a6e9ae708f..fb3016e0bc 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -5,6 +5,7 @@ * @requires core */ +import replace from '@rollup/plugin-replace'; import { parse } from 'acorn'; import { ancestor } from 'acorn-walk'; import escodegen from 'escodegen'; @@ -1219,31 +1220,44 @@ function shadergenerator(p5, fn) { ifs: [], updateComponents: function(node, _emplaceAt) { if (node.componentsChanged) { + const lines = []; if (isVectorNode(node)) { node.componentNames.forEach((name, i) => { - const lines = []; if (node[name] !== node.originalValues[i]) { const replacement = nodeConstructors['float'](node[name]); const line = ` ${node.temporaryVariable}.${name} = ${replacement.toGLSLBase(this)};`; lines.push(line); } - if (_emplaceAt) { - this.declarations.splice(_emplaceAt, 0, ...lines) - } else { - this.declarations.push(...lines); - } }); } else { const components = node.componentNames.map((name) => { return node[name] }); const replacement = nodeConstructors[node.type](components); - this.declarations.push(line); + const line = ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this.context)};` + lines.push(line); + } + if (_emplaceAt) { + this.declarations.splice(_emplaceAt, 0, ...lines) + } else { + this.declarations.push(...lines); } node.componentsChanged = false; } } } + // const updateComponents = (node) => { + // if (node.componentsChanged) { + // const components = node.componentNames.map((componentName) => { + // return node[componentName] + // }); + // const replacement = nodeConstructors[node.type](components); + // this.context.declarations.push( + // ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this.context)};` + // ); + // } + // } + this.uniformNodes = []; } } From 4863a4d739bbfa0f4e67947d5ea4b5757ff37ee8 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Tue, 1 Apr 2025 21:09:33 +0100 Subject: [PATCH 41/49] make copy of components which have actually changed before updating --- src/webgl/ShaderGenerator.js | 55 +++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index fb3016e0bc..fcc0227e4d 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -4,8 +4,6 @@ * @for p5 * @requires core */ - -import replace from '@rollup/plugin-replace'; import { parse } from 'acorn'; import { ancestor } from 'acorn-walk'; import escodegen from 'escodegen'; @@ -39,6 +37,7 @@ function shadergenerator(p5, fn) { } const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations); const generatedModifyArgument = generator.generate(); + console.log(generatedModifyArgument['Vertex getCameraInputs']) return oldModify.call(this, generatedModifyArgument); } else { @@ -61,7 +60,7 @@ function shadergenerator(p5, fn) { case '<': return 'lessThan'; case '&&': return 'and'; case '||': return 'or'; - } + } } function ancestorIsUniform(ancestor) { @@ -322,7 +321,19 @@ function shadergenerator(p5, fn) { let oldLength = context.declarations.length; result = this.getTemporaryVariable(context); let diff = context.declarations.length - 1 - oldLength; - this.dependsOn.forEach(d => context.updateComponents(d, diff > 0 ? diff : undefined)); + if (Array.isArray(this.dependsOn)){ + this.dependsOn.forEach(d => context.updateComponents(d, diff > 0 ? diff : undefined)); + } else { + this.dependsOn.nodesArray.forEach((node, i) => { + const originalComponents = this.dependsOn[i]; + const currentComponents = node.componentNames.map(name => node[name]); + if (!originalComponents) return; + const dependencies = originalComponents.map((component, i) => + component === currentComponents[i] + ) + context.updateComponents(node, diff > 0 ? diff : undefined, dependencies); + }) + } } else { result = this.toGLSL(context); } @@ -339,7 +350,7 @@ function shadergenerator(p5, fn) { score += isBinaryExpressionNode(this) * 2; score += isVectorType(this) * 3; score += this.usedIn.length; - return score >= 4; + return score >= 5; } getTemporaryVariable(context) { @@ -567,9 +578,18 @@ function shadergenerator(p5, fn) { } super(isInternal, functionSignature.returnType); - userArgs.forEach(arg => { + + if (userArgs.find(arg => isVectorNode(arg))) { + this.dependsOn = { nodesArray: [] }; + } + userArgs.forEach((arg, i) => { arg.usedIn.push(this); - this.dependsOn.push(arg); + let arr = Array.isArray(this.dependsOn) ? this.dependsOn : this.dependsOn.nodesArray; + if (isVectorType(arg)) { + this.dependsOn[i] = []; + arg.componentNames.forEach(name => this.dependsOn[i].push(arg[name])); + } + arr.push(arg); }); this.name = name; this.args = userArgs; @@ -924,7 +944,7 @@ function shadergenerator(p5, fn) { } else { result = value.toGLSLBase(context); } - + if (isVariableNode(node) || hasTemporaryVariable(node)) { statement = `${node.toGLSLBase(context)} = ${result};`; } else { @@ -1206,7 +1226,6 @@ function shadergenerator(p5, fn) { this.output.fragmentDeclarations.add(`in ${node.type} ${node.name};`); } - resetGLSLContext() { this.uniformNodes.forEach((node) => { node.usedIn = []; @@ -1218,11 +1237,15 @@ function shadergenerator(p5, fn) { declarations: [], varyings: [], ifs: [], - updateComponents: function(node, _emplaceAt) { + updateComponents: function(node, _emplaceAt, _changedComponents) { if (node.componentsChanged) { + if (!_changedComponents) { + _changedComponents = node.componentNames.map(() => true); + } const lines = []; if (isVectorNode(node)) { node.componentNames.forEach((name, i) => { + if (!_changedComponents[i]) return; if (node[name] !== node.originalValues[i]) { const replacement = nodeConstructors['float'](node[name]); const line = ` ${node.temporaryVariable}.${name} = ${replacement.toGLSLBase(this)};`; @@ -1246,18 +1269,6 @@ function shadergenerator(p5, fn) { } } } - // const updateComponents = (node) => { - // if (node.componentsChanged) { - // const components = node.componentNames.map((componentName) => { - // return node[componentName] - // }); - // const replacement = nodeConstructors[node.type](components); - // this.context.declarations.push( - // ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this.context)};` - // ); - // } - // } - this.uniformNodes = []; } } From 9529a801ffe2623a54dddb6f71e6cb509280d29f Mon Sep 17 00:00:00 2001 From: 23036879 Date: Tue, 1 Apr 2025 22:37:05 +0100 Subject: [PATCH 42/49] properly generate float components in conditionals --- src/webgl/ShaderGenerator.js | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index fcc0227e4d..a15760345c 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -37,7 +37,6 @@ function shadergenerator(p5, fn) { } const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations); const generatedModifyArgument = generator.generate(); - console.log(generatedModifyArgument['Vertex getCameraInputs']) return oldModify.call(this, generatedModifyArgument); } else { @@ -437,11 +436,16 @@ function shadergenerator(p5, fn) { } class FloatNode extends BaseNode { - constructor(x = 0, isInternal = false){ + constructor(x = 0, isInternal = false, _parent = false){ super(isInternal, 'float'); if (Array.isArray(x)) { x = x[0]; } + if (_parent) { + const { parent, name } = _parent + this.name = name; + this.parent = parent; + } this.x = x; } @@ -471,7 +475,8 @@ function shadergenerator(p5, fn) { addVectorComponents(values) { this.componentsChanged = false; this.componentNames.forEach((componentName, i) => { - let value = isFloatNode(values[i]) ? values[i] : new FloatNode(values[i], true); + const info = { name: componentName, parent: this }; + let value = isFloatNode(values[i]) ? values[i] : new FloatNode(values[i], true, info); Object.defineProperty(this, componentName, { get() { return value; @@ -481,7 +486,7 @@ function shadergenerator(p5, fn) { if (isUnaryExpressionNode(this)) { this.node.value = newValue; } else { - value = isFloatNode(newValue) ? newValue : new FloatNode(newValue, true); + value = isFloatNode(newValue) ? newValue : new FloatNode(newValue, true, { name: componentName, info }); } } }) @@ -490,7 +495,7 @@ function shadergenerator(p5, fn) { toGLSL(context) { if (!this.componentsChanged || !this.defined) { - let glslArgs = this.componentNames.map((name, i) => this.originalValues[i].toGLSLBase(context)).join(', '); + let glslArgs = this.componentNames.map((_name, i) => this.originalValues[i].toGLSLBase(context)).join(', '); this.defined = true; return `${this.type}(${glslArgs})`; } else { @@ -867,7 +872,7 @@ function shadergenerator(p5, fn) { const diff = newLength - oldLength; this.insertionPoint += diff; - let str = ` if (${this.condition.toGLSL(context)}) {` + let str = `\n if (${this.condition.toGLSL(context)}) {` // let str = ` if (${this.conditionString}) {` str += `\n ${this.ifBranch.toGLSL(context)}` str += `\n }`; @@ -884,7 +889,7 @@ function shadergenerator(p5, fn) { if (this.elseBranch) { str += ` else {` str += `\n ${this.elseBranch.toGLSL(context)}` - str += `\n }`; + str += `\n }\n`; } return str; @@ -910,7 +915,8 @@ function shadergenerator(p5, fn) { return parent; }, set(newParent) { - const flatDependencies = this.dependsOn.map(d => [...Object.values(d)]).flat(); + const flatDependencies = this.dependsOn.map(d => [...Object.values(d)]) + .flat().filter(n => n !== -1); newParent.dependsOn.push(...flatDependencies) parent = newParent; } @@ -921,9 +927,9 @@ function shadergenerator(p5, fn) { if (!isShaderNode(value) || value.type !== node.type) { value = nodeConstructors[node.type](value); this.declarations.push(value); - this.dependsOn.push({ node }); + this.dependsOn.push({ node, parent: node.parent | -1 }); } else { - this.dependsOn.push( { node, value }); + this.dependsOn.push( { node, value, parent: node.parent | -1 }); } } @@ -932,7 +938,6 @@ function shadergenerator(p5, fn) { this.dependsOn.forEach(({ node, value }) => { let statement; let result; - // if (isShadeNode(value) && node.type === value.type) { if (!value) { let decl = this.declarations[declarationsIndex]; declarationsIndex++; @@ -947,6 +952,8 @@ function shadergenerator(p5, fn) { if (isVariableNode(node) || hasTemporaryVariable(node)) { statement = `${node.toGLSLBase(context)} = ${result};`; + } else if (isFloatNode(node) && node.name) { + statement = `${node.parent.toGLSLBase(context)}.${node.name} = ${result};`; } else { node.temporaryVariable = `temp_${context.getNextID()}`; statement = `${node.type} ${node.toGLSLBase(context)} = ${result};` @@ -955,7 +962,7 @@ function shadergenerator(p5, fn) { this.statements.push(statement); }) - return this.statements.join (`\n `) + return this.statements.join(`\n `); } } From fa21c5ac0302716a1969eb6f4080614684581be9 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Wed, 2 Apr 2025 11:14:59 +0100 Subject: [PATCH 43/49] vec components added after proxy creation for correct parenting --- src/webgl/ShaderGenerator.js | 153 ++++++++++++++++++----------------- 1 file changed, 80 insertions(+), 73 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index a15760345c..ea78014190 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -37,6 +37,7 @@ function shadergenerator(p5, fn) { } const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations); const generatedModifyArgument = generator.generate(); + console.log(generatedModifyArgument['Vertex getCameraInputs']) return oldModify.call(this, generatedModifyArgument); } else { @@ -188,16 +189,6 @@ function shadergenerator(p5, fn) { node.operator = '='; node.right = rightReplacementNode; } - // if (node.right.type === 'ArrayExpression') { - // node.right = { - // type: 'CallExpression', - // callee: { - // type: 'Identifier', - // name: 'dynamicNode', - // }, - // arguments: [node.right] - // } - // } if (_state.varyings[node.left.name]) { node.type = 'ExpressionStatement'; node.expression = { @@ -264,6 +255,7 @@ function shadergenerator(p5, fn) { this.usedIn = []; this.dependsOn = []; this.srcLine = null; + this.usedInConditional = false; // Stack Capture is used to get the original line of user code for Debug purposes if (GLOBAL_SHADER.srcLocations === true && isInternal === false) { try { @@ -281,9 +273,9 @@ function shadergenerator(p5, fn) { if (this.type.startsWith('vec')) { const vectorDimensions = parseInt(this.type.slice(3)); this.componentNames = ['x', 'y', 'z', 'w'].slice(0, vectorDimensions); - + const proxy = this; for (let componentName of this.componentNames) { - let value = new ComponentNode(this, componentName, 'float', true); + let value = new ComponentNode(proxy, componentName, 'float', true); Object.defineProperty(this, componentName, { get() { return value; @@ -301,12 +293,29 @@ function shadergenerator(p5, fn) { } } - checkIfUsedInConditional(context) { + forceTemporaryVariable() { + if (!(isFloatNode(this) && isVectorNode(this.parent)) || !isVariableNode(this)) + this.useTemp = true; + } + + assertUsedInConditional() { + this.usedInConditional = true; + this.forceTemporaryVariable(); + } + + checkConditionalDependencies(context) { + // if (!this.usedInConditional) return; context.ifs.forEach((statement) => { if (statement.insertionPoint > -1) return; statement.dependeciesFulfilled += statement.dependsOn - .filter((dependecy) => dependecy === this) - .length; + .filter((dependecy) => dependecy === this) + .length; + // console.log("CHECKING: "); + // console.log("DEPENDS ON, this: ", statement.dependsOn, this); + // console.log("this === any dep:", statement.dependsOn.filter(d => d == this).length) + // console.log("FULFILLED / TOTAL: ", statement.dependeciesFulfilled ,'/', statement.dependsOn.length); + // console.log(context.declarations.join('\n')); + // console.log('\n') if (statement.dependsOn.length === statement.dependeciesFulfilled) { statement.saveState(context); } @@ -336,12 +345,12 @@ function shadergenerator(p5, fn) { } else { result = this.toGLSL(context); } - this.checkIfUsedInConditional(context); + this.checkConditionalDependencies(context); return result; } shouldUseTemporaryVariable() { - if (this.componentsChanged || hasTemporaryVariable(this)) { return true; } + if (this.componentsChanged || hasTemporaryVariable(this) || this.useTemp) { return true; } if (this.isInternal || isVariableNode(this) || isConditionalNode(this) || this.type === 'sampler2D') { return false; } // Swizzles must use temporary variables as otherwise they will not be registered let score = 0; @@ -349,7 +358,7 @@ function shadergenerator(p5, fn) { score += isBinaryExpressionNode(this) * 2; score += isVectorType(this) * 3; score += this.usedIn.length; - return score >= 5; + return score >= 4; } getTemporaryVariable(context) { @@ -466,14 +475,14 @@ function shadergenerator(p5, fn) { class VectorNode extends BaseNode { constructor(values, type, isInternal = false) { super(isInternal, type); - values = conformVectorParameters(values, parseInt(type.slice(3))); - this.componentNames = ['x', 'y', 'z', 'w'].slice(0, values.length); - this.addVectorComponents(values); - this.originalValues = this.componentNames.map(name => this[name]); + this.originalValues = conformVectorParameters(values, parseInt(type.slice(3))); + this.componentNames = ['x', 'y', 'z', 'w'].slice(0, this.originalValues.length); } - addVectorComponents(values) { + addVectorComponents() { + const values = this.originalValues; this.componentsChanged = false; + this.componentNames.forEach((componentName, i) => { const info = { name: componentName, parent: this }; let value = isFloatNode(values[i]) ? values[i] : new FloatNode(values[i], true, info); @@ -486,11 +495,12 @@ function shadergenerator(p5, fn) { if (isUnaryExpressionNode(this)) { this.node.value = newValue; } else { - value = isFloatNode(newValue) ? newValue : new FloatNode(newValue, true, { name: componentName, info }); + value = isFloatNode(newValue) ? newValue : new FloatNode(newValue, true, info); } } }) }); + this.originalValues = this.componentNames.map(name => this[name]); } toGLSL(context) { @@ -599,7 +609,6 @@ function shadergenerator(p5, fn) { this.name = name; this.args = userArgs; this.argumentTypes = functionSignature.args; - this.addVectorComponents(); } deconstructArgs(context) { @@ -622,7 +631,6 @@ function shadergenerator(p5, fn) { constructor(name, type, isInternal = false) { super(isInternal, type); this.name = name; - this.addVectorComponents(); } toGLSL(context) { @@ -696,7 +704,6 @@ function shadergenerator(p5, fn) { operand.usedIn.push(this); } this.type = this.determineType(); - this.addVectorComponents(); } // We know that both this.left and this.right are nodes because of BaseNode.enforceType @@ -760,9 +767,6 @@ function shadergenerator(p5, fn) { super(isInternal, node.type) this.node = node; this.operator = operator; - if (isVectorType(this)) { - this.addVectorComponents(); - } } toGLSL(context) { @@ -824,27 +828,16 @@ function shadergenerator(p5, fn) { GLOBAL_SHADER.context.ifs.push(this); } - checkConditionDependencies(condition) { - [condition.left, condition.right].forEach((node) => { - if (node.shouldUseTemporaryVariable() || isVariableNode(node)) { - this.dependsOn.push(node); - } - }); - } - if(condition, branchCallback) { this.condition = condition; - this.conditionString = condition.toGLSLBase(GLOBAL_SHADER.context); - // this.checkConditionDependencies(condition); + this.conditionString = condition.toGLSL(GLOBAL_SHADER.context); this.ifBranch = branch(branchCallback); this.ifBranch.parent = this; } elseIf(condition, branchCallback) { let elseBranch = branch(branchCallback); - // this.checkConditionDependencies(condition); branchCallback.parent = this; - this.dependsOn.push(condition.left, condition.right); this.elseIfs.push({ condition, elseBranch }); return this; } @@ -861,7 +854,9 @@ function shadergenerator(p5, fn) { saveState(context) { if (this.insertionPoint = -1) { + context.declarations.join('\n'); this.insertionPoint = context.declarations.length; + console.log(this.insertionPoint); } } @@ -872,27 +867,28 @@ function shadergenerator(p5, fn) { const diff = newLength - oldLength; this.insertionPoint += diff; - let str = `\n if (${this.condition.toGLSL(context)}) {` - // let str = ` if (${this.conditionString}) {` - str += `\n ${this.ifBranch.toGLSL(context)}` - str += `\n }`; + let codelines = [ + `\n if (${this.conditionString}) {`, + `\n ${this.ifBranch.toGLSL(context)}`, + `\n }` + ]; if (this.elseIfs.length) { this.elseIfs.forEach((elif) => { let { condition, elseBranch } = elif; - str += ` else if (${condition.toGLSL(context)}) {` - str += `\n ${elseBranch.toGLSL(context)}` - str += `\n }`; + codelines.push(` else if (${condition.toGLSL(context)}) {`); + codelines.push(`\n ${elseBranch.toGLSL(context)}`); + codelines.push(`\n }`); }) } if (this.elseBranch) { - str += ` else {` - str += `\n ${this.elseBranch.toGLSL(context)}` - str += `\n }\n`; + codelines.push(` else {`); + codelines.push(`\n ${this.elseBranch.toGLSL(context)}`); + codelines.push(`\n }\n`); } - - return str; + codelines.push('\n'); + return codelines.flat().join(''); } }; @@ -904,9 +900,10 @@ function shadergenerator(p5, fn) { } class BranchNode { - constructor(_condition) { + constructor() { BRANCH = this; this.statements = []; + this.assignments = []; this.dependsOn = []; this.declarations = []; let parent = null; @@ -915,9 +912,7 @@ function shadergenerator(p5, fn) { return parent; }, set(newParent) { - const flatDependencies = this.dependsOn.map(d => [...Object.values(d)]) - .flat().filter(n => n !== -1); - newParent.dependsOn.push(...flatDependencies) + newParent.dependsOn.push(...this.dependsOn) parent = newParent; } }) @@ -927,17 +922,26 @@ function shadergenerator(p5, fn) { if (!isShaderNode(value) || value.type !== node.type) { value = nodeConstructors[node.type](value); this.declarations.push(value); - this.dependsOn.push({ node, parent: node.parent | -1 }); + this.assignments.push({ node }); } else { - this.dependsOn.push( { node, value, parent: node.parent | -1 }); + this.assignments.push({ node, value }); + } + node = node.parent ? node.parent : node; + node.assertUsedInConditional(); + this.dependsOn.push(node) + value = value.parent ? value.parent : value; + if (value.shouldUseTemporaryVariable()) { + value.assertUsedInConditional(); + this.dependsOn.push(value); } } toGLSL(context) { let declarationsIndex = 0; - this.dependsOn.forEach(({ node, value }) => { + this.assignments.forEach(({ node, value }) => { let statement; let result; + if (!value) { let decl = this.declarations[declarationsIndex]; declarationsIndex++; @@ -952,9 +956,11 @@ function shadergenerator(p5, fn) { if (isVariableNode(node) || hasTemporaryVariable(node)) { statement = `${node.toGLSLBase(context)} = ${result};`; - } else if (isFloatNode(node) && node.name) { + } + else if (isFloatNode(node) && node.name) { statement = `${node.parent.toGLSLBase(context)}.${node.name} = ${result};`; - } else { + } + else { node.temporaryVariable = `temp_${context.getNextID()}`; statement = `${node.type} ${node.toGLSLBase(context)} = ${result};` } @@ -1370,12 +1376,13 @@ function shadergenerator(p5, fn) { sampler2D: 'Texture', }; - function dynamicAddSwizzleTrap(node) { - if (node.type.startsWith('vec')) { - return new Proxy(node, swizzleTrap(parseInt(node.type.slice(3)))); - } else { - return node; - } + function dynamicAddSwizzleTrap(node, _size) { + if (node.type.startsWith('vec') || _size) { + const size = _size ? _size : parseInt(node.type.slice(3)); + node = new Proxy(node, swizzleTrap(size)); + node.addVectorComponents(); + } + return node; } function binaryExpressionNodeConstructor(a, b, operator, isInternal) { @@ -1396,7 +1403,7 @@ function shadergenerator(p5, fn) { function fnNodeConstructor(name, userArgs, properties, isInternal) { const node = new FunctionCallNode(name, userArgs, properties, isInternal) if (node.type.startsWith('vec')) { - return new Proxy(node, swizzleTrap(+node.type.slice(3))) + return dynamicAddSwizzleTrap(node); } else { return node; } @@ -1405,9 +1412,9 @@ function shadergenerator(p5, fn) { const nodeConstructors = { int: (value) => new IntNode(value), float: (value) => new FloatNode(value), - vec2: (value) => new Proxy(new VectorNode(value, 'vec2'), swizzleTrap(2)), - vec3: (value) => new Proxy(new VectorNode(value, 'vec3'), swizzleTrap(3)), - vec4: (value) => new Proxy(new VectorNode(value, 'vec4'), swizzleTrap(4)), + vec2: (value) => dynamicAddSwizzleTrap(new VectorNode(value, 'vec2')), + vec3: (value) => dynamicAddSwizzleTrap(new VectorNode(value, 'vec3')), + vec4: (value) => dynamicAddSwizzleTrap(new VectorNode(value, 'vec4')), dynamicVector: function(value) { const size = computeVectorLength(value); return this[`vec${size}`](value); From 94dc159ca5507f4c08fd1d94a7cb458666d0300f Mon Sep 17 00:00:00 2001 From: 23036879 Date: Wed, 2 Apr 2025 15:12:18 +0100 Subject: [PATCH 44/49] temp --- src/webgl/ShaderGenerator.js | 95 ++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 32 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index ea78014190..f0b4676ba6 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -299,23 +299,39 @@ function shadergenerator(p5, fn) { } assertUsedInConditional() { - this.usedInConditional = true; - this.forceTemporaryVariable(); + this.usedInConditional = true; + this.forceTemporaryVariable(); } - - checkConditionalDependencies(context) { - // if (!this.usedInConditional) return; + + isUsedInConditional() { + return this.usedInConditional; + } + + dependentsUsedInConditional() { + let thisDeps = Array.isArray(this.dependsOn) ? this.dependsOn : this.dependsOn.nodesArray + thisDeps = thisDeps.map((d) => { + return Array.isArray(d) ? d[0].parent : d; + }); + let depsUsed = thisDeps.filter(d => d.isUsedInConditional()); + return depsUsed; + } + + checkConditionalDependencies(context, dependentOnConditional) { context.ifs.forEach((statement) => { if (statement.insertionPoint > -1) return; - statement.dependeciesFulfilled += statement.dependsOn - .filter((dependecy) => dependecy === this) - .length; - // console.log("CHECKING: "); - // console.log("DEPENDS ON, this: ", statement.dependsOn, this); - // console.log("this === any dep:", statement.dependsOn.filter(d => d == this).length) - // console.log("FULFILLED / TOTAL: ", statement.dependeciesFulfilled ,'/', statement.dependsOn.length); - // console.log(context.declarations.join('\n')); - // console.log('\n') + if (statement.dependsOn.includes(this) + && !statement.dependeciesFulfilled.includes(this)) { + statement.dependeciesFulfilled.push(this); + this.oldName = this.toGLSLBase(context); + this.temporaryVariable = `temp_${context.getNextID()}`; + context.declarations.push( + ` ${this.type} ${this.toGLSLBase(context)} = ${this.oldName};` + ); + } + console.log(dependentOnConditional) + if (dependentOnConditional.includes(this)) { + console.log(context.declarations.join('\n')) + } if (statement.dependsOn.length === statement.dependeciesFulfilled) { statement.saveState(context); } @@ -330,28 +346,36 @@ function shadergenerator(p5, fn) { result = this.getTemporaryVariable(context); let diff = context.declarations.length - 1 - oldLength; if (Array.isArray(this.dependsOn)){ - this.dependsOn.forEach(d => context.updateComponents(d, diff > 0 ? diff : undefined)); + this.dependsOn.forEach(d => { + context.updateComponents(d, diff > 0 ? diff : undefined); + }); } else { - this.dependsOn.nodesArray.forEach((node, i) => { - const originalComponents = this.dependsOn[i]; - const currentComponents = node.componentNames.map(name => node[name]); - if (!originalComponents) return; - const dependencies = originalComponents.map((component, i) => - component === currentComponents[i] - ) - context.updateComponents(node, diff > 0 ? diff : undefined, dependencies); - }) + // Update the incorrect components + this.dependsOn.nodesArray.forEach((nodeDependency, i) => { + const originalComponents = this.dependsOn[i]; + const currentComponents = nodeDependency.componentNames.map(name => nodeDependency[name]); + if (!originalComponents) return; + const dependencies = originalComponents.map((component, i) => + component === currentComponents[i] + ) + context.updateComponents(nodeDependency, diff > 0 ? diff : undefined, dependencies); + }) } } else { result = this.toGLSL(context); } - this.checkConditionalDependencies(context); + const depsUsedInConditional = this.dependentsUsedInConditional(); + if (this.isUsedInConditional() || depsUsedInConditional.length > 0) { + this.checkConditionalDependencies(context, depsUsedInConditional) + }; return result; } shouldUseTemporaryVariable() { if (this.componentsChanged || hasTemporaryVariable(this) || this.useTemp) { return true; } if (this.isInternal || isVariableNode(this) || isConditionalNode(this) || this.type === 'sampler2D') { return false; } + + // return false; // Swizzles must use temporary variables as otherwise they will not be registered let score = 0; score += isFunctionCallNode(this) * 2; @@ -504,7 +528,7 @@ function shadergenerator(p5, fn) { } toGLSL(context) { - if (!this.componentsChanged || !this.defined) { + if ((!this.componentsChanged || !this.defined) && !this.oldName) { let glslArgs = this.componentNames.map((_name, i) => this.originalValues[i].toGLSLBase(context)).join(', '); this.defined = true; return `${this.type}(${glslArgs})`; @@ -616,6 +640,7 @@ function shadergenerator(p5, fn) { if (isIntType(argNode) && this.argumentTypes[i] != 'float') { argNode = argNode.toFloat(); } + argNode.toGLSLBase(context); return argNode.toGLSLBase(context); }).join(', '); return argsString; @@ -821,7 +846,7 @@ function shadergenerator(p5, fn) { constructor(condition, branchCallback) { this.dependsOn = []; this.if(condition, branchCallback); - this.dependeciesFulfilled = 0; + this.dependeciesFulfilled = []; this.insertionPoint = -1; this.elseIfs = []; this.elseBranch = null; @@ -854,9 +879,7 @@ function shadergenerator(p5, fn) { saveState(context) { if (this.insertionPoint = -1) { - context.declarations.join('\n'); this.insertionPoint = context.declarations.length; - console.log(this.insertionPoint); } } @@ -927,9 +950,12 @@ function shadergenerator(p5, fn) { this.assignments.push({ node, value }); } node = node.parent ? node.parent : node; + value = value.parent ? value.parent : value; + if ([node, value].some(n => this.dependsOn.includes(n))) { + return; + } node.assertUsedInConditional(); this.dependsOn.push(node) - value = value.parent ? value.parent : value; if (value.shouldUseTemporaryVariable()) { value.assertUsedInConditional(); this.dependsOn.push(value); @@ -1235,8 +1261,8 @@ function shadergenerator(p5, fn) { this.context.varyings[node.name] = []; } this.context.varyings[node.name].push({ node, value }); - this.output.vertexDeclarations.add(`out ${node.type} ${node.name};`); - this.output.fragmentDeclarations.add(`in ${node.type} ${node.name};`); + this.output.vertexDeclarations.add(`OUT ${node.type} ${node.name};`); + this.output.fragmentDeclarations.add(`IN ${node.type} ${node.name};`); } resetGLSLContext() { @@ -1364,6 +1390,11 @@ function shadergenerator(p5, fn) { fn.instanceID = function() { return variableConstructor('gl_InstanceID', 'int'); } + + fn.getTexture = function(...userArgs) { + const props = { args: ['sampler2D', 'vec2'], returnType: 'vec4', isp5Function: true }; + return fnNodeConstructor('getTexture', userArgs, props); + } // Generating uniformFloat, uniformVec, createFloat, etc functions // Maps a GLSL type to the name suffix for method names From b89c506065b7f0275732885500b04e858ae1456f Mon Sep 17 00:00:00 2001 From: 23036879 Date: Wed, 2 Apr 2025 21:02:53 +0100 Subject: [PATCH 45/49] ifStatement dependents fixed --- src/webgl/ShaderGenerator.js | 71 ++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index f0b4676ba6..08f696d76b 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -37,7 +37,6 @@ function shadergenerator(p5, fn) { } const generator = new ShaderGenerator(generatorFunction, this, options.srcLocations); const generatedModifyArgument = generator.generate(); - console.log(generatedModifyArgument['Vertex getCameraInputs']) return oldModify.call(this, generatedModifyArgument); } else { @@ -307,33 +306,19 @@ function shadergenerator(p5, fn) { return this.usedInConditional; } - dependentsUsedInConditional() { - let thisDeps = Array.isArray(this.dependsOn) ? this.dependsOn : this.dependsOn.nodesArray - thisDeps = thisDeps.map((d) => { - return Array.isArray(d) ? d[0].parent : d; - }); - let depsUsed = thisDeps.filter(d => d.isUsedInConditional()); - return depsUsed; - } - - checkConditionalDependencies(context, dependentOnConditional) { + checkConditionalDependencies(context) { context.ifs.forEach((statement) => { - if (statement.insertionPoint > -1) return; - if (statement.dependsOn.includes(this) - && !statement.dependeciesFulfilled.includes(this)) { - statement.dependeciesFulfilled.push(this); - this.oldName = this.toGLSLBase(context); - this.temporaryVariable = `temp_${context.getNextID()}`; - context.declarations.push( - ` ${this.type} ${this.toGLSLBase(context)} = ${this.oldName};` - ); + const isUsedSatisfied = () => statement.usedInSatisfied.length >= 1; + const isDepsSatisfied = () => statement.dependsOn.length === statement.dependsOnSatisfied.length; + if (statement.insertionPoint > -1 || !statement.usedIn.length) return; + if (statement.dependsOn.includes(this) && !statement.dependsOnSatisfied.includes(this)) { + statement.dependsOnSatisfied.push(this); } - console.log(dependentOnConditional) - if (dependentOnConditional.includes(this)) { - console.log(context.declarations.join('\n')) - } - if (statement.dependsOn.length === statement.dependeciesFulfilled) { - statement.saveState(context); + if (statement.usedIn.includes(this) && !statement.usedInSatisfied.includes(this)) { + statement.usedInSatisfied.push(this); + } + if (isDepsSatisfied() && isUsedSatisfied()) { + statement.saveState(context, isDepsSatisfied(), isUsedSatisfied()); } }); } @@ -364,10 +349,7 @@ function shadergenerator(p5, fn) { } else { result = this.toGLSL(context); } - const depsUsedInConditional = this.dependentsUsedInConditional(); - if (this.isUsedInConditional() || depsUsedInConditional.length > 0) { - this.checkConditionalDependencies(context, depsUsedInConditional) - }; + this.checkConditionalDependencies(context) return result; } @@ -845,8 +827,11 @@ function shadergenerator(p5, fn) { class ConditionalNode { constructor(condition, branchCallback) { this.dependsOn = []; + this.usedIn = []; + this.dependsOnSatisfied = []; + this.usedInSatisfied = []; + this.states = []; this.if(condition, branchCallback); - this.dependeciesFulfilled = []; this.insertionPoint = -1; this.elseIfs = []; this.elseBranch = null; @@ -877,10 +862,13 @@ function shadergenerator(p5, fn) { return new ConditionalDiscard(this.condition); }; - saveState(context) { - if (this.insertionPoint = -1) { - this.insertionPoint = context.declarations.length; - } + saveState(context, usedInSatisfied, dependsOnSatisfied) { + this.states.push({ + line: context.declarations.length, + usedInSatisfied, + dependsOnSatisfied + }); + this.insertionPoint = context.declarations.length - 1; } toGLSL(context) { @@ -1207,6 +1195,7 @@ function shadergenerator(p5, fn) { } this.context.ifs.forEach((statement) => { + if (statement.usedIn.length === 0) { return; } const lines = statement.toGLSL(this.context); this.context.declarations.splice(statement.insertionPoint, 0, lines); }) @@ -1432,12 +1421,14 @@ function shadergenerator(p5, fn) { } function fnNodeConstructor(name, userArgs, properties, isInternal) { - const node = new FunctionCallNode(name, userArgs, properties, isInternal) - if (node.type.startsWith('vec')) { - return dynamicAddSwizzleTrap(node); - } else { - return node; + let node = new FunctionCallNode(name, userArgs, properties, isInternal); + node = dynamicAddSwizzleTrap(node); + if (node.args.some(arg => arg.isUsedInConditional())) { + GLOBAL_SHADER.context.ifs.forEach(statement => { + statement.usedIn.push(node); + }); } + return node; } const nodeConstructors = { From 6adfbe0e042e3aa61075159be0d2461a24fcf7a4 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 4 Apr 2025 10:22:19 +0100 Subject: [PATCH 46/49] dependsOn prototype simplified --- src/webgl/ShaderGenerator.js | 96 +++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 41 deletions(-) diff --git a/src/webgl/ShaderGenerator.js b/src/webgl/ShaderGenerator.js index 08f696d76b..3dfe92e797 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -297,8 +297,9 @@ function shadergenerator(p5, fn) { this.useTemp = true; } - assertUsedInConditional() { + assertUsedInConditional(branch) { this.usedInConditional = true; + this.usedIn.push(branch); this.forceTemporaryVariable(); } @@ -311,7 +312,7 @@ function shadergenerator(p5, fn) { const isUsedSatisfied = () => statement.usedInSatisfied.length >= 1; const isDepsSatisfied = () => statement.dependsOn.length === statement.dependsOnSatisfied.length; if (statement.insertionPoint > -1 || !statement.usedIn.length) return; - if (statement.dependsOn.includes(this) && !statement.dependsOnSatisfied.includes(this)) { + if (statement.dependsOn.some(d => d.node === this) && !statement.dependsOnSatisfied.includes(this)) { statement.dependsOnSatisfied.push(this); } if (statement.usedIn.includes(this) && !statement.usedInSatisfied.includes(this)) { @@ -330,22 +331,17 @@ function shadergenerator(p5, fn) { let oldLength = context.declarations.length; result = this.getTemporaryVariable(context); let diff = context.declarations.length - 1 - oldLength; - if (Array.isArray(this.dependsOn)){ - this.dependsOn.forEach(d => { - context.updateComponents(d, diff > 0 ? diff : undefined); - }); - } else { - // Update the incorrect components - this.dependsOn.nodesArray.forEach((nodeDependency, i) => { - const originalComponents = this.dependsOn[i]; - const currentComponents = nodeDependency.componentNames.map(name => nodeDependency[name]); - if (!originalComponents) return; - const dependencies = originalComponents.map((component, i) => - component === currentComponents[i] - ) - context.updateComponents(nodeDependency, diff > 0 ? diff : undefined, dependencies); - }) - } + diff = diff > 0 ? diff : undefined; + this.dependsOn.forEach(dependency => { + if (dependency.isVector) { + const dependencies = dependency.originalComponents.map((component, i) => + component === dependency.currentComponents[i] + ); + context.updateComponents(dependency.node, diff, dependencies); + } else { + context.updateComponents(dependency.node, diff); + } + }); } else { result = this.toGLSL(context); } @@ -600,18 +596,6 @@ function shadergenerator(p5, fn) { super(isInternal, functionSignature.returnType); - if (userArgs.find(arg => isVectorNode(arg))) { - this.dependsOn = { nodesArray: [] }; - } - userArgs.forEach((arg, i) => { - arg.usedIn.push(this); - let arr = Array.isArray(this.dependsOn) ? this.dependsOn : this.dependsOn.nodesArray; - if (isVectorType(arg)) { - this.dependsOn[i] = []; - arg.componentNames.forEach(name => this.dependsOn[i].push(arg[name])); - } - arr.push(arg); - }); this.name = name; this.args = userArgs; this.argumentTypes = functionSignature.args; @@ -873,7 +857,7 @@ function shadergenerator(p5, fn) { toGLSL(context) { const oldLength = context.declarations.length; - this.dependsOn.forEach(dep => context.updateComponents(dep)); + this.dependsOn.forEach(dep => context.updateComponents(dep.node)); const newLength = context.declarations.length; const diff = newLength - oldLength; this.insertionPoint += diff; @@ -939,14 +923,14 @@ function shadergenerator(p5, fn) { } node = node.parent ? node.parent : node; value = value.parent ? value.parent : value; - if ([node, value].some(n => this.dependsOn.includes(n))) { + if ([node, value].some(n => this.dependsOn.some(d=>d.node===n))) { return; } - node.assertUsedInConditional(); - this.dependsOn.push(node) + node.assertUsedInConditional(this); + this.dependsOn.push(makeDependencyObject(node)) if (value.shouldUseTemporaryVariable()) { - value.assertUsedInConditional(); - this.dependsOn.push(value); + value.assertUsedInConditional(this); + this.dependsOn.push(makeDependencyObject(value)); } } @@ -1071,7 +1055,7 @@ function shadergenerator(p5, fn) { } function isConditionalNode(node) { - return (node instanceof ConditionalNode) + return (node instanceof ConditionalNode || node instanceof BranchNode) } function hasTemporaryVariable(node) { @@ -1302,6 +1286,28 @@ function shadergenerator(p5, fn) { } // User function helpers + function makeDependencyObject(dep) { + if (isVectorType(dep)) { + return { + node: dep, + isVector: true, + originalComponents: [...dep.componentNames.map(name => dep[name])], + get currentComponents() { + return dep.componentNames.map(name => dep[name]); + } + }; + } else { + return { + node: dep, + isVector: false + }; + } + } + + function makeDependencyArray(dependencies) { + return dependencies.map(dep => makeDependencyObject(dep)); + } + function conformVectorParameters(value, vectorDimensions) { // Allow arguments as arrays or otherwise. The following are all equivalent: // ([0,0,0,0]) (0,0,0,0) (0) ([0]) @@ -1423,11 +1429,19 @@ function shadergenerator(p5, fn) { function fnNodeConstructor(name, userArgs, properties, isInternal) { let node = new FunctionCallNode(name, userArgs, properties, isInternal); node = dynamicAddSwizzleTrap(node); - if (node.args.some(arg => arg.isUsedInConditional())) { - GLOBAL_SHADER.context.ifs.forEach(statement => { - statement.usedIn.push(node); + node.dependsOn = makeDependencyArray(node.args); + const dependsOnConditionals = node.args.map(arg => { + const conditionals = arg.usedIn.filter(n => isConditionalNode(n)).map(c => { + if (c instanceof BranchNode) { + return c.parent; + } else { + return c; + } }); - } + return conditionals; + }).flat(); + dependsOnConditionals.forEach(conditional => conditional.usedIn.push(node)); + return node; } From fa277e55c8fb5d6743d186b51429e85de8c85316 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Fri, 4 Apr 2025 10:25:44 +0100 Subject: [PATCH 47/49] fix typo in documentation for vertexProperty --- src/shape/vertex.js | 2 +- src/webgl/p5.Geometry.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shape/vertex.js b/src/shape/vertex.js index cb7a9a5228..ecce4a6c43 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -1418,7 +1418,7 @@ function vertex(p5, fn){ /** Sets the shader's vertex property or attribute variables. * - * An vertex property or vertex attribute is a variable belonging to a vertex in a shader. p5.js provides some + * A vertex property, or vertex attribute, is a variable belonging to a vertex in a shader. p5.js provides some * default properties, such as `aPosition`, `aNormal`, `aVertexColor`, etc. These are * set using vertex(), normal() * and fill() respectively. Custom properties can also diff --git a/src/webgl/p5.Geometry.js b/src/webgl/p5.Geometry.js index 3f1a322d0a..258c9bb7cd 100644 --- a/src/webgl/p5.Geometry.js +++ b/src/webgl/p5.Geometry.js @@ -1736,7 +1736,7 @@ class Geometry { /** Sets the shader's vertex property or attribute variables. * - * An vertex property or vertex attribute is a variable belonging to a vertex in a shader. p5.js provides some + * A vertex property, or vertex attribute, is a variable belonging to a vertex in a shader. p5.js provides some * default properties, such as `aPosition`, `aNormal`, `aVertexColor`, etc. These are * set using vertex(), normal() * and fill() respectively. Custom properties can also From bc12e739df7ac2163c382ddfdea5c7f99d570532 Mon Sep 17 00:00:00 2001 From: 23036879 Date: Sat, 5 Apr 2025 11:13:22 +0100 Subject: [PATCH 48/49] fix bad ref to context --- 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 3dfe92e797..b1b75cd251 100644 --- a/src/webgl/ShaderGenerator.js +++ b/src/webgl/ShaderGenerator.js @@ -1269,7 +1269,7 @@ function shadergenerator(p5, fn) { return node[name] }); const replacement = nodeConstructors[node.type](components); - const line = ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this.context)};` + const line = ` ${node.temporaryVariable} = ${replacement.toGLSLBase(this)};` lines.push(line); } if (_emplaceAt) { From 73345ff709d7b808321c0775331cb7c99af3b71a Mon Sep 17 00:00:00 2001 From: 23036879 Date: Sat, 5 Apr 2025 11:23:50 +0100 Subject: [PATCH 49/49] sketch finish --- preview/global/sketch.js | 50 ++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/preview/global/sketch.js b/preview/global/sketch.js index bcc3363c1d..b0cd6c8045 100644 --- a/preview/global/sketch.js +++ b/preview/global/sketch.js @@ -12,22 +12,16 @@ let pixellizeShader; let fresnelShader; let bloomShader; - function fresnelShaderCallback() { const fresnelPower = uniformFloat(2); const fresnelBias = uniformFloat(-0.1); const fresnelScale = uniformFloat(2); getCameraInputs((inputs) => { - let myValue = [1,0,0]; let n = normalize(inputs.normal); - If(n == n, () => assign(myValue, 1)); let v = normalize(-inputs.position); let base = 1.0 - dot(n, v); let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias; - - - let col = mix(myValue, [1, .5, .7], fresnel); - + let col = mix([0, 0, 0], [1, .5, .7], fresnel); inputs.color = [col, 1]; return inputs; }); @@ -48,7 +42,7 @@ function starShaderCallback() { let r = skyRadius; r *= 1.5 * sin(phi); let x = r * sin(phi) * cos(theta); - let y = r *1.5* cos(phi); + let y = r * 1.5 * cos(phi); let z = r * sin(phi) * sin(theta); return [x, y, z]; } @@ -66,7 +60,7 @@ function starShaderCallback() { } function pixellizeShaderCallback() { - const pixelSize = uniformFloat(()=> width/2); + const pixelSize = uniformFloat(()=> width*.75); getColor((input, canvasContent) => { let coord = input.texCoord; coord = floor(coord * pixelSize) / pixelSize; @@ -80,7 +74,7 @@ function bloomShaderCallback() { getColor((input, canvasContent) => { const blurredCol = texture(canvasContent, input.texCoord); const originalCol = texture(preBlur, input.texCoord); - const brightPass = max(originalCol - 0.3, 0.7) * 1.2; + const brightPass = max(originalCol, 0.3) * 1.5; const bloom = originalCol + blurredCol * brightPass; return bloom; }); @@ -88,43 +82,43 @@ function bloomShaderCallback() { async function setup(){ createCanvas(windowWidth, windowHeight, WEBGL); - stars = buildGeometry(() => sphere(20, 4, 2)) + stars = buildGeometry(() => sphere(30, 4, 2)) originalFrameBuffer = createFramebuffer(); - // starShader = baseMaterialShader().modify(starShaderCallback); - // starStrokeShader = baseStrokeShader().modify(starShaderCallback) + starShader = baseMaterialShader().modify(starShaderCallback); + starStrokeShader = baseStrokeShader().modify(starShaderCallback) fresnelShader = baseColorShader().modify(fresnelShaderCallback); - // bloomShader = baseFilterShader().modify(bloomShaderCallback); - // pixellizeShader = baseFilterShader().modify(pixellizeShaderCallback); + bloomShader = baseFilterShader().modify(bloomShaderCallback); + pixellizeShader = baseFilterShader().modify(pixellizeShaderCallback); } function draw(){ originalFrameBuffer.begin(); background(0); - orbitControl(); + orbitControl(); - // push() - // strokeWeight(4) - // stroke(255,0,0) - // rotateX(PI/2 + millis() * 0.0005); - // fill(255,100, 150) - // strokeShader(starStrokeShader) - // shader(starShader); - // model(stars, 5000); - // pop() + push() + strokeWeight(4) + stroke(255,0,0) + rotateX(PI/2 + millis() * 0.0005); + fill(255,100, 150) + strokeShader(starStrokeShader) + shader(starShader); + model(stars, 2000); + pop() push() shader(fresnelShader) noStroke() sphere(500); pop() + filter(pixellizeShader); - // filter(pixellizeShader); originalFrameBuffer.end(); imageMode(CENTER) image(originalFrameBuffer, 0, 0) - // filter(BLUR, 20) - // filter(bloomShader); + filter(BLUR, 20) + filter(bloomShader); }