From 91904917f4992269299cbed592fa2fed6a7f3e94 Mon Sep 17 00:00:00 2001 From: nityam Date: Fri, 17 Apr 2026 15:26:59 +0530 Subject: [PATCH 1/2] prompt before tessellating very large shapes --- src/core/p5.Renderer3D.js | 2 + src/webgl/ShapeBuilder.js | 23 ++++++++ test/unit/webgl/p5.RendererGL.js | 92 ++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/src/core/p5.Renderer3D.js b/src/core/p5.Renderer3D.js index 1fde0670bc..21d246eca0 100644 --- a/src/core/p5.Renderer3D.js +++ b/src/core/p5.Renderer3D.js @@ -221,6 +221,8 @@ export class Renderer3D extends Renderer { // Used by beginShape/endShape functions to construct a p5.Geometry this.shapeBuilder = new ShapeBuilder(this); + this._largeTessellationAcknowledged = false; + this.geometryBufferCache = new GeometryBufferCache(this); this.curStrokeCap = constants.ROUND; diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index 124fa62bfa..6a2c723f78 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -148,6 +148,29 @@ export class ShapeBuilder { } if (this.shapeMode === constants.PATH) { + const vertexCount = this.geometry.vertices.length; + const MAX_SAFE_TESSELLATION_VERTICES = 50000; + + if (vertexCount > MAX_SAFE_TESSELLATION_VERTICES) { + const p5Class = this.renderer._pInst.constructor; + if ( + !p5Class.disableFriendlyErrors && + !this.renderer._largeTessellationAcknowledged + ) { + const proceed = window.confirm( + '🌸 p5.js says:\n\n' + + `This shape has ${vertexCount} vertices. Tessellating shapes with this ` + + 'many vertices can be very slow and may cause your browser to become ' + + 'unresponsive.\n\n' + + 'Do you want to continue tessellating this shape?' + ); + if (!proceed) { + return; + } + this.renderer._largeTessellationAcknowledged = true; + } + } + this.isProcessingVertices = true; this._tesselateShape(); this.isProcessingVertices = false; diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 4d3eaaf7aa..22ede1f326 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -2032,6 +2032,98 @@ suite('p5.RendererGL', function() { [-10, 0, 10] ); }); + + suite('large tessellation guard', function() { + test('prompts user before tessellating >50k vertices', function() { + const renderer = myp5.createCanvas(10, 10, myp5.WEBGL); + const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); + const tessSpy = vi.spyOn( + renderer.shapeBuilder, + '_tesselateShape' + ).mockImplementation(() => {}); + + myp5.beginShape(myp5.TESS); + for (let i = 0; i < 60000; i++) { + myp5.vertex(i % 100, Math.floor(i / 100), 0); + } + myp5.endShape(); + + expect(confirmSpy).toHaveBeenCalled(); + expect(confirmSpy.mock.calls[0][0]).toContain('60000'); + expect(tessSpy).not.toHaveBeenCalled(); + + confirmSpy.mockRestore(); + tessSpy.mockRestore(); + }); + + test('only prompts once when user approves large tessellation', function() { + const renderer = myp5.createCanvas(10, 10, myp5.WEBGL); + const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true); + const tessSpy = vi.spyOn( + renderer.shapeBuilder, + '_tesselateShape' + ).mockImplementation(() => {}); + + myp5.beginShape(myp5.TESS); + for (let i = 0; i < 60000; i++) { + myp5.vertex(i % 100, Math.floor(i / 100), 0); + } + myp5.endShape(); + + expect(confirmSpy).toHaveBeenCalledTimes(1); + expect(renderer._largeTessellationAcknowledged).toBe(true); + + myp5.beginShape(myp5.TESS); + for (let i = 0; i < 60000; i++) { + myp5.vertex(i % 100, Math.floor(i / 100), 0); + } + myp5.endShape(); + + expect(confirmSpy).toHaveBeenCalledTimes(1); + + confirmSpy.mockRestore(); + tessSpy.mockRestore(); + }); + + test('skips prompt when p5.disableFriendlyErrors is true', function() { + const renderer = myp5.createCanvas(10, 10, myp5.WEBGL); + const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); + const tessSpy = vi.spyOn( + renderer.shapeBuilder, + '_tesselateShape' + ).mockImplementation(() => {}); + p5.disableFriendlyErrors = true; + + myp5.beginShape(myp5.TESS); + for (let i = 0; i < 60000; i++) { + myp5.vertex(i % 100, Math.floor(i / 100), 0); + } + myp5.endShape(); + + expect(confirmSpy).not.toHaveBeenCalled(); + expect(tessSpy).toHaveBeenCalled(); + + p5.disableFriendlyErrors = false; + confirmSpy.mockRestore(); + tessSpy.mockRestore(); + }); + + test('works normally for <50k vertices', function() { + const renderer = myp5.createCanvas(10, 10, myp5.WEBGL); + const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); + + myp5.beginShape(myp5.TESS); + myp5.vertex(-10, -10, 0); + myp5.vertex(10, -10, 0); + myp5.vertex(10, 10, 0); + myp5.vertex(-10, 10, 0); + myp5.endShape(myp5.CLOSE); + + expect(confirmSpy).not.toHaveBeenCalled(); + + confirmSpy.mockRestore(); + }); + }); }); suite('color interpolation', function() { From 746e46c2e004ee1c306a404a9ce220760f9287bd Mon Sep 17 00:00:00 2001 From: nityam Date: Fri, 17 Apr 2026 15:47:54 +0530 Subject: [PATCH 2/2] use beginShape without TESS arg for dev-2.0 path mode --- test/unit/webgl/p5.RendererGL.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 22ede1f326..c0e2e94a49 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -2042,7 +2042,7 @@ suite('p5.RendererGL', function() { '_tesselateShape' ).mockImplementation(() => {}); - myp5.beginShape(myp5.TESS); + myp5.beginShape(); for (let i = 0; i < 60000; i++) { myp5.vertex(i % 100, Math.floor(i / 100), 0); } @@ -2064,7 +2064,7 @@ suite('p5.RendererGL', function() { '_tesselateShape' ).mockImplementation(() => {}); - myp5.beginShape(myp5.TESS); + myp5.beginShape(); for (let i = 0; i < 60000; i++) { myp5.vertex(i % 100, Math.floor(i / 100), 0); } @@ -2073,7 +2073,7 @@ suite('p5.RendererGL', function() { expect(confirmSpy).toHaveBeenCalledTimes(1); expect(renderer._largeTessellationAcknowledged).toBe(true); - myp5.beginShape(myp5.TESS); + myp5.beginShape(); for (let i = 0; i < 60000; i++) { myp5.vertex(i % 100, Math.floor(i / 100), 0); } @@ -2094,7 +2094,7 @@ suite('p5.RendererGL', function() { ).mockImplementation(() => {}); p5.disableFriendlyErrors = true; - myp5.beginShape(myp5.TESS); + myp5.beginShape(); for (let i = 0; i < 60000; i++) { myp5.vertex(i % 100, Math.floor(i / 100), 0); } @@ -2112,7 +2112,7 @@ suite('p5.RendererGL', function() { const renderer = myp5.createCanvas(10, 10, myp5.WEBGL); const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); - myp5.beginShape(myp5.TESS); + myp5.beginShape(); myp5.vertex(-10, -10, 0); myp5.vertex(10, -10, 0); myp5.vertex(10, 10, 0);