mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-11-10 12:34:11 +01:00
Indirectly packed
- Use glsl structs to pack vec/mat data in ssbos - Support RenderStages - Dynamically sized draw storage
This commit is contained in:
parent
ebef176089
commit
f84b169bd7
@ -4,7 +4,7 @@ root = true
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
continuation_indent_size = 8
|
||||
ij_continuation_indent_size = 8
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
@ -16,6 +16,10 @@ indent_size = 2
|
||||
|
||||
[*.java]
|
||||
indent_style = tab
|
||||
ij_java_blank_lines_before_class_end = 0
|
||||
ij_java_blank_lines_after_anonymous_class_header = 0
|
||||
ij_java_blank_lines_after_class_header = 0
|
||||
ij_java_blank_lines_before_method_body = 0
|
||||
ij_java_else_on_new_line = false
|
||||
ij_continuation_indent_size = 8
|
||||
ij_java_class_count_to_use_import_on_demand = 99
|
||||
|
@ -1,6 +1,22 @@
|
||||
package com.jozufozu.flywheel.backend.instancing.indirect;
|
||||
|
||||
import static org.lwjgl.opengl.GL46.*;
|
||||
import static org.lwjgl.opengl.GL46.GL_DRAW_INDIRECT_BUFFER;
|
||||
import static org.lwjgl.opengl.GL46.GL_DYNAMIC_STORAGE_BIT;
|
||||
import static org.lwjgl.opengl.GL46.GL_MAP_FLUSH_EXPLICIT_BIT;
|
||||
import static org.lwjgl.opengl.GL46.GL_MAP_PERSISTENT_BIT;
|
||||
import static org.lwjgl.opengl.GL46.GL_MAP_WRITE_BIT;
|
||||
import static org.lwjgl.opengl.GL46.GL_SHADER_STORAGE_BUFFER;
|
||||
import static org.lwjgl.opengl.GL46.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL46.glCopyNamedBufferSubData;
|
||||
import static org.lwjgl.opengl.GL46.glDeleteBuffers;
|
||||
import static org.lwjgl.opengl.GL46.glFlushMappedNamedBufferRange;
|
||||
import static org.lwjgl.opengl.GL46.glGenBuffers;
|
||||
import static org.lwjgl.opengl.GL46.glNamedBufferStorage;
|
||||
import static org.lwjgl.opengl.GL46.nglBindBuffersRange;
|
||||
import static org.lwjgl.opengl.GL46.nglCreateBuffers;
|
||||
import static org.lwjgl.opengl.GL46.nglDeleteBuffers;
|
||||
import static org.lwjgl.opengl.GL46.nglMapNamedBufferRange;
|
||||
import static org.lwjgl.opengl.GL46.nglNamedBufferSubData;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.system.Pointer;
|
||||
@ -135,10 +151,23 @@ public class IndirectBuffers {
|
||||
|
||||
void createDrawStorage(int drawCount) {
|
||||
freeDrawStorage();
|
||||
|
||||
var drawSize = DRAW_COMMAND_STRIDE * drawCount;
|
||||
glNamedBufferStorage(draw, drawSize, SUB_DATA_BITS);
|
||||
drawPtr = MemoryUtil.nmemAlloc(drawSize);
|
||||
// drawPtr = nglMapNamedBufferRange(draw, 0, drawSize, MAP_BITS);
|
||||
if (maxDrawCount > 0) {
|
||||
int drawNew = glGenBuffers();
|
||||
|
||||
glNamedBufferStorage(drawNew, drawSize, SUB_DATA_BITS);
|
||||
|
||||
glDeleteBuffers(draw);
|
||||
|
||||
MemoryUtil.memPutInt(buffers.ptr() + INT_SIZE * 3, drawNew);
|
||||
draw = drawNew;
|
||||
drawPtr = MemoryUtil.nmemRealloc(drawPtr, drawSize);
|
||||
} else {
|
||||
|
||||
glNamedBufferStorage(draw, drawSize, SUB_DATA_BITS);
|
||||
drawPtr = MemoryUtil.nmemAlloc(drawSize);
|
||||
}
|
||||
maxDrawCount = drawCount;
|
||||
FlwMemoryTracker._allocGPUMemory(maxDrawCount * DRAW_COMMAND_STRIDE);
|
||||
}
|
||||
|
@ -0,0 +1,194 @@
|
||||
package com.jozufozu.flywheel.backend.instancing.indirect;
|
||||
|
||||
import static org.lwjgl.opengl.GL42.GL_COMMAND_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
|
||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.GL46.glBindVertexArray;
|
||||
import static org.lwjgl.opengl.GL46.glCreateVertexArrays;
|
||||
import static org.lwjgl.opengl.GL46.glDeleteVertexArrays;
|
||||
import static org.lwjgl.opengl.GL46.glDispatchCompute;
|
||||
import static org.lwjgl.opengl.GL46.glEnableVertexArrayAttrib;
|
||||
import static org.lwjgl.opengl.GL46.glVertexArrayElementBuffer;
|
||||
import static org.lwjgl.opengl.GL46.glVertexArrayVertexBuffer;
|
||||
|
||||
import com.jozufozu.flywheel.api.RenderStage;
|
||||
import com.jozufozu.flywheel.api.instancer.InstancedPart;
|
||||
import com.jozufozu.flywheel.api.struct.StorageBufferWriter;
|
||||
import com.jozufozu.flywheel.api.struct.StructType;
|
||||
import com.jozufozu.flywheel.api.vertex.VertexType;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
import com.jozufozu.flywheel.backend.instancing.PipelineCompiler;
|
||||
import com.jozufozu.flywheel.core.Components;
|
||||
import com.jozufozu.flywheel.core.Materials;
|
||||
import com.jozufozu.flywheel.core.QuadConverter;
|
||||
import com.jozufozu.flywheel.core.uniform.UniformBuffer;
|
||||
|
||||
public class IndirectCullingGroup<T extends InstancedPart> {
|
||||
|
||||
private static final int BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT;
|
||||
|
||||
final StorageBufferWriter<T> storageBufferWriter;
|
||||
final GlProgram compute;
|
||||
final GlProgram draw;
|
||||
private final VertexType vertexType;
|
||||
private final long objectStride;
|
||||
|
||||
final IndirectBuffers buffers;
|
||||
|
||||
final IndirectMeshPool meshPool;
|
||||
private final int elementBuffer;
|
||||
|
||||
int vertexArray;
|
||||
|
||||
final IndirectDrawSet<T> drawSet = new IndirectDrawSet<>();
|
||||
|
||||
private boolean hasCulledThisFrame;
|
||||
private boolean needsMemoryBarrier;
|
||||
private int instanceCountThisFrame;
|
||||
|
||||
IndirectCullingGroup(StructType<T> structType, VertexType vertexType) {
|
||||
this.vertexType = vertexType;
|
||||
storageBufferWriter = structType.getStorageBufferWriter();
|
||||
|
||||
objectStride = storageBufferWriter.getAlignment();
|
||||
buffers = new IndirectBuffers(objectStride);
|
||||
buffers.createBuffers();
|
||||
buffers.createObjectStorage(128);
|
||||
buffers.createDrawStorage(2);
|
||||
|
||||
meshPool = new IndirectMeshPool(vertexType, 1024);
|
||||
|
||||
vertexArray = glCreateVertexArrays();
|
||||
|
||||
elementBuffer = QuadConverter.getInstance()
|
||||
.quads2Tris(2048).buffer.handle();
|
||||
setupVertexArray();
|
||||
|
||||
var indirectShader = structType.getIndirectShader();
|
||||
compute = ComputeCullerCompiler.INSTANCE.get(indirectShader);
|
||||
draw = PipelineCompiler.INSTANCE.get(new PipelineCompiler.Context(vertexType, Materials.CHEST, indirectShader, Components.WORLD, Components.INDIRECT));
|
||||
}
|
||||
|
||||
private void setupVertexArray() {
|
||||
glVertexArrayElementBuffer(vertexArray, elementBuffer);
|
||||
|
||||
var meshLayout = vertexType.getLayout();
|
||||
var meshAttribs = meshLayout.getAttributeCount();
|
||||
|
||||
var attributes = meshLayout.getAttributes();
|
||||
|
||||
long offset = 0;
|
||||
for (int i = 0; i < meshAttribs; i++) {
|
||||
var attribute = attributes.get(i);
|
||||
glEnableVertexArrayAttrib(vertexArray, i);
|
||||
glVertexArrayVertexBuffer(vertexArray, i, meshPool.vbo, offset, meshLayout.getStride());
|
||||
attribute.format(vertexArray, i);
|
||||
offset += attribute.getByteWidth();
|
||||
}
|
||||
}
|
||||
|
||||
void beginFrame() {
|
||||
hasCulledThisFrame = false;
|
||||
needsMemoryBarrier = true;
|
||||
instanceCountThisFrame = calculateTotalInstanceCountAndPrepareBatches();
|
||||
}
|
||||
|
||||
void submit(RenderStage stage) {
|
||||
if (drawSet.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (instanceCountThisFrame == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
cull();
|
||||
dispatchDraw(stage);
|
||||
}
|
||||
|
||||
private void cull() {
|
||||
if (hasCulledThisFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
buffers.updateCounts(instanceCountThisFrame, drawSet.size());
|
||||
meshPool.uploadAll();
|
||||
uploadInstanceData();
|
||||
uploadIndirectCommands();
|
||||
|
||||
UniformBuffer.getInstance()
|
||||
.sync();
|
||||
|
||||
compute.bind();
|
||||
buffers.bindAll();
|
||||
|
||||
var groupCount = (instanceCountThisFrame + 31) >> 5; // ceil(instanceCount / 32)
|
||||
glDispatchCompute(groupCount, 1, 1);
|
||||
hasCulledThisFrame = true;
|
||||
}
|
||||
|
||||
private void dispatchDraw(RenderStage stage) {
|
||||
if (!drawSet.contains(stage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
draw.bind();
|
||||
glBindVertexArray(vertexArray);
|
||||
buffers.bindObjectAndTarget();
|
||||
buffers.bindIndirectBuffer();
|
||||
|
||||
memoryBarrier();
|
||||
|
||||
drawSet.submit(stage);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
private void memoryBarrier() {
|
||||
if (needsMemoryBarrier) {
|
||||
glMemoryBarrier(BARRIER_BITS);
|
||||
needsMemoryBarrier = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadInstanceData() {
|
||||
long objectPtr = buffers.objectPtr;
|
||||
long batchIDPtr = buffers.batchPtr;
|
||||
|
||||
for (int i = 0, batchesSize = drawSet.indirectDraws.size(); i < batchesSize; i++) {
|
||||
var batch = drawSet.indirectDraws.get(i);
|
||||
var instanceCount = batch.instancer.getInstanceCount();
|
||||
batch.writeObjects(objectPtr, batchIDPtr, i);
|
||||
|
||||
objectPtr += instanceCount * objectStride;
|
||||
batchIDPtr += instanceCount * IndirectBuffers.INT_SIZE;
|
||||
}
|
||||
|
||||
buffers.flushObjects(objectPtr - buffers.objectPtr);
|
||||
buffers.flushBatchIDs(batchIDPtr - buffers.batchPtr);
|
||||
}
|
||||
|
||||
private void uploadIndirectCommands() {
|
||||
long writePtr = buffers.drawPtr;
|
||||
for (var batch : drawSet.indirectDraws) {
|
||||
batch.writeIndirectCommand(writePtr);
|
||||
writePtr += IndirectBuffers.DRAW_COMMAND_STRIDE;
|
||||
}
|
||||
buffers.flushDrawCommands(writePtr - buffers.drawPtr);
|
||||
}
|
||||
|
||||
private int calculateTotalInstanceCountAndPrepareBatches() {
|
||||
int baseInstance = 0;
|
||||
for (var batch : drawSet.indirectDraws) {
|
||||
batch.prepare(baseInstance);
|
||||
baseInstance += batch.instancer.instanceCount;
|
||||
}
|
||||
return baseInstance;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
glDeleteVertexArrays(vertexArray);
|
||||
buffers.delete();
|
||||
meshPool.delete();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.jozufozu.flywheel.backend.instancing.indirect;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import com.jozufozu.flywheel.api.instancer.InstancedPart;
|
||||
import com.jozufozu.flywheel.api.material.Material;
|
||||
|
||||
public final class IndirectDraw<T extends InstancedPart> {
|
||||
final IndirectInstancer<T> instancer;
|
||||
final IndirectMeshPool.BufferedMesh mesh;
|
||||
final Material material;
|
||||
int baseInstance = -1;
|
||||
|
||||
boolean needsFullWrite = true;
|
||||
|
||||
IndirectDraw(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;
|
||||
}
|
||||
|
||||
void writeObjects(long objectPtr, long batchIDPtr, int batchID) {
|
||||
if (needsFullWrite) {
|
||||
instancer.writeFull(objectPtr, batchIDPtr, batchID);
|
||||
} else if (instancer.anyToUpdate) {
|
||||
instancer.writeSparse(objectPtr, batchIDPtr, batchID);
|
||||
}
|
||||
instancer.anyToUpdate = false;
|
||||
}
|
||||
|
||||
public void writeIndirectCommand(long ptr) {
|
||||
var boundingSphere = mesh.mesh.getBoundingSphere();
|
||||
|
||||
MemoryUtil.memPutInt(ptr, mesh.getIndexCount()); // count
|
||||
MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be incremented by the compute shader
|
||||
MemoryUtil.memPutInt(ptr + 8, 0); // firstIndex - all models share the same index buffer
|
||||
MemoryUtil.memPutInt(ptr + 12, mesh.getBaseVertex()); // baseVertex
|
||||
MemoryUtil.memPutInt(ptr + 16, baseInstance); // baseInstance
|
||||
|
||||
boundingSphere.getToAddress(ptr + 20); // boundingSphere
|
||||
}
|
||||
}
|
@ -10,15 +10,14 @@ import com.jozufozu.flywheel.api.vertex.VertexType;
|
||||
import com.jozufozu.flywheel.core.model.Mesh;
|
||||
import com.jozufozu.flywheel.util.Pair;
|
||||
|
||||
public class RenderLists {
|
||||
public class IndirectDrawManager {
|
||||
|
||||
public final Map<Pair<StructType<?>, VertexType>, IndirectList<?>> lists = new HashMap<>();
|
||||
public final Map<Pair<StructType<?>, VertexType>, IndirectCullingGroup<?>> lists = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <D extends InstancedPart> void add(IndirectInstancer<D> instancer, Material material, Mesh mesh) {
|
||||
var indirectList = (IndirectList<D>) lists.computeIfAbsent(Pair.of(instancer.structType, mesh.getVertexType()),
|
||||
p -> new IndirectList<>(p.first(), p.second()));
|
||||
var indirectList = (IndirectCullingGroup<D>) lists.computeIfAbsent(Pair.of(instancer.type, mesh.getVertexType()), p -> new IndirectCullingGroup<>(p.first(), p.second()));
|
||||
|
||||
indirectList.add(instancer, material, mesh);
|
||||
indirectList.drawSet.add(instancer, material, indirectList.meshPool.alloc(mesh));
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.jozufozu.flywheel.backend.instancing.indirect;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
|
||||
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
|
||||
import static org.lwjgl.opengl.GL43.glMultiDrawElementsIndirect;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.jozufozu.flywheel.api.RenderStage;
|
||||
import com.jozufozu.flywheel.api.instancer.InstancedPart;
|
||||
import com.jozufozu.flywheel.api.material.Material;
|
||||
|
||||
public class IndirectDrawSet<T extends InstancedPart> {
|
||||
|
||||
final List<IndirectDraw<T>> indirectDraws = new ArrayList<>();
|
||||
|
||||
public boolean isEmpty() {
|
||||
return indirectDraws.isEmpty();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return indirectDraws.size();
|
||||
}
|
||||
|
||||
public void add(IndirectInstancer<T> instancer, Material material, IndirectMeshPool.BufferedMesh bufferedMesh) {
|
||||
indirectDraws.add(new IndirectDraw<>(instancer, material, bufferedMesh));
|
||||
}
|
||||
|
||||
public void submit(RenderStage stage) {
|
||||
final int stride = (int) IndirectBuffers.DRAW_COMMAND_STRIDE;
|
||||
for (int i = 0, indirectDrawsSize = indirectDraws.size(); i < indirectDrawsSize; i++) {
|
||||
var batch = indirectDraws.get(i);
|
||||
var material = batch.material;
|
||||
|
||||
if (material.getRenderStage() != stage) {
|
||||
continue;
|
||||
}
|
||||
material.setup();
|
||||
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, i * stride, 1, stride);
|
||||
material.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains(RenderStage stage) {
|
||||
for (var draw : indirectDraws) {
|
||||
if (draw.material.getRenderStage() == stage) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
import com.jozufozu.flywheel.api.RenderStage;
|
||||
import com.jozufozu.flywheel.api.context.ContextShader;
|
||||
import com.jozufozu.flywheel.api.instancer.InstancedPart;
|
||||
import com.jozufozu.flywheel.api.struct.StructType;
|
||||
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
|
||||
@ -16,7 +17,6 @@ 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.util.WeakHashSet;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
|
||||
@ -38,7 +38,7 @@ public class IndirectEngine implements Engine {
|
||||
protected final Map<StructType<?>, IndirectFactory<?>> factories = new HashMap<>();
|
||||
|
||||
protected final List<IndirectModel<?>> uninitializedModels = new ArrayList<>();
|
||||
protected final RenderLists renderLists = new RenderLists();
|
||||
protected final IndirectDrawManager indirectDrawManager = new IndirectDrawManager();
|
||||
|
||||
/**
|
||||
* The set of instance managers that are attached to this engine.
|
||||
@ -65,16 +65,11 @@ public class IndirectEngine implements Engine {
|
||||
|
||||
@Override
|
||||
public void renderStage(TaskEngine taskEngine, RenderContext context, RenderStage stage) {
|
||||
if (stage != RenderStage.AFTER_SOLID_TERRAIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
setup();
|
||||
|
||||
for (IndirectList<?> list : renderLists.lists.values()) {
|
||||
for (var list : indirectDrawManager.lists.values()) {
|
||||
list.submit(stage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
@ -93,7 +88,8 @@ public class IndirectEngine implements Engine {
|
||||
factories.values()
|
||||
.forEach(IndirectFactory::delete);
|
||||
|
||||
renderLists.lists.values().forEach(IndirectList::delete);
|
||||
indirectDrawManager.lists.values()
|
||||
.forEach(IndirectCullingGroup::delete);
|
||||
|
||||
factories.clear();
|
||||
}
|
||||
@ -126,9 +122,13 @@ public class IndirectEngine implements Engine {
|
||||
@Override
|
||||
public void beginFrame(TaskEngine taskEngine, RenderContext context) {
|
||||
for (var model : uninitializedModels) {
|
||||
model.init(renderLists);
|
||||
model.init(indirectDrawManager);
|
||||
}
|
||||
uninitializedModels.clear();
|
||||
|
||||
for (IndirectCullingGroup<?> value : indirectDrawManager.lists.values()) {
|
||||
value.beginFrame();
|
||||
}
|
||||
}
|
||||
|
||||
private void shiftListeners(int cX, int cY, int cZ) {
|
||||
@ -141,7 +141,7 @@ public class IndirectEngine implements Engine {
|
||||
|
||||
@Override
|
||||
public void addDebugInfo(List<String> info) {
|
||||
info.add("GL33 Instanced Arrays");
|
||||
info.add("GL46 Indirect");
|
||||
info.add("Origin: " + originCoordinate.getX() + ", " + originCoordinate.getY() + ", " + originCoordinate.getZ());
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.jozufozu.flywheel.backend.instancing.indirect;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import com.jozufozu.flywheel.api.instancer.InstancedPart;
|
||||
import com.jozufozu.flywheel.api.struct.StructType;
|
||||
import com.jozufozu.flywheel.backend.instancing.AbstractInstancer;
|
||||
@ -8,7 +10,6 @@ import com.jozufozu.flywheel.core.layout.BufferLayout;
|
||||
public class IndirectInstancer<D extends InstancedPart> extends AbstractInstancer<D> {
|
||||
|
||||
public final BufferLayout instanceFormat;
|
||||
public final StructType<D> structType;
|
||||
public final IndirectModel<D> parent;
|
||||
int instanceCount = 0;
|
||||
|
||||
@ -18,7 +19,6 @@ public class IndirectInstancer<D extends InstancedPart> extends AbstractInstance
|
||||
super(type);
|
||||
this.parent = parent;
|
||||
this.instanceFormat = type.getLayout();
|
||||
this.structType = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -40,6 +40,33 @@ public class IndirectInstancer<D extends InstancedPart> extends AbstractInstance
|
||||
anyToRemove = false;
|
||||
}
|
||||
|
||||
public void writeSparse(long objectPtr, long batchIDPtr, int batchID) {
|
||||
var storageBufferWriter = this.type.getStorageBufferWriter();
|
||||
long objectStride = storageBufferWriter.getAlignment();
|
||||
for (int i = 0, size = data.size(); i < size; i++) {
|
||||
final var element = data.get(i);
|
||||
if (element.checkDirtyAndClear()) {
|
||||
storageBufferWriter.write(objectPtr + i * objectStride, element);
|
||||
|
||||
MemoryUtil.memPutInt(batchIDPtr + i * IndirectBuffers.INT_SIZE, batchID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void writeFull(long objectPtr, long batchIDPtr, int batchID) {
|
||||
var storageBufferWriter = this.type.getStorageBufferWriter();
|
||||
var objectStride = storageBufferWriter.getAlignment();
|
||||
for (var object : data) {
|
||||
// write object
|
||||
storageBufferWriter.write(objectPtr, object);
|
||||
objectPtr += objectStride;
|
||||
|
||||
// write batchID
|
||||
MemoryUtil.memPutInt(batchIDPtr, batchID);
|
||||
batchIDPtr += IndirectBuffers.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
// noop
|
||||
|
@ -1,246 +0,0 @@
|
||||
package com.jozufozu.flywheel.backend.instancing.indirect;
|
||||
|
||||
import static org.lwjgl.opengl.GL46.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
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.StorageBufferWriter;
|
||||
import com.jozufozu.flywheel.api.struct.StructType;
|
||||
import com.jozufozu.flywheel.api.vertex.VertexType;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
import com.jozufozu.flywheel.backend.instancing.PipelineCompiler;
|
||||
import com.jozufozu.flywheel.core.Components;
|
||||
import com.jozufozu.flywheel.core.Materials;
|
||||
import com.jozufozu.flywheel.core.QuadConverter;
|
||||
import com.jozufozu.flywheel.core.model.Mesh;
|
||||
import com.jozufozu.flywheel.core.uniform.UniformBuffer;
|
||||
|
||||
public class IndirectList<T extends InstancedPart> {
|
||||
|
||||
final StorageBufferWriter<T> storageBufferWriter;
|
||||
final GlProgram compute;
|
||||
final GlProgram draw;
|
||||
private final VertexType vertexType;
|
||||
private final long objectStride;
|
||||
|
||||
final IndirectBuffers buffers;
|
||||
|
||||
final IndirectMeshPool meshPool;
|
||||
private final int elementBuffer;
|
||||
|
||||
int vertexArray;
|
||||
|
||||
final List<Batch> batches = new ArrayList<>();
|
||||
|
||||
IndirectList(StructType<T> structType, VertexType vertexType) {
|
||||
this.vertexType = vertexType;
|
||||
storageBufferWriter = structType.getStorageBufferWriter();
|
||||
|
||||
objectStride = storageBufferWriter.getAlignment();
|
||||
buffers = new IndirectBuffers(objectStride);
|
||||
buffers.createBuffers();
|
||||
buffers.createObjectStorage(128);
|
||||
buffers.createDrawStorage(16);
|
||||
|
||||
meshPool = new IndirectMeshPool(vertexType, 1024);
|
||||
|
||||
vertexArray = glCreateVertexArrays();
|
||||
|
||||
elementBuffer = QuadConverter.getInstance()
|
||||
.quads2Tris(2048).buffer.handle();
|
||||
setupVertexArray();
|
||||
|
||||
var indirectShader = structType.getIndirectShader();
|
||||
compute = ComputeCullerCompiler.INSTANCE.get(indirectShader);
|
||||
draw = PipelineCompiler.INSTANCE.get(new PipelineCompiler.Context(vertexType, Materials.CHEST, indirectShader, Components.WORLD, Components.INDIRECT));
|
||||
}
|
||||
|
||||
private void setupVertexArray() {
|
||||
glVertexArrayElementBuffer(vertexArray, elementBuffer);
|
||||
|
||||
var meshLayout = vertexType.getLayout();
|
||||
var meshAttribs = meshLayout.getAttributeCount();
|
||||
|
||||
var attributes = meshLayout.getAttributes();
|
||||
|
||||
long offset = 0;
|
||||
for (int i = 0; i < meshAttribs; i++) {
|
||||
var attribute = attributes.get(i);
|
||||
glEnableVertexArrayAttrib(vertexArray, i);
|
||||
glVertexArrayVertexBuffer(vertexArray, i, meshPool.vbo, offset, meshLayout.getStride());
|
||||
attribute.format(vertexArray, i);
|
||||
offset += attribute
|
||||
.getByteWidth();
|
||||
}
|
||||
}
|
||||
|
||||
public void add(IndirectInstancer<T> instancer, Material material, Mesh mesh) {
|
||||
batches.add(new Batch(instancer, material, meshPool.alloc(mesh)));
|
||||
}
|
||||
|
||||
void submit(RenderStage stage) {
|
||||
if (batches.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int instanceCountThisFrame = calculateTotalInstanceCountAndPrepareBatches();
|
||||
|
||||
if (instanceCountThisFrame == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Sort meshes by material and draw many contiguous sections of the draw indirect buffer,
|
||||
// adjusting uniforms/textures accordingly
|
||||
buffers.updateCounts(instanceCountThisFrame, batches.size());
|
||||
meshPool.uploadAll();
|
||||
uploadInstanceData();
|
||||
uploadIndirectCommands();
|
||||
|
||||
UniformBuffer.getInstance().sync();
|
||||
|
||||
dispatchCompute(instanceCountThisFrame);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
dispatchDraw();
|
||||
}
|
||||
|
||||
private void dispatchDraw() {
|
||||
draw.bind();
|
||||
glVertexArrayElementBuffer(vertexArray, elementBuffer);
|
||||
glBindVertexArray(vertexArray);
|
||||
buffers.bindIndirectBuffer();
|
||||
|
||||
final int stride = (int) IndirectBuffers.DRAW_COMMAND_STRIDE;
|
||||
long offset = 0;
|
||||
for (var batch : batches) {
|
||||
|
||||
batch.material.setup();
|
||||
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, offset, 1, stride);
|
||||
batch.material.clear();
|
||||
offset += stride;
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchCompute(int instanceCount) {
|
||||
compute.bind();
|
||||
buffers.bindAll();
|
||||
|
||||
var groupCount = (instanceCount + 31) >> 5; // ceil(instanceCount / 32)
|
||||
glDispatchCompute(groupCount, 1, 1);
|
||||
}
|
||||
|
||||
private void uploadInstanceData() {
|
||||
long objectPtr = buffers.objectPtr;
|
||||
long batchIDPtr = buffers.batchPtr;
|
||||
|
||||
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);
|
||||
buffers.flushBatchIDs(batchIDPtr - buffers.batchPtr);
|
||||
}
|
||||
|
||||
private void uploadIndirectCommands() {
|
||||
long writePtr = buffers.drawPtr;
|
||||
for (var batch : batches) {
|
||||
batch.writeIndirectCommand(writePtr);
|
||||
writePtr += IndirectBuffers.DRAW_COMMAND_STRIDE;
|
||||
}
|
||||
buffers.flushDrawCommands(writePtr - buffers.drawPtr);
|
||||
}
|
||||
|
||||
private int calculateTotalInstanceCountAndPrepareBatches() {
|
||||
int baseInstance = 0;
|
||||
for (var batch : batches) {
|
||||
batch.prepare(baseInstance);
|
||||
baseInstance += batch.instancer.instanceCount;
|
||||
}
|
||||
return baseInstance;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
MemoryUtil.memPutInt(ptr, mesh.getIndexCount()); // count
|
||||
MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be incremented by the compute shader
|
||||
MemoryUtil.memPutInt(ptr + 8, 0); // firstIndex - all models share the same index buffer
|
||||
MemoryUtil.memPutInt(ptr + 12, mesh.getBaseVertex()); // baseVertex
|
||||
MemoryUtil.memPutInt(ptr + 16, baseInstance); // baseInstance
|
||||
|
||||
boundingSphere.getToAddress(ptr + 20); // boundingSphere
|
||||
}
|
||||
}
|
||||
}
|
@ -14,12 +14,12 @@ public class IndirectModel<D extends InstancedPart> {
|
||||
this.instancer = new IndirectInstancer<>(this, type);
|
||||
}
|
||||
|
||||
public void init(RenderLists renderLists) {
|
||||
public void init(IndirectDrawManager indirectDrawManager) {
|
||||
var materialMeshMap = this.model.getMeshes();
|
||||
for (var entry : materialMeshMap.entrySet()) {
|
||||
var material = entry.getKey();
|
||||
var mesh = entry.getValue();
|
||||
renderLists.add(instancer, material, mesh);
|
||||
indirectDrawManager.add(instancer, material, mesh);
|
||||
|
||||
return; // TODO: support multiple meshes per model
|
||||
}
|
||||
|
@ -23,21 +23,21 @@ public class OrientedStorageWriter implements StorageBufferWriter<OrientedPart>
|
||||
MemoryUtil.memPutFloat(ptr + 20, d.posY);
|
||||
MemoryUtil.memPutFloat(ptr + 24, d.posZ);
|
||||
|
||||
MemoryUtil.memPutFloat(ptr + 32, d.pivotX);
|
||||
MemoryUtil.memPutFloat(ptr + 36, d.pivotY);
|
||||
MemoryUtil.memPutFloat(ptr + 40, d.pivotZ);
|
||||
MemoryUtil.memPutFloat(ptr + 28, d.pivotX);
|
||||
MemoryUtil.memPutFloat(ptr + 32, d.pivotY);
|
||||
MemoryUtil.memPutFloat(ptr + 36, d.pivotZ);
|
||||
|
||||
MemoryUtil.memPutShort(ptr + 44, d.skyLight);
|
||||
MemoryUtil.memPutShort(ptr + 46, d.blockLight);
|
||||
MemoryUtil.memPutShort(ptr + 40, d.skyLight);
|
||||
MemoryUtil.memPutShort(ptr + 42, d.blockLight);
|
||||
|
||||
MemoryUtil.memPutByte(ptr + 48, d.r);
|
||||
MemoryUtil.memPutByte(ptr + 49, d.g);
|
||||
MemoryUtil.memPutByte(ptr + 50, d.b);
|
||||
MemoryUtil.memPutByte(ptr + 51, d.a);
|
||||
MemoryUtil.memPutByte(ptr + 44, d.r);
|
||||
MemoryUtil.memPutByte(ptr + 45, d.g);
|
||||
MemoryUtil.memPutByte(ptr + 46, d.b);
|
||||
MemoryUtil.memPutByte(ptr + 47, d.a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlignment() {
|
||||
return 64;
|
||||
return 48;
|
||||
}
|
||||
}
|
||||
|
@ -16,16 +16,16 @@ public class TransformedStorageWriter implements StorageBufferWriter<Transformed
|
||||
public void write(long ptr, TransformedPart instance) {
|
||||
MatrixExtension.writeUnsafe(instance.model, ptr);
|
||||
MatrixExtension.writeUnsafe(instance.normal, ptr + 64);
|
||||
MemoryUtil.memPutByte(ptr + 112, instance.r);
|
||||
MemoryUtil.memPutByte(ptr + 113, instance.g);
|
||||
MemoryUtil.memPutByte(ptr + 114, instance.b);
|
||||
MemoryUtil.memPutByte(ptr + 115, instance.a);
|
||||
MemoryUtil.memPutShort(ptr + 116, instance.skyLight);
|
||||
MemoryUtil.memPutShort(ptr + 118, instance.blockLight);
|
||||
MemoryUtil.memPutByte(ptr + 100, instance.r);
|
||||
MemoryUtil.memPutByte(ptr + 101, instance.g);
|
||||
MemoryUtil.memPutByte(ptr + 102, instance.b);
|
||||
MemoryUtil.memPutByte(ptr + 103, instance.a);
|
||||
MemoryUtil.memPutShort(ptr + 104, instance.skyLight);
|
||||
MemoryUtil.memPutShort(ptr + 106, instance.blockLight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlignment() {
|
||||
return 128;
|
||||
return 108;
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,7 @@ import com.jozufozu.flywheel.core.Components;
|
||||
import com.jozufozu.flywheel.core.RenderContext;
|
||||
import com.jozufozu.flywheel.core.source.FileResolution;
|
||||
import com.jozufozu.flywheel.event.BeginFrameEvent;
|
||||
import com.jozufozu.flywheel.util.extension.MatrixExtension;
|
||||
import com.jozufozu.flywheel.util.joml.FrustumIntersection;
|
||||
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
|
@ -1,24 +1,32 @@
|
||||
#use "flywheel:api/vertex.glsl"
|
||||
#use "flywheel:util/quaternion.glsl"
|
||||
#use "flywheel:util/types.glsl"
|
||||
|
||||
#define FLW_INSTANCE_STRUCT Instance
|
||||
struct Instance {
|
||||
vec4 rotation;
|
||||
vec3 pos;
|
||||
vec3 pivot;
|
||||
Vec4F rotation;
|
||||
Vec3F pos;
|
||||
Vec3F pivot;
|
||||
uint light;
|
||||
uint color;
|
||||
};
|
||||
|
||||
void flw_transformBoundingSphere(in Instance i, inout vec3 center, inout float radius) {
|
||||
center = rotateVertexByQuat(center - i.pivot, i.rotation) + i.pivot + i.pos;
|
||||
vec4 rotation = unpackVec4F(i.rotation);
|
||||
vec3 pivot = unpackVec3F(i.pivot);
|
||||
vec3 pos = unpackVec3F(i.pos);
|
||||
|
||||
center = rotateVertexByQuat(center - pivot, rotation) + pivot + pos;
|
||||
}
|
||||
|
||||
#ifdef VERTEX_SHADER
|
||||
#ifdef VERTEX_SHADER
|
||||
void flw_instanceVertex(Instance i) {
|
||||
flw_vertexPos = vec4(rotateVertexByQuat(flw_vertexPos.xyz - i.pivot, i.rotation) + i.pivot + i.pos, 1.0);
|
||||
flw_vertexNormal = rotateVertexByQuat(flw_vertexNormal, i.rotation);
|
||||
vec4 rotation = unpackVec4F(i.rotation);
|
||||
vec3 pivot = unpackVec3F(i.pivot);
|
||||
vec3 pos = unpackVec3F(i.pos);
|
||||
flw_vertexPos = vec4(rotateVertexByQuat(flw_vertexPos.xyz - pivot, rotation) + pivot + pos, 1.0);
|
||||
flw_vertexNormal = rotateVertexByQuat(flw_vertexNormal, rotation);
|
||||
flw_vertexColor = unpackUnorm4x8(i.color);
|
||||
flw_vertexLight = vec2(float((i.light >> 16) & 0xFFFFu), float(i.light & 0xFFFFu)) / 15.0;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@ -1,22 +1,23 @@
|
||||
#use "flywheel:api/vertex.glsl"
|
||||
#use "flywheel:util/types.glsl"
|
||||
|
||||
#define FLW_INSTANCE_STRUCT Instance
|
||||
struct Instance {
|
||||
mat4 pose;
|
||||
mat3 normal;
|
||||
Mat4F pose;
|
||||
Mat3F normal;
|
||||
uint color;
|
||||
uint light;
|
||||
};
|
||||
|
||||
void flw_transformBoundingSphere(in Instance i, inout vec3 center, inout float radius) {
|
||||
center = (i.pose * vec4(center, 1.0)).xyz;
|
||||
center = (unpackMat4F(i.pose) * vec4(center, 1.0)).xyz;
|
||||
}
|
||||
|
||||
#ifdef VERTEX_SHADER
|
||||
void flw_instanceVertex(Instance i) {
|
||||
flw_vertexPos = i.pose * flw_vertexPos;
|
||||
flw_vertexNormal = i.normal * flw_vertexNormal;
|
||||
flw_vertexPos = unpackMat4F(i.pose) * flw_vertexPos;
|
||||
flw_vertexNormal = unpackMat3F(i.normal) * flw_vertexNormal;
|
||||
flw_vertexColor = unpackUnorm4x8(i.color);
|
||||
flw_vertexLight = vec2(float((i.light >> 16) & 0xFFFFu), float(i.light & 0xFFFFu)) / 15.0;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@ -1,3 +1,4 @@
|
||||
// Types intended for use is SSBOs to achieve tighter data packing.
|
||||
|
||||
struct Vec3F {
|
||||
float x;
|
||||
@ -5,13 +6,58 @@ struct Vec3F {
|
||||
float z;
|
||||
};
|
||||
|
||||
struct Vec4F {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
};
|
||||
|
||||
struct Mat4F {
|
||||
Vec4F c0;
|
||||
Vec4F c1;
|
||||
Vec4F c2;
|
||||
Vec4F c3;
|
||||
};
|
||||
|
||||
struct Mat3F {
|
||||
Vec3F c0;
|
||||
Vec3F c1;
|
||||
Vec3F c2;
|
||||
};
|
||||
|
||||
// 4-aligned instead of a 16-aligned vec4
|
||||
struct BoundingSphere {
|
||||
Vec3F center;
|
||||
float radius;
|
||||
};
|
||||
|
||||
vec3 unpackVec3F(in Vec3F v) {
|
||||
return vec3(v.x, v.y, v.z);
|
||||
}
|
||||
|
||||
vec4 unpackVec4F(in Vec4F v) {
|
||||
return vec4(v.x, v.y, v.z, v.w);
|
||||
}
|
||||
|
||||
mat4 unpackMat4F(in Mat4F m) {
|
||||
return mat4(
|
||||
unpackVec4F(m.c0),
|
||||
unpackVec4F(m.c1),
|
||||
unpackVec4F(m.c2),
|
||||
unpackVec4F(m.c3)
|
||||
);
|
||||
}
|
||||
|
||||
mat3 unpackMat3F(in Mat3F m) {
|
||||
return mat3(
|
||||
unpackVec3F(m.c0),
|
||||
unpackVec3F(m.c1),
|
||||
unpackVec3F(m.c2)
|
||||
);
|
||||
}
|
||||
|
||||
void unpackBoundingSphere(in BoundingSphere sphere, out vec3 center, out float radius) {
|
||||
center = vec3(sphere.center.x, sphere.center.y, sphere.center.z);
|
||||
center = unpackVec3F(sphere.center);
|
||||
radius = sphere.radius;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user