Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/webgl/p5.RendererGL.Immediate.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,29 @@ p5.RendererGL.prototype._processVertices = function(mode) {
this.immediateMode.shapeMode !== constants.LINES;

if (shouldTess) {
const vertexCount = this.immediateMode.geometry.vertices.length;
const MAX_SAFE_TESSELLATION_VERTICES = 50000;

if (vertexCount > MAX_SAFE_TESSELLATION_VERTICES) {
// If FES is disabled (or minified build), just run tessellation as-is.
// Otherwise, prompt the user once to decide whether to continue.
if (!p5.disableFriendlyErrors && !this._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) {
// User cancelled — draw nothing for this shape.
return;
}
// User approved — skip this prompt for the rest of the session.
this._largeTessellationAcknowledged = true;
}
}

this._tesselateShape();
}
};
Expand Down
5 changes: 5 additions & 0 deletions src/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,11 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
}
};

// Flag set to true once the user approves tessellating a large shape via
// the confirm() prompt. Once set, we skip the prompt for the rest of the
// session so the user is only interrupted once.
this._largeTessellationAcknowledged = false;

this.curStrokeWeight = 1;
this.pointSize = this.curStrokeWeight;
this.curStrokeCap = constants.ROUND;
Expand Down
109 changes: 109 additions & 0 deletions test/unit/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -1686,6 +1686,115 @@ suite('p5.RendererGL', function() {
done();
});

test('TESS mode prompts user before tessellating >50k vertices', function(done) {
var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
// Stub confirm() so the user "cancels" — shape should draw nothing
var confirmStub = sinon.stub(window, 'confirm').returns(false);

renderer.beginShape(myp5.TESS);
for (let i = 0; i < 60000; i++) {
renderer.vertex(i % 100, Math.floor(i / 100), 0);
}
renderer.endShape();

assert.isTrue(
confirmStub.called,
'window.confirm should be called when vertex count exceeds threshold'
);
assert.isTrue(
confirmStub.args[0][0].includes('60000'),
'confirm message should include the actual vertex count'
);
// Shape mode must NOT be changed to TRIANGLE_FAN — draw nothing on cancel
assert.notEqual(
renderer.immediateMode.shapeMode,
myp5.TRIANGLE_FAN,
'Shape mode should not fall back to TRIANGLE_FAN when user cancels'
);

confirmStub.restore();
done();
});

test('TESS mode only prompts once when user approves large tessellation', function(done) {
var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
// User approves on the first prompt
var confirmStub = sinon.stub(window, 'confirm').returns(true);

// First large shape — should prompt
renderer.beginShape(myp5.TESS);
for (let i = 0; i < 60000; i++) {
renderer.vertex(i % 100, Math.floor(i / 100), 0);
}
renderer.endShape();

assert.equal(confirmStub.callCount, 1, 'confirm should be called once on first large shape');
assert.isTrue(
renderer._largeTessellationAcknowledged,
'_largeTessellationAcknowledged should be set after user approves'
);

// Second large shape — should NOT prompt again
renderer.beginShape(myp5.TESS);
for (let i = 0; i < 60000; i++) {
renderer.vertex(i % 100, Math.floor(i / 100), 0);
}
renderer.endShape();

assert.equal(confirmStub.callCount, 1, 'confirm should not be called again after acknowledgement');

confirmStub.restore();
done();
});

test('TESS mode skips prompt when p5.disableFriendlyErrors is true', function(done) {
var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
var confirmStub = sinon.stub(window, 'confirm').returns(false);
p5.disableFriendlyErrors = true;

renderer.beginShape(myp5.TESS);
for (let i = 0; i < 60000; i++) {
renderer.vertex(i % 100, Math.floor(i / 100), 0);
}
renderer.endShape();

assert.isFalse(
confirmStub.called,
'window.confirm should not be called when p5.disableFriendlyErrors is true'
);

p5.disableFriendlyErrors = false;
confirmStub.restore();
done();
});

test('TESS mode works normally for <50k vertices', function(done) {
var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
var confirmStub = sinon.stub(window, 'confirm').returns(false);

// 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);

assert.isFalse(
confirmStub.called,
'window.confirm should not be called for shapes with fewer than 50k vertices'
);

assert.equal(
renderer.immediateMode.shapeMode,
myp5.TRIANGLES,
'Shape mode should be TRIANGLES after normal tessellation'
);

confirmStub.restore();
done();
});

test('TESS does not affect stroke colors', function(done) {
var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);

Expand Down