mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-06 04:16:36 +01:00
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:
parent
961fafce0d
commit
257ee07e0e
20 changed files with 279 additions and 268 deletions
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public class TransformCall<I extends Instance> {
|
|||
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);
|
||||
|
|
|
@ -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<I extends Instance> {
|
||||
|
||||
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<I> drawSet = new IndirectDrawSet<>();
|
||||
|
||||
private final IndirectBuffers buffers;
|
||||
public final IndirectMeshPool meshPool;
|
||||
public final IndirectDrawSet<I> drawSet = new IndirectDrawSet<>();
|
||||
private boolean hasCulledThisFrame;
|
||||
private boolean needsMemoryBarrier;
|
||||
private int instanceCountThisFrame;
|
||||
|
||||
IndirectCullingGroup(InstanceType<I> instanceType, VertexType vertexType) {
|
||||
this.vertexType = vertexType;
|
||||
|
||||
instanceStride = instanceType.getLayout()
|
||||
.getStride();
|
||||
buffers = new IndirectBuffers(instanceStride);
|
||||
|
@ -49,33 +37,24 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||
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<I> 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<I extends Instance> {
|
|||
}
|
||||
|
||||
UniformBuffer.syncAndBind(draw);
|
||||
vertexArray.bindForDraw();
|
||||
meshPool.bindForDraw();
|
||||
buffers.bindForDraw();
|
||||
|
||||
memoryBarrier();
|
||||
|
@ -164,7 +143,6 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||
}
|
||||
|
||||
public void delete() {
|
||||
vertexArray.delete();
|
||||
buffers.delete();
|
||||
meshPool.delete();
|
||||
}
|
||||
|
|
|
@ -64,12 +64,12 @@ public class IndirectDraw<I extends Instance> {
|
|||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -64,9 +64,9 @@ public class IndirectDrawManager {
|
|||
var material = entry.getKey();
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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<Mesh, BufferedMesh> meshes = new HashMap<>();
|
||||
private final List<BufferedMesh> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ public class InstancedDrawManager {
|
|||
private final List<InstancedInstancer<?>> initializedInstancers = new ArrayList<>();
|
||||
private final Map<RenderStage, DrawSet> drawSets = new EnumMap<>(RenderStage.class);
|
||||
private final Map<VertexType, InstancedMeshPool> 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<Map.Entry<ShaderState, Collection<DrawCall>>> {
|
||||
|
|
|
@ -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<GlVertexArray> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <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
|
||||
default ElementBuffer createEBO() {
|
||||
return QuadConverter.getInstance()
|
||||
.quads2Tris(getVertexCount() / 4);
|
||||
default IndexSequence indexSequence() {
|
||||
return QuadIndexSequence.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default int indexCount() {
|
||||
return vertexCount() / 2 * 3;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ public class SimpleLazyModel implements Model {
|
|||
}
|
||||
|
||||
public int getVertexCount() {
|
||||
return supplier.map(Mesh::getVertexCount)
|
||||
return supplier.map(Mesh::vertexCount)
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue