From 40152c9fe4f5c9931fce865d2490b80fa9958d25 Mon Sep 17 00:00:00 2001 From: Viet Nguyen Date: Tue, 28 Oct 2025 19:43:26 -0700 Subject: [PATCH 1/3] Enhance p5.Shader.modify to normalize return statements in shader hooks --- src/webgl/p5.Shader.js | 53 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/src/webgl/p5.Shader.js b/src/webgl/p5.Shader.js index a82f112361..2a2e0f0489 100644 --- a/src/webgl/p5.Shader.js +++ b/src/webgl/p5.Shader.js @@ -317,8 +317,8 @@ p5.Shader = class { for (const key in this.hooks.vertex) { console.log( (this.hooks.modified.vertex[key] ? '[MODIFIED] ' : '') + - key + - this.hooks.vertex[key] + key + + this.hooks.vertex[key] ); } console.log(''); @@ -326,8 +326,8 @@ p5.Shader = class { for (const key in this.hooks.fragment) { console.log( (this.hooks.modified.fragment[key] ? '[MODIFIED] ' : '') + - key + - this.hooks.fragment[key] + key + + this.hooks.fragment[key] ); } console.log(''); @@ -431,6 +431,39 @@ p5.Shader = class { */ modify(hooks) { p5._validateParameters('p5.Shader.modify', arguments); + + // Internal helper to normalize shader hooks + // Automatically appends a return statement when: + // - The hook's return type matches its first parameter type (e.g. Inputs getX(Inputs x)) + // - No explicit 'return' is present in the user-provided code + const normalizeReturnIfMissing = (hookDef, impl) => { + // Example: hookDef = "Inputs getPixelInputs" + // Example: impl = "(Inputs inputs) { ... }" + const defMatch = /^(\w+)\s+(\w+)$/.exec(hookDef.trim()); + if (!defMatch) return impl; + const [, returnType] = defMatch; + + // Skip void-return hooks + if (returnType === 'void') return impl; + + // Strip // and /* */ comments before searching for 'return' + const withoutComments = impl + .replace(/\/\/.*$/gm, '') + .replace(/\/\*[\s\S]*?\*\//g, ''); + if (/\breturn\b/.test(withoutComments)) return impl; + + // Extract the first parameter type and name + const sigMatch = /^\s*\(\s*([\w\s[\]]+)\s+(\w+)\s*\)/.exec(impl); + if (!sigMatch) return impl; + const [, paramType, paramName] = sigMatch; + + // Only normalize when return type matches first param type + if (paramType.trim() !== returnType.trim()) return impl; + + // Append 'return ;' before the last closing brace + return impl.replace(/\}\s*$/, ` return ${paramName};\n}`); + }; + const newHooks = { vertex: {}, fragment: {}, @@ -446,9 +479,9 @@ p5.Shader = class { newHooks.fragment.declarations = (newHooks.fragment.declarations || '') + '\n' + hooks[key]; } else if (this.hooks.vertex[key]) { - newHooks.vertex[key] = hooks[key]; + newHooks.vertex[key] = normalizeReturnIfMissing(key, hooks[key]); } else if (this.hooks.fragment[key]) { - newHooks.fragment[key] = hooks[key]; + newHooks.fragment[key] = normalizeReturnIfMissing(key, hooks[key]); } else { newHooks.helpers[key] = hooks[key]; } @@ -1331,8 +1364,8 @@ p5.Shader = class { ) { console.log( '🌸 p5.js says: ' + - "You're trying to use a number as the data for a texture." + - 'Please use a texture.' + "You're trying to use a number as the data for a texture." + + 'Please use a texture.' ); return this; } @@ -1372,8 +1405,8 @@ p5.Shader = class { ) { console.log( '🌸 p5.js says: ' + - "You're trying to use a number as the data for a texture." + - 'Please use a texture.' + "You're trying to use a number as the data for a texture." + + 'Please use a texture.' ); break; } From 7d8251678c37aee22d1a14c4279fe04705f85a20 Mon Sep 17 00:00:00 2001 From: Viet Nguyen Date: Fri, 31 Oct 2025 14:39:12 -0700 Subject: [PATCH 2/3] Added shader modification tests for auto-return and normalization behavior --- test/unit/webgl/p5.Shader.js | 89 ++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js index 044bf6ec0a..c32297c36f 100644 --- a/test/unit/webgl/p5.Shader.js +++ b/test/unit/webgl/p5.Shader.js @@ -385,6 +385,95 @@ suite('p5.Shader', function() { }); expect(modified.fragSrc()).to.match(/#define AUGMENTED_HOOK_getVertexColor/); }); + + test('auto-returns when param/return types match (no explicit return)', function() { + const modified = myShader.modify({ + 'vec4 getVertexColor': `(vec4 c) { + c.rgb = vec3(1.0, 0.0, 0.0); + }` + }); + expect(modified.fragSrc()).to.match(/#define AUGMENTED_HOOK_getVertexColor/); + expect(modified.fragSrc()).to.match(/getVertexColor[\s\S]*?\{[\s\S]*?return\s+c\s*;[\s\S]*?\}/); + }); + + test('explicit return is preserved and not duplicated', function() { + const modified = myShader.modify({ + 'vec4 getVertexColor': `(vec4 c) { + c.rgb *= 0.5; + return c; + }` + }); + expect(modified.fragSrc()).to.match(/#define AUGMENTED_HOOK_getVertexColor/); + + const body = modified.fragSrc().match(/getVertexColor[\s\S]*?\{([\s\S]*?)\}/)[1]; + const matches = (body.match(/return\s+c\s*;/g) || []).length; + expect(matches).to.equal(1); + }); + + test('commented return does not block normalization', function() { + const modified = myShader.modify({ + 'vec4 getVertexColor': `(vec4 c) { + /* return c; */ + // return c; + c.a = 1.0; + }` + }); + expect(modified.fragSrc()).to.match(/#define AUGMENTED_HOOK_getVertexColor/); + expect(modified.fragSrc()).to.match(/getVertexColor[\s\S]*?return\s+c\s*;/); + }); + + test('void hooks are not normalized', function() { + const modified = myShader.modify({ + 'void beforeFragment': `() { + // no-op + }`, + 'vec4 getVertexColor': `(vec4 c) { + return c; + }` + }); + + const src = modified.fragSrc(); + const bfBody = src.match(/HOOK_beforeFragment[\s\S]*?\{([\s\S]*?)\}/); + if (bfBody) { + expect(bfBody[1]).not.to.match(/\breturn\b/); + } + }); + + test('mismatched types are not auto-returned and cause compiler error', function() { + const bad = myp5.createShader( + ` + precision highp float; + attribute vec3 aPosition; + uniform mat4 uModelViewMatrix, uProjectionMatrix; + void main() { + gl_Position = uProjectMatrix * uModelViewMatrix * vec4(aPosition, 1.0); + } + `, + ` + precision highp float; + void main() { + gl_FragColor = HOOK_getVertexColor(vec4(0.0, 1.0, 0.0, 1.0)); + } + `, + { + fragment: { + 'vec4 getVertexColor': '(vec2 uv) { }' + } + } + ); + + let threw = false; + try { + bad.bindShader(); + } catch (e) { + threw = true; + } finally { + try { + bad.unbindShader(); + } catch (_) {} + } + expect(threw).to.equal(true); + }); }); }); }); From 28421e8dc0c935863869b534a4f3da9dfcb98e7b Mon Sep 17 00:00:00 2001 From: Viet Nguyen Date: Sun, 2 Nov 2025 15:12:41 -0800 Subject: [PATCH 3/3] Revert indentation and formatting --- src/webgl/p5.Shader.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/webgl/p5.Shader.js b/src/webgl/p5.Shader.js index 2a2e0f0489..2628e73f2b 100644 --- a/src/webgl/p5.Shader.js +++ b/src/webgl/p5.Shader.js @@ -317,8 +317,8 @@ p5.Shader = class { for (const key in this.hooks.vertex) { console.log( (this.hooks.modified.vertex[key] ? '[MODIFIED] ' : '') + - key + - this.hooks.vertex[key] + key + + this.hooks.vertex[key] ); } console.log(''); @@ -326,8 +326,8 @@ p5.Shader = class { for (const key in this.hooks.fragment) { console.log( (this.hooks.modified.fragment[key] ? '[MODIFIED] ' : '') + - key + - this.hooks.fragment[key] + key + + this.hooks.fragment[key] ); } console.log(''); @@ -1364,8 +1364,8 @@ p5.Shader = class { ) { console.log( '🌸 p5.js says: ' + - "You're trying to use a number as the data for a texture." + - 'Please use a texture.' + "You're trying to use a number as the data for a texture." + + 'Please use a texture.' ); return this; } @@ -1405,8 +1405,8 @@ p5.Shader = class { ) { console.log( '🌸 p5.js says: ' + - "You're trying to use a number as the data for a texture." + - 'Please use a texture.' + "You're trying to use a number as the data for a texture." + + 'Please use a texture.' ); break; }