Things on the screen

- Specialize MeshPool for the indirect engine
 - Temporarily force the engine to be indirect
 - Fix compilation issues with test shaders
 - VertexAttribute supports DSA
 - Fix Miniball issues in dev-env
 - VertexList implements PointSet for use with Miniball
 - Meshes store their bounding spheres
 - Add hook/system property to load renderdoc on client launch
 - StructTypes provide separate StorageBufferWriter for indirect
This commit is contained in:
Jozufozu 2022-08-05 10:51:07 -07:00
parent 6234df6440
commit 9d657aed40
36 changed files with 644 additions and 131 deletions

View file

@ -21,6 +21,8 @@ apply plugin: 'eclipse'
apply plugin: 'maven-publish'
apply plugin: 'org.spongepowered.mixin'
jarJar.enable()
boolean dev = System.getenv('RELEASE') == null || System.getenv('RELEASE').equalsIgnoreCase('false');
ext.buildNumber = System.getenv('BUILD_NUMBER')
@ -31,8 +33,6 @@ version = mod_version + (dev && buildNumber != null ? "-${buildNumber}" : '')
java.toolchain.languageVersion = JavaLanguageVersion.of(17)
jarJar.enable()
println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch'))
minecraft {
mappings channel: 'parchment', version: "${parchment_version}-${minecraft_version}"
@ -47,6 +47,7 @@ minecraft {
property 'mixin.env.remapRefMap', 'true'
property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg"
property 'flw.dumpShaderSource', 'true'
property 'flw.loadRenderDoc', 'true'
arg '-mixin.config=flywheel.mixins.json'
@ -108,15 +109,31 @@ repositories {
includeGroup "maven.modrinth"
}
}
mavenCentral()
}
// Fix for loading non-mod libraries in dev-env, used for miniball.
// https://gist.github.com/SizableShrimp/66b22f1b24c255e1491c8d98d3f11f83
// v--------------------------------------------------------------------v
configurations {
library
implementation.extendsFrom library
}
minecraft.runs.all {
lazyToken('minecraft_classpath') {
configurations.library.copyRecursive().resolve().collect { it.absolutePath }.join(File.pathSeparator)
}
}
// ^--------------------------------------------------------------------^
dependencies {
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
implementation "com.dreizak:miniball:1.0.3"
jarJar(group: 'com.dreizak', name: 'miniball', version: '[1.0,2.0)') {
jarJar.pin(it, "[1.0,2.0)")
jarJar(group: 'com.dreizak', name: 'miniball', version: "1.0.3") {
jarJar.ranged(it, "[1.0,2.0)")
}
library "com.dreizak:miniball:1.0.3"
// switch to implementation for debugging
compileOnly fg.deobf("maven.modrinth:starlight-forge:1.0.2+1.18.2")
@ -139,6 +156,7 @@ mixin {
// Workaround for SpongePowered/MixinGradle#38
afterEvaluate {
tasks.configureReobfTaskForReobfJar.mustRunAfter(tasks.compileJava)
tasks.configureReobfTaskForReobfJarJar.mustRunAfter(tasks.compileJava)
}
tasks.withType(JavaCompile).configureEach {
@ -151,6 +169,10 @@ javadoc {
options.addStringOption('Xdoclint:none', '-quiet')
}
compileJava {
options.compilerArgs = ['-Xdiags:verbose']
}
jar {
manifest {
attributes([

View file

@ -0,0 +1,10 @@
package com.jozufozu.flywheel.api.struct;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
public interface StorageBufferWriter<T extends InstancedPart> {
void write(final long ptr, final T instance, final int batchID);
int getAlignment();
}

View file

@ -36,6 +36,8 @@ public interface StructType<S extends InstancedPart> {
VertexTransformer<? extends S> getVertexTransformer();
StorageBufferWriter<S> getStorageBufferWriter();
public interface VertexTransformer<S extends InstancedPart> {
void transform(MutableVertexList vertexList, S struct, ClientLevel level);
}

View file

@ -1,5 +1,7 @@
package com.jozufozu.flywheel.api.vertex;
import com.dreizak.miniball.model.PointSet;
/**
* A read only view of a vertex buffer.
*
@ -9,7 +11,7 @@ package com.jozufozu.flywheel.api.vertex;
* </p>
* TODO: more flexible elements?
*/
public interface VertexList {
public interface VertexList extends PointSet {
float x(int index);
float y(int index);
@ -47,4 +49,24 @@ public interface VertexList {
default boolean isEmpty() {
return getVertexCount() == 0;
}
@Override
default int size() {
return getVertexCount();
}
@Override
default int dimension() {
return 3;
}
@Override
default double coord(int i, int j) {
return switch (j) {
case 0 -> x(i);
case 1 -> y(i);
case 2 -> z(i);
default -> throw new IllegalArgumentException("Invalid dimension: " + j);
};
}
}

View file

@ -54,7 +54,8 @@ public class Backend {
}
public static void refresh() {
TYPE = chooseEngine();
// TODO: Revert when done testing
TYPE = BackendType.INDIRECT; // chooseEngine();
}
public static boolean isOn() {

View file

@ -26,4 +26,8 @@ public enum GLSLVersion {
public String toString() {
return Integer.toString(version);
}
public String getVersionLine() {
return "#version " + version + '\n';
}
}

View file

@ -3,5 +3,18 @@ package com.jozufozu.flywheel.backend.gl.array;
public interface VertexAttribute {
int getByteWidth();
/**
* Apply this vertex attribute to the bound vertex array.
* @param offset The byte offset to the first element of the attribute.
* @param i The attribute index.
* @param stride The byte stride between consecutive elements of the attribute.
*/
void pointer(long offset, int i, int stride);
/**
* Use DSA to apply this vertex attribute to the given vertex array.
* @param vaobj The vertex array object to modify.
* @param i The attribute index.
*/
void format(int vaobj, int i);
}

View file

@ -1,6 +1,7 @@
package com.jozufozu.flywheel.backend.gl.array;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL45;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
@ -22,4 +23,9 @@ public record VertexAttributeF(GlNumericType type, int size, boolean normalized)
public void pointer(long offset, int i, int stride) {
GL32.glVertexAttribPointer(i, size(), type().getGlEnum(), normalized(), stride, offset);
}
@Override
public void format(int vaobj, int i) {
GL45.glVertexArrayAttribFormat(vaobj, i, size(), type().getGlEnum(), normalized(), 0);
}
}

View file

@ -1,6 +1,7 @@
package com.jozufozu.flywheel.backend.gl.array;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL45;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
@ -21,4 +22,9 @@ public record VertexAttributeI(GlNumericType type, int size) implements VertexAt
public void pointer(long offset, int i, int stride) {
GL32.glVertexAttribIPointer(i, size(), type().getGlEnum(), stride, offset);
}
@Override
public void format(int vaobj, int i) {
GL45.glVertexArrayAttribIFormat(vaobj, i, size(), type().getGlEnum(), 0);
}
}

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.backend.instancing;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.instancer.Instancer;
@ -60,6 +61,14 @@ public abstract class AbstractInstancer<D extends InstancedPart> implements Inst
return data.size();
}
public List<D> getRange(int start, int end) {
return data.subList(start, end);
}
public List<D> getAll() {
return data;
}
protected void removeDeletedInstances() {
// Figure out which elements are to be removed.
final int oldSize = this.data.size();

View file

@ -19,14 +19,6 @@ public class CPUInstancer<D extends InstancedPart> extends AbstractInstancer<D>
}
}
public List<D> getRange(int start, int end) {
return data.subList(start, end);
}
public List<D> getAll() {
return data;
}
@Override
public void notifyDirty() {
// noop

View file

@ -1,34 +1,42 @@
package com.jozufozu.flywheel.backend.instancing.indirect;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.core.compile.Memoizer;
import com.jozufozu.flywheel.core.compile.ProgramAssembler;
import com.jozufozu.flywheel.core.compile.ShaderCompilationException;
import com.jozufozu.flywheel.core.source.CompilationContext;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
public class ComputeCompiler extends Memoizer<FileResolution, GlProgram> {
public class ComputeCullerCompiler extends Memoizer<FileResolution, GlProgram> {
public static final ComputeCompiler INSTANCE = new ComputeCompiler();
public static final ComputeCullerCompiler INSTANCE = new ComputeCullerCompiler();
private ComputeCompiler() {
private ComputeCullerCompiler() {
}
@Override
protected GlProgram _create(FileResolution file) {
CompilationContext context = new CompilationContext();
var header = GLSLVersion.V460.getVersionLine();
String source = file.getFile()
.generateFinalSource(new CompilationContext());
.generateFinalSource(context);
var shader = new GlShader(source, ShaderType.COMPUTE, ImmutableList.of(file.getFileLoc()));
try {
var shader = new GlShader(header + source, ShaderType.COMPUTE, ImmutableList.of(file.getFileLoc()));
return new ProgramAssembler(file.getFileLoc())
.attachShader(shader)
.link()
.build(GlProgram::new);
return new ProgramAssembler(file.getFileLoc()).attachShader(shader)
.link()
.build(GlProgram::new);
} catch (ShaderCompilationException e) {
throw e.withErrorLog(context);
}
}
@Override

View file

@ -1,10 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.indirect;
public class DSABuffer {
int id;
int byteSize;
public DSABuffer(int id) {
this.id = id;
}
}

View file

@ -0,0 +1,65 @@
package com.jozufozu.flywheel.backend.instancing.indirect;
import org.jetbrains.annotations.NotNull;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.core.WorldProgram;
import com.jozufozu.flywheel.core.compile.CompileUtil;
import com.jozufozu.flywheel.core.compile.Memoizer;
import com.jozufozu.flywheel.core.compile.ProgramAssembler;
import com.jozufozu.flywheel.core.compile.ShaderCompilationException;
import com.jozufozu.flywheel.core.source.CompilationContext;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
public class IndirectDrawCompiler extends Memoizer<IndirectDrawCompiler.Program, GlProgram> {
public static final IndirectDrawCompiler INSTANCE = new IndirectDrawCompiler();
private IndirectDrawCompiler() {
}
@Override
protected GlProgram _create(IndirectDrawCompiler.Program program) {
GlShader vertexShader = compile(program.vertex.getFile(), ShaderType.VERTEX);
GlShader fragmentShader = compile(program.fragment.getFile(), ShaderType.FRAGMENT);
return new ProgramAssembler(program.vertex.getFileLoc())
.attachShader(vertexShader)
.attachShader(fragmentShader)
.link()
.build(WorldProgram::new);
}
@NotNull
private static GlShader compile(SourceFile file, ShaderType type) {
var context = new CompilationContext();
try {
var header = CompileUtil.generateHeader(GLSLVersion.V460, type);
var source = file.generateFinalSource(context);
return new GlShader(header + source, type, ImmutableList.of(file.name));
} catch (ShaderCompilationException e) {
throw e.withErrorLog(context);
}
}
@Override
protected void _destroy(GlProgram value) {
value.delete();
}
public static void invalidateAll(ReloadRenderersEvent ignored) {
INSTANCE.invalidate();
}
public record Program(FileResolution vertex, FileResolution fragment) {
}
}

View file

@ -149,9 +149,6 @@ public class IndirectEngine implements Engine {
model.init(renderLists);
}
uninitializedModels.clear();
MeshPool.getInstance()
.flush();
}
private void shiftListeners(int cX, int cY, int cZ) {

View file

@ -11,7 +11,7 @@ public class IndirectInstancer<D extends InstancedPart> extends AbstractInstance
public final BufferLayout instanceFormat;
public final StructType<D> structType;
public final InstancedModel<D> parent;
int maxInstanceCount = 0;
int instanceCount = 0;
boolean anyToUpdate;
@ -28,7 +28,7 @@ public class IndirectInstancer<D extends InstancedPart> extends AbstractInstance
}
public boolean isEmpty() {
return !anyToUpdate && !anyToRemove && maxInstanceCount == 0;
return !anyToUpdate && !anyToRemove && instanceCount == 0;
}
void update() {
@ -36,7 +36,7 @@ public class IndirectInstancer<D extends InstancedPart> extends AbstractInstance
removeDeletedInstances();
}
maxInstanceCount = data.size();
instanceCount = data.size();
anyToRemove = false;
}

View file

@ -1,121 +1,244 @@
package com.jozufozu.flywheel.backend.instancing.indirect;
import java.nio.ByteBuffer;
import static org.lwjgl.opengl.GL46.*;
import java.text.Format;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.lwjgl.opengl.GL46;
import org.lwjgl.opengl.GL46C;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
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.backend.gl.GlNumericType;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.instancing.instancing.MeshPool;
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;
import com.jozufozu.flywheel.core.vertex.Formats;
public class IndirectList<T extends InstancedPart> {
private static final int DRAW_COMMAND_STRIDE = 20;
final StorageBufferWriter<T> storageBufferWriter;
final GlProgram compute;
final GlProgram draw;
private final StructType<T> type;
private final long maxObjectCount;
private final long objectStride;
private final int maxBatchCount;
private final long objectClientStorage;
private final int elementBuffer;
/**
* Stores raw instance data per-object.
*/
DSABuffer objectBuffer;
int objectBuffer;
int targetBuffer;
/**
* Stores bounding spheres
*/
DSABuffer boundingSphereBuffer;
int boundingSphereBuffer;
/**
* Stores drawIndirect structs.
*/
DSABuffer drawBuffer;
DSABuffer targetBuffer;
int drawBuffer;
final int[] buffers = new int[4];
final IndirectMeshPool meshPool;
int vertexArray;
final int[] shaderStorageBuffers = new int[4];
final List<Batch<T>> batches = new ArrayList<>();
IndirectList(StructType<T> structType) {
GL46.glCreateBuffers(buffers);
objectBuffer = new DSABuffer(buffers[0]);
targetBuffer = new DSABuffer(buffers[1]);
boundingSphereBuffer = new DSABuffer(buffers[2]);
drawBuffer = new DSABuffer(buffers[3]);
type = structType;
storageBufferWriter = type.getStorageBufferWriter();
compute = ComputeCompiler.INSTANCE.get(Components.Files.CULL_INSTANCES);
draw = null;
if (storageBufferWriter == null) {
throw new NullPointerException();
}
glCreateBuffers(shaderStorageBuffers);
objectBuffer = shaderStorageBuffers[0];
targetBuffer = shaderStorageBuffers[1];
boundingSphereBuffer = shaderStorageBuffers[2];
drawBuffer = shaderStorageBuffers[3];
meshPool = new IndirectMeshPool(Formats.BLOCK, 1024);
// FIXME: Resizable buffers
maxObjectCount = 1024L * 100L;
maxBatchCount = 64;
// +4 for the batch id
objectStride = storageBufferWriter.getAlignment();
glNamedBufferStorage(objectBuffer, objectStride * maxObjectCount, GL_DYNAMIC_STORAGE_BIT);
glNamedBufferStorage(targetBuffer, 4 * maxObjectCount, GL_DYNAMIC_STORAGE_BIT);
glNamedBufferStorage(boundingSphereBuffer, 16 * maxBatchCount, GL_DYNAMIC_STORAGE_BIT);
glNamedBufferStorage(drawBuffer, DRAW_COMMAND_STRIDE * maxBatchCount, GL_DYNAMIC_STORAGE_BIT);
objectClientStorage = MemoryUtil.nmemAlloc(objectStride * maxObjectCount);
vertexArray = glCreateVertexArrays();
elementBuffer = QuadConverter.getInstance()
.quads2Tris(2048).buffer.handle();
setupVertexArray();
compute = ComputeCullerCompiler.INSTANCE.get(Components.Files.CULL_INSTANCES);
draw = IndirectDrawCompiler.INSTANCE.get(new IndirectDrawCompiler.Program(Components.Files.DRAW_INDIRECT_VERTEX, Components.Files.DRAW_INDIRECT_FRAGMENT));
}
private void setupVertexArray() {
glVertexArrayElementBuffer(vertexArray, elementBuffer);
var meshLayout = Formats.BLOCK.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();
}
glEnableVertexArrayAttrib(vertexArray, meshAttribs);
glVertexArrayVertexBuffer(vertexArray, meshAttribs, targetBuffer, 0, 4);
glVertexArrayAttribIFormat(vertexArray, meshAttribs, 1, GlNumericType.UINT.getGlEnum(), 0);
}
public void add(Mesh mesh, IndirectInstancer<T> instancer) {
var pool = MeshPool.getInstance();
var buffered = pool.alloc(mesh);
batches.add(new Batch<>(instancer, buffered));
batches.add(new Batch<>(instancer, meshPool.alloc(mesh)));
}
public void prepare() {
void submit() {
int instanceCountThisFrame = calculateTotalInstanceCount();
if (instanceCountThisFrame == 0) {
return;
}
meshPool.uploadAll();
uploadInstanceData();
uploadBoundingSpheres();
uploadIndirectCommands();
UniformBuffer.getInstance().sync();
dispatchCompute(instanceCountThisFrame);
issueMemoryBarrier();
dispatchDraw();
}
private void dispatchDraw() {
draw.bind();
Materials.BELL.setup();
glVertexArrayElementBuffer(vertexArray, elementBuffer);
glBindVertexArray(vertexArray);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, drawBuffer);
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, 0, batches.size(), 0);
Materials.BELL.clear();
}
private static void issueMemoryBarrier() {
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
}
private void uploadBoundingSpheres() {
try (var stack = MemoryStack.stackPush()) {
var size = batches.size() * 20;
final int size = batches.size() * 16;
final long basePtr = stack.nmalloc(size);
long ptr = basePtr;
for (Batch<T> batch : batches) {
var boundingSphere = batch.mesh.mesh.getBoundingSphere();
boundingSphere.getToAddress(ptr);
ptr += 16;
}
GL46C.nglNamedBufferSubData(boundingSphereBuffer, 0, size, basePtr);
}
}
private void dispatchCompute(int instanceCount) {
compute.bind();
glBindBuffersBase(GL_SHADER_STORAGE_BUFFER, 0, shaderStorageBuffers);
var groupCount = (instanceCount + 31) >> 5; // ceil(totalInstanceCount / 32)
glDispatchCompute(groupCount, 1, 1);
}
private void uploadInstanceData() {
long ptr = objectClientStorage;
int baseInstance = 0;
int batchID = 0;
for (var batch : batches) {
batch.baseInstance = baseInstance;
var instancer = batch.instancer;
for (T t : instancer.getAll()) {
storageBufferWriter.write(ptr, t, batchID);
ptr += objectStride;
}
baseInstance += batch.instancer.instanceCount;
batchID++;
}
GL46C.nglNamedBufferSubData(objectBuffer, 0, ptr - objectClientStorage, objectClientStorage);
}
private void uploadIndirectCommands() {
try (var stack = MemoryStack.stackPush()) {
var size = batches.size() * DRAW_COMMAND_STRIDE;
long basePtr = stack.nmalloc(size);
long writePtr = basePtr;
for (Batch<T> batch : batches) {
batch.writeIndirectCommand(writePtr);
writePtr += 20;
writePtr += DRAW_COMMAND_STRIDE;
}
GL46C.nglNamedBufferData(drawBuffer.id, size, basePtr, GL46.GL_STREAM_DRAW);
GL46C.nglNamedBufferSubData(drawBuffer, 0, size, basePtr);
}
}
public void submit() {
compute.bind();
GL46.glBindBuffersBase(GL46.GL_SHADER_STORAGE_BUFFER, 0, buffers);
var groupCount = (getTotalInstanceCount() + 31) >> 5; // ceil(totalInstanceCount / 32)
GL46.glDispatchCompute(groupCount, 1, 1);
draw.bind();
GL46.glMemoryBarrier(GL46.GL_SHADER_STORAGE_BARRIER_BIT);
GL46.glBindBuffer(GL46.GL_DRAW_INDIRECT_BUFFER, drawBuffer.id);
GL46.glMultiDrawElementsIndirect(GL46.GL_TRIANGLES, GL46.GL_UNSIGNED_INT, 0, batches.size(), 0);
}
private int getTotalInstanceCount() {
return 0;
private int calculateTotalInstanceCount() {
int total = 0;
for (Batch<T> batch : batches) {
batch.instancer.update();
total += batch.instancer.instanceCount;
}
return total;
}
private static final class Batch<T extends InstancedPart> {
final IndirectInstancer<T> instancer;
final MeshPool.BufferedMesh mesh;
final IndirectMeshPool.BufferedMesh mesh;
int baseInstance;
private Batch(IndirectInstancer<T> instancer, MeshPool.BufferedMesh mesh) {
private Batch(IndirectInstancer<T> instancer, IndirectMeshPool.BufferedMesh mesh) {
this.instancer = instancer;
this.mesh = mesh;
}
public void writeIndirectCommand(long ptr) {
// typedef struct {
// GLuint count;
// GLuint instanceCount;
// GLuint firstIndex;
// GLuint baseVertex;
// GLuint baseInstance;
//} DrawElementsIndirectCommand;
MemoryUtil.memPutInt(ptr, mesh.getVertexCount());
MemoryUtil.memPutInt(ptr + 4, 0);
MemoryUtil.memPutInt(ptr + 8, 0);
MemoryUtil.memPutInt(ptr + 12, mesh.getBaseVertex());
MemoryUtil.memPutInt(ptr + 16, 0);
MemoryUtil.memPutInt(ptr, mesh.getVertexCount()); // 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
}
}
}

View file

@ -0,0 +1,118 @@
package com.jozufozu.flywheel.backend.instancing.indirect;
import static org.lwjgl.opengl.GL46.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.instancing.instancing.ElementBuffer;
import com.jozufozu.flywheel.core.model.Mesh;
public class IndirectMeshPool {
private final Map<Mesh, BufferedMesh> meshes = new HashMap<>();
private final List<BufferedMesh> meshList = new ArrayList<>();
final VertexType vertexType;
final int vbo;
private final ByteBuffer clientStorage;
private boolean dirty;
/**
* Create a new mesh pool.
*/
public IndirectMeshPool(VertexType type, int vertexCapacity) {
vertexType = type;
vbo = glCreateBuffers();
var byteCapacity = type.byteOffset(vertexCapacity);
glNamedBufferStorage(vbo, byteCapacity, GL_DYNAMIC_STORAGE_BIT);
clientStorage = MemoryUtil.memAlloc(byteCapacity);
}
/**
* Allocate a model in the arena.
*
* @param mesh The model to allocate.
* @return A handle to the allocated model.
*/
public BufferedMesh alloc(Mesh mesh) {
return meshes.computeIfAbsent(mesh, m -> {
BufferedMesh bufferedModel = new BufferedMesh(m);
meshList.add(bufferedModel);
dirty = true;
return bufferedModel;
});
}
@Nullable
public BufferedMesh get(Mesh mesh) {
return meshes.get(mesh);
}
void uploadAll() {
if (!dirty) {
return;
}
dirty = false;
int byteIndex = 0;
int baseVertex = 0;
for (BufferedMesh model : meshList) {
model.byteIndex = byteIndex;
model.baseVertex = baseVertex;
model.buffer(clientStorage);
byteIndex += model.getByteSize();
baseVertex += model.mesh.getVertexCount();
}
glNamedBufferSubData(vbo, 0, clientStorage);
}
public void delete() {
glDeleteBuffers(vbo);
meshes.clear();
meshList.clear();
}
public class BufferedMesh {
public final Mesh mesh;
private long byteIndex;
private int baseVertex;
public BufferedMesh(Mesh mesh) {
this.mesh = mesh;
}
private void buffer(ByteBuffer buffer) {
var writer = IndirectMeshPool.this.vertexType.createWriter(buffer);
writer.seek(this.byteIndex);
writer.writeVertexList(this.mesh.getReader());
}
public int getByteSize() {
return IndirectMeshPool.this.vertexType.getLayout().getStride() * this.mesh.getVertexCount();
}
public int getBaseVertex() {
return baseVertex;
}
public int getVertexCount() {
return this.mesh.getVertexCount();
}
}
}

View file

@ -1,13 +1,7 @@
package com.jozufozu.flywheel.backend.instancing.indirect;
import java.util.List;
import java.util.Map;
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.core.model.Mesh;
import com.jozufozu.flywheel.core.model.Model;
public class InstancedModel<D extends InstancedPart> {
@ -42,7 +36,7 @@ public class InstancedModel<D extends InstancedPart> {
}
public int getVertexCount() {
return model.getVertexCount() * instancer.maxInstanceCount;
return model.getVertexCount() * instancer.instanceCount;
}
public void delete() {

View file

@ -27,7 +27,7 @@ public class MeshPool {
private static MeshPool allocator;
public static MeshPool getInstance() {
static MeshPool getInstance() {
if (allocator == null) {
allocator = new MeshPool();
}
@ -188,7 +188,7 @@ public class MeshPool {
public class BufferedMesh {
private final ElementBuffer ebo;
private final Mesh mesh;
public final Mesh mesh;
private final VertexType type;
private long byteIndex;
private int baseVertex;

View file

@ -49,6 +49,8 @@ public class Components {
public static final FileResolution CRUMBLING_VERTEX = contextVertex(ResourceUtil.subPath(Names.CRUMBLING, ".vert"));
public static final FileResolution CRUMBLING_FRAGMENT = contextFragment(ResourceUtil.subPath(Names.CRUMBLING, ".frag"));
public static final FileResolution CULL_INSTANCES = compute(Flywheel.rl("compute/cull_instances.glsl"));
public static final FileResolution DRAW_INDIRECT_VERTEX = FileResolution.get(ResourceUtil.subPath(Names.DRAW_INDIRECT, ".vert"));
public static final FileResolution DRAW_INDIRECT_FRAGMENT = FileResolution.get(ResourceUtil.subPath(Names.DRAW_INDIRECT, ".frag"));
private static FileResolution compute(ResourceLocation rl) {
return FileResolution.get(rl);
@ -115,5 +117,6 @@ public class Components {
public static final ResourceLocation SHADED = Flywheel.rl("material/shaded");
public static final ResourceLocation WORLD = Flywheel.rl("context/world");
public static final ResourceLocation CRUMBLING = Flywheel.rl("context/crumbling");
public static final ResourceLocation DRAW_INDIRECT = Flywheel.rl("compute/draw_instances");
}
}

View file

@ -2,19 +2,24 @@ package com.jozufozu.flywheel.core.hardcoded;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.system.MemoryStack;
import com.jozufozu.flywheel.api.vertex.VertexList;
import com.jozufozu.flywheel.core.model.Mesh;
import com.jozufozu.flywheel.core.model.ModelUtil;
import com.jozufozu.flywheel.core.vertex.Formats;
import com.jozufozu.flywheel.core.vertex.PosTexNormalVertex;
import com.jozufozu.flywheel.core.vertex.PosTexNormalWriterUnsafe;
import com.jozufozu.flywheel.util.joml.Vector4f;
import com.jozufozu.flywheel.util.joml.Vector4fc;
public class ModelPart implements Mesh {
private final int vertices;
private final String name;
private final VertexList reader;
private final @NotNull Vector4f boundingSphere;
public ModelPart(List<PartBuilder.CuboidBuilder> cuboids, String name) {
this.name = name;
@ -35,6 +40,8 @@ public class ModelPart implements Mesh {
reader = writer.intoReader(this.vertices);
}
boundingSphere = ModelUtil.computeBoundingSphere(reader);
}
public static PartBuilder builder(String name, int sizeU, int sizeV) {
@ -60,4 +67,9 @@ public class ModelPart implements Mesh {
public PosTexNormalVertex getVertexType() {
return Formats.POS_TEX_NORMAL;
}
@Override
public Vector4fc getBoundingSphere() {
return boundingSphere;
}
}

View file

@ -35,7 +35,7 @@ public class BufferLayout {
this.stride = calculateStride(this.attributes) + padding;
}
public Collection<VertexAttribute> getAttributes() {
public List<VertexAttribute> getAttributes() {
return attributes;
}

View file

@ -7,6 +7,7 @@ import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.api.vertex.VertexWriter;
import com.jozufozu.flywheel.backend.instancing.instancing.ElementBuffer;
import com.jozufozu.flywheel.core.QuadConverter;
import com.jozufozu.flywheel.util.joml.Vector4fc;
/**
* A mesh that can be rendered by flywheel.
@ -39,6 +40,8 @@ public interface Mesh {
VertexList getReader();
Vector4fc getBoundingSphere();
/**
* @return The number of vertices the model has.
*/

View file

@ -3,6 +3,7 @@ package com.jozufozu.flywheel.core.model;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.dreizak.miniball.highdim.Miniball;
@ -11,6 +12,7 @@ import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.vertex.VertexList;
import com.jozufozu.flywheel.core.Materials;
import com.jozufozu.flywheel.core.vertex.Formats;
import com.jozufozu.flywheel.util.joml.Vector4f;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.datafixers.util.Pair;
@ -74,4 +76,12 @@ public class ModelUtil {
}
return null;
}
@NotNull
public static Vector4f computeBoundingSphere(VertexList reader) {
var miniball = new Miniball(reader);
double[] center = miniball.center();
double radius = miniball.radius();
return new Vector4f((float) center[0], (float) center[1], (float) center[2], (float) radius);
}
}

View file

@ -2,16 +2,21 @@ package com.jozufozu.flywheel.core.model;
import com.jozufozu.flywheel.api.vertex.VertexList;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.util.joml.Vector4f;
import com.jozufozu.flywheel.util.joml.Vector4fc;
public class SimpleMesh implements Mesh {
private final VertexList reader;
private final VertexType vertexType;
private final String name;
private final Vector4f boundingSphere;
public SimpleMesh(VertexList reader, VertexType vertexType, String name) {
this.reader = reader;
this.vertexType = vertexType;
this.name = name;
boundingSphere = ModelUtil.computeBoundingSphere(reader);
}
@Override
@ -29,6 +34,11 @@ public class SimpleMesh implements Mesh {
return reader;
}
@Override
public Vector4fc getBoundingSphere() {
return boundingSphere;
}
@Override
public String toString() {
return "SimpleMesh{" + "name='" + name + "',vertexType='" + vertexType + "}";

View file

@ -184,24 +184,24 @@ public class SourceFile {
return "#use " + '"' + name + '"';
}
public String generateFinalSource(CompilationContext env) {
return generateFinalSource(env, Collections.emptyList());
public String generateFinalSource(CompilationContext context) {
return generateFinalSource(context, Collections.emptyList());
}
public String generateFinalSource(CompilationContext env, List<Pair<Span, String>> replacements) {
public String generateFinalSource(CompilationContext context, List<Pair<Span, String>> replacements) {
var out = new StringBuilder();
for (Import include : flattenedImports) {
SourceFile file = include.getFile();
if (file == null || env.contains(file)) {
if (file == null || context.contains(file)) {
continue;
}
out.append(file.generateLineHeader(env))
out.append(file.generateLineHeader(context))
.append(file.replaceAndElide(replacements));
}
out.append(this.generateLineHeader(env))
out.append(this.generateLineHeader(context))
.append(this.replaceAndElide(replacements));
return out.toString();

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.core.structs.model;
import java.nio.ByteBuffer;
import com.jozufozu.flywheel.api.struct.StorageBufferWriter;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.struct.StructWriter;
import com.jozufozu.flywheel.core.Components;
@ -33,6 +34,11 @@ public class TransformedType implements StructType<TransformedPart> {
return new TransformedWriterUnsafe(this, backing);
}
@Override
public StorageBufferWriter<TransformedPart> getStorageBufferWriter() {
return null; // TODO
}
@Override
public FileResolution getInstanceShader() {
return Components.Files.TRANSFORMED;

View file

@ -0,0 +1,45 @@
package com.jozufozu.flywheel.core.structs.oriented;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.struct.StorageBufferWriter;
public class OrientedStorageWriter implements StorageBufferWriter<OrientedPart> {
public static final OrientedStorageWriter INSTANCE = new OrientedStorageWriter();
private OrientedStorageWriter() {
}
@Override
public void write(final long ptr, OrientedPart d, final int batchID) {
MemoryUtil.memPutFloat(ptr, d.qX);
MemoryUtil.memPutFloat(ptr + 4, d.qY);
MemoryUtil.memPutFloat(ptr + 8, d.qZ);
MemoryUtil.memPutFloat(ptr + 12, d.qW);
MemoryUtil.memPutFloat(ptr + 16, d.posX);
MemoryUtil.memPutFloat(ptr + 20, d.posY);
MemoryUtil.memPutFloat(ptr + 24, d.posZ);
MemoryUtil.memPutFloat(ptr + 28, d.pivotX);
MemoryUtil.memPutFloat(ptr + 32, d.pivotY);
MemoryUtil.memPutFloat(ptr + 36, d.pivotZ);
MemoryUtil.memPutShort(ptr + 40, d.blockLight);
MemoryUtil.memPutShort(ptr + 42, d.skyLight);
MemoryUtil.memPutByte(ptr + 44, d.r);
MemoryUtil.memPutByte(ptr + 45, d.g);
MemoryUtil.memPutByte(ptr + 46, d.b);
MemoryUtil.memPutByte(ptr + 47, d.a);
MemoryUtil.memPutInt(ptr + 48, batchID);
}
@Override
public int getAlignment() {
return 52;
}
}

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.core.structs.oriented;
import java.nio.ByteBuffer;
import com.jozufozu.flywheel.api.struct.StorageBufferWriter;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.struct.StructWriter;
import com.jozufozu.flywheel.core.Components;
@ -36,6 +37,11 @@ public class OrientedType implements StructType<OrientedPart> {
return new OrientedWriterUnsafe(this, backing);
}
@Override
public OrientedStorageWriter getStorageBufferWriter() {
return OrientedStorageWriter.INSTANCE;
}
@Override
public FileResolution getInstanceShader() {
return Components.Files.ORIENTED;

View file

@ -0,0 +1,27 @@
package com.jozufozu.flywheel.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.main.Main;
@Mixin(Main.class)
public class ClientMainMixin {
@Inject(method = "main", at = @At("HEAD"))
private static void injectRenderDoc(CallbackInfo ci) {
// Only try to load RenderDoc if a system property is set.
if (System.getProperty("flw.loadRenderDoc") == null) {
return;
}
try {
System.loadLibrary("renderdoc");
} catch (Throwable ignored) {
// Oh well, we tried.
// On Windows, RenderDoc installs to "C:\Program Files\RenderDoc\"
System.err.println("Is RenderDoc in your PATH?");
}
}
}

View file

@ -1,10 +1,9 @@
#version 450
#define FLW_SUBGROUP_SIZE 32
layout(local_size_x = FLW_SUBGROUP_SIZE) in;
#use "flywheel:compute/objects.glsl"
#use "flywheel:util/quaternion.glsl"
layout(std130, binding = 3) uniform FrameData {
layout(std140, binding = 3) uniform FrameData {
vec4 a1; // vec4(nx.x, px.x, ny.x, py.x)
vec4 a2; // vec4(nx.y, px.y, ny.y, py.y)
vec4 a3; // vec4(nx.z, px.z, ny.z, py.z)
@ -13,23 +12,22 @@ layout(std130, binding = 3) uniform FrameData {
vec2 b2; // vec2(nz.y, pz.y)
vec2 b3; // vec2(nz.z, pz.z)
vec2 b4; // vec2(nz.w, pz.w)
uint drawCount;
} frustum;
// populated by instancers
layout(binding = 0) readonly buffer ObjectBuffer {
layout(std430, binding = 0) readonly buffer ObjectBuffer {
Instance objects[];
};
layout(binding = 1) writeonly buffer TargetBuffer {
layout(std430, binding = 1) writeonly buffer TargetBuffer {
uint objectIDs[];
};
layout(binding = 2) readonly buffer BoundingSpheres {
layout(std430, binding = 2) readonly buffer BoundingSpheres {
vec4 boundingSpheres[];
};
layout(binding = 3) buffer DrawCommands {
layout(std430, binding = 3) buffer DrawCommands {
MeshDrawCommand drawCommands[];
};
@ -44,16 +42,16 @@ bool isVisible(uint objectID, uint batchID) {
vec4 sphere = boundingSpheres[batchID];
vec3 pivot = objects[objectID].pivot;
vec3 center = rotateQuat(sphere.xyz - pivot, objects[objectID].orientation) + pivot + objects[objectID].position;
vec3 center = rotateVertexByQuat(sphere.xyz - pivot, objects[objectID].rotation) + pivot + objects[objectID].pos;
float radius = sphere.r;
return testSphere(center, radius);
return true; //testSphere(center, radius);
}
void main() {
uint objectID = gl_GlobalInvocationID.x;
if (objectID >= frustum.drawCount) {
if (objectID >= objects.length()) {
return;
}

View file

@ -0,0 +1,9 @@
#use "flywheel:api/fragment.glsl"
#use "flywheel:context/world.frag"
#use "flywheel:material/default.frag"
void main() {
flw_initFragment();
flw_materialFragment();
flw_contextFragment();
}

View file

@ -1,22 +1,23 @@
#use "flywheel:api/vertex.glsl"
#use "flywheel:compute/objects.glsl"
#use "flywheel:pos_tex_normal.glsl"
#use "flywheel:layout/block.vert"
#use "flywheel:context/world.vert"
#use "flywheel:util/quaternion.glsl"
// populated by instancers
layout(binding = 0) readonly buffer ObjectBuffer {
layout(std430, binding = 0) readonly buffer ObjectBuffer {
Instance objects[];
};
layout(binding = 1) readonly buffer TargetBuffer {
layout(std430, binding = 1) readonly buffer TargetBuffer {
uint objectIDs[];
};
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);
flw_vertexColor = i.color;
flw_vertexLight = i.light / 15.0;
flw_vertexColor = unpackUnorm4x8(i.color);
flw_vertexLight = unpackUnorm2x16(i.light) / 15.0;
}
void main() {

View file

@ -1,14 +1,14 @@
struct Instance {
ivec2 light;
vec4 color;
vec4 rotation;
vec3 pos;
vec3 pivot;
vec4 rotation;
uint light;
uint color;
uint batchID;
};
struct MeshDrawCommands {
struct MeshDrawCommand {
uint indexCount;
uint instanceCount;
uint firstIndex;

View file

@ -11,6 +11,7 @@
"BufferUploaderMixin",
"ChunkRebuildHooksMixin",
"ClientLevelMixin",
"ClientMainMixin",
"EntityTypeMixin",
"FixFabulousDepthMixin",
"FogUpdateMixin",