From c4b828ba56bd0608fbc070b0c13738bbfa88d81f Mon Sep 17 00:00:00 2001 From: JozsefA Date: Mon, 31 May 2021 14:58:49 -0700 Subject: [PATCH] Allocate no more than 3 quad->triangle EBOs ever. --- .../jozufozu/flywheel/backend/Backend.java | 4 + .../flywheel/backend/gl/buffer/GlBuffer.java | 6 + .../backend/instancing/RenderMaterial.java | 2 +- .../flywheel/backend/model/ElementBuffer.java | 25 +-- .../flywheel/backend/model/IndexedModel.java | 1 - .../jozufozu/flywheel/core/QuadConverter.java | 189 ++++++++++++------ .../render/RenderedContraption.java | 2 +- 7 files changed, 153 insertions(+), 76 deletions(-) diff --git a/src/main/java/com/jozufozu/flywheel/backend/Backend.java b/src/main/java/com/jozufozu/flywheel/backend/Backend.java index 5e577a590..b56df9c5c 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/Backend.java +++ b/src/main/java/com/jozufozu/flywheel/backend/Backend.java @@ -25,6 +25,7 @@ import com.jozufozu.flywheel.backend.gl.versioned.GlCompat; import com.jozufozu.flywheel.backend.instancing.InstanceData; import com.jozufozu.flywheel.backend.instancing.MaterialSpec; import com.jozufozu.flywheel.core.CrumblingRenderer; +import com.jozufozu.flywheel.core.QuadConverter; import com.jozufozu.flywheel.core.WorldContext; import com.jozufozu.flywheel.core.WorldTileRenderer; import com.jozufozu.flywheel.core.shader.WorldProgram; @@ -90,6 +91,9 @@ public class Backend { tileRenderer.invalidate(); world.loadedTileEntityList.forEach(tileRenderer::add); } + + QuadConverter quadConverter = QuadConverter.getNullable(); + if (quadConverter != null) quadConverter.free(); }); listeners.setupFrameListener((world, stack, info, gameRenderer, lightTexture) -> { diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/buffer/GlBuffer.java b/src/main/java/com/jozufozu/flywheel/backend/gl/buffer/GlBuffer.java index 4430fa76d..d970e85e8 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/gl/buffer/GlBuffer.java +++ b/src/main/java/com/jozufozu/flywheel/backend/gl/buffer/GlBuffer.java @@ -1,5 +1,7 @@ package com.jozufozu.flywheel.backend.gl.buffer; +import java.nio.ByteBuffer; + import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL30; @@ -47,6 +49,10 @@ public class GlBuffer extends GlObject { GL15.glBufferData(type.glEnum, size, usage.glEnum); } + public void upload(ByteBuffer directBuffer) { + GL15.glBufferData(type.glEnum, directBuffer, usage.glEnum); + } + public MappedBuffer getBuffer(int offset, int length) { if (Backend.compat.mapBufferRange != MapBufferRange.UNSUPPORTED) { return new MappedBufferRange(this, offset, length, GL30.GL_MAP_WRITE_BIT); diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/RenderMaterial.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/RenderMaterial.java index 434a04722..8c87729fb 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/RenderMaterial.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/RenderMaterial.java @@ -148,7 +148,7 @@ public class RenderMaterial

{ vertices.rewind(); - BufferedModel bufferedModel = new IndexedModel(GlPrimitive.TRIANGLES, format, vertices, vertexCount, QuadConverter.getInstance().getEboForNQuads(vertexCount / 4)); + BufferedModel bufferedModel = new IndexedModel(GlPrimitive.TRIANGLES, format, vertices, vertexCount, QuadConverter.getInstance().quads2Tris(vertexCount / 4)); //BufferedModel bufferedModel = new BufferedModel(GlPrimitive.QUADS, format, vertices, vertexCount); return new Instancer<>(bufferedModel, renderer, spec.getInstanceFormat(), spec.getInstanceFactory()); diff --git a/src/main/java/com/jozufozu/flywheel/backend/model/ElementBuffer.java b/src/main/java/com/jozufozu/flywheel/backend/model/ElementBuffer.java index 8726f5c3e..8227ad019 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/model/ElementBuffer.java +++ b/src/main/java/com/jozufozu/flywheel/backend/model/ElementBuffer.java @@ -1,30 +1,25 @@ package com.jozufozu.flywheel.backend.model; -import java.nio.ByteBuffer; - import com.jozufozu.flywheel.backend.gl.GlNumericType; import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer; -import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType; -public class ElementBuffer extends GlBuffer { +public class ElementBuffer { + private final GlBuffer buffer; public final int elementCount; public final GlNumericType eboIndexType; - public ElementBuffer(ByteBuffer indices, int elementCount, GlNumericType indexType) { - super(GlBufferType.ELEMENT_ARRAY_BUFFER); + public ElementBuffer(GlBuffer backing, int elementCount, GlNumericType indexType) { + this.buffer = backing; this.eboIndexType = indexType; this.elementCount = elementCount; + } - int indicesSize = elementCount * indexType.getByteWidth(); + public void bind() { + buffer.bind(); + } - bind(); - - alloc(indicesSize); - getBuffer(0, indicesSize) - .put(indices) - .flush(); - - unbind(); + public void unbind() { + buffer.unbind(); } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/model/IndexedModel.java b/src/main/java/com/jozufozu/flywheel/backend/model/IndexedModel.java index ddc9737d3..4fe88223f 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/model/IndexedModel.java +++ b/src/main/java/com/jozufozu/flywheel/backend/model/IndexedModel.java @@ -45,6 +45,5 @@ public class IndexedModel extends BufferedModel { @Override public void delete() { super.delete(); - ebo.delete(); } } diff --git a/src/main/java/com/jozufozu/flywheel/core/QuadConverter.java b/src/main/java/com/jozufozu/flywheel/core/QuadConverter.java index 648389385..1993a043c 100644 --- a/src/main/java/com/jozufozu/flywheel/core/QuadConverter.java +++ b/src/main/java/com/jozufozu/flywheel/core/QuadConverter.java @@ -2,87 +2,160 @@ package com.jozufozu.flywheel.core; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.EnumMap; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; import com.jozufozu.flywheel.backend.gl.GlNumericType; +import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer; +import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType; import com.jozufozu.flywheel.backend.model.ElementBuffer; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import net.minecraft.util.math.MathHelper; - +/** + * A class to manage EBOs that index quads as triangles. + */ public class QuadConverter { - private static QuadConverter INSTANCE = new QuadConverter(); + public static final int STARTING_CAPACITY = 42; + private static QuadConverter INSTANCE; + + @Nonnull public static QuadConverter getInstance() { + if (INSTANCE == null) { + INSTANCE = new QuadConverter(STARTING_CAPACITY); // 255 / 6 = 42 + } + return INSTANCE; } - private final Int2ObjectMap quads2Tris; - - public QuadConverter() { - quads2Tris = new Int2ObjectOpenHashMap<>(); + @Nullable + public static QuadConverter getNullable() { + return INSTANCE; } - public ElementBuffer getEboForNQuads(int quads) { - quads2Tris.values().removeIf(CachedEbo::noReferences); + Map ebos; + int[] capacities; - CachedEbo ebo = quads2Tris.computeIfAbsent(quads, quadCount -> { - int triangleCount = quadCount * 2; - int indexCount = triangleCount * 3; + public QuadConverter(int initialCapacity) { + this.ebos = new EnumMap<>(GlNumericType.class); + initCapacities(); - GlNumericType type; - - int bitWidth = MathHelper.log2(indexCount); - if (bitWidth <= 8) { - type = GlNumericType.UBYTE; - } else if (bitWidth <= 16) { - type = GlNumericType.USHORT; - } else { - type = GlNumericType.UINT; - } - ByteBuffer indices = ByteBuffer.allocate(indexCount * type.getByteWidth()); - indices.order(ByteOrder.nativeOrder()); - - for (int i = 0; i < quadCount; i++) { - int qStart = 4 * i; - // triangle 1 - type.castAndBuffer(indices, qStart); - type.castAndBuffer(indices, qStart + 1); - type.castAndBuffer(indices, qStart + 2); - // triangle 2 - type.castAndBuffer(indices, qStart); - type.castAndBuffer(indices, qStart + 2); - type.castAndBuffer(indices, qStart + 3); - } - - indices.flip(); - - return new CachedEbo(indices, indexCount, type); - }); - - ebo.refCount++; - - return ebo; + fillBuffer(initialCapacity); } - private class CachedEbo extends ElementBuffer { - int refCount = 1; + public ElementBuffer quads2Tris(int quads) { + int indexCount = quads * 6; + GlNumericType type = getSmallestIndexType(indexCount); - public CachedEbo(ByteBuffer indices, int elementCount, GlNumericType indexType) { - super(indices, elementCount, indexType); + if (quads > getCapacity(type)) { + fillBuffer(quads, indexCount, type); } - @Override - public void delete() { - refCount--; + return new ElementBuffer(getBuffer(type), indexCount, type); + } - if (refCount == 0) - super.delete(); + private void initCapacities() { + this.capacities = new int[GlNumericType.values().length]; + } + + private int getCapacity(GlNumericType type) { + return capacities[type.ordinal()]; + } + + private void updateCapacity(GlNumericType type, int capacity) { + if (getCapacity(type) < capacity) { + capacities[type.ordinal()] = capacity; + } + } + + public void free() { + ebos.values().forEach(GlBuffer::delete); + ebos.clear(); + initCapacities(); + } + + private void fillBuffer(int quads) { + int indexCount = quads * 6; + + fillBuffer(quads, indexCount, getSmallestIndexType(indexCount)); + } + + private void fillBuffer(int quads, int indexCount, GlNumericType type) { + MemoryStack stack = MemoryStack.stackPush(); + int bytes = indexCount * type.getByteWidth(); + + ByteBuffer indices; + if (bytes > stack.getSize()) { + indices = MemoryUtil.memAlloc(bytes); // not enough space on the preallocated stack + } else { + stack.push(); + indices = stack.malloc(bytes); } - public boolean noReferences() { - return refCount == 0; + indices.order(ByteOrder.nativeOrder()); + + fillBuffer(indices, type, quads); + + GlBuffer buffer = getBuffer(type); + + buffer.bind(); + buffer.upload(indices); + buffer.unbind(); + + if (bytes > stack.getSize()) { + MemoryUtil.memFree(indices); + } else { + stack.pop(); } + + updateCapacity(type, quads); + } + + private void fillBuffer(ByteBuffer indices, GlNumericType type, int quads) { + for (int i = 0, max = 4 * quads; i < max; i += 4) { + // triangle a + type.castAndBuffer(indices, i); + type.castAndBuffer(indices, i + 1); + type.castAndBuffer(indices, i + 2); + // triangle b + type.castAndBuffer(indices, i); + type.castAndBuffer(indices, i + 2); + type.castAndBuffer(indices, i + 3); + } + indices.flip(); + } + + private GlBuffer getBuffer(GlNumericType type) { + return ebos.computeIfAbsent(type, $ -> new GlBuffer(GlBufferType.ELEMENT_ARRAY_BUFFER)); + } + + /** + * Given the needed number of indices, what is the smallest bit width type that can index everything?
+ * + *

+	 * | indexCount   | type  |
+	 * |--------------|-------|
+	 * | [0, 255)     | byte  |
+	 * | [256, 65536)	| short	|
+	 * | [65537, )	| int	|
+	 * 
+ */ + private static GlNumericType getSmallestIndexType(int indexCount) { + indexCount = indexCount >>> 8; + if (indexCount == 0) { + return GlNumericType.UBYTE; + } + indexCount = indexCount >>> 8; + if (indexCount == 0) { + return GlNumericType.USHORT; + } + + return GlNumericType.UINT; } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/RenderedContraption.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/RenderedContraption.java index 86b7c67fc..20432ddb5 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/RenderedContraption.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/RenderedContraption.java @@ -203,6 +203,6 @@ public class RenderedContraption extends ContraptionWorldHolder { vertices.rewind(); - return new IndexedModel(GlPrimitive.TRIANGLES, format, vertices, vertexCount, QuadConverter.getInstance().getEboForNQuads(vertexCount / 4)); + return new IndexedModel(GlPrimitive.TRIANGLES, format, vertices, vertexCount, QuadConverter.getInstance().quads2Tris(vertexCount / 4)); } }