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
6 changes: 6 additions & 0 deletions src/core/p5.Renderer3D.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1995,6 +1997,10 @@ const webGPUAddonMessage = 'Add the WebGPU add-on to your project and pass WEBGP
function renderer3D(p5, fn) {
p5.Renderer3D = Renderer3D;

ShapeBuilder.prototype.friendlyErrorsDisabled = function() {
return Boolean(p5.disableFriendlyErrors);
};

/**
* Creates a <a href="#/p5/p5.StorageBuffer">`p5.StorageBuffer`</a>, which is
* a block of data that shaders can read from, and compute shaders
Expand Down
25 changes: 25 additions & 0 deletions src/webgl/ShapeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export class ShapeBuilder {
this.bufferStrides = { ...INITIAL_BUFFER_STRIDES };
}

friendlyErrorsDisabled() {
return false;
}

constructFromContours(shape, contours) {
if (this._useUserVertexProperties){
this._resetUserVertexProperties();
Expand Down Expand Up @@ -148,6 +152,27 @@ 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 &&
!this.friendlyErrorsDisabled() &&
!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;
Expand Down
92 changes: 92 additions & 0 deletions test/unit/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
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();
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();
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();
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.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() {
Expand Down