Sparse instance updates

- Use MemoryBlock in IndirectBuffers
This commit is contained in:
Jozufozu 2022-08-16 23:43:23 -07:00
parent 3eb15fc84d
commit 2910e33626
6 changed files with 115 additions and 96 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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