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
This commit is contained in:
Jozufozu 2023-05-28 18:43:54 -07:00
parent 961fafce0d
commit 257ee07e0e
20 changed files with 279 additions and 268 deletions

View file

@ -23,7 +23,6 @@ import com.jozufozu.flywheel.lib.material.MaterialIndices;
import com.jozufozu.flywheel.lib.material.Materials; import com.jozufozu.flywheel.lib.material.Materials;
import com.jozufozu.flywheel.lib.model.Models; import com.jozufozu.flywheel.lib.model.Models;
import com.jozufozu.flywheel.lib.model.PartialModel; 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.util.ShadersModHandler;
import com.jozufozu.flywheel.lib.vertex.VertexTypes; import com.jozufozu.flywheel.lib.vertex.VertexTypes;
import com.jozufozu.flywheel.mixin.PausedPartialTickAccessor; import com.jozufozu.flywheel.mixin.PausedPartialTickAccessor;
@ -35,7 +34,6 @@ import net.minecraft.commands.synchronization.EmptyArgumentSerializer;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.IExtensionPoint; import net.minecraftforge.fml.IExtensionPoint;
@ -79,7 +77,6 @@ public class Flywheel {
forgeEventBus.addListener(BackendManagerImpl::onReloadRenderers); forgeEventBus.addListener(BackendManagerImpl::onReloadRenderers);
forgeEventBus.addListener(EventPriority.HIGHEST, QuadConverter::onReloadRenderers);
forgeEventBus.addListener(Models::onReloadRenderers); forgeEventBus.addListener(Models::onReloadRenderers);
forgeEventBus.addListener(DrawBuffer::onReloadRenderers); forgeEventBus.addListener(DrawBuffer::onReloadRenderers);
forgeEventBus.addListener(UniformBuffer::onReloadRenderers); forgeEventBus.addListener(UniformBuffer::onReloadRenderers);

View file

@ -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.
* <p>
* Do not write outside the range {@code [ptr, ptr + count * 4]}.
*/
void fill(long ptr, int count);
}

View file

