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); 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. * Check if this backend is supported.

View file

@ -8,9 +8,6 @@ public interface BackendConfig {
/** /**
* How smooth/accurate our flw_light impl is. * 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. * @return The current light smoothness setting.
*/ */
LightSmoothness lightSmoothness(); LightSmoothness lightSmoothness();

View file

@ -17,6 +17,7 @@ public final class Backends {
*/ */
public static final Backend INSTANCING = SimpleBackend.builder() public static final Backend INSTANCING = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(level, new InstancedDrawManager(InstancingPrograms.get()), 256)) .engineFactory(level -> new EngineImpl(level, new InstancedDrawManager(InstancingPrograms.get()), 256))
.priority(500)
.supported(() -> GlCompat.SUPPORTS_INSTANCING && InstancingPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse()) .supported(() -> GlCompat.SUPPORTS_INSTANCING && InstancingPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse())
.register(Flywheel.rl("instancing")); .register(Flywheel.rl("instancing"));
@ -25,7 +26,7 @@ public final class Backends {
*/ */
public static final Backend INDIRECT = SimpleBackend.builder() public static final Backend INDIRECT = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(level, new IndirectDrawManager(IndirectPrograms.get()), 256)) .engineFactory(level -> new EngineImpl(level, new IndirectDrawManager(IndirectPrograms.get()), 256))
.fallback(() -> Backends.INSTANCING) .priority(1000)
.supported(() -> GlCompat.SUPPORTS_INDIRECT && IndirectPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse()) .supported(() -> GlCompat.SUPPORTS_INDIRECT && IndirectPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse())
.register(Flywheel.rl("indirect")); .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.CutoutShader;
import dev.engine_room.flywheel.api.material.FogShader; 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.material.MaterialShaders;
import dev.engine_room.flywheel.api.registry.Registry; import dev.engine_room.flywheel.api.registry.Registry;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap;
@ -26,8 +25,6 @@ public final class MaterialShaderIndices {
private static Index fogSources; private static Index fogSources;
@Nullable @Nullable
private static Index cutoutSources; private static Index cutoutSources;
@Nullable
private static Index lightSources;
private MaterialShaderIndices() { private MaterialShaderIndices() {
} }
@ -60,13 +57,6 @@ public final class MaterialShaderIndices {
return cutoutSources; return cutoutSources;
} }
public static Index lightSources() {
if (lightSources == null) {
lightSources = indexFromRegistry(LightShader.REGISTRY, LightShader::source);
}
return lightSources;
}
public static int vertexIndex(MaterialShaders shaders) { public static int vertexIndex(MaterialShaders shaders) {
return vertexSources().index(shaders.vertexSource()); return vertexSources().index(shaders.vertexSource());
} }
@ -83,10 +73,6 @@ public final class MaterialShaderIndices {
return cutoutSources().index(cutoutShader.source()); 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) { private static <T> Index indexFromRegistry(Registry<T> registry, Function<T, ResourceLocation> sourceFunc) {
if (!registry.isFrozen()) { if (!registry.isFrozen()) {
throw new IllegalStateException("Cannot create index from registry that is not frozen!"); 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.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType; 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.MaterialShaderIndices;
import dev.engine_room.flywheel.backend.compile.component.UberShaderComponent; import dev.engine_room.flywheel.backend.compile.component.UberShaderComponent;
import dev.engine_room.flywheel.backend.compile.core.CompilerStats; import dev.engine_room.flywheel.backend.compile.core.CompilerStats;
@ -46,16 +47,15 @@ public final class FlwPrograms {
var fragmentMaterialComponent = createFragmentMaterialComponent(loader); var fragmentMaterialComponent = createFragmentMaterialComponent(loader);
var fogComponent = createFogComponent(loader); var fogComponent = createFogComponent(loader);
var cutoutComponent = createCutoutComponent(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. // Probably means the shader sources are missing.
stats.emitErrorLog(); stats.emitErrorLog();
return; return;
} }
List<SourceComponent> vertexComponents = List.of(vertexComponentsHeader, vertexMaterialComponent); 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(); var pipelineKeys = createPipelineKeys();
InstancingPrograms.reload(sources, pipelineKeys, vertexComponents, fragmentComponents); InstancingPrograms.reload(sources, pipelineKeys, vertexComponents, fragmentComponents);
@ -66,7 +66,9 @@ public final class FlwPrograms {
ImmutableList.Builder<PipelineProgramKey> builder = ImmutableList.builder(); ImmutableList.Builder<PipelineProgramKey> builder = ImmutableList.builder();
for (ContextShader contextShader : ContextShader.values()) { for (ContextShader contextShader : ContextShader.values()) {
for (InstanceType<?> instanceType : InstanceType.REGISTRY) { 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(); return builder.build();
@ -119,18 +121,4 @@ public final class FlwPrograms {
.switchOn(GlslExpr.variable("_flw_uberCutoutIndex")) .switchOn(GlslExpr.variable("_flw_uberCutoutIndex"))
.build(loader); .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.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType; 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.InstanceStructComponent;
import dev.engine_room.flywheel.backend.compile.component.SsboInstanceComponent; import dev.engine_room.flywheel.backend.compile.component.SsboInstanceComponent;
import dev.engine_room.flywheel.backend.compile.core.CompilationHarness; import dev.engine_room.flywheel.backend.compile.core.CompilationHarness;
@ -167,8 +168,8 @@ public class IndirectPrograms extends AtomicReferenceCounted {
return instance != null; return instance != null;
} }
public GlProgram getIndirectProgram(InstanceType<?> instanceType, ContextShader contextShader) { public GlProgram getIndirectProgram(InstanceType<?> instanceType, ContextShader contextShader, LightShader light) {
return pipeline.get(new PipelineProgramKey(instanceType, contextShader)); return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light));
} }
public GlProgram getCullingProgram(InstanceType<?> instanceType) { public GlProgram getCullingProgram(InstanceType<?> instanceType) {

View file

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

View file

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

View file

@ -1,12 +1,14 @@
package dev.engine_room.flywheel.backend.compile; package dev.engine_room.flywheel.backend.compile;
import dev.engine_room.flywheel.api.instance.InstanceType; 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. * Represents the entire context of a program's usage.
* *
* @param instanceType The instance shader to use. * @param instanceType The instance shader to use.
* @param contextShader The context 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() { public int capacity() {
return top; 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.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.FlwBackend; import dev.engine_room.flywheel.backend.FlwBackend;
import dev.engine_room.flywheel.backend.engine.embed.Environment; 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 dev.engine_room.flywheel.lib.util.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 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); 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, // 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. // so there are no:tm: threads we could be racing with.
for (var instancer : initializationQueue) { for (var instancer : initializationQueue) {

View file

@ -89,7 +89,7 @@ public class EngineImpl implements Engine {
try (var state = GlStateTracker.getRestoreState()) { try (var state = GlStateTracker.getRestoreState()) {
Uniforms.update(context); Uniforms.update(context);
environmentStorage.flush(); environmentStorage.flush();
drawManager.flush(lightStorage); drawManager.flush(lightStorage, environmentStorage);
} }
} }
@ -107,6 +107,7 @@ public class EngineImpl implements Engine {
public void delete() { public void delete() {
drawManager.delete(); drawManager.delete();
lightStorage.delete(); lightStorage.delete();
environmentStorage.delete();
} }
public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) { 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) { public static int packUberShader(Material material) {
var fog = MaterialShaderIndices.fogIndex(material.fog()); var fog = MaterialShaderIndices.fogIndex(material.fog());
var cutout = MaterialShaderIndices.cutoutIndex(material.cutout()); var cutout = MaterialShaderIndices.cutoutIndex(material.cutout());
var light = MaterialShaderIndices.lightIndex(material.light()); return (cutout & 0xFFFF) | (fog & 0xFFFF) << 16;
return (light & 0x3FF) | (cutout & 0x3FF) << 10 | (fog & 0x3FF) << 20;
} }
// Packed format: // Packed format:

View file

@ -16,7 +16,9 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.AbstractTexture; import net.minecraft.client.renderer.texture.AbstractTexture;
public final class MaterialRenderState { 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::blur)
.thenComparing(Material::mipmap) .thenComparing(Material::mipmap)
.thenComparing(Material::backfaceCulling) .thenComparing(Material::backfaceCulling)
@ -177,4 +179,18 @@ public final class MaterialRenderState {
RenderSystem.depthMask(true); RenderSystem.depthMask(true);
RenderSystem.colorMask(true, true, true, 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.compile.ContextShader;
import dev.engine_room.flywheel.backend.engine.EngineImpl; import dev.engine_room.flywheel.backend.engine.EngineImpl;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram; import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import dev.engine_room.flywheel.lib.util.ExtraMemoryOps;
import net.minecraft.core.Vec3i; import net.minecraft.core.Vec3i;
public class EmbeddedEnvironment implements VisualEmbedding, Environment { public class EmbeddedEnvironment implements VisualEmbedding, Environment {
@ -31,6 +32,8 @@ public class EmbeddedEnvironment implements VisualEmbedding, Environment {
private final Matrix4f poseComposed = new Matrix4f(); private final Matrix4f poseComposed = new Matrix4f();
private final Matrix3f normalComposed = new Matrix3f(); private final Matrix3f normalComposed = new Matrix3f();
public int matrixIndex = 0;
private boolean deleted = false; private boolean deleted = false;
public EmbeddedEnvironment(EngineImpl engine, VisualType visualType, Vec3i renderOrigin, @Nullable EmbeddedEnvironment parent) { public EmbeddedEnvironment(EngineImpl engine, VisualType visualType, Vec3i renderOrigin, @Nullable EmbeddedEnvironment parent) {
@ -81,23 +84,25 @@ public class EmbeddedEnvironment implements VisualEmbedding, Environment {
return ContextShader.EMBEDDED; return ContextShader.EMBEDDED;
} }
@Override
public void setupCull(GlProgram program) {
program.setBool(EmbeddingUniforms.USE_MODEL_MATRIX, true);
program.setMat4(EmbeddingUniforms.MODEL_MATRIX, poseComposed);
}
@Override @Override
public void setupDraw(GlProgram program) { public void setupDraw(GlProgram program) {
program.setMat4(EmbeddingUniforms.MODEL_MATRIX, poseComposed); program.setMat4(EmbeddingUniforms.MODEL_MATRIX, poseComposed);
program.setMat3(EmbeddingUniforms.NORMAL_MATRIX, normalComposed); program.setMat3(EmbeddingUniforms.NORMAL_MATRIX, normalComposed);
} }
public void flush() { @Override
public int matrixIndex() {
return matrixIndex;
}
public void flush(long ptr) {
poseComposed.identity(); poseComposed.identity();
normalComposed.identity(); normalComposed.identity();
composeMatrices(poseComposed, normalComposed); composeMatrices(poseComposed, normalComposed);
ExtraMemoryOps.putMatrix4f(ptr, poseComposed);
ExtraMemoryOps.putMatrix3fPadded(ptr + 16 * Float.BYTES, normalComposed);
} }
private void composeMatrices(Matrix4f pose, Matrix3f normal) { private void composeMatrices(Matrix4f pose, Matrix3f normal) {

View file

@ -1,12 +1,8 @@
package dev.engine_room.flywheel.backend.engine.embed; package dev.engine_room.flywheel.backend.engine.embed;
public final class EmbeddingUniforms { public final class EmbeddingUniforms {
/** public static final String MODEL_MATRIX = "_flw_modelMatrixUniform";
* Only used by cull shaders. public static final String NORMAL_MATRIX = "_flw_normalMatrixUniform";
*/
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";
private EmbeddingUniforms() { private EmbeddingUniforms() {
} }

View file

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

View file

@ -1,18 +1,47 @@
package dev.engine_room.flywheel.backend.engine.embed; 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.ReferenceLinkedOpenHashSet;
import it.unimi.dsi.fastutil.objects.ReferenceSet; import it.unimi.dsi.fastutil.objects.ReferenceSet;
import it.unimi.dsi.fastutil.objects.ReferenceSets;
public class EnvironmentStorage { 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) { public void track(EmbeddedEnvironment environment) {
environments.add(environment); synchronized (lock) {
if (environments.add(environment)) {
environment.matrixIndex = arena.alloc();
}
}
} }
public void flush() { public void flush() {
environments.removeIf(EmbeddedEnvironment::isDeleted); environments.removeIf(embeddedEnvironment -> {
environments.forEach(EmbeddedEnvironment::flush); 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 @Override
public void setupCull(GlProgram cullProgram) { public void setupDraw(GlProgram drawProgram) {
cullProgram.setBool(EmbeddingUniforms.USE_MODEL_MATRIX, false);
} }
@Override @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 DRAW = 4;
public static final int LIGHT_LUT = 5; public static final int LIGHT_LUT = 5;
public static final int LIGHT_SECTION = 6; public static final int LIGHT_SECTION = 6;
public static final int MATRICES = 7;
private BufferBindings() { private BufferBindings() {
} }

View file

@ -16,10 +16,10 @@ public class IndirectBuffers {
public static final long INT_SIZE = Integer.BYTES; public static final long INT_SIZE = Integer.BYTES;
public static final long PTR_SIZE = Pointer.POINTER_SIZE; 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. // 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; public static final long DRAW_COMMAND_OFFSET = 0;
// Offsets to the 3 segments // 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.GL30.glUniform1ui;
import static org.lwjgl.opengl.GL42.GL_COMMAND_BARRIER_BIT; import static org.lwjgl.opengl.GL42.GL_COMMAND_BARRIER_BIT;
import static org.lwjgl.opengl.GL42.glMemoryBarrier; 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 static org.lwjgl.opengl.GL43.glDispatchCompute;
import java.util.ArrayList; 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.InstancerKey;
import dev.engine_room.flywheel.backend.engine.MaterialRenderState; import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
import dev.engine_room.flywheel.backend.engine.MeshPool; 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.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlCompat; import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram; 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; import dev.engine_room.flywheel.lib.math.MoreMath;
public class IndirectCullingGroup<I extends Instance> { public class IndirectCullingGroup<I extends Instance> {
private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::visualType) private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::visualType)
.thenComparing(IndirectDraw::isEmbedded)
.thenComparing(IndirectDraw::bias) .thenComparing(IndirectDraw::bias)
.thenComparing(IndirectDraw::indexOfMeshInModel) .thenComparing(IndirectDraw::indexOfMeshInModel)
.thenComparing(IndirectDraw::material, MaterialRenderState.COMPARATOR); .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 InstanceType<I> instanceType;
private final Environment environment;
private final long instanceStride; private final long instanceStride;
private final IndirectBuffers buffers; private final IndirectBuffers buffers;
private final List<IndirectInstancer<I>> instancers = new ArrayList<>(); private final List<IndirectInstancer<I>> instancers = new ArrayList<>();
@ -48,24 +45,19 @@ public class IndirectCullingGroup<I extends Instance> {
private final IndirectPrograms programs; private final IndirectPrograms programs;
private final GlProgram cullProgram; private final GlProgram cullProgram;
private final GlProgram applyProgram;
private final GlProgram drawProgram;
private boolean needsDrawBarrier; private boolean needsDrawBarrier;
private boolean needsDrawSort; private boolean needsDrawSort;
private int instanceCountThisFrame; private int instanceCountThisFrame;
IndirectCullingGroup(InstanceType<I> instanceType, Environment environment, IndirectPrograms programs) { IndirectCullingGroup(InstanceType<I> instanceType, IndirectPrograms programs) {
this.instanceType = instanceType; this.instanceType = instanceType;
this.environment = environment;
instanceStride = MoreMath.align4(instanceType.layout() instanceStride = MoreMath.align4(instanceType.layout()
.byteSize()); .byteSize());
buffers = new IndirectBuffers(instanceStride); buffers = new IndirectBuffers(instanceStride);
this.programs = programs; this.programs = programs;
cullProgram = programs.getCullingProgram(instanceType); cullProgram = programs.getCullingProgram(instanceType);
applyProgram = programs.getApplyProgram();
drawProgram = programs.getIndirectProgram(instanceType, environment.contextShader());
} }
public void flushInstancers() { public void flushInstancers() {
@ -125,10 +117,7 @@ public class IndirectCullingGroup<I extends Instance> {
Uniforms.bindAll(); Uniforms.bindAll();
cullProgram.bind(); cullProgram.bind();
environment.setupCull(cullProgram);
buffers.bindForCompute(); buffers.bindForCompute();
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
glDispatchCompute(GlCompat.getComputeGroupCount(instanceCountThisFrame), 1, 1); glDispatchCompute(GlCompat.getComputeGroupCount(instanceCountThisFrame), 1, 1);
} }
@ -137,9 +126,7 @@ public class IndirectCullingGroup<I extends Instance> {
return; return;
} }
applyProgram.bind();
buffers.bindForCompute(); buffers.bindForCompute();
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
glDispatchCompute(GlCompat.getComputeGroupCount(indirectDraws.size()), 1, 1); 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++) { for (int start = 0, i = 0; i < indirectDraws.size(); i++) {
var draw1 = indirectDraws.get(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 the next draw call has a different VisualType or Material, start a new MultiDraw
if (i == indirectDraws.size() - 1 || visualType1 != indirectDraws.get(i + 1) if (i == indirectDraws.size() - 1 || incompatibleDraws(draw1, indirectDraws.get(i + 1))) {
.visualType() || !material1.equals(indirectDraws.get(i + 1) multiDraws.computeIfAbsent(draw1.visualType(), s -> new ArrayList<>())
.material())) { .add(new MultiDraw(draw1.material(), draw1.isEmbedded(), start, i + 1));
multiDraws.computeIfAbsent(visualType1, s -> new ArrayList<>())
.add(new MultiDraw(material1, start, i + 1));
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) { public boolean hasVisualType(VisualType visualType) {
return multiDraws.containsKey(visualType); return multiDraws.containsKey(visualType);
} }
@ -199,17 +193,24 @@ public class IndirectCullingGroup<I extends Instance> {
return; return;
} }
drawProgram.bind();
buffers.bindForDraw(); buffers.bindForDraw();
environment.setupDraw(drawProgram);
drawBarrier(); drawBarrier();
var flwBaseDraw = drawProgram.getUniformLocation("_flw_baseDraw"); GlProgram lastProgram = null;
int baseDrawUniformLoc = -1;
for (var multiDraw : multiDraws.get(visualType)) { 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); MaterialRenderState.setup(multiDraw.material);
@ -218,7 +219,7 @@ public class IndirectCullingGroup<I extends Instance> {
} }
public void bindWithContextShader(ContextShader override) { public void bindWithContextShader(ContextShader override) {
var program = programs.getIndirectProgram(instanceType, override); var program = programs.getIndirectProgram(instanceType, override, LightShaders.SMOOTH_WHEN_EMBEDDED);
program.bind(); program.bind();
@ -226,13 +227,15 @@ public class IndirectCullingGroup<I extends Instance> {
drawBarrier(); drawBarrier();
var flwBaseDraw = drawProgram.getUniformLocation("_flw_baseDraw"); var flwBaseDraw = program.getUniformLocation("_flw_baseDraw");
glUniform1ui(flwBaseDraw, 0); glUniform1ui(flwBaseDraw, 0);
} }
private void drawBarrier() { private void drawBarrier() {
if (needsDrawBarrier) { 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; needsDrawBarrier = false;
} }
} }
@ -290,7 +293,7 @@ public class IndirectCullingGroup<I extends Instance> {
return out; 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() { 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); 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.MaterialShaderIndices;
import dev.engine_room.flywheel.backend.engine.MaterialEncoder; import dev.engine_room.flywheel.backend.engine.MaterialEncoder;
import dev.engine_room.flywheel.backend.engine.MeshPool; import dev.engine_room.flywheel.backend.engine.MeshPool;
import dev.engine_room.flywheel.backend.engine.embed.EmbeddedEnvironment;
public class IndirectDraw { public class IndirectDraw {
private final IndirectInstancer<?> instancer; private final IndirectInstancer<?> instancer;
@ -46,6 +47,10 @@ public class IndirectDraw {
return material; return material;
} }
public boolean isEmbedded() {
return instancer.environment instanceof EmbeddedEnvironment;
}
public MeshPool.PooledMesh mesh() { public MeshPool.PooledMesh mesh() {
return mesh; return mesh;
} }
@ -71,10 +76,12 @@ public class IndirectDraw {
MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex); // modelIndex MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex); // modelIndex
MemoryUtil.memPutInt(ptr + 24, materialVertexIndex); // materialVertexIndex MemoryUtil.memPutInt(ptr + 24, instancer.environment.matrixIndex()); // matrixIndex
MemoryUtil.memPutInt(ptr + 28, materialFragmentIndex); // materialFragmentIndex
MemoryUtil.memPutInt(ptr + 32, packedFogAndCutout); // packedFogAndCutout MemoryUtil.memPutInt(ptr + 28, materialVertexIndex); // materialVertexIndex
MemoryUtil.memPutInt(ptr + 36, packedMaterialProperties); // packedMaterialProperties 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) { 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 + 20, instancer.modelIndex); // modelIndex
MemoryUtil.memPutInt(ptr + 24, MaterialShaderIndices.vertexIndex(materialOverride.shaders())); // materialVertexIndex MemoryUtil.memPutInt(ptr + 24, instancer.environment.matrixIndex()); // matrixIndex
MemoryUtil.memPutInt(ptr + 28, MaterialShaderIndices.fragmentIndex(materialOverride.shaders())); // materialFragmentIndex
MemoryUtil.memPutInt(ptr + 32, MaterialEncoder.packUberShader(materialOverride)); // packedFogAndCutout MemoryUtil.memPutInt(ptr + 28, MaterialShaderIndices.vertexIndex(materialOverride.shaders())); // materialVertexIndex
MemoryUtil.memPutInt(ptr + 36, MaterialEncoder.packProperties(materialOverride)); // packedMaterialProperties 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() { 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.GL11.GL_UNSIGNED_INT;
import static org.lwjgl.opengl.GL30.glBindBufferRange; import static org.lwjgl.opengl.GL30.glBindBufferRange;
import static org.lwjgl.opengl.GL40.glDrawElementsIndirect; 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 static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import java.util.HashMap; 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.backend.Engine;
import dev.engine_room.flywheel.api.instance.Instance; 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.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.Samplers; import dev.engine_room.flywheel.backend.Samplers;
import dev.engine_room.flywheel.backend.compile.ContextShader; import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms; import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
import dev.engine_room.flywheel.backend.engine.CommonCrumbling; import dev.engine_room.flywheel.backend.engine.CommonCrumbling;
import dev.engine_room.flywheel.backend.engine.DrawManager; 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.InstancerKey;
import dev.engine_room.flywheel.backend.engine.LightStorage; import dev.engine_room.flywheel.backend.engine.LightStorage;
import dev.engine_room.flywheel.backend.engine.MaterialRenderState; import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
import dev.engine_room.flywheel.backend.engine.MeshPool; import dev.engine_room.flywheel.backend.engine.MeshPool;
import dev.engine_room.flywheel.backend.engine.TextureBinder; 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.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlStateTracker; import dev.engine_room.flywheel.backend.gl.GlStateTracker;
import dev.engine_room.flywheel.backend.gl.array.GlVertexArray; 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 StagingBuffer stagingBuffer;
private final MeshPool meshPool; private final MeshPool meshPool;
private final GlVertexArray vertexArray; 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 GlBuffer crumblingDrawBuffer = new GlBuffer();
private final LightBuffers lightBuffers; private final LightBuffers lightBuffers;
private final MatrixBuffer matrixBuffer;
private boolean needsBarrier = false;
public IndirectDrawManager(IndirectPrograms programs) { public IndirectDrawManager(IndirectPrograms programs) {
this.programs = programs; this.programs = programs;
@ -51,6 +57,7 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
vertexArray = GlVertexArray.create(); vertexArray = GlVertexArray.create();
meshPool.bind(vertexArray); meshPool.bind(vertexArray);
lightBuffers = new LightBuffers(); lightBuffers = new LightBuffers();
matrixBuffer = new MatrixBuffer();
} }
@Override @Override
@ -61,8 +68,7 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
protected <I extends Instance> void initialize(InstancerKey<I> key, IndirectInstancer<?> instancer) { 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(key.type(), t -> new IndirectCullingGroup<>(t, programs));
var group = (IndirectCullingGroup<I>) cullingGroups.computeIfAbsent(groupKey, t -> new IndirectCullingGroup<>(t.instanceType(), t.environment(), programs));
group.add((IndirectInstancer<I>) instancer, key, meshPool); group.add((IndirectInstancer<I>) instancer, key, meshPool);
} }
@ -85,8 +91,14 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
vertexArray.bindForDraw(); vertexArray.bindForDraw();
lightBuffers.bind(); lightBuffers.bind();
matrixBuffer.bind();
Uniforms.bindAll(); Uniforms.bindAll();
if (needsBarrier) {
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
needsBarrier = false;
}
for (var group : cullingGroups.values()) { for (var group : cullingGroups.values()) {
group.submit(visualType); group.submit(visualType);
} }
@ -97,8 +109,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
} }
@Override @Override
public void flush(LightStorage lightStorage) { public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
super.flush(lightStorage); super.flush(lightStorage, environmentStorage);
for (var group : cullingGroups.values()) { for (var group : cullingGroups.values()) {
group.flushInstancers(); group.flushInstancers();
@ -116,19 +128,35 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
lightBuffers.flush(stagingBuffer, lightStorage); lightBuffers.flush(stagingBuffer, lightStorage);
matrixBuffer.flush(stagingBuffer, environmentStorage);
for (var group : cullingGroups.values()) { for (var group : cullingGroups.values()) {
group.upload(stagingBuffer); group.upload(stagingBuffer);
} }
stagingBuffer.flush(); 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()) { for (var group : cullingGroups.values()) {
group.dispatchCull(); group.dispatchCull();
} }
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
programs.getApplyProgram()
.bind();
for (var group : cullingGroups.values()) { for (var group : cullingGroups.values()) {
group.dispatchApply(); group.dispatchApply();
} }
needsBarrier = true;
} }
@Override @Override

View file

@ -49,10 +49,11 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
public void writeModel(long ptr) { public void writeModel(long ptr) {
MemoryUtil.memPutInt(ptr, 0); // instanceCount - to be incremented by the cull shader MemoryUtil.memPutInt(ptr, 0); // instanceCount - to be incremented by the cull shader
MemoryUtil.memPutInt(ptr + 4, baseInstance); // baseInstance MemoryUtil.memPutInt(ptr + 4, baseInstance); // baseInstance
MemoryUtil.memPutFloat(ptr + 8, boundingSphere.x()); // boundingSphere MemoryUtil.memPutInt(ptr + 8, environment.matrixIndex()); // matrixIndex
MemoryUtil.memPutFloat(ptr + 12, boundingSphere.y()); MemoryUtil.memPutFloat(ptr + 12, boundingSphere.x()); // boundingSphere
MemoryUtil.memPutFloat(ptr + 16, boundingSphere.z()); MemoryUtil.memPutFloat(ptr + 16, boundingSphere.y());
MemoryUtil.memPutFloat(ptr + 20, boundingSphere.w()); MemoryUtil.memPutFloat(ptr + 20, boundingSphere.z());
MemoryUtil.memPutFloat(ptr + 24, boundingSphere.w());
} }
public void uploadInstances(StagingBuffer stagingBuffer, int instanceVbo) { 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.MaterialRenderState;
import dev.engine_room.flywheel.backend.engine.MeshPool; import dev.engine_room.flywheel.backend.engine.MeshPool;
import dev.engine_room.flywheel.backend.engine.TextureBinder; 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.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlStateTracker; import dev.engine_room.flywheel.backend.gl.GlStateTracker;
import dev.engine_room.flywheel.backend.gl.TextureBuffer; import dev.engine_room.flywheel.backend.gl.TextureBuffer;
import dev.engine_room.flywheel.backend.gl.array.GlVertexArray; import dev.engine_room.flywheel.backend.gl.array.GlVertexArray;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram; 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 dev.engine_room.flywheel.lib.material.SimpleMaterial;
import net.minecraft.client.resources.model.ModelBakery; import net.minecraft.client.resources.model.ModelBakery;
@ -58,8 +60,8 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
} }
@Override @Override
public void flush(LightStorage lightStorage) { public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
super.flush(lightStorage); super.flush(lightStorage, environmentStorage);
this.instancers.values() this.instancers.values()
.removeIf(instancer -> { .removeIf(instancer -> {
@ -170,7 +172,7 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
GroupKey<?> shader = groupEntry.getKey(); 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(); program.bind();
for (var progressEntry : byProgress.int2ObjectEntrySet()) { for (var progressEntry : byProgress.int2ObjectEntrySet()) {

View file

@ -54,12 +54,13 @@ public class InstancedRenderStage {
var environment = shader.environment(); 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(); program.bind();
environment.setupDraw(program); environment.setupDraw(program);
for (var drawCall : drawCalls.draws) {
var material = drawCall.material(); var material = drawCall.material();
uploadMaterialUniform(program, material); uploadMaterialUniform(program, material);

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,7 @@ flat in uvec3 _flw_packedMaterial;
void main() { void main() {
_flw_uberMaterialFragmentIndex = _flw_packedMaterial.x; _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_unpackMaterialProperties(_flw_packedMaterial.z, flw_material);
_flw_main(); _flw_main();

View file

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

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 { struct ModelDescriptor {
uint instanceCount; uint instanceCount;
uint baseInstance; uint baseInstance;
uint matrixIndex;
BoundingSphere boundingSphere; BoundingSphere boundingSphere;
}; };

View file

@ -5,7 +5,7 @@ uniform uvec4 _flw_packedMaterial;
void main() { void main() {
_flw_uberMaterialFragmentIndex = _flw_packedMaterial.y; _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_unpackMaterialProperties(_flw_packedMaterial.w, flw_material);
_flw_main(); _flw_main();

View file

@ -5,11 +5,21 @@
uniform uvec4 _flw_packedMaterial; uniform uvec4 _flw_packedMaterial;
uniform int _flw_baseInstance = 0; uniform int _flw_baseInstance = 0;
#ifdef FLW_EMBEDDED
uniform mat4 _flw_modelMatrixUniform;
uniform mat3 _flw_normalMatrixUniform;
#endif
void main() { void main() {
_flw_uberMaterialVertexIndex = _flw_packedMaterial.x; _flw_uberMaterialVertexIndex = _flw_packedMaterial.x;
_flw_unpackMaterialProperties(_flw_packedMaterial.w, flw_material); _flw_unpackMaterialProperties(_flw_packedMaterial.w, flw_material);
FlwInstance instance = _flw_unpackInstance(_flw_baseInstance + gl_InstanceID); 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)); _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 uint _FLW_COMPLETELY_SOLID = 0x7FFFFFFu;
const float _FLW_EPSILON = 1e-5; 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_indexLut(uint index);
uint _flw_indexLight(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. /// 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 base The base index in the LUT, should point to the start of a coordinate span.
/// @param coord The coordinate to look for. /// @param coord The coordinate to look for.
@ -103,57 +102,120 @@ bool flw_lightFetch(ivec3 blockPos, out vec2 lightCoord) {
return true; 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 _flw_fetchSolid3x3x3(uint sectionOffset, ivec3 blockInSectionPos) {
uint ret = 0; uint ret = 0;
uint index = 0; // The formatter does NOT like these macros
for (int y = -1; y <= 1; y++) { // @formatter:off
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;
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; 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_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) #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. /// 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 interpolant The position within the center block.
/// @param c00..c11 4 offsets to determine which "direction" we are averaging. /// @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 /// @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) { vec3 _flw_lightForDirection(uint[27] lights, vec3 interpolant, uint c00, uint c01, uint c10, uint 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.);
// Sum up the light and number of valid blocks in each corner for this direction // Sum up the light and number of valid blocks in each corner for this direction
uint[8] summed; uint[8] summed;
for (uint i = 0; i < 8; i++) {
uint corner = corners[i]; // @formatter:off
summed[i] = lights[ic00 + corner] + lights[ic01 + corner] + lights[ic10 + corner] + lights[ic11 + corner];
#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. // The final light and number of valid blocks for each corner.
vec3[8] adjusted; vec3[8] adjusted;
for (uint i = 0; i < 8; i++) {
#ifdef _FLW_INNER_FACE_CORRECTION #ifdef _FLW_INNER_FACE_CORRECTION
// If the current corner has no valid blocks, use the opposite // If the current corner has no valid blocks, use the opposite
// corner's light based on which direction we're evaluating. // 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. // 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 #else
uint cornerIndex = i; #define _FLW_CORNER_INDEX(i) i
#endif #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. // @formatter:off
adjusted[i].xy = vec2(unpacked.xy) * normalizers[unpacked.z];
adjusted[i].z = float(unpacked.z); #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 // Trilinear interpolation, including valid count
vec3 light00 = mix(adjusted[0], adjusted[1], interpolant.z); vec3 light00 = mix(adjusted[0], adjusted[1], interpolant.x);
vec3 light01 = mix(adjusted[2], adjusted[3], interpolant.z); vec3 light01 = mix(adjusted[2], adjusted[3], interpolant.x);
vec3 light10 = mix(adjusted[4], adjusted[5], interpolant.z); vec3 light10 = mix(adjusted[4], adjusted[5], interpolant.x);
vec3 light11 = mix(adjusted[6], adjusted[7], interpolant.z); vec3 light11 = mix(adjusted[6], adjusted[7], interpolant.x);
vec3 light0 = mix(light00, light01, interpolant.y); vec3 light0 = mix(light00, light01, interpolant.z);
vec3 light1 = mix(light10, light11, interpolant.y); 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 // Normalize the light coords
light.xy *= 1. / 15.; 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 // The block's position in the section adjusted into 18x18x18 space
ivec3 blockInSectionPos = (blockPos & 0xF) + 1; 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 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. // 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.light = mix(light0, light1, interpolant.x) / 15.;
light.ao = 1.; 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); uint solid = _flw_fetchSolid3x3x3(sectionOffset, blockInSectionPos);
@ -304,27 +376,27 @@ bool flw_light(vec3 worldPos, vec3 normal, out FlwLightAo light) {
vec3 lightX; vec3 lightX;
if (normal.x > _FLW_EPSILON) { 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) { } 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 { } else {
lightX = vec3(0.); lightX = vec3(0.);
} }
vec3 lightZ; vec3 lightZ;
if (normal.z > _FLW_EPSILON) { 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) { } 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 { } else {
lightZ = vec3(0.); lightZ = vec3(0.);
} }
vec3 lightY; vec3 lightY;
if (normal.y > _FLW_EPSILON) { 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) { } 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 { } else {
lightY = vec3(0.); lightY = vec3(0.);
} }
@ -335,7 +407,8 @@ bool flw_light(vec3 worldPos, vec3 normal, out FlwLightAo light) {
light.light = lightAo.xy; light.light = lightAo.xy;
light.ao = lightAo.z; 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.light = vec2(_flw_lightAt(sectionOffset, blockInSectionPos)) / 15.;
light.ao = 1.; light.ao = 1.;

View file

@ -53,9 +53,3 @@ void _flw_unpackUint2x16(uint s, out uint hi, out uint lo) {
hi = (s >> 16) & 0xFFFFu; hi = (s >> 16) & 0xFFFFu;
lo = s & 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.Objects;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.Function; 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.Backend;
import dev.engine_room.flywheel.api.backend.BackendManager;
import dev.engine_room.flywheel.api.backend.Engine; import dev.engine_room.flywheel.api.backend.Engine;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelAccessor;
public final class SimpleBackend implements Backend { public final class SimpleBackend implements Backend {
private final Function<LevelAccessor, Engine> engineFactory; private final Function<LevelAccessor, Engine> engineFactory;
private final Supplier<Backend> fallback; private final int priority;
private final BooleanSupplier isSupported; 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.engineFactory = engineFactory;
this.fallback = fallback;
this.isSupported = isSupported; this.isSupported = isSupported;
} }
@ -32,13 +30,8 @@ public final class SimpleBackend implements Backend {
} }
@Override @Override
public Backend findFallback() { public int priority() {
if (isSupported()) { return priority;
return this;
} else {
return fallback.get()
.findFallback();
}
} }
@Override @Override
@ -48,7 +41,7 @@ public final class SimpleBackend implements Backend {
public static final class Builder { public static final class Builder {
private Function<LevelAccessor, Engine> engineFactory; private Function<LevelAccessor, Engine> engineFactory;
private Supplier<Backend> fallback = BackendManager::offBackend; private int priority = 0;
private BooleanSupplier isSupported; private BooleanSupplier isSupported;
public Builder engineFactory(Function<LevelAccessor, Engine> engineFactory) { public Builder engineFactory(Function<LevelAccessor, Engine> engineFactory) {
@ -56,8 +49,8 @@ public final class SimpleBackend implements Backend {
return this; return this;
} }
public Builder fallback(Supplier<Backend> fallback) { public Builder priority(int priority) {
this.fallback = fallback; this.priority = priority;
return this; return this;
} }
@ -68,10 +61,9 @@ public final class SimpleBackend implements Backend {
public Backend register(ResourceLocation id) { public Backend register(ResourceLocation id) {
Objects.requireNonNull(engineFactory); Objects.requireNonNull(engineFactory);
Objects.requireNonNull(fallback);
Objects.requireNonNull(isSupported); 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()); 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) { public static void putMatrix4f(long ptr, Matrix4fc matrix) {
MemoryUtil.memPutFloat(ptr, matrix.m00()); MemoryUtil.memPutFloat(ptr, matrix.m00());
MemoryUtil.memPutFloat(ptr + 4, matrix.m01()); MemoryUtil.memPutFloat(ptr + 4, matrix.m01());

View file

@ -1,8 +1,9 @@
package dev.engine_room.flywheel.impl; package dev.engine_room.flywheel.impl;
import java.util.ArrayList;
import dev.engine_room.flywheel.api.Flywheel; import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.backend.Backend; 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.impl.visualization.VisualizationManagerImpl;
import dev.engine_room.flywheel.lib.backend.SimpleBackend; import dev.engine_room.flywheel.lib.backend.SimpleBackend;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
@ -31,21 +32,48 @@ public final class BackendManagerImpl {
return backend != OFF_BACKEND; 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() { private static Backend findDefaultBackend() {
// TODO: Automatically select the best default config based on the user's driver var backendsByPriority = backendsByPriority();
// TODO: Figure out how this will work if custom backends are registered and without hardcoding the default backends if (backendsByPriority.isEmpty()) {
return Backends.INDIRECT; // 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() { private static void chooseBackend() {
var preferred = FlwConfig.INSTANCE.backend(); var preferred = FlwConfig.INSTANCE.backend();
var actual = preferred.findFallback(); if (preferred.isSupported()) {
backend = preferred;
if (preferred != actual) { return;
FlwImpl.LOGGER.warn("Flywheel backend fell back from '{}' to '{}'", Backend.REGISTRY.getIdOrThrow(preferred), Backend.REGISTRY.getIdOrThrow(actual));
} }
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() { public static String getBackendString() {

View file

@ -8,12 +8,10 @@ import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis; import com.mojang.math.Axis;
import dev.engine_room.flywheel.api.instance.Instance; 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.api.visualization.VisualizationContext;
import dev.engine_room.flywheel.lib.instance.InstanceTypes; import dev.engine_room.flywheel.lib.instance.InstanceTypes;
import dev.engine_room.flywheel.lib.instance.TransformedInstance; import dev.engine_room.flywheel.lib.instance.TransformedInstance;
import dev.engine_room.flywheel.lib.material.CutoutShaders; 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.material.SimpleMaterial;
import dev.engine_room.flywheel.lib.model.ModelCache; import dev.engine_room.flywheel.lib.model.ModelCache;
import dev.engine_room.flywheel.lib.model.SingleMeshModel; 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.transform.TransformStack;
import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual;
import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; 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.model.geom.ModelLayers;
import net.minecraft.client.renderer.Sheets; import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.Material;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.core.SectionPos;
import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.block.ShulkerBoxBlock; import net.minecraft.world.level.block.ShulkerBoxBlock;
import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity; 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() private static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder()
.cutout(CutoutShaders.ONE_TENTH) .cutout(CutoutShaders.ONE_TENTH)
.light(LightShaders.SMOOTH)
.texture(Sheets.SHULKER_SHEET) .texture(Sheets.SHULKER_SHEET)
.mipmap(false) .mipmap(false)
.backfaceCulling(false) .backfaceCulling(false)
@ -72,7 +67,6 @@ public class ShulkerBoxVisual extends AbstractBlockEntityVisual<ShulkerBoxBlockE
.translate(getVisualPosition()) .translate(getVisualPosition())
.translate(0.5f) .translate(0.5f)
.scale(0.9995f) .scale(0.9995f)
.scale(11f)
.rotate(rotation) .rotate(rotation)
.scale(1, -1, -1) .scale(1, -1, -1)
.translateY(-1); .translateY(-1);
@ -126,26 +120,9 @@ public class ShulkerBoxVisual extends AbstractBlockEntityVisual<ShulkerBoxBlockE
stack.popPose(); 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 @Override
public void updateLight(float partialTick) { public void updateLight(float partialTick) {
// relight(base, lid); relight(base, lid);
} }
@Override @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; package dev.engine_room.flywheel.backend;
import org.jetbrains.annotations.UnknownNullability;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
public class FlwBackendXplatImpl implements FlwBackendXplat { public class FlwBackendXplatImpl implements FlwBackendXplat {
@UnknownNullability
public static BackendConfig CONFIG;
@Override @Override
public int getLightEmission(BlockState state, BlockGetter level, BlockPos pos) { public int getLightEmission(BlockState state, BlockGetter level, BlockPos pos) {
return state.getLightEmission(); return state.getLightEmission();
@ -12,6 +17,6 @@ public class FlwBackendXplatImpl implements FlwBackendXplat {
@Override @Override
public BackendConfig getConfig() { 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.Backend;
import dev.engine_room.flywheel.api.backend.BackendManager; 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.fabricmc.loader.api.FabricLoader;
import net.minecraft.ResourceLocationException; import net.minecraft.ResourceLocationException;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@ -35,12 +39,16 @@ public class FabricFlwConfig implements FlwConfig {
private final File file; private final File file;
public final FabricBackendConfig backendConfig = new FabricBackendConfig();
public Backend backend = BackendManager.defaultBackend(); public Backend backend = BackendManager.defaultBackend();
public boolean limitUpdates = LIMIT_UPDATES_DEFAULT; public boolean limitUpdates = LIMIT_UPDATES_DEFAULT;
public int workerThreads = WORKER_THREADS_DEFAULT; public int workerThreads = WORKER_THREADS_DEFAULT;
public FabricFlwConfig(File file) { public FabricFlwConfig(File file) {
this.file = file; this.file = file;
FlwBackendXplatImpl.CONFIG = backendConfig;
} }
@Override @Override
@ -90,6 +98,17 @@ public class FabricFlwConfig implements FlwConfig {
readBackend(object); readBackend(object);
readLimitUpdates(object); readLimitUpdates(object);
readWorkerThreads(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) { private void readBackend(JsonObject object) {
@ -158,6 +177,55 @@ public class FabricFlwConfig implements FlwConfig {
object.addProperty("backend", Backend.REGISTRY.getIdOrThrow(backend).toString()); object.addProperty("backend", Backend.REGISTRY.getIdOrThrow(backend).toString());
object.addProperty("limitUpdates", limitUpdates); object.addProperty("limitUpdates", limitUpdates);
object.addProperty("workerThreads", workerThreads); 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; 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.Backend;
import dev.engine_room.flywheel.api.backend.BackendManager; 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.LightSmoothnessArgument;
import dev.engine_room.flywheel.backend.compile.LightSmoothness; import dev.engine_room.flywheel.backend.compile.LightSmoothness;
import dev.engine_room.flywheel.backend.engine.uniform.DebugMode; import dev.engine_room.flywheel.backend.engine.uniform.DebugMode;
@ -129,12 +128,12 @@ public final class FlwCommands {
command.then(ClientCommandManager.literal("lightSmoothness") command.then(ClientCommandManager.literal("lightSmoothness")
.then(ClientCommandManager.argument("mode", LightSmoothnessArgument.INSTANCE) .then(ClientCommandManager.argument("mode", LightSmoothnessArgument.INSTANCE)
.executes(context -> { .executes(context -> {
var oldValue = FabricBackendConfig.INSTANCE.lightSmoothness; var oldValue = FabricFlwConfig.INSTANCE.backendConfig.lightSmoothness;
var newValue = context.getArgument("mode", LightSmoothness.class); var newValue = context.getArgument("mode", LightSmoothness.class);
if (oldValue != newValue) { if (oldValue != newValue) {
FabricBackendConfig.INSTANCE.lightSmoothness = newValue; FabricFlwConfig.INSTANCE.backendConfig.lightSmoothness = newValue;
FabricBackendConfig.INSTANCE.save(); FabricFlwConfig.INSTANCE.save();
Minecraft.getInstance() Minecraft.getInstance()
.reloadResourcePacks(); .reloadResourcePacks();
} }

View file

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

View file

@ -1,10 +1,15 @@
package dev.engine_room.flywheel.backend; package dev.engine_room.flywheel.backend;
import org.jetbrains.annotations.UnknownNullability;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
public class FlwBackendXplatImpl implements FlwBackendXplat { public class FlwBackendXplatImpl implements FlwBackendXplat {
@UnknownNullability
public static BackendConfig CONFIG;
@Override @Override
public int getLightEmission(BlockState state, BlockGetter level, BlockPos pos) { public int getLightEmission(BlockState state, BlockGetter level, BlockPos pos) {
return state.getLightEmission(level, pos); return state.getLightEmission(level, pos);
@ -12,6 +17,6 @@ public class FlwBackendXplatImpl implements FlwBackendXplat {
@Override @Override
public BackendConfig getConfig() { 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.Backend;
import dev.engine_room.flywheel.api.backend.BackendManager; 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.LightSmoothnessArgument;
import dev.engine_room.flywheel.backend.compile.LightSmoothness; import dev.engine_room.flywheel.backend.compile.LightSmoothness;
import dev.engine_room.flywheel.backend.engine.uniform.DebugMode; import dev.engine_room.flywheel.backend.engine.uniform.DebugMode;
@ -124,7 +123,7 @@ public final class FlwCommands {
return Command.SINGLE_SUCCESS; return Command.SINGLE_SUCCESS;
}))); })));
var lightSmoothnessValue = ForgeBackendConfig.INSTANCE.client.lightSmoothness; var lightSmoothnessValue = ForgeFlwConfig.INSTANCE.client.backendConfig.lightSmoothness;
command.then(Commands.literal("lightSmoothness") command.then(Commands.literal("lightSmoothness")
.then(Commands.argument("mode", LightSmoothnessArgument.INSTANCE) .then(Commands.argument("mode", LightSmoothnessArgument.INSTANCE)
.executes(context -> { .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.Flywheel;
import dev.engine_room.flywheel.api.event.EndClientResourceReloadEvent; import dev.engine_room.flywheel.api.event.EndClientResourceReloadEvent;
import dev.engine_room.flywheel.api.event.ReloadLevelRendererEvent; 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.LightSmoothnessArgument;
import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader; import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
@ -51,7 +50,6 @@ public final class FlywheelForge {
IEventBus forgeEventBus = NeoForge.EVENT_BUS; IEventBus forgeEventBus = NeoForge.EVENT_BUS;
ForgeFlwConfig.INSTANCE.registerSpecs(modContainer); ForgeFlwConfig.INSTANCE.registerSpecs(modContainer);
ForgeBackendConfig.INSTANCE.registerSpecs(modLoadingContext);
if (FMLLoader.getDist().isClient()) { if (FMLLoader.getDist().isClient()) {
Supplier<Runnable> toRun = () -> () -> FlywheelForge.clientInit(forgeEventBus, modEventBus); 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.Backend;
import dev.engine_room.flywheel.api.backend.BackendManager; 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.ResourceLocationException;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.neoforged.fml.ModContainer; import net.neoforged.fml.ModContainer;
@ -21,6 +24,8 @@ public class ForgeFlwConfig implements FlwConfig {
Pair<ClientConfig, ModConfigSpec> clientPair = new ModConfigSpec.Builder().configure(ClientConfig::new); Pair<ClientConfig, ModConfigSpec> clientPair = new ModConfigSpec.Builder().configure(ClientConfig::new);
this.client = clientPair.getLeft(); this.client = clientPair.getLeft();
clientSpec = clientPair.getRight(); clientSpec = clientPair.getRight();
FlwBackendXplatImpl.CONFIG = client.backendConfig;
} }
@Override @Override
@ -72,6 +77,8 @@ public class ForgeFlwConfig implements FlwConfig {
public final ModConfigSpec.BooleanValue limitUpdates; public final ModConfigSpec.BooleanValue limitUpdates;
public final ModConfigSpec.IntValue workerThreads; public final ModConfigSpec.IntValue workerThreads;
public final ForgeBackendConfig backendConfig;
private ClientConfig(ModConfigSpec.Builder builder) { private ClientConfig(ModConfigSpec.Builder builder) {
backend = builder.comment("Select the backend to use.") backend = builder.comment("Select the backend to use.")
.define("backend", Backend.REGISTRY.getIdOrThrow(BackendManager.defaultBackend()).toString()); .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.") 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() .defineInRange("workerThreads", -1, -1, Runtime.getRuntime()
.availableProcessors()); .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();
} }
} }
} }