From a945eb4579fb6cdc16f33a251d8070fda57730f0 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 14 Sep 2021 21:27:36 -0500 Subject: [PATCH] Fully add multi-context uploading --- .../seibel/lod/builders/LodBufferBuilder.java | 134 ++++++++---------- .../java/com/seibel/lod/proxy/GlProxy.java | 80 ++++------- .../com/seibel/lod/render/LodRenderer.java | 13 +- .../com/seibel/lod/render/RenderUtil.java | 2 +- 4 files changed, 92 insertions(+), 137 deletions(-) diff --git a/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java b/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java index a08253da7..1cf15ddf7 100644 --- a/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java +++ b/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java @@ -30,7 +30,6 @@ import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL15C; -import com.mojang.datafixers.util.Pair; import com.seibel.lod.builders.lodTemplates.Box; import com.seibel.lod.config.LodConfig; import com.seibel.lod.objects.DataPoint; @@ -55,7 +54,7 @@ import net.minecraft.util.math.ChunkPos; * This object is used to create NearFarBuffer objects. * * @author James Seibel - * @version 8-24-2021 + * @version 9-14-2021 */ public class LodBufferBuilder { @@ -68,7 +67,6 @@ public class LodBufferBuilder */ public static ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.threading.numberOfBufferBuilderThreads.get(), new LodThreadFactory(LodBufferBuilder.class.getSimpleName() + " - builder")); - public volatile ByteBuffer clearByteBuffer; /** * The buffers that are used to create LODs using far fog */ @@ -84,8 +82,8 @@ public class LodBufferBuilder */ public volatile VertexBuffer[][] drawableVbos; - public GlProxyContext buildingContext = GlProxyContext.BUILDER; - public GlProxyContext renderContext = GlProxyContext.RENDER; + public GlProxyContext buildingContext = GlProxyContext.ALPHA; + public GlProxyContext renderContext = GlProxyContext.BETA; /** * if this is true the LOD buffers are currently being @@ -397,8 +395,6 @@ public class LodBufferBuilder buildableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide]; drawableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide]; - clearByteBuffer = new BufferBuilder(bufferMaxCapacity).buffer; - clearByteBuffer.position(clearByteBuffer.limit()-1); for (int x = 0; x < numbRegionsWide; x++) { @@ -406,17 +402,8 @@ public class LodBufferBuilder { buildableBuffers[x][z] = new BufferBuilder(bufferMaxCapacity); - buildableVbos[x][z] = new VertexBuffer(LodRenderer.LOD_VERTEX_FORMAT); drawableVbos[x][z] = new VertexBuffer(LodRenderer.LOD_VERTEX_FORMAT); - - GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableVbos[x][z].id); - GL15C.glBufferData(GL15.GL_ARRAY_BUFFER, bufferMaxCapacity, GL15C.GL_DYNAMIC_DRAW); - GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); - - GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, drawableVbos[x][z].id); - GL15C.glBufferData(GL15.GL_ARRAY_BUFFER, bufferMaxCapacity, GL15C.GL_DYNAMIC_DRAW); - GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); } } @@ -445,6 +432,7 @@ public class LodBufferBuilder private void startBuffers(boolean fullRegen, LodDimension lodDim) { for (int x = 0; x < buildableBuffers.length; x++) + { for (int z = 0; z < buildableBuffers.length; z++) { if (fullRegen || lodDim.regen[x][z]) @@ -452,6 +440,7 @@ public class LodBufferBuilder buildableBuffers[x][z].begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT); } } + } } /** @@ -461,86 +450,88 @@ public class LodBufferBuilder { for (int x = 0; x < buildableBuffers.length; x++) for (int z = 0; z < buildableBuffers.length; z++) - if (buildableBuffers[x][z] != null && buildableBuffers[x][z].building() && (fullRegen || lodDim.regen[x][z])) - { - buildableBuffers[x][z].end(); - } + buildableBuffers[x][z].end(); } /** - * Called from the LodRenderer to create the - * BufferBuilders at the right size. + * Upload all buildableBuffers to the GPU. */ private void uploadBuffers(boolean fullRegen, LodDimension lodDim) { GlProxy.getInstance().setGlContext(buildingContext); + // used to prevent debug printing multiple times per upload cycle + boolean bufferMapFail = false; + for (int x = 0; x < buildableVbos.length; x++) { for (int z = 0; z < buildableVbos.length; z++) { -// if (fullRegen || lodDim.regen[x][z]) -// { - vboUpload(x, z, lodDim, buildableVbos); + if (fullRegen || lodDim.regen[x][z]) + { + ByteBuffer builderBuffer = buildableBuffers[x][z].popNextBuffer().getSecond(); + bufferMapFail = vboUpload(buildableVbos[x][z], builderBuffer, bufferMapFail); lodDim.regen[x][z] = false; -// }// regen region - }// z - }// x + } + } + } + + // make sure all the buffers have been uploaded. + // this probably is necessary, but it makes me feel good :) GL11.glFlush(); GlProxy.getInstance().setGlContext(GlProxyContext.NONE); } - public void vboUpload(int xLocal, int zLocal, LodDimension lodDim, VertexBuffer[][] buildableVbos) + /** + * Uploads the uploadBuffer into the VBO in GPU memory. + */ + private boolean vboUpload(VertexBuffer vbo, ByteBuffer uploadBuffer, boolean bufferMapFail) { - VertexBuffer vbo = buildableVbos[xLocal][zLocal]; - BufferBuilder bufferBuilder = buildableBuffers[xLocal][zLocal]; - GlProxyContext test = GlProxy.getInstance().getGlContext(); - - long start = System.nanoTime(); - long end = start; - + // this shouldn't happen, but just to be safe if (vbo.id != -1) { - Pair pair = bufferBuilder.popNextBuffer(); - - ByteBuffer bytebuffer = pair.getSecond(); - vbo.vertexCount = bytebuffer.remaining() / vbo.format.getVertexSize(); - - if (xLocal == 0 && zLocal == 0) while (GL11.glGetError() != GL11.GL_NO_ERROR) {} - - GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.id); // 34962 = 0x8892 = GL_ARRAY_BUFFER -// ByteBuffer vboBuffer = GL15C.glMapBuffer(GL15.GL_ARRAY_BUFFER, GL15.GL_WRITE_ONLY); - - GL15C.glBufferData(GL15.GL_ARRAY_BUFFER, bytebuffer, GL15C.GL_STATIC_DRAW); - -// clearByteBuffer.position(0); -// clearByteBuffer.position(clearByteBuffer.limit()-1); - //clearByteBuffer - -// GL15C.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0L, clearByteBuffer); - GL15C.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0L, bytebuffer); + vbo.vertexCount = uploadBuffer.remaining() / vbo.format.getVertexSize(); -// long bufferSize = GL43C.glGetBufferParameteri64(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE); -// GL43C.glClearBufferSubData(GL15.GL_ARRAY_BUFFER, GL43., 0, bufferSize, GL11.GL_RGB, GL11.GL_FLOAT, (ByteBuffer) null); -// if (xLocal == 0 && zLocal == 0) ClientProxy.LOGGER.info("err: " + GL11.glGetError()); + GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.id); + // make sure enough space is allocated to fit the builderBuffer + GL15C.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer.capacity(), GL15C.GL_DYNAMIC_DRAW); + // try to get a pointer to the VBO's byteBuffer in GPU memory + ByteBuffer vboBuffer = GL15C.glMapBuffer(GL15.GL_ARRAY_BUFFER, GL15.GL_WRITE_ONLY); + + // upload the builderBuffer to the GPU + if (vboBuffer != null) + { + // This is the best way to upload lots of data, since writes directly to GPU + // memory, and doesn't pause OpenGL. + vboBuffer.put(uploadBuffer); + } + else + { + // Sometimes the vboBuffer is null (I think it may be due to buffer sizes + // changing or a setup process that didn't complete), so in that case + // we have to use this method which is slower and pauses OpenGL, + // but always succeeds. + GL15C.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer, GL15C.GL_DYNAMIC_DRAW); + + // only print to console once per upload cycle + if (!bufferMapFail) + ClientProxy.LOGGER.debug("LOD buffer upload glMapBuffer failed, using slower glBufferData."); + bufferMapFail = true; + } + + + GL15C.glUnmapBuffer(GL15.GL_ARRAY_BUFFER); GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); -// GL15C.glUnmapBuffer(GL15.GL_ARRAY_BUFFER); } - end = System.nanoTime(); - - - if (xLocal == 0 && zLocal == 0) - { - double time = (end - start) * 0.000001 * lodDim.getWidth() * lodDim.getWidth(); - ClientProxy.LOGGER.info("upload: " + time + "\t" + test); - } + // just used to improve debug printing + return bufferMapFail; } /** @@ -552,24 +543,21 @@ public class LodBufferBuilder // since this is called on the main render thread if (bufferLock.tryLock()) { - VertexBuffer[][] tmp = drawableVbos; + VertexBuffer[][] tmpVbo = drawableVbos; drawableVbos = buildableVbos; - buildableVbos = tmp; + buildableVbos = tmpVbo; - GlProxyContext context = renderContext; + GlProxyContext tmpContext = renderContext; renderContext = buildingContext; - buildingContext = context; + buildingContext = tmpContext; drawableCenterChunkPos = buildableCenterChunkPos; // the vbos have been swapped switchVbos = false; bufferLock.unlock(); - -// ClientProxy.LOGGER.info("Get vertex Buffers: " + GlProxy.getInstance().getGlContext()); } -// ClientProxy.LOGGER.info("Get vbo first: " + drawableVbos[0][0].id + "\t" + GlProxy.getInstance().getGlContext()); return new VertexBuffersAndOffset(drawableVbos, drawableCenterChunkPos, renderContext); } diff --git a/src/main/java/com/seibel/lod/proxy/GlProxy.java b/src/main/java/com/seibel/lod/proxy/GlProxy.java index 5a8fbffc6..8a9ffddf8 100644 --- a/src/main/java/com/seibel/lod/proxy/GlProxy.java +++ b/src/main/java/com/seibel/lod/proxy/GlProxy.java @@ -1,24 +1,24 @@ package com.seibel.lod.proxy; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.Future; - -import org.lwjgl.glfw.GLFW; -import org.lwjgl.glfw.GLFWErrorCallback; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GLCapabilities; import org.lwjgl.opengl.WGL; import com.mojang.blaze3d.systems.RenderSystem; -import com.seibel.lod.builders.LodBufferBuilder; /** * A singleton that holds references to different openGL contexts. * + *

+ * Helpful OpenGL resources:

+ * + * https://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
+ * https://learnopengl.com/Advanced-OpenGL/Advanced-Data
+ * https://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one

+ * + * * @author James Seibel - * @version 9-9-2021 + * @version 9-14-2021 */ public class GlProxy { @@ -36,57 +36,35 @@ public class GlProxy private GlProxy() { - GLFWErrorCallback errorfun = GLFWErrorCallback.createPrint(); - GLFW.glfwSetErrorCallback(errorfun); - - // getting Minecraft's context has to be done on the render thread, // where the GL context is if (!RenderSystem.isOnRenderThread()) throw new IllegalStateException(GlProxy.class.getSimpleName() + " was created outside the render thread!"); + minecraftGlContext = WGL.wglGetCurrentContext(); minecraftGlCapabilities = GL.getCapabilities(); deviceContext = WGL.wglGetCurrentDC(); - - Callable callable = () -> - { - lodBuilderGlContext = WGL.wglCreateContext(deviceContext); -// if (!WGL.wglShareLists(minecraftGlContext, lodBuilderGlContext)) -// throw new IllegalStateException("Unable to share lists between Minecraft and builder contexts."); - if (!WGL.wglMakeCurrent(deviceContext, lodBuilderGlContext)) - throw new IllegalStateException("Unable to change OpenGL contexts! tried to change to [" + GlProxyContext.BUILDER.toString() + "] from [" + GlProxyContext.MINECRAFT.toString() + "]"); - lodBuilderGlCapabilities = GL.createCapabilities(); - WGL.wglMakeCurrent(deviceContext, 0L); + lodBuilderGlContext = WGL.wglCreateContext(deviceContext); + if (!WGL.wglShareLists(minecraftGlContext, lodBuilderGlContext)) + throw new IllegalStateException("Unable to share lists between Minecraft and builder contexts."); + if (!WGL.wglMakeCurrent(deviceContext, lodBuilderGlContext)) + throw new IllegalStateException("Unable to change OpenGL contexts! tried to change to [" + GlProxyContext.ALPHA.toString() + "] from [" + GlProxyContext.MINECRAFT.toString() + "]"); + lodBuilderGlCapabilities = GL.createCapabilities(); + WGL.wglMakeCurrent(deviceContext, 0L); - return null; - }; - - ArrayList> list = new ArrayList>(); - list.add(callable); - try - { - List> futuresBuffer = LodBufferBuilder.mainGenThread.invokeAll(list); - - for (Future future : futuresBuffer) - if (!future.isDone()) - ClientProxy.LOGGER.error("GLProxy failed to setup."); - } - catch (InterruptedException e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - lodRenderGlContext = WGL.wglCreateContext(deviceContext); -// if (!WGL.wglShareLists(lodBuilderGlContext, lodRenderGlContext)) -// throw new IllegalStateException("Unable to share lists between builder and render contexts."); + if (!WGL.wglShareLists(minecraftGlContext, lodRenderGlContext)) + throw new IllegalStateException("Unable to share lists between builder and render contexts."); if (!WGL.wglMakeCurrent(deviceContext, lodRenderGlContext)) - throw new IllegalStateException("Unable to change OpenGL contexts! tried to change to [" + GlProxyContext.BUILDER.toString() + "] from [" + GlProxyContext.RENDER.toString() + "]"); + throw new IllegalStateException("Unable to change OpenGL contexts! tried to change to [" + GlProxyContext.ALPHA.toString() + "] from [" + GlProxyContext.BETA.toString() + "]"); lodRenderGlCapabilities = GL.createCapabilities(); + + + // Since this is called on the render thread, make sure the Minecraft context is used in the end WGL.wglMakeCurrent(deviceContext, minecraftGlContext); } @@ -102,11 +80,11 @@ public class GlProxy GLCapabilities newGlCapabilities = null; switch(context) { - case BUILDER: + case ALPHA: contextPointer = lodBuilderGlContext; newGlCapabilities = lodBuilderGlCapabilities; break; - case RENDER: + case BETA: contextPointer = lodRenderGlContext; newGlCapabilities = lodRenderGlCapabilities; break; @@ -133,11 +111,11 @@ public class GlProxy long currentContext = WGL.wglGetCurrentContext(); if(currentContext == lodBuilderGlContext) { - return GlProxyContext.BUILDER; + return GlProxyContext.ALPHA; } else if(currentContext == lodRenderGlContext) { - return GlProxyContext.RENDER; + return GlProxyContext.BETA; } else if(currentContext == minecraftGlContext) { @@ -152,8 +130,8 @@ public class GlProxy public enum GlProxyContext { MINECRAFT, - BUILDER, - RENDER, + ALPHA, + BETA, /** used to un-bind threads */ NONE, diff --git a/src/main/java/com/seibel/lod/render/LodRenderer.java b/src/main/java/com/seibel/lod/render/LodRenderer.java index b9768a28f..6b80091fd 100644 --- a/src/main/java/com/seibel/lod/render/LodRenderer.java +++ b/src/main/java/com/seibel/lod/render/LodRenderer.java @@ -28,7 +28,6 @@ import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL15C; import org.lwjgl.opengl.NVFogDistance; -import org.lwjgl.opengl.WGL; import com.mojang.blaze3d.matrix.MatrixStack; import com.mojang.blaze3d.platform.GlStateManager; @@ -47,7 +46,6 @@ import com.seibel.lod.objects.NearFarFogSettings; import com.seibel.lod.objects.RegionPos; import com.seibel.lod.proxy.ClientProxy; import com.seibel.lod.proxy.GlProxy; -import com.seibel.lod.proxy.GlProxy.GlProxyContext; import com.seibel.lod.util.DetailDistanceUtil; import com.seibel.lod.util.LodUtil; import com.seibel.lod.wrappers.MinecraftWrapper; @@ -75,7 +73,7 @@ import net.minecraft.util.math.vector.Vector3f; * This is where LODs are draw to the world. * * @author James Seibel - * @version 9-7-2021 + * @version 9-14-2021 */ public class LodRenderer { @@ -103,7 +101,6 @@ public class LodRenderer */ private static Boolean fancyFogAvailable = null; private static GlProxy glProxy; - private GlProxyContext renderContext = null; /** * If true the LODs colors will be replaced with @@ -208,7 +205,6 @@ public class LodRenderer // create the GlProxy TODO this should probably be done somewhere else glProxy = GlProxy.getInstance(); - ClientProxy.LOGGER.error("share lists renderer: " + WGL.wglShareLists(GlProxy.getInstance().minecraftGlContext, GlProxy.getInstance().lodBuilderGlContext)); } @@ -251,8 +247,6 @@ public class LodRenderer { return; } - - GlProxy.getInstance().setGlContext(renderContext); @@ -262,8 +256,6 @@ public class LodRenderer // set the required open GL settings - GlProxy.getInstance().setGlContext(renderContext); - if (LodConfig.CLIENT.debugging.debugMode.get() == DebugMode.SHOW_DETAIL_WIREFRAME) GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE); else @@ -365,8 +357,6 @@ public class LodRenderer // over the LODs GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); - GlProxy.getInstance().setGlContext(GlProxyContext.MINECRAFT); - // replace the buffers used to draw and build, // this is only done when the createLodBufferGenerationThread // has finished executing on a parallel thread. @@ -691,7 +681,6 @@ public class LodRenderer VertexBuffersAndOffset result = lodBufferBuilder.getVertexBuffers(); vbos = result.vbos; vbosCenter = result.drawableCenterChunkPos; - renderContext = result.drawingContext; } /** diff --git a/src/main/java/com/seibel/lod/render/RenderUtil.java b/src/main/java/com/seibel/lod/render/RenderUtil.java index 010f69372..2b9593683 100644 --- a/src/main/java/com/seibel/lod/render/RenderUtil.java +++ b/src/main/java/com/seibel/lod/render/RenderUtil.java @@ -92,7 +92,7 @@ public class RenderUtil public static int getBufferMemoryForRegion() { // calculate the max amount of buffer memory needed (in bytes) - return LodUtil.REGION_WIDTH_IN_CHUNKS * LodUtil.REGION_WIDTH_IN_CHUNKS * + return LodUtil.REGION_WIDTH_IN_CHUNKS * LodUtil.REGION_WIDTH_IN_CHUNKS * 6 * // TODO this really needs to be more accurate LodConfig.CLIENT.graphics.lodTemplate.get().getBufferMemoryForSingleLod(); }