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:
Jozufozu 2022-08-22 15:40:33 -07:00
commit 4bb7c4bd48
103 changed files with 2480 additions and 440 deletions

View file

@ -4,7 +4,7 @@ root = true
[*]
indent_style = space
indent_size = 4
continuation_indent_size = 8
ij_continuation_indent_size = 8
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
@ -16,6 +16,10 @@ indent_size = 2
[*.java]
indent_style = tab
ij_java_blank_lines_before_class_end = 0
ij_java_blank_lines_after_anonymous_class_header = 0
ij_java_blank_lines_after_class_header = 0
ij_java_blank_lines_before_method_body = 0
ij_java_else_on_new_line = false
ij_continuation_indent_size = 8
ij_java_class_count_to_use_import_on_demand = 99

View file

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

View file

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

View file

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

View file

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

View file

@ -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() {

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,7 @@
package com.jozufozu.flywheel.api.vertex;
import com.dreizak.miniball.model.PointSet;
/**
* A read only view of a vertex buffer.
*
@ -9,7 +11,7 @@ package com.jozufozu.flywheel.api.vertex;
* </p>
* TODO: more flexible elements?
*/
public interface VertexList {
public interface VertexList extends PointSet {
float x(int index);
float y(int index);
@ -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);
};
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.backend.instancing;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.instancer.Instancer;
@ -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();

View file

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

View file

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

View file

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

View file

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

View file

@ -120,6 +120,10 @@ public class DrawBuffer {
}
public void free() {
if (memory == null) {
return;
}
memory.free();
memory = null;
buffer = null;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,52 @@
package com.jozufozu.flywheel.backend.instancing.indirect;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.material.Material;
public final class IndirectDraw<T extends InstancedPart> {
final IndirectInstancer<T> instancer;
final IndirectMeshPool.BufferedMesh mesh;
final Material material;
int baseInstance = -1;
boolean needsFullWrite = true;
IndirectDraw(IndirectInstancer<T> instancer, Material material, IndirectMeshPool.BufferedMesh mesh) {
this.instancer = instancer;
this.material = material;
this.mesh = mesh;
}
public void prepare(int baseInstance) {
instancer.update();
if (baseInstance == this.baseInstance) {
needsFullWrite = false;
return;
}
this.baseInstance = baseInstance;
needsFullWrite = true;
}
void writeObjects(long objectPtr, long batchIDPtr, int batchID) {
if (needsFullWrite) {
instancer.writeFull(objectPtr, batchIDPtr, batchID);
} else if (instancer.anyToUpdate) {
instancer.writeSparse(objectPtr, batchIDPtr, batchID);
}
instancer.anyToUpdate = false;
}
public void writeIndirectCommand(long ptr) {
var boundingSphere = mesh.mesh.getBoundingSphere();
MemoryUtil.memPutInt(ptr, mesh.getIndexCount()); // count
MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be incremented by the compute shader
MemoryUtil.memPutInt(ptr + 8, 0); // firstIndex - all models share the same index buffer
MemoryUtil.memPutInt(ptr + 12, mesh.getBaseVertex()); // baseVertex
MemoryUtil.memPutInt(ptr + 16, baseInstance); // baseInstance
boundingSphere.getToAddress(ptr + 20); // boundingSphere
}
}

View file

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

View file

@ -0,0 +1,54 @@
package com.jozufozu.flywheel.backend.instancing.indirect;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
import static org.lwjgl.opengl.GL43.glMultiDrawElementsIndirect;
import java.util.ArrayList;
import java.util.List;
import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.material.Material;
public class IndirectDrawSet<T extends InstancedPart> {
final List<IndirectDraw<T>> indirectDraws = new ArrayList<>();
public boolean isEmpty() {
return indirectDraws.isEmpty();
}
public int size() {
return indirectDraws.size();
}
public void add(IndirectInstancer<T> instancer, Material material, IndirectMeshPool.BufferedMesh bufferedMesh) {
indirectDraws.add(new IndirectDraw<>(instancer, material, bufferedMesh));
}
public void submit(RenderStage stage) {
final int stride = (int) IndirectBuffers.DRAW_COMMAND_STRIDE;
for (int i = 0, indirectDrawsSize = indirectDraws.size(); i < indirectDrawsSize; i++) {
var batch = indirectDraws.get(i);
var material = batch.material;
if (material.getRenderStage() != stage) {
continue;
}
material.setup();
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, i * stride, 1, stride);
material.clear();
}
}
public boolean contains(RenderStage stage) {
for (var draw : indirectDraws) {
if (draw.material.getRenderStage() == stage) {
return true;
}
}
return false;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.instancing.indirect;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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.
*/

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,4 @@
#ifdef COMPUTE_SHADER
uint flw_objectID;
uint flw_batchID;
#endif

View file

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

View file

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

View file

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

View file

@ -0,0 +1,5 @@
out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 1.0, 1.0, 0.2);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,7 @@
#use "flywheel:api/fragment.glsl"
void main() {
flw_initFragment();
flw_materialFragment();
flw_contextFragment();
}

View file

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

View file

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

View file

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