Allocate no more than 3 quad->triangle EBOs ever.

This commit is contained in:
JozsefA 2021-05-31 14:58:49 -07:00
parent a526156440
commit c4b828ba56
7 changed files with 153 additions and 76 deletions

View file

@ -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) -> {

View file

@ -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);

View file

@ -148,7 +148,7 @@ public class RenderMaterial<P extends WorldProgram, D extends InstanceData> {
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());

View file

@ -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();
}
}

View file

@ -45,6 +45,5 @@ public class IndexedModel extends BufferedModel {
@Override
public void delete() {
super.delete();
ebo.delete();
}
}

View file

@ -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<CachedEbo> quads2Tris;
public QuadConverter() {
quads2Tris = new Int2ObjectOpenHashMap<>();
@Nullable
public static QuadConverter getNullable() {
return INSTANCE;
}
public ElementBuffer getEboForNQuads(int quads) {
quads2Tris.values().removeIf(CachedEbo::noReferences);
Map<GlNumericType, GlBuffer> 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? <br>
*
* <pre>
* | indexCount | type |
* |--------------|-------|
* | [0, 255) | byte |
* | [256, 65536) | short |
* | [65537, ) | int |
* </pre>
*/
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;
}
}

View file

@ -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));
}
}