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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like tests are currently failing here:

   2) Graphics
       p5.Graphics.resizeCanvas
         Rendering
           p5.prototype.resizeCanvas
             p5.Framebuffer
               sizing
                 p5.RendererGL
                   beginShape() in WEBGL mode
                     TESS mode skips prompt when p5.disableFriendlyErrors is true:
     Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

Does this test also take a while locally?

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
Loading