Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed - Improve WebGL beginShape() performance when only drawing tria… #6445

Closed
wants to merge 2 commits into from
Closed
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
63 changes: 52 additions & 11 deletions src/image/p5.Image.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,33 @@ p5.Image = class {
/**
* Helper function for animating GIF-based images with time
*/
/**
* Set the pixel density of the image.
*
* @method _setPixelDensity
* @param {Number} density - The pixel density to set.
* @example
* <div><code>
* let img = new p5.Image(100, 100);
* img._setPixelDensity(2);
* </code></div>
*/
_setPixelDensity(density) {
if (density <= 0) {
console.error('Pixel density must be greater than 0.');
return;
}

this._pixelDensity = density;

// Adjust canvas dimensions based on pixel density
this.canvas.width = this.width * density;
this.canvas.height = this.height * density;

// Update the drawing context
this.drawingContext = this.canvas.getContext('2d');
}

_animateGif(pInst) {
const props = this.gifProperties;
const curTime = pInst._lastRealFrameTime;
Expand Down Expand Up @@ -482,9 +509,9 @@ p5.Image = class {
width = this.canvas.width;
height = this.canvas.height;
} else if (width === 0) {
width = this.canvas.width * height / this.canvas.height;
width = (this.canvas.width * height) / this.canvas.height;
} else if (height === 0) {
height = this.canvas.height * width / this.canvas.width;
height = (this.canvas.height * width) / this.canvas.width;
}

width = Math.floor(width);
Expand All @@ -501,8 +528,8 @@ p5.Image = class {
let pos = 0;
for (let y = 0; y < dst.height; y++) {
for (let x = 0; x < dst.width; x++) {
const srcX = Math.floor(x * src.width / dst.width);
const srcY = Math.floor(y * src.height / dst.height);
const srcX = Math.floor((x * src.width) / dst.width);
const srcY = Math.floor((y * src.height) / dst.height);
let srcPos = (srcY * src.width + srcX) * 4;
dst.data[pos++] = src.data[srcPos++]; // R
dst.data[pos++] = src.data[srcPos++]; // G
Expand All @@ -521,11 +548,19 @@ p5.Image = class {
}
}

tempCanvas.getContext('2d').drawImage(
this.canvas,
0, 0, this.canvas.width, this.canvas.height,
0, 0, tempCanvas.width, tempCanvas.height
);
tempCanvas
.getContext('2d')
.drawImage(
this.canvas,
0,
0,
this.canvas.width,
this.canvas.height,
0,
0,
tempCanvas.width,
tempCanvas.height
);

// Resize the original canvas, which will clear its contents
this.canvas.width = this.width = width;
Expand All @@ -534,8 +569,14 @@ p5.Image = class {
//Copy the image back
this.drawingContext.drawImage(
tempCanvas,
0, 0, width, height,
0, 0, width, height
0,
0,
width,
height,
0,
0,
width,
height
);

if (this.pixels.length > 0) {
Expand Down
118 changes: 56 additions & 62 deletions src/webgl/p5.RendererGL.Immediate.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ import './p5.RenderBuffer';
* and TESS(WEBGL only)
* @chainable
*/
p5.RendererGL.prototype.beginShape = function(mode) {
this.immediateMode.shapeMode =
mode !== undefined ? mode : constants.TESS;
p5.RendererGL.prototype.beginShape = function (mode) {
this.immediateMode.shapeMode = mode !== undefined ? mode : constants.TESS;
this.immediateMode.geometry.reset();
this.immediateMode.contourIndices = [];
return this;
Expand All @@ -46,7 +45,7 @@ const immediateBufferStrides = {
uvs: 2
};

p5.RendererGL.prototype.beginContour = function() {
p5.RendererGL.prototype.beginContour = function () {
if (this.immediateMode.shapeMode !== constants.TESS) {
throw new Error('WebGL mode can only use contours with beginShape(TESS).');
}
Expand All @@ -65,7 +64,7 @@ p5.RendererGL.prototype.beginContour = function() {
* @chainable
* @TODO implement handling of <a href="#/p5.Vector">p5.Vector</a> args
*/
p5.RendererGL.prototype.vertex = function(x, y) {
p5.RendererGL.prototype.vertex = function (x, y) {
// WebGL 1 doesn't support QUADS or QUAD_STRIP, so we duplicate data to turn
// QUADS into TRIANGLES and QUAD_STRIP into TRIANGLE_STRIP. (There is no extra
// work to convert QUAD_STRIP here, since the only difference is in how edges
Expand Down Expand Up @@ -134,14 +133,11 @@ p5.RendererGL.prototype.vertex = function(x, y) {
u /= this._tex.width;
v /= this._tex.height;
}
} else if (
this._tex === null &&
arguments.length >= 4
) {
} else if (this._tex === null && arguments.length >= 4) {
// Only throw this warning if custom uv's have been provided
console.warn(
'You must first call texture() before using' +
' vertex() with image based u and v coordinates'
'vertex() with image based u and v coordinates'
);
}
}
Expand Down Expand Up @@ -171,7 +167,7 @@ p5.RendererGL.prototype.vertex = function(x, y) {
* @param {Vector} v
* @chainable
*/
p5.RendererGL.prototype.normal = function(xorv, y, z) {
p5.RendererGL.prototype.normal = function (xorv, y, z) {
if (xorv instanceof p5.Vector) {
this._currentNormal = xorv;
} else {
Expand All @@ -185,7 +181,7 @@ p5.RendererGL.prototype.normal = function(xorv, y, z) {
* End shape drawing and render vertices to screen.
* @chainable
*/
p5.RendererGL.prototype.endShape = function(
p5.RendererGL.prototype.endShape = function (
mode,
isCurve,
isBezier,
Expand All @@ -201,6 +197,9 @@ p5.RendererGL.prototype.endShape = function(
);
return this;
}
if (this.immediateMode.geometry.vertices.length === 3) {
this.immediateMode.shapeMode === constants.TRIANGLES;
}
this.isProcessingVertices = true;
this._processVertices(...arguments);
this.isProcessingVertices = false;
Expand Down Expand Up @@ -262,7 +261,7 @@ p5.RendererGL.prototype.endShape = function(
* POINTS,LINES,LINE_STRIP,LINE_LOOP,TRIANGLES,
* TRIANGLE_STRIP, TRIANGLE_FAN and TESS(WEBGL only)
*/
p5.RendererGL.prototype._processVertices = function(mode) {
p5.RendererGL.prototype._processVertices = function (mode) {
if (this.immediateMode.geometry.vertices.length === 0) return;

const calculateStroke = this._doStroke;
Expand All @@ -285,13 +284,11 @@ p5.RendererGL.prototype._processVertices = function(mode) {
// We tesselate when drawing curves or convex shapes
const shouldTess =
this._doFill &&
(
this.isBezier ||
(this.isBezier ||
this.isQuadratic ||
this.isCurve ||
convexShape ||
hasContour
) &&
hasContour) &&
this.immediateMode.shapeMode !== constants.LINES;

if (shouldTess) {
Expand All @@ -305,7 +302,7 @@ p5.RendererGL.prototype._processVertices = function(mode) {
* @private
* @returns {Array[Number]} indices for custom shape vertices indicating edges.
*/
p5.RendererGL.prototype._calculateEdges = function(
p5.RendererGL.prototype._calculateEdges = function (
shapeMode,
verts,
shouldClose
Expand Down Expand Up @@ -391,7 +388,7 @@ p5.RendererGL.prototype._calculateEdges = function(
* Called from _processVertices() when applicable. This function tesselates immediateMode.geometry.
* @private
*/
p5.RendererGL.prototype._tesselateShape = function() {
p5.RendererGL.prototype._tesselateShape = function () {
// TODO: handle non-TESS shape modes that have contours
this.immediateMode.shapeMode = constants.TRIANGLES;
const contours = [[]];
Expand All @@ -403,7 +400,7 @@ p5.RendererGL.prototype._tesselateShape = function() {
this.immediateMode.contourIndices.shift();
contours.push([]);
}
contours[contours.length-1].push(
contours[contours.length - 1].push(
this.immediateMode.geometry.vertices[i].x,
this.immediateMode.geometry.vertices[i].y,
this.immediateMode.geometry.vertices[i].z,
Expand Down Expand Up @@ -443,43 +440,42 @@ p5.RendererGL.prototype._tesselateShape = function() {
// We record index mappings in a Map so that once we have found a
// corresponding vertex, we don't need to loop to find it again.
const newIndex = new Map();
this.immediateMode.geometry.edges =
this.immediateMode.geometry.edges.map(edge => edge.map(origIdx => {
if (!newIndex.has(origIdx)) {
const orig = originalVertices[origIdx];
let newVertIndex = this.immediateMode.geometry.vertices.findIndex(
v =>
orig.x === v.x &&
orig.y === v.y &&
orig.z === v.z
);
if (newVertIndex === -1) {
// The tesselation process didn't output a vertex with the exact
// coordinate as before, potentially due to numerical issues. This
// doesn't happen often, but in this case, pick the closest point
let closestDist = Infinity;
let closestIndex = 0;
for (
let i = 0;
i < this.immediateMode.geometry.vertices.length;
i++
) {
const vert = this.immediateMode.geometry.vertices[i];
const dX = orig.x - vert.x;
const dY = orig.y - vert.y;
const dZ = orig.z - vert.z;
const dist = dX*dX + dY*dY + dZ*dZ;
if (dist < closestDist) {
closestDist = dist;
closestIndex = i;
this.immediateMode.geometry.edges = this.immediateMode.geometry.edges.map(
edge =>
edge.map(origIdx => {
if (!newIndex.has(origIdx)) {
const orig = originalVertices[origIdx];
let newVertIndex = this.immediateMode.geometry.vertices.findIndex(
v => orig.x === v.x && orig.y === v.y && orig.z === v.z
);
if (newVertIndex === -1) {
// The tesselation process didn't output a vertex with the exact
// coordinate as before, potentially due to numerical issues. This
// doesn't happen often, but in this case, pick the closest point
let closestDist = Infinity;
let closestIndex = 0;
for (
let i = 0;
i < this.immediateMode.geometry.vertices.length;
i++
) {
const vert = this.immediateMode.geometry.vertices[i];
const dX = orig.x - vert.x;
const dY = orig.y - vert.y;
const dZ = orig.z - vert.z;
const dist = dX * dX + dY * dY + dZ * dZ;
if (dist < closestDist) {
closestDist = dist;
closestIndex = i;
}
}
newVertIndex = closestIndex;
}
newVertIndex = closestIndex;
newIndex.set(origIdx, newVertIndex);
}
newIndex.set(origIdx, newVertIndex);
}
return newIndex.get(origIdx);
}));
return newIndex.get(origIdx);
})
);
}
this.immediateMode.geometry.vertexColors = colors;
};
Expand All @@ -489,9 +485,9 @@ p5.RendererGL.prototype._tesselateShape = function() {
* enabling all appropriate buffers, applying color blend, and drawing the fill geometry.
* @private
*/
p5.RendererGL.prototype._drawImmediateFill = function(count = 1) {
p5.RendererGL.prototype._drawImmediateFill = function (count = 1) {
const gl = this.GL;
this._useVertexColor = (this.immediateMode.geometry.vertexColors.length > 0);
this._useVertexColor = this.immediateMode.geometry.vertexColors.length > 0;

let shader;
shader = this._getImmediateFillShader();
Expand All @@ -511,17 +507,15 @@ p5.RendererGL.prototype._drawImmediateFill = function(count = 1) {
0,
this.immediateMode.geometry.vertices.length
);
}
else {
} else {
try {
gl.drawArraysInstanced(
this.immediateMode.shapeMode,
0,
this.immediateMode.geometry.vertices.length,
count
);
}
catch (e) {
} catch (e) {
console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode');
}
}
Expand All @@ -533,11 +527,11 @@ p5.RendererGL.prototype._drawImmediateFill = function(count = 1) {
* enabling all appropriate buffers, applying color blend, and drawing the stroke geometry.
* @private
*/
p5.RendererGL.prototype._drawImmediateStroke = function() {
p5.RendererGL.prototype._drawImmediateStroke = function () {
const gl = this.GL;

this._useLineColor =
(this.immediateMode.geometry.vertexStrokeColors.length > 0);
this.immediateMode.geometry.vertexStrokeColors.length > 0;

const shader = this._getImmediateStrokeShader();
this._setStrokeUniforms(shader);
Expand Down