Compare commits

..

2 Commits

Author SHA1 Message Date
Jozufozu
483eb199fa Finding some common ground
- Make MeshPool common between instancing and indirect
- Remove empty instancers and delete meshes on indirect
- Inline IndirectModel into IndirectInstancer since it was basically
  just a thin wrapper
- Share one meshpool between all culling groups
- Always upload draw commands because the mesh may have moved
- No longer need to set the base instance in the apply shader
2024-02-19 16:51:11 -06:00
Jozufozu
7d59fdc86c Eviction notice
- For instancing, remove empty instancers and delete meshes
- Specify in InstancerProvider's contract that instancers should not be
  kept around, but reusing them within one frame in guaranteed to be
  safe
- Do all instancer updates in flush, and remove unnecessary checks that
  would be made later in the frame
- Remove isEmpty from Mesh
- Remove staging buffer param from indirect mesh pool
2024-02-19 10:53:52 -06:00
18 changed files with 453 additions and 576 deletions

View File

@ -9,7 +9,11 @@ public interface InstancerProvider {
/** /**
* Get an instancer for the given instance type rendering the given model. * Get an instancer for the given instance type rendering the given model.
* *
* <p>Calling this method twice with the same arguments will return the same instancer.</p> * <p>Calling this method twice with the same arguments in the
* same frame will return the same instancer.</p>
*
* <p>It is not safe to store instancers between frames. Each
* time you need an instancer, you should call this method.</p>
* *
* @return An instancer for the given instance type rendering the given model. * @return An instancer for the given instance type rendering the given model.
*/ */

View File

@ -13,14 +13,6 @@ public interface Mesh {
*/ */
int vertexCount(); int vertexCount();
/**
* Is there nothing to render?
* @return true if there are no vertices.
*/
default boolean isEmpty() {
return vertexCount() == 0;
}
/** /**
* Write this mesh into a vertex list. Vertices with index {@literal <}0 or {@literal >=}{@link #vertexCount()} 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.

View File

@ -81,19 +81,19 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
changed.set(handle.index); changed.set(handle.index);
} }
public int getInstanceCount() { public int instanceCount() {
return instances.size(); return instances.size();
} }
public void notifyDirty(int index) { public void notifyDirty(int index) {
if (index < 0 || index >= getInstanceCount()) { if (index < 0 || index >= instanceCount()) {
return; return;
} }
changed.set(index); changed.set(index);
} }
public void notifyRemoval(int index) { public void notifyRemoval(int index) {
if (index < 0 || index >= getInstanceCount()) { if (index < 0 || index >= instanceCount()) {
return; return;
} }
deleted.set(index); deleted.set(index);
@ -159,6 +159,6 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
@Override @Override
public String toString() { public String toString() {
return "AbstractInstancer[" + getInstanceCount() + ']'; return "AbstractInstancer[" + instanceCount() + ']';
} }
} }

View File

@ -59,23 +59,23 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
var out = create(key); var out = create(key);
// Only queue the instancer for initialization if it has anything to render. // Only queue the instancer for initialization if it has anything to render.
if (key.model() if (checkAndWarnEmptyModel(key.model())) {
.meshes()
.isEmpty()) {
warnEmptyModel();
} else {
// Thread safety: this method is called atomically from within computeIfAbsent, // Thread safety: this method is called atomically from within computeIfAbsent,
// so we don't need extra synchronization to protect the queue. // so we don't need extra synchronization to protect the queue.
initializationQueue.add(new UninitializedInstancer<>(key, out)); initializationQueue.add(new UninitializedInstancer<>(key, out));
} }
return out; return out;
} }
protected record UninitializedInstancer<N, I extends Instance>(InstancerKey<I> key, N instancer) { protected record UninitializedInstancer<N, I extends Instance>(InstancerKey<I> key, N instancer) {
} }
private static void warnEmptyModel() { private static boolean checkAndWarnEmptyModel(Model model) {
if (!model.meshes().isEmpty()) {
return true;
}
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("Creating an instancer for a model with no meshes! Stack trace:"); builder.append("Creating an instancer for a model with no meshes! Stack trace:");
@ -85,5 +85,7 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
.append(f.toString())); .append(f.toString()));
Flywheel.LOGGER.warn(builder.toString()); Flywheel.LOGGER.warn(builder.toString());
return false;
} }
} }

View File

@ -0,0 +1,249 @@
package com.jozufozu.flywheel.backend.engine;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL32;
import com.jozufozu.flywheel.api.model.IndexSequence;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.vertex.VertexView;
import com.jozufozu.flywheel.backend.InternalVertex;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ReferenceArraySet;
public class MeshPool {
private final VertexView vertexView;
private final Map<Mesh, BufferedMesh> meshes = new HashMap<>();
private final List<BufferedMesh> meshList = new ArrayList<>();
private final GlBuffer vbo;
private final GlBuffer ebo;
private boolean dirty;
private boolean anyToRemove;
/**
* Create a new mesh pool.
*/
public MeshPool() {
vertexView = InternalVertex.createVertexView();
vbo = new GlBuffer();
ebo = new GlBuffer();
}
/**
* Allocate a model in the arena.
*
* @param mesh The model to allocate.
* @return A handle to the allocated model.
*/
public BufferedMesh alloc(Mesh mesh) {
return meshes.computeIfAbsent(mesh, this::_alloc);
}
private BufferedMesh _alloc(Mesh m) {
BufferedMesh bufferedModel = new BufferedMesh(m);
meshList.add(bufferedModel);
dirty = true;
return bufferedModel;
}
@Nullable
public BufferedMesh get(Mesh mesh) {
return meshes.get(mesh);
}
public void flush() {
if (!dirty) {
return;
}
if (anyToRemove) {
anyToRemove = false;
processDeletions();
}
uploadAll();
dirty = false;
}
private void processDeletions() {
// remove deleted meshes
meshList.removeIf(bufferedMesh -> {
boolean deleted = bufferedMesh.deleted();
if (deleted) {
meshes.remove(bufferedMesh.mesh);
}
return deleted;
});
}
private void uploadAll() {
long neededSize = 0;
Reference2IntMap<IndexSequence> indexCounts = new Reference2IntOpenHashMap<>();
indexCounts.defaultReturnValue(0);
for (BufferedMesh mesh : meshList) {
neededSize += mesh.byteSize();
int count = indexCounts.getInt(mesh.mesh.indexSequence());
int newCount = Math.max(count, mesh.indexCount());
if (newCount > count) {
indexCounts.put(mesh.mesh.indexSequence(), newCount);
}
}
long totalIndexCount = 0;
for (int count : indexCounts.values()) {
totalIndexCount += count;
}
final var indexBlock = MemoryBlock.malloc(totalIndexCount * GlNumericType.UINT.byteWidth());
final long indexPtr = indexBlock.ptr();
Reference2IntMap<IndexSequence> firstIndices = new Reference2IntOpenHashMap<>();
int firstIndex = 0;
for (Reference2IntMap.Entry<IndexSequence> entries : indexCounts.reference2IntEntrySet()) {
var indexSequence = entries.getKey();
var indexCount = entries.getIntValue();
firstIndices.put(indexSequence, firstIndex);
indexSequence.fill(indexPtr + (long) firstIndex * GlNumericType.UINT.byteWidth(), indexCount);
firstIndex += indexCount;
}
final var vertexBlock = MemoryBlock.malloc(neededSize);
final long vertexPtr = vertexBlock.ptr();
int byteIndex = 0;
int baseVertex = 0;
for (BufferedMesh mesh : meshList) {
mesh.byteIndex = byteIndex;
mesh.baseVertex = baseVertex;
mesh.write(vertexPtr, vertexView);
byteIndex += mesh.byteSize();
baseVertex += mesh.vertexCount();
mesh.firstIndex = firstIndices.getInt(mesh.mesh.indexSequence());
}
vbo.upload(vertexBlock);
ebo.upload(indexBlock);
vertexBlock.free();
indexBlock.free();
}
public void bind(GlVertexArray vertexArray) {
vertexArray.setElementBuffer(ebo.handle());
vertexArray.bindVertexBuffer(0, vbo.handle(), 0, InternalVertex.STRIDE);
vertexArray.bindAttributes(0, 0, InternalVertex.ATTRIBUTES);
}
public void delete() {
vbo.delete();
ebo.delete();
meshes.clear();
meshList.clear();
}
public class BufferedMesh {
private final Mesh mesh;
private final int vertexCount;
private final int byteSize;
private long byteIndex;
private int baseVertex;
private int firstIndex;
private int referenceCount = 0;
private final Set<GlVertexArray> boundTo = new ReferenceArraySet<>();
private BufferedMesh(Mesh mesh) {
this.mesh = mesh;
vertexCount = mesh.vertexCount();
byteSize = vertexCount * InternalVertex.STRIDE;
}
public int vertexCount() {
return vertexCount;
}
public int byteSize() {
return byteSize;
}
public int indexCount() {
return mesh.indexCount();
}
public int baseVertex() {
return baseVertex;
}
public int firstIndex() {
return firstIndex;
}
public boolean deleted() {
return referenceCount <= 0;
}
public boolean invalid() {
return mesh.vertexCount() == 0 || deleted() || byteIndex == -1;
}
private void write(long ptr, VertexView vertexView) {
vertexView.ptr(ptr + byteIndex);
vertexView.vertexCount(vertexCount);
mesh.write(vertexView);
}
public void draw(int instanceCount) {
if (instanceCount > 1) {
GL32.glDrawElementsInstanced(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, firstIndex, instanceCount);
} else {
GL32.glDrawElements(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, firstIndex);
}
}
public void setup(GlVertexArray vao) {
if (boundTo.add(vao)) {
vao.setElementBuffer(MeshPool.this.ebo.handle());
vao.bindVertexBuffer(0, MeshPool.this.vbo.handle(), byteIndex, InternalVertex.STRIDE);
vao.bindAttributes(0, 0, InternalVertex.ATTRIBUTES);
}
}
public void acquire() {
referenceCount++;
}
public void drop() {
if (--referenceCount == 0) {
MeshPool.this.dirty = true;
MeshPool.this.anyToRemove = true;
}
}
}
}

View File

@ -25,6 +25,7 @@ import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.model.Model; import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.backend.compile.IndirectPrograms; import com.jozufozu.flywheel.backend.compile.IndirectPrograms;
import com.jozufozu.flywheel.backend.engine.MaterialRenderState; import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
import com.jozufozu.flywheel.backend.engine.MeshPool;
import com.jozufozu.flywheel.backend.engine.textures.TextureBinder; import com.jozufozu.flywheel.backend.engine.textures.TextureBinder;
import com.jozufozu.flywheel.backend.engine.textures.TextureSourceImpl; import com.jozufozu.flywheel.backend.engine.textures.TextureSourceImpl;
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms; import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
@ -43,8 +44,7 @@ public class IndirectCullingGroup<I extends Instance> {
private final Context context; private final Context context;
private final long objectStride; private final long objectStride;
private final IndirectBuffers buffers; private final IndirectBuffers buffers;
private final IndirectMeshPool meshPool; private final List<IndirectInstancer<?>> instancers = new ArrayList<>();
private final List<IndirectModel> indirectModels = new ArrayList<>();
private final List<IndirectDraw> indirectDraws = new ArrayList<>(); private final List<IndirectDraw> indirectDraws = new ArrayList<>();
private final Map<RenderStage, List<MultiDraw>> multiDraws = new EnumMap<>(RenderStage.class); private final Map<RenderStage, List<MultiDraw>> multiDraws = new EnumMap<>(RenderStage.class);
@ -54,7 +54,7 @@ public class IndirectCullingGroup<I extends Instance> {
private final GlProgram drawProgram; private final GlProgram drawProgram;
private boolean needsDrawBarrier; private boolean needsDrawBarrier;
private boolean hasNewDraws; private boolean needsDrawSort;
private int instanceCountThisFrame; private int instanceCountThisFrame;
IndirectCullingGroup(InstanceType<I> instanceType, Context context, IndirectPrograms programs) { IndirectCullingGroup(InstanceType<I> instanceType, Context context, IndirectPrograms programs) {
@ -63,7 +63,6 @@ public class IndirectCullingGroup<I extends Instance> {
objectStride = instanceType.layout() objectStride = instanceType.layout()
.byteSize() + IndirectBuffers.INT_SIZE; .byteSize() + IndirectBuffers.INT_SIZE;
buffers = new IndirectBuffers(objectStride); buffers = new IndirectBuffers(objectStride);
meshPool = new IndirectMeshPool();
this.programs = programs; this.programs = programs;
// TODO: Culling programs need to be context aware. // TODO: Culling programs need to be context aware.
@ -72,18 +71,40 @@ public class IndirectCullingGroup<I extends Instance> {
drawProgram = programs.getIndirectProgram(instanceType, context.contextShader()); drawProgram = programs.getIndirectProgram(instanceType, context.contextShader());
} }
public void flush(StagingBuffer stagingBuffer) { public void flushInstancers() {
needsDrawBarrier = true; instanceCountThisFrame = 0;
instanceCountThisFrame = prepareModels(); int modelIndex = 0;
for (var iterator = instancers.iterator(); iterator.hasNext(); ) {
var instancer = iterator.next();
instancer.update();
var instanceCount = instancer.instanceCount();
if (instanceCount == 0) {
iterator.remove();
for (IndirectDraw draw : instancer.draws()) {
draw.delete();
}
continue;
}
instancer.index = modelIndex;
instancer.baseInstance = instanceCountThisFrame;
instanceCountThisFrame += instanceCount;
modelIndex++;
}
if (indirectDraws.removeIf(IndirectDraw::deleted)) {
needsDrawSort = true;
}
}
public void upload(StagingBuffer stagingBuffer) {
if (nothingToDo()) { if (nothingToDo()) {
return; return;
} }
buffers.updateCounts(instanceCountThisFrame, indirectModels.size(), indirectDraws.size()); buffers.updateCounts(instanceCountThisFrame, instancers.size(), indirectDraws.size());
// Must flush the mesh pool first so everything else has the right baseVertex and baseIndex.
meshPool.flush(stagingBuffer);
// Upload only objects that have changed. // Upload only objects that have changed.
uploadObjects(stagingBuffer); uploadObjects(stagingBuffer);
@ -91,14 +112,14 @@ public class IndirectCullingGroup<I extends Instance> {
// We need to upload the models every frame to reset the instance count. // We need to upload the models every frame to reset the instance count.
uploadModels(stagingBuffer); uploadModels(stagingBuffer);
if (hasNewDraws) { if (needsDrawSort) {
sortDraws(); sortDraws();
// Draws, however, only need to be updated when we get new ones. needsDrawSort = false;
// The instanceCount and baseInstance will be updated by the applyProgram,
// and all other fields are constant to the lifetime of the draw.
uploadDraws(stagingBuffer);
hasNewDraws = false;
} }
uploadDraws(stagingBuffer);
needsDrawBarrier = true;
} }
public void dispatchCull() { public void dispatchCull() {
@ -132,18 +153,6 @@ public class IndirectCullingGroup<I extends Instance> {
return nothingToDo() || !multiDraws.containsKey(stage); return nothingToDo() || !multiDraws.containsKey(stage);
} }
/**
* @return the total instance count
*/
private int prepareModels() {
int baseInstance = 0;
for (var model : indirectModels) {
model.prepare(baseInstance);
baseInstance += model.instancer.getInstanceCount();
}
return baseInstance;
}
private void sortDraws() { private void sortDraws() {
multiDraws.clear(); multiDraws.clear();
// sort by stage, then material // sort by stage, then material
@ -169,20 +178,18 @@ public class IndirectCullingGroup<I extends Instance> {
return multiDraws.containsKey(stage); return multiDraws.containsKey(stage);
} }
public void add(IndirectInstancer<I> instancer, Model model, RenderStage stage) { public void add(IndirectInstancer<I> instancer, Model model, RenderStage stage, MeshPool meshPool) {
int modelIndex = indirectModels.size(); instancer.index = instancers.size();
instancer.setModelIndex(modelIndex); instancers.add(instancer);
var indirectModel = new IndirectModel(instancer, modelIndex, model.boundingSphere());
indirectModels.add(indirectModel);
for (Map.Entry<Material, Mesh> entry : model.meshes().entrySet()) { for (Map.Entry<Material, Mesh> entry : model.meshes().entrySet()) {
IndirectMeshPool.BufferedMesh bufferedMesh = meshPool.alloc(entry.getValue()); MeshPool.BufferedMesh bufferedMesh = meshPool.alloc(entry.getValue());
var draw = new IndirectDraw(indirectModel, entry.getKey(), bufferedMesh, stage); var draw = new IndirectDraw(instancer, entry.getKey(), bufferedMesh, stage);
indirectDraws.add(draw); indirectDraws.add(draw);
instancer.addDraw(draw); instancer.addDraw(draw);
} }
hasNewDraws = true; needsDrawSort = true;
} }
public void submit(RenderStage stage, TextureSourceImpl textures) { public void submit(RenderStage stage, TextureSourceImpl textures) {
@ -190,9 +197,7 @@ public class IndirectCullingGroup<I extends Instance> {
return; return;
} }
Uniforms.bindForDraw();
drawProgram.bind(); drawProgram.bind();
meshPool.bindForDraw();
buffers.bindForDraw(); buffers.bindForDraw();
drawBarrier(); drawBarrier();
@ -215,8 +220,6 @@ public class IndirectCullingGroup<I extends Instance> {
program.bind(); program.bind();
Uniforms.bindForDraw();
meshPool.bindForDraw();
buffers.bindForCrumbling(); buffers.bindForCrumbling();
drawBarrier(); drawBarrier();
@ -236,8 +239,8 @@ public class IndirectCullingGroup<I extends Instance> {
private void uploadObjects(StagingBuffer stagingBuffer) { private void uploadObjects(StagingBuffer stagingBuffer) {
long pos = 0; long pos = 0;
for (IndirectModel model : indirectModels) { for (var model : instancers) {
var instanceCount = model.instancer.getInstanceCount(); var instanceCount = model.instanceCount();
model.uploadObjects(stagingBuffer, pos, buffers.object.handle()); model.uploadObjects(stagingBuffer, pos, buffers.object.handle());
pos += instanceCount * objectStride; pos += instanceCount * objectStride;
@ -245,7 +248,7 @@ public class IndirectCullingGroup<I extends Instance> {
} }
private void uploadModels(StagingBuffer stagingBuffer) { private void uploadModels(StagingBuffer stagingBuffer) {
var totalSize = indirectModels.size() * IndirectBuffers.MODEL_STRIDE; var totalSize = instancers.size() * IndirectBuffers.MODEL_STRIDE;
var handle = buffers.model.handle(); var handle = buffers.model.handle();
stagingBuffer.enqueueCopy(totalSize, handle, 0, this::writeModels); stagingBuffer.enqueueCopy(totalSize, handle, 0, this::writeModels);
@ -259,8 +262,8 @@ public class IndirectCullingGroup<I extends Instance> {
} }
private void writeModels(long writePtr) { private void writeModels(long writePtr) {
for (var model : indirectModels) { for (var model : instancers) {
model.write(writePtr); model.writeModel(writePtr);
writePtr += IndirectBuffers.MODEL_STRIDE; writePtr += IndirectBuffers.MODEL_STRIDE;
} }
} }
@ -274,7 +277,6 @@ public class IndirectCullingGroup<I extends Instance> {
public void delete() { public void delete() {
buffers.delete(); buffers.delete();
meshPool.delete();
} }
private record MultiDraw(Material material, int start, int end) { private record MultiDraw(Material material, int start, int end) {

View File

@ -6,35 +6,43 @@ import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.backend.ShaderIndices; import com.jozufozu.flywheel.backend.ShaderIndices;
import com.jozufozu.flywheel.backend.engine.MaterialEncoder; import com.jozufozu.flywheel.backend.engine.MaterialEncoder;
import com.jozufozu.flywheel.backend.engine.MeshPool;
public class IndirectDraw { public class IndirectDraw {
private final IndirectModel model; private final IndirectInstancer<?> model;
private final Material material; private final Material material;
private final IndirectMeshPool.BufferedMesh mesh; private final MeshPool.BufferedMesh mesh;
private final RenderStage stage; private final RenderStage stage;
private final int materialVertexIndex; private final int materialVertexIndex;
private final int materialFragmentIndex; private final int materialFragmentIndex;
private final int packedFogAndCutout; private final int packedFogAndCutout;
private final int packedMaterialProperties; private final int packedMaterialProperties;
private boolean deleted;
public IndirectDraw(IndirectModel model, Material material, IndirectMeshPool.BufferedMesh mesh, RenderStage stage) { public IndirectDraw(IndirectInstancer<?> model, Material material, MeshPool.BufferedMesh mesh, RenderStage stage) {
this.model = model; this.model = model;
this.material = material; this.material = material;
this.mesh = mesh; this.mesh = mesh;
this.stage = stage; this.stage = stage;
mesh.acquire();
this.materialVertexIndex = ShaderIndices.getVertexShaderIndex(material.shaders()); this.materialVertexIndex = ShaderIndices.getVertexShaderIndex(material.shaders());
this.materialFragmentIndex = ShaderIndices.getFragmentShaderIndex(material.shaders()); this.materialFragmentIndex = ShaderIndices.getFragmentShaderIndex(material.shaders());
this.packedFogAndCutout = MaterialEncoder.packFogAndCutout(material); this.packedFogAndCutout = MaterialEncoder.packFogAndCutout(material);
this.packedMaterialProperties = MaterialEncoder.packProperties(material); this.packedMaterialProperties = MaterialEncoder.packProperties(material);
} }
public boolean deleted() {
return deleted;
}
public Material material() { public Material material() {
return material; return material;
} }
public IndirectMeshPool.BufferedMesh mesh() { public MeshPool.BufferedMesh mesh() {
return mesh; return mesh;
} }
@ -47,9 +55,9 @@ public class IndirectDraw {
MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be set by the apply shader MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be set by the apply shader
MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex
MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex
MemoryUtil.memPutInt(ptr + 16, 0); // baseInstance - to be set by the apply shader MemoryUtil.memPutInt(ptr + 16, model.baseInstance); // baseInstance
MemoryUtil.memPutInt(ptr + 20, model.index); // modelIndex - never changes MemoryUtil.memPutInt(ptr + 20, model.index); // modelIndex
MemoryUtil.memPutInt(ptr + 24, materialVertexIndex); // materialVertexIndex MemoryUtil.memPutInt(ptr + 24, materialVertexIndex); // materialVertexIndex
MemoryUtil.memPutInt(ptr + 28, materialFragmentIndex); // materialFragmentIndex MemoryUtil.memPutInt(ptr + 28, materialFragmentIndex); // materialFragmentIndex
@ -62,7 +70,7 @@ public class IndirectDraw {
MemoryUtil.memPutInt(ptr + 4, 1); // instanceCount - only drawing one instance MemoryUtil.memPutInt(ptr + 4, 1); // instanceCount - only drawing one instance
MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex
MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex
MemoryUtil.memPutInt(ptr + 16, model.baseInstance() + instanceIndex); // baseInstance MemoryUtil.memPutInt(ptr + 16, model.baseInstance + instanceIndex); // baseInstance
MemoryUtil.memPutInt(ptr + 20, model.index); // modelIndex MemoryUtil.memPutInt(ptr + 20, model.index); // modelIndex
@ -71,4 +79,14 @@ public class IndirectDraw {
MemoryUtil.memPutInt(ptr + 32, MaterialEncoder.packFogAndCutout(materialOverride)); // packedFogAndCutout MemoryUtil.memPutInt(ptr + 32, MaterialEncoder.packFogAndCutout(materialOverride)); // packedFogAndCutout
MemoryUtil.memPutInt(ptr + 36, MaterialEncoder.packProperties(materialOverride)); // packedMaterialProperties MemoryUtil.memPutInt(ptr + 36, MaterialEncoder.packProperties(materialOverride)); // packedMaterialProperties
} }
public void delete() {
if (deleted) {
return;
}
mesh.drop();
deleted = true;
}
} }

View File

@ -22,9 +22,12 @@ import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
import com.jozufozu.flywheel.backend.engine.InstancerKey; import com.jozufozu.flywheel.backend.engine.InstancerKey;
import com.jozufozu.flywheel.backend.engine.InstancerStorage; import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.backend.engine.MaterialRenderState; import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
import com.jozufozu.flywheel.backend.engine.MeshPool;
import com.jozufozu.flywheel.backend.engine.textures.TextureBinder; import com.jozufozu.flywheel.backend.engine.textures.TextureBinder;
import com.jozufozu.flywheel.backend.engine.textures.TextureSourceImpl; import com.jozufozu.flywheel.backend.engine.textures.TextureSourceImpl;
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
import com.jozufozu.flywheel.backend.gl.GlStateTracker; import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer; import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType; import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.lib.context.ContextShaders; import com.jozufozu.flywheel.lib.context.ContextShaders;
@ -40,6 +43,8 @@ import net.minecraft.client.resources.model.ModelBakery;
public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>> { public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>> {
private final IndirectPrograms programs; private final IndirectPrograms programs;
private final StagingBuffer stagingBuffer; private final StagingBuffer stagingBuffer;
private final MeshPool meshPool;
private final GlVertexArray vertexArray;
private final TextureSourceImpl textures = new TextureSourceImpl(); private final TextureSourceImpl textures = new TextureSourceImpl();
private final Map<GroupKey<?>, IndirectCullingGroup<?>> cullingGroups = new HashMap<>(); private final Map<GroupKey<?>, IndirectCullingGroup<?>> cullingGroups = new HashMap<>();
private final GlBuffer crumblingDrawBuffer = new GlBuffer(); private final GlBuffer crumblingDrawBuffer = new GlBuffer();
@ -47,11 +52,17 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
public IndirectDrawManager(IndirectPrograms programs) { public IndirectDrawManager(IndirectPrograms programs) {
this.programs = programs; this.programs = programs;
stagingBuffer = new StagingBuffer(this.programs); stagingBuffer = new StagingBuffer(this.programs);
meshPool = new MeshPool();
vertexArray = GlVertexArray.create();
meshPool.bind(vertexArray);
} }
@Override @Override
protected <I extends Instance> IndirectInstancer<?> create(InstancerKey<I> key) { protected <I extends Instance> IndirectInstancer<?> create(InstancerKey<I> key) {
return new IndirectInstancer<>(key.type(), key.context()); return new IndirectInstancer<>(key.type(), key.context(), key.model());
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -59,7 +70,7 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
protected <I extends Instance> void initialize(InstancerKey<I> key, IndirectInstancer<?> instancer) { protected <I extends Instance> void initialize(InstancerKey<I> key, IndirectInstancer<?> instancer) {
var groupKey = new GroupKey<>(key.type(), key.context()); var groupKey = new GroupKey<>(key.type(), key.context());
var group = (IndirectCullingGroup<I>) cullingGroups.computeIfAbsent(groupKey, t -> new IndirectCullingGroup<>(t.type, t.context, programs)); var group = (IndirectCullingGroup<I>) cullingGroups.computeIfAbsent(groupKey, t -> new IndirectCullingGroup<>(t.type, t.context, programs));
group.add((IndirectInstancer<I>) instancer, key.model(), key.stage()); group.add((IndirectInstancer<I>) instancer, key.model(), key.stage(), meshPool);
} }
public boolean hasStage(RenderStage stage) { public boolean hasStage(RenderStage stage) {
@ -74,6 +85,9 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
public void renderStage(RenderStage stage) { public void renderStage(RenderStage stage) {
TextureBinder.bindLightAndOverlay(); TextureBinder.bindLightAndOverlay();
vertexArray.bindForDraw();
Uniforms.bindForDraw();
for (var group : cullingGroups.values()) { for (var group : cullingGroups.values()) {
group.submit(stage, textures); group.submit(stage, textures);
} }
@ -86,10 +100,18 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
public void flush() { public void flush() {
super.flush(); super.flush();
for (var group : cullingGroups.values()) {
group.flushInstancers();
}
instancers.values().removeIf(instancer -> instancer.instanceCount() == 0);
meshPool.flush();
stagingBuffer.reclaim(); stagingBuffer.reclaim();
for (var group : cullingGroups.values()) { for (var group : cullingGroups.values()) {
group.flush(stagingBuffer); group.upload(stagingBuffer);
} }
stagingBuffer.flush(); stagingBuffer.flush();
@ -113,6 +135,8 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
stagingBuffer.delete(); stagingBuffer.delete();
meshPool.delete();
crumblingDrawBuffer.delete(); crumblingDrawBuffer.delete();
} }
@ -126,6 +150,9 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
try (var state = GlStateTracker.getRestoreState()) { try (var state = GlStateTracker.getRestoreState()) {
TextureBinder.bindLightAndOverlay(); TextureBinder.bindLightAndOverlay();
vertexArray.bindForDraw();
Uniforms.bindForDraw();
var crumblingMaterial = SimpleMaterial.builder(); var crumblingMaterial = SimpleMaterial.builder();
// Scratch memory for writing draw commands. // Scratch memory for writing draw commands.

View File

@ -3,27 +3,34 @@ package com.jozufozu.flywheel.backend.engine.indirect;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.joml.Vector4fc;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.context.Context; import com.jozufozu.flywheel.api.context.Context;
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.instance.InstanceWriter; import com.jozufozu.flywheel.api.instance.InstanceWriter;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.backend.engine.AbstractInstancer; import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I> { public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I> {
private final long objectStride; private final long objectStride;
private final InstanceWriter<I> writer; private final InstanceWriter<I> writer;
private final List<IndirectDraw> associatedDraws = new ArrayList<>(); private final List<IndirectDraw> associatedDraws = new ArrayList<>();
private int modelIndex; private final Vector4fc boundingSphere;
public int index;
public int baseInstance = -1;
private int lastModelIndex = -1;
private long lastStartPos = -1; private long lastStartPos = -1;
public IndirectInstancer(InstanceType<I> type, Context context) { public IndirectInstancer(InstanceType<I> type, Context context, Model model) {
super(type, context); super(type, context);
this.objectStride = type.layout() this.objectStride = type.layout()
.byteSize() + IndirectBuffers.INT_SIZE; .byteSize() + IndirectBuffers.INT_SIZE;
writer = this.type.writer(); writer = this.type.writer();
boundingSphere = model.boundingSphere();
} }
public void addDraw(IndirectDraw draw) { public void addDraw(IndirectDraw draw) {
@ -38,8 +45,17 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
removeDeletedInstances(); removeDeletedInstances();
} }
public void upload(StagingBuffer stagingBuffer, long startPos, int dstVbo) { public void writeModel(long ptr) {
if (shouldUploadAll(startPos)) { MemoryUtil.memPutInt(ptr, 0); // instanceCount - to be incremented by the cull shader
MemoryUtil.memPutInt(ptr + 4, baseInstance); // baseInstance
MemoryUtil.memPutFloat(ptr + 8, boundingSphere.x()); // boundingSphere
MemoryUtil.memPutFloat(ptr + 12, boundingSphere.y());
MemoryUtil.memPutFloat(ptr + 16, boundingSphere.z());
MemoryUtil.memPutFloat(ptr + 20, boundingSphere.w());
}
public void uploadObjects(StagingBuffer stagingBuffer, long startPos, int dstVbo) {
if (shouldUploadAll(startPos)) {
uploadAll(stagingBuffer, startPos, dstVbo); uploadAll(stagingBuffer, startPos, dstVbo);
} else { } else {
uploadChanged(stagingBuffer, startPos, dstVbo); uploadChanged(stagingBuffer, startPos, dstVbo);
@ -47,10 +63,11 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
changed.clear(); changed.clear();
lastStartPos = startPos; lastStartPos = startPos;
lastModelIndex = index;
} }
private boolean shouldUploadAll(long startPos) { private boolean shouldUploadAll(long startPos) {
return startPos != lastStartPos; return startPos != lastStartPos || index != lastModelIndex;
} }
private void uploadChanged(StagingBuffer stagingBuffer, long baseByte, int dstVbo) { private void uploadChanged(StagingBuffer stagingBuffer, long baseByte, int dstVbo) {
@ -81,11 +98,7 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
} }
private void writeOne(long ptr, I instance) { private void writeOne(long ptr, I instance) {
MemoryUtil.memPutInt(ptr, modelIndex); MemoryUtil.memPutInt(ptr, index);
writer.write(ptr + IndirectBuffers.INT_SIZE, instance); writer.write(ptr + IndirectBuffers.INT_SIZE, instance);
} }
public void setModelIndex(int modelIndex) {
this.modelIndex = modelIndex;
}
} }

View File

@ -1,183 +0,0 @@
package com.jozufozu.flywheel.backend.engine.indirect;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.vertex.VertexView;
import com.jozufozu.flywheel.backend.InternalVertex;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.QuadIndexSequence;
public class IndirectMeshPool {
private final VertexView vertexView;
private final Map<Mesh, BufferedMesh> meshes = new HashMap<>();
private final List<BufferedMesh> meshList = new ArrayList<>();
private final GlVertexArray vertexArray;
private final GlBuffer vbo;
private final GlBuffer ebo;
private boolean dirty;
/**
* Create a new mesh pool.
*/
public IndirectMeshPool() {
vertexView = InternalVertex.createVertexView();
vbo = new GlBuffer();
ebo = new GlBuffer();
vertexArray = GlVertexArray.create();
vertexArray.setElementBuffer(ebo.handle());
vertexArray.bindVertexBuffer(0, vbo.handle(), 0, InternalVertex.STRIDE);
vertexArray.bindAttributes(0, 0, InternalVertex.ATTRIBUTES);
}
/**
* Allocate a model in the arena.
*
* @param mesh The model to allocate.
* @return A handle to the allocated model.
*/
public BufferedMesh alloc(Mesh mesh) {
return meshes.computeIfAbsent(mesh, m -> {
BufferedMesh bufferedModel = new BufferedMesh(m);
meshList.add(bufferedModel);
dirty = true;
return bufferedModel;
});
}
@Nullable
public BufferedMesh get(Mesh mesh) {
return meshes.get(mesh);
}
public void flush(StagingBuffer stagingBuffer) {
if (dirty) {
// TODO: use the staging buffer and be smarter about allocation in general.
uploadAll(stagingBuffer);
dirty = false;
}
}
private void uploadAll(StagingBuffer stagingBuffer) {
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.write(vertexPtr, vertexView);
byteIndex += mesh.size();
baseVertex += 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;
}
}
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() {
vertexArray.delete();
vbo.delete();
ebo.delete();
meshes.clear();
meshList.clear();
}
public static class BufferedMesh {
private final Mesh mesh;
private final int vertexCount;
private final int byteSize;
private long byteIndex;
private int baseVertex;
private int firstIndex;
private BufferedMesh(Mesh mesh) {
this.mesh = mesh;
vertexCount = mesh.vertexCount();
byteSize = vertexCount * InternalVertex.STRIDE;
}
public int vertexCount() {
return vertexCount;
}
public int size() {
return byteSize;
}
public int indexCount() {
return mesh.indexCount();
}
public int baseVertex() {
return baseVertex;
}
public int firstIndex() {
return firstIndex;
}
private void write(long ptr, VertexView vertexView) {
vertexView.ptr(ptr + byteIndex);
vertexView.vertexCount(vertexCount);
mesh.write(vertexView);
}
}
}

View File

@ -1,40 +0,0 @@
package com.jozufozu.flywheel.backend.engine.indirect;
import org.joml.Vector4fc;
import org.lwjgl.system.MemoryUtil;
public class IndirectModel {
public final IndirectInstancer<?> instancer;
public final int index;
private final Vector4fc boundingSphere;
private int baseInstance = -1;
public IndirectModel(IndirectInstancer<?> instancer, int index, Vector4fc boundingSphere) {
this.instancer = instancer;
this.index = index;
this.boundingSphere = boundingSphere;
}
public int baseInstance() {
return baseInstance;
}
public void prepare(int baseInstance) {
instancer.update();
this.baseInstance = baseInstance;
}
public void uploadObjects(StagingBuffer stagingBuffer, long start, int dstVbo) {
instancer.upload(stagingBuffer, start, dstVbo);
}
public void write(long ptr) {
MemoryUtil.memPutInt(ptr, 0); // instanceCount - to be incremented by the cull shader
MemoryUtil.memPutInt(ptr + 4, baseInstance); // baseInstance
MemoryUtil.memPutFloat(ptr + 8, boundingSphere.x()); // boundingSphere
MemoryUtil.memPutFloat(ptr + 12, boundingSphere.y());
MemoryUtil.memPutFloat(ptr + 16, boundingSphere.z());
MemoryUtil.memPutFloat(ptr + 20, boundingSphere.w());
}
}

View File

@ -4,39 +4,35 @@ import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.backend.InternalVertex; import com.jozufozu.flywheel.backend.InternalVertex;
import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl; import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
import com.jozufozu.flywheel.backend.engine.MeshPool;
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray; import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
public class DrawCall { public class DrawCall {
public final ShaderState shaderState; public final ShaderState shaderState;
private final InstancedInstancer<?> instancer; private final InstancedInstancer<?> instancer;
private final InstancedMeshPool.BufferedMesh mesh; private final MeshPool.BufferedMesh mesh;
@Nullable private final GlVertexArray vao;
private GlVertexArray vao;
@Nullable @Nullable
private GlVertexArray vaoScratch; private GlVertexArray vaoScratch;
private boolean deleted;
public DrawCall(InstancedInstancer<?> instancer, InstancedMeshPool.BufferedMesh mesh, ShaderState shaderState) { public DrawCall(InstancedInstancer<?> instancer, MeshPool.BufferedMesh mesh, ShaderState shaderState) {
this.instancer = instancer; this.instancer = instancer;
this.mesh = mesh; this.mesh = mesh;
this.shaderState = shaderState; this.shaderState = shaderState;
mesh.acquire();
vao = GlVertexArray.create(); vao = GlVertexArray.create();
} }
public boolean isInvalid() { public boolean deleted() {
return instancer.isInvalid() || vao == null; return deleted;
} }
public void render() { public void render() {
if (isInvalid() || mesh.isEmpty()) { if (mesh.invalid()) {
return;
}
instancer.update();
int instanceCount = instancer.getInstanceCount();
if (instanceCount <= 0) {
return; return;
} }
@ -45,17 +41,15 @@ public class DrawCall {
vao.bindForDraw(); vao.bindForDraw();
mesh.draw(instanceCount); mesh.draw(instancer.instanceCount());
} }
public void renderOne(InstanceHandleImpl impl) { public void renderOne(InstanceHandleImpl impl) {
if (isInvalid() || mesh.isEmpty()) { if (mesh.invalid()) {
return; return;
} }
instancer.update(); int instanceCount = instancer.instanceCount();
int instanceCount = instancer.getInstanceCount();
if (instanceCount <= 0 || impl.index >= instanceCount) { if (instanceCount <= 0 || impl.index >= instanceCount) {
return; return;
} }
@ -78,14 +72,19 @@ public class DrawCall {
} }
public void delete() { public void delete() {
if (vao != null) { if (deleted) {
vao.delete(); return;
vao = null;
} }
vao.delete();
if (vaoScratch != null) { if (vaoScratch != null) {
vaoScratch.delete(); vaoScratch.delete();
vaoScratch = null; vaoScratch = null;
} }
mesh.drop();
deleted = true;
} }
} }

View File

@ -98,11 +98,7 @@ public class InstancedCrumbling {
continue; continue;
} }
List<DrawCall> draws = instancer.drawCalls(); for (DrawCall draw : instancer.drawCalls()) {
draws.removeIf(DrawCall::isInvalid);
for (DrawCall draw : draws) {
out.computeIfAbsent(draw.shaderState, $ -> new Int2ObjectArrayMap<>()) out.computeIfAbsent(draw.shaderState, $ -> new Int2ObjectArrayMap<>())
.computeIfAbsent(progress, $ -> new ArrayList<>()) .computeIfAbsent(progress, $ -> new ArrayList<>())
.add(() -> draw.renderOne(impl)); .add(() -> draw.renderOne(impl));

View File

@ -10,9 +10,9 @@ import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap; import com.google.common.collect.ListMultimap;
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.model.Mesh;
import com.jozufozu.flywheel.backend.engine.InstancerKey; import com.jozufozu.flywheel.backend.engine.InstancerKey;
import com.jozufozu.flywheel.backend.engine.InstancerStorage; import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.backend.engine.MeshPool;
public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>> { public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>> {
/** /**
@ -22,8 +22,7 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
/** /**
* A map of vertex types to their mesh pools. * A map of vertex types to their mesh pools.
*/ */
private final InstancedMeshPool meshPool = new InstancedMeshPool(); private final MeshPool meshPool = new MeshPool();
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);
@ -32,6 +31,24 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
public void flush() { public void flush() {
super.flush(); super.flush();
var instancers = this.instancers.values();
instancers.removeIf(instancer -> {
// Update the instancers and remove any that are empty.
instancer.update();
if (instancer.instanceCount() == 0) {
instancer.delete();
return true;
} else {
return false;
}
});
for (DrawSet drawSet : drawSets.values()) {
// Remove the draw calls for any instancers we deleted.
drawSet.prune();
}
meshPool.flush(); meshPool.flush();
} }
@ -46,12 +63,6 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
drawSets.values() drawSets.values()
.forEach(DrawSet::delete); .forEach(DrawSet::delete);
drawSets.clear(); drawSets.clear();
eboCache.invalidate();
}
private InstancedMeshPool.BufferedMesh alloc(Mesh mesh) {
return meshPool.alloc(mesh, eboCache);
} }
@Override @Override
@ -68,7 +79,7 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
var meshes = key.model() var meshes = key.model()
.meshes(); .meshes();
for (var entry : meshes.entrySet()) { for (var entry : meshes.entrySet()) {
var mesh = alloc(entry.getValue()); var mesh = meshPool.alloc(entry.getValue());
ShaderState shaderState = new ShaderState(entry.getKey(), key.type(), key.context()); ShaderState shaderState = new ShaderState(entry.getKey(), key.type(), key.context());
DrawCall drawCall = new DrawCall(instancer, mesh, shaderState); DrawCall drawCall = new DrawCall(instancer, mesh, shaderState);
@ -111,5 +122,10 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
.entrySet() .entrySet()
.iterator(); .iterator();
} }
public void prune() {
drawCalls.values()
.removeIf(DrawCall::deleted);
}
} }
} }

View File

@ -39,14 +39,6 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
writer = type.writer(); writer = type.writer();
} }
public int getAttributeCount() {
return instanceAttributes.size();
}
public boolean isInvalid() {
return vbo == null;
}
public void init() { public void init() {
if (vbo != null) { if (vbo != null) {
return; return;
@ -58,28 +50,22 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
public void update() { public void update() {
removeDeletedInstances(); removeDeletedInstances();
ensureBufferCapacity();
updateBuffer(); updateBuffer();
} }
private void ensureBufferCapacity() {
int count = instances.size();
int byteSize = instanceStride * count;
if (vbo.ensureCapacity(byteSize)) {
// The vbo has moved, so we need to re-bind attributes
boundTo.clear();
}
}
private void updateBuffer() { private void updateBuffer() {
if (changed.isEmpty() || vbo == null) { if (changed.isEmpty() || vbo == null) {
return; return;
} }
try (MappedBuffer buf = vbo.map()) { int byteSize = instanceStride * instances.size();
long ptr = buf.ptr(); if (vbo.ensureCapacity(byteSize)) {
// The vbo has moved, so we need to re-bind attributes
boundTo.clear();
}
writeChanged(ptr); try (MappedBuffer buf = vbo.map()) {
writeChanged(buf.ptr());
changed.clear(); changed.clear();
} catch (Exception e) { } catch (Exception e) {
@ -129,6 +115,10 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
} }
vbo.delete(); vbo.delete();
vbo = null; vbo = null;
for (DrawCall drawCall : drawCalls) {
drawCall.delete();
}
} }
public void addDrawCall(DrawCall drawCall) { public void addDrawCall(DrawCall drawCall) {

View File

@ -1,205 +0,0 @@
package com.jozufozu.flywheel.backend.engine.instancing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL32;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.vertex.VertexView;
import com.jozufozu.flywheel.backend.InternalVertex;
import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
public class InstancedMeshPool {
private final VertexView vertexView;
private final Map<Mesh, BufferedMesh> meshes = new HashMap<>();
private final List<BufferedMesh> allBuffered = new ArrayList<>();
private final List<BufferedMesh> pendingUpload = new ArrayList<>();
private final GlBuffer vbo;
private long byteSize;
private boolean dirty;
private boolean anyToRemove;
/**
* Create a new mesh pool.
*/
public InstancedMeshPool() {
vertexView = InternalVertex.createVertexView();
int stride = InternalVertex.STRIDE;
vbo = new GlBuffer();
vbo.growthFunction(l -> Math.max(l + stride * 128L, (long) (l * 1.6)));
}
/**
* Allocate a mesh in the arena.
*
* @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, EboCache eboCache) {
return meshes.computeIfAbsent(mesh, m -> {
BufferedMesh bufferedMesh = new BufferedMesh(m, byteSize, eboCache);
byteSize += bufferedMesh.size();
allBuffered.add(bufferedMesh);
pendingUpload.add(bufferedMesh);
dirty = true;
return bufferedMesh;
});
}
@Nullable
public BufferedMesh get(Mesh mesh) {
return meshes.get(mesh);
}
public void flush() {
if (!dirty) {
return;
}
if (anyToRemove) {
processDeletions();
}
vbo.ensureCapacity(byteSize);
uploadPending();
dirty = false;
pendingUpload.clear();
}
private void processDeletions() {
// remove deleted meshes
allBuffered.removeIf(bufferedMesh -> {
boolean deleted = bufferedMesh.isDeleted();
if (deleted) {
meshes.remove(bufferedMesh.mesh);
}
return deleted;
});
// re-evaluate first vertex for each mesh
int byteIndex = 0;
for (BufferedMesh mesh : allBuffered) {
if (mesh.byteIndex != byteIndex) {
pendingUpload.add(mesh);
}
mesh.byteIndex = byteIndex;
byteIndex += mesh.size();
}
this.byteSize = byteIndex;
this.anyToRemove = false;
}
private void uploadPending() {
try (MappedBuffer mapped = vbo.map()) {
long ptr = mapped.ptr();
for (BufferedMesh mesh : pendingUpload) {
mesh.write(ptr, vertexView);
mesh.boundTo.clear();
}
pendingUpload.clear();
} catch (Exception e) {
Flywheel.LOGGER.error("Error uploading pooled meshes:", e);
}
}
public void delete() {
vbo.delete();
meshes.clear();
allBuffered.clear();
pendingUpload.clear();
}
@Override
public String toString() {
return "InstancedMeshPool{" + "byteSize=" + byteSize + ", meshCount=" + meshes.size() + '}';
}
public class BufferedMesh {
private final Mesh mesh;
private final int vertexCount;
private final int byteSize;
private final int ebo;
private long byteIndex;
private boolean deleted;
private final Set<GlVertexArray> boundTo = new HashSet<>();
private BufferedMesh(Mesh mesh, long byteIndex, EboCache eboCache) {
this.mesh = mesh;
vertexCount = mesh.vertexCount();
byteSize = vertexCount * InternalVertex.STRIDE;
this.byteIndex = byteIndex;
this.ebo = eboCache.get(mesh.indexSequence(), mesh.indexCount());
}
public int vertexCount() {
return vertexCount;
}
public int size() {
return byteSize;
}
public boolean isDeleted() {
return deleted;
}
public boolean isEmpty() {
return mesh.isEmpty() || isDeleted();
}
private void write(long ptr, VertexView vertexView) {
if (isEmpty()) {
return;
}
vertexView.ptr(ptr + byteIndex);
vertexView.vertexCount(vertexCount);
mesh.write(vertexView);
}
public void setup(GlVertexArray vao) {
if (boundTo.add(vao)) {
vao.bindVertexBuffer(0, InstancedMeshPool.this.vbo.handle(), byteIndex, InternalVertex.STRIDE);
vao.bindAttributes(0, 0, InternalVertex.ATTRIBUTES);
vao.setElementBuffer(ebo);
}
}
public void draw(int instanceCount) {
if (instanceCount > 1) {
GL32.glDrawElementsInstanced(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, 0, instanceCount);
} else {
GL32.glDrawElements(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, 0);
}
}
public void delete() {
deleted = true;
InstancedMeshPool.this.dirty = true;
InstancedMeshPool.this.anyToRemove = true;
}
}
}

View File

@ -94,8 +94,6 @@ public class InstancingEngine extends AbstractEngine {
var shader = entry.getKey(); var shader = entry.getKey();
var drawCalls = entry.getValue(); var drawCalls = entry.getValue();
drawCalls.removeIf(DrawCall::isInvalid);
if (drawCalls.isEmpty()) { if (drawCalls.isEmpty()) {
continue; continue;
} }

View File

@ -22,5 +22,4 @@ void main() {
uint modelIndex = drawCommands[drawIndex].modelIndex; uint modelIndex = drawCommands[drawIndex].modelIndex;
drawCommands[drawIndex].instanceCount = models[modelIndex].instanceCount; drawCommands[drawIndex].instanceCount = models[modelIndex].instanceCount;
drawCommands[drawIndex].baseInstance = models[modelIndex].baseInstance;
} }