Add RenderThreadTaskHandler
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
-7
@@ -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
|
||||
|
||||
+144
@@ -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<Runnable> 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
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user