Merge remote-tracking branch 'upstream/1.20/dev' into feat/multi-loader-1.21

# Conflicts:
#	forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java
#	forge/src/main/java/dev/engine_room/flywheel/impl/ForgeFlwConfig.java
This commit is contained in:
IThundxr 2024-08-22 20:30:26 -04:00
commit 0919c0f02b
Failed to generate hash of commit
55 changed files with 640 additions and 455 deletions

View file

@ -14,9 +14,15 @@ public interface Backend {
Engine createEngine(LevelAccessor level);
/**
* Get a fallback backend in case this backend is not supported.
* The priority of this backend.
* <p>The backend with the highest priority upon first launch will be chosen as the default backend.
*
* <p>If the selected backend becomes unavailable for whatever reason, the next supported backend
* with a LOWER priority than the selected one will be chosen.
*
* @return The priority of this backend.
*/
Backend findFallback();
int priority();
/**
* Check if this backend is supported.

View file

@ -8,9 +8,6 @@ public interface BackendConfig {
/**
* How smooth/accurate our flw_light impl is.
*
* <p>This makes more sense here as a backend-specific config because it's tightly coupled to
* our backend's implementation. 3rd party backend may have different approaches and configurations.
*
* @return The current light smoothness setting.
*/
LightSmoothness lightSmoothness();

View file

@ -17,6 +17,7 @@ public final class Backends {
*/
public static final Backend INSTANCING = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(level, new InstancedDrawManager(InstancingPrograms.get()), 256))
.priority(500)
.supported(() -> GlCompat.SUPPORTS_INSTANCING && InstancingPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse())
.register(Flywheel.rl("instancing"));
@ -25,7 +26,7 @@ public final class Backends {
*/
public static final Backend INDIRECT = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(level, new IndirectDrawManager(IndirectPrograms.get()), 256))
.fallback(() -> Backends.INSTANCING)
.priority(1000)
.supported(() -> GlCompat.SUPPORTS_INDIRECT && IndirectPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse())
.register(Flywheel.rl("indirect"));

View file

@ -8,7 +8,6 @@ import org.jetbrains.annotations.Unmodifiable;
import dev.engine_room.flywheel.api.material.CutoutShader;
import dev.engine_room.flywheel.api.material.FogShader;
import dev.engine_room.flywheel.api.material.LightShader;
import dev.engine_room.flywheel.api.material.MaterialShaders;
import dev.engine_room.flywheel.api.registry.Registry;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
@ -26,8 +25,6 @@ public final class MaterialShaderIndices {
private static Index fogSources;
@Nullable
private static Index cutoutSources;
@Nullable
private static Index lightSources;
private MaterialShaderIndices() {
}
@ -60,13 +57,6 @@ public final class MaterialShaderIndices {
return cutoutSources;
}
public static Index lightSources() {
if (lightSources == null) {
lightSources = indexFromRegistry(LightShader.REGISTRY, LightShader::source);
}
return lightSources;
}
public static int vertexIndex(MaterialShaders shaders) {
return vertexSources().index(shaders.vertexSource());
}
@ -83,10 +73,6 @@ public final class MaterialShaderIndices {
return cutoutSources().index(cutoutShader.source());
}
public static int lightIndex(LightShader lightShader) {
return lightSources().index(lightShader.source());
}
private static <T> Index indexFromRegistry(Registry<T> registry, Function<T, ResourceLocation> sourceFunc) {
if (!registry.isFrozen()) {
throw new IllegalStateException("Cannot create index from registry that is not frozen!");

View file

@ -10,6 +10,7 @@ import com.google.common.collect.ImmutableList;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.LightShader;
import dev.engine_room.flywheel.backend.MaterialShaderIndices;
import dev.engine_room.flywheel.backend.compile.component.UberShaderComponent;
import dev.engine_room.flywheel.backend.compile.core.CompilerStats;
@ -46,16 +47,15 @@ public final class FlwPrograms {
var fragmentMaterialComponent = createFragmentMaterialComponent(loader);
var fogComponent = createFogComponent(loader);
var cutoutComponent = createCutoutComponent(loader);
var lightComponent = createLightComponent(loader);
if (stats.errored() || vertexComponentsHeader == null || fragmentComponentsHeader == null || vertexMaterialComponent == null || fragmentMaterialComponent == null || fogComponent == null || cutoutComponent == null || lightComponent == null) {
if (stats.errored() || vertexComponentsHeader == null || fragmentComponentsHeader == null || vertexMaterialComponent == null || fragmentMaterialComponent == null || fogComponent == null || cutoutComponent == null) {
// Probably means the shader sources are missing.
stats.emitErrorLog();
return;
}
List<SourceComponent> vertexComponents = List.of(vertexComponentsHeader, vertexMaterialComponent);
List<SourceComponent> fragmentComponents = List.of(fragmentComponentsHeader, fragmentMaterialComponent, fogComponent, cutoutComponent, lightComponent);
List<SourceComponent> fragmentComponents = List.of(fragmentComponentsHeader, fragmentMaterialComponent, fogComponent, cutoutComponent);
var pipelineKeys = createPipelineKeys();
InstancingPrograms.reload(sources, pipelineKeys, vertexComponents, fragmentComponents);
@ -66,7 +66,9 @@ public final class FlwPrograms {
ImmutableList.Builder<PipelineProgramKey> builder = ImmutableList.builder();
for (ContextShader contextShader : ContextShader.values()) {
for (InstanceType<?> instanceType : InstanceType.REGISTRY) {
builder.add(new PipelineProgramKey(instanceType, contextShader));
for (LightShader light : LightShader.REGISTRY.getAll()) {
builder.add(new PipelineProgramKey(instanceType, contextShader, light));
}
}
}
return builder.build();
@ -119,18 +121,4 @@ public final class FlwPrograms {
.switchOn(GlslExpr.variable("_flw_uberCutoutIndex"))
.build(loader);
}
// TODO: Do not uber this component. Shader compile times are very high now
@Nullable
private static UberShaderComponent createLightComponent(SourceLoader loader) {
return UberShaderComponent.builder(Flywheel.rl("light"))
.materialSources(MaterialShaderIndices.lightSources()
.all())
.adapt(FnSignature.create()
.returnType("void")
.name("flw_shaderLight")
.build())
.switchOn(GlslExpr.variable("_flw_uberLightIndex"))
.build(loader);
}
}

View file

@ -9,6 +9,7 @@ import com.google.common.collect.ImmutableList;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.LightShader;
import dev.engine_room.flywheel.backend.compile.component.InstanceStructComponent;
import dev.engine_room.flywheel.backend.compile.component.SsboInstanceComponent;
import dev.engine_room.flywheel.backend.compile.core.CompilationHarness;
@ -167,8 +168,8 @@ public class IndirectPrograms extends AtomicReferenceCounted {
return instance != null;
}
public GlProgram getIndirectProgram(InstanceType<?> instanceType, ContextShader contextShader) {
return pipeline.get(new PipelineProgramKey(instanceType, contextShader));
public GlProgram getIndirectProgram(InstanceType<?> instanceType, ContextShader contextShader, LightShader light) {
return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light));
}
public GlProgram getCullingProgram(InstanceType<?> instanceType) {

View file

@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.LightShader;
import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import dev.engine_room.flywheel.backend.glsl.GlslVersion;
@ -78,8 +79,8 @@ public class InstancingPrograms extends AtomicReferenceCounted {
return instance != null;
}
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader) {
return pipeline.get(new PipelineProgramKey(instanceType, contextShader));
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader, LightShader light) {
return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light));
}
@Override

View file

@ -56,7 +56,8 @@ public final class PipelineCompiler {
.nameMapper(key -> {
var context = key.contextShader()
.nameLowerCase();
return "pipeline/" + pipeline.compilerMarker() + "/" + context;
return "pipeline/" + pipeline.compilerMarker() + "/" + ResourceUtil.toDebugFileNameNoExtension(key.light()
.source()) + "_" + context;
})
.requireExtensions(extensions)
.enableExtension("GL_ARB_conservative_depth")
@ -65,6 +66,8 @@ public final class PipelineCompiler {
.onCompile((key, comp) -> lightSmoothness.onCompile(comp))
.withResource(API_IMPL_FRAG)
.withComponents(fragmentComponents)
.withResource(key -> key.light()
.source())
.withResource(pipeline.fragmentMain()))
.preLink((key, program) -> {
program.bindAttribLocation("_flw_aPos", 0);

View file

@ -1,12 +1,14 @@
package dev.engine_room.flywheel.backend.compile;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.LightShader;
/**
* Represents the entire context of a program's usage.
*
* @param instanceType The instance shader to use.
* @param contextShader The context shader to use.
* @param light The light shader to use.
*/
public record PipelineProgramKey(InstanceType<?> instanceType, ContextShader contextShader) {
public record PipelineProgramKey(InstanceType<?> instanceType, ContextShader contextShader, LightShader light) {
}

View file

@ -51,4 +51,8 @@ public class Arena {
public int capacity() {
return top;
}
public long byteCapacity() {
return memoryBlock.size();
}
}

View file

@ -16,6 +16,7 @@ import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.FlwBackend;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
import dev.engine_room.flywheel.lib.util.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@ -40,7 +41,7 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
return (Instancer<I>) instancers.computeIfAbsent(new InstancerKey<>(environment, type, model, visualType, bias), this::createAndDeferInit);
}
public void flush(LightStorage lightStorage) {
public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
// Thread safety: flush is called from the render thread after all visual updates have been made,
// so there are no:tm: threads we could be racing with.
for (var instancer : initializationQueue) {

View file

@ -89,7 +89,7 @@ public class EngineImpl implements Engine {
try (var state = GlStateTracker.getRestoreState()) {
Uniforms.update(context);
environmentStorage.flush();
drawManager.flush(lightStorage);
drawManager.flush(lightStorage, environmentStorage);
}
}
@ -107,6 +107,7 @@ public class EngineImpl implements Engine {
public void delete() {
drawManager.delete();
lightStorage.delete();
environmentStorage.delete();
}
public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) {

View file

@ -55,8 +55,7 @@ public final class MaterialEncoder {
public static int packUberShader(Material material) {
var fog = MaterialShaderIndices.fogIndex(material.fog());
var cutout = MaterialShaderIndices.cutoutIndex(material.cutout());
var light = MaterialShaderIndices.lightIndex(material.light());
return (light & 0x3FF) | (cutout & 0x3FF) << 10 | (fog & 0x3FF) << 20;
return (cutout & 0xFFFF) | (fog & 0xFFFF) << 16;
}
// Packed format:

View file

@ -16,7 +16,9 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.AbstractTexture;
public final class MaterialRenderState {
public static final Comparator<Material> COMPARATOR = Comparator.comparing(Material::texture)
public static final Comparator<Material> COMPARATOR = Comparator.comparing((Material m) -> m.light()
.source())
.thenComparing(Material::texture)
.thenComparing(Material::blur)
.thenComparing(Material::mipmap)
.thenComparing(Material::backfaceCulling)
@ -177,4 +179,18 @@ public final class MaterialRenderState {
RenderSystem.depthMask(true);
RenderSystem.colorMask(true, true, true, true);
}
public static boolean materialEquals(Material lhs, Material rhs) {
if (lhs == rhs) {
return true;
}
// Not here because ubershader: useLight, useOverlay, diffuse, shaders, fog shader, and cutout shader
// Everything in the comparator should be here.
return lhs.blur() == rhs.blur() && lhs.mipmap() == rhs.mipmap() && lhs.backfaceCulling() == rhs.backfaceCulling() && lhs.polygonOffset() == rhs.polygonOffset() && lhs.light()
.source()
.equals(rhs.light()
.source()) && lhs.texture()
.equals(rhs.texture()) && lhs.depthTest() == rhs.depthTest() && lhs.transparency() == rhs.transparency() && lhs.writeMask() == rhs.writeMask();
}
}

View file

@ -16,6 +16,7 @@ import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.engine.EngineImpl;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import dev.engine_room.flywheel.lib.util.ExtraMemoryOps;
import net.minecraft.core.Vec3i;
public class EmbeddedEnvironment implements VisualEmbedding, Environment {
@ -31,6 +32,8 @@ public class EmbeddedEnvironment implements VisualEmbedding, Environment {
private final Matrix4f poseComposed = new Matrix4f();
private final Matrix3f normalComposed = new Matrix3f();
public int matrixIndex = 0;
private boolean deleted = false;
public EmbeddedEnvironment(EngineImpl engine, VisualType visualType, Vec3i renderOrigin, @Nullable EmbeddedEnvironment parent) {
@ -81,23 +84,25 @@ public class EmbeddedEnvironment implements VisualEmbedding, Environment {
return ContextShader.EMBEDDED;
}
@Override
public void setupCull(GlProgram program) {
program.setBool(EmbeddingUniforms.USE_MODEL_MATRIX, true);
program.setMat4(EmbeddingUniforms.MODEL_MATRIX, poseComposed);
}
@Override
public void setupDraw(GlProgram program) {
program.setMat4(EmbeddingUniforms.MODEL_MATRIX, poseComposed);
program.setMat3(EmbeddingUniforms.NORMAL_MATRIX, normalComposed);
}
public void flush() {
@Override
public int matrixIndex() {
return matrixIndex;
}
public void flush(long ptr) {
poseComposed.identity();
normalComposed.identity();
composeMatrices(poseComposed, normalComposed);
ExtraMemoryOps.putMatrix4f(ptr, poseComposed);
ExtraMemoryOps.putMatrix3fPadded(ptr + 16 * Float.BYTES, normalComposed);
}
private void composeMatrices(Matrix4f pose, Matrix3f normal) {

View file

@ -1,12 +1,8 @@
package dev.engine_room.flywheel.backend.engine.embed;
public final class EmbeddingUniforms {
/**
* Only used by cull shaders.
*/
public static final String USE_MODEL_MATRIX = "_flw_useModelMatrix";
public static final String MODEL_MATRIX = "_flw_modelMatrix";
public static final String NORMAL_MATRIX = "_flw_normalMatrix";
public static final String MODEL_MATRIX = "_flw_modelMatrixUniform";
public static final String NORMAL_MATRIX = "_flw_normalMatrixUniform";
private EmbeddingUniforms() {
}

View file

@ -6,7 +6,7 @@ import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
public interface Environment {
ContextShader contextShader();
void setupCull(GlProgram cullProgram);
void setupDraw(GlProgram drawProgram);
int matrixIndex();
}

View file

@ -1,18 +1,47 @@
package dev.engine_room.flywheel.backend.engine.embed;
import dev.engine_room.flywheel.backend.engine.Arena;
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
import it.unimi.dsi.fastutil.objects.ReferenceSet;
import it.unimi.dsi.fastutil.objects.ReferenceSets;
public class EnvironmentStorage {
protected final ReferenceSet<EmbeddedEnvironment> environments = ReferenceSets.synchronize(new ReferenceLinkedOpenHashSet<>());
public static final int MATRIX_SIZE_BYTES = (16 + 12) * Float.BYTES;
protected final Object lock = new Object();
protected final ReferenceSet<EmbeddedEnvironment> environments = new ReferenceLinkedOpenHashSet<>();
// Note than the arena starts indexing at zero, but we reserve zero for the identity matrix.
// Any time an ID from the arena is written we want to add one to it.
public final Arena arena = new Arena(MATRIX_SIZE_BYTES, 32);
{
// Reserve the identity matrix. Burns a few bytes but oh well.
arena.alloc();
}
public void track(EmbeddedEnvironment environment) {
environments.add(environment);
synchronized (lock) {
if (environments.add(environment)) {
environment.matrixIndex = arena.alloc();
}
}
}
public void flush() {
environments.removeIf(EmbeddedEnvironment::isDeleted);
environments.forEach(EmbeddedEnvironment::flush);
environments.removeIf(embeddedEnvironment -> {
var deleted = embeddedEnvironment.isDeleted();
if (deleted && embeddedEnvironment.matrixIndex > 0) {
arena.free(embeddedEnvironment.matrixIndex);
}
return deleted;
});
for (EmbeddedEnvironment environment : environments) {
environment.flush(arena.indexToPointer(environment.matrixIndex));
}
}
public void delete() {
arena.delete();
}
}

View file

@ -15,11 +15,11 @@ public class GlobalEnvironment implements Environment {
}
@Override
public void setupCull(GlProgram cullProgram) {
cullProgram.setBool(EmbeddingUniforms.USE_MODEL_MATRIX, false);
public void setupDraw(GlProgram drawProgram) {
}
@Override
public void setupDraw(GlProgram drawProgram) {
public int matrixIndex() {
return 0;
}
}

View file

@ -8,6 +8,7 @@ public final class BufferBindings {
public static final int DRAW = 4;
public static final int LIGHT_LUT = 5;
public static final int LIGHT_SECTION = 6;
public static final int MATRICES = 7;
private BufferBindings() {
}

View file

@ -16,10 +16,10 @@ public class IndirectBuffers {
public static final long INT_SIZE = Integer.BYTES;
public static final long PTR_SIZE = Pointer.POINTER_SIZE;
public static final long MODEL_STRIDE = 24;
public static final long MODEL_STRIDE = 28;
// Byte size of a draw command, plus our added mesh data.
public static final long DRAW_COMMAND_STRIDE = 40;
public static final long DRAW_COMMAND_STRIDE = 44;
public static final long DRAW_COMMAND_OFFSET = 0;
// Offsets to the 3 segments

View file

@ -5,7 +5,6 @@ import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
import static org.lwjgl.opengl.GL30.glUniform1ui;
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.GL43.glDispatchCompute;
import java.util.ArrayList;
@ -24,22 +23,20 @@ import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
import dev.engine_room.flywheel.backend.engine.InstancerKey;
import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
import dev.engine_room.flywheel.backend.engine.MeshPool;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import dev.engine_room.flywheel.lib.material.LightShaders;
import dev.engine_room.flywheel.lib.math.MoreMath;
public class IndirectCullingGroup<I extends Instance> {
private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::visualType)
.thenComparing(IndirectDraw::isEmbedded)
.thenComparing(IndirectDraw::bias)
.thenComparing(IndirectDraw::indexOfMeshInModel)
.thenComparing(IndirectDraw::material, MaterialRenderState.COMPARATOR);
private static final int DRAW_BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT;
private final InstanceType<I> instanceType;
private final Environment environment;
private final long instanceStride;
private final IndirectBuffers buffers;
private final List<IndirectInstancer<I>> instancers = new ArrayList<>();
@ -48,24 +45,19 @@ public class IndirectCullingGroup<I extends Instance> {
private final IndirectPrograms programs;
private final GlProgram cullProgram;
private final GlProgram applyProgram;
private final GlProgram drawProgram;
private boolean needsDrawBarrier;
private boolean needsDrawSort;
private int instanceCountThisFrame;
IndirectCullingGroup(InstanceType<I> instanceType, Environment environment, IndirectPrograms programs) {
IndirectCullingGroup(InstanceType<I> instanceType, IndirectPrograms programs) {
this.instanceType = instanceType;
this.environment = environment;
instanceStride = MoreMath.align4(instanceType.layout()
.byteSize());
buffers = new IndirectBuffers(instanceStride);
this.programs = programs;
cullProgram = programs.getCullingProgram(instanceType);
applyProgram = programs.getApplyProgram();
drawProgram = programs.getIndirectProgram(instanceType, environment.contextShader());
}
public void flushInstancers() {
@ -125,10 +117,7 @@ public class IndirectCullingGroup<I extends Instance> {
Uniforms.bindAll();
cullProgram.bind();
environment.setupCull(cullProgram);
buffers.bindForCompute();
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
glDispatchCompute(GlCompat.getComputeGroupCount(instanceCountThisFrame), 1, 1);
}
@ -137,9 +126,7 @@ public class IndirectCullingGroup<I extends Instance> {
return;
}
applyProgram.bind();
buffers.bindForCompute();
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
glDispatchCompute(GlCompat.getComputeGroupCount(indirectDraws.size()), 1, 1);
}
@ -158,20 +145,27 @@ public class IndirectCullingGroup<I extends Instance> {
for (int start = 0, i = 0; i < indirectDraws.size(); i++) {
var draw1 = indirectDraws.get(i);
var material1 = draw1.material();
var visualType1 = draw1.visualType();
// if the next draw call has a different VisualType or Material, start a new MultiDraw
if (i == indirectDraws.size() - 1 || visualType1 != indirectDraws.get(i + 1)
.visualType() || !material1.equals(indirectDraws.get(i + 1)
.material())) {
multiDraws.computeIfAbsent(visualType1, s -> new ArrayList<>())
.add(new MultiDraw(material1, start, i + 1));
if (i == indirectDraws.size() - 1 || incompatibleDraws(draw1, indirectDraws.get(i + 1))) {
multiDraws.computeIfAbsent(draw1.visualType(), s -> new ArrayList<>())
.add(new MultiDraw(draw1.material(), draw1.isEmbedded(), start, i + 1));
start = i + 1;
}
}
}
private boolean incompatibleDraws(IndirectDraw draw1, IndirectDraw draw2) {
if (draw1.visualType() != draw2.visualType()) {
return true;
}
if (draw1.isEmbedded() != draw2.isEmbedded()) {
return true;
}
return !MaterialRenderState.materialEquals(draw1.material(), draw2.material());
}
public boolean hasVisualType(VisualType visualType) {
return multiDraws.containsKey(visualType);
}
@ -199,17 +193,24 @@ public class IndirectCullingGroup<I extends Instance> {
return;
}
drawProgram.bind();
buffers.bindForDraw();
environment.setupDraw(drawProgram);
drawBarrier();
var flwBaseDraw = drawProgram.getUniformLocation("_flw_baseDraw");
GlProgram lastProgram = null;
int baseDrawUniformLoc = -1;
for (var multiDraw : multiDraws.get(visualType)) {
glUniform1ui(flwBaseDraw, multiDraw.start);
var drawProgram = programs.getIndirectProgram(instanceType, multiDraw.embedded ? ContextShader.EMBEDDED : ContextShader.DEFAULT, multiDraw.material.light());
if (drawProgram != lastProgram) {
lastProgram = drawProgram;
// Don't need to do this unless the program changes.
drawProgram.bind();
baseDrawUniformLoc = drawProgram.getUniformLocation("_flw_baseDraw");
}
glUniform1ui(baseDrawUniformLoc, multiDraw.start);
MaterialRenderState.setup(multiDraw.material);
@ -218,7 +219,7 @@ public class IndirectCullingGroup<I extends Instance> {
}
public void bindWithContextShader(ContextShader override) {
var program = programs.getIndirectProgram(instanceType, override);
var program = programs.getIndirectProgram(instanceType, override, LightShaders.SMOOTH_WHEN_EMBEDDED);
program.bind();
@ -226,13 +227,15 @@ public class IndirectCullingGroup<I extends Instance> {
drawBarrier();
var flwBaseDraw = drawProgram.getUniformLocation("_flw_baseDraw");
var flwBaseDraw = program.getUniformLocation("_flw_baseDraw");
glUniform1ui(flwBaseDraw, 0);
}
private void drawBarrier() {
if (needsDrawBarrier) {
glMemoryBarrier(DRAW_BARRIER_BITS);
// In theory all command buffer writes will be protected by
// the shader storage barrier bit, but better safe than sorry.
glMemoryBarrier(GL_COMMAND_BARRIER_BIT);
needsDrawBarrier = false;
}
}
@ -290,7 +293,7 @@ public class IndirectCullingGroup<I extends Instance> {
return out;
}
private record MultiDraw(Material material, int start, int end) {
private record MultiDraw(Material material, boolean embedded, int start, int end) {
private void submit() {
GlCompat.safeMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, this.start * IndirectBuffers.DRAW_COMMAND_STRIDE, this.end - this.start, (int) IndirectBuffers.DRAW_COMMAND_STRIDE);
}

View file

@ -7,6 +7,7 @@ import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.MaterialShaderIndices;
import dev.engine_room.flywheel.backend.engine.MaterialEncoder;
import dev.engine_room.flywheel.backend.engine.MeshPool;
import dev.engine_room.flywheel.backend.engine.embed.EmbeddedEnvironment;
public class IndirectDraw {
private final IndirectInstancer<?> instancer;
@ -46,6 +47,10 @@ public class IndirectDraw {
return material;
}
public boolean isEmbedded() {
return instancer.environment instanceof EmbeddedEnvironment;
}
public MeshPool.PooledMesh mesh() {
return mesh;
}
@ -71,10 +76,12 @@ public class IndirectDraw {
MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex); // modelIndex
MemoryUtil.memPutInt(ptr + 24, materialVertexIndex); // materialVertexIndex
MemoryUtil.memPutInt(ptr + 28, materialFragmentIndex); // materialFragmentIndex
MemoryUtil.memPutInt(ptr + 32, packedFogAndCutout); // packedFogAndCutout
MemoryUtil.memPutInt(ptr + 36, packedMaterialProperties); // packedMaterialProperties
MemoryUtil.memPutInt(ptr + 24, instancer.environment.matrixIndex()); // matrixIndex
MemoryUtil.memPutInt(ptr + 28, materialVertexIndex); // materialVertexIndex
MemoryUtil.memPutInt(ptr + 32, materialFragmentIndex); // materialFragmentIndex
MemoryUtil.memPutInt(ptr + 36, packedFogAndCutout); // packedFogAndCutout
MemoryUtil.memPutInt(ptr + 40, packedMaterialProperties); // packedMaterialProperties
}
public void writeWithOverrides(long ptr, int instanceIndex, Material materialOverride) {
@ -86,10 +93,12 @@ public class IndirectDraw {
MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex); // modelIndex
MemoryUtil.memPutInt(ptr + 24, MaterialShaderIndices.vertexIndex(materialOverride.shaders())); // materialVertexIndex
MemoryUtil.memPutInt(ptr + 28, MaterialShaderIndices.fragmentIndex(materialOverride.shaders())); // materialFragmentIndex
MemoryUtil.memPutInt(ptr + 32, MaterialEncoder.packUberShader(materialOverride)); // packedFogAndCutout
MemoryUtil.memPutInt(ptr + 36, MaterialEncoder.packProperties(materialOverride)); // packedMaterialProperties
MemoryUtil.memPutInt(ptr + 24, instancer.environment.matrixIndex()); // matrixIndex
MemoryUtil.memPutInt(ptr + 28, MaterialShaderIndices.vertexIndex(materialOverride.shaders())); // materialVertexIndex
MemoryUtil.memPutInt(ptr + 32, MaterialShaderIndices.fragmentIndex(materialOverride.shaders())); // materialFragmentIndex
MemoryUtil.memPutInt(ptr + 36, MaterialEncoder.packUberShader(materialOverride)); // packedFogAndCutout
MemoryUtil.memPutInt(ptr + 40, MaterialEncoder.packProperties(materialOverride)); // packedMaterialProperties
}
public void delete() {

View file

@ -4,6 +4,8 @@ import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
import static org.lwjgl.opengl.GL30.glBindBufferRange;
import static org.lwjgl.opengl.GL40.glDrawElementsIndirect;
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BARRIER_BIT;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import java.util.HashMap;
@ -12,18 +14,19 @@ import java.util.Map;
import dev.engine_room.flywheel.api.backend.Engine;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.Samplers;
import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
import dev.engine_room.flywheel.backend.engine.CommonCrumbling;
import dev.engine_room.flywheel.backend.engine.DrawManager;
import dev.engine_room.flywheel.backend.engine.GroupKey;
import dev.engine_room.flywheel.backend.engine.InstancerKey;
import dev.engine_room.flywheel.backend.engine.LightStorage;
import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
import dev.engine_room.flywheel.backend.engine.MeshPool;
import dev.engine_room.flywheel.backend.engine.TextureBinder;
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlStateTracker;
import dev.engine_room.flywheel.backend.gl.array.GlVertexArray;
@ -38,9 +41,12 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
private final StagingBuffer stagingBuffer;
private final MeshPool meshPool;
private final GlVertexArray vertexArray;
private final Map<GroupKey<?>, IndirectCullingGroup<?>> cullingGroups = new HashMap<>();
private final Map<InstanceType<?>, IndirectCullingGroup<?>> cullingGroups = new HashMap<>();
private final GlBuffer crumblingDrawBuffer = new GlBuffer();
private final LightBuffers lightBuffers;
private final MatrixBuffer matrixBuffer;
private boolean needsBarrier = false;
public IndirectDrawManager(IndirectPrograms programs) {
this.programs = programs;
@ -51,6 +57,7 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
vertexArray = GlVertexArray.create();
meshPool.bind(vertexArray);
lightBuffers = new LightBuffers();
matrixBuffer = new MatrixBuffer();
}
@Override
@ -61,8 +68,7 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
@SuppressWarnings("unchecked")
@Override
protected <I extends Instance> void initialize(InstancerKey<I> key, IndirectInstancer<?> instancer) {
var groupKey = new GroupKey<>(key.type(), key.environment());
var group = (IndirectCullingGroup<I>) cullingGroups.computeIfAbsent(groupKey, t -> new IndirectCullingGroup<>(t.instanceType(), t.environment(), programs));
var group = (IndirectCullingGroup<I>) cullingGroups.computeIfAbsent(key.type(), t -> new IndirectCullingGroup<>(t, programs));
group.add((IndirectInstancer<I>) instancer, key, meshPool);
}
@ -85,8 +91,14 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
vertexArray.bindForDraw();
lightBuffers.bind();
matrixBuffer.bind();
Uniforms.bindAll();
if (needsBarrier) {
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
needsBarrier = false;
}
for (var group : cullingGroups.values()) {
group.submit(visualType);
}
@ -97,8 +109,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
}
@Override
public void flush(LightStorage lightStorage) {
super.flush(lightStorage);
public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
super.flush(lightStorage, environmentStorage);
for (var group : cullingGroups.values()) {
group.flushInstancers();
@ -116,19 +128,35 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
lightBuffers.flush(stagingBuffer, lightStorage);
matrixBuffer.flush(stagingBuffer, environmentStorage);
for (var group : cullingGroups.values()) {
group.upload(stagingBuffer);
}
stagingBuffer.flush();
// We could probably save some driver calls here when there are
// actually zero instances, but that feels like a very rare case
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
matrixBuffer.bind();
for (var group : cullingGroups.values()) {
group.dispatchCull();
}
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
programs.getApplyProgram()
.bind();
for (var group : cullingGroups.values()) {
group.dispatchApply();
}
needsBarrier = true;
}
@Override

View file

@ -49,10 +49,11 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
public void writeModel(long ptr) {
MemoryUtil.memPutInt(ptr, 0); // instanceCount - to be incremented by the cull shader
MemoryUtil.memPutInt(ptr + 4, baseInstance); // baseInstance
MemoryUtil.memPutFloat(ptr + 8, boundingSphere.x()); // boundingSphere
MemoryUtil.memPutFloat(ptr + 12, boundingSphere.y());
MemoryUtil.memPutFloat(ptr + 16, boundingSphere.z());
MemoryUtil.memPutFloat(ptr + 20, boundingSphere.w());
MemoryUtil.memPutInt(ptr + 8, environment.matrixIndex()); // matrixIndex
MemoryUtil.memPutFloat(ptr + 12, boundingSphere.x()); // boundingSphere
MemoryUtil.memPutFloat(ptr + 16, boundingSphere.y());
MemoryUtil.memPutFloat(ptr + 20, boundingSphere.z());
MemoryUtil.memPutFloat(ptr + 24, boundingSphere.w());
}
public void uploadInstances(StagingBuffer stagingBuffer, int instanceVbo) {

View file

@ -0,0 +1,33 @@
package dev.engine_room.flywheel.backend.engine.indirect;
import org.lwjgl.opengl.GL46;
import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
public class MatrixBuffer {
private final ResizableStorageArray matrices = new ResizableStorageArray(EnvironmentStorage.MATRIX_SIZE_BYTES);
public void flush(StagingBuffer stagingBuffer, EnvironmentStorage environmentStorage) {
var arena = environmentStorage.arena;
var capacity = arena.capacity();
if (capacity == 0) {
return;
}
matrices.ensureCapacity(capacity);
stagingBuffer.enqueueCopy(arena.byteCapacity(), matrices.handle(), 0, ptr -> {
MemoryUtil.memCopy(arena.indexToPointer(0), ptr, arena.byteCapacity());
});
}
public void bind() {
if (matrices.capacity() == 0) {
return;
}
GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.MATRICES, matrices.handle(), 0, matrices.byteCapacity());
}
}

View file

@ -23,11 +23,13 @@ import dev.engine_room.flywheel.backend.engine.MaterialEncoder;
import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
import dev.engine_room.flywheel.backend.engine.MeshPool;
import dev.engine_room.flywheel.backend.engine.TextureBinder;
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlStateTracker;
import dev.engine_room.flywheel.backend.gl.TextureBuffer;
import dev.engine_room.flywheel.backend.gl.array.GlVertexArray;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import dev.engine_room.flywheel.lib.material.LightShaders;
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
import net.minecraft.client.resources.model.ModelBakery;
@ -58,8 +60,8 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
}
@Override
public void flush(LightStorage lightStorage) {
super.flush(lightStorage);
public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
super.flush(lightStorage, environmentStorage);
this.instancers.values()
.removeIf(instancer -> {
@ -170,7 +172,7 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
GroupKey<?> shader = groupEntry.getKey();
var program = programs.get(shader.instanceType(), ContextShader.CRUMBLING);
var program = programs.get(shader.instanceType(), ContextShader.CRUMBLING, LightShaders.SMOOTH_WHEN_EMBEDDED);
program.bind();
for (var progressEntry : byProgress.int2ObjectEntrySet()) {

View file

@ -54,12 +54,13 @@ public class InstancedRenderStage {
var environment = shader.environment();
var program = programs.get(shader.instanceType(), environment.contextShader());
for (var drawCall : drawCalls.draws) {
var program = programs.get(shader.instanceType(), environment.contextShader(), drawCall.material()
.light());
program.bind();
environment.setupDraw(program);
for (var drawCall : drawCalls.draws) {
var material = drawCall.material();
uploadMaterialUniform(program, material);

View file

@ -67,8 +67,8 @@ vec2 getCrumblingTexCoord() {
#endif
#ifdef FLW_EMBEDDED
uniform mat4 _flw_modelMatrix;
uniform mat3 _flw_normalMatrix;
mat4 _flw_modelMatrix;
mat3 _flw_normalMatrix;
#endif
flat out uint _flw_instanceID;

View file

@ -1,4 +1,3 @@
uint _flw_uberMaterialFragmentIndex;
uint _flw_uberFogIndex;
uint _flw_uberCutoutIndex;
uint _flw_uberLightIndex;

View file

@ -5,3 +5,4 @@
#define _FLW_DRAW_BUFFER_BINDING 4
#define _FLW_LIGHT_LUT_BUFFER_BINDING 5
#define _FLW_LIGHT_SECTIONS_BUFFER_BINDING 6
#define _FLW_MATRIX_BUFFER_BINDING 7

View file

@ -2,6 +2,7 @@
#include "flywheel:internal/indirect/model_descriptor.glsl"
#include "flywheel:internal/uniforms/uniforms.glsl"
#include "flywheel:util/matrix.glsl"
#include "flywheel:internal/indirect/matrices.glsl"
layout(local_size_x = _FLW_SUBGROUP_SIZE) in;
@ -17,8 +18,9 @@ layout(std430, binding = _FLW_MODEL_BUFFER_BINDING) restrict buffer ModelBuffer
ModelDescriptor _flw_models[];
};
uniform mat4 _flw_modelMatrix;
uniform bool _flw_useModelMatrix = false;
layout(std430, binding = _FLW_MATRIX_BUFFER_BINDING) restrict buffer MatrixBuffer {
Matrices _flw_matrices[];
};
// Disgustingly vectorized sphere frustum intersection taking advantage of ahead of time packing.
// Only uses 6 fmas and some boolean ops.
@ -34,6 +36,7 @@ bool _flw_testSphere(vec3 center, float radius) {
}
bool _flw_isVisible(uint instanceIndex, uint modelIndex) {
uint matrixIndex = _flw_models[modelIndex].matrixIndex;
BoundingSphere sphere = _flw_models[modelIndex].boundingSphere;
vec3 center;
@ -44,8 +47,8 @@ bool _flw_isVisible(uint instanceIndex, uint modelIndex) {
flw_transformBoundingSphere(instance, center, radius);
if (_flw_useModelMatrix) {
transformBoundingSphere(_flw_modelMatrix, center, radius);
if (matrixIndex > 0) {
transformBoundingSphere(_flw_matrices[matrixIndex].pose, center, radius);
}
return _flw_testSphere(center, radius);

View file

@ -6,6 +6,7 @@ struct MeshDrawCommand {
uint baseInstance;
uint modelIndex;
uint matrixIndex;
uint materialVertexIndex;
uint materialFragmentIndex;

View file

@ -6,7 +6,7 @@ flat in uvec3 _flw_packedMaterial;
void main() {
_flw_uberMaterialFragmentIndex = _flw_packedMaterial.x;
_flw_unpackUint3x10(_flw_packedMaterial.y, _flw_uberFogIndex, _flw_uberCutoutIndex, _flw_uberLightIndex);
_flw_unpackUint2x16(_flw_packedMaterial.y, _flw_uberFogIndex, _flw_uberCutoutIndex);
_flw_unpackMaterialProperties(_flw_packedMaterial.z, flw_material);
_flw_main();

View file

@ -3,6 +3,7 @@
#include "flywheel:internal/indirect/buffer_bindings.glsl"
#include "flywheel:internal/indirect/draw_command.glsl"
#include "flywheel:internal/indirect/light.glsl"
#include "flywheel:internal/indirect/matrices.glsl"
layout(std430, binding = _FLW_TARGET_BUFFER_BINDING) restrict readonly buffer TargetBuffer {
uint _flw_instanceIndices[];
@ -12,6 +13,12 @@ layout(std430, binding = _FLW_DRAW_BUFFER_BINDING) restrict readonly buffer Draw
MeshDrawCommand _flw_drawCommands[];
};
#ifdef FLW_EMBEDDED
layout(std430, binding = _FLW_MATRIX_BUFFER_BINDING) restrict buffer MatrixBuffer {
Matrices _flw_matrices[];
};
#endif
uniform uint _flw_baseDraw;
flat out uvec3 _flw_packedMaterial;
@ -29,7 +36,13 @@ void main() {
_flw_unpackMaterialProperties(packedMaterialProperties, flw_material);
_flw_packedMaterial = uvec3(draw.materialFragmentIndex, draw.packedFogAndCutout, packedMaterialProperties);
#if __VERSION__ < 460
#ifdef FLW_EMBEDDED
_flw_unpackMatrices(_flw_matrices[draw.matrixIndex], _flw_modelMatrix, _flw_normalMatrix);
// _flw_modelMatrix = mat4(1.);
// _flw_normalMatrix = mat3(1.);
#endif
#if __VERSION__ < 460
uint instanceIndex = _flw_instanceIndices[gl_BaseInstanceARB + gl_InstanceID];
#else
uint instanceIndex = _flw_instanceIndices[gl_BaseInstance + gl_InstanceID];

View file

@ -0,0 +1,11 @@
struct Matrices {
mat4 pose;
vec4 normalA;
vec4 normalB;
vec4 normalC;
};
void _flw_unpackMatrices(in Matrices mats, out mat4 pose, out mat3 normal) {
pose = mats.pose;
normal = mat3(mats.normalA.xyz, mats.normalB.xyz, mats.normalC.xyz);
}

View file

@ -8,6 +8,7 @@ struct BoundingSphere {
struct ModelDescriptor {
uint instanceCount;
uint baseInstance;
uint matrixIndex;
BoundingSphere boundingSphere;
};

View file

@ -5,7 +5,7 @@ uniform uvec4 _flw_packedMaterial;
void main() {
_flw_uberMaterialFragmentIndex = _flw_packedMaterial.y;
_flw_unpackUint3x10(_flw_packedMaterial.z, _flw_uberFogIndex, _flw_uberCutoutIndex, _flw_uberLightIndex);
_flw_unpackUint2x16(_flw_packedMaterial.z, _flw_uberFogIndex, _flw_uberCutoutIndex);
_flw_unpackMaterialProperties(_flw_packedMaterial.w, flw_material);
_flw_main();

View file

@ -5,11 +5,21 @@
uniform uvec4 _flw_packedMaterial;
uniform int _flw_baseInstance = 0;
#ifdef FLW_EMBEDDED
uniform mat4 _flw_modelMatrixUniform;
uniform mat3 _flw_normalMatrixUniform;
#endif
void main() {
_flw_uberMaterialVertexIndex = _flw_packedMaterial.x;
_flw_unpackMaterialProperties(_flw_packedMaterial.w, flw_material);
FlwInstance instance = _flw_unpackInstance(_flw_baseInstance + gl_InstanceID);
#ifdef FLW_EMBEDDED
_flw_modelMatrix = _flw_modelMatrixUniform;
_flw_normalMatrix = _flw_normalMatrixUniform;
#endif
_flw_main(instance, uint(gl_InstanceID));
}

View file

@ -11,14 +11,13 @@ const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4;
const uint _FLW_COMPLETELY_SOLID = 0x7FFFFFFu;
const float _FLW_EPSILON = 1e-5;
const uint _FLW_LOWER_10_BITS = 0x3FFu;
const uint _FLW_UPPER_10_BITS = 0xFFF00000u;
uint _flw_indexLut(uint index);
uint _flw_indexLight(uint index);
// Adding this option takes my test world from ~800 to ~1250 FPS on my 3060ti.
// I have not taken it to a profiler otherwise.
#pragma optionNV (unroll all)
/// Find the index for the next step in the LUT.
/// @param base The base index in the LUT, should point to the start of a coordinate span.
/// @param coord The coordinate to look for.
@ -103,57 +102,120 @@ bool flw_lightFetch(ivec3 blockPos, out vec2 lightCoord) {
return true;
}
/// Premtively collect all light in a 3x3x3 area centered on our block.
/// Depending on the normal, we won't use all the data, but fetching on demand will have many duplicated fetches.
///
/// The output is a 3-component vector <blockLight, skyLight, valid ? 1 : 0> packed into a single uint to save
/// memory and ALU ops later on. 10 bits are used for each component. This allows 4 such packed ints to be added
/// together with room to spare before overflowing into the next component.
uint[27] _flw_fetchLight3x3x3(uint sectionOffset, ivec3 blockInSectionPos, uint solid) {
uint[27] lights;
uint index = 0u;
uint mask = 1u;
for (int y = -1; y <= 1; y++) {
for (int z = -1; z <= 1; z++) {
for (int x = -1; x <= 1; x++) {
// 0 if the block is solid, 1 if it's not.
uint notSolid = uint((solid & mask) == 0u);
uvec2 light = _flw_lightAt(sectionOffset, uvec3(blockInSectionPos + ivec3(x, y, z)));
lights[index] = light.x;
lights[index] |= (light.y) << 10;
lights[index] |= (notSolid) << 20;
index++;
mask <<= 1;
}
}
}
return lights;
}
uint _flw_fetchSolid3x3x3(uint sectionOffset, ivec3 blockInSectionPos) {
uint ret = 0;
uint index = 0;
for (int y = -1; y <= 1; y++) {
for (int z = -1; z <= 1; z++) {
for (int x = -1; x <= 1; x++) {
bool flag = _flw_isSolid(sectionOffset, uvec3(blockInSectionPos + ivec3(x, y, z)));
ret |= uint(flag) << index;
// The formatter does NOT like these macros
// @formatter:off
index++;
}
}
#define _FLW_FETCH_SOLID(x, y, z, i) { \
bool flag = _flw_isSolid(sectionOffset, uvec3(blockInSectionPos + ivec3(x, y, z))); \
ret |= uint(flag) << i; \
}
/// fori y, z, x: unrolled
_FLW_FETCH_SOLID(-1, -1, -1, 0)
_FLW_FETCH_SOLID(0, -1, -1, 1)
_FLW_FETCH_SOLID(1, -1, -1, 2)
_FLW_FETCH_SOLID(-1, -1, 0, 3)
_FLW_FETCH_SOLID(0, -1, 0, 4)
_FLW_FETCH_SOLID(1, -1, 0, 5)
_FLW_FETCH_SOLID(-1, -1, 1, 6)
_FLW_FETCH_SOLID(0, -1, 1, 7)
_FLW_FETCH_SOLID(1, -1, 1, 8)
_FLW_FETCH_SOLID(-1, 0, -1, 9)
_FLW_FETCH_SOLID(0, 0, -1, 10)
_FLW_FETCH_SOLID(1, 0, -1, 11)
_FLW_FETCH_SOLID(-1, 0, 0, 12)
_FLW_FETCH_SOLID(0, 0, 0, 13)
_FLW_FETCH_SOLID(1, 0, 0, 14)
_FLW_FETCH_SOLID(-1, 0, 1, 15)
_FLW_FETCH_SOLID(0, 0, 1, 16)
_FLW_FETCH_SOLID(1, 0, 1, 17)
_FLW_FETCH_SOLID(-1, 1, -1, 18)
_FLW_FETCH_SOLID(0, 1, -1, 19)
_FLW_FETCH_SOLID(1, 1, -1, 20)
_FLW_FETCH_SOLID(-1, 1, 0, 21)
_FLW_FETCH_SOLID(0, 1, 0, 22)
_FLW_FETCH_SOLID(1, 1, 0, 23)
_FLW_FETCH_SOLID(-1, 1, 1, 24)
_FLW_FETCH_SOLID(0, 1, 1, 25)
_FLW_FETCH_SOLID(1, 1, 1, 26)
// @formatter:on
return ret;
}
/// Premtively collect all light in a 3x3x3 area centered on our block.
/// Depending on the normal, we won't use all the data, but fetching on demand will have many duplicated fetches.
/// Only fetching what we'll actually use using a bitmask turned out significantly slower, but perhaps a less
/// granular approach could see wins.
///
/// The output is a 3-component vector <blockLight, skyLight, valid ? 1 : 0> packed into a single uint to save
/// memory and ALU ops later on. 10 bits are used for each component. This allows 4 such packed ints to be added
/// together with room to spare before overflowing into the next component.
uint[27] _flw_fetchLight3x3x3(uint sectionOffset, ivec3 blockInSectionPos, uint solidMask) {
uint[27] lights;
// @formatter:off
#define _FLW_FETCH_LIGHT(_x, _y, _z, i) { \
uvec2 light = _flw_lightAt(sectionOffset, uvec3(blockInSectionPos + ivec3(_x, _y, _z))); \
lights[i] = (light.x) | ((light.y) << 10) | (uint((solidMask & (1u << i)) == 0u) << 20); \
}
/// fori y, z, x: unrolled
_FLW_FETCH_LIGHT(-1, -1, -1, 0)
_FLW_FETCH_LIGHT(0, -1, -1, 1)
_FLW_FETCH_LIGHT(1, -1, -1, 2)
_FLW_FETCH_LIGHT(-1, -1, 0, 3)
_FLW_FETCH_LIGHT(0, -1, 0, 4)
_FLW_FETCH_LIGHT(1, -1, 0, 5)
_FLW_FETCH_LIGHT(-1, -1, 1, 6)
_FLW_FETCH_LIGHT(0, -1, 1, 7)
_FLW_FETCH_LIGHT(1, -1, 1, 8)
_FLW_FETCH_LIGHT(-1, 0, -1, 9)
_FLW_FETCH_LIGHT(0, 0, -1, 10)
_FLW_FETCH_LIGHT(1, 0, -1, 11)
_FLW_FETCH_LIGHT(-1, 0, 0, 12)
_FLW_FETCH_LIGHT(0, 0, 0, 13)
_FLW_FETCH_LIGHT(1, 0, 0, 14)
_FLW_FETCH_LIGHT(-1, 0, 1, 15)
_FLW_FETCH_LIGHT(0, 0, 1, 16)
_FLW_FETCH_LIGHT(1, 0, 1, 17)
_FLW_FETCH_LIGHT(-1, 1, -1, 18)
_FLW_FETCH_LIGHT(0, 1, -1, 19)
_FLW_FETCH_LIGHT(1, 1, -1, 20)
_FLW_FETCH_LIGHT(-1, 1, 0, 21)
_FLW_FETCH_LIGHT(0, 1, 0, 22)
_FLW_FETCH_LIGHT(1, 1, 0, 23)
_FLW_FETCH_LIGHT(-1, 1, 1, 24)
_FLW_FETCH_LIGHT(0, 1, 1, 25)
_FLW_FETCH_LIGHT(1, 1, 1, 26)
// @formatter:on
return lights;
}
#define _flw_index3x3x3(x, y, z) ((x) + (z) * 3u + (y) * 9u)
#define _flw_index3x3x3v(p) _flw_index3x3x3((p).x, (p).y, (p).z)
#define _flw_validCountToAo(validCount) (1. - (4. - (validCount)) * 0.2)
/// Calculate the light for a direction by averaging the light at the corners of the block.
@ -167,65 +229,73 @@ uint _flw_fetchSolid3x3x3(uint sectionOffset, ivec3 blockInSectionPos) {
/// @param interpolant The position within the center block.
/// @param c00..c11 4 offsets to determine which "direction" we are averaging.
/// @param oppositeMask A bitmask telling this function which bit to flip to get the opposite index for a given corner
vec3 _flw_lightForDirection(uint[27] lights, vec3 interpolant, uvec3 c00, uvec3 c01, uvec3 c10, uvec3 c11, uint oppositeMask) {
// Constant propatation should inline all of these index calculations,
// but since they're distributive we can lay them out more nicely.
uint ic00 = _flw_index3x3x3v(c00);
uint ic01 = _flw_index3x3x3v(c01);
uint ic10 = _flw_index3x3x3v(c10);
uint ic11 = _flw_index3x3x3v(c11);
const uint[8] corners = uint[](
_flw_index3x3x3(0u, 0u, 0u),
_flw_index3x3x3(0u, 0u, 1u),
_flw_index3x3x3(0u, 1u, 0u),
_flw_index3x3x3(0u, 1u, 1u),
_flw_index3x3x3(1u, 0u, 0u),
_flw_index3x3x3(1u, 0u, 1u),
_flw_index3x3x3(1u, 1u, 0u),
_flw_index3x3x3(1u, 1u, 1u)
);
// Division and branching are both kinda expensive, so use this table for the valid block normalization
const float[5] normalizers = float[](0., 1., 1. / 2., 1. / 3., 1. / 4.);
vec3 _flw_lightForDirection(uint[27] lights, vec3 interpolant, uint c00, uint c01, uint c10, uint c11, uint oppositeMask) {
// Sum up the light and number of valid blocks in each corner for this direction
uint[8] summed;
for (uint i = 0; i < 8; i++) {
uint corner = corners[i];
summed[i] = lights[ic00 + corner] + lights[ic01 + corner] + lights[ic10 + corner] + lights[ic11 + corner];
// @formatter:off
#define _FLW_SUM_CORNER(_x, _y, _z, i) { \
const uint corner = _flw_index3x3x3(_x, _y, _z); \
summed[i] = lights[c00 + corner] + lights[c01 + corner] + lights[c10 + corner] + lights[c11 + corner]; \
}
_FLW_SUM_CORNER(0u, 0u, 0u, 0)
_FLW_SUM_CORNER(1u, 0u, 0u, 1)
_FLW_SUM_CORNER(0u, 0u, 1u, 2)
_FLW_SUM_CORNER(1u, 0u, 1u, 3)
_FLW_SUM_CORNER(0u, 1u, 0u, 4)
_FLW_SUM_CORNER(1u, 1u, 0u, 5)
_FLW_SUM_CORNER(0u, 1u, 1u, 6)
_FLW_SUM_CORNER(1u, 1u, 1u, 7)
// @formatter:on
// The final light and number of valid blocks for each corner.
vec3[8] adjusted;
for (uint i = 0; i < 8; i++) {
#ifdef _FLW_INNER_FACE_CORRECTION
// If the current corner has no valid blocks, use the opposite
// corner's light based on which direction we're evaluating.
// Because of how our corners are indexed, moving along one axis is the same as flipping a bit.
uint cornerIndex = (summed[i] & 0xFFF00000u) == 0u ? i ^ oppositeMask : i;
#define _FLW_CORNER_INDEX(i) ((summed[i] & _FLW_UPPER_10_BITS) == 0u ? i ^ oppositeMask : i)
#else
uint cornerIndex = i;
#define _FLW_CORNER_INDEX(i) i
#endif
uint corner = summed[cornerIndex];
uvec3 unpacked = uvec3(corner & 0x3FFu, (corner >> 10u) & 0x3FFu, corner >> 20u);
// Division and branching (to avoid dividing by zero) are both kinda expensive, so use this table for the valid block normalization
const float[5] normalizers = float[](0., 1., 1. / 2., 1. / 3., 1. / 4.);
// Normalize by the number of valid blocks.
adjusted[i].xy = vec2(unpacked.xy) * normalizers[unpacked.z];
adjusted[i].z = float(unpacked.z);
// @formatter:off
#define _FLW_ADJUST_CORNER(i) { \
uint corner = summed[_FLW_CORNER_INDEX(i)]; \
uint validCount = corner >> 20u; \
adjusted[i].xy = vec2(corner & _FLW_LOWER_10_BITS, (corner >> 10u) & _FLW_LOWER_10_BITS) * normalizers[validCount]; \
adjusted[i].z = float(validCount); \
}
_FLW_ADJUST_CORNER(0)
_FLW_ADJUST_CORNER(1)
_FLW_ADJUST_CORNER(2)
_FLW_ADJUST_CORNER(3)
_FLW_ADJUST_CORNER(4)
_FLW_ADJUST_CORNER(5)
_FLW_ADJUST_CORNER(6)
_FLW_ADJUST_CORNER(7)
// @formatter:on
// Trilinear interpolation, including valid count
vec3 light00 = mix(adjusted[0], adjusted[1], interpolant.z);
vec3 light01 = mix(adjusted[2], adjusted[3], interpolant.z);
vec3 light10 = mix(adjusted[4], adjusted[5], interpolant.z);
vec3 light11 = mix(adjusted[6], adjusted[7], interpolant.z);
vec3 light00 = mix(adjusted[0], adjusted[1], interpolant.x);
vec3 light01 = mix(adjusted[2], adjusted[3], interpolant.x);
vec3 light10 = mix(adjusted[4], adjusted[5], interpolant.x);
vec3 light11 = mix(adjusted[6], adjusted[7], interpolant.x);
vec3 light0 = mix(light00, light01, interpolant.y);
vec3 light1 = mix(light10, light11, interpolant.y);
vec3 light0 = mix(light00, light01, interpolant.z);
vec3 light1 = mix(light10, light11, interpolant.z);
vec3 light = mix(light0, light1, interpolant.x);
vec3 light = mix(light0, light1, interpolant.y);
// Normalize the light coords
light.xy *= 1. / 15.;
@ -251,7 +321,8 @@ bool flw_light(vec3 worldPos, vec3 normal, out FlwLightAo light) {
// The block's position in the section adjusted into 18x18x18 space
ivec3 blockInSectionPos = (blockPos & 0xF) + 1;
#if _FLW_LIGHT_SMOOTHNESS == 1// Directly trilerp as if sampling a texture
// Directly trilerp as if sampling a texture
#if _FLW_LIGHT_SMOOTHNESS == 1
// The lowest corner of the 2x2x2 area we'll be trilinear interpolating.
// The ugly bit on the end evaluates to -1 or 0 depending on which side of 0.5 we are.
@ -283,7 +354,8 @@ bool flw_light(vec3 worldPos, vec3 normal, out FlwLightAo light) {
light.light = mix(light0, light1, interpolant.x) / 15.;
light.ao = 1.;
#elif _FLW_LIGHT_SMOOTHNESS == 2// Lighting and AO accurate to chunk baking
// Lighting and AO accurate to chunk baking
#elif _FLW_LIGHT_SMOOTHNESS == 2
uint solid = _flw_fetchSolid3x3x3(sectionOffset, blockInSectionPos);
@ -304,27 +376,27 @@ bool flw_light(vec3 worldPos, vec3 normal, out FlwLightAo light) {
vec3 lightX;
if (normal.x > _FLW_EPSILON) {
lightX = _flw_lightForDirection(lights, interpolant, uvec3(1u, 0u, 0u), uvec3(1u, 0u, 1u), uvec3(1u, 1u, 0u), uvec3(1u, 1u, 1u), 4u);
lightX = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(1u, 0u, 0u), _flw_index3x3x3(1u, 0u, 1u), _flw_index3x3x3(1u, 1u, 0u), _flw_index3x3x3(1u, 1u, 1u), 1u);
} else if (normal.x < -_FLW_EPSILON) {
lightX = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 0u), uvec3(0u, 0u, 1u), uvec3(0u, 1u, 0u), uvec3(0u, 1u, 1u), 4u);
lightX = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 0u, 0u), _flw_index3x3x3(0u, 0u, 1u), _flw_index3x3x3(0u, 1u, 0u), _flw_index3x3x3(0u, 1u, 1u), 1u);
} else {
lightX = vec3(0.);
}
vec3 lightZ;
if (normal.z > _FLW_EPSILON) {
lightZ = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 1u), uvec3(0u, 1u, 1u), uvec3(1u, 0u, 1u), uvec3(1u, 1u, 1u), 1u);
lightZ = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 0u, 1u), _flw_index3x3x3(0u, 1u, 1u), _flw_index3x3x3(1u, 0u, 1u), _flw_index3x3x3(1u, 1u, 1u), 2u);
} else if (normal.z < -_FLW_EPSILON) {
lightZ = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 0u), uvec3(0u, 1u, 0u), uvec3(1u, 0u, 0u), uvec3(1u, 1u, 0u), 1u);
lightZ = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 0u, 0u), _flw_index3x3x3(0u, 1u, 0u), _flw_index3x3x3(1u, 0u, 0u), _flw_index3x3x3(1u, 1u, 0u), 2u);
} else {
lightZ = vec3(0.);
}
vec3 lightY;
if (normal.y > _FLW_EPSILON) {
lightY = _flw_lightForDirection(lights, interpolant, uvec3(0u, 1u, 0u), uvec3(0u, 1u, 1u), uvec3(1u, 1u, 0u), uvec3(1u, 1u, 1u), 2u);
lightY = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 1u, 0u), _flw_index3x3x3(0u, 1u, 1u), _flw_index3x3x3(1u, 1u, 0u), _flw_index3x3x3(1u, 1u, 1u), 4u);
} else if (normal.y < -_FLW_EPSILON) {
lightY = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 0u), uvec3(0u, 0u, 1u), uvec3(1u, 0u, 0u), uvec3(1u, 0u, 1u), 2u);
lightY = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 0u, 0u), _flw_index3x3x3(0u, 0u, 1u), _flw_index3x3x3(1u, 0u, 0u), _flw_index3x3x3(1u, 0u, 1u), 4u);
} else {
lightY = vec3(0.);
}
@ -335,7 +407,8 @@ bool flw_light(vec3 worldPos, vec3 normal, out FlwLightAo light) {
light.light = lightAo.xy;
light.ao = lightAo.z;
#else// Entirely flat lighting, the lowest setting and a fallback in case an invalid option is set
// Entirely flat lighting, the lowest setting and a fallback in case an invalid option is set
#else
light.light = vec2(_flw_lightAt(sectionOffset, blockInSectionPos)) / 15.;
light.ao = 1.;

View file

@ -53,9 +53,3 @@ void _flw_unpackUint2x16(uint s, out uint hi, out uint lo) {
hi = (s >> 16) & 0xFFFFu;
lo = s & 0xFFFFu;
}
void _flw_unpackUint3x10(uint s, out uint hi, out uint mi, out uint lo) {
hi = (s >> 20) & 0x3FFu;
mi = (s >> 10) & 0x3FFu;
lo = s & 0x3FFu;
}

View file

@ -3,22 +3,20 @@ package dev.engine_room.flywheel.lib.backend;
import java.util.Objects;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Supplier;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.api.backend.BackendManager;
import dev.engine_room.flywheel.api.backend.Engine;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.LevelAccessor;
public final class SimpleBackend implements Backend {
private final Function<LevelAccessor, Engine> engineFactory;
private final Supplier<Backend> fallback;
private final int priority;
private final BooleanSupplier isSupported;
public SimpleBackend(Function<LevelAccessor, Engine> engineFactory, Supplier<Backend> fallback, BooleanSupplier isSupported) {
public SimpleBackend(int priority, Function<LevelAccessor, Engine> engineFactory, BooleanSupplier isSupported) {
this.priority = priority;
this.engineFactory = engineFactory;
this.fallback = fallback;
this.isSupported = isSupported;
}
@ -32,13 +30,8 @@ public final class SimpleBackend implements Backend {
}
@Override
public Backend findFallback() {
if (isSupported()) {
return this;
} else {
return fallback.get()
.findFallback();
}
public int priority() {
return priority;
}
@Override
@ -48,7 +41,7 @@ public final class SimpleBackend implements Backend {
public static final class Builder {
private Function<LevelAccessor, Engine> engineFactory;
private Supplier<Backend> fallback = BackendManager::offBackend;
private int priority = 0;
private BooleanSupplier isSupported;
public Builder engineFactory(Function<LevelAccessor, Engine> engineFactory) {
@ -56,8 +49,8 @@ public final class SimpleBackend implements Backend {
return this;
}
public Builder fallback(Supplier<Backend> fallback) {
this.fallback = fallback;
public Builder priority(int priority) {
this.priority = priority;
return this;
}
@ -68,10 +61,9 @@ public final class SimpleBackend implements Backend {
public Backend register(ResourceLocation id) {
Objects.requireNonNull(engineFactory);
Objects.requireNonNull(fallback);
Objects.requireNonNull(isSupported);
return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(engineFactory, fallback, isSupported));
return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(priority, engineFactory, isSupported));
}
}
}

View file

@ -61,6 +61,21 @@ public final class ExtraMemoryOps {
MemoryUtil.memPutFloat(ptr + 32, matrix.m22());
}
public static void putMatrix3fPadded(long ptr, Matrix3fc matrix) {
MemoryUtil.memPutFloat(ptr, matrix.m00());
MemoryUtil.memPutFloat(ptr + 4, matrix.m01());
MemoryUtil.memPutFloat(ptr + 8, matrix.m02());
MemoryUtil.memPutFloat(ptr + 12, 0.0f);
MemoryUtil.memPutFloat(ptr + 16, matrix.m10());
MemoryUtil.memPutFloat(ptr + 20, matrix.m11());
MemoryUtil.memPutFloat(ptr + 24, matrix.m12());
MemoryUtil.memPutFloat(ptr + 28, 0.0f);
MemoryUtil.memPutFloat(ptr + 32, matrix.m20());
MemoryUtil.memPutFloat(ptr + 36, matrix.m21());
MemoryUtil.memPutFloat(ptr + 40, matrix.m22());
MemoryUtil.memPutFloat(ptr + 44, 0.0f);
}
public static void putMatrix4f(long ptr, Matrix4fc matrix) {
MemoryUtil.memPutFloat(ptr, matrix.m00());
MemoryUtil.memPutFloat(ptr + 4, matrix.m01());

View file

@ -1,8 +1,9 @@
package dev.engine_room.flywheel.impl;
import java.util.ArrayList;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.backend.Backends;
import dev.engine_room.flywheel.impl.visualization.VisualizationManagerImpl;
import dev.engine_room.flywheel.lib.backend.SimpleBackend;
import net.minecraft.client.multiplayer.ClientLevel;
@ -31,21 +32,48 @@ public final class BackendManagerImpl {
return backend != OFF_BACKEND;
}
// Don't store this statically because backends can theoretically change their priorities at runtime.
private static ArrayList<Backend> backendsByPriority() {
var backends = new ArrayList<>(Backend.REGISTRY.getAll());
// Sort with keys backwards so that the highest priority is first.
backends.sort((a, b) -> Integer.compare(b.priority(), a.priority()));
return backends;
}
private static Backend findDefaultBackend() {
// TODO: Automatically select the best default config based on the user's driver
// TODO: Figure out how this will work if custom backends are registered and without hardcoding the default backends
return Backends.INDIRECT;
var backendsByPriority = backendsByPriority();
if (backendsByPriority.isEmpty()) {
// This probably shouldn't happen, but fail gracefully.
FlwImpl.LOGGER.warn("No backends registered, defaulting to 'flywheel:off'");
return OFF_BACKEND;
}
return backendsByPriority.get(0);
}
private static void chooseBackend() {
var preferred = FlwConfig.INSTANCE.backend();
var actual = preferred.findFallback();
if (preferred != actual) {
FlwImpl.LOGGER.warn("Flywheel backend fell back from '{}' to '{}'", Backend.REGISTRY.getIdOrThrow(preferred), Backend.REGISTRY.getIdOrThrow(actual));
if (preferred.isSupported()) {
backend = preferred;
return;
}
backend = actual;
var backendsByPriority = backendsByPriority();
var startIndex = backendsByPriority.indexOf(preferred) + 1;
// For safety in case we don't find anything
backend = OFF_BACKEND;
for (int i = startIndex; i < backendsByPriority.size(); i++) {
var candidate = backendsByPriority.get(i);
if (candidate.isSupported()) {
backend = candidate;
break;
}
}
FlwImpl.LOGGER.warn("Flywheel backend fell back from '{}' to '{}'", Backend.REGISTRY.getIdOrThrow(preferred), Backend.REGISTRY.getIdOrThrow(backend));
}
public static String getBackendString() {

View file

@ -8,12 +8,10 @@ import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.visual.ShaderLightVisual;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import dev.engine_room.flywheel.lib.instance.InstanceTypes;
import dev.engine_room.flywheel.lib.instance.TransformedInstance;
import dev.engine_room.flywheel.lib.material.CutoutShaders;
import dev.engine_room.flywheel.lib.material.LightShaders;
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
import dev.engine_room.flywheel.lib.model.ModelCache;
import dev.engine_room.flywheel.lib.model.SingleMeshModel;
@ -21,20 +19,17 @@ import dev.engine_room.flywheel.lib.model.part.ModelPartConverter;
import dev.engine_room.flywheel.lib.transform.TransformStack;
import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual;
import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.resources.model.Material;
import net.minecraft.core.Direction;
import net.minecraft.core.SectionPos;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.block.ShulkerBoxBlock;
import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity;
public class ShulkerBoxVisual extends AbstractBlockEntityVisual<ShulkerBoxBlockEntity> implements SimpleDynamicVisual, ShaderLightVisual {
public class ShulkerBoxVisual extends AbstractBlockEntityVisual<ShulkerBoxBlockEntity> implements SimpleDynamicVisual {
private static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder()
.cutout(CutoutShaders.ONE_TENTH)
.light(LightShaders.SMOOTH)
.texture(Sheets.SHULKER_SHEET)
.mipmap(false)
.backfaceCulling(false)
@ -72,7 +67,6 @@ public class ShulkerBoxVisual extends AbstractBlockEntityVisual<ShulkerBoxBlockE
.translate(getVisualPosition())
.translate(0.5f)
.scale(0.9995f)
.scale(11f)
.rotate(rotation)
.scale(1, -1, -1)
.translateY(-1);
@ -126,26 +120,9 @@ public class ShulkerBoxVisual extends AbstractBlockEntityVisual<ShulkerBoxBlockE
stack.popPose();
}
@Override
public void setSectionCollector(SectionCollector sectionCollector) {
var center = SectionPos.asLong(pos);
var out = new LongArraySet();
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
for (int z = -1; z <= 1; z++) {
out.add(SectionPos.offset(center, x, y, z));
}
}
}
sectionCollector.sections(out);
}
@Override
public void updateLight(float partialTick) {
// relight(base, lid);
relight(base, lid);
}
@Override

View file

@ -1,106 +0,0 @@
package dev.engine_room.flywheel.backend;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.nio.file.Path;
import java.util.Locale;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import dev.engine_room.flywheel.backend.compile.LightSmoothness;
import net.fabricmc.loader.api.FabricLoader;
public class FabricBackendConfig implements BackendConfig {
public static final Path PATH = FabricLoader.getInstance()
.getConfigDir()
.resolve("flywheel-backend.json");
public static final FabricBackendConfig INSTANCE = new FabricBackendConfig(PATH.toFile());
private static final Gson GSON = new GsonBuilder().setPrettyPrinting()
.create();
private final File file;
public LightSmoothness lightSmoothness = LightSmoothness.SMOOTH;
public FabricBackendConfig(File file) {
this.file = file;
}
@Override
public LightSmoothness lightSmoothness() {
return lightSmoothness;
}
public void load() {
if (file.exists()) {
try (FileReader reader = new FileReader(file)) {
fromJson(JsonParser.parseReader(reader));
} catch (Exception e) {
FlwBackend.LOGGER.warn("Could not load config from file '{}'", file.getAbsolutePath(), e);
}
}
// In case we found an error in the config file, immediately save to fix it.
save();
}
public void save() {
try (FileWriter writer = new FileWriter(file)) {
GSON.toJson(toJson(), writer);
} catch (Exception e) {
FlwBackend.LOGGER.warn("Could not save config to file '{}'", file.getAbsolutePath(), e);
}
}
public void fromJson(JsonElement json) {
if (!(json instanceof JsonObject object)) {
FlwBackend.LOGGER.warn("Config JSON must be an object");
lightSmoothness = LightSmoothness.SMOOTH;
return;
}
readLightSmoothness(object);
}
private void readLightSmoothness(JsonObject object) {
var backendJson = object.get("lightSmoothness");
String msg = null;
if (backendJson instanceof JsonPrimitive primitive && primitive.isString()) {
var value = primitive.getAsString();
for (var item : LightSmoothness.values()) {
if (item.name()
.equalsIgnoreCase(value)) {
lightSmoothness = item;
return;
}
}
msg = "Unknown 'lightSmoothness' value: " + value;
} else if (backendJson != null) {
msg = "'lightSmoothness' value must be a string";
}
// Don't log an error if the field is missing.
if (msg != null) {
FlwBackend.LOGGER.warn(msg);
}
lightSmoothness = LightSmoothness.SMOOTH;
}
public JsonObject toJson() {
JsonObject object = new JsonObject();
object.addProperty("lightSmoothness", lightSmoothness.toString()
.toLowerCase(Locale.ROOT));
return object;
}
}

View file

@ -1,10 +1,15 @@
package dev.engine_room.flywheel.backend;
import org.jetbrains.annotations.UnknownNullability;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
public class FlwBackendXplatImpl implements FlwBackendXplat {
@UnknownNullability
public static BackendConfig CONFIG;
@Override
public int getLightEmission(BlockState state, BlockGetter level, BlockPos pos) {
return state.getLightEmission();
@ -12,6 +17,6 @@ public class FlwBackendXplatImpl implements FlwBackendXplat {
@Override
public BackendConfig getConfig() {
return FabricBackendConfig.INSTANCE;
return CONFIG;
}
}

View file

@ -14,6 +14,10 @@ import com.google.gson.JsonPrimitive;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.api.backend.BackendManager;
import dev.engine_room.flywheel.backend.BackendConfig;
import dev.engine_room.flywheel.backend.FlwBackend;
import dev.engine_room.flywheel.backend.FlwBackendXplatImpl;
import dev.engine_room.flywheel.backend.compile.LightSmoothness;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.ResourceLocationException;
import net.minecraft.resources.ResourceLocation;
@ -35,12 +39,16 @@ public class FabricFlwConfig implements FlwConfig {
private final File file;
public final FabricBackendConfig backendConfig = new FabricBackendConfig();
public Backend backend = BackendManager.defaultBackend();
public boolean limitUpdates = LIMIT_UPDATES_DEFAULT;
public int workerThreads = WORKER_THREADS_DEFAULT;
public FabricFlwConfig(File file) {
this.file = file;
FlwBackendXplatImpl.CONFIG = backendConfig;
}
@Override
@ -90,6 +98,17 @@ public class FabricFlwConfig implements FlwConfig {
readBackend(object);
readLimitUpdates(object);
readWorkerThreads(object);
readFlwBackend(object);
}
private void readFlwBackend(JsonObject object) {
var flwBackendJson = object.get("flw_backend");
if (flwBackendJson instanceof JsonObject flwBackendObject) {
backendConfig.fromJson(flwBackendObject);
} else {
FlwImpl.CONFIG_LOGGER.warn("'flw_backend' value must be an object");
}
}
private void readBackend(JsonObject object) {
@ -158,6 +177,55 @@ public class FabricFlwConfig implements FlwConfig {
object.addProperty("backend", Backend.REGISTRY.getIdOrThrow(backend).toString());
object.addProperty("limitUpdates", limitUpdates);
object.addProperty("workerThreads", workerThreads);
object.add("flw_backend", backendConfig.toJson());
return object;
}
public static class FabricBackendConfig implements BackendConfig {
public static final LightSmoothness LIGHT_SMOOTHNESS_DEFAULT = LightSmoothness.SMOOTH;
public LightSmoothness lightSmoothness = LIGHT_SMOOTHNESS_DEFAULT;
@Override
public LightSmoothness lightSmoothness() {
return lightSmoothness;
}
public void fromJson(JsonObject object) {
readLightSmoothness(object);
}
private void readLightSmoothness(JsonObject object) {
var backendJson = object.get("lightSmoothness");
String msg = null;
if (backendJson instanceof JsonPrimitive primitive && primitive.isString()) {
var value = primitive.getAsString();
for (var item : LightSmoothness.values()) {
if (item.name()
.equalsIgnoreCase(value)) {
lightSmoothness = item;
return;
}
}
msg = "Unknown 'lightSmoothness' value: " + value;
} else if (backendJson != null) {
msg = "'lightSmoothness' value must be a string";
}
// Don't log an error if the field is missing.
if (msg != null) {
FlwBackend.LOGGER.warn(msg);
}
lightSmoothness = LIGHT_SMOOTHNESS_DEFAULT;
}
public JsonObject toJson() {
JsonObject object = new JsonObject();
object.addProperty("lightSmoothness", lightSmoothness.getSerializedName());
return object;
}
}
}

View file

@ -8,7 +8,6 @@ import com.mojang.brigadier.context.CommandContext;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.api.backend.BackendManager;
import dev.engine_room.flywheel.backend.FabricBackendConfig;
import dev.engine_room.flywheel.backend.LightSmoothnessArgument;
import dev.engine_room.flywheel.backend.compile.LightSmoothness;
import dev.engine_room.flywheel.backend.engine.uniform.DebugMode;
@ -129,12 +128,12 @@ public final class FlwCommands {
command.then(ClientCommandManager.literal("lightSmoothness")
.then(ClientCommandManager.argument("mode", LightSmoothnessArgument.INSTANCE)
.executes(context -> {
var oldValue = FabricBackendConfig.INSTANCE.lightSmoothness;
var oldValue = FabricFlwConfig.INSTANCE.backendConfig.lightSmoothness;
var newValue = context.getArgument("mode", LightSmoothness.class);
if (oldValue != newValue) {
FabricBackendConfig.INSTANCE.lightSmoothness = newValue;
FabricBackendConfig.INSTANCE.save();
FabricFlwConfig.INSTANCE.backendConfig.lightSmoothness = newValue;
FabricFlwConfig.INSTANCE.save();
Minecraft.getInstance()
.reloadResourcePacks();
}

View file

@ -12,7 +12,6 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import dev.engine_room.flywheel.api.event.EndClientResourceReloadCallback;
import dev.engine_room.flywheel.backend.FabricBackendConfig;
import dev.engine_room.flywheel.impl.FabricFlwConfig;
import dev.engine_room.flywheel.impl.FlwImpl;
import net.minecraft.client.Minecraft;
@ -30,7 +29,6 @@ abstract class MinecraftMixin {
// Load the config after we freeze registries,
// so we can find third party backends.
FabricFlwConfig.INSTANCE.load();
FabricBackendConfig.INSTANCE.load();
}
@Inject(method = "method_53522", at = @At("HEAD"))

View file

@ -1,10 +1,15 @@
package dev.engine_room.flywheel.backend;
import org.jetbrains.annotations.UnknownNullability;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
public class FlwBackendXplatImpl implements FlwBackendXplat {
@UnknownNullability
public static BackendConfig CONFIG;
@Override
public int getLightEmission(BlockState state, BlockGetter level, BlockPos pos) {
return state.getLightEmission(level, pos);
@ -12,6 +17,6 @@ public class FlwBackendXplatImpl implements FlwBackendXplat {
@Override
public BackendConfig getConfig() {
return ForgeBackendConfig.INSTANCE;
return CONFIG;
}
}

View file

@ -1,39 +0,0 @@
package dev.engine_room.flywheel.backend;
import org.apache.commons.lang3.tuple.Pair;
import dev.engine_room.flywheel.backend.compile.LightSmoothness;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.config.ModConfig;
public class ForgeBackendConfig implements BackendConfig {
public static final ForgeBackendConfig INSTANCE = new ForgeBackendConfig();
public final ClientConfig client;
private final ForgeConfigSpec clientSpec;
private ForgeBackendConfig() {
Pair<ClientConfig, ForgeConfigSpec> clientPair = new ForgeConfigSpec.Builder().configure(ClientConfig::new);
this.client = clientPair.getLeft();
clientSpec = clientPair.getRight();
}
@Override
public LightSmoothness lightSmoothness() {
return client.lightSmoothness.get();
}
public void registerSpecs(ModLoadingContext context) {
context.registerConfig(ModConfig.Type.CLIENT, clientSpec, "flywheel-backend.toml");
}
public static class ClientConfig {
public final ForgeConfigSpec.EnumValue<LightSmoothness> lightSmoothness;
private ClientConfig(ForgeConfigSpec.Builder builder) {
lightSmoothness = builder.comment("How smooth flywheel's shader-based lighting should be. May have a large performance impact.")
.defineEnum("lightSmoothness", LightSmoothness.SMOOTH);
}
}
}

View file

@ -6,7 +6,6 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.api.backend.BackendManager;
import dev.engine_room.flywheel.backend.ForgeBackendConfig;
import dev.engine_room.flywheel.backend.LightSmoothnessArgument;
import dev.engine_room.flywheel.backend.compile.LightSmoothness;
import dev.engine_room.flywheel.backend.engine.uniform.DebugMode;
@ -124,7 +123,7 @@ public final class FlwCommands {
return Command.SINGLE_SUCCESS;
})));
var lightSmoothnessValue = ForgeBackendConfig.INSTANCE.client.lightSmoothness;
var lightSmoothnessValue = ForgeFlwConfig.INSTANCE.client.backendConfig.lightSmoothness;
command.then(Commands.literal("lightSmoothness")
.then(Commands.argument("mode", LightSmoothnessArgument.INSTANCE)
.executes(context -> {

View file

@ -8,7 +8,6 @@ import org.jetbrains.annotations.UnknownNullability;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.event.EndClientResourceReloadEvent;
import dev.engine_room.flywheel.api.event.ReloadLevelRendererEvent;
import dev.engine_room.flywheel.backend.ForgeBackendConfig;
import dev.engine_room.flywheel.backend.LightSmoothnessArgument;
import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
@ -51,7 +50,6 @@ public final class FlywheelForge {
IEventBus forgeEventBus = NeoForge.EVENT_BUS;
ForgeFlwConfig.INSTANCE.registerSpecs(modContainer);
ForgeBackendConfig.INSTANCE.registerSpecs(modLoadingContext);
if (FMLLoader.getDist().isClient()) {
Supplier<Runnable> toRun = () -> () -> FlywheelForge.clientInit(forgeEventBus, modEventBus);

View file

@ -5,6 +5,9 @@ import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.api.backend.BackendManager;
import dev.engine_room.flywheel.backend.BackendConfig;
import dev.engine_room.flywheel.backend.FlwBackendXplatImpl;
import dev.engine_room.flywheel.backend.compile.LightSmoothness;
import net.minecraft.ResourceLocationException;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.fml.ModContainer;
@ -21,6 +24,8 @@ public class ForgeFlwConfig implements FlwConfig {
Pair<ClientConfig, ModConfigSpec> clientPair = new ModConfigSpec.Builder().configure(ClientConfig::new);
this.client = clientPair.getLeft();
clientSpec = clientPair.getRight();
FlwBackendXplatImpl.CONFIG = client.backendConfig;
}
@Override
@ -72,6 +77,8 @@ public class ForgeFlwConfig implements FlwConfig {
public final ModConfigSpec.BooleanValue limitUpdates;
public final ModConfigSpec.IntValue workerThreads;
public final ForgeBackendConfig backendConfig;
private ClientConfig(ModConfigSpec.Builder builder) {
backend = builder.comment("Select the backend to use.")
.define("backend", Backend.REGISTRY.getIdOrThrow(BackendManager.defaultBackend()).toString());
@ -82,6 +89,25 @@ public class ForgeFlwConfig implements FlwConfig {
workerThreads = builder.comment("The number of worker threads to use. Set to -1 to let Flywheel decide. Set to 0 to disable parallelism. Requires a game restart to take effect.")
.defineInRange("workerThreads", -1, -1, Runtime.getRuntime()
.availableProcessors());
builder.comment("Config options for flywheel's build-in backends.")
.push("flw_backends");
backendConfig = new ForgeBackendConfig(builder);
}
}
public static class ForgeBackendConfig implements BackendConfig {
public final ForgeConfigSpec.EnumValue<LightSmoothness> lightSmoothness;
public ForgeBackendConfig(ForgeConfigSpec.Builder builder) {
lightSmoothness = builder.comment("How smooth flywheel's shader-based lighting should be. May have a large performance impact.")
.defineEnum("lightSmoothness", LightSmoothness.SMOOTH);
}
@Override
public LightSmoothness lightSmoothness() {
return lightSmoothness.get();
}
}
}