From cbb4bd41a99bb3675c7034b14b41ac98c7a98ed7 Mon Sep 17 00:00:00 2001 From: aashu2006 Date: Mon, 22 Jun 2026 06:27:48 +0530 Subject: [PATCH 1/6] Add instances() API for primitive instancing --- src/core/p5.Renderer3D.js | 10 +++++- src/webgl/3d_primitives.js | 71 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/core/p5.Renderer3D.js b/src/core/p5.Renderer3D.js index 363773befc..c4acc17b72 100644 --- a/src/core/p5.Renderer3D.js +++ b/src/core/p5.Renderer3D.js @@ -247,6 +247,8 @@ export class Renderer3D extends Renderer { this._curShader = undefined; this.drawShapeCount = 1; + // Set by the instances() wrapper; undefined means "no instancing". + this._instanceCount = undefined; this.scratchMat3 = new Matrix(3); @@ -548,6 +550,11 @@ export class Renderer3D extends Renderer { } model(model, count = 1) { + // Use _instanceCount only when count was NOT explicitly passed. + // arguments.length distinguishes model(geom) from model(geom, 1). + if (arguments.length < 2 && this._instanceCount !== undefined) { + count = this._instanceCount; + } if (model.vertices.length > 0) { if (this.geometryBuilder) { this.geometryBuilder.addRetained(model); @@ -679,6 +686,7 @@ export class Renderer3D extends Renderer { } _drawGeometryScaled(model, scaleX, scaleY, scaleZ) { + const count = this._instanceCount || 1; let originalModelMatrix = this.states.uModelMatrix; this.states.setValue("uModelMatrix", this.states.uModelMatrix.clone()); try { @@ -687,7 +695,7 @@ export class Renderer3D extends Renderer { if (this.geometryBuilder) { this.geometryBuilder.addRetained(model); } else { - this._drawGeometry(model); + this._drawGeometry(model, { count }); } } finally { this.states.setValue("uModelMatrix", originalModelMatrix); diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index 0d10bfdf4b..1cb4014534 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -2595,6 +2595,77 @@ function primitives3D(p5, fn){ } return this._renderer.curveDetail(d); }; + + /** + * Draws `count` instances of the next 3D primitive or model using WebGL + * instanced rendering. + * + * Call a draw method on the returned object to render that primitive + * `count` times in a single draw call. Instance-specific transforms and + * attributes (position, color, etc.) are supplied through a custom shader + * that reads per-instance data from an instanced attribute buffer. + * + * @method instances + * @param {Number} count number of instances to draw. Must be a positive + * integer. + * @returns {Object} an object with methods `sphere`, `box`, `plane`, + * `ellipsoid`, `cylinder`, `cone`, `torus`, and `model`. Call one of + * these to draw `count` instances of that primitive. + * + * @example + *
+ * + * // Draw 10 spheres in a single instanced draw call. + * // A custom shader reads gl_InstanceID to offset each sphere. + * function setup() { + * createCanvas(100, 100, WEBGL); + * } + * function draw() { + * background(200); + * instances(10).sphere(20); + * } + * + *
+ */ + fn.instances = function(count) { + this._assert3d('instances'); + + if (typeof count !== 'number' || !isFinite(count) || count < 1) { + console.log( + '🌸 p5.js says: instances() requires a positive integer count.' + + ' Clamping to 1.' + ); + count = Math.max(1, Math.round(count) || 1); + } else { + count = Math.round(count); + } + + const r = this._renderer; + const p = this; + + // Each wrapped method: set _instanceCount, call the renderer method with + // the correct `this`, clear _instanceCount in finally so it never leaks. + const wrap = method => function(...args) { + r._instanceCount = count; + try { + method.apply(r, args); + } finally { + r._instanceCount = undefined; + } + return p; + }; + + return { + sphere: wrap(r.sphere), + box: wrap(r.box), + plane: wrap(r.plane), + ellipsoid: wrap(r.ellipsoid), + cylinder: wrap(r.cylinder), + cone: wrap(r.cone), + torus: wrap(r.torus), + model: wrap(r.model) + }; + }; } export default primitives3D; From 8d70be16f70d3b54f9091dac87f4cd1fc2d8dbc3 Mon Sep 17 00:00:00 2001 From: aashu2006 Date: Mon, 22 Jun 2026 06:47:11 +0530 Subject: [PATCH 2/6] Add tests for instances() API --- test/unit/webgl/p5.RendererGL.js | 67 +++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 69ccf620da..90e12e329a 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -1,4 +1,4 @@ -import { suite } from 'vitest'; +import { suite, vi } from 'vitest'; import p5 from '../../../src/app.js'; import '../../js/chai_helpers'; const toArray = typedArray => Array.from(typedArray); @@ -2527,6 +2527,71 @@ void main() { }); }); + suite('instances() API', function() { + let drawSpy; + + beforeEach(function() { + myp5.createCanvas(10, 10, myp5.WEBGL); + drawSpy = vi.spyOn(myp5._renderer, '_drawGeometry'); + }); + + afterEach(function() { + vi.restoreAllMocks(); + }); + + test('instances(5).sphere() sets correct instanceCount on draw and clears it', function() { + myp5.instances(5).sphere(10); + + expect(drawSpy).toHaveBeenCalled(); + const lastCall = drawSpy.mock.calls[drawSpy.mock.calls.length - 1]; + assert.equal(lastCall[1].count, 5); + + assert.isUndefined(myp5._renderer._instanceCount); + }); + + test('instances(5).box() sets correct instanceCount on draw and clears it', function() { + myp5.instances(5).box(10); + + expect(drawSpy).toHaveBeenCalled(); + const lastCall = drawSpy.mock.calls[drawSpy.mock.calls.length - 1]; + assert.equal(lastCall[1].count, 5); + + assert.isUndefined(myp5._renderer._instanceCount); + }); + + test('instances(5).model(geom) uses instances count', function() { + const geom = new p5.Geometry(); + geom.gid = 'instances_model_test'; + geom.vertices.push(myp5.createVector(0, 0, 0)); + geom.vertices.push(myp5.createVector(1, 0, 0)); + geom.vertices.push(myp5.createVector(1, 1, 0)); + + myp5.instances(5).model(geom); + + expect(drawSpy).toHaveBeenCalled(); + const lastCall = drawSpy.mock.calls[drawSpy.mock.calls.length - 1]; + assert.equal(lastCall[1].count, 5); + + assert.isUndefined(myp5._renderer._instanceCount); + }); + + test('instances(10).model(geom, 1) has explicit-count precedence', function() { + const geom = new p5.Geometry(); + geom.gid = 'instances_precedence_test'; + geom.vertices.push(myp5.createVector(0, 0, 0)); + geom.vertices.push(myp5.createVector(1, 0, 0)); + geom.vertices.push(myp5.createVector(1, 1, 0)); + + myp5.instances(10).model(geom, 1); + + expect(drawSpy).toHaveBeenCalled(); + const lastCall = drawSpy.mock.calls[drawSpy.mock.calls.length - 1]; + assert.equal(lastCall[1].count, 1); + + assert.isUndefined(myp5._renderer._instanceCount); + }); + }); + suite('clip()', function() { //let myp5; function getClippedPixels(mode, mask) { From 83e3a1da1cb3d5c45c4dbf4602533c520223bb75 Mon Sep 17 00:00:00 2001 From: aashu2006 Date: Fri, 26 Jun 2026 00:52:22 +0530 Subject: [PATCH 3/6] Address review feedback for instances API --- src/core/p5.Renderer3D.js | 8 ++--- src/webgl/3d_primitives.js | 51 ++++++++++++++++++++++++-------- test/unit/visual/cases/webgl.js | 19 ++++++++++++ test/unit/visual/cases/webgpu.js | 19 ++++++++++++ utils/typescript.mjs | 24 ++++++++++----- 5 files changed, 95 insertions(+), 26 deletions(-) diff --git a/src/core/p5.Renderer3D.js b/src/core/p5.Renderer3D.js index c4acc17b72..8c9e0453d0 100644 --- a/src/core/p5.Renderer3D.js +++ b/src/core/p5.Renderer3D.js @@ -549,12 +549,8 @@ export class Renderer3D extends Renderer { this.updateShapeVertexProperties(); } - model(model, count = 1) { - // Use _instanceCount only when count was NOT explicitly passed. - // arguments.length distinguishes model(geom) from model(geom, 1). - if (arguments.length < 2 && this._instanceCount !== undefined) { - count = this._instanceCount; - } + model(model, count) { + count = count ?? this._instanceCount ?? 1; if (model.vertices.length > 0) { if (this.geometryBuilder) { this.geometryBuilder.addRetained(model); diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index 1cb4014534..ce67a97818 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -2596,6 +2596,18 @@ function primitives3D(p5, fn){ return this._renderer.curveDetail(d); }; + /** + * @typedef {Object} InstancesWrapper + * @property {function(radius: Number=, detailX: Number=, detailY: Number=): undefined} sphere + * @property {function(width: Number=, height: Number=, depth: Number=, detailX: Number=, detailY: Number=): undefined} box + * @property {function(width: Number=, height: Number=, detailX: Number=, detailY: Number=): undefined} plane + * @property {function(radiusX: Number=, radiusY: Number=, radiusZ: Number=, detailX: Number=, detailY: Number=): undefined} ellipsoid + * @property {function(radius: Number=, height: Number=, detailX: Number=, detailY: Number=, bottomCap: Boolean=, topCap: Boolean=): undefined} cylinder + * @property {function(radius: Number=, height: Number=, detailX: Number=, detailY: Number=, cap: Boolean=): undefined} cone + * @property {function(radius: Number=, tubeRadius: Number=, detailX: Number=, detailY: Number=): undefined} torus + * @property {function(model: p5.Geometry, count: Number=): undefined} model + */ + /** * Draws `count` instances of the next 3D primitive or model using WebGL * instanced rendering. @@ -2608,21 +2620,38 @@ function primitives3D(p5, fn){ * @method instances * @param {Number} count number of instances to draw. Must be a positive * integer. - * @returns {Object} an object with methods `sphere`, `box`, `plane`, + * @returns {p5.InstancesWrapper} an object with methods `sphere`, `box`, `plane`, * `ellipsoid`, `cylinder`, `cone`, `torus`, and `model`. Call one of * these to draw `count` instances of that primitive. * * @example *
* - * // Draw 10 spheres in a single instanced draw call. - * // A custom shader reads gl_InstanceID to offset each sphere. + * let myShader; + * let count = 5; + * * function setup() { - * createCanvas(100, 100, WEBGL); + * createCanvas(200, 200, WEBGL); + * myShader = buildMaterialShader(drawSpaced); + * describe('Five red spheres arranged in a horizontal line.'); * } + * + * function drawSpaced() { + * worldInputs.begin(); + * // Spread spheres evenly across the canvas based on their index + * let spacing = width / count; + * worldInputs.position.x += + * (instanceIndex - (count - 1) / 2) * spacing; + * worldInputs.end(); + * } + * * function draw() { - * background(200); - * instances(10).sphere(20); + * background(220); + * lights(); + * noStroke(); + * fill('red'); + * shader(myShader); + * instances(count).sphere(15); * } * *
@@ -2631,17 +2660,16 @@ function primitives3D(p5, fn){ this._assert3d('instances'); if (typeof count !== 'number' || !isFinite(count) || count < 1) { - console.log( - '🌸 p5.js says: instances() requires a positive integer count.' + - ' Clamping to 1.' + p5._friendlyError( + 'instances() requires a positive integer count. Clamping to 1.', + 'instances' ); - count = Math.max(1, Math.round(count) || 1); + count = 1; } else { count = Math.round(count); } const r = this._renderer; - const p = this; // Each wrapped method: set _instanceCount, call the renderer method with // the correct `this`, clear _instanceCount in finally so it never leaks. @@ -2652,7 +2680,6 @@ function primitives3D(p5, fn){ } finally { r._instanceCount = undefined; } - return p; }; return { diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 972d9bc5f0..82517f3865 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -1101,6 +1101,25 @@ visualSuite('WebGL', function() { p5.model(obj, numInstances); screenshot(); }); + + visualTest('instances() API draws multiple spaced primitives', (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const count = 5; + const shader = p5.baseMaterialShader().modify(() => { + p5.getWorldInputs((inputs) => { + let spacing = p5.width / count; + inputs.position.x += (p5.instanceIndex - (count - 1) / 2.0) * spacing; + return inputs; + }); + }, { p5 }); + p5.background(220); + p5.lights(); + p5.noStroke(); + p5.fill('red'); + p5.shader(shader); + p5.instances(count).sphere(7); + screenshot(); + }); }); visualSuite('p5.strands', () => { diff --git a/test/unit/visual/cases/webgpu.js b/test/unit/visual/cases/webgpu.js index 4fc098850b..b0437165f9 100644 --- a/test/unit/visual/cases/webgpu.js +++ b/test/unit/visual/cases/webgpu.js @@ -326,6 +326,25 @@ visualSuite("WebGPU", function () { await screenshot(); }); + visualTest('instances() API draws multiple spaced primitives (WebGPU)', async function(p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + const count = 5; + const shader = p5.baseMaterialShader().modify(() => { + p5.getWorldInputs((inputs) => { + let spacing = p5.width / count; + inputs.position.x += (p5.instanceIndex - (count - 1) / 2.0) * spacing; + return inputs; + }); + }, { p5 }); + p5.background(220); + p5.lights(); + p5.noStroke(); + p5.fill('red'); + p5.shader(shader); + p5.instances(count).sphere(7); + await screenshot(); + }); + visualTest('random() colors a basic shader (WebGPU)', async function(p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); const shader = p5.baseColorShader().modify(() => { diff --git a/utils/typescript.mjs b/utils/typescript.mjs index 751ff117f8..382c9e90fa 100644 --- a/utils/typescript.mjs +++ b/utils/typescript.mjs @@ -366,19 +366,27 @@ function convertFunctionTypeForInterface(typeNode, options) { .map((param, i) => { let typeObj; let paramName; - if (param.type === 'ParameterType') { - typeObj = param.expression; - paramName = param.name ?? `p${i}`; - } else if (typeof param.type === 'object' && param.type !== null) { - typeObj = param.type; - paramName = param.name ?? `p${i}`; + let isOptional = false; + + let currentParam = param; + if (currentParam && currentParam.type === 'OptionalType') { + isOptional = true; + currentParam = currentParam.expression; + } + + if (currentParam && currentParam.type === 'ParameterType') { + typeObj = currentParam.expression; + paramName = currentParam.name ?? `p${i}`; + } else if (currentParam && typeof currentParam.type === 'object' && currentParam.type !== null) { + typeObj = currentParam.type; + paramName = currentParam.name ?? `p${i}`; } else { // param itself is a plain type node - typeObj = param; + typeObj = currentParam; paramName = `p${i}`; } const paramType = convertTypeToTypeScript(typeObj, options); - return `${paramName}: ${paramType}`; + return `${paramName}${isOptional ? '?' : ''}: ${paramType}`; }) .join(', '); From f35f2909c6d2bf55aca1b9e8372d3c7ed41e5945 Mon Sep 17 00:00:00 2001 From: aashu2006 Date: Fri, 26 Jun 2026 01:31:02 +0530 Subject: [PATCH 4/6] Fix and complete visual tests for instances API --- test/unit/visual/cases/webgl.js | 2 +- test/unit/visual/cases/webgpu.js | 2 +- .../000.png | Bin 0 -> 862 bytes .../metadata.json | 3 +++ .../000.png | Bin 0 -> 869 bytes .../metadata.json | 3 +++ 6 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced primitives/000.png create mode 100644 test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced primitives/metadata.json create mode 100644 test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws multiple spaced primitives (WebGPU)/000.png create mode 100644 test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws multiple spaced primitives (WebGPU)/metadata.json diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 82517f3865..ce94d083b1 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -1111,7 +1111,7 @@ visualSuite('WebGL', function() { inputs.position.x += (p5.instanceIndex - (count - 1) / 2.0) * spacing; return inputs; }); - }, { p5 }); + }, { p5, count }); p5.background(220); p5.lights(); p5.noStroke(); diff --git a/test/unit/visual/cases/webgpu.js b/test/unit/visual/cases/webgpu.js index b0437165f9..7ae4f9df7d 100644 --- a/test/unit/visual/cases/webgpu.js +++ b/test/unit/visual/cases/webgpu.js @@ -335,7 +335,7 @@ visualSuite("WebGPU", function () { inputs.position.x += (p5.instanceIndex - (count - 1) / 2.0) * spacing; return inputs; }); - }, { p5 }); + }, { p5, count }); p5.background(220); p5.lights(); p5.noStroke(); diff --git a/test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced primitives/000.png b/test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced primitives/000.png new file mode 100644 index 0000000000000000000000000000000000000000..7404725fee39e466e6fce011c0c75d60f3f23349 GIT binary patch literal 862 zcmV-k1EKthP)UV=r)S{a zq!XC5@zB*(U!AVoO^Bn>=yVwr!;<`~A9B$ivc5=tkwQ06f3*r-Y-ij0BAu_UP-A_O zLKoZFw!TQ`t1Hx4U!>5*cD7x<7x}ce7vAsegy&mZ;kEQu@dxG7D>4oSotJwjKYDY1 z)bK*YIqsuoIgkg;8_kz;@j$>U6;8Bn42R*aPTG|EiplX|FzBVYl(K2wBcfEG{9YRdH9s$AXs`}eI1@0!V4CT zJW5$=co|3EA}(UEt^s?E)|%D!)XV^uJPaZ>d5+lfa3*rp5T2~$OrGN`7_^9^1{+s3 zxM@XkPG{BWF@|A?$>l`N7~&k`bMqMTUNLHT(-jw6j`gbYh&ZQ3wH)tri+ZUc&s&_E zRbyWM|M9QB!F1|k%d!5Mlc!c%^Uh9ZjmH|&>qPrhR!{S`w>s;Q`dO`(uGEYnMk!-^ z&sok^`n=*q2OmmPiAF?hqBd3y!IFnT#3tid5qTIBdqfQ%{D{e!JPU*1tZu94@PLco zHRLae1``G1-^x>iLF~z!jAIY?L;N=h4v{y(l7qtwmY6yq0zy8r+H literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced primitives/metadata.json b/test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced primitives/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced primitives/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws multiple spaced primitives (WebGPU)/000.png b/test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws multiple spaced primitives (WebGPU)/000.png new file mode 100644 index 0000000000000000000000000000000000000000..71fe81335380378501bdc26d5005a33447c8d76f GIT binary patch literal 869 zcmV-r1DgDaP)ki}#V*$QCb0|*j>ya7I#A&!$kxW{@IwOY@{ zw$f5!HOfs@SDn6HuR9|2dcEU$P^jkQU-^*J_K@X8%8O*Wf%2!7>0)QLEiW?j)n(dP zUL@1S&TLy=Wag{Ow6VNMri-1~cK%-E!}fOdYIC!CvbHwVuJZS)rB_5P=E$l2k9$4B z@14%T!{?6T-imuEk5OFM70}i2hl=fPx4NN;(6VxBY=l8g9D5G5mmUmy+Tho!{hp}@ zgYf1IJUA1XdoJ#ceBmA|KT@7@)Ly6u7Z~EIhQA_RmM+yINA&tK8A#-->QgsIq_$dB z)xH>XO$@b|Bd3P&nuLSl?CTTw1wMdb+JdcConKJAD8V4sW{Bp<;XG{0(Kl-@_M?a3 zEIeQkx$7}UP7fxqSk|!CQk1&}V^M=aRPh!y&Kfp*@{Pbe#x?$nk<9HgPbB9!&Tu!=DnHEE>*# zsrRiGx$8tYL{85feyrW2@0@zf(L?T&BXgdQH4LI}aTb1IX=|(cQV;rF@kcFkOg?Gd z5(D>HIW>As+=@6f&X3-*I6Uz}_9Eg<&0Je7=Q1pUL2#_$m@RAweAw$%3yS?tr~0U2 zY3?bf_DH>T_24?n7nQ^LFgR-FV8?Ns1%u!i0*}-$=J19aXeMTZWe?|?3z~cQb4vqm z?Cex*dZ2;tDZiuIuo?X_cV7(LU2(T3 Date: Fri, 26 Jun 2026 02:54:43 +0530 Subject: [PATCH 5/6] Expand instances API to additional retained geometry shapes --- src/webgl/3d_primitives.js | 21 +++++++++++++----- test/unit/visual/cases/webgl.js | 18 +++++++++++++++ test/unit/visual/cases/webgpu.js | 18 +++++++++++++++ .../000.png | Bin 0 -> 305 bytes .../metadata.json | 3 +++ .../000.png | Bin 0 -> 305 bytes .../metadata.json | 3 +++ 7 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced 2D rects/000.png create mode 100644 test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced 2D rects/metadata.json create mode 100644 test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws multiple spaced 2D rects (WebGPU)/000.png create mode 100644 test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws multiple spaced 2D rects (WebGPU)/metadata.json diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index ce67a97818..c38b7019ef 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -2605,6 +2605,11 @@ function primitives3D(p5, fn){ * @property {function(radius: Number=, height: Number=, detailX: Number=, detailY: Number=, bottomCap: Boolean=, topCap: Boolean=): undefined} cylinder * @property {function(radius: Number=, height: Number=, detailX: Number=, detailY: Number=, cap: Boolean=): undefined} cone * @property {function(radius: Number=, tubeRadius: Number=, detailX: Number=, detailY: Number=): undefined} torus + * @property {function(x1: Number, y1: Number, x2: Number, y2: Number, x3: Number, y3: Number): undefined} triangle + * @property {function(x: Number, y: Number, w: Number, h: Number=, detailX: Number=, detailY: Number=): undefined} rect + * @property {function(x1: Number, y1: Number, z1: Number=, x2: Number, y2: Number, z2: Number=, x3: Number, y3: Number, z3: Number=, x4: Number, y4: Number, z4: Number=, detailX: Number=, detailY: Number=): undefined} quad + * @property {function(x: Number, y: Number, w: Number, h: Number=, detail: Number=): undefined} ellipse + * @property {function(x: Number, y: Number, w: Number, h: Number, start: Number, stop: Number, mode: String=, detail: Number=): undefined} arc * @property {function(model: p5.Geometry, count: Number=): undefined} model */ @@ -2621,7 +2626,8 @@ function primitives3D(p5, fn){ * @param {Number} count number of instances to draw. Must be a positive * integer. * @returns {p5.InstancesWrapper} an object with methods `sphere`, `box`, `plane`, - * `ellipsoid`, `cylinder`, `cone`, `torus`, and `model`. Call one of + * `ellipsoid`, `cylinder`, `cone`, `torus`, `triangle`, `rect`, `quad`, + * `ellipse`, `arc`, and `model`. Call one of * these to draw `count` instances of that primitive. * * @example @@ -2671,12 +2677,12 @@ function primitives3D(p5, fn){ const r = this._renderer; - // Each wrapped method: set _instanceCount, call the renderer method with - // the correct `this`, clear _instanceCount in finally so it never leaks. - const wrap = method => function(...args) { + // Each wrapped method: set _instanceCount, call the method with + // the correct context, clear _instanceCount in finally so it never leaks. + const wrap = (method, ctx = r) => function(...args) { r._instanceCount = count; try { - method.apply(r, args); + method.apply(ctx, args); } finally { r._instanceCount = undefined; } @@ -2690,6 +2696,11 @@ function primitives3D(p5, fn){ cylinder: wrap(r.cylinder), cone: wrap(r.cone), torus: wrap(r.torus), + triangle: wrap(this.triangle, this), + rect: wrap(this.rect, this), + quad: wrap(this.quad, this), + ellipse: wrap(this.ellipse, this), + arc: wrap(this.arc, this), model: wrap(r.model) }; }; diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index ce94d083b1..71b3f776a2 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -1120,6 +1120,24 @@ visualSuite('WebGL', function() { p5.instances(count).sphere(7); screenshot(); }); + + visualTest('instances() API draws multiple spaced 2D rects', (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const count = 3; + const shader = p5.baseMaterialShader().modify(() => { + p5.getWorldInputs(inputs => { + let spacing = p5.width / count; + inputs.position.x += (p5.instanceIndex - (count - 1) / 2.0) * spacing; + return inputs; + }); + }, { p5, count }); + p5.background(220); + p5.noStroke(); + p5.fill('blue'); + p5.shader(shader); + p5.instances(count).rect(-5, -5, 10, 10); + screenshot(); + }); }); visualSuite('p5.strands', () => { diff --git a/test/unit/visual/cases/webgpu.js b/test/unit/visual/cases/webgpu.js index 7ae4f9df7d..af4d93f365 100644 --- a/test/unit/visual/cases/webgpu.js +++ b/test/unit/visual/cases/webgpu.js @@ -345,6 +345,24 @@ visualSuite("WebGPU", function () { await screenshot(); }); + visualTest('instances() API draws multiple spaced 2D rects (WebGPU)', async function(p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + const count = 3; + const shader = p5.baseMaterialShader().modify(() => { + p5.getWorldInputs(inputs => { + let spacing = p5.width / count; + inputs.position.x += (p5.instanceIndex - (count - 1) / 2.0) * spacing; + return inputs; + }); + }, { p5, count }); + p5.background(220); + p5.noStroke(); + p5.fill('blue'); + p5.shader(shader); + p5.instances(count).rect(-5, -5, 10, 10); + await screenshot(); + }); + visualTest('random() colors a basic shader (WebGPU)', async function(p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); const shader = p5.baseColorShader().modify(() => { diff --git a/test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced 2D rects/000.png b/test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced 2D rects/000.png new file mode 100644 index 0000000000000000000000000000000000000000..58d3d20fffb870266d985567887b9ad348e5ea04 GIT binary patch literal 305 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETXPz#OAr*{oCOL978wj{I?~Yq| zyNaFv@lTtca*yqMou;4K(X~KD@`u1-ZJ&v1|F7?vu6S(6R|Dr4PYydq33m$ibZ``G zbxF9pRibdQFsq>w)9ZCdJEZ4obO;JYlz>Ez)~|o`3AMJXVsMp*wc8fA@Lzt$neEJi&Vclk9-~YGmbxz)%x0?y7UUqr& z5^n$MX9{a24|BM=uwih{MW6_Sr>mdKI;Vst0Nba42LJ#7 literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced 2D rects/metadata.json b/test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced 2D rects/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws multiple spaced 2D rects/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws multiple spaced 2D rects (WebGPU)/000.png b/test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws multiple spaced 2D rects (WebGPU)/000.png new file mode 100644 index 0000000000000000000000000000000000000000..58d3d20fffb870266d985567887b9ad348e5ea04 GIT binary patch literal 305 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETXPz#OAr*{oCOL978wj{I?~Yq| zyNaFv@lTtca*yqMou;4K(X~KD@`u1-ZJ&v1|F7?vu6S(6R|Dr4PYydq33m$ibZ``G zbxF9pRibdQFsq>w)9ZCdJEZ4obO;JYlz>Ez)~|o`3AMJXVsMp*wc8fA@Lzt$neEJi&Vclk9-~YGmbxz)%x0?y7UUqr& z5^n$MX9{a24|BM=uwih{MW6_Sr>mdKI;Vst0Nba42LJ#7 literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws multiple spaced 2D rects (WebGPU)/metadata.json b/test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws multiple spaced 2D rects (WebGPU)/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws multiple spaced 2D rects (WebGPU)/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file From f134f1d10ad61a43e5db0113b7c3278ce3aa8302 Mon Sep 17 00:00:00 2001 From: aashu2006 Date: Fri, 26 Jun 2026 20:59:12 +0530 Subject: [PATCH 6/6] Support instancing for immediate-mode primitives --- src/core/p5.Renderer3D.js | 140 ++++++++---------- src/shape/vertex.js | 17 ++- src/webgl/3d_primitives.js | 20 ++- src/webgl/loading.js | 14 +- src/webgpu/p5.RendererWebGPU.js | 10 +- test/unit/visual/cases/webgl.js | 21 +++ test/unit/visual/cases/webgpu.js | 23 ++- .../000.png | Bin 0 -> 748 bytes .../metadata.json | 3 + .../000.png | Bin 0 -> 801 bytes .../metadata.json | 3 + 11 files changed, 143 insertions(+), 108 deletions(-) create mode 100644 test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws immediate-mode shape primitives/000.png create mode 100644 test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws immediate-mode shape primitives/metadata.json create mode 100644 test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws immediate-mode shape primitives (WebGPU)/000.png create mode 100644 test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws immediate-mode shape primitives (WebGPU)/metadata.json diff --git a/src/core/p5.Renderer3D.js b/src/core/p5.Renderer3D.js index 8c9e0453d0..cebf29a6bd 100644 --- a/src/core/p5.Renderer3D.js +++ b/src/core/p5.Renderer3D.js @@ -532,7 +532,7 @@ export class Renderer3D extends Renderer { } endShape(mode, count) { - this.drawShapeCount = count; + this.drawShapeCount = count ?? this._instanceCount ?? 1; super.endShape(mode, count); } @@ -2038,61 +2038,55 @@ function renderer3D(p5, fn) { * item is accessed by index, and its properties are available by name. * * ```js example - * let instanceData; - * let instancesShader; - * let instance; - * let count = 5; - * - * async function setup() { - * await createCanvas(200, 200, WEBGPU); - * - * let data = []; - * for (let i = 0; i < count; i++) { - * data.push({ - * position: createVector( - * random(-1, 1) * width / 2, - * random(-1, 1) * height / 2, - * 0, - * ), - * color: color( - * random(255), - * random(255), - * random(255) - * ) - * }); - * } - * instanceData = createStorage(data); - * instance = buildGeometry(drawInstance); - * instancesShader = buildMaterialShader(drawInstances); - * describe('Five spheres at random positions, each a different random color.'); - * } - * - * function drawInstance() { - * sphere(15); - * } - * - * function drawInstances() { - * let data = uniformStorage(instanceData); - * let itemColor = sharedVec4(); - * - * worldInputs.begin(); - * let item = data[instanceID()]; - * itemColor = item.color; - * worldInputs.position += item.position; - * worldInputs.end(); - * - * finalColor.begin(); - * finalColor.set(itemColor); - * finalColor.end(); - * } - * - * function draw() { - * background(220); - * lights(); - * noStroke(); - * shader(instancesShader); - * model(instance, count); - * } + * let instanceData; + * let instancesShader; + * let count = 5; + * + * async function setup() { + * await createCanvas(200, 200, WEBGPU); + * + * let data = []; + * for (let i = 0; i < count; i++) { + * data.push({ + * position: createVector( + * random(-1, 1) * width / 2, + * random(-1, 1) * height / 2, + * 0, + * ), + * color: color( + * random(255), + * random(255), + * random(255) + * ) + * }); + * } + * instanceData = createStorage(data); + * instancesShader = buildMaterialShader(drawInstances); + * describe('Five spheres at random positions, each a different random color.'); + * } + * + * function drawInstances() { + * let data = uniformStorage(instanceData); + * let itemColor = sharedVec4(); + * + * worldInputs.begin(); + * let item = data[instanceIndex]; + * itemColor = item.color; + * worldInputs.position += item.position; + * worldInputs.end(); + * + * finalColor.begin(); + * finalColor.set(itemColor); + * finalColor.end(); + * } + * + * function draw() { + * background(220); + * lights(); + * noStroke(); + * shader(instancesShader); + * instances(count).sphere(15); + * } * ``` * * You can also store a plain list of numbers by passing an array of numbers. @@ -2246,15 +2240,14 @@ function renderer3D(p5, fn) { * initial data. Connect your iteration function to the storage by passing the storage * into `uniformStorage`. * - * Often, compute shaders are paired with `model(myGeometry, count)` + * Often, compute shaders are paired with `instances(count)` * to draw one instance per object in the storage, and a shader that uses - * `instanceID()` to position each instance. + * `instanceIndex` to position each instance. * * ```js example * let particles; * let computeShader; * let displayShader; - * let instance; * const numParticles = 100; * * async function setup() { @@ -2262,7 +2255,6 @@ function renderer3D(p5, fn) { * particles = createStorage(makeParticles(width / 2, height / 2)); * computeShader = buildComputeShader(simulate); * displayShader = buildMaterialShader(display); - * instance = buildGeometry(drawParticle); * describe('100 orange particles shooting outward.'); * } * @@ -2279,10 +2271,6 @@ function renderer3D(p5, fn) { * return data; * } * - * function drawParticle() { - * sphere(2); - * } - * * function simulate() { * let data = uniformStorage(particles); * let idx = index.x; @@ -2292,7 +2280,7 @@ function renderer3D(p5, fn) { * function display() { * let data = uniformStorage(particles); * worldInputs.begin(); - * let pos = data[instanceID()].position; + * let pos = data[instanceIndex].position; * worldInputs.position.xy += pos - [width / 2, height / 2]; * worldInputs.end(); * } @@ -2306,7 +2294,7 @@ function renderer3D(p5, fn) { * noStroke(); * fill(255, 200, 50); * shader(displayShader); - * model(instance, numParticles); + * instances(numParticles).sphere(2); * } * ``` * @@ -2314,7 +2302,6 @@ function renderer3D(p5, fn) { * let particles; * let computeShader; * let displayShader; - * let instance; * const numParticles = 50; * * async function setup() { @@ -2337,14 +2324,9 @@ function renderer3D(p5, fn) { * * computeShader = buildComputeShader(simulate); * displayShader = buildMaterialShader(display); - * instance = buildGeometry(drawParticle); * describe('50 white spheres bouncing around the canvas.'); * } * - * function drawParticle() { - * sphere(3); - * } - * * function simulate() { * let r = 3; * let data = uniformStorage(particles); @@ -2367,7 +2349,7 @@ function renderer3D(p5, fn) { * function display() { * let data = uniformStorage(particles); * worldInputs.begin(); - * let pos = data[instanceID()].position; + * let pos = data[instanceIndex].position; * worldInputs.position.xy += pos; * worldInputs.end(); * } @@ -2379,7 +2361,7 @@ function renderer3D(p5, fn) { * fill(255); * lights(); * shader(displayShader); - * model(instance, numParticles); + * instances(numParticles).sphere(3); * } * ``` * @@ -2416,7 +2398,6 @@ function renderer3D(p5, fn) { * let particles; * let computeShader; * let displayShader; - * let instance; * const numParticles = 50; * * async function setup() { @@ -2439,14 +2420,9 @@ function renderer3D(p5, fn) { * * computeShader = buildComputeShader(simulate); * displayShader = buildMaterialShader(display); - * instance = buildGeometry(drawParticle); * describe('50 white spheres bouncing around the canvas.'); * } * - * function drawParticle() { - * sphere(3); - * } - * * function simulate() { * let r = 3; * let data = uniformStorage(particles); @@ -2469,7 +2445,7 @@ function renderer3D(p5, fn) { * function display() { * let data = uniformStorage(particles); * worldInputs.begin(); - * let pos = data[instanceID()].position; + * let pos = data[instanceIndex].position; * worldInputs.position.xy += pos; * worldInputs.end(); * } @@ -2481,7 +2457,7 @@ function renderer3D(p5, fn) { * fill(255); * lights(); * shader(displayShader); - * model(instance, numParticles); + * instances(numParticles).sphere(3); * } * ``` * diff --git a/src/shape/vertex.js b/src/shape/vertex.js index 0f41a6c043..7222370550 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -833,11 +833,20 @@ function vertex(p5, fn){ * describe('A row of four squares. Their colors transition from purple on the left to red on the right'); * } */ - fn.endShape = function(mode, count = 1) { + fn.endShape = function(mode, count) { // p5._validateParameters('endShape', arguments); - if (count < 1) { - console.log('🌸 p5.js says: You can not have less than one instance'); - count = 1; + if (typeof mode === 'number') { + count = mode; + mode = undefined; + } + + if (count !== undefined) { + if (count < 1) { + console.log('🌸 p5.js says: You can not have less than one instance'); + count = 1; + } else { + count = Math.round(count); + } } this._renderer.endShape(mode, count); diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index c38b7019ef..7531d9bd7f 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -2611,6 +2611,10 @@ function primitives3D(p5, fn){ * @property {function(x: Number, y: Number, w: Number, h: Number=, detail: Number=): undefined} ellipse * @property {function(x: Number, y: Number, w: Number, h: Number, start: Number, stop: Number, mode: String=, detail: Number=): undefined} arc * @property {function(model: p5.Geometry, count: Number=): undefined} model + * @property {function(x1: Number, y1: Number, z1: Number|Number, x2: Number=, y2: Number=, z2: Number=): undefined} line + * @property {function(x: Number, y: Number, z: Number=): undefined} point + * @property {function(x1: Number, y1: Number, z1: Number|Number, x2: Number=, y2: Number=, z2: Number=, x3: Number=, y3: Number=, z3: Number=, x4: Number=, y4: Number=, z4: Number=): undefined} bezier + * @property {function(x1: Number, y1: Number, z1: Number|Number, x2: Number=, y2: Number=, z2: Number=, x3: Number=, y3: Number=, z3: Number=, x4: Number=, y4: Number=, z4: Number=): undefined} spline */ /** @@ -2627,7 +2631,7 @@ function primitives3D(p5, fn){ * integer. * @returns {p5.InstancesWrapper} an object with methods `sphere`, `box`, `plane`, * `ellipsoid`, `cylinder`, `cone`, `torus`, `triangle`, `rect`, `quad`, - * `ellipse`, `arc`, and `model`. Call one of + * `ellipse`, `arc`, `model`, `line`, `point`, `bezier`, and `spline`. Call one of * these to draw `count` instances of that primitive. * * @example @@ -2688,7 +2692,7 @@ function primitives3D(p5, fn){ } }; - return { + const result = { sphere: wrap(r.sphere), box: wrap(r.box), plane: wrap(r.plane), @@ -2701,8 +2705,18 @@ function primitives3D(p5, fn){ quad: wrap(this.quad, this), ellipse: wrap(this.ellipse, this), arc: wrap(this.arc, this), - model: wrap(r.model) + model: wrap(r.model), + line: wrap(this.line, this), + point: wrap(this.point, this), + bezier: wrap(this.bezier, this), + spline: wrap(this.spline, this) }; + + if (typeof this.curve === 'function') { + result.curve = wrap(this.curve, this); + } + + return result; }; } diff --git a/src/webgl/loading.js b/src/webgl/loading.js index 5edd0d0791..aafc095e80 100755 --- a/src/webgl/loading.js +++ b/src/webgl/loading.js @@ -1077,23 +1077,17 @@ function loading(p5, fn){ * } * ``` * - * Multiple instances can be drawn at once with `model(geometry, count)`. On its own, + * Multiple instances can be drawn at once with `instances(count)`. On its own, * all the instances get drawn to the same spot, but you can use - * `instanceID()` inside of a shader to handle each instance. + * `instanceIndex` inside of a shader to handle each instance. * At large counts, this often runs faster than using a `for` loop. * * ```js example * let instancesShader; - * let instance; * let count = 5; * - * function drawInstance() { - * sphere(15); - * } - * * function setup() { * createCanvas(200, 200, WEBGL); - * instance = buildGeometry(drawInstance); * instancesShader = buildMaterialShader(drawSpaced); * } * @@ -1102,7 +1096,7 @@ function loading(p5, fn){ * // Spread spheres evenly across the canvas based on their index * let spacing = width / count; * worldInputs.position.x += - * (instanceID() - (count - 1) / 2) * spacing; + * (instanceIndex - (count - 1) / 2) * spacing; * worldInputs.end(); * } * @@ -1112,7 +1106,7 @@ function loading(p5, fn){ * noStroke(); * fill('red'); * shader(instancesShader); - * model(instance, count); + * instances(count).sphere(15); * } * ``` * diff --git a/src/webgpu/p5.RendererWebGPU.js b/src/webgpu/p5.RendererWebGPU.js index 3b909230b7..87ec15f8b4 100644 --- a/src/webgpu/p5.RendererWebGPU.js +++ b/src/webgpu/p5.RendererWebGPU.js @@ -58,7 +58,6 @@ function rendererWebGPU(p5, fn) { * let particles; * let computeShader; * let displayShader; - * let instance; * const numParticles = 100; * * async function setup() { @@ -66,7 +65,6 @@ function rendererWebGPU(p5, fn) { * particles = createStorage(makeParticles(width / 2, height / 2)); * computeShader = buildComputeShader(simulate); * displayShader = buildMaterialShader(display); - * instance = buildGeometry(drawParticle); * describe('100 orange particles shooting outward.'); * } * @@ -83,10 +81,6 @@ function rendererWebGPU(p5, fn) { * return data; * } * - * function drawParticle() { - * sphere(2); - * } - * * function simulate() { * let data = uniformStorage(particles); * let idx = index.x; @@ -96,7 +90,7 @@ function rendererWebGPU(p5, fn) { * function display() { * let data = uniformStorage(particles); * worldInputs.begin(); - * let pos = data[instanceID()].position; + * let pos = data[instanceIndex].position; * worldInputs.position.xy += pos - [width / 2, height / 2]; * worldInputs.end(); * } @@ -110,7 +104,7 @@ function rendererWebGPU(p5, fn) { * noStroke(); * fill(255, 200, 50); * shader(displayShader); - * model(instance, numParticles); + * instances(numParticles).sphere(2); * } * ``` * diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 71b3f776a2..56406f8d3f 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -1138,6 +1138,27 @@ visualSuite('WebGL', function() { p5.instances(count).rect(-5, -5, 10, 10); screenshot(); }); + + visualTest('instances() API draws immediate-mode shape primitives', (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const count = 4; + const shader = p5.baseMaterialShader().modify(() => { + p5.getWorldInputs(inputs => { + let spacing = p5.width / count; + inputs.position.x += (p5.instanceIndex - (count - 1) / 2.0) * spacing; + return inputs; + }); + }, { p5, count }); + p5.background(220); + p5.stroke(0); + p5.strokeWeight(2); + p5.shader(shader); + p5.instances(count).line(0, -15, 0, 0, 15, 0); + p5.instances(count).point(0, -10, 0); + p5.instances(count).bezier(-5, -5, 0, -2, 5, 0, 2, -5, 0, 5, 5, 0); + p5.instances(count).spline(-5, 5, 0, -2, -5, 0, 2, 5, 0, 5, -5, 0); + screenshot(); + }); }); visualSuite('p5.strands', () => { diff --git a/test/unit/visual/cases/webgpu.js b/test/unit/visual/cases/webgpu.js index af4d93f365..96972b766e 100644 --- a/test/unit/visual/cases/webgpu.js +++ b/test/unit/visual/cases/webgpu.js @@ -363,6 +363,27 @@ visualSuite("WebGPU", function () { await screenshot(); }); + visualTest('instances() API draws immediate-mode shape primitives (WebGPU)', async function(p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + const count = 4; + const shader = p5.baseMaterialShader().modify(() => { + p5.getWorldInputs(inputs => { + let spacing = p5.width / count; + inputs.position.x += (p5.instanceIndex - (count - 1) / 2.0) * spacing; + return inputs; + }); + }, { p5, count }); + p5.background(220); + p5.stroke(0); + p5.strokeWeight(2); + p5.shader(shader); + p5.instances(count).line(0, -15, 0, 0, 15, 0); + p5.instances(count).point(0, -10, 0); + p5.instances(count).bezier(-5, -5, 0, -2, 5, 0, 2, -5, 0, 5, 5, 0); + p5.instances(count).spline(-5, 5, 0, -2, -5, 0, 2, 5, 0, 5, -5, 0); + await screenshot(); + }); + visualTest('random() colors a basic shader (WebGPU)', async function(p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); const shader = p5.baseColorShader().modify(() => { @@ -419,7 +440,7 @@ visualSuite("WebGPU", function () { p5.shader(shader); p5.plane(20, 20); await screenshot(); - }, { focus: true }); + }); }); visualTest('randomGaussian() colors a basic shader (WebGPU)', async function(p5, screenshot) { diff --git a/test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws immediate-mode shape primitives/000.png b/test/unit/visual/screenshots/WebGL/instanced randering/instances() API draws immediate-mode shape primitives/000.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec09c70f33660194cb9283db09b6802303d7a07 GIT binary patch literal 748 zcmViS*Qg^0aztY;xG%Co-K*9Z0V5hZlPn^20hh zyA>gwTbMaOnMme_b`Do2(z%711C)tmZfNIlStr6bIH0QKa{2Nb9Up*SE<6btkH^v2 z@WbISoXLj&B*TJ;)9DoU)_BF^LoCH*FB_hH3_1%!Ai!uedh!>4zu&_dNdW$wbFf~o zS=_5)@#79ov)PO#fHPu!mm^%Ljmc!<8zjC|l{X`6tF*eAyC&33zW5;>pG{}W;3So^CS5`ljZ zqA5T&yANkCv2P~e98QJeV_`*A^|R}eLvbIEhs=LZiy~l2aJ^nJ11K_VBF5!%@z3aR zIQUZvU9DEG{tu?#^U-{AsX1&d*7g&LiLi+{2NWde*+~Tae!pk&X!_f|rjwGxPQk|X zLdYDO1f0(7B{=q01e^-wua|wQobluUHqc+MwmBr8dp1W9A_tNPa9m&&#~Kp>FCmN^ zk@&$|@Wj|l2(jDb;I8~_4ljhnIK)6Ac&*s@1MC=7Y|{sRx7$5kQEaU^a2H@V(e&fo z3&F?%ez`#`7Q94YgP30`msLn=5jpM^AvwC3Ge((6&Ti(2RVI?7i#cPIiRA2NBvxyx z_lfWg4q8{%@V!Z e21!IgR09BXzz;ba4PGJu0000LYlA3- ze#J8!4lCU&sb4ktzjaGLoley-=;n+spA*g62QD8_4Swl9jxN6qf`eo*7z~1f_<{WB zg7T06|7x{D1V4Z|zF+Wcw_CH6Yesw#6Hhel!DcN;U{3cMjYid4ZIH*~5qiB|s7qFZ z`~4pLA2vkzXoj3lrwGGrHVd-|GvZ4<;EU#u2OquR^a1R&hvN9@3okw!1cf3bfE1|) z=qVro@U8RLJ*x~4TMhx=mL4t$)!d%3-(ux zkNbQ+2mIvB=kq{5b=ct59_s7!t3kM0kO6iM>EVm;iwqq+pHI85TIhPc#{C1E&Bp9m zymD=N7)gB5GYWgEL9{R$+-|pkw2b&lhlnm)G!k5|*FeqTaKN;0V=|e9_#aHOdGY-) z06Ki+H|aqw9Kh=~NIFECL>%Bx!4M+c@AnpF!T!qNq+X|`E2lce$w5!yd-&mfk7^JS z*dpC|p-K~jQ$ z*?iofE{_2eLkD$vO?_DhX}X9Ub!CtoyO@)soJh{y%)wPoB*!l1N`K##`4>;m4fFUjP6A|Nl;6+O+@x f00v1!K~w_(&EXq4Y*wVO00000NkvXXu0mjf2eof$ literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws immediate-mode shape primitives (WebGPU)/metadata.json b/test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws immediate-mode shape primitives (WebGPU)/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGPU/Shaders/instances() API draws immediate-mode shape primitives (WebGPU)/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file