Better config/backend to account for multiple engines

- Rename probably too many things
 - Needs to be tested with Optifine
This commit is contained in:
Jozufozu 2021-12-22 21:35:48 -08:00
parent a1bf03689c
commit d78f030264
18 changed files with 195 additions and 168 deletions

View file

@ -3,10 +3,12 @@ package com.jozufozu.flywheel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.jozufozu.flywheel.config.EngineArgument;
import com.jozufozu.flywheel.config.FlwCommands;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.config.FlwPackets;
import net.minecraft.commands.synchronization.ArgumentTypes;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge;
@ -39,5 +41,6 @@ public class Flywheel {
private void setup(final FMLCommonSetupEvent event) {
FlwPackets.registerPackets();
ArgumentTypes.register("flywheel:engine", EngineArgument.class, EngineArgument.SERIALIZER);
}
}

View file

@ -17,13 +17,11 @@ import com.jozufozu.flywheel.api.FlywheelWorld;
import com.jozufozu.flywheel.api.InstanceData;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.config.FlwEngine;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
@ -32,20 +30,18 @@ public class Backend {
public static final Logger log = LogManager.getLogger(Backend.class);
protected static final Backend INSTANCE = new Backend();
private FlwEngine engine;
public static Backend getInstance() {
return INSTANCE;
}
public final Loader loader;
private FlwEngine engine;
public GLCapabilities capabilities;
public GlCompat compat;
private boolean instancedArrays;
private boolean enabled;
public final Loader loader;
private final List<ShaderContext<?>> contexts = new ArrayList<>();
private final Map<ResourceLocation, StructType<?>> materialRegistry = new HashMap<>();
private final Map<ResourceLocation, ProgramSpec> programSpecRegistry = new HashMap<>();
@ -61,16 +57,11 @@ public class Backend {
* (Meshlet, MDI, GL31 Draw Instanced are planned), this will name which one is in use.
*/
public String getBackendDescriptor() {
if (enabled) {
ClientLevel level = Minecraft.getInstance().level;
return engine.getProperName();
}
if (level == null) {
return "Invalid";
}
return InstancedRenderDispatcher.getEngineName(level);
}
return "Disabled";
public FlwEngine getEngine() {
return engine;
}
/**
@ -111,41 +102,22 @@ public class Backend {
return programSpecRegistry.get(name);
}
public boolean available() {
return canUseVBOs();
}
public boolean canUseInstancing() {
return enabled && instancedArrays;
}
public boolean canUseVBOs() {
return enabled && gl20();
}
public boolean gl33() {
return capabilities.OpenGL33;
}
public boolean gl20() {
return capabilities.OpenGL20;
}
public void refresh() {
OptifineHandler.refresh();
boolean usingShaders = OptifineHandler.usingShaders();
capabilities = GL.createCapabilities();
compat = new GlCompat(capabilities);
instancedArrays = compat.instancedArraysSupported();
engine = FlwConfig.get()
.getEngine();
FlwConfig config = FlwConfig.get();
enabled = config.enabled() && !OptifineHandler.usingShaders();
engine = config.client.engine.get();
}
public boolean canUseInstancing(@Nullable Level world) {
return canUseInstancing() && isFlywheelWorld(world);
enabled = switch (engine) {
case OFF -> false;
case BATCHING -> true;
case INSTANCING -> !usingShaders && compat.instancedArraysSupported();
};
}
public Collection<StructType<?>> allMaterials() {
@ -160,6 +132,14 @@ public class Backend {
return contexts;
}
public static boolean isOn() {
return getInstance().enabled;
}
public static boolean canUseInstancing(@Nullable Level world) {
return isOn() && isFlywheelWorld(world);
}
/**
* Used to avoid calling Flywheel functions on (fake) worlds that don't specifically support it.
*/
@ -184,7 +164,7 @@ public class Backend {
/**
* INTERNAL USE ONLY
*/
public void _clearContexts() {
void _clearContexts() {
GameStateRegistry.clear();
programSpecRegistry.clear();
contexts.forEach(ShaderContext::delete);
@ -194,8 +174,4 @@ public class Backend {
public static void init() {
}
public FlwEngine getEngine() {
return engine;
}
}

View file

@ -64,36 +64,34 @@ public class Loader implements ResourceManagerReloadListener {
public void onResourceManagerReload(ResourceManager manager) {
backend.refresh();
if (backend.gl20()) {
shouldCrash = false;
backend._clearContexts();
shouldCrash = false;
backend._clearContexts();
Resolver.INSTANCE.invalidate();
ModLoader.get()
.postEvent(new GatherContextEvent(backend, firstLoad));
Resolver.INSTANCE.invalidate();
ModLoader.get()
.postEvent(new GatherContextEvent(backend, firstLoad));
ShaderSources sources = new ShaderSources(manager);
ShaderSources sources = new ShaderSources(manager);
loadProgramSpecs(manager);
loadProgramSpecs(manager);
Resolver.INSTANCE.resolve(sources);
Resolver.INSTANCE.resolve(sources);
for (ShaderContext<?> context : backend.allContexts()) {
context.load();
}
for (ShaderContext<?> context : backend.allContexts()) {
context.load();
}
if (shouldCrash) {
throw new ShaderLoadingException("Could not load all shaders, see log for details");
}
if (shouldCrash) {
throw new ShaderLoadingException("Could not load all shaders, see log for details");
}
Backend.log.info("Loaded all shader programs.");
Backend.log.info("Loaded all shader programs.");
ClientLevel world = Minecraft.getInstance().level;
if (Backend.isFlywheelWorld(world)) {
// TODO: looks like it might be good to have another event here
InstancedRenderDispatcher.resetInstanceWorld(world);
CrumblingRenderer.reset();
}
ClientLevel world = Minecraft.getInstance().level;
if (Backend.isFlywheelWorld(world)) {
// TODO: looks like it might be good to have another event here
InstancedRenderDispatcher.resetInstanceWorld(world);
CrumblingRenderer.reset();
}
firstLoad = false;

View file

@ -154,8 +154,7 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
}
public void add(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
if (!Backend.isOn()) return;
if (canInstance(obj)) {
addInternal(obj);
@ -163,8 +162,7 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
}
public void queueAdd(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
if (!Backend.isOn()) return;
synchronized (queuedAdditions) {
queuedAdditions.add(obj);
@ -172,8 +170,7 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
}
public void queueUpdate(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
if (!Backend.isOn()) return;
synchronized (queuedUpdates) {
queuedUpdates.add(obj);
}
@ -191,8 +188,7 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
* @param obj the object to update.
*/
public void update(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
if (!Backend.isOn()) return;
if (canInstance(obj)) {
AbstractInstance instance = getInstance(obj);
@ -213,8 +209,7 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
}
public void remove(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
if (!Backend.isOn()) return;
if (canInstance(obj)) {
AbstractInstance instance = getInstance(obj);
@ -231,8 +226,7 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
@Nullable
protected <I extends T> AbstractInstance getInstance(I obj) {
if (!Backend.getInstance()
.canUseInstancing()) return null;
if (!Backend.isOn()) return null;
return instances.get(obj);
}
@ -291,8 +285,7 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
}
protected void addInternal(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
if (!Backend.isOn()) return;
AbstractInstance instance = instances.get(obj);

View file

@ -40,7 +40,7 @@ public class InstanceWorld {
.getEngine();
switch (engine) {
case GL33 -> {
case INSTANCING -> {
InstancingEngine<WorldProgram> manager = InstancingEngine.builder(Contexts.WORLD)
.build(this.taskEngine);

View file

@ -41,10 +41,6 @@ public class InstancedRenderDispatcher {
getEntities(entity.level).queueUpdate(entity);
}
public static String getEngineName(LevelAccessor world) {
return instanceWorlds.get(world).engine.getName();
}
public static InstanceManager<BlockEntity> getTiles(LevelAccessor world) {
return instanceWorlds.get(world)
.getTileEntityInstanceManager();
@ -81,8 +77,7 @@ public class InstancedRenderDispatcher {
if (event.layer == null) return;
ClientLevel world = event.getWorld();
if (!Backend.getInstance()
.canUseInstancing(world)) return;
if (!Backend.canUseInstancing(world)) return;
instanceWorlds.get(world).renderLayer(event);
}
@ -90,8 +85,7 @@ public class InstancedRenderDispatcher {
@SubscribeEvent
public static void onReloadRenderers(ReloadRenderersEvent event) {
ClientLevel world = event.getWorld();
if (Backend.getInstance()
.canUseInstancing() && world != null) {
if (Backend.isOn() && world != null) {
resetInstanceWorld(world);
}
}

View file

@ -17,7 +17,6 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
public enum BooleanConfig {
ENGINE(() -> BooleanConfig::enabled),
NORMAL_OVERLAY(() -> BooleanConfig::normalOverlay),
;
@ -52,29 +51,6 @@ public enum BooleanConfig {
return null;
}
@OnlyIn(Dist.CLIENT)
private static void enabled(BooleanDirective state) {
LocalPlayer player = Minecraft.getInstance().player;
if (player == null || state == null) return;
if (state == BooleanDirective.DISPLAY) {
Component text = new TextComponent("Flywheel renderer is currently: ").append(boolToText(FlwConfig.get().enabled()));
player.displayClientMessage(text, false);
return;
}
boolean enabled = state.get();
boolean cannotUse = OptifineHandler.usingShaders() && enabled;
FlwConfig.get().client.enabled.set(enabled);
Component text = boolToText(FlwConfig.get().enabled()).append(new TextComponent(" Flywheel renderer").withStyle(ChatFormatting.WHITE));
Component error = new TextComponent("Flywheel renderer does not support Optifine Shaders").withStyle(ChatFormatting.RED);
player.displayClientMessage(cannotUse ? error : text, false);
Backend.reloadWorldRenderers();
}
@OnlyIn(Dist.CLIENT)
private static void normalOverlay(BooleanDirective state) {
LocalPlayer player = Minecraft.getInstance().player;

View file

@ -0,0 +1,69 @@
package com.jozufozu.flywheel.config;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import com.google.gson.JsonObject;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.commands.synchronization.ArgumentSerializer;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.TranslatableComponent;
public class EngineArgument implements ArgumentType<FlwEngine> {
public static final EngineArgument INSTANCE = new EngineArgument();
public static final Serializer SERIALIZER = new Serializer();
private static final Dynamic2CommandExceptionType INVALID = new Dynamic2CommandExceptionType((found, constants) -> {
return new TranslatableComponent("commands.forge.arguments.enum.invalid", constants, found);
});
private EngineArgument() {
}
@Override
public FlwEngine parse(StringReader reader) throws CommandSyntaxException {
String string = reader.readUnquotedString();
FlwEngine engine = FlwEngine.byName(string);
if (engine == null) {
throw INVALID.createWithContext(reader, string, FlwEngine.validNames());
}
return engine;
}
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
return SharedSuggestionProvider.suggest(FlwEngine.validNames(), builder);
}
@Override
public Collection<String> getExamples() {
return FlwEngine.validNames();
}
public static class Serializer implements ArgumentSerializer<EngineArgument> {
private Serializer() {
}
public void serializeToNetwork(EngineArgument argument, FriendlyByteBuf buffer) {
}
public EngineArgument deserializeFromNetwork(FriendlyByteBuf buffer) {
return INSTANCE;
}
public void serializeToJson(EngineArgument argument, JsonObject json) {
}
}
}

View file

@ -10,15 +10,15 @@ import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.server.command.EnumArgument;
public class EngineConfigCommand {
public ArgumentBuilder<CommandSourceStack, ?> register() {
return Commands.literal("engine")
public static ArgumentBuilder<CommandSourceStack, ?> register() {
return Commands.literal("backend")
.executes(context -> {
ServerPlayer player = context.getSource()
.getPlayerOrException();
FlwPackets.channel.send(PacketDistributor.PLAYER.with(() -> player), new SConfigureEnginePacket());
return Command.SINGLE_SUCCESS;
})
.then(Commands.argument("type", EnumArgument.enumArgument(FlwEngine.class))
.then(Commands.argument("type", EngineArgument.INSTANCE)
.executes(context -> {
FlwEngine type = context.getArgument("type", FlwEngine.class);

View file

@ -15,9 +15,8 @@ public class FlwCommands {
.getDispatcher();
dispatcher.register(Commands.literal("flywheel")
.then(new BooleanConfigCommand("backend", BooleanConfig.ENGINE).register())
.then(new BooleanConfigCommand("debugNormals", BooleanConfig.NORMAL_OVERLAY).register())
.then(new EngineConfigCommand().register())
.then(EngineConfigCommand.register())
);
}
}

View file

@ -26,8 +26,8 @@ public class FlwConfig {
return INSTANCE;
}
public boolean enabled() {
return client.enabled.get();
public FlwEngine getEngine() {
return client.engine.get();
}
public boolean debugNormals() {
@ -38,17 +38,13 @@ public class FlwConfig {
}
public static class ClientConfig {
public final BooleanValue enabled;
public final ForgeConfigSpec.EnumValue<FlwEngine> engine;
public final BooleanValue debugNormals;
public ClientConfig(ForgeConfigSpec.Builder builder) {
enabled = builder.comment("Enable or disable the entire engine")
.define("enabled", true);
engine = builder.comment("Enable or disable the entire engine")
.defineEnum("backend", FlwEngine.GL33);
.defineEnum("backend", FlwEngine.INSTANCING);
debugNormals = builder.comment("Enable or disable a debug overlay that colors pixels by their normal")
.define("debugNormals", false);

View file

@ -1,9 +1,12 @@
package com.jozufozu.flywheel.config;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.OptifineHandler;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
@ -13,15 +16,50 @@ import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
public enum FlwEngine {
BATCHING(new TextComponent("Batching").withStyle(ChatFormatting.BLUE)),
GL33(new TextComponent("GL 3.3 Instanced Arrays").withStyle(ChatFormatting.GREEN)),
OFF("off", "Off", new TextComponent("Disabled Flywheel").withStyle(ChatFormatting.RED)),
BATCHING("batching", "Parallel Batching", new TextComponent("Using Batching Engine").withStyle(ChatFormatting.GREEN)),
INSTANCING("instancing", "GL33 Instanced Arrays", new TextComponent("Using Instancing Engine").withStyle(ChatFormatting.GREEN)),
;
private final Component name;
private static final Map<String, FlwEngine> lookup;
FlwEngine(Component name) {
this.name = name;
static {
lookup = new HashMap<>();
for (FlwEngine value : values()) {
lookup.put(value.shortName, value);
}
}
private final Component message;
private final String shortName;
private final String properName;
FlwEngine(String shortName, String properName, Component message) {
this.shortName = shortName;
this.properName = properName;
this.message = message;
}
public String getProperName() {
return properName;
}
public void encode(FriendlyByteBuf buffer) {
buffer.writeByte(this.ordinal());
}
public static void handle(@Nullable FlwEngine type) {
LocalPlayer player = Minecraft.getInstance().player;
if (player == null) return;
if (type != null) {
FlwConfig.get().client.engine.set(type);
player.displayClientMessage(type.message, false);
Backend.reloadWorldRenderers();
} else {
player.displayClientMessage(FlwConfig.get().getEngine().message, false);
}
}
@Nullable
@ -33,25 +71,12 @@ public enum FlwEngine {
return values()[b];
}
public void encode(FriendlyByteBuf buffer) {
buffer.writeByte(this.ordinal());
@Nullable
public static FlwEngine byName(String name) {
return lookup.get(name);
}
public void switchTo() {
LocalPlayer player = Minecraft.getInstance().player;
if (player == null) return;
// if (state == BooleanDirective.DISPLAY) {
// Component text = new TextComponent("Flywheel renderer is currently: ").append(boolToText(FlwConfig.get().enabled()));
// player.displayClientMessage(text, false);
// return;
// }
FlwConfig.get().client.engine.set(this);
Component text = new TextComponent("Using ").withStyle(ChatFormatting.WHITE).append(name);
player.displayClientMessage(text, false);
Backend.reloadWorldRenderers();
public static Collection<String> validNames() {
return lookup.keySet();
}
}

View file

@ -29,9 +29,7 @@ public class SConfigureEnginePacket {
}
public void execute(Supplier<NetworkEvent.Context> ctx) {
if (type != null) {
type.switchTo();
}
FlwEngine.handle(type);
ctx.get()
.setPacketHandled(true);
}

View file

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

View file

@ -54,8 +54,7 @@ public class CrumblingRenderer {
}
public static void renderBreaking(RenderLayerEvent event) {
if (!Backend.getInstance()
.canUseInstancing(event.getWorld())) return;
if (!Backend.canUseInstancing(event.getWorld())) return;
Int2ObjectMap<List<BlockEntity>> activeStages = getActiveStageTiles(event.getWorld());
@ -120,8 +119,7 @@ public class CrumblingRenderer {
@SubscribeEvent
public static void onReloadRenderers(ReloadRenderersEvent event) {
ClientLevel world = event.getWorld();
if (Backend.getInstance()
.canUseInstancing() && world != null) {
if (Backend.isOn() && world != null) {
reset();
}
}

View file

@ -23,8 +23,7 @@ public class CancelEntityRenderMixin {
@Redirect(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientLevel;entitiesForRendering()Ljava/lang/Iterable;"))
private Iterable<Entity> filterEntities(ClientLevel world) {
Iterable<Entity> entities = world.entitiesForRendering();
if (Backend.getInstance()
.canUseInstancing()) {
if (Backend.isOn()) {
ArrayList<Entity> filtered = Lists.newArrayList(entities);

View file

@ -23,7 +23,7 @@ public class ChunkRebuildHooksMixin {
@Inject(method = "handleBlockEntity", at = @At("HEAD"), cancellable = true)
private <E extends BlockEntity> void addAndFilterBEs(ChunkRenderDispatcher.CompiledChunk compiledChunk, Set<BlockEntity> set, E be, CallbackInfo ci) {
if (Backend.getInstance().canUseInstancing() && Backend.isFlywheelWorld(be.getLevel())) {
if (Backend.isOn() && Backend.isFlywheelWorld(be.getLevel())) {
InstancedRenderRegistry registry = InstancedRenderRegistry.getInstance();
if (registry.canInstance(be.getType()))

View file

@ -1,6 +1,5 @@
package com.jozufozu.flywheel.mixin;
import org.lwjgl.opengl.GL20;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@ -9,7 +8,6 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.OptifineHandler;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer;
import com.jozufozu.flywheel.event.BeginFrameEvent;
@ -74,8 +72,7 @@ public class RenderHooksMixin {
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/LevelRenderer;checkPoseStack(Lcom/mojang/blaze3d/vertex/PoseStack;)V", ordinal = 2 // after the game renders the breaking overlay normally
), method = "renderLevel")
private void renderBlockBreaking(PoseStack stack, float p_228426_2_, long p_228426_3_, boolean p_228426_5_, Camera info, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f p_228426_9_, CallbackInfo ci) {
if (!Backend.getInstance()
.available()) return;
if (!Backend.isOn()) return;
Vec3 cameraPos = info.getPosition();