diff --git a/index.html b/index.html index c247274..2ff3426 100644 --- a/index.html +++ b/index.html @@ -26,6 +26,8 @@ const MAX_INSTANCES_PER_DRAWABLE = QueryArgs.getInt("instancesPerDrawable", navigator.userAgentData?.mobile ? 500 : 1000); const INSTANCE_ELEMENT_LENGTH = 16; + const SPLIT_INDIRECT_ARGS_BUFFER = QueryArgs.getBool("splitIndirectArgsBuffer", true); + const GEOMETRY_SHADER = (geometry, culled = false) => { const layout = geometry.layout; @@ -52,13 +54,18 @@ @group(1) @binding(0) var material: Material; @group(2) @binding(0) var instances: array; - @group(2) @binding(1) var culledInstances: array; + + struct CulledInstances { + indirectIndex: u32, + instances: array, + } + @group(2) @binding(1) var culled: CulledInstances; @vertex fn vertexMain(in: VertexIn) -> VertexOut { var out: VertexOut; #if ${culled} - let instanceIndex = culledInstances[in.instanceIndex]; + let instanceIndex = culled.instances[in.instanceIndex]; #else let instanceIndex = in.instanceIndex; #endif @@ -100,13 +107,21 @@ @group(0) @binding(0) var camera: CameraUniforms; @group(1) @binding(0) var instances: array; - @group(1) @binding(1) var culledInstances: array; + + struct CulledInstances { + indirectIndex: u32, + instances: array, + } + @group(1) @binding(1) var culled: CulledInstances; struct IndirectArgs { drawCount: u32, instanceCount: atomic, + reserved0: u32, + reserved1: u32, + reserved2: u32, } - @group(1) @binding(2) var indirect: IndirectArgs; + @group(1) @binding(2) var indirectArgs: array; fn isVisible(instanceIndex: u32) -> bool { let model = instances[instanceIndex]; @@ -130,8 +145,8 @@ if (!isVisible(instanceIndex)) { return; } - let culledIndex = atomicAdd(&indirect.instanceCount, 1u); - culledInstances[culledIndex] = instanceIndex; + let culledIndex = atomicAdd(&indirectArgs[culled.indirectIndex].instanceCount, 1u); + culled.instances[culledIndex] = instanceIndex; } `; @@ -398,6 +413,20 @@ this.instanceArray = new Float32Array(MAX_INSTANCES_PER_DRAWABLE * INSTANCE_ELEMENT_LENGTH); + let indirectBuffer; + let indirectBufferOffset = 0; + let indirectArgs; + + if (!SPLIT_INDIRECT_ARGS_BUFFER) { + indirectBuffer = this.device.createBuffer({ + label: 'Instance indirect', + size: 20 * this.materials.length * this.geometries.length, + usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + mappedAtCreation: true, + }); + indirectArgs = new Uint32Array(indirectBuffer.getMappedRange()); + } + for (const material of this.materials) { for (const geometry of this.geometries) { let instances = []; @@ -416,22 +445,34 @@ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST }); + const indirectOffset = indirectBufferOffset; + if (SPLIT_INDIRECT_ARGS_BUFFER) { + indirectBuffer = this.device.createBuffer({ + label: 'Instance indirect', + size: 20, + usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + mappedAtCreation: true, + }); + const indirectArgs = new Uint32Array(indirectBuffer.getMappedRange()); + indirectArgs[0] = geometry.drawCount; + indirectArgs[1] = MAX_INSTANCES_PER_DRAWABLE; + indirectBuffer.unmap(); + } else { + const index = (indirectOffset / 20) * 5; + indirectArgs[index] = geometry.drawCount; + indirectArgs[index+1] = MAX_INSTANCES_PER_DRAWABLE; + indirectBufferOffset += 20; + } + const culledInstanceBuffer = this.device.createBuffer({ label: 'Culled Instance', - size: MAX_INSTANCES_PER_DRAWABLE * Uint32Array.BYTES_PER_ELEMENT, - usage: GPUBufferUsage.STORAGE - }); - - const indirectBuffer = this.device.createBuffer({ - label: 'Instance indirect', - size: 20, - usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + size: (MAX_INSTANCES_PER_DRAWABLE * Uint32Array.BYTES_PER_ELEMENT) + 4, + usage: GPUBufferUsage.STORAGE, mappedAtCreation: true, }); - const indirectArgs = new Uint32Array(indirectBuffer.getMappedRange()); - indirectArgs[0] = geometry.drawCount; - indirectArgs[1] = MAX_INSTANCES_PER_DRAWABLE; - indirectBuffer.unmap(); + const culledInstanceArray = new Uint32Array(culledInstanceBuffer.getMappedRange(0, 4)); + culledInstanceArray[0] = indirectOffset / 20; + culledInstanceBuffer.unmap(); const instanceBindGroup = this.device.createBindGroup({ label: 'Instance', @@ -467,12 +508,17 @@ instanceCount: MAX_INSTANCES_PER_DRAWABLE, instanceBuffer, indirectBuffer, + indirectOffset, instanceBindGroup, culledInstanceBindGroup, }); } } + if (!SPLIT_INDIRECT_ARGS_BUFFER) { + indirectBuffer.unmap(); + } + this.updateInstanceBuffer(performance.now()); const updateInstanceCount = () => { @@ -588,7 +634,7 @@ commandEncoder.pushDebugGroup('Reset indirect instance counts'); // Clear the instance count of the indirect buffer for each drawable for (const drawable of this.drawables) { - commandEncoder.clearBuffer(drawable.indirectBuffer, 4, 4); + commandEncoder.clearBuffer(drawable.indirectBuffer, drawable.indirectOffset + 4, 4); } commandEncoder.popDebugGroup(); @@ -637,9 +683,9 @@ case RenderModes.culled: if(drawable.geometry.indexBinding) { - renderEncoder.drawIndexedIndirect(drawable.indirectBuffer, 0); + renderEncoder.drawIndexedIndirect(drawable.indirectBuffer, drawable.indirectOffset); } else { - renderEncoder.drawIndirect(drawable.indirectBuffer, 0); + renderEncoder.drawIndirect(drawable.indirectBuffer, drawable.indirectOffset); } break; }