State restoration confusion

- Do not wrap BeginFrameEvent and RenderStageEvent with GL restore state
- Move GL state restoration into InstancingEngine
- Remove BufferUploaderMixin
- Add binding enum values to GlBufferType
- Remove debugNormals config
- Rename some occurrences of "world" to "level"
This commit is contained in:
PepperCode1 2022-09-16 13:32:13 -07:00
parent adb0b5c2a8
commit 94e792f25e
21 changed files with 99 additions and 141 deletions

View file

@ -94,7 +94,7 @@ public class Flywheel {
forgeEventBus.addListener(ForgeEvents::unloadWorld);
forgeEventBus.addListener(ForgeEvents::tickLight);
forgeEventBus.addListener(EventPriority.LOWEST, RenderWork::onRenderWorldLast);
forgeEventBus.addListener(EventPriority.LOWEST, RenderWork::onRenderLevelLast);
modEventBus.addListener(PartialModel::onModelRegistry);
modEventBus.addListener(PartialModel::onModelBake);

View file

@ -1,13 +1,13 @@
package com.jozufozu.flywheel.api;
/**
* A marker interface custom worlds can override to indicate
* that block entities and entities inside the world should
* A marker interface custom levels can override to indicate
* that block entities and entities inside the level should
* render with Flywheel.
*
* {@link net.minecraft.client.Minecraft#level Minecraft#level} is special cased and will support Flywheel by default.
*/
public interface FlywheelWorld {
public interface FlywheelLevel {
default boolean supportsFlywheel() {
return true;
}

View file

@ -3,7 +3,7 @@ package com.jozufozu.flywheel.backend;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import com.jozufozu.flywheel.api.FlywheelWorld;
import com.jozufozu.flywheel.api.FlywheelLevel;
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.backend.instancing.ParallelTaskEngine;
import com.jozufozu.flywheel.config.BackendType;
@ -61,21 +61,21 @@ public class Backend {
return TYPE != BackendType.OFF;
}
public static boolean canUseInstancing(@Nullable Level world) {
return isOn() && isFlywheelWorld(world);
public static boolean canUseInstancing(@Nullable Level level) {
return isOn() && isFlywheelLevel(level);
}
/**
* Used to avoid calling Flywheel functions on (fake) worlds that don't specifically support it.
* Used to avoid calling Flywheel functions on (fake) levels that don't specifically support it.
*/
public static boolean isFlywheelWorld(@Nullable LevelAccessor world) {
if (world == null) return false;
public static boolean isFlywheelLevel(@Nullable LevelAccessor level) {
if (level == null) return false;
if (!world.isClientSide()) return false;
if (!level.isClientSide()) return false;
if (world instanceof FlywheelWorld && ((FlywheelWorld) world).supportsFlywheel()) return true;
if (level instanceof FlywheelLevel && ((FlywheelLevel) level).supportsFlywheel()) return true;
return world == Minecraft.getInstance().level;
return level == Minecraft.getInstance().level;
}
public static boolean isGameActive() {

View file

@ -80,9 +80,9 @@ public class Loader implements ResourceManagerReloadListener {
Backend.LOGGER.info("Compiled all programs in " + StringUtil.formatTime(compileEnd - compileStart));
ClientLevel world = Minecraft.getInstance().level;
if (Backend.canUseInstancing(world)) {
InstancedRenderDispatcher.resetInstanceWorld(world);
ClientLevel level = Minecraft.getInstance().level;
if (Backend.canUseInstancing(level)) {
InstancedRenderDispatcher.resetInstanceLevel(level);
}
}

View file

@ -6,12 +6,11 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import net.minecraftforge.client.event.RenderLevelLastEvent;
public class RenderWork {
private static final Queue<Runnable> runs = new ConcurrentLinkedQueue<>();
private static final Queue<Runnable> RUNS = new ConcurrentLinkedQueue<>();
public static void onRenderWorldLast(RenderLevelLastEvent event) {
while (!runs.isEmpty()) {
runs.remove()
public static void onRenderLevelLast(RenderLevelLastEvent event) {
while (!RUNS.isEmpty()) {
RUNS.remove()
.run();
}
}
@ -20,6 +19,6 @@ public class RenderWork {
* Queue work to be executed at the end of a frame
*/
public static void enqueue(Runnable run) {
runs.add(run);
RUNS.add(run);
}
}

View file

@ -7,13 +7,12 @@ import com.mojang.blaze3d.platform.GlStateManager;
* Tracks bound buffers/vbos because GlStateManager doesn't do that for us.
*/
public class GlStateTracker {
private static final int[] buffers = new int[GlBufferType.values().length];
private static final int[] BUFFERS = new int[GlBufferType.values().length];
private static int vao;
private static int program;
public static int getBuffer(GlBufferType type) {
return buffers[type.ordinal()];
return BUFFERS[type.ordinal()];
}
public static int getVertexArray() {
@ -25,7 +24,7 @@ public class GlStateTracker {
}
public static void _setBuffer(GlBufferType type, int buffer) {
buffers[type.ordinal()] = buffer;
BUFFERS[type.ordinal()] = buffer;
}
public static void _setProgram(int id) {
@ -37,7 +36,7 @@ public class GlStateTracker {
}
public static State getRestoreState() {
return new State(buffers.clone(), vao, program);
return new State(BUFFERS.clone(), vao, program);
}
public record State(int[] buffers, int vao, int program) implements AutoCloseable {
@ -53,7 +52,7 @@ public class GlStateTracker {
GlBufferType[] values = GlBufferType.values();
for (int i = 0; i < values.length; i++) {
if (buffers[i] != GlStateTracker.buffers[i]) {
if (buffers[i] != GlStateTracker.BUFFERS[i]) {
GlStateManager._glBindBuffer(values[i].glEnum, buffers[i]);
}
}

View file

@ -12,25 +12,27 @@ import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.mojang.blaze3d.platform.GlStateManager;
public enum GlBufferType {
ARRAY_BUFFER(GL15C.GL_ARRAY_BUFFER),
ELEMENT_ARRAY_BUFFER(GL15C.GL_ELEMENT_ARRAY_BUFFER),
PIXEL_PACK_BUFFER(GL21.GL_PIXEL_PACK_BUFFER),
PIXEL_UNPACK_BUFFER(GL21.GL_PIXEL_UNPACK_BUFFER),
TRANSFORM_FEEDBACK_BUFFER(GL30.GL_TRANSFORM_FEEDBACK_BUFFER),
UNIFORM_BUFFER(GL31.GL_UNIFORM_BUFFER),
TEXTURE_BUFFER(GL31.GL_TEXTURE_BUFFER),
COPY_READ_BUFFER(GL31.GL_COPY_READ_BUFFER),
COPY_WRITE_BUFFER(GL31.GL_COPY_WRITE_BUFFER),
DRAW_INDIRECT_BUFFER(GL40.GL_DRAW_INDIRECT_BUFFER),
ATOMIC_COUNTER_BUFFER(GL42.GL_ATOMIC_COUNTER_BUFFER),
DISPATCH_INDIRECT_BUFFER(GL43.GL_DISPATCH_INDIRECT_BUFFER),
SHADER_STORAGE_BUFFER(GL43.GL_SHADER_STORAGE_BUFFER),
ARRAY_BUFFER(GL15C.GL_ARRAY_BUFFER, GL15C.GL_ARRAY_BUFFER_BINDING),
ELEMENT_ARRAY_BUFFER(GL15C.GL_ELEMENT_ARRAY_BUFFER, GL15C.GL_ELEMENT_ARRAY_BUFFER_BINDING),
PIXEL_PACK_BUFFER(GL21.GL_PIXEL_PACK_BUFFER, GL21.GL_PIXEL_PACK_BUFFER_BINDING),
PIXEL_UNPACK_BUFFER(GL21.GL_PIXEL_UNPACK_BUFFER, GL21.GL_PIXEL_UNPACK_BUFFER_BINDING),
TRANSFORM_FEEDBACK_BUFFER(GL30.GL_TRANSFORM_FEEDBACK_BUFFER, GL30.GL_TRANSFORM_FEEDBACK_BUFFER_BINDING),
UNIFORM_BUFFER(GL31.GL_UNIFORM_BUFFER, GL31.GL_UNIFORM_BUFFER_BINDING),
TEXTURE_BUFFER(GL31.GL_TEXTURE_BUFFER, GL31.GL_TEXTURE_BUFFER),
COPY_READ_BUFFER(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_READ_BUFFER),
COPY_WRITE_BUFFER(GL31.GL_COPY_WRITE_BUFFER, GL31.GL_COPY_WRITE_BUFFER),
DRAW_INDIRECT_BUFFER(GL40.GL_DRAW_INDIRECT_BUFFER, GL40.GL_DRAW_INDIRECT_BUFFER_BINDING),
ATOMIC_COUNTER_BUFFER(GL42.GL_ATOMIC_COUNTER_BUFFER, GL42.GL_ATOMIC_COUNTER_BUFFER_BINDING),
DISPATCH_INDIRECT_BUFFER(GL43.GL_DISPATCH_INDIRECT_BUFFER, GL43.GL_DISPATCH_INDIRECT_BUFFER_BINDING),
SHADER_STORAGE_BUFFER(GL43.GL_SHADER_STORAGE_BUFFER, GL43.GL_SHADER_STORAGE_BUFFER_BINDING),
;
public final int glEnum;
public final int glBindingEnum;
GlBufferType(int glEnum) {
GlBufferType(int glEnum, int glBindingEnum) {
this.glEnum = glEnum;
this.glBindingEnum = glBindingEnum;
}
public static GlBufferType fromTarget(int pTarget) {

View file

@ -142,10 +142,10 @@ public class InstanceWorld implements AutoCloseable {
/**
* Instantiate all the necessary instances to render the given world.
*/
public void loadEntities(ClientLevel world) {
public void loadEntities(ClientLevel level) {
// Block entities are loaded while chunks are baked.
// Entities are loaded with the world, so when chunks are reloaded they need to be re-added.
ClientLevelExtension.getAllLoadedEntities(world)
// Entities are loaded with the level, so when chunks are reloaded they need to be re-added.
ClientLevelExtension.getAllLoadedEntities(level)
.forEach(entities::add);
}

View file

@ -3,6 +3,7 @@ package com.jozufozu.flywheel.backend.instancing;
import java.util.List;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.backend.instancing.effect.Effect;
import com.jozufozu.flywheel.config.FlwCommands;
import com.jozufozu.flywheel.config.FlwConfig;
@ -77,11 +78,11 @@ public class InstancedRenderDispatcher {
return;
}
Minecraft mc = Minecraft.getInstance();
ClientLevel world = mc.level;
ClientLevel level = mc.level;
AnimationTickHolder.tick();
if (Backend.isOn()) {
instanceWorlds.get(world)
instanceWorlds.get(level)
.tick();
}
}
@ -94,22 +95,22 @@ public class InstancedRenderDispatcher {
}
public static void onRenderStage(RenderStageEvent event) {
ClientLevel world = event.getContext().level();
if (!Backend.canUseInstancing(world)) return;
ClientLevel level = event.getContext().level();
if (!Backend.canUseInstancing(level)) return;
instanceWorlds.get(world).renderStage(event.getContext(), event.getStage());
instanceWorlds.get(level).renderStage(event.getContext(), event.getStage());
}
public static void onReloadRenderers(ReloadRenderersEvent event) {
ClientLevel world = event.getWorld();
if (Backend.isOn() && world != null) {
resetInstanceWorld(world);
ClientLevel level = event.getLevel();
if (Backend.isOn() && level != null) {
resetInstanceLevel(level);
}
}
public static void resetInstanceWorld(ClientLevel world) {
instanceWorlds.replace(world, InstanceWorld::delete)
.loadEntities(world);
public static void resetInstanceLevel(ClientLevel level) {
instanceWorlds.replace(level, InstanceWorld::delete)
.loadEntities(level);
}
public static void getDebugString(List<String> debug) {

View file

@ -47,20 +47,20 @@ public class BlockEntityInstanceManager extends InstanceManager<BlockEntity> {
return false;
}
Level world = blockEntity.getLevel();
Level level = blockEntity.getLevel();
if (world == null) {
if (level == null) {
return false;
}
if (world.isEmptyBlock(blockEntity.getBlockPos())) {
if (level.isEmptyBlock(blockEntity.getBlockPos())) {
return false;
}
if (Backend.isFlywheelWorld(world)) {
if (Backend.isFlywheelLevel(level)) {
BlockPos pos = blockEntity.getBlockPos();
BlockGetter existingChunk = world.getChunkForCollisions(pos.getX() >> 4, pos.getZ() >> 4);
BlockGetter existingChunk = level.getChunkForCollisions(pos.getX() >> 4, pos.getZ() >> 4);
return existingChunk != null;
}

View file

@ -42,12 +42,12 @@ public class EntityInstanceManager extends InstanceManager<Entity> {
return false;
}
Level world = entity.level;
Level level = entity.level;
if (Backend.isFlywheelWorld(world)) {
if (Backend.isFlywheelLevel(level)) {
BlockPos pos = entity.blockPosition();
BlockGetter existingChunk = world.getChunkForCollisions(pos.getX() >> 4, pos.getZ() >> 4);
BlockGetter existingChunk = level.getChunkForCollisions(pos.getX() >> 4, pos.getZ() >> 4);
return existingChunk != null;
}

View file

@ -13,6 +13,7 @@ import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
@ -66,7 +67,9 @@ public class InstancingEngine implements Engine {
@Override
public void beginFrame(TaskEngine taskEngine, RenderContext context) {
drawManager.flush();
try (var restoreState = GlStateTracker.getRestoreState()) {
drawManager.flush();
}
}
@Override
@ -77,9 +80,11 @@ public class InstancingEngine implements Engine {
return;
}
setup();
render(drawSet);
try (var restoreState = GlStateTracker.getRestoreState()) {
setup();
render(drawSet);
}
}
protected void setup() {

View file

@ -54,23 +54,6 @@ public class FlwCommands {
return Command.SINGLE_SUCCESS;
})));
commandBuilder.addValue(config.client.debugNormals, "debugNormals", (builder, value) -> booleanValueCommand(builder, value,
(source, bool) -> {
LocalPlayer player = Minecraft.getInstance().player;
if (player == null) return;
Component text = new TextComponent("Normal debug mode is currently: ").append(boolToText(bool));
player.displayClientMessage(text, false);
},
(source, bool) -> {
LocalPlayer player = Minecraft.getInstance().player;
if (player == null) return;
Component text = boolToText(bool).append(new TextComponent(" normal debug mode").withStyle(ChatFormatting.WHITE));
player.displayClientMessage(text, false);
}
));
commandBuilder.addValue(config.client.limitUpdates, "limitUpdates", (builder, value) -> booleanValueCommand(builder, value,
(source, bool) -> {
LocalPlayer player = Minecraft.getInstance().player;
@ -90,7 +73,18 @@ public class FlwCommands {
}
));
commandBuilder.command.then(Commands.literal("debugCrumble")
// TODO
commandBuilder.command.then(Commands.literal("debugNormals"))
.executes(context -> {
LocalPlayer player = Minecraft.getInstance().player;
if (player == null) return 0;
player.displayClientMessage(new TextComponent("This command is not yet implemented."), false);
return Command.SINGLE_SUCCESS;
});
commandBuilder.command.then(Commands.literal("debugCrumbling")
.then(Commands.argument("pos", BlockPosArgument.blockPos())
.then(Commands.argument("stage", IntegerArgumentType.integer(0, 9))
.executes(context -> {
@ -106,7 +100,7 @@ public class FlwCommands {
executor.level.destroyBlockProgress(executor.getId(), pos, value);
return 1;
return Command.SINGLE_SUCCESS;
}))));
commandBuilder.build(event.getDispatcher());

View file

@ -31,10 +31,6 @@ public class FlwConfig {
return client.backend.get();
}
public boolean debugNormals() {
return client.debugNormals.get();
}
public boolean limitUpdates() {
return client.limitUpdates.get();
}
@ -44,16 +40,12 @@ public class FlwConfig {
public static class ClientConfig {
public final EnumValue<BackendType> 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);
debugNormals = builder.comment("Enable or disable a debug overlay that colors pixels by their normal.")
.define("debugNormals", false);
limitUpdates = builder.comment("Enable or disable instance update limiting with distance.")
.define("limitUpdates", true);
}

View file

@ -11,7 +11,7 @@ import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.FlywheelWorld;
import com.jozufozu.flywheel.api.FlywheelLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@ -43,7 +43,7 @@ import net.minecraft.world.level.storage.WritableLevelData;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.ticks.LevelTickAccess;
public class VirtualRenderWorld extends Level implements FlywheelWorld {
public class VirtualRenderWorld extends Level implements FlywheelLevel {
public final Map<BlockPos, BlockState> blocksAdded = new HashMap<>();
public final Map<BlockPos, BlockEntity> besAdded = new HashMap<>();
public final Set<SectionPos> spannedSections = new HashSet<>();

View file

@ -6,14 +6,14 @@ import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraftforge.eventbus.api.Event;
public class ReloadRenderersEvent extends Event {
private final ClientLevel world;
private final ClientLevel level;
public ReloadRenderersEvent(ClientLevel world) {
this.world = world;
public ReloadRenderersEvent(ClientLevel level) {
this.level = level;
}
@Nullable
public ClientLevel getWorld() {
return world;
public ClientLevel getLevel() {
return level;
}
}

View file

@ -1,28 +0,0 @@
package com.jozufozu.flywheel.mixin;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.mojang.blaze3d.vertex.BufferUploader;
import com.mojang.blaze3d.vertex.VertexFormat;
@Mixin(BufferUploader.class)
public class BufferUploaderMixin {
@Shadow
@Nullable
private static VertexFormat lastFormat;
// Stop BufferUploader from clearing buffer state if nothing is bound
@Inject(method = "reset", at = @At("HEAD"))
private static void flywheel$onReset(CallbackInfo ci) {
// Trust our tracker over BufferUploader's.
if (GlStateTracker.getVertexArray() == 0) {
lastFormat = null;
}
}
}

View file

@ -12,7 +12,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
@ -44,9 +43,7 @@ public class LevelRendererMixin {
private void flywheel$beginRender(PoseStack pPoseStack, float pPartialTick, long pFinishNanoTime, boolean pRenderBlockOutline, Camera pCamera, GameRenderer pGameRenderer, LightTexture pLightTexture, Matrix4f pProjectionMatrix, CallbackInfo ci) {
flywheel$renderContext = new RenderContext((LevelRenderer) (Object) this, level, pPoseStack, RenderContext.createViewProjection(pPoseStack, pProjectionMatrix), pProjectionMatrix, renderBuffers, pCamera);
try (var restoreState = GlStateTracker.getRestoreState()) {
MinecraftForge.EVENT_BUS.post(new BeginFrameEvent(flywheel$renderContext));
}
MinecraftForge.EVENT_BUS.post(new BeginFrameEvent(flywheel$renderContext));
}
@Inject(at = @At("TAIL"), method = "renderLevel")
@ -74,9 +71,7 @@ public class LevelRendererMixin {
@Unique
private void flywheel$dispatch(RenderStage stage) {
if (flywheel$renderContext != null) {
try (var restoreState = GlStateTracker.getRestoreState()) {
MinecraftForge.EVENT_BUS.post(new RenderStageEvent(flywheel$renderContext, stage));
}
MinecraftForge.EVENT_BUS.post(new RenderStageEvent(flywheel$renderContext, stage));
}
}

View file

@ -18,14 +18,14 @@ public class NetworkLightUpdateMixin {
@Inject(at = @At("TAIL"), method = "handleLightUpdatePacket")
private void flywheel$onLightPacket(ClientboundLightUpdatePacket packet, CallbackInfo ci) {
RenderWork.enqueue(() -> {
ClientLevel world = Minecraft.getInstance().level;
ClientLevel level = Minecraft.getInstance().level;
if (world == null) return;
if (level == null) return;
int chunkX = packet.getX();
int chunkZ = packet.getZ();
LightUpdater.get(world)
LightUpdater.get(level)
.onLightPacket(chunkX, chunkZ);
});
}

View file

@ -8,7 +8,6 @@
"BlockEntityRenderDispatcherAccessor",
"BlockEntityTypeMixin",
"BufferBuilderMixin",
"BufferUploaderMixin",
"ChunkRebuildHooksMixin",
"ClientLevelMixin",
"EntityTypeMixin",