Skip to content

Commit

Permalink
Merge pull request #6376 from davepagurek/fix/fbo-clip
Browse files Browse the repository at this point in the history
Fix clip() on both the main canvas and framebuffers
  • Loading branch information
Qianqianye authored Aug 31, 2023
2 parents bb4fb81 + 7257558 commit 36437b3
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 6 deletions.
5 changes: 4 additions & 1 deletion src/webgl/p5.Framebuffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ class Framebuffer {
this.target = target;
this.target._renderer.framebuffers.add(this);

this._isClipApplied = false;

/**
* A <a href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference
* /Global_Objects/Uint8ClampedArray' target='_blank'>Uint8ClampedArray</a>
Expand Down Expand Up @@ -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 {
Expand All @@ -975,6 +977,7 @@ class Framebuffer {
this.target._renderer._origViewport.height
);
}
this.target._renderer._applyStencilTestIfClipping();
}

/**
Expand Down
38 changes: 33 additions & 5 deletions src/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,9 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
this._isErasing = false;

// clipping
this._clipDepth = null;
this._clipDepths = [];
this._isClipApplied = false;
this._stencilTestOn = false;

// lights
this._enableLighting = false;
Expand Down Expand Up @@ -1159,12 +1161,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
Expand Down Expand Up @@ -1201,15 +1211,18 @@ 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();
}

_clearClip() {
this.GL.clearStencil(1);
this.GL.clear(this.GL.STENCIL_BUFFER_BIT);
this._clipDepth = null;
if (this._clipDepths.length > 0) {
this._clipDepths.pop();
}
this.drawTarget()._isClipApplied = false;
}

/**
Expand Down Expand Up @@ -1557,11 +1570,26 @@ 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);
}
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;
}
}
}
resetMatrix() {
this.uMVMatrix.set(
Expand Down
52 changes: 52 additions & 0 deletions test/unit/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -2265,5 +2265,57 @@ 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);
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);
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]
);
}
}
);
});
});

0 comments on commit 36437b3

Please sign in to comment.