Commands, config, and a logo

- Added /flywheel backend command to replace /create experimentalRendering
 - Added /flywheel normalOverlay command
 - Need to think more about a good way to do this in a client only way
 - Added basic config
 - Logo in README.md (thanks, dani!)
This commit is contained in:
JozsefA 2021-06-18 22:52:33 -07:00
parent 4af63c6fee
commit 69cf9e9b21
13 changed files with 348 additions and 38 deletions

View file

@ -1,14 +1,20 @@
# Flywheel
A modern engine for modded Minecraft.
<div align="center">
<img src="https://i.imgur.com/yVFgPpr.png" alt="Logo" width="250">
<h1>Flywheel</h1>
<h6>A modern engine for modded Minecraft.</h6>
<a href="https://discord.gg/xjD59ThnXy"><img src="https://img.shields.io/discord/841464837406195712?color=844685&label=Discord&style=flat" alt="Discord"></a>
<a href="https://www.curseforge.com/minecraft/mc-mods/flywheel"><img src="http://cf.way2muchnoise.eu/flywheel.svg" alt="Curseforge Downloads"></a>
<br>
</div>
### About
The goal of this project is to provide tools for mod developers so they no longer have to worry about performance, or limitations of Minecraft's archaic rendering engine.
That said, this is primarily an outlet for me to have fun with graphics programming.
## Instancing
### Instancing
So far, Flywheel provides an alternate, unified path for entity and tile entity rendering that takes advantage of GPU instancing. In doing so, Flywheel gives the developer the flexibility to define their own vertex and instance formats, and write custom shaders to ingest that data.
### Shader Abstraction
### Shaders
To accomodate the developer and leave more in the hands of the engine, Flywheel provides a custom shader loading and templating system to hide the details of the CPU/GPU interface. This system is a work in progress. There will be breaking changes, and I make no guarantees of backwards compatibility.

View file

@ -0,0 +1,15 @@
package com.jozufozu.flywheel;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.core.AtlasStitcher;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
public class Client {
public static void clientInit() {
Backend.init();
FMLJavaModLoadingContext.get().getModEventBus().addListener(AtlasStitcher.getInstance()::onTextureStitch);
}
}

View file

@ -1,13 +1,16 @@
package com.jozufozu.flywheel;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.config.FlwCommands;
import com.jozufozu.flywheel.config.FlwPackets;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent;
import net.minecraftforge.fml.event.lifecycle.InterModProcessEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -19,32 +22,16 @@ public class Flywheel {
private static final Logger LOGGER = LogManager.getLogger();
public Flywheel() {
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::enqueueIMC);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::processIMC);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::doClientStuff);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup);
MinecraftForge.EVENT_BUS.register(this);
}
MinecraftForge.EVENT_BUS.addListener(FlwCommands::onServerStarting);
private void setup(final FMLCommonSetupEvent event) {
FlwConfig.init();
}
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> Client::clientInit);
}
private void doClientStuff(final FMLClientSetupEvent event) {
}
private void enqueueIMC(final InterModEnqueueEvent event) {
}
private void processIMC(final InterModProcessEvent event) {
}
@SubscribeEvent
public void onServerStarting(FMLServerStartingEvent event) {
}
private void setup(final FMLCommonSetupEvent event) {
FlwPackets.registerPackets();
}
}

View file

@ -6,6 +6,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.jozufozu.flywheel.config.FlwConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL;
@ -143,8 +145,7 @@ public class Backend {
compat.drawInstancedSupported() &&
compat.instancedArraysSupported();
// TODO: Config
enabled = !OptifineHandler.usingShaders();
enabled = FlwConfig.get().enabled() && !OptifineHandler.usingShaders();
}
public boolean canUseInstancing(World world) {
@ -178,7 +179,13 @@ public class Backend {
return (world instanceof IFlywheelWorld && ((IFlywheelWorld) world).supportsFlywheel()) || world == Minecraft.getInstance().world;
}
public static boolean isGameActive() {
return !(Minecraft.getInstance().world == null || Minecraft.getInstance().player == null);
}
public static void reloadWorldRenderers() {
RenderWork.enqueue(Minecraft.getInstance().worldRenderer::loadRenderers);
}
public static void init() { }
}

View file

