Move async GL calls to the render thread

Hopefully to fix potential crashes on non-Nvidia or less stable GPU drivers
This commit is contained in:
James Seibel
2024-01-18 19:11:33 -06:00
parent ac0eacb721
commit 3c4e57cff5
4 changed files with 67 additions and 19 deletions
@@ -382,7 +382,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
{ {
this.buffersUploaded = false; this.buffersUploaded = false;
GLProxy.getInstance().recordOpenGlCall(() -> GLProxy.getInstance().queueRunningOnRenderThread(() ->
{ {
for (GLVertexBuffer buffer : this.vbos) for (GLVertexBuffer buffer : this.vbos)
{ {
@@ -50,7 +50,6 @@ import java.util.Arrays;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
/** /**
* A singleton that holds references to different openGL contexts * A singleton that holds references to different openGL contexts
@@ -65,15 +64,11 @@ import java.util.stream.Stream;
* *
* https://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one <br> * https://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one <br>
* https://stackoverflow.com/questions/63509735/massive-performance-loss-with-glmapbuffer <br><br> * https://stackoverflow.com/questions/63509735/massive-performance-loss-with-glmapbuffer <br><br>
*
* @author James Seibel
*/ */
public class GLProxy public class GLProxy
{ {
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
public static final ConfigBasedLogger GL_LOGGER = new ConfigBasedLogger(LogManager.getLogger(GLProxy.class), public static final ConfigBasedLogger GL_LOGGER = new ConfigBasedLogger(LogManager.getLogger(GLProxy.class),
() -> Config.Client.Advanced.Logging.logRendererGLEvent.get()); () -> Config.Client.Advanced.Logging.logRendererGLEvent.get());
@@ -88,6 +83,17 @@ public class GLProxy
private static GLProxy instance = null; private static GLProxy instance = null;
private ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
/**
* the list that items should be added to. <br>
* Two lists exist to prevent potential concurrency issues where we are reading and writing
* at the same time.
*/
private final ArrayList<Runnable> addRenderThreadRunnableList = new ArrayList<>();
/** the list that items should be read from */
private final ArrayList<Runnable> readRenderThreadRunnableList = new ArrayList<>();
/** Minecraft's GLFW window */ /** Minecraft's GLFW window */
public final long minecraftGlContext; public final long minecraftGlContext;
/** Minecraft's GL capabilities */ /** Minecraft's GL capabilities */
@@ -459,9 +465,9 @@ public class GLProxy
//============================// //=========================//
// MC render thread runnables // // Worker Thread Runnables //
//============================// //=========================//
/** /**
* Asynchronously calls the given runnable on proxy's OpenGL context. * Asynchronously calls the given runnable on proxy's OpenGL context.
@@ -472,14 +478,18 @@ public class GLProxy
public void recordOpenGlCall(Runnable renderCall) public void recordOpenGlCall(Runnable renderCall)
{ {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
this.workerThread.execute(() -> this.runnableContainer(renderCall, stackTrace)); this.workerThread.execute(() -> this.runOpenGlCall(renderCall, stackTrace, true));
} }
private void runnableContainer(Runnable renderCall, StackTraceElement[] stackTrace) private void runOpenGlCall(Runnable renderCall, StackTraceElement[] stackTrace, boolean useProxyWorkerContext)
{ {
try try
{ {
// set up the context... // set up the context if requested...
this.setGlContext(EGLProxyContext.PROXY_WORKER); if (useProxyWorkerContext)
{
this.setGlContext(EGLProxyContext.PROXY_WORKER);
}
// ...run the actual code... // ...run the actual code...
renderCall.run(); renderCall.run();
} }
@@ -492,7 +502,10 @@ public class GLProxy
finally finally
{ {
// ...and make sure the context is released when the thread finishes // ...and make sure the context is released when the thread finishes
this.setGlContext(EGLProxyContext.NONE); if (useProxyWorkerContext)
{
this.setGlContext(EGLProxyContext.NONE);
}
} }
} }
@@ -528,6 +541,35 @@ public class GLProxy
//=========================//
// Render Thread Runnables //
//=========================//
public void queueRunningOnRenderThread(Runnable renderCall)
{
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
this.addRenderThreadRunnableList.add(() -> this.runOpenGlCall(renderCall, stackTrace, false));
}
/**
* Doesn't do any thread/GL Context validation.
* Running this outside of the render thread may cause crashes or other issues.
*/
public void runRenderThreadTasks()
{
ArrayList<Runnable> tempList = this.addRenderThreadRunnableList;
this.addRenderThreadRunnableList = this.readRenderThreadRunnableList;
this.readRenderThreadRunnableList = tempList;
for (Runnable runnable : this.readRenderThreadRunnableList)
{
runnable.run();
}
this.readRenderThreadRunnableList.clear();
}
//=========// //=========//
// logging // // logging //
//=========// //=========//
@@ -122,9 +122,12 @@ public class GLBuffer implements AutoCloseable
} }
private static void destroyBufferId(boolean async, int id) private static void destroyBufferId(boolean async, int id)
{ {
if (async && GLProxy.getInstance().getGlContext() != EGLProxyContext.PROXY_WORKER) EGLProxyContext glContext = GLProxy.getInstance().getGlContext();
if (async
&& glContext != EGLProxyContext.PROXY_WORKER
&& glContext != EGLProxyContext.MINECRAFT)
{ {
GLProxy.getInstance().recordOpenGlCall(() -> destroyBufferId(false, id)); GLProxy.getInstance().queueRunningOnRenderThread(() -> destroyBufferId(false, id));
} }
else else
{ {
@@ -200,7 +203,7 @@ public class GLBuffer implements AutoCloseable
LodUtil.assertTrue(this.bufferStorage, "Buffer is not bufferStorage but its trying to use bufferStorage upload method!"); LodUtil.assertTrue(this.bufferStorage, "Buffer is not bufferStorage but its trying to use bufferStorage upload method!");
int bbSize = bb.limit() - bb.position(); int bbSize = bb.limit() - bb.position();
this.destroy(false); this.destroy(true);
this.create(true); this.create(true);
this.bind(); this.bind();
GL44.glBufferStorage(this.getBufferBindingTarget(), bb, bufferStorageHint); GL44.glBufferStorage(this.getBufferBindingTarget(), bb, bufferStorageHint);
@@ -313,7 +316,7 @@ public class GLBuffer implements AutoCloseable
{ {
// recreate if the buffer storage type changed // recreate if the buffer storage type changed
this.bind(); this.bind();
this.destroy(false); this.destroy(true);
this.create(uploadMethod.useBufferStorage); this.create(uploadMethod.useBufferStorage);
this.bind(); this.bind();
} }
@@ -486,6 +486,9 @@ public class LodRenderer
profiler.popPush("LOD cleanup"); profiler.popPush("LOD cleanup");
LagSpikeCatcher drawCleanup = new LagSpikeCatcher(); LagSpikeCatcher drawCleanup = new LagSpikeCatcher();
GLProxy.getInstance().runRenderThreadTasks();
lightmap.unbind(); lightmap.unbind();
if (ENABLE_IBO) if (ENABLE_IBO)
{ {
@@ -666,7 +669,7 @@ public class LodRenderer
this.setupLock.lock(); this.setupLock.lock();
EVENT_LOGGER.info("Queuing Renderer Cleanup for main render thread"); EVENT_LOGGER.info("Queuing Renderer Cleanup for main render thread");
GLProxy.getInstance().recordOpenGlCall(() -> GLProxy.getInstance().queueRunningOnRenderThread(() ->
{ {
EVENT_LOGGER.info("Renderer Cleanup Started"); EVENT_LOGGER.info("Renderer Cleanup Started");