mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-11-14 06:24:12 +01:00
Compare commits
No commits in common. "483eb199fa67bd4fa4fd297c2770d6e8d9e345e2" and "593784d6140558229a903ddb77a21a2943a2f6d6" have entirely different histories.
483eb199fa
...
593784d614
@ -9,11 +9,7 @@ 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 in the
|
* <p>Calling this method twice with the same arguments will return the same instancer.</p>
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
|
@ -13,6 +13,14 @@ 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.
|
||||||
|
@ -81,19 +81,19 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
|
|||||||
changed.set(handle.index);
|
changed.set(handle.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int instanceCount() {
|
public int getInstanceCount() {
|
||||||
return instances.size();
|
return instances.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyDirty(int index) {
|
public void notifyDirty(int index) {
|
||||||
if (index < 0 || index >= instanceCount()) {
|
if (index < 0 || index >= getInstanceCount()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
changed.set(index);
|
changed.set(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyRemoval(int index) {
|
public void notifyRemoval(int index) {
|
||||||
if (index < 0 || index >= instanceCount()) {
|
if (index < 0 || index >= getInstanceCount()) {
|
||||||
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[" + instanceCount() + ']';
|
return "AbstractInstancer[" + getInstanceCount() + ']';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,11 @@ 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 (checkAndWarnEmptyModel(key.model())) {
|
if (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));
|
||||||
@ -71,11 +75,7 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean checkAndWarnEmptyModel(Model model) {
|
private static void warnEmptyModel() {
|
||||||
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,7 +85,5 @@ 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,249 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -25,7 +25,6 @@ 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;
|
||||||
@ -44,7 +43,8 @@ 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 List<IndirectInstancer<?>> instancers = new ArrayList<>();
|
private final IndirectMeshPool meshPool;
|
||||||
|
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 needsDrawSort;
|
private boolean hasNewDraws;
|
||||||
private int instanceCountThisFrame;
|
private int instanceCountThisFrame;
|
||||||
|
|
||||||
IndirectCullingGroup(InstanceType<I> instanceType, Context context, IndirectPrograms programs) {
|
IndirectCullingGroup(InstanceType<I> instanceType, Context context, IndirectPrograms programs) {
|
||||||
@ -63,6 +63,7 @@ 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.
|
||||||
@ -71,40 +72,18 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||||||
drawProgram = programs.getIndirectProgram(instanceType, context.contextShader());
|
drawProgram = programs.getIndirectProgram(instanceType, context.contextShader());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flushInstancers() {
|
public void flush(StagingBuffer stagingBuffer) {
|
||||||
instanceCountThisFrame = 0;
|
needsDrawBarrier = true;
|
||||||
int modelIndex = 0;
|
instanceCountThisFrame = prepareModels();
|
||||||
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, instancers.size(), indirectDraws.size());
|
buffers.updateCounts(instanceCountThisFrame, indirectModels.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);
|
||||||
@ -112,14 +91,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 (needsDrawSort) {
|
if (hasNewDraws) {
|
||||||
sortDraws();
|
sortDraws();
|
||||||
needsDrawSort = false;
|
// Draws, however, only need to be updated when we get new ones.
|
||||||
}
|
// The instanceCount and baseInstance will be updated by the applyProgram,
|
||||||
|
// and all other fields are constant to the lifetime of the draw.
|
||||||
uploadDraws(stagingBuffer);
|
uploadDraws(stagingBuffer);
|
||||||
|
hasNewDraws = false;
|
||||||
needsDrawBarrier = true;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispatchCull() {
|
public void dispatchCull() {
|
||||||
@ -153,6 +132,18 @@ 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
|
||||||
@ -178,18 +169,20 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||||||
return multiDraws.containsKey(stage);
|
return multiDraws.containsKey(stage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(IndirectInstancer<I> instancer, Model model, RenderStage stage, MeshPool meshPool) {
|
public void add(IndirectInstancer<I> instancer, Model model, RenderStage stage) {
|
||||||
instancer.index = instancers.size();
|
int modelIndex = indirectModels.size();
|
||||||
instancers.add(instancer);
|
instancer.setModelIndex(modelIndex);
|
||||||
|
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()) {
|
||||||
MeshPool.BufferedMesh bufferedMesh = meshPool.alloc(entry.getValue());
|
IndirectMeshPool.BufferedMesh bufferedMesh = meshPool.alloc(entry.getValue());
|
||||||
var draw = new IndirectDraw(instancer, entry.getKey(), bufferedMesh, stage);
|
var draw = new IndirectDraw(indirectModel, entry.getKey(), bufferedMesh, stage);
|
||||||
indirectDraws.add(draw);
|
indirectDraws.add(draw);
|
||||||
instancer.addDraw(draw);
|
instancer.addDraw(draw);
|
||||||
}
|
}
|
||||||
|
|
||||||
needsDrawSort = true;
|
hasNewDraws = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void submit(RenderStage stage, TextureSourceImpl textures) {
|
public void submit(RenderStage stage, TextureSourceImpl textures) {
|
||||||
@ -197,7 +190,9 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Uniforms.bindForDraw();
|
||||||
drawProgram.bind();
|
drawProgram.bind();
|
||||||
|
meshPool.bindForDraw();
|
||||||
buffers.bindForDraw();
|
buffers.bindForDraw();
|
||||||
|
|
||||||
drawBarrier();
|
drawBarrier();
|
||||||
@ -220,6 +215,8 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||||||
|
|
||||||
program.bind();
|
program.bind();
|
||||||
|
|
||||||
|
Uniforms.bindForDraw();
|
||||||
|
meshPool.bindForDraw();
|
||||||
buffers.bindForCrumbling();
|
buffers.bindForCrumbling();
|
||||||
|
|
||||||
drawBarrier();
|
drawBarrier();
|
||||||
@ -239,8 +236,8 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||||||
|
|
||||||
private void uploadObjects(StagingBuffer stagingBuffer) {
|
private void uploadObjects(StagingBuffer stagingBuffer) {
|
||||||
long pos = 0;
|
long pos = 0;
|
||||||
for (var model : instancers) {
|
for (IndirectModel model : indirectModels) {
|
||||||
var instanceCount = model.instanceCount();
|
var instanceCount = model.instancer.getInstanceCount();
|
||||||
model.uploadObjects(stagingBuffer, pos, buffers.object.handle());
|
model.uploadObjects(stagingBuffer, pos, buffers.object.handle());
|
||||||
|
|
||||||
pos += instanceCount * objectStride;
|
pos += instanceCount * objectStride;
|
||||||
@ -248,7 +245,7 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void uploadModels(StagingBuffer stagingBuffer) {
|
private void uploadModels(StagingBuffer stagingBuffer) {
|
||||||
var totalSize = instancers.size() * IndirectBuffers.MODEL_STRIDE;
|
var totalSize = indirectModels.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);
|
||||||
@ -262,8 +259,8 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void writeModels(long writePtr) {
|
private void writeModels(long writePtr) {
|
||||||
for (var model : instancers) {
|
for (var model : indirectModels) {
|
||||||
model.writeModel(writePtr);
|
model.write(writePtr);
|
||||||
writePtr += IndirectBuffers.MODEL_STRIDE;
|
writePtr += IndirectBuffers.MODEL_STRIDE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,6 +274,7 @@ 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) {
|
||||||
|
@ -6,43 +6,35 @@ 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 IndirectInstancer<?> model;
|
private final IndirectModel model;
|
||||||
private final Material material;
|
private final Material material;
|
||||||
private final MeshPool.BufferedMesh mesh;
|
private final IndirectMeshPool.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(IndirectInstancer<?> model, Material material, MeshPool.BufferedMesh mesh, RenderStage stage) {
|
public IndirectDraw(IndirectModel model, Material material, IndirectMeshPool.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 MeshPool.BufferedMesh mesh() {
|
public IndirectMeshPool.BufferedMesh mesh() {
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,9 +47,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, model.baseInstance); // baseInstance
|
MemoryUtil.memPutInt(ptr + 16, 0); // baseInstance - to be set by the apply shader
|
||||||
|
|
||||||
MemoryUtil.memPutInt(ptr + 20, model.index); // modelIndex
|
MemoryUtil.memPutInt(ptr + 20, model.index); // modelIndex - never changes
|
||||||
|
|
||||||
MemoryUtil.memPutInt(ptr + 24, materialVertexIndex); // materialVertexIndex
|
MemoryUtil.memPutInt(ptr + 24, materialVertexIndex); // materialVertexIndex
|
||||||
MemoryUtil.memPutInt(ptr + 28, materialFragmentIndex); // materialFragmentIndex
|
MemoryUtil.memPutInt(ptr + 28, materialFragmentIndex); // materialFragmentIndex
|
||||||
@ -70,7 +62,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
|
||||||
|
|
||||||
@ -79,14 +71,4 @@ 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,9 @@ 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;
|
||||||
@ -43,8 +40,6 @@ 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();
|
||||||
@ -52,17 +47,11 @@ 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(), key.model());
|
return new IndirectInstancer<>(key.type(), key.context());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@ -70,7 +59,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(), meshPool);
|
group.add((IndirectInstancer<I>) instancer, key.model(), key.stage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasStage(RenderStage stage) {
|
public boolean hasStage(RenderStage stage) {
|
||||||
@ -85,9 +74,6 @@ 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);
|
||||||
}
|
}
|
||||||
@ -100,18 +86,10 @@ 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.upload(stagingBuffer);
|
group.flush(stagingBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
stagingBuffer.flush();
|
stagingBuffer.flush();
|
||||||
@ -135,8 +113,6 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
|
|||||||
|
|
||||||
stagingBuffer.delete();
|
stagingBuffer.delete();
|
||||||
|
|
||||||
meshPool.delete();
|
|
||||||
|
|
||||||
crumblingDrawBuffer.delete();
|
crumblingDrawBuffer.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,9 +126,6 @@ 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.
|
||||||
|
@ -3,34 +3,27 @@ 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 final Vector4fc boundingSphere;
|
private int modelIndex;
|
||||||
|
|
||||||
|
|
||||||
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, Model model) {
|
public IndirectInstancer(InstanceType<I> type, Context context) {
|
||||||
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) {
|
||||||
@ -45,16 +38,7 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
|
|||||||
removeDeletedInstances();
|
removeDeletedInstances();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeModel(long ptr) {
|
public void upload(StagingBuffer stagingBuffer, long startPos, int dstVbo) {
|
||||||
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)) {
|
if (shouldUploadAll(startPos)) {
|
||||||
uploadAll(stagingBuffer, startPos, dstVbo);
|
uploadAll(stagingBuffer, startPos, dstVbo);
|
||||||
} else {
|
} else {
|
||||||
@ -63,11 +47,10 @@ 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 || index != lastModelIndex;
|
return startPos != lastStartPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void uploadChanged(StagingBuffer stagingBuffer, long baseByte, int dstVbo) {
|
private void uploadChanged(StagingBuffer stagingBuffer, long baseByte, int dstVbo) {
|
||||||
@ -98,7 +81,11 @@ 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, index);
|
MemoryUtil.memPutInt(ptr, modelIndex);
|
||||||
writer.write(ptr + IndirectBuffers.INT_SIZE, instance);
|
writer.write(ptr + IndirectBuffers.INT_SIZE, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setModelIndex(int modelIndex) {
|
||||||
|
this.modelIndex = modelIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,183 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
@ -4,35 +4,39 @@ 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 MeshPool.BufferedMesh mesh;
|
private final InstancedMeshPool.BufferedMesh mesh;
|
||||||
|
|
||||||
private final GlVertexArray vao;
|
@Nullable
|
||||||
|
private GlVertexArray vao;
|
||||||
@Nullable
|
@Nullable
|
||||||
private GlVertexArray vaoScratch;
|
private GlVertexArray vaoScratch;
|
||||||
private boolean deleted;
|
|
||||||
|
|
||||||
public DrawCall(InstancedInstancer<?> instancer, MeshPool.BufferedMesh mesh, ShaderState shaderState) {
|
public DrawCall(InstancedInstancer<?> instancer, InstancedMeshPool.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 deleted() {
|
public boolean isInvalid() {
|
||||||
return deleted;
|
return instancer.isInvalid() || vao == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render() {
|
public void render() {
|
||||||
if (mesh.invalid()) {
|
if (isInvalid() || mesh.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
instancer.update();
|
||||||
|
|
||||||
|
int instanceCount = instancer.getInstanceCount();
|
||||||
|
if (instanceCount <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,15 +45,17 @@ public class DrawCall {
|
|||||||
|
|
||||||
vao.bindForDraw();
|
vao.bindForDraw();
|
||||||
|
|
||||||
mesh.draw(instancer.instanceCount());
|
mesh.draw(instanceCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void renderOne(InstanceHandleImpl impl) {
|
public void renderOne(InstanceHandleImpl impl) {
|
||||||
if (mesh.invalid()) {
|
if (isInvalid() || mesh.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int instanceCount = instancer.instanceCount();
|
instancer.update();
|
||||||
|
|
||||||
|
int instanceCount = instancer.getInstanceCount();
|
||||||
if (instanceCount <= 0 || impl.index >= instanceCount) {
|
if (instanceCount <= 0 || impl.index >= instanceCount) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -72,19 +78,14 @@ public class DrawCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void delete() {
|
public void delete() {
|
||||||
if (deleted) {
|
if (vao != null) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
vao.delete();
|
vao.delete();
|
||||||
|
vao = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (vaoScratch != null) {
|
if (vaoScratch != null) {
|
||||||
vaoScratch.delete();
|
vaoScratch.delete();
|
||||||
vaoScratch = null;
|
vaoScratch = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
mesh.drop();
|
|
||||||
|
|
||||||
deleted = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,11 @@ public class InstancedCrumbling {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (DrawCall draw : instancer.drawCalls()) {
|
List<DrawCall> draws = 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));
|
||||||
|
@ -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,7 +22,8 @@ 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 MeshPool meshPool = new MeshPool();
|
private final InstancedMeshPool meshPool = new InstancedMeshPool();
|
||||||
|
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);
|
||||||
@ -31,24 +32,6 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +46,12 @@ 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
|
||||||
@ -79,7 +68,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 = meshPool.alloc(entry.getValue());
|
var mesh = 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);
|
||||||
@ -122,10 +111,5 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
|
|||||||
.entrySet()
|
.entrySet()
|
||||||
.iterator();
|
.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void prune() {
|
|
||||||
drawCalls.values()
|
|
||||||
.removeIf(DrawCall::deleted);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,14 @@ 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;
|
||||||
@ -50,22 +58,28 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
int byteSize = instanceStride * instances.size();
|
|
||||||
if (vbo.ensureCapacity(byteSize)) {
|
|
||||||
// The vbo has moved, so we need to re-bind attributes
|
|
||||||
boundTo.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
try (MappedBuffer buf = vbo.map()) {
|
try (MappedBuffer buf = vbo.map()) {
|
||||||
writeChanged(buf.ptr());
|
long ptr = buf.ptr();
|
||||||
|
|
||||||
|
writeChanged(ptr);
|
||||||
|
|
||||||
changed.clear();
|
changed.clear();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -115,10 +129,6 @@ 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) {
|
||||||
|
@ -0,0 +1,205 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -94,6 +94,8 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -22,4 +22,5 @@ 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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user