diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java
index 2a9eb17ad..132598e0a 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java
@@ -31,6 +31,8 @@ import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
+import com.seibel.distanthorizons.core.render.RenderParams;
+import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.render.renderer.*;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d;
@@ -49,7 +51,6 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
-import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
@@ -464,11 +465,8 @@ public class ClientApi
try
{
- // make sure the GLProxy is created for future use
- GLProxy glProxy = GLProxy.getInstance();
-
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
- glProxy.runRenderThreadTasks();
+ RenderThreadTaskHandler.INSTANCE.runRenderThreadTasks();
}
catch (Exception e)
{
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientPluginChannelApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientPluginChannelApi.java
index afb1f05e8..90a3a356f 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientPluginChannelApi.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientPluginChannelApi.java
@@ -9,7 +9,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession;
-import com.seibel.distanthorizons.core.render.glObject.GLProxy;
+import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.NotNull;
@@ -90,7 +90,7 @@ public class ClientPluginChannelApi
LOGGER.info("Server level key received: [" + msg.levelKey + "].");
- GLProxy.queueRunningOnRenderThread(() ->
+ RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread(() ->
{
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/gui/EmbeddedFrameUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/config/gui/EmbeddedFrameUtil.java
deleted file mode 100644
index 49bf35b04..000000000
--- a/core/src/main/java/com/seibel/distanthorizons/core/config/gui/EmbeddedFrameUtil.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * This file is part of the Distant Horizons mod
- * licensed under the GNU LGPL v3 License.
- *
- * Copyright (C) 2020 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.distanthorizons.core.config.gui;
-
-import com.seibel.distanthorizons.core.jar.EPlatform;
-import org.jetbrains.annotations.NotNull;
-import org.lwjgl.system.jawt.JAWT;
-import org.lwjgl.system.macosx.*;
-
-import java.awt.*;
-import java.lang.reflect.*;
-import java.util.regex.*;
-
-import static org.lwjgl.glfw.GLFWNativeCocoa.*;
-import static org.lwjgl.glfw.GLFWNativeWin32.*;
-import static org.lwjgl.glfw.GLFWNativeX11.*;
-import static org.lwjgl.system.JNI.*;
-import static org.lwjgl.system.jawt.JAWTFunctions.*;
-import static org.lwjgl.system.macosx.ObjCRuntime.*;
-
-// Some of the code is from https://github.com/LWJGL/lwjgl3/blob/master/modules/samples/src/test/java/org/lwjgl/demo/system/jawt/EmbeddedFrameUtil.java
-// which is licensed under https://www.lwjgl.org/license
-
-/**
- * Some utils for embedding awt and swing items into lwjgl windows
- *
- * @author Ran
- * @author coolGi
- */
-public final class EmbeddedFrameUtil
-{
-
- private static final int JAVA_VERSION;
-
- private static final JAWT awt;
-
- static
- {
- Pattern p = Pattern.compile("^(?:1[.])?([1-9][0-9]*)[.-]");
- Matcher m = p.matcher(System.getProperty("java.version"));
-
- if (!m.find())
- {
- throw new IllegalStateException("Failed to parse java.version");
- }
-
- JAVA_VERSION = Integer.parseInt(m.group(1));
-
- awt = JAWT.calloc();
- awt.version(JAVA_VERSION < 9 ? JAWT_VERSION_1_4 : JAWT_VERSION_9);
- if (!JAWT_GetAWT(awt))
- {
- throw new RuntimeException("GetAWT failed");
- }
- }
-
- private static String getEmbeddedFrameImpl()
- {
- switch (EPlatform.get())
- {
- case LINUX:
- return "sun.awt.X11.XEmbeddedFrame";
- case WINDOWS:
- return "sun.awt.windows.WEmbeddedFrame";
- case MACOS:
- return "sun.lwawt.macosx.CViewEmbeddedFrame";
- default:
- throw new IllegalStateException();
- }
- }
-
- private static long getEmbeddedFrameHandle(long window)
- {
- switch (EPlatform.get())
- {
- case LINUX:
- return glfwGetX11Window(window);
- case WINDOWS:
- return glfwGetWin32Window(window);
- case MACOS:
- long objc_msgSend = ObjCRuntime.getLibrary().getFunctionAddress("objc_msgSend");
- return invokePPP(glfwGetCocoaWindow(window), sel_getUid("contentView"), objc_msgSend);
- default:
- throw new IllegalStateException();
- }
- }
-
- public static Frame embeddedFrameCreate(long window)
- {
- if (JAVA_VERSION < 9)
- {
- try
- {
- @SuppressWarnings("unchecked")
- Class extends Frame> EmdeddedFrame = (Class extends Frame>) Class.forName(getEmbeddedFrameImpl());
- Constructor extends Frame> c = EmdeddedFrame.getConstructor(long.class);
-
- return c.newInstance(getEmbeddedFrameHandle(window));
- }
- catch (Exception e)
- {
- throw new RuntimeException(e);
- }
- }
- else
- {
- return nJAWT_CreateEmbeddedFrame(getEmbeddedFrameHandle(window), awt.CreateEmbeddedFrame());
- }
- }
-
- static void embeddedFrameSynthesizeWindowActivation(Frame embeddedFrame, boolean doActivate)
- {
- if (JAVA_VERSION < 9)
- {
- try
- {
- embeddedFrame
- .getClass()
- .getMethod("synthesizeWindowActivation", boolean.class)
- .invoke(embeddedFrame, doActivate);
- }
- catch (Exception e)
- {
- throw new RuntimeException(e);
- }
- }
- else
- {
- JAWT_SynthesizeWindowActivation(embeddedFrame, doActivate, awt.SynthesizeWindowActivation());
- }
- }
-
- public static void embeddedFrameSetBounds(Frame embeddedFrame, int x, int y, int width, int height)
- {
- if (JAVA_VERSION < 9)
- {
- try
- {
- Method setLocationPrivate = embeddedFrame
- .getClass()
- .getSuperclass()
- .getDeclaredMethod("setBoundsPrivate", int.class, int.class, int.class, int.class);
- setLocationPrivate.setAccessible(true);
- setLocationPrivate.invoke(embeddedFrame, x, y, width, height);
- }
- catch (Exception e)
- {
- throw new RuntimeException(e);
- }
- }
- else
- {
- JAWT_SetBounds(embeddedFrame, x, y, width, height, awt.SetBounds());
- }
- }
-
-
- public static void hideFrame(@NotNull Frame embeddedFrame)
- {
- embeddedFrame.setVisible(false);
- embeddedFrameSynthesizeWindowActivation(embeddedFrame, false);
- }
-
- public static void showFrame(@NotNull Frame embeddedFrame)
- {
- embeddedFrameSynthesizeWindowActivation(embeddedFrame, true);
- embeddedFrame.setVisible(true);
- }
- public static void placeAtCenter(Frame embeddedFrame, int windowWidth, int windowHeight, int frameWidth, int frameHeight, float scale)
- {
- float scaleFactor = (100.0F - scale) / 100.0F;
- float newWidth = frameWidth * scaleFactor;
- float newHeight = frameHeight * scaleFactor;
- float newX = (windowWidth - newWidth) / 2F;
- float newY = (windowHeight - newHeight) / 2F;
- embeddedFrameSetBounds(embeddedFrame, Math.round(newX), Math.round(newY), Math.round(newWidth), Math.round(newHeight));
- }
-
-}
\ No newline at end of file
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodRenderSection.java b/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodRenderSection.java
index 0c88cb5bf..39f3c217e 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodRenderSection.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodRenderSection.java
@@ -150,13 +150,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
/** @return true if the upload started, false if it wasn't able to for any reason */
public synchronized boolean uploadRenderDataToGpuAsync()
{
- if (!GLProxy.hasInstance())
- {
- // it's possible to try uploading buffers before the GLProxy has been initialized
- // which would cause the system to crash
- return false;
- }
-
if (this.getAndBuildRenderDataFutureRef.get() != null)
{
// don't accidentally queue multiple uploads at the same time
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderThreadTaskHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderThreadTaskHandler.java
new file mode 100644
index 000000000..dc068a552
--- /dev/null
+++ b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderThreadTaskHandler.java
@@ -0,0 +1,144 @@
+package com.seibel.distanthorizons.core.render;
+
+import com.seibel.distanthorizons.core.config.Config;
+import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
+import com.seibel.distanthorizons.core.logging.DhLogger;
+import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
+import com.seibel.distanthorizons.core.util.TimerUtil;
+import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
+import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
+
+import java.util.Timer;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class RenderThreadTaskHandler
+{
+ public static final DhLogger LOGGER = new DhLoggerBuilder()
+ .fileLevelConfig(Config.Common.Logging.logRendererGLEventToFile)
+ .chatLevelConfig(Config.Common.Logging.logRendererGLEventToChat)
+ .build();
+
+ private static final ConcurrentLinkedQueue RENDER_THREAD_RUNNABLE_QUEUE = new ConcurrentLinkedQueue<>();
+
+ private static final Timer TIMER = TimerUtil.CreateTimer("Cleanup timer");
+ private static final long MS_BETWEEN_CLEANUP_TICKS = 1_000L;
+ private static final long MS_BEFORE_RUN_CLEANUP_TIMER = 1_000L;
+
+
+ public static final RenderThreadTaskHandler INSTANCE = new RenderThreadTaskHandler();
+
+
+ private long msSinceGlTasksRun = System.currentTimeMillis();
+
+
+
+ //=============//
+ // constructor //
+ //=============//
+ //region
+
+ private RenderThreadTaskHandler() { TIMER.scheduleAtFixedRate(TimerUtil.createTimerTask(this::manualCleanupTick), MS_BETWEEN_CLEANUP_TICKS, MS_BETWEEN_CLEANUP_TICKS); }
+
+ //endregion
+
+
+
+ //==============//
+ // task queuing //
+ //==============//
+ //region
+
+ public void queueRunningOnRenderThread(Runnable renderCall)
+ {
+ StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ RENDER_THREAD_RUNNABLE_QUEUE.add(() -> this.createRenderThreadRunnable(renderCall, stackTrace));
+ }
+ private void createRenderThreadRunnable(Runnable renderCall, StackTraceElement[] stackTrace)
+ {
+ try
+ {
+ renderCall.run();
+ }
+ catch (Exception e)
+ {
+ RuntimeException error = new RuntimeException("Uncaught Exception during GL call execution:", e);
+ error.setStackTrace(stackTrace);
+ LOGGER.error("[" + Thread.currentThread().getName() + "] ran into an unexpected error running a GL call, Error: ["+ e.getMessage() +"].", error);
+ }
+ }
+
+ //endregion
+
+
+
+ //===========//
+ // run tasks //
+ //===========//
+ //region
+
+ /**
+ * Doesn't do any thread/GL Context validation.
+ * Running this outside of the render thread may cause crashes or other issues.
+ */
+ public void runRenderThreadTasks()
+ {
+ IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
+
+ int frameLimit = MC_RENDER.getFrameLimit();
+ if (frameLimit <= 1)
+ {
+ frameLimit = 4; // 240 FPS
+ }
+
+ // https://fpstoms.com/
+ int msPerFrame = 1000 / frameLimit;
+ this.runRenderThreadTasks(msPerFrame);
+ }
+ private void runRenderThreadTasks(long msMaxRunTime)
+ {
+ long startTimeMs = System.currentTimeMillis();
+ this.msSinceGlTasksRun = startTimeMs;
+
+ Runnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
+ while(runnable != null)
+ {
+ runnable.run();
+
+ // only try running for 4ms (240 FPS) at a time to prevent random lag spikes
+ long currentTimeMs = System.currentTimeMillis();
+ long runDuration = currentTimeMs - startTimeMs;
+ if (runDuration > msMaxRunTime)
+ {
+ break;
+ }
+
+ runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
+ }
+ }
+
+ /**
+ * Should only be called if our render code isn't being hit for some reason.
+ * Normally this only happens if there's a mod that limits MC's framerate to 0.
+ */
+ private void manualCleanupTick()
+ {
+ long nowMs = System.currentTimeMillis();
+ long msSinceLast = nowMs - this.msSinceGlTasksRun;
+ if (msSinceLast > MS_BEFORE_RUN_CLEANUP_TIMER)
+ {
+ return;
+ }
+
+ // We haven't gotten a frame for a while,
+ // this means we could have GL jobs building up.
+ // Run the queued tasks on MC's executor (hopefully this should always run,
+ // even if DH's render code isn't being hit).
+ IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
+ MC.executeOnRenderThread(() -> this.runRenderThreadTasks(1_000));
+ }
+
+ //end region
+
+
+
+}