Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix clip() on both the main canvas and framebuffers #6376

Merged
merged 2 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -423,7 +423,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 @@ -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
Expand Down Expand Up @@ -1097,15 +1107,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 @@ -1453,11 +1466,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 @@ -2080,5 +2080,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]
);
}
}
);
});
});