From b42d2a1f251fbc0686343d3314becf5064979054 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Wed, 26 Feb 2025 19:18:47 +0530 Subject: [PATCH 1/6] Updated Stencil Test Case to prevent it from disable after every draw loop --- src/webgl/p5.RendererGL.js | 57 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index eefb3211f3..26298e4e2f 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -427,6 +427,46 @@ class RendererGL extends Renderer { this.drawShapeCount = 1; this.scratchMat3 = new Matrix(3); + + this.isStencilTestOn = false; // Track stencil test state + this._userEnabledStencil = false; // Track whether user enabled stencil + // Override WebGL enable function + const prevEnable = this.drawingContext.enable; + this.drawingContext.enable = (key) => { + if (key === this.drawingContext.STENCIL_TEST) { + // When clip() calls enable(), don't mark as user-enabled + if (!this._clipping) { + this._userEnabledStencil = true; + } + this.isStencilTestOn = true; + } + return prevEnable.call(this.drawingContext, key); + }; + + // Override WebGL disable function + const prevDisable = this.drawingContext.disable; + this.drawingContext.disable = (key) => { + if (key === this.drawingContext.STENCIL_TEST) { + // When pop() disables the stencil test after clip(), + // restore the user's stencil test setting + if (this._clipDepth === this._pushPopDepth) { + this.isStencilTestOn = this._userEnabledStencil; + } else { + this.isStencilTestOn = false; + this._userEnabledStencil = false; + } + } + return prevDisable.call(this.drawingContext, key); + }; + + // Override WebGL getEnabled function + const prevGetEnabled = this.drawingContext.getEnabled; + this.drawingContext.getEnabled = (key) => { + if (key === this.drawingContext.STENCIL_TEST) { + return this.isStencilTestOn; + } + return prevGetEnabled.call(this.drawingContext, key); + }; } ////////////////////////////////////////////// @@ -1024,7 +1064,10 @@ class RendererGL extends Renderer { //Clear depth every frame this.GL.clearStencil(0); this.GL.clear(this.GL.DEPTH_BUFFER_BIT | this.GL.STENCIL_BUFFER_BIT); - this.GL.disable(this.GL.STENCIL_TEST); + if (!this.isStencilTestOn) { + this.GL.disable(this.GL.STENCIL_TEST); + } + } /** @@ -1387,7 +1430,8 @@ class RendererGL extends Renderer { this.drawTarget()._isClipApplied = true; const gl = this.GL; - gl.clearStencil(0); + this._savedStencilTestState = this._userEnabledStencil; + gl.clearStencil(0); gl.clear(gl.STENCIL_BUFFER_BIT); gl.enable(gl.STENCIL_TEST); this._stencilTestOn = true; @@ -1739,6 +1783,11 @@ class RendererGL extends Renderer { this._pushPopDepth === this._clipDepths[this._clipDepths.length - 1] ) { this._clearClip(); + if (!this._savedStencilTestState) { + this.GL.disable(this.GL.STENCIL_TEST); + } + + this._userEnabledStencil = this._savedStencilTestState; } super.pop(...args); this._applyStencilTestIfClipping(); @@ -1750,7 +1799,9 @@ class RendererGL extends Renderer { this.GL.enable(this.GL.STENCIL_TEST); this._stencilTestOn = true; } else { - this.GL.disable(this.GL.STENCIL_TEST); + if (!this.isStencilTestOn) { + this.GL.disable(this.GL.STENCIL_TEST); + } this._stencilTestOn = false; } } From 2e88c827ff79efdda3171f59ed1057197236c713 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Sat, 1 Mar 2025 01:51:03 +0530 Subject: [PATCH 2/6] updated beginClip and pop --- src/webgl/p5.RendererGL.js | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 26298e4e2f..511744e5b8 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -429,15 +429,9 @@ class RendererGL extends Renderer { this.scratchMat3 = new Matrix(3); this.isStencilTestOn = false; // Track stencil test state - this._userEnabledStencil = false; // Track whether user enabled stencil - // Override WebGL enable function const prevEnable = this.drawingContext.enable; this.drawingContext.enable = (key) => { if (key === this.drawingContext.STENCIL_TEST) { - // When clip() calls enable(), don't mark as user-enabled - if (!this._clipping) { - this._userEnabledStencil = true; - } this.isStencilTestOn = true; } return prevEnable.call(this.drawingContext, key); @@ -449,12 +443,13 @@ class RendererGL extends Renderer { if (key === this.drawingContext.STENCIL_TEST) { // When pop() disables the stencil test after clip(), // restore the user's stencil test setting - if (this._clipDepth === this._pushPopDepth) { - this.isStencilTestOn = this._userEnabledStencil; - } else { - this.isStencilTestOn = false; - this._userEnabledStencil = false; - } + // if (this._clipDepth === this._pushPopDepth) { + // this.isStencilTestOn = this._userEnabledStencil; + // } else { + // this.isStencilTestOn = false; + // this._userEnabledStencil = false; + // } + this.isStencilTestOn = false; } return prevDisable.call(this.drawingContext, key); }; @@ -1430,7 +1425,7 @@ class RendererGL extends Renderer { this.drawTarget()._isClipApplied = true; const gl = this.GL; - this._savedStencilTestState = this._userEnabledStencil; + this.isStencilTestOn = this.drawingContext.getEnabled(this.drawingContext.STENCIL_TEST); gl.clearStencil(0); gl.clear(gl.STENCIL_BUFFER_BIT); gl.enable(gl.STENCIL_TEST); @@ -1783,11 +1778,11 @@ class RendererGL extends Renderer { this._pushPopDepth === this._clipDepths[this._clipDepths.length - 1] ) { this._clearClip(); - if (!this._savedStencilTestState) { - this.GL.disable(this.GL.STENCIL_TEST); + if (this.isStencilTestOn) { + this.GL.enable(this.GL.STENCIL_TEST); + } else { + this.GL.disable(this.GL.STENCIL_TEST); } - - this._userEnabledStencil = this._savedStencilTestState; } super.pop(...args); this._applyStencilTestIfClipping(); From 38abe636c555a10120cab088e33f97c80c4c3a23 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Sat, 1 Mar 2025 02:18:58 +0530 Subject: [PATCH 3/6] fixed breaking the tests --- src/webgl/p5.RendererGL.js | 45 +++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 511744e5b8..e27563aa9a 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -429,27 +429,29 @@ class RendererGL extends Renderer { this.scratchMat3 = new Matrix(3); this.isStencilTestOn = false; // Track stencil test state + this._userEnabledStencil = false; // Track whether user enabled stencil + // Override WebGL enable function const prevEnable = this.drawingContext.enable; this.drawingContext.enable = (key) => { - if (key === this.drawingContext.STENCIL_TEST) { - this.isStencilTestOn = true; - } - return prevEnable.call(this.drawingContext, key); + if (key === this.drawingContext.STENCIL_TEST) { + if (!this._clipping) { + this._userEnabledStencil = true; + } + this.isStencilTestOn = true; + } + return prevEnable.call(this.drawingContext, key); }; // Override WebGL disable function const prevDisable = this.drawingContext.disable; this.drawingContext.disable = (key) => { if (key === this.drawingContext.STENCIL_TEST) { - // When pop() disables the stencil test after clip(), - // restore the user's stencil test setting - // if (this._clipDepth === this._pushPopDepth) { - // this.isStencilTestOn = this._userEnabledStencil; - // } else { - // this.isStencilTestOn = false; - // this._userEnabledStencil = false; - // } - this.isStencilTestOn = false; + if (this._clipDepth === this._pushPopDepth) { + this.isStencilTestOn = this._userEnabledStencil; + } else { + this.isStencilTestOn = false; + this._userEnabledStencil = false; + } } return prevDisable.call(this.drawingContext, key); }; @@ -1425,7 +1427,9 @@ class RendererGL extends Renderer { this.drawTarget()._isClipApplied = true; const gl = this.GL; - this.isStencilTestOn = this.drawingContext.getEnabled(this.drawingContext.STENCIL_TEST); + this._savedStencilTestState = this._userEnabledStencil; + this._preClipStencilState = this.drawingContext.getEnabled(this.drawingContext.STENCIL_TEST); + // this.isStencilTestOn = this.drawingContext.getEnabled(this.drawingContext.STENCIL_TEST); gl.clearStencil(0); gl.clear(gl.STENCIL_BUFFER_BIT); gl.enable(gl.STENCIL_TEST); @@ -1778,11 +1782,22 @@ class RendererGL extends Renderer { this._pushPopDepth === this._clipDepths[this._clipDepths.length - 1] ) { this._clearClip(); - if (this.isStencilTestOn) { + // if (this.isStencilTestOn) { + // this.GL.enable(this.GL.STENCIL_TEST); + // } else { + // this.GL.disable(this.GL.STENCIL_TEST); + // } + // if (!this._savedStencilTestState) { + // this.GL.disable(this.GL.STENCIL_TEST); + // } + if (this._preClipStencilState) { this.GL.enable(this.GL.STENCIL_TEST); } else { this.GL.disable(this.GL.STENCIL_TEST); } + + // Reset saved state + this._userEnabledStencil = this._savedStencilTestState; } super.pop(...args); this._applyStencilTestIfClipping(); From bc4b5f869527aa9186e17d3bb432aba1def744de Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Sun, 2 Mar 2025 18:03:15 +0530 Subject: [PATCH 4/6] Added internal functions and some logic changes --- src/webgl/p5.RendererGL.js | 79 +++++++++++++++----------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index e27563aa9a..ccd001c921 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -428,41 +428,37 @@ class RendererGL extends Renderer { this.scratchMat3 = new Matrix(3); - this.isStencilTestOn = false; // Track stencil test state - this._userEnabledStencil = false; // Track whether user enabled stencil + this._userEnabledStencil = false; + this.isStencilTestOn = false; + // Store original methods for internal use + this._internalEnable = this.drawingContext.enable; + this._internalDisable = this.drawingContext.disable; + // Override WebGL enable function - const prevEnable = this.drawingContext.enable; this.drawingContext.enable = (key) => { - if (key === this.drawingContext.STENCIL_TEST) { - if (!this._clipping) { - this._userEnabledStencil = true; - } - this.isStencilTestOn = true; + // When enable is called with STENCIL_TEST + if (key === this.drawingContext.STENCIL_TEST) { + // If not during clip(), mark as user-enabled + if (!this._clipping) { + this._userEnabledStencil = true; } - return prevEnable.call(this.drawingContext, key); + } + return this._internalEnable.call(this.drawingContext, key); }; // Override WebGL disable function - const prevDisable = this.drawingContext.disable; this.drawingContext.disable = (key) => { - if (key === this.drawingContext.STENCIL_TEST) { - if (this._clipDepth === this._pushPopDepth) { - this.isStencilTestOn = this._userEnabledStencil; - } else { - this.isStencilTestOn = false; - this._userEnabledStencil = false; - } - } - return prevDisable.call(this.drawingContext, key); - }; - - // Override WebGL getEnabled function - const prevGetEnabled = this.drawingContext.getEnabled; - this.drawingContext.getEnabled = (key) => { - if (key === this.drawingContext.STENCIL_TEST) { - return this.isStencilTestOn; + if (key === this.drawingContext.STENCIL_TEST) { + // When pop() disables the stencil test after clip(), + // preserve the user's stencil test setting + if (this._clipDepth === this._pushPopDepth) { + // Don't change user setting here, just use internal disable + } else { + // User explicitly disabled stencil test + this._userEnabledStencil = false; } - return prevGetEnabled.call(this.drawingContext, key); + } + return this._internalDisable.call(this.drawingContext, key); }; } @@ -1061,8 +1057,8 @@ class RendererGL extends Renderer { //Clear depth every frame this.GL.clearStencil(0); this.GL.clear(this.GL.DEPTH_BUFFER_BIT | this.GL.STENCIL_BUFFER_BIT); - if (!this.isStencilTestOn) { - this.GL.disable(this.GL.STENCIL_TEST); + if (!this._userEnabledStencil) { + this._internalDisable.call(this.GL, this.GL.STENCIL_TEST); } } @@ -1427,9 +1423,6 @@ class RendererGL extends Renderer { this.drawTarget()._isClipApplied = true; const gl = this.GL; - this._savedStencilTestState = this._userEnabledStencil; - this._preClipStencilState = this.drawingContext.getEnabled(this.drawingContext.STENCIL_TEST); - // this.isStencilTestOn = this.drawingContext.getEnabled(this.drawingContext.STENCIL_TEST); gl.clearStencil(0); gl.clear(gl.STENCIL_BUFFER_BIT); gl.enable(gl.STENCIL_TEST); @@ -1782,22 +1775,12 @@ class RendererGL extends Renderer { this._pushPopDepth === this._clipDepths[this._clipDepths.length - 1] ) { this._clearClip(); - // if (this.isStencilTestOn) { - // this.GL.enable(this.GL.STENCIL_TEST); - // } else { - // this.GL.disable(this.GL.STENCIL_TEST); - // } - // if (!this._savedStencilTestState) { - // this.GL.disable(this.GL.STENCIL_TEST); - // } - if (this._preClipStencilState) { - this.GL.enable(this.GL.STENCIL_TEST); - } else { - this.GL.disable(this.GL.STENCIL_TEST); + if (!this._userEnabledStencil) { + this._internalDisable.call(this.GL, this.GL.STENCIL_TEST); } // Reset saved state - this._userEnabledStencil = this._savedStencilTestState; + // this._userEnabledStencil = this._savedStencilTestState; } super.pop(...args); this._applyStencilTestIfClipping(); @@ -1809,9 +1792,9 @@ class RendererGL extends Renderer { this.GL.enable(this.GL.STENCIL_TEST); this._stencilTestOn = true; } else { - if (!this.isStencilTestOn) { - this.GL.disable(this.GL.STENCIL_TEST); - } + if (!this._userEnabledStencil) { + this._internalDisable.call(this.GL, this.GL.STENCIL_TEST); + } this._stencilTestOn = false; } } From ed2682835cd1abc0da3bb4ee613b3e28ebea132b Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Mon, 3 Mar 2025 03:00:52 +0530 Subject: [PATCH 5/6] Minor changes and added tests for the stencil tests --- src/webgl/p5.RendererGL.js | 16 +---- test/unit/webgl/p5.RendererGL.js | 103 ++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 14 deletions(-) diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index ccd001c921..96513367ff 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -429,16 +429,13 @@ class RendererGL extends Renderer { this.scratchMat3 = new Matrix(3); this._userEnabledStencil = false; - this.isStencilTestOn = false; // Store original methods for internal use this._internalEnable = this.drawingContext.enable; this._internalDisable = this.drawingContext.disable; // Override WebGL enable function this.drawingContext.enable = (key) => { - // When enable is called with STENCIL_TEST if (key === this.drawingContext.STENCIL_TEST) { - // If not during clip(), mark as user-enabled if (!this._clipping) { this._userEnabledStencil = true; } @@ -449,14 +446,7 @@ class RendererGL extends Renderer { // Override WebGL disable function this.drawingContext.disable = (key) => { if (key === this.drawingContext.STENCIL_TEST) { - // When pop() disables the stencil test after clip(), - // preserve the user's stencil test setting - if (this._clipDepth === this._pushPopDepth) { - // Don't change user setting here, just use internal disable - } else { - // User explicitly disabled stencil test this._userEnabledStencil = false; - } } return this._internalDisable.call(this.drawingContext, key); }; @@ -1423,9 +1413,9 @@ class RendererGL extends Renderer { this.drawTarget()._isClipApplied = true; const gl = this.GL; - gl.clearStencil(0); + gl.clearStencil(0); gl.clear(gl.STENCIL_BUFFER_BIT); - gl.enable(gl.STENCIL_TEST); + this._internalEnable.call(gl, gl.STENCIL_TEST); this._stencilTestOn = true; gl.stencilFunc( gl.ALWAYS, // the test @@ -1789,7 +1779,7 @@ class RendererGL extends Renderer { const drawTarget = this.drawTarget(); if (drawTarget._isClipApplied !== this._stencilTestOn) { if (drawTarget._isClipApplied) { - this.GL.enable(this.GL.STENCIL_TEST); + this._internalEnable.call(this.GL, this.GL.STENCIL_TEST); this._stencilTestOn = true; } else { if (!this._userEnabledStencil) { diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 3897bf7fb2..1d4368f4b1 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -1,3 +1,4 @@ +import { suite } from 'vitest'; import p5 from '../../../src/app.js'; import '../../js/chai_helpers'; const toArray = (typedArray) => Array.from(typedArray); @@ -2684,5 +2685,105 @@ suite('p5.RendererGL', function() { expect(logs.join('\n')).to.match(/One of the geometries has a custom vertex property 'aCustom' with fewer values than vertices./); } ); - }) + }); + + suite('Stencil Test Tracking', function() { + test('Stencil test is disabled by default', + function() { + myp5.createCanvas(50, 50, myp5.WEBGL); + const gl = myp5._renderer.GL; + const isEnabled = gl.isEnabled(gl.STENCIL_TEST); + + assert.equal(isEnabled, false); + assert.equal(myp5._renderer._userEnabledStencil, false); + } + ); + + test('Tracks when user manually enables stencil test', + function() { + myp5.createCanvas(50, 50, myp5.WEBGL); + const gl = myp5._renderer.GL; + + gl.enable(gl.STENCIL_TEST); + assert.equal(myp5._renderer._userEnabledStencil, true); + assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); + } + ); + + test('Tracks when user manually disables stencil test', + function() { + myp5.createCanvas(50, 50, myp5.WEBGL); + const gl = myp5._renderer.GL; + + gl.enable(gl.STENCIL_TEST); + gl.disable(gl.STENCIL_TEST); + + assert.equal(myp5._renderer._userEnabledStencil, false); + assert.equal(gl.isEnabled(gl.STENCIL_TEST), false); + } + ); + + test('Maintains stencil test state across draw cycles when user enabled', + function() { + let drawCalled = false; + + myp5.createCanvas(50, 50, myp5.WEBGL); + const originalDraw = myp5.draw; + + myp5.draw = function() { + drawCalled = true; + if (originalDraw) originalDraw.call(myp5); + }; + + const gl = myp5._renderer.GL; + gl.enable(gl.STENCIL_TEST); + + myp5.redraw(); + + assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); + assert.equal(myp5._renderer._userEnabledStencil, true); + + myp5.draw = originalDraw; + } + ); + + test('Internal clip operations preserve user stencil test setting', + function() { + myp5.createCanvas(50, 50, myp5.WEBGL); + const gl = myp5._renderer.GL; + + gl.enable(gl.STENCIL_TEST); + + myp5.push(); + myp5.clip(() => { + myp5.rect(0, 0, 10, 10); + }); + myp5.pop(); + + assert.equal(myp5._renderer._userEnabledStencil, true); + assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); + } + ); + + test('Internal clip operations do not enable stencil test for future draw cycles', + function() { + myp5.createCanvas(50, 50, myp5.WEBGL); + const gl = myp5._renderer.GL; + + gl.disable(gl.STENCIL_TEST); + assert.equal(myp5._renderer._userEnabledStencil, false); + + myp5.push(); + myp5.clip(() => { + myp5.rect(0, 0, 10, 10); + }); + myp5.pop(); + + myp5.redraw(); + + assert.equal(myp5._renderer._userEnabledStencil, false); + assert.equal(gl.isEnabled(gl.STENCIL_TEST), false); + } + ); + }); }); From 441468edc60b8d0caa9882cf47e7f2fe7e9db189 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Mon, 3 Mar 2025 03:22:07 +0530 Subject: [PATCH 6/6] fixed the linting which happened because of merging conflicts mb --- test/unit/webgl/p5.RendererGL.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 9a48d8af78..6a58ec6627 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -2803,6 +2803,7 @@ suite('p5.RendererGL', function() { assert.equal(gl.isEnabled(gl.STENCIL_TEST), false); } ); + }); suite('Matrix getters', function() { test('uModelMatrix', function() { @@ -2882,5 +2883,5 @@ suite('p5.RendererGL', function() { myp5.createCanvas(50, 50, myp5.WEBGL); myp5.checkPMatrix(); }); - }); + }); });