From 051df1e1cac7a4492e1fb6493634e3eee22fb2a6 Mon Sep 17 00:00:00 2001 From: Nixxx19 Date: Sun, 22 Feb 2026 17:45:38 +0530 Subject: [PATCH] fix: prevent browser freeze when tessellating >50k vertices --- src/webgl/p5.RendererGL.Immediate.js | 17 +++++++ test/unit/webgl/p5.RendererGL.js | 67 ++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/src/webgl/p5.RendererGL.Immediate.js b/src/webgl/p5.RendererGL.Immediate.js index 31ce48f630..915db04537 100644 --- a/src/webgl/p5.RendererGL.Immediate.js +++ b/src/webgl/p5.RendererGL.Immediate.js @@ -313,6 +313,23 @@ p5.RendererGL.prototype._processVertices = function(mode) { this.immediateMode.shapeMode !== constants.LINES; if (shouldTess) { + // libtess can't handle >65k vertices and will freeze the browser + const vertexCount = this.immediateMode.geometry.vertices.length; + const MAX_SAFE_TESSELATION_VERTICES = 50000; + + if (vertexCount > MAX_SAFE_TESSELATION_VERTICES) { + p5._friendlyError( + 'p5.js WebGL: Tessellation warning', + `Attempting to tessellate a shape with ${vertexCount} vertices. ` + + `Tessellation of shapes with more than ${MAX_SAFE_TESSELATION_VERTICES} vertices ` + + 'may cause the browser to freeze. Consider reducing the number of vertices ' + + 'or using a different shape mode (e.g., TRIANGLES, TRIANGLE_STRIP) instead of TESS.' + ); + // skip tessellation to prevent freeze, use TRIANGLE_FAN as fallback + this.immediateMode.shapeMode = constants.TRIANGLE_FAN; + return; + } + this._tesselateShape(); } }; diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 3c7e3df1a2..26e0d24b84 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -1686,6 +1686,73 @@ suite('p5.RendererGL', function() { done(); }); + test('TESS mode warns and prevents freeze for >50k vertices', function(done) { + var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); + var friendlyErrorSpy = sinon.spy(p5, '_friendlyError'); + + renderer.beginShape(myp5.TESS); + for (let i = 0; i < 60000; i++) { + renderer.vertex( + Math.random() * 10 - 5, + Math.random() * 10 - 5, + Math.random() * 10 - 5 + ); + } + renderer.endShape(); + + assert.isTrue( + friendlyErrorSpy.called, + 'p5._friendlyError should be called for large vertex count' + ); + + const warningCall = friendlyErrorSpy.getCalls().find(call => + call.args[0] && call.args[0].includes('Tessellation warning') + ); + assert.isDefined( + warningCall, + 'Tessellation warning should be shown' + ); + + assert.equal( + renderer.immediateMode.shapeMode, + myp5.TRIANGLE_FAN, + 'Shape mode should be changed to TRIANGLE_FAN when tessellation is skipped' + ); + + friendlyErrorSpy.restore(); + done(); + }); + + test('TESS mode works normally for <50k vertices', function(done) { + var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); + var friendlyErrorSpy = sinon.spy(p5, '_friendlyError'); + + // use a simple shape that tessellates quickly + renderer.beginShape(myp5.TESS); + renderer.vertex(-10, -10, 0); + renderer.vertex(10, -10, 0); + renderer.vertex(10, 10, 0); + renderer.vertex(-10, 10, 0); + renderer.endShape(myp5.CLOSE); + + const warningCall = friendlyErrorSpy.getCalls().find(call => + call.args[0] && call.args[0].includes('Tessellation warning') + ); + assert.isUndefined( + warningCall, + 'No tessellation warning should be shown for <50k vertices' + ); + + assert.equal( + renderer.immediateMode.shapeMode, + myp5.TRIANGLES, + 'Shape mode should be TRIANGLES after normal tessellation' + ); + + friendlyErrorSpy.restore(); + done(); + }); + test('TESS does not affect stroke colors', function(done) { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);