diff --git a/examples/multi-draw-instanced.js b/examples/multi-draw-instanced.js index e40c64ee..cf7c3ae9 100644 --- a/examples/multi-draw-instanced.js +++ b/examples/multi-draw-instanced.js @@ -3,7 +3,7 @@ import createContext from "../index.js"; import { perspective as createCamera, orbiter as createOrbiter } from "pex-cam"; import createGUI from "pex-gui"; -import { cube, sphere, torus } from "primitive-geometry"; +import { cube, plane, sphere, torus } from "primitive-geometry"; import typedArrayConcat from "typed-array-concat"; import merge from "geom-merge"; import splitVertices from "geom-split-vertices"; @@ -11,11 +11,11 @@ import computeNormals from "geom-normals"; import gridCells from "grid-cells"; import { viewportToCanvasPosition } from "./utils.js"; -import basicFrag from "./shaders/basic.frag.js"; import basicMultiDrawVert from "./shaders/basic-multi-draw.vert.js"; +import basicFrag from "./shaders/basic.frag.js"; const pixelRatio = devicePixelRatio; -const ctx = (window.ctx = createContext({ pixelRatio, debug: false })); +const ctx = createContext({ pixelRatio, debug: false }); const gui = createGUI(ctx, { theme: { columnWidth: 340 } }); @@ -37,20 +37,16 @@ const camera = createCamera({ position: [3, 1, 3], target: [3, 0, 0], }); -createOrbiter({ camera }); +createOrbiter({ camera, element: ctx.gl.canvas }); // Create geometries const size = 0.3; +const planeGeometry = plane(); const sphereGeometry = sphere({ radius: size / 2 }); sphereGeometry.positions = sphereGeometry.positions.map((value, i) => i % 3 === 0 ? value - size * 1.2 : value, ); -const unindexedSphere = splitVertices( - sphereGeometry.positions, - sphereGeometry.cells, -); const cubeGeometry = cube({ sx: size }); -const unindexedCube = splitVertices(cubeGeometry.positions, cubeGeometry.cells); const torusGeometry = torus({ radius: size * 0.5, minorRadius: size * 0.5 * 0.4, @@ -58,28 +54,12 @@ const torusGeometry = torus({ torusGeometry.positions = torusGeometry.positions.map((value, i) => i % 3 === 0 ? value + size * 1.2 : value, ); -const unindexedTorus = splitVertices( - torusGeometry.positions, - torusGeometry.cells, -); -const geometries = [sphereGeometry, cubeGeometry, torusGeometry]; -const unindexedGeometries = [unindexedSphere, unindexedCube, unindexedTorus]; -// if (unindexed) { -// geometries.forEach((geometry) => { -// geometry.normals = computeNormals(geometry.positions, geometry.cells); -// }); -// } +const geometries = [planeGeometry, sphereGeometry, cubeGeometry, torusGeometry]; +const unindexedGeometries = geometries.map((geometry) => + splitVertices(geometry.positions, geometry.cells), +); const indexedMergedGeometry = merge(geometries); -// const indexedMergedGeometry = { -// positions: typedArrayConcat( -// Float32Array, -// ...geometries.map((g) => g.positions) -// ), -// normals: typedArrayConcat(Float32Array, ...geometries.map((g) => g.normals)), -// // uvs: typedArrayConcat(Float32Array, ...geometries.map((g) => g.uvs)), -// cells: typedArrayConcat(CellsConstructor, ...geometries.map((g) => g.cells)), -// }; const unindexedMergedGeometry = merge(unindexedGeometries); // Buffers @@ -94,15 +74,15 @@ const offsets = new Int32Array(geometries.length); const firsts = new Int32Array(geometries.length); const baseVertices = new Int32Array(geometries.length); const baseInstances = new Int32Array(geometries.length); -const instanceCounts = new Int32Array([3, 6, 9]); // 3 spheres, 6 cubes, 9 torii +const instanceCounts = new Int32Array([1, 3, 6, 9]); // 1 plane, 3 spheres, 6 cubes, 9 torii const instancePositions = []; // 3 + 6 + 9 = 18 positions for (let i = 0; i < geometries.length; i++) { const numInstancesOfEachGeometry = instanceCounts[i]; for (let j = 0; j < numInstancesOfEachGeometry; j++) { - instancePositions.push([j, i, 0]); - // instancePositions.push([j - 3, i - (geometries.length - 1) / 2, 0]); + // instancePositions.push([j, i, 0]); + instancePositions.push([j - 3, i - (geometries.length - 1) / 2, 0]); } counts[i] = geometries[i].cells.length; @@ -124,6 +104,9 @@ for (let i = 0; i < geometries.length; i++) { } } +// Ignore first geometry to test offsets +instancePositions.shift(); + const instancedOffsets = ctx.vertexBuffer(instancePositions); // Commands @@ -161,6 +144,20 @@ ${basicMultiDrawVert}`, uViewMatrix: camera.viewMatrix, }, }; +const drawInstancedBaseCmd = { + pipeline: ctx.pipeline({ + depthTest: true, + vert: /* glsl */ ` +${ctx.capabilities.multiDrawInstancedBase ? "#define USE_MULTI_DRAW_INSTANCED_BASE" : ""} +#define USE_INSTANCED_OFFSET +${basicMultiDrawVert}`, + frag: basicFrag, + }), + uniforms: { + uProjectionMatrix: camera.projectionMatrix, + uViewMatrix: camera.viewMatrix, + }, +}; const commands = { "MultiDraw Indexed": { @@ -202,7 +199,7 @@ const commands = { // Ignore first geometry to test offsets countsOffset: 1, offsetsOffset: 1, - instanceCountsOffset: 1, + instanceCountsOffset: 1, // offset in instanceCounts array drawCount: geometries.length - 1, }, }, @@ -274,14 +271,19 @@ ctx.frame(() => { headers[header].setPosition( ...viewportToCanvasPosition(cell, H, pixelRatio).map((n) => n + 10), ); - ctx.submit( - options.multiDraw.instanceCounts ? drawInstancedCmd : drawCmd, - { - viewport: cell, - scissor: cell, - ...options, - }, + const hasBaseInstances = !!( + options.multiDraw.baseInstances || options.multiDraw.baseVertices ); + const cmd = options.multiDraw.instanceCounts + ? hasBaseInstances + ? drawInstancedBaseCmd + : drawInstancedCmd + : drawCmd; + ctx.submit(cmd, { + viewport: cell, + scissor: cell, + ...options, + }); } }); diff --git a/examples/multi-draw.js b/examples/multi-draw.js index a38b0aef..04f804b7 100644 --- a/examples/multi-draw.js +++ b/examples/multi-draw.js @@ -6,6 +6,7 @@ import { plane, cube, sphere, torus } from "primitive-geometry"; import merge from "geom-merge"; import splitVertices from "geom-split-vertices"; +import basicMultiDrawVert from "./shaders/basic-multi-draw.vert.js"; import basicFrag from "./shaders/basic.frag.js"; const ctx = createContext({ pixelRatio: devicePixelRatio }); @@ -13,7 +14,7 @@ const ctx = createContext({ pixelRatio: devicePixelRatio }); const camera = createCamera({ position: [0, 0, 3], }); -createOrbiter({ camera }); +createOrbiter({ camera, element: ctx.gl.canvas }); const clearCmd = { pass: ctx.pass({ @@ -28,12 +29,12 @@ const unindexed = true; const planeGeometry = plane(); const sphereGeometry = sphere(); sphereGeometry.positions = sphereGeometry.positions.map((value, i) => - i % 3 === 0 ? value - 1.25 : value + i % 3 === 0 ? value - 1.25 : value, ); const torusGeometry = torus(); const cubeGeometry = cube(); cubeGeometry.positions = cubeGeometry.positions.map((value, i) => - i % 3 === 0 ? value + 1.25 : value + i % 3 === 0 ? value + 1.25 : value, ); const geometries = [ planeGeometry, @@ -41,7 +42,7 @@ const geometries = [ torusGeometry, cubeGeometry, ].map((geometry) => - unindexed ? splitVertices(geometry.positions, geometry.cells) : geometry + unindexed ? splitVertices(geometry.positions, geometry.cells) : geometry, ); const geometry = merge(geometries); @@ -68,44 +69,13 @@ if (unindexed) { } } } -console.log("geometry", geometry); -console.log("counts", counts); -console.log("offsets", offsets); -console.log("firsts", firsts); const drawCmd = { pipeline: ctx.pipeline({ depthTest: true, vert: /* glsl */ ` ${ctx.capabilities.multiDraw ? "#define USE_MULTI_DRAW" : ""} -#ifdef USE_MULTI_DRAW - #extension GL_ANGLE_multi_draw: require -#endif - -attribute vec3 aPosition; - -uniform mat4 uProjectionMatrix; -uniform mat4 uViewMatrix; - -varying vec4 vColor; - -void main () { - #ifdef USE_MULTI_DRAW - if (gl_DrawID == 0) { - vColor = vec4(1.0, 0.5, 0.5, 1.0); - } else if (gl_DrawID == 1) { - vColor = vec4(0.5, 1.0, 0.5, 1.0); - } else if (gl_DrawID == 2) { - vColor = vec4(0.5, 0.5, 1.0, 1.0); - } else { - vColor = vec4(1.0, 1.0, 0.5, 1.0); - } - #else - vColor = vec4(1.0, 1.0, 1.0, 1.0); - #endif - - gl_Position = uProjectionMatrix * uViewMatrix * vec4(aPosition, 1.0); -}`, +${basicMultiDrawVert}`, frag: basicFrag, }), attributes: { diff --git a/examples/shaders/basic-multi-draw.vert.js b/examples/shaders/basic-multi-draw.vert.js index 3f2c8c9b..f5ff4964 100644 --- a/examples/shaders/basic-multi-draw.vert.js +++ b/examples/shaders/basic-multi-draw.vert.js @@ -1,6 +1,9 @@ export default /* glsl */ ` -#ifdef USE_MULTI_DRAW +#if defined(USE_MULTI_DRAW) || defined(USE_MULTI_DRAW_INSTANCED_BASE) #extension GL_ANGLE_multi_draw: require +#else + uniform highp int uDrawID; + #define gl_DrawID uDrawID #endif attribute vec3 aPosition; @@ -21,7 +24,6 @@ void main () { position.xyz += aOffset; #endif -#ifdef USE_MULTI_DRAW if (gl_DrawID == 0) { vColor = vec4(1.0, 0.5, 0.5, 1.0); } else if (gl_DrawID == 1) { @@ -31,9 +33,6 @@ void main () { } else { vColor = vec4(1.0, 1.0, 0.5, 1.0); } -#else - vColor = vec4(1.0, 1.0, 1.0, 1.0); -#endif gl_Position = uProjectionMatrix * uViewMatrix * position; } diff --git a/package.json b/package.json index d099beef..a09d263b 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,6 @@ "engines": { "node": ">=22.0.0", "npm": ">=10.5.1", - "snowdev": ">=2.5.x" + "snowdev": ">=2.7.x" } } diff --git a/utils.js b/utils.js index 5c7486d2..2008eb93 100644 --- a/utils.js +++ b/utils.js @@ -95,39 +95,47 @@ function enableVertexData(ctx, vertexLayout, cmd, updateState) { ctx.state.activeAttributes[location + 3] = buffer; } + const type = attrib.type || buffer.type; + const stride = attrib.stride || 64; + const baseOffset = + !ctx.capabilities.drawInstancedBase && + attrib.divisor && + cmd.baseInstance + ? Math.floor(cmd.baseInstance / attrib.divisor) * stride + : 0; // TODO: is this still valid? // we still check for buffer type because while e.g. pex-renderer would copy buffer type to attrib, a raw pex-context example probably would not gl.vertexAttribPointer( location, 4, - attrib.type || buffer.type, + type, attrib.normalized || false, - attrib.stride || 64, - attrib.offset || 0, + stride, + (attrib.offset || 0) + baseOffset, ); gl.vertexAttribPointer( location + 1, 4, - attrib.type || buffer.type, + type, attrib.normalized || false, - attrib.stride || 64, - attrib.offset || 16, + stride, + (attrib.offset || 16) + baseOffset, ); gl.vertexAttribPointer( location + 2, 4, - attrib.type || buffer.type, + type, attrib.normalized || false, - attrib.stride || 64, - attrib.offset || 32, + stride, + (attrib.offset || 32) + baseOffset, ); gl.vertexAttribPointer( location + 3, 4, - attrib.type || buffer.type, + type, attrib.normalized || false, - attrib.stride || 64, - attrib.offset || 48, + stride, + (attrib.offset || 48) + baseOffset, ); const divisor = attrib.divisor || 0; gl.vertexAttribDivisor(location + 0, divisor); @@ -140,18 +148,17 @@ function enableVertexData(ctx, vertexLayout, cmd, updateState) { let offset = attrib.offset || 0; const type = attrib.type || buffer.type; - // Instanced Base fallback + // Base vertex/instance fallback if ( !ctx.capabilities.drawInstancedBase && (cmd.baseInstance || cmd.baseVertex) ) { - if (indices) { - offset += - (attrib.divisor ? cmd.baseInstance : cmd.baseVertex) * - size * - ctx.DataTypeConstructor[type].BYTES_PER_ELEMENT; - } else { - if (attrib.divisor) offset += (cmd.count * cmd.baseInstance) / size; + const bytesPerElement = ctx.DataTypeConstructor[type].BYTES_PER_ELEMENT; + if (attrib.divisor) { + const stride = attrib.stride || size * bytesPerElement; + offset += Math.floor(cmd.baseInstance / attrib.divisor) * stride; + } else if (indices) { + offset += (cmd.baseVertex || 0) * size * bytesPerElement; } } @@ -223,30 +230,143 @@ function drawElements(ctx, cmd, instanced, primitive) { cmd.multiDraw.drawCount || cmd.multiDraw.counts.length, ); } else { - // Multidraw elements instanced (or multidraw base fallback with offset from enableVertexData) - const ext = gl.getExtension("WEBGL_multi_draw"); - - // TODO: fallback + // Multidraw elements instanced (or multidraw base fallback) if (cmd.multiDraw.baseVertices || cmd.multiDraw.baseInstances) { - console.error("Unsupported multiDrawInstancedBase. No fallback."); + const countsOffset = cmd.multiDraw.countsOffset || 0; + const offsetsOffset = cmd.multiDraw.offsetsOffset || 0; + const instanceCountsOffset = + cmd.multiDraw.instanceCountsOffset || 0; + const baseVerticesOffset = cmd.multiDraw.baseVerticesOffset || 0; + const baseInstancesOffset = cmd.multiDraw.baseInstancesOffset || 0; + const drawCount = + cmd.multiDraw.drawCount || cmd.multiDraw.counts.length; + const drawIDLoc = + ctx.state.program?.uniforms["uDrawID"]?.location ?? null; + if (ctx.capabilities.drawInstancedBase) { + const ext = gl.getExtension( + "WEBGL_draw_instanced_base_vertex_base_instance", + ); + for (let i = 0; i < drawCount; i++) { + if (drawIDLoc !== null) gl.uniform1i(drawIDLoc, i); + ext.drawElementsInstancedBaseVertexBaseInstanceWEBGL( + primitive, + cmd.multiDraw.counts[i + countsOffset], + type, + cmd.multiDraw.offsets[i + offsetsOffset], + cmd.multiDraw.instanceCounts[i + instanceCountsOffset], + cmd.multiDraw.baseVertices?.[i + baseVerticesOffset] || 0, + cmd.multiDraw.baseInstances?.[i + baseInstancesOffset] || 0, + ); + } + } else { + for (let i = 0; i < drawCount; i++) { + if (cmd.attributes) { + enableVertexData( + ctx, + ctx.state.vertexLayout, + { + attributes: cmd.attributes, + indices: cmd.indices, + baseVertex: + cmd.multiDraw.baseVertices?.[i + baseVerticesOffset] || + 0, + baseInstance: + cmd.multiDraw.baseInstances?.[ + i + baseInstancesOffset + ] || 0, + }, + false, + ); + } + if (drawIDLoc !== null) gl.uniform1i(drawIDLoc, i); + gl.drawElementsInstanced( + primitive, + cmd.multiDraw.counts[i + countsOffset], + type, + cmd.multiDraw.offsets[i + offsetsOffset], + cmd.multiDraw.instanceCounts[i + instanceCountsOffset], + ); + } + } + } else { + const ext = gl.getExtension("WEBGL_multi_draw"); + ext.multiDrawElementsInstancedWEBGL( + primitive, + cmd.multiDraw.counts, + cmd.multiDraw.countsOffset || 0, + type, + cmd.multiDraw.offsets, + cmd.multiDraw.offsetsOffset || 0, + cmd.multiDraw.instanceCounts, + cmd.multiDraw.instanceCountsOffset || 0, + cmd.multiDraw.drawCount || cmd.multiDraw.counts.length, + ); } - - ext.multiDrawElementsInstancedWEBGL( - primitive, - cmd.multiDraw.counts, - cmd.multiDraw.countsOffset || 0, - type, - cmd.multiDraw.offsets, - cmd.multiDraw.offsetsOffset || 0, - cmd.multiDraw.instanceCounts, - cmd.multiDraw.instanceCountsOffset || 0, - cmd.multiDraw.drawCount || cmd.multiDraw.counts.length, - ); } } else { // Multi draw elements instanced fallback - // TODO: fallback - console.error("Unsupported multidraw. No fallback."); + const countsOffset = cmd.multiDraw.countsOffset || 0; + const offsetsOffset = cmd.multiDraw.offsetsOffset || 0; + const instanceCountsOffset = cmd.multiDraw.instanceCountsOffset || 0; + const drawCount = + cmd.multiDraw.drawCount || cmd.multiDraw.counts.length; + if ( + ctx.capabilities.drawInstancedBase && + (cmd.multiDraw.baseVertices || cmd.multiDraw.baseInstances) + ) { + const baseVerticesOffset = cmd.multiDraw.baseVerticesOffset || 0; + const baseInstancesOffset = cmd.multiDraw.baseInstancesOffset || 0; + const ext = gl.getExtension( + "WEBGL_draw_instanced_base_vertex_base_instance", + ); + const drawIDLoc = + ctx.state.program?.uniforms["uDrawID"]?.location ?? null; + for (let i = 0; i < drawCount; i++) { + if (drawIDLoc !== null) gl.uniform1i(drawIDLoc, i); + ext.drawElementsInstancedBaseVertexBaseInstanceWEBGL( + primitive, + cmd.multiDraw.counts[i + countsOffset], + type, + cmd.multiDraw.offsets[i + offsetsOffset], + cmd.multiDraw.instanceCounts[i + instanceCountsOffset], + cmd.multiDraw.baseVertices?.[i + baseVerticesOffset] || 0, + cmd.multiDraw.baseInstances?.[i + baseInstancesOffset] || 0, + ); + } + } else { + const baseVerticesOffset = cmd.multiDraw.baseVerticesOffset || 0; + const baseInstancesOffset = cmd.multiDraw.baseInstancesOffset || 0; + const drawIDLoc = + ctx.state.program?.uniforms["uDrawID"]?.location ?? null; + for (let i = 0; i < drawCount; i++) { + if ( + (cmd.multiDraw.baseVertices || cmd.multiDraw.baseInstances) && + cmd.attributes + ) { + enableVertexData( + ctx, + ctx.state.vertexLayout, + { + attributes: cmd.attributes, + indices: cmd.indices, + baseVertex: + cmd.multiDraw.baseVertices?.[i + baseVerticesOffset] || 0, + baseInstance: + cmd.multiDraw.baseInstances?.[i + baseInstancesOffset] || 0, + }, + false, + ); + } + if (drawIDLoc !== null) gl.uniform1i(drawIDLoc, i); + gl.drawElementsInstanced( + primitive, + cmd.multiDraw.counts[i + countsOffset], + type, + cmd.multiDraw.offsets[i + offsetsOffset], + cmd.multiDraw.instanceCounts[i + instanceCountsOffset], + ); + } + } } } else { // Non-multi drawing @@ -293,7 +413,10 @@ function drawElements(ctx, cmd, instanced, primitive) { const offsetsOffset = cmd.multiDraw.offsetsOffset || 0; const drawCount = cmd.multiDraw.drawCount || cmd.multiDraw.counts.length; + const drawIDLoc = + ctx.state.program?.uniforms["uDrawID"]?.location ?? null; for (let i = 0; i < drawCount; i++) { + if (drawIDLoc !== null) gl.uniform1i(drawIDLoc, i); gl.drawElements( primitive, cmd.multiDraw.counts[i + countsOffset], @@ -312,39 +435,135 @@ function drawArrays(ctx, cmd, instanced, primitive) { const gl = ctx.gl; if (instanced) { - if (cmd.multiDraw && ctx.capabilities.multiDraw) { - if ( - cmd.multiDraw.baseInstances && - ctx.capabilities.multiDrawInstancedBase - ) { - const ext = gl.getExtension( - "WEBGL_multi_draw_instanced_base_vertex_base_instance", - ); + if (cmd.multiDraw) { + if (ctx.capabilities.multiDraw) { + if ( + cmd.multiDraw.baseInstances && + ctx.capabilities.multiDrawInstancedBase + ) { + const ext = gl.getExtension( + "WEBGL_multi_draw_instanced_base_vertex_base_instance", + ); - ext.multiDrawArraysInstancedBaseInstanceWEBGL( - primitive, - cmd.multiDraw.firsts, - cmd.multiDraw.firstsOffset || 0, - cmd.multiDraw.counts, - cmd.multiDraw.countsOffset || 0, - cmd.multiDraw.instanceCounts, - cmd.multiDraw.instanceCountsOffset || 0, - cmd.multiDraw.baseInstances, - cmd.multiDraw.baseInstancesOffset || 0, - cmd.multiDraw.drawCount || cmd.multiDraw.firsts.length, - ); + ext.multiDrawArraysInstancedBaseInstanceWEBGL( + primitive, + cmd.multiDraw.firsts, + cmd.multiDraw.firstsOffset || 0, + cmd.multiDraw.counts, + cmd.multiDraw.countsOffset || 0, + cmd.multiDraw.instanceCounts, + cmd.multiDraw.instanceCountsOffset || 0, + cmd.multiDraw.baseInstances, + cmd.multiDraw.baseInstancesOffset || 0, + cmd.multiDraw.drawCount || cmd.multiDraw.firsts.length, + ); + } else { + // Multidraw arrays instanced (or multidraw base fallback) + if (cmd.multiDraw.baseInstances) { + const firstsOffset = cmd.multiDraw.firstsOffset || 0; + const countsOffset = cmd.multiDraw.countsOffset || 0; + const instanceCountsOffset = + cmd.multiDraw.instanceCountsOffset || 0; + const baseInstancesOffset = cmd.multiDraw.baseInstancesOffset || 0; + const drawCount = + cmd.multiDraw.drawCount || cmd.multiDraw.firsts.length; + const drawIDLoc = + ctx.state.program?.uniforms["uDrawID"]?.location ?? null; + if (ctx.capabilities.drawInstancedBase) { + const ext = gl.getExtension( + "WEBGL_draw_instanced_base_vertex_base_instance", + ); + for (let i = 0; i < drawCount; i++) { + if (drawIDLoc !== null) gl.uniform1i(drawIDLoc, i); + ext.drawArraysInstancedBaseInstanceWEBGL( + primitive, + cmd.multiDraw.firsts[i + firstsOffset], + cmd.multiDraw.counts[i + countsOffset], + cmd.multiDraw.instanceCounts[i + instanceCountsOffset], + cmd.multiDraw.baseInstances[i + baseInstancesOffset], + ); + } + } else { + for (let i = 0; i < drawCount; i++) { + const baseInstance = + cmd.multiDraw.baseInstances[i + baseInstancesOffset]; + if (baseInstance && cmd.attributes) { + enableVertexData( + ctx, + ctx.state.vertexLayout, + { attributes: cmd.attributes, baseInstance }, + false, + ); + } + if (drawIDLoc !== null) gl.uniform1i(drawIDLoc, i); + gl.drawArraysInstanced( + primitive, + cmd.multiDraw.firsts[i + firstsOffset], + cmd.multiDraw.counts[i + countsOffset], + cmd.multiDraw.instanceCounts[i + instanceCountsOffset], + ); + } + } + } else { + const ext = gl.getExtension("WEBGL_multi_draw"); + ext.multiDrawArraysInstancedWEBGL( + primitive, + cmd.multiDraw.firsts, + cmd.multiDraw.firstsOffset || 0, + cmd.multiDraw.counts, + cmd.multiDraw.countsOffset || 0, + cmd.multiDraw.instanceCounts, + cmd.multiDraw.instanceCountsOffset || 0, + cmd.multiDraw.drawCount || cmd.multiDraw.firsts.length, + ); + } + } } else { - const ext = gl.getExtension("WEBGL_multi_draw"); - ext.multiDrawArraysInstancedWEBGL( - primitive, - cmd.multiDraw.firsts, - cmd.multiDraw.firstsOffset || 0, - cmd.multiDraw.counts, - cmd.multiDraw.countsOffset || 0, - cmd.multiDraw.instanceCounts, - cmd.multiDraw.instanceCountsOffset || 0, - cmd.multiDraw.drawCount || cmd.multiDraw.firsts.length, - ); + // Multidraw arrays instanced fallback + const firstsOffset = cmd.multiDraw.firstsOffset || 0; + const countsOffset = cmd.multiDraw.countsOffset || 0; + const instanceCountsOffset = cmd.multiDraw.instanceCountsOffset || 0; + const drawCount = + cmd.multiDraw.drawCount || cmd.multiDraw.firsts.length; + const drawIDLoc = + ctx.state.program?.uniforms["uDrawID"]?.location ?? null; + if (ctx.capabilities.drawInstancedBase && cmd.multiDraw.baseInstances) { + const baseInstancesOffset = cmd.multiDraw.baseInstancesOffset || 0; + const ext = gl.getExtension( + "WEBGL_draw_instanced_base_vertex_base_instance", + ); + for (let i = 0; i < drawCount; i++) { + if (drawIDLoc !== null) gl.uniform1i(drawIDLoc, i); + ext.drawArraysInstancedBaseInstanceWEBGL( + primitive, + cmd.multiDraw.firsts[i + firstsOffset], + cmd.multiDraw.counts[i + countsOffset], + cmd.multiDraw.instanceCounts[i + instanceCountsOffset], + cmd.multiDraw.baseInstances[i + baseInstancesOffset], + ); + } + } else { + const baseInstancesOffset = cmd.multiDraw.baseInstancesOffset || 0; + for (let i = 0; i < drawCount; i++) { + const baseInstance = + cmd.multiDraw.baseInstances?.[i + baseInstancesOffset] || 0; + if (baseInstance && cmd.attributes) { + enableVertexData( + ctx, + ctx.state.vertexLayout, + { attributes: cmd.attributes, baseInstance }, + false, + ); + } + if (drawIDLoc !== null) gl.uniform1i(drawIDLoc, i); + gl.drawArraysInstanced( + primitive, + cmd.multiDraw.firsts[i + firstsOffset], + cmd.multiDraw.counts[i + countsOffset], + cmd.multiDraw.instanceCounts[i + instanceCountsOffset], + ); + } + } } } else { // Non-multi drawing @@ -393,7 +612,10 @@ function drawArrays(ctx, cmd, instanced, primitive) { const countsOffset = cmd.multiDraw.countsOffset || 0; const drawCount = cmd.multiDraw.drawCount || cmd.multiDraw.firsts.length; + const drawIDLoc = + ctx.state.program?.uniforms["uDrawID"]?.location ?? null; for (let i = 0; i < drawCount; i++) { + if (drawIDLoc !== null) gl.uniform1i(drawIDLoc, i); gl.drawArrays( primitive, cmd.multiDraw.firsts[i + firstsOffset],