mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-13 15:56:07 +01:00
Sparse instance updates
- Use MemoryBlock in IndirectBuffers
This commit is contained in:
parent
3eb15fc84d
commit
2910e33626
6 changed files with 115 additions and 96 deletions
|
@ -5,6 +5,8 @@ import static org.lwjgl.opengl.GL46.*;
|
|||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.system.Pointer;
|
||||
|
||||
import com.jozufozu.flywheel.backend.memory.MemoryBlock;
|
||||
|
||||
public class IndirectBuffers {
|
||||
public static final int BUFFER_COUNT = 4;
|
||||
public static final long INT_SIZE = Integer.BYTES;
|
||||
|
@ -30,7 +32,7 @@ public class IndirectBuffers {
|
|||
private static final long BATCH_SIZE_OFFSET = TARGET_SIZE_OFFSET + PTR_SIZE;
|
||||
private static final long DRAW_SIZE_OFFSET = BATCH_SIZE_OFFSET + PTR_SIZE;
|
||||
|
||||
final long buffers;
|
||||
final MemoryBlock buffers;
|
||||
final long objectStride;
|
||||
int object;
|
||||
int target;
|
||||
|
@ -46,21 +48,16 @@ public class IndirectBuffers {
|
|||
|
||||
IndirectBuffers(long objectStride) {
|
||||
this.objectStride = objectStride;
|
||||
this.buffers = MemoryUtil.nmemAlloc(BUFFERS_SIZE_BYTES);
|
||||
|
||||
if (this.buffers == MemoryUtil.NULL) {
|
||||
throw new OutOfMemoryError();
|
||||
}
|
||||
|
||||
MemoryUtil.memSet(this.buffers, 0, BUFFERS_SIZE_BYTES);
|
||||
this.buffers = MemoryBlock.calloc(BUFFERS_SIZE_BYTES, 1);
|
||||
}
|
||||
|
||||
void createBuffers() {
|
||||
nglCreateBuffers(4, buffers);
|
||||
object = MemoryUtil.memGetInt(buffers);
|
||||
target = MemoryUtil.memGetInt(buffers + 4);
|
||||
batch = MemoryUtil.memGetInt(buffers + 8);
|
||||
draw = MemoryUtil.memGetInt(buffers + 12);
|
||||
final long ptr = buffers.ptr();
|
||||
nglCreateBuffers(4, ptr);
|
||||
object = MemoryUtil.memGetInt(ptr);
|
||||
target = MemoryUtil.memGetInt(ptr + 4);
|
||||
batch = MemoryUtil.memGetInt(ptr + 8);
|
||||
draw = MemoryUtil.memGetInt(ptr + 12);
|
||||
}
|
||||
|
||||
void updateCounts(int objectCount, int drawCount) {
|
||||
|
@ -72,14 +69,15 @@ public class IndirectBuffers {
|
|||
createDrawStorage(drawCount);
|
||||
}
|
||||
|
||||
long objectSize = objectStride * objectCount;
|
||||
long targetSize = INT_SIZE * objectCount;
|
||||
long drawSize = DRAW_COMMAND_STRIDE * drawCount;
|
||||
final long objectSize = objectStride * objectCount;
|
||||
final long targetSize = INT_SIZE * objectCount;
|
||||
final long drawSize = DRAW_COMMAND_STRIDE * drawCount;
|
||||
|
||||
MemoryUtil.memPutAddress(buffers + OBJECT_SIZE_OFFSET, objectSize);
|
||||
MemoryUtil.memPutAddress(buffers + TARGET_SIZE_OFFSET, targetSize);
|
||||
MemoryUtil.memPutAddress(buffers + BATCH_SIZE_OFFSET, targetSize);
|
||||
MemoryUtil.memPutAddress(buffers + DRAW_SIZE_OFFSET, drawSize);
|
||||
final long ptr = buffers.ptr();
|
||||
MemoryUtil.memPutAddress(ptr + OBJECT_SIZE_OFFSET, objectSize);
|
||||
MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, targetSize);
|
||||
MemoryUtil.memPutAddress(ptr + BATCH_SIZE_OFFSET, targetSize);
|
||||
MemoryUtil.memPutAddress(ptr + DRAW_SIZE_OFFSET, drawSize);
|
||||
}
|
||||
|
||||
void createObjectStorage(int objectCount) {
|
||||
|
@ -112,7 +110,12 @@ public class IndirectBuffers {
|
|||
}
|
||||
|
||||
private void bindN(int bufferCount) {
|
||||
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, bufferCount, buffers, buffers + OFFSET_OFFSET, buffers + SIZE_OFFSET);
|
||||
if (bufferCount > BUFFER_COUNT) {
|
||||
throw new IllegalArgumentException("Can't bind more than " + BUFFER_COUNT + " buffers");
|
||||
}
|
||||
|
||||
final long ptr = buffers.ptr();
|
||||
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, bufferCount, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET);
|
||||
}
|
||||
|
||||
void bindIndirectBuffer() {
|
||||
|
@ -131,4 +134,9 @@ public class IndirectBuffers {
|
|||
nglNamedBufferSubData(draw, 0, length, drawPtr);
|
||||
// glFlushMappedNamedBufferRange(this.draw, 0, length);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
nglDeleteBuffers(BUFFER_COUNT, buffers.ptr());
|
||||
buffers.free();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,18 +10,13 @@ import org.lwjgl.opengl.GL32;
|
|||
|
||||
import com.jozufozu.flywheel.api.RenderStage;
|
||||
import com.jozufozu.flywheel.api.instancer.InstancedPart;
|
||||
import com.jozufozu.flywheel.api.material.Material;
|
||||
import com.jozufozu.flywheel.api.struct.StructType;
|
||||
import com.jozufozu.flywheel.api.vertex.VertexType;
|
||||
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
|
||||
import com.jozufozu.flywheel.backend.instancing.Engine;
|
||||
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
|
||||
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
|
||||
import com.jozufozu.flywheel.core.RenderContext;
|
||||
import com.jozufozu.flywheel.api.context.ContextShader;
|
||||
import com.jozufozu.flywheel.backend.instancing.PipelineCompiler;
|
||||
import com.jozufozu.flywheel.core.source.FileResolution;
|
||||
import com.jozufozu.flywheel.core.uniform.UniformBuffer;
|
||||
import com.jozufozu.flywheel.util.WeakHashSet;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
|
||||
|
@ -42,7 +37,7 @@ public class IndirectEngine implements Engine {
|
|||
|
||||
protected final Map<StructType<?>, IndirectFactory<?>> factories = new HashMap<>();
|
||||
|
||||
protected final List<InstancedModel<?>> uninitializedModels = new ArrayList<>();
|
||||
protected final List<IndirectModel<?>> uninitializedModels = new ArrayList<>();
|
||||
protected final RenderLists renderLists = new RenderLists();
|
||||
|
||||
/**
|
||||
|
@ -102,6 +97,8 @@ public class IndirectEngine implements Engine {
|
|||
factories.values()
|
||||
.forEach(IndirectFactory::delete);
|
||||
|
||||
renderLists.lists.values().forEach(IndirectList::delete);
|
||||
|
||||
factories.clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,11 +13,11 @@ import com.jozufozu.flywheel.core.model.Model;
|
|||
|
||||
public class IndirectFactory<D extends InstancedPart> implements InstancerFactory<D> {
|
||||
|
||||
protected final Map<Model, InstancedModel<D>> models = new HashMap<>();
|
||||
protected final Map<Model, IndirectModel<D>> models = new HashMap<>();
|
||||
protected final StructType<D> type;
|
||||
private final Consumer<InstancedModel<D>> creationListener;
|
||||
private final Consumer<IndirectModel<D>> creationListener;
|
||||
|
||||
public IndirectFactory(StructType<D> type, Consumer<InstancedModel<D>> creationListener) {
|
||||
public IndirectFactory(StructType<D> type, Consumer<IndirectModel<D>> creationListener) {
|
||||
this.type = type;
|
||||
this.creationListener = creationListener;
|
||||
}
|
||||
|
@ -27,23 +27,8 @@ public class IndirectFactory<D extends InstancedPart> implements InstancerFactor
|
|||
return models.computeIfAbsent(modelKey, this::createInstancer).getInstancer();
|
||||
}
|
||||
|
||||
public int getInstanceCount() {
|
||||
return models.values()
|
||||
.stream()
|
||||
.map(InstancedModel::getInstancer)
|
||||
.mapToInt(AbstractInstancer::getInstanceCount)
|
||||
.sum();
|
||||
}
|
||||
|
||||
public int getVertexCount() {
|
||||
return models.values()
|
||||
.stream()
|
||||
.mapToInt(InstancedModel::getVertexCount)
|
||||
.sum();
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
models.values().forEach(InstancedModel::delete);
|
||||
models.values().forEach(IndirectModel::delete);
|
||||
models.clear();
|
||||
}
|
||||
|
||||
|
@ -53,21 +38,13 @@ public class IndirectFactory<D extends InstancedPart> implements InstancerFactor
|
|||
public void clear() {
|
||||
models.values()
|
||||
.stream()
|
||||
.map(InstancedModel::getInstancer)
|
||||
.map(IndirectModel::getInstancer)
|
||||
.forEach(AbstractInstancer::clear);
|
||||
}
|
||||
|
||||
private InstancedModel<D> createInstancer(Model model) {
|
||||
var instancer = new InstancedModel<>(type, model);
|
||||
private IndirectModel<D> createInstancer(Model model) {
|
||||
var instancer = new IndirectModel<>(type, model);
|
||||
this.creationListener.accept(instancer);
|
||||
return instancer;
|
||||
}
|
||||
|
||||
// private void bindInstanceAttributes(GlVertexArray vao) {
|
||||
// vao.bindAttributes(this.vbo, this.attributeBaseIndex, this.instanceFormat, 0L);
|
||||
//
|
||||
// for (int i = 0; i < this.instanceFormat.getAttributeCount(); i++) {
|
||||
// vao.setAttributeDivisor(this.attributeBaseIndex + i, 1);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package com.jozufozu.flywheel.backend.instancing.indirect;
|
|||
|
||||
import com.jozufozu.flywheel.api.instancer.InstancedPart;
|
||||
import com.jozufozu.flywheel.api.struct.StructType;
|
||||
import com.jozufozu.flywheel.api.struct.StructWriter;
|
||||
import com.jozufozu.flywheel.backend.instancing.AbstractInstancer;
|
||||
import com.jozufozu.flywheel.core.layout.BufferLayout;
|
||||
|
||||
|
@ -10,12 +9,12 @@ public class IndirectInstancer<D extends InstancedPart> extends AbstractInstance
|
|||
|
||||
public final BufferLayout instanceFormat;
|
||||
public final StructType<D> structType;
|
||||
public final InstancedModel<D> parent;
|
||||
public final IndirectModel<D> parent;
|
||||
int instanceCount = 0;
|
||||
|
||||
boolean anyToUpdate;
|
||||
|
||||
public IndirectInstancer(InstancedModel<D> parent, StructType<D> type) {
|
||||
public IndirectInstancer(IndirectModel<D> parent, StructType<D> type) {
|
||||
super(type);
|
||||
this.parent = parent;
|
||||
this.instanceFormat = type.getLayout();
|
||||
|
|
|
@ -5,7 +5,6 @@ import static org.lwjgl.opengl.GL46.*;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import com.jozufozu.flywheel.api.RenderStage;
|
||||
|
@ -27,7 +26,6 @@ public class IndirectList<T extends InstancedPart> {
|
|||
final StorageBufferWriter<T> storageBufferWriter;
|
||||
final GlProgram compute;
|
||||
final GlProgram draw;
|
||||
private final StructType<T> structType;
|
||||
private final VertexType vertexType;
|
||||
private final long objectStride;
|
||||
|
||||
|
@ -38,12 +36,11 @@ public class IndirectList<T extends InstancedPart> {
|
|||
|
||||
int vertexArray;
|
||||
|
||||
final List<Batch<T>> batches = new ArrayList<>();
|
||||
final List<Batch> batches = new ArrayList<>();
|
||||
|
||||
IndirectList(StructType<T> structType, VertexType vertexType) {
|
||||
this.structType = structType;
|
||||
this.vertexType = vertexType;
|
||||
storageBufferWriter = this.structType.getStorageBufferWriter();
|
||||
storageBufferWriter = structType.getStorageBufferWriter();
|
||||
|
||||
objectStride = storageBufferWriter.getAlignment();
|
||||
buffers = new IndirectBuffers(objectStride);
|
||||
|
@ -59,7 +56,7 @@ public class IndirectList<T extends InstancedPart> {
|
|||
.quads2Tris(2048).buffer.handle();
|
||||
setupVertexArray();
|
||||
|
||||
var indirectShader = this.structType.getIndirectShader();
|
||||
var indirectShader = structType.getIndirectShader();
|
||||
compute = ComputeCullerCompiler.INSTANCE.get(indirectShader);
|
||||
draw = PipelineCompiler.INSTANCE.get(new PipelineCompiler.Context(vertexType, Materials.CHEST, indirectShader, Components.WORLD, Components.INDIRECT));
|
||||
}
|
||||
|
@ -84,14 +81,14 @@ public class IndirectList<T extends InstancedPart> {
|
|||
}
|
||||
|
||||
public void add(IndirectInstancer<T> instancer, Material material, Mesh mesh) {
|
||||
batches.add(new Batch<>(instancer, material, meshPool.alloc(mesh)));
|
||||
batches.add(new Batch(instancer, material, meshPool.alloc(mesh)));
|
||||
}
|
||||
|
||||
void submit(RenderStage stage) {
|
||||
if (batches.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int instanceCountThisFrame = calculateTotalInstanceCount();
|
||||
int instanceCountThisFrame = calculateTotalInstanceCountAndPrepareBatches();
|
||||
|
||||
if (instanceCountThisFrame == 0) {
|
||||
return;
|
||||
|
@ -119,7 +116,7 @@ public class IndirectList<T extends InstancedPart> {
|
|||
|
||||
final int stride = (int) IndirectBuffers.DRAW_COMMAND_STRIDE;
|
||||
long offset = 0;
|
||||
for (Batch<T> batch : batches) {
|
||||
for (var batch : batches) {
|
||||
|
||||
batch.material.setup();
|
||||
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, offset, 1, stride);
|
||||
|
@ -139,22 +136,14 @@ public class IndirectList<T extends InstancedPart> {
|
|||
private void uploadInstanceData() {
|
||||
long objectPtr = buffers.objectPtr;
|
||||
long batchIDPtr = buffers.batchPtr;
|
||||
int baseInstance = 0;
|
||||
int batchID = 0;
|
||||
for (var batch : batches) {
|
||||
batch.baseInstance = baseInstance;
|
||||
var instancer = batch.instancer;
|
||||
for (T t : instancer.getAll()) {
|
||||
// write object
|
||||
storageBufferWriter.write(objectPtr, t);
|
||||
objectPtr += objectStride;
|
||||
|
||||
// write batchID
|
||||
MemoryUtil.memPutInt(batchIDPtr, batchID);
|
||||
batchIDPtr += IndirectBuffers.INT_SIZE;
|
||||
}
|
||||
baseInstance += batch.instancer.instanceCount;
|
||||
batchID++;
|
||||
for (int i = 0, batchesSize = batches.size(); i < batchesSize; i++) {
|
||||
var batch = batches.get(i);
|
||||
var instanceCount = batch.instancer.getInstanceCount();
|
||||
batch.write(objectPtr, batchIDPtr, i);
|
||||
|
||||
objectPtr += instanceCount * objectStride;
|
||||
batchIDPtr += instanceCount * IndirectBuffers.INT_SIZE;
|
||||
}
|
||||
|
||||
buffers.flushObjects(objectPtr - buffers.objectPtr);
|
||||
|
@ -163,34 +152,85 @@ public class IndirectList<T extends InstancedPart> {
|
|||
|
||||
private void uploadIndirectCommands() {
|
||||
long writePtr = buffers.drawPtr;
|
||||
for (Batch<T> batch : batches) {
|
||||
for (var batch : batches) {
|
||||
batch.writeIndirectCommand(writePtr);
|
||||
writePtr += IndirectBuffers.DRAW_COMMAND_STRIDE;
|
||||
}
|
||||
buffers.flushDrawCommands(writePtr - buffers.drawPtr);
|
||||
}
|
||||
|
||||
private int calculateTotalInstanceCount() {
|
||||
int total = 0;
|
||||
for (Batch<T> batch : batches) {
|
||||
batch.instancer.update();
|
||||
total += batch.instancer.instanceCount;
|
||||
private int calculateTotalInstanceCountAndPrepareBatches() {
|
||||
int baseInstance = 0;
|
||||
for (var batch : batches) {
|
||||
batch.prepare(baseInstance);
|
||||
baseInstance += batch.instancer.instanceCount;
|
||||
}
|
||||
return total;
|
||||
return baseInstance;
|
||||
}
|
||||
|
||||
private static final class Batch<T extends InstancedPart> {
|
||||
public void delete() {
|
||||
glDeleteVertexArrays(vertexArray);
|
||||
buffers.delete();
|
||||
meshPool.delete();
|
||||
}
|
||||
|
||||
private final class Batch {
|
||||
final IndirectInstancer<T> instancer;
|
||||
final IndirectMeshPool.BufferedMesh mesh;
|
||||
final Material material;
|
||||
int baseInstance = -1;
|
||||
|
||||
boolean needsFullWrite = true;
|
||||
|
||||
private Batch(IndirectInstancer<T> instancer, Material material, IndirectMeshPool.BufferedMesh mesh) {
|
||||
this.instancer = instancer;
|
||||
this.material = material;
|
||||
this.mesh = mesh;
|
||||
}
|
||||
|
||||
public void prepare(int baseInstance) {
|
||||
instancer.update();
|
||||
if (baseInstance == this.baseInstance) {
|
||||
needsFullWrite = false;
|
||||
return;
|
||||
}
|
||||
this.baseInstance = baseInstance;
|
||||
needsFullWrite = true;
|
||||
}
|
||||
|
||||
private void write(long objectPtr, long batchIDPtr, int batchID) {
|
||||
if (needsFullWrite) {
|
||||
writeFull(objectPtr, batchIDPtr, batchID);
|
||||
} else if (instancer.anyToUpdate) {
|
||||
writeSparse(objectPtr, batchIDPtr, batchID);
|
||||
}
|
||||
instancer.anyToUpdate = false;
|
||||
}
|
||||
|
||||
private void writeSparse(long objectPtr, long batchIDPtr, int batchID) {
|
||||
var all = instancer.getAll();
|
||||
for (int i = 0; i < all.size(); i++) {
|
||||
final var element = all.get(i);
|
||||
if (element.checkDirtyAndClear()) {
|
||||
storageBufferWriter.write(objectPtr + i * objectStride, element);
|
||||
|
||||
MemoryUtil.memPutInt(batchIDPtr + i * IndirectBuffers.INT_SIZE, batchID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeFull(long objectPtr, long batchIDPtr, int batchID) {
|
||||
for (var object : this.instancer.getAll()) {
|
||||
// write object
|
||||
storageBufferWriter.write(objectPtr, object);
|
||||
objectPtr += objectStride;
|
||||
|
||||
// write batchID
|
||||
MemoryUtil.memPutInt(batchIDPtr, batchID);
|
||||
batchIDPtr += IndirectBuffers.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
public void writeIndirectCommand(long ptr) {
|
||||
var boundingSphere = mesh.mesh.getBoundingSphere();
|
||||
|
||||
|
|
|
@ -4,16 +4,14 @@ import com.jozufozu.flywheel.api.instancer.InstancedPart;
|
|||
import com.jozufozu.flywheel.api.struct.StructType;
|
||||
import com.jozufozu.flywheel.core.model.Model;
|
||||
|
||||
public class InstancedModel<D extends InstancedPart> {
|
||||
public class IndirectModel<D extends InstancedPart> {
|
||||
|
||||
private final Model model;
|
||||
private final StructType<D> type;
|
||||
private final IndirectInstancer<D> instancer;
|
||||
|
||||
public InstancedModel(StructType<D> type, Model model) {
|
||||
public IndirectModel(StructType<D> type, Model model) {
|
||||
this.model = model;
|
||||
this.instancer = new IndirectInstancer<>(this, type);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void init(RenderLists renderLists) {
|
Loading…
Reference in a new issue