From 1353fbfc6e158fa3d22323f3b35f4ace89af9fd0 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Wed, 23 Aug 2023 09:34:02 -0400 Subject: [PATCH 1/2] Fix clip() on both the main canvas and framebuffers --- src/webgl/p5.RendererGL.js | 17 ++++++++---- test/unit/webgl/p5.RendererGL.js | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index e163d93c32..d77344231a 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -423,7 +423,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { this._isErasing = false; // clipping - this._clipDepth = null; + this._clipDepths = []; // lights this._enableLighting = false; @@ -1097,7 +1097,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { // Mark the depth at which the clip has been applied so that we can clear it // when we pop past this depth - this._clipDepth = this._pushPopDepth; + this._clipDepths.push(this._pushPopDepth); super.endClip(); } @@ -1105,7 +1105,9 @@ p5.RendererGL = class RendererGL extends p5.Renderer { _clearClip() { this.GL.clearStencil(1); this.GL.clear(this.GL.STENCIL_BUFFER_BIT); - this._clipDepth = null; + if (this._clipDepths.length > 0) { + this._clipDepths.pop(); + } } /** @@ -1453,9 +1455,14 @@ p5.RendererGL = class RendererGL extends p5.Renderer { return style; } pop(...args) { - if (this._pushPopDepth === this._clipDepth) { + if ( + this._clipDepths.length > 0 && + this._pushPopDepth === this._clipDepths[this._clipDepths.length - 1] + ) { this._clearClip(); - this.GL.disable(this.GL.STENCIL_TEST); + if (this._clipDepths.length === 0) { + this.GL.disable(this.GL.STENCIL_TEST); + } } super.pop(...args); } diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 4ff0648141..80c59169f4 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -2080,5 +2080,52 @@ suite('p5.RendererGL', function() { // Inside the clipped region should be red assert.deepEqual(getPixel(pixels, 15, 15), [255, 0, 0, 255]); }); + + test( + 'It can mask a separate shape in a framebuffer from the main canvas', + function() { + myp5.createCanvas(50, 50, myp5.WEBGL); + const fbo = myp5.createFramebuffer({ antialias: false }); + myp5.rectMode(myp5.CENTER); + myp5.background('red'); + expect(myp5._renderer._clipDepths.length).to.equal(0); + myp5.push(); + myp5.beginClip(); + myp5.rect(-5, -5, 20, 20); + myp5.endClip(); + expect(myp5._renderer._clipDepths.length).to.equal(1); + + fbo.begin(); + myp5.beginClip(); + myp5.rect(5, 5, 20, 20); + myp5.endClip(); + myp5.fill('blue'); + myp5.rect(0, 0, myp5.width, myp5.height); + expect(myp5._renderer._clipDepths.length).to.equal(2); + fbo.end(); + expect(myp5._renderer._clipDepths.length).to.equal(1); + + myp5.imageMode(myp5.CENTER); + myp5.image(fbo, 0, 0); + myp5.pop(); + expect(myp5._renderer._clipDepths.length).to.equal(0); + + // In the middle of the canvas, the framebuffer's clip and the + // main canvas's clip intersect, so the blue should show through + assert.deepEqual( + myp5.get(myp5.width / 2, myp5.height / 2), + [0, 0, 255, 255] + ); + + // To either side of the center, nothing should be on top of + // the red background color + for (const side of [-1, 1]) { + assert.deepEqual( + myp5.get(myp5.width / 2 + side * 10, myp5.height / 2 + side * 10), + [255, 0, 0, 255] + ); + } + } + ); }); }); From 7257558aebdd7bf684197960600550fda3b84dc1 Mon Sep 17 00:00:00 2001 From: Dave Pagurek Date: Wed, 23 Aug 2023 10:36:55 -0400 Subject: [PATCH 2/2] Keep track of whether clipping is applied to each draw target --- src/webgl/p5.Framebuffer.js | 5 ++++- src/webgl/p5.RendererGL.js | 25 +++++++++++++++++++++++-- test/unit/webgl/p5.RendererGL.js | 5 +++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/webgl/p5.Framebuffer.js b/src/webgl/p5.Framebuffer.js index 102739a2cd..beb6109bc9 100644 --- a/src/webgl/p5.Framebuffer.js +++ b/src/webgl/p5.Framebuffer.js @@ -110,6 +110,8 @@ class Framebuffer { this.target = target; this.target._renderer.framebuffers.add(this); + this._isClipApplied = false; + /** * A Uint8ClampedArray @@ -960,12 +962,12 @@ class Framebuffer { */ end() { const gl = this.gl; + this.target.pop(); const fbo = this.target._renderer.activeFramebuffers.pop(); if (fbo !== this) { throw new Error("It looks like you've called end() while another Framebuffer is active."); } this._beforeEnd(); - this.target.pop(); if (this.prevFramebuffer) { this.prevFramebuffer._beforeBegin(); } else { @@ -975,6 +977,7 @@ class Framebuffer { this.target._renderer._origViewport.height ); } + this.target._renderer._applyStencilTestIfClipping(); } /** diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index d77344231a..a04e17e17e 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -424,6 +424,8 @@ p5.RendererGL = class RendererGL extends p5.Renderer { // clipping this._clipDepths = []; + this._isClipApplied = false; + this._stencilTestOn = false; // lights this._enableLighting = false; @@ -1055,12 +1057,20 @@ p5.RendererGL = class RendererGL extends p5.Renderer { } } + drawTarget() { + return this.activeFramebuffers[this.activeFramebuffers.length - 1] || this; + } + beginClip(options = {}) { super.beginClip(options); + + this.drawTarget()._isClipApplied = true; + const gl = this.GL; gl.clearStencil(0); gl.clear(gl.STENCIL_BUFFER_BIT); gl.enable(gl.STENCIL_TEST); + this._stencilTestOn = true; gl.stencilFunc( gl.ALWAYS, // the test 1, // reference value @@ -1108,6 +1118,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { if (this._clipDepths.length > 0) { this._clipDepths.pop(); } + this.drawTarget()._isClipApplied = false; } /** @@ -1460,11 +1471,21 @@ p5.RendererGL = class RendererGL extends p5.Renderer { this._pushPopDepth === this._clipDepths[this._clipDepths.length - 1] ) { this._clearClip(); - if (this._clipDepths.length === 0) { + } + super.pop(...args); + this._applyStencilTestIfClipping(); + } + _applyStencilTestIfClipping() { + const drawTarget = this.drawTarget(); + if (drawTarget._isClipApplied !== this._stencilTestOn) { + if (drawTarget._isClipApplied) { + this.GL.enable(this.GL.STENCIL_TEST); + this._stencilTestOn = true; + } else { this.GL.disable(this.GL.STENCIL_TEST); + this._stencilTestOn = false; } } - super.pop(...args); } resetMatrix() { this.uMVMatrix.set( diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 80c59169f4..dc821cad00 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -2102,8 +2102,13 @@ suite('p5.RendererGL', function() { myp5.fill('blue'); myp5.rect(0, 0, myp5.width, myp5.height); expect(myp5._renderer._clipDepths.length).to.equal(2); + expect(myp5._renderer.drawTarget()).to.equal(fbo); + expect(fbo._isClipApplied).to.equal(true); fbo.end(); + expect(fbo._isClipApplied).to.equal(false); expect(myp5._renderer._clipDepths.length).to.equal(1); + expect(myp5._renderer.drawTarget()).to.equal(myp5._renderer); + expect(myp5._renderer._isClipApplied).to.equal(true); myp5.imageMode(myp5.CENTER); myp5.image(fbo, 0, 0);