diff --git a/src/core/filterShaders.js b/src/core/filterShaders.js index 20baa8f000..d0398bb5bd 100644 --- a/src/core/filterShaders.js +++ b/src/core/filterShaders.js @@ -102,11 +102,20 @@ export function makeFilterShader(renderer, operation, p5) { const sampleCoord = uv + p5.vec2(sample, sample) / inputs.canvasSize * direction; const weight = quadWeight(sample, (numSamples - 1.0) * 0.5 * spacing); - avg += weight * p5.getTexture(canvasContent, sampleCoord); + const texSample = p5.getTexture(canvasContent, sampleCoord); + avg += weight * texSample * p5.vec4( + texSample.a, texSample.a, texSample.a, 1 + ); total += weight; } - return avg / total; + const blended = avg / total; + return p5.vec4( + blended.r / blended.a, + blended.g / blended.a, + blended.b / blended.a, + blended.a + ); }); }, { p5 }); diff --git a/src/core/p5.Renderer3D.js b/src/core/p5.Renderer3D.js index 89f074a8c1..8d96778fac 100644 --- a/src/core/p5.Renderer3D.js +++ b/src/core/p5.Renderer3D.js @@ -1669,6 +1669,9 @@ export class Renderer3D extends Renderer { //// TEXT SUPPORT METHODS ////////////////////////////// + _beforeDrawText() {} + _afterDrawText() {} + textCanvas() { if (!this._textCanvas) { this._textCanvas = document.createElement('canvas'); diff --git a/src/strands/strands_for.js b/src/strands/strands_for.js index 9d4092f39d..59a5ec0623 100644 --- a/src/strands/strands_for.js +++ b/src/strands/strands_for.js @@ -297,8 +297,8 @@ export class StrandsFor { const scopeEndBlock = CFG.createBasicBlock(cfg, BlockType.SCOPE_END); CFG.addEdge(cfg, updateBlock, scopeEndBlock); - // Loop back to break check - CFG.addEdge(cfg, scopeEndBlock, breakCheckBlock); + // Connect end of for loop to the merge agter the loop + CFG.addEdge(cfg, scopeEndBlock, mergeBlock); // Break condition exits to merge CFG.addEdge(cfg, breakCheckBlock, mergeBlock); diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index c143581702..a7037d3759 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -312,6 +312,17 @@ class RendererGL extends Renderer3D { } } + ////////////////////////////////////////////// + // Text + ////////////////////////////////////////////// + + _beforeDrawText() { + this.GL.pixelStorei(this.GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + } + _afterDrawText() { + this.GL.pixelStorei(this.GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); + } + ////////////////////////////////////////////// // Setting ////////////////////////////////////////////// @@ -413,9 +424,10 @@ class RendererGL extends Renderer3D { gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); - // Make sure all images are loaded into the canvas non-premultiplied so that - // they can be handled consistently in shaders. - gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + // Make sure all images are loaded into the canvas premultiplied so that + // they match the way we render colors. This will make framebuffer textures + // be encoded the same way as textures from everything else. + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); this._viewport = this.drawingContext.getParameter( this.drawingContext.VIEWPORT ); diff --git a/src/webgl/shaders/light_texture.frag b/src/webgl/shaders/light_texture.frag index f61fc39cca..e02083b97b 100644 --- a/src/webgl/shaders/light_texture.frag +++ b/src/webgl/shaders/light_texture.frag @@ -14,12 +14,13 @@ void main(void) { } else { vec4 baseColor = isTexture - // Textures come in with non-premultiplied alpha. Apply tint. - ? TEXTURE(uSampler, vVertTexCoord) * (uTint/255.) - // Colors come in with non-premultiplied alpha. - : vColor; - // Convert to premultiplied alpha for consistent output - baseColor.rgb *= baseColor.a; + // Textures come in with premultiplied alpha. To apply tint and still have + // premultiplied alpha output, we need to multiply the RGB channels by the + // tint RGB, and all channels by the tint alpha. + ? TEXTURE(uSampler, vVertTexCoord) * vec4(uTint.rgb/255., 1.) * (uTint.a/255.) + // Colors come in with unmultiplied alpha, so we need to multiply the RGB + // channels by alpha to convert it to premultiplied alpha. + : vec4(vColor.rgb * vColor.a, vColor.a); OUT_COLOR = vec4(baseColor.rgb * vDiffuseColor + vSpecularColor, baseColor.a); } } diff --git a/src/webgl/shaders/phong.frag b/src/webgl/shaders/phong.frag index 5711a01e6d..78cfb76163 100644 --- a/src/webgl/shaders/phong.frag +++ b/src/webgl/shaders/phong.frag @@ -47,8 +47,13 @@ void main(void) { inputs.texCoord = vTexCoord; inputs.ambientLight = uAmbientColor; inputs.color = isTexture - ? TEXTURE(uSampler, vTexCoord) * (uTint/255.) + ? TEXTURE(uSampler, vTexCoord) * (vec4(uTint.rgb/255., 1.) * uTint.a/255.) : vColor; + if (isTexture && inputs.color.a > 0.0) { + // Textures come in with premultiplied alpha. Temporarily unpremultiply it + // so hooks users don't have to think about premultiplied alpha. + inputs.color.rgb /= inputs.color.a; + } inputs.shininess = uShininess; inputs.metalness = uMetallic; inputs.ambientMaterial = uHasSetAmbient ? uAmbientMatColor.rgb : inputs.color.rgb; diff --git a/src/webgl/shaders/webgl2Compatibility.glsl b/src/webgl/shaders/webgl2Compatibility.glsl index 15bfe7ed24..8c9dbddec6 100644 --- a/src/webgl/shaders/webgl2Compatibility.glsl +++ b/src/webgl/shaders/webgl2Compatibility.glsl @@ -26,5 +26,9 @@ out vec4 outColor; #endif #ifdef FRAGMENT_SHADER -#define getTexture TEXTURE +vec4 getTexture(in sampler2D content, vec2 coord) { + vec4 color = TEXTURE(content, coord); + if (color.a > 0.) color.rgb /= color.a; + return color; +} #endif diff --git a/src/webgl/text.js b/src/webgl/text.js index c8db34fc87..083ec0364b 100644 --- a/src/webgl/text.js +++ b/src/webgl/text.js @@ -766,7 +766,7 @@ function text(p5, fn) { // this will have to do for now... sh.setUniform('uMaterialColor', curFillColor); - + this._beforeDrawText(); this.glyphDataCache = this.glyphDataCache || new Set(); try { @@ -827,6 +827,7 @@ function text(p5, fn) { this.states.setValue('strokeColor', doStroke); this.states.setValue('drawMode', drawMode); + this._afterDrawText(); this.pop(); } }; diff --git a/src/webgpu/p5.RendererWebGPU.js b/src/webgpu/p5.RendererWebGPU.js index 5445a0aa80..8ed51bd96a 100644 --- a/src/webgpu/p5.RendererWebGPU.js +++ b/src/webgpu/p5.RendererWebGPU.js @@ -1818,12 +1818,12 @@ function rendererWebGPU(p5, fn) { } // Find the input parameter name from the main function signature (anchored to start) - const inputMatch = postMain.match(/^\s*\((\w+):\s*\w+\)/); + const inputMatch = main.match(/fn main\s*\((\w+):\s*\w+\)/); if (inputMatch) { const inputVarName = inputMatch[1]; initStatements = initStatements.replace(/INPUT_VAR/g, inputVarName); // Insert after the main function parameter but before any other code (anchored to start) - postMain = postMain.replace(/^(\s*\(\w+:\s*\w+\)\s*[^{]*\{)/, `$1\n${initStatements}`); + postMain = initStatements + postMain; } } } diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js index 6da4dd61e7..e9645b8426 100644 --- a/test/unit/webgl/p5.Shader.js +++ b/test/unit/webgl/p5.Shader.js @@ -1128,6 +1128,40 @@ suite('p5.Shader', function() { const pixelColor = myp5.get(25, 25); assert.approximately(pixelColor[0], 127, 5); // 0.5 * 255 ≈ 127 }); + + test('handle statements after for loop before return', () => { + myp5.createCanvas(50, 50, myp5.WEBGL); + + const testShader = myp5.baseMaterialShader().modify(() => { + myp5.getPixelInputs(inputs => { + let avg = myp5.vec3(0.0); + let total = 0.0; + + // Simulate blur-like accumulation in for loop + for (let i = 0; i < 3; i++) { + const sample = myp5.vec3(0.2, 0.1, 0.3); + const weight = 1.0; + avg += weight * sample; + total += weight; + } + + const blended = avg / total; + + inputs.color = [blended.x, blended.y, blended.z, 1.0]; + return inputs; + }); + }, { myp5 }); + + myp5.noStroke(); + myp5.shader(testShader); + myp5.plane(myp5.width, myp5.height); + + // Expected result: (3 * [0.2, 0.1, 0.3]) / 3 = [0.2, 0.1, 0.3] + const pixelColor = myp5.get(25, 25); + assert.approximately(pixelColor[0], 51, 5); // 0.2 * 255 ≈ 51 + assert.approximately(pixelColor[1], 25, 5); // 0.1 * 255 ≈ 25 + assert.approximately(pixelColor[2], 77, 5); // 0.3 * 255 ≈ 77 + }); }); suite('passing data between shaders', () => {