Skip to content

Add support for webGL instancing #6276

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

Merged
merged 28 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e61c8e3
main changes for the adding of instancing
RandomGamingDev Jul 23, 2023
6874098
fixed some more linting issues
RandomGamingDev Jul 23, 2023
629ee21
readded constants.
RandomGamingDev Jul 23, 2023
a655759
added count < 1 check
RandomGamingDev Jul 23, 2023
c8e799a
documentation example
RandomGamingDev Jul 25, 2023
6eda10d
moved the example to the right place and removed this.
RandomGamingDev Jul 28, 2023
4626423
linting fix
RandomGamingDev Jul 28, 2023
988db4b
removed now unnecessary overload for FrameBufferCamera
RandomGamingDev Jul 31, 2023
c5ad6a5
wrong branch, reverted to previous commit
RandomGamingDev Jul 31, 2023
6c9557e
documentation updates
RandomGamingDev Aug 1, 2023
4bd9209
Update vertex.js
aferriss Aug 1, 2023
f407425
unit test
RandomGamingDev Aug 5, 2023
cb18f99
Merge branch 'main' into instancing
RandomGamingDev Aug 5, 2023
db5bfea
fixed syntax error from merging part of main
RandomGamingDev Aug 5, 2023
2f7a480
Merge branch 'processing:main' into instancing
RandomGamingDev Aug 17, 2023
3563716
resetted eslintrc.json as suggested by dave
RandomGamingDev Aug 17, 2023
72e7b6a
added myp5. in front of the p5.js enums
RandomGamingDev Aug 17, 2023
8f219ef
added myp5. in front of createShader()
RandomGamingDev Aug 17, 2023
c90f1b7
put the correct color for the background test's deep equal
RandomGamingDev Aug 17, 2023
3b8da42
added myp5. in front of width and height
RandomGamingDev Aug 17, 2023
d6c7271
forgot to actually instance changed the val from 1 to 2
RandomGamingDev Aug 17, 2023
79e1f92
removed the done parameter
RandomGamingDev Aug 17, 2023
3c46ec0
Update vertex.js example fragment shader
aferriss Aug 17, 2023
38788a0
Update vertex.js typo
aferriss Aug 17, 2023
f61bcd5
Update vertex.js typo linter
aferriss Aug 17, 2023
7a0dbcf
added the documentation suggested by adam
RandomGamingDev Aug 17, 2023
371e1fe
Merge branch 'instancing' of https://github.com/RandomGamingDev/p5.js…
RandomGamingDev Aug 17, 2023
cf20cd6
made it so that the updated example shader compiles
RandomGamingDev Aug 17, 2023
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
98 changes: 95 additions & 3 deletions src/core/shape/vertex.js
Original file line number Diff line number Diff line change
Expand Up @@ -592,11 +592,19 @@ p5.prototype.endContour = function() {
* The <a href="#/p5/endShape">endShape()</a> function is the companion to <a href="#/p5/beginShape">beginShape()</a> and may only be
* called after <a href="#/p5/beginShape">beginShape()</a>. When <a href="#/p5/endshape">endShape()</a> is called, all of the image
* data defined since the previous call to <a href="#/p5/beginShape">beginShape()</a> is written into the image
* buffer. The constant CLOSE as the value for the `mode` parameter to close
* buffer. The constant CLOSE is the value for the `mode` parameter to close
* the shape (to connect the beginning and the end).
* When using instancing with <a href="#/p5/endShape">endShape()</a> the instancing will not apply to the strokes.
* When the count parameter is used with a value greater than 1, it enables instancing for shapes built when in WEBGL mode. Instancing
* is a feature that allows the GPU to efficiently draw multiples of the same shape. It's often used for particle effects or other
* times when you need a lot of repetition. In order to take advantage of instancing, you will also need to write your own custom
* shader using the gl_InstanceID keyword. You can read more about instancing
* <a href="https://webglfundamentals.org/webgl/lessons/webgl-instanced-drawing.html">here</a> or by working from the example on this
* page.
*
* @method endShape
* @param {Constant} [mode] use CLOSE to close the shape
* @param {Integer} [count] number of times you want to draw/instance the shape (for WebGL mode).
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this be renamed to "instanceCount" or something a little more descriptive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problem I have with the name instanceCount is that it could be taken the wrong way. Perhaps something like numInstances would work, but I was thinking of keeping its name simple and understandable since drawing smth a certain amount of times makes sense in my view. If that's a problem then I'd probably change it to numInstances, but it might be less understandable rather than more.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If u look at the rest of the documentation most of the parameter names are decently simple.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was mainly thinking about the renaming in the context of helping future maintainers understand what this is for. I still think count is too ambiguous in this context. numInstances sounds fine to me if you prefer that.

* @chainable
* @example
* <div>
Expand All @@ -617,21 +625,105 @@ p5.prototype.endContour = function() {
* </code>
* </div>
*
* @example
* <div>
* <code>
* let fx;
* let vs = `#version 300 es
*
* precision mediump float;
*
* in vec3 aPosition;
* flat out int instanceID;
*
* uniform mat4 uModelViewMatrix;
* uniform mat4 uProjectionMatrix;
*
* void main() {
*
* // copy the instance ID to the fragment shader
* instanceID = gl_InstanceID;
* vec4 positionVec4 = vec4(aPosition, 1.0);
*
* // gl_InstanceID represents a numeric value for each instance
* // using gl_InstanceID allows us to move each instance separately
* // here we move each instance horizontally by id * 100
* float xOffset = float(gl_InstanceID) * 100.0;
*
* // apply the offset to the final position
* gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4 -
* vec4(xOffset, 0.0, 0.0, 0.0);
* }
* `;
* let fs = `#version 300 es
*
* precision mediump float;
*
* out vec4 outColor;
* flat in int instanceID;
* uniform float numInstances;
*
* void main() {
* vec4 red = vec4(1.0, 0.0, 0.0, 1.0);
* vec4 blue = vec4(0.0, 0.0, 1.0, 1.0);
*
* // Normalize the instance id
* float normId = float(instanceID) / numInstances;
*
* // Mix between two colors using the normalized instance id
* outColor = mix(red, blue, normId);
* }
* `;
*
* function setup() {
* createCanvas(400, 400, WEBGL);
* fx = createShader(vs, fs);
* }
*
* function draw() {
* background(220);
*
* // strokes aren't instanced, and are rather used for debug purposes
* shader(fx);
* fx.setUniform('numInstances', 4);
*
* beginShape();
* vertex(30, 20);
* vertex(85, 20);
* vertex(85, 75);
* vertex(30, 75);
* vertex(30, 20);
* endShape(CLOSE, 4);
*
* resetShader();
* }
* </code>
* </div>
*
* @alt
* Triangle line shape with smallest interior angle on bottom and upside-down L.
*/
p5.prototype.endShape = function(mode) {
p5.prototype.endShape = function(mode, count = 1) {
p5._validateParameters('endShape', arguments);
if (count < 1) {
console.log('🌸 p5.js says: You can not have less than one instance');
count = 1;
}

if (this._renderer.isP3D) {
this._renderer.endShape(
mode,
isCurve,
isBezier,
isQuadratic,
isContour,
shapeKind
shapeKind,
count
);
} else {
if (count !== 1) {
console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode');
}
if (vertices.length === 0) {
return this;
}
Expand Down
32 changes: 24 additions & 8 deletions src/webgl/p5.RendererGL.Immediate.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ p5.RendererGL.prototype.endShape = function(
isBezier,
isQuadratic,
isContour,
shapeKind
shapeKind,
count = 1
) {
if (this.immediateMode.shapeMode === constants.POINTS) {
this._drawPoints(
Expand Down Expand Up @@ -228,7 +229,7 @@ p5.RendererGL.prototype.endShape = function(
!this.geometryBuilder &&
this.immediateMode.geometry.vertices.length >= 3
) {
this._drawImmediateFill();
this._drawImmediateFill(count);
}
}
if (this._doStroke) {
Expand Down Expand Up @@ -489,7 +490,7 @@ p5.RendererGL.prototype._tesselateShape = function() {
* enabling all appropriate buffers, applying color blend, and drawing the fill geometry.
* @private
*/
p5.RendererGL.prototype._drawImmediateFill = function() {
p5.RendererGL.prototype._drawImmediateFill = function(count = 1) {
const gl = this.GL;
this._useVertexColor = (this.immediateMode.geometry.vertexColors.length > 0);

Expand All @@ -505,11 +506,26 @@ p5.RendererGL.prototype._drawImmediateFill = function() {

this._applyColorBlend(this.curFillColor);

gl.drawArrays(
this.immediateMode.shapeMode,
0,
this.immediateMode.geometry.vertices.length
);
if (count === 1) {
gl.drawArrays(
this.immediateMode.shapeMode,
0,
this.immediateMode.geometry.vertices.length
);
}
else {
try {
gl.drawArraysInstanced(
this.immediateMode.shapeMode,
0,
this.immediateMode.geometry.vertices.length,
count
);
}
catch (e) {
console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode');
}
}
shader.unbindShader();
};

Expand Down
80 changes: 80 additions & 0 deletions test/unit/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -1870,6 +1870,86 @@ suite('p5.RendererGL', function() {
});
});

suite('instancing', function() {
test('instanced', function() {
let defShader;

const vertShader = `#version 300 es

in vec3 aPosition;
in vec2 aTexCoord;

uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;

out vec2 vTexCoord;

void main() {
vTexCoord = aTexCoord;

vec4 pos = vec4(aPosition, 1.0);
pos.x += float(gl_InstanceID);
vec4 wPos = uProjectionMatrix * uModelViewMatrix * pos;
gl_Position = wPos;
}`;

const fragShader = `#version 300 es

#ifdef GL_ES
precision mediump float;
#endif

in vec2 vTexCoord;

out vec4 fragColor;

void main() {
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;

myp5.createCanvas(2, 1, myp5.WEBGL);
myp5.noStroke();
myp5.pixelDensity(1);

defShader = myp5.createShader(vertShader, fragShader);

myp5.background(0);
myp5.shader(defShader);
{
// Check to make sure that pixels are empty first
assert.deepEqual(
myp5.get(0, 0),
[0, 0, 0, 255]
);
assert.deepEqual(
myp5.get(1, 0),
[0, 0, 0, 255]
);

const siz = 1;
myp5.translate(-myp5.width / 2, -myp5.height / 2);
myp5.beginShape();
myp5.vertex(0, 0);
myp5.vertex(0, siz);
myp5.vertex(siz, siz);
myp5.vertex(siz, 0);
myp5.endShape(myp5.CLOSE, 2);

// check the pixels after instancing to make sure that they're the correct color
assert.deepEqual(
myp5.get(0, 0),
[255, 0, 0, 255]
);
assert.deepEqual(
myp5.get(1, 0),
[255, 0, 0, 255]
);
}
myp5.resetShader();
});
});

suite('clip()', function() {
//let myp5;
function getClippedPixels(mode, mask) {
Expand Down