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