mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-02-04 17:24:59 +01:00
Merge branch '1.18/culling' into 1.18/next
# Conflicts: # build.gradle # src/main/java/com/jozufozu/flywheel/Flywheel.java # src/main/java/com/jozufozu/flywheel/backend/Loader.java # src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java # src/main/java/com/jozufozu/flywheel/backend/instancing/batching/BatchingDrawTracker.java # src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/MeshPool.java # src/main/java/com/jozufozu/flywheel/core/compile/ProgramCompiler.java # src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingRenderer.java # src/main/java/com/jozufozu/flywheel/core/model/ModelUtil.java # src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java # src/main/java/com/jozufozu/flywheel/mixin/matrix/Matrix3fMixin.java # src/main/java/com/jozufozu/flywheel/mixin/matrix/Matrix4fMixin.java
This commit is contained in:
commit
4bb7c4bd48
103 changed files with 2480 additions and 440 deletions
|
@ -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
|
||||
|
|
32
build.gradle
32
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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.<p>
|
||||
* 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();
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.jozufozu.flywheel.api.struct;
|
||||
|
||||
import com.jozufozu.flywheel.api.instancer.InstancedPart;
|
||||
|
||||
public interface StorageBufferWriter<T extends InstancedPart> {
|
||||
|
||||
void write(final long ptr, final T instance);
|
||||
|
||||
int getAlignment();
|
||||
}
|
|
@ -29,6 +29,10 @@ public interface StructType<S extends InstancedPart> {
|
|||
|
||||
VertexTransformer<S> getVertexTransformer();
|
||||
|
||||
StorageBufferWriter<S> getStorageBufferWriter();
|
||||
|
||||
FileResolution getIndirectShader();
|
||||
|
||||
interface VertexTransformer<S extends InstancedPart> {
|
||||
void transform(MutableVertexList vertexList, S struct, ClientLevel level);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.jozufozu.flywheel.api.vertex;
|
||||
|
||||
import com.dreizak.miniball.model.PointSet;
|
||||
|
||||
/**
|
||||
* A read only view of a vertex buffer.
|
||||
*
|
||||
|
@ -9,7 +11,7 @@ package com.jozufozu.flywheel.api.vertex;
|
|||
* </p>
|
||||
* TODO: more flexible elements?
|
||||
*/
|
||||
public interface VertexList {
|
||||
public interface VertexList extends PointSet {
|
||||
float x(int index);
|
||||
|
||||
float y(int index);
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,4 +26,8 @@ public enum GLSLVersion {
|
|||
public String toString() {
|
||||
return Integer.toString(version);
|
||||
}
|
||||
|
||||
public String getVersionLine() {
|
||||
return "#version " + version + '\n';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<D extends InstancedPart> implements Inst
|
|||
|
||||
/**
|
||||
* Copy a data from another Instancer to this.
|
||||
*
|
||||
* <p>
|
||||
* 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<D extends InstancedPart> implements Inst
|
|||
return data.size();
|
||||
}
|
||||
|
||||
public List<D> getRange(int start, int end) {
|
||||
return data.subList(start, end);
|
||||
}
|
||||
|
||||
public List<D> getAll() {
|
||||
return data;
|
||||
}
|
||||
|
||||
protected void removeDeletedInstances() {
|
||||
// Figure out which elements are to be removed.
|
||||
final int oldSize = this.data.size();
|
||||
|
|
|
@ -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<T> {
|
||||
|
@ -100,27 +99,26 @@ public abstract class InstanceManager<T> {
|
|||
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 <I> void distributeWork(TaskEngine taskEngine, List<I> instances, Consumer<I> action) {
|
||||
|
@ -140,7 +138,7 @@ public abstract class InstanceManager<T> {
|
|||
}
|
||||
}
|
||||
|
||||
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<T> {
|
|||
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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* A ProgramCompiler is also responsible for deleting programs and shaders on renderer reload.
|
||||
* </p>
|
||||
*/
|
||||
public class PipelineCompiler extends Memoizer<PipelineCompiler.Context, GlProgram> {
|
||||
|
||||
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<SourceFile> getVertexComponents() {
|
||||
return ImmutableList.of(vertexType.getLayoutShader().getFile(), instanceShader.getFile(), material.getVertexShader().getFile(),
|
||||
contextShader.getVertexShader(), pipelineShader.vertex().getFile());
|
||||
}
|
||||
|
||||
ImmutableList<SourceFile> 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<ShaderCompiler.Context, GlShader> {
|
||||
|
||||
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.<ResourceLocation>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<SourceFile> components) {
|
||||
|
||||
public String generateHeader() {
|
||||
return CompileUtil.generateHeader(glslVersion, shaderType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,14 +19,6 @@ public class CPUInstancer<D extends InstancedPart> extends AbstractInstancer<D>
|
|||
}
|
||||
}
|
||||
|
||||
public List<D> getRange(int start, int end) {
|
||||
return data.subList(start, end);
|
||||
}
|
||||
|
||||
public List<D> getAll() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDirty() {
|
||||
// noop
|
||||
|
|
|
@ -120,6 +120,10 @@ public class DrawBuffer {
|
|||
}
|
||||
|
||||
public void free() {
|
||||
if (memory == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
memory.free();
|
||||
memory = null;
|
||||
buffer = null;
|
||||
|
|
|
@ -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<T extends BlockEntity> extends Abstrac
|
|||
return pos;
|
||||
}
|
||||
|
||||
protected InstancerFactory<TransformedPart> getTransformFactory() {
|
||||
return instancerManager.factory(StructTypes.TRANSFORMED);
|
||||
}
|
||||
|
||||
protected InstancerFactory<OrientedPart> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<E extends Entity> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<FileResolution, GlProgram> {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<T extends InstancedPart> {
|
||||
|
||||
private static final int BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT;
|
||||
|
||||
final StorageBufferWriter<T> storageBufferWriter;
|
||||
final GlProgram compute;
|
||||
final GlProgram draw;
|
||||
private final VertexType vertexType;
|
||||
private final long objectStride;
|
||||
|
||||
final IndirectBuffers buffers;
|
||||
|
||||
final IndirectMeshPool meshPool;
|
||||
private final int elementBuffer;
|
||||
|
||||
int vertexArray;
|
||||
|
||||
final IndirectDrawSet<T> drawSet = new IndirectDrawSet<>();
|
||||
|
||||
private boolean hasCulledThisFrame;
|
||||
private boolean needsMemoryBarrier;
|
||||
private int instanceCountThisFrame;
|
||||
|
||||
IndirectCullingGroup(StructType<T> structType, VertexType vertexType) {
|
||||
this.vertexType = vertexType;
|
||||
storageBufferWriter = structType.getStorageBufferWriter();
|
||||
|
||||
objectStride = storageBufferWriter.getAlignment();
|
||||
buffers = new IndirectBuffers(objectStride);
|
||||
buffers.createBuffers();
|
||||
buffers.createObjectStorage(128);
|
||||
buffers.createDrawStorage(2);
|
||||
|
||||
meshPool = new IndirectMeshPool(vertexType, 1024);
|
||||
|
||||
vertexArray = glCreateVertexArrays();
|
||||
|
||||
elementBuffer = QuadConverter.getInstance()
|
||||
.quads2Tris(2048).buffer.handle();
|
||||
setupVertexArray();
|
||||
|
||||
var indirectShader = structType.getIndirectShader();
|
||||
compute = ComputeCullerCompiler.INSTANCE.get(indirectShader);
|
||||
draw = PipelineCompiler.INSTANCE.get(new PipelineCompiler.Context(vertexType, Materials.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();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package com.jozufozu.flywheel.backend.instancing.indirect;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import com.jozufozu.flywheel.api.instancer.InstancedPart;
|
||||
import com.jozufozu.flywheel.api.material.Material;
|
||||
|
||||
public final class IndirectDraw<T extends InstancedPart> {
|
||||
final IndirectInstancer<T> instancer;
|
||||
final IndirectMeshPool.BufferedMesh mesh;
|
||||
final Material material;
|
||||
int baseInstance = -1;
|
||||
|
||||
boolean needsFullWrite = true;
|
||||
|
||||
IndirectDraw(IndirectInstancer<T> instancer, Material material, IndirectMeshPool.BufferedMesh mesh) {
|
||||
this.instancer = instancer;
|
||||
this.material = material;
|
||||
this.mesh = mesh;
|
||||
}
|
||||
|
||||
public void prepare(int baseInstance) {
|
||||
instancer.update();
|
||||
if (baseInstance == this.baseInstance) {
|
||||
needsFullWrite = false;
|
||||
return;
|
||||
}
|
||||
this.baseInstance = baseInstance;
|
||||
needsFullWrite = true;
|
||||
}
|
||||
|
||||
void writeObjects(long objectPtr, long batchIDPtr, int batchID) {
|
||||
if (needsFullWrite) {
|
||||
instancer.writeFull(objectPtr, batchIDPtr, batchID);
|
||||
} else if (instancer.anyToUpdate) {
|
||||
instancer.writeSparse(objectPtr, batchIDPtr, batchID);
|
||||
}
|
||||
instancer.anyToUpdate = false;
|
||||
}
|
||||
|
||||
public void writeIndirectCommand(long ptr) {
|
||||
var boundingSphere = mesh.mesh.getBoundingSphere();
|
||||
|
||||
MemoryUtil.memPutInt(ptr, mesh.getIndexCount()); // count
|
||||
MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be incremented by the compute shader
|
||||
MemoryUtil.memPutInt(ptr + 8, 0); // firstIndex - all models share the same index buffer
|
||||
MemoryUtil.memPutInt(ptr + 12, mesh.getBaseVertex()); // baseVertex
|
||||
MemoryUtil.memPutInt(ptr + 16, baseInstance); // baseInstance
|
||||
|
||||
boundingSphere.getToAddress(ptr + 20); // boundingSphere
|
||||
}
|
||||
}
|
|
@ -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<Pair<StructType<?>, VertexType>, IndirectCullingGroup<?>> lists = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <D extends InstancedPart> void add(IndirectInstancer<D> instancer, Material material, Mesh mesh) {
|
||||
var indirectList = (IndirectCullingGroup<D>) lists.computeIfAbsent(Pair.of(instancer.type, mesh.getVertexType()), p -> new IndirectCullingGroup<>(p.first(), p.second()));
|
||||
|
||||
indirectList.drawSet.add(instancer, material, indirectList.meshPool.alloc(mesh));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package com.jozufozu.flywheel.backend.instancing.indirect;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
|
||||
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
|
||||
import static org.lwjgl.opengl.GL43.glMultiDrawElementsIndirect;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.jozufozu.flywheel.api.RenderStage;
|
||||
import com.jozufozu.flywheel.api.instancer.InstancedPart;
|
||||
import com.jozufozu.flywheel.api.material.Material;
|
||||
|
||||
public class IndirectDrawSet<T extends InstancedPart> {
|
||||
|
||||
final List<IndirectDraw<T>> indirectDraws = new ArrayList<>();
|
||||
|
||||
public boolean isEmpty() {
|
||||
return indirectDraws.isEmpty();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return indirectDraws.size();
|
||||
}
|
||||
|
||||
public void add(IndirectInstancer<T> instancer, Material material, IndirectMeshPool.BufferedMesh bufferedMesh) {
|
||||
indirectDraws.add(new IndirectDraw<>(instancer, material, bufferedMesh));
|
||||
}
|
||||
|
||||
public void submit(RenderStage stage) {
|
||||
final int stride = (int) IndirectBuffers.DRAW_COMMAND_STRIDE;
|
||||
for (int i = 0, indirectDrawsSize = indirectDraws.size(); i < indirectDrawsSize; i++) {
|
||||
var batch = indirectDraws.get(i);
|
||||
var material = batch.material;
|
||||
|
||||
if (material.getRenderStage() != stage) {
|
||||
continue;
|
||||
}
|
||||
material.setup();
|
||||
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, i * stride, 1, stride);
|
||||
material.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains(RenderStage stage) {
|
||||
for (var draw : indirectDraws) {
|
||||
if (draw.material.getRenderStage() == stage) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -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<StructType<?>, IndirectFactory<?>> factories = new HashMap<>();
|
||||
|
||||
protected final List<IndirectModel<?>> uninitializedModels = new ArrayList<>();
|
||||
protected final IndirectDrawManager indirectDrawManager = new IndirectDrawManager();
|
||||
|
||||
/**
|
||||
* The set of instance managers that are attached to this engine.
|
||||
*/
|
||||
private final WeakHashSet<InstanceManager<?>> instanceManagers;
|
||||
|
||||
public IndirectEngine(ContextShader context) {
|
||||
this.context = context;
|
||||
|
||||
this.instanceManagers = new WeakHashSet<>();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@NotNull
|
||||
@Override
|
||||
public <D extends InstancedPart> IndirectFactory<D> factory(StructType<D> type) {
|
||||
return (IndirectFactory<D>) factories.computeIfAbsent(type, this::createFactory);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private <D extends InstancedPart> IndirectFactory<D> createFactory(StructType<D> 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<String> info) {
|
||||
info.add("GL46 Indirect");
|
||||
info.add("Origin: " + originCoordinate.getX() + ", " + originCoordinate.getY() + ", " + originCoordinate.getZ());
|
||||
}
|
||||
}
|
|
@ -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<D extends InstancedPart> implements InstancerFactory<D> {
|
||||
|
||||
protected final Map<Model, IndirectModel<D>> models = new HashMap<>();
|
||||
protected final StructType<D> type;
|
||||
private final Consumer<IndirectModel<D>> creationListener;
|
||||
|
||||
public IndirectFactory(StructType<D> type, Consumer<IndirectModel<D>> creationListener) {
|
||||
this.type = type;
|
||||
this.creationListener = creationListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instancer<D> 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<D> createInstancer(Model model) {
|
||||
var instancer = new IndirectModel<>(type, model);
|
||||
this.creationListener.accept(instancer);
|
||||
return instancer;
|
||||
}
|
||||
}
|
|
@ -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<D extends InstancedPart> extends AbstractInstancer<D> {
|
||||
|
||||
public final BufferLayout instanceFormat;
|
||||
public final IndirectModel<D> parent;
|
||||
int instanceCount = 0;
|
||||
|
||||
boolean anyToUpdate;
|
||||
|
||||
public IndirectInstancer(IndirectModel<D> parent, StructType<D> 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
|
||||
}
|
||||
}
|
|
@ -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<Mesh, BufferedMesh> meshes = new HashMap<>();
|
||||
private final List<BufferedMesh> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<D extends InstancedPart> {
|
||||
|
||||
private final Model model;
|
||||
private final IndirectInstancer<D> instancer;
|
||||
|
||||
public IndirectModel(StructType<D> 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<D> getInstancer() {
|
||||
return instancer;
|
||||
}
|
||||
|
||||
public Model getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public int getVertexCount() {
|
||||
return model.getVertexCount() * instancer.instanceCount;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.backend.instancing.indirect;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import net.minecraft.MethodsReturnNonnullByDefault;
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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<String, BackendType> lookup;
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<ErrorReporter, SourceFile> LAYOUT_VERTEX = SourceChecks.checkFunctionArity("flw_layoutVertex", 0);
|
||||
public static final BiConsumer<ErrorReporter, SourceFile> INSTANCE_VERTEX = SourceChecks.checkFunctionArity("flw_instanceVertex", 0);
|
||||
public static final BiConsumer<ErrorReporter, SourceFile> LAYOUT_VERTEX = SourceChecks.checkFunctionArity("flw_layoutVertex", 0)
|
||||
.andThen(SourceChecks.checkDefine("FLW_INSTANCE_BASE_INDEX"));
|
||||
public static final BiConsumer<ErrorReporter, SourceFile> INSTANCE_VERTEX = SourceChecks.checkFunctionParameterTypeExists("flw_instanceVertex", 1, 0)
|
||||
.andThen(SourceChecks.checkDefine("FLW_INSTANCE_STRUCT"));
|
||||
public static final BiConsumer<ErrorReporter, SourceFile> MATERIAL_VERTEX = SourceChecks.checkFunctionArity("flw_materialVertex", 0);
|
||||
public static final BiConsumer<ErrorReporter, SourceFile> MATERIAL_FRAGMENT = SourceChecks.checkFunctionArity("flw_materialFragment", 0);
|
||||
public static final BiConsumer<ErrorReporter, SourceFile> CONTEXT_VERTEX = SourceChecks.checkFunctionArity("flw_contextVertex", 0);
|
||||
public static final BiConsumer<ErrorReporter, SourceFile> CONTEXT_FRAGMENT = SourceChecks.checkFunctionArity("flw_contextFragment", 0).andThen(SourceChecks.checkFunctionArity("flw_initFragment", 0));
|
||||
|
||||
public static final BiConsumer<ErrorReporter, SourceFile> 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");
|
||||
}
|
||||
}
|
||||
|
|
104
src/main/java/com/jozufozu/flywheel/core/DebugRender.java
Normal file
104
src/main/java/com/jozufozu/flywheel/core/DebugRender.java
Normal file
|
@ -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<GlProgram> SHADER = Lazy.of(() -> DebugCompiler.INSTANCE.get(new DebugCompiler.Context(Files.VERTEX, Files.FRAGMENT)));
|
||||
|
||||
private static final Lazy<Frustum> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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.<p>
|
||||
* Useful for writing experimental shaders or
|
||||
*/
|
||||
public class DebugCompiler extends Memoizer<DebugCompiler.Context, GlProgram> {
|
||||
|
||||
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<ShaderCompiler.Context, GlShader> {
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<FragmentCompiler.Context, GlShader> {
|
||||
|
||||
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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* A ProgramCompiler is also responsible for deleting programs and shaders on renderer reload.
|
||||
* </p>
|
||||
*/
|
||||
public class ProgramCompiler extends Memoizer<ProgramCompiler.Context, GlProgram> {
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
|
@ -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<VertexCompiler.Context, GlShader> {
|
||||
|
||||
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<Pair<Span, String>>();
|
||||
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) {
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<PartBuilder.CuboidBuilder> 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<PartBuilder.CuboidBuilder> cuboids) {
|
||||
int vertices = 0;
|
||||
for (PartBuilder.CuboidBuilder cuboid : cuboids) {
|
||||
vertices += cuboid.vertices();
|
||||
}
|
||||
return vertices;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ public class BufferLayout {
|
|||
this.stride = calculateStride(this.attributes) + padding;
|
||||
}
|
||||
|
||||
public Collection<VertexAttribute> getAttributes() {
|
||||
public List<VertexAttribute> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 + "}";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
// }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Pair<Span, String>> replacements) {
|
||||
public String generateFinalSource(CompilationContext context) {
|
||||
List<Pair<Span, String>> 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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<OrientedPart> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -34,11 +34,21 @@ public class OrientedType implements StructType<OrientedPart> {
|
|||
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<OrientedPart> getVertexTransformer() {
|
||||
return (vertexList, struct, level) -> {
|
||||
|
|
|
@ -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<TransformedPart> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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<TransformedPart> {
|
|||
return TransformedWriter.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageBufferWriter<TransformedPart> getStorageBufferWriter() {
|
||||
return TransformedStorageWriter.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileResolution getInstanceShader() {
|
||||
return Components.Files.TRANSFORMED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileResolution getIndirectShader() {
|
||||
return Components.Files.TRANSFORMED_INDIRECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexTransformer<TransformedPart> getVertexTransformer() {
|
||||
return (vertexList, struct, level) -> {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,10 @@ public class Lazy<T> implements Supplier<T> {
|
|||
return value;
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
public <Q> Lazy<Q> lazyMap(Function<T, Q> func) {
|
||||
return new Lazy<>(() -> func.apply(get()));
|
||||
}
|
||||
|
|
|
@ -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}.
|
||||
* <p>
|
||||
|
@ -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.<p>
|
||||
* Uses a different format that is friendly towards an optimized instruction-parallel
|
||||
* implementation of sphere-frustum intersection.<p>
|
||||
* The format is as follows:<p>
|
||||
* {@code vec4(nxX, pxX, nyX, pyX)}<br>
|
||||
* {@code vec4(nxY, pxY, nyY, pyY)}<br>
|
||||
* {@code vec4(nxZ, pxZ, nyZ, pyZ)}<br>
|
||||
* {@code vec4(nxW, pxW, nyW, pyW)}<br>
|
||||
* {@code vec2(nzX, pzX)}<br>
|
||||
* {@code vec2(nzY, pzY)}<br>
|
||||
* {@code vec2(nzZ, pzZ)}<br>
|
||||
* {@code vec2(nzW, pzW)}<br>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
#ifdef COMPUTE_SHADER
|
||||
uint flw_objectID;
|
||||
uint flw_batchID;
|
||||
#endif
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.));
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = vec4(1.0, 1.0, 1.0, 0.2);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#use "flywheel:api/fragment.glsl"
|
||||
|
||||
void main() {
|
||||
flw_initFragment();
|
||||
flw_materialFragment();
|
||||
flw_contextFragment();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#use "flywheel:api/vertex.glsl"
|
||||
|
||||
void main() {
|
||||
flw_layoutVertex();
|
||||
flw_instanceVertex();
|
||||
flw_materialVertex();
|
||||
flw_contextVertex();
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue