From 257ee07e0eb2973f6819bc2da65e9bbb1df32c2f Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 28 May 2023 18:43:54 -0700 Subject: [PATCH] Index sequences but they're unsafe - Not totally happy with this but it's functional and better than directly supplying a GL object. - Meshes provide an IndexSequence and the length of the sequence. - IndexSequence can fill a buffer given a length. - Special case QuadIndexSequence to optimize the most common case. - All other sequences are treated as if they are unique - Instancing uses EBOCache to manage ebos - Indirect does it directly in the meshpool --- .../java/com/jozufozu/flywheel/Flywheel.java | 3 - .../flywheel/api/model/IndexSequence.java | 13 ++ .../com/jozufozu/flywheel/api/model/Mesh.java | 32 +++-- .../jozufozu/flywheel/api/model/Model.java | 2 +- .../engine/batching/BatchedMeshPool.java | 6 +- .../engine/batching/TransformCall.java | 2 +- .../engine/indirect/IndirectCullingGroup.java | 48 ++----- .../backend/engine/indirect/IndirectDraw.java | 8 +- .../engine/indirect/IndirectDrawManager.java | 4 +- .../engine/indirect/IndirectMeshPool.java | 117 ++++++++++++------ .../backend/engine/instancing/EBOCache.java | 80 ++++++++++++ .../instancing/InstancedDrawManager.java | 7 +- .../engine/instancing/InstancedMeshPool.java | 22 ++-- .../flywheel/gl/buffer/ElementBuffer.java | 23 ---- .../flywheel/lib/model/QuadIndexSequence.java | 31 +++++ .../jozufozu/flywheel/lib/model/QuadMesh.java | 23 ++-- .../flywheel/lib/model/SimpleLazyModel.java | 2 +- .../flywheel/lib/model/SimpleMesh.java | 8 +- .../flywheel/lib/modelpart/ModelPart.java | 8 +- .../flywheel/lib/util/QuadConverter.java | 108 ---------------- 20 files changed, 279 insertions(+), 268 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/api/model/IndexSequence.java create mode 100644 src/main/java/com/jozufozu/flywheel/backend/engine/instancing/EBOCache.java delete mode 100644 src/main/java/com/jozufozu/flywheel/gl/buffer/ElementBuffer.java create mode 100644 src/main/java/com/jozufozu/flywheel/lib/model/QuadIndexSequence.java delete mode 100644 src/main/java/com/jozufozu/flywheel/lib/util/QuadConverter.java diff --git a/src/main/java/com/jozufozu/flywheel/Flywheel.java b/src/main/java/com/jozufozu/flywheel/Flywheel.java index 1b1638a59..145d5f661 100644 --- a/src/main/java/com/jozufozu/flywheel/Flywheel.java +++ b/src/main/java/com/jozufozu/flywheel/Flywheel.java @@ -23,7 +23,6 @@ import com.jozufozu.flywheel.lib.material.MaterialIndices; import com.jozufozu.flywheel.lib.material.Materials; import com.jozufozu.flywheel.lib.model.Models; import com.jozufozu.flywheel.lib.model.PartialModel; -import com.jozufozu.flywheel.lib.util.QuadConverter; import com.jozufozu.flywheel.lib.util.ShadersModHandler; import com.jozufozu.flywheel.lib.vertex.VertexTypes; import com.jozufozu.flywheel.mixin.PausedPartialTickAccessor; @@ -35,7 +34,6 @@ import net.minecraft.commands.synchronization.EmptyArgumentSerializer; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.IExtensionPoint; @@ -79,7 +77,6 @@ public class Flywheel { forgeEventBus.addListener(BackendManagerImpl::onReloadRenderers); - forgeEventBus.addListener(EventPriority.HIGHEST, QuadConverter::onReloadRenderers); forgeEventBus.addListener(Models::onReloadRenderers); forgeEventBus.addListener(DrawBuffer::onReloadRenderers); forgeEventBus.addListener(UniformBuffer::onReloadRenderers); diff --git a/src/main/java/com/jozufozu/flywheel/api/model/IndexSequence.java b/src/main/java/com/jozufozu/flywheel/api/model/IndexSequence.java new file mode 100644 index 000000000..6844417bf --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/api/model/IndexSequence.java @@ -0,0 +1,13 @@ +package com.jozufozu.flywheel.api.model; + +/** + * Represents a sequence of unsigned integer vertex indices. + */ +public interface IndexSequence { + /** + * Populate the given memory region with indices. + *

+ * Do not write outside the range {@code [ptr, ptr + count * 4]}. + */ + void fill(long ptr, int count); +} diff --git a/src/main/java/com/jozufozu/flywheel/api/model/Mesh.java b/src/main/java/com/jozufozu/flywheel/api/model/Mesh.java index ef8494a89..16fd1486a 100644 --- a/src/main/java/com/jozufozu/flywheel/api/model/Mesh.java +++ b/src/main/java/com/jozufozu/flywheel/api/model/Mesh.java @@ -4,56 +4,64 @@ import org.joml.Vector4fc; import com.jozufozu.flywheel.api.vertex.MutableVertexList; import com.jozufozu.flywheel.api.vertex.VertexType; -import com.jozufozu.flywheel.gl.buffer.ElementBuffer; /** * A holder for arbitrary vertex data that can be written to memory or a vertex list. */ public interface Mesh { - VertexType getVertexType(); + VertexType vertexType(); /** * @return The number of vertices this mesh has. */ - int getVertexCount(); + int vertexCount(); /** * Is there nothing to render? * @return true if there are no vertices. */ default boolean isEmpty() { - return getVertexCount() == 0; + return vertexCount() == 0; } /** * The size in bytes that this mesh's data takes up. */ default int size() { - return getVertexType().getLayout().getStride() * getVertexCount(); + return vertexType().getLayout() + .getStride() * vertexCount(); } /** - * Write this mesh into memory. The written data will use the format defined by {@link #getVertexType()} and the amount of + * Write this mesh into memory. The written data will use the format defined by {@link #vertexType()} and the amount of * bytes written will be the same as the return value of {@link #size()}. + * * @param ptr The address to which data is written to. */ void write(long ptr); /** - * Write this mesh into a vertex list. Vertices with index {@literal <}0 or {@literal >=}{@link #getVertexCount()} will not be + * Write this mesh into a vertex list. Vertices with index {@literal <}0 or {@literal >=}{@link #vertexCount()} will not be * read or modified. + * * @param vertexList The vertex list to which data is written to. */ void write(MutableVertexList vertexList); + IndexSequence indexSequence(); + + int indexCount(); + /** - * Create an element buffer object that indexes the vertices of this mesh. - * @return an element buffer object indexing this model's vertices. + * Get a vec4 representing this mesh's bounding sphere in the format (x, y, z, radius). + * + * @return A vec4 view. */ - ElementBuffer createEBO(); - - Vector4fc getBoundingSphere(); + Vector4fc boundingSphere(); + /** + * Free this mesh's resources, memory, etc. + */ void delete(); /** diff --git a/src/main/java/com/jozufozu/flywheel/api/model/Model.java b/src/main/java/com/jozufozu/flywheel/api/model/Model.java index 6998040c2..d3d4234e6 100644 --- a/src/main/java/com/jozufozu/flywheel/api/model/Model.java +++ b/src/main/java/com/jozufozu/flywheel/api/model/Model.java @@ -12,7 +12,7 @@ public interface Model { default int getVertexCount() { int size = 0; for (Mesh mesh : getMeshes().values()) { - size += mesh.getVertexCount(); + size += mesh.vertexCount(); } return size; } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchedMeshPool.java b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchedMeshPool.java index 6987c2a78..8bc6dd946 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchedMeshPool.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchedMeshPool.java @@ -160,8 +160,8 @@ public class BatchedMeshPool { private BufferedMesh(Mesh mesh, long byteIndex) { this.mesh = mesh; - vertexCount = mesh.getVertexCount(); - boundingSphere = mesh.getBoundingSphere(); + vertexCount = mesh.vertexCount(); + boundingSphere = mesh.boundingSphere(); byteSize = vertexCount * vertexFormat.getVertexSize(); this.byteIndex = byteIndex; } @@ -174,7 +174,7 @@ public class BatchedMeshPool { return vertexCount; } - public Vector4fc getBoundingSphere() { + public Vector4fc boundingSphere() { return boundingSphere; } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/TransformCall.java b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/TransformCall.java index c0bcb6d7b..12358edfc 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/TransformCall.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/TransformCall.java @@ -35,7 +35,7 @@ public class TransformCall { MaterialVertexTransformer materialVertexTransformer = material.getVertexTransformer(); meshVertexCount = mesh.getVertexCount(); - Vector4fc meshBoundingSphere = mesh.getBoundingSphere(); + Vector4fc meshBoundingSphere = mesh.boundingSphere(); drawPlan = ForEachPlan.of(instancer::getAll, (instance, ctx) -> { var boundingSphere = new Vector4f(meshBoundingSphere); diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java index 31fe39c52..8ad9fbec7 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java @@ -8,40 +8,28 @@ import static org.lwjgl.opengl.GL43.glDispatchCompute; import com.jozufozu.flywheel.api.event.RenderStage; import com.jozufozu.flywheel.api.instance.Instance; import com.jozufozu.flywheel.api.instance.InstanceType; -import com.jozufozu.flywheel.api.layout.BufferLayout; +import com.jozufozu.flywheel.api.material.Material; +import com.jozufozu.flywheel.api.model.Mesh; import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.backend.compile.IndirectPrograms; import com.jozufozu.flywheel.backend.engine.UniformBuffer; -import com.jozufozu.flywheel.gl.array.GlVertexArray; import com.jozufozu.flywheel.gl.shader.GlProgram; import com.jozufozu.flywheel.lib.context.Contexts; -import com.jozufozu.flywheel.lib.util.QuadConverter; public class IndirectCullingGroup { - private static final int BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT; - final GlProgram compute; - final GlProgram draw; - private final VertexType vertexType; + private final GlProgram compute; + private final GlProgram draw; private final long instanceStride; - - final IndirectBuffers buffers; - - final IndirectMeshPool meshPool; - private final int elementBuffer; - - GlVertexArray vertexArray; - - final IndirectDrawSet drawSet = new IndirectDrawSet<>(); - + private final IndirectBuffers buffers; + public final IndirectMeshPool meshPool; + public final IndirectDrawSet drawSet = new IndirectDrawSet<>(); private boolean hasCulledThisFrame; private boolean needsMemoryBarrier; private int instanceCountThisFrame; IndirectCullingGroup(InstanceType instanceType, VertexType vertexType) { - this.vertexType = vertexType; - instanceStride = instanceType.getLayout() .getStride(); buffers = new IndirectBuffers(instanceStride); @@ -49,33 +37,24 @@ public class IndirectCullingGroup { buffers.createObjectStorage(128); buffers.createDrawStorage(2); - meshPool = new IndirectMeshPool(vertexType, 1024); - - vertexArray = GlVertexArray.create(); - - elementBuffer = QuadConverter.getInstance() - .quads2Tris(2048).glBuffer; - setupVertexArray(); + meshPool = new IndirectMeshPool(vertexType); var indirectPrograms = IndirectPrograms.get(); compute = indirectPrograms.getCullingProgram(instanceType); draw = indirectPrograms.getIndirectProgram(vertexType, instanceType, Contexts.WORLD); } - private void setupVertexArray() { - vertexArray.setElementBuffer(elementBuffer); - BufferLayout type = vertexType.getLayout(); - vertexArray.bindVertexBuffer(0, meshPool.vbo, 0, type.getStride()); - vertexArray.bindAttributes(0, 0, type.attributes()); + public void add(IndirectInstancer instancer, RenderStage stage, Material material, Mesh mesh) { + drawSet.add(instancer, material, stage, meshPool.alloc(mesh)); } - void beginFrame() { + public void beginFrame() { hasCulledThisFrame = false; needsMemoryBarrier = true; instanceCountThisFrame = calculateTotalInstanceCountAndPrepareBatches(); } - void submit(RenderStage stage) { + public void submit(RenderStage stage) { if (drawSet.isEmpty()) { return; } @@ -112,7 +91,7 @@ public class IndirectCullingGroup { } UniformBuffer.syncAndBind(draw); - vertexArray.bindForDraw(); + meshPool.bindForDraw(); buffers.bindForDraw(); memoryBarrier(); @@ -164,7 +143,6 @@ public class IndirectCullingGroup { } public void delete() { - vertexArray.delete(); buffers.delete(); meshPool.delete(); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDraw.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDraw.java index 798c28411..245fb79a4 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDraw.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDraw.java @@ -64,12 +64,12 @@ public class IndirectDraw { } public void writeIndirectCommand(long ptr) { - var boundingSphere = mesh.getMesh().getBoundingSphere(); + var boundingSphere = mesh.boundingSphere(); - MemoryUtil.memPutInt(ptr, mesh.getIndexCount()); // count + MemoryUtil.memPutInt(ptr, mesh.indexCount()); // count MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be incremented by the compute shader - MemoryUtil.memPutInt(ptr + 8, 0); // firstIndex - all models share the same index buffer - MemoryUtil.memPutInt(ptr + 12, mesh.getBaseVertex()); // baseVertex + MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex); // firstIndex + MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex); // baseVertex MemoryUtil.memPutInt(ptr + 16, baseInstance); // baseInstance boundingSphere.getToAddress(ptr + 20); // boundingSphere diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDrawManager.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDrawManager.java index 832cd14e2..0235357d9 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDrawManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDrawManager.java @@ -64,9 +64,9 @@ public class IndirectDrawManager { var material = entry.getKey(); var mesh = entry.getValue(); - var indirectList = (IndirectCullingGroup) renderLists.computeIfAbsent(Pair.of(instancer.type, mesh.getVertexType()), p -> new IndirectCullingGroup<>(p.first(), p.second())); + var indirectList = (IndirectCullingGroup) renderLists.computeIfAbsent(Pair.of(instancer.type, mesh.vertexType()), p -> new IndirectCullingGroup<>(p.first(), p.second())); - indirectList.drawSet.add(instancer, material, stage, indirectList.meshPool.alloc(mesh)); + indirectList.add(instancer, stage, material, mesh); break; // TODO: support multiple meshes per model } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectMeshPool.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectMeshPool.java index c966ab56b..2472d0c96 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectMeshPool.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectMeshPool.java @@ -1,21 +1,21 @@ package com.jozufozu.flywheel.backend.engine.indirect; -import static org.lwjgl.opengl.GL15.glDeleteBuffers; -import static org.lwjgl.opengl.GL44.GL_DYNAMIC_STORAGE_BIT; -import static org.lwjgl.opengl.GL45.glCreateBuffers; -import static org.lwjgl.opengl.GL45.glNamedBufferStorage; -import static org.lwjgl.opengl.GL45.nglNamedBufferSubData; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jetbrains.annotations.Nullable; +import org.joml.Vector4fc; +import com.jozufozu.flywheel.api.layout.BufferLayout; import com.jozufozu.flywheel.api.model.Mesh; import com.jozufozu.flywheel.api.vertex.VertexType; +import com.jozufozu.flywheel.gl.GlNumericType; +import com.jozufozu.flywheel.gl.array.GlVertexArray; +import com.jozufozu.flywheel.gl.buffer.GlBuffer; import com.jozufozu.flywheel.lib.memory.MemoryBlock; +import com.jozufozu.flywheel.lib.model.QuadIndexSequence; public class IndirectMeshPool { private final VertexType vertexType; @@ -23,20 +23,25 @@ public class IndirectMeshPool { private final Map meshes = new HashMap<>(); private final List meshList = new ArrayList<>(); - final int vbo; - private final MemoryBlock clientStorage; + final GlVertexArray vertexArray; + final GlBuffer vbo; + final GlBuffer ebo; private boolean dirty; /** * Create a new mesh pool. */ - public IndirectMeshPool(VertexType type, int vertexCapacity) { + public IndirectMeshPool(VertexType type) { vertexType = type; - vbo = glCreateBuffers(); - var byteCapacity = type.getLayout().getStride() * vertexCapacity; - glNamedBufferStorage(vbo, byteCapacity, GL_DYNAMIC_STORAGE_BIT); - clientStorage = MemoryBlock.malloc(byteCapacity); + vbo = new GlBuffer(); + ebo = new GlBuffer(); + vertexArray = GlVertexArray.create(); + + vertexArray.setElementBuffer(ebo.handle()); + BufferLayout layout = vertexType.getLayout(); + vertexArray.bindVertexBuffer(0, vbo.handle(), 0, layout.getStride()); + vertexArray.bindAttributes(0, 0, layout.attributes()); } public VertexType getVertexType() { @@ -72,64 +77,98 @@ public class IndirectMeshPool { } private void uploadAll() { - final long ptr = clientStorage.ptr(); + long neededSize = 0; + int maxQuadIndexCount = 0; + int nonQuadIndexCount = 0; + for (BufferedMesh mesh : meshList) { + neededSize += mesh.size(); + + if (mesh.mesh.indexSequence() == QuadIndexSequence.INSTANCE) { + maxQuadIndexCount = Math.max(maxQuadIndexCount, mesh.mesh.indexCount()); + } else { + nonQuadIndexCount += mesh.mesh.indexCount(); + } + } + + final long totalIndexCount = maxQuadIndexCount + nonQuadIndexCount; + + final var vertexBlock = MemoryBlock.malloc(neededSize); + final var indexBlock = MemoryBlock.malloc(totalIndexCount * GlNumericType.UINT.byteWidth()); + + final long vertexPtr = vertexBlock.ptr(); + final long indexPtr = indexBlock.ptr(); int byteIndex = 0; int baseVertex = 0; + int firstIndex = maxQuadIndexCount; for (BufferedMesh mesh : meshList) { mesh.byteIndex = byteIndex; mesh.baseVertex = baseVertex; - mesh.buffer(ptr); + mesh.buffer(vertexPtr); byteIndex += mesh.size(); - baseVertex += mesh.mesh.getVertexCount(); + baseVertex += mesh.mesh.vertexCount(); + + var indexFiller = mesh.mesh.indexSequence(); + if (indexFiller == QuadIndexSequence.INSTANCE) { + mesh.firstIndex = 0; + } else { + var indexCount = mesh.mesh.indexCount(); + mesh.firstIndex = firstIndex; + indexFiller.fill(indexPtr + (long) firstIndex * GlNumericType.UINT.byteWidth(), indexCount); + + firstIndex += indexCount; + } } - nglNamedBufferSubData(vbo, 0, byteIndex, ptr); + if (maxQuadIndexCount > 0) { + QuadIndexSequence.INSTANCE.fill(indexPtr, maxQuadIndexCount); + } + + vbo.upload(vertexBlock); + ebo.upload(indexBlock); + + vertexBlock.free(); + indexBlock.free(); + } + + public void bindForDraw() { + vertexArray.bindForDraw(); } public void delete() { - clientStorage.free(); - glDeleteBuffers(vbo); + vertexArray.delete(); + vbo.delete(); + ebo.delete(); meshes.clear(); meshList.clear(); } - public class BufferedMesh { + public static class BufferedMesh { private final Mesh mesh; - private final int vertexCount; - - private long byteIndex; - private int baseVertex; + public long byteIndex; + public int firstIndex; + public int baseVertex; private BufferedMesh(Mesh mesh) { this.mesh = mesh; - vertexCount = mesh.getVertexCount(); - } - - public Mesh getMesh() { - return mesh; } public int size() { return mesh.size(); } - public int getBaseVertex() { - return baseVertex; - } - - public int getIndexCount() { - return vertexCount * 6 / 4; - } - - public VertexType getVertexType() { - return vertexType; + public int indexCount() { + return mesh.indexCount(); } private void buffer(long ptr) { mesh.write(ptr + byteIndex); } + + public Vector4fc boundingSphere() { + return mesh.boundingSphere(); + } } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/EBOCache.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/EBOCache.java new file mode 100644 index 000000000..66f7cc5f2 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/EBOCache.java @@ -0,0 +1,80 @@ +package com.jozufozu.flywheel.backend.engine.instancing; + +import java.util.ArrayList; +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.lwjgl.system.MemoryUtil; + +import com.jozufozu.flywheel.api.model.IndexSequence; +import com.jozufozu.flywheel.gl.GlNumericType; +import com.jozufozu.flywheel.gl.buffer.GlBuffer; +import com.jozufozu.flywheel.gl.buffer.GlBufferUsage; +import com.jozufozu.flywheel.lib.memory.FlwMemoryTracker; +import com.jozufozu.flywheel.lib.model.QuadIndexSequence; +import com.mojang.blaze3d.platform.GlStateManager; + +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; + +public class EBOCache { + private final List quads = new ArrayList<>(); + private final Object2ReferenceMap others = new Object2ReferenceOpenHashMap<>(); + + public void delete() { + quads.forEach(Entry::delete); + others.values() + .forEach(Entry::delete); + } + + public int get(IndexSequence indexSequence, int indexCount) { + if (indexSequence == QuadIndexSequence.INSTANCE) { + return getQuads(indexCount); + } else { + return others.computeIfAbsent(new Key(indexSequence, indexCount), Key::create).ebo; + } + } + + private int getQuads(int indexCount) { + // Use an existing quad EBO if there's one big enough. + for (Entry quadEBO : quads) { + if (quadEBO.gpuSize >= indexCount * GlNumericType.UINT.byteWidth()) { + return quadEBO.ebo; + } + } + // If not, create a new one. + var out = Entry.create(QuadIndexSequence.INSTANCE, indexCount); + quads.add(out); + return out.ebo; + } + + private record Key(IndexSequence provider, int indexCount) { + private Entry create() { + return Entry.create(provider, indexCount); + } + } + + private record Entry(int ebo, int gpuSize) { + + @NotNull + private static Entry create(IndexSequence provider, int indexCount) { + int byteSize = indexCount * GlNumericType.UINT.byteWidth(); + var ebo = GlBuffer.IMPL.create(); + + final long ptr = MemoryUtil.nmemAlloc(byteSize); + provider.fill(ptr, indexCount); + + GlBuffer.IMPL.data(ebo, byteSize, ptr, GlBufferUsage.STATIC_DRAW.glEnum); + FlwMemoryTracker._allocGPUMemory(byteSize); + + MemoryUtil.nmemFree(ptr); + + return new Entry(ebo, byteSize); + } + + private void delete() { + GlStateManager._glDeleteBuffers(this.ebo); + FlwMemoryTracker._freeGPUMemory(this.gpuSize); + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedDrawManager.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedDrawManager.java index b07ba7c72..e48d7d816 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedDrawManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedDrawManager.java @@ -28,6 +28,7 @@ public class InstancedDrawManager { private final List> initializedInstancers = new ArrayList<>(); private final Map drawSets = new EnumMap<>(RenderStage.class); private final Map meshPools = new HashMap<>(); + private final EBOCache eboCache = new EBOCache(); public DrawSet get(RenderStage stage) { return drawSets.getOrDefault(stage, DrawSet.EMPTY); @@ -69,6 +70,8 @@ public class InstancedDrawManager { initializedInstancers.forEach(InstancedInstancer::delete); initializedInstancers.clear(); + + eboCache.delete(); } public void clearInstancers() { @@ -89,8 +92,8 @@ public class InstancedDrawManager { } private InstancedMeshPool.BufferedMesh alloc(Mesh mesh) { - return meshPools.computeIfAbsent(mesh.getVertexType(), InstancedMeshPool::new) - .alloc(mesh); + return meshPools.computeIfAbsent(mesh.vertexType(), InstancedMeshPool::new) + .alloc(mesh, eboCache); } public static class DrawSet implements Iterable>> { diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedMeshPool.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedMeshPool.java index 5e00a0604..d2901ada9 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedMeshPool.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedMeshPool.java @@ -16,7 +16,6 @@ import com.jozufozu.flywheel.api.model.Mesh; import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.gl.GlPrimitive; import com.jozufozu.flywheel.gl.array.GlVertexArray; -import com.jozufozu.flywheel.gl.buffer.ElementBuffer; import com.jozufozu.flywheel.gl.buffer.GlBuffer; import com.jozufozu.flywheel.gl.buffer.MappedBuffer; @@ -50,16 +49,17 @@ public class InstancedMeshPool { /** * Allocate a mesh in the arena. * - * @param mesh The mesh to allocate. + * @param mesh The mesh to allocate. + * @param eboCache The EBO cache to use. * @return A handle to the allocated mesh. */ - public BufferedMesh alloc(Mesh mesh) { + public BufferedMesh alloc(Mesh mesh, EBOCache eboCache) { return meshes.computeIfAbsent(mesh, m -> { - if (m.getVertexType() != vertexType) { + if (m.vertexType() != vertexType) { throw new IllegalArgumentException("Mesh has wrong vertex type"); } - BufferedMesh bufferedMesh = new BufferedMesh(m, byteSize); + BufferedMesh bufferedMesh = new BufferedMesh(m, byteSize, eboCache); byteSize += bufferedMesh.size(); allBuffered.add(bufferedMesh); pendingUpload.add(bufferedMesh); @@ -145,16 +145,16 @@ public class InstancedMeshPool { public class BufferedMesh { private final Mesh mesh; - private final ElementBuffer ebo; + private final int ebo; private long byteIndex; private boolean deleted; private final Set boundTo = new HashSet<>(); - private BufferedMesh(Mesh mesh, long byteIndex) { + private BufferedMesh(Mesh mesh, long byteIndex, EBOCache eboCache) { this.mesh = mesh; this.byteIndex = byteIndex; - this.ebo = mesh.createEBO(); + this.ebo = eboCache.get(mesh.indexSequence(), mesh.indexCount()); } public int size() { @@ -188,15 +188,15 @@ public class InstancedMeshPool { BufferLayout type = vertexType.getLayout(); vao.bindVertexBuffer(0, InstancedMeshPool.this.vbo.handle(), byteIndex, type.getStride()); vao.bindAttributes(0, 0, type.attributes()); - vao.setElementBuffer(ebo.glBuffer); + vao.setElementBuffer(ebo); } } public void draw(int instanceCount) { if (instanceCount > 1) { - GL32.glDrawElementsInstanced(GlPrimitive.TRIANGLES.glEnum, ebo.getElementCount(), ebo.getEboIndexType().asGLType, 0, instanceCount); + GL32.glDrawElementsInstanced(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, 0, instanceCount); } else { - GL32.glDrawElements(GlPrimitive.TRIANGLES.glEnum, ebo.getElementCount(), ebo.getEboIndexType().asGLType, 0); + GL32.glDrawElements(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, 0); } } diff --git a/src/main/java/com/jozufozu/flywheel/gl/buffer/ElementBuffer.java b/src/main/java/com/jozufozu/flywheel/gl/buffer/ElementBuffer.java deleted file mode 100644 index 8ee468976..000000000 --- a/src/main/java/com/jozufozu/flywheel/gl/buffer/ElementBuffer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.jozufozu.flywheel.gl.buffer; - -import com.mojang.blaze3d.vertex.VertexFormat; - -public class ElementBuffer { - protected final int elementCount; - protected final VertexFormat.IndexType eboIndexType; - public final int glBuffer; - - public ElementBuffer(int backing, int elementCount, VertexFormat.IndexType indexType) { - this.elementCount = elementCount; - this.eboIndexType = indexType; - this.glBuffer = backing; - } - - public int getElementCount() { - return elementCount; - } - - public VertexFormat.IndexType getEboIndexType() { - return eboIndexType; - } -} diff --git a/src/main/java/com/jozufozu/flywheel/lib/model/QuadIndexSequence.java b/src/main/java/com/jozufozu/flywheel/lib/model/QuadIndexSequence.java new file mode 100644 index 000000000..b6a00bc0f --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/lib/model/QuadIndexSequence.java @@ -0,0 +1,31 @@ +package com.jozufozu.flywheel.lib.model; + +import org.lwjgl.system.MemoryUtil; + +import com.jozufozu.flywheel.api.model.IndexSequence; + +public class QuadIndexSequence implements IndexSequence { + public static final QuadIndexSequence INSTANCE = new QuadIndexSequence(); + + private QuadIndexSequence() { + } + + @Override + public void fill(long ptr, int count) { + int numVertices = 4 * (count / 6); + int baseVertex = 0; + while (baseVertex < numVertices) { + // triangle a + MemoryUtil.memPutInt(ptr, baseVertex); + MemoryUtil.memPutInt(ptr + 4, baseVertex + 1); + MemoryUtil.memPutInt(ptr + 8, baseVertex + 2); + // triangle b + MemoryUtil.memPutInt(ptr + 12, baseVertex); + MemoryUtil.memPutInt(ptr + 16, baseVertex + 2); + MemoryUtil.memPutInt(ptr + 20, baseVertex + 3); + + baseVertex += 4; + ptr += 24; + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/lib/model/QuadMesh.java b/src/main/java/com/jozufozu/flywheel/lib/model/QuadMesh.java index 2c46b040f..7549a9dc5 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/model/QuadMesh.java +++ b/src/main/java/com/jozufozu/flywheel/lib/model/QuadMesh.java @@ -1,23 +1,16 @@ package com.jozufozu.flywheel.lib.model; +import com.jozufozu.flywheel.api.model.IndexSequence; import com.jozufozu.flywheel.api.model.Mesh; -import com.jozufozu.flywheel.gl.buffer.ElementBuffer; -import com.jozufozu.flywheel.lib.util.QuadConverter; public interface QuadMesh extends Mesh { - /** - * Create an element buffer object that indexes the vertices of this mesh. - * - *

- * Very often models in minecraft are made up of sequential quads, which is a very predictable pattern. - * The default implementation accommodates this, however this can be overridden to change the behavior and - * support more complex models. - *

- * @return an element buffer object indexing this model's vertices. - */ @Override - default ElementBuffer createEBO() { - return QuadConverter.getInstance() - .quads2Tris(getVertexCount() / 4); + default IndexSequence indexSequence() { + return QuadIndexSequence.INSTANCE; + } + + @Override + default int indexCount() { + return vertexCount() / 2 * 3; } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/model/SimpleLazyModel.java b/src/main/java/com/jozufozu/flywheel/lib/model/SimpleLazyModel.java index 86424149b..90d9b5c9c 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/model/SimpleLazyModel.java +++ b/src/main/java/com/jozufozu/flywheel/lib/model/SimpleLazyModel.java @@ -29,7 +29,7 @@ public class SimpleLazyModel implements Model { } public int getVertexCount() { - return supplier.map(Mesh::getVertexCount) + return supplier.map(Mesh::vertexCount) .orElse(0); } diff --git a/src/main/java/com/jozufozu/flywheel/lib/model/SimpleMesh.java b/src/main/java/com/jozufozu/flywheel/lib/model/SimpleMesh.java index 197b6ee7f..28a967263 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/model/SimpleMesh.java +++ b/src/main/java/com/jozufozu/flywheel/lib/model/SimpleMesh.java @@ -28,7 +28,7 @@ public class SimpleMesh implements QuadMesh { } vertexCount = bytes / stride; - vertexList = getVertexType().createVertexList(); + vertexList = vertexType().createVertexList(); vertexList.ptr(contents.ptr()); vertexList.vertexCount(vertexCount); @@ -36,12 +36,12 @@ public class SimpleMesh implements QuadMesh { } @Override - public VertexType getVertexType() { + public VertexType vertexType() { return vertexType; } @Override - public int getVertexCount() { + public int vertexCount() { return vertexCount; } @@ -56,7 +56,7 @@ public class SimpleMesh implements QuadMesh { } @Override - public Vector4fc getBoundingSphere() { + public Vector4fc boundingSphere() { return boundingSphere; } diff --git a/src/main/java/com/jozufozu/flywheel/lib/modelpart/ModelPart.java b/src/main/java/com/jozufozu/flywheel/lib/modelpart/ModelPart.java index e6b1f544c..c5596bf8b 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/modelpart/ModelPart.java +++ b/src/main/java/com/jozufozu/flywheel/lib/modelpart/ModelPart.java @@ -32,7 +32,7 @@ public class ModelPart implements QuadMesh { cuboid.write(writer); } - vertexList = getVertexType().createVertexList(); + vertexList = vertexType().createVertexList(); vertexList.ptr(ptr); vertexList.vertexCount(vertexCount); @@ -44,12 +44,12 @@ public class ModelPart implements QuadMesh { } @Override - public PosTexNormalVertex getVertexType() { + public PosTexNormalVertex vertexType() { return VertexTypes.POS_TEX_NORMAL; } @Override - public int getVertexCount() { + public int vertexCount() { return vertexCount; } @@ -64,7 +64,7 @@ public class ModelPart implements QuadMesh { } @Override - public Vector4fc getBoundingSphere() { + public Vector4fc boundingSphere() { return boundingSphere; } diff --git a/src/main/java/com/jozufozu/flywheel/lib/util/QuadConverter.java b/src/main/java/com/jozufozu/flywheel/lib/util/QuadConverter.java deleted file mode 100644 index 0d01923c1..000000000 --- a/src/main/java/com/jozufozu/flywheel/lib/util/QuadConverter.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.jozufozu.flywheel.lib.util; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.lwjgl.system.MemoryUtil; - -import com.jozufozu.flywheel.api.event.ReloadRenderersEvent; -import com.jozufozu.flywheel.gl.GlNumericType; -import com.jozufozu.flywheel.gl.buffer.ElementBuffer; -import com.jozufozu.flywheel.gl.buffer.GlBuffer; -import com.jozufozu.flywheel.gl.buffer.GlBufferUsage; -import com.mojang.blaze3d.platform.GlStateManager; -import com.mojang.blaze3d.vertex.VertexFormat; - -import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; - -/** - * A class to manage EBOs that index quads as triangles. - */ -public class QuadConverter { - private static QuadConverter INSTANCE; - - @NotNull - public static QuadConverter getInstance() { - if (INSTANCE == null) { - INSTANCE = new QuadConverter(); - } - - return INSTANCE; - } - - @Nullable - public static QuadConverter getNullable() { - return INSTANCE; - } - - private final Int2ReferenceMap cache = new Int2ReferenceArrayMap<>(); - private final int ebo; - private int quadCapacity; - - public QuadConverter() { - this.ebo = GlBuffer.IMPL.create(); - this.quadCapacity = 0; - } - - public ElementBuffer quads2Tris(int quads) { - if (quads > quadCapacity) { - grow(quads * 2); - } - - return cache.computeIfAbsent(quads, this::createElementBuffer); - } - - @NotNull - private ElementBuffer createElementBuffer(int quads) { - return new ElementBuffer(ebo, quads * 6, VertexFormat.IndexType.INT); - } - - private void grow(int quads) { - int byteSize = quads * 6 * GlNumericType.UINT.byteWidth(); - final long ptr = MemoryUtil.nmemAlloc(byteSize); - - fillBuffer(ptr, quads); - - GlBuffer.IMPL.data(ebo, byteSize, ptr, GlBufferUsage.STATIC_DRAW.glEnum); - - MemoryUtil.nmemFree(ptr); - - this.quadCapacity = quads; - } - - public void delete() { - GlStateManager._glDeleteBuffers(ebo); - this.cache.clear(); - this.quadCapacity = 0; - } - - private void fillBuffer(long ptr, int quads) { - int numVertices = 4 * quads; - int baseVertex = 0; - while (baseVertex < numVertices) { - writeQuadIndicesUnsafe(ptr, baseVertex); - - baseVertex += 4; - ptr += 6 * 4; - } - } - - private void writeQuadIndicesUnsafe(long ptr, int baseVertex) { - // triangle a - MemoryUtil.memPutInt(ptr, baseVertex); - MemoryUtil.memPutInt(ptr + 4, baseVertex + 1); - MemoryUtil.memPutInt(ptr + 8, baseVertex + 2); - // triangle b - MemoryUtil.memPutInt(ptr + 12, baseVertex); - MemoryUtil.memPutInt(ptr + 16, baseVertex + 2); - MemoryUtil.memPutInt(ptr + 20, baseVertex + 3); - } - - // make sure this gets reset first, so it has a chance to repopulate - public static void onReloadRenderers(ReloadRenderersEvent event) { - if (INSTANCE != null) { - INSTANCE.delete(); - INSTANCE = null; - } - } -}