diff --git a/src/main/java/com/jozufozu/flywheel/Flywheel.java b/src/main/java/com/jozufozu/flywheel/Flywheel.java index 27bb51e40..2f347b16b 100644 --- a/src/main/java/com/jozufozu/flywheel/Flywheel.java +++ b/src/main/java/com/jozufozu/flywheel/Flywheel.java @@ -12,6 +12,7 @@ import com.jozufozu.flywheel.backend.instancing.batching.DrawBuffer; import com.jozufozu.flywheel.config.BackendTypeArgument; import com.jozufozu.flywheel.config.FlwCommands; import com.jozufozu.flywheel.config.FlwConfig; +import com.jozufozu.flywheel.core.BackendTypes; import com.jozufozu.flywheel.core.Components; import com.jozufozu.flywheel.core.DebugRender; import com.jozufozu.flywheel.core.PartialModel; @@ -74,6 +75,7 @@ public class Flywheel { CrashReportCallables.registerCrashCallable("Flywheel Backend", Backend::getBackendDescriptor); ShadersModHandler.init(); + BackendTypes.init(); Backend.init(); forgeEventBus.addListener(FlwCommands::registerClientCommands); diff --git a/src/main/java/com/jozufozu/flywheel/backend/Backend.java b/src/main/java/com/jozufozu/flywheel/backend/Backend.java index b48077615..62ed1f2c7 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/Backend.java +++ b/src/main/java/com/jozufozu/flywheel/backend/Backend.java @@ -4,10 +4,9 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import com.jozufozu.flywheel.api.FlywheelWorld; -import com.jozufozu.flywheel.backend.gl.versioned.GlCompat; import com.jozufozu.flywheel.backend.instancing.ParallelTaskEngine; -import com.jozufozu.flywheel.config.BackendType; import com.jozufozu.flywheel.config.FlwConfig; +import com.jozufozu.flywheel.core.BackendTypes; import com.mojang.logging.LogUtils; import net.minecraft.client.Minecraft; @@ -58,7 +57,7 @@ public class Backend { } public static boolean isOn() { - return TYPE != BackendType.OFF; + return TYPE != BackendTypes.OFF; } public static boolean canUseInstancing(@Nullable Level world) { @@ -87,18 +86,15 @@ public class Backend { } private static BackendType chooseEngine() { - BackendType preferredChoice = FlwConfig.get() + var preferred = FlwConfig.get() .getBackendType(); + var actual = preferred.findFallback(); - boolean usingShaders = ShadersModHandler.isShaderPackInUse(); - boolean canUseEngine = switch (preferredChoice) { - case OFF -> true; - case BATCHING -> !usingShaders; - case INSTANCING -> !usingShaders && GlCompat.getInstance().instancedArraysSupported(); - case INDIRECT -> !usingShaders && GlCompat.getInstance().supportsIndirect(); - }; + if (preferred != actual) { + LOGGER.warn("Flywheel backend fell back from '{}' to '{}'", preferred.getShortName(), actual.getShortName()); + } - return canUseEngine ? preferredChoice : BackendType.OFF; + return actual; } public static void init() { diff --git a/src/main/java/com/jozufozu/flywheel/backend/BackendType.java b/src/main/java/com/jozufozu/flywheel/backend/BackendType.java new file mode 100644 index 000000000..d7124a3d9 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/BackendType.java @@ -0,0 +1,20 @@ +package com.jozufozu.flywheel.backend; + +import com.jozufozu.flywheel.backend.instancing.Engine; + +import net.minecraft.network.chat.Component; + +public interface BackendType { + + String getProperName(); + + String getShortName(); + + Component getEngineMessage(); + + Engine createEngine(); + + BackendType findFallback(); + + boolean supported(); +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/SimpleBackendType.java b/src/main/java/com/jozufozu/flywheel/backend/SimpleBackendType.java new file mode 100644 index 000000000..6f93b845a --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/SimpleBackendType.java @@ -0,0 +1,111 @@ +package com.jozufozu.flywheel.backend; + +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +import com.jozufozu.flywheel.backend.instancing.Engine; +import com.jozufozu.flywheel.core.BackendTypes; + +import net.minecraft.network.chat.Component; + +public class SimpleBackendType implements BackendType { + + + private final String properName; + private final String shortName; + private final Component engineMessage; + private final Supplier engineSupplier; + private final Supplier fallback; + private final BooleanSupplier isSupported; + + public SimpleBackendType(String properName, String shortName, Component engineMessage, Supplier engineSupplier, Supplier fallback, BooleanSupplier isSupported) { + this.properName = properName; + this.shortName = shortName; + this.engineMessage = engineMessage; + this.engineSupplier = engineSupplier; + this.fallback = fallback; + this.isSupported = isSupported; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public String getProperName() { + return properName; + } + + @Override + public String getShortName() { + return shortName; + } + + @Override + public Component getEngineMessage() { + return engineMessage; + } + + @Override + public Engine createEngine() { + return engineSupplier.get(); + } + + @Override + public BackendType findFallback() { + if (this.supported()) { + return this; + } else { + return fallback.get() + .findFallback(); + } + } + + @Override + public boolean supported() { + return isSupported.getAsBoolean(); + } + + public static class Builder { + private String properName; + private String shortName; + private Component engineMessage; + private Supplier engineSupplier; + private Supplier fallback; + private BooleanSupplier booleanSupplier; + + public Builder setProperName(String properName) { + this.properName = properName; + return this; + } + + public Builder setShortName(String shortName) { + this.shortName = shortName; + return this; + } + + public Builder setEngineMessage(Component engineMessage) { + this.engineMessage = engineMessage; + return this; + } + + public Builder setEngineSupplier(Supplier engineSupplier) { + this.engineSupplier = engineSupplier; + return this; + } + + public Builder setFallback(Supplier fallback) { + this.fallback = fallback; + return this; + } + + public Builder supported(BooleanSupplier booleanSupplier) { + this.booleanSupplier = booleanSupplier; + return this; + } + + public BackendType register() { + return BackendTypes.register(new SimpleBackendType(properName, shortName, engineMessage, engineSupplier, fallback, booleanSupplier)); + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java index 387cb98bf..69ac4e759 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java @@ -4,14 +4,10 @@ import com.jozufozu.flywheel.api.RenderStage; import com.jozufozu.flywheel.api.instance.DynamicInstance; import com.jozufozu.flywheel.api.instance.TickableInstance; import com.jozufozu.flywheel.backend.Backend; -import com.jozufozu.flywheel.backend.instancing.batching.BatchingEngine; import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager; import com.jozufozu.flywheel.backend.instancing.effect.Effect; import com.jozufozu.flywheel.backend.instancing.effect.EffectInstanceManager; import com.jozufozu.flywheel.backend.instancing.entity.EntityInstanceManager; -import com.jozufozu.flywheel.backend.instancing.indirect.IndirectEngine; -import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine; -import com.jozufozu.flywheel.core.Components; import com.jozufozu.flywheel.core.RenderContext; import com.jozufozu.flywheel.event.BeginFrameEvent; import com.jozufozu.flywheel.extension.ClientLevelExtension; @@ -37,12 +33,8 @@ public class InstanceWorld implements AutoCloseable { private final InstanceManager effects; public static InstanceWorld create(LevelAccessor level) { - var engine = switch (Backend.getBackendType()) { - case INDIRECT -> new IndirectEngine(Components.WORLD); - case INSTANCING -> new InstancingEngine(Components.WORLD, 100 * 100); - case BATCHING -> new BatchingEngine(); - case OFF -> throw new IllegalStateException("Cannot create instance world when backend is off."); - }; + var engine = Backend.getBackendType() + .createEngine(); var entities = new EntityInstanceManager(engine); var blockEntities = new BlockEntityInstanceManager(engine); diff --git a/src/main/java/com/jozufozu/flywheel/config/BackendType.java b/src/main/java/com/jozufozu/flywheel/config/BackendType.java deleted file mode 100644 index a64da9ddd..000000000 --- a/src/main/java/com/jozufozu/flywheel/config/BackendType.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.jozufozu.flywheel.config; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -import org.jetbrains.annotations.Nullable; - -public enum BackendType { - OFF("Off"), - - /** - * Use a thread pool to buffer instances in parallel on the CPU. - */ - BATCHING("Parallel Batching"), - - /** - * Use GPU instancing to render everything. - */ - INSTANCING("GL33 Instanced Arrays"), - - /** - * Use Compute shaders to cull instances. - */ - INDIRECT("GL46 Compute Culling"), - ; - - private static final Map lookup; - - static { - lookup = new HashMap<>(); - for (BackendType value : values()) { - lookup.put(value.getShortName(), value); - } - } - - private final String properName; - private final String shortName; - - BackendType(String properName) { - this.properName = properName; - shortName = name().toLowerCase(Locale.ROOT); - } - - public String getProperName() { - return properName; - } - - public String getShortName() { - return shortName; - } - - @Nullable - public static BackendType byName(String name) { - return lookup.get(name); - } - - public static Collection validNames() { - return lookup.keySet(); - } -} diff --git a/src/main/java/com/jozufozu/flywheel/config/BackendTypeArgument.java b/src/main/java/com/jozufozu/flywheel/config/BackendTypeArgument.java index 7f2545a3a..90bc069f1 100644 --- a/src/main/java/com/jozufozu/flywheel/config/BackendTypeArgument.java +++ b/src/main/java/com/jozufozu/flywheel/config/BackendTypeArgument.java @@ -3,6 +3,8 @@ package com.jozufozu.flywheel.config; import java.util.Collection; import java.util.concurrent.CompletableFuture; +import com.jozufozu.flywheel.backend.BackendType; +import com.jozufozu.flywheel.core.BackendTypes; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; @@ -26,22 +28,22 @@ public enum BackendTypeArgument implements ArgumentType { public BackendType parse(StringReader reader) throws CommandSyntaxException { String string = reader.readUnquotedString(); - BackendType engine = BackendType.byName(string); + BackendType engine = BackendTypes.getBackendType(string); if (engine == null) { - throw INVALID.createWithContext(reader, string, BackendType.validNames()); + throw INVALID.createWithContext(reader, string, BackendTypes.validNames()); } return engine; } public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { - return SharedSuggestionProvider.suggest(BackendType.validNames(), builder); + return SharedSuggestionProvider.suggest(BackendTypes.validNames(), builder); } @Override public Collection getExamples() { - return BackendType.validNames(); + return BackendTypes.validNames(); } public static BackendTypeArgument getInstance() { diff --git a/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java b/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java index 5fb8a5032..b66a6a707 100644 --- a/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java +++ b/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java @@ -5,6 +5,9 @@ import java.util.function.BiConsumer; import org.jetbrains.annotations.NotNull; import com.jozufozu.flywheel.backend.Backend; +import com.jozufozu.flywheel.backend.BackendType; +import com.jozufozu.flywheel.backend.SimpleBackendType; +import com.jozufozu.flywheel.core.BackendTypes; import com.jozufozu.flywheel.core.uniform.FrustumProvider; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; @@ -36,7 +39,8 @@ public class FlwCommands { .executes(context -> { LocalPlayer player = Minecraft.getInstance().player; if (player != null) { - player.displayClientMessage(getEngineMessage(value.get()), false); + player.displayClientMessage(BackendTypes.getBackendType(value.get()) + .getEngineMessage(), false); } return Command.SINGLE_SUCCESS; }) @@ -44,8 +48,8 @@ public class FlwCommands { .executes(context -> { LocalPlayer player = Minecraft.getInstance().player; if (player != null) { - BackendType type = context.getArgument("type", BackendType.class); - value.set(type); + BackendType type = context.getArgument("type", SimpleBackendType.class); + value.set(type.getShortName()); Component message = getEngineMessage(type); player.displayClientMessage(message, false); @@ -155,12 +159,7 @@ public class FlwCommands { } public static Component getEngineMessage(@NotNull BackendType type) { - return switch (type) { - case OFF -> new TextComponent("Disabled Flywheel").withStyle(ChatFormatting.RED); - case INSTANCING -> new TextComponent("Using Instancing Engine").withStyle(ChatFormatting.GREEN); - case BATCHING -> new TextComponent("Using Batching Engine").withStyle(ChatFormatting.GREEN); - case INDIRECT -> new TextComponent("Using Indirect Engine").withStyle(ChatFormatting.GREEN); - }; + return type.getEngineMessage(); } public static class ConfigCommandBuilder { diff --git a/src/main/java/com/jozufozu/flywheel/config/FlwConfig.java b/src/main/java/com/jozufozu/flywheel/config/FlwConfig.java index 1872131f7..2cc5f18df 100644 --- a/src/main/java/com/jozufozu/flywheel/config/FlwConfig.java +++ b/src/main/java/com/jozufozu/flywheel/config/FlwConfig.java @@ -2,9 +2,11 @@ package com.jozufozu.flywheel.config; import org.apache.commons.lang3.tuple.Pair; +import com.jozufozu.flywheel.backend.BackendType; +import com.jozufozu.flywheel.core.BackendTypes; + import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.common.ForgeConfigSpec.BooleanValue; -import net.minecraftforge.common.ForgeConfigSpec.EnumValue; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.config.ModConfig; @@ -28,7 +30,7 @@ public class FlwConfig { } public BackendType getBackendType() { - return client.backend.get(); + return BackendTypes.getBackendType(client.backend.get()); } public boolean debugNormals() { @@ -43,13 +45,14 @@ public class FlwConfig { } public static class ClientConfig { - public final EnumValue backend; + public final ForgeConfigSpec.ConfigValue backend; public final BooleanValue debugNormals; public final BooleanValue limitUpdates; public ClientConfig(ForgeConfigSpec.Builder builder) { backend = builder.comment("Select the backend to use.") - .defineEnum("backend", BackendType.INSTANCING); + .define("backend", BackendTypes.defaultForCurrentPC() + .getShortName()); debugNormals = builder.comment("Enable or disable a debug overlay that colors pixels by their normal.") .define("debugNormals", false); diff --git a/src/main/java/com/jozufozu/flywheel/core/BackendTypes.java b/src/main/java/com/jozufozu/flywheel/core/BackendTypes.java new file mode 100644 index 000000000..5ea394179 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/core/BackendTypes.java @@ -0,0 +1,96 @@ +package com.jozufozu.flywheel.core; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import com.jozufozu.flywheel.backend.BackendType; +import com.jozufozu.flywheel.backend.ShadersModHandler; +import com.jozufozu.flywheel.backend.SimpleBackendType; +import com.jozufozu.flywheel.backend.gl.versioned.GlCompat; +import com.jozufozu.flywheel.backend.instancing.batching.BatchingEngine; +import com.jozufozu.flywheel.backend.instancing.indirect.IndirectEngine; +import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine; + +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.TextComponent; + +public class BackendTypes { + public static final Map BACKEND_TYPES = new HashMap<>(); + + public static BackendType register(BackendType type) { + BACKEND_TYPES.put(type.getShortName(), type); + return type; + } + + public static final BackendType OFF = SimpleBackendType.builder() + .setProperName("Off") + .setShortName("off") + .setEngineMessage(new TextComponent("Disabled Flywheel").withStyle(ChatFormatting.RED)) + .setEngineSupplier(() -> { + throw new IllegalStateException("Cannot create engine when backend is off."); + }) + .setFallback(() -> BackendTypes.OFF) + .supported(() -> true) + .register(); + + public static BackendType defaultForCurrentPC() { + // TODO: Automatically select the best default config based on the user's driver + return INDIRECT; + } + + /** + * Use a thread pool to buffer instances in parallel on the CPU. + */ + public static final BackendType BATCHING = SimpleBackendType.builder() + .setProperName("Parallel Batching") + .setShortName("batching") + .setEngineMessage(new TextComponent("Using Batching Engine").withStyle(ChatFormatting.GREEN)) + .setEngineSupplier(BatchingEngine::new) + .setFallback(() -> BackendTypes.OFF) + .supported(() -> !ShadersModHandler.isShaderPackInUse()) + .register(); + + @Nullable + public static BackendType getBackendType(String name) { + return BACKEND_TYPES.get(name); + } + + /** + * Use GPU instancing to render everything. + */ + public static final BackendType INSTANCING = SimpleBackendType.builder() + .setProperName("GL33 Instanced Arrays") + .setShortName("instancing") + .setEngineMessage(new TextComponent("Using Instancing Engine").withStyle(ChatFormatting.GREEN)) + .setEngineSupplier(() -> new InstancingEngine(Components.WORLD, 100 * 100)) + .setFallback(() -> BackendTypes.BATCHING) + .supported(() -> !ShadersModHandler.isShaderPackInUse() && GlCompat.getInstance() + .instancedArraysSupported()) + .register(); + + public static Collection validNames() { + return BACKEND_TYPES.keySet(); + } + + /** + * Use Compute shaders to cull instances. + */ + public static final BackendType INDIRECT = SimpleBackendType.builder() + .setProperName("GL46 Compute Culling") + .setShortName("indirect") + .setEngineMessage(new TextComponent("Using Indirect Engine").withStyle(ChatFormatting.GREEN)) + .setEngineSupplier(() -> new IndirectEngine(Components.WORLD)) + .setFallback(() -> BackendTypes.INSTANCING) + .supported(() -> !ShadersModHandler.isShaderPackInUse() && GlCompat.getInstance() + .supportsIndirect()) + .register(); + + public static void init() { + // noop + } + + +}