Start rework to allow individual render region swapping

This commit is contained in:
tom lee
2022-02-18 14:56:54 +08:00
parent fd81a8e067
commit 3e7fed7ad4
12 changed files with 506 additions and 349 deletions
@@ -33,7 +33,6 @@ import org.lwjgl.glfw.GLFW;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.render.GLProxy;
@@ -235,10 +234,13 @@ public class ClientApi
ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler());
} catch (RuntimeException e) {
rendererDisabledBecauseOfExceptions = true;
ClientApi.LOGGER.error("Renderer thrown an uncaught exception: ",e);
try {
//ClientApi.renderer.ma ();
} catch (RuntimeException welpLookLikeWeWillLeakResource) {}
throw e;
MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons"
+ " renderer has encountered an exception!");
MC.sendChatMessage("\u00A74Renderer is now disabled to prevent futher issues.");
MC.sendChatMessage("\u00A74Exception detail: "+e.toString());
} catch (RuntimeException noMessagesThen) {}
}
}
profiler.pop(); // end LOD
@@ -254,8 +256,7 @@ public class ClientApi
}
catch (Exception e)
{
ClientApi.LOGGER.error("client proxy: " + e.getMessage());
e.printStackTrace();
ClientApi.LOGGER.error("client proxy uncaught exception: ", e);
}
}
@@ -19,10 +19,8 @@
package com.seibel.lod.core.builders.bufferBuilding;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
@@ -33,9 +31,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantLock;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL44;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
@@ -43,12 +38,11 @@ import com.seibel.lod.core.enums.config.VanillaOverdraw;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.enums.rendering.GLProxyContext;
import com.seibel.lod.core.objects.PosToRenderContainer;
import com.seibel.lod.core.objects.RenderRegion;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.LodRegion;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.opengl.LodQuadBuilder;
import com.seibel.lod.core.objects.opengl.LodVertexBuffer;
import com.seibel.lod.core.objects.opengl.RenderRegion;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.util.DataPointUtil;
@@ -58,7 +52,7 @@ import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.MovableGridList;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.util.SpamReducedLogger;
import com.seibel.lod.core.util.UnitBytes;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
@@ -143,14 +137,8 @@ public class LodBufferBuilderFactory {
/** Used when building new VBOs */
public volatile MovableGridList<RenderRegion> buildableVbos;
public volatile int buildableCenterBlockX;
public volatile int buildableCenterBlockY;
public volatile int buildableCenterBlockZ;
/** VBOs that are sent over to the LodNodeRenderer */
public volatile MovableGridList<RenderRegion> drawableVbos;
public volatile int drawableCenterBlockX;
public volatile int drawableCenterBlockY;
public volatile int drawableCenterBlockZ;
/**
* if this is true the LOD buffers need to be reset and the Renderer should call
* the lodGenBuffers nomatter it should have been a full or partial regen or not
@@ -186,9 +174,6 @@ public class LodBufferBuilderFactory {
private MovableGridList<PosToRenderContainer> setsToRender;
private int lastX = 0;
private int lastZ = 0;
public LodBufferBuilderFactory() {
}
@@ -246,14 +231,8 @@ public class LodBufferBuilderFactory {
int playerRegionX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL, playerX, LodUtil.REGION_DETAIL_LEVEL);
int playerRegionZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL, playerZ, LodUtil.REGION_DETAIL_LEVEL);
int renderRange;
int vboX;
int vboY;
int vboZ;
boolean tooFar = Math.abs(buildableCenterBlockX - playerX)
+ Math.abs(buildableCenterBlockZ - playerZ) > 100_000;
if (fullRegen || tooFar || buildableVbos == null || setsToRender == null) {
if (fullRegen || buildableVbos == null || setsToRender == null) {
if (buildableVbos != null) {
buildableVbos.clear(RenderRegion::close);
}
@@ -261,20 +240,8 @@ public class LodBufferBuilderFactory {
renderRange = lodDim.getWidth() / 2; // get lodDim half width
buildableVbos = new MovableGridList<RenderRegion>(renderRange, playerRegionX, playerRegionZ);
setsToRender = new MovableGridList<PosToRenderContainer>(renderRange, playerRegionX, playerRegionZ);
// this will be the center of the VBOs once they have been built
// FIXME: Currently this will drift apart from player pos if there has not been
// a fullRegen for a while
buildableCenterBlockX = playerX;
buildableCenterBlockY = playerY;
buildableCenterBlockZ = playerZ;
vboX = playerX;
vboY = playerY;
vboZ = playerZ;
} else {
renderRange = buildableVbos.gridCentreToEdge;
vboX = buildableCenterBlockX;
vboY = buildableCenterBlockY;
vboZ = buildableCenterBlockZ;
buildableVbos.move(playerRegionX, playerRegionZ, RenderRegion::close);
setsToRender.move(playerRegionX, playerRegionZ);
}
@@ -292,8 +259,6 @@ public class LodBufferBuilderFactory {
// minCullingRange);
// int cullingRangeZ = Math.max((int)(1.5 * Math.abs(lastZ - playerZ)),
// minCullingRange);
lastX = playerX;
lastZ = playerZ;
List<CompletableFuture<?>> futuresBuffer = new LinkedList<CompletableFuture<?>>();
for (int indexX = 0; indexX < buildableVbos.gridSize; indexX++) {
for (int indexZ = 0; indexZ < buildableVbos.gridSize; indexZ++) {
@@ -324,19 +289,11 @@ public class LodBufferBuilderFactory {
this.quadBuilder = quadBuilder;
this.regionPos = regionPos;
}
LodQuadBuilder quadBuilder() {
return quadBuilder;
}
RegionPos regionPos() {
return regionPos;
}
}
CompletableFuture<ResultPair> future = CompletableFuture.supplyAsync(() -> {
LodQuadBuilder quadBuilder = new LodQuadBuilder(6);
makeLodRenderData(quadBuilder, lodDim, regionPos, pX, pZ, vboX, vboZ, minDetail);
makeLodRenderData(quadBuilder, lodDim, regionPos, pX, pZ, minDetail);
return new ResultPair(quadBuilder, regionPos);
}, bufferUploadThread).whenCompleteAsync((result, e) -> {
if (e != null)
@@ -400,7 +357,7 @@ public class LodBufferBuilderFactory {
}
private RegionPos makeLodRenderData(LodQuadBuilder quadBuilder, LodDimension lodDim, RegionPos regPos, int playerX,
int playerZ, int vboX, int vboZ, byte minDetail) {// , int cullingRangeX, int cullingRangeZ) {
int playerZ, byte minDetail) {// , int cullingRangeX, int cullingRangeZ) {
// Variable initialization
int playerChunkX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL, playerX, LodUtil.CHUNK_DETAIL_LEVEL);
@@ -523,29 +480,15 @@ public class LodBufferBuilderFactory {
if (!ramLogger.canMaybeLog())
return;
ramLogger.info("Dumping Ram Usage for buffer usage...");
long bufferCount = 0;
long fullBufferCount = 0;
long totalUsage = 0;
int maxLength = MAX_TRIANGLES_PER_BUFFER * (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3);
StatsMap statsMap = new StatsMap();
if (buildableVbos == null) {
ramLogger.info("Buildable VBOs are null!");
} else
for (RenderRegion buffers : buildableVbos) {
if (buffers == null)
continue;
LodVertexBuffer[] bs = buffers.debugGetBuffers().clone();
for (LodVertexBuffer b : bs) {
if (b == null)
continue;
bufferCount++;
if (b.size == maxLength) {
fullBufferCount++;
} else if (b.size > maxLength) {
ramLogger.info("BUFFER OVERSIZED: {} (max size is {})", new UnitBytes(b.size),
new UnitBytes(maxLength));
}
totalUsage += b.size;
}
buffers.debugDumpStats(statsMap);
}
if (drawableVbos == null) {
ramLogger.info("Drawable VBOs are null!");
@@ -553,23 +496,10 @@ public class LodBufferBuilderFactory {
for (RenderRegion buffers : drawableVbos) {
if (buffers == null)
continue;
LodVertexBuffer[] bs = buffers.debugGetBuffers().clone();
for (LodVertexBuffer b : bs) {
if (b == null)
continue;
bufferCount++;
if (b.size == maxLength) {
fullBufferCount++;
} else if (b.size > maxLength) {
ramLogger.info("BUFFER OVERSIZED: {} (max size is {})", new UnitBytes(b.size),
new UnitBytes(maxLength));
}
totalUsage += b.size;
}
buffers.debugDumpStats(statsMap);
}
ramLogger.info("================================================");
ramLogger.info("Buffers: [{}], Full-sized Buffers: [{}], Total: [{}]", bufferCount, fullBufferCount,
new UnitBytes(totalUsage));
ramLogger.info("Stats: {}", statsMap);
ramLogger.info("================================================");
ramLogger.incLogTries();
}
@@ -621,163 +551,21 @@ public class LodBufferBuilderFactory {
// determine the upload method
GpuUploadMethod uploadMethod = glProxy.getGpuUploadMethod();
// determine the upload timeout
long BPerNS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds(); // MB -> B =
// 1/1,000,000.
// MS -> NS =
// 1,000,000.
// So, MBPerMS =
// BPerNS.
long remainingNS = 0; // We don't want to pause for like 0.1 ms... so we store those tiny MS.
long totalBytes = 0;
int vbosNeeded = quadBuilder.getCurrentNeededVertexBuffers();
// Setup the VBO array
LagSpikeCatcher vboSetup = new LagSpikeCatcher();
RenderRegion renderRegion = buildableVbos.get(p.x, p.z);
if (renderRegion == null)
renderRegion = buildableVbos.setAndGet(p.x, p.z, new RenderRegion(vbosNeeded));
else
renderRegion.resize(vbosNeeded);
RenderRegion newRenderRegion = RenderRegion.updateStatus(renderRegion, quadBuilder, p);
if (newRenderRegion != null) {
renderRegion = buildableVbos.setAndGet(p.x, p.z, newRenderRegion);
}
vboSetup.end("vboSetup");
// Start the upload
Iterator<ByteBuffer> iter = quadBuilder.makeVertexBuffers();
int i = 0;
while (iter.hasNext()) {
ByteBuffer bb = iter.next();
LagSpikeCatcher vboU = new LagSpikeCatcher();
vboUpload(p, i++, bb, uploadMethod);
vboU.end("vboUpload");
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
totalBytes += bb.limit();
remainingNS += bb.limit() * BPerNS;
if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS)) {
if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
try {
Thread.sleep(remainingNS / 1000000, (int) (remainingNS % 1000000));
} catch (InterruptedException e) {
}
remainingNS = 0;
}
}
if (ENABLE_BUFFER_UPLOAD_LOGGING)
ClientApi.LOGGER.info("Uploaded {} sub buffers for {} with total of {}", i, p,
new UnitBytes(totalBytes));
renderRegion.uploadBuffers(quadBuilder, uploadMethod);
} finally {
glProxy.setGlContext(oldContext);
}
}
/** Uploads the uploadBuffer so the GPU can use it. */
private void vboUpload(RegionPos regPos, int iIndex, ByteBuffer uploadBuffer, GpuUploadMethod uploadMethod) {
int maxLength = MAX_TRIANGLES_PER_BUFFER * (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3);
boolean useBuffStorage = uploadMethod == GpuUploadMethod.BUFFER_STORAGE;
RenderRegion region = buildableVbos.get(regPos.x, regPos.z);
LodVertexBuffer vbo = region.getOrMakeVbo(iIndex, useBuffStorage);
// this is how many points will be rendered
vbo.vertexCount = (uploadBuffer.limit() / LodUtil.LOD_VERTEX_FORMAT.getByteSize());
// If size is zero, just ignore it.
if (uploadBuffer.limit() == 0)
return;
LagSpikeCatcher bindBuff = new LagSpikeCatcher();
bindBuff.end("glBindBuffer vbo.id");
try {
// if possible use the faster buffer storage route
if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE) {
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
long size = vbo.size;
if (size < uploadBuffer.limit()
|| size > uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) {
int newSize = (int) (uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxLength)
newSize = maxLength;
LagSpikeCatcher buffResizeRegen = new LagSpikeCatcher();
GL32.glDeleteBuffers(vbo.id);
vbo.id = GL32.glGenBuffers();
buffResizeRegen.end("glDeleteBuffers BuffStorage resize");
LagSpikeCatcher buffResize = new LagSpikeCatcher();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, newSize, GL44.GL_DYNAMIC_STORAGE_BIT);
vbo.size = newSize;
buffResize.end("glBufferStorage BuffStorage resize");
}
LagSpikeCatcher buffSubData = new LagSpikeCatcher();
GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer);
buffSubData.end("glBufferSubData BuffStorage");
} else if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING) {
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
// no stuttering but high GPU usage
// stores everything in system memory instead of GPU memory
// making rendering much slower.
// Unless the user is running integrated graphics,
// in that case this will actually work better than SUB_DATA.
long size = vbo.size;
if (size < uploadBuffer.limit()
|| size > uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) {
int newSize = (int) (uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxLength)
newSize = maxLength;
LagSpikeCatcher buffResize = new LagSpikeCatcher();
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
vbo.size = newSize;
buffResize.end("glBufferData BuffMapping resize");
}
ByteBuffer vboBuffer;
// map buffer range is better since it can be explicitly unsynchronized
LagSpikeCatcher buffMap = new LagSpikeCatcher();
vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer.limit(),
GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);
buffMap.end("glMapBufferRange BuffMapping");
LagSpikeCatcher buffWrite = new LagSpikeCatcher();
vboBuffer.put(uploadBuffer);
LagSpikeCatcher buffUnmap = new LagSpikeCatcher();
GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER);
buffUnmap.end("glUnmapBuffer");
buffWrite.end("WriteData BuffMapping");
} else if (uploadMethod == GpuUploadMethod.DATA) {
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
// TODO: Check this nonsense comment!
// hybrid bufferData //
// high stutter, low GPU usage
// But simplest/most compatible
LagSpikeCatcher buffData = new LagSpikeCatcher();
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, uploadBuffer, GL32.GL_STATIC_DRAW);
vbo.size = uploadBuffer.limit();
buffData.end("glBufferData Data");
} else {
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
// TODO: Check this nonsense comment!
// hybrid subData/bufferData //
// less stutter, low GPU usage
long size = vbo.size;
if (size < uploadBuffer.limit()
|| size > uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) {
int newSize = (int) (uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxLength)
newSize = maxLength;
LagSpikeCatcher buffResize = new LagSpikeCatcher();
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
vbo.size = newSize;
buffResize.end("glBufferData SubData resize");
}
LagSpikeCatcher buffSubData = new LagSpikeCatcher();
GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer);
buffSubData.end("glBufferSubData SubData");
}
} catch (Exception e) {
ClientApi.LOGGER.error("vboUpload failed: " + e.getClass().getSimpleName());
e.printStackTrace();
}
}// vboUpload
private boolean swapBuffers() {
bufferLock.lock();
if (ENABLE_BUFFER_SWAP_LOGGING)
@@ -790,16 +578,6 @@ public class LodBufferBuilderFactory {
buildableVbos = tmpVbo;
// ClientApi.LOGGER.info("Lod Swapped Buffers: "+drawableVbos.toDetailString());
int tempX = drawableCenterBlockX;
int tempY = drawableCenterBlockY;
int tempZ = drawableCenterBlockZ;
drawableCenterBlockX = buildableCenterBlockX;
drawableCenterBlockY = buildableCenterBlockY;
drawableCenterBlockZ = buildableCenterBlockZ;
buildableCenterBlockX = tempX;
buildableCenterBlockY = tempY;
buildableCenterBlockZ = tempZ;
// the vbos have been swapped
switchVbos = false;
@@ -824,14 +602,6 @@ public class LodBufferBuilderFactory {
return shouldDrawFrontBuffer() ? drawableVbos : null;
}
public int getFrontBuffersCenterX() {
return drawableCenterBlockX;
}
public int getFrontBuffersCenterZ() {
return drawableCenterBlockZ;
}
public void triggerReset() {
allBuffersRequireReset = true;
hideBackBuffer = true;
@@ -31,6 +31,8 @@ package com.seibel.lod.core.enums.config;
*/
public enum BufferRebuildTimes
{
CONSTANT(0, 0, 0, 1),
FREQUENT(1000, 500, 2500, 1),
NORMAL(2000, 1000, 5000, 4),
@@ -0,0 +1,43 @@
package com.seibel.lod.core.objects;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.opengl.ComplexRenderRegion;
import com.seibel.lod.core.objects.opengl.LodQuadBuilder;
import com.seibel.lod.core.objects.opengl.SimpleRenderRegion;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
public abstract class RenderRegion implements AutoCloseable
{
// target can be Null.
// If return null, means all status updated without switching objects.
@SuppressWarnings("resource")
public static RenderRegion updateStatus(RenderRegion target, LodQuadBuilder builder, RegionPos regPos) {
boolean useSimpleRegion = (builder.getCurrentNeededVertexBuffers() <= 6) || true;
if ((target instanceof SimpleRenderRegion && !useSimpleRegion) ||
target instanceof ComplexRenderRegion && useSimpleRegion) {
target.close();
target = null;
}
if (target == null) {
return useSimpleRegion ?
new SimpleRenderRegion(builder.getCurrentNeededVertexBuffers(), regPos)
: new ComplexRenderRegion(regPos);
}
return null;
}
public abstract void uploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod);
public abstract boolean shouldRender(IMinecraftRenderWrapper renderer, boolean enableDirectionalCulling);
public abstract void render(LodRenderProgram shaderProgram);
public abstract void debugDumpStats(StatsMap statsMap);
@Override
public abstract void close();
}
@@ -0,0 +1,114 @@
package com.seibel.lod.core.objects.opengl;
import java.util.TreeMap;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.objects.RenderRegion;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.render.RenderUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.util.UnitBytes;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
public class ComplexRenderRegion extends RenderRegion {
LodVertexBuffer[] vbos;
final RegionPos regPos;
private static final float FULL_SIZED_BUFFERS =
LodBufferBuilderFactory.MAX_TRIANGLES_PER_BUFFER * LodUtil.LOD_VERTEX_FORMAT.getByteSize();
public ComplexRenderRegion(RegionPos pos) {
vbos = new LodVertexBuffer[1];
regPos = pos;
}
public void resize(int size) {
if (vbos.length != size) {
LodVertexBuffer[] newVbos = new LodVertexBuffer[size];
if (vbos.length > size) {
for (int i=size; i<vbos.length; i++) {
vbos[i].close();
vbos[i] = null;
}
}
for (int i=0; i<newVbos.length && i<vbos.length; i++) {
newVbos[i] = vbos[i];
vbos[i] = null;
}
for (LodVertexBuffer b : vbos) {
if (b != null) throw new RuntimeException("LEAKING VBO!");
}
vbos = newVbos;
}
}
public LodVertexBuffer[] debugGetBuffers() {
return vbos;
}
@Override
public void close() {
}
public LodVertexBuffer getOrMakeVbo(int iIndex, boolean useBuffStorage) {
if (vbos[iIndex] == null) {
vbos[iIndex] = new LodVertexBuffer(useBuffStorage);
} else if (vbos[iIndex].isBufferStorage != useBuffStorage) {
vbos[iIndex].close();
vbos[iIndex] = new LodVertexBuffer(useBuffStorage);
}
return vbos[iIndex];
}
@Override
public boolean shouldRender(IMinecraftRenderWrapper renderer, boolean enableDirectionalCulling) {
if (enableDirectionalCulling && !RenderUtil.isRegionInViewFrustum(renderer.getCameraBlockPosition(),
renderer.getLookAtVector(), regPos.x, regPos.z)) return false;
return true;
}
@Override
public void render(LodRenderProgram shaderProgram)
{
for (LodVertexBuffer vbo : vbos) {
if (vbo == null) continue;
if (vbo.vertexCount == 0) continue;
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
shaderProgram.bindVertexBuffer(vbo.id);
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.vertexCount);
}
}
@Override
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RegionRegions");
statsMap.incStat("SimpleRegionRegions");
for (LodVertexBuffer b : vbos) {
if (b == null) continue;
statsMap.incStat("Buffers");
if (b.size == FULL_SIZED_BUFFERS) {
statsMap.incStat("FullsizedBuffers");
}
statsMap.incBytesStat("TotalUsage", b.size);
}
}
@Override
public void uploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod)
{
// TODO Auto-generated method stub
}
}
@@ -19,9 +19,13 @@
package com.seibel.lod.core.objects.opengl;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL44;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.rendering.GLProxyContext;
import com.seibel.lod.core.render.GLProxy;
@@ -34,20 +38,151 @@ import com.seibel.lod.core.render.GLProxy;
*/
public class LodVertexBuffer implements AutoCloseable
{
/**
* When uploading to a buffer that is too small, recreate it this many times
* bigger than the upload payload
*/
public static final double BUFFER_EXPANSION_MULTIPLIER = 1.3;
public static int count = 0;
public int id;
public int vertexCount;
public final boolean isBufferStorage;
public boolean isBufferStorage;
public long size = 0;
public LodVertexBuffer(boolean isBufferStorage)
{
_create(isBufferStorage);
}
private void _create(boolean asBufferStorage) {
if (GLProxy.getInstance().getGlContext() == GLProxyContext.NONE)
throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a [" + LodVertexBuffer.class.getSimpleName() + "] outside a OpenGL contex.");
this.id = GL32.glGenBuffers();
this.isBufferStorage = isBufferStorage;
this.isBufferStorage = asBufferStorage;
count++;
ClientApi.LOGGER.debug("LodVertexBuffer Count: "+count);
}
private void _destroy() {
if (GLProxy.getInstance().getGlContext() == GLProxyContext.PROXY_WORKER) {
GL32.glDeleteBuffers(this.id);
} else {
final int id = this.id;
GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(id));
}
this.id = -1;
count--;
}
private void _uploadBufferStorage(ByteBuffer bb, int maxExpensionSize) {
if (!isBufferStorage) throw new IllegalStateException("Buffer isn't bufferStorage but its trying to use BufferStorage upload method!");
int bbSize = bb.limit() - bb.position();
if (size < bbSize || size > bbSize * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) {
int newSize = (int) (bbSize * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxExpensionSize) newSize = maxExpensionSize;
GL32.glDeleteBuffers(id);
id = GL32.glGenBuffers();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, newSize, GL44.GL_MAP_WRITE_BIT);
size = newSize;
} else {
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
}
// map buffer range is better since it can be explicitly unsynchronized
ByteBuffer vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, bbSize,
GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);
if (vboBuffer == null) {
ClientApi.LOGGER.error("MapBufferRange Failed: bbSize: {}, maxSize: {}, size: {}", bbSize, maxExpensionSize, size);
}
vboBuffer.put(bb);
GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER);
}
// no stuttering but high GPU usage
// stores everything in system memory instead of GPU memory
// making rendering much slower.
// Unless the user is running integrated graphics,
// in that case this will actually work better than SUB_DATA.
private void _uploadBufferMapping(ByteBuffer bb, int maxExpensionSize) {
if (isBufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use BufferMapping upload method!");
int bbSize = bb.limit() - bb.position();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
if (size < bbSize || size > bbSize * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) {
int newSize = (int) (bbSize * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxExpensionSize) newSize = maxExpensionSize;
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
size = newSize;
}
// map buffer range is better since it can be explicitly unsynchronized
ByteBuffer vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, bbSize,
GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);
vboBuffer.put(bb);
GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER);
}
// bufferData
// simplest/most compatible
private void _uploadData(ByteBuffer bb) {
if (isBufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use Data upload method!");
int bbSize = bb.limit() - bb.position();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, bb, GL32.GL_STATIC_DRAW);
size = bbSize;
}
// bufferSubData
// less stutter, low GPU usage?
private void _uploadSubData(ByteBuffer bb, int maxExpensionSize) {
if (isBufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use SubData upload method!");
int bbSize = bb.limit() - bb.position();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
if (size < bbSize || size > bbSize * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) {
int newSize = (int) (bbSize * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxExpensionSize) newSize = maxExpensionSize;
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
size = newSize;
}
GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, bb);
}
public void uploadBuffer(ByteBuffer bb, int vertCount, GpuUploadMethod uploadMethod, int maxExpensionSize) {
if (vertCount < 0) throw new IllegalArgumentException("VertCount is negative!");
vertexCount = vertCount;
int bbSize = bb.limit()-bb.position();
if (bbSize > maxExpensionSize)
throw new IllegalArgumentException("maxExpensionSize is "+maxExpensionSize+" but buffer size is "+bbSize+"!");
// If size is zero, just ignore it.
if (bbSize == 0) return;
boolean useBuffStorage = uploadMethod == GpuUploadMethod.BUFFER_STORAGE;
if (useBuffStorage != isBufferStorage) {
_destroy();
_create(useBuffStorage);
}
try {
switch (uploadMethod) {
case AUTO:
throw new IllegalArgumentException("GpuUploadMethod AUTO must be resolved before call to uploadBuffer()!");
case BUFFER_MAPPING:
_uploadBufferMapping(bb, maxExpensionSize);
break;
case BUFFER_STORAGE:
_uploadBufferStorage(bb, maxExpensionSize);
break;
case DATA:
_uploadData(bb);
break;
case SUB_DATA:
_uploadSubData(bb, maxExpensionSize);
break;
default:
throw new IllegalArgumentException("Invalid GpuUploadMethod enum");
}
} catch (IllegalArgumentException e) {
throw e;
} catch (Exception e) {
ClientApi.LOGGER.error("vboUpload failed: ", e);
}
}
@@ -56,15 +191,7 @@ public class LodVertexBuffer implements AutoCloseable
{
if (this.id >= 0)
{
if (GLProxy.getInstance().getGlContext() == GLProxyContext.PROXY_WORKER) {
GL32.glDeleteBuffers(this.id);
} else {
final int id = this.id;
GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(id));
}
this.id = -1;
count--;
ClientApi.LOGGER.debug("LodVertexBuffer Count: "+count);
_destroy();
if (count==0) ClientApi.LOGGER.info("All LodVerrtexBuffer is freed.");
} else {
ClientApi.LOGGER.error("LodVertexBuffer double close!");
@@ -1,50 +0,0 @@
package com.seibel.lod.core.objects.opengl;
public class RenderRegion implements AutoCloseable {
LodVertexBuffer[] vbos;
public RenderRegion(int size) {
vbos = new LodVertexBuffer[size];
}
public void resize(int size) {
if (vbos.length != size) {
LodVertexBuffer[] newVbos = new LodVertexBuffer[size];
if (vbos.length > size) {
for (int i=size; i<vbos.length; i++) {
vbos[i].close();
vbos[i] = null;
}
}
for (int i=0; i<newVbos.length && i<vbos.length; i++) {
newVbos[i] = vbos[i];
vbos[i] = null;
}
for (LodVertexBuffer b : vbos) {
if (b != null) throw new RuntimeException("LEAKING VBO!");
}
vbos = newVbos;
}
}
public LodVertexBuffer[] debugGetBuffers() {
return vbos;
}
@Override
public void close() {
}
public LodVertexBuffer getOrMakeVbo(int iIndex, boolean useBuffStorage) {
if (vbos[iIndex] == null) {
vbos[iIndex] = new LodVertexBuffer(useBuffStorage);
} else if (vbos[iIndex].isBufferStorage != useBuffStorage) {
vbos[iIndex].close();
vbos[iIndex] = new LodVertexBuffer(useBuffStorage);
}
return vbos[iIndex];
}
}
@@ -0,0 +1,136 @@
package com.seibel.lod.core.objects.opengl;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.objects.RenderRegion;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.render.RenderUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
public class SimpleRenderRegion extends RenderRegion {
LodVertexBuffer[] vbos;
final RegionPos regPos;
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final int FULL_SIZED_BUFFERS =
LodBufferBuilderFactory.MAX_TRIANGLES_PER_BUFFER * LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3;
public SimpleRenderRegion(int size, RegionPos pos) {
vbos = new LodVertexBuffer[size];
regPos = pos;
}
private void resize(int size) {
if (vbos.length != size) {
LodVertexBuffer[] newVbos = new LodVertexBuffer[size];
if (vbos.length > size) {
for (int i=size; i<vbos.length; i++) {
if (vbos[i]!=null) vbos[i].close();
vbos[i] = null;
}
}
for (int i=0; i<newVbos.length && i<vbos.length; i++) {
newVbos[i] = vbos[i];
vbos[i] = null;
}
for (LodVertexBuffer b : vbos) {
if (b != null) throw new RuntimeException("LEAKING VBO!");
}
vbos = newVbos;
}
}
public LodVertexBuffer[] debugGetBuffers() {
return vbos;
}
@Override
public void close() {
for (LodVertexBuffer b : vbos) {
if (b != null) b.close();
}
vbos = new LodVertexBuffer[0];
}
private LodVertexBuffer getOrMakeVbo(int iIndex, boolean useBuffStorage) {
if (vbos[iIndex] == null) {
vbos[iIndex] = new LodVertexBuffer(useBuffStorage);
}
return vbos[iIndex];
}
@Override
public void uploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod)
{
resize(builder.getCurrentNeededVertexBuffers());
long remainingNS = 0;
long BPerNS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds();
int i = 0;
Iterator<ByteBuffer> iter = builder.makeVertexBuffers();
while (iter.hasNext()) {
ByteBuffer bb = iter.next();
LodVertexBuffer vbo = getOrMakeVbo(i++, uploadMethod==GpuUploadMethod.BUFFER_STORAGE);
int size = bb.limit() - bb.position();
vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), uploadMethod, FULL_SIZED_BUFFERS);
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
remainingNS += size * BPerNS;
if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS)) {
if (remainingNS > LodBufferBuilderFactory.MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
remainingNS = LodBufferBuilderFactory.MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
try {
Thread.sleep(remainingNS / 1000000, (int) (remainingNS % 1000000));
} catch (InterruptedException e) {
}
remainingNS = 0;
}
}
}
@Override
public boolean shouldRender(IMinecraftRenderWrapper renderer, boolean enableDirectionalCulling) {
if (enableDirectionalCulling && !RenderUtil.isRegionInViewFrustum(renderer.getCameraBlockPosition(),
renderer.getLookAtVector(), regPos.x, regPos.z)) return false;
return true;
}
@Override
public void render(LodRenderProgram shaderProgram)
{
for (LodVertexBuffer vbo : vbos) {
if (vbo == null) continue;
if (vbo.vertexCount == 0) continue;
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
shaderProgram.bindVertexBuffer(vbo.id);
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.vertexCount);
}
}
@Override
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RegionRegions");
statsMap.incStat("SimpleRegionRegions");
for (LodVertexBuffer b : vbos) {
if (b == null) continue;
statsMap.incStat("Buffers");
if (b.size == FULL_SIZED_BUFFERS) {
statsMap.incStat("FullsizedBuffers");
}
statsMap.incBytesStat("TotalUsage", b.size);
}
}
}
@@ -159,7 +159,7 @@ public class GLProxy
{
// boolean enableDebugLogging = CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL;
boolean enableDebugLogging = false;
boolean enableDebugLogging = true;
// this must be created on minecraft's render context to work correctly
ClientApi.LOGGER.info("Creating " + GLProxy.class.getSimpleName() + "... If this is the last message you see in the log there must have been a OpenGL error.");
@@ -34,15 +34,12 @@ 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.IReflectionHandler;
import com.seibel.lod.core.objects.RenderRegion;
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.LodVertexBuffer;
import com.seibel.lod.core.objects.opengl.RenderRegion;
import com.seibel.lod.core.render.objects.LightmapTexture;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.MovableGridList;
import com.seibel.lod.core.util.SingletonHandler;
@@ -220,8 +217,6 @@ public class LodRenderer
swapBuffer.end("SwapBuffer");
// Get the front buffers to draw
MovableGridList<RenderRegion> regions = lodBufferBuilderFactory.getFrontBuffers();
int vbosCenterX = lodBufferBuilderFactory.getFrontBuffersCenterX();
int vbosCenterZ = lodBufferBuilderFactory.getFrontBuffersCenterZ();
if (regions == null) {
// There is no vbos, which means nothing needs to be drawn. So skip rendering
@@ -317,43 +312,26 @@ public class LodRenderer
LagSpikeCatcher draw = new LagSpikeCatcher();
boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
// where the center of the buffers is (needed when culling regions)
// render each of the buffers
int lowRegionX = regions.getCenterX() - regions.gridCentreToEdge;
int lowRegionZ = regions.getCenterY() - regions.gridCentreToEdge;
int drawCall = 0;
int vCount0 = 0;
for (int regionX=lowRegionX; regionX<lowRegionX+regions.gridSize; regionX++) {
for (int regionZ=lowRegionZ; regionZ<lowRegionZ+regions.gridSize; regionZ++) {
if (regions.get(regionX, regionZ) == null) continue;
RenderRegion region = regions.get(regionX, regionZ);
if (region == null) continue;
if (!region.shouldRender(MC_RENDER, !cullingDisabled)) continue;
if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(),
MC_RENDER.getLookAtVector(), regionX, regionZ)) {
RenderRegion region = regions.get(regionX, regionZ);
//TODO improve this
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
Mat4f localModelViewMatrix = baseModelViewMatrix.copy();
localModelViewMatrix.multiplyTranslationMatrix(
(regionX * LodUtil.REGION_WIDTH) - cameraPos.x,
LodBuilder.MIN_WORLD_HEIGHT - cameraPos.y,
(regionZ * LodUtil.REGION_WIDTH) - cameraPos.z);
shaderProgram.fillUniformModelMatrix(localModelViewMatrix);
for (LodVertexBuffer vbo : region.debugGetBuffers()) {
if (vbo == null) continue;
if (vbo.vertexCount == 0) {
vCount0++;
continue;
}
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
shaderProgram.bindVertexBuffer(vbo.id);
drawCall++;
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.vertexCount);
}
}
Mat4f localModelViewMatrix = baseModelViewMatrix.copy();
localModelViewMatrix.multiplyTranslationMatrix(
(regionX * LodUtil.REGION_WIDTH) - cameraPos.x,
LodBuilder.MIN_WORLD_HEIGHT - cameraPos.y,
(regionZ * LodUtil.REGION_WIDTH) - cameraPos.z);
shaderProgram.fillUniformModelMatrix(localModelViewMatrix);
region.render(shaderProgram);
}
}
@@ -0,0 +1,34 @@
package com.seibel.lod.core.util;
import java.util.TreeMap;
public class StatsMap
{
final TreeMap<String, Long> longMap = new TreeMap<String, Long>();
final TreeMap<String, UnitBytes> bytesMap = new TreeMap<String, UnitBytes>();
/**
*
*/
@SuppressWarnings("unused")
private static final long serialVersionUID = 1926219295516863173L;
public StatsMap() {super();}
public void incStat(String key) {
incStat(key, 1);
}
public void incStat(String key, long value) {
longMap.put(key, longMap.getOrDefault(key, 0L)+value);
}
public void incBytesStat(String key, long bytes) {
bytesMap.put(key, new UnitBytes(bytesMap.getOrDefault(key, new UnitBytes(0)).value()+bytes));
}
@Override
public String toString() {
return longMap.toString() + " " + bytesMap.toString();
}
}
@@ -6,6 +6,8 @@ public class UnitBytes
public UnitBytes(long value) {
this.value = value;
}
public long value() {return value;}
public static long byteToGB(long v) {
return v/1073741824;
}