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

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.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();
/**

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

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

View file

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

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

View file

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

View file

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

View file

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

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