From f35cf673e18b2c2b6c837f2b36a969e4d5603452 Mon Sep 17 00:00:00 2001 From: TomTheFurry Date: Thu, 26 May 2022 12:38:56 +0800 Subject: [PATCH] Working on connecting the dots with a7 stuff --- .../core/api/internal/InternalApiShared.java | 3 - .../lod/core/api/internal/a7/ClientApi.java | 318 ++++++++++ .../lod/core/api/internal/a7/ServerApi.java | 107 ++++ .../lod/core/api/internal/a7/SharedApi.java | 51 ++ .../lod/core/enums/rendering/DebugMode.java | 45 +- .../seibel/lod/core/objects/a7/DHLevel.java | 72 ++- .../seibel/lod/core/objects/a7/DHWorld.java | 63 +- .../lod/core/objects/a7/LodQuadTree.java | 2 + .../lod/core/objects/a7/data/DataFile.java | 20 +- .../core/objects/a7/data/DataFileHandler.java | 19 +- .../a7/render/RenderBufferHandler.java | 2 +- .../seibel/lod/core/render/a7LodRenderer.java | 563 ++++++++++++++++++ .../com/seibel/lod/core/util/EventLoop.java | 32 + .../minecraft/IMinecraftSharedWrapper.java | 7 + .../minecraft/IProfilerWrapper.java | 4 + 15 files changed, 1276 insertions(+), 32 deletions(-) create mode 100644 src/main/java/com/seibel/lod/core/api/internal/a7/ClientApi.java create mode 100644 src/main/java/com/seibel/lod/core/api/internal/a7/ServerApi.java create mode 100644 src/main/java/com/seibel/lod/core/api/internal/a7/SharedApi.java create mode 100644 src/main/java/com/seibel/lod/core/render/a7LodRenderer.java create mode 100644 src/main/java/com/seibel/lod/core/util/EventLoop.java create mode 100644 src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftSharedWrapper.java diff --git a/src/main/java/com/seibel/lod/core/api/internal/InternalApiShared.java b/src/main/java/com/seibel/lod/core/api/internal/InternalApiShared.java index 8e49de5a4..db64edfff 100644 --- a/src/main/java/com/seibel/lod/core/api/internal/InternalApiShared.java +++ b/src/main/java/com/seibel/lod/core/api/internal/InternalApiShared.java @@ -19,12 +19,9 @@ package com.seibel.lod.core.api.internal; -import com.seibel.lod.core.ModInfo; import com.seibel.lod.core.builders.lodBuilding.LodBuilder; import com.seibel.lod.core.enums.config.VerticalQuality; import com.seibel.lod.core.objects.lod.LodWorld; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** * This stores objects and variables that diff --git a/src/main/java/com/seibel/lod/core/api/internal/a7/ClientApi.java b/src/main/java/com/seibel/lod/core/api/internal/a7/ClientApi.java new file mode 100644 index 000000000..5653527ec --- /dev/null +++ b/src/main/java/com/seibel/lod/core/api/internal/a7/ClientApi.java @@ -0,0 +1,318 @@ +/* + * This file is part of the Distant Horizons mod (formerly the LOD Mod), + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2022 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.api.internal.a7; + +import com.seibel.lod.core.Config; +import com.seibel.lod.core.ModInfo; +import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory; +import com.seibel.lod.core.enums.rendering.DebugMode; +import com.seibel.lod.core.enums.rendering.RendererType; +import com.seibel.lod.core.handlers.LodDimensionFinder; +import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler; +import com.seibel.lod.core.logging.ConfigBasedLogger; +import com.seibel.lod.core.logging.ConfigBasedSpamLogger; +import com.seibel.lod.core.logging.SpamReducedLogger; +import com.seibel.lod.core.objects.a7.DHLevel; +import com.seibel.lod.core.objects.a7.DHWorld; +import com.seibel.lod.core.objects.math.Mat4f; +import com.seibel.lod.core.render.GLProxy; +import com.seibel.lod.core.render.LodRenderer; +import com.seibel.lod.core.render.RenderSystemTest; +import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.lwjgl.glfw.GLFW; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + * This holds the methods that should be called + * by the host mod loader (Fabric, Forge, etc.). + * Specifically for the client. + * + * @author James Seibel + * @version 2022-4-27 + */ +public class ClientApi +{ + public static final Logger LOGGER = LogManager.getLogger(ClientApi.class.getSimpleName()); + public static boolean prefLoggerEnabled = false; + + public static final ClientApi INSTANCE = new ClientApi(); + public static final LodBufferBuilderFactory lodBufferBuilderFactory = new LodBufferBuilderFactory(); + public static LodRenderer renderer = new LodRenderer(lodBufferBuilderFactory); + public static RenderSystemTest testRenderer = new RenderSystemTest(); + private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class); + private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class); + private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class); + private static final ServerApi EVENT_API = ServerApi.INSTANCE; + + public static final boolean ENABLE_LAG_SPIKE_LOGGING = false; + public static final long LAG_SPIKE_THRESHOLD_NS = TimeUnit.NANOSECONDS.convert(16, TimeUnit.MILLISECONDS); + + public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS); + + public static LodDimensionFinder DIMENSION_FINDER = new LodDimensionFinder();; + + public static class LagSpikeCatcher { + long timer = System.nanoTime(); + public LagSpikeCatcher() {} + public void end(String source) { + if (!ENABLE_LAG_SPIKE_LOGGING) return; + timer = System.nanoTime() - timer; + if (timer > LAG_SPIKE_THRESHOLD_NS) { + LOGGER.info("LagSpikeCatcher: "+source+" took "+Duration.ofNanos(timer)+"!"); + } + } + } + + /** + * there is some setup that should only happen once, + * once this is true that setup has completed + */ + private boolean firstTimeSetupComplete = false; + private boolean configOverrideReminderPrinted = false; + + public boolean rendererDisabledBecauseOfExceptions = false; + private ClientApi() + { + + } + + public static void logToChat(Level logLevel, String str) { + String prefix = "["+ModInfo.READABLE_NAME+"] "; + if (logLevel == Level.ERROR) { + prefix += "\u00A74"; + } else if (logLevel == Level.WARN) { + prefix += "\u00A76"; + } else if (logLevel == Level.INFO) { + prefix += "\u00A7f"; + } else if (logLevel == Level.DEBUG) { + prefix += "\u00A77"; + } else if (logLevel == Level.TRACE) { + prefix += "\u00A78"; + } else { + prefix += "\u00A7f"; + } + prefix += "\u00A7l\u00A7u"; + prefix += logLevel.name(); + prefix += ":\u00A7r "; + if (MC != null) MC.sendChatMessage(prefix + str); + } + + public void clientChunkLoadEvent(IChunkWrapper chunk, IWorldWrapper world) + { + //TODO: Implement + } + public void clientChunkSaveEvent(IChunkWrapper chunk, IWorldWrapper world) + { + //TODO: Implement + } + + public void clientLevelUnloadEvent(IWorldWrapper world) + { + if (SharedApi.currentServer != null) return; + if (SharedApi.currentWorld != null) { + SharedApi.currentWorld.unloadLevel(world); + } + } + public void clientLevelLoadEvent(IWorldWrapper world) + { + if (SharedApi.currentServer != null) return; + if (SharedApi.currentWorld != null) { + SharedApi.currentWorld.getOrLoadLevel(world); + } + } + + private long lastFlush = 0; + + public void preRender() { + IProfilerWrapper profiler = MC.getProfiler(); + profiler.pop(); // get out of "terrain" + profiler.push("DH-PreRender"); + boolean doFlush = System.nanoTime() - lastFlush >= SPAM_LOGGER_FLUSH_NS; + if (doFlush) { + lastFlush = System.nanoTime(); + SpamReducedLogger.flushAll(); + } + ConfigBasedLogger.updateAll(); + ConfigBasedSpamLogger.updateAll(doFlush); + // only run the first time setup once + if (!firstTimeSetupComplete) firstFrameSetup(); + if (ModInfo.IS_DEV_BUILD) + { + // config overrides should only be used in the developer builds + applyDeveloperConfigOverrides(); + } + + if (SharedApi.currentServer == null && SharedApi.currentWorld != null) { + // In single player. + SharedApi.currentWorld.asyncTick(); + } + //FIXME: Is it always 'terrain' that is the previous thing in the profiler? + profiler.push("terrain"); // go back into "terrain" + } + + public void renderLods(IWorldWrapper world, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks) + { + IProfilerWrapper profiler = MC.getProfiler(); + profiler.pop(); // get out of "terrain" + profiler.push("DH-RenderLevel"); + try { + if (!MC.playerExists()) return; + if (world == null) return; + DHWorld dhWorld = SharedApi.currentWorld; + if (dhWorld == null) return; + DHLevel level = (SharedApi.currentServer == null) ? dhWorld.getOrLoadLevel(world) : dhWorld.getLevel(world); + if (level == null) return; + + if (prefLoggerEnabled) { + level.dumpRamUsage(); + } + + if (SharedApi.currentServer == null) { + // In multiplayer, without access to the server-side stuff. So we need to do some extra work. + level.asyncTick(); + } + + if (Config.Client.Advanced.Debugging.rendererType.get() == RendererType.DEFAULT) { + if (MC_RENDER.playerHasBlindnessEffect()) { + // if the player is blind, don't render LODs, + // and don't change minecraft's fog + // which blindness relies on. + return; + } + if (MC_RENDER.getLightmapWrapper() == null) + return; + profiler.push("Render-Lods"); + if (!rendererDisabledBecauseOfExceptions) { + try { + level.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler); + } catch (RuntimeException e) { + rendererDisabledBecauseOfExceptions = true; + LOGGER.error("Renderer thrown an uncaught exception: ", e); + try { + MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons" + + " renderer has encountered an exception!"); + MC.sendChatMessage("\u00A74Renderer is now disabled to prevent further issues."); + MC.sendChatMessage("\u00A74Exception detail: " + e.toString()); + } catch (RuntimeException ignored) { + } + } + } + profiler.pop(); // "Render-Lods" + } else if (Config.Client.Advanced.Debugging.rendererType.get() == RendererType.DEBUG) { + profiler.push("Render-Test"); + try { + ClientApi.testRenderer.render(); + } catch (RuntimeException e) { + LOGGER.error("Renderer thrown an uncaught exception: ", e); + try { + MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons" + + " renderer has encountered an exception!"); + MC.sendChatMessage("\u00A74Renderer is now disabled to prevent further issues."); + MC.sendChatMessage("\u00A74Exception detail: " + e.toString()); + } catch (RuntimeException ignored) { + } + } + profiler.pop(); // end LODTestRendering + } + } + catch (Exception e) + { + LOGGER.error("client level rendering uncaught exception: ", e); + } finally { + profiler.pop(); // end LOD + profiler.push("terrain"); // go back into "terrain" + } + } + + /** used in a development environment to change settings on the fly */ + private void applyDeveloperConfigOverrides() + { + // remind the user that the config override is active + if (!configOverrideReminderPrinted) + { + MC.sendChatMessage(ModInfo.READABLE_NAME + " experimental build " + ModInfo.VERSION); + MC.sendChatMessage("You are running an unsupported version of the mod!"); + MC.sendChatMessage("Here be dragons!"); + + configOverrideReminderPrinted = true; + } + } + + //=================// + // DEBUG USE // + //=================// + + // Trigger once on key press, with CLIENT PLAYER. + public void keyPressedEvent(int glfwKey) + { + if (!Config.Client.Advanced.Debugging.enableDebugKeybindings.get()) + return; + + if (glfwKey == GLFW.GLFW_KEY_F8) + { + Config.Client.Advanced.Debugging.debugMode.set(DebugMode.next(Config.Client.Advanced.Debugging.debugMode.get())); + MC.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugMode.get()); + } + if (glfwKey == GLFW.GLFW_KEY_F6) + { + Config.Client.Advanced.Debugging.rendererType.set(RendererType.next(Config.Client.Advanced.Debugging.rendererType.get())); + MC.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererType.get()); + } + if (glfwKey == GLFW.GLFW_KEY_P) + { + prefLoggerEnabled = !prefLoggerEnabled; + MC.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled")); + } + } + + //=================// + // Lod maintenance // + //=================// + + // FIXME: I need a onLastFrameCleanup() callback in Render Thread... Which calls renderer.cleanup() + + /** This event is called once during the first frame Minecraft renders in the world. */ + public void firstFrameSetup() + { + // make sure the GLProxy is created before the LodBufferBuilder needs it + GLProxy.getInstance(); + + firstTimeSetupComplete = true; + } + + + + + + + + + +} diff --git a/src/main/java/com/seibel/lod/core/api/internal/a7/ServerApi.java b/src/main/java/com/seibel/lod/core/api/internal/a7/ServerApi.java new file mode 100644 index 000000000..b5afcddd8 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/api/internal/a7/ServerApi.java @@ -0,0 +1,107 @@ +/* + * This file is part of the Distant Horizons mod (formerly the LOD Mod), + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2022 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.api.internal.a7; + +import com.seibel.lod.core.api.internal.InternalApiShared; +import com.seibel.lod.core.builders.lodBuilding.LodBuilder; +import com.seibel.lod.core.builders.worldGeneration.BatchGenerator; +import com.seibel.lod.core.enums.WorldType; +import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler; +import com.seibel.lod.core.logging.DhLoggerBuilder; +import com.seibel.lod.core.objects.DHChunkPos; +import com.seibel.lod.core.objects.DHRegionPos; +import com.seibel.lod.core.objects.a7.DHWorld; +import com.seibel.lod.core.objects.a7.Server; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.render.GLProxy; +import com.seibel.lod.core.render.LodRenderer; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.wrapperInterfaces.IVersionConstants; +import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; +import org.apache.logging.log4j.Logger; + +import java.lang.invoke.MethodHandles; + +/** + * This holds the methods that should be called by the host mod loader (Fabric, + * Forge, etc.). Specifically server and client events. + * + * @author James Seibel + * @version 2021-11-12 + */ +public class ServerApi +{ + public static final boolean ENABLE_STACK_DUMP_LOGGING = false; + public static final ServerApi INSTANCE = new ServerApi(); + private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); + private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class); + + private boolean isCurrentlyOnSinglePlayerServer = false; + + private ServerApi() + { + } + + // =============// + // tick events // + // =============// + + private int lastWorldGenTickDelta = 0; + public void serverTickEvent() + { + lastWorldGenTickDelta--; + if (SharedApi.currentWorld != null && lastWorldGenTickDelta <= 0) { + lastWorldGenTickDelta = 20; + DHWorld dhWorld = SharedApi.currentWorld; + dhWorld.tick(); + } + } + + public void serverWorldLoadEvent() { + Server server = new Server(); + SharedApi.currentServer = server; + SharedApi.currentWorld = new DHWorld(); + } + + public void serverWorldUnloadEvent() { + SharedApi.currentWorld.close(); + SharedApi.currentWorld = null; + SharedApi.currentServer = null; + } + + public void serverLevelLoadEvent(IWorldWrapper world) { + SharedApi.currentWorld.getOrLoadLevel(world); + } + public void serverLevelUnloadEvent(IWorldWrapper world) { + SharedApi.currentWorld.unloadLevel(world); + } + public void serverSaveEvent() { + SharedApi.currentWorld.save(); + } + + public void chunkSaveEvent(IChunkWrapper chunk, IWorldWrapper world) { + //TODO + } +} diff --git a/src/main/java/com/seibel/lod/core/api/internal/a7/SharedApi.java b/src/main/java/com/seibel/lod/core/api/internal/a7/SharedApi.java new file mode 100644 index 000000000..0820e4e5e --- /dev/null +++ b/src/main/java/com/seibel/lod/core/api/internal/a7/SharedApi.java @@ -0,0 +1,51 @@ +package com.seibel.lod.core.api.internal.a7; + +import com.seibel.lod.core.objects.a7.DHWorld; +import com.seibel.lod.core.objects.a7.Server; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; + +public class SharedApi { + public static DHWorld currentWorld; + public static Server currentServer; + public static IMinecraftSharedWrapper MC; + public static IMinecraftClientWrapper MC_CLIENT; + + public static void onServerStart() { + if (MC.isServerJar()) { + ServerApi.INSTANCE.serverWorldLoadEvent(); + } else if (MC_CLIENT.hasSinglePlayerServer()) { + ServerApi.INSTANCE.serverWorldLoadEvent(); + } // else do nothing + } + + public static void onServerStop() { + if (MC.isServerJar()) { + ServerApi.INSTANCE.serverWorldUnloadEvent(); + } else if (MC_CLIENT.hasSinglePlayerServer()) { + ServerApi.INSTANCE.serverWorldUnloadEvent(); + } // else do nothing + } + + public static void onLevelLoad(IWorldWrapper world) { + if (MC.isServerJar()) { + ServerApi.INSTANCE.serverLevelLoadEvent(world); + } else if (MC_CLIENT.hasSinglePlayerServer()) { + ServerApi.INSTANCE.serverLevelLoadEvent(world); + } else { + ClientApi.INSTANCE.clientLevelLoadEvent(world); + } + } + + public static void onLevelUnload(IWorldWrapper world) { + if (MC.isServerJar()) { + ServerApi.INSTANCE.serverLevelUnloadEvent(world); + } else if (MC_CLIENT.hasSinglePlayerServer()) { + ServerApi.INSTANCE.serverLevelUnloadEvent(world); + } else { + ClientApi.INSTANCE.clientLevelUnloadEvent(world); + } + } + +} diff --git a/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java b/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java index e4edbb3d5..e20f14fe7 100644 --- a/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java +++ b/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java @@ -51,24 +51,37 @@ public enum DebugMode /** Only draw overlapping LOD quads, and draws in wireframe. */ SHOW_OVERLAPPING_QUADS_WIREFRAME; - /** used when cycling through the different modes */ - private DebugMode next; - - static - { - OFF.next = SHOW_WIREFRAME; - SHOW_WIREFRAME.next = SHOW_DETAIL; - SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME; - SHOW_DETAIL_WIREFRAME.next = SHOW_GENMODE; - SHOW_GENMODE.next = SHOW_GENMODE_WIREFRAME; - SHOW_GENMODE_WIREFRAME.next = SHOW_OVERLAPPING_QUADS; - SHOW_OVERLAPPING_QUADS.next = SHOW_OVERLAPPING_QUADS_WIREFRAME; - SHOW_OVERLAPPING_QUADS_WIREFRAME.next = OFF; - } - /** returns the next debug mode */ + // Deprecated: use DebugMode.next() instead + @Deprecated public DebugMode getNext() { - return this.next; + return next(this); + } + + public static DebugMode next(DebugMode type) { + switch (type) { + case OFF: return SHOW_WIREFRAME; + case SHOW_WIREFRAME: return SHOW_DETAIL; + case SHOW_DETAIL: return SHOW_DETAIL_WIREFRAME; + case SHOW_DETAIL_WIREFRAME: return SHOW_GENMODE; + case SHOW_GENMODE: return SHOW_GENMODE_WIREFRAME; + case SHOW_GENMODE_WIREFRAME: return SHOW_OVERLAPPING_QUADS; + case SHOW_OVERLAPPING_QUADS: return SHOW_OVERLAPPING_QUADS_WIREFRAME; + default: return OFF; + } + } + + public static DebugMode previous(DebugMode type) { + switch (type) { + case OFF: return SHOW_OVERLAPPING_QUADS_WIREFRAME; + case SHOW_OVERLAPPING_QUADS_WIREFRAME: return SHOW_OVERLAPPING_QUADS; + case SHOW_OVERLAPPING_QUADS: return SHOW_GENMODE_WIREFRAME; + case SHOW_GENMODE_WIREFRAME: return SHOW_GENMODE; + case SHOW_GENMODE: return SHOW_DETAIL_WIREFRAME; + case SHOW_DETAIL_WIREFRAME: return SHOW_DETAIL; + case SHOW_DETAIL: return SHOW_WIREFRAME; + default: return OFF; + } } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/DHLevel.java b/src/main/java/com/seibel/lod/core/objects/a7/DHLevel.java index cf4fa755c..07b0d6f8e 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/DHLevel.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/DHLevel.java @@ -1,20 +1,27 @@ package com.seibel.lod.core.objects.a7; +import com.seibel.lod.core.api.internal.InternalApiShared; +import com.seibel.lod.core.api.internal.a7.ClientApi; import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler; import com.seibel.lod.core.objects.a7.data.DataFileHandler; import com.seibel.lod.core.objects.a7.pos.DhBlockPos2D; import com.seibel.lod.core.objects.a7.render.RenderBufferHandler; -import com.seibel.lod.core.render.LodRenderProgram; +import com.seibel.lod.core.objects.math.Mat4f; +import com.seibel.lod.core.render.a7LodRenderer; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.EventLoop; import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; +import java.io.Closeable; import java.io.File; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; -public class DHLevel extends LodQuadTree { +public class DHLevel extends LodQuadTree implements Closeable { private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class); public final File saveFolder; // Could be null, for no saving @@ -23,11 +30,16 @@ public class DHLevel extends LodQuadTree { public final ExecutorService dhTickerThread = LodUtil.makeSingleThreadPool("DHLevelTickerThread", 2); private final AtomicBoolean isRunning = new AtomicBoolean(false); public final IWorldWrapper level; + public a7LodRenderer renderer; + public final DHWorld world; - public DHLevel(File saveFolder, IWorldWrapper level) { + public EventLoop eventLoop; + + public DHLevel(DHWorld world, File saveFolder, IWorldWrapper level) { super(CONFIG.client().graphics().quality().getLodChunkRenderDistance()*16, MC.getPlayerBlockPos().x, MC.getPlayerBlockPos().z); + this.world = world; this.saveFolder = saveFolder; if (saveFolder != null) { dataFileHandler = new DataFileHandler(saveFolder, this); @@ -36,6 +48,7 @@ public class DHLevel extends LodQuadTree { } renderBufferHandler = new RenderBufferHandler(this); this.level = level; + eventLoop = new EventLoop(world.dhTickerThread, this::tick); } // Should be called by server tick thread, or called by render thread but only 20 times per second, or less? @@ -64,11 +77,60 @@ public class DHLevel extends LodQuadTree { return dataFileHandler; } - public void render(LodRenderProgram renderContext) { - renderBufferHandler.render(renderContext); + public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) { + if (renderer == null) { + renderer = new a7LodRenderer(this); + } + renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler); } public int getMinY() { return level.getMinHeight(); } + public void dumpRamUsage() { + //TODO + } + public void asyncTick() { + eventLoop.tick(); + } + public void close() { + eventLoop.halt(); + if (dataFileHandler != null) { + dataFileHandler.close(); + } + } + public void saveFlush() { + if (dataFileHandler != null) { + dataFileHandler.save(); + } + } + + public void viewDistanceChangedEvent() + { + // calculate how wide the dimension(s) should be in regions + int chunksWide; + if (MC.getWrappedClientWorld().getDimensionType().hasCeiling()) + chunksWide = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), + LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1; + else + chunksWide = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * 2 + 1; + + int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS); + // make sure we have an odd number of regions + newWidth += (newWidth & 1) == 0 ? 1 : 0; + + // do the dimensions need to change in size? + if (InternalApiShared.lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths) + { + // update the dimensions to fit the new width + InternalApiShared.lodWorld.resizeDimensionRegionWidth(newWidth); + InternalApiShared.lodBuilder.defaultDimensionWidthInRegions = newWidth; + ClientApi.renderer.setupBuffers(); + + recalculateWidths = false; + // LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + // + newWidth ); + } + DetailDistanceUtil.updateSettings(); + } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/DHWorld.java b/src/main/java/com/seibel/lod/core/objects/a7/DHWorld.java index 98e5e8653..5164fa6fa 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/DHWorld.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/DHWorld.java @@ -1,25 +1,36 @@ package com.seibel.lod.core.objects.a7; -import com.seibel.lod.core.handlers.LodDimensionFinder; +import com.seibel.lod.core.Config; +import com.seibel.lod.core.api.internal.InternalApiShared; +import com.seibel.lod.core.api.internal.a7.ClientApi; import com.seibel.lod.core.objects.a7.io.DHFolderHandler; import com.seibel.lod.core.objects.a7.io.LevelToFileMatcher; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.EventLoop; +import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; +import java.io.Closeable; import java.io.File; import java.util.HashMap; +import java.util.Iterator; +import java.util.concurrent.ExecutorService; -public class DHWorld { +public class DHWorld implements Closeable { private final File saveDir; private final HashMap levels; - private LevelToFileMatcher levelToFileMatcher = null; + public ExecutorService dhTickerThread = LodUtil.makeSingleThreadPool("DHTickerThread", 2); + + public EventLoop eventLoop = new EventLoop(dhTickerThread, this::tick); + public DHWorld() { saveDir = DHFolderHandler.getCurrentWorldFolder(); levels = new HashMap<>(); } - public DHLevel getLevel(IWorldWrapper wrapper) { + public DHLevel getOrLoadLevel(IWorldWrapper wrapper) { if (!levels.containsKey(wrapper)) { if (levelToFileMatcher == null || levelToFileMatcher.getTargetWorld() != wrapper) { levelToFileMatcher = new LevelToFileMatcher(saveDir, wrapper); @@ -34,4 +45,48 @@ public class DHWorld { } } else return levels.get(wrapper); } + + public DHLevel getLevel(IWorldWrapper wrapper) { + return levels.get(wrapper); + } + + public void unloadLevel(IWorldWrapper wrapper) { + if (levels.containsKey(wrapper)) { + levels.get(wrapper).close(); + levels.remove(wrapper); + } + } + + public void tick() { + int newViewDistance = Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * 16; + Iterator iterator = levels.values().iterator(); + while (iterator.hasNext()) { + DHLevel level = iterator.next(); + if (level.viewDistance != newViewDistance) { + level.close(); + iterator.remove(); + } + } + DetailDistanceUtil.updateSettings(); + } + public void doWorldGen() { + } + public void asyncTick() { + eventLoop.tick(); + } + + public void save() { + for (DHLevel level : levels.values()) { + level.saveFlush(); + } + } + + @Override + public void close() { + eventLoop.halt(); + for (DHLevel level : levels.values()) { + level.close(); + } + levels.clear(); + } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java b/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java index caa1e213d..7ad74a67d 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java @@ -97,6 +97,7 @@ public abstract class LodQuadTree { } final SectionDetailLayer[] sectionDetailLayers; + public final int viewDistance; /** * Constructor of the quadTree @@ -106,6 +107,7 @@ public abstract class LodQuadTree { */ public LodQuadTree(int viewDistance, int initialPlayerX, int initialPlayerZ) { assertContainerTypeConfigCorrect(); + this.viewDistance = viewDistance; { // Calculate the max section detail byte maxDetailLevel = getMaxDetailInRange(viewDistance * Math.sqrt(2)); diff --git a/src/main/java/com/seibel/lod/core/objects/a7/data/DataFile.java b/src/main/java/com/seibel/lod/core/objects/a7/data/DataFile.java index 064e7b1be..6c6515d63 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/data/DataFile.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/data/DataFile.java @@ -107,7 +107,17 @@ public class DataFile { return path.exists() && path.isFile() && path.canRead() && path.canWrite(); } - public void save(DHLevel level) throws IOException { + public void saveIfNeeded(DHLevel level, boolean freeMemory) { + if (loadedData == null) return; + if (!verifyPath()) return; + try { + save(level, freeMemory); + } catch (IOException e) { + //FIXME: Log and review this handling + } + } + + public void save(DHLevel level, boolean freeMemory) throws IOException { if (loadedData == null) throw new IllegalStateException("No data loaded"); if (!verifyPath()) throw new IOException("File path became invalid"); DataSourceSaver saver; @@ -149,7 +159,15 @@ public class DataFile { dataLevel = newDataLevel; loader = saver; + if (freeMemory) { + loadedData = null; + } + } + public void close(DHLevel level) { + if (loadedData != null) { + saveIfNeeded(level, true); + } } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java b/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java index 45d5845e7..6805470bb 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java @@ -9,15 +9,17 @@ import com.seibel.lod.core.objects.a7.render.RenderDataSource; import com.seibel.lod.core.objects.a7.render.RenderDataSourceLoader; import com.seibel.lod.core.util.LodUtil; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.rmi.server.ExportException; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -public class DataFileHandler implements RenderDataProvider { +public class DataFileHandler implements RenderDataProvider, Closeable { public static final List CONVERTERS = new ArrayList<>(); public static final String FILE_EXTENSION = ".lod"; @@ -122,7 +124,7 @@ public class DataFileHandler implements RenderDataProvider { DataFile dataFile = new DataFile(newFile, saver, dataSource); dataFiles.put(pos, dataFile); try { - dataFile.save(level); + dataFile.save(level, false); } catch (Exception e) { dataFiles.remove(pos, dataFile); //TODO: Log error @@ -141,4 +143,17 @@ public class DataFileHandler implements RenderDataProvider { }); } + @Override + public void close() { + IO_MANAGER.shutdown(); + try { + IO_MANAGER.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException ignored) {} + dataFiles.values().forEach((f) -> f.close(level)); + } + + public void save() { + //TODO: Make it free memory that is not needed + dataFiles.values().forEach(f -> f.saveIfNeeded(level, false)); + } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java index 3659dc4ce..5a8126367 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java @@ -4,7 +4,6 @@ import com.seibel.lod.core.objects.Pos2D; import com.seibel.lod.core.objects.a7.LodQuadTree; import com.seibel.lod.core.objects.a7.LodSection; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; -import com.seibel.lod.core.objects.a7.render.RenderBuffer; import com.seibel.lod.core.render.LodRenderProgram; import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.util.gridList.MovableGridRingList; @@ -142,4 +141,5 @@ public class RenderBufferHandler { public void close() { renderBufferNodes.clear(RenderBufferNode::close); } + } diff --git a/src/main/java/com/seibel/lod/core/render/a7LodRenderer.java b/src/main/java/com/seibel/lod/core/render/a7LodRenderer.java new file mode 100644 index 000000000..35827b3e3 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/render/a7LodRenderer.java @@ -0,0 +1,563 @@ +/* + * This file is part of the Distant Horizons mod (formerly the LOD Mod), + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2022 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.render; + +import com.seibel.lod.core.Config; +import com.seibel.lod.core.api.internal.InternalApiShared; +import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory; +import com.seibel.lod.core.enums.rendering.DebugMode; +import com.seibel.lod.core.enums.rendering.FogColorMode; +import com.seibel.lod.core.enums.rendering.FogDistance; +import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler; +import com.seibel.lod.core.logging.ConfigBasedLogger; +import com.seibel.lod.core.logging.ConfigBasedSpamLogger; +import com.seibel.lod.core.objects.BoolType; +import com.seibel.lod.core.objects.DHBlockPos; +import com.seibel.lod.core.objects.Pos2D; +import com.seibel.lod.core.objects.a7.DHLevel; +import com.seibel.lod.core.objects.lod.LodDimension; +import com.seibel.lod.core.objects.math.Mat4f; +import com.seibel.lod.core.objects.math.Vec3d; +import com.seibel.lod.core.objects.math.Vec3f; +import com.seibel.lod.core.objects.opengl.RenderRegion; +import com.seibel.lod.core.render.objects.GLState; +import com.seibel.lod.core.render.objects.QuadElementBuffer; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.gridList.EdgeDistanceBooleanGrid; +import com.seibel.lod.core.util.gridList.MovableGridRingList; +import com.seibel.lod.core.util.gridList.PosArrayGridList; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; +import com.seibel.lod.core.wrapperInterfaces.misc.ILightMapWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; +import org.apache.logging.log4j.LogManager; +import org.lwjgl.opengl.GL32; + +import java.awt.*; +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + * This is where all the magic happens.
+ * This is where LODs are draw to the world. + * + * @author James Seibel + * @version 12-12-2021 + */ +public class a7LodRenderer +{ + public static final ConfigBasedLogger EVENT_LOGGER = new ConfigBasedLogger(LogManager.getLogger(a7LodRenderer.class), + () -> Config.Client.Advanced.Debugging.DebugSwitch.logRendererBufferEvent.get()); + + public static ConfigBasedSpamLogger tickLogger = new ConfigBasedSpamLogger(LogManager.getLogger(a7LodRenderer.class), + () -> Config.Client.Advanced.Debugging.DebugSwitch.logRendererBufferEvent.get(),1); + public static final boolean ENABLE_DRAW_LAG_SPIKE_LOGGING = false; + public static final boolean ENABLE_DUMP_GL_STATE = true; + public static final long DRAW_LAG_SPIKE_THRESHOLD_NS = TimeUnit.NANOSECONDS.convert(20, TimeUnit.MILLISECONDS); + + public static final boolean ENABLE_IBO = true; + public static class LagSpikeCatcher { + long timer = System.nanoTime(); + public LagSpikeCatcher() {} + public void end(String source) { + if (!ENABLE_DRAW_LAG_SPIKE_LOGGING) return; + timer = System.nanoTime() - timer; + if (timer> DRAW_LAG_SPIKE_THRESHOLD_NS) { //4 ms + EVENT_LOGGER.debug("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!"); + } + + } + } + private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class); + private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class); + + public DebugMode previousDebugMode = null; + public final DHLevel level; + + // The shader program + LodRenderProgram shaderProgram = null; + public QuadElementBuffer quadIBO = null; + + public a7LodRenderer(DHLevel level) + { + this.level = level; + } + + public void drawLODs(Mat4f baseModelViewMatrix, Mat4f baseProjectionMatrix, float partialTicks, IProfilerWrapper profiler) + { + //=================================// + // determine if LODs should render // + //=================================// + if (MC_RENDER.playerHasBlindnessEffect()) + { + // if the player is blind, don't render LODs, + // and don't change minecraft's fog + // which blindness relies on. + return; + } + if (MC_RENDER.getLightmapWrapper() == null) + return; + + // get MC's shader program + // Save all MC render state + LagSpikeCatcher drawSaveGLState = new LagSpikeCatcher(); + GLState currentState = new GLState(); + if (ENABLE_DUMP_GL_STATE) { + tickLogger.debug("Saving GL state: {}", currentState); + } + drawSaveGLState.end("drawSaveGLState"); + + GLProxy glProxy = GLProxy.getInstance(); + if (canVanillaFogBeDisabled && CONFIG.client().graphics().fogQuality().getDisableVanillaFog()) + if (!MC_RENDER.tryDisableVanillaFog()) + canVanillaFogBeDisabled = false; + + // TODO move the buffer regeneration logic into its own class (probably called in the client api instead) + // starting here... + LagSpikeCatcher updateStatus = new LagSpikeCatcher(); + updateRegenStatus(lodDim, partialTicks); + updateStatus.end("LodDrawSetup:UpdateStatus"); + + + // FIXME: Currently, we check for last Lod Dimension so that we can trigger a cleanup() if dimension has changed + // The better thing to do is to call cleanup() on leaving dimensions in the EventApi, but only for client-side. + if (markToCleanup) { + LagSpikeCatcher drawObjectClenup = new LagSpikeCatcher(); + markToCleanup = false; + cleanup(); // This will unset the isSetupComplete, causing a setup() call. + drawObjectClenup.end("drawObjectClenup"); + } + + //=================// + // create the LODs // + //=================// + + // only regenerate the LODs if: + // 1. we want to regenerate LODs + // 2. we aren't already regenerating the LODs + // 3. we aren't waiting for the build and draw buffers to swap + // (this is to prevent thread conflicts) + LagSpikeCatcher swapBuffer = new LagSpikeCatcher(); + if (partialRegen || fullRegen) { + if (lodBufferBuilderFactory.updateAndSwapLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(), + MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), fullRegen)) { + // the regen process has been started, + // it will be done when lodBufferBuilder.newBuffersAvailable() is true + fullRegen = false; + partialRegen = false; + } + } + swapBuffer.end("SwapBuffer"); + // Get the front buffers to draw + MovableGridRingList regions = lodBufferBuilderFactory.getRenderRegions(); + + if (regions == null) { + // There is no vbos, which means nothing needs to be drawn. So skip rendering + return; + } + + //===================// + // draw params setup // + //===================// + + profiler.push("LOD draw setup"); + LagSpikeCatcher drawSetup = new LagSpikeCatcher(); + /*---------Set GL State--------*/ + // Make sure to unbind current VBO so we don't mess up vanilla settings + LagSpikeCatcher drawGLSetup = new LagSpikeCatcher(); + LagSpikeCatcher drawBindBuff = new LagSpikeCatcher(); + //GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, MC_RENDER.getTargetFrameBuffer()); + GL32.glViewport(0,0, MC_RENDER.getTargetFrameBufferViewportWidth(), MC_RENDER.getTargetFrameBufferViewportHeight()); + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); + drawBindBuff.end("drawBindBuff"); + // set the required open GL settings + LagSpikeCatcher drawSetPolygon = new LagSpikeCatcher(); + if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME + || CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_GENMODE_WIREFRAME + || CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_WIREFRAME + || CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_OVERLAPPING_QUADS_WIREFRAME) { + GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE); + //GL32.glDisable(GL32.GL_CULL_FACE); + } + else { + GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL); + GL32.glEnable(GL32.GL_CULL_FACE); + } + drawSetPolygon.end("drawSetPolygon"); + LagSpikeCatcher drawEnableDepth = new LagSpikeCatcher(); + GL32.glEnable(GL32.GL_DEPTH_TEST); + // GL32.glDisable(GL32.GL_DEPTH_TEST); + GL32.glDepthFunc(GL32.GL_LESS); + drawEnableDepth.end("drawEnableDepth"); + drawGLSetup.end("drawGLSetup"); + // enable transparent rendering + // GL32.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA); + // GL32.glEnable(GL32.GL_BLEND); + GL32.glDisable(GL32.GL_BLEND); + GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT); + + /*---------Bind required objects--------*/ + // Setup LodRenderProgram and the LightmapTexture if it has not yet been done + // also binds LightmapTexture, VAO, and ShaderProgram + if (!isSetupComplete) { + LagSpikeCatcher drawObjectSetup = new LagSpikeCatcher(); + setup(); + drawObjectSetup.end("drawObjectSetup"); + } else { + LagSpikeCatcher drawShaderBind = new LagSpikeCatcher(); + LodFogConfig newConfig = shaderProgram.isShaderUsable(); + if (newConfig != null) { + shaderProgram.free(); + shaderProgram = new LodRenderProgram(newConfig); + } + shaderProgram.bind(); + drawShaderBind.end("drawShaderBind"); + } + LagSpikeCatcher drawSetActiveTexture = new LagSpikeCatcher(); + GL32.glActiveTexture(GL32.GL_TEXTURE0); + drawSetActiveTexture.end("drawSetActiveTexture"); + LagSpikeCatcher drawCalculateParams = new LagSpikeCatcher(); + //LightmapTexture lightmapTexture = new LightmapTexture(); + + /*---------Get required data--------*/ + // Get the matrixs for rendering + int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH; + int farPlaneBlockDistance; + // required for setupFog and setupProjectionMatrix + if (MC.getWrappedClientWorld().getDimensionType().hasCeiling()) + farPlaneBlockDistance = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * LodUtil.CHUNK_WIDTH; + else + farPlaneBlockDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * LodUtil.CHUNK_WIDTH; + drawCalculateParams.end("drawCalculateParams"); + + Mat4f combinedMatrix = createCombinedMatrix(baseProjectionMatrix, baseModelViewMatrix, + vanillaBlockRenderedDistance, farPlaneBlockDistance, partialTicks); + + /*---------Fill uniform data--------*/ + LagSpikeCatcher drawFillData = new LagSpikeCatcher(); + // Fill the uniform data. Note: GL33.GL_TEXTURE0 == texture bindpoint 0 + shaderProgram.fillUniformData(combinedMatrix, + MC_RENDER.isFogStateSpecial() ? getSpecialFogColor(partialTicks) : getFogColor(partialTicks), + 0, MC.getWrappedClientWorld().getHeight(), MC.getWrappedClientWorld().getMinHeight(), farPlaneBlockDistance, + vanillaBlockRenderedDistance, MC_RENDER.isFogStateSpecial()); + + // Note: Since lightmapTexture is changing every frame, it's faster to recreate it than to reuse the old one. + LagSpikeCatcher drawFillLightmap = new LagSpikeCatcher(); + ILightMapWrapper lightmap = MC_RENDER.getLightmapWrapper(); + lightmap.bind(); + + if (ENABLE_IBO) quadIBO.bind(); + + //lightmapTexture.fillData(MC_RENDER.getLightmapTextureWidth(), MC_RENDER.getLightmapTextureHeight(), MC_RENDER.getLightmapPixels()); + drawFillLightmap.end("drawFillLightmap"); + drawFillData.end("DrawFillData"); + //GL32.glEnable( GL32.GL_POLYGON_OFFSET_FILL ); + //GL32.glPolygonOffset( 1f, 1f ); + + //===========// + // rendering // + //===========// + drawSetup.end("LodDrawSetup"); + profiler.popPush("LOD draw"); + LagSpikeCatcher draw = new LagSpikeCatcher(); + + boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling(); + Vec3d cameraPos = MC_RENDER.getCameraExactPosition(); + DHBlockPos cameraBlockPos = MC_RENDER.getCameraBlockPosition(); + Vec3f cameraDir = MC_RENDER.getLookAtVector(); + int drawCount = 0; + + { + int ox,oy,dx,dy; + ox = oy = dx = 0; + dy = -1; + int len = regions.getSize(); + int maxI = len*len; + int halfLen = len/2; + for(int i =0; i < maxI; i++){ + if ((-halfLen <= ox) && (ox <= halfLen) && (-halfLen <= oy) && (oy <= halfLen)){ + Pos2D pos = regions.getCenter(); + int regionX = ox+pos.x; + int regionZ = oy+pos.y; + { + RenderRegion region = regions.get(regionX, regionZ); + if (region == null) continue; + if (region.render(lodDim, cameraPos, cameraBlockPos, cameraDir, + !cullingDisabled, shaderProgram)) drawCount++; + } + } + if( (ox == oy) || ((ox < 0) && (ox == -oy)) || ((ox > 0) && (ox == 1-oy))){ + int temp = dx; + dx = -dy; + dy = temp; + } + ox += dx; + oy += dy; + } + } + //if (drawCall==0) + // tickLogger.info("DrawCall Count: {}", drawCount); + + //================// + // render cleanup // + //================// + draw.end("LodDraw"); + profiler.popPush("LOD cleanup"); + LagSpikeCatcher drawCleanup = new LagSpikeCatcher(); + lightmap.unbind(); + if (ENABLE_IBO) quadIBO.unbind(); + + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); + + shaderProgram.unbind(); + //lightmapTexture.free(); + GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT); + + currentState.restore(); + drawCleanup.end("LodDrawCleanup"); + + // end of internal LOD profiling + profiler.pop(); + tickLogger.incLogTries(); + } + + //=================// + // Setup Functions // + //=================// + + /** Setup all render objects - REQUIRES to be in render thread */ + private void setup() { + if (isSetupComplete) { + EVENT_LOGGER.warn("Renderer setup called but it has already completed setup!"); + return; + } + if (!GLProxy.hasInstance()) { + EVENT_LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!"); + return; + } + + EVENT_LOGGER.info("Setting up renderer"); + isSetupComplete = true; + shaderProgram = new LodRenderProgram(LodFogConfig.generateFogConfig()); + if (ENABLE_IBO) { + quadIBO = new QuadElementBuffer(); + quadIBO.reserve(LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER); + } + EVENT_LOGGER.info("Renderer setup complete"); + } + + /** Create all buffers that will be used. */ + public void setupBuffers() + { + lodBufferBuilderFactory.triggerReset(); + } + + private Color getFogColor(float partialTicks) + { + Color fogColor; + + if (CONFIG.client().graphics().fogQuality().getFogColorMode() == FogColorMode.USE_SKY_COLOR) + fogColor = MC_RENDER.getSkyColor(); + else + fogColor = MC_RENDER.getFogColor(partialTicks); + + return fogColor; + } + private Color getSpecialFogColor(float partialTicks) + { + return MC_RENDER.getSpecialFogColor(partialTicks); + } + + private static float calculateNearClipPlane(float distance, float partialTicks) { + double fov = MC_RENDER.getFov(partialTicks); + double aspectRatio = (double)MC_RENDER.getScreenWidth()/MC_RENDER.getScreenHeight(); + return (float) (distance + / Math.sqrt(1d + LodUtil.pow2(Math.tan(fov/180d*Math.PI/2d)) + * (LodUtil.pow2(aspectRatio) + 1d))); + } + + /** + * create and return a new projection matrix based on MC's projection matrix + * @param projMat this is Minecraft's current projection matrix + * @param modelMat this is Minecraft's current model matrix + * @param vanillaBlockRenderedDistance Minecraft's vanilla far plane distance + */ + private static Mat4f createCombinedMatrix(Mat4f projMat, Mat4f modelMat, float vanillaBlockRenderedDistance, + int farPlaneBlockDistance, float partialTicks) + { + //Create a copy of the current matrix, so the current matrix isn't modified. + Mat4f lodProj = projMat.copy(); + + float nearClipPlane; + if (CONFIG.client().advanced().getLodOnlyMode()) { + nearClipPlane = 0.1f; + } else if (CONFIG.client().graphics().advancedGraphics().getUseExtendedNearClipPlane()) { + nearClipPlane = Math.min((vanillaBlockRenderedDistance-16f),8f*16f); + } else { + nearClipPlane = 16f; + } + + //Set new far and near clip plane values. + lodProj.setClipPlanes( + calculateNearClipPlane(nearClipPlane, partialTicks), + (float)((farPlaneBlockDistance+LodUtil.REGION_WIDTH) * Math.sqrt(2))); + + lodProj.multiply(modelMat); + + return lodProj; + } + + //======================// + // Cleanup Functions // + //======================// + + /** cleanup and free all render objects. REQUIRES to be in render thread + * (Many objects are Native, outside of JVM, and need manual cleanup) */ + private void cleanup() { + if (!isSetupComplete) { + EVENT_LOGGER.warn("Renderer cleanup called but Renderer has not completed setup!"); + return; + } + if (!GLProxy.hasInstance()) { + EVENT_LOGGER.warn("Renderer Cleanup called but the GLProxy has never been inited!"); + return; + } + isSetupComplete = false; + EVENT_LOGGER.info("Renderer Cleanup Started"); + shaderProgram.free(); + if (quadIBO != null) quadIBO.destroy(false); + EVENT_LOGGER.info("Renderer Cleanup Complete"); + } + + /** Calls the BufferBuilder's destroyBuffers method. */ + public void destroyBuffers() + { + lodBufferBuilderFactory.destroyBuffers(); + } + + //======================// + // Other Misc Functions // + //======================// + + /** + * If this is called then the next time "drawLODs" is called + * the LODs will be regenerated; the same as if the player moved. + */ + public void regenerateLODsNextFrame() + { + fullRegen = true; + } + + // returns whether anything changed + private boolean updateVanillaRenderedChunks(LodDimension lodDim) { + // if the player is high enough, draw all LODs + IWorldWrapper world = MC.getWrappedClientWorld(); + if (lastUpdatedPos.getY() > world.getHeight()-world.getMinHeight() || + CONFIG.client().advanced().getLodOnlyMode()) { + if (vanillaChunks != null) { + vanillaChunks = null; + return true; + } + return false; + } + + LagSpikeCatcher getChunks = new LagSpikeCatcher(); + EdgeDistanceBooleanGrid edgeGrid = LodUtil.readVanillaRenderedChunks(lodDim); + if (edgeGrid == null) { + if (vanillaChunks != null) { + vanillaChunks = null; + return true; + } + return false; + } + getChunks.end("LodDrawSetup:UpdateStatus:UpdateVanillaChunks:getChunks"); + PosArrayGridList grid = new PosArrayGridList<>(edgeGrid.gridSize, edgeGrid.getOffsetX(), edgeGrid.getOffsetY()); + + int overdrawOffset = LodUtil.computeOverdrawOffset(lodDim); + edgeGrid.flagAllWithDistance(grid, (i) -> (i >= overdrawOffset)); + vanillaChunks = grid; + return true; + } + + private void updateRegenStatus(LodDimension lodDim, float partialTicks) { + short chunkRenderDistance = (short) MC_RENDER.getRenderDistance(); + long newTime = System.currentTimeMillis(); + DHBlockPos newPos = MC.getPlayerBlockPos(); + boolean shouldUpdateChunks = false; + boolean tryPartialGen = false; + boolean tryFullGen = false; + + // check if the view distance or config changed + if (InternalApiShared.previousLodRenderDistance != CONFIG.client().graphics().quality().getLodChunkRenderDistance() + || chunkRenderDistance != prevRenderDistance + || prevFogDistance != CONFIG.client().graphics().fogQuality().getFogDistance()) + { + DetailDistanceUtil.updateSettings(); // FIXME: This should NOT be here! + prevFogDistance = CONFIG.client().graphics().fogQuality().getFogDistance(); + prevRenderDistance = chunkRenderDistance; + tryFullGen = true; + } else if (CONFIG.client().advanced().debugging().getDebugMode() != previousDebugMode) + { // did the user change the debug setting? + previousDebugMode = CONFIG.client().advanced().debugging().getDebugMode(); + tryFullGen = true; + } + + // check if the player has moved + if (newTime - prevPlayerPosTime > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveTimeout) { + if (lastUpdatedPos == null + || Math.abs(newPos.getX() - lastUpdatedPos.getX()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16 + || Math.abs(newPos.getZ() - lastUpdatedPos.getZ()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16) + { + shouldUpdateChunks = true; + } + prevPlayerPosTime = newTime; + } + + // check if the vanilla rendered chunks changed + if (newTime - prevVanillaChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().renderedChunkTimeout) + { + shouldUpdateChunks = true; + prevVanillaChunkTime = newTime; + } + + // check if there is any newly generated terrain to show + if (newTime - prevChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().chunkChangeTimeout) + { + tryPartialGen = true; + prevChunkTime = newTime; + } + + shouldUpdateChunks |= tryFullGen; + if (shouldUpdateChunks) { + lastUpdatedPos = newPos; + tryPartialGen |= updateVanillaRenderedChunks(lodDim); + } + + if (tryFullGen) { + fullRegen = true; + } else if (tryPartialGen) { + partialRegen = true; + } + } + +} diff --git a/src/main/java/com/seibel/lod/core/util/EventLoop.java b/src/main/java/com/seibel/lod/core/util/EventLoop.java new file mode 100644 index 000000000..c8f9c4e51 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/EventLoop.java @@ -0,0 +1,32 @@ +package com.seibel.lod.core.util; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; + +public class EventLoop { + private final ExecutorService executorService; + private final Runnable runnable; + private CompletableFuture future; + public EventLoop(ExecutorService executorService, Runnable runnable) { + this.executorService = executorService; + this.runnable = runnable; + } + public void tick() { + if (future != null && future.isDone()) { + try { + future.join(); + } catch (Exception ignored) {} finally {future = null;} + } + if (future == null) { + future = CompletableFuture.runAsync(runnable, executorService); + } + } + public void halt() { + if (future != null) { + future.cancel(true); + } + } + public boolean isRunning() { + return future != null && !future.isDone(); + } +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftSharedWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftSharedWrapper.java new file mode 100644 index 000000000..146e18a89 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftSharedWrapper.java @@ -0,0 +1,7 @@ +package com.seibel.lod.core.wrapperInterfaces.minecraft; + +public interface IMinecraftSharedWrapper { + boolean isServerJar(); + + +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IProfilerWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IProfilerWrapper.java index 3b713ba25..483e954a1 100644 --- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IProfilerWrapper.java +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IProfilerWrapper.java @@ -27,6 +27,10 @@ import com.seibel.lod.core.handlers.dependencyInjection.IBindable; */ public interface IProfilerWrapper extends IBindable { + // Note to self: + // if "unspecified" shows up in the pie chart, it is + // possibly because the amount of time between sections + // is too small for the profiler to measures void push(String newSection); void popPush(String newSection);