@ -20,6 +20,7 @@ import com.jozufozu.flywheel.core.CrumblingInstanceManager;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.event.RenderLayerEvent;
import com.jozufozu.flywheel.util.AnimationTickHolder;
import com.jozufozu.flywheel.util.WorldAttached;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
@ -39,7 +40,9 @@ import net.minecraft.util.LazyValue;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@ -67,9 +70,15 @@ public class InstancedRenderDispatcher {
return entityInstanceManager.get(world);
}
public static void tick() {
@SubscribeEvent
public static void tick(TickEvent.ClientTickEvent event) {
if (!Backend.isGameActive() || event.phase == TickEvent.Phase.START) {
return;
}
Minecraft mc = Minecraft.getInstance();
ClientWorld world = mc.world;
AnimationTickHolder.tick();
Entity renderViewEntity = mc.renderViewEntity != null ? mc.renderViewEntity : mc.player;

View file

@ -0,0 +1,77 @@
package com.jozufozu.flywheel.config;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.OptifineHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.player.ClientPlayerEntity;
import net.minecraft.util.text.IFormattableTextComponent;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import java.util.function.Consumer;
import java.util.function.Supplier;
public enum BooleanConfig {
ENGINE(() -> BooleanConfig::enabled),
NORMAL_OVERLAY(() -> BooleanConfig::normalOverlay),
;
final Supplier<Consumer<BooleanDirective>> receiver;
BooleanConfig(Supplier<Consumer<BooleanDirective>> receiver) {
this.receiver = receiver;
}
public SConfigureBooleanPacket packet(BooleanDirective directive) {
return new SConfigureBooleanPacket(this, directive);
}
@OnlyIn(Dist.CLIENT)
private static void enabled(BooleanDirective state) {
ClientPlayerEntity player = Minecraft.getInstance().player;
if (player == null || state == null) return;
if (state == BooleanDirective.DISPLAY) {
ITextComponent text = new StringTextComponent("Flywheel Renderer is currently: ").append(boolToText(FlwConfig.get().client.enabled.get()));
player.sendStatusMessage(text, false);
return;
}
boolean enabled = state.get();
boolean cannotUseER = OptifineHandler.usingShaders() && enabled;
FlwConfig.get().client.enabled.set(enabled);
ITextComponent text = boolToText(FlwConfig.get().client.enabled.get()).append(new StringTextComponent(" Flywheel Renderer").formatted(TextFormatting.WHITE));
ITextComponent error = new StringTextComponent("Flywheel Renderer does not support Optifine Shaders").formatted(TextFormatting.RED);
player.sendStatusMessage(cannotUseER ? error : text, false);
Backend.reloadWorldRenderers();
}
@OnlyIn(Dist.CLIENT)
private static void normalOverlay(BooleanDirective state) {
ClientPlayerEntity player = Minecraft.getInstance().player;
if (player == null || state == null) return;
if (state == BooleanDirective.DISPLAY) {
ITextComponent text = new StringTextComponent("Normal overlay is currently: ").append(boolToText(FlwConfig.get().client.normalDebug.get()));
player.sendStatusMessage(text, false);
return;
}
FlwConfig.get().client.normalDebug.set(state.get());
ITextComponent text = boolToText(FlwConfig.get().client.normalDebug.get()).append(new StringTextComponent(" Normal Overlay").formatted(TextFormatting.WHITE));
player.sendStatusMessage(text, false);
}
private static IFormattableTextComponent boolToText(boolean b) {
return b ? new StringTextComponent("enabled").formatted(TextFormatting.DARK_GREEN) : new StringTextComponent("disabled").formatted(TextFormatting.RED);
}
}

View file

@ -0,0 +1,49 @@
package com.jozufozu.flywheel.config;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.builder.ArgumentBuilder;
import net.minecraft.command.CommandSource;
import net.minecraft.command.Commands;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraftforge.fml.network.PacketDistributor;
public class BooleanConfigCommand {
private final String name;
private final BooleanConfig value;
public BooleanConfigCommand(String name, BooleanConfig value) {
this.name = name;
this.value = value;
}
public ArgumentBuilder<CommandSource, ?> register() {
return Commands.literal(name)
.executes(context -> {
ServerPlayerEntity player = context.getSource().asPlayer();
FlwPackets.channel.send(
PacketDistributor.PLAYER.with(() -> player),
new SConfigureBooleanPacket(value, BooleanDirective.DISPLAY)
);
return Command.SINGLE_SUCCESS;
})
.then(Commands.literal("on").executes(context -> {
ServerPlayerEntity player = context.getSource().asPlayer();
FlwPackets.channel.send(
PacketDistributor.PLAYER.with(() -> player),
new SConfigureBooleanPacket(value, BooleanDirective.TRUE)
);
return Command.SINGLE_SUCCESS;
}))
.then(Commands.literal("off").executes(context -> {
ServerPlayerEntity player = context.getSource().asPlayer();
FlwPackets.channel.send(
PacketDistributor.PLAYER.with(() -> player),
new SConfigureBooleanPacket(value, BooleanDirective.FALSE)
);
return Command.SINGLE_SUCCESS;
}));
}
}

View file

@ -0,0 +1,22 @@
package com.jozufozu.flywheel.config;
public enum BooleanDirective {
TRUE(true),
FALSE(false),
/**
* Don't change anything, just display what the value currently is.
*/
DISPLAY(true),
;
private final boolean b;
BooleanDirective(boolean b) {
this.b = b;
}
public boolean get() {
if (this == DISPLAY) throw new IllegalStateException("Cannot get value from DISPLAY directive");
return b;
}
}

View file

@ -0,0 +1,22 @@
package com.jozufozu.flywheel.config;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.command.CommandSource;
import net.minecraft.command.Commands;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.network.PacketDistributor;
public class FlwCommands {
@SubscribeEvent
public static void onServerStarting(FMLServerStartingEvent event) {
CommandDispatcher<CommandSource> dispatcher = event.getServer().getCommandManager().getDispatcher();
dispatcher.register(Commands.literal("flywheel")
.then(new BooleanConfigCommand("backend", BooleanConfig.ENGINE).register())
.then(new BooleanConfigCommand("normalOverlay", BooleanConfig.NORMAL_OVERLAY).register()));
}
}

View file

@ -0,0 +1,52 @@
package com.jozufozu.flywheel.config;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.common.ForgeConfigSpec.BooleanValue;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.config.ModConfig;
import org.apache.commons.lang3.tuple.Pair;
public class FlwConfig {
private static final FlwConfig INSTANCE = new FlwConfig();
public final ClientConfig client;
public FlwConfig() {
Pair<ClientConfig, ForgeConfigSpec> client = new ForgeConfigSpec.Builder().configure(ClientConfig::new);
this.client = client.getLeft();
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, client.getRight());
}
public static FlwConfig get() {
return INSTANCE;
}
public boolean enabled() {
return client.enabled.get();
}
public boolean normalOverlayEnabled() {
return client.normalDebug.get();
}
public static void init() { }
public static class ClientConfig {
public final BooleanValue enabled;
public final BooleanValue normalDebug;
public ClientConfig(ForgeConfigSpec.Builder builder) {
enabled = builder.comment("Enable or disable the entire engine")
.define("enabled", true);
normalDebug = builder.comment("Enable or disable a debug overlay that colors pixels by their normal")
.define("normalDebug", false);
}
}
}

View file

@ -0,0 +1,28 @@
package com.jozufozu.flywheel.config;
import com.jozufozu.flywheel.Flywheel;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.network.NetworkDirection;
import net.minecraftforge.fml.network.NetworkRegistry;
import net.minecraftforge.fml.network.simple.SimpleChannel;
public class FlwPackets {
public static final ResourceLocation CHANNEL_NAME = new ResourceLocation(Flywheel.ID, "network");
public static final String NETWORK_VERSION = new ResourceLocation(Flywheel.ID, "1").toString();
public static SimpleChannel channel;
public static void registerPackets() {
channel = NetworkRegistry.ChannelBuilder.named(CHANNEL_NAME)
.serverAcceptedVersions(NETWORK_VERSION::equals)
.clientAcceptedVersions(NETWORK_VERSION::equals)
.networkProtocolVersion(() -> NETWORK_VERSION)
.simpleChannel();
channel.messageBuilder(SConfigureBooleanPacket.class, 0, NetworkDirection.PLAY_TO_CLIENT)
.decoder(SConfigureBooleanPacket::new)
.encoder(SConfigureBooleanPacket::encode)
.consumer(SConfigureBooleanPacket::execute)
.add();
}
}

View file

@ -0,0 +1,35 @@
package com.jozufozu.flywheel.config;
import net.minecraft.network.PacketBuffer;
import net.minecraftforge.fml.network.NetworkEvent;
import java.util.function.Supplier;
/**
* Thanks, @zelophed
*/
public class SConfigureBooleanPacket {
private final BooleanConfig target;
private final BooleanDirective directive;
public SConfigureBooleanPacket(BooleanConfig target, BooleanDirective directive) {
this.target = target;
this.directive = directive;
}
public SConfigureBooleanPacket(PacketBuffer buffer) {
target = BooleanConfig.values()[buffer.readByte()];
directive = BooleanDirective.values()[buffer.readByte()];
}
public void encode(PacketBuffer buffer) {
buffer.writeByte(target.ordinal());
buffer.writeByte(directive.ordinal());
}
public void execute(Supplier<NetworkEvent.Context> ctx) {
target.receiver.get().accept(directive);
}
}

View file

@ -1,6 +1,7 @@
package com.jozufozu.flywheel.core.shader.gamestate;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.core.shader.spec.IBooleanStateProvider;
import net.minecraft.util.ResourceLocation;
@ -16,7 +17,7 @@ public class NormalDebugStateProvider implements IBooleanStateProvider {
@Override
public boolean isTrue() {
return false;
return FlwConfig.get().normalOverlayEnabled();
}
@Override