@ -4,56 +4,64 @@ import org.joml.Vector4fc;
import com.jozufozu.flywheel.api.vertex.MutableVertexList; import com.jozufozu.flywheel.api.vertex.MutableVertexList;
import com.jozufozu.flywheel.api.vertex.VertexType; 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. * A holder for arbitrary vertex data that can be written to memory or a vertex list.
*/ */
public interface Mesh { public interface Mesh {
VertexType getVertexType(); VertexType vertexType();
/** /**
* @return The number of vertices this mesh has. * @return The number of vertices this mesh has.
*/ */
int getVertexCount(); int vertexCount();
/** /**
* Is there nothing to render? * Is there nothing to render?
* @return true if there are no vertices. * @return true if there are no vertices.
*/ */
default boolean isEmpty() { default boolean isEmpty() {
return getVertexCount() == 0; return vertexCount() == 0;
} }
/** /**
* The size in bytes that this mesh's data takes up. * The size in bytes that this mesh's data takes up.
*/ */
default int size() { 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()}. * bytes written will be the same as the return value of {@link #size()}.
*
* @param ptr The address to which data is written to. * @param ptr The address to which data is written to.
*/ */
void write(long ptr); 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. * read or modified.
*
* @param vertexList The vertex list to which data is written to. * @param vertexList The vertex list to which data is written to.
*/ */
void write(MutableVertexList vertexList); void write(MutableVertexList vertexList);
IndexSequence indexSequence();
int indexCount();
/** /**
* Create an element buffer object that indexes the vertices of this mesh. * Get a vec4 representing this mesh's bounding sphere in the format (x, y, z, radius).
* @return an element buffer object indexing this model's vertices. *
* @return A vec4 view.
*/ */
ElementBuffer createEBO(); Vector4fc boundingSphere();
Vector4fc getBoundingSphere();
/**
* Free this mesh's resources, memory, etc.
*/
void delete(); void delete();
/** /**

View file

@ -12,7 +12,7 @@ public interface Model {
default int getVertexCount() { default int getVertexCount() {
int size = 0; int size = 0;
for (Mesh mesh : getMeshes().values()) { for (Mesh mesh : getMeshes().values()) {
size += mesh.getVertexCount(); size += mesh.vertexCount();
} }
return size; return size;
} }

View file

@ -160,8 +160,8 @@ public class BatchedMeshPool {
private BufferedMesh(Mesh mesh, long byteIndex) { private BufferedMesh(Mesh mesh, long byteIndex) {
this.mesh = mesh; this.mesh = mesh;
vertexCount = mesh.getVertexCount(); vertexCount = mesh.vertexCount();
boundingSphere = mesh.getBoundingSphere(); boundingSphere = mesh.boundingSphere();
byteSize = vertexCount * vertexFormat.getVertexSize(); byteSize = vertexCount * vertexFormat.getVertexSize();
this.byteIndex = byteIndex; this.byteIndex = byteIndex;
} }
@ -174,7 +174,7 @@ public class BatchedMeshPool {
return vertexCount; return vertexCount;
} }
public Vector4fc getBoundingSphere() { public Vector4fc boundingSphere() {
return boundingSphere; return boundingSphere;
} }

View file

@ -35,7 +35,7 @@ public class TransformCall<I extends Instance> {
MaterialVertexTransformer materialVertexTransformer = material.getVertexTransformer(); MaterialVertexTransformer materialVertexTransformer = material.getVertexTransformer();
meshVertexCount = mesh.getVertexCount(); meshVertexCount = mesh.getVertexCount();
Vector4fc meshBoundingSphere = mesh.getBoundingSphere(); Vector4fc meshBoundingSphere = mesh.boundingSphere();
drawPlan = ForEachPlan.of(instancer::getAll, (instance, ctx) -> { drawPlan = ForEachPlan.of(instancer::getAll, (instance, ctx) -> {
var boundingSphere = new Vector4f(meshBoundingSphere); var boundingSphere = new Vector4f(meshBoundingSphere);

View file

@ -8,40 +8,28 @@ import static org.lwjgl.opengl.GL43.glDispatchCompute;
import com.jozufozu.flywheel.api.event.RenderStage; import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance; import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType; 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.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.compile.IndirectPrograms; import com.jozufozu.flywheel.backend.compile.IndirectPrograms;
import com.jozufozu.flywheel.backend.engine.UniformBuffer; 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.gl.shader.GlProgram;
import com.jozufozu.flywheel.lib.context.Contexts; import com.jozufozu.flywheel.lib.context.Contexts;
import com.jozufozu.flywheel.lib.util.QuadConverter;
public class IndirectCullingGroup<I extends Instance> { public class IndirectCullingGroup<I extends Instance> {
private static final int BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT; private static final int BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT;
final GlProgram compute; private final GlProgram compute;
final GlProgram draw; private final GlProgram draw;
private final VertexType vertexType;
private final long instanceStride; private final long instanceStride;
private final IndirectBuffers buffers;
final IndirectBuffers buffers; public final IndirectMeshPool meshPool;
public final IndirectDrawSet<I> drawSet = new IndirectDrawSet<>();
final IndirectMeshPool meshPool;
private final int elementBuffer;
GlVertexArray vertexArray;
final IndirectDrawSet<I> drawSet = new IndirectDrawSet<>();
private boolean hasCulledThisFrame; private boolean hasCulledThisFrame;
private boolean needsMemoryBarrier; private boolean needsMemoryBarrier;
private int instanceCountThisFrame; private int instanceCountThisFrame;
IndirectCullingGroup(InstanceType<I> instanceType, VertexType vertexType) { IndirectCullingGroup(InstanceType<I> instanceType, VertexType vertexType) {
this.vertexType = vertexType;
instanceStride = instanceType.getLayout() instanceStride = instanceType.getLayout()
.getStride(); .getStride();
buffers = new IndirectBuffers(instanceStride); buffers = new IndirectBuffers(instanceStride);
@ -49,33 +37,24 @@ public class IndirectCullingGroup<I extends Instance> {
buffers.createObjectStorage(128); buffers.createObjectStorage(128);
buffers.createDrawStorage(2); buffers.createDrawStorage(2);
meshPool = new IndirectMeshPool(vertexType, 1024); meshPool = new IndirectMeshPool(vertexType);
vertexArray = GlVertexArray.create();
elementBuffer = QuadConverter.getInstance()
.quads2Tris(2048).glBuffer;
setupVertexArray();
var indirectPrograms = IndirectPrograms.get(); var indirectPrograms = IndirectPrograms.get();
compute = indirectPrograms.getCullingProgram(instanceType); compute = indirectPrograms.getCullingProgram(instanceType);
draw = indirectPrograms.getIndirectProgram(vertexType, instanceType, Contexts.WORLD); draw = indirectPrograms.getIndirectProgram(vertexType, instanceType, Contexts.WORLD);
} }
private void setupVertexArray() { public void add(IndirectInstancer<I> instancer, RenderStage stage, Material material, Mesh mesh) {
vertexArray.setElementBuffer(elementBuffer); drawSet.add(instancer, material, stage, meshPool.alloc(mesh));
BufferLayout type = vertexType.getLayout();
vertexArray.bindVertexBuffer(0, meshPool.vbo, 0, type.getStride());
vertexArray.bindAttributes(0, 0, type.attributes());
} }
void beginFrame() { public void beginFrame() {
hasCulledThisFrame = false; hasCulledThisFrame = false;
needsMemoryBarrier = true; needsMemoryBarrier = true;
instanceCountThisFrame = calculateTotalInstanceCountAndPrepareBatches(); instanceCountThisFrame = calculateTotalInstanceCountAndPrepareBatches();
} }
void submit(RenderStage stage) { public void submit(RenderStage stage) {
if (drawSet.isEmpty()) { if (drawSet.isEmpty()) {
return; return;
} }
@ -112,7 +91,7 @@ public class IndirectCullingGroup<I extends Instance> {
} }
UniformBuffer.syncAndBind(draw); UniformBuffer.syncAndBind(draw);
vertexArray.bindForDraw(); meshPool.bindForDraw();
buffers.bindForDraw(); buffers.bindForDraw();
memoryBarrier(); memoryBarrier();
@ -164,7 +143,6 @@ public class IndirectCullingGroup<I extends Instance> {
} }
public void delete() { public void delete() {
vertexArray.delete();
buffers.delete(); buffers.delete();
meshPool.delete(); meshPool.delete();
} }

View file

@ -64,12 +64,12 @@ public class IndirectDraw<I extends Instance> {
} }
public void writeIndirectCommand(long ptr) { 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 + 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 + 8, mesh.firstIndex); // firstIndex
MemoryUtil.memPutInt(ptr + 12, mesh.getBaseVertex()); // baseVertex MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex); // baseVertex
MemoryUtil.memPutInt(ptr + 16, baseInstance); // baseInstance MemoryUtil.memPutInt(ptr + 16, baseInstance); // baseInstance
boundingSphere.getToAddress(ptr + 20); // boundingSphere boundingSphere.getToAddress(ptr + 20); // boundingSphere

View file

@ -64,9 +64,9 @@ public class IndirectDrawManager {
var material = entry.getKey(); var material = entry.getKey();
var mesh = entry.getValue(); var mesh = entry.getValue();
var indirectList = (IndirectCullingGroup<I>) renderLists.computeIfAbsent(Pair.of(instancer.type, mesh.getVertexType()), p -> new IndirectCullingGroup<>(p.first(), p.second())); var indirectList = (IndirectCullingGroup<I>) 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 break; // TODO: support multiple meshes per model
} }

View file

@ -1,21 +1,21 @@
package com.jozufozu.flywheel.backend.engine.indirect; 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.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.jetbrains.annotations.Nullable; 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.model.Mesh;
import com.jozufozu.flywheel.api.vertex.VertexType; 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.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.QuadIndexSequence;
public class IndirectMeshPool { public class IndirectMeshPool {
private final VertexType vertexType; private final VertexType vertexType;
@ -23,20 +23,25 @@ public class IndirectMeshPool {
private final Map<Mesh, BufferedMesh> meshes = new HashMap<>(); private final Map<Mesh, BufferedMesh> meshes = new HashMap<>();
private final List<BufferedMesh> meshList = new ArrayList<>(); private final List<BufferedMesh> meshList = new ArrayList<>();
final int vbo; final GlVertexArray vertexArray;
private final MemoryBlock clientStorage; final GlBuffer vbo;
final GlBuffer ebo;
private boolean dirty; private boolean dirty;
/** /**
* Create a new mesh pool. * Create a new mesh pool.
*/ */
public IndirectMeshPool(VertexType type, int vertexCapacity) { public IndirectMeshPool(VertexType type) {
vertexType = type; vertexType = type;
vbo = glCreateBuffers(); vbo = new GlBuffer();
var byteCapacity = type.getLayout().getStride() * vertexCapacity; ebo = new GlBuffer();
glNamedBufferStorage(vbo, byteCapacity, GL_DYNAMIC_STORAGE_BIT); vertexArray = GlVertexArray.create();
clientStorage = MemoryBlock.malloc(byteCapacity);
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() { public VertexType getVertexType() {
@ -72,64 +77,98 @@ public class IndirectMeshPool {
} }
private void uploadAll() { 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 byteIndex = 0;
int baseVertex = 0; int baseVertex = 0;
int firstIndex = maxQuadIndexCount;
for (BufferedMesh mesh : meshList) { for (BufferedMesh mesh : meshList) {
mesh.byteIndex = byteIndex; mesh.byteIndex = byteIndex;
mesh.baseVertex = baseVertex; mesh.baseVertex = baseVertex;
mesh.buffer(ptr); mesh.buffer(vertexPtr);
byteIndex += mesh.size(); 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() { public void delete() {
clientStorage.free(); vertexArray.delete();
glDeleteBuffers(vbo); vbo.delete();
ebo.delete();
meshes.clear(); meshes.clear();
meshList.clear(); meshList.clear();
} }
public class BufferedMesh { public static class BufferedMesh {
private final Mesh mesh; private final Mesh mesh;
private final int vertexCount; public long byteIndex;
public int firstIndex;
private long byteIndex; public int baseVertex;
private int baseVertex;
private BufferedMesh(Mesh mesh) { private BufferedMesh(Mesh mesh) {
this.mesh = mesh; this.mesh = mesh;
vertexCount = mesh.getVertexCount();
}
public Mesh getMesh() {
return mesh;
} }
public int size() { public int size() {
return mesh.size(); return mesh.size();
} }
public int getBaseVertex() { public int indexCount() {
return baseVertex; return mesh.indexCount();
}
public int getIndexCount() {
return vertexCount * 6 / 4;
}
public VertexType getVertexType() {
return vertexType;
} }
private void buffer(long ptr) { private void buffer(long ptr) {
mesh.write(ptr + byteIndex); mesh.write(ptr + byteIndex);
} }
public Vector4fc boundingSphere() {
return mesh.boundingSphere();
}
} }
} }

View file

@ -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<Entry> quads = new ArrayList<>();
private final Object2ReferenceMap<Key, Entry> 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);
}
}
}

View file

@ -28,6 +28,7 @@ public class InstancedDrawManager {
private final List<InstancedInstancer<?>> initializedInstancers = new ArrayList<>(); private final List<InstancedInstancer<?>> initializedInstancers = new ArrayList<>();
private final Map<RenderStage, DrawSet> drawSets = new EnumMap<>(RenderStage.class); private final Map<RenderStage, DrawSet> drawSets = new EnumMap<>(RenderStage.class);
private final Map<VertexType, InstancedMeshPool> meshPools = new HashMap<>(); private final Map<VertexType, InstancedMeshPool> meshPools = new HashMap<>();
private final EBOCache eboCache = new EBOCache();
public DrawSet get(RenderStage stage) { public DrawSet get(RenderStage stage) {
return drawSets.getOrDefault(stage, DrawSet.EMPTY); return drawSets.getOrDefault(stage, DrawSet.EMPTY);
@ -69,6 +70,8 @@ public class InstancedDrawManager {
initializedInstancers.forEach(InstancedInstancer::delete); initializedInstancers.forEach(InstancedInstancer::delete);
initializedInstancers.clear(); initializedInstancers.clear();
eboCache.delete();
} }
public void clearInstancers() { public void clearInstancers() {
@ -89,8 +92,8 @@ public class InstancedDrawManager {
} }
private InstancedMeshPool.BufferedMesh alloc(Mesh mesh) { private InstancedMeshPool.BufferedMesh alloc(Mesh mesh) {
return meshPools.computeIfAbsent(mesh.getVertexType(), InstancedMeshPool::new) return meshPools.computeIfAbsent(mesh.vertexType(), InstancedMeshPool::new)
.alloc(mesh); .alloc(mesh, eboCache);
} }
public static class DrawSet implements Iterable<Map.Entry<ShaderState, Collection<DrawCall>>> { public static class DrawSet implements Iterable<Map.Entry<ShaderState, Collection<DrawCall>>> {

View file

@ -16,7 +16,6 @@ import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.gl.GlPrimitive; import com.jozufozu.flywheel.gl.GlPrimitive;
import com.jozufozu.flywheel.gl.array.GlVertexArray; 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.GlBuffer;
import com.jozufozu.flywheel.gl.buffer.MappedBuffer; import com.jozufozu.flywheel.gl.buffer.MappedBuffer;
@ -50,16 +49,17 @@ public class InstancedMeshPool {
/** /**
* Allocate a mesh in the arena. * 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. * @return A handle to the allocated mesh.
*/ */
public BufferedMesh alloc(Mesh mesh) { public BufferedMesh alloc(Mesh mesh, EBOCache eboCache) {
return meshes.computeIfAbsent(mesh, m -> { return meshes.computeIfAbsent(mesh, m -> {
if (m.getVertexType() != vertexType) { if (m.vertexType() != vertexType) {
throw new IllegalArgumentException("Mesh has wrong vertex type"); throw new IllegalArgumentException("Mesh has wrong vertex type");
} }
BufferedMesh bufferedMesh = new BufferedMesh(m, byteSize); BufferedMesh bufferedMesh = new BufferedMesh(m, byteSize, eboCache);
byteSize += bufferedMesh.size(); byteSize += bufferedMesh.size();
allBuffered.add(bufferedMesh); allBuffered.add(bufferedMesh);
pendingUpload.add(bufferedMesh); pendingUpload.add(bufferedMesh);
@ -145,16 +145,16 @@ public class InstancedMeshPool {
public class BufferedMesh { public class BufferedMesh {
private final Mesh mesh; private final Mesh mesh;
private final ElementBuffer ebo; private final int ebo;
private long byteIndex; private long byteIndex;
private boolean deleted; private boolean deleted;
private final Set<GlVertexArray> boundTo = new HashSet<>(); private final Set<GlVertexArray> boundTo = new HashSet<>();
private BufferedMesh(Mesh mesh, long byteIndex) { private BufferedMesh(Mesh mesh, long byteIndex, EBOCache eboCache) {
this.mesh = mesh; this.mesh = mesh;
this.byteIndex = byteIndex; this.byteIndex = byteIndex;
this.ebo = mesh.createEBO(); this.ebo = eboCache.get(mesh.indexSequence(), mesh.indexCount());
} }
public int size() { public int size() {
@ -188,15 +188,15 @@ public class InstancedMeshPool {
BufferLayout type = vertexType.getLayout(); BufferLayout type = vertexType.getLayout();
vao.bindVertexBuffer(0, InstancedMeshPool.this.vbo.handle(), byteIndex, type.getStride()); vao.bindVertexBuffer(0, InstancedMeshPool.this.vbo.handle(), byteIndex, type.getStride());
vao.bindAttributes(0, 0, type.attributes()); vao.bindAttributes(0, 0, type.attributes());
vao.setElementBuffer(ebo.glBuffer); vao.setElementBuffer(ebo);
} }
} }
public void draw(int instanceCount) { public void draw(int instanceCount) {
if (instanceCount > 1) { 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 { } else {
GL32.glDrawElements(GlPrimitive.TRIANGLES.glEnum, ebo.getElementCount(), ebo.getEboIndexType().asGLType, 0); GL32.glDrawElements(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, 0);
} }
} }

View file

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

View file

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

View file

@ -1,23 +1,16 @@
package com.jozufozu.flywheel.lib.model; package com.jozufozu.flywheel.lib.model;
import com.jozufozu.flywheel.api.model.IndexSequence;
import com.jozufozu.flywheel.api.model.Mesh; 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 { public interface QuadMesh extends Mesh {
/**
* Create an element buffer object that indexes the vertices of this mesh.
*
* <p>
* 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.
* </p>
* @return an element buffer object indexing this model's vertices.
*/
@Override @Override
default ElementBuffer createEBO() { default IndexSequence indexSequence() {
return QuadConverter.getInstance() return QuadIndexSequence.INSTANCE;
.quads2Tris(getVertexCount() / 4); }
@Override
default int indexCount() {
return vertexCount() / 2 * 3;
} }
} }

View file

@ -29,7 +29,7 @@ public class SimpleLazyModel implements Model {
} }
public int getVertexCount() { public int getVertexCount() {
return supplier.map(Mesh::getVertexCount) return supplier.map(Mesh::vertexCount)
.orElse(0); .orElse(0);
} }

View file

@ -28,7 +28,7 @@ public class SimpleMesh implements QuadMesh {
} }
vertexCount = bytes / stride; vertexCount = bytes / stride;
vertexList = getVertexType().createVertexList(); vertexList = vertexType().createVertexList();
vertexList.ptr(contents.ptr()); vertexList.ptr(contents.ptr());
vertexList.vertexCount(vertexCount); vertexList.vertexCount(vertexCount);
@ -36,12 +36,12 @@ public class SimpleMesh implements QuadMesh {
} }
@Override @Override
public VertexType getVertexType() { public VertexType vertexType() {
return vertexType; return vertexType;
} }
@Override @Override
public int getVertexCount() { public int vertexCount() {
return vertexCount; return vertexCount;
} }
@ -56,7 +56,7 @@ public class SimpleMesh implements QuadMesh {
} }
@Override @Override
public Vector4fc getBoundingSphere() { public Vector4fc boundingSphere() {
return boundingSphere; return boundingSphere;
} }

View file

@ -32,7 +32,7 @@ public class ModelPart implements QuadMesh {
cuboid.write(writer); cuboid.write(writer);
} }
vertexList = getVertexType().createVertexList(); vertexList = vertexType().createVertexList();
vertexList.ptr(ptr); vertexList.ptr(ptr);
vertexList.vertexCount(vertexCount); vertexList.vertexCount(vertexCount);
@ -44,12 +44,12 @@ public class ModelPart implements QuadMesh {
} }
@Override @Override
public PosTexNormalVertex getVertexType() { public PosTexNormalVertex vertexType() {
return VertexTypes.POS_TEX_NORMAL; return VertexTypes.POS_TEX_NORMAL;
} }
@Override @Override
public int getVertexCount() { public int vertexCount() {
return vertexCount; return vertexCount;
} }
@ -64,7 +64,7 @@ public class ModelPart implements QuadMesh {
} }
@Override @Override
public Vector4fc getBoundingSphere() { public Vector4fc boundingSphere() {
return boundingSphere; return boundingSphere;
} }

View file

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