diff --git a/.editorconfig b/.editorconfig
index 1eeca6433..2c468ead4 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -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
diff --git a/build.gradle b/build.gradle
index b0d9b40e5..c7f9b0696 100644
--- a/build.gradle
+++ b/build.gradle
@@ -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')
@@ -45,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'
property 'flw.debugMemorySafety', 'true'
arg '-mixin.config=flywheel.mixins.json'
@@ -107,11 +110,32 @@ 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}"
+ 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")
@@ -133,6 +157,7 @@ mixin {
// Workaround for SpongePowered/MixinGradle#38
afterEvaluate {
tasks.configureReobfTaskForReobfJar.mustRunAfter(tasks.compileJava)
+ tasks.configureReobfTaskForReobfJarJar.mustRunAfter(tasks.compileJava)
}
tasks.withType(JavaCompile).configureEach {
@@ -145,6 +170,10 @@ javadoc {
options.addStringOption('Xdoclint:none', '-quiet')
}
+compileJava {
+ options.compilerArgs = ['-Xdiags:verbose']
+}
+
jar {
manifest {
attributes([
@@ -172,7 +201,9 @@ void addLicense(jarTask) {
}
jar.finalizedBy('reobfJar')
+tasks.jarJar.finalizedBy('reobfJarJar')
addLicense(jar)
+addLicense(tasks.jarJar)
publishing {
publications {
@@ -181,6 +212,7 @@ publishing {
from components.java
fg.component(it)
+ jarJar.component(it)
}
}
diff --git a/src/main/java/com/jozufozu/flywheel/Flywheel.java b/src/main/java/com/jozufozu/flywheel/Flywheel.java
index b239c2926..27bb51e40 100644
--- a/src/main/java/com/jozufozu/flywheel/Flywheel.java
+++ b/src/main/java/com/jozufozu/flywheel/Flywheel.java
@@ -7,15 +7,16 @@ import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.RenderWork;
import com.jozufozu.flywheel.backend.ShadersModHandler;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
+import com.jozufozu.flywheel.backend.instancing.PipelineCompiler;
import com.jozufozu.flywheel.backend.instancing.batching.DrawBuffer;
import com.jozufozu.flywheel.config.BackendTypeArgument;
import com.jozufozu.flywheel.config.FlwCommands;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.core.Components;
+import com.jozufozu.flywheel.core.DebugRender;
import com.jozufozu.flywheel.core.PartialModel;
import com.jozufozu.flywheel.core.QuadConverter;
import com.jozufozu.flywheel.core.StitchedSprite;
-import com.jozufozu.flywheel.core.compile.ProgramCompiler;
import com.jozufozu.flywheel.core.model.Models;
import com.jozufozu.flywheel.event.EntityWorldHandler;
import com.jozufozu.flywheel.event.ForgeEvents;
@@ -78,7 +79,7 @@ public class Flywheel {
forgeEventBus.addListener(FlwCommands::registerClientCommands);
forgeEventBus.addListener(EventPriority.HIGHEST, QuadConverter::onReloadRenderers);
- forgeEventBus.addListener(ProgramCompiler::onReloadRenderers);
+ forgeEventBus.addListener(PipelineCompiler::onReloadRenderers);
forgeEventBus.addListener(Models::onReloadRenderers);
forgeEventBus.addListener(DrawBuffer::onReloadRenderers);
@@ -105,6 +106,7 @@ public class Flywheel {
// forgeEventBus.addListener(ExampleEffect::onReload);
Components.init();
+ DebugRender.init();
VanillaInstances.init();
diff --git a/src/main/java/com/jozufozu/flywheel/core/compile/ContextShader.java b/src/main/java/com/jozufozu/flywheel/api/context/ContextShader.java
similarity index 91%
rename from src/main/java/com/jozufozu/flywheel/core/compile/ContextShader.java
rename to src/main/java/com/jozufozu/flywheel/api/context/ContextShader.java
index 66d8c850f..37e9320ed 100644
--- a/src/main/java/com/jozufozu/flywheel/core/compile/ContextShader.java
+++ b/src/main/java/com/jozufozu/flywheel/api/context/ContextShader.java
@@ -1,4 +1,4 @@
-package com.jozufozu.flywheel.core.compile;
+package com.jozufozu.flywheel.api.context;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.core.source.FileResolution;
diff --git a/src/main/java/com/jozufozu/flywheel/api/instance/Instance.java b/src/main/java/com/jozufozu/flywheel/api/instance/Instance.java
index 6cc6e89d8..c52837e45 100644
--- a/src/main/java/com/jozufozu/flywheel/api/instance/Instance.java
+++ b/src/main/java/com/jozufozu/flywheel/api/instance/Instance.java
@@ -1,9 +1,19 @@
package com.jozufozu.flywheel.api.instance;
+import com.jozufozu.flywheel.util.joml.FrustumIntersection;
+
import net.minecraft.core.BlockPos;
public interface Instance {
BlockPos getWorldPosition();
+ /**
+ * Check this instance against a frustum.
+ * An implementor may choose to return a constant to skip the frustum check.
+ * @param frustum A frustum intersection tester for the current frame.
+ * @return {@code true} if this instance should be considered for updates.
+ */
+ boolean checkFrustum(FrustumIntersection frustum);
+
boolean isRemoved();
}
diff --git a/src/main/java/com/jozufozu/flywheel/api/instancer/InstancedPart.java b/src/main/java/com/jozufozu/flywheel/api/instancer/InstancedPart.java
index 50379c54e..e06799ba1 100644
--- a/src/main/java/com/jozufozu/flywheel/api/instancer/InstancedPart.java
+++ b/src/main/java/com/jozufozu/flywheel/api/instancer/InstancedPart.java
@@ -25,12 +25,9 @@ public abstract class InstancedPart {
}
public final boolean checkDirtyAndClear() {
- if (dirty) {
- dirty = false;
- return true;
- } else {
- return false;
- }
+ boolean wasDirty = dirty;
+ dirty = false;
+ return wasDirty;
}
public final boolean isRemoved() {
diff --git a/src/main/java/com/jozufozu/flywheel/api/pipeline/PipelineShader.java b/src/main/java/com/jozufozu/flywheel/api/pipeline/PipelineShader.java
new file mode 100644
index 000000000..155614856
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/api/pipeline/PipelineShader.java
@@ -0,0 +1,8 @@
+package com.jozufozu.flywheel.api.pipeline;
+
+import com.jozufozu.flywheel.backend.gl.GLSLVersion;
+import com.jozufozu.flywheel.core.source.FileResolution;
+
+public record PipelineShader(GLSLVersion glslVersion, FileResolution vertex, FileResolution fragment) {
+
+}
diff --git a/src/main/java/com/jozufozu/flywheel/api/struct/StorageBufferWriter.java b/src/main/java/com/jozufozu/flywheel/api/struct/StorageBufferWriter.java
new file mode 100644
index 000000000..02eb42db9
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/api/struct/StorageBufferWriter.java
@@ -0,0 +1,10 @@
+package com.jozufozu.flywheel.api.struct;
+
+import com.jozufozu.flywheel.api.instancer.InstancedPart;
+
+public interface StorageBufferWriter {
+
+ void write(final long ptr, final T instance);
+
+ int getAlignment();
+}
diff --git a/src/main/java/com/jozufozu/flywheel/api/struct/StructType.java b/src/main/java/com/jozufozu/flywheel/api/struct/StructType.java
index ef14279de..0c35a7124 100644
--- a/src/main/java/com/jozufozu/flywheel/api/struct/StructType.java
+++ b/src/main/java/com/jozufozu/flywheel/api/struct/StructType.java
@@ -29,6 +29,10 @@ public interface StructType {
VertexTransformer getVertexTransformer();
+ StorageBufferWriter getStorageBufferWriter();
+
+ FileResolution getIndirectShader();
+
interface VertexTransformer {
void transform(MutableVertexList vertexList, S struct, ClientLevel level);
}
diff --git a/src/main/java/com/jozufozu/flywheel/api/uniform/UniformProvider.java b/src/main/java/com/jozufozu/flywheel/api/uniform/UniformProvider.java
index 982be51be..06716f2bc 100644
--- a/src/main/java/com/jozufozu/flywheel/api/uniform/UniformProvider.java
+++ b/src/main/java/com/jozufozu/flywheel/api/uniform/UniformProvider.java
@@ -7,7 +7,7 @@ public abstract class UniformProvider {
protected long ptr;
protected Notifier notifier;
- public abstract int getSize();
+ public abstract int getActualByteSize();
public void updatePtr(long ptr, Notifier notifier) {
this.ptr = ptr;
diff --git a/src/main/java/com/jozufozu/flywheel/api/vertex/VertexList.java b/src/main/java/com/jozufozu/flywheel/api/vertex/VertexList.java
index ec47c17c8..b8edf82a9 100644
--- a/src/main/java/com/jozufozu/flywheel/api/vertex/VertexList.java
+++ b/src/main/java/com/jozufozu/flywheel/api/vertex/VertexList.java
@@ -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;
*
* TODO: more flexible elements?
*/
-public interface VertexList {
+public interface VertexList extends PointSet {
float x(int index);
float y(int index);
@@ -78,4 +80,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);
+ };
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/Backend.java b/src/main/java/com/jozufozu/flywheel/backend/Backend.java
index b24df2eb6..b48077615 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/Backend.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/Backend.java
@@ -95,6 +95,7 @@ public class Backend {
case OFF -> true;
case BATCHING -> !usingShaders;
case INSTANCING -> !usingShaders && GlCompat.getInstance().instancedArraysSupported();
+ case INDIRECT -> !usingShaders && GlCompat.getInstance().supportsIndirect();
};
return canUseEngine ? preferredChoice : BackendType.OFF;
diff --git a/src/main/java/com/jozufozu/flywheel/backend/Loader.java b/src/main/java/com/jozufozu/flywheel/backend/Loader.java
index d37b19a23..7400c5cfa 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/Loader.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/Loader.java
@@ -5,10 +5,10 @@ import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.core.ComponentRegistry;
-import com.jozufozu.flywheel.core.compile.ContextShader;
-import com.jozufozu.flywheel.core.compile.ProgramCompiler;
+import com.jozufozu.flywheel.api.context.ContextShader;
+import com.jozufozu.flywheel.backend.instancing.PipelineCompiler;
+import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.source.FileResolution;
-import com.jozufozu.flywheel.core.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.source.ShaderSources;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.util.StringUtil;
@@ -51,8 +51,7 @@ public class Loader implements ResourceManagerReloadListener {
FileResolution.run(errorReporter, sources);
if (errorReporter.hasErrored()) {
- errorReporter.dump();
- throw new ShaderLoadingException("Failed to resolve all source files, see log for details");
+ throw errorReporter.dump();
}
sources.postResolve();
@@ -69,8 +68,8 @@ public class Loader implements ResourceManagerReloadListener {
for (StructType> structType : ComponentRegistry.structTypes) {
for (VertexType vertexType : ComponentRegistry.vertexTypes) {
for (ContextShader contextShader : ComponentRegistry.contextShaders) {
- var ctx = new ProgramCompiler.Context(vertexType, material, structType.getInstanceShader(), contextShader);
- ProgramCompiler.INSTANCE.getProgram(ctx);
+ var ctx = new PipelineCompiler.Context(vertexType, material, structType.getInstanceShader(), contextShader, Components.INSTANCED_ARRAYS);
+ PipelineCompiler.INSTANCE.getProgram(ctx);
}
}
}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/GLSLVersion.java b/src/main/java/com/jozufozu/flywheel/backend/gl/GLSLVersion.java
index eb128ad69..52ac01c2a 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/gl/GLSLVersion.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/gl/GLSLVersion.java
@@ -26,4 +26,8 @@ public enum GLSLVersion {
public String toString() {
return Integer.toString(version);
}
+
+ public String getVersionLine() {
+ return "#version " + version + '\n';
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/array/VertexAttribute.java b/src/main/java/com/jozufozu/flywheel/backend/gl/array/VertexAttribute.java
index 18c458643..d18a568d6 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/gl/array/VertexAttribute.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/gl/array/VertexAttribute.java
@@ -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);
}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/array/VertexAttributeF.java b/src/main/java/com/jozufozu/flywheel/backend/gl/array/VertexAttributeF.java
index 001e73d06..65cb51b7c 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/gl/array/VertexAttributeF.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/gl/array/VertexAttributeF.java
@@ -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);
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/array/VertexAttributeI.java b/src/main/java/com/jozufozu/flywheel/backend/gl/array/VertexAttributeI.java
index d0f0bc903..64934cd3a 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/gl/array/VertexAttributeI.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/gl/array/VertexAttributeI.java
@@ -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);
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/GlProgram.java b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/GlProgram.java
index 84650d9b2..ea80d4cf5 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/GlProgram.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/GlProgram.java
@@ -5,25 +5,19 @@ import static org.lwjgl.opengl.GL20.glGetUniformLocation;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
-import java.nio.FloatBuffer;
-
import org.jetbrains.annotations.NotNull;
-import org.lwjgl.system.MemoryStack;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlObject;
import com.mojang.blaze3d.shaders.ProgramManager;
-import com.mojang.math.Matrix4f;
import net.minecraft.resources.ResourceLocation;
-public abstract class GlProgram extends GlObject {
- private static final FloatBuffer floatBuffer = MemoryStack.stackGet()
- .mallocFloat(16);
+public class GlProgram extends GlObject {
public final ResourceLocation name;
- protected GlProgram(ResourceLocation name, int handle) {
+ public GlProgram(ResourceLocation name, int handle) {
this.name = name;
setHandle(handle);
}
@@ -70,11 +64,6 @@ public abstract class GlProgram extends GlObject {
return samplerUniform;
}
- protected static void uploadMatrixUniform(int uniform, Matrix4f mat) {
- mat.store(floatBuffer);
- glUniformMatrix4fv(uniform, false, floatBuffer);
- }
-
@Override
protected void deleteInternal(int handle) {
glDeleteProgram(handle);
diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/ShaderType.java b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/ShaderType.java
index ca42adc46..f5b106232 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/ShaderType.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/ShaderType.java
@@ -1,10 +1,12 @@
package com.jozufozu.flywheel.backend.gl.shader;
import org.lwjgl.opengl.GL20;
+import org.lwjgl.opengl.GL43;
public enum ShaderType {
VERTEX("vertex", "VERTEX_SHADER", "vert", GL20.GL_VERTEX_SHADER),
FRAGMENT("fragment", "FRAGMENT_SHADER", "frag", GL20.GL_FRAGMENT_SHADER),
+ COMPUTE("compute", "COMPUTE_SHADER", "glsl", GL43.GL_COMPUTE_SHADER),
;
public final String name;
diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/versioned/GlCompat.java b/src/main/java/com/jozufozu/flywheel/backend/gl/versioned/GlCompat.java
index 97a9f6850..8380d111e 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/gl/versioned/GlCompat.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/gl/versioned/GlCompat.java
@@ -31,12 +31,15 @@ public class GlCompat {
public final InstancedArrays instancedArrays;
public final BufferStorage bufferStorage;
public final boolean amd;
+ public final boolean supportsIndirect;
private GlCompat() {
GLCapabilities caps = GL.createCapabilities();
instancedArrays = getLatest(InstancedArrays.class, caps);
bufferStorage = getLatest(BufferStorage.class, caps);
+ supportsIndirect = true;
+
amd = _isAmdWindows();
}
@@ -108,5 +111,9 @@ public class GlCompat {
// vendor string I got was "ATI Technologies Inc."
return vendor.contains("ATI") || vendor.contains("AMD");
}
+
+ public boolean supportsIndirect() {
+ return supportsIndirect;
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/AbstractInstancer.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/AbstractInstancer.java
index 78bf26d6a..9bbcc3c2e 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/instancing/AbstractInstancer.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/AbstractInstancer.java
@@ -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;
@@ -28,7 +29,7 @@ public abstract class AbstractInstancer implements Inst
/**
* Copy a data from another Instancer to this.
- *
+ *
* This has the effect of swapping out one model for another.
* @param inOther the data associated with a different model.
*/
@@ -59,6 +60,14 @@ public abstract class AbstractInstancer implements Inst
return data.size();
}
+ public List getRange(int start, int end) {
+ return data.subList(start, end);
+ }
+
+ public List getAll() {
+ return data;
+ }
+
protected void removeDeletedInstances() {
// Figure out which elements are to be removed.
final int oldSize = this.data.size();
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java
index 297d9d0e9..319fb9607 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java
@@ -15,9 +15,8 @@ import com.jozufozu.flywheel.backend.instancing.ratelimit.NonLimiter;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.light.LightUpdater;
-import com.mojang.math.Vector3f;
+import com.jozufozu.flywheel.util.joml.FrustumIntersection;
-import net.minecraft.client.Camera;
import net.minecraft.core.BlockPos;
public abstract class InstanceManager {
@@ -100,27 +99,26 @@ public abstract class InstanceManager {
int dY = pos.getY() - cY;
int dZ = pos.getZ() - cZ;
- if (tick.shouldUpdate(dX, dY, dZ)) instance.tick();
+ if (!tick.shouldUpdate(dX, dY, dZ)) {
+ return;
+ }
+
+ instance.tick();
}
public void beginFrame(TaskEngine taskEngine, RenderContext context) {
frame.tick();
processQueuedAdditions();
- Camera camera = context.camera();
- Vector3f look = camera.getLookVector();
- float lookX = look.x();
- float lookY = look.y();
- float lookZ = look.z();
-
// integer camera pos
- BlockPos cameraIntPos = camera.getBlockPosition();
+ BlockPos cameraIntPos = context.camera().getBlockPosition();
int cX = cameraIntPos.getX();
int cY = cameraIntPos.getY();
int cZ = cameraIntPos.getZ();
+ FrustumIntersection culler = context.culler();
var instances = getStorage().getInstancesForUpdate();
- distributeWork(taskEngine, instances, instance -> updateInstance(instance, lookX, lookY, lookZ, cX, cY, cZ));
+ distributeWork(taskEngine, instances, instance -> updateInstance(instance, culler, cX, cY, cZ));
}
private static void distributeWork(TaskEngine taskEngine, List instances, Consumer action) {
@@ -140,7 +138,7 @@ public abstract class InstanceManager {
}
}
- protected void updateInstance(DynamicInstance dyn, float lookX, float lookY, float lookZ, int cX, int cY, int cZ) {
+ protected void updateInstance(DynamicInstance dyn, FrustumIntersection test, int cX, int cY, int cZ) {
if (!dyn.decreaseFramerateWithDistance()) {
dyn.beginFrame();
return;
@@ -151,15 +149,14 @@ public abstract class InstanceManager {
int dY = worldPos.getY() - cY;
int dZ = worldPos.getZ() - cZ;
- // is it more than 2 blocks behind the camera?
- int dist = 2;
- float dot = (dX + lookX * dist) * lookX + (dY + lookY * dist) * lookY + (dZ + lookZ * dist) * lookZ;
- if (dot < 0) {
+ if (!frame.shouldUpdate(dX, dY, dZ)) {
return;
}
- if (frame.shouldUpdate(dX, dY, dZ))
+ if (dyn.checkFrustum(test)) {
dyn.beginFrame();
+ }
+
}
public void add(T obj) {
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java
index 4eb9af7d8..387cb98bf 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java
@@ -9,6 +9,7 @@ import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceM
import com.jozufozu.flywheel.backend.instancing.effect.Effect;
import com.jozufozu.flywheel.backend.instancing.effect.EffectInstanceManager;
import com.jozufozu.flywheel.backend.instancing.entity.EntityInstanceManager;
+import com.jozufozu.flywheel.backend.instancing.indirect.IndirectEngine;
import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine;
import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.RenderContext;
@@ -37,6 +38,7 @@ public class InstanceWorld implements AutoCloseable {
public static InstanceWorld create(LevelAccessor level) {
var engine = switch (Backend.getBackendType()) {
+ case INDIRECT -> new IndirectEngine(Components.WORLD);
case INSTANCING -> new InstancingEngine(Components.WORLD, 100 * 100);
case BATCHING -> new BatchingEngine();
case OFF -> throw new IllegalStateException("Cannot create instance world when backend is off.");
@@ -133,10 +135,7 @@ public class InstanceWorld implements AutoCloseable {
*/
public void renderStage(RenderContext context, RenderStage stage) {
taskEngine.syncPoint();
- context.pushPose();
- context.translateBack(context.camera().getPosition());
engine.renderStage(taskEngine, context, stage);
- context.popPose();
}
/**
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/PipelineCompiler.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/PipelineCompiler.java
new file mode 100644
index 000000000..ee78cc709
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/PipelineCompiler.java
@@ -0,0 +1,157 @@
+package com.jozufozu.flywheel.backend.instancing;
+
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+import com.jozufozu.flywheel.api.context.ContextShader;
+import com.jozufozu.flywheel.api.material.Material;
+import com.jozufozu.flywheel.api.pipeline.PipelineShader;
+import com.jozufozu.flywheel.api.vertex.VertexType;
+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.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.SourceFile;
+import com.jozufozu.flywheel.event.ReloadRenderersEvent;
+
+import net.minecraft.resources.ResourceLocation;
+
+/**
+ * A caching compiler.
+ *
+ *
+ * This class is responsible for compiling programs on the fly. An instance of this class will keep a cache of
+ * compiled programs, and will only compile a program if it is not already in the cache.
+ *
+ *
+ * A ProgramCompiler is also responsible for deleting programs and shaders on renderer reload.
+ *
+ */
+public class PipelineCompiler extends Memoizer {
+
+ public static final PipelineCompiler INSTANCE = new PipelineCompiler();
+
+ private final ShaderCompiler shaderCompiler;
+
+ private PipelineCompiler() {
+ this.shaderCompiler = new ShaderCompiler();
+ }
+
+ /**
+ * Get or compile a spec to the given vertex type, accounting for all game state conditions specified by the spec.
+ *
+ * @param ctx The context of compilation.
+ * @return A compiled GlProgram.
+ */
+ public GlProgram getProgram(PipelineCompiler.Context ctx) {
+ return super.get(ctx);
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ shaderCompiler.invalidate();
+ }
+
+ @Override
+ protected GlProgram _create(PipelineCompiler.Context ctx) {
+
+ var glslVersion = ctx.pipelineShader()
+ .glslVersion();
+
+ var vertex = new ShaderCompiler.Context(glslVersion, ShaderType.VERTEX, ctx.getVertexComponents());
+ var fragment = new ShaderCompiler.Context(glslVersion, ShaderType.FRAGMENT, ctx.getFragmentComponents());
+
+ return new ProgramAssembler(ctx.instanceShader.getFileLoc())
+ .attachShader(shaderCompiler.get(vertex))
+ .attachShader(shaderCompiler.get(fragment))
+ .link()
+ .build(ctx.contextShader.factory());
+ }
+
+ @Override
+ protected void _destroy(GlProgram value) {
+ value.delete();
+ }
+
+ public static void onReloadRenderers(ReloadRenderersEvent event) {
+ INSTANCE.invalidate();
+ }
+
+ /**
+ * Represents the entire context of a program's usage.
+ *
+ * @param vertexType The vertexType the program should be adapted for.
+ * @param material The material shader to use. TODO: Flatten materials
+ * @param instanceShader The instance shader to use.
+ * @param contextShader The context shader to use.
+ */
+ public record Context(VertexType vertexType, Material material, FileResolution instanceShader,
+ ContextShader contextShader, PipelineShader pipelineShader) {
+
+ ImmutableList getVertexComponents() {
+ return ImmutableList.of(vertexType.getLayoutShader().getFile(), instanceShader.getFile(), material.getVertexShader().getFile(),
+ contextShader.getVertexShader(), pipelineShader.vertex().getFile());
+ }
+
+ ImmutableList getFragmentComponents() {
+ return ImmutableList.of(material.getFragmentShader().getFile(), contextShader.getFragmentShader(),
+ pipelineShader.fragment().getFile());
+ }
+ }
+
+ /**
+ * Handles compilation and deletion of vertex shaders.
+ */
+ public static class ShaderCompiler extends Memoizer {
+
+ private ShaderCompiler() {
+ }
+
+ @Override
+ protected GlShader _create(Context key) {
+ StringBuilder finalSource = new StringBuilder();
+
+ finalSource.append(key.generateHeader());
+ finalSource.append("#extension GL_ARB_explicit_attrib_location : enable\n");
+ finalSource.append("#extension GL_ARB_conservative_depth : enable\n");
+ finalSource.append("#extension GL_ARB_enhanced_layouts : enable\n");
+
+ var ctx = new CompilationContext();
+
+ var names = ImmutableList.builder();
+ for (var file : key.components) {
+ finalSource.append(file.generateFinalSource(ctx));
+ names.add(file.name);
+ }
+
+ try {
+ return new GlShader(finalSource.toString(), key.shaderType, names.build());
+ } catch (ShaderCompilationException e) {
+ throw e.withErrorLog(ctx);
+ }
+ }
+
+ @Override
+ protected void _destroy(GlShader value) {
+ value.delete();
+ }
+
+ /**
+ * @param glslVersion The GLSL version to use.
+ * @param components A list of shader components to stitch together, in order.
+ */
+ public record Context(GLSLVersion glslVersion, ShaderType shaderType, List components) {
+
+ public String generateHeader() {
+ return CompileUtil.generateHeader(glslVersion, shaderType);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/batching/CPUInstancer.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/batching/CPUInstancer.java
index 97fc5737f..61cc12016 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/instancing/batching/CPUInstancer.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/batching/CPUInstancer.java
@@ -19,14 +19,6 @@ public class CPUInstancer extends AbstractInstancer
}
}
- public List getRange(int start, int end) {
- return data.subList(start, end);
- }
-
- public List getAll() {
- return data;
- }
-
@Override
public void notifyDirty() {
// noop
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/batching/DrawBuffer.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/batching/DrawBuffer.java
index 52675cdfb..053b9d52c 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/instancing/batching/DrawBuffer.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/batching/DrawBuffer.java
@@ -120,6 +120,10 @@ public class DrawBuffer {
}
public void free() {
+ if (memory == null) {
+ return;
+ }
+
memory.free();
memory = null;
buffer = null;
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/blockentity/BlockEntityInstance.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/blockentity/BlockEntityInstance.java
index add54b13b..7689ca9a2 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/instancing/blockentity/BlockEntityInstance.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/blockentity/BlockEntityInstance.java
@@ -14,6 +14,7 @@ import com.jozufozu.flywheel.core.structs.oriented.OrientedPart;
import com.jozufozu.flywheel.core.structs.transformed.TransformedPart;
import com.jozufozu.flywheel.util.box.GridAlignedBB;
import com.jozufozu.flywheel.util.box.ImmutableBox;
+import com.jozufozu.flywheel.util.joml.FrustumIntersection;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.BlockEntity;
@@ -90,16 +91,13 @@ public abstract class BlockEntityInstance extends Abstrac
return pos;
}
- protected InstancerFactory getTransformFactory() {
- return instancerManager.factory(StructTypes.TRANSFORMED);
- }
-
- protected InstancerFactory getOrientedFactory() {
- return instancerManager.factory(StructTypes.ORIENTED);
- }
-
@Override
public ImmutableBox getVolume() {
return GridAlignedBB.from(pos);
}
+
+ @Override
+ public boolean checkFrustum(FrustumIntersection frustum) {
+ return frustum.testAab(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1);
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstance.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstance.java
index 4e6926317..352aa832b 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstance.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstance.java
@@ -8,6 +8,7 @@ import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceM
import com.jozufozu.flywheel.light.LightListener;
import com.jozufozu.flywheel.light.TickingLightListener;
import com.jozufozu.flywheel.util.box.GridAlignedBB;
+import com.jozufozu.flywheel.util.joml.FrustumIntersection;
import com.mojang.math.Vector3f;
import net.minecraft.core.BlockPos;
@@ -96,4 +97,11 @@ public abstract class EntityInstance extends AbstractInstance
public BlockPos getWorldPosition() {
return entity.blockPosition();
}
+
+ @Override
+ public boolean checkFrustum(FrustumIntersection frustum) {
+ AABB aabb = entity.getBoundingBox();
+ return frustum.testAab((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ,
+ (float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ);
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/ComputeCullerCompiler.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/ComputeCullerCompiler.java
new file mode 100644
index 000000000..5f664367b
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/ComputeCullerCompiler.java
@@ -0,0 +1,56 @@
+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.Components;
+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.event.ReloadRenderersEvent;
+
+public class ComputeCullerCompiler extends Memoizer {
+
+ public static final ComputeCullerCompiler INSTANCE = new ComputeCullerCompiler();
+
+ private ComputeCullerCompiler() {
+ }
+
+ @Override
+ protected GlProgram _create(FileResolution file) {
+
+ var finalSource = new StringBuilder();
+ CompilationContext context = new CompilationContext();
+
+ finalSource.append(CompileUtil.generateHeader(GLSLVersion.V460, ShaderType.COMPUTE));
+ finalSource.append(file.getFile()
+ .generateFinalSource(context));
+
+ finalSource.append(Components.Pipeline.INDIRECT_CULL.getFile()
+ .generateFinalSource(context));
+
+ try {
+ var shader = new GlShader(finalSource.toString(), ShaderType.COMPUTE, ImmutableList.of(file.getFileLoc()));
+
+ return new ProgramAssembler(file.getFileLoc()).attachShader(shader)
+ .link()
+ .build(GlProgram::new);
+ } catch (ShaderCompilationException e) {
+ throw e.withErrorLog(context);
+ }
+ }
+
+ @Override
+ protected void _destroy(GlProgram value) {
+ value.delete();
+ }
+
+ public static void invalidateAll(ReloadRenderersEvent ignored) {
+ INSTANCE.invalidate();
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectBuffers.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectBuffers.java
new file mode 100644
index 000000000..f99a6d799
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectBuffers.java
@@ -0,0 +1,223 @@
+package com.jozufozu.flywheel.backend.instancing.indirect;
+
+import static org.lwjgl.opengl.GL45.glCreateBuffers;
+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.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;
+
+import com.jozufozu.flywheel.backend.memory.FlwMemoryTracker;
+import com.jozufozu.flywheel.backend.memory.MemoryBlock;
+
+public class IndirectBuffers {
+ public static final int BUFFER_COUNT = 4;
+ public static final long INT_SIZE = Integer.BYTES;
+ public static final long PTR_SIZE = Pointer.POINTER_SIZE;
+
+ // DRAW COMMAND
+ public static final long DRAW_COMMAND_STRIDE = 36;
+ public static final long DRAW_COMMAND_OFFSET = 0;
+
+ // BITS
+ private static final int SUB_DATA_BITS = GL_DYNAMIC_STORAGE_BIT;
+ private static final int PERSISTENT_BITS = GL_MAP_PERSISTENT_BIT | GL_MAP_WRITE_BIT;
+ private static final int MAP_BITS = PERSISTENT_BITS | GL_MAP_FLUSH_EXPLICIT_BIT;
+ private static final int GPU_ONLY_BITS = 0;
+
+ // OFFSETS
+ private static final long OFFSET_OFFSET = BUFFER_COUNT * INT_SIZE;
+ private static final long SIZE_OFFSET = OFFSET_OFFSET + BUFFER_COUNT * PTR_SIZE;
+ private static final long BUFFERS_SIZE_BYTES = SIZE_OFFSET + BUFFER_COUNT * PTR_SIZE;
+
+ private static final long OBJECT_SIZE_OFFSET = SIZE_OFFSET;
+ private static final long TARGET_SIZE_OFFSET = OBJECT_SIZE_OFFSET + PTR_SIZE;
+ private static final long BATCH_SIZE_OFFSET = TARGET_SIZE_OFFSET + PTR_SIZE;
+ private static final long DRAW_SIZE_OFFSET = BATCH_SIZE_OFFSET + PTR_SIZE;
+
+ final MemoryBlock buffers;
+ final long objectStride;
+ int object;
+ int target;
+ int batch;
+ int draw;
+
+ long objectPtr;
+ long batchPtr;
+ long drawPtr;
+
+ int maxObjectCount = 0;
+ int maxDrawCount = 0;
+
+ float objectGrowthFactor = 2f;
+ float drawGrowthFactor = 2f;
+
+ IndirectBuffers(long objectStride) {
+ this.objectStride = objectStride;
+ this.buffers = MemoryBlock.calloc(BUFFERS_SIZE_BYTES, 1);
+ }
+
+ void createBuffers() {
+ final long ptr = buffers.ptr();
+ nglCreateBuffers(4, ptr);
+ object = MemoryUtil.memGetInt(ptr);
+ target = MemoryUtil.memGetInt(ptr + 4);
+ batch = MemoryUtil.memGetInt(ptr + 8);
+ draw = MemoryUtil.memGetInt(ptr + 12);
+ }
+
+ void updateCounts(int objectCount, int drawCount) {
+
+ if (objectCount > maxObjectCount) {
+ var newObjectCount = maxObjectCount;
+ while (newObjectCount <= objectCount) {
+ newObjectCount *= objectGrowthFactor;
+ }
+ createObjectStorage(newObjectCount);
+ }
+ if (drawCount > maxDrawCount) {
+ var newDrawCount = maxDrawCount;
+ while (newDrawCount <= drawCount) {
+ newDrawCount *= drawGrowthFactor;
+ }
+ createDrawStorage(newDrawCount);
+ }
+
+ final long objectSize = objectStride * objectCount;
+ final long targetSize = INT_SIZE * objectCount;
+ final long drawSize = DRAW_COMMAND_STRIDE * drawCount;
+
+ final long ptr = buffers.ptr();
+ MemoryUtil.memPutAddress(ptr + OBJECT_SIZE_OFFSET, objectSize);
+ MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, targetSize);
+ MemoryUtil.memPutAddress(ptr + BATCH_SIZE_OFFSET, targetSize);
+ MemoryUtil.memPutAddress(ptr + DRAW_SIZE_OFFSET, drawSize);
+ }
+
+ void createObjectStorage(int objectCount) {
+ freeObjectStogare();
+ var objectSize = objectStride * objectCount;
+ var targetSize = INT_SIZE * objectCount;
+
+ if (maxObjectCount > 0) {
+ var ptr = buffers.ptr();
+ nglCreateBuffers(3, ptr);
+
+ int objectNew = MemoryUtil.memGetInt(ptr);
+ int targetNew = MemoryUtil.memGetInt(ptr + 4);
+ int batchNew = MemoryUtil.memGetInt(ptr + 8);
+
+ glNamedBufferStorage(objectNew, objectSize, PERSISTENT_BITS);
+ glNamedBufferStorage(targetNew, targetSize, GPU_ONLY_BITS);
+ glNamedBufferStorage(batchNew, targetSize, PERSISTENT_BITS);
+
+ glCopyNamedBufferSubData(object, objectNew, 0, 0, objectStride * maxObjectCount);
+ glCopyNamedBufferSubData(target, targetNew, 0, 0, INT_SIZE * maxObjectCount);
+ glCopyNamedBufferSubData(batch, batchNew, 0, 0, INT_SIZE * maxObjectCount);
+
+ glDeleteBuffers(object);
+ glDeleteBuffers(target);
+ glDeleteBuffers(batch);
+
+ object = objectNew;
+ target = targetNew;
+ batch = batchNew;
+ } else {
+ glNamedBufferStorage(object, objectSize, PERSISTENT_BITS);
+ glNamedBufferStorage(target, targetSize, GPU_ONLY_BITS);
+ glNamedBufferStorage(batch, targetSize, PERSISTENT_BITS);
+ }
+
+ objectPtr = nglMapNamedBufferRange(object, 0, objectSize, MAP_BITS);
+ batchPtr = nglMapNamedBufferRange(batch, 0, targetSize, MAP_BITS);
+ maxObjectCount = objectCount;
+
+ FlwMemoryTracker._allocGPUMemory(maxObjectCount * objectStride + maxObjectCount * INT_SIZE);
+ }
+
+ void createDrawStorage(int drawCount) {
+ freeDrawStorage();
+
+ var drawSize = DRAW_COMMAND_STRIDE * drawCount;
+ if (maxDrawCount > 0) {
+ int drawNew = glCreateBuffers();
+
+ 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);
+ }
+
+ private void freeObjectStogare() {
+ FlwMemoryTracker._freeGPUMemory(maxObjectCount * objectStride + maxObjectCount * INT_SIZE);
+ }
+
+ private void freeDrawStorage() {
+ FlwMemoryTracker._freeGPUMemory(maxDrawCount * DRAW_COMMAND_STRIDE);
+ }
+
+ public void bindAll() {
+ bindN(BUFFER_COUNT);
+ }
+
+ public void bindObjectAndTarget() {
+ bindN(2);
+ }
+
+ private void bindN(int bufferCount) {
+ if (bufferCount > BUFFER_COUNT) {
+ throw new IllegalArgumentException("Can't bind more than " + BUFFER_COUNT + " buffers");
+ }
+
+ final long ptr = buffers.ptr();
+ nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, bufferCount, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET);
+ }
+
+ void bindIndirectBuffer() {
+ glBindBuffer(GL_DRAW_INDIRECT_BUFFER, draw);
+ }
+
+ void flushBatchIDs(long length) {
+ glFlushMappedNamedBufferRange(batch, 0, length);
+ }
+
+ void flushObjects(long length) {
+ glFlushMappedNamedBufferRange(object, 0, length);
+ }
+
+ void flushDrawCommands(long length) {
+ nglNamedBufferSubData(draw, 0, length, drawPtr);
+ // glFlushMappedNamedBufferRange(this.draw, 0, length);
+ }
+
+ public void delete() {
+ nglDeleteBuffers(BUFFER_COUNT, buffers.ptr());
+ buffers.free();
+ freeObjectStogare();
+ freeDrawStorage();
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectCullingGroup.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectCullingGroup.java
new file mode 100644
index 000000000..f1d21bdca
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectCullingGroup.java
@@ -0,0 +1,197 @@
+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 {
+
+ private static final int BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT;
+
+ final StorageBufferWriter 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 drawSet = new IndirectDrawSet<>();
+
+ private boolean hasCulledThisFrame;
+ private boolean needsMemoryBarrier;
+ private int instanceCountThisFrame;
+
+ IndirectCullingGroup(StructType 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.SHULKER, 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();
+
+ UniformBuffer.getInstance()
+ .sync();
+
+ 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();
+ }
+
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDraw.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDraw.java
new file mode 100644
index 000000000..c4d3cbaea
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDraw.java
@@ -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 {
+ final IndirectInstancer instancer;
+ final IndirectMeshPool.BufferedMesh mesh;
+ final Material material;
+ int baseInstance = -1;
+
+ boolean needsFullWrite = true;
+
+ IndirectDraw(IndirectInstancer 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
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDrawManager.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDrawManager.java
new file mode 100644
index 000000000..956694f59
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDrawManager.java
@@ -0,0 +1,23 @@
+package com.jozufozu.flywheel.backend.instancing.indirect;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.jozufozu.flywheel.api.instancer.InstancedPart;
+import com.jozufozu.flywheel.api.material.Material;
+import com.jozufozu.flywheel.api.struct.StructType;
+import com.jozufozu.flywheel.api.vertex.VertexType;
+import com.jozufozu.flywheel.core.model.Mesh;
+import com.jozufozu.flywheel.util.Pair;
+
+public class IndirectDrawManager {
+
+ public final Map, VertexType>, IndirectCullingGroup>> lists = new HashMap<>();
+
+ @SuppressWarnings("unchecked")
+ public void add(IndirectInstancer instancer, Material material, Mesh mesh) {
+ var indirectList = (IndirectCullingGroup) lists.computeIfAbsent(Pair.of(instancer.type, mesh.getVertexType()), p -> new IndirectCullingGroup<>(p.first(), p.second()));
+
+ indirectList.drawSet.add(instancer, material, indirectList.meshPool.alloc(mesh));
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDrawSet.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDrawSet.java
new file mode 100644
index 000000000..5bf8c18fc
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDrawSet.java
@@ -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 {
+
+ final List> indirectDraws = new ArrayList<>();
+
+ public boolean isEmpty() {
+ return indirectDraws.isEmpty();
+ }
+
+ public int size() {
+ return indirectDraws.size();
+ }
+
+ public void add(IndirectInstancer 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;
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectEngine.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectEngine.java
new file mode 100644
index 000000000..f6585868f
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectEngine.java
@@ -0,0 +1,147 @@
+package com.jozufozu.flywheel.backend.instancing.indirect;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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;
+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.util.WeakHashSet;
+import com.mojang.blaze3d.systems.RenderSystem;
+
+import net.minecraft.client.Camera;
+import net.minecraft.client.Minecraft;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Vec3i;
+import net.minecraft.util.Mth;
+import net.minecraft.world.phys.Vec3;
+
+public class IndirectEngine implements Engine {
+
+ public static int MAX_ORIGIN_DISTANCE = 100;
+
+ protected BlockPos originCoordinate = BlockPos.ZERO;
+
+ protected final ContextShader context;
+
+ protected final Map, IndirectFactory>> factories = new HashMap<>();
+
+ protected final List> uninitializedModels = new ArrayList<>();
+ protected final IndirectDrawManager indirectDrawManager = new IndirectDrawManager();
+
+ /**
+ * The set of instance managers that are attached to this engine.
+ */
+ private final WeakHashSet> instanceManagers;
+
+ public IndirectEngine(ContextShader context) {
+ this.context = context;
+
+ this.instanceManagers = new WeakHashSet<>();
+ }
+
+ @SuppressWarnings("unchecked")
+ @NotNull
+ @Override
+ public IndirectFactory factory(StructType type) {
+ return (IndirectFactory) factories.computeIfAbsent(type, this::createFactory);
+ }
+
+ @NotNull
+ private IndirectFactory createFactory(StructType type) {
+ return new IndirectFactory<>(type, uninitializedModels::add);
+ }
+
+ @Override
+ public void renderStage(TaskEngine taskEngine, RenderContext context, RenderStage stage) {
+ setup();
+
+ for (var list : indirectDrawManager.lists.values()) {
+ list.submit(stage);
+ }
+ }
+
+ private void setup() {
+ GlTextureUnit.T2.makeActive();
+ Minecraft.getInstance().gameRenderer.lightTexture().turnOnLightLayer();
+
+ RenderSystem.depthMask(true);
+ RenderSystem.colorMask(true, true, true, true);
+ RenderSystem.enableDepthTest();
+ RenderSystem.depthFunc(GL32.GL_LEQUAL);
+ RenderSystem.enableCull();
+ }
+
+ @Override
+ public void delete() {
+ factories.values()
+ .forEach(IndirectFactory::delete);
+
+ indirectDrawManager.lists.values()
+ .forEach(IndirectCullingGroup::delete);
+
+ factories.clear();
+ }
+
+ @Override
+ public Vec3i getOriginCoordinate() {
+ return originCoordinate;
+ }
+
+ @Override
+ public void attachManagers(InstanceManager>... listener) {
+ instanceManagers.addAll(List.of(listener));
+ }
+
+ @Override
+ public boolean maintainOriginCoordinate(Camera camera) {
+ Vec3 cameraPos = camera.getPosition();
+
+ double distanceSqr = Vec3.atLowerCornerOf(originCoordinate)
+ .subtract(cameraPos)
+ .lengthSqr();
+
+ if (distanceSqr > MAX_ORIGIN_DISTANCE * MAX_ORIGIN_DISTANCE) {
+ shiftListeners(Mth.floor(cameraPos.x), Mth.floor(cameraPos.y), Mth.floor(cameraPos.z));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void beginFrame(TaskEngine taskEngine, RenderContext context) {
+ for (var model : uninitializedModels) {
+ model.init(indirectDrawManager);
+ }
+ uninitializedModels.clear();
+
+ for (IndirectCullingGroup> value : indirectDrawManager.lists.values()) {
+ value.beginFrame();
+ }
+ }
+
+ private void shiftListeners(int cX, int cY, int cZ) {
+ originCoordinate = new BlockPos(cX, cY, cZ);
+
+ factories.values().forEach(IndirectFactory::clear);
+
+ instanceManagers.forEach(InstanceManager::onOriginShift);
+ }
+
+ @Override
+ public void addDebugInfo(List info) {
+ info.add("GL46 Indirect");
+ info.add("Origin: " + originCoordinate.getX() + ", " + originCoordinate.getY() + ", " + originCoordinate.getZ());
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectFactory.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectFactory.java
new file mode 100644
index 000000000..d0bfaa3b8
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectFactory.java
@@ -0,0 +1,50 @@
+package com.jozufozu.flywheel.backend.instancing.indirect;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import com.jozufozu.flywheel.api.instancer.InstancedPart;
+import com.jozufozu.flywheel.api.instancer.Instancer;
+import com.jozufozu.flywheel.api.instancer.InstancerFactory;
+import com.jozufozu.flywheel.api.struct.StructType;
+import com.jozufozu.flywheel.backend.instancing.AbstractInstancer;
+import com.jozufozu.flywheel.core.model.Model;
+
+public class IndirectFactory implements InstancerFactory {
+
+ protected final Map> models = new HashMap<>();
+ protected final StructType type;
+ private final Consumer> creationListener;
+
+ public IndirectFactory(StructType type, Consumer> creationListener) {
+ this.type = type;
+ this.creationListener = creationListener;
+ }
+
+ @Override
+ public Instancer model(Model modelKey) {
+ return models.computeIfAbsent(modelKey, this::createInstancer).getInstancer();
+ }
+
+ public void delete() {
+ models.values().forEach(IndirectModel::delete);
+ models.clear();
+ }
+
+ /**
+ * Clear all instance data without freeing resources.
+ */
+ public void clear() {
+ models.values()
+ .stream()
+ .map(IndirectModel::getInstancer)
+ .forEach(AbstractInstancer::clear);
+ }
+
+ private IndirectModel createInstancer(Model model) {
+ var instancer = new IndirectModel<>(type, model);
+ this.creationListener.accept(instancer);
+ return instancer;
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectInstancer.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectInstancer.java
new file mode 100644
index 000000000..c1c24ba46
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectInstancer.java
@@ -0,0 +1,74 @@
+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;
+import com.jozufozu.flywheel.core.layout.BufferLayout;
+
+public class IndirectInstancer extends AbstractInstancer {
+
+ public final BufferLayout instanceFormat;
+ public final IndirectModel parent;
+ int instanceCount = 0;
+
+ boolean anyToUpdate;
+
+ public IndirectInstancer(IndirectModel parent, StructType type) {
+ super(type);
+ this.parent = parent;
+ this.instanceFormat = type.getLayout();
+ }
+
+ @Override
+ public void notifyDirty() {
+ anyToUpdate = true;
+ }
+
+ public boolean isEmpty() {
+ return !anyToUpdate && !anyToRemove && instanceCount == 0;
+ }
+
+ void update() {
+ if (anyToRemove) {
+ removeDeletedInstances();
+ }
+
+ instanceCount = data.size();
+
+ 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
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectMeshPool.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectMeshPool.java
new file mode 100644
index 000000000..367e4ffdb
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectMeshPool.java
@@ -0,0 +1,127 @@
+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.backend.memory.MemoryBlock;
+import com.jozufozu.flywheel.core.model.Mesh;
+
+public class IndirectMeshPool {
+
+ private final Map meshes = new HashMap<>();
+ private final List meshList = new ArrayList<>();
+
+ final VertexType vertexType;
+
+ final int vbo;
+ private final MemoryBlock 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 = MemoryBlock.malloc(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;
+
+ final long ptr = clientStorage.ptr();
+
+ int byteIndex = 0;
+ int baseVertex = 0;
+ for (BufferedMesh model : meshList) {
+ model.byteIndex = byteIndex;
+ model.baseVertex = baseVertex;
+
+ model.buffer(ptr);
+
+ byteIndex += model.getByteSize();
+ baseVertex += model.mesh.getVertexCount();
+ }
+
+ nglNamedBufferSubData(vbo, 0, byteIndex, ptr);
+ }
+
+ public void delete() {
+ clientStorage.free();
+ glDeleteBuffers(vbo);
+ meshes.clear();
+ meshList.clear();
+ }
+
+ public class BufferedMesh {
+
+ public final Mesh mesh;
+ private final int vertexCount;
+ private long byteIndex;
+ private int baseVertex;
+
+ public BufferedMesh(Mesh mesh) {
+ this.mesh = mesh;
+
+ vertexCount = mesh.getVertexCount();
+ }
+
+ private void buffer(long ptr) {
+ this.mesh.write(ptr + byteIndex);
+ }
+
+ public int getByteSize() {
+ return IndirectMeshPool.this.vertexType.getLayout().getStride() * this.vertexCount;
+ }
+
+ public int getBaseVertex() {
+ return baseVertex;
+ }
+
+ public int getVertexCount() {
+ return this.vertexCount;
+ }
+
+ public int getIndexCount() {
+ return this.vertexCount * 6 / 4;
+ }
+ }
+
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectModel.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectModel.java
new file mode 100644
index 000000000..29638638b
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectModel.java
@@ -0,0 +1,43 @@
+package com.jozufozu.flywheel.backend.instancing.indirect;
+
+import com.jozufozu.flywheel.api.instancer.InstancedPart;
+import com.jozufozu.flywheel.api.struct.StructType;
+import com.jozufozu.flywheel.core.model.Model;
+
+public class IndirectModel {
+
+ private final Model model;
+ private final IndirectInstancer instancer;
+
+ public IndirectModel(StructType type, Model model) {
+ this.model = model;
+ this.instancer = new IndirectInstancer<>(this, type);
+ }
+
+ public void init(IndirectDrawManager indirectDrawManager) {
+ var materialMeshMap = this.model.getMeshes();
+ for (var entry : materialMeshMap.entrySet()) {
+ var material = entry.getKey();
+ var mesh = entry.getValue();
+ indirectDrawManager.add(instancer, material, mesh);
+
+ return; // TODO: support multiple meshes per model
+ }
+ }
+
+ public IndirectInstancer getInstancer() {
+ return instancer;
+ }
+
+ public Model getModel() {
+ return model;
+ }
+
+ public int getVertexCount() {
+ return model.getVertexCount() * instancer.instanceCount;
+ }
+
+ public void delete() {
+
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/ShaderState.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/ShaderState.java
new file mode 100644
index 000000000..7f1844eb2
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/ShaderState.java
@@ -0,0 +1,8 @@
+package com.jozufozu.flywheel.backend.instancing.indirect;
+
+import com.jozufozu.flywheel.api.material.Material;
+import com.jozufozu.flywheel.api.struct.StructType;
+import com.jozufozu.flywheel.api.vertex.VertexType;
+
+public record ShaderState(Material material, VertexType vertex, StructType> instance) {
+}
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/package-info.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/package-info.java
new file mode 100644
index 000000000..6ec4f9bda
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/package-info.java
@@ -0,0 +1,6 @@
+@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
+package com.jozufozu.flywheel.backend.instancing.indirect;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import net.minecraft.MethodsReturnNonnullByDefault;
diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java
index 0417d9124..23f5ba6df 100644
--- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java
+++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java
@@ -16,10 +16,11 @@ import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
+import com.jozufozu.flywheel.backend.instancing.PipelineCompiler;
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
+import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.RenderContext;
-import com.jozufozu.flywheel.core.compile.ContextShader;
-import com.jozufozu.flywheel.core.compile.ProgramCompiler;
+import com.jozufozu.flywheel.api.context.ContextShader;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.uniform.UniformBuffer;
import com.jozufozu.flywheel.util.WeakHashSet;
@@ -122,9 +123,9 @@ public class InstancingEngine implements Engine {
.getInstanceShader();
Material material = desc.material();
- var ctx = new ProgramCompiler.Context(vertexType, material, instanceShader, context);
+ var ctx = new PipelineCompiler.Context(vertexType, material, instanceShader, context, Components.INSTANCED_ARRAYS);
- ProgramCompiler.INSTANCE.getProgram(ctx)
+ PipelineCompiler.INSTANCE.getProgram(ctx)
.bind();
UniformBuffer.getInstance().sync();
}
diff --git a/src/main/java/com/jozufozu/flywheel/config/BackendType.java b/src/main/java/com/jozufozu/flywheel/config/BackendType.java
index 822c28973..a64da9ddd 100644
--- a/src/main/java/com/jozufozu/flywheel/config/BackendType.java
+++ b/src/main/java/com/jozufozu/flywheel/config/BackendType.java
@@ -19,6 +19,11 @@ public enum BackendType {
* Use GPU instancing to render everything.
*/
INSTANCING("GL33 Instanced Arrays"),
+
+ /**
+ * Use Compute shaders to cull instances.
+ */
+ INDIRECT("GL46 Compute Culling"),
;
private static final Map lookup;
diff --git a/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java b/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java
index c9365b451..5fb8a5032 100644
--- a/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java
+++ b/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java
@@ -5,6 +5,7 @@ import java.util.function.BiConsumer;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.backend.Backend;
+import com.jozufozu.flywheel.core.uniform.FrustumProvider;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
@@ -109,6 +110,23 @@ public class FlwCommands {
return 1;
}))));
+ commandBuilder.command.then(Commands.literal("debugFrustum")
+ .then(Commands.literal("pause")
+ .executes(context -> {
+ FrustumProvider.PAUSED = true;
+ return 1;
+ }))
+ .then(Commands.literal("unpause")
+ .executes(context -> {
+ FrustumProvider.PAUSED = false;
+ return 1;
+ }))
+ .then(Commands.literal("capture")
+ .executes(context -> {
+ FrustumProvider.CAPTURE = true;
+ return 1;
+ })));
+
commandBuilder.build(event.getDispatcher());
}
@@ -141,6 +159,7 @@ public class FlwCommands {
case OFF -> new TextComponent("Disabled Flywheel").withStyle(ChatFormatting.RED);
case INSTANCING -> new TextComponent("Using Instancing Engine").withStyle(ChatFormatting.GREEN);
case BATCHING -> new TextComponent("Using Batching Engine").withStyle(ChatFormatting.GREEN);
+ case INDIRECT -> new TextComponent("Using Indirect Engine").withStyle(ChatFormatting.GREEN);
};
}
diff --git a/src/main/java/com/jozufozu/flywheel/core/ComponentRegistry.java b/src/main/java/com/jozufozu/flywheel/core/ComponentRegistry.java
index 6200c4679..c44251baf 100644
--- a/src/main/java/com/jozufozu/flywheel/core/ComponentRegistry.java
+++ b/src/main/java/com/jozufozu/flywheel/core/ComponentRegistry.java
@@ -11,7 +11,7 @@ import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.uniform.UniformProvider;
import com.jozufozu.flywheel.api.vertex.VertexType;
-import com.jozufozu.flywheel.core.compile.ContextShader;
+import com.jozufozu.flywheel.api.context.ContextShader;
import net.minecraft.resources.ResourceLocation;
diff --git a/src/main/java/com/jozufozu/flywheel/core/Components.java b/src/main/java/com/jozufozu/flywheel/core/Components.java
index 3edc8496d..ab85c20d4 100644
--- a/src/main/java/com/jozufozu/flywheel/core/Components.java
+++ b/src/main/java/com/jozufozu/flywheel/core/Components.java
@@ -3,7 +3,9 @@ package com.jozufozu.flywheel.core;
import java.util.function.BiConsumer;
import com.jozufozu.flywheel.Flywheel;
-import com.jozufozu.flywheel.core.compile.ContextShader;
+import com.jozufozu.flywheel.api.context.ContextShader;
+import com.jozufozu.flywheel.api.pipeline.PipelineShader;
+import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.core.crumbling.CrumblingProgram;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.SourceChecks;
@@ -11,6 +13,7 @@ import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.core.structs.StructTypes;
import com.jozufozu.flywheel.core.uniform.FogProvider;
+import com.jozufozu.flywheel.core.uniform.FrustumProvider;
import com.jozufozu.flywheel.core.uniform.ViewProvider;
import com.jozufozu.flywheel.core.vertex.Formats;
import com.jozufozu.flywheel.util.ResourceUtil;
@@ -22,9 +25,13 @@ public class Components {
public static final ViewProvider VIEW_PROVIDER = ComponentRegistry.register(new ViewProvider());
public static final FogProvider FOG_PROVIDER = ComponentRegistry.register(new FogProvider());
+ public static final FrustumProvider FRUSTUM_PROVIDER = ComponentRegistry.register(new FrustumProvider());
public static final ContextShader WORLD = ComponentRegistry.register(new ContextShader(WorldProgram::new, Files.WORLD_VERTEX, Files.WORLD_FRAGMENT));
public static final ContextShader CRUMBLING = ComponentRegistry.register(new ContextShader(CrumblingProgram::new, Files.WORLD_VERTEX, Files.CRUMBLING_FRAGMENT));
+ public static final PipelineShader INSTANCED_ARRAYS = new PipelineShader(GLSLVersion.V420, Pipeline.INSTANCED_ARRAYS_DRAW, Pipeline.DRAW_FRAGMENT);
+ public static final PipelineShader INDIRECT = new PipelineShader(GLSLVersion.V460, Pipeline.INDIRECT_DRAW, Pipeline.DRAW_FRAGMENT);
+
public static void init() {
Files.init();
Formats.init();
@@ -32,14 +39,29 @@ public class Components {
Materials.init();
}
+ public static class Pipeline {
+ public static final FileResolution DRAW_FRAGMENT = pipeline("pipeline/draw.frag");
+ public static final FileResolution INSTANCED_ARRAYS_DRAW = pipeline("pipeline/instanced_arrays_draw.vert");
+ public static final FileResolution INDIRECT_DRAW = pipeline("pipeline/indirect_draw.vert");
+ public static final FileResolution INDIRECT_CULL = pipeline("pipeline/indirect_cull.glsl");
+
+ private static FileResolution pipeline(String name) {
+ return FileResolution.get(Flywheel.rl(name))
+ .validateWith(Checks.PIPELINE);
+ }
+ }
+
public static class Files {
public static final FileResolution VIEW_UNIFORMS = uniform(Flywheel.rl("uniform/view.glsl"));
public static final FileResolution FOG_UNIFORMS = uniform(Flywheel.rl("uniform/fog.glsl"));
+ public static final FileResolution FRUSTUM_UNIFORMS = uniform(Flywheel.rl("uniform/frustum.glsl"));
public static final FileResolution BLOCK_LAYOUT = layoutVertex(ResourceUtil.subPath(Names.BLOCK, ".vert"));
public static final FileResolution POS_TEX_NORMAL_LAYOUT = layoutVertex(ResourceUtil.subPath(Names.POS_TEX_NORMAL, ".vert"));
public static final FileResolution TRANSFORMED = instanceVertex(ResourceUtil.subPath(Names.TRANSFORMED, ".vert"));
+ public static final FileResolution TRANSFORMED_INDIRECT = instanceVertex(ResourceUtil.subPath(Names.TRANSFORMED, "_indirect.glsl"));
public static final FileResolution ORIENTED = instanceVertex(ResourceUtil.subPath(Names.ORIENTED, ".vert"));
+ public static final FileResolution ORIENTED_INDIRECT = instanceVertex(ResourceUtil.subPath(Names.ORIENTED, "_indirect.glsl"));
public static final FileResolution DEFAULT_VERTEX = materialVertex(ResourceUtil.subPath(Names.DEFAULT, ".vert"));
public static final FileResolution SHADED_VERTEX = materialVertex(ResourceUtil.subPath(Names.SHADED, ".vert"));
public static final FileResolution DEFAULT_FRAGMENT = materialFragment(ResourceUtil.subPath(Names.DEFAULT, ".frag"));
@@ -49,6 +71,10 @@ 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"));
+ private static FileResolution compute(ResourceLocation rl) {
+ return FileResolution.get(rl);
+ }
+
private static FileResolution uniform(ResourceLocation location) {
return FileResolution.get(location);
}
@@ -59,8 +85,7 @@ public class Components {
}
private static FileResolution instanceVertex(ResourceLocation location) {
- return FileResolution.get(location)
- .validateWith(Checks.INSTANCE_VERTEX);
+ return FileResolution.get(location); // .validateWith(Checks.INSTANCE_VERTEX);
}
private static FileResolution materialVertex(ResourceLocation location) {
@@ -90,12 +115,16 @@ public class Components {
public static class Checks {
- public static final BiConsumer LAYOUT_VERTEX = SourceChecks.checkFunctionArity("flw_layoutVertex", 0);
- public static final BiConsumer INSTANCE_VERTEX = SourceChecks.checkFunctionArity("flw_instanceVertex", 0);
+ public static final BiConsumer LAYOUT_VERTEX = SourceChecks.checkFunctionArity("flw_layoutVertex", 0)
+ .andThen(SourceChecks.checkDefine("FLW_INSTANCE_BASE_INDEX"));
+ public static final BiConsumer INSTANCE_VERTEX = SourceChecks.checkFunctionParameterTypeExists("flw_instanceVertex", 1, 0)
+ .andThen(SourceChecks.checkDefine("FLW_INSTANCE_STRUCT"));
public static final BiConsumer MATERIAL_VERTEX = SourceChecks.checkFunctionArity("flw_materialVertex", 0);
public static final BiConsumer MATERIAL_FRAGMENT = SourceChecks.checkFunctionArity("flw_materialFragment", 0);
public static final BiConsumer CONTEXT_VERTEX = SourceChecks.checkFunctionArity("flw_contextVertex", 0);
public static final BiConsumer CONTEXT_FRAGMENT = SourceChecks.checkFunctionArity("flw_contextFragment", 0).andThen(SourceChecks.checkFunctionArity("flw_initFragment", 0));
+
+ public static final BiConsumer PIPELINE = SourceChecks.checkFunctionArity("main", 0);
}
public static class Names {
@@ -110,5 +139,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");
}
}
diff --git a/src/main/java/com/jozufozu/flywheel/core/DebugRender.java b/src/main/java/com/jozufozu/flywheel/core/DebugRender.java
new file mode 100644
index 000000000..10c8e414c
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/core/DebugRender.java
@@ -0,0 +1,104 @@
+package com.jozufozu.flywheel.core;
+
+import org.lwjgl.opengl.GL46;
+import org.lwjgl.system.MemoryStack;
+
+import com.jozufozu.flywheel.Flywheel;
+import com.jozufozu.flywheel.backend.gl.GlStateTracker;
+import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
+import com.jozufozu.flywheel.core.compile.DebugCompiler;
+import com.jozufozu.flywheel.core.source.FileResolution;
+import com.jozufozu.flywheel.util.Lazy;
+import com.jozufozu.flywheel.util.joml.FrustumIntersection;
+import com.mojang.blaze3d.systems.RenderSystem;
+
+public class DebugRender {
+
+ private static final Lazy SHADER = Lazy.of(() -> DebugCompiler.INSTANCE.get(new DebugCompiler.Context(Files.VERTEX, Files.FRAGMENT)));
+
+ private static final Lazy FRUSTUM_VBO = Lazy.of(Frustum::new);
+
+ public static void init() {
+ Files.init();
+ }
+
+ public static void updateFrustum(FrustumIntersection culler) {
+ FRUSTUM_VBO.get()
+ .upload(culler);
+ }
+
+ public static void drawFrustum() {
+ if (!FRUSTUM_VBO.isInitialized()) {
+ return;
+ }
+
+ RenderSystem.disableCull();
+ RenderSystem.enableBlend();
+ RenderSystem.defaultBlendFunc();
+
+ try (var ignored = GlStateTracker.getRestoreState()) {
+ SHADER.get()
+ .bind();
+ FRUSTUM_VBO.get()
+ .draw();
+ }
+ }
+
+ public static class Files {
+ public static final FileResolution VERTEX = FileResolution.get(Flywheel.rl("debug/debug.vert"));
+ public static final FileResolution FRAGMENT = FileResolution.get(Flywheel.rl("debug/debug.frag"));
+
+ public static void init() {
+
+ }
+ }
+
+ // FIXME: This never worked (and the thing it was meant to debug is already fixed),
+ // but it should be a quick turnaround
+ private static class Frustum {
+ private static final int[] indices = new int[]{
+ 0, 2, 3, 0, 3, 1,
+ 2, 6, 7, 2, 7, 3,
+ 6, 4, 5, 6, 5, 7,
+ 4, 0, 1, 4, 1, 5,
+ 0, 4, 6, 0, 6, 2,
+ 1, 5, 7, 1, 7, 3,
+ };
+
+ private static final int elementCount = indices.length;
+ private static final int indicesSize = elementCount * 4;
+ private static final int verticesSize = 3 * 8 * 4;
+ private final int buffer;
+ private final int vao;
+
+ public Frustum() {
+ // holy moly DSA is nice
+ buffer = GL46.glCreateBuffers();
+ GL46.glNamedBufferStorage(buffer, verticesSize + indicesSize, GL46.GL_DYNAMIC_STORAGE_BIT);
+ GL46.glNamedBufferSubData(buffer, 0, indices);
+
+ vao = GL46.glCreateVertexArrays();
+ GL46.glEnableVertexArrayAttrib(vao, 0);
+ GL46.glVertexArrayElementBuffer(vao, buffer);
+ GL46.glVertexArrayVertexBuffer(vao, 0, buffer, indicesSize, 3 * 4);
+ GL46.glVertexArrayAttribFormat(vao, 0, 3, GL46.GL_FLOAT, false, 0);
+ }
+
+ public void upload(FrustumIntersection culler) {
+ try (var stack = MemoryStack.stackPush()) {
+ var buf = stack.malloc(3 * 8 * 4);
+
+ culler.getCorners(buf);
+
+ GL46.glNamedBufferSubData(buffer, indicesSize, buf);
+ }
+ }
+
+ public void draw() {
+ GL46.glEnableVertexArrayAttrib(vao, 0);
+ GL46.glVertexArrayElementBuffer(vao, buffer);
+ GL46.glBindVertexArray(vao);
+ GL46.glDrawElements(GL46.GL_TRIANGLES, elementCount, GL46.GL_UNSIGNED_INT, 0);
+ }
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/core/RenderContext.java b/src/main/java/com/jozufozu/flywheel/core/RenderContext.java
index 6fd3effbb..4664a052a 100644
--- a/src/main/java/com/jozufozu/flywheel/core/RenderContext.java
+++ b/src/main/java/com/jozufozu/flywheel/core/RenderContext.java
@@ -2,11 +2,10 @@ package com.jozufozu.flywheel.core;
import org.jetbrains.annotations.NotNull;
-import com.jozufozu.flywheel.util.transform.TransformStack;
+import com.jozufozu.flywheel.extension.Matrix4fExtension;
+import com.jozufozu.flywheel.util.joml.FrustumIntersection;
import com.mojang.blaze3d.vertex.PoseStack;
-import com.mojang.math.Matrix3f;
import com.mojang.math.Matrix4f;
-import com.mojang.math.Quaternion;
import net.minecraft.client.Camera;
import net.minecraft.client.multiplayer.ClientLevel;
@@ -14,44 +13,7 @@ import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.RenderBuffers;
public record RenderContext(LevelRenderer renderer, ClientLevel level, PoseStack stack, Matrix4f viewProjection,
- Matrix4f projection, RenderBuffers buffers, Camera camera) implements TransformStack {
-
- @Override
- public TransformStack multiply(Quaternion quaternion) {
- return TransformStack.cast(stack).multiply(quaternion);
- }
-
- @Override
- public TransformStack scale(float factorX, float factorY, float factorZ) {
- return TransformStack.cast(stack).scale(factorX, factorY, factorZ);
- }
-
- @Override
- public TransformStack pushPose() {
- stack.pushPose();
- return TransformStack.cast(stack);
- }
-
- @Override
- public TransformStack popPose() {
- stack.popPose();
- return TransformStack.cast(stack);
- }
-
- @Override
- public TransformStack mulPose(Matrix4f pose) {
- return TransformStack.cast(stack).mulPose(pose);
- }
-
- @Override
- public TransformStack mulNormal(Matrix3f normal) {
- return TransformStack.cast(stack).mulNormal(normal);
- }
-
- @Override
- public TransformStack translate(double x, double y, double z) {
- return TransformStack.cast(stack).translate(x, y, z);
- }
+ Matrix4f projection, RenderBuffers buffers, Camera camera, FrustumIntersection culler) {
@NotNull
public static Matrix4f createViewProjection(PoseStack view, Matrix4f projection) {
@@ -59,4 +21,12 @@ public record RenderContext(LevelRenderer renderer, ClientLevel level, PoseStack
viewProjection.multiply(view.last().pose());
return viewProjection;
}
+
+ public static FrustumIntersection createCuller(Matrix4f viewProjection, float camX, float camY, float camZ) {
+ com.jozufozu.flywheel.util.joml.Matrix4f proj = Matrix4fExtension.clone(viewProjection);
+
+ proj.translate(camX, camY, camZ);
+
+ return new FrustumIntersection(proj);
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/core/compile/CompileUtil.java b/src/main/java/com/jozufozu/flywheel/core/compile/CompileUtil.java
index 19784f7fb..d4641970e 100644
--- a/src/main/java/com/jozufozu/flywheel/core/compile/CompileUtil.java
+++ b/src/main/java/com/jozufozu/flywheel/core/compile/CompileUtil.java
@@ -16,10 +16,8 @@ public class CompileUtil {
public static final Pattern vecType = Pattern.compile("^[biud]?vec([234])$");
public static final Pattern matType = Pattern.compile("^mat([234])(?:x([234]))?$");
- protected static String generateHeader(GLSLVersion version, ShaderType type) {
- return "#version " + version + '\n'
- + "#extension GL_ARB_explicit_attrib_location : enable\n"
- + "#extension GL_ARB_conservative_depth : enable\n"
+ public static String generateHeader(GLSLVersion version, ShaderType type) {
+ return version.getVersionLine()
+ type.getDefineStatement()
+ '\n';
}
diff --git a/src/main/java/com/jozufozu/flywheel/core/compile/DebugCompiler.java b/src/main/java/com/jozufozu/flywheel/core/compile/DebugCompiler.java
new file mode 100644
index 000000000..319d2dc11
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/core/compile/DebugCompiler.java
@@ -0,0 +1,89 @@
+package com.jozufozu.flywheel.core.compile;
+
+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.source.CompilationContext;
+import com.jozufozu.flywheel.core.source.FileResolution;
+import com.jozufozu.flywheel.event.ReloadRenderersEvent;
+
+/**
+ * Simple shader compiler that pulls no excessive tricks.
+ * Useful for writing experimental shaders or
+ */
+public class DebugCompiler extends Memoizer {
+
+ public static final DebugCompiler INSTANCE = new DebugCompiler();
+
+ private final ShaderCompiler shaderCompiler;
+
+ private DebugCompiler() {
+ this.shaderCompiler = new ShaderCompiler();
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ shaderCompiler.invalidate();
+ }
+
+ @Override
+ protected GlProgram _create(DebugCompiler.Context ctx) {
+
+ return new ProgramAssembler(ctx.vertex.getFileLoc())
+ .attachShader(shaderCompiler.vertex(ctx.vertex))
+ .attachShader(shaderCompiler.fragment(ctx.fragment))
+ .link()
+ .build(GlProgram::new);
+ }
+
+ @Override
+ protected void _destroy(GlProgram value) {
+ value.delete();
+ }
+
+ public static void invalidateAll(ReloadRenderersEvent ignored) {
+ INSTANCE.invalidate();
+ }
+
+ public record Context(FileResolution vertex, FileResolution fragment) {
+ }
+
+ /**
+ * Handles compilation and deletion of vertex shaders.
+ */
+ private static class ShaderCompiler extends Memoizer {
+
+ public GlShader vertex(FileResolution source) {
+ return get(new Context(source, ShaderType.VERTEX));
+ }
+
+ public GlShader fragment(FileResolution source) {
+ return get(new Context(source, ShaderType.FRAGMENT));
+ }
+
+ @Override
+ protected GlShader _create(Context ctx) {
+ var index = new CompilationContext();
+
+ String source = CompileUtil.generateHeader(GLSLVersion.V420, ctx.type) + ctx.source.getFile()
+ .generateFinalSource(index);
+
+ try {
+ return new GlShader(source, ctx.type, ImmutableList.of(ctx.source.getFileLoc()));
+ } catch (ShaderCompilationException e) {
+ throw e.withErrorLog(index);
+ }
+ }
+
+ @Override
+ protected void _destroy(GlShader value) {
+ value.delete();
+ }
+
+ public record Context(FileResolution source, ShaderType type) {
+ }
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/core/compile/FragmentCompiler.java b/src/main/java/com/jozufozu/flywheel/core/compile/FragmentCompiler.java
deleted file mode 100644
index 2923de834..000000000
--- a/src/main/java/com/jozufozu/flywheel/core/compile/FragmentCompiler.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.jozufozu.flywheel.core.compile;
-
-import com.google.common.collect.ImmutableList;
-import com.jozufozu.flywheel.backend.gl.GLSLVersion;
-import com.jozufozu.flywheel.backend.gl.shader.GlShader;
-import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
-import com.jozufozu.flywheel.core.source.CompilationContext;
-import com.jozufozu.flywheel.core.source.SourceFile;
-
-/**
- * Handles compilation and deletion of fragment shaders.
- */
-public class FragmentCompiler extends Memoizer {
-
- public FragmentCompiler() {
- }
-
- @Override
- protected GlShader _create(Context key) {
- StringBuilder finalSource = new StringBuilder();
-
- finalSource.append(CompileUtil.generateHeader(GLSLVersion.V420, ShaderType.FRAGMENT));
-
- var ctx = new CompilationContext();
-
- // MATERIAL
-
- SourceFile materialShader = key.materialShader;
- finalSource.append(materialShader.generateFinalSource(ctx));
-
- // CONTEXT
-
- SourceFile contextShaderSource = key.contextShader;
- finalSource.append(contextShaderSource.generateFinalSource(ctx));
-
- // MAIN
-
- finalSource.append(generateFooter());
-
- try {
- return new GlShader(finalSource.toString(), ShaderType.FRAGMENT, ImmutableList.of(materialShader.name, contextShaderSource.name));
- } catch (ShaderCompilationException e) {
- throw e.withErrorLog(ctx);
- }
- }
-
- protected String generateFooter() {
- return """
- void main() {
- flw_initFragment();
-
- flw_materialFragment();
-
- flw_contextFragment();
- }
- """;
- }
-
- @Override
- protected void _destroy(GlShader value) {
- value.delete();
- }
-
- /**
- * Represents the conditions under which a shader is compiled.
- *
- * @param materialShader The fragment material shader source.
- */
- public record Context(SourceFile materialShader, SourceFile contextShader) {
-
- }
-}
diff --git a/src/main/java/com/jozufozu/flywheel/core/compile/ProgramCompiler.java b/src/main/java/com/jozufozu/flywheel/core/compile/ProgramCompiler.java
deleted file mode 100644
index 9aee2efc8..000000000
--- a/src/main/java/com/jozufozu/flywheel/core/compile/ProgramCompiler.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.jozufozu.flywheel.core.compile;
-
-import com.jozufozu.flywheel.api.material.Material;
-import com.jozufozu.flywheel.api.vertex.VertexType;
-import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
-import com.jozufozu.flywheel.core.source.FileResolution;
-import com.jozufozu.flywheel.event.ReloadRenderersEvent;
-
-/**
- * A caching compiler.
- *
- *
- * This class is responsible for compiling programs on the fly. An instance of this class will keep a cache of
- * compiled programs, and will only compile a program if it is not already in the cache.
- *
- *
- * A ProgramCompiler is also responsible for deleting programs and shaders on renderer reload.
- *
- */
-public class ProgramCompiler extends Memoizer {
-
- public static final ProgramCompiler INSTANCE = new ProgramCompiler();
-
- private final VertexCompiler vertexCompiler;
- private final FragmentCompiler fragmentCompiler;
-
- private ProgramCompiler() {
- this.vertexCompiler = new VertexCompiler();
- this.fragmentCompiler = new FragmentCompiler();
- }
-
- /**
- * Get or compile a spec to the given vertex type, accounting for all game state conditions specified by the spec.
- *
- * @param ctx The context of compilation.
- * @return A compiled GlProgram.
- */
- public GlProgram getProgram(ProgramCompiler.Context ctx) {
- return super.get(ctx);
- }
-
- @Override
- public void invalidate() {
- super.invalidate();
- vertexCompiler.invalidate();
- fragmentCompiler.invalidate();
- }
-
- @Override
- protected GlProgram _create(ProgramCompiler.Context ctx) {
- // TODO: try-catch here to prevent crashing if shaders failed to compile
- Material material = ctx.material;
- FileResolution instanceShader = ctx.instanceShader();
- ContextShader contextShader = ctx.contextShader;
-
- var vertex = new VertexCompiler.Context(ctx.vertexType(), instanceShader.getFile(), material.getVertexShader().getFile(),
- contextShader.getVertexShader());
-
- var fragment = new FragmentCompiler.Context(material.getFragmentShader().getFile(), contextShader.getFragmentShader());
-
- return new ProgramAssembler(instanceShader.getFileLoc())
- .attachShader(vertexCompiler.get(vertex))
- .attachShader(fragmentCompiler.get(fragment))
- .link()
- .build(contextShader.factory());
- }
-
- @Override
- protected void _destroy(GlProgram value) {
- value.delete();
- }
-
- public static void onReloadRenderers(ReloadRenderersEvent event) {
- INSTANCE.invalidate();
- }
-
- /**
- * Represents the entire context of a program's usage.
- *
- * @param vertexType The vertexType the program should be adapted for.
- * @param material The material shader to use.
- * @param instanceShader The instance shader to use.
- * @param contextShader The context shader to use.
- */
- public record Context(VertexType vertexType, Material material, FileResolution instanceShader,
- ContextShader contextShader) {
- }
-}
diff --git a/src/main/java/com/jozufozu/flywheel/core/compile/VertexCompiler.java b/src/main/java/com/jozufozu/flywheel/core/compile/VertexCompiler.java
deleted file mode 100644
index 50aba6150..000000000
--- a/src/main/java/com/jozufozu/flywheel/core/compile/VertexCompiler.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.jozufozu.flywheel.core.compile;
-
-import java.util.ArrayList;
-
-import com.google.common.collect.ImmutableList;
-import com.jozufozu.flywheel.api.vertex.VertexType;
-import com.jozufozu.flywheel.backend.gl.GLSLVersion;
-import com.jozufozu.flywheel.backend.gl.shader.GlShader;
-import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
-import com.jozufozu.flywheel.core.source.CompilationContext;
-import com.jozufozu.flywheel.core.source.SourceFile;
-import com.jozufozu.flywheel.core.source.parse.ShaderField;
-import com.jozufozu.flywheel.core.source.span.Span;
-import com.jozufozu.flywheel.util.Pair;
-
-/**
- * Handles compilation and deletion of vertex shaders.
- */
-public class VertexCompiler extends Memoizer {
-
- public VertexCompiler() {
- }
-
- @Override
- protected GlShader _create(Context key) {
- StringBuilder finalSource = new StringBuilder();
-
- finalSource.append(CompileUtil.generateHeader(GLSLVersion.V420, ShaderType.VERTEX));
-
- var index = new CompilationContext();
-
- // LAYOUT
-
- var layoutShader = key.vertexType.getLayoutShader().getFile();
- finalSource.append(layoutShader.generateFinalSource(index));
-
- // INSTANCE
-
- int attributeBaseIndex = key.vertexType.getLayout()
- .getAttributeCount();
-
- var instanceShader = key.instanceShader;
- var replacements = new ArrayList>();
- for (ShaderField field : instanceShader.fields.values()) {
- if (field.decoration != ShaderField.Decoration.IN) {
- continue;
- }
-
- int location = Integer.parseInt(field.location.get());
- int newLocation = location + attributeBaseIndex;
- replacements.add(Pair.of(field.location, Integer.toString(newLocation)));
- }
- finalSource.append(instanceShader.generateFinalSource(index, replacements));
-
- // MATERIAL
-
- var materialShader = key.materialShader;
- finalSource.append(materialShader.generateFinalSource(index));
-
- // CONTEXT
-
- var contextShaderSource = key.contextShader;
- finalSource.append(contextShaderSource.generateFinalSource(index));
-
- // MAIN
-
- finalSource.append("""
- void main() {
- flw_layoutVertex();
-
- flw_instanceVertex();
-
- flw_materialVertex();
-
- flw_contextVertex();
- }
- """);
-
- try {
- return new GlShader(finalSource.toString(), ShaderType.VERTEX, ImmutableList.of(layoutShader.name, instanceShader.name, materialShader.name, contextShaderSource.name));
- } catch (ShaderCompilationException e) {
- throw e.withErrorLog(index);
- }
- }
-
- @Override
- protected void _destroy(GlShader value) {
- value.delete();
- }
-
- /**
- * @param vertexType The vertex type to use.
- * @param instanceShader The instance shader source.
- * @param materialShader The vertex material shader source.
- * @param contextShader The context shader source.
- */
- public record Context(VertexType vertexType, SourceFile instanceShader, SourceFile materialShader, SourceFile contextShader) {
- }
-}
diff --git a/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingInstanceManager.java b/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingInstanceManager.java
deleted file mode 100644
index c58b5984d..000000000
--- a/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingInstanceManager.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.jozufozu.flywheel.core.crumbling;
-
-import com.jozufozu.flywheel.api.instance.DynamicInstance;
-import com.jozufozu.flywheel.api.instancer.InstancerManager;
-import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager;
-
-public class CrumblingInstanceManager extends BlockEntityInstanceManager {
-
- public CrumblingInstanceManager(InstancerManager instancerManager) {
- super(instancerManager);
- }
-
- @Override
- protected void updateInstance(DynamicInstance dyn, float lookX, float lookY, float lookZ, int cX, int cY, int cZ) {
- dyn.beginFrame();
- }
-}
diff --git a/src/main/java/com/jozufozu/flywheel/core/hardcoded/ModelPart.java b/src/main/java/com/jozufozu/flywheel/core/hardcoded/ModelPart.java
index 4dc301213..b381d859b 100644
--- a/src/main/java/com/jozufozu/flywheel/core/hardcoded/ModelPart.java
+++ b/src/main/java/com/jozufozu/flywheel/core/hardcoded/ModelPart.java
@@ -6,25 +6,23 @@ import com.jozufozu.flywheel.api.vertex.MutableVertexList;
import com.jozufozu.flywheel.api.vertex.ReusableVertexList;
import com.jozufozu.flywheel.backend.memory.MemoryBlock;
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.util.joml.Vector4f;
+import com.jozufozu.flywheel.util.joml.Vector4fc;
public class ModelPart implements Mesh {
private final int vertexCount;
private final MemoryBlock contents;
private final ReusableVertexList vertexList;
private final String name;
+ private final Vector4f boundingSphere;
public ModelPart(List cuboids, String name) {
this.name = name;
- {
- int vertices = 0;
- for (PartBuilder.CuboidBuilder cuboid : cuboids) {
- vertices += cuboid.vertices();
- }
- this.vertexCount = vertices;
- }
+ this.vertexCount = countVertices(cuboids);
contents = MemoryBlock.malloc(size());
long ptr = contents.ptr();
@@ -36,6 +34,8 @@ public class ModelPart implements Mesh {
vertexList = getVertexType().createVertexList();
vertexList.ptr(ptr);
vertexList.setVertexCount(vertexCount);
+
+ boundingSphere = ModelUtil.computeBoundingSphere(vertexList);
}
public static PartBuilder builder(String name, int sizeU, int sizeV) {
@@ -71,4 +71,17 @@ public class ModelPart implements Mesh {
public String name() {
return name;
}
+
+ @Override
+ public Vector4fc getBoundingSphere() {
+ return boundingSphere;
+ }
+
+ private static int countVertices(List cuboids) {
+ int vertices = 0;
+ for (PartBuilder.CuboidBuilder cuboid : cuboids) {
+ vertices += cuboid.vertices();
+ }
+ return vertices;
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/core/layout/BufferLayout.java b/src/main/java/com/jozufozu/flywheel/core/layout/BufferLayout.java
index 74f958d6a..8f28d9f2d 100644
--- a/src/main/java/com/jozufozu/flywheel/core/layout/BufferLayout.java
+++ b/src/main/java/com/jozufozu/flywheel/core/layout/BufferLayout.java
@@ -35,7 +35,7 @@ public class BufferLayout {
this.stride = calculateStride(this.attributes) + padding;
}
- public Collection getAttributes() {
+ public List getAttributes() {
return attributes;
}
diff --git a/src/main/java/com/jozufozu/flywheel/core/model/Mesh.java b/src/main/java/com/jozufozu/flywheel/core/model/Mesh.java
index ecf138451..9a120f6b6 100644
--- a/src/main/java/com/jozufozu/flywheel/core/model/Mesh.java
+++ b/src/main/java/com/jozufozu/flywheel/core/model/Mesh.java
@@ -4,6 +4,7 @@ import com.jozufozu.flywheel.api.vertex.MutableVertexList;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.instancing.instancing.ElementBuffer;
import com.jozufozu.flywheel.core.QuadConverter;
+import com.jozufozu.flywheel.util.joml.Vector4fc;
/**
* A holder for arbitrary vertex data that can be written to memory or a vertex list.
@@ -12,6 +13,8 @@ public interface Mesh {
VertexType getVertexType();
+ Vector4fc getBoundingSphere();
+
/**
* @return The number of vertices this mesh has.
*/
diff --git a/src/main/java/com/jozufozu/flywheel/core/model/ModelUtil.java b/src/main/java/com/jozufozu/flywheel/core/model/ModelUtil.java
index 3163b0298..edfbf2ca2 100644
--- a/src/main/java/com/jozufozu/flywheel/core/model/ModelUtil.java
+++ b/src/main/java/com/jozufozu/flywheel/core/model/ModelUtil.java
@@ -3,17 +3,21 @@ 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 org.lwjgl.system.MemoryUtil;
+import com.dreizak.miniball.highdim.Miniball;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.vertex.ReusableVertexList;
+import com.jozufozu.flywheel.api.vertex.VertexList;
import com.jozufozu.flywheel.api.vertex.VertexListProvider;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.memory.MemoryBlock;
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.DrawState;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.datafixers.util.Pair;
@@ -89,4 +93,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);
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/core/model/SimpleMesh.java b/src/main/java/com/jozufozu/flywheel/core/model/SimpleMesh.java
index d184a05d6..95ce7d8bf 100644
--- a/src/main/java/com/jozufozu/flywheel/core/model/SimpleMesh.java
+++ b/src/main/java/com/jozufozu/flywheel/core/model/SimpleMesh.java
@@ -4,6 +4,8 @@ import com.jozufozu.flywheel.api.vertex.MutableVertexList;
import com.jozufozu.flywheel.api.vertex.ReusableVertexList;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.memory.MemoryBlock;
+import com.jozufozu.flywheel.util.joml.Vector4f;
+import com.jozufozu.flywheel.util.joml.Vector4fc;
public class SimpleMesh implements Mesh {
private final VertexType vertexType;
@@ -11,6 +13,7 @@ public class SimpleMesh implements Mesh {
private final MemoryBlock contents;
private final ReusableVertexList vertexList;
private final String name;
+ private final Vector4f boundingSphere;
public SimpleMesh(VertexType vertexType, MemoryBlock contents, String name) {
this.vertexType = vertexType;
@@ -27,6 +30,8 @@ public class SimpleMesh implements Mesh {
vertexList = getVertexType().createVertexList();
vertexList.ptr(contents.ptr());
vertexList.setVertexCount(vertexCount);
+
+ boundingSphere = ModelUtil.computeBoundingSphere(vertexList);
}
@Override
@@ -59,6 +64,11 @@ public class SimpleMesh implements Mesh {
return name;
}
+ @Override
+ public Vector4fc getBoundingSphere() {
+ return boundingSphere;
+ }
+
@Override
public String toString() {
return "SimpleMesh{" + "name='" + name + "',vertexType='" + vertexType + "}";
diff --git a/src/main/java/com/jozufozu/flywheel/core/source/FileResolution.java b/src/main/java/com/jozufozu/flywheel/core/source/FileResolution.java
index e2d74269f..ea4ca0d43 100644
--- a/src/main/java/com/jozufozu/flywheel/core/source/FileResolution.java
+++ b/src/main/java/com/jozufozu/flywheel/core/source/FileResolution.java
@@ -114,7 +114,7 @@ public class FileResolution {
ErrorBuilder builder = errorReporter.error(String.format("could not find source for file %s", fileLoc));
for (Span location : neededAt) {
builder.pointAtFile(location.getSourceFile())
- .pointAt(location, 1);
+ .pointAt(location);
}
}
@@ -162,4 +162,25 @@ public class FileResolution {
public String toString() {
return "FileResolution[" + fileLoc + "]";
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ FileResolution that = (FileResolution) o;
+
+ return fileLoc.equals(that.fileLoc);
+ }
+
+ @Override
+ public int hashCode() {
+ // FileResolutions are interned and therefore can be hashed based on object identity.
+ // Overriding this to make it explicit.
+ return System.identityHashCode(this);
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/core/source/SourceChecks.java b/src/main/java/com/jozufozu/flywheel/core/source/SourceChecks.java
index a8247a33e..1d4023705 100644
--- a/src/main/java/com/jozufozu/flywheel/core/source/SourceChecks.java
+++ b/src/main/java/com/jozufozu/flywheel/core/source/SourceChecks.java
@@ -54,4 +54,12 @@ public class SourceChecks {
return func;
}
+
+ public static BiConsumer super ErrorReporter, ? super SourceFile> checkDefine(String define) {
+ return (errorReporter, file) -> {
+// if (!file.hasDefine(define)) {
+// errorReporter.generateMissingDefine(file, define, "\"" + define + "\" define not defined");
+// }
+ };
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/core/source/SourceFile.java b/src/main/java/com/jozufozu/flywheel/core/source/SourceFile.java
index 4e63ef409..faeb46acb 100644
--- a/src/main/java/com/jozufozu/flywheel/core/source/SourceFile.java
+++ b/src/main/java/com/jozufozu/flywheel/core/source/SourceFile.java
@@ -184,24 +184,21 @@ public class SourceFile {
return "#use " + '"' + name + '"';
}
- public String generateFinalSource(CompilationContext env) {
- return generateFinalSource(env, Collections.emptyList());
- }
-
- public String generateFinalSource(CompilationContext env, List> replacements) {
+ public String generateFinalSource(CompilationContext context) {
+ List> replacements = Collections.emptyList();
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();
diff --git a/src/main/java/com/jozufozu/flywheel/core/source/error/ErrorBuilder.java b/src/main/java/com/jozufozu/flywheel/core/source/error/ErrorBuilder.java
index 93ab86e84..1a4390e33 100644
--- a/src/main/java/com/jozufozu/flywheel/core/source/error/ErrorBuilder.java
+++ b/src/main/java/com/jozufozu/flywheel/core/source/error/ErrorBuilder.java
@@ -88,6 +88,10 @@ public class ErrorBuilder {
.pointAt(span, 0);
}
+ public ErrorBuilder pointAt(Span span) {
+ return pointAt(span, 0);
+ }
+
public ErrorBuilder pointAt(Span span, int ctxLines) {
if (span.lines() == 1) {
@@ -123,7 +127,9 @@ public class ErrorBuilder {
for (ErrorLine line : lines) {
int length = line.neededMargin();
- if (length > maxLength) maxLength = length;
+ if (length > maxLength) {
+ maxLength = length;
+ }
}
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/jozufozu/flywheel/core/source/error/ErrorReporter.java b/src/main/java/com/jozufozu/flywheel/core/source/error/ErrorReporter.java
index 859ec71e2..90224f0e2 100644
--- a/src/main/java/com/jozufozu/flywheel/core/source/error/ErrorReporter.java
+++ b/src/main/java/com/jozufozu/flywheel/core/source/error/ErrorReporter.java
@@ -3,9 +3,11 @@ package com.jozufozu.flywheel.core.source.error;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.Backend;
+import com.jozufozu.flywheel.core.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
import com.jozufozu.flywheel.core.source.parse.ShaderStruct;
@@ -82,10 +84,12 @@ public class ErrorReporter {
return !reportedErrors.isEmpty();
}
- public void dump() {
- for (var error : reportedErrors) {
- Backend.LOGGER.error(error.build());
- }
+ public ShaderLoadingException dump() {
+ var allErrors = reportedErrors.stream()
+ .map(ErrorBuilder::build)
+ .collect(Collectors.joining());
+
+ return new ShaderLoadingException(allErrors);
}
public static void printLines(CharSequence source) {
diff --git a/src/main/java/com/jozufozu/flywheel/core/structs/oriented/OrientedStorageWriter.java b/src/main/java/com/jozufozu/flywheel/core/structs/oriented/OrientedStorageWriter.java
new file mode 100644
index 000000000..17183e8cc
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/core/structs/oriented/OrientedStorageWriter.java
@@ -0,0 +1,43 @@
+package com.jozufozu.flywheel.core.structs.oriented;
+
+
+import org.lwjgl.system.MemoryUtil;
+
+import com.jozufozu.flywheel.api.struct.StorageBufferWriter;
+
+public class OrientedStorageWriter implements StorageBufferWriter {
+
+ public static final OrientedStorageWriter INSTANCE = new OrientedStorageWriter();
+
+ private OrientedStorageWriter() {
+ }
+
+ @Override
+ public void write(final long ptr, OrientedPart d) {
+ 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.skyLight);
+ MemoryUtil.memPutShort(ptr + 42, d.blockLight);
+
+ 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 48;
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/core/structs/oriented/OrientedType.java b/src/main/java/com/jozufozu/flywheel/core/structs/oriented/OrientedType.java
index 66f2653f5..039fdd1e5 100644
--- a/src/main/java/com/jozufozu/flywheel/core/structs/oriented/OrientedType.java
+++ b/src/main/java/com/jozufozu/flywheel/core/structs/oriented/OrientedType.java
@@ -34,11 +34,21 @@ public class OrientedType implements StructType {
return OrientedWriter.INSTANCE;
}
+ @Override
+ public OrientedStorageWriter getStorageBufferWriter() {
+ return OrientedStorageWriter.INSTANCE;
+ }
+
@Override
public FileResolution getInstanceShader() {
return Components.Files.ORIENTED;
}
+ @Override
+ public FileResolution getIndirectShader() {
+ return Components.Files.ORIENTED_INDIRECT;
+ }
+
@Override
public VertexTransformer getVertexTransformer() {
return (vertexList, struct, level) -> {
diff --git a/src/main/java/com/jozufozu/flywheel/core/structs/transformed/TransformedStorageWriter.java b/src/main/java/com/jozufozu/flywheel/core/structs/transformed/TransformedStorageWriter.java
new file mode 100644
index 000000000..a9f370442
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/core/structs/transformed/TransformedStorageWriter.java
@@ -0,0 +1,31 @@
+package com.jozufozu.flywheel.core.structs.transformed;
+
+import org.lwjgl.system.MemoryUtil;
+
+import com.jozufozu.flywheel.api.struct.StorageBufferWriter;
+import com.jozufozu.flywheel.extension.MatrixWrite;
+
+public class TransformedStorageWriter implements StorageBufferWriter {
+
+ public static final TransformedStorageWriter INSTANCE = new TransformedStorageWriter();
+
+ private TransformedStorageWriter() {
+ }
+
+ @Override
+ public void write(long ptr, TransformedPart instance) {
+ MatrixWrite.writeUnsafe(instance.model, ptr);
+ MatrixWrite.writeUnsafe(instance.normal, ptr + 64);
+ 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 108;
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/core/structs/transformed/TransformedType.java b/src/main/java/com/jozufozu/flywheel/core/structs/transformed/TransformedType.java
index 8eb36eeb6..aa6ba75d9 100644
--- a/src/main/java/com/jozufozu/flywheel/core/structs/transformed/TransformedType.java
+++ b/src/main/java/com/jozufozu/flywheel/core/structs/transformed/TransformedType.java
@@ -1,5 +1,6 @@
package com.jozufozu.flywheel.core.structs.transformed;
+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;
@@ -31,11 +32,21 @@ public class TransformedType implements StructType {
return TransformedWriter.INSTANCE;
}
+ @Override
+ public StorageBufferWriter getStorageBufferWriter() {
+ return TransformedStorageWriter.INSTANCE;
+ }
+
@Override
public FileResolution getInstanceShader() {
return Components.Files.TRANSFORMED;
}
+ @Override
+ public FileResolution getIndirectShader() {
+ return Components.Files.TRANSFORMED_INDIRECT;
+ }
+
@Override
public VertexTransformer getVertexTransformer() {
return (vertexList, struct, level) -> {
diff --git a/src/main/java/com/jozufozu/flywheel/core/structs/transformed/TransformedWriterUnsafe.java b/src/main/java/com/jozufozu/flywheel/core/structs/transformed/TransformedWriterUnsafe.java
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/main/java/com/jozufozu/flywheel/core/uniform/FogProvider.java b/src/main/java/com/jozufozu/flywheel/core/uniform/FogProvider.java
index 29873074b..05cfdc8b3 100644
--- a/src/main/java/com/jozufozu/flywheel/core/uniform/FogProvider.java
+++ b/src/main/java/com/jozufozu/flywheel/core/uniform/FogProvider.java
@@ -10,7 +10,7 @@ import com.mojang.blaze3d.systems.RenderSystem;
public class FogProvider extends UniformProvider {
@Override
- public int getSize() {
+ public int getActualByteSize() {
return 16 + 8 + 4;
}
diff --git a/src/main/java/com/jozufozu/flywheel/core/uniform/FrustumProvider.java b/src/main/java/com/jozufozu/flywheel/core/uniform/FrustumProvider.java
new file mode 100644
index 000000000..0f8346cad
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/core/uniform/FrustumProvider.java
@@ -0,0 +1,59 @@
+package com.jozufozu.flywheel.core.uniform;
+
+import org.lwjgl.system.MemoryUtil;
+
+import com.jozufozu.flywheel.api.uniform.UniformProvider;
+import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
+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 net.minecraft.core.Vec3i;
+import net.minecraft.world.phys.Vec3;
+import net.minecraftforge.common.MinecraftForge;
+
+public class FrustumProvider extends UniformProvider {
+
+ public static boolean PAUSED = false;
+ public static boolean CAPTURE = false;
+
+ public FrustumProvider() {
+ MinecraftForge.EVENT_BUS.addListener(this::beginFrame);
+ }
+
+ @Override
+ public int getActualByteSize() {
+ return 96;
+ }
+
+ @Override
+ public FileResolution getUniformShader() {
+ return Components.Files.FRUSTUM_UNIFORMS;
+ }
+
+ public void beginFrame(BeginFrameEvent event) {
+ update(event.getContext());
+ }
+
+ public void update(RenderContext context) {
+ if (ptr == MemoryUtil.NULL || (PAUSED && !CAPTURE)) {
+ return;
+ }
+
+ Vec3i originCoordinate = InstancedRenderDispatcher.getOriginCoordinate(context.level());
+ Vec3 camera = context.camera()
+ .getPosition();
+
+ var camX = (float) (camera.x - originCoordinate.getX());
+ var camY = (float) (camera.y - originCoordinate.getY());
+ var camZ = (float) (camera.z - originCoordinate.getZ());
+
+ var shiftedCuller = RenderContext.createCuller(context.viewProjection(), -camX, -camY, -camZ);
+
+ shiftedCuller.getJozuPackedPlanes(ptr);
+
+ notifier.signalChanged();
+ CAPTURE = false;
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/core/uniform/UniformBuffer.java b/src/main/java/com/jozufozu/flywheel/core/uniform/UniformBuffer.java
index 39ce69a94..e3ecee4ab 100644
--- a/src/main/java/com/jozufozu/flywheel/core/uniform/UniformBuffer.java
+++ b/src/main/java/com/jozufozu/flywheel/core/uniform/UniformBuffer.java
@@ -45,11 +45,11 @@ public class UniformBuffer {
int totalBytes = 0;
int index = 0;
for (UniformProvider provider : providers) {
- int size = provider.getSize();
+ int size = alignPo2(provider.getActualByteSize(), 16);
builder.add(new Allocated(provider, totalBytes, size, index));
- totalBytes = align(totalBytes + size);
+ totalBytes = alignUniformBuffer(totalBytes + size);
index++;
}
@@ -80,7 +80,7 @@ public class UniformBuffer {
}
// https://stackoverflow.com/questions/3407012/rounding-up-to-the-nearest-multiple-of-a-number
- private static int align(int numToRound) {
+ private static int alignUniformBuffer(int numToRound) {
if (PO2_ALIGNMENT) {
return (numToRound + OFFSET_ALIGNMENT - 1) & -OFFSET_ALIGNMENT;
} else {
@@ -88,6 +88,10 @@ public class UniformBuffer {
}
}
+ private static int alignPo2(int numToRound, int alignment) {
+ return (numToRound + alignment - 1) & -alignment;
+ }
+
private class Allocated implements UniformProvider.Notifier {
private final UniformProvider provider;
private final int offset;
diff --git a/src/main/java/com/jozufozu/flywheel/core/uniform/ViewProvider.java b/src/main/java/com/jozufozu/flywheel/core/uniform/ViewProvider.java
index a1d844de1..89175710e 100644
--- a/src/main/java/com/jozufozu/flywheel/core/uniform/ViewProvider.java
+++ b/src/main/java/com/jozufozu/flywheel/core/uniform/ViewProvider.java
@@ -26,7 +26,7 @@ public class ViewProvider extends UniformProvider {
}
@Override
- public int getSize() {
+ public int getActualByteSize() {
return 4 * 16 + 16 + 4;
}
@@ -56,7 +56,7 @@ public class ViewProvider extends UniformProvider {
MemoryUtil.memPutFloat(ptr + 64, camX);
MemoryUtil.memPutFloat(ptr + 68, camY);
MemoryUtil.memPutFloat(ptr + 72, camZ);
- MemoryUtil.memPutInt(ptr + 80, constantAmbientLight);
+ MemoryUtil.memPutInt(ptr + 76, constantAmbientLight);
notifier.signalChanged();
}
diff --git a/src/main/java/com/jozufozu/flywheel/extension/Matrix3fExtension.java b/src/main/java/com/jozufozu/flywheel/extension/Matrix3fExtension.java
new file mode 100644
index 000000000..ef8ce3cc6
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/extension/Matrix3fExtension.java
@@ -0,0 +1,16 @@
+package com.jozufozu.flywheel.extension;
+
+import com.jozufozu.flywheel.util.joml.Matrix3f;
+
+public interface Matrix3fExtension {
+
+ Matrix3f flywheel$store(Matrix3f matrix);
+
+ static Matrix3f clone(com.mojang.math.Matrix3f moj) {
+ return ((Matrix3fExtension)(Object) moj).flywheel$store(new Matrix3f());
+ }
+
+ static void store(com.mojang.math.Matrix3f moj, Matrix3f joml) {
+ ((Matrix3fExtension)(Object) moj).flywheel$store(joml);
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/extension/Matrix4fExtension.java b/src/main/java/com/jozufozu/flywheel/extension/Matrix4fExtension.java
new file mode 100644
index 000000000..ea77ca703
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/extension/Matrix4fExtension.java
@@ -0,0 +1,16 @@
+package com.jozufozu.flywheel.extension;
+
+import com.jozufozu.flywheel.util.joml.Matrix4f;
+
+public interface Matrix4fExtension {
+
+ Matrix4f flywheel$store(Matrix4f matrix);
+
+ static Matrix4f clone(com.mojang.math.Matrix4f moj) {
+ return ((Matrix4fExtension)(Object) moj).flywheel$store(new Matrix4f());
+ }
+
+ static void store(com.mojang.math.Matrix4f moj, Matrix4f joml) {
+ ((Matrix4fExtension)(Object) moj).flywheel$store(joml);
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/extension/MatrixWrite.java b/src/main/java/com/jozufozu/flywheel/extension/MatrixWrite.java
index 90747af22..15dead550 100644
--- a/src/main/java/com/jozufozu/flywheel/extension/MatrixWrite.java
+++ b/src/main/java/com/jozufozu/flywheel/extension/MatrixWrite.java
@@ -2,6 +2,7 @@ package com.jozufozu.flywheel.extension;
import java.nio.ByteBuffer;
+import com.mojang.math.Matrix3f;
import com.mojang.math.Matrix4f;
/**
@@ -24,4 +25,8 @@ public interface MatrixWrite {
static void writeUnsafe(Matrix4f matrix, long ptr) {
((MatrixWrite) (Object) matrix).flywheel$writeUnsafe(ptr);
}
+
+ static void writeUnsafe(Matrix3f matrix, long ptr) {
+ ((MatrixWrite) (Object) matrix).flywheel$writeUnsafe(ptr);
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/mixin/ClientMainMixin.java b/src/main/java/com/jozufozu/flywheel/mixin/ClientMainMixin.java
new file mode 100644
index 000000000..49f7d0abe
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/mixin/ClientMainMixin.java
@@ -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?");
+ }
+ }
+}
diff --git a/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java b/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java
index 5e843dd60..e248e1f59 100644
--- a/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java
+++ b/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java
@@ -26,6 +26,7 @@ import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderBuffers;
+import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.MinecraftForge;
@Mixin(value = LevelRenderer.class, priority = 1001) // Higher priority to go after Sodium
@@ -42,7 +43,10 @@ public class LevelRendererMixin {
@Inject(at = @At("HEAD"), method = "renderLevel")
private void flywheel$beginRender(PoseStack pPoseStack, float pPartialTick, long pFinishNanoTime, boolean pRenderBlockOutline, Camera pCamera, GameRenderer pGameRenderer, LightTexture pLightTexture, Matrix4f pProjectionMatrix, CallbackInfo ci) {
- flywheel$renderContext = new RenderContext((LevelRenderer) (Object) this, level, pPoseStack, RenderContext.createViewProjection(pPoseStack, pProjectionMatrix), pProjectionMatrix, renderBuffers, pCamera);
+ var viewProjection = RenderContext.createViewProjection(pPoseStack, pProjectionMatrix);
+ var cameraPos = pCamera.getPosition();
+ var culler = RenderContext.createCuller(viewProjection, (float) -cameraPos.x, (float) -cameraPos.y, (float) -cameraPos.z);
+ flywheel$renderContext = new RenderContext((LevelRenderer) (Object) this, level, pPoseStack, viewProjection, pProjectionMatrix, renderBuffers, pCamera, culler);
try (var restoreState = GlStateTracker.getRestoreState()) {
MinecraftForge.EVENT_BUS.post(new BeginFrameEvent(flywheel$renderContext));
diff --git a/src/main/java/com/jozufozu/flywheel/mixin/matrix/Matrix3fMixin.java b/src/main/java/com/jozufozu/flywheel/mixin/matrix/Matrix3fMixin.java
index f986dee92..2690b8f99 100644
--- a/src/main/java/com/jozufozu/flywheel/mixin/matrix/Matrix3fMixin.java
+++ b/src/main/java/com/jozufozu/flywheel/mixin/matrix/Matrix3fMixin.java
@@ -6,11 +6,12 @@ import org.lwjgl.system.MemoryUtil;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
+import com.jozufozu.flywheel.extension.Matrix3fExtension;
import com.jozufozu.flywheel.extension.MatrixWrite;
import com.mojang.math.Matrix3f;
@Mixin(Matrix3f.class)
-public abstract class Matrix3fMixin implements MatrixWrite {
+public abstract class Matrix3fMixin implements MatrixWrite, Matrix3fExtension {
@Shadow protected float m00;
@Shadow protected float m01;
@Shadow protected float m02;
@@ -46,4 +47,9 @@ public abstract class Matrix3fMixin implements MatrixWrite {
buffer.putFloat(m12);
buffer.putFloat(m22);
}
+
+ @Override
+ public com.jozufozu.flywheel.util.joml.Matrix3f flywheel$store(com.jozufozu.flywheel.util.joml.Matrix3f matrix) {
+ return matrix.set(m00, m10, m20, m01, m11, m21, m02, m12, m22);
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/mixin/matrix/Matrix4fMixin.java b/src/main/java/com/jozufozu/flywheel/mixin/matrix/Matrix4fMixin.java
index 6a5f62459..542e71929 100644
--- a/src/main/java/com/jozufozu/flywheel/mixin/matrix/Matrix4fMixin.java
+++ b/src/main/java/com/jozufozu/flywheel/mixin/matrix/Matrix4fMixin.java
@@ -6,11 +6,12 @@ import org.lwjgl.system.MemoryUtil;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
+import com.jozufozu.flywheel.extension.Matrix4fExtension;
import com.jozufozu.flywheel.extension.MatrixWrite;
import com.mojang.math.Matrix4f;
@Mixin(Matrix4f.class)
-public abstract class Matrix4fMixin implements MatrixWrite {
+public abstract class Matrix4fMixin implements MatrixWrite, Matrix4fExtension {
@Shadow protected float m00;
@Shadow protected float m01;
@Shadow protected float m02;
@@ -67,4 +68,13 @@ public abstract class Matrix4fMixin implements MatrixWrite {
buf.putFloat(m23);
buf.putFloat(m33);
}
+
+ @Override
+ public com.jozufozu.flywheel.util.joml.Matrix4f flywheel$store(com.jozufozu.flywheel.util.joml.Matrix4f matrix) {
+ return matrix.set(
+ m00, m10, m20, m30,
+ m01, m11, m21, m31,
+ m02, m12, m22, m32,
+ m03, m13, m23, m33);
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/util/Lazy.java b/src/main/java/com/jozufozu/flywheel/util/Lazy.java
index a1fdd660e..2945da562 100644
--- a/src/main/java/com/jozufozu/flywheel/util/Lazy.java
+++ b/src/main/java/com/jozufozu/flywheel/util/Lazy.java
@@ -26,6 +26,10 @@ public class Lazy implements Supplier {
return value;
}
+ public boolean isInitialized() {
+ return value != null;
+ }
+
public Lazy lazyMap(Function func) {
return new Lazy<>(() -> func.apply(get()));
}
diff --git a/src/main/java/com/jozufozu/flywheel/util/joml/FrustumIntersection.java b/src/main/java/com/jozufozu/flywheel/util/joml/FrustumIntersection.java
index a85b90676..4e4881aaf 100644
--- a/src/main/java/com/jozufozu/flywheel/util/joml/FrustumIntersection.java
+++ b/src/main/java/com/jozufozu/flywheel/util/joml/FrustumIntersection.java
@@ -23,6 +23,9 @@
*/
package com.jozufozu.flywheel.util.joml;
+import java.nio.ByteBuffer;
+
+import org.lwjgl.system.MemoryUtil;
/**
* Efficiently performs frustum intersection tests by caching the frustum planes of an arbitrary transformation {@link Matrix4fc matrix}.
*
@@ -950,4 +953,94 @@ public class FrustumIntersection {
return da >= 0.0f || db >= 0.0f;
}
+ public void getCorners(ByteBuffer buffer) {
+
+ Vector3f scratch = new Vector3f();
+ Vector3f result = new Vector3f();
+
+ long addr = MemoryUtil.memAddress(buffer);
+ planeIntersect(planes[0], planes[2], planes[4], result, scratch); result.getToAddress(addr);
+ planeIntersect(planes[0], planes[2], planes[5], result, scratch); result.getToAddress(addr + 12);
+ planeIntersect(planes[0], planes[3], planes[4], result, scratch); result.getToAddress(addr + 24);
+ planeIntersect(planes[0], planes[3], planes[5], result, scratch); result.getToAddress(addr + 36);
+ planeIntersect(planes[1], planes[2], planes[4], result, scratch); result.getToAddress(addr + 48);
+ planeIntersect(planes[1], planes[2], planes[5], result, scratch); result.getToAddress(addr + 60);
+ planeIntersect(planes[1], planes[3], planes[4], result, scratch); result.getToAddress(addr + 72);
+ planeIntersect(planes[1], planes[3], planes[5], result, scratch); result.getToAddress(addr + 84);
+ }
+
+ private Vector3f planeIntersect(Vector4f a, Vector4f b, Vector4f c, Vector3f result, Vector3f scratch) {
+ // Formula used
+ // d1 ( N2 * N3 ) + d2 ( N3 * N1 ) + d3 ( N1 * N2 )
+ //P = ---------------------------------------------------------------------
+ // N1 . ( N2 * N3 )
+ //
+ // Note: N refers to the normal, d refers to the displacement. '.' means dot product. '*' means cross product
+
+ float f = result.set(b.x, b.y, b.z).cross(c.x, c.y, c.z).dot(a.x, a.y, a.z);
+
+ result.set(0);
+ scratch.set(b.x, b.y, b.z).cross(c.x, c.y, c.z).mul(a.z);
+ result.add(scratch);
+ scratch.set(c.x, c.y, c.z).cross(a.x, a.y, a.z).mul(b.z);
+ result.add(scratch);
+ scratch.set(a.x, a.y, a.z).cross(b.x, b.y, b.z).mul(c.z);
+ result.add(scratch);
+
+ return result.div(f);
+ }
+
+ /**
+ * Writes the planes of this frustum to the given buffer.
+ * Uses a different format that is friendly towards an optimized instruction-parallel
+ * implementation of sphere-frustum intersection.
+ * The format is as follows:
+ * {@code vec4(nxX, pxX, nyX, pyX)}
+ * {@code vec4(nxY, pxY, nyY, pyY)}
+ * {@code vec4(nxZ, pxZ, nyZ, pyZ)}
+ * {@code vec4(nxW, pxW, nyW, pyW)}
+ * {@code vec2(nzX, pzX)}
+ * {@code vec2(nzY, pzY)}
+ * {@code vec2(nzZ, pzZ)}
+ * {@code vec2(nzW, pzW)}
+ *
+ * @param addr The buffer to write the planes to.
+ */
+ public void getJozuPackedPlanes(long addr) {
+ MemoryUtil.memPutFloat(addr, nxX);
+ MemoryUtil.memPutFloat(addr + 4, pxX);
+ MemoryUtil.memPutFloat(addr + 8, nyX);
+ MemoryUtil.memPutFloat(addr + 12, pyX);
+ MemoryUtil.memPutFloat(addr + 16, nxY);
+ MemoryUtil.memPutFloat(addr + 20, pxY);
+ MemoryUtil.memPutFloat(addr + 24, nyY);
+ MemoryUtil.memPutFloat(addr + 28, pyY);
+ MemoryUtil.memPutFloat(addr + 32, nxZ);
+ MemoryUtil.memPutFloat(addr + 36, pxZ);
+ MemoryUtil.memPutFloat(addr + 40, nyZ);
+ MemoryUtil.memPutFloat(addr + 44, pyZ);
+ MemoryUtil.memPutFloat(addr + 48, nxW);
+ MemoryUtil.memPutFloat(addr + 52, pxW);
+ MemoryUtil.memPutFloat(addr + 56, nyW);
+ MemoryUtil.memPutFloat(addr + 60, pyW);
+ MemoryUtil.memPutFloat(addr + 64, nzX);
+ MemoryUtil.memPutFloat(addr + 68, pzX);
+ MemoryUtil.memPutFloat(addr + 72, nzY);
+ MemoryUtil.memPutFloat(addr + 76, pzY);
+ MemoryUtil.memPutFloat(addr + 80, nzZ);
+ MemoryUtil.memPutFloat(addr + 84, pzZ);
+ MemoryUtil.memPutFloat(addr + 88, nzW);
+ MemoryUtil.memPutFloat(addr + 92, pzW);
+ }
+
+ public void getPlanes(ByteBuffer buffer) {
+ long addr = MemoryUtil.memAddress(buffer);
+
+ planes[0].getToAddress(addr);
+ planes[1].getToAddress(addr + 16);
+ planes[2].getToAddress(addr + 32);
+ planes[3].getToAddress(addr + 48);
+ planes[4].getToAddress(addr + 64);
+ planes[5].getToAddress(addr + 80);
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/util/joml/Quaternionf.java b/src/main/java/com/jozufozu/flywheel/util/joml/Quaternionf.java
index f783ca20e..ad2ef13cd 100644
--- a/src/main/java/com/jozufozu/flywheel/util/joml/Quaternionf.java
+++ b/src/main/java/com/jozufozu/flywheel/util/joml/Quaternionf.java
@@ -32,6 +32,8 @@ import java.nio.FloatBuffer;
import java.text.DecimalFormat;
import java.text.NumberFormat;
+import com.mojang.math.Quaternion;
+
/**
* Quaternion of 4 single-precision floats which can represent rotation and uniform scaling.
*
@@ -130,6 +132,13 @@ public class Quaternionf implements Externalizable, Cloneable, Quaternionfc {
w = cos;
}
+ public Quaternionf(Quaternion moj) {
+ x = moj.i();
+ y = moj.j();
+ z = moj.k();
+ w = moj.r();
+ }
+
/**
* @return the first component of the vector part
*/
diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/effect/ExampleEffect.java b/src/main/java/com/jozufozu/flywheel/vanilla/effect/ExampleEffect.java
index c0adede2e..60cfa49fa 100644
--- a/src/main/java/com/jozufozu/flywheel/vanilla/effect/ExampleEffect.java
+++ b/src/main/java/com/jozufozu/flywheel/vanilla/effect/ExampleEffect.java
@@ -18,6 +18,7 @@ import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.util.AnimationTickHolder;
import com.jozufozu.flywheel.util.box.GridAlignedBB;
import com.jozufozu.flywheel.util.box.ImmutableBox;
+import com.jozufozu.flywheel.util.joml.FrustumIntersection;
import com.jozufozu.flywheel.util.joml.Vector3f;
import net.minecraft.client.Minecraft;
@@ -297,5 +298,10 @@ public class ExampleEffect implements Effect {
public boolean decreaseFramerateWithDistance() {
return false;
}
+
+ @Override
+ public boolean checkFrustum(FrustumIntersection frustum) {
+ return true;
+ }
}
}
diff --git a/src/main/resources/assets/flywheel/flywheel/api/cull.glsl b/src/main/resources/assets/flywheel/flywheel/api/cull.glsl
new file mode 100644
index 000000000..d89bebbf3
--- /dev/null
+++ b/src/main/resources/assets/flywheel/flywheel/api/cull.glsl
@@ -0,0 +1,4 @@
+#ifdef COMPUTE_SHADER
+uint flw_objectID;
+uint flw_batchID;
+#endif
diff --git a/src/main/resources/assets/flywheel/flywheel/api/fragment.glsl b/src/main/resources/assets/flywheel/flywheel/api/fragment.glsl
index 735f7a84e..11be92a1e 100644
--- a/src/main/resources/assets/flywheel/flywheel/api/fragment.glsl
+++ b/src/main/resources/assets/flywheel/flywheel/api/fragment.glsl
@@ -1,3 +1,4 @@
+#ifdef FRAGMENT_SHADER
in vec4 flw_vertexPos;
in vec4 flw_vertexColor;
in vec2 flw_vertexTexCoord;
@@ -32,3 +33,4 @@ vec4 flw_fogFilter(vec4 color);
* Guard calls with FLW_DISCARD
*/
bool flw_discardPredicate(vec4 finalColor);
+#endif
diff --git a/src/main/resources/assets/flywheel/flywheel/api/vertex.glsl b/src/main/resources/assets/flywheel/flywheel/api/vertex.glsl
index c5d670ec3..c00b99cbd 100644
--- a/src/main/resources/assets/flywheel/flywheel/api/vertex.glsl
+++ b/src/main/resources/assets/flywheel/flywheel/api/vertex.glsl
@@ -1,3 +1,4 @@
+#ifdef VERTEX_SHADER
out vec4 flw_vertexPos;
out vec4 flw_vertexColor;
out vec2 flw_vertexTexCoord;
@@ -11,3 +12,4 @@ out vec4 flw_var0;
out vec4 flw_var1;
out vec4 flw_var2;
out vec4 flw_var3;
+#endif
diff --git a/src/main/resources/assets/flywheel/flywheel/compute/sphere_cull_frustum_experiment.glsl b/src/main/resources/assets/flywheel/flywheel/compute/sphere_cull_frustum_experiment.glsl
new file mode 100644
index 000000000..43ef57f8b
--- /dev/null
+++ b/src/main/resources/assets/flywheel/flywheel/compute/sphere_cull_frustum_experiment.glsl
@@ -0,0 +1,72 @@
+#version 450
+#define FLW_SUBGROUP_SIZE 32
+
+layout(local_size_x = FLW_SUBGROUP_SIZE) in;
+
+
+// in uvec3 gl_NumWorkGroups;
+// in uvec3 gl_WorkGroupID;
+// in uvec3 gl_LocalInvocationID;
+// in uvec3 gl_GlobalInvocationID;
+// in uint gl_LocalInvocationIndex;
+
+layout(std430, binding = 0) buffer Frustum1 {
+ 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)
+ vec4 a4; // vec4(nx.w, px.w, ny.w, py.w)
+ vec2 b1; // vec2(nz.x, pz.x)
+ vec2 b2; // vec2(nz.y, pz.y)
+ vec2 b3; // vec2(nz.z, pz.z)
+ vec2 b4; // vec2(nz.w, pz.w)
+} frustum1;
+
+layout(binding = 1) buffer Frustum2 {
+ vec4 nx;
+ vec4 px;
+ vec4 ny;
+ vec4 py;
+ vec4 nz;
+ vec4 pz;
+} frustum2;
+
+layout(binding = 2) buffer Result {
+ bool res1;
+ bool res2;
+ bool res3;
+} result;
+
+// 83 - 27 = 56 spirv instruction results
+bool testSphere1(vec4 sphere) {
+ return
+ all(lessThanEqual(fma(frustum1.a1, sphere.xxxx, fma(frustum1.a2, sphere.yyyy, fma(frustum1.a3, sphere.zzzz, frustum1.a4))), -sphere.wwww)) &&
+ all(lessThanEqual(fma(frustum1.b1, sphere.xx, fma(frustum1.b2, sphere.yy, fma(frustum1.b3, sphere.zz, frustum1.b4))), -sphere.ww));
+}
+
+// 236 - 92 = 144 spirv instruction results
+bool testSphere2(vec4 sphere) {
+ return
+ fma(frustum2.nx.x, sphere.x, fma(frustum2.nx.y, sphere.y, fma(frustum2.nx.z, sphere.z, frustum2.nx.w))) >= -sphere.w &&
+ fma(frustum2.px.x, sphere.x, fma(frustum2.px.y, sphere.y, fma(frustum2.px.z, sphere.z, frustum2.px.w))) >= -sphere.w &&
+ fma(frustum2.ny.x, sphere.x, fma(frustum2.ny.y, sphere.y, fma(frustum2.ny.z, sphere.z, frustum2.ny.w))) >= -sphere.w &&
+ fma(frustum2.py.x, sphere.x, fma(frustum2.py.y, sphere.y, fma(frustum2.py.z, sphere.z, frustum2.py.w))) >= -sphere.w &&
+ fma(frustum2.nz.x, sphere.x, fma(frustum2.nz.y, sphere.y, fma(frustum2.nz.z, sphere.z, frustum2.nz.w))) >= -sphere.w &&
+ fma(frustum2.pz.x, sphere.x, fma(frustum2.pz.y, sphere.y, fma(frustum2.pz.z, sphere.z, frustum2.pz.w))) >= -sphere.w;
+}
+
+// 322 - 240 = 82 spirv instruction results
+bool testSphere3(vec4 sphere) {
+ return
+ (dot(frustum2.nx.xyz, sphere.xyz) + frustum2.nx.w) >= -sphere.w &&
+ (dot(frustum2.px.xyz, sphere.xyz) + frustum2.px.w) >= -sphere.w &&
+ (dot(frustum2.ny.xyz, sphere.xyz) + frustum2.ny.w) >= -sphere.w &&
+ (dot(frustum2.py.xyz, sphere.xyz) + frustum2.py.w) >= -sphere.w &&
+ (dot(frustum2.nz.xyz, sphere.xyz) + frustum2.nz.w) >= -sphere.w &&
+ (dot(frustum2.pz.xyz, sphere.xyz) + frustum2.pz.w) >= -sphere.w;
+}
+
+void main() {
+ result.res1 = testSphere1(vec4(0., 1., 0., 1.));
+ result.res2 = testSphere2(vec4(0., 1., 0., 1.));
+ result.res3 = testSphere3(vec4(0., 1., 0., 1.));
+}
diff --git a/src/main/resources/assets/flywheel/flywheel/debug/debug.frag b/src/main/resources/assets/flywheel/flywheel/debug/debug.frag
new file mode 100644
index 000000000..ab41dc91f
--- /dev/null
+++ b/src/main/resources/assets/flywheel/flywheel/debug/debug.frag
@@ -0,0 +1,5 @@
+out vec4 fragColor;
+
+void main() {
+ fragColor = vec4(1.0, 1.0, 1.0, 0.2);
+}
diff --git a/src/main/resources/assets/flywheel/flywheel/debug/debug.vert b/src/main/resources/assets/flywheel/flywheel/debug/debug.vert
new file mode 100644
index 000000000..6a5d60ca1
--- /dev/null
+++ b/src/main/resources/assets/flywheel/flywheel/debug/debug.vert
@@ -0,0 +1,7 @@
+#use "flywheel:uniform/view.glsl"
+
+layout(location = 0) in vec3 worldPos;
+
+void main() {
+ gl_Position = flw_viewProjection * vec4(worldPos, 1.0);
+}
diff --git a/src/main/resources/assets/flywheel/flywheel/instance/oriented.vert b/src/main/resources/assets/flywheel/flywheel/instance/oriented.vert
index 93a50ddee..880d55de4 100644
--- a/src/main/resources/assets/flywheel/flywheel/instance/oriented.vert
+++ b/src/main/resources/assets/flywheel/flywheel/instance/oriented.vert
@@ -1,11 +1,11 @@
#use "flywheel:api/vertex.glsl"
#use "flywheel:util/quaternion.glsl"
-layout(location = 0) in ivec2 oriented_light;
-layout(location = 1) in vec4 oriented_color;
-layout(location = 2) in vec3 oriented_pos;
-layout(location = 3) in vec3 oriented_pivot;
-layout(location = 4) in vec4 oriented_rotation;
+layout(location = FLW_INSTANCE_BASE_INDEX + 0) in ivec2 oriented_light;
+layout(location = FLW_INSTANCE_BASE_INDEX + 1) in vec4 oriented_color;
+layout(location = FLW_INSTANCE_BASE_INDEX + 2) in vec3 oriented_pos;
+layout(location = FLW_INSTANCE_BASE_INDEX + 3) in vec3 oriented_pivot;
+layout(location = FLW_INSTANCE_BASE_INDEX + 4) in vec4 oriented_rotation;
void flw_instanceVertex() {
flw_vertexPos = vec4(rotateVertexByQuat(flw_vertexPos.xyz - oriented_pivot, oriented_rotation) + oriented_pivot + oriented_pos, 1.0);
diff --git a/src/main/resources/assets/flywheel/flywheel/instance/oriented_indirect.glsl b/src/main/resources/assets/flywheel/flywheel/instance/oriented_indirect.glsl
new file mode 100644
index 000000000..cd0f90fb2
--- /dev/null
+++ b/src/main/resources/assets/flywheel/flywheel/instance/oriented_indirect.glsl
@@ -0,0 +1,32 @@
+#use "flywheel:api/vertex.glsl"
+#use "flywheel:util/quaternion.glsl"
+#use "flywheel:util/types.glsl"
+
+#define FLW_INSTANCE_STRUCT Instance
+struct Instance {
+ Vec4F rotation;
+ Vec3F pos;
+ Vec3F pivot;
+ uint light;
+ uint color;
+};
+
+void flw_transformBoundingSphere(in Instance i, inout vec3 center, inout float radius) {
+ 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
+void flw_instanceVertex(Instance i) {
+ 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
diff --git a/src/main/resources/assets/flywheel/flywheel/instance/transformed.vert b/src/main/resources/assets/flywheel/flywheel/instance/transformed.vert
index e2ea23ef4..2c6161d18 100644
--- a/src/main/resources/assets/flywheel/flywheel/instance/transformed.vert
+++ b/src/main/resources/assets/flywheel/flywheel/instance/transformed.vert
@@ -1,9 +1,9 @@
#use "flywheel:api/vertex.glsl"
-layout(location = 0) in ivec2 transformed_light;
-layout(location = 1) in vec4 transformed_color;
-layout(location = 2) in mat4 transformed_pose;
-layout(location = 6) in mat3 transformed_normal;
+layout(location = FLW_INSTANCE_BASE_INDEX + 0) in ivec2 transformed_light;
+layout(location = FLW_INSTANCE_BASE_INDEX + 1) in vec4 transformed_color;
+layout(location = FLW_INSTANCE_BASE_INDEX + 2) in mat4 transformed_pose;
+layout(location = FLW_INSTANCE_BASE_INDEX + 6) in mat3 transformed_normal;
void flw_instanceVertex() {
flw_vertexPos = transformed_pose * flw_vertexPos;
diff --git a/src/main/resources/assets/flywheel/flywheel/instance/transformed_indirect.glsl b/src/main/resources/assets/flywheel/flywheel/instance/transformed_indirect.glsl
new file mode 100644
index 000000000..f0067c4b9
--- /dev/null
+++ b/src/main/resources/assets/flywheel/flywheel/instance/transformed_indirect.glsl
@@ -0,0 +1,27 @@
+#use "flywheel:api/vertex.glsl"
+#use "flywheel:util/types.glsl"
+
+#define FLW_INSTANCE_STRUCT Instance
+struct Instance {
+ Mat4F pose;
+ Mat3F normal;
+ uint color;
+ uint light;
+};
+
+void flw_transformBoundingSphere(in Instance i, inout vec3 center, inout float radius) {
+ mat4 pose = unpackMat4F(i.pose);
+ center = (pose * vec4(center, 1.0)).xyz;
+
+ float scale = max(length(pose[0].xyz), max(length(pose[1].xyz), length(pose[2].xyz)));
+ radius *= scale;
+}
+
+ #ifdef VERTEX_SHADER
+void flw_instanceVertex(Instance i) {
+ 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
diff --git a/src/main/resources/assets/flywheel/flywheel/layout/block.vert b/src/main/resources/assets/flywheel/flywheel/layout/block.vert
index a3af3b16d..4123724aa 100644
--- a/src/main/resources/assets/flywheel/flywheel/layout/block.vert
+++ b/src/main/resources/assets/flywheel/flywheel/layout/block.vert
@@ -5,6 +5,7 @@ layout(location = 1) in vec4 _flw_v_color;
layout(location = 2) in vec2 _flw_v_texCoord;
layout(location = 3) in ivec2 _flw_v_light;
layout(location = 4) in vec3 _flw_v_normal;
+#define FLW_INSTANCE_BASE_INDEX 5
void flw_layoutVertex() {
flw_vertexPos = vec4(_flw_v_pos, 1.0);
diff --git a/src/main/resources/assets/flywheel/flywheel/layout/pos_tex_normal.vert b/src/main/resources/assets/flywheel/flywheel/layout/pos_tex_normal.vert
index 4ecfb2c3c..80b02f8bc 100644
--- a/src/main/resources/assets/flywheel/flywheel/layout/pos_tex_normal.vert
+++ b/src/main/resources/assets/flywheel/flywheel/layout/pos_tex_normal.vert
@@ -3,6 +3,7 @@
layout(location = 0) in vec3 _flw_v_pos;
layout(location = 1) in vec2 _flw_v_texCoord;
layout(location = 2) in vec3 _flw_v_normal;
+#define FLW_INSTANCE_BASE_INDEX 3
void flw_layoutVertex() {
flw_vertexPos = vec4(_flw_v_pos, 1.0);
diff --git a/src/main/resources/assets/flywheel/flywheel/pipeline/draw.frag b/src/main/resources/assets/flywheel/flywheel/pipeline/draw.frag
new file mode 100644
index 000000000..f57dd9a87
--- /dev/null
+++ b/src/main/resources/assets/flywheel/flywheel/pipeline/draw.frag
@@ -0,0 +1,7 @@
+#use "flywheel:api/fragment.glsl"
+
+void main() {
+ flw_initFragment();
+ flw_materialFragment();
+ flw_contextFragment();
+}
diff --git a/src/main/resources/assets/flywheel/flywheel/pipeline/indirect_cull.glsl b/src/main/resources/assets/flywheel/flywheel/pipeline/indirect_cull.glsl
new file mode 100644
index 000000000..b1706edf0
--- /dev/null
+++ b/src/main/resources/assets/flywheel/flywheel/pipeline/indirect_cull.glsl
@@ -0,0 +1,68 @@
+#define FLW_SUBGROUP_SIZE 32
+layout(local_size_x = FLW_SUBGROUP_SIZE) in;
+#use "flywheel:api/cull.glsl"
+#use "flywheel:uniform/frustum.glsl"
+#use "flywheel:util/types.glsl"
+
+struct MeshDrawCommand {
+ uint indexCount;
+ uint instanceCount;
+ uint firstIndex;
+ uint vertexOffset;
+ uint baseInstance;
+
+ BoundingSphere boundingSphere;
+};
+
+// populated by instancers
+layout(std430, binding = 0) restrict readonly buffer ObjectBuffer {
+ FLW_INSTANCE_STRUCT objects[];
+};
+
+layout(std430, binding = 1) restrict writeonly buffer TargetBuffer {
+ uint objectIDs[];
+};
+
+layout(std430, binding = 2) restrict readonly buffer BatchBuffer {
+ uint batchIDs[];
+};
+
+layout(std430, binding = 3) restrict buffer DrawCommands {
+ MeshDrawCommand drawCommands[];
+};
+
+// 83 - 27 = 56 spirv instruction results
+bool testSphere(vec3 center, float radius) {
+ bvec4 xyInside = greaterThanEqual(fma(flw_planes.xyX, center.xxxx, fma(flw_planes.xyY, center.yyyy, fma(flw_planes.xyZ, center.zzzz, flw_planes.xyW))), -radius.xxxx);
+ bvec2 zInside = greaterThanEqual(fma(flw_planes.zX, center.xx, fma(flw_planes.zY, center.yy, fma(flw_planes.zZ, center.zz, flw_planes.zW))), -radius.xx);
+
+ return all(xyInside) && all(zInside);
+}
+
+bool isVisible() {
+ BoundingSphere sphere = drawCommands[flw_batchID].boundingSphere;
+
+ vec3 center;
+ float radius;
+ unpackBoundingSphere(sphere, center, radius);
+ flw_transformBoundingSphere(objects[flw_objectID], center, radius);
+
+ return testSphere(center, radius);
+}
+
+void main() {
+ flw_objectID = gl_GlobalInvocationID.x;
+
+ if (flw_objectID >= objects.length()) {
+ return;
+ }
+
+ flw_batchID = batchIDs[flw_objectID];
+
+ if (isVisible()) {
+ uint batchIndex = atomicAdd(drawCommands[flw_batchID].instanceCount, 1);
+ uint globalIndex = drawCommands[flw_batchID].baseInstance + batchIndex;
+
+ objectIDs[globalIndex] = flw_objectID;
+ }
+}
diff --git a/src/main/resources/assets/flywheel/flywheel/pipeline/indirect_draw.vert b/src/main/resources/assets/flywheel/flywheel/pipeline/indirect_draw.vert
new file mode 100644
index 000000000..7583ebbd6
--- /dev/null
+++ b/src/main/resources/assets/flywheel/flywheel/pipeline/indirect_draw.vert
@@ -0,0 +1,18 @@
+#use "flywheel:api/vertex.glsl"
+
+layout(std430, binding = 0) restrict readonly buffer ObjectBuffer {
+ FLW_INSTANCE_STRUCT objects[];
+};
+
+layout(std430, binding = 1) restrict readonly buffer TargetBuffer {
+ uint objectIDs[];
+};
+
+void main() {
+ uint instanceIndex = objectIDs[gl_BaseInstance + gl_InstanceID];
+ flw_layoutVertex();
+ FLW_INSTANCE_STRUCT i = objects[instanceIndex];
+ flw_instanceVertex(i);
+ flw_materialVertex();
+ flw_contextVertex();
+}
diff --git a/src/main/resources/assets/flywheel/flywheel/pipeline/instanced_arrays_draw.vert b/src/main/resources/assets/flywheel/flywheel/pipeline/instanced_arrays_draw.vert
new file mode 100644
index 000000000..0b75edde3
--- /dev/null
+++ b/src/main/resources/assets/flywheel/flywheel/pipeline/instanced_arrays_draw.vert
@@ -0,0 +1,8 @@
+#use "flywheel:api/vertex.glsl"
+
+void main() {
+ flw_layoutVertex();
+ flw_instanceVertex();
+ flw_materialVertex();
+ flw_contextVertex();
+}
diff --git a/src/main/resources/assets/flywheel/flywheel/uniform/frustum.glsl b/src/main/resources/assets/flywheel/flywheel/uniform/frustum.glsl
new file mode 100644
index 000000000..39866a922
--- /dev/null
+++ b/src/main/resources/assets/flywheel/flywheel/uniform/frustum.glsl
@@ -0,0 +1,14 @@
+struct FLWPackedPlanes {
+ vec4 xyX; //
+ vec4 xyY; //
+ vec4 xyZ; //
+ vec4 xyW; //
+ vec2 zX; //
+ vec2 zY; //
+ vec2 zZ; //
+ vec2 zW; //
+};
+
+layout(std140, binding = 2) uniform FLWFrustum {
+ FLWPackedPlanes flw_planes;
+};
diff --git a/src/main/resources/assets/flywheel/flywheel/util/types.glsl b/src/main/resources/assets/flywheel/flywheel/util/types.glsl
new file mode 100644
index 000000000..3a93dbda4
--- /dev/null
+++ b/src/main/resources/assets/flywheel/flywheel/util/types.glsl
@@ -0,0 +1,63 @@
+// Types intended for use is SSBOs to achieve tighter data packing.
+
+struct Vec3F {
+ float x;
+ float y;
+ 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 = unpackVec3F(sphere.center);
+ radius = sphere.radius;
+}
diff --git a/src/main/resources/flywheel.mixins.json b/src/main/resources/flywheel.mixins.json
index f0cf17998..1d26183e7 100644
--- a/src/main/resources/flywheel.mixins.json
+++ b/src/main/resources/flywheel.mixins.json
@@ -11,6 +11,7 @@
"BufferUploaderMixin",
"ChunkRebuildHooksMixin",
"ClientLevelMixin",
+ "ClientMainMixin",
"EntityTypeMixin",
"FixFabulousDepthMixin",
"FogUpdateMixin",