From 2910e336260e280290f28d8255522f594192c8ef Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Tue, 16 Aug 2022 23:43:23 -0700 Subject: [PATCH] Sparse instance updates - Use MemoryBlock in IndirectBuffers --- .../instancing/indirect/IndirectBuffers.java | 50 +++++---- .../instancing/indirect/IndirectEngine.java | 9 +- .../instancing/indirect/IndirectFactory.java | 37 ++----- .../indirect/IndirectInstancer.java | 5 +- .../instancing/indirect/IndirectList.java | 104 ++++++++++++------ ...InstancedModel.java => IndirectModel.java} | 6 +- 6 files changed, 115 insertions(+), 96 deletions(-) rename src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/{InstancedModel.java => IndirectModel.java} (84%) diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectBuffers.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectBuffers.java index 6f32d02c4..3fade4ff6 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectBuffers.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectBuffers.java @@ -5,6 +5,8 @@ import static org.lwjgl.opengl.GL46.*; import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.Pointer; +import com.jozufozu.flywheel.backend.memory.MemoryBlock; + public class IndirectBuffers { public static final int BUFFER_COUNT = 4; public static final long INT_SIZE = Integer.BYTES; @@ -30,7 +32,7 @@ public class IndirectBuffers { private static final long BATCH_SIZE_OFFSET = TARGET_SIZE_OFFSET + PTR_SIZE; private static final long DRAW_SIZE_OFFSET = BATCH_SIZE_OFFSET + PTR_SIZE; - final long buffers; + final MemoryBlock buffers; final long objectStride; int object; int target; @@ -46,21 +48,16 @@ public class IndirectBuffers { IndirectBuffers(long objectStride) { this.objectStride = objectStride; - this.buffers = MemoryUtil.nmemAlloc(BUFFERS_SIZE_BYTES); - - if (this.buffers == MemoryUtil.NULL) { - throw new OutOfMemoryError(); - } - - MemoryUtil.memSet(this.buffers, 0, BUFFERS_SIZE_BYTES); + this.buffers = MemoryBlock.calloc(BUFFERS_SIZE_BYTES, 1); } void createBuffers() { - nglCreateBuffers(4, buffers); - object = MemoryUtil.memGetInt(buffers); - target = MemoryUtil.memGetInt(buffers + 4); - batch = MemoryUtil.memGetInt(buffers + 8); - draw = MemoryUtil.memGetInt(buffers + 12); + final long ptr = buffers.ptr(); + nglCreateBuffers(4, ptr); + object = MemoryUtil.memGetInt(ptr); + target = MemoryUtil.memGetInt(ptr + 4); + batch = MemoryUtil.memGetInt(ptr + 8); + draw = MemoryUtil.memGetInt(ptr + 12); } void updateCounts(int objectCount, int drawCount) { @@ -72,14 +69,15 @@ public class IndirectBuffers { createDrawStorage(drawCount); } - long objectSize = objectStride * objectCount; - long targetSize = INT_SIZE * objectCount; - long drawSize = DRAW_COMMAND_STRIDE * drawCount; + final long objectSize = objectStride * objectCount; + final long targetSize = INT_SIZE * objectCount; + final long drawSize = DRAW_COMMAND_STRIDE * drawCount; - MemoryUtil.memPutAddress(buffers + OBJECT_SIZE_OFFSET, objectSize); - MemoryUtil.memPutAddress(buffers + TARGET_SIZE_OFFSET, targetSize); - MemoryUtil.memPutAddress(buffers + BATCH_SIZE_OFFSET, targetSize); - MemoryUtil.memPutAddress(buffers + DRAW_SIZE_OFFSET, drawSize); + final long ptr = buffers.ptr(); + MemoryUtil.memPutAddress(ptr + OBJECT_SIZE_OFFSET, objectSize); + MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, targetSize); + MemoryUtil.memPutAddress(ptr + BATCH_SIZE_OFFSET, targetSize); + MemoryUtil.memPutAddress(ptr + DRAW_SIZE_OFFSET, drawSize); } void createObjectStorage(int objectCount) { @@ -112,7 +110,12 @@ public class IndirectBuffers { } private void bindN(int bufferCount) { - nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, bufferCount, buffers, buffers + OFFSET_OFFSET, buffers + SIZE_OFFSET); + if (bufferCount > BUFFER_COUNT) { + throw new IllegalArgumentException("Can't bind more than " + BUFFER_COUNT + " buffers"); + } + + final long ptr = buffers.ptr(); + nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, bufferCount, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); } void bindIndirectBuffer() { @@ -131,4 +134,9 @@ public class IndirectBuffers { nglNamedBufferSubData(draw, 0, length, drawPtr); // glFlushMappedNamedBufferRange(this.draw, 0, length); } + + public void delete() { + nglDeleteBuffers(BUFFER_COUNT, buffers.ptr()); + buffers.free(); + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectEngine.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectEngine.java index 5b4a61d2e..3722d7ee1 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectEngine.java @@ -10,18 +10,13 @@ import org.lwjgl.opengl.GL32; import com.jozufozu.flywheel.api.RenderStage; import com.jozufozu.flywheel.api.instancer.InstancedPart; -import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.struct.StructType; -import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.backend.gl.GlTextureUnit; import com.jozufozu.flywheel.backend.instancing.Engine; import com.jozufozu.flywheel.backend.instancing.InstanceManager; import com.jozufozu.flywheel.backend.instancing.TaskEngine; import com.jozufozu.flywheel.core.RenderContext; import com.jozufozu.flywheel.api.context.ContextShader; -import com.jozufozu.flywheel.backend.instancing.PipelineCompiler; -import com.jozufozu.flywheel.core.source.FileResolution; -import com.jozufozu.flywheel.core.uniform.UniformBuffer; import com.jozufozu.flywheel.util.WeakHashSet; import com.mojang.blaze3d.systems.RenderSystem; @@ -42,7 +37,7 @@ public class IndirectEngine implements Engine { protected final Map, IndirectFactory> factories = new HashMap<>(); - protected final List> uninitializedModels = new ArrayList<>(); + protected final List> uninitializedModels = new ArrayList<>(); protected final RenderLists renderLists = new RenderLists(); /** @@ -102,6 +97,8 @@ public class IndirectEngine implements Engine { factories.values() .forEach(IndirectFactory::delete); + renderLists.lists.values().forEach(IndirectList::delete); + factories.clear(); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectFactory.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectFactory.java index cf2f64912..d0bfaa3b8 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectFactory.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectFactory.java @@ -13,11 +13,11 @@ import com.jozufozu.flywheel.core.model.Model; public class IndirectFactory implements InstancerFactory { - protected final Map> models = new HashMap<>(); + protected final Map> models = new HashMap<>(); protected final StructType type; - private final Consumer> creationListener; + private final Consumer> creationListener; - public IndirectFactory(StructType type, Consumer> creationListener) { + public IndirectFactory(StructType type, Consumer> creationListener) { this.type = type; this.creationListener = creationListener; } @@ -27,23 +27,8 @@ public class IndirectFactory implements InstancerFactor return models.computeIfAbsent(modelKey, this::createInstancer).getInstancer(); } - public int getInstanceCount() { - return models.values() - .stream() - .map(InstancedModel::getInstancer) - .mapToInt(AbstractInstancer::getInstanceCount) - .sum(); - } - - public int getVertexCount() { - return models.values() - .stream() - .mapToInt(InstancedModel::getVertexCount) - .sum(); - } - public void delete() { - models.values().forEach(InstancedModel::delete); + models.values().forEach(IndirectModel::delete); models.clear(); } @@ -53,21 +38,13 @@ public class IndirectFactory implements InstancerFactor public void clear() { models.values() .stream() - .map(InstancedModel::getInstancer) + .map(IndirectModel::getInstancer) .forEach(AbstractInstancer::clear); } - private InstancedModel createInstancer(Model model) { - var instancer = new InstancedModel<>(type, model); + private IndirectModel createInstancer(Model model) { + var instancer = new IndirectModel<>(type, model); this.creationListener.accept(instancer); return instancer; } - -// private void bindInstanceAttributes(GlVertexArray vao) { -// vao.bindAttributes(this.vbo, this.attributeBaseIndex, this.instanceFormat, 0L); -// -// for (int i = 0; i < this.instanceFormat.getAttributeCount(); i++) { -// vao.setAttributeDivisor(this.attributeBaseIndex + i, 1); -// } -// } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectInstancer.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectInstancer.java index 561bfe3db..2a2cef2bb 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectInstancer.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectInstancer.java @@ -2,7 +2,6 @@ package com.jozufozu.flywheel.backend.instancing.indirect; import com.jozufozu.flywheel.api.instancer.InstancedPart; import com.jozufozu.flywheel.api.struct.StructType; -import com.jozufozu.flywheel.api.struct.StructWriter; import com.jozufozu.flywheel.backend.instancing.AbstractInstancer; import com.jozufozu.flywheel.core.layout.BufferLayout; @@ -10,12 +9,12 @@ public class IndirectInstancer extends AbstractInstance public final BufferLayout instanceFormat; public final StructType structType; - public final InstancedModel parent; + public final IndirectModel parent; int instanceCount = 0; boolean anyToUpdate; - public IndirectInstancer(InstancedModel parent, StructType type) { + public IndirectInstancer(IndirectModel parent, StructType type) { super(type); this.parent = parent; this.instanceFormat = type.getLayout(); diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectList.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectList.java index 90bfe5777..264b644b2 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectList.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectList.java @@ -5,7 +5,6 @@ import static org.lwjgl.opengl.GL46.*; import java.util.ArrayList; import java.util.List; -import org.jetbrains.annotations.NotNull; import org.lwjgl.system.MemoryUtil; import com.jozufozu.flywheel.api.RenderStage; @@ -27,7 +26,6 @@ public class IndirectList { final StorageBufferWriter storageBufferWriter; final GlProgram compute; final GlProgram draw; - private final StructType structType; private final VertexType vertexType; private final long objectStride; @@ -38,12 +36,11 @@ public class IndirectList { int vertexArray; - final List> batches = new ArrayList<>(); + final List batches = new ArrayList<>(); IndirectList(StructType structType, VertexType vertexType) { - this.structType = structType; this.vertexType = vertexType; - storageBufferWriter = this.structType.getStorageBufferWriter(); + storageBufferWriter = structType.getStorageBufferWriter(); objectStride = storageBufferWriter.getAlignment(); buffers = new IndirectBuffers(objectStride); @@ -59,7 +56,7 @@ public class IndirectList { .quads2Tris(2048).buffer.handle(); setupVertexArray(); - var indirectShader = this.structType.getIndirectShader(); + var indirectShader = structType.getIndirectShader(); compute = ComputeCullerCompiler.INSTANCE.get(indirectShader); draw = PipelineCompiler.INSTANCE.get(new PipelineCompiler.Context(vertexType, Materials.CHEST, indirectShader, Components.WORLD, Components.INDIRECT)); } @@ -84,14 +81,14 @@ public class IndirectList { } public void add(IndirectInstancer instancer, Material material, Mesh mesh) { - batches.add(new Batch<>(instancer, material, meshPool.alloc(mesh))); + batches.add(new Batch(instancer, material, meshPool.alloc(mesh))); } void submit(RenderStage stage) { if (batches.isEmpty()) { return; } - int instanceCountThisFrame = calculateTotalInstanceCount(); + int instanceCountThisFrame = calculateTotalInstanceCountAndPrepareBatches(); if (instanceCountThisFrame == 0) { return; @@ -119,7 +116,7 @@ public class IndirectList { final int stride = (int) IndirectBuffers.DRAW_COMMAND_STRIDE; long offset = 0; - for (Batch batch : batches) { + for (var batch : batches) { batch.material.setup(); glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, offset, 1, stride); @@ -139,22 +136,14 @@ public class IndirectList { private void uploadInstanceData() { long objectPtr = buffers.objectPtr; long batchIDPtr = buffers.batchPtr; - int baseInstance = 0; - int batchID = 0; - for (var batch : batches) { - batch.baseInstance = baseInstance; - var instancer = batch.instancer; - for (T t : instancer.getAll()) { - // write object - storageBufferWriter.write(objectPtr, t); - objectPtr += objectStride; - // write batchID - MemoryUtil.memPutInt(batchIDPtr, batchID); - batchIDPtr += IndirectBuffers.INT_SIZE; - } - baseInstance += batch.instancer.instanceCount; - batchID++; + for (int i = 0, batchesSize = batches.size(); i < batchesSize; i++) { + var batch = batches.get(i); + var instanceCount = batch.instancer.getInstanceCount(); + batch.write(objectPtr, batchIDPtr, i); + + objectPtr += instanceCount * objectStride; + batchIDPtr += instanceCount * IndirectBuffers.INT_SIZE; } buffers.flushObjects(objectPtr - buffers.objectPtr); @@ -163,34 +152,85 @@ public class IndirectList { private void uploadIndirectCommands() { long writePtr = buffers.drawPtr; - for (Batch batch : batches) { + for (var batch : batches) { batch.writeIndirectCommand(writePtr); writePtr += IndirectBuffers.DRAW_COMMAND_STRIDE; } buffers.flushDrawCommands(writePtr - buffers.drawPtr); } - private int calculateTotalInstanceCount() { - int total = 0; - for (Batch batch : batches) { - batch.instancer.update(); - total += batch.instancer.instanceCount; + private int calculateTotalInstanceCountAndPrepareBatches() { + int baseInstance = 0; + for (var batch : batches) { + batch.prepare(baseInstance); + baseInstance += batch.instancer.instanceCount; } - return total; + return baseInstance; } - private static final class Batch { + public void delete() { + glDeleteVertexArrays(vertexArray); + buffers.delete(); + meshPool.delete(); + } + + private final class Batch { final IndirectInstancer instancer; final IndirectMeshPool.BufferedMesh mesh; final Material material; int baseInstance = -1; + boolean needsFullWrite = true; + private Batch(IndirectInstancer instancer, Material material, IndirectMeshPool.BufferedMesh mesh) { this.instancer = instancer; this.material = material; this.mesh = mesh; } + public void prepare(int baseInstance) { + instancer.update(); + if (baseInstance == this.baseInstance) { + needsFullWrite = false; + return; + } + this.baseInstance = baseInstance; + needsFullWrite = true; + } + + private void write(long objectPtr, long batchIDPtr, int batchID) { + if (needsFullWrite) { + writeFull(objectPtr, batchIDPtr, batchID); + } else if (instancer.anyToUpdate) { + writeSparse(objectPtr, batchIDPtr, batchID); + } + instancer.anyToUpdate = false; + } + + private void writeSparse(long objectPtr, long batchIDPtr, int batchID) { + var all = instancer.getAll(); + for (int i = 0; i < all.size(); i++) { + final var element = all.get(i); + if (element.checkDirtyAndClear()) { + storageBufferWriter.write(objectPtr + i * objectStride, element); + + MemoryUtil.memPutInt(batchIDPtr + i * IndirectBuffers.INT_SIZE, batchID); + } + } + } + + private void writeFull(long objectPtr, long batchIDPtr, int batchID) { + for (var object : this.instancer.getAll()) { + // write object + storageBufferWriter.write(objectPtr, object); + objectPtr += objectStride; + + // write batchID + MemoryUtil.memPutInt(batchIDPtr, batchID); + batchIDPtr += IndirectBuffers.INT_SIZE; + } + } + public void writeIndirectCommand(long ptr) { var boundingSphere = mesh.mesh.getBoundingSphere(); diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/InstancedModel.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectModel.java similarity index 84% rename from src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/InstancedModel.java rename to src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectModel.java index dfa76f2c8..88e5107ea 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/InstancedModel.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectModel.java @@ -4,16 +4,14 @@ import com.jozufozu.flywheel.api.instancer.InstancedPart; import com.jozufozu.flywheel.api.struct.StructType; import com.jozufozu.flywheel.core.model.Model; -public class InstancedModel { +public class IndirectModel { private final Model model; - private final StructType type; private final IndirectInstancer instancer; - public InstancedModel(StructType type, Model model) { + public IndirectModel(StructType type, Model model) { this.model = model; this.instancer = new IndirectInstancer<>(this, type); - this.type = type; } public void init(RenderLists renderLists) {