Back-and better than ever

- Move BackendType to backend package
 - BackendType is now an interface whose implementors must be registered
This commit is contained in:
Jozufozu 2022-08-22 22:17:36 -07:00
parent 4bb7c4bd48
commit 6436f0e9ba
10 changed files with 260 additions and 101 deletions

View file

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

View file

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

View file

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

View file

@ -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<Engine> engineSupplier;
private final Supplier<BackendType> fallback;
private final BooleanSupplier isSupported;
public SimpleBackendType(String properName, String shortName, Component engineMessage, Supplier<Engine> engineSupplier, Supplier<BackendType> 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<Engine> engineSupplier;
private Supplier<BackendType> 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<Engine> engineSupplier) {
this.engineSupplier = engineSupplier;
return this;
}
public Builder setFallback(Supplier<BackendType> 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));
}
}
}

View file

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

View file

@ -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<String, BackendType> 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<String> validNames() {
return lookup.keySet();
}
}

View file

@ -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<BackendType> {
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 <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
return SharedSuggestionProvider.suggest(BackendType.validNames(), builder);
return SharedSuggestionProvider.suggest(BackendTypes.validNames(), builder);
}
@Override
public Collection<String> getExamples() {
return BackendType.validNames();
return BackendTypes.validNames();
}
public static BackendTypeArgument getInstance() {

View file

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

View file

@ -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<BackendType> backend;
public final ForgeConfigSpec.ConfigValue<String> 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);

View file

@ -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<String, BackendType> 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<String> 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
}
}