Indirectly packed

- Use glsl structs to pack vec/mat data in ssbos
 - Support RenderStages
 - Dynamically sized draw storage
This commit is contained in:
Jozufozu 2022-08-21 16:47:15 -07:00
parent ebef176089
commit f84b169bd7
17 changed files with 471 additions and 306 deletions

View File

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

View File

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

View File

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

View File

@ -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
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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
}
}
}

View File

@ -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